Spanish i18n proof of concept - #848
Conversation
Adds the orchestrator (AGENTS.md) and seven self-contained skill files under .agents/skills/ for linting, accuracy verification, fresh-user evaluation, gap analysis, translation, corrections capture, and screenshot triage. Seeds glossary.yml with product terms that must never be translated and spelling variants lint should flag. Adds empty style-exceptions.yml and translation-rules.yml with schema comments. Stops ignoring /i18n so machine translations can be committed alongside the English source, per the translation architecture in AGENTS.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
Enables Docusaurus i18n with English as the source language and Spanish
as a second locale, translating enough of the site to evaluate the
workflow before committing to full coverage. Untranslated content falls
back to English, so partial coverage is a valid steady state.
Translated:
- Site chrome: navbar, footer, docs sidebar categories, blog SEO strings
- Homepage: React strings wrapped in <Translate>/translate() with
explicit ids so they survive edits to the English copy
- Docs pages: What is OpenFn?, Try out v2, Key Concepts, Get Help
Spanish conventions are documented in the new contributor guide: OpenFn
product nouns (Project, Workflow, Trigger, Step, Job, Adaptor,
Credential, Work Order, Run) stay in English because that is what the
Lightning UI and project.yaml show, while surrounding prose is
translated.
Three things had to be fixed for the multi-locale build to pass:
- .gitignore listed /i18n, which would have silently dropped every
translation file.
- sidebars-adaptors.js generated ~100 items per label ('Functions',
'Overview', ...). Docusaurus derives a sidebar item's translation key
from `key ?? label`, so these collided and threw for any non-default
locale. Each generated item now carries a key namespaced by adaptor.
- Four relative `.md` doc links crossed the translated/untranslated
boundary and could not resolve, failing the Spanish build while
English passed. They are now site-absolute, which is locale-prefixed
at build time. ~100 such links remain elsewhere in docs/ and are noted
in the guide as a follow-up.
Also sets editLocalizedFiles so "Edit this page" on a translated page
points at the translation rather than the English source.
Verified: `yarn build` passes for both locales; Spanish pages render
with translated chrome, locale dropdown, html lang="es", per-locale
sitemaps and hreflang alternates; untranslated pages fall back to
English.
Known gap: Algolia DocSearch uses contextualSearch, and the crawler is
only configured for the English site, so search returns nothing from a
/es/ page until the crawler config is updated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0191JhrXtWiHnUGwfwesiq7T
Keeps every rule from the spec; drops long command snippets, exhaustive path tables, and repeated output templates so the files are easier to read and maintain. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
The three rule files stay and are maintained by humans directly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
… glossary check, exempt from file cap Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
Aligns the i18n proof of concept with the translate/lint skills and the rule
files on claude/docs-agent-skills-0e700j, so the two branches compose instead
of contradicting each other.
Provenance front matter. Each translated page now carries
translation_source_hash (the commit that last changed its English source) and
translation_review_status: machine, per .agents/skills/translate.md. Without
these, the skill's decision table treats a translation as unstamped and
regenerates it, so this hand-written Spanish would have been silently
discarded on the first run. Verified that Docusaurus permits unknown front
matter keys and does not leak them into rendered output.
Heading anchors. Translated headings whose text changed now pin the English
anchor, e.g. `## Nuestros productos {#our-products}`, so links written against
the English page keep resolving in every locale. Anchors were taken from the
built English HTML rather than derived by hand, and the build confirms EN and
ES heading ids are now identical on all three affected pages. Key Concepts
needed none: every heading there is a glossary product noun that stays in
English.
Glossary compliance. Ran the skill's pre-commit check against glossary.yml.
Fixed-term counts now match the English on all four pages. Two changes came
out of it: removed a translator's note admonition I had added to Key Concepts,
which was content absent from the English and broke the "same callouts" rule,
and restored a maintainer HTML comment dropped from the support page.
Guide scope. docs/contribute/translating.md no longer defines terminology
policy; glossary.yml and translation-rules.yml are the authority, and the page
now covers only what they do not: how locales are wired into Docusaurus, the
three failure modes that only appear on non-default locale builds, and the
known gaps. Its terminology section had duplicated the glossary and would have
drifted.
Also aligns .gitignore with the skills branch (byte-identical, so the shared
edit no longer conflicts) and reframes the relative-link cleanup as work for
the lint skill, since site-absolute internal links are already house style in
AGENTS.md.
Verified: yarn build passes for both locales; Prettier clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0191JhrXtWiHnUGwfwesiq7T
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
code.json deliberately omits ~130 theme.* keys (inherited from @docusaurus/theme-translations) and the computed footer copyright. write-translations has no way to know that and will re-add them all, so the next person to run it can silently commit ownership of strings Docusaurus already translates for us. Documents the check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0191JhrXtWiHnUGwfwesiq7T
Starts from a product PR and finds the docs pages that need updating. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
Commit SHAs made on a branch dangle after a squash merge. A git blob hash is the same wherever the file lives and still lets a reviewer recover the English they approved. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
…cation map Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
…rch invocations, separate ease from impact Based on a test run against the CLI section. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
Defaults to every release in the last month across the product repos and reads changelogs before diffs. A single PR remains an optional narrower input. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CntaKNYyBkumpWvm5mTJa7
…ranslate skill Brings AGENTS.md, the agent skills, and the rule files (glossary.yml, translation-rules.yml, style-exceptions.yml) onto this branch so the Spanish translations can be produced under the translate skill rather than audited against it after the fact. Merged rather than copied so the files keep one history: when #846 lands, this branch's diff against main shows only the i18n work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0191JhrXtWiHnUGwfwesiq7T
The translate skill says to translate the English page as it is on disk after Prettier has run, so the recorded content hash matches what was translated. This page had trailing whitespace and unwrapped prose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0191JhrXtWiHnUGwfwesiq7T
Re-runs all four pages through .agents/skills/translate.md rather than hand-writing them and auditing afterwards. Pre-flight checks pass (locale enabled, i18n/ tracked, rule files valid YAML), and every "Before you commit" check passes except one, explained below. What the skill changed that the earlier audit had missed: - "flujos de trabajo" is now "Workflows" wherever it names the OpenFn object rather than the industry category. The glossary's product_noun rule draws that line; applying it while translating catches cases that counting terms afterwards does not. - "triggers" is now "Triggers" consistently. - Internal links carry the locale (/es/documentation/...) as the skill specifies. - Adaptor casing now mirrors the English page exactly (5 "Adaptors", 12 "adaptors"); the earlier version over-capitalised. - translation_source_hash is now a git hash-object content hash, so it no longer dangles when the branch is squashed onto main. Also formats docs/get-help/support.md with Prettier first, since the skill records the hash of the English as it stands after formatting. One check still fails, and it is the English's fault: docs/get-started/terminology.md:87 closes an admonition with `:::note` instead of `:::`, so the callout count reads 10 in the English and 9 in the correct Spanish. The English page renders a stray empty "note" box before the Adaptor heading as a result. Left for the English pass per the skill's rule about not fixing English while translating; this check will keep failing on this page until that one character is fixed. Verified: yarn build passes for both locales; Prettier clean; EN and ES heading ids identical; no double locale prefix in rendered links. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0191JhrXtWiHnUGwfwesiq7T
Retranslated under the
|
| Command | baseUrl for es |
Route paths | /es/… links |
|---|---|---|---|
yarn build |
/es/ |
/es/documentation/… |
resolve |
yarn build --locale es |
/ |
/documentation/… |
reported broken |
So the single-locale build fails on translated pages the real build is perfectly happy with. That is a false-failure generator, and my own guide had been recommending yarn build --locale es as the fast path. The guide now carries a :::danger callout: treat a --locale failure as suspect until reproduced with a full build, and always run plain yarn build before opening a PR.
Worth considering whether the skill's "build that locale and make sure it passes" should say to run the full build, for the same reason.
The one check that still fails
get-started/terminology.md — callouts 10 (EN) vs 9 (ES).
This is the English's fault and it is not cosmetic. docs/get-started/terminology.md:87 closes an admonition with :::note instead of a bare :::, so it parses as a second opener. The live English page renders a stray empty "note" box just above the Adaptor heading. The Spanish closes it correctly, which is the only reason the counts differ.
Left unfixed per the skill's rule about not fixing English while translating — but flagging that this check will fail on this page on every future run until that one character changes. Happy to send it as a separate English PR.
Still open from before
translation_modelis still omitted. I am instructed not to put model identifiers in repo artifacts.translation_agent: claude-codewould keep the forensic signal without one.- Glossary candidates: Input, Output, Path, History, Logs, Collaborators, and "Webhook Event" as a UI label. Kept in English, not in
glossary.yml. Humans maintain that file so I have not touched it. state2→0 in Key Concepts — "initial state" / "final state" read as ordinary English to me. Still a reviewer call.sidebars-adaptors.jssits in none of AGENTS.md's edit buckets. Suggest adding it to "ask before editing".- This PR still mixes enablement, English fixes and translations. Say the word and I will split the Spanish into its own PR per the skill's one-PR-per-locale rule.
yarn build passes for both locales. Prettier clean. EN and ES heading ids identical.
Generated by Claude Code
Short Description
Enables Docusaurus i18n with English as the source and Spanish as a second locale, and translates enough of the site (chrome, homepage, four docs pages) to evaluate the workflow before committing to full coverage. Untranslated content falls back to English, so partial coverage is a valid steady state.
Details
What changed
Site wiring
i18nblock indocusaurus.config.js(endefault,esadded;frdeliberately not enabled), plus alocaleDropdownin the navbar andeditLocalizedFilesso "Edit this page" points at the translation rather than the English source..gitignorelisted/i18n, which would have silently dropped every translation file. Removed.sidebars-adaptors.js: every generated item now carries akeynamespaced by adaptor.src/pages/index.js: homepage strings wrapped in<Translate>/translate()with explicit ids.Spanish content
translation_review_status: machine.code.jsonholds only ourhomepage.*keys plus the 11theme.*strings upstream has left in English. The other ~130 are inherited from@docusaurus/theme-translationsso they keep improving on upgrade.New page:
docs/contribute/translating.md, covering how locales are wired into the build and the failure modes that only appear on non-default locales. It deliberately does not define terminology policy.Three things that broke the multi-locale build
All three only fail on non-default locales, so English keeps passing and they are confusing to debug.
key ?? label. The generated adaptors sidebar repeatsFunctions,Configuration,Changelog,README.mdandOverviewonce per adaptor — about 100 collisions per label — and threwMultiple docs sidebar items produce the same translation key. Fixed with explicit keys..mdlinks across the translated/untranslated boundary. These only resolve when both pages come from the same content directory. Four links crossed the boundary and failed the Spanish build; they are now site-absolute, which is already house style./i18nin.gitignore.Parts that need careful review
The Spanish prose itself. I am not a native speaker and none of this has been human-reviewed. Two specific judgement calls to check rather than skim:
workflowinWhat is OpenFn?drops from 7 mentions to 1. I read "workflow automation" as the product category rather than the OpenFnworkflowobject, and translated it as "automatización de flujos de trabajo".workflowisproduct_noun: trueso this is permitted, but it is a big swing on the most visible page on the site and it is my call, not a reviewer's.stategoes from 2 mentions to 0 in Key Concepts. "initial state" / "final state" read as ordinary English to me, so they became "estado inicial" / "estado final". If those name thestateobject, they should stay English.Register. Spanish uses "tú" throughout, matching the documented default.
translation-rules.ymlis currentlyrules: [], so nothing backs that in the rule file yet. If the first reviewer prefers "usted", all four pages need redoing.Heading anchors. Translated headings whose text changed pin the English anchor (
## Nuestros productos {#our-products}) so existing links keep resolving. Anchors were taken from the built English HTML rather than derived by hand, and the build confirms EN and ES heading ids are identical on all three affected pages. Key Concepts needed none — every heading there is a glossary product noun that stays in English.Where I deviated from best practice
Disclosing these rather than hoping they go unnoticed.
sidebars-adaptors.jswithout asking. It sits in none of the edit buckets in theAGENTS.mdonclaude/docs-agent-skills-0e700j—sidebars-main.jsis edit-freely, generated adaptor pages are do-not-edit, the sidebar generator is unlisted. The build could not pass without it, but I should have raised it first. Suggest adding it to "ask before editing".glossary.ymlafterwards, not produced under it. A clean run of thetranslateskill would likely give more consistent output. Treat these four pages as a demonstration that the plumbing works, not as a sample of what the skill will produce.credentions,acount,succesful,successed,tansactions. The Spanish is now more correct than its source, which breaks the "English is source of truth" invariant and will make future diffs confusing. Listed below for the English pass instead.Artículosblog SEO metadata is an improvement, not a translation. The English default is the literal string "Blog"; I set the Spanish to "Artículos". So the two locales now differ semantically rather than one being a translation of the other.Questions
translation_modelis missing from the front matter, deliberately. Thetranslateskill asks for it; I am instructed not to put model identifiers in anything pushed to a repository, so I stamped the other two fields and left it out rather than quietly breaking either rule.translation_review_status: machinealready records that a machine wrote it, so the field's remaining value is forensics after a model upgrade — which is real.translation_agent: claude-codewould keep that signal without an identifier. Your call.translation_source_hashdoes not survive squash merges, and this PR proves it. Most ofmainisTitle (#NNN)squash commits. Three of these pages point atbf33ecd, which is onmainand fine.try-out.mdpoints at an in-branch commit, because I edited that English page here — it will not exist onmainafter squash, so it ships dangling on day one. A content hash of the English file (sha256) would be squash-proof, rebase-proof, and answers the actual question the field exists for: has the English changed since I translated it?proseWrap: alwaysat 80 columns and English and Spanish wrap at different points, sowork ordersplits across a newline and whole-word matching misses it. My first run reported four mismatches; two were this artifact. Normalise whitespace before counting or multi-word glossary terms will throw false positives on every run.:::noteto Key Concepts explaining to Spanish readers why the product nouns are in English. It broke the "same callouts, same structure" rule, so I removed it — a mechanical check cost some genuinely useful reader guidance. There is currently no way to add locale-only content that survives regeneration.glossary.yml: Input, Output, Path, History, Logs, Collaborators, and Webhook Event as a UI label. Humans maintain that file, so I did not touch it.For the next English pass
Spotted while translating, not fixed here:
docs/get-started/terminology.md:87closes an admonition with:::noteinstead of:::. Every other one on the page uses a bare:::. My Spanish closes it correctly, which is the only reason the callout counts differ between the two.docs/get-started/terminology.md:79— "Financial tansactions" → "transactions".try-out.md(acount,credentions),terminology.md(succesfully,succesful,successed),inspect-runs.md(excute).Known gaps
contextualSearch: true, which scopes results to the browsing language, and the crawler is only configured for English. Not fixable from this repo, and worth resolving before the Spanish site is promoted anywhere..mdlinks remain across ~35 pages. Harmless while both ends are English; a build failure the moment either end is translated. These are house-style violations, so they belong to thelintskill section by section rather than one mechanical PR.yarn buildbuilds every locale.Verification
yarn buildpasses for both locales. Prettier clean. Verified in Chromium against the production build: translated chrome and content, locale dropdown,html lang="es", per-locale sitemaps (846 URLs each),hreflangalternates includingx-default, edit links resolving to thei18n/file on translated pages, and untranslated pages falling back to English with Spanish chrome. Confirmed thetranslation_*front matter keys do not leak into rendered output.AI Usage
🤖 Generated with Claude Code
https://claude.ai/code/session_0191JhrXtWiHnUGwfwesiq7T
Generated by Claude Code