diff --git a/src/utils/validate-translations.spec.js b/src/utils/validate-translations.spec.js new file mode 100644 index 000000000..b4c2848d7 --- /dev/null +++ b/src/utils/validate-translations.spec.js @@ -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 `` 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(/]*>([\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 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(/]*>([\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 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([]); + }); + });*/ +});