Files
KoalaSync/scripts/test-website-locales.mjs
T

136 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const localesDir = path.join(__dirname, '..', 'website', 'locales');
const enPath = path.join(localesDir, 'en.json');
if (!fs.existsSync(enPath)) {
console.error('CRITICAL: website/locales/en.json is missing!');
process.exit(1);
}
const enDict = JSON.parse(fs.readFileSync(enPath, 'utf8'));
const enKeys = Object.keys(enDict).sort();
const localeFiles = fs.readdirSync(localesDir)
.filter(file => file.endsWith('.json'))
.sort();
let hasError = false;
const encryptedChatComparisonKeys = [
'ALT_TELEPARTY_INTRO',
'ALT_TELEPARTY_WHEN_TELE_BODY',
'ALT_TELEPARTY_MAIN_DIFF_BODY',
'ALT_TELEPARTY_LIMITS_BODY',
'ALT_TELEPARTY_SUMMARY_BODY',
'ALT_SCREEN_LIMITS_BODY',
'ALT_W2G_MAIN_DIFF_BODY',
'ALT_W2G_LIMITS_BODY',
'ALT_W2G_SUMMARY_BODY',
'ALT_SCENER_INTRO',
'ALT_SCENER_MAIN_DIFF_BODY',
'ALT_SCENER_LIMITS_BODY',
'ALT_KOSMI_LIMITS_BODY',
'ALT_TWOSEVEN_LIMITS_BODY'
];
const staleChatClaims = /does not currently have a built-in chat|has no built-in chat|no built-in chat box overlay|no built-in chat or webcam|no built-in chat or voice/i;
for (const key of encryptedChatComparisonKeys) {
const value = enDict[key];
if (typeof value !== 'string' || !value.includes('end-to-end encrypted')) {
hasError = true;
console.error(`❌ English comparison key ${key} must describe KoalaSync's end-to-end encrypted chat.`);
}
if (staleChatClaims.test(value)) {
hasError = true;
console.error(`❌ English comparison key ${key} still claims that KoalaSync has no built-in text chat.`);
}
}
if (!enDict.ALT_TWOSEVEN_LIMITS_BODY.includes('floating and dockable')) {
hasError = true;
console.error('❌ TwoSeven comparison must describe KoalaSync text chat as floating and dockable.');
}
console.log(`Auditing website i18n locales using ${enKeys.length} baseline keys from en.json...\n`);
for (const file of localeFiles) {
const filePath = path.join(localesDir, file);
try {
const raw = fs.readFileSync(filePath, 'utf8');
// Check for duplicate keys in raw JSON before parsing
const keyRe = /"(\w+)"\s*:/g;
const seenKeys = {};
let dupes = [];
let m;
while ((m = keyRe.exec(raw)) !== null) {
if (seenKeys[m[1]]) {
dupes.push(m[1]);
}
seenKeys[m[1]] = true;
}
if (dupes.length > 0) {
hasError = true;
console.error(`${file} has duplicate keys: ${[...new Set(dupes)].join(', ')}`);
continue;
}
const dict = JSON.parse(raw);
const keys = Object.keys(dict).sort();
const missingKeys = enKeys.filter(k => !keys.includes(k));
const extraKeys = keys.filter(k => !enKeys.includes(k));
const emptyKeys = keys.filter(k => {
if (file === 'en.json' && k === 'CANONICAL_PATH') return false;
return typeof dict[k] === 'string' && dict[k].trim() === '';
});
const placeholderKeys = keys.filter(k => {
if (typeof dict[k] !== 'string') return false;
const val = dict[k];
const lower = val.toLowerCase();
return (
lower.includes('placeholder') ||
lower.includes('todo:') ||
lower.includes('tbd:') ||
/\bTODO\b/.test(val) ||
/\bTBD\b/.test(val)
);
});
if (missingKeys.length > 0 || extraKeys.length > 0 || emptyKeys.length > 0 || placeholderKeys.length > 0) {
hasError = true;
console.error(`${file} has inconsistencies:`);
if (missingKeys.length > 0) {
console.error(` Missing keys (${missingKeys.length}):`, missingKeys);
}
if (extraKeys.length > 0) {
console.error(` Extra keys (${extraKeys.length}):`, extraKeys);
}
if (emptyKeys.length > 0) {
console.error(` Empty translation values (${emptyKeys.length}):`, emptyKeys);
}
if (placeholderKeys.length > 0) {
console.error(` Placeholder/TODO values (${placeholderKeys.length}):`, placeholderKeys);
}
} else {
console.log(`${file} is fully consistent (matches all keys).`);
}
} catch (err) {
hasError = true;
console.error(`❌ Failed to parse ${file}:`, err.message);
}
}
console.log('');
if (hasError) {
console.error('❌ Website locale consistency check failed! Fix the errors above.');
process.exit(1);
} else {
console.log('🎉 All website locale files are perfectly synchronized and consistent!');
process.exit(0);
}