Loading scripts/check-lang.js +56 −14 Changes for scripts/check-lang.js: 56 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -2,18 +2,16 @@ import path from 'path'; import * as fs from 'node:fs/promises'; import * as yaml from 'yaml'; const projRoot = path.resolve(import.meta.dirname, ".."); const verbose = process.argv.includes("--verbose"); const projRoot = path.resolve(import.meta.dirname, '..'); const verbose = process.argv.includes('--verbose'); let exit = 0; if (!verbose) console.debug = () => { }; if (!verbose) console.debug = () => {}; const langs = []; const langDir = path.resolve(projRoot, 'src/lang/'); for (const langFile of await fs.readdir(langDir)) if (langFile.endsWith(".yaml")) if (langFile.endsWith('.yaml')) try { console.debug(`Reading file ${langFile}`); const data = await fs.readFile(path.resolve(langDir, langFile), 'utf8'); Loading @@ -27,7 +25,8 @@ for (const langFile of await fs.readdir(langDir)) const keys = await fs.readFile(path.resolve(projRoot, 'index.html'), 'utf8').then((data) => { /** @type string[] */ const keys = []; for (const [match, value] of data.toString().matchAll(/t\('([^']+)'\)/g)) { for (const [match, v1, v2] of data.toString().matchAll(/\bt\('([^']+)'\)|\bv-t(?:\.raw)?="([^"]+)"/g)) { const value = v1 ?? v2; if (value.match(/^[a-z]+(\.[a-z]+)*$/i)) { console.debug(`Found key ${value}`); keys.push(value); Loading @@ -37,7 +36,7 @@ const keys = await fs.readFile(path.resolve(projRoot, 'index.html'), 'utf8').the } } if (!keys.length) { console.error("Error found no translation key in HTML file"); console.error('Error found no translation key in HTML file'); exit = 1; } return keys; Loading @@ -46,25 +45,68 @@ keys.sort(); /** * @param {string[]} key * @param {* | undefined} lang * @param {Record<string, any>} lang */ function checkKeyExists(key, lang) { return lang && (key.length === 0 || checkKeyExists(key.slice(1), lang[key[0]])); } const used = new Set(); for (const [lang, data] of langs) { const missing = []; for (const key of keys) { if (!checkKeyExists(key.split('.'), data)) if (!used.has(key)) { if (checkKeyExists(key.split('.'), data)) { used.add(key); } else { missing.push(key); } if (missing.length) console.error(`Missing keys from ${lang}: \n${missing.map((s) => '\t' + s).join('\n')}`); } } if (missing.length) console.error(`Missing keys from ${lang}: \n${missing.map((s) => '\t' + s).join('\n')}`); } if (verbose) { /** * @param {string | Record<string | Record<string, any>>} lang * @param {Array<string>} keys * @param {string} prefix */ const unusedListKeys = (lang, keys = [], prefix = '') => { if (typeof lang === 'string') { if (!used.has(prefix)) keys.push(prefix); } else { for (const [key, value] of Object.entries(lang)) { unusedListKeys(value, keys, (prefix ? prefix + '.' : '') + key); } } return keys; }; /** @type {Set<string>} */ let unused = new Set(); const langUnused = langs.map(([lang, data]) => { const keys = new Set(unusedListKeys(data)); unused = unused.union(keys); return [lang, keys]; }), unusedArr = Array.from(unused).sort(), maxLen = unusedArr.reduce((p, v) => Math.max(p, v.length), 0), lines = unusedArr .map( (key) => ` - ${key.padStart(maxLen)} : ${langUnused .filter(([_, vs]) => vs.has(key)) .map(([n, _]) => n) .join(', ')}`, ) .join('\n'); console.log('Unused keys found in YAMLs: \n' + lines); } if (exit !== 0) { console.log("Some errors were found"); console.log('Some errors were found'); } else { console.log("All i18n data checked successfully"); console.log('All i18n data checked successfully'); } process.exit(exit); Loading
scripts/check-lang.js +56 −14 Changes for scripts/check-lang.js: 56 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -2,18 +2,16 @@ import path from 'path'; import * as fs from 'node:fs/promises'; import * as yaml from 'yaml'; const projRoot = path.resolve(import.meta.dirname, ".."); const verbose = process.argv.includes("--verbose"); const projRoot = path.resolve(import.meta.dirname, '..'); const verbose = process.argv.includes('--verbose'); let exit = 0; if (!verbose) console.debug = () => { }; if (!verbose) console.debug = () => {}; const langs = []; const langDir = path.resolve(projRoot, 'src/lang/'); for (const langFile of await fs.readdir(langDir)) if (langFile.endsWith(".yaml")) if (langFile.endsWith('.yaml')) try { console.debug(`Reading file ${langFile}`); const data = await fs.readFile(path.resolve(langDir, langFile), 'utf8'); Loading @@ -27,7 +25,8 @@ for (const langFile of await fs.readdir(langDir)) const keys = await fs.readFile(path.resolve(projRoot, 'index.html'), 'utf8').then((data) => { /** @type string[] */ const keys = []; for (const [match, value] of data.toString().matchAll(/t\('([^']+)'\)/g)) { for (const [match, v1, v2] of data.toString().matchAll(/\bt\('([^']+)'\)|\bv-t(?:\.raw)?="([^"]+)"/g)) { const value = v1 ?? v2; if (value.match(/^[a-z]+(\.[a-z]+)*$/i)) { console.debug(`Found key ${value}`); keys.push(value); Loading @@ -37,7 +36,7 @@ const keys = await fs.readFile(path.resolve(projRoot, 'index.html'), 'utf8').the } } if (!keys.length) { console.error("Error found no translation key in HTML file"); console.error('Error found no translation key in HTML file'); exit = 1; } return keys; Loading @@ -46,25 +45,68 @@ keys.sort(); /** * @param {string[]} key * @param {* | undefined} lang * @param {Record<string, any>} lang */ function checkKeyExists(key, lang) { return lang && (key.length === 0 || checkKeyExists(key.slice(1), lang[key[0]])); } const used = new Set(); for (const [lang, data] of langs) { const missing = []; for (const key of keys) { if (!checkKeyExists(key.split('.'), data)) if (!used.has(key)) { if (checkKeyExists(key.split('.'), data)) { used.add(key); } else { missing.push(key); } if (missing.length) console.error(`Missing keys from ${lang}: \n${missing.map((s) => '\t' + s).join('\n')}`); } } if (missing.length) console.error(`Missing keys from ${lang}: \n${missing.map((s) => '\t' + s).join('\n')}`); } if (verbose) { /** * @param {string | Record<string | Record<string, any>>} lang * @param {Array<string>} keys * @param {string} prefix */ const unusedListKeys = (lang, keys = [], prefix = '') => { if (typeof lang === 'string') { if (!used.has(prefix)) keys.push(prefix); } else { for (const [key, value] of Object.entries(lang)) { unusedListKeys(value, keys, (prefix ? prefix + '.' : '') + key); } } return keys; }; /** @type {Set<string>} */ let unused = new Set(); const langUnused = langs.map(([lang, data]) => { const keys = new Set(unusedListKeys(data)); unused = unused.union(keys); return [lang, keys]; }), unusedArr = Array.from(unused).sort(), maxLen = unusedArr.reduce((p, v) => Math.max(p, v.length), 0), lines = unusedArr .map( (key) => ` - ${key.padStart(maxLen)} : ${langUnused .filter(([_, vs]) => vs.has(key)) .map(([n, _]) => n) .join(', ')}`, ) .join('\n'); console.log('Unused keys found in YAMLs: \n' + lines); } if (exit !== 0) { console.log("Some errors were found"); console.log('Some errors were found'); } else { console.log("All i18n data checked successfully"); console.log('All i18n data checked successfully'); } process.exit(exit);