Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5116bec
Initial plan
Copilot Mar 6, 2026
97cecf1
Fix null TypeError and empty section when no languages are available
Copilot Mar 6, 2026
2deefe2
Keep plugin UI visible when no languages available; disable controls …
Copilot Mar 7, 2026
1475519
Revert: restore native dropdown fallback when no languages are available
Copilot Mar 7, 2026
ee1e636
Merge branch 'main' into copilot/fix-empty-language-section
swissspidy Mar 10, 2026
707778c
Rename plugin
swissspidy Mar 10, 2026
6ceed0b
Revert "Revert: restore native dropdown fallback when no languages ar…
swissspidy Mar 10, 2026
a32122b
Update test and wording
swissspidy Mar 10, 2026
f3b7053
Add placeholder text to empty dropdown
swissspidy Mar 10, 2026
c215e16
Merge branch 'main' into copilot/fix-empty-language-section
swissspidy Mar 10, 2026
704ac79
Lint fix
swissspidy Mar 10, 2026
4d60c89
Remove prepare step
swissspidy Mar 10, 2026
a16fc90
Fix unit test
swissspidy Mar 10, 2026
89648da
Capital plugin name
swissspidy Mar 10, 2026
3820476
Actually add plugin to env
swissspidy Mar 10, 2026
f925dcf
Fix matcher
swissspidy Mar 10, 2026
22218eb
Fix failing e2e test: create no-languages plugin and fix test setup
Copilot Mar 10, 2026
5c3bb6b
Combine disallow-file-mods and no-languages test plugins into one
Copilot Mar 10, 2026
b845d14
Fix strict mode violation: use precise CSS class selector for empty-m…
Copilot Mar 10, 2026
4f0832d
Install languages pre-tests again
swissspidy Mar 11, 2026
de17268
Apply suggestion from @swissspidy
swissspidy Mar 11, 2026
3ac5fc7
Ensure plugin is deactivated
swissspidy Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,9 @@ jobs:
chmod -R 767 ./ # TODO: Possibly integrate in wp-env
npm run wp-env start

# Ensure the wp-content/languages folder exists and is writable.
# See https://github.com/WordPress/gutenberg/issues/22515
# and https://github.com/WordPress/gutenberg/tree/trunk/packages/env#installing-a-plugin-or-theme-on-the-development-instance
- name: Prepare tests
run: |
WP_ENV_DIR=$(npm run wp-env install-path --silent 2>&1 | head -1)
cd $WP_ENV_DIR
mkdir -p tests-WordPress/wp-content/languages tests-WordPress/wp-content/upgrade
chmod -R 767 tests-WordPress/wp-content/languages tests-WordPress/wp-content/upgrade
docker compose run --rm -u $(id -u) -e HOME=/tmp tests-cli wp language core install de_CH de_DE es_ES fr_FR it_IT
cd -
npm run wp-env run tests-cli wp language core install de_CH de_DE es_ES fr_FR it_IT

- name: Run tests
run: npm run test:e2e
Expand Down
1 change: 1 addition & 0 deletions .wp-env.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
".",
"./tests/e2e/plugins/custom-internationalized-plugin",
"./tests/e2e/plugins/merge-translations",
"./tests/e2e/plugins/no-languages",
"./tests/e2e/plugins/no-merge-translations"
]
}
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@testing-library/user-event": "^14.6.1",
"@types/expect-puppeteer": "^5.0.6",
"@types/jest-environment-puppeteer": "^5.0.6",
"@wordpress/e2e-test-utils-playwright": "^1.41.0",
"@wordpress/env": "^11.1.0",
"@wordpress/scripts": "^31.0.0",
"@wp-now/wp-now": "^0.1.74",
Expand Down
9 changes: 8 additions & 1 deletion src/components/ActiveLocales.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface ActiveLocalesProps {
showOptionSiteDefault?: boolean;
setLanguages: ( cb: ( languages: Language[] ) => Language[] ) => void;
setSelectedLanguage: ( language: Language ) => void;
hasAnyLanguages: boolean;
}

