chore: add check locales keys
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
"author": "jialin",
|
"author": "jialin",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "max build",
|
"build": "max build",
|
||||||
|
"check:locales": "npx tsx ./src/locales/check.ts",
|
||||||
"dev": "max dev",
|
"dev": "max dev",
|
||||||
"format": "prettier --cache --write .",
|
"format": "prettier --cache --write .",
|
||||||
"postinstall": "max setup",
|
"postinstall": "max setup",
|
||||||
|
|||||||
@@ -35,3 +35,9 @@ To add a new language configuration, follow these steps:
|
|||||||
- Review and ensure all translations are complete.
|
- Review and ensure all translations are complete.
|
||||||
- If any translations are missing, they will default to `English`.
|
- If any translations are missing, they will default to `English`.
|
||||||
- Your new language configuration is now ready for use!
|
- 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[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
// ================ check if all locales are correctly named start ====================
|
||||||
Object.values(langConfigMap).forEach(({ lang }) => {
|
Object.values(langConfigMap).forEach(({ lang }) => {
|
||||||
const expectedDir = lang;
|
const expectedDir = lang;
|
||||||
const expectedFile = `${lang}.ts`;
|
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) {
|
if (errors.length) {
|
||||||
console.error('❌ Errors found:');
|
console.error('❌ Errors found:');
|
||||||
errors.forEach(console.error);
|
errors.forEach(console.error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
} else if (!validateLocales()) {
|
||||||
|
process.exit(1);
|
||||||
} else {
|
} 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.form.files': 'files',
|
||||||
'models.table.status': 'Status'
|
'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':
|
'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>)。',
|
'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.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.script.install': 'スクリプトインストール',
|
||||||
'resources.worker.container.install': 'コンテナインストール(Linuxのみ)',
|
'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.button.moreInfo': '更多信息',
|
||||||
'common.text.warning': '警告',
|
'common.text.warning': '警告',
|
||||||
'common.text.error': '错误',
|
'common.text.error': '错误',
|
||||||
'common.text.tips': '提示'
|
'common.text.tips': '提示',
|
||||||
|
'settings.system': '系统设置',
|
||||||
|
'common.filter.byId': '按 ID 查询'
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user