From becc18e3007e9812a7bb2dcfebefc6c19ad3f102 Mon Sep 17 00:00:00 2001 From: CaIon Date: Tue, 7 Jul 2026 14:02:09 +0800 Subject: [PATCH] fix(i18n): add language detection mapping for Chinese locales --- web/default/src/i18n/config.ts | 8 ++++++-- web/default/src/i18n/languages.ts | 26 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/web/default/src/i18n/config.ts b/web/default/src/i18n/config.ts index 7bbaba62..42ec7dc5 100644 --- a/web/default/src/i18n/config.ts +++ b/web/default/src/i18n/config.ts @@ -20,6 +20,7 @@ import i18n from 'i18next' import LanguageDetector from 'i18next-browser-languagedetector' import { initReactI18next } from 'react-i18next' +import { convertDetectedLanguage } from './languages' import en from './locales/en.json' import fr from './locales/fr.json' import ja from './locales/ja.json' @@ -44,8 +45,8 @@ i18n .init({ resources, fallbackLng: 'en', - supportedLngs: ['en', 'zhCN', 'fr', 'ru', 'ja', 'vi','zhTW'], - load: 'currentOnly', // Convert zh-CN -> zh + supportedLngs: ['en', 'zhCN', 'fr', 'ru', 'ja', 'vi', 'zhTW'], + load: 'currentOnly', nsSeparator: false, // Allow literal colons in keys (e.g., URLs, labels) debug: import.meta.env.DEV, interpolation: { @@ -54,6 +55,9 @@ i18n detection: { order: ['localStorage', 'navigator'], caches: ['localStorage'], + // Browsers report `zh-CN`/`zh-TW`/`zh`; map them onto our `zhCN`/`zhTW` + // codes (non-Chinese codes pass through for normal supportedLngs matching). + convertDetectedLanguage, }, }) diff --git a/web/default/src/i18n/languages.ts b/web/default/src/i18n/languages.ts index 873107ee..2c5a3ec4 100644 --- a/web/default/src/i18n/languages.ts +++ b/web/default/src/i18n/languages.ts @@ -33,7 +33,7 @@ export type InterfaceLanguageCode = export function normalizeInterfaceLanguage(value?: string | null): string { if (!value) return 'en' - var normalized = value.trim().replace(/_/g, '-').toLowerCase() + let normalized = value.trim().replaceAll('_', '-').toLowerCase() if (value === 'zh-TW' || value === 'zh-HK' || value === 'zh-MO' || value === 'zhTW') { normalized = 'zhTW' } @@ -46,6 +46,30 @@ export function normalizeInterfaceLanguage(value?: string | null): string { : 'en' } +/** + * Map a browser-detected locale onto the interface language codes this project + * uses with i18next (`zhCN` / `zhTW`). + * + * Browsers report standard BCP-47 tags (`zh-CN`, `zh-TW`, `zh-Hant`, `zh`, ...), + * but `supportedLngs`/resources use the non-standard camelCase codes, so without + * this mapping a Chinese browser would never match and fall back to English. + * Non-Chinese codes are returned unchanged so i18next's own `supportedLngs` + * matching still applies (e.g. `fr-FR` -> `fr`, `ja` -> `ja`). + */ +export function convertDetectedLanguage(value: string): string { + const lower = value.trim().replaceAll('_', '-').toLowerCase() + if (!lower.startsWith('zh')) return value + if ( + lower === 'zh-tw' || + lower === 'zh-hk' || + lower === 'zh-mo' || + lower.startsWith('zh-hant') + ) { + return 'zhTW' + } + return 'zhCN' +} + /** * Convert an interface language code (the values i18next uses, such as `zhCN` / * `zhTW`) into a valid BCP-47 locale tag that the `Intl.*` APIs accept.