At first this looked like a straightforward missing-translation problem. The Polish page had the correct title “Polityka plików cookie (UE)”, the expected permalink, and the Complianz banner linked to the Polish version. Yet the generated policy itself still started in English: Introduction, What are cookies?, What are scripts?, and the following paragraphs were all English.
The more useful clue was that the document was not completely English. Further down the page, some service labels and dynamic fields were already translated into Polish, while the surrounding legal text stayed in English. That mixed result turned out to be the key to the whole investigation.
Clearing caches, checking locales again, and looking for the entire policy body under Polylang → String translations did not solve it. The actual cause was timing: Complianz built the main document elements before Polylang, in The language is set from content mode, had selected the language of the current page.
I did not fix this by editing Complianz files or by disconnecting the document from synchronization. Instead I deployed a narrow MU-plugin that, after Polylang has defined the request language, safely rebuilds only the synchronized EU Cookie Policy for Polish and German. The production result was PL PASS / DE PASS / EN PASS.
Important: this is a real compatibility case and a tested workaround, not an official Complianz or Polylang patch. The code is intentionally narrow and should not be installed blindly on every multilingual WordPress site.
The symptom: the page language was right, the document language was not
The site uses three languages. English is the default Polylang language; Polish and German are linked translations.
At the time of diagnosis the relevant baseline was:
- WordPress
7.1; - PHP
8.5.7; - Complianz
7.5.4; - Polylang in
The language is set from contentmode; - locales
en_US,de_DE,pl_PL; - EN as the default language;
- the legal document still set to
Synchronize document with Complianz.
The English Cookie Policy (EU) worked normally. I then created the linked Polish page in Polylang. Its title was Polish and, after publication, page ID 2453 worked at /polityka-plikow-cookie-ue/. The Complianz banner also linked to the correct Polish page.
The problem was inside the document: most of the main headings and paragraphs were still English.

The German translation reproduced the same pattern: the page context was German, but most of the synchronized policy body remained English.
The strongest clue: only part of the document was wrong
If the whole translation pack had been missing, I would have expected the page to be consistently English. It was not.
Inside the service/cookie section I could see translated Polish labels such as Używanie, Udostępnianie danych, Nazwa, Wygaśnięcie and Funkcja. At the same time headings such as Placed cookies, Consent and other legal paragraphs around them were still English.
That suggested that not every part of the policy is produced at the same point in the WordPress request. Some data was rendered later and already saw the correct Polish locale, while the main document elements had been translated earlier and were stuck in English.
That observation matched the Complianz source code exactly.

What I checked before auditing the code
Before looking at WordPress hook ordering, I ruled out the obvious causes.
1. Polylang locales
The language configuration was correct:
- English →
en_US— default language; - Deutsch →
de_DE; - Polski →
pl_PL.
So this was not a simple locale typo or a page assigned to the wrong Polylang language.

2. Complianz translation files
I checked wp-content/languages/plugins/. The German and Polish Complianz resources were physically present, including files matching complianz-gdpr-de_DE.* and complianz-gdpr-pl_PL.*.
That ruled out the easiest explanation: WordPress simply had not downloaded the plugin translations.

3. Cache
Cache was a reasonable suspect because an earlier, separate banner-localization issue had looked like stale output. For the legal document, however, I purged the available page-cache layers and the same mixed-language pattern remained in both Polish and German.
I therefore do not classify this legal-document issue as a cache bug.
4. Polylang String translations
Polylang → String translations is the right place for parts of the Complianz banner and custom strings. It did not contain the full generated legal-document body, such as all of the Introduction and What are cookies? paragraphs.
Manually recreating the entire legal document there would have been the wrong fix.
5. Document synchronization
The Complianz block was still set to:
Synchronize document with Complianz
I deliberately did not use Edit document and stop synchronization as the solution. That would make the body editable, but it gives up one of the main benefits of a generated legal document: ongoing synchronization with Complianz.

