UX: Use English as language label where appropriate (#36762)

PR makes two changes: 

- replaces "English (US) with "English" everywhere
- in the language switcher dropdown, it replaces "English (UK)" with
"English" if that variant of English is the only one enabled

"English (US)" is needlessly geo-specific. Given it is the default
language, using "English" as the label is shorter and clearer. It still
differentiates from "English (UK)" when choosing the interface language
for users (or in admin UIs):

<img width="500" alt="CleanShot 2025-12-17 at 17 38 32@2x"
src="https://github.com/user-attachments/assets/abd7674c-2b72-4e5c-8543-050b7812c743"
/>

And the PR also strips `(UK)` from the language switcher list when
"English (UK)" is the only English variant enabled. This is a bit hacky,
but it lets us have cleaner UX (especially for countries like Canada,
Australia, where sites may opt for `en_GB` as their default) while
keeping the necessary technical separation between English and English
(UK) in admin and user preference screens.

---------

Co-authored-by: Natalie Tay <natalie.tay@gmail.com>
This commit is contained in:
Penar Musaraj
2025-12-18 13:10:23 -05:00
committed by GitHub
co-authored by Natalie Tay
parent 64f614c808
commit 3baf375056
11 changed files with 194 additions and 14 deletions
@@ -42,13 +42,62 @@ export default class LanguageSwitcher extends Component {
}
get content() {
return this.siteSettings.available_content_localization_locales.map(
const langs = this.siteSettings.available_content_localization_locales.map(
({ value }) => ({
name: this.languageNameLookup.getLanguageName(value),
value,
isActive: value === this.currentLocale,
})
);
// Cleanup "English" name when `en_GB` is the only English variant
if (!langs.some(({ value }) => value === "en")) {
const ukLang = langs.find((lang) => lang.value === "en_GB");
if (ukLang) {
ukLang.name = this.normalizeUKEnglish(ukLang.name);
}
}
// Cleanup "Português" name when `pt_BR` is the only variant
if (!langs.some(({ value }) => value === "pt")) {
const ptbrName = langs.find((lang) => lang.value === "pt_BR");
if (ptbrName) {
ptbrName.name = this.normalizeBRPortuguese(ptbrName.name);
}
}
return langs;
}
normalizeUKEnglish(text) {
// strip all variants of "English (region)"
let result = text.replace("(English (UK))", "");
result = result
.replace(/\s*\([^)]*\)/g, "")
.replace(/\s+/g, " ")
.trim();
// Add back "(English)"
if (text.includes("English") && !result.match(/^English/i)) {
result += " (English)";
}
return result;
}
normalizeBRPortuguese(text) {
let result = text.replace("(Português (BR))", "");
result = result
.replace(/\s*\([^)]*\)/g, "")
.replace(/\s+/g, " ")
.trim();
// Add back "(Português)"
if (text.includes("Português") && !result.match(/^Português/i)) {
result += " (Português)";
}
return result;
}
@action
@@ -0,0 +1,131 @@
import { render, triggerEvent } from "@ember/test-helpers";
import { module, test } from "qunit";
import LanguageSwitcher from "discourse/components/language-switcher";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import I18n from "discourse-i18n";
module("Integration | Component | <LanguageSwitcher />", function (hooks) {
setupRenderingTest(hooks);
async function open() {
await triggerEvent(".fk-d-menu__trigger", "click");
}
hooks.beforeEach(function () {
this.siteSettings.available_content_localization_locales = [
{ value: "en" },
{ value: "fr" },
{ value: "de" },
];
this.siteSettings.available_locales = [
{ value: "en", name: "English" },
{ value: "fr", name: "Français (French)" },
{ value: "de", name: "Deutsch (German)" },
];
});
test("renders the current language code", async function (assert) {
await render(<template><LanguageSwitcher /></template>);
assert
.dom(".language-switcher__locale")
.hasText(I18n.locale.split("_")[0].toUpperCase());
});
test("opens menu with available locales", async function (assert) {
await render(<template><LanguageSwitcher /></template>);
await open();
assert.dom("[data-menu-option-id='en']").exists();
assert.dom("[data-menu-option-id='fr']").exists();
assert.dom("[data-menu-option-id='de']").exists();
});
test("displays locale names from language lookup service", async function (assert) {
await render(<template><LanguageSwitcher /></template>);
await open();
assert.dom("[data-menu-option-id='en'] .btn").hasText("English");
assert.dom("[data-menu-option-id='fr'] .btn").hasText("Français (French)");
});
test("marks current locale as selected", async function (assert) {
await render(<template><LanguageSwitcher /></template>);
await open();
assert.dom(`[data-menu-option-id='${I18n.locale}']`).hasClass("--selected");
});
test("normalizes en_GB when en is not available", async function (assert) {
this.siteSettings.available_content_localization_locales = [
{ value: "en_GB" },
{ value: "fr" },
];
this.siteSettings.available_locales = [
{ value: "en_GB", name: "English (UK)" },
{ value: "fr", name: "Français (French)" },
];
await render(<template><LanguageSwitcher /></template>);
await open();
assert.dom("[data-menu-option-id='en_GB'] .btn").hasText("English");
});
test("does not normalize en_GB when en is also available", async function (assert) {
this.siteSettings.available_content_localization_locales = [
{ value: "en" },
{ value: "en_GB" },
];
this.siteSettings.available_locales = [
{ value: "en", name: "English" },
{ value: "en_GB", name: "English (UK)" },
];
await render(<template><LanguageSwitcher /></template>);
await open();
assert.dom("[data-menu-option-id='en_GB'] .btn").hasText("English (UK)");
});
test("normalizes pt_BR when pt is not available", async function (assert) {
this.siteSettings.available_content_localization_locales = [
{ value: "pt_BR" },
{ value: "en" },
];
this.siteSettings.available_locales = [
{ value: "pt_BR", name: "Portuguese (Português (BR))" },
{ value: "en", name: "English" },
];
await render(<template><LanguageSwitcher /></template>);
await open();
assert
.dom("[data-menu-option-id='pt_BR'] .btn")
.hasText("Portuguese (Português)");
});
test("does not normalize pt_BR when pt is also available", async function (assert) {
this.siteSettings.available_content_localization_locales = [
{ value: "pt" },
{ value: "pt_BR" },
];
this.siteSettings.available_locales = [
{ value: "pt", name: "Português" },
{ value: "pt_BR", name: "Português (Português (BR))" },
];
await render(<template><LanguageSwitcher /></template>);
await open();
assert
.dom("[data-menu-option-id='pt_BR'] .btn")
.hasText("Português (Português (BR))");
});
});