From c0d92fbdd5a8a860bdfca257d8d52e8e6699fac7 Mon Sep 17 00:00:00 2001 From: jialin Date: Fri, 28 Mar 2025 19:09:56 +0800 Subject: [PATCH] chore: add check locales keys --- package.json | 1 + src/locales/README.md | 6 ++ src/locales/check.ts | 100 ++++++++++++++++++++++++++++++++- src/locales/en-US/models.ts | 5 ++ src/locales/ja-JP/models.ts | 9 ++- src/locales/ja-JP/resources.ts | 31 +++++++++- src/locales/zh-CN/common.ts | 4 +- 7 files changed, 152 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 317f1dcb..c87ba26a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "author": "jialin", "scripts": { "build": "max build", + "check:locales": "npx tsx ./src/locales/check.ts", "dev": "max dev", "format": "prettier --cache --write .", "postinstall": "max setup", diff --git a/src/locales/README.md b/src/locales/README.md index 8bb11502..80b372b6 100644 --- a/src/locales/README.md +++ b/src/locales/README.md @@ -35,3 +35,9 @@ To add a new language configuration, follow these steps: - Review and ensure all translations are complete. - If any translations are missing, they will default to `English`. - Your new language configuration is now ready for use! + +## 5. **Verify that all language keys are consistent.** + +```bash +npm run check:locales +``` diff --git a/src/locales/check.ts b/src/locales/check.ts index f8b39ac9..7d9ea479 100644 --- a/src/locales/check.ts +++ b/src/locales/check.ts @@ -12,6 +12,7 @@ const existingEntries = new Set(fs.readdirSync(localesDir)); const errors: string[] = []; +// ================ check if all locales are correctly named start ==================== Object.values(langConfigMap).forEach(({ lang }) => { const expectedDir = lang; const expectedFile = `${lang}.ts`; @@ -25,10 +26,107 @@ Object.values(langConfigMap).forEach(({ lang }) => { } }); +// ================ check if all locales are correctly named end ==================== + +const baseLang = 'en-US'; // standard language +const fileExt = '.ts'; + +function parseLangFile(filePath: string): Record { + if (!fs.existsSync(filePath)) return {}; + + const content = fs.readFileSync(filePath, 'utf-8').trim(); + + try { + // 1. extract the default export object + const match = content.match(/export\s+default\s+({[\s\S]*});?/); + if (match) { + let jsonString = match[1]; + + // 2. remove trailing commas + jsonString = jsonString.replace(/,\s*}/g, '}'); + + // 3. parse the object + return new Function(`return ${jsonString}`)(); + } + } catch (error) { + console.error(`❌ Error parsing ${filePath}:`, error); + } + + return {}; +} + +// get all modules in a language directory +function getLangFiles(lang: string): Record> { + const langPath = path.join(localesDir, lang); + if (!fs.existsSync(langPath)) return {}; + + return fs + .readdirSync(langPath) + .filter((file) => file.endsWith(fileExt)) + .reduce( + (acc, file) => { + const filePath = path.join(langPath, file); + acc[file] = parseLangFile(filePath); + return acc; + }, + {} as Record> + ); +} + +function compareKeys( + base: Record, + target: Record +): { missing: string[]; extra: string[] } { + const baseKeys = new Set(Object.keys(base)); + const targetKeys = new Set(Object.keys(target)); + + return { + missing: [...baseKeys].filter((key) => !targetKeys.has(key)), + extra: [...targetKeys].filter((key) => !baseKeys.has(key)) + }; +} + +// varify all locales base on en-US +function validateLocales() { + const baseFiles = getLangFiles(baseLang); // en-US as base + const languages = fs + .readdirSync(localesDir) + .filter( + (dir) => + fs.statSync(path.join(localesDir, dir)).isDirectory() && + dir !== baseLang + ); + + let hasErrors = false; + + languages.forEach((lang) => { + console.log(`🔍 Checking ${lang}...`); + const targetFiles = getLangFiles(lang); + + Object.entries(baseFiles).forEach(([fileName, baseData]) => { + const targetData = targetFiles[fileName] || {}; + + const { missing, extra } = compareKeys(baseData, targetData); + + if (missing.length || extra.length) { + hasErrors = true; + console.log(`❌ [${lang}] ${fileName}:`); + if (missing.length) + console.log(` ===== Missing keys: ===== \n`, missing); + if (extra.length) console.log(' ===== Extra keys: ===== \n', extra); + } + }); + }); + + return !hasErrors; +} + if (errors.length) { console.error('❌ Errors found:'); errors.forEach(console.error); process.exit(1); +} else if (!validateLocales()) { + process.exit(1); } else { - console.log('✅ All locales are correctly named.'); + console.log('✅ All keys are consistent!'); } diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index 797c180a..cd67a48c 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -123,3 +123,8 @@ export default { 'models.form.files': 'files', 'models.table.status': 'Status' }; + +// ========== To-Do: Translate Keys (Remove After Translation) ========== +// 1. 'models.form.files', +// 2. 'models.table.status', +// ========== End of To-Do List ========== diff --git a/src/locales/ja-JP/models.ts b/src/locales/ja-JP/models.ts index f7ef14cf..0ac6ee6d 100644 --- a/src/locales/ja-JP/models.ts +++ b/src/locales/ja-JP/models.ts @@ -116,5 +116,12 @@ export default { 'models.form.backend.warning.llamabox': 'llama-boxバックエンドを使用するには、モデルファイルのフルパスを指定してください(例:/data/models/model.gguf)。分割モデルの場合、最初のシャードのパスを指定してください(例:/data/models/model-00001-of-00004.gguf)。', 'models.form.keyvalue.paste': - '複数行のテキストを貼り付けます。各行にはキーと値のペアが含まれ、キーと値は=記号で区切られ、異なるキーと値のペアは改行文字で区切られます。' + '複数行のテキストを貼り付けます。各行にはキーと値のペアが含まれ、キーと値は=記号で区切られ、異なるキーと値のペアは改行文字で区切られます。', + 'models.form.files': 'files', + 'models.table.status': 'Status' }; + +// ========== To-Do: Translate Keys (Remove After Translation) ========== +// 1. 'models.form.files', +// 2. 'models.table.status' +// ========== End of To-Do List ========== diff --git a/src/locales/ja-JP/resources.ts b/src/locales/ja-JP/resources.ts index 69674f74..9a02cc1c 100644 --- a/src/locales/ja-JP/resources.ts +++ b/src/locales/ja-JP/resources.ts @@ -56,5 +56,34 @@ export default { 'ラベルを選択してコマンドを生成し、コピーを使用してコマンドをコピーします。', 'resources.worker.script.install': 'スクリプトインストール', 'resources.worker.container.install': 'コンテナインストール(Linuxのみ)', - 'resources.worker.cann.tips': `ASCEND_VISIBLE_DEVICES を必要なGPUインデックスに設定します。GPU0からGPU3の場合、ASCEND_VISIBLE_DEVICES=0,1,2,3 または ASCEND_VISIBLE_DEVICES=0-3 を使用します。` + 'resources.worker.cann.tips': `ASCEND_VISIBLE_DEVICES を必要なGPUインデックスに設定します。GPU0からGPU3の場合、ASCEND_VISIBLE_DEVICES=0,1,2,3 または ASCEND_VISIBLE_DEVICES=0-3 を使用します。`, + 'resources.modelfiles.form.path': 'Storage path', + 'resources.modelfiles.modelfile': 'Model Files', + 'resources.modelfiles.download': 'Add Model File', + 'resources.modelfiles.size': 'Size', + 'resources.modelfiles.selecttarget': 'Select Target', + 'resources.modelfiles.form.localdir': 'Local Directory', + 'resources.modelfiles.form.localdir.tips': + 'The default storage directory is /var/lib/gpustack/cache or the directory specified with --data-dir.', + 'resources.modelfiles.retry.download': 'Retry Download', + 'resources.modelfiles.storagePath.holder': + 'Waiting for download to complete...', + 'resources.filter.worker': 'Filter by Worker', + 'resources.filter.source': 'Filter by Source', + 'resources.modelfiles.delete.tips': 'Also delete the file from disk!' }; + +// ========== To-Do: Translate Keys (Remove After Translation) ========== +// 1. 'resources.modelfiles.form.path', +// 2. 'resources.modelfiles.modelfile', +// 3. 'resources.modelfiles.download', +// 4. 'resources.modelfiles.size', +// 5. 'resources.modelfiles.selecttarget', +// 6. 'resources.modelfiles.form.localdir', +// 7. 'resources.modelfiles.form.localdir.tips', +// 8. 'resources.modelfiles.retry.download', +// 9. 'resources.modelfiles.storagePath.holder', +// 10. 'resources.filter.worker', +// 11. 'resources.filter.source', +// 12. 'resources.modelfiles.delete.tips' +// ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/common.ts b/src/locales/zh-CN/common.ts index b93aaeaa..049de0f8 100644 --- a/src/locales/zh-CN/common.ts +++ b/src/locales/zh-CN/common.ts @@ -226,5 +226,7 @@ export default { 'common.button.moreInfo': '更多信息', 'common.text.warning': '警告', 'common.text.error': '错误', - 'common.text.tips': '提示' + 'common.text.tips': '提示', + 'settings.system': '系统设置', + 'common.filter.byId': '按 ID 查询' };