A short false alarm: the first test returned 404
During the first Polish test, the banner pointed to /?page_id=2453 and an anonymous visitor received a 404. That was not part of the translation bug.
The Polish page was still a Draft at that point, so a public 404 was expected WordPress behavior. Once it was published, routing worked correctly and the page received /polityka-plikow-cookie-ue/. Only then was the actual defect visible: the correct Polish page with an English policy body.
It is a small detail, but a useful debugging lesson: keep routing/status problems separate from rendering/localization problems.
Source audit: where the Cookie Policy is actually built
After ruling out locales, translation files, cache and String translations, I reviewed the exact production files from Complianz 7.5.4. Four places mattered.
gutenberg/block.php
The complianz/document render callback does not rebuild translations. For a synchronized document it effectively delegates to:
$html = COMPLIANZ::$document->get_document_html( $type, $region );
The Gutenberg block itself therefore does not select the locale or regenerate the main strings.
documents/class-document.php
get_document_html() reads an already prepared array:
$elements = COMPLIANZ::$config->pages[ $region ][ $type ]["document_elements"];
It then loops over those elements and renders their headings and content. Callback-driven elements can execute during this later render phase.
That is why a mixed result is possible: normal elements can already be frozen, while some dynamic fragments are evaluated later.
config/class-config.php
This is where the timing difference becomes important.
load_documents() loads the document files and applies the cmplz_document_elements filter. In the production code, load_documents() is registered on WordPress init.
config/documents/cookie-policy-eu.php
The main EU Cookie Policy elements are built directly when this file is included. For example:
_x(
'Introduction',
'Legal document cookie policy:paragraph title',
'complianz-gdpr'
)
is evaluated immediately and the result is stored in document_elements.
If the active locale is still English at that moment, the array already contains Introduction. Changing the request language later does not retroactively retranslate that existing value.
The other half of the problem: when Polylang defines the language
Polylang’s developer documentation describes two different paths. With The language is set from content, Polylang has to wait for the queried content and defines the language on the WordPress wp action at priority 5.
The same documentation explicitly warns plugin authors not to translate strings before wp has fired in this mode.
The simplified request flow in my setup was therefore:
WordPress init
↓
Complianz load_documents()
↓
cookie-policy-eu.php runs _x()/__()
↓
document_elements are built while the locale is still EN
↓
WordPress wp, priority 5
↓
Polylang defines the final PL or DE language
↓
Complianz renders the already-built document_elements
↓
some callbacks run later and already see PL/DE
That explains the odd symptom precisely: the main policy text was English, while some dynamic parts were correctly Polish or German.

