Add Korean UI localization (#2895)

* Add Korean UI localization

Refreshes the Korean translation from #1544 against the current client locale surface and adds cross-platform locale detection coverage.\n\nCo-authored-by: JinHyeok Jeong <150323322+Kesta-bos@users.noreply.github.com>

* fix(i18n): preserve reviewed Korean status labels

Restore the wording corrections contributed in the original Korean localization work and lock them with exact-copy assertions.

Co-authored-by: JinHyeok Jeong <150323322+Kesta-bos@users.noreply.github.com>

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Co-authored-by: JinHyeok Jeong <150323322+Kesta-bos@users.noreply.github.com>
This commit is contained in:
Appcaster
2026-08-05 22:47:33 +02:00
committed by GitHub
co-authored by Mohamed Boudra JinHyeok Jeong
parent 94b57f34ad
commit 0728148657
15 changed files with 2424 additions and 22 deletions
+37 -18
View File
@@ -1,4 +1,4 @@
export type SupportedLocale = "ar" | "en" | "es" | "fr" | "ja" | "pt-BR" | "ru" | "zh-CN";
export type SupportedLocale = "ar" | "en" | "es" | "fr" | "ja" | "ko" | "pt-BR" | "ru" | "zh-CN";
export type AppLanguage = "system" | SupportedLocale;
export interface LanguageOption {
@@ -15,6 +15,7 @@ export const LANGUAGE_OPTIONS: LanguageOption[] = [
{ value: "es", labelKey: "settings.general.language.options.es" },
{ value: "fr", labelKey: "settings.general.language.options.fr" },
{ value: "ja", labelKey: "settings.general.language.options.ja" },
{ value: "ko", labelKey: "settings.general.language.options.ko" },
{ value: "pt-BR", labelKey: "settings.general.language.options.ptBR" },
{ value: "ru", labelKey: "settings.general.language.options.ru" },
{ value: "zh-CN", labelKey: "settings.general.language.options.zhCN" },
@@ -27,6 +28,7 @@ const SUPPORTED_LANGUAGES = new Set<AppLanguage>([
"es",
"fr",
"ja",
"ko",
"pt-BR",
"ru",
"zh-CN",
@@ -37,6 +39,7 @@ const LANGUAGE_NATIVE_NAMES: Record<SupportedLocale, string> = {
es: "Español",
fr: "Français",
ja: "日本語",
ko: "한국어",
"pt-BR": "Português brasileiro",
ru: "Русский",
"zh-CN": "简体中文",
@@ -48,6 +51,7 @@ const LANGUAGE_NAMES_BY_LOCALE: Record<SupportedLocale, Record<SupportedLocale,
es: "الإسبانية",
fr: "الفرنسية",
ja: "اليابانية",
ko: "الكورية",
"pt-BR": "البرتغالية البرازيلية",
ru: "الروسية",
"zh-CN": "الصينية المبسطة",
@@ -58,6 +62,7 @@ const LANGUAGE_NAMES_BY_LOCALE: Record<SupportedLocale, Record<SupportedLocale,
es: "Spanish",
fr: "French",
ja: "Japanese",
ko: "Korean",
"pt-BR": "Brazilian Portuguese",
ru: "Russian",
"zh-CN": "Simplified Chinese",
@@ -68,6 +73,7 @@ const LANGUAGE_NAMES_BY_LOCALE: Record<SupportedLocale, Record<SupportedLocale,
es: "español",
fr: "francés",
ja: "japonés",
ko: "coreano",
"pt-BR": "portugués brasileño",
ru: "ruso",
"zh-CN": "chino simplificado",
@@ -78,6 +84,7 @@ const LANGUAGE_NAMES_BY_LOCALE: Record<SupportedLocale, Record<SupportedLocale,
es: "espagnol",
fr: "français",
ja: "japonais",
ko: "coréen",
"pt-BR": "portugais brésilien",
ru: "russe",
"zh-CN": "chinois simplifié",
@@ -88,16 +95,29 @@ const LANGUAGE_NAMES_BY_LOCALE: Record<SupportedLocale, Record<SupportedLocale,
es: "スペイン語",
fr: "フランス語",
ja: "日本語",
ko: "韓国語",
"pt-BR": "ブラジルポルトガル語",
ru: "ロシア語",
"zh-CN": "簡体字中国語",
},
ko: {
ar: "아랍어",
en: "영어",
es: "스페인어",
fr: "프랑스어",
ja: "일본어",
ko: "한국어",
"pt-BR": "브라질 포르투갈어",
ru: "러시아어",
"zh-CN": "중국어 간체",
},
"pt-BR": {
ar: "árabe",
en: "inglês",
es: "espanhol",
fr: "francês",
ja: "japonês",
ko: "coreano",
"pt-BR": "Português brasileiro",
ru: "russo",
"zh-CN": "chinês simplificado",
@@ -108,6 +128,7 @@ const LANGUAGE_NAMES_BY_LOCALE: Record<SupportedLocale, Record<SupportedLocale,
es: "испанский",
fr: "французский",
ja: "японский",
ko: "корейский",
"pt-BR": "бразильский португальский",
ru: "русский",
"zh-CN": "упрощенный китайский",
@@ -118,12 +139,23 @@ const LANGUAGE_NAMES_BY_LOCALE: Record<SupportedLocale, Record<SupportedLocale,
es: "西班牙语",
fr: "法语",
ja: "日语",
ko: "韩语",
"pt-BR": "巴西葡萄牙语",
ru: "俄语",
"zh-CN": "简体中文",
},
};
const REGIONAL_LANGUAGE_LOCALES: Readonly<Record<string, SupportedLocale>> = {
ar: "ar",
en: "en",
es: "es",
fr: "fr",
ja: "ja",
ko: "ko",
ru: "ru",
};
export function parseAppLanguage(value: unknown): AppLanguage | null {
return typeof value === "string" && SUPPORTED_LANGUAGES.has(value as AppLanguage)
? (value as AppLanguage)
@@ -158,27 +190,14 @@ export function resolveSupportedLocale(
for (const locale of systemLocales) {
const normalized = locale.toLowerCase();
if (normalized === "ar" || normalized.startsWith("ar-")) {
return "ar";
}
if (normalized === "en" || normalized.startsWith("en-")) {
return "en";
}
if (normalized === "es" || normalized.startsWith("es-")) {
return "es";
}
if (normalized === "fr" || normalized.startsWith("fr-")) {
return "fr";
}
if (normalized === "ja" || normalized.startsWith("ja-")) {
return "ja";
const baseLanguage = normalized.split("-", 1)[0];
const regionalLocale = REGIONAL_LANGUAGE_LOCALES[baseLanguage];
if (regionalLocale) {
return regionalLocale;
}
if (normalized === "pt" || normalized === "pt-br") {
return "pt-BR";
}
if (normalized === "ru" || normalized.startsWith("ru-")) {
return "ru";
}
if (normalized === "zh" || normalized === "zh-cn" || normalized.startsWith("zh-hans")) {
return "zh-CN";
}