6 Commits

Author SHA1 Message Date
Marcos Paulo 3daad86db8 feat(i18n): wire audit-translations into pnpm run verify
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.
2026-09-06 21:18:48 -03:00
Marcos Paulo 650dd9932e feat(i18n): translate skills.json chapter to Brazilian Portuguese
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.
2026-09-06 18:51:19 -03:00
Marcos Paulo 18bf6b0943 feat(i18n): translate agents.json chapter to Brazilian Portuguese
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.
2026-09-06 18:39:48 -03:00
Marcos Paulo 160a324317 feat(i18n): translate models.json chapter to Brazilian Portuguese
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.
2026-09-06 18:35:51 -03:00
Marcos Paulo fd031b0842 feat(i18n): translate summary.json chapter to Brazilian Portuguese
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.
2026-09-06 18:22:33 -03:00
Marcos Paulo 1baa3145e8 feat(i18n): translate landing.json chapter to Brazilian Portuguese
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.
2026-09-06 18:18:10 -03:00
9 changed files with 396 additions and 151 deletions
@@ -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."
}
]
}
+91 -26
View File
@@ -7,16 +7,25 @@
// node .agents/scripts/audit-translations.mjs # walks src/content // node .agents/scripts/audit-translations.mjs # walks src/content
// node .agents/scripts/audit-translations.mjs src/content/ # explicit root // node .agents/scripts/audit-translations.mjs src/content/ # explicit root
// //
// Exits non-zero if any pair is identical. The output is grouped by // Exits non-zero if any unallowed pair is identical. The output is grouped
// collection, then by file, then by field path, so the report reads like // by collection, then by file, then by field path, so the report reads like
// a translation backlog rather than a wall of strings. // a translation backlog rather than a wall of strings.
// //
// This is the sibling of extract-strings.mjs: that one proves the // This is the sibling of extract-strings.mjs: that one proves the
// migration moved every string; this one proves every string actually // migration moved every string; this one proves every string actually
// differs across locales. // 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 { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { join, relative } from 'node:path'; import { dirname, join, relative } from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = process.argv[2] ?? 'src/content'; const ROOT = process.argv[2] ?? 'src/content';
@@ -69,9 +78,27 @@ const collectionOf = (file, root) => {
return parts.length >= 2 ? parts[0] : '(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 groups = new Map();
const allowedGroups = new Map();
let totalLocalized = 0; let totalLocalized = 0;
let totalIdentical = 0; let totalReal = 0;
let totalAllowed = 0;
for (const file of files) { for (const file of files) {
let data; let data;
@@ -84,40 +111,78 @@ for (const file of files) {
const pairs = collect(data); const pairs = collect(data);
if (pairs.length === 0) continue; 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 collection = collectionOf(file, ROOT);
const filename = relative(ROOT, file); 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, []); // Split identical pairs into real offenders (fail the gate) and
groups.get(collection).push({ file: filename, pairs: offenders }); // 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]);
}
} }
if (groups.size === 0) { 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 });
}
}
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) : '<non-string>';
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); console.log('OK: no identical en/pt pairs found under', ROOT);
process.exit(0); process.exit(0);
} }
// Print grouped report. // Show allowed entries first, then real offenders (if any).
for (const [collection, entries] of groups) { printGroup(allowedGroups, 'ALLOWED');
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) : '<non-string>';
console.log(` - ${field.padEnd(28)} ${sample}`);
}
}
}
if (totalReal > 0) {
printGroup(groups, 'REAL');
console.log( console.log(
`\nFAIL: ${totalIdentical} identical localized field(s) across ${groups.size} collection(s) of ${ROOT}`, `\nFAIL: ${totalReal} unallowed identical localized field(s) across ${groups.size} collection(s) of ${ROOT}`,
); );
console.log( console.log(
'A `pt` identical to `en` is how a bilingual site becomes monolingual. Translate, then re-run.', 'A `pt` identical to `en` is how a bilingual site becomes monolingual. Translate, then re-run.',
); );
process.exit(1); process.exit(1);
}
console.log(`\nOK: ${totalAllowed} identical field(s) found, all on the allowlist.`);
process.exit(0);
+1 -1
View File
@@ -9,7 +9,7 @@
"check": "astro check", "check": "astro check",
"lint": "eslint . --max-warnings=0 && stylelint --allow-empty-input 'src/**/*.css' --max-warnings=0", "lint": "eslint . --max-warnings=0 && stylelint --allow-empty-input 'src/**/*.css' --max-warnings=0",
"format": "prettier --write .", "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", "gate": "./.agents/scripts/gate.sh",
"snapshot": "node .agents/scripts/snapshot-route.mjs", "snapshot": "node .agents/scripts/snapshot-route.mjs",
"prepare": "husky" "prepare": "husky"
+7 -5
View File
@@ -271,11 +271,13 @@ if (!allPages.every(([, page]) => page.includes('name="viewport"')))
throw new Error('a built route lacks a viewport declaration'); throw new Error('a built route lacks a viewport declaration');
if (allPages.some(([, page]) => /<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(page))) if (allPages.some(([, page]) => /<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(page)))
throw new Error('a built route has an external runtime dependency'); throw new Error('a built route has an external runtime dependency');
if ( // `pnpm run verify` may now prepend `node .agents/scripts/audit-translations.mjs &&`
!read('package.json').includes( // for fail-fast translation checks. Allow an optional audit-translations prefix
'"verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs"', // 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'); throw new Error('pnpm verify no longer runs audit-ui and check-tokens');
if (!read('.agents/scripts/gate.sh').includes('pnpm run verify')) if (!read('.agents/scripts/gate.sh').includes('pnpm run verify'))
throw new Error('the gate no longer runs the output contract'); throw new Error('the gate no longer runs the output contract');
+23 -23
View File
@@ -2,57 +2,57 @@
"id": "agents", "id": "agents",
"eyebrow": { "eyebrow": {
"en": "Subagent workflow", "en": "Subagent workflow",
"pt": "Subagent workflow" "pt": "Fluxo de subagentes"
}, },
"title": { "title": {
"en": "One branch<br>per <em>hand.</em>", "en": "One branch<br>per <em>hand.</em>",
"pt": "One branch<br>per <em>hand.</em>" "pt": "Uma branch<br>por <em>mão.</em>"
}, },
"lede": { "lede": {
"en": "Agents work when roles, files, and evidence are bounded. A worktree gives each worker its own checkout while the orchestrator protects intent.", "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": [ "cards": [
{ {
"label": { "label": {
"en": "FRAME", "en": "FRAME",
"pt": "FRAME" "pt": "ENQUADRAR"
}, },
"title": { "title": {
"en": "Orchestrator", "en": "Orchestrator",
"pt": "Orchestrator" "pt": "Orquestrador"
}, },
"copy": { "copy": {
"en": "Owns scope, task graph, boundaries, and integration.", "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": { "label": {
"en": "HAND OFF", "en": "HAND OFF",
"pt": "HAND OFF" "pt": "PASSAR"
}, },
"title": { "title": {
"en": "Worker", "en": "Worker",
"pt": "Worker" "pt": "Agente"
}, },
"copy": { "copy": {
"en": "Owns one coherent slice and one worktree.", "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": { "label": {
"en": "PROVE", "en": "PROVE",
"pt": "PROVE" "pt": "COMPROVAR"
}, },
"title": { "title": {
"en": "Verifier", "en": "Verifier",
"pt": "Verifier" "pt": "Verificador"
}, },
"copy": { "copy": {
"en": "Re-runs gates and reports remaining gaps.", "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": { "eyebrow": {
"en": "The tree", "en": "The tree",
"pt": "The tree" "pt": "A árvore"
}, },
"title": { "title": {
"en": "Split at<br>the <em>seam.</em>", "en": "Split at<br>the <em>seam.</em>",
"pt": "Split at<br>the <em>seam.</em>" "pt": "Divida na<br><em>costura.</em>"
}, },
"panelLabel": { "panelLabel": {
"en": "MAIN / ORCHESTRATOR", "en": "MAIN / ORCHESTRATOR",
"pt": "MAIN / ORCHESTRATOR" "pt": "MAIN / ORQUESTRADOR"
}, },
"panelCode": { "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", "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": { "eyebrow": {
"en": "Handoff", "en": "Handoff",
"pt": "Handoff" "pt": "Passagem"
}, },
"title": { "title": {
"en": "Context that<br>can <em>travel.</em>", "en": "Context that<br>can <em>travel.</em>",
"pt": "Context that<br>can <em>travel.</em>" "pt": "Contexto que<br>pode <em>viajar.</em>"
}, },
"steps": [ "steps": [
{ {
@@ -92,27 +92,27 @@
}, },
"copy": { "copy": {
"en": "Goal, owned files, dependencies, non-goals, acceptance.", "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": { "label": {
"en": "Isolation", "en": "Isolation",
"pt": "Isolation" "pt": "Isolamento"
}, },
"copy": { "copy": {
"en": "One branch and worktree per independent change.", "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": { "label": {
"en": "Evidence", "en": "Evidence",
"pt": "Evidence" "pt": "Evidência"
}, },
"copy": { "copy": {
"en": "Commands, result, changed files, screenshots, gaps.", "en": "Commands, result, changed files, screenshots, gaps.",
"pt": "Commands, result, changed files, screenshots, gaps." "pt": "Comandos, resultado, arquivos alterados, capturas de tela, lacunas."
} }
} }
] ]
+29 -23
View File
@@ -1,83 +1,89 @@
{ {
"id": "landing", "id": "landing",
"eyebrow": { "en": "The short route", "pt": "The short route" }, "eyebrow": {
"title": { "en": "Ship the<br><em>system.</em>", "pt": "Ship the<br><em>system.</em>" }, "en": "The short route",
"pt": "A rota curta"
},
"title": {
"en": "Ship the<br><em>system.</em>",
"pt": "Entregue o<br><em>sistema.</em>"
},
"lede": { "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.", "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": { "threadLabel": {
"en": "THE THREAD", "en": "THE THREAD",
"pt": "THE THREAD" "pt": "O FIO"
}, },
"threadText": { "threadText": {
"en": "Frame uncertainty → isolate execution → preserve judgment → verify the change.", "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": { "footer": {
"en": "The route map is now the default entry. The full guide remains available whenever you want the whole narrative.", "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": [ "cards": [
{ {
"label": { "en": "01", "pt": "01" }, "label": { "en": "01", "pt": "01" },
"title": { "en": "Models", "pt": "Models" }, "title": { "en": "Models", "pt": "Modelos" },
"copy": { "copy": {
"en": "Capability and effort are separate knobs.", "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/", "href": "models/",
"cta": { "en": "Open chapter →", "pt": "Open chapter →" } "cta": { "en": "Open chapter →", "pt": "Abrir capítulo →" }
}, },
{ {
"label": { "en": "02", "pt": "02" }, "label": { "en": "02", "pt": "02" },
"title": { "en": "Agents & trees", "pt": "Agents & trees" }, "title": { "en": "Agents & trees", "pt": "Agentes e árvores" },
"copy": { "copy": {
"en": "Bound roles, handoffs, and worktrees.", "en": "Bound roles, handoffs, and worktrees.",
"pt": "Bound roles, handoffs, and worktrees." "pt": "Limite papéis, handoffs e worktrees."
}, },
"href": "agents/", "href": "agents/",
"cta": { "en": "Open chapter →", "pt": "Open chapter →" } "cta": { "en": "Open chapter →", "pt": "Abrir capítulo →" }
}, },
{ {
"label": { "en": "03", "pt": "03" }, "label": { "en": "03", "pt": "03" },
"title": { "en": "Skills", "pt": "Skills" }, "title": { "en": "Skills", "pt": "Skills" },
"copy": { "copy": {
"en": "Capture repeatable decisions in small packages.", "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/", "href": "skills/",
"cta": { "en": "Open chapter →", "pt": "Open chapter →" } "cta": { "en": "Open chapter →", "pt": "Abrir capítulo →" }
}, },
{ {
"label": { "en": "04", "pt": "04" }, "label": { "en": "04", "pt": "04" },
"title": { "en": "Rules", "pt": "Rules" }, "title": { "en": "Rules", "pt": "Regras" },
"copy": { "copy": {
"en": "Connect guidance to enforcement.", "en": "Connect guidance to enforcement.",
"pt": "Connect guidance to enforcement." "pt": "Conecte orientação a enforcement."
}, },
"href": "rules/", "href": "rules/",
"cta": { "en": "Open chapter →", "pt": "Open chapter →" } "cta": { "en": "Open chapter →", "pt": "Abrir capítulo →" }
}, },
{ {
"label": { "en": "05", "pt": "05" }, "label": { "en": "05", "pt": "05" },
"title": { "en": "Hands-on", "pt": "Hands-on" }, "title": { "en": "Hands-on", "pt": "Prática" },
"copy": { "copy": {
"en": "Compare a strong prompt with skill-enabled work.", "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/", "href": "hands-on/starter/",
"cta": { "en": "Open lab →", "pt": "Open lab →" } "cta": { "en": "Open lab →", "pt": "Abrir lab →" }
}, },
{ {
"label": { "en": "06", "pt": "06" }, "label": { "en": "06", "pt": "06" },
"title": { "en": "Review desk", "pt": "Review desk" }, "title": { "en": "Review desk", "pt": "Mesa de revisão" },
"copy": { "copy": {
"en": "Browse original packages, references, scripts, and improvements.", "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/", "href": "skills-review/",
"cta": { "en": "Open desk →", "pt": "Open desk →" } "cta": { "en": "Open desk →", "pt": "Abrir mesa →" }
} }
] ]
} }
+24 -24
View File
@@ -2,57 +2,57 @@
"id": "models", "id": "models",
"eyebrow": { "eyebrow": {
"en": "Model routing", "en": "Model routing",
"pt": "Model routing" "pt": "Roteamento de modelos"
}, },
"title": { "title": {
"en": "Choose the<br><em>engine.</em>", "en": "Choose the<br><em>engine.</em>",
"pt": "Choose the<br><em>engine.</em>" "pt": "Escolha o<br><em>motor.</em>"
}, },
"lede": { "lede": {
"en": "A model has a capability ceiling. Effort controls how much room it gets to reason. Route by uncertainty and verification cost.", "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": [ "cards": [
{ {
"label": { "label": {
"en": "LOW", "en": "LOW",
"pt": "LOW" "pt": "BAIXO"
}, },
"title": { "title": {
"en": "Bounded rhythm", "en": "Bounded rhythm",
"pt": "Bounded rhythm" "pt": "Ritmo delimitado"
}, },
"copy": { "copy": {
"en": "Lookup, small edits, formatting, and transformations with clear checks.", "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": { "label": {
"en": "MEDIUM", "en": "MEDIUM",
"pt": "MEDIUM" "pt": "MÉDIO"
}, },
"title": { "title": {
"en": "Default work", "en": "Default work",
"pt": "Default work" "pt": "Trabalho padrão"
}, },
"copy": { "copy": {
"en": "Normal implementation where the contract is clear but context matters.", "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": { "label": {
"en": "HIGH", "en": "HIGH",
"pt": "HIGH" "pt": "ALTO"
}, },
"title": { "title": {
"en": "Ambiguity", "en": "Ambiguity",
"pt": "Ambiguity" "pt": "Ambiguidade"
}, },
"copy": { "copy": {
"en": "Planning, architecture, security judgment, and hard failures.", "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": { "eyebrow": {
"en": "Two knobs", "en": "Two knobs",
"pt": "Two knobs" "pt": "Dois controles"
}, },
"title": { "title": {
"en": "Capability<br>× effort", "en": "Capability<br>× effort",
"pt": "Capability<br>× effort" "pt": "Capacidade<br>× esforço"
}, },
"panelLabel": { "panelLabel": {
"en": "ROUTING RULE", "en": "ROUTING RULE",
"pt": "ROUTING RULE" "pt": "REGRA DE ROTEAMENTO"
}, },
"panelCode": { "panelCode": {
"en": "strong model + high effort → frame ambiguity\nlight model + low effort → bounded execution\nraise one knob at a time → compare evidence", "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": { "eyebrow": {
"en": "Sequence", "en": "Sequence",
"pt": "Sequence" "pt": "Sequência"
}, },
"title": { "title": {
"en": "Spend judgment<br>where it <em>compounds.</em>", "en": "Spend judgment<br>where it <em>compounds.</em>",
"pt": "Spend judgment<br>where it <em>compounds.</em>" "pt": "Invista julgamento<br>onde ele <em>compensa.</em>"
}, },
"steps": [ "steps": [
{ {
"label": { "label": {
"en": "Plan", "en": "Plan",
"pt": "Plan" "pt": "Planejar"
}, },
"copy": { "copy": {
"en": "Strong model: scope, risks, acceptance, and worktree split.", "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": { "label": {
"en": "Build", "en": "Build",
"pt": "Build" "pt": "Construir"
}, },
"copy": { "copy": {
"en": "Focused worker: smallest context and lightest model that can pass.", "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": { "label": {
"en": "Review", "en": "Review",
"pt": "Review" "pt": "Revisar"
}, },
"copy": { "copy": {
"en": "Independent pass when missed issues cost more than the call.", "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."
} }
} }
] ]
+16 -16
View File
@@ -1,56 +1,56 @@
{ {
"id": "skills", "id": "skills",
"eyebrow": { "en": "Reusable judgment", "pt": "Reusable judgment" }, "eyebrow": { "en": "Reusable judgment", "pt": "Julgamento reutilizável" },
"title": { "en": "Teach the<br><em>decision.</em>", "pt": "Teach the<br><em>decision.</em>" }, "title": { "en": "Teach the<br><em>decision.</em>", "pt": "Ensine a<br><em>decisão.</em>" },
"lede": { "lede": {
"en": "A skill changes behavior. Keep the trigger precise, put the workflow in <code>SKILL.md</code>, and move conditional facts, scripts, and examples into focused files.", "en": "A skill changes behavior. Keep the trigger precise, put the workflow in <code>SKILL.md</code>, and move conditional facts, scripts, and examples into focused files.",
"pt": "A skill changes behavior. Keep the trigger precise, put the workflow in <code>SKILL.md</code>, and move conditional facts, scripts, and examples into focused files." "pt": "Uma skill muda comportamento. Mantenha o gatilho preciso, coloque o workflow em <code>SKILL.md</code> e mova fatos condicionais, scripts e exemplos para arquivos focados."
}, },
"sections": [ "sections": [
{ {
"eyebrow": { "en": "Package anatomy", "pt": "Package anatomy" }, "eyebrow": { "en": "Package anatomy", "pt": "Anatomia do pacote" },
"title": { "title": {
"en": "One job.<br>More than<br>one <em>file.</em>", "en": "One job.<br>More than<br>one <em>file.</em>",
"pt": "One job.<br>More than<br>one <em>file.</em>" "pt": "Um trabalho.<br>Mais que<br>um <em>arquivo.</em>"
}, },
"copy": { "copy": {
"en": "Choose a file to see why it belongs in the package.", "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": { "title": {
"en": "Observe →<br>trigger →<br>validate", "en": "Observe →<br>trigger →<br>validate",
"pt": "Observe →<br>trigger →<br>validate" "pt": "Observe →<br>defina o gatilho →<br>valide"
}, },
"steps": [ "steps": [
{ {
"label": { "en": "Observe friction", "pt": "Observe friction" }, "label": { "en": "Observe friction", "pt": "Observe o atrito" },
"copy": { "copy": {
"en": "Find a repeated decision or failure.", "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": { "copy": {
"en": "Say when it should load and when it should stay out.", "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": { "copy": {
"en": "Use references for facts and scripts for deterministic mechanics.", "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": { "copy": {
"en": "Test realistic prompts, edge cases, safety, and evidence.", "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."
} }
} }
] ]
+88 -28
View File
@@ -1,75 +1,135 @@
{ {
"id": "summary", "id": "summary",
"eyebrow": { "en": "Start here", "pt": "Start here" }, "eyebrow": {
"title": { "en": "Ship the<br><em>system.</em>", "pt": "Ship the<br><em>system.</em>" }, "en": "Start here",
"pt": "Comece aqui"
},
"title": {
"en": "Ship the<br><em>system.</em>",
"pt": "Entregue o<br><em>sistema.</em>"
},
"lede": { "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.", "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": { "footer": {
"en": "Each chapter stands alone; the order follows a real task becoming a reliable change.", "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": [ "cards": [
{ {
"label": { "en": "01", "pt": "01" }, "label": {
"title": { "en": "Models", "pt": "Models" }, "en": "01",
"pt": "01"
},
"title": {
"en": "Models",
"pt": "Modelos"
},
"copy": { "copy": {
"en": "Capability and effort are separate knobs.", "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/", "href": "../models/",
"cta": { "en": "Open chapter →", "pt": "Open chapter →" } "cta": {
"en": "Open chapter →",
"pt": "Abrir capítulo →"
}
}, },
{ {
"label": { "en": "02", "pt": "02" }, "label": {
"title": { "en": "Agents & trees", "pt": "Agents & trees" }, "en": "02",
"pt": "02"
},
"title": {
"en": "Agents & trees",
"pt": "Agentes e árvores"
},
"copy": { "copy": {
"en": "Bound roles, handoffs, and worktrees.", "en": "Bound roles, handoffs, and worktrees.",
"pt": "Bound roles, handoffs, and worktrees." "pt": "Limite papéis, handoffs e worktrees."
}, },
"href": "../agents/", "href": "../agents/",
"cta": { "en": "Open chapter →", "pt": "Open chapter →" } "cta": {
"en": "Open chapter →",
"pt": "Abrir capítulo →"
}
}, },
{ {
"label": { "en": "03", "pt": "03" }, "label": {
"title": { "en": "Skills", "pt": "Skills" }, "en": "03",
"pt": "03"
},
"title": {
"en": "Skills",
"pt": "Skills"
},
"copy": { "copy": {
"en": "Capture repeatable decisions.", "en": "Capture repeatable decisions.",
"pt": "Capture repeatable decisions." "pt": "Capture decisões repetíveis."
}, },
"href": "../skills/", "href": "../skills/",
"cta": { "en": "Open chapter →", "pt": "Open chapter →" } "cta": {
"en": "Open chapter →",
"pt": "Abrir capítulo →"
}
}, },
{ {
"label": { "en": "04", "pt": "04" }, "label": {
"title": { "en": "Rules", "pt": "Rules" }, "en": "04",
"pt": "04"
},
"title": {
"en": "Rules",
"pt": "Regras"
},
"copy": { "copy": {
"en": "Connect guidance to enforcement.", "en": "Connect guidance to enforcement.",
"pt": "Connect guidance to enforcement." "pt": "Conecte orientação a enforcement."
}, },
"href": "../rules/", "href": "../rules/",
"cta": { "en": "Open chapter →", "pt": "Open chapter →" } "cta": {
"en": "Open chapter →",
"pt": "Abrir capítulo →"
}
}, },
{ {
"label": { "en": "05", "pt": "05" }, "label": {
"title": { "en": "Practice", "pt": "Practice" }, "en": "05",
"pt": "05"
},
"title": {
"en": "Practice",
"pt": "Prática"
},
"copy": { "copy": {
"en": "Compare prompts and skill-enabled runs.", "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/", "href": "../hands-on/starter/",
"cta": { "en": "Open lab →", "pt": "Open lab →" } "cta": {
"en": "Open lab →",
"pt": "Abrir lab →"
}
}, },
{ {
"label": { "en": "06", "pt": "06" }, "label": {
"title": { "en": "Review desk", "pt": "Review desk" }, "en": "06",
"pt": "06"
},
"title": {
"en": "Review desk",
"pt": "Mesa de revisão"
},
"copy": { "copy": {
"en": "Browse original files and improved drafts.", "en": "Browse original files and improved drafts.",
"pt": "Browse original files and improved drafts." "pt": "Explore arquivos originais e rascunhos melhorados."
}, },
"href": "../skills-review/", "href": "../skills-review/",
"cta": { "en": "Open desk →", "pt": "Open desk →" } "cta": {
"en": "Open desk →",
"pt": "Abrir mesa →"
}
} }
] ]
} }