feat(i18n): English localization (phase 3)
CI / Lint e auditorias (push) Successful in 1m51s
CI / Trivy (dependências e segredos) (push) Successful in 15s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m41s

- Translate content to English across messages/en.json, data/en, lib/data/en, and public/exemplos/en
- Implement localized routing, slugs, and language negotiation with fallback to en
- Build dedicated search index for English with strict book exclusion
- Update components and pages to dynamically render localized strings and datasets
- Verify full structural parity and exact 1:1 match on Portuguese pages via comparator

Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
Lucas RayanandAntigravity committed 2026-11-01 14:17:13 -03:00
1 parent 22b1f26aed
commit c1bd2c7e11
61 files changed
+18182 -1225

No files matched your search

+98 -3
View File
@@ -34,6 +34,9 @@ const TECHNICAL_ALLOWLIST = new Set([
"ESPDocs",
"Lucas Rayan Guerra",
"Ciência Embarcada",
"Hardware",
"Component Registry",
"ESP Component Registry",
"ESP32",
"ESP32-S2",
"ESP32-S3",
@@ -75,6 +78,7 @@ const TECHNICAL_ALLOWLIST = new Set([
"JTAG",
"Gitea",
"GitHub",
"LinkedIn",
"MIT",
"DevKit",
"SoC",
@@ -101,6 +105,36 @@ const TECHNICAL_ALLOWLIST = new Set([
"6",
]);
// Chaves específicas do livro (seção 7: apenas permitidas no pt)
const BOOK_KEYS = new Set([
"Header.book",
"Header.bookNew",
"Header.bookTitle",
"Header.bookSubtitle",
"Footer.bookNew",
"Footer.bookTitle",
"Footer.bookSubtitle",
"Footer.amazon",
"Footer.uiclap",
"Home.bookChapters",
"Home.bookTitle",
"Home.bookSubtitle",
"Home.bookNewBadge",
"Home.bookDescription",
"Home.bookParts",
"Home.bookChaptersCount",
"Home.buyDigital",
"Home.buyPrint",
"Home.fullRecord",
"Livro.title",
"Livro.subtitle",
"Livro.badge",
"Livro.description",
"Livro.buyDigital",
"Livro.buyPrint",
"Livro.chaptersTitle",
]);
let totalErros = 0;
let totalAvisos = 0;
@@ -237,6 +271,10 @@ function verificarOutrosIdiomas(ptMessages) {
let iguaisSemAllowlist = 0;
for (const [k, vPt] of Object.entries(chavesPt)) {
// Chaves exclusivas do livro não são esperadas fora do pt (§7)
if (BOOK_KEYS.has(k) || k.startsWith("Livro.")) {
continue;
}
if (!(k in chavesLoc)) {
faltando++;
} else {
@@ -253,7 +291,11 @@ function verificarOutrosIdiomas(ptMessages) {
}
for (const k of Object.keys(chavesLoc)) {
if (!(k in chavesPt)) extras++;
if (BOOK_KEYS.has(k) || k.startsWith("Livro.")) {
falha(`'${loc}': chave proibida do livro '${k}' presente no catálogo fora de pt.`);
} else if (!(k in chavesPt)) {
extras++;
}
}
if (isEnabled) {
@@ -280,6 +322,19 @@ function verificarOutrosIdiomas(ptMessages) {
}
}
// Usos técnicos legítimos permitidos (seção 10: allowlist explícita)
const TECHNICAL_BOOK_ALLOWLIST = [
"https://esp-rs.github.io/book/",
];
function sanitizarTextoParaLivro(conteudo) {
let limpo = conteudo;
for (const termo of TECHNICAL_BOOK_ALLOWLIST) {
limpo = limpo.split(termo).join("");
}
return limpo;
}
// ---------------------------------------------------------------------------
// 3. Verificação de Regras do Livro (§7 da spec: ausência fora do pt)
// ---------------------------------------------------------------------------
@@ -294,7 +349,7 @@ function verificarRegrasLivro() {
// Checar messages/<loc>.json
const pathMsg = `messages/${loc}.json`;
if (existsSync(pathMsg)) {
const conteudo = readFileSync(pathMsg, "utf8");
const conteudo = sanitizarTextoParaLivro(readFileSync(pathMsg, "utf8"));
const m = conteudo.match(FORBIDDEN_BOOK_TERMS);
if (m) {
falha(`Termo proibido do livro '${m[0]}' encontrado em '${pathMsg}'.`);
@@ -307,13 +362,53 @@ function verificarRegrasLivro() {
if (existsSync(dirData)) {
const arquivos = readdirSync(dirData).filter((f) => f.endsWith(".json"));
for (const arq of arquivos) {
const conteudo = readFileSync(join(dirData, arq), "utf8");
const conteudo = sanitizarTextoParaLivro(readFileSync(join(dirData, arq), "utf8"));
const m = conteudo.match(FORBIDDEN_BOOK_TERMS);
if (m) {
falha(`Termo proibido do livro '${m[0]}' encontrado em '${dirData}/${arq}'.`);
violacoes++;
}
}
const dirConexoes = join(dirData, "conexoes");
if (existsSync(dirConexoes)) {
const arqsConexoes = readdirSync(dirConexoes).filter((f) => f.endsWith(".json"));
for (const arq of arqsConexoes) {
const conteudo = sanitizarTextoParaLivro(readFileSync(join(dirConexoes, arq), "utf8"));
const m = conteudo.match(FORBIDDEN_BOOK_TERMS);
if (m) {
falha(`Termo proibido do livro '${m[0]}' encontrado em '${dirConexoes}/${arq}'.`);
violacoes++;
}
}
}
}
// Checar lib/data/<loc>/
const dirLibData = `lib/data/${loc}`;
if (existsSync(dirLibData)) {
const arquivos = readdirSync(dirLibData).filter((f) => f.endsWith(".js"));
for (const arq of arquivos) {
const conteudo = sanitizarTextoParaLivro(readFileSync(join(dirLibData, arq), "utf8"));
const m = conteudo.match(FORBIDDEN_BOOK_TERMS);
if (m) {
falha(`Termo proibido do livro '${m[0]}' encontrado em '${dirLibData}/${arq}'.`);
violacoes++;
}
}
}
// Checar public/exemplos/<loc>/
const dirExemplos = `public/exemplos/${loc}`;
if (existsSync(dirExemplos)) {
const arquivos = readdirSync(dirExemplos);
for (const arq of arquivos) {
const conteudo = sanitizarTextoParaLivro(readFileSync(join(dirExemplos, arq), "utf8"));
const m = conteudo.match(FORBIDDEN_BOOK_TERMS);
if (m) {
falha(`Termo proibido do livro '${m[0]}' encontrado em '${dirExemplos}/${arq}'.`);
violacoes++;
}
}
}
}