WIP: #7324 - validate_translations #1134

Draft
jsegarra wants to merge 5 commits from validate_translations into dev
1 changed files with 162 additions and 0 deletions
Showing only changes of commit 5ae07983f6 - Show all commits

View File

@ -0,0 +1,162 @@
import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
import yaml from 'js-yaml';
// 📌 Cargar traducciones YAML
const localesPath = path.resolve(__dirname + '../../i18n/locale');
const locales = fs.readdirSync(localesPath).reduce((acc, file) => {
const locale = path.basename(file, '.yml');
acc[locale] = yaml.load(fs.readFileSync(path.join(localesPath, file), 'utf8'));
return acc;
}, {});
// 📌 Extraer todas las claves de traducción de los archivos .vue
async function extractTranslationKeysFromUsage() {
const vueFiles = await glob('src/**/*.vue');
const regex = /\$t\(['"`]([\w.]+)['"`]\)|t\(['"`]([\w.]+)['"`]\)/g;
const keys = new Set();
vueFiles.forEach((file) => {
const content = fs.readFileSync(file, 'utf8');
let match;
while ((match = regex.exec(content)) !== null) {
keys.add(match[1] || match[2]);
}
});
return Array.from(keys);
}
// 📌 Extraer claves de la etiqueta `<i18n>` en los componentes Vue
async function extractTranslationKeysFromI18nTag() {
const vueFiles = await glob('src/**/*.vue');
const keys = new Set();
for (const file of vueFiles) {
const content = fs.readFileSync(file, 'utf8');
const i18nMatch = content.match(/<i18n[^>]*>([\s\S]*?)<\/i18n>/);
if (i18nMatch) {
try {
const i18nContent = yaml.load(i18nMatch[1]);
extractKeysFromObject(i18nContent).forEach((key) => keys.add(key));
} catch (err) {
console.warn(`⚠️ Error parsing <i18n> block in ${file}:`, err.message);
}
}
}
return Array.from(keys);
}
// 📌 Extraer claves y valores de un objeto anidado
function extractKeysFromObject(obj, prefix = '') {
let keys = [];
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
keys = keys.concat(extractKeysFromObject(obj[key], `${prefix}${key}.`));
} else {
// Aquí estamos recogiendo tanto la clave como su valor
keys.push({ [`${prefix}${key}`]: obj[key] });
}
}
return keys;
}
// 📌 Validar claves extraídas contra las traducciones disponibles
function validateKeys(keys, translations) {
const missingKeys = [];
keys.forEach((key) => {
const parts = key.split('.');
let current = translations;
for (const part of parts) {
if (current[part] !== undefined) {
current = current[part];
} else {
missingKeys.push(key);
break;
}
}
});
return missingKeys;
}
// 📌 Ejecutar las validaciones
describe('🔍 Translation Keys Validation', async () => {
const vueFiles = await glob([
'src/pages/*.vue',
'src/components/CreateNewCityForm.vue',
]);
const regex = /\$t\(['"`]([\w.]+)['"`]\)|t\(['"`]([\w.]+)['"`]\)/g;
vueFiles.forEach((file) => {
const keys = new Set();
const content = fs.readFileSync(file, 'utf8');
let match;
while ((match = regex.exec(content)) !== null) {
keys.add(match[1] || match[2]);
}
const i18nMatch = content.match(/<i18n[^>]*>([\s\S]*?)<\/i18n>/);
if (i18nMatch) {
try {
const i18nContent = yaml.load(i18nMatch[1]);
if (Object.keys(i18nContent).length < Object.keys(locales).length) {
const langs = Object.keys(locales);
const current = Object.keys(i18nContent);
langs
.filter((x) => !current.includes(x))
.forEach((lang) => {
i18nContent[lang] = i18nContent[current[0]];
});
}
Object.entries(i18nContent).forEach(([local, value]) => {
Object.entries(value).forEach(([k, v]) => (locales[local][k] = v));
});
// extractKeysFromObject(i18nContent).forEach((key) => keys.add(key));
} catch (err) {
console.warn(`⚠️ Error parsing <i18n> block in ${file}:`, err.message);
}
}
Object.entries(locales).forEach(([locale, translations]) => {
it(`should have all translation keys in ${locale}.${file}`, () => {
const missingKeys = validateKeys(keys, translations);
// if (missingKeys.length > 0) {
// console.error(`❌ Missing keys in ${file}:`, missingKeys);
// }
expect(missingKeys, `Missing keys in ${file}`).toHaveLength(0);
});
});
});
/*
const [usageKeys, i18nTagKeys] = await Promise.all([
extractTranslationKeysFromUsage(),
// extractTranslationKeysFromI18nTag(),
]);
const allKeys = [...new Set([...usageKeys, ...i18nTagKeys])];
Object.entries(locales).forEach(([locale, translations]) => {
it(`should have all translation keys in ${locale}`, () => {
const missingKeys = validateKeys(allKeys, translations);
if (missingKeys.length > 0) {
console.error(`❌ Missing keys in ${locale}:`, missingKeys);
}
expect(missingKeys).toEqual([]);
});
});*/
});