- Updated site tagline and footer description in multiple languages. - Enhanced error messages for various scenarios in the API service. - Added translations for new error codes related to AI features and PDF processing. - Improved user feedback in the UI components by utilizing i18n for dynamic text. - Refactored error handling in the API service to map backend error codes to user-friendly messages.
84 lines
2.5 KiB
JavaScript
84 lines
2.5 KiB
JavaScript
/**
|
|
* check-i18n-keys.mjs
|
|
* Scans all .ts/.tsx files in src/ and verifies every static t('key') call
|
|
* exists as a dot-path entry in src/i18n/en.json.
|
|
*
|
|
* Usage: node scripts/check-i18n-keys.mjs
|
|
* Exit code 1 if any missing keys are found.
|
|
*/
|
|
|
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
import { join, relative } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
const ROOT = join(__dirname, '..');
|
|
const SRC = join(ROOT, 'src');
|
|
const EN_JSON = join(ROOT, 'src', 'i18n', 'en.json');
|
|
|
|
// Load en.json and build a flat Set of all dot-paths
|
|
function flattenKeys(obj, prefix = '') {
|
|
const keys = new Set();
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
const path = prefix ? `${prefix}.${k}` : k;
|
|
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
for (const nested of flattenKeys(v, path)) keys.add(nested);
|
|
} else {
|
|
keys.add(path);
|
|
}
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
const enJson = JSON.parse(readFileSync(EN_JSON, 'utf8'));
|
|
const definedKeys = flattenKeys(enJson);
|
|
|
|
// Collect all .ts/.tsx files under src/
|
|
function* walkFiles(dir) {
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
if (statSync(full).isDirectory()) {
|
|
yield* walkFiles(full);
|
|
} else if (/\.(tsx?|jsx?)$/.test(entry) && !entry.endsWith('.d.ts')) {
|
|
yield full;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extract static string arguments from t('...') or t("...") calls.
|
|
// Matches: t('key'), t("key"), t(`key`), useTranslation().t('key'),
|
|
// as well as i18n.t('key') patterns.
|
|
const T_CALL_RE = /\bt\(\s*(['"`])([^'"`\s]+)\1/g;
|
|
|
|
const missing = [];
|
|
|
|
for (const file of walkFiles(SRC)) {
|
|
const rel = relative(ROOT, file).replace(/\\/g, '/');
|
|
const content = readFileSync(file, 'utf8');
|
|
const lines = content.split('\n');
|
|
|
|
for (const [lineIdx, line] of lines.entries()) {
|
|
let match;
|
|
T_CALL_RE.lastIndex = 0;
|
|
while ((match = T_CALL_RE.exec(line)) !== null) {
|
|
const key = match[2];
|
|
// Skip dynamic keys (contain ${) or non-string patterns
|
|
if (key.includes('${') || key.includes('(')) continue;
|
|
if (!definedKeys.has(key)) {
|
|
missing.push({ file: rel, line: lineIdx + 1, key });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (missing.length === 0) {
|
|
console.log('✓ All t() keys are present in en.json');
|
|
process.exit(0);
|
|
} else {
|
|
console.error(`✗ Found ${missing.length} missing i18n key(s):\n`);
|
|
for (const { file, line, key } of missing) {
|
|
console.error(` ${file}:${line} → "${key}"`);
|
|
}
|
|
process.exit(1);
|
|
}
|