mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add multi-language support for 20 locales
Add complete i18n infrastructure with 21 supported languages: English, Simplified Chinese, Traditional Chinese, Japanese, Korean, Spanish, French, Italian, Brazilian Portuguese, German, Dutch, Swedish, Russian, Polish, Ukrainian, Arabic (RTL), Turkish, Hindi, Vietnamese, Indonesian, and Thai. - I18nProvider context with three-tier locale detection (user preference > navigator.languages > instance default > English) - ~1500 translation keys per locale with TypeScript-enforced completeness - Dynamic code-splitting: only the active locale is loaded at runtime - Language selectors in footer, login page, settings, and mobile sidebar - Arabic RTL support with CSS logical properties across all components - Tool names, descriptions, and categories translated via i18n helpers - Public API endpoint GET /api/v1/config/locale for instance default - Multi-script font stack (CJK, Arabic, Devanagari, Thai, Cyrillic) - format() and plural() helpers for interpolation and pluralization - API error translation mapping (translateApiError) - 36 Playwright e2e tests verifying all 21 locales load correctly - 25 unit tests for format, plural, locale detection, and completeness - Updated translations.md docs and CLAUDE.md conventions
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1661
-97
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,45 @@
|
||||
import { en } from "./en.js";
|
||||
|
||||
export type { TranslationKeys } from "./en.js";
|
||||
export { en } from "./en.js";
|
||||
export { en };
|
||||
|
||||
export type SupportedLocale = {
|
||||
code: string;
|
||||
name: string;
|
||||
nativeName: string;
|
||||
dir: "ltr" | "rtl";
|
||||
};
|
||||
|
||||
export const SUPPORTED_LOCALES: SupportedLocale[] = [
|
||||
{ code: "en", name: "English", nativeName: "English", dir: "ltr" },
|
||||
{ code: "zh-CN", name: "Chinese (Simplified)", nativeName: "简体中文", dir: "ltr" },
|
||||
{ code: "zh-TW", name: "Chinese (Traditional)", nativeName: "繁體中文", dir: "ltr" },
|
||||
{ code: "ja", name: "Japanese", nativeName: "日本語", dir: "ltr" },
|
||||
{ code: "ko", name: "Korean", nativeName: "한국어", dir: "ltr" },
|
||||
{ code: "es", name: "Spanish", nativeName: "Español", dir: "ltr" },
|
||||
{ code: "fr", name: "French", nativeName: "Français", dir: "ltr" },
|
||||
{ code: "it", name: "Italian", nativeName: "Italiano", dir: "ltr" },
|
||||
{ code: "pt-BR", name: "Portuguese (Brazil)", nativeName: "Português (Brasil)", dir: "ltr" },
|
||||
{ code: "de", name: "German", nativeName: "Deutsch", dir: "ltr" },
|
||||
{ code: "nl", name: "Dutch", nativeName: "Nederlands", dir: "ltr" },
|
||||
{ code: "sv", name: "Swedish", nativeName: "Svenska", dir: "ltr" },
|
||||
{ code: "ru", name: "Russian", nativeName: "Русский", dir: "ltr" },
|
||||
{ code: "pl", name: "Polish", nativeName: "Polski", dir: "ltr" },
|
||||
{ code: "uk", name: "Ukrainian", nativeName: "Українська", dir: "ltr" },
|
||||
{ code: "ar", name: "Arabic", nativeName: "العربية", dir: "rtl" },
|
||||
{ code: "tr", name: "Turkish", nativeName: "Türkçe", dir: "ltr" },
|
||||
{ code: "hi", name: "Hindi", nativeName: "हिन्दी", dir: "ltr" },
|
||||
{ code: "vi", name: "Vietnamese", nativeName: "Tiếng Việt", dir: "ltr" },
|
||||
{ code: "id", name: "Indonesian", nativeName: "Bahasa Indonesia", dir: "ltr" },
|
||||
{ code: "th", name: "Thai", nativeName: "ไทย", dir: "ltr" },
|
||||
];
|
||||
|
||||
export async function loadTranslations(locale: string): Promise<import("./en.js").TranslationKeys> {
|
||||
if (locale === "en") return en;
|
||||
try {
|
||||
const mod = await import(`./${locale}.js`);
|
||||
return mod[locale] ?? mod.default ?? en;
|
||||
} catch {
|
||||
return en;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user