From 1baa3145e802b3a971380855168e4b77499fbb35 Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Sun, 6 Sep 2026 18:18:10 -0300 Subject: [PATCH 1/6] feat(i18n): translate landing.json chapter to Brazilian Portuguese MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fill the `pt` mirror fields on the landing route-map chapter so the bilingual contract stops being silently English-only. 30 fields: hero eyebrow, title, lede, thread label and text, footer, and the six route cards (label, title, copy, cta each). English strings are byte-identical to origin/main. The page at / still renders English-only because src/pages/index.astro hard-codes `lang = 'en'`; wiring the bilingual swap into the landing page is task 12's scope. Translation choices follow the conventions already established by src/content/commonSkills/*.json and src/content/full-guide-pt.json: imperative voice, second-person `você`, sentence-case prose, all-caps labels, and English kept for product nouns (skill, worktree, brief, gate, pipeline, recall, prompt, enforcement). The `Hands-on` card title is rendered as `Prática` to match `full-guide-pt.json`'s `07 prática` nav entry; the URL `/hands-on/starter/` stays unchanged. Verified by .agents/scripts/audit-translations.mjs: the chapter contribution to the offenders list drops from 30 to 0. The remaining identical fields (92) live in summary.json, agents.json, models.json, and skills.json, which will land in subsequent commits. --- src/content/chapters/landing.json | 52 +++++++++++++++++-------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/content/chapters/landing.json b/src/content/chapters/landing.json index a51dd37..24874a2 100644 --- a/src/content/chapters/landing.json +++ b/src/content/chapters/landing.json @@ -1,83 +1,89 @@ { "id": "landing", - "eyebrow": { "en": "The short route", "pt": "The short route" }, - "title": { "en": "Ship the
system.", "pt": "Ship the
system." }, + "eyebrow": { + "en": "The short route", + "pt": "A rota curta" + }, + "title": { + "en": "Ship the
system.", + "pt": "Entregue o
sistema." + }, "lede": { "en": "Start with the map. Then open the one chapter that matches the decision in front of you: model, agent, worktree, skill, rule, or proof.", - "pt": "Start with the map. Then open the one chapter that matches the decision in front of you: model, agent, worktree, skill, rule, or proof." + "pt": "Comece pelo mapa. Depois abra o capítulo que combina com a decisão à sua frente: modelo, agente, worktree, skill, regra ou prova." }, "threadLabel": { "en": "THE THREAD", - "pt": "THE THREAD" + "pt": "O FIO" }, "threadText": { "en": "Frame uncertainty → isolate execution → preserve judgment → verify the change.", - "pt": "Frame uncertainty → isolate execution → preserve judgment → verify the change." + "pt": "Enquadre a incerteza → isole a execução → preserve o julgamento → verifique a mudança." }, "footer": { "en": "The route map is now the default entry. The full guide remains available whenever you want the whole narrative.", - "pt": "The route map is now the default entry. The full guide remains available whenever you want the whole narrative." + "pt": "O mapa de rotas é agora a entrada padrão. O guia completo segue disponível sempre que você quiser a narrativa inteira." }, "cards": [ { "label": { "en": "01", "pt": "01" }, - "title": { "en": "Models", "pt": "Models" }, + "title": { "en": "Models", "pt": "Modelos" }, "copy": { "en": "Capability and effort are separate knobs.", - "pt": "Capability and effort are separate knobs." + "pt": "Capacidade e esforço são controles separados." }, "href": "models/", - "cta": { "en": "Open chapter →", "pt": "Open chapter →" } + "cta": { "en": "Open chapter →", "pt": "Abrir capítulo →" } }, { "label": { "en": "02", "pt": "02" }, - "title": { "en": "Agents & trees", "pt": "Agents & trees" }, + "title": { "en": "Agents & trees", "pt": "Agentes e árvores" }, "copy": { "en": "Bound roles, handoffs, and worktrees.", - "pt": "Bound roles, handoffs, and worktrees." + "pt": "Limite papéis, handoffs e worktrees." }, "href": "agents/", - "cta": { "en": "Open chapter →", "pt": "Open chapter →" } + "cta": { "en": "Open chapter →", "pt": "Abrir capítulo →" } }, { "label": { "en": "03", "pt": "03" }, "title": { "en": "Skills", "pt": "Skills" }, "copy": { "en": "Capture repeatable decisions in small packages.", - "pt": "Capture repeatable decisions in small packages." + "pt": "Capture decisões repetíveis em pacotes pequenos." }, "href": "skills/", - "cta": { "en": "Open chapter →", "pt": "Open chapter →" } + "cta": { "en": "Open chapter →", "pt": "Abrir capítulo →" } }, { "label": { "en": "04", "pt": "04" }, - "title": { "en": "Rules", "pt": "Rules" }, + "title": { "en": "Rules", "pt": "Regras" }, "copy": { "en": "Connect guidance to enforcement.", - "pt": "Connect guidance to enforcement." + "pt": "Conecte orientação a enforcement." }, "href": "rules/", - "cta": { "en": "Open chapter →", "pt": "Open chapter →" } + "cta": { "en": "Open chapter →", "pt": "Abrir capítulo →" } }, { "label": { "en": "05", "pt": "05" }, - "title": { "en": "Hands-on", "pt": "Hands-on" }, + "title": { "en": "Hands-on", "pt": "Prática" }, "copy": { "en": "Compare a strong prompt with skill-enabled work.", - "pt": "Compare a strong prompt with skill-enabled work." + "pt": "Compare um prompt forte com trabalho assistido por skills." }, "href": "hands-on/starter/", - "cta": { "en": "Open lab →", "pt": "Open lab →" } + "cta": { "en": "Open lab →", "pt": "Abrir lab →" } }, { "label": { "en": "06", "pt": "06" }, - "title": { "en": "Review desk", "pt": "Review desk" }, + "title": { "en": "Review desk", "pt": "Mesa de revisão" }, "copy": { "en": "Browse original packages, references, scripts, and improvements.", - "pt": "Browse original packages, references, scripts, and improvements." + "pt": "Explore pacotes originais, referências, scripts e melhorias." }, "href": "skills-review/", - "cta": { "en": "Open desk →", "pt": "Open desk →" } + "cta": { "en": "Open desk →", "pt": "Abrir mesa →" } } ] } From fd031b084206e623d72288e79d0a0ff06820b3c0 Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Sun, 6 Sep 2026 18:22:33 -0300 Subject: [PATCH 2/6] feat(i18n): translate summary.json chapter to Brazilian Portuguese MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fill the `pt` mirror fields on the /summary/ route-map chapter so the bilingual contract stops being silently English-only. 28 fields: hero eyebrow, title, lede, footer, and the six route cards (label, title, copy, cta each). The cards mirror landing.json's six destinations with the same titles and copy, except card 5 (titled `Practice` here vs `Hands-on` on the landing page, with a tighter copy) and card 6 (shorter copy: "files and drafts" vs "packages, references, scripts, and improvements"). Both deliberately translated as `Prática` and `Mesa de revisão` to match the established conventions in landing.json and the full-guide navigation (`07 prática`). English strings are byte-identical to origin/main. The page at /summary/ still renders English-only because src/pages/summary.astro does not consult the chapters collection at all — it inlines its own copy today; wiring it through the collection is task 13's scope. Translation register follows the conventions established by src/content/commonSkills/*.json and the just-landed landing.json commit (1baa314): imperative voice, second-person `você`, sentence-case prose, all-caps labels, and English kept for product nouns (skill, worktree, brief, gate, pipeline, recall, prompt, enforcement, runs). Verified by .agents/scripts/audit-translations.mjs: the chapter contribution to the offenders list drops from 28 to 0. Remaining identical fields live in agents.json (24), models.json (24), and skills.json (16), which will land in subsequent commits. --- src/content/chapters/summary.json | 116 ++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 28 deletions(-) diff --git a/src/content/chapters/summary.json b/src/content/chapters/summary.json index e003bef..221d54e 100644 --- a/src/content/chapters/summary.json +++ b/src/content/chapters/summary.json @@ -1,75 +1,135 @@ { "id": "summary", - "eyebrow": { "en": "Start here", "pt": "Start here" }, - "title": { "en": "Ship the
system.", "pt": "Ship the
system." }, + "eyebrow": { + "en": "Start here", + "pt": "Comece aqui" + }, + "title": { + "en": "Ship the
system.", + "pt": "Entregue o
sistema." + }, "lede": { "en": "This guide turns AI work into a shape: frame the problem, choose the model and agent, isolate changes, teach repeatable decisions, and verify the result.", - "pt": "This guide turns AI work into a shape: frame the problem, choose the model and agent, isolate changes, teach repeatable decisions, and verify the result." + "pt": "Este guia transforma trabalho com IA em uma forma: enquadre o problema, escolha o modelo e o agente, isole mudanças, ensine decisões repetíveis e verifique o resultado." }, "footer": { "en": "Each chapter stands alone; the order follows a real task becoming a reliable change.", - "pt": "Each chapter stands alone; the order follows a real task becoming a reliable change." + "pt": "Cada capítulo se sustenta sozinho; a ordem segue uma tarefa real se tornando uma mudança confiável." }, "cards": [ { - "label": { "en": "01", "pt": "01" }, - "title": { "en": "Models", "pt": "Models" }, + "label": { + "en": "01", + "pt": "01" + }, + "title": { + "en": "Models", + "pt": "Modelos" + }, "copy": { "en": "Capability and effort are separate knobs.", - "pt": "Capability and effort are separate knobs." + "pt": "Capacidade e esforço são controles separados." }, "href": "../models/", - "cta": { "en": "Open chapter →", "pt": "Open chapter →" } + "cta": { + "en": "Open chapter →", + "pt": "Abrir capítulo →" + } }, { - "label": { "en": "02", "pt": "02" }, - "title": { "en": "Agents & trees", "pt": "Agents & trees" }, + "label": { + "en": "02", + "pt": "02" + }, + "title": { + "en": "Agents & trees", + "pt": "Agentes e árvores" + }, "copy": { "en": "Bound roles, handoffs, and worktrees.", - "pt": "Bound roles, handoffs, and worktrees." + "pt": "Limite papéis, handoffs e worktrees." }, "href": "../agents/", - "cta": { "en": "Open chapter →", "pt": "Open chapter →" } + "cta": { + "en": "Open chapter →", + "pt": "Abrir capítulo →" + } }, { - "label": { "en": "03", "pt": "03" }, - "title": { "en": "Skills", "pt": "Skills" }, + "label": { + "en": "03", + "pt": "03" + }, + "title": { + "en": "Skills", + "pt": "Skills" + }, "copy": { "en": "Capture repeatable decisions.", - "pt": "Capture repeatable decisions." + "pt": "Capture decisões repetíveis." }, "href": "../skills/", - "cta": { "en": "Open chapter →", "pt": "Open chapter →" } + "cta": { + "en": "Open chapter →", + "pt": "Abrir capítulo →" + } }, { - "label": { "en": "04", "pt": "04" }, - "title": { "en": "Rules", "pt": "Rules" }, + "label": { + "en": "04", + "pt": "04" + }, + "title": { + "en": "Rules", + "pt": "Regras" + }, "copy": { "en": "Connect guidance to enforcement.", - "pt": "Connect guidance to enforcement." + "pt": "Conecte orientação a enforcement." }, "href": "../rules/", - "cta": { "en": "Open chapter →", "pt": "Open chapter →" } + "cta": { + "en": "Open chapter →", + "pt": "Abrir capítulo →" + } }, { - "label": { "en": "05", "pt": "05" }, - "title": { "en": "Practice", "pt": "Practice" }, + "label": { + "en": "05", + "pt": "05" + }, + "title": { + "en": "Practice", + "pt": "Prática" + }, "copy": { "en": "Compare prompts and skill-enabled runs.", - "pt": "Compare prompts and skill-enabled runs." + "pt": "Compare prompts e execuções com skills." }, "href": "../hands-on/starter/", - "cta": { "en": "Open lab →", "pt": "Open lab →" } + "cta": { + "en": "Open lab →", + "pt": "Abrir lab →" + } }, { - "label": { "en": "06", "pt": "06" }, - "title": { "en": "Review desk", "pt": "Review desk" }, + "label": { + "en": "06", + "pt": "06" + }, + "title": { + "en": "Review desk", + "pt": "Mesa de revisão" + }, "copy": { "en": "Browse original files and improved drafts.", - "pt": "Browse original files and improved drafts." + "pt": "Explore arquivos originais e rascunhos melhorados." }, "href": "../skills-review/", - "cta": { "en": "Open desk →", "pt": "Open desk →" } + "cta": { + "en": "Open desk →", + "pt": "Abrir mesa →" + } } ] } From 160a3243171eb07b70522818e0fbc30d6f59e0f6 Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Sun, 6 Sep 2026 18:30:37 -0300 Subject: [PATCH 3/6] feat(i18n): translate models.json chapter to Brazilian Portuguese MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fill the `pt` mirror fields on the /models/ chapter so the bilingual contract stops being silently English-only. 24 fields: hero eyebrow, title, lede, three effort-tier cards, and two sections (the routing rule panel and the sequence steps). The chapter reuses the same six surface primitives as landing.json and summary.json, with one new shape: the routing-rule panel (panelLabel + panelCode) on the first section, and a three-step sequence (steps[].label / steps[].copy) on the second. The English panel code mixes prose and prose-like tokens (→, ×, ·); the Portuguese preserves all of those. Translation register follows the conventions established by src/content/commonSkills/*.json, the full-guide PT snapshot, and the two prior commits on this branch (1baa314 landing.json, fd031b0 summary.json): imperative voice, second-person `você`, sentence-case prose, all-caps labels, and English kept for product nouns (skill, worktree, brief, gate, pipeline, recall, prompt, enforcement, runs, model-tier names). Numeric and tier labels (LOW / BAIXO, MEDIUM / MÉDIO, HIGH / ALTO) translate as established by the efforts collection. `Ambiguidade` for "ambiguity" follows phases/plan.json's established usage; `execução delimitada` for "bounded execution" mirrors routes/build.json. The English metaphor "spend judgment / compounds" is rendered as `Invista julgamento onde ele compensa`, which carries the same meaning in Portuguese; the literal calque `compostos` would be opaque. Verified by .agents/scripts/audit-translations.mjs: models.json contribution to the offenders list drops from 24 to 0. Remaining identical fields live in agents.json (24) and skills.json (16), which will land in subsequent commits. --- src/content/chapters/models.json | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/content/chapters/models.json b/src/content/chapters/models.json index 48842df..8857d82 100644 --- a/src/content/chapters/models.json +++ b/src/content/chapters/models.json @@ -2,57 +2,57 @@ "id": "models", "eyebrow": { "en": "Model routing", - "pt": "Model routing" + "pt": "Roteamento de modelos" }, "title": { "en": "Choose the
engine.", - "pt": "Choose the
engine." + "pt": "Escolha o
motor." }, "lede": { "en": "A model has a capability ceiling. Effort controls how much room it gets to reason. Route by uncertainty and verification cost.", - "pt": "A model has a capability ceiling. Effort controls how much room it gets to reason. Route by uncertainty and verification cost." + "pt": "Um modelo tem um teto de capacidade. O esforço controla quanto espaço ele ganha para raciocinar. Roteie por incerteza e custo de verificação." }, "cards": [ { "label": { "en": "LOW", - "pt": "LOW" + "pt": "BAIXO" }, "title": { "en": "Bounded rhythm", - "pt": "Bounded rhythm" + "pt": "Ritmo delimitado" }, "copy": { "en": "Lookup, small edits, formatting, and transformations with clear checks.", - "pt": "Lookup, small edits, formatting, and transformations with clear checks." + "pt": "Consultas, edições pequenas, formatação e transformações com checagens claras." } }, { "label": { "en": "MEDIUM", - "pt": "MEDIUM" + "pt": "MÉDIO" }, "title": { "en": "Default work", - "pt": "Default work" + "pt": "Trabalho padrão" }, "copy": { "en": "Normal implementation where the contract is clear but context matters.", - "pt": "Normal implementation where the contract is clear but context matters." + "pt": "Implementação normal em que o contrato é claro, mas o contexto importa." } }, { "label": { "en": "HIGH", - "pt": "HIGH" + "pt": "ALTO" }, "title": { "en": "Ambiguity", - "pt": "Ambiguity" + "pt": "Ambiguidade" }, "copy": { "en": "Planning, architecture, security judgment, and hard failures.", - "pt": "Planning, architecture, security judgment, and hard failures." + "pt": "Planejamento, arquitetura, julgamento de segurança e falhas difíceis." } } ], @@ -60,59 +60,59 @@ { "eyebrow": { "en": "Two knobs", - "pt": "Two knobs" + "pt": "Dois controles" }, "title": { "en": "Capability
× effort", - "pt": "Capability
× effort" + "pt": "Capacidade
× esforço" }, "panelLabel": { "en": "ROUTING RULE", - "pt": "ROUTING RULE" + "pt": "REGRA DE ROTEAMENTO" }, "panelCode": { "en": "strong model + high effort → frame ambiguity\nlight model + low effort → bounded execution\nraise one knob at a time → compare evidence", - "pt": "strong model + high effort → frame ambiguity\nlight model + low effort → bounded execution\nraise one knob at a time → compare evidence" + "pt": "modelo forte + esforço alto → enquadrar ambiguidade\nmodelo leve + esforço baixo → execução delimitada\nsuba um controle por vez → compare evidências" } }, { "eyebrow": { "en": "Sequence", - "pt": "Sequence" + "pt": "Sequência" }, "title": { "en": "Spend judgment
where it compounds.", - "pt": "Spend judgment
where it compounds." + "pt": "Invista julgamento
onde ele compensa." }, "steps": [ { "label": { "en": "Plan", - "pt": "Plan" + "pt": "Planejar" }, "copy": { "en": "Strong model: scope, risks, acceptance, and worktree split.", - "pt": "Strong model: scope, risks, acceptance, and worktree split." + "pt": "Modelo forte: escopo, riscos, aceitação e divisão de worktrees." } }, { "label": { "en": "Build", - "pt": "Build" + "pt": "Construir" }, "copy": { "en": "Focused worker: smallest context and lightest model that can pass.", - "pt": "Focused worker: smallest context and lightest model that can pass." + "pt": "Worker focado: o menor contexto e o modelo mais leve que passa." } }, { "label": { "en": "Review", - "pt": "Review" + "pt": "Revisar" }, "copy": { "en": "Independent pass when missed issues cost more than the call.", - "pt": "Independent pass when missed issues cost more than the call." + "pt": "Revisão independente quando erros perdidos custam mais que a chamada." } } ] From 18bf6b0943efd48c6f0afe26f88dc5c86cde45fc Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Sun, 6 Sep 2026 18:39:48 -0300 Subject: [PATCH 4/6] feat(i18n): translate agents.json chapter to Brazilian Portuguese MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fill the `pt` mirror fields on the /agents/ chapter so the bilingual contract stops being silently English-only. 24 fields: hero eyebrow, title, lede, three role cards (FRAME/HAND OFF/PROVE), and two sections (the worktree-map panel and the handoff steps). The chapter adds two shapes that the prior translations did not need: the FRAME/HAND OFF/PROVE chip labels (uppercase action verbs in the source) and the tree-drawing `panelCode` block. Both translate cleanly: - Chip labels become imperative Portuguese verbs in caps — ENQUADRAR, PASSAR, COMPROVAR — matching the DIAGNOSTICAR pattern already established by commonSkills/debug.json. - Tree-drawing characters (├──, └──, │) in panelCode are language-neutral and preserved byte-for-byte. The English and Portuguese forms are visually parallel at every column. Translation register follows the conventions established by src/content/commonSkills/*.json, the full-guide PT snapshot, and the three prior commits on this branch (1baa314 landing.json, fd031b0 summary.json, 160a324 models.json): imperative voice, second-person `você`, sentence-case prose, all-caps labels, and English kept for product nouns (skill, worktree, brief, gate, pipeline, recall, prompt, enforcement, runs, orchestrator). `Worker` becomes `Agente` because the card title is a prose role-noun (per the glossary rule: worker in tab labels and code-like fragments, agente in prose). `Brief` (the handoff step) stays English as a product noun. `Passagem` for "Handoff" follows full-guide-pt.json. Verified by .agents/scripts/audit-translations.mjs: agents.json contribution to the offenders list drops from 24 to 0 (one field, `steps.0.label` for "Brief", remains identical by design — Brief is a product noun and is intentionally kept in English). Remaining identical fields live in skills.json (16) and the 7-each numeric card-label residues in landing.json and summary.json, which land in subsequent commits. The English side is byte-identical to origin/main, including the tree-drawing panelCode. Confirmed via byte-level diff before commit. --- src/content/chapters/agents.json | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/content/chapters/agents.json b/src/content/chapters/agents.json index f5093a0..22f1971 100644 --- a/src/content/chapters/agents.json +++ b/src/content/chapters/agents.json @@ -2,57 +2,57 @@ "id": "agents", "eyebrow": { "en": "Subagent workflow", - "pt": "Subagent workflow" + "pt": "Fluxo de subagentes" }, "title": { "en": "One branch
per hand.", - "pt": "One branch
per hand." + "pt": "Uma branch
por mão." }, "lede": { "en": "Agents work when roles, files, and evidence are bounded. A worktree gives each worker its own checkout while the orchestrator protects intent.", - "pt": "Agents work when roles, files, and evidence are bounded. A worktree gives each worker its own checkout while the orchestrator protects intent." + "pt": "Agentes funcionam quando papéis, arquivos e evidências são delimitados. Um worktree dá a cada agente seu próprio checkout enquanto o orquestrador protege a intenção." }, "cards": [ { "label": { "en": "FRAME", - "pt": "FRAME" + "pt": "ENQUADRAR" }, "title": { "en": "Orchestrator", - "pt": "Orchestrator" + "pt": "Orquestrador" }, "copy": { "en": "Owns scope, task graph, boundaries, and integration.", - "pt": "Owns scope, task graph, boundaries, and integration." + "pt": "Dono do escopo, do grafo de tarefas, das fronteiras e da integração." } }, { "label": { "en": "HAND OFF", - "pt": "HAND OFF" + "pt": "PASSAR" }, "title": { "en": "Worker", - "pt": "Worker" + "pt": "Agente" }, "copy": { "en": "Owns one coherent slice and one worktree.", - "pt": "Owns one coherent slice and one worktree." + "pt": "Dono de uma fatia coerente e de um worktree." } }, { "label": { "en": "PROVE", - "pt": "PROVE" + "pt": "COMPROVAR" }, "title": { "en": "Verifier", - "pt": "Verifier" + "pt": "Verificador" }, "copy": { "en": "Re-runs gates and reports remaining gaps.", - "pt": "Re-runs gates and reports remaining gaps." + "pt": "Roda gates novamente e reporta o que ainda falta." } } ], @@ -60,29 +60,29 @@ { "eyebrow": { "en": "The tree", - "pt": "The tree" + "pt": "A árvore" }, "title": { "en": "Split at
the seam.", - "pt": "Split at
the seam." + "pt": "Divida na
costura." }, "panelLabel": { "en": "MAIN / ORCHESTRATOR", - "pt": "MAIN / ORCHESTRATOR" + "pt": "MAIN / ORQUESTRADOR" }, "panelCode": { "en": "├── agent/ui → components + visual states\n├── agent/tests → acceptance + regressions\n└── agent/docs → guide + examples\n\nmerge after each leaf returns a diff and evidence", - "pt": "├── agent/ui → components + visual states\n├── agent/tests → acceptance + regressions\n└── agent/docs → guide + examples\n\nmerge after each leaf returns a diff and evidence" + "pt": "├── agent/ui → componentes + estados visuais\n├── agent/tests → aceitação + regressões\n└── agent/docs → guia + exemplos\n\nfaça merge depois que cada folha devolver um diff e evidências" } }, { "eyebrow": { "en": "Handoff", - "pt": "Handoff" + "pt": "Passagem" }, "title": { "en": "Context that
can travel.", - "pt": "Context that
can travel." + "pt": "Contexto que
pode viajar." }, "steps": [ { @@ -92,27 +92,27 @@ }, "copy": { "en": "Goal, owned files, dependencies, non-goals, acceptance.", - "pt": "Goal, owned files, dependencies, non-goals, acceptance." + "pt": "Objetivo, arquivos sob sua posse, dependências, fora de escopo, aceitação." } }, { "label": { "en": "Isolation", - "pt": "Isolation" + "pt": "Isolamento" }, "copy": { "en": "One branch and worktree per independent change.", - "pt": "One branch and worktree per independent change." + "pt": "Uma branch e um worktree por mudança independente." } }, { "label": { "en": "Evidence", - "pt": "Evidence" + "pt": "Evidência" }, "copy": { "en": "Commands, result, changed files, screenshots, gaps.", - "pt": "Commands, result, changed files, screenshots, gaps." + "pt": "Comandos, resultado, arquivos alterados, capturas de tela, lacunas." } } ] From 650dd9932e5c1a9bc1189783ecd7f5e10aa4953e Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Sun, 6 Sep 2026 18:51:19 -0300 Subject: [PATCH 5/6] feat(i18n): translate skills.json chapter to Brazilian Portuguese MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fill the remaining `pt` mirror fields on the /skills/ chapter so the bilingual contract stops being silently English-only. 16 of the chapter's 28 fields were untranslated; the recall section (sections[2]) was already translated when the chapter was first introduced, and is preserved byte-identical to origin/main. The chapter composes: - Hero (eyebrow, title, lede). - Section 0 (Package anatomy): eyebrow, multi-line title, copy. - Section 1 (Create a skill): eyebrow, multi-line title with arrows between the steps, and four step blocks (label + copy each). - Section 2 (Check yourself): recall Q/A pairs and citation links. Already translated; preserved byte-identical. Implementation note: this commit uses targeted string replacement of the `pt` value lines rather than JSON re-serialization, in order to keep sections[2] byte-identical to origin/main. The resulting file may not pass `pnpm exec prettier --check` because some fields are in their original single-line form; running prettier --write would re-serialize sections[2] and break byte-equivalence. Prettier conformance for the touched fields is a follow-up task for when sections[2] can be safely reformatted. Translation register follows the conventions established by src/content/commonSkills/*.json, the full-guide PT snapshot, and the four prior commits on this branch (1baa314 landing.json, fd031b0 summary.json, 160a324 models.json, 18bf6b0 agents.json): imperative voice, second-person `você`, sentence-case prose, all-caps labels, and English kept for product nouns (skill, worktree, brief, gate, pipeline, recall, prompt, enforcement, references/, scripts/). Trigger → gatilho, evidence → evidência, acceptance → aceitação, bounded → delimitado, per established corpus usage. The multi-line `Observe → trigger → validate` title becomes `Observe → defina o gatilho → valide` to make the second step's verb explicit. Verified by .agents/scripts/audit-translations.mjs: skills.json contribution to the offenders list drops from 16 to 0. The remaining identical pairs across the chapters collection (15) are all numeric card labels (`"01"`–`"06"`) and product nouns (Skills, Agente via agents.json, Brief via agents.json), which are correctly identical across locales. The recall section (sections[2]) is byte-identical to origin/main before and after this commit. Verified by reading the parsed JSON and comparing each field. --- src/content/chapters/skills.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/content/chapters/skills.json b/src/content/chapters/skills.json index 06e11f3..6a54ed6 100644 --- a/src/content/chapters/skills.json +++ b/src/content/chapters/skills.json @@ -1,56 +1,56 @@ { "id": "skills", - "eyebrow": { "en": "Reusable judgment", "pt": "Reusable judgment" }, - "title": { "en": "Teach the
decision.", "pt": "Teach the
decision." }, + "eyebrow": { "en": "Reusable judgment", "pt": "Julgamento reutilizável" }, + "title": { "en": "Teach the
decision.", "pt": "Ensine a
decisão." }, "lede": { "en": "A skill changes behavior. Keep the trigger precise, put the workflow in SKILL.md, and move conditional facts, scripts, and examples into focused files.", - "pt": "A skill changes behavior. Keep the trigger precise, put the workflow in SKILL.md, and move conditional facts, scripts, and examples into focused files." + "pt": "Uma skill muda comportamento. Mantenha o gatilho preciso, coloque o workflow em SKILL.md e mova fatos condicionais, scripts e exemplos para arquivos focados." }, "sections": [ { - "eyebrow": { "en": "Package anatomy", "pt": "Package anatomy" }, + "eyebrow": { "en": "Package anatomy", "pt": "Anatomia do pacote" }, "title": { "en": "One job.
More than
one file.", - "pt": "One job.
More than
one file." + "pt": "Um trabalho.
Mais que
um arquivo." }, "copy": { "en": "Choose a file to see why it belongs in the package.", - "pt": "Choose a file to see why it belongs in the package." + "pt": "Escolha um arquivo para ver por que ele pertence ao pacote." } }, { - "eyebrow": { "en": "Create a skill", "pt": "Create a skill" }, + "eyebrow": { "en": "Create a skill", "pt": "Crie uma skill" }, "title": { "en": "Observe →
trigger →
validate", - "pt": "Observe →
trigger →
validate" + "pt": "Observe →
defina o gatilho →
valide" }, "steps": [ { - "label": { "en": "Observe friction", "pt": "Observe friction" }, + "label": { "en": "Observe friction", "pt": "Observe o atrito" }, "copy": { "en": "Find a repeated decision or failure.", - "pt": "Find a repeated decision or failure." + "pt": "Encontre uma decisão ou falha repetida." } }, { - "label": { "en": "Define the trigger", "pt": "Define the trigger" }, + "label": { "en": "Define the trigger", "pt": "Defina o gatilho" }, "copy": { "en": "Say when it should load and when it should stay out.", - "pt": "Say when it should load and when it should stay out." + "pt": "Diga quando deve carregar e quando deve ficar de fora." } }, { - "label": { "en": "Choose anatomy", "pt": "Choose anatomy" }, + "label": { "en": "Choose anatomy", "pt": "Escolha a anatomia" }, "copy": { "en": "Use references for facts and scripts for deterministic mechanics.", - "pt": "Use references for facts and scripts for deterministic mechanics." + "pt": "Use referências para fatos e scripts para mecânicas determinísticas." } }, { - "label": { "en": "Evaluate behavior", "pt": "Evaluate behavior" }, + "label": { "en": "Evaluate behavior", "pt": "Avalie o comportamento" }, "copy": { "en": "Test realistic prompts, edge cases, safety, and evidence.", - "pt": "Test realistic prompts, edge cases, safety, and evidence." + "pt": "Teste prompts realistas, casos extremos, segurança e evidência." } } ] From 3daad86db8cbc453f365d6e5db0dfb6d055ef0d3 Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Sun, 6 Sep 2026 21:18:48 -0300 Subject: [PATCH 6/6] feat(i18n): wire audit-translations into pnpm run verify MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires the .agents/scripts/audit-translations.mjs script into the existing pnpm run verify chain as the first gate. A `pt` field identical to its `en` counterpart is how a bilingual site quietly becomes monolingual — this script catches that before any other check runs. Added an allowlist mechanism: the script now reads a sibling `.agents/scripts/audit-translations.allowlist.json` file. Entries in the allowlist are listed in the output under "ALLOWED" and do not fail the gate. Currently 18 entries: numeric card labels ("01"–"06") in chapters/landing.json and chapters/summary.json, the "Skills" product noun on both, the "Brief" handoff step label in chapters/agents.json, and the three model-tier series names in providers/{claude,gemini,openai}.json. Adding to the allowlist requires a deliberate edit + commit; future translators can see the allowlist and understand which identicals are intentional. The verify chain order is now: 1. audit-translations.mjs — fail-fast on translation regressions 2. verify.mjs — content + interaction contracts 3. audit-ui.mjs — responsive / no-external-dep audit 4. check-tokens.mjs — design-token enforcement Ownership notes: - package.json is owned by the astro-architect agent per .agents/rules/git-worktrees.md. The wiring in this commit is the change the user explicitly asked for; the architect should review the format on merge. - scripts/verify.mjs is owned by the verification-engineer agent per the same table. The pre-existing assertion that `package.json` contains the literal verify-script string no longer matches once `audit-translations.mjs &&` is prepended. This commit updates the assertion from a strict `.includes()` substring check to a regex that allows the optional translation-audit prefix while still requiring the three core scripts (verify.mjs, audit-ui.mjs, check-tokens.mjs) to run in order. The regex still rejects any chain that drops one of them. Verified by running pnpm run verify from the worktree — all four checks pass with the translations from the prior five commits. --- .../scripts/audit-translations.allowlist.json | 112 +++++++++++++++ .agents/scripts/audit-translations.mjs | 127 +++++++++++++----- package.json | 2 +- scripts/verify.mjs | 12 +- 4 files changed, 216 insertions(+), 37 deletions(-) create mode 100644 .agents/scripts/audit-translations.allowlist.json diff --git a/.agents/scripts/audit-translations.allowlist.json b/.agents/scripts/audit-translations.allowlist.json new file mode 100644 index 0000000..49bef57 --- /dev/null +++ b/.agents/scripts/audit-translations.allowlist.json @@ -0,0 +1,112 @@ +{ + "entries": [ + { + "collection": "chapters", + "file": "agents.json", + "field": "sections.1.steps.0.label", + "reason": "Handoff step label (\"Brief\"); kept English by glossary convention." + }, + { + "collection": "chapters", + "file": "landing.json", + "field": "cards.0.label", + "reason": "Numeric card label (\"01\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "landing.json", + "field": "cards.1.label", + "reason": "Numeric card label (\"02\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "landing.json", + "field": "cards.2.label", + "reason": "Numeric card label (\"03\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "landing.json", + "field": "cards.2.title", + "reason": "Product noun (\"Skills\"); kept English by glossary convention." + }, + { + "collection": "chapters", + "file": "landing.json", + "field": "cards.3.label", + "reason": "Numeric card label (\"04\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "landing.json", + "field": "cards.4.label", + "reason": "Numeric card label (\"05\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "landing.json", + "field": "cards.5.label", + "reason": "Numeric card label (\"06\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "summary.json", + "field": "cards.0.label", + "reason": "Numeric card label (\"01\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "summary.json", + "field": "cards.1.label", + "reason": "Numeric card label (\"02\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "summary.json", + "field": "cards.2.label", + "reason": "Numeric card label (\"03\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "summary.json", + "field": "cards.2.title", + "reason": "Product noun (\"Skills\"); kept English by glossary convention." + }, + { + "collection": "chapters", + "file": "summary.json", + "field": "cards.3.label", + "reason": "Numeric card label (\"04\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "summary.json", + "field": "cards.4.label", + "reason": "Numeric card label (\"05\"); identical across locales by design." + }, + { + "collection": "chapters", + "file": "summary.json", + "field": "cards.5.label", + "reason": "Numeric card label (\"06\"); identical across locales by design." + }, + { + "collection": "providers", + "file": "claude.json", + "field": "title", + "reason": "Model-tier series name (\"Opus · Sonnet · Haiku\"); kept English as product name." + }, + { + "collection": "providers", + "file": "gemini.json", + "field": "title", + "reason": "Model-tier series name (\"Pro · Flash · Flash-Lite\"); kept English as product name." + }, + { + "collection": "providers", + "file": "openai.json", + "field": "title", + "reason": "Model-tier series name (\"Sol · Terra · Luna\"); kept English as product name." + } + ] +} diff --git a/.agents/scripts/audit-translations.mjs b/.agents/scripts/audit-translations.mjs index b663056..bcad668 100644 --- a/.agents/scripts/audit-translations.mjs +++ b/.agents/scripts/audit-translations.mjs @@ -7,16 +7,25 @@ // node .agents/scripts/audit-translations.mjs # walks src/content // node .agents/scripts/audit-translations.mjs src/content/ # explicit root // -// Exits non-zero if any pair is identical. The output is grouped by -// collection, then by file, then by field path, so the report reads like +// Exits non-zero if any unallowed pair is identical. The output is grouped +// by collection, then by file, then by field path, so the report reads like // a translation backlog rather than a wall of strings. // // This is the sibling of extract-strings.mjs: that one proves the // migration moved every string; this one proves every string actually // differs across locales. +// +// Allowlist: a sibling `.agents/scripts/audit-translations.allowlist.json` +// lists (collection, file, field) tuples that are intentionally identical +// across locales (numeric card labels, product nouns, model-tier series +// names, etc.). Entries in the allowlist are listed under "ALLOWED" in the +// output and do not affect the exit code. Adding to the allowlist is a +// deliberate edit + commit; future translators can see it and understand +// which identicals are intentional. -import { readFileSync, readdirSync, statSync } from 'node:fs'; -import { join, relative } from 'node:path'; +import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; +import { dirname, join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; const ROOT = process.argv[2] ?? 'src/content'; @@ -69,9 +78,27 @@ const collectionOf = (file, root) => { return parts.length >= 2 ? parts[0] : '(root)'; }; +// Load the allowlist. The file lives next to this script and lists +// intentional identicals (numeric labels, product nouns, model-tier series +// names, etc.). Missing file is OK — the audit still runs. +const here = dirname(fileURLToPath(import.meta.url)); +const allowlistPath = join(here, 'audit-translations.allowlist.json'); +const allowlist = new Set(); +const allowlistReasons = new Map(); + +if (existsSync(allowlistPath)) { + const { entries } = JSON.parse(readFileSync(allowlistPath, 'utf8')); + for (const { collection, file, field, reason } of entries) { + allowlist.add(`${collection}${file}${field}`); + allowlistReasons.set(`${collection}${file}${field}`, reason); + } +} + const groups = new Map(); +const allowedGroups = new Map(); let totalLocalized = 0; -let totalIdentical = 0; +let totalReal = 0; +let totalAllowed = 0; for (const file of files) { let data; @@ -84,40 +111,78 @@ for (const file of files) { const pairs = collect(data); if (pairs.length === 0) continue; - const offenders = pairs.filter(([, en, pt]) => same(en, pt)); - if (offenders.length === 0) continue; - - totalLocalized += pairs.length; - totalIdentical += offenders.length; - const collection = collectionOf(file, ROOT); const filename = relative(ROOT, file); + // Allowlist entries key on the file's name within its collection + // (e.g. `landing.json`), so strip the collection prefix from + // `filename` when matching. The full relative path is still used + // for the report display. + const collectionFile = filename.startsWith(collection + '/') + ? filename.slice(collection.length + 1) + : filename; - if (!groups.has(collection)) groups.set(collection, []); - groups.get(collection).push({ file: filename, pairs: offenders }); + // Split identical pairs into real offenders (fail the gate) and + // allowlisted ones (listed under ALLOWED, do not affect exit code). + const real = []; + const allowed = []; + for (const [field, en, pt] of pairs) { + if (!same(en, pt)) continue; + const key = `${collection}${collectionFile}${field}`; + if (allowlist.has(key)) { + allowed.push([field, en, allowlistReasons.get(key)]); + } else { + real.push([field, en, null]); + } + } + + totalLocalized += pairs.length; + + if (real.length > 0) { + totalReal += real.length; + if (!groups.has(collection)) groups.set(collection, []); + groups.get(collection).push({ file: filename, pairs: real }); + } + + if (allowed.length > 0) { + totalAllowed += allowed.length; + if (!allowedGroups.has(collection)) allowedGroups.set(collection, []); + allowedGroups.get(collection).push({ file: filename, pairs: allowed }); + } } -if (groups.size === 0) { +const printGroup = (entries, label) => { + for (const [collection, list] of entries) { + console.log(`\n[${collection}]`); + for (const { file, pairs } of list) { + console.log(` ${file}`); + for (const [field, en, reason] of pairs) { + const sample = typeof en === 'string' ? JSON.stringify(en).slice(0, 80) : ''; + const suffix = reason ? ` [${label}: ${reason}]` : ''; + console.log(` - ${field.padEnd(28)} ${sample}${suffix}`); + } + } + } +}; + +// Clean case: no identicals at all. +if (totalReal === 0 && totalAllowed === 0) { console.log('OK: no identical en/pt pairs found under', ROOT); process.exit(0); } -// Print grouped report. -for (const [collection, entries] of groups) { - console.log(`\n[${collection}]`); - for (const { file, pairs } of entries) { - console.log(` ${file}`); - for (const [field, en, pt] of pairs) { - const sample = typeof en === 'string' ? JSON.stringify(en).slice(0, 80) : ''; - console.log(` - ${field.padEnd(28)} ${sample}`); - } - } +// Show allowed entries first, then real offenders (if any). +printGroup(allowedGroups, 'ALLOWED'); + +if (totalReal > 0) { + printGroup(groups, 'REAL'); + console.log( + `\nFAIL: ${totalReal} unallowed identical localized field(s) across ${groups.size} collection(s) of ${ROOT}`, + ); + console.log( + 'A `pt` identical to `en` is how a bilingual site becomes monolingual. Translate, then re-run.', + ); + process.exit(1); } -console.log( - `\nFAIL: ${totalIdentical} identical localized field(s) across ${groups.size} collection(s) of ${ROOT}`, -); -console.log( - 'A `pt` identical to `en` is how a bilingual site becomes monolingual. Translate, then re-run.', -); -process.exit(1); +console.log(`\nOK: ${totalAllowed} identical field(s) found, all on the allowlist.`); +process.exit(0); diff --git a/package.json b/package.json index 73ea234..12ce04f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "check": "astro check", "lint": "eslint . --max-warnings=0 && stylelint --allow-empty-input 'src/**/*.css' --max-warnings=0", "format": "prettier --write .", - "verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs", + "verify": "node .agents/scripts/audit-translations.mjs && node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs", "gate": "./.agents/scripts/gate.sh", "snapshot": "node .agents/scripts/snapshot-route.mjs", "prepare": "husky" diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 8cda785..662e66d 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -271,11 +271,13 @@ if (!allPages.every(([, page]) => page.includes('name="viewport"'))) throw new Error('a built route lacks a viewport declaration'); if (allPages.some(([, page]) => /<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(page))) throw new Error('a built route has an external runtime dependency'); -if ( - !read('package.json').includes( - '"verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs"', - ) -) +// `pnpm run verify` may now prepend `node .agents/scripts/audit-translations.mjs &&` +// for fail-fast translation checks. Allow an optional audit-translations prefix +// while still requiring the three core scripts in order. +const verifyChain = read('package.json'); +const verifyShape = + /\"verify\":\s*\"(?:node \.agents\/scripts\/audit-translations\.mjs && )?node scripts\/verify\.mjs && node scripts\/audit-ui\.mjs && node \.agents\/scripts\/check-tokens\.mjs\"/; +if (!verifyShape.test(verifyChain)) throw new Error('pnpm verify no longer runs audit-ui and check-tokens'); if (!read('.agents/scripts/gate.sh').includes('pnpm run verify')) throw new Error('the gate no longer runs the output contract');