Skip to content

I18N: Read the revision date key that GlotPress writes into .l10n.php files - #12844

Open
jigneshbhavani wants to merge 1 commit into
WordPress:trunkfrom
jigneshbhavani:fix/l10n-php-revision-date-key
Open

I18N: Read the revision date key that GlotPress writes into .l10n.php files#12844
jigneshbhavani wants to merge 1 commit into
WordPress:trunkfrom
jigneshbhavani:fix/l10n-php-revision-date-key

Conversation

@jigneshbhavani

Copy link
Copy Markdown

wp_get_l10n_php_file_data() maps the PO headers it returns onto the keys a .l10n.php file stores them under:

$headers = array(
	'POT-Creation-Date'  => 'pot-creation-date',
	'PO-Revision-Date'   => 'po-revision-date',
	'Project-Id-Version' => 'project-id-version',
	'X-Generator'        => 'x-generator',
);

GlotPress writes the revision date under translation-revision-date. The key core looks for is absent from every language pack currently served from WordPress.org, so PO-Revision-Date always comes back empty.

The key was renamed upstream

File X-Generator Revision date key
tests/phpunit/data/languages/de_CH.l10n.php (already in core) GlotPress/4.0.0-beta.2 po-revision-date
twentytwentyfive-nl_NL.l10n.php GlotPress/4.0.3 translation-revision-date
simple-history-es_ES.l10n.php GlotPress/4.0.3 translation-revision-date

So the mapping was correct when the function was introduced in 6.6 and stopped matching when the name changed. Project-Id-Version and X-Generator still resolve, which is why the failure is silent: only the revision date is lost.

The value is intact under the other name. For the Simple History packs, the .l10n.php value and the .po header agree to the second:

Locale .l10n.php translation-revision-date .po PO-Revision-Date
es_ES 2026-06-26 08:56:45+0000 2026-06-26 08:56:45+0000
nl_NL 2026-06-09 10:37:20+0000 2026-06-09 10:37:20+0000
de_DE 2026-07-18 14:16:42+0000 2026-07-18 14:16:42+0000

Why it matters

wp_get_installed_translations() falls back to this reader when a translation has no sibling .po file. That value goes to api.wordpress.org to determine which language packs are out of date. An empty revision date means a pack that is already installed and current gets offered again on every check.

Measured on trunk with the Twenty Twenty-Five nl_NL pack, freshly installed, changing nothing but whether the .po is on disk:

State wp_get_installed_translations() reports Offers after wp_update_themes()
.po present 2025-08-05 20:37:56+0000 0
.po absent '' 1

In the second row the installed pack is byte-identical to the one the API is serving.

Why the existing tests did not catch it

The only coverage is Tests_L10n::test_wp_get_installed_translations_for_core(), and the .l10n.php fixture it asserts against, de_CH.l10n.php, predates the rename and still uses po-revision-date. The assertion passes because the fixture matches the code, not because either matches a current language pack.

The change

Each PO header now maps to an ordered list of keys the file may use, preferring translation-revision-date and falling back to po-revision-date, so files written before and after the rename both resolve.

POT-Creation-Date maps to pot-creation-date, which appears in neither format. I have left it alone rather than guess at an intended source; it is worth a separate decision.

Testing

  • New fixture tests/phpunit/data/l10n/translation-revision-date.l10n.php in the current GlotPress format. It is deliberately in data/l10n/ rather than data/languages/, because the latter is WP_LANG_DIR for the suite and glob()s *.l10n.php, so a file added there would change what wp_get_installed_translations() returns for other tests.
  • New test_wp_get_l10n_php_file_data_reads_revision_date() covers both spellings. On trunk it fails with '2026-06-26 08:56:45+0000' expected, '' actual. With the change it passes, 4 assertions.
  • --group l10n: 267 tests, 808 assertions, 0 failures.
  • --group i18n: 268 tests, 813 assertions, 0 failures.
  • Both groups report one warning, Tests_Locale::test_get_weekday_undefined_index, a PHPUnit 10 deprecation. I confirmed it is identical with the changes stashed, so it is pre-existing and unrelated.
  • phpcs clean.

Relationship to #65429

This came out of investigating #65429, but it is not the cause of that report. The reporters there confirmed their .po files were present, which means this fallback never ran on their sites. #65429 remains open and unexplained. Filing separately because the defect stands on its own.

Trac ticket: https://core.trac.wordpress.org/ticket/65809

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Drafting the patch, the test and this description. I found the key mismatch by installing real language packs locally and comparing what shipped against what core read, measured the offer counts on the rendered update check with and without the .po present, compared the packs against the API response timestamps, traced the rename through the X-Generator values, confirmed the pre-existing test warning against a stashed baseline, and I take responsibility for the result.

… files.

wp_get_l10n_php_file_data() maps `PO-Revision-Date` onto `po-revision-date`,
but GlotPress writes that value under `translation-revision-date`. The key core
looks for is absent from every language pack currently served, so the function
returns an empty revision date.

The name changed upstream. The `de_CH.l10n.php` fixture already in core reports
`GlotPress/4.0.0-beta.2` and uses `po-revision-date`, while packs generated by
`GlotPress/4.0.3` use `translation-revision-date`. The mapping was correct when
the function was added in 6.6 and stopped matching later. `Project-Id-Version`
and `X-Generator` still resolve, which is why nothing looked broken.

This matters because wp_get_installed_translations() falls back to this reader
when a translation has no sibling `.po` file, and the result is sent to
api.wordpress.org to decide which language packs are stale. An empty revision
date means an installed, current pack is offered again on every check.

Accept both spellings, preferring the current one, so files written before and
after the rename resolve.

`POT-Creation-Date` maps to `pot-creation-date`, which appears in neither
format, and is left alone rather than guessed at.

The existing coverage did not catch this because the only `.l10n.php` fixture it
asserts against predates the rename. Add a fixture in the current format and a
test that fails on trunk with an empty string.

Props bejignesh.
Fixes #65809.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props bejignesh.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@irozum irozum left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — wp_get_l10n_php_file_data() was still only looking for po-revision-date, but GlotPress renamed that key to translation-revision-date a while back, so PO-Revision-Date silently comes back empty for any current language pack that lacks a sibling .po file, which then causes wp_get_installed_translations() to treat an up-to-date pack as stale on every update check. The fix (checking translation-revision-date first, falling back to po-revision-date) is correctly scoped — I confirmed wp_get_l10n_php_file_data() has a single caller in core (wp_get_installed_translations()), so there's no back-compat surface beyond this file.

I ran the l10n and i18n groups plus PHPStan — all clean, and the counts (267/268 tests, 808/813 assertions, 0 failures) match what's in the PR description. I also grepped for other references to po-revision-date/translation-revision-date in src/ and tests/ and didn't find anything else that would need updating in step with this.

One small thing worth a sentence in the description (not blocking): POT-Creation-Date is left mapped only to pot-creation-date, which per your own table doesn't appear in either fixture format, so it's presumably always empty too — you note this is a separate decision, which seems right, but it might be worth a quick follow-up ticket so it doesn't get lost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants