{"id":393500,"date":"2026-10-28T21:11:31","date_gmt":"2026-10-28T21:11:31","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/cosmo-multilingual-kit-for-polylang\/"},"modified":"2026-10-31T15:13:37","modified_gmt":"2026-10-31T15:13:37","slug":"cosmo-multilingual-kit-for-polylang","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/cosmo-multilingual-kit-for-polylang\/","author":23555548,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.2.2","stable_tag":"1.2.2","tested":"7.1.4","requires":"6.4","requires_php":"7.4","requires_plugins":null,"header_name":"Cosmo Multilingual Kit for Polylang","header_author":"Bakhodir Sharipov","header_description":"Fills the gaps of Polylang free: the same slug in every language, translated URL bases, per-language ACF options pages, duplicate-as-translation, per-language image alt, theme strings and author fields.","assets_banners_color":"","last_updated":"2026-10-31 15:13:37","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/cosmoc0der\/cosmo-multilingual-kit-for-polylang","header_author_uri":"https:\/\/github.com\/cosmoc0der","rating":0,"author_block_rating":0,"active_installs":0,"downloads":177,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.1.0":{"tag":"1.1.0","author":"bakhods","date":"2026-10-28 21:11:06","revision":3771661},"1.1.1":{"tag":"1.1.1","author":"bakhods","date":"2026-10-29 09:46:54","revision":3772554},"1.2.0":{"tag":"1.2.0","author":"bakhods","date":"2026-10-30 17:27:27","revision":3775778},"1.2.1":{"tag":"1.2.1","author":"bakhods","date":"2026-10-31 13:40:05","revision":3776907},"1.2.2":{"tag":"1.2.2","author":"bakhods","date":"2026-10-31 15:13:37","revision":3777026}},"upgrade_notice":[],"ratings":[],"assets_icons":[],"assets_banners":[],"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.1.0","1.1.1","1.2.0","1.2.1","1.2.2"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[],"plugin_tags":[2211,99,10601,562,2333],"plugin_category":[48,59],"plugin_contributors":[278202],"plugin_business_model":[],"class_list":["post-393500","plugin","type-plugin","status-publish","hentry","plugin_tags-acf","plugin_tags-multilingual","plugin_tags-polylang","plugin_tags-slugs","plugin_tags-translation","plugin_category-language-tools","plugin_category-utilities-and-tools","plugin_contributors-bakhods","plugin_committers-bakhods"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/s.w.org\/plugins\/geopattern-icon\/cosmo-multilingual-kit-for-polylang.svg","icon_2x":false,"generated":true},"screenshots":[],"raw_content":"<!--section=description-->\n<p>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.<\/p>\n\n<ul>\n<li><strong>The same slug in every language.<\/strong> <code>\/about-us\/<\/code> and <code>\/es\/about-us\/<\/code> instead of <code>\/es\/about-us-2\/<\/code>, for posts, pages, custom post types and terms. Requests resolve to the post or term of the requested language.<\/li>\n<li><strong>Translated URL bases.<\/strong> <code>\/success-stories\/<\/code> in English, <code>\/es\/casos-de-exito\/<\/code> in Spanish, for post types, taxonomies and the blog base.<\/li>\n<li><strong>Per-language ACF \/ SCF options pages.<\/strong> Language tabs above the options page, a \"copy from the default language\" button, and a fallback to the default language for empty values. <code>get_field( 'name', 'options' )<\/code> keeps working.<\/li>\n<li><strong>Duplicate as translation.<\/strong> A \"Duplicate \u2192 Espa\u00f1ol\" row action copies blocks, custom fields, featured image, template and terms, and links the posts as translations.<\/li>\n<li><strong>Copy from another language.<\/strong> 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.<\/li>\n<li><strong>Outdated translations.<\/strong> 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.<\/li>\n<li><strong>Menu sync.<\/strong> 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.<\/li>\n<li><strong>Translated ids on output.<\/strong> Reusable blocks and ACF post object, relationship and taxonomy values, options pages included, show the current language's translation when there is one.<\/li>\n<li><strong>Default category in every language.<\/strong> Repairs the default category translation Polylang leaves without a language when languages are created programmatically, and creates missing ones.<\/li>\n<li><strong>Image alt per language<\/strong>, with a fallback to the library alt, for images output with <code>wp_get_attachment_image()<\/code>. The block editor's Image block saves its alt into the post content and is translated with the post.<\/li>\n<li><strong>Theme strings<\/strong> in Languages \u2192 Translations.<\/li>\n<li><strong>Author profile fields per language<\/strong>, including the biography fallback Polylang free lacks.<\/li>\n<li><strong>Language switcher data<\/strong> that hides untranslated, hidden or not-yet-translated copies, or keeps them greyed out, plus hreflang <code>x-default<\/code>.<\/li>\n<li><strong>Paged archives.<\/strong> The switcher and hreflang of <code>\/page\/2\/<\/code> 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.<\/li>\n<li><strong>No foreign slugs.<\/strong> A URL reached through another language's slug, like <code>\/pt\/recursos\/<\/code> for <code>\/pt\/blog-e-noticias\/<\/code>, redirects to the translation's own address instead of serving a duplicate.<\/li>\n<li><strong>One Yoast sitemap per language<\/strong>, if you prefer it. With languages in directories Yoast's per-type maps mix every language, as they do with WPML; opted in, the sitemap index lists <code>en-sitemap.xml<\/code>, <code>es-sitemap.xml<\/code>\u2026 instead. Yoast's own rules for noindex, exclusions and canonicals still apply.<\/li>\n<li><strong>Untranslated front page.<\/strong> 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.<\/li>\n<\/ul>\n\n<p>Requires Polylang 3.7 or later.<\/p>\n\n<p>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.<\/p>\n\n<h3>Configuration<\/h3>\n\n<p>Everything is configured through a single filter, typically in the theme's <code>functions.php<\/code>:<\/p>\n\n<pre><code>add_filter( 'cosmo\/multilingual_kit\/config', function ( $config ) {\n    return array_merge( $config, [\n        'post_types'       =&gt; [ 'post', 'page', 'story' ],\n        'taxonomies'       =&gt; [ 'category', 'post_tag', 'story_category' ],\n        'bases'            =&gt; [ 'blog', 'story', 'story_category' ],\n        'base_map'         =&gt; [ 'story' =&gt; [ 'es' =&gt; 'casos-de-exito' ] ],\n        'options_pages'    =&gt; [ 'site-options' ],\n        'strings'          =&gt; [ 'read_more' =&gt; 'Read more' ],\n        'strings_group'    =&gt; 'My theme',\n        'user_fields'      =&gt; [ 'position' =&gt; 'Position' ],\n        'pending_meta'     =&gt; 'translation_pending',\n        'hidden_languages' =&gt; [ 'uz' ],\n        'sitemaps'         =&gt; 'language',\n    ] );\n} );\n<\/code><\/pre>\n\n<ul>\n<li><code>post_types<\/code>, <code>taxonomies<\/code> \u2014 made translatable in addition to Polylang's settings.<\/li>\n<li><code>bases<\/code> \u2014 sections whose URL base can be translated: post type names, taxonomy names, or <code>blog<\/code>. A default can be given explicitly (<code>'blog' =&gt; 'news'<\/code>), otherwise the registered rewrite slug is used. Every URL of the section follows the translated base: nested terms, feeds, paging, archives.<\/li>\n<li><code>base_map<\/code> \u2014 translated bases per language. Flush permalinks after changing it, and let your SEO plugin rebuild its URL cache.<\/li>\n<li><code>options_pages<\/code> \u2014 ACF options page slugs stored per language. Storage goes by the page's post id, so every options page that keeps the default <code>options<\/code> post id becomes per-language too; give pages that should stay shared their own <code>post_id<\/code>. A field left empty in a language shows the default language value, except for cells inside repeaters and flexible content.<\/li>\n<li><code>strings<\/code>, <code>strings_group<\/code> \u2014 template strings registered with Polylang.<\/li>\n<li><code>user_fields<\/code> \u2014 profile fields translated per language.<\/li>\n<li><code>pending_meta<\/code> \u2014 meta key set on duplicated copies; while set, the language switcher hides that language.<\/li>\n<li><code>hidden_languages<\/code> \u2014 languages left out of the switcher and hreflang, e.g. while their content is being prepared.<\/li>\n<li><code>x_default<\/code> \u2014 add hreflang <code>x-default<\/code> (default <code>true<\/code>).<\/li>\n<li><code>prefill_translations<\/code> \u2014 pre-fill translations created with Polylang's \"+\" from the source post (default <code>true<\/code>, off with Polylang PRO, which does it itself).<\/li>\n<li><code>sitemaps<\/code> \u2014 Yoast sitemaps with languages in directories: <code>mixed<\/code> keeps Yoast's per-type maps with every language in them (default), <code>language<\/code> gives one map per language. Subdomains and domains get a sitemap per host either way.<\/li>\n<li><code>modules<\/code> \u2014 switch modules off: <code>[ 'media' =&gt; false ]<\/code>. Keys: <code>slugs<\/code>, <code>bases<\/code>, <code>default_term<\/code>, <code>options<\/code>, <code>duplicate<\/code>, <code>translations<\/code>, <code>menus<\/code>, <code>ids<\/code>, <code>media<\/code>, <code>strings<\/code>, <code>users<\/code>, <code>frontend<\/code>, <code>sitemaps<\/code>.<\/li>\n<\/ul>\n\n<h3>Template functions<\/h3>\n\n<ul>\n<li><code>cosmo_mlk_language()<\/code>, <code>cosmo_mlk_default_language()<\/code>, <code>cosmo_mlk_locale( $language )<\/code>, <code>cosmo_mlk_languages()<\/code><\/li>\n<li><code>cosmo_mlk_home_url( $path, $language )<\/code>, <code>cosmo_mlk_url_prefix( $language )<\/code>, <code>cosmo_mlk_url_base( $key, $language )<\/code><\/li>\n<li><code>cosmo_mlk_switcher( $args )<\/code> \u2014 languages for a switcher: slug, name, locale, url, flag, current, available. Pass <code>keep_unavailable =&gt; true<\/code> to keep untranslated languages with an empty url.<\/li>\n<li><code>cosmo_mlk_t( $key, $fallback )<\/code> \u2014 translated template string<\/li>\n<li><code>cosmo_mlk_user_field( $user_id, $key, $language )<\/code>, <code>cosmo_mlk_image_alt( $attachment_id, $language )<\/code><\/li>\n<li><code>cosmo_mlk_translated_post( $post_id, $language )<\/code>, <code>cosmo_mlk_readable_translation( $post_id, $language )<\/code>, <code>cosmo_mlk_pending_translations()<\/code><\/li>\n<li><code>cosmo_mlk_term_by_slug( $slug, $taxonomy, $language )<\/code>, <code>cosmo_mlk_insert_term( $name, $taxonomy, $language, $args )<\/code><\/li>\n<li><code>cosmo_mlk_duplicate_post( $post_id, $language, $args )<\/code>, <code>cosmo_mlk_link_translations( $map )<\/code>, <code>cosmo_mlk_link_term_translations( $map )<\/code><\/li>\n<li><code>cosmo_mlk_copy_post( $from, $to )<\/code>, <code>cosmo_mlk_translation_outdated( $post_id )<\/code><\/li>\n<li><code>cosmo_mlk_sync_menus()<\/code><\/li>\n<li><code>cosmo_mlk_copy_options( $from, $to, $pages )<\/code>, <code>cosmo_mlk_migrate_legacy_options( $language )<\/code><\/li>\n<\/ul>\n\n<p>Every function keeps working with Polylang switched off, falling back to a single default language.<\/p>\n\n<h3>Hooks<\/h3>\n\n<ul>\n<li><code>cosmo\/multilingual_kit\/config<\/code> \u2014 the configuration above.<\/li>\n<li><code>cosmo\/multilingual_kit\/switcher<\/code> \u2014 filters the switcher language list.<\/li>\n<li><code>cosmo\/multilingual_kit\/per_page<\/code> \u2014 posts per page of a post type archive, for the paged URLs in the language sitemaps: <code>( $per_page, $post_type )<\/code>. Defaults to the Reading setting.<\/li>\n<li><code>cosmo\/multilingual_kit\/duplicated<\/code> \u2014 fires after a translation is created by copying: <code>( $new_id, $source_id, $language )<\/code>.<\/li>\n<li><code>cosmo\/multilingual_kit\/copied<\/code> \u2014 fires after an existing translation is overwritten from another language: <code>( $post_id, $source_id, $language )<\/code>.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Install and activate Polylang 3.7 or later, and add your languages.<\/li>\n<li>Upload the plugin through <strong>Plugins - Add New - Upload Plugin<\/strong>, or install it from the plugin directory.<\/li>\n<li>Activate <strong>Cosmo Multilingual Kit for Polylang<\/strong>.<\/li>\n<li>Add the <code>cosmo\/multilingual_kit\/config<\/code> filter to your theme (see Configuration in the description) and flush permalinks under <strong>Settings - Permalinks<\/strong>.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"i%20switched%20an%20existing%20site%20to%20per-language%20options%20and%20the%20settings%20look%20empty\"><h3>I switched an existing site to per-language options and the settings look empty<\/h3><\/dt>\n<dd><p>Values saved before were stored without a language. Run <code>cosmo_mlk_migrate_legacy_options()<\/code> once to move them to the default language.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20polylang%20pro%3F\"><h3>Does it work with Polylang PRO?<\/h3><\/dt>\n<dd><p>It targets Polylang free. With PRO active, the <code>slugs<\/code>, <code>bases<\/code> and <code>duplicate<\/code> modules switch themselves off, since PRO covers them. An explicit <code>modules<\/code> entry in the config overrides that.<\/p><\/dd>\n<dt id=\"which%20modules%20switch%20themselves%20off%3F\"><h3>Which modules switch themselves off?<\/h3><\/dt>\n<dd><p>The Plugins screen lists them with the reason:<\/p>\n\n<ul>\n<li><code>slugs<\/code> and <code>bases<\/code> and <code>duplicate<\/code> with Polylang PRO.<\/li>\n<li><code>slugs<\/code> when Polylang sets the language from the content: with no language in the URL, shared slugs cannot be told apart.<\/li>\n<li><code>options<\/code> with ACF Options For Polylang, which stores options per language on its own.<\/li>\n<li><code>media<\/code> when Polylang media translation is on, as every language then has its own attachment.<\/li>\n<\/ul><\/dd>\n<dt id=\"which%20url%20modes%20are%20supported%3F\"><h3>Which URL modes are supported?<\/h3><\/dt>\n<dd><p>Language as a directory (with or without <code>\/language\/<\/code>), as a subdomain, or as a domain. Sites installed in a subdirectory work too.<\/p><\/dd>\n<dt id=\"can%20i%20duplicate%20woocommerce%20products%3F\"><h3>Can I duplicate WooCommerce products?<\/h3><\/dt>\n<dd><p>No. WooCommerce keeps product data outside post meta, so a plain copy would give a broken product.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.2.2<\/h4>\n\n<ul>\n<li>A posts page sharing its slug with its translations shows each language's blog instead of an empty page.<\/li>\n<\/ul>\n\n<h4>1.2.1<\/h4>\n\n<ul>\n<li>Yoast sitemaps stay mixed across languages by default, as in 1.1 and WPML. One sitemap per language is opted into with the new <code>sitemaps =&gt; 'language'<\/code> config key.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>Paged archives keep the page number in the switcher and get hreflang, which Polylang leaves out.<\/li>\n<li>URLs reached through another language's slug redirect to the translation's own address.<\/li>\n<li>New <code>sitemaps<\/code> module: one Yoast sitemap per language, paged archives included.<\/li>\n<\/ul>\n\n<h4>1.1.1<\/h4>\n\n<ul>\n<li>Template functions are pluggable, so the plugin activates on a theme that declares its own fallbacks for them.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Copy from another language, pre-filled new translations and outdated translation tracking.<\/li>\n<li>Menu sync across languages.<\/li>\n<li>Reusable blocks and ACF relational fields follow the current language.<\/li>\n<li>Switcher can keep untranslated languages greyed out.<\/li>\n<li>Untranslated front page no longer exposes an empty blog in other languages.<\/li>\n<li>Hidden languages are left out of hreflang.<\/li>\n<li>Options pages: get_field() by name works in a language that was never saved.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>First release.<\/li>\n<\/ul>","raw_excerpt":"Fills the gaps of Polylang free: one slug in every language, translated URL bases, per-language ACF options, duplicate-as-translation and more.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/393500","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=393500"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/bakhods"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=393500"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=393500"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=393500"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=393500"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=393500"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=393500"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}