mirror of
https://github.com/discourse/discourse.git
synced 2026-08-12 05:55:39 -05:00
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:
co-authored by
Natalie Tay
parent
64f614c808
commit
3baf375056
@@ -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))");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user