The fix: a narrow MU-plugin instead of editing Complianz
Editing Complianz vendor files would have been the wrong direction. A plugin update would overwrite the change, and maintaining a private fork would make future audits harder.
I also did not want to redesign the site’s Polylang URL architecture just to solve one compatibility edge case. The language is set from content remains an intentional part of this site’s configuration.
The production solution is a separate MU-plugin:
wp-content/mu-plugins/bybrzoza-complianz-polylang-legal-i18n-fix.php
Its entry point runs on:
add_action(
'wp',
'bybrzoza_cmplz_polylang_relocalize_cookie_policy',
20
);
Priority 20 is deliberate: it runs after the documented wp/5 point where Polylang defines the language in this mode.
The plugin does not switch languages itself. It first verifies that Polylang and WordPress already agree on the active locale and only then, behind several additional guards, rebuilds the specific document_elements array.
Why the workaround has so many guardrails
A much shorter snippet could simply include cookie-policy-eu.php again on every request. I deliberately did not do that.
Version 0.1.0 only acts when all relevant conditions match:
- frontend request only;
- normal WordPress page;
- Polylang and Complianz are available;
- required Complianz constants and objects exist;
- Complianz is
>= 7.5.4and< 8.0.0; - Polylang’s locale and WordPress’s locale match;
- the locale is exactly
pl_PLorde_DE; - the page is a
cookie-statementdocument; - its region is
eu; - the expected
document_elementsstructure exists; - the first heading is exactly the broken English state
Introduction; _x()executed after language selection now returns a real translated value.
If the document is already localized correctly, the plugin self-noops. That matters for future updates: if upstream fixes the timing, this workaround should not force a rebuild of an already correct document.
After re-including the document file, the plugin applies the cmplz_document_elements filter again, mirroring the normal Complianz path. It then verifies the rebuilt state. If the result is unexpected or an exception occurs, it restores the exact request-local original elements and stops.
There are no database writes.
Full production-tested MU-plugin 0.1.0 source
<?php
/**
* Plugin Name: byBrzoza Complianz + Polylang Legal i18n Fix
* Description: Narrow compatibility fix for Complianz legal-document translations when Polylang uses “The language is set from content”.
* Version: 0.1.0
* Author: byBrzoza
*/
defined( 'ABSPATH' ) || exit;
/**
* Rebuild the synchronized EU Cookie Policy after Polylang has selected the
* language from the current page content.
*
* Why this exists:
* - Polylang's content-based language mode defines the language on `wp`
* (priority 5).
* - Complianz 7.5.4 loads/translates cookie-policy-eu.php earlier, on `init`.
* - The resulting document_elements can therefore be frozen in the default
* locale (EN), while later callback-driven sections render in PL/DE.
*
* Safety / update behavior:
* - front end only;
* - only synchronized EU Cookie Policy pages;
* - only pl_PL and de_DE;
* - only if the current main heading is exactly the broken EN value
* "Introduction";
* - self-noops if Complianz/upstream already renders the correct translation;
* - guards Complianz version and internal structure and does nothing on an
* unexpected state;
* - no database writes and no vendor-file modifications.
*/
function bybrzoza_cmplz_polylang_relocalize_cookie_policy(): void {
static $done = false;
if ( $done ) {
return;
}
$done = true;
if ( is_admin() || wp_doing_ajax() || wp_doing_cron() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
return;
}
if ( ! function_exists( 'pll_current_language' ) || ! class_exists( 'COMPLIANZ' ) ) {
return;
}
if ( ! defined( 'CMPLZ_VERSION' ) || ! defined( 'CMPLZ_PATH' ) ) {
return;
}
if ( ! isset( COMPLIANZ::$config, COMPLIANZ::$document ) ) {
return;
}
// Tested against Complianz 7.5.4. Allow 7.x updates only while the guarded
// internal structure still matches; stop automatically at the next major.
$cmplz_version = strtok( (string) CMPLZ_VERSION, '#' );
if ( version_compare( $cmplz_version, '7.5.4', '<' ) || version_compare( $cmplz_version, '8.0.0', '>=' ) ) {
return;
}
if ( ! is_page() ) {
return;
}
global $post;
if ( ! ( $post instanceof WP_Post ) ) {
return;
}
// Ensure Polylang and WordPress agree on the active locale at this point.
$pll_locale = pll_current_language( 'locale' );
$wp_locale = get_locale();
if ( ! is_string( $pll_locale ) || $pll_locale === '' || $pll_locale !== $wp_locale ) {
return;
}
if ( ! in_array( $wp_locale, array( 'pl_PL', 'de_DE' ), true ) ) {
return;
}
if ( ! method_exists( COMPLIANZ::$document, 'get_document_data' ) ) {
return;
}
$document_data = COMPLIANZ::$document->get_document_data( $post->ID );
if ( ! is_array( $document_data ) || ( $document_data['type'] ?? '' ) !== 'cookie-statement' ) {
return;
}
$region = $document_data['region'] ?? false;
if ( ! $region && function_exists( 'cmplz_get_region_from_legacy_type' ) ) {
$region = cmplz_get_region_from_legacy_type( 'cookie-statement' );
}
if ( $region !== 'eu' ) {
return;
}
$pages = COMPLIANZ::$config->pages;
if ( ! isset( $pages['eu']['cookie-statement']['document_elements'] ) || ! is_array( $pages['eu']['cookie-statement']['document_elements'] ) ) {
return;
}
$original_elements = $pages['eu']['cookie-statement']['document_elements'];
$current_intro = bybrzoza_cmplz_find_first_document_title( $original_elements );
$expected_intro = _x( 'Introduction', 'Legal document cookie policy:paragraph title', 'complianz-gdpr' );
// Missing/failed translation: fail closed rather than rewriting anything.
if ( ! is_string( $expected_intro ) || $expected_intro === '' || $expected_intro === 'Introduction' ) {
return;
}
// Upstream/another integration already fixed it: no-op.
if ( $current_intro === $expected_intro ) {
return;
}
// Do not overwrite a custom/unknown document state.
if ( $current_intro !== 'Introduction' ) {
return;
}
$document_file = trailingslashit( CMPLZ_PATH ) . 'config/documents/cookie-policy-eu.php';
if ( ! is_readable( $document_file ) ) {
return;
}
$reload = function ( string $file ): bool {
include $file;
if ( ! isset( $this->pages['eu']['cookie-statement']['document_elements'] ) || ! is_array( $this->pages['eu']['cookie-statement']['document_elements'] ) ) {
return false;
}
// Mirror Complianz::load_documents() so integrations using this official
// filter are preserved on the rebuilt element set.
$this->pages['eu']['cookie-statement']['document_elements'] = apply_filters(
'cmplz_document_elements',
$this->pages['eu']['cookie-statement']['document_elements'],
'eu',
'cookie-statement',
$this->fields
);
return true;
};
$restore = function ( array $elements ): void {
$this->pages['eu']['cookie-statement']['document_elements'] = $elements;
};
try {
$loaded = $reload->call( COMPLIANZ::$config, $document_file );
if ( ! $loaded ) {
$restore->call( COMPLIANZ::$config, $original_elements );
return;
}
$pages_after = COMPLIANZ::$config->pages;
$new_elements = $pages_after['eu']['cookie-statement']['document_elements'] ?? array();
$new_intro = is_array( $new_elements ) ? bybrzoza_cmplz_find_first_document_title( $new_elements ) : null;
if ( $new_intro !== $expected_intro ) {
$restore->call( COMPLIANZ::$config, $original_elements );
}
} catch ( Throwable $e ) {
// Front-end availability wins over the workaround. Restore the exact
// request-local original state and fail closed without exposing details.
$restore->call( COMPLIANZ::$config, $original_elements );
}
}
/**
* Return the first non-empty document title.
*
* @param array $elements Complianz document elements.
* @return string|null
*/
function bybrzoza_cmplz_find_first_document_title( array $elements ): ?string {
foreach ( $elements as $element ) {
if ( is_array( $element ) && isset( $element['title'] ) && is_string( $element['title'] ) && $element['title'] !== '' ) {
return $element['title'];
}
}
return null;
}
// In Polylang's "language from content" mode the locale is defined on `wp`
// at priority 5. Running later avoids changing the site's URL architecture.
add_action( 'wp', 'bybrzoza_cmplz_polylang_relocalize_cookie_policy', 20 );
The attachment included with the publication package contains the exact production file and README. I kept the bybrzoza_ prefix intentionally because that is the exact source that passed the production PL/DE/EN test; renaming functions would create a slightly different variant from the one actually validated.
Installation, step by step
I treated this as a normal production change, not as a snippet to paste experimentally.
- Backup. Make sure you have a current files/database backup or a recently verified restore point.
- MU-plugin directory. Check whether
wp-content/mu-plugins/exists and create it if necessary. - Upload one file. Place
bybrzoza-complianz-polylang-legal-i18n-fix.phpdirectly in that directory. - Do not edit Complianz or Polylang. The workaround requires no vendor-file changes.
- Purge the active page cache. This purge is only to remove old generated HTML after the code change; cache was not the root cause.
- Check PL. The Polish Cookie Policy should have both a Polish page context and a Polish main body.
- Check DE. Verify the same for German.
- Check the EN regression. The English base
/cookie-policy-eu/must stay English. - Check the banner link and Polylang relation. The fix should not change routing or translation relationships.
My final production smoke test returned:
PL: PASS
DE: PASS
EN regression: PASS
Why I did not change Site Language or the Polylang URL mode
You will find advice online suggesting that multilingual legal-document issues can be solved by changing the WordPress Site Language or by switching Polylang to language-prefixed URLs.
Those may be valid architectural choices on another site, but they are not neutral fixes for one document. Changing the way language is determined affects routing, existing URLs, SEO, canonical URLs, hreflang and the language switcher.
For this site, preserving The language is set from content was an explicit requirement, so I fixed the timing conflict rather than changing the entire multilingual architecture.
I also did not change WordPress Site Language just to force Polish or German policy text. English is intentionally the default language of this installation.
Rollback
Rollback is deliberately simple because the workaround neither modifies vendor files nor writes to the database.
- Delete:
wp-content/mu-plugins/bybrzoza-complianz-polylang-legal-i18n-fix.php
- Purge the active page cache.
That is all. There is no database rollback.
After a Complianz or Polylang update
A normal plugin update will not overwrite this MU-plugin, but that does not guarantee permanent compatibility with internal upstream structures.
After every Complianz or Polylang update I run a quick smoke test:
- PL Cookie Policy;
- DE Cookie Policy;
- EN regression;
- banner links;
- Polylang relations/language switching.
Version 0.1.0 deliberately runs only on Complianz >=7.5.4 and <8.0.0. On Complianz 8.x it does nothing. A major release is a signal for a new source audit, not a reason to remove the upper bound from version_compare() without testing.
When not to use this workaround
I would not install this MU-plugin if:
- you use a different Polylang language-selection mode;
- the problem is simply missing translation files;
- you do not use the generated/synchronized EU Cookie Policy;
- your problem is the cookie banner rather than the legal document;
- your symptom does not match the broken state described here;
- you are already on Complianz 8.x without a fresh code audit;
- current Complianz/Polylang already renders the policy correctly without the workaround.
This is not a universal “Complianz translation plugin”. It is a compatibility workaround for a specific execution-order problem in a specific configuration.
Upstream status on September 1, 2026
At the time of this article, the newest public Complianz release is 7.5.4, published on August 31, 2026. That is the same version on which this production defect was confirmed. Its public changelog does not mention a fix for Polylang The language is set from content or the legal-document init/wp timing problem.
Polylang’s newest public release is 3.8.7, published on August 17, 2026. The production case itself was diagnosed on 3.8.6, so I am not claiming that I personally reproduced the exact defect again on 3.8.7. What is still current is Polylang’s developer documentation: in content-based mode it continues to define the language on wp at priority 5 and warns plugin authors not to translate earlier.
Complianz’s current Polylang guide still describes the expected workflow where a linked legal-document page is published and the generated document is translated automatically. Another Complianz documentation page also warns that content-based URL/language settings are not the intended setup for multilingual legal documents. I kept the existing architecture anyway and treated the MU-plugin as a narrow compatibility workaround instead of migrating the whole site.
What I learned from this case
The easiest way to lose time here was to keep purging caches or looking for the entire Cookie Policy in Polylang String translations. The strongest signal was the unusual one: the main legal paragraphs were English, but some later-rendered sections were already Polish.
That shifted the investigation from “is the translation missing?” to “when is the translation being evaluated?”
The source audit confirmed the timing mismatch: Complianz prepared the main document_elements on init, while Polylang in content-based mode defined the language later on wp/5. A guarded rebuild on wp/20 preserved the existing URLs, kept Complianz synchronization enabled, and left the English base document untouched.
The most important part of the final fix was not just making it work. It was making it easy to remove, narrow enough not to interfere with unrelated pages, and able to safely do nothing when upstream behavior changes.
Sources and version status
The root-cause analysis used the exact production Complianz 7.5.4 files: gutenberg/block.php, documents/class-document.php, config/class-config.php, config/documents/cookie-policy-eu.php and complianz-gpdr.php. Upstream status was rechecked on September 1, 2026.
- Polylang Developers — When Polylang does load the language?
- Complianz — Translate legal documents to multiple languages with Polylang
- Complianz — Editing Legal Documents
- Complianz — How to translate legal documents to your own language?
- WordPress.org — Complianz plugin / changelog
- WordPress.org — Polylang plugin / changelog
Did this guide help?
If it saved you some time or frustration, you can buy me a coffee.

