Polylang free covers the basics. Building a real multilingual theme on it, you quickly run into things that only Polylang PRO or WPML handle. This plugin adds them, configured from your theme in code so the setup travels with the theme rather than the database.
/about-us/ and /es/about-us/ instead of /es/about-us-2/, for posts, pages, custom post types and terms. Requests resolve to the post or term of the requested language./success-stories/ in English, /es/casos-de-exito/ in Spanish, for post types, taxonomies and the blog base.get_field( 'name', 'options' ) keeps working.wp_get_attachment_image(). The block editor’s Image block saves its alt into the post content and is translated with the post.x-default.Requires Polylang 3.7 or later.
Cosmo Multilingual Kit for Polylang is an independent, unofficial plugin. “Polylang” is a trademark of WP SYNTEX; this plugin is not affiliated with or endorsed by WP SYNTEX, and the name refers to that plugin solely to describe what this one works with.
Everything is configured through a single filter, typically in the theme’s functions.php:
add_filter( 'cosmo/multilingual_kit/config', function ( $config ) {
return array_merge( $config, [
'post_types' => [ 'post', 'page', 'story' ],
'taxonomies' => [ 'category', 'post_tag', 'story_category' ],
'bases' => [ 'blog', 'story', 'story_category' ],
'base_map' => [ 'story' => [ 'es' => 'casos-de-exito' ] ],
'options_pages' => [ 'site-options' ],
'strings' => [ 'read_more' => 'Read more' ],
'strings_group' => 'My theme',
'user_fields' => [ 'position' => 'Position' ],
'pending_meta' => 'translation_pending',
'hidden_languages' => [ 'uz' ],
] );
} );
post_types, taxonomies — made translatable in addition to Polylang’s settings.bases — sections whose URL base can be translated: post type names, taxonomy names, or blog. A default can be given explicitly ('blog' => 'news'), otherwise the registered rewrite slug is used. Every URL of the section follows the translated base: nested terms, feeds, paging, archives.base_map — translated bases per language. Flush permalinks after changing it, and let your SEO plugin rebuild its URL cache.options_pages — ACF options page slugs stored per language. Storage goes by the page’s post id, so every options page that keeps the default options post id becomes per-language too; give pages that should stay shared their own post_id. A field left empty in a language shows the default language value, except for cells inside repeaters and flexible content.strings, strings_group — template strings registered with Polylang.user_fields — profile fields translated per language.pending_meta — meta key set on duplicated copies; while set, the language switcher hides that language.hidden_languages — languages left out of the switcher and hreflang, e.g. while their content is being prepared.x_default — add hreflang x-default (default true).prefill_translations — pre-fill translations created with Polylang’s “+” from the source post (default true, off with Polylang PRO, which does it itself).modules — switch modules off: [ 'media' => false ]. Keys: slugs, bases, default_term, options, duplicate, translations, menus, ids, media, strings, users, frontend.cosmo_mlk_language(), cosmo_mlk_default_language(), cosmo_mlk_locale( $language ), cosmo_mlk_languages()cosmo_mlk_home_url( $path, $language ), cosmo_mlk_url_prefix( $language ), cosmo_mlk_url_base( $key, $language )cosmo_mlk_switcher( $args ) — languages for a switcher: slug, name, locale, url, flag, current, available. Pass keep_unavailable => true to keep untranslated languages with an empty url.cosmo_mlk_t( $key, $fallback ) — translated template stringcosmo_mlk_user_field( $user_id, $key, $language ), cosmo_mlk_image_alt( $attachment_id, $language )cosmo_mlk_translated_post( $post_id, $language ), cosmo_mlk_readable_translation( $post_id, $language ), cosmo_mlk_pending_translations()cosmo_mlk_term_by_slug( $slug, $taxonomy, $language ), cosmo_mlk_insert_term( $name, $taxonomy, $language, $args )cosmo_mlk_duplicate_post( $post_id, $language, $args ), cosmo_mlk_link_translations( $map ), cosmo_mlk_link_term_translations( $map )cosmo_mlk_copy_post( $from, $to ), cosmo_mlk_translation_outdated( $post_id )cosmo_mlk_sync_menus()cosmo_mlk_copy_options( $from, $to, $pages ), cosmo_mlk_migrate_legacy_options( $language )Every function keeps working with Polylang switched off, falling back to a single default language.
cosmo/multilingual_kit/config — the configuration above.cosmo/multilingual_kit/switcher — filters the switcher language list.cosmo/multilingual_kit/duplicated — fires after a translation is created by copying: ( $new_id, $source_id, $language ).cosmo/multilingual_kit/copied — fires after an existing translation is overwritten from another language: ( $post_id, $source_id, $language ).