chore: add check locales keys
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||
+99
-1
@@ -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<string, any> {
|
||||
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<string, Record<string, any>> {
|
||||
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<string, Record<string, any>>
|
||||
);
|
||||
}
|
||||
|
||||
function compareKeys(
|
||||
base: Record<string, any>,
|
||||
target: Record<string, any>
|
||||
): { 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!');
|
||||
}
|
||||
|
||||
@@ -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 ==========
|
||||
|
||||
@@ -116,5 +116,12 @@ export default {
|
||||
'models.form.backend.warning.llamabox':
|
||||
'llama-boxバックエンドを使用するには、モデルファイルのフルパスを指定してください(例:<span style="font-weight: 700">/data/models/model.gguf</span>)。分割モデルの場合、最初のシャードのパスを指定してください(例:<span style="font-weight: 700">/data/models/model-00001-of-00004.gguf</span>)。',
|
||||
'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 ==========
|
||||
|
||||
@@ -56,5 +56,34 @@ export default {
|
||||
'ラベルを選択してコマンドを生成し、コピーを使用してコマンドをコピーします。',
|
||||
'resources.worker.script.install': 'スクリプトインストール',
|
||||
'resources.worker.container.install': 'コンテナインストール(Linuxのみ)',
|
||||
'resources.worker.cann.tips': `<span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES</span> を必要なGPUインデックスに設定します。GPU0からGPU3の場合、<span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES=0,1,2,3</span> または <span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES=0-3</span> を使用します。`
|
||||
'resources.worker.cann.tips': `<span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES</span> を必要なGPUインデックスに設定します。GPU0からGPU3の場合、<span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES=0,1,2,3</span> または <span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES=0-3</span> を使用します。`,
|
||||
'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 <span class="desc-block">/var/lib/gpustack/cache</span> or the directory specified with <span class="desc-block">--data-dir</span>.',
|
||||
'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 ==========
|
||||
|
||||
@@ -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 查询'
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user