Change default language to en-US and add US language (#6621)

This commit is contained in:
Anthony Stirling
2026-06-11 20:36:23 +01:00
committed by GitHub
parent 88adb7adad
commit 946c032fb5
41 changed files with 8578 additions and 188 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en-GB">
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<base href="%BASE_URL%" />
File diff suppressed because it is too large Load Diff
@@ -243,7 +243,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
const currentLanguage =
supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
supportedLanguages["en-GB"] ||
supportedLanguages["en-US"] ||
"English"; // Fallback if supportedLanguages lookup fails
// Hide the language selector if there's only one language option
+10 -9
View File
@@ -5,7 +5,8 @@ import TomlBackend from "@app/i18n/tomlBackend";
// Define supported languages (based on your existing translations)
export const supportedLanguages = {
"en-GB": "English",
"en-US": "English (US)",
"en-GB": "English (UK)",
"ar-AR": "العربية",
"az-AZ": "Azərbaycan Dili",
"bg-BG": "Български",
@@ -72,7 +73,7 @@ i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en-GB",
fallbackLng: "en-US",
supportedLngs: Object.keys(supportedLanguages),
load: "currentOnly",
nonExplicitSupportedLngs: false,
@@ -100,8 +101,8 @@ i18n
order: ["localStorage", "navigator", "htmlTag"],
caches: [], // Don't cache auto-detected language - only cache when user manually selects
convertDetectedLanguage: (lng: string) => {
// Map en and en-US to en-GB
if (lng === "en" || lng === "en-US") return "en-GB";
// Map bare en to en-US
if (lng === "en") return "en-US";
return lng;
},
},
@@ -154,7 +155,7 @@ export function normalizeLanguageCode(languageCode: string): string {
}
/**
* Convert language codes to underscore format (e.g., en-GB → en_GB)
* Convert language codes to underscore format (e.g., en-US → en_US)
* Used for backend API communication which expects underscore format
*/
export function toUnderscoreFormat(languageCode: string): string {
@@ -212,7 +213,7 @@ export function setUserLanguage(language: string): void {
* Updates the supported languages list dynamically based on config
* If configLanguages is null/empty, all languages remain available
* Otherwise, only the specified languages are enabled with the first valid
* option (preferring en-GB when present) used as the fallback language.
* option (preferring en-US when present) used as the fallback language.
*
* @param configLanguages - Optional array of language codes from server config (ui.languages)
* @param defaultLocale - Optional default language for new users (system.defaultLocale)
@@ -248,12 +249,12 @@ export function updateSupportedLanguages(
return;
}
// Determine fallback: prefer validDefault if in the list, then en-GB, then first valid language
// Determine fallback: prefer validDefault if in the list, then en-US, then first valid language
const fallback =
validDefault && validLanguages.includes(validDefault)
? validDefault
: validLanguages.includes("en-GB")
? "en-GB"
: validLanguages.includes("en-US")
? "en-US"
: validLanguages[0];
i18n.options.supportedLngs = validLanguages;
@@ -154,8 +154,8 @@ export async function mockAppApis(
email: "[email protected]",
roles: ["ROLE_USER"],
},
languages = ["en-GB"],
defaultLocale = "en-GB",
languages = ["en-US"],
defaultLocale = "en-US",
endpointsAvailability = {},
backendStatus = "UP",
} = opts;
@@ -6,9 +6,9 @@ import { parse } from "smol-toml";
const REPO_ROOT = path.join(__dirname, "../../../..");
const SRC_ROOT = path.join(__dirname, "../..");
const EN_GB_FILE = path.join(
const EN_US_FILE = path.join(
__dirname,
"../../../public/locales/en-GB/translation.toml",
"../../../public/locales/en-US/translation.toml",
);
const IGNORED_DIRS = new Set(["tests", "__mocks__"]);
@@ -174,14 +174,14 @@ const extractKeys = (file: string): FoundKey[] => {
describe("Missing translation coverage", () => {
test(
"fails if any en-GB translation key used in source is missing",
"fails if any en-US translation key used in source is missing",
{ timeout: 10000 },
() => {
expect(fs.existsSync(EN_GB_FILE)).toBe(true);
expect(fs.existsSync(EN_US_FILE)).toBe(true);
const localeContent = fs.readFileSync(EN_GB_FILE, "utf8");
const enGb = parse(localeContent);
const availableKeys = flattenKeys(enGb);
const localeContent = fs.readFileSync(EN_US_FILE, "utf8");
const enUs = parse(localeContent);
const availableKeys = flattenKeys(enUs);
const usedKeys = listSourceFiles()
.flatMap(extractKeys)
@@ -211,7 +211,7 @@ describe("Missing translation coverage", () => {
// Output errors in GitHub Annotations format so they appear tagged in the code in CI
for (const { key, fallback, file, line, column } of annotations) {
process.stderr.write(
`::error file=${file},line=${line},col=${column}::Missing en-GB translation for ${key} (${fallback})\n`,
`::error file=${file},line=${line},col=${column}::Missing en-US translation for ${key} (${fallback})\n`,
);
}
@@ -28,8 +28,8 @@ test.describe("OAuth/SSO login buttons", () => {
route.fulfill({
json: {
enableLogin: true,
languages: ["en-GB"],
defaultLocale: "en-GB",
languages: ["en-US"],
defaultLocale: "en-US",
oauth2: {
enabled: true,
providers: [
@@ -6,9 +6,9 @@ import { parse } from "smol-toml";
const REPO_ROOT = path.join(__dirname, "../../../..");
const SRC_ROOT = path.join(__dirname, "../..");
const EN_GB_FILE = path.join(
const EN_US_FILE = path.join(
__dirname,
"../../../public/locales/en-GB/translation.toml",
"../../../public/locales/en-US/translation.toml",
);
const IGNORED_DIRS = new Set(["tests", "__mocks__"]);
@@ -151,13 +151,13 @@ const isIgnored = (key: string): boolean => {
describe("Unused translation coverage", () => {
test(
"fails if any en-GB translation key has no source references",
"fails if any en-US translation key has no source references",
{ timeout: 30_000 },
() => {
expect(fs.existsSync(EN_GB_FILE)).toBe(true);
expect(fs.existsSync(EN_US_FILE)).toBe(true);
const enGb = parse(fs.readFileSync(EN_GB_FILE, "utf8"));
const availableKeys = Array.from(flattenKeys(enGb));
const enUs = parse(fs.readFileSync(EN_US_FILE, "utf8"));
const availableKeys = Array.from(flattenKeys(enUs));
expect(availableKeys.length).toBeGreaterThan(100); // sanity check
const sourceFiles = listSourceFiles();
@@ -184,20 +184,20 @@ describe("Unused translation coverage", () => {
});
const localeRelative = path
.relative(REPO_ROOT, EN_GB_FILE)
.relative(REPO_ROOT, EN_US_FILE)
.replace(/\\/g, "/");
// GitHub Annotations format so unused keys show up tagged on the
// translation file in CI.
for (const key of unused) {
process.stderr.write(
`::error file=${localeRelative}::Unused en-GB translation: ${key}\n`,
`::error file=${localeRelative}::Unused en-US translation: ${key}\n`,
);
}
expect(
unused,
`Found ${unused.length} unused en-GB translation key(s). ` +
`Found ${unused.length} unused en-US translation key(s). ` +
`Remove them from ${localeRelative}, or (if the usage is too ` +
`dynamic for the heuristic to spot) add to IGNORED_KEY_PATTERNS ` +
`in this test.`,
@@ -13,7 +13,16 @@ const languageDefinitions: LanguageDefinition[] = [
{
ocrCode: "eng",
displayName: "English",
browserCodes: ["en", "en-GB", "en-AU", "en-CA", "en-IE", "en-NZ", "en-ZA"],
browserCodes: [
"en",
"en-US",
"en-GB",
"en-AU",
"en-CA",
"en-IE",
"en-NZ",
"en-ZA",
],
},
// Spanish
@@ -908,12 +917,12 @@ languageDefinitions.forEach((lang) => {
* Maps a browser language code to an OCR language code
* Handles exact matches and similar language fallbacks
*
* @param browserLanguage - The browser language code (e.g., 'en-GB', 'fr-FR')
* @param browserLanguage - The browser language code (e.g., 'en-US', 'fr-FR')
* @returns OCR language code if found, null if no match
*
* @example
* mapBrowserLanguageToOcr('de-DE') // Returns 'deu'
* mapBrowserLanguageToOcr('en-GB') // Returns 'eng'
* mapBrowserLanguageToOcr('en-US') // Returns 'eng'
* mapBrowserLanguageToOcr('zh-CN') // Returns 'chi_sim'
*/
export function mapBrowserLanguageToOcr(
@@ -940,7 +949,7 @@ export function mapBrowserLanguageToOcr(
if (match) return match;
}
// Try base language code (e.g., 'en' from 'en-GB')
// Try base language code (e.g., 'en' from 'en-US')
const baseLanguage = normalizedInput.split("-")[0];
const baseMatch = browserToOcrMap.get(baseLanguage);
if (baseMatch) return baseMatch;
@@ -1002,7 +1011,7 @@ export function getBrowserLanguagesForOcr(ocrCode: string): string[] {
*
* @example
* getAutoOcrLanguage('de-DE') // Returns ['deu']
* getAutoOcrLanguage('en-GB') // Returns ['eng']
* getAutoOcrLanguage('en-US') // Returns ['eng']
* getAutoOcrLanguage('unknown') // Returns []
*/
export function getAutoOcrLanguage(currentLanguage: string): string[] {
@@ -625,7 +625,7 @@ export default function AdminGeneralSection() {
data={defaultLocaleOptions}
searchable
clearable
placeholder="en_GB"
placeholder="en_US"
comboboxProps={{ zIndex: Z_INDEX_CONFIG_MODAL }}
disabled={!loginEnabled}
/>
@@ -26,10 +26,10 @@ vi.mock("react-i18next", () => ({
// Mock i18n module to avoid initialization
vi.mock("@app/i18n", () => ({
updateSupportedLanguages: vi.fn(),
supportedLanguages: { "en-GB": "English" },
supportedLanguages: { "en-US": "English (US)" },
rtlLanguages: [],
default: {
language: "en-GB",
language: "en-US",
changeLanguage: vi.fn(),
options: {},
},
@@ -50,7 +50,7 @@ export function TrialStatusBanner() {
}
const trialEndDate = new Date(trialStatus.trialEnd).toLocaleDateString(
"en-GB",
"en-US",
{
month: "short",
day: "numeric",
+1 -1
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en-GB">
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />