138 lines
3.9 KiB
JavaScript
138 lines
3.9 KiB
JavaScript
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const messagesDir = join(__dirname, '..', 'public', 'intl', 'messages');
|
|
const sourcePath = join(messagesDir, 'en-US.json');
|
|
const removeExtraKeys = process.argv.includes('--remove-extra-keys');
|
|
const help = process.argv.includes('--help') || process.argv.includes('-h');
|
|
|
|
if (help) {
|
|
console.log('Usage: node scripts/check-missing-messages.js [--remove-extra-keys]');
|
|
process.exit(0);
|
|
}
|
|
|
|
const enRaw = readFileSync(sourcePath, 'utf8');
|
|
const newline = enRaw.includes('\r\n') ? '\r\n' : '\n';
|
|
const en = JSON.parse(enRaw);
|
|
|
|
// Flatten nested structure: { label: { foo: 'bar' } } -> { 'label.foo': 'bar' }
|
|
function flatten(obj, prefix = '') {
|
|
const result = {};
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
const key = prefix ? `${prefix}.${k}` : k;
|
|
if (typeof v === 'object' && v !== null) {
|
|
Object.assign(result, flatten(v, key));
|
|
} else {
|
|
result[key] = v;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function setNestedValue(target, flatKey, value) {
|
|
const parts = flatKey.split('.');
|
|
let current = target;
|
|
|
|
for (let index = 0; index < parts.length - 1; index++) {
|
|
current[parts[index]] ??= {};
|
|
current = current[parts[index]];
|
|
}
|
|
|
|
current[parts.at(-1)] = value;
|
|
}
|
|
|
|
function buildNormalizedObject(flat) {
|
|
const ordered = {};
|
|
|
|
for (const key of enKeys) {
|
|
if (key in flat) {
|
|
setNestedValue(ordered, key, flat[key]);
|
|
}
|
|
}
|
|
|
|
return ordered;
|
|
}
|
|
|
|
const enFlat = flatten(en);
|
|
const enKeys = Object.keys(enFlat);
|
|
const enKeySet = new Set(enKeys);
|
|
console.log(`en-US.json has ${enKeys.length} keys`);
|
|
|
|
const files = readdirSync(messagesDir)
|
|
.filter(f => f.endsWith('.json') && f !== 'en-US.json')
|
|
.sort();
|
|
|
|
const allMissing = {};
|
|
const allExtra = {};
|
|
let totalMissing = 0;
|
|
let totalExtra = 0;
|
|
let updatedFiles = 0;
|
|
|
|
for (const fname of files) {
|
|
const filePath = join(messagesDir, fname);
|
|
const raw = readFileSync(filePath, 'utf8');
|
|
const data = JSON.parse(raw);
|
|
const flat = flatten(data);
|
|
const missing = enKeys.filter(k => !(k in flat));
|
|
const extra = Object.keys(flat).filter(k => !enKeySet.has(k)).sort();
|
|
|
|
if (missing.length) {
|
|
allMissing[fname] = missing;
|
|
console.log(`${fname}: ${missing.length} missing`);
|
|
totalMissing += missing.length;
|
|
}
|
|
|
|
if (extra.length) {
|
|
allExtra[fname] = extra;
|
|
console.log(`${fname}: ${extra.length} extra`);
|
|
totalExtra += extra.length;
|
|
}
|
|
|
|
if (removeExtraKeys && extra.length) {
|
|
const normalized = buildNormalizedObject(flat);
|
|
const nextRaw = `${JSON.stringify(normalized, null, 2).replace(/\n/g, newline)}${newline}`;
|
|
|
|
if (nextRaw !== raw) {
|
|
writeFileSync(filePath, nextRaw, 'utf8');
|
|
updatedFiles++;
|
|
}
|
|
}
|
|
}
|
|
console.log(`\nTotal missing across all locales: ${totalMissing}`);
|
|
console.log(`Total extra across all locales: ${totalExtra}`);
|
|
|
|
if (removeExtraKeys) {
|
|
console.log(`Files updated to remove extra keys: ${updatedFiles}`);
|
|
}
|
|
|
|
const keyCounts = {};
|
|
for (const missing of Object.values(allMissing)) {
|
|
for (const k of missing) {
|
|
keyCounts[k] = (keyCounts[k] || 0) + 1;
|
|
}
|
|
}
|
|
const sorted = Object.entries(keyCounts).sort((a, b) => b[1] - a[1]);
|
|
if (sorted.length) {
|
|
console.log('\nMost commonly missing keys:');
|
|
for (const [k, count] of sorted) {
|
|
console.log(` "${k}": missing from ${count} files (en value: "${enFlat[k]}")`);
|
|
}
|
|
}
|
|
|
|
const extraKeyCounts = {};
|
|
for (const extra of Object.values(allExtra)) {
|
|
for (const k of extra) {
|
|
extraKeyCounts[k] = (extraKeyCounts[k] || 0) + 1;
|
|
}
|
|
}
|
|
const sortedExtra = Object.entries(extraKeyCounts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
if (sortedExtra.length) {
|
|
console.log('\nMost commonly extra keys:');
|
|
for (const [k, count] of sortedExtra) {
|
|
console.log(` "${k}": extra in ${count} files`);
|
|
}
|
|
}
|