feat: 擴展 i18n 支援 65 種語言 + 完整文件系統
🌐 國際化 (i18n) - 從 5 種語言擴展到 65 種語言支援 - 新增:歐洲語系 (de, fr, es, it, pt, ru, nl, pl, tr, uk, cs, sv, da, fi, no, el, hu, ro, bg, hr, sk, sl, lt, lv, et, is, ga, cy, mt, mk, sq, ca, eu, gl, sr) - 新增:中東語系 (ar, he, fa) - 新增:南亞語系 (hi, bn, ta, te, mr, gu, kn, ml, ne, si) - 新增:東南亞語系 (th, vi, id, ms, fil, my, km, lo) - 新增:非洲語系 (af, sw, am, zu) - 完整翻譯所有 UI 文字 📖 文件系統 - 重構 README.md 為入口型首頁 - 新增 docs/getting-started.md - 快速入門指南 - 新增 docs/docker.md - Docker 部署指南 - 新增 docs/environment-variables.md - 環境變數說明 - 新增 docs/url-id-and-storage.md - 儲存結構說明 - 新增 docs/advanced-usage.md - 進階使用指南 - 更新 docs/i18n.md - 65 種語言清單 ✅ 建構驗證 - TypeScript 編譯通過 - Docker 建構通過
This commit is contained in:
parent
53b83b425f
commit
5c68308c36
69 changed files with 7823 additions and 274 deletions
|
|
@ -1,10 +1,90 @@
|
|||
// Core languages
|
||||
import en from "../locales/en.json";
|
||||
import zhTW from "../locales/zh-TW.json";
|
||||
import zhCN from "../locales/zh-CN.json";
|
||||
import ja from "../locales/ja.json";
|
||||
import ko from "../locales/ko.json";
|
||||
|
||||
export type SupportedLocale = "en" | "zh-TW" | "zh-CN" | "ja" | "ko";
|
||||
// European languages
|
||||
import de from "../locales/de.json";
|
||||
import fr from "../locales/fr.json";
|
||||
import es from "../locales/es.json";
|
||||
import it from "../locales/it.json";
|
||||
import pt from "../locales/pt.json";
|
||||
import ru from "../locales/ru.json";
|
||||
import nl from "../locales/nl.json";
|
||||
import pl from "../locales/pl.json";
|
||||
import uk from "../locales/uk.json";
|
||||
import cs from "../locales/cs.json";
|
||||
import sv from "../locales/sv.json";
|
||||
import da from "../locales/da.json";
|
||||
import fi from "../locales/fi.json";
|
||||
import no from "../locales/no.json";
|
||||
import el from "../locales/el.json";
|
||||
import hu from "../locales/hu.json";
|
||||
import ro from "../locales/ro.json";
|
||||
import bg from "../locales/bg.json";
|
||||
import hr from "../locales/hr.json";
|
||||
import sk from "../locales/sk.json";
|
||||
import sl from "../locales/sl.json";
|
||||
import lt from "../locales/lt.json";
|
||||
import lv from "../locales/lv.json";
|
||||
import et from "../locales/et.json";
|
||||
import sr from "../locales/sr.json";
|
||||
import ca from "../locales/ca.json";
|
||||
import eu from "../locales/eu.json";
|
||||
import gl from "../locales/gl.json";
|
||||
import is from "../locales/is.json";
|
||||
import ga from "../locales/ga.json";
|
||||
import cy from "../locales/cy.json";
|
||||
import mt from "../locales/mt.json";
|
||||
import mk from "../locales/mk.json";
|
||||
import sq from "../locales/sq.json";
|
||||
|
||||
// Middle East & Central Asian languages
|
||||
import ar from "../locales/ar.json";
|
||||
import he from "../locales/he.json";
|
||||
import fa from "../locales/fa.json";
|
||||
import tr from "../locales/tr.json";
|
||||
|
||||
// South Asian languages
|
||||
import hi from "../locales/hi.json";
|
||||
import bn from "../locales/bn.json";
|
||||
import ta from "../locales/ta.json";
|
||||
import te from "../locales/te.json";
|
||||
import mr from "../locales/mr.json";
|
||||
import gu from "../locales/gu.json";
|
||||
import kn from "../locales/kn.json";
|
||||
import ml from "../locales/ml.json";
|
||||
import ne from "../locales/ne.json";
|
||||
import si from "../locales/si.json";
|
||||
|
||||
// Southeast Asian languages
|
||||
import th from "../locales/th.json";
|
||||
import vi from "../locales/vi.json";
|
||||
import id from "../locales/id.json";
|
||||
import ms from "../locales/ms.json";
|
||||
import fil from "../locales/fil.json";
|
||||
import my from "../locales/my.json";
|
||||
import km from "../locales/km.json";
|
||||
import lo from "../locales/lo.json";
|
||||
|
||||
// African languages
|
||||
import af from "../locales/af.json";
|
||||
import sw from "../locales/sw.json";
|
||||
import am from "../locales/am.json";
|
||||
import zu from "../locales/zu.json";
|
||||
|
||||
export type SupportedLocale =
|
||||
| "en" | "zh-TW" | "zh-CN" | "ja" | "ko"
|
||||
| "de" | "fr" | "es" | "it" | "pt" | "ru" | "nl" | "pl" | "uk"
|
||||
| "cs" | "sv" | "da" | "fi" | "no" | "el" | "hu" | "ro" | "bg"
|
||||
| "hr" | "sk" | "sl" | "lt" | "lv" | "et" | "sr" | "ca" | "eu"
|
||||
| "gl" | "is" | "ga" | "cy" | "mt" | "mk" | "sq"
|
||||
| "ar" | "he" | "fa" | "tr"
|
||||
| "hi" | "bn" | "ta" | "te" | "mr" | "gu" | "kn" | "ml" | "ne" | "si"
|
||||
| "th" | "vi" | "id" | "ms" | "fil" | "my" | "km" | "lo"
|
||||
| "af" | "sw" | "am" | "zu";
|
||||
|
||||
export interface LocaleConfig {
|
||||
code: SupportedLocale;
|
||||
|
|
@ -13,11 +93,82 @@ export interface LocaleConfig {
|
|||
}
|
||||
|
||||
export const supportedLocales: LocaleConfig[] = [
|
||||
// Primary languages
|
||||
{ code: "zh-TW", name: "Chinese (Traditional)", nativeName: "繁體中文" },
|
||||
{ code: "zh-CN", name: "Chinese (Simplified)", nativeName: "简体中文" },
|
||||
{ code: "en", name: "English", nativeName: "English" },
|
||||
{ code: "ja", name: "Japanese", nativeName: "日本語" },
|
||||
{ code: "ko", name: "Korean", nativeName: "한국어" },
|
||||
|
||||
// European languages
|
||||
{ code: "de", name: "German", nativeName: "Deutsch" },
|
||||
{ code: "fr", name: "French", nativeName: "Français" },
|
||||
{ code: "es", name: "Spanish", nativeName: "Español" },
|
||||
{ code: "it", name: "Italian", nativeName: "Italiano" },
|
||||
{ code: "pt", name: "Portuguese", nativeName: "Português" },
|
||||
{ code: "ru", name: "Russian", nativeName: "Русский" },
|
||||
{ code: "nl", name: "Dutch", nativeName: "Nederlands" },
|
||||
{ code: "pl", name: "Polish", nativeName: "Polski" },
|
||||
{ code: "uk", name: "Ukrainian", nativeName: "Українська" },
|
||||
{ code: "cs", name: "Czech", nativeName: "Čeština" },
|
||||
{ code: "sv", name: "Swedish", nativeName: "Svenska" },
|
||||
{ code: "da", name: "Danish", nativeName: "Dansk" },
|
||||
{ code: "fi", name: "Finnish", nativeName: "Suomi" },
|
||||
{ code: "no", name: "Norwegian", nativeName: "Norsk" },
|
||||
{ code: "el", name: "Greek", nativeName: "Ελληνικά" },
|
||||
{ code: "hu", name: "Hungarian", nativeName: "Magyar" },
|
||||
{ code: "ro", name: "Romanian", nativeName: "Română" },
|
||||
{ code: "bg", name: "Bulgarian", nativeName: "Български" },
|
||||
{ code: "hr", name: "Croatian", nativeName: "Hrvatski" },
|
||||
{ code: "sk", name: "Slovak", nativeName: "Slovenčina" },
|
||||
{ code: "sl", name: "Slovenian", nativeName: "Slovenščina" },
|
||||
{ code: "lt", name: "Lithuanian", nativeName: "Lietuvių" },
|
||||
{ code: "lv", name: "Latvian", nativeName: "Latviešu" },
|
||||
{ code: "et", name: "Estonian", nativeName: "Eesti" },
|
||||
{ code: "sr", name: "Serbian", nativeName: "Српски" },
|
||||
{ code: "ca", name: "Catalan", nativeName: "Català" },
|
||||
{ code: "eu", name: "Basque", nativeName: "Euskara" },
|
||||
{ code: "gl", name: "Galician", nativeName: "Galego" },
|
||||
{ code: "is", name: "Icelandic", nativeName: "Íslenska" },
|
||||
{ code: "ga", name: "Irish", nativeName: "Gaeilge" },
|
||||
{ code: "cy", name: "Welsh", nativeName: "Cymraeg" },
|
||||
{ code: "mt", name: "Maltese", nativeName: "Malti" },
|
||||
{ code: "mk", name: "Macedonian", nativeName: "Македонски" },
|
||||
{ code: "sq", name: "Albanian", nativeName: "Shqip" },
|
||||
|
||||
// Middle East & Central Asian languages
|
||||
{ code: "ar", name: "Arabic", nativeName: "العربية" },
|
||||
{ code: "he", name: "Hebrew", nativeName: "עברית" },
|
||||
{ code: "fa", name: "Persian", nativeName: "فارسی" },
|
||||
{ code: "tr", name: "Turkish", nativeName: "Türkçe" },
|
||||
|
||||
// South Asian languages
|
||||
{ code: "hi", name: "Hindi", nativeName: "हिन्दी" },
|
||||
{ code: "bn", name: "Bengali", nativeName: "বাংলা" },
|
||||
{ code: "ta", name: "Tamil", nativeName: "தமிழ்" },
|
||||
{ code: "te", name: "Telugu", nativeName: "తెలుగు" },
|
||||
{ code: "mr", name: "Marathi", nativeName: "मराठी" },
|
||||
{ code: "gu", name: "Gujarati", nativeName: "ગુજરાતી" },
|
||||
{ code: "kn", name: "Kannada", nativeName: "ಕನ್ನಡ" },
|
||||
{ code: "ml", name: "Malayalam", nativeName: "മലയാളം" },
|
||||
{ code: "ne", name: "Nepali", nativeName: "नेपाली" },
|
||||
{ code: "si", name: "Sinhala", nativeName: "සිංහල" },
|
||||
|
||||
// Southeast Asian languages
|
||||
{ code: "th", name: "Thai", nativeName: "ไทย" },
|
||||
{ code: "vi", name: "Vietnamese", nativeName: "Tiếng Việt" },
|
||||
{ code: "id", name: "Indonesian", nativeName: "Bahasa Indonesia" },
|
||||
{ code: "ms", name: "Malay", nativeName: "Bahasa Melayu" },
|
||||
{ code: "fil", name: "Filipino", nativeName: "Filipino" },
|
||||
{ code: "my", name: "Burmese", nativeName: "မြန်မာ" },
|
||||
{ code: "km", name: "Khmer", nativeName: "ខ្មែរ" },
|
||||
{ code: "lo", name: "Lao", nativeName: "ລາວ" },
|
||||
|
||||
// African languages
|
||||
{ code: "af", name: "Afrikaans", nativeName: "Afrikaans" },
|
||||
{ code: "sw", name: "Swahili", nativeName: "Kiswahili" },
|
||||
{ code: "am", name: "Amharic", nativeName: "አማርኛ" },
|
||||
{ code: "zu", name: "Zulu", nativeName: "isiZulu" },
|
||||
];
|
||||
|
||||
// Default to zh-TW, fallback to en
|
||||
|
|
@ -30,11 +181,82 @@ export type TranslationKey = keyof TranslationData;
|
|||
export type NestedTranslationKey<T extends TranslationKey> = keyof TranslationData[T];
|
||||
|
||||
const translations: Record<SupportedLocale, TranslationData> = {
|
||||
// Core languages
|
||||
en,
|
||||
"zh-TW": zhTW as TranslationData,
|
||||
"zh-CN": zhCN as TranslationData,
|
||||
ja: ja as TranslationData,
|
||||
ko: ko as TranslationData,
|
||||
|
||||
// European languages
|
||||
de: de as TranslationData,
|
||||
fr: fr as TranslationData,
|
||||
es: es as TranslationData,
|
||||
it: it as TranslationData,
|
||||
pt: pt as TranslationData,
|
||||
ru: ru as TranslationData,
|
||||
nl: nl as TranslationData,
|
||||
pl: pl as TranslationData,
|
||||
uk: uk as TranslationData,
|
||||
cs: cs as TranslationData,
|
||||
sv: sv as TranslationData,
|
||||
da: da as TranslationData,
|
||||
fi: fi as TranslationData,
|
||||
no: no as TranslationData,
|
||||
el: el as TranslationData,
|
||||
hu: hu as TranslationData,
|
||||
ro: ro as TranslationData,
|
||||
bg: bg as TranslationData,
|
||||
hr: hr as TranslationData,
|
||||
sk: sk as TranslationData,
|
||||
sl: sl as TranslationData,
|
||||
lt: lt as TranslationData,
|
||||
lv: lv as TranslationData,
|
||||
et: et as TranslationData,
|
||||
sr: sr as TranslationData,
|
||||
ca: ca as TranslationData,
|
||||
eu: eu as TranslationData,
|
||||
gl: gl as TranslationData,
|
||||
is: is as TranslationData,
|
||||
ga: ga as TranslationData,
|
||||
cy: cy as TranslationData,
|
||||
mt: mt as TranslationData,
|
||||
mk: mk as TranslationData,
|
||||
sq: sq as TranslationData,
|
||||
|
||||
// Middle East & Central Asian languages
|
||||
ar: ar as TranslationData,
|
||||
he: he as TranslationData,
|
||||
fa: fa as TranslationData,
|
||||
tr: tr as TranslationData,
|
||||
|
||||
// South Asian languages
|
||||
hi: hi as TranslationData,
|
||||
bn: bn as TranslationData,
|
||||
ta: ta as TranslationData,
|
||||
te: te as TranslationData,
|
||||
mr: mr as TranslationData,
|
||||
gu: gu as TranslationData,
|
||||
kn: kn as TranslationData,
|
||||
ml: ml as TranslationData,
|
||||
ne: ne as TranslationData,
|
||||
si: si as TranslationData,
|
||||
|
||||
// Southeast Asian languages
|
||||
th: th as TranslationData,
|
||||
vi: vi as TranslationData,
|
||||
id: id as TranslationData,
|
||||
ms: ms as TranslationData,
|
||||
fil: fil as TranslationData,
|
||||
my: my as TranslationData,
|
||||
km: km as TranslationData,
|
||||
lo: lo as TranslationData,
|
||||
|
||||
// African languages
|
||||
af: af as TranslationData,
|
||||
sw: sw as TranslationData,
|
||||
am: am as TranslationData,
|
||||
zu: zu as TranslationData,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -51,16 +273,14 @@ export function t<T extends TranslationKey>(
|
|||
key: NestedTranslationKey<T>,
|
||||
params?: Record<string, string | number>,
|
||||
): string {
|
||||
const translation = translations[locale]?.[category]?.[key as keyof TranslationData[T]];
|
||||
const localeData = translations[locale];
|
||||
const translation = localeData?.[category]?.[key as keyof TranslationData[T]];
|
||||
|
||||
if (typeof translation !== "string") {
|
||||
// Fallback to English
|
||||
const fallback =
|
||||
translations[defaultLocale]?.[category]?.[key as keyof TranslationData[T]];
|
||||
translations[fallbackLocale]?.[category]?.[key as keyof TranslationData[T]];
|
||||
if (typeof fallback !== "string") {
|
||||
// Log missing translation in development
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Missing translation: ${category}.${String(key)}`);
|
||||
return `${category}.${String(key)}`;
|
||||
}
|
||||
return interpolate(fallback, params);
|
||||
|
|
@ -105,9 +325,12 @@ export function detectLocale(acceptLanguage?: string): SupportedLocale {
|
|||
|
||||
// Try to find a match
|
||||
for (const lang of languages) {
|
||||
// Direct match
|
||||
if (isValidLocale(lang.code)) {
|
||||
return lang.code as SupportedLocale;
|
||||
// Direct match (case-insensitive)
|
||||
const directMatch = supportedLocales.find(
|
||||
(l) => l.code.toLowerCase() === lang.code
|
||||
);
|
||||
if (directMatch) {
|
||||
return directMatch.code;
|
||||
}
|
||||
|
||||
// Check for language variants (e.g., zh-tw, zh-hant)
|
||||
|
|
@ -120,11 +343,16 @@ export function detectLocale(acceptLanguage?: string): SupportedLocale {
|
|||
}
|
||||
}
|
||||
|
||||
// Check base language
|
||||
// Check base language (e.g., "en-US" -> "en")
|
||||
const baseCode = lang.code.split("-")[0];
|
||||
if (baseCode === "ja") return "ja";
|
||||
if (baseCode === "ko") return "ko";
|
||||
if (baseCode === "en") return "en";
|
||||
if (baseCode) {
|
||||
const baseMatch = supportedLocales.find(
|
||||
(l) => l.code.toLowerCase() === baseCode || l.code.toLowerCase().startsWith(baseCode + "-")
|
||||
);
|
||||
if (baseMatch) {
|
||||
return baseMatch.code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultLocale;
|
||||
|
|
@ -173,7 +401,7 @@ export type Translator = ReturnType<typeof createTranslator>;
|
|||
|
||||
// Export all translations for client-side use
|
||||
export function getTranslations(locale: SupportedLocale): TranslationData {
|
||||
return translations[locale] ?? translations[defaultLocale];
|
||||
return translations[locale] ?? translations[fallbackLocale];
|
||||
}
|
||||
|
||||
// Export locale codes as array
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue