feat(i18n): add multi-language support (ja/ko/vi/ru/zh-TW) with system auto-detection

- Extend AppLanguage to 7 locales: zh-CN, zh-TW, en, ja, ko, vi, ru
- Auto-detect system language via navigator.languages; fallback to English
- Add BCP47 locale mapping for date formatting and sorting
- Create 50 translation JSON files (5 languages × 10 namespaces, 1137 keys each)
- Update language selector with native language names
- Extend locale key parity test to cover all 7 languages (70 assertions)
- Add VNC/RDP desktop, serial terminal, AI agent, port forwarding, and SFTP features
- Add credential encryption, host metrics, session backup crypto, and zmodem bridge
- Update Rust backend: desktop services, host metrics, SSH/SFTP enhancements
This commit is contained in:
Logan.Li committed 2026-08-12 00:07:48 +08:00
1 parent fea6d8db04
commit b0d0e3a5f3
301 files changed
+28002 -2705

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest'
import { I18N_NAMESPACES } from '../../src/i18n'
import { APP_LANGUAGES, type AppLanguage } from '../../src/i18n/detectLocale'
function flattenKeys(value: unknown, prefix = ''): string[] {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return prefix ? [prefix] : []
}
const entries = Object.entries(value as Record<string, unknown>)
if (entries.length === 0) return prefix ? [prefix] : []
return entries.flatMap(([key, nested]) => {
const next = prefix ? `${prefix}.${key}` : key
if (nested !== null && typeof nested === 'object' && !Array.isArray(nested)) {
const childKeys = flattenKeys(nested, next)
return childKeys.length > 0 ? childKeys : [next]
}
return [next]
})
}
async function loadLocale(namespace: string, language: AppLanguage) {
const mod = await import(`../../src/i18n/locales/${language}/${namespace}.json`)
return mod.default as Record<string, unknown>
}
describe('i18n locale key parity', () => {
for (const namespace of I18N_NAMESPACES) {
for (const language of APP_LANGUAGES) {
it(`${language} keys match en for ${namespace}`, async () => {
const [locale, en] = await Promise.all([
loadLocale(namespace, language),
loadLocale(namespace, 'en'),
])
const localeKeys = flattenKeys(locale).sort()
const enKeys = flattenKeys(en).sort()
expect(localeKeys).toEqual(enKeys)
})
}
}
})