Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 8, 2025

Fix incorrect language installation status when plugin's slug differs from text domain

Problem:
When a plugin or theme's directory slug is different from its text domain, wp language plugin list and wp language theme list incorrectly show all translations as "uninstalled" even when they are actually installed.

Root Cause:

  • wp_get_installed_translations('plugins') returns translations indexed by text domain
  • The code checks for installed languages using the plugin/theme slug instead of text domain
  • When slug ≠ text domain, the check fails and returns an empty array

Solution:

  • Modified get_installed_languages() in CommandWithTranslation.php to:
    • Read the plugin's Text Domain header from plugin data
    • Read the theme's TextDomain property from theme data
    • Use the text domain (not slug) to check installed translations
    • Fall back to slug if no text domain is specified
    • Add proper error checking for theme existence
  • Add test case for plugins with different slug and text domain
  • Add test case for themes with different slug and text domain
  • Improved tests to verify languages show as "installed" in list output
  • Run security checks (no issues found)

Changes Made:

  1. Modified get_installed_languages() in src/WP_CLI/CommandWithTranslation.php

    • Added logic to retrieve text domain from plugin/theme headers
    • Uses text domain instead of slug to check installed translations
    • Falls back to slug when text domain is empty
    • Added theme existence check for safety
  2. Added test scenario in features/language-plugin.feature

    • Tests plugins with text domain different from slug
    • Verifies that installed translations are correctly shown as "installed" in list output
    • Verifies is-installed command works correctly
  3. Added test scenario in features/language-theme.feature

    • Tests themes with text domain different from slug
    • Verifies that installed translations are correctly shown as "installed" in list output
    • Verifies is-installed command works correctly

The fix is minimal, surgical, and addresses the exact issue described in the problem statement.

Original prompt

This section details on the original issue you should resolve

<issue_title>Incorrect language installation status when plugin's slug is different from its text domain</issue_title>
<issue_description>## Bug Report

Describe the current, buggy behavior

When listing available languages for a plugin using the wp language plugin list <plugin> command, the status is always uninstalled if the plugin's slug ≠ plugin's text domain.

This is due to the wp_get_installed_translations() method used in

protected function get_installed_languages( $slug = 'default' ) {
$available = wp_get_installed_translations( $this->obj_type );
$available = ! empty( $available[ $slug ] ) ? array_keys( $available[ $slug ] ) : array();
$available[] = 'en_US';
return $available;
}

wp_get_installed_translations() returns an array keyed by the text domain. As a result, the subsequent check for available languages ($available = ! empty( $available[ $slug ] ) ? array_keys( $available[ $slug ] ) : array();) results in an empty array if the plugin's slug is different from its text domain.

get_installed_languages() is used by the list command:

public function list_( $args, $assoc_args ) {
$all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false );
if ( ! $all && empty( $args ) ) {
WP_CLI::error( 'Please specify one or more plugins, or use --all.' );
}
if ( $all ) {
$args = array_map( '\WP_CLI\Utils\get_plugin_name', array_keys( $this->get_all_plugins() ) );
if ( empty( $args ) ) {
WP_CLI::success( 'No plugins installed.' );
return;
}
}
$updates = $this->get_translation_updates();
$current_locale = get_locale();
$translations = array();
$plugins = new \WP_CLI\Fetchers\Plugin();
foreach ( $args as $plugin ) {
if ( ! $plugins->get( $plugin ) ) {
WP_CLI::warning( "Plugin '{$plugin}' not found." );
continue;
}
$installed_translations = $this->get_installed_languages( $plugin );
$available_translations = $this->get_all_languages( $plugin );
foreach ( $available_translations as $translation ) {
$translation['plugin'] = $plugin;
$translation['status'] = in_array( $translation['language'], $installed_translations, true ) ? 'installed' : 'uninstalled';
if ( $current_locale === $translation['language'] ) {
$translation['status'] = 'active';
}
$filter_args = array(
'language' => $translation['language'],
'type' => 'plugin',
'slug' => $plugin,
);
$update = wp_list_filter( $updates, $filter_args );
$translation['update'] = $update ? 'available' : 'none';
// Support features like --status=active.
foreach ( array_keys( $translation ) as $field ) {
if ( isset( $assoc_args[ $field ] ) && $assoc_args[ $field ] !== $translation[ $field ] ) {
continue 2;
}
}
$translations[] = $translation;
}
}
$formatter = $this->get_formatter( $assoc_args );
$formatter->display_items( $translations );
}

That's where the following check (against an empty $installed_translations array returned by get_installed_languages()) results in the uninstalled value:

$translation['status'] = in_array( $translation['language'], $installed_translations, true ) ? 'installed' : 'uninstalled';

A solution is to account for the discrepancy between plugin slugs and text domains in the get_installed_languages() method.</issue_description>

Comments on the Issue (you are @copilot in this section)

@swissspidy Can you give an example of a plugin where this is happening?

How would WP-CLI know what the correct text domain would be for the plugin?

WordPress plugins are really meant to have matching slugs and text domains. If they don't match, this kind of situation is to be expected. I assume WordPress itself also doesn't recognize the translations as being installed in that case.</comment_new>
<comment_new>@swissspidy

It does because it relies on the text domain, not the plugin slug.

For loading, yes, but for updates I don't think WP has any such logic. At least I can't find it.

Looking at the Text Domain header sounds like an interesting hardening step, but also a bit unusual because of that.</comment_new>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits November 8, 2025 16:23
…n slug

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix incorrect language installation status for plugins Fix language status detection for plugins/themes with text domain different from slug Nov 8, 2025
Copilot AI requested a review from swissspidy November 8, 2025 16:33
@swissspidy
Copy link
Member

@copilot you added a test for plugins, but can you add one for themes as well?

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 10, 2025

@copilot you added a test for plugins, but can you add one for themes as well?

Done! Added a test scenario for themes in commit a770faf. The test creates a theme with a different text domain and verifies that installed translations are correctly detected via the text domain rather than the slug.

@swissspidy swissspidy marked this pull request as ready for review November 10, 2025 16:12
@swissspidy swissspidy requested a review from a team as a code owner November 10, 2025 16:12
@codecov
Copy link

codecov bot commented Nov 10, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

This comment was marked as resolved.

@swissspidy

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
@swissspidy swissspidy merged commit 07c72a3 into main Jan 21, 2026
44 checks passed
@swissspidy swissspidy deleted the copilot/fix-language-installation-status branch January 21, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect language installation status when plugin's slug is different from its text domain

2 participants