Opened 5 weeks ago
Last modified 2 weeks ago
#65809 new defect (bug)
wp_get_l10n_php_file_data() reads the wrong revision date key from .l10n.php files
| Reported by: | bejignesh | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | 7.2 |
| Component: | I18N | Version: | 6.6 |
| Severity: | normal | Keywords: | has-patch |
| Cc: | Focuses: |
Description (last modified by )
wp_get_l10n_php_file_data() in wp-includes/l10n.php maps the PO headers it returns onto the keys a .l10n.php file stores them under:
'PO-Revision-Date' => 'po-revision-date',
No generated .l10n.php file uses that key. The revision date is written as translation-revision-date, so PO-Revision-Date always comes back empty.
tests/phpunit/data/languages/de_CH.l10n.php has the same wrong key, which is why the existing coverage in Tests_L10n::test_wp_get_installed_translations_for_core() passes. The fixture matches the code rather than a real file. tests/phpunit/data/l10n/plural-complex.php, added earlier in [57518], uses the correct key.
Why it matters
wp_get_installed_translations() falls back to this reader when a translation has no sibling .po file. That value is sent to api.wordpress.org to work out which language packs are out of date. With an empty revision date, a pack that is already installed and current gets offered again.
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 | empty string | 1 |
Fix
Read translation-revision-date, and correct the de_CH.l10n.php fixture to use it as well.
POT-Creation-Date maps to pot-creation-date, which no format writes, so it stays empty. Left alone here.
This came out of #65429 but is not the cause of that report, where the .po files were present. Filed separately since the defect stands on its own.
An earlier version of this description claimed the key had been renamed upstream. That was wrong, see comments below. The fixture was simply wrong from the start.
Change History (10)
This ticket was mentioned in PR #12844 on WordPress/wordpress-develop by @bejignesh.
5 weeks ago
#1
This ticket was mentioned in Slack in #glotpress by swissspidy. View the logs.
5 weeks ago
This ticket was mentioned in Slack in #meta-i18n by swissspidy. View the logs.
5 weeks ago
#4
@
5 weeks ago
- Keywords needs-patch added; has-patch has-unit-tests removed
- Milestone Awaiting Review → 7.2
As discussed in https://wordpress.slack.com/archives/C02RP4R9F/p1785880797649229 there was never any renaming, Claude hallucinated that.
#5
@
5 weeks ago
- Description modified (diff)
- Summary wp_get_l10n_php_file_data() reads a revision date key that GlotPress no longer writes → wp_get_l10n_php_file_data() reads the wrong revision date key from .l10n.php files
Thanks for catching it, you're right.
tests/phpunit/data/l10n/plural-complex.php has been in core since [57518] with the same x-generator as de_CH.l10n.php, GlotPress/4.0.0-beta.2, and it already uses translation-revision-date. Same generator version, correct header. And de_CH.l10n.php went in with the reader itself in [58061] and [58062], same ticket #60554, so it was matching the assumption in the code rather than any real export. It's the only file in the repo with po-revision-date.
So the fixture was wrong from the beginning. I inferred the rename from that one file's x-generator and wrote it up as fact without checking. My mistake, not yours.
I'll retitle, drop the rename claim from the description, and cut the patch down to what you described: the correct key in wp_get_l10n_php_file_data() and in de_CH.l10n.php, nothing else. test_wp_get_installed_translations_for_core() already asserts that value, so the coverage stays once the fixture is right.
@bejignesh commented on PR #12844:
5 weeks ago
#6
Agreed, thanks for catching it.
There was no rename. tests/phpunit/data/l10n/plural-complex.php has been in core since [57518] with the same x-generator as de_CH.l10n.php, GlotPress/4.0.0-beta.2, and it already uses translation-revision-date. And de_CH.l10n.php went in with the reader itself in [58061] and [58062], so it was matching the assumption in the code rather than any real export. It's the only file in the repo with po-revision-date. I inferred the rename from that one file and wrote it up as fact without checking.
@irozum, the rename claim in your review came from my description and is wrong. Flagging it so it does not travel further.
Reducing to the two changes you listed: the correct key in wp_get_l10n_php_file_data() and in de_CH.l10n.php. Dropping the fallback array, the new fixture and the new test. test_wp_get_installed_translations_for_core() already covers the value.
#7
@
5 weeks ago
- Keywords has-patch added; needs-patch removed
Patch reduced to the two key changes in aa7d30f: wp_get_l10n_php_file_data() and the de_CH.l10n.php fixture. No new test, test_wp_get_installed_translations_for_core() already covers the value.
@swissspidy commented on PR #12844:
2 weeks ago
#8
Hmm looks like WP-CLI i18n-command does use PO-Revision-Date when generating the files, and WP_Translation_File::transform will also just use the same header. :/
@bejignesh commented on PR #12844:
2 weeks ago
#9
@swissspidy Confirmed. WP_Translation_File::transform() writes po-revision-date as well, so the approved patch would have emptied the date for every converted file, and WP-CLI does the same.
Both spellings are real, so the reader now holds a list of names per header and takes the first present, translation-revision-date then po-revision-date. I put them in the header map rather than special casing after the loop. One test per producer, and the converted one generates its input through transform() at runtime so it cannot drift from what core writes.
Retitled, since "the correct key" does not fit when there are two.
@swissspidy commented on PR #12844:
2 weeks ago
#10
Oh, thanks. I wasn't implying we need to revert the change again, I first want to wrap my head around it. Not sure what approach is best.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
wp_get_l10n_php_file_data()maps the PO headers it returns onto the keys a.l10n.phpfile stores them under: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, soPO-Revision-Datealways comes back empty.### The key was renamed upstream
X-Generatortests/phpunit/data/languages/de_CH.l10n.php(already in core)po-revision-datetwentytwentyfive-nl_NL.l10n.phptranslation-revision-datesimple-history-es_ES.l10n.phptranslation-revision-dateSo the mapping was correct when the function was introduced in 6.6 and stopped matching when the name changed.
Project-Id-VersionandX-Generatorstill 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.phpvalue and the.poheader agree to the second:.l10n.phptranslation-revision-date.poPO-Revision-Date### Why it matters
wp_get_installed_translations()falls back to this reader when a translation has no sibling.pofile. 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
.pois on disk:wp_get_installed_translations()reportswp_update_themes().popresent2025-08-05 20:37:56+0000.poabsent''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.phpfixture it asserts against,de_CH.l10n.php, predates the rename and still usespo-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-dateand falling back topo-revision-date, so files written before and after the rename both resolve.POT-Creation-Datemaps topot-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
tests/phpunit/data/l10n/translation-revision-date.l10n.phpin the current GlotPress format. It is deliberately indata/l10n/rather thandata/languages/, because the latter isWP_LANG_DIRfor the suite andglob()s*.l10n.php, so a file added there would change whatwp_get_installed_translations()returns for other tests.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.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.phpcsclean.### Relationship to #65429
This came out of investigating #65429, but it is not the cause of that report. The reporters there confirmed their
.pofiles 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.## 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
.popresent, compared the packs against the API response timestamps, traced the rename through theX-Generatorvalues, confirmed the pre-existing test warning against a stashed baseline, and I take responsibility for the result.