Plugin: Polylang

Plugin: Polylang

There is also a free, easy to use, and very light-weight plugin called Polylang. Migration from WPML to Polylang should be painless with dedicated plugin WPML to Polylang by PolyLang creators.

Polylang Pro is required for ACF Pro compatibility, as stated in official document. They also recommend to use third-party plugin to translate ACF options: ACF Options For Polylang

Not sure if Pro is really required as some users are stating that “ACF works great with Polylang and everything works out of the box as expected. No dirty hacks are needed, except for “options” page.”

For CF7, if you don´t want to create a form for each language, you can use one single CF7 form for all languages using plugin Multilingual Contact Form 7 with Polylang

For “String translation” Polylang officialy suggests using any PO/MO tools: How to translate the themes and plugins hardcoded strings?, but there is an extension: Theme and plugin translation for Polylang (TTfP) that does exactly that ans makes wonders.

Using Polylang without paying for Pro

If you need proper WooCommerce, I guess you must buy that module, but for everything else, free is sufficient.

Main differences Pro vs Free:

  1. Important: Translations can share the same slug
  2. Important: Translate slugs in URLS (custom post types, taxonomies and more…)
  3. Good to have: Duplicate posts across languages
  4. Good to have: Additional integration with ACF Pro, meaning ACF Options fields

Every difference is solved with free plugins:

Translations can share the same slug in the URL

When we say “same slug” we mean that /wizard, /nl/wizard and /de/wizard are the same. This is the main reason to buy Pro, as explained here

It is Properly solved with plugin grappler/polylang-slug, but there is this plugin as an alternative that should also work lufton/polylang-share-slug. Official Polylang Pro also mentions polylang-slug project in source code.

Bas news it that on this plugin that works, in repository states: “This plugin is not being actively developed”

Duplicate posts across languages

Not sure this is essential functionality, but it is useful. It means that when we create a new translation, the whole post content, title and attachments are copied.

Based on reviews, this one is quite active and tested: Polylang Duplicate Content, but as usual, we have a viable alternative: aucor/polylang-copy-content

Also not sure about the following problem, that probably arises when cloning post using some specialized plugins: mindtwo/post-duplicator-polylang

Rewriting slugs aka translate slugs in post types

Based on this article on Translating URLS slugs from 2015, I believe this option was free before but now it is not.

There are too many plugins to enable rewriting slugs, but I’ve discovered that this one is only maintained and works: webnucleo/wp-polylang-translate-rewrite-slugs

The one on the plugin repository is real exploding bomb with bad code: WP Slug Post Type Custom Language (Polylang) | carlosramosweb/wp-slug-post-type-custom-language.

There is also this code snippet that might work as well: stouch/wp-plugin-polylang-localized-taxonomy-slug and I think this for similar purpose extracted from Pro: rizkiaprita/polylang-pro

REST API Support

Is probably solved with maru3l/wp-rest-polylang

Additional integration with ACF Pro

Is perfectly resolved with “Options” plugin: ACF Options For Polylang | BeAPI/acf-options-for-polylang

Amazing additions to the plugin


Very Good


How to remove the Polylang plugin

Quite nice and possible.

Polylang and WP-CLI

Polylang stores language information as a taxonomy ‘language’ for posts and ‘term_language’ for terms.

In WP-CLI we can use, but without it, we can use:

To list languages:

wp term list language

List English pages

wp post list --post_type=page --lang=en


wp post list --post_type=page --lang=nl --fields=ID,post_title

Using SQL to modify Polylang data

Source: Search and replace query with multilanguage site (polylang)

List all English pages

SELECT id, post_title FROM `wp_posts` WHERE post_type = 'page' AND id IN (
    SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(`description`, '"en";i:', -1), ';',1) FROM `wp_term_taxonomy` WHERE `taxonomy` = 'post_translations' )

Do a basic search-replace on one language, for example nl for Dutch

UPDATE `wp_posts` AS a
  SELECT id, post_title FROM `wp_posts` WHERE post_type = 'page' AND id IN (SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(`description`, '"en";i:', -1), ';',1) FROM `wp_term_taxonomy` WHERE `taxonomy` = 'post_translations' )
) AS b
ON a.ID = b.ID
SET a.post_content = replace(a.post_content, '<!-- wp:block {"ref":2323} /-->', '<!-- wp:block {"ref":4247} /-->');

In above query, I’m actually replacing one Gutenberg Reusable Block with another for specific language.

Translation of Reusable Blocks

The problem exists both on WPML and on Polylang and most of the other translation plugins for WordPress.

It sometimes worked, but just by luck

Currently, reusable blocks can’t be translated, as they are stored on pages by their ID.

Manifestation and temporary solution

In any language some page is displayed, the reusable block is presented in the original language used when that block was inserted into that page.

I’ve used a temporary fix that is not bulletproof, but it worked for some time. The idea is to check during page rendering if a reusable block present on the page has translation available and if it has it, to use that translation instead of the original reusable block.

Read more about this idea on Polylang support thread:

Proper permanent solution

The only proper solution is to fully ignore “language” info on reusable blocks as that is obviously not working. Then on every page translation insert the block that you need in that exact language, without expecting automatic language switching on reusable blocks.

To ease this process, it is best to include language abbreviations in block titles (EN, DE, etc) so that when using them on pages, to distinguish them easily.

I’m assuming that URL modifications > Hide URL language information for default language option is set and active

Polylang uses a cookie to remember the language selected by the user when he comes back to visit again the website. This cookie is also used to get the language information when not available in another way. Examples are AJAX requests (quite important) or the login page (less important).

About Polylang Cookie and EU cookie law

Polylang plugin is setting a cookie for language preference using the ˙set-cookie` header from PHP.

Polylang works just fine without the cookie, if you have turned on “URL modifications > The language is set from the directory name in pretty permalinks”. In this case, it is safe to disable the cookie by using define('PLL_COOKIE', false);

There are lot more options that could be set in wp-config.php

It is possible to modify cookie expiration using pll_cookie_expiration hook. For example, if you prefer a cookie expiring at the end of the session, add this: add_filter( 'pll_cookie_expiration', function() { return 0; } );; more info here

Polylang Language Auto-detection

Nginx Cache and Polylang have a problem, a huge one. Let’s try to explain a problem.

When the Polylang setting Detect browser language: When the front page is visited, set the language according to the browser preference is checked, then the first visitor that comes along will get redirected to his/her language correctly, but … that redirect response will be cached by Nginx! Now subsequent visitors with different browser languages will get served the same cached response, and they will be redirected to the wrong language.

That’s why we have a problem with static caching. On the server side, it can be solved by updating the proxy_cache_key directive to include pll_language cookie as mentioned in this support thread uses a Nginx web server to cache pages. There is a known issue that Polylang language detection has with caching on the server. It is described more in the following support comment - Nginx fastcgi_cache and Polylang homepage redirects and another article

… and another article from WP Rocket Knowledge Base

Possibilities in solving the homepage cache problem

  1. One solution could be NOT to cache the homepage, but as our server is quite slow, we must avoid this. There is also no way to bust cache only the homepage on For regular PHP-based caching plugins, Polylang is exactly using this cache-busting technique, as seen in the code here polylang/cache-compat.php](

Polylang can’t turn off the cache on the homepage, as the cache resides later in the response chain, on Nginx server and not on the PHP plugin level.

  1. Turn off language detection and turn off cookies, so user will never redirect. This is a viable solution, but in our case with 90% of users from non-default language, we shouldn’t use this one

  2. Write custom auto-detection and redirection code, based not on server PHP but on client-side JavaScript. This is the best way to go.

Final approach

  1. Don’t use Detect browser language option as it won’t work. It detects language using PHP $_SERVER['HTTP_ACCEPT_LANGUAGE'] and as such has a problem with server caching
  2. Leave PLL_COOKIE as it doesn’t influence anything else, and we won’t use it anyway
  3. Use custom JavaScript code for language detection and store selection inside a custom cookie

Ideas on JavaScript implementation

Interesting JavaScript implementations found: inside this support thread and this one

Redirection is done only on the home page and only on the default language, URL without language suffix.

Verify functionality
  1. Test language detection — in new incognito mode (so that there are no cookies set), navigate to, and you’ll be redirected to your browser language, probably German
  2. Test last used language — in normal mode, switch using language switcher to a different language. After that, open a new tab and navigate to You should be redirected to that selected language.

date 15. Jul 2021 | modified 17. Jan 2023
filename: Plugins » Polylang