export function ActiveLocales( {
Expand All @@ -21,6 +22,7 @@ export function ActiveLocales( {
showOptionSiteDefault = false,
selectedLanguage,
setSelectedLanguage,
hasAnyLanguages,
}: ActiveLocalesProps ) {
Comment on lines 10 to 26
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

hasAnyLanguages was added as a required prop on ActiveLocalesProps, but there are existing call sites that don't provide it (e.g. src/components/test/ActiveLocales.tsx). This will break TypeScript builds/unit tests. Either update all ActiveLocales usages to pass the prop, or make hasAnyLanguages optional with a sensible default inside ActiveLocales.

Copilot uses AI. Check for mistakes.
const listRef = useRef< HTMLUListElement >();

Expand Down Expand Up @@ -184,7 +186,12 @@ export function ActiveLocales( {
<div className="active-locales wp-clearfix">
{ isEmpty && (
<div className="active-locales-empty-message">
{ __( 'Nothing set.', 'preferred-languages' ) }
{ hasAnyLanguages
? __( 'Nothing set.', 'preferred-languages' )
: __(
'No languages available.',
'preferred-languages'
) }
<br />
{ emptyMessage }
</div>
Expand Down
9 changes: 9 additions & 0 deletions src/components/InactiveLocales.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ function InactiveLocalesSelect( {
) }
</optgroup>
) }
{ ! hasItems && (
<option
key="unavailable"
value="unavailable"
lang="unavailable"
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The placeholder <option> uses lang="unavailable", which is not a valid BCP47 language tag and can confuse assistive tech / validation. For a non-language placeholder option, omit the lang attribute (or set it to an empty string) instead.

Suggested change
lang="unavailable"

Copilot uses AI. Check for mistakes.
>
{ __( 'No languages available.', 'preferred-languages' ) }
</option>
) }
</SelectControl>
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/PreferredLanguages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ function PreferredLanguages( props: PreferredLanguagesProps ) {
props.preferredLanguages[ 0 ]
);

const hasAnyLanguages = allLanguages.length > 0;

const inactiveLocales = allLanguages.filter(
( language ) =>
! preferredLanguages.find(
Expand Down Expand Up @@ -202,6 +204,7 @@ function PreferredLanguages( props: PreferredLanguagesProps ) {
showOptionSiteDefault={ showOptionSiteDefault }
selectedLanguage={ selectedLanguage }
setSelectedLanguage={ setSelectedLanguage }
hasAnyLanguages={ hasAnyLanguages }
/>
<InactiveLocales
languages={ inactiveLocales }
Expand Down
2 changes: 1 addition & 1 deletion src/components/test/PreferredLanguages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ describe( 'PreferredLanguages', () => {
screen.getByRole( 'option', { name: /Español/ } )
).toHaveAttribute( 'aria-selected', 'true' );

expect( dropdown ).not.toHaveValue();
expect( dropdown ).toHaveValue( 'unavailable' );
expect( dropdown ).toBeDisabled();
expect(
screen.getByRole( 'button', { name: /Add/ } )
Expand Down
11 changes: 0 additions & 11 deletions src/preferred-languages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,6 @@ declare global {
domReady( () => {
const props = window.PreferredLanguages;

// If there are no installed languages and no available translations.
if ( ! props.allLanguages.length ) {
// Settings -> General.
document.querySelector( '.site-preferred-languages-wrap' )?.remove();

// Network Settings.
document.querySelector( '.network-preferred-languages-wrap' )?.remove();

return;
}

const container = document.querySelector( '#preferred-languages-root' );

// Replace original language settings with the Preferred Languages UI.
Expand Down
13 changes: 13 additions & 0 deletions tests/e2e/plugins/no-languages/no-languages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php
/*
Plugin Name: No Languages
Plugin URI: https://wordpress.org/
Description: For testing purposes only. Removes all available languages and translations.
Version: 1.0.0
Text Domain: no-languages
Domain Path: languages/
*/

add_filter( 'file_mod_allowed', '__return_false' );
add_filter( 'get_available_languages', '__return_empty_array' );
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The no-languages test plugin claims to remove available languages and translations, but it only filters get_available_languages. The plugin UI builds allLanguages from both get_available_languages() and wp_get_available_translations() (see inc/functions.php), so translations will still populate the dropdown for admins and the "no languages" e2e suite may fail/flap. Update the plugin to also short-circuit available translations (e.g., via the translations_api filter / relevant transient pre-filters) so wp_get_available_translations() returns an empty array in tests.

Suggested change
add_filter( 'get_available_languages', '__return_empty_array' );
add_filter( 'get_available_languages', '__return_empty_array' );
add_filter(
'translations_api',
function ( $result, $action, $args ) {
if ( 'core' === $action ) {
// Return an empty translations set so wp_get_available_translations() yields an empty array.
return array(
'translations' => array(),
);
}
return $result;
},
10,
3
);

Copilot uses AI. Check for mistakes.
add_filter( 'wp_get_available_translations', '__return_empty_array' );
50 changes: 50 additions & 0 deletions tests/e2e/specs/settings.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
import { test, expect } from '@wordpress/e2e-test-utils-playwright';

test.describe( 'No Languages Available', () => {
test.beforeEach( async ( { requestUtils } ) => {
await requestUtils.activatePlugin( 'no-languages' );
} );

test.afterEach( async ( { requestUtils } ) => {
await requestUtils.deactivatePlugin( 'no-languages' );
} );

test( 'should still display the preferred languages UI with no selectable languages', async ( {
admin,
page,
} ) => {
const errors: string[] = [];
page.on( 'console', ( msg ) => {
if ( msg.type() === 'error' ) {
errors.push( msg.text() );
}
} );
Comment on lines +16 to +21
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The test collects console error messages into errors, but never asserts on it. This means the regression (e.g., the TypeError this PR aims to prevent) could still happen without failing the test. Add an assertion at the end of the test that errors is empty (and consider waiting for the UI to settle before asserting).

Copilot uses AI. Check for mistakes.

await admin.visitAdminPage( 'options-general.php' );

// The native WordPress language dropdown should be replaced.
await expect( page.locator( '#WPLANG' ) ).toBeHidden();

// The plugin's preferred-languages UI should still be visible.
await expect(
page.getByRole( 'listbox', { name: 'Language' } )
).toBeVisible();

// The inactive locales dropdown should be disabled since there are no languages.
await expect(
page.getByRole( 'combobox', { name: 'Inactive Locales' } )
).toHaveValue( 'unavailable' );

// The "Add to list" button should also be disabled.
await expect(
page.getByRole( 'button', { name: /Add to list/ } )
).toBeDisabled();

const introText = page.getByText(
'Choose languages for displaying WordPress in, in order of preference.'
);
const fallbackText = page.locator( '.active-locales-empty-message' );

await expect( introText ).toBeVisible();
await expect( fallbackText ).toBeVisible();
} );
} );

test.describe( 'Settings Page', () => {
test( 'should display the preferred languages form', async ( {
admin,
Expand Down
Loading