Skip to content

Spanish i18n proof of concept - #848

Open
brandonjackson wants to merge 19 commits into
mainfrom
claude/i18n-poc-spanish-mm7x73
Open

Spanish i18n proof of concept#848
brandonjackson wants to merge 19 commits into
mainfrom
claude/i18n-poc-spanish-mm7x73

Conversation

@brandonjackson

Copy link
Copy Markdown

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

  • i18n block in docusaurus.config.js (en default, es added; fr deliberately not enabled), plus a localeDropdown in the navbar and editLocalizedFiles so "Edit this page" points at the translation rather than the English source.
  • .gitignore listed /i18n, which would have silently dropped every translation file. Removed.
  • sidebars-adaptors.js: every generated item now carries a key namespaced by adaptor.
  • src/pages/index.js: homepage strings wrapped in <Translate>/translate() with explicit ids.

Spanish content

  • Chrome: navbar, footer, docs sidebar categories, blog SEO strings.
  • Docs pages: What is OpenFn?, Try out v2, Key Concepts, Get Help. All stamped translation_review_status: machine.
  • code.json holds only our homepage.* keys plus the 11 theme.* strings upstream has left in English. The other ~130 are inherited from @docusaurus/theme-translations so 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.

  1. Sidebar translation-key collisions. Docusaurus derives a sidebar item's translation key from key ?? label. The generated adaptors sidebar repeats Functions, Configuration, Changelog, README.md and Overview once per adaptor — about 100 collisions per label — and threw Multiple docs sidebar items produce the same translation key. Fixed with explicit keys.
  2. Relative .md links 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.
  3. /i18n in .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:

  • workflow in What is OpenFn? drops from 7 mentions to 1. I read "workflow automation" as the product category rather than the OpenFn workflow object, and translated it as "automatización de flujos de trabajo". workflow is product_noun: true so 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.
  • state goes 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 the state object, they should stay English.

Register. Spanish uses "tú" throughout, matching the documented default. translation-rules.yml is currently rules: [], 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.

  • This PR mixes four kinds of change: build config, English fixes, a new English page, and Spanish content. A reviewer cannot sensibly approve Spanish prose and webpack-adjacent config with the same eye. It should probably be two PRs — enablement, then translations. Happy to split.
  • I edited sidebars-adaptors.js without asking. It sits in none of the edit buckets in the AGENTS.md on claude/docs-agent-skills-0e700jsidebars-main.js is 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".
  • The translations were written by hand and audited against glossary.yml afterwards, not produced under it. A clean run of the translate skill 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.
  • I silently corrected English typos in the Spanish. 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.
  • I translated pages that had never been linted or accuracy-checked, inverting the intended order of work. Any factual error in the English is now faithfully reproduced in Spanish.
  • The compliance check I ran is my own script, not the skill's. It found real problems, but "passes" means "passes my approximation". My first version had a false-positive bug (see below).
  • Artículos blog 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_model is missing from the front matter, deliberately. The translate skill 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: machine already records that a machine wrote it, so the field's remaining value is forensics after a model upgrade — which is real. translation_agent: claude-code would keep that signal without an identifier. Your call.
  • translation_source_hash does not survive squash merges, and this PR proves it. Most of main is Title (#NNN) squash commits. Three of these pages point at bf33ecd, which is on main and fine. try-out.md points at an in-branch commit, because I edited that English page here — it will not exist on main after 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?
  • The term-count check needs to normalise line wrapping. Prettier runs proseWrap: always at 80 columns and English and Spanish wrap at different points, so work order splits 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.
  • Should translated pages be allowed locale-specific reader notes? I had added a :::note to 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 candidates. I kept these in English but they are not in 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:87 closes an admonition with :::note instead 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".
  • Typos listed above: try-out.md (acount, credentions), terminology.md (succesfully, succesful, successed), inspect-runs.md (excute).

Known gaps

  • Search returns nothing on the Spanish site. Algolia DocSearch runs with 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.
  • ~100 relative .md links 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 the lint skill section by section rather than one mechanical PR.
  • Full builds are sloweryarn build builds every locale.

Verification

yarn build passes 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), hreflang alternates including x-default, edit links resolving to the i18n/ file on translated pages, and untranslated pages falling back to English with Spanish chrome. Confirmed the translation_* front matter keys do not leak into rendered output.

AI Usage

  • I have used Claude Code
  • I have used another model
  • I have not used AI

🤖 Generated with Claude Code

https://claude.ai/code/session_0191JhrXtWiHnUGwfwesiq7T


Generated by Claude Code

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
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

Copy link
Copy Markdown
Author

Retranslated under the translate skill

Merged claude/docs-agent-skills-0e700j (#846) into this branch and re-ran all four pages through .agents/skills/translate.md, rather than hand-writing them and auditing afterwards as the first pass did.

Merge order: #846 should land first. Merged rather than copied so the shared files keep one history — this branch's diff against #846 is only the i18n work (20 files), and once #846 is on main this PR's diff shrinks to that.

Pre-flight and pre-commit

All three pre-flight checks pass: es enabled in the config, i18n/ tracked, rule files valid YAML. Every "Before you commit" check passes except one, covered below.

docs/get-help/support.md got a Prettier pass first, since the skill records the hash of the English after formatting.

What the skill caught that the audit had missed

This is the useful result — producing translations under the glossary is not the same as checking them against it afterwards:

  • flujos de trabajoWorkflows wherever it names the OpenFn object rather than the industry category. This is the product_noun distinction, and it only gets drawn reliably while translating. It also resolves the workflow 7→1 count I flagged as a judgement call: the remaining lowercase uses are "workflow automation", the category.
  • triggersTriggers, consistently.
  • Adaptor casing now mirrors the English exactly — 5 Adaptors, 12 adaptors. My hand-written version over-capitalised, then my correction over-lowercased. The glossary is silent here (case_sensitive is false for adaptor), so mirroring the source is the defensible default.
  • Internal links carry the locale (/es/documentation/...) as the skill specifies.
  • translation_source_hash is now a git hash-object content hash, so it no longer dangles when this branch squashes.

Both of the concerns I raised in the previous round are fixed in the skill itself — the content hash (8ae2859) and the line-wrap normalisation before matching multi-word terms (80a924e). The line-wrap fix matters: work order counts clean now without the false positive.

A trap worth knowing about, and a correction to myself

Following the skill's /es/documentation/... instruction, yarn build --locale es failed with seven broken links. I concluded the instruction was wrong and edited the skill.

I was wrong, and I have reverted that edit.agents/skills/translate.md is byte-identical to #846 again. A full yarn build passes with those links, and they render correctly with no double prefix.

The two build commands disagree about baseUrl:

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_model is still omitted. I am instructed not to put model identifiers in repo artifacts. translation_agent: claude-code would 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.
  • state 2→0 in Key Concepts — "initial state" / "final state" read as ordinary English to me. Still a reviewer call.
  • sidebars-adaptors.js sits 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants