Skip to content

Always set the Language property on translation updates - #187

Open
victorcamnerin wants to merge 1 commit into
wp-cli:mainfrom
victorcamnerin:fix/undefined-language-property
Open

Always set the Language property on translation updates#187
victorcamnerin wants to merge 1 commit into
wp-cli:mainfrom
victorcamnerin:fix/undefined-language-property

Conversation

@victorcamnerin

@victorcamnerin victorcamnerin commented Aug 31, 2026

Copy link
Copy Markdown

Summary

CommandWithTranslation::update() logs every pack it is about to install with
"Updating '{$update->Language}' translation for {$update->Name} {$update->Version}...",
but Language is only assigned inside an isset( $translation->english_name ) guard. When
that lookup misses, the property is never set, so PHP 8 raises
Warning: Undefined property: stdClass::$Language and the locale prints as an empty string.

The two sides can legitimately disagree:

  • get_translation_updates() filters the update check through plugins_update_check_locales,
    so the transient offers a pack for every locale installed on the site.
  • get_all_languages() then re-queries translations_api() pinned to the installed
    extension version.

When that response doesn't list the locale, wp_list_filter() returns an empty array,
reset() yields false, and (object) false has no english_name.

This is easy to hit on a multi-locale site with a mixed set of extensions — we see it a few
times a day on a production site running five locales, from a nightly
wp language plugin update --all. It is cosmetic (it happens before $upgrader->upgrade(),
and the packs still install), but it produces a steady stream of PHP warnings in cron output
and error trackers. The guard has been unchanged since v2.0.0.

Falling back to the raw locale code keeps the line meaningful: Updating 'de_DE' translation for Akismet 4.0... rather than Updating '' translation for Akismet 4.0....

Testing

Adds a Behat scenario that mocks the translations API to return {"translations":[]} after
the update check has already offered a de_DE pack, then asserts STDERR is empty. It fails
on main and passes with this change.

I could not run composer phpcs or composer behat locally — my environment has no network
access to codeload.github.com, so dev dependencies would not install. Only php -l was
verified here; please let CI be the judge on the rest.

Summary by CodeRabbit

  • Bug Fixes
    • Translation updates now proceed when the translation service does not list the installed translation.
    • Improved reliability when updating plugin translations after a plugin upgrade.

`wp language <core|plugin|theme> update` reads `$update->Language` when
logging each pack it is about to install, but only assigned it when the
translations API returned an `english_name` for that locale.

The two sides can disagree: the update check offers packs for every
locale installed on the site, while `get_all_languages()` re-queries the
translations API pinned to the *installed* extension version. When that
lookup returns nothing, `reset()` yields false, the property is never
set, and PHP 8 raises "Warning: Undefined property: stdClass::$Language"
before printing the locale as an empty string.

Fall back to the raw locale code so the line stays meaningful and the
command stays warning-free.
@victorcamnerin
victorcamnerin requested a review from a team as a code owner August 31, 2026 07:54
@github-actions

Copy link
Copy Markdown
Contributor

Hello! 👋

Thanks for opening this pull request! Please check out our contributing guidelines. We appreciate you taking the initiative to contribute to this project.

Contributing isn't limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation.

Here are some useful Composer commands to get you started:

  • composer install: Install dependencies.
  • composer test: Run the full test suite.
  • composer phpcs: Check for code style violations.
  • composer phpcbf: Automatically fix code style violations.
  • composer phpunit: Run unit tests.
  • composer behat: Run behavior-driven tests.

To run a single Behat test, you can use the following command:

# Run all tests in a single file
composer behat features/some-feature.feature

# Run only a specific scenario (where 123 is the line number of the "Scenario:" title)
composer behat features/some-feature.feature:123

You can find a list of all available Behat steps in our handbook.

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b31b5a0-0be7-41fa-8cc1-43602fc94bfe

📥 Commits

Reviewing files that changed from the base of the PR and between 1929dfe and 5b1b51f.

📒 Files selected for processing (2)
  • features/language-plugin.feature
  • src/WP_CLI/CommandWithTranslation.php

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The translation update path now falls back to the existing language value when the API does not provide english_name. A Gherkin scenario verifies that Akismet's de_DE translation still updates when the API returns an empty list.

Changes

Translation update fallback

Layer / File(s) Summary
Language fallback and regression coverage
src/WP_CLI/CommandWithTranslation.php, features/language-plugin.feature
The update object always receives english_name or language. The new scenario verifies the update output and empty STDERR when the translations API returns no languages.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 5b1b5

The update now shows the locale code instead of emitting an undefined-property warning when translation metadata is unavailable. No actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: brianhenryie, ernilambar, janw-me

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main code change: always assigning the Language property on translation updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (1 skipped: 1 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Some tools did not complete. Review the errors below.

🔧 PHPStan (2.2.8)

Invalid configuration:
Unexpected item 'parameters › strictRules'.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added bug command:language Related to 'language' command command:language-plugin Related to 'language plugin' command scope:testing Related to testing labels Aug 31, 2026
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@swissspidy

Copy link
Copy Markdown
Member

This is easy to hit on a multi-locale site with a mixed set of extensions

Is it? I would appreciate some clear reproduction steps. Sounds like the translation API is returning some fields but not all? 🤔

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

Labels

bug command:language Related to 'language' command command:language-plugin Related to 'language plugin' command scope:testing Related to testing state:unconfirmed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants