feat(i18n): add Vietnamese (vi) locale (#382)

Add a complete Vietnamese translation for the Management Center UI:

- src/i18n/locales/vi.json: full translation of all 1540 keys from
  en.json, with all interpolation placeholders preserved
- src/i18n/index.ts: register the vi resource bundle
- src/types/common.ts: extend the Language type with 'vi'
- src/utils/constants.ts: add 'vi' to LANGUAGE_ORDER and
  LANGUAGE_LABEL_KEYS
- src/utils/language.ts: auto-detect vi-* browser languages
- locales/{en,zh-CN,zh-TW,ru}.json: add the Tiếng Việt label so the
  new language appears in every language switcher

Tested by building with vite and deploying the bundle against a live
CLIProxyAPI v7.2.133 server; vi-* browsers resolve to the Vietnamese
UI and all strings render with correct placeholders.

Co-authored-by: nguyenha935 <nguyenha935@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
nguyenha935
2026-10-05 03:01:41 +08:00
committed by GitHub
co-authored by nguyenha935 Claude Opus 5
parent dfc1b1cfaa
commit fc9b8e2d40
9 changed files with 1739 additions and 6 deletions
+2
View File
@@ -8,6 +8,7 @@ import zhCN from './locales/zh-CN.json';
import zhTW from './locales/zh-TW.json';
import en from './locales/en.json';
import ru from './locales/ru.json';
import vi from './locales/vi.json';
import { getInitialLanguage } from '@/utils/language';
i18n.use(initReactI18next).init({
@@ -16,6 +17,7 @@ i18n.use(initReactI18next).init({
'zh-TW': { translation: zhTW },
en: { translation: en },
ru: { translation: ru },
vi: { translation: vi },
},
lng: getInitialLanguage(),
fallbackLng: 'zh-CN',
+2 -1
View File
@@ -1805,7 +1805,8 @@
"chinese": "中文",
"chinese_tw": "Traditional Chinese (Taiwan)",
"english": "English",
"russian": "Русский"
"russian": "Русский",
"vietnamese": "Tiếng Việt"
},
"theme": {
"switch": "Theme",
+2 -1
View File
@@ -1799,7 +1799,8 @@
"chinese": "中文",
"chinese_tw": "繁體中文(台灣)",
"english": "English",
"russian": "Русский"
"russian": "Русский",
"vietnamese": "Tiếng Việt"
},
"theme": {
"switch": "Тема",
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1803,7 +1803,8 @@
"chinese": "中文",
"chinese_tw": "繁體中文(台灣)",
"english": "English",
"russian": "Русский"
"russian": "Русский",
"vietnamese": "Tiếng Việt"
},
"theme": {
"switch": "主题",
+2 -1
View File
@@ -1829,7 +1829,8 @@
"chinese": "簡體中文",
"chinese_tw": "繁體中文(台灣)",
"english": "英文",
"russian": "俄文"
"russian": "俄文",
"vietnamese": "Tiếng Việt"
},
"theme": {
"switch": "主題",
+1 -1
View File
@@ -4,7 +4,7 @@
export type Theme = 'light' | 'white' | 'dark' | 'auto';
export type Language = 'zh-CN' | 'zh-TW' | 'en' | 'ru';
export type Language = 'zh-CN' | 'zh-TW' | 'en' | 'ru' | 'vi';
export type NotificationType = 'info' | 'success' | 'warning' | 'error';
+2 -1
View File
@@ -34,12 +34,13 @@ export const STORAGE_KEY_THEME = 'cli-proxy-theme';
export const STORAGE_KEY_LANGUAGE = 'cli-proxy-language';
// 语言配置
export const LANGUAGE_ORDER = defineLanguageOrder(['zh-CN', 'zh-TW', 'en', 'ru'] as const);
export const LANGUAGE_ORDER = defineLanguageOrder(['zh-CN', 'zh-TW', 'en', 'ru', 'vi'] as const);
export const LANGUAGE_LABEL_KEYS: Record<Language, string> = {
'zh-CN': 'language.chinese',
'zh-TW': 'language.chinese_tw',
en: 'language.english',
ru: 'language.russian',
vi: 'language.vietnamese',
};
export const SUPPORTED_LANGUAGES = LANGUAGE_ORDER;
+1
View File
@@ -45,6 +45,7 @@ const getBrowserLanguage = (): Language => {
if (TRADITIONAL_CHINESE_PREFIXES.some((prefix) => lower.startsWith(prefix))) return 'zh-TW';
if (lower.startsWith('zh')) return 'zh-CN';
if (lower.startsWith('ru')) return 'ru';
if (lower.startsWith('vi')) return 'vi';
return 'en';
};