Description
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.
- The same slug in every language.
/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. - Translated URL bases.
/success-stories/in English,/es/casos-de-exito/in Spanish, for post types, taxonomies and the blog base. - Per-language ACF / SCF options pages. Language tabs above the options page, a “copy from the default language” button, and a fallback to the default language for empty values.
get_field( 'name', 'options' )keeps working. - Duplicate as translation. A “Duplicate Español” row action copies blocks, custom fields, featured image, template and terms, and links the posts as translations.
- Copy from another language. A “Translation” box in the editor replaces a translation’s title, content, custom fields and terms with those of any other language, like WPML’s “Copy content from”. New translations created with Polylang’s “+” start pre-filled from the source post.
- Outdated translations. A translation remembers what it was copied from. Once the source changes, the editor box and the posts list flag it as outdated until it is marked up to date or copied again.
- Menu sync. One button on the Menus screen copies the default language menus to every other language and assigns them to the same locations. Items point to the translated posts and terms, labels already translated are kept, and items removed from the source are removed from the copies.
- Translated ids on output. Reusable blocks and ACF post object, relationship and taxonomy values, options pages included, show the current language’s translation when there is one.
- Default category in every language. Repairs the default category translation Polylang leaves without a language when languages are created programmatically, and creates missing ones.
- Image alt per language, with a fallback to the library alt, for images output with
wp_get_attachment_image(). The block editor’s Image block saves its alt into the post content and is translated with the post. - Theme strings in Languages Translations.
- Author profile fields per language, including the biography fallback Polylang free lacks.
- Language switcher data that hides untranslated, hidden or not-yet-translated copies, or keeps them greyed out, plus hreflang
x-default. - Paged archives. The switcher and hreflang of
/page/2/point to page 2 of each translation, and a language with fewer pages is left out. Polylang prints no hreflang on paged archives at all. - No foreign slugs. A URL reached through another language’s slug, like
/pt/recursos/for/pt/blog-e-noticias/, redirects to the translation’s own address instead of serving a duplicate. - One Yoast sitemap per language. With languages in directories the sitemap index lists
en-sitemap.xml,es-sitemap.xml… instead of per-type maps that mix every language. Yoast’s own rules for noindex, exclusions and canonicals still apply. - Untranslated front page. Until the static front page is translated, a language’s home redirects to the default language one instead of showing its empty blog, home links follow, and hreflang leaves that language out. Hidden languages are left out of hreflang too.
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.
Configuration
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, orblog. 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 defaultoptionspost id becomes per-language too; give pages that should stay shared their ownpost_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 hreflangx-default(defaulttrue).prefill_translations— pre-fill translations created with Polylang’s “+” from the source post (defaulttrue, 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,sitemaps.
Template functions
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. Passkeep_unavailable => trueto 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.
Hooks
cosmo/multilingual_kit/config— the configuration above.cosmo/multilingual_kit/switcher— filters the switcher language list.cosmo/multilingual_kit/per_page— posts per page of a post type archive, for the paged URLs in the language sitemaps:( $per_page, $post_type ). Defaults to the Reading setting.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 ).
Installation
- Install and activate Polylang 3.7 or later, and add your languages.
- Upload the plugin through Plugins – Add New – Upload Plugin, or install it from the plugin directory.
- Activate Cosmo Multilingual Kit for Polylang.
- Add the
cosmo/multilingual_kit/configfilter to your theme (see Configuration in the description) and flush permalinks under Settings – Permalinks.
FAQ
-
I switched an existing site to per-language options and the settings look empty
-
Values saved before were stored without a language. Run
cosmo_mlk_migrate_legacy_options()once to move them to the default language. -
Does it work with Polylang PRO?
-
It targets Polylang free. With PRO active, the
slugs,basesandduplicatemodules switch themselves off, since PRO covers them. An explicitmodulesentry in the config overrides that. -
Which modules switch themselves off?
-
The Plugins screen lists them with the reason:
slugsandbasesandduplicatewith Polylang PRO.slugswhen Polylang sets the language from the content: with no language in the URL, shared slugs cannot be told apart.optionswith ACF Options For Polylang, which stores options per language on its own.mediawhen Polylang media translation is on, as every language then has its own attachment.
-
Which URL modes are supported?
-
Language as a directory (with or without
/language/), as a subdomain, or as a domain. Sites installed in a subdirectory work too. -
Can I duplicate WooCommerce products?
-
No. WooCommerce keeps product data outside post meta, so a plain copy would give a broken product.
Reviews
There are no reviews for this plugin.
Contributors & Developers
“Cosmo Multilingual Kit for Polylang” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “Cosmo Multilingual Kit for Polylang” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
1.2.0
- Paged archives keep the page number in the switcher and get hreflang, which Polylang leaves out.
- URLs reached through another language’s slug redirect to the translation’s own address.
- New
sitemapsmodule: one Yoast sitemap per language, paged archives included.
1.1.1
- Template functions are pluggable, so the plugin activates on a theme that declares its own fallbacks for them.
1.1.0
- Copy from another language, pre-filled new translations and outdated translation tracking.
- Menu sync across languages.
- Reusable blocks and ACF relational fields follow the current language.
- Switcher can keep untranslated languages greyed out.
- Untranslated front page no longer exposes an empty blog in other languages.
- Hidden languages are left out of hreflang.
- Options pages: get_field() by name works in a language that was never saved.
1.0.0
- First release.