多国语言支持
This commit is contained in:
1 parent
b98c0333e6
commit
04de5e5a8e
23 files changed
+7795
-1279
No files matched your search
+53
-14
@@ -1,8 +1,12 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { build } from 'esbuild'
|
||||
|
||||
const zh = JSON.parse(await readFile('src/i18n/locales/zh-CN.json', 'utf8'))
|
||||
const en = JSON.parse(await readFile('src/i18n/locales/en.json', 'utf8'))
|
||||
const localeCodes = ['zh-CN', 'zh-TW', 'en', 'ja', 'ko', 'fr', 'de', 'pt-BR', 'es']
|
||||
const localeMessages = Object.fromEntries(await Promise.all(localeCodes.map(async (code) => [
|
||||
code,
|
||||
JSON.parse(await readFile(`src/i18n/locales/${code}.json`, 'utf8')),
|
||||
])))
|
||||
const zh = localeMessages['zh-CN']
|
||||
|
||||
function flattenKeys(value, prefix = '') {
|
||||
const keys = []
|
||||
@@ -18,14 +22,41 @@ function flattenKeys(value, prefix = '') {
|
||||
}
|
||||
|
||||
const zhKeys = flattenKeys(zh)
|
||||
const enKeys = flattenKeys(en)
|
||||
const missingInEnglish = zhKeys.filter((key) => !enKeys.includes(key))
|
||||
const missingInChinese = enKeys.filter((key) => !zhKeys.includes(key))
|
||||
if (missingInEnglish.length || missingInChinese.length) {
|
||||
throw new Error([
|
||||
missingInEnglish.length ? `Missing in English: ${missingInEnglish.join(', ')}` : '',
|
||||
missingInChinese.length ? `Missing in Chinese: ${missingInChinese.join(', ')}` : '',
|
||||
].filter(Boolean).join('\n'))
|
||||
|
||||
function getPath(value, path) {
|
||||
return path.split('.').reduce((current, key) => current?.[key], value)
|
||||
}
|
||||
function placeholders(value) {
|
||||
return [...String(value).matchAll(/\{[a-zA-Z0-9_]+\}/g)].map((match) => match[0]).sort()
|
||||
}
|
||||
|
||||
for (const code of localeCodes.slice(1)) {
|
||||
const messages = localeMessages[code]
|
||||
const keys = flattenKeys(messages)
|
||||
const missing = zhKeys.filter((key) => !keys.includes(key))
|
||||
const extra = keys.filter((key) => !zhKeys.includes(key))
|
||||
if (missing.length || extra.length) {
|
||||
throw new Error([
|
||||
missing.length ? `${code} missing: ${missing.join(', ')}` : '',
|
||||
extra.length ? `${code} extra: ${extra.join(', ')}` : '',
|
||||
].filter(Boolean).join('\n'))
|
||||
}
|
||||
for (const key of zhKeys) {
|
||||
if (!String(getPath(messages, key) ?? '').trim()) {
|
||||
throw new Error(`${code}.${key} is empty`)
|
||||
}
|
||||
const expected = placeholders(getPath(zh, key))
|
||||
const actual = placeholders(getPath(messages, key))
|
||||
if (JSON.stringify(expected) !== JSON.stringify(actual)) {
|
||||
throw new Error(`${code}.${key} placeholders differ: ${expected.join(',')} != ${actual.join(',')}`)
|
||||
}
|
||||
}
|
||||
if (JSON.stringify(messages).includes('ZXQV') || /\uE000|\uE001|\uE002/.test(JSON.stringify(messages))) {
|
||||
throw new Error(`${code} contains unresolved translation tokens`)
|
||||
}
|
||||
if (!String(messages.app?.title || '').includes('Yeomai')) {
|
||||
throw new Error(`${code} title must keep the Yeomai brand`)
|
||||
}
|
||||
}
|
||||
|
||||
const bundled = await build({
|
||||
@@ -42,12 +73,20 @@ const i18nModule = await import(
|
||||
|
||||
const cases = [
|
||||
[null, ['zh-CN'], 'zh-CN'],
|
||||
[null, ['zh-Hant-TW'], 'zh-CN'],
|
||||
[null, ['zh-Hant-TW'], 'zh-TW'],
|
||||
[null, ['zh-HK'], 'zh-TW'],
|
||||
[null, ['en-US'], 'en'],
|
||||
[null, ['fr-FR'], 'en'],
|
||||
[null, ['ko-KR', 'en-US'], 'en'],
|
||||
[null, ['ja-JP'], 'ja'],
|
||||
[null, ['ko-KR'], 'ko'],
|
||||
[null, ['fr-FR'], 'fr'],
|
||||
[null, ['de-DE'], 'de'],
|
||||
[null, ['pt-PT'], 'pt-BR'],
|
||||
[null, ['es-MX'], 'es'],
|
||||
[null, ['it-IT'], 'en'],
|
||||
[null, ['it-IT', 'de-DE'], 'de'],
|
||||
['en', ['zh-CN'], 'en'],
|
||||
['zh-CN', ['en-US'], 'zh-CN'],
|
||||
['ja', ['en-US'], 'ja'],
|
||||
]
|
||||
for (const [saved, browserLanguages, expected] of cases) {
|
||||
const actual = i18nModule.resolveAppLocale(saved, browserLanguages)
|
||||
@@ -56,4 +95,4 @@ for (const [saved, browserLanguages, expected] of cases) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`i18n 校验通过:${zhKeys.length} 个消息键,中英语言包一致,自动语言与缓存优先级正确`)
|
||||
console.log(`i18n 校验通过:${localeCodes.length} 种语言、${zhKeys.length} 个消息键,键与占位符一致,自动语言与缓存优先级正确`)
|
||||
Reference in new issue
Block a user