feat(i18n): Spanish localization (phase 4)
CI / Lint e auditorias (push) Successful in 1m31s
CI / Trivy (dependências e segredos) (push) Successful in 10s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m23s

Complete implementation of Spanish (es-419) language support:
- Canonical catalogs and data: messages/es.json, data/es/**, lib/data/es/**, public/exemplos/es/**
- Canonical slugs: /es/series, /es/catalogo, /es/comparacion, /es/componentes, /es/diagnostico, /es/frameworks, /es/glosario, /es/selector, /es/acerca
- Dedicated search index at /api/busca/es with Spanish accent normalization
- Strict exclusion of book references in Spanish routes and catalogs
- Language selector and root negotiation with es active (ENABLED_LOCALES = ['pt', 'en', 'es'])
- Extended verification suite: check-i18n, smoke-imagem and verifica-vazamentos

Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
Lucas RayanandAntigravity committed 2026-11-01 19:50:20 -03:00
1 parent 0940814522
commit 660b28c09d
72 files changed
+17848 -947

No files matched your search

+16 -4
View File
@@ -125,6 +125,15 @@ const TECHNICAL_ALLOWLIST = new Set([
"4",
"5",
"6",
"Buscar...",
"Catálogo de placas",
"Diagnóstico",
"Índice completo",
"Periféricos",
"Modern C++",
"Núcleos de CPU (Dual-Core vs Single-Core)",
"Bluetooth Classic (BR/EDR)",
"Ethernet MAC 10/100 Mbps",
]);
function isTechnicalAllowlisted(val, path = "") {
@@ -142,7 +151,7 @@ function isTechnicalAllowlisted(val, path = "") {
if (/^(\p{Extended_Pictographic}|\p{Emoji_Presentation}|\s)+$/u.test(s)) return true;
// Medidas, números, percentuais, frequências, consumos, intervalos
if (/^[-+]?\d+([.,]\d+)?(\s*[%]|(\s*(MHz|GHz|KB|MB|GB|µA|mA|A|V|mV|mW|W|dBm|°C|mm|ohms|kΩ|µF|nF)))?$/i.test(s)) return true;
if (/^[-+]?\d+([.,]\d+)?\s*(a|to|-)\s*[-+]?\d+([.,]\d+)?(\s*(MHz|GHz|KB|MB|GB|µA|mA|A|V|mV|mW|W|dBm|°C|mm|ohms|kΩ|µF|nF))?$/i.test(s)) return true;
if (/^[-+]?\d+([.,]\d+)?(\s*°C)?\s*(a|to|-)\s*[-+]?\d+(\/\d+)*([.,]\d+)?(\s*(MHz|GHz|KB|MB|GB|µA|mA|A|V|mV|mW|W|dBm|°C|mm|ohms|kΩ|µF|nF))?$/i.test(s)) return true;
if (/^\d+\s*mW\s*\([^)]*\)$/i.test(s)) return true;
// Hardware specs / contagem de periféricos: "1 (CAN 2.0, TWAI®)", "1 (Single-core)", "2 (Dual-core)"
if (/^\d+(\s*\(.*\))?(\s*(\+|\/|,)\s*\d+(\s*\(.*\))?)*$/i.test(s)) return true;
@@ -416,7 +425,7 @@ async function verificarDadosPorIdioma() {
const problemas = encontrarIdenticos(ptSeries, locSeries, `data/${loc}/series.json`);
if (problemas.length > 0) {
falha(`'${loc}': ${problemas.length} valor(es) string em series.json idênticos ao pt fora da allowlist.`);
for (const p of problemas.slice(0, 5)) {
for (const p of problemas.slice(0, 50)) {
console.log(` ${CORES.cinza}${p.path}: "${p.value}"${CORES.reset}`);
}
} else {
@@ -433,7 +442,7 @@ async function verificarDadosPorIdioma() {
const problemas = encontrarIdenticos(ptFw, locFw, `data/${loc}/frameworks.json`);
if (problemas.length > 0) {
falha(`'${loc}': ${problemas.length} valor(es) string em frameworks.json idênticos ao pt fora da allowlist.`);
for (const p of problemas.slice(0, 5)) {
for (const p of problemas.slice(0, 50)) {
console.log(` ${CORES.cinza}${p.path}: "${p.value}"${CORES.reset}`);
}
} else {
@@ -454,7 +463,7 @@ async function verificarDadosPorIdioma() {
const problemas = encontrarIdenticos(pt, en, `conexoes/${f}`);
totalConexoesProblemas += problemas.length;
if (problemas.length > 0) {
for (const p of problemas.slice(0, 3)) {
for (const p of problemas.slice(0, 10)) {
console.log(` ${CORES.cinza}${p.path}: "${p.value}"${CORES.reset}`);
}
}
@@ -503,6 +512,9 @@ async function verificarDadosPorIdioma() {
const problemasGloss = encontrarIdenticos(ptGloss, locGloss, `lib/data/${loc}/glossario.js`);
if (problemasGloss.length > 0) {
falha(`'${loc}': ${problemasGloss.length} string(s) em glossario.js idênticas ao pt fora da allowlist.`);
for (const p of problemasGloss) {
console.log(` ${CORES.cinza}${p.path}: "${p.value}"${CORES.reset}`);
}
} else {
sucesso(`'${loc}': lib/data/${loc}/glossario.js validado sem strings vazadas.`);
}