GDS Content Translation
Polylang Pro helpers for block translation rules, link remapping, and editorial translation status
by Genero · github.com/generoi/gds-content-translation · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/generoi/gds-content-translation/archive/refs/heads/master.zipReadme
GDS Content Translation
WordPress plugin for Polylang Pro editorial workflows: translation status dashboard, one-click machine translation or source-language copy, block attribute translation rules, post ID sync, and internal link remapping.
Requires Polylang Pro with optional DeepL machine translation.
Requirements
- PHP >= 8.0
- WordPress >= 6.0
- Polylang Pro >= 3.3
Installation
composer require generoi/gds-content-translation
wp plugin activate gds-content-translation
For local path development (before the package is on Packagist):
{
"repositories": [
{
"type": "path",
"url": "web/app/plugins/gds-content-translation",
"options": { "symlink": true }
}
],
"require": {
"generoi/gds-content-translation": "@dev"
}
}
Features
Translation status admin
Polylang → Content translation status: overview of missing translations, proof-read flags, open block notes, and two ways to create a missing translation per language:
- AI translation — machine-translates the source post with Polylang Pro's configured service (DeepL). Only shown when machine translation is enabled and a service is configured.
- Copy original — creates the translation as a verbatim copy of the source (default) language, with no machine translation involved. Needs no service, so it also works when DeepL is unconfigured or out of quota.
Both run through Polylang Pro's translation pipeline, so blocks, internal links, post IDs, terms and metas are remapped to the target language either way, and both create the translation as a draft and open it in the editor.
Polylang → Content translation settings: choose which post types appear as tabs on the status screen. Unchecked types are hidden from the dashboard UI.
Programmatic exclusions still work via filter (always applied on top of saved settings):
add_filter('gds_content_translation_excluded_post_types', function (array $postTypes): array {
return array_merge($postTypes, ['shop_order']);
});
Polylang block integration
The plugin registers Polylang Pro hooks and exposes project-specific rules via WordPress filters. Themes (or site-specific mu-plugins) declare which custom block attributes are translatable strings, which hold post/attachment IDs, and which hold internal URLs.
Built-in defaults (no theme code required):
core/queryhandpicked posts (query.include) → sync post IDscore/buttonurlattribute +<a href>in block HTML → rewrite internal links
Configuring block rules (themes)
Add filters in your theme app/filters.php (or a small mu-plugin). The plugin merges your rules into Polylang’s native filters.
1. Translatable text attributes (DeepL / XLIFF)
Use for RichText and other string attributes stored in block JSON (not inner HTML).
Maps to Polylang’s pll_blocks_rules_for_attributes.
add_filter('gds_content_translation_pll_blocks_rules_for_attributes', function (array $rules): array {
return array_merge($rules, [
'my-theme/hero' => [
'heading' => true,
'intro' => true,
],
'my-theme/feature-list' => [
'items' => [
'*' => [
'title' => true,
'description' => true,
],
],
],
]);
});
2. Post / attachment ID sync
Use when a block stores a numeric ID that should point at the translated post or attachment after machine translation or content sync.
Maps to Polylang’s pll_sync_block_rules_for_attributes.
add_filter('gds_content_translation_pll_sync_block_rules_for_attributes', function (array $rules): array {
return array_merge($rules, [
'my-theme/post-teaser' => [
'post' => [
'postId' => true,
],
],
'my-theme/media-card' => [
'attachment' => [
'mediaId' => true,
],
],
]);
});
Types: post, term, attachment, wp_block.
3. Internal URL attributes
Use when a block stores a URL string (not an ID) in attributes — e.g. card blocks with a url field.
The plugin also rewrites <a href> inside block HTML (buttons, paragraphs). This filter adds attribute-based URLs.
add_filter('gds_content_translation_link_url_attributes_by_block', function (array $attributesByBlock): array {
return array_merge($attributesByBlock, [
'my-theme/numbered-card' => ['url'],
'my-theme/info-card' => ['url'],
]);
});
4. Post meta (custom fields)
Use for plain register_post_meta() / meta box values that are frontend text and should be machine-translated.
Maps to Polylang’s pll_post_metas_to_export. Keys are grouped by post type.
add_filter('gds_content_translation_pll_post_metas_to_export', function (array $rulesByPostType): array {
return array_merge($rulesByPostType, [
'person' => [
'person_job_title' => 1,
'person_department' => 1,
'person_sales_area' => 1,
],
]);
});
Use 1 for scalar string metas. Nested array metas use the same shape as Polylang’s export rules (sub-key => 1).
Keep locale-invariant metas (phone, email, attachment IDs) on pll_copy_post_metas sync only — do not list them here.
5. ACF fields
Polylang Pro handles ACF via field-level translation modes. Set defaults for fields that are not configured in the ACF UI:
add_filter('gds_content_translation_acf_field_translations', function (array $modesByFieldName): array {
return array_merge($modesByFieldName, [
'material_file' => 'copy_once', // attachment ID
'hero_intro' => 'translate', // frontend text
]);
});
Modes: translate, translate_once, copy_once, sync, ignore.
LOFS / GDS theme example
The lofs theme registers its gds/* blocks in app/filters.php:
add_filter('gds_content_translation_pll_blocks_rules_for_attributes', function (array $rules): array {
return array_merge($rules, [
'gds/timeline' => ['tag' => true, 'heading' => true],
'gds/check-list' => [
'items' => ['*' => ['title' => true, 'description' => true]],
],
// …
]);
});
add_filter('gds_content_translation_pll_sync_block_rules_for_attributes', function (array $rules): array {
return array_merge($rules, [
'gds/post-teaser' => ['post' => ['postId' => true]],
'gds/media-card' => ['attachment' => ['mediaId' => true]],
]);
});
add_filter('gds_content_translation_link_url_attributes_by_block', function (array $attributesByBlock): array {
return array_merge($attributesByBlock, [
'gds/numbered-card' => ['url'],
'gds/info-card' => ['url'],
]);
});
When rules run
| Feature | When | Persisted? |
|---|---|---|
| Text attributes | Machine translation, XLIFF import, sync | Yes — saved in post content |
| Post meta text | Machine translation, XLIFF import | Yes — saved in post meta |
| ACF text fields | Machine translation, XLIFF import | Yes — saved in post meta |
ID sync (postId, etc.) |
Machine translation, Polylang content sync | Yes |
| Link remapping (sync hook) | Machine translation, Polylang content sync | Yes |
| Link remapping (render hook) | Every frontend block render | No — runtime fallback for old content |
If a translation does not exist for a linked post, IDs become 0 (teaser hidden) and URLs are left unchanged.
Development
cd web/app/plugins/gds-content-translation
composer install
composer lint:fix
Testing
Tests run against a real WordPress install with Polylang active, provided by
wp-env
(requires Docker).
npx @wordpress/env start
npx @wordpress/env run tests-cli --env-cwd=wp-content/plugins/gds-content-translation vendor/bin/phpunit
npx @wordpress/env stop
tests/Unit covers the rule-merging filters, tests/Integration covers block
translation against configured en / fi languages. Tests that need Polylang
skip themselves when it is not installed.
License
MIT
Read the full README on GitHub →
Releases
These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.