From 494e2a24b671d40c078789d7ef6de369ef89adf1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:33:20 +0000 Subject: [PATCH 01/13] Initial plan From aa556c5d2a7c15c340924a4fd808936d5f663e61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:37:19 +0000 Subject: [PATCH 02/13] docs: add i18n architectural plans to specs/i18n Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- specs/i18n/plan-a-locale-keyed-content.md | 253 +++++++++++ ...-externalized-strings-full-translations.md | 397 ++++++++++++++++++ 2 files changed, 650 insertions(+) create mode 100644 specs/i18n/plan-a-locale-keyed-content.md create mode 100644 specs/i18n/plan-b-externalized-strings-full-translations.md diff --git a/specs/i18n/plan-a-locale-keyed-content.md b/specs/i18n/plan-a-locale-keyed-content.md new file mode 100644 index 0000000..97387bb --- /dev/null +++ b/specs/i18n/plan-a-locale-keyed-content.md @@ -0,0 +1,253 @@ +# Plan A — Locale-Keyed Content Files + +## Overview + +Introduce locale-specific JSON content directories that mirror the existing +`content/` tree. The generator merges each locale's translations on top of the +English baseline at build time, then emits a fully localised copy of every +page under a locale-prefixed path (e.g. `site/pt-BR/language/slug.html`). + +A language selector in the navigation bar lets users switch locales. The +English site continues to live at `/` (no prefix) so existing links and +search-engine rankings are preserved. + +--- + +## Directory Layout + +``` +content/ # English source of truth (unchanged) + language/ + type-inference-with-var.json + collections/ + ... + +content/i18n/ # Locale overlays + pt-BR/ + language/ + type-inference-with-var.json # Only the fields that differ + ui.json # Navigation / section labels + ja/ + language/ + type-inference-with-var.json + ui.json + ... + +templates/ # Templates unchanged +html-generators/ + categories.properties # English display names + categories.pt-BR.properties # Localised category display names + generate.java / generate.py # Extended to accept a --locale flag + +site/ # Generated output + index.html # English home (no prefix) + language/ + type-inference-with-var.html + data/ + snippets.json # English search index + pt-BR/ # Locale subtree + index.html + language/ + type-inference-with-var.html + data/ + snippets.json + ja/ + ... +``` + +--- + +## JSON Overlay Schema + +Each locale overlay file contains only the fields that change; everything else +is inherited from the English baseline file. + +```json +// content/i18n/pt-BR/language/type-inference-with-var.json +{ + "title": "Inferência de tipo com var", + "summary": "Use var para deixar o compilador inferir o tipo local.", + "explanation": "...", + "oldApproach": "Tipos explícitos", + "modernApproach": "Palavra-chave var", + "whyModernWins": [ + { "icon": "⚡", "title": "Menos ruído", "desc": "..." }, + { "icon": "👁", "title": "Mais legível", "desc": "..." }, + { "icon": "🔒", "title": "Seguro", "desc": "..." } + ] +} +``` + +Fields **not** present in the overlay (`oldCode`, `modernCode`, `jdkVersion`, +`docs`, `related`, `prev`, `next`, `support`) are always taken from the +English baseline — code snippets, links, and navigation stay language-neutral. + +### `ui.json` — UI Strings + +```json +// content/i18n/pt-BR/ui.json +{ + "nav": { + "allPatterns": "← Todos os padrões", + "toggleTheme": "Alternar tema", + "viewOnGitHub": "Ver no GitHub" + }, + "sections": { + "codeComparison": "Comparação de código", + "whyModernWins": "Por que a forma moderna ganha", + "oldApproach": "Abordagem antiga", + "modernApproach": "Abordagem moderna", + "sinceJdk": "Desde o JDK", + "difficulty": "Dificuldade", + "jdkSupport": "Suporte ao JDK", + "howItWorks": "Como funciona", + "relatedDocs": "Documentação relacionada", + "relatedPatterns": "Padrões relacionados" + }, + "filters": { + "show": "Mostrar:", + "all": "Todos", + "difficulty": { + "beginner": "Iniciante", + "intermediate": "Intermediário", + "advanced": "Avançado" + } + }, + "search": { + "placeholder": "Buscar padrões...", + "noResults": "Nenhum resultado encontrado." + } +} +``` + +--- + +## Generator Changes + +1. **Accept `--locale` flag** (e.g. `generate.java --locale pt-BR`). + When omitted the generator builds only the English site (current + behaviour). + +2. **Merge algorithm**: + ``` + merged = deepMerge(english_baseline, locale_overlay) + ``` + Merge happens in memory; neither source file is modified. + +3. **Output path**: all files for locale `` are written to + `site//…` with relative asset paths adjusted + (`../../styles.css`, `../../app.js`, etc.). + +4. **Category display names**: loaded from + `html-generators/categories..properties` when it exists, + falling back to `categories.properties`. + +5. **`data/snippets.json`**: a separate + `site//data/snippets.json` is generated from the merged content + so the locale-specific search index is accurate. + +6. **Build all locales at once**: + ```bash + jbang html-generators/generate.java # English + jbang html-generators/generate.java --locale pt-BR + jbang html-generators/generate.java --locale ja + ``` + Or, when a build-all mode is added: + ```bash + jbang html-generators/generate.java --all-locales + ``` + +--- + +## Template Changes + +Templates (`templates/slug-template.html`, `templates/index.html`) gain +additional `{{…}}` tokens for localised UI strings: + +| Token | Source field | +|---|---| +| `{{labelCodeComparison}}` | `sections.codeComparison` | +| `{{labelWhyModernWins}}` | `sections.whyModernWins` | +| `{{labelHowItWorks}}` | `sections.howItWorks` | +| `{{labelRelatedDocs}}` | `sections.relatedDocs` | +| `{{labelRelatedPatterns}}` | `sections.relatedPatterns` | +| `{{navAllPatterns}}` | `nav.allPatterns` | +| `{{searchPlaceholder}}` | `search.placeholder` | + +English values are hard-coded defaults (matching the current literal strings) +so the English build requires no `ui.json`. + +--- + +## HTML `` Changes + +Each detail page gains `` tags and the `` +attribute is set to the locale: + +```html + + + ... + + + + +``` + +--- + +## Navigation — Language Selector + +A locale pill is added to the nav bar (right-hand side, beside the theme +toggle). It is rendered as a ` + + + + +``` + +JavaScript (`app.js`) listens for changes and navigates to the equivalent +page in the chosen locale by rewriting the path prefix. + +--- + +## GitHub Actions Changes + +The deploy workflow gains a step that loops over every locale directory in +`content/i18n/` and invokes the generator for each: + +```yaml +- name: Generate locales + run: | + jbang html-generators/generate.java + for locale in content/i18n/*/; do + loc=$(basename "$locale") + jbang html-generators/generate.java --locale "$loc" + done +``` + +--- + +## Migration Path + +| Phase | Work | +|---|---| +| 1 | Add `ui.json` English defaults; extract hard-coded label strings from templates into tokens | +| 2 | Extend generator to accept `--locale`, implement merge algorithm | +| 3 | Add language selector to nav; write `app.js` path-rewrite logic | +| 4 | Create first locale overlay (e.g. `pt-BR`) as a proof-of-concept | +| 5 | Update GitHub Actions; add `hreflang` and `` to templates | + +--- + +## Trade-offs + +| Pros | Cons | +|---|---| +| Overlay-only translations mean small locale files | Translators must know the JSON schema | +| English site path unchanged — SEO not affected | Each new locale doubles generated-file count | +| Build-time generation — no runtime i18n library needed | Generator becomes more complex | +| Code snippets stay language-neutral automatically | Navigation (`prev`/`next`) still points to English slugs (cross-locale navigation not addressed) | diff --git a/specs/i18n/plan-b-externalized-strings-full-translations.md b/specs/i18n/plan-b-externalized-strings-full-translations.md new file mode 100644 index 0000000..5637d81 --- /dev/null +++ b/specs/i18n/plan-b-externalized-strings-full-translations.md @@ -0,0 +1,397 @@ +# Plan B — Externalized UI Strings + Full Translation Files + +## Overview + +Separate concerns into two distinct layers: + +1. **UI strings layer** — every piece of hard-coded copy in the templates + (labels, button text, nav, footer, etc.) is moved into a per-locale + `strings/{locale}.json` file and injected at build time. + +2. **Content translation layer** — translated pattern JSON files are + complete, stand-alone replacements (not overlays) stored next to the + English originals. The generator falls back to the English file for any + pattern that has not yet been translated. + +This approach treats English as just another locale and unifies the build +pipeline so all locales are first-class citizens. + +--- + +## Directory Layout + +``` +content/ # Unchanged English content + language/ + type-inference-with-var.json + collections/ + ... + +translations/ # New top-level directory + strings/ + en.json # English UI strings (extracted from templates) + pt-BR.json + ja.json + content/ + pt-BR/ + language/ + type-inference-with-var.json # Full translated JSON (all fields) + collections/ + ... + ja/ + language/ + ... + +templates/ # Templates gain {{…}} tokens for every + slug-template.html # hard-coded UI string (no literal English left) + index.html + ... + +html-generators/ + locales.properties # Ordered list of supported locales + display names + generate.java / generate.py # Rewritten to iterate all locales + +site/ # Generated output + index.html # English home (locale = en, path = /) + language/ + type-inference-with-var.html + data/ + snippets.json + pt-BR/ + index.html + language/ + type-inference-with-var.html + data/ + snippets.json + ja/ + ... +``` + +--- + +## `strings/{locale}.json` Schema + +Every user-visible string in the templates is assigned a dot-separated key: + +```json +// translations/strings/en.json +{ + "site": { + "title": "java.evolved", + "tagline": "Java has evolved. Your code can too.", + "description": "A collection of modern Java code snippets. Every old Java pattern next to its clean, modern replacement — side by side." + }, + "nav": { + "allPatterns": "← All patterns", + "toggleTheme": "Toggle theme", + "viewOnGitHub": "View on GitHub" + }, + "sections": { + "codeComparison": "Code Comparison", + "whyModernWins": "Why the modern way wins", + "oldApproach": "Old Approach", + "modernApproach": "Modern Approach", + "sinceJdk": "Since JDK", + "difficulty": "Difficulty", + "jdkSupport": "JDK Support", + "howItWorks": "How it works", + "relatedDocs": "Related Documentation", + "relatedPatterns": "Related patterns" + }, + "filters": { + "show": "Show:", + "all": "All", + "difficulty": { + "beginner": "Beginner", + "intermediate": "Intermediate", + "advanced": "Advanced" + } + }, + "search": { + "placeholder": "Search patterns…", + "noResults": "No results found.", + "esc": "ESC" + }, + "copy": { + "copy": "Copy", + "copied": "Copied!" + }, + "footer": { + "madeWith": "Made with ❤️ by", + "inspiredBy": "Inspired by", + "viewOnGitHub": "View on GitHub" + }, + "support": { + "available": "Available", + "preview": "Preview", + "experimental": "Experimental" + } +} +``` + +```json +// translations/strings/pt-BR.json +// Strings files support partial translation: missing keys fall back to en.json at build time. +{ + "site": { + "tagline": "O Java evoluiu. Seu código também pode.", + "description": "Uma coleção de snippets modernos de Java..." + }, + "nav": { + "allPatterns": "← Todos os padrões", + "toggleTheme": "Alternar tema" + }, + "sections": { + "codeComparison": "Comparação de código", + "whyModernWins": "Por que a forma moderna ganha", + "howItWorks": "Como funciona", + "relatedDocs": "Documentação relacionada", + "relatedPatterns": "Padrões relacionados" + } +} +``` + +Missing keys in a locale's strings file fall back to the `en.json` value — +strings files are partial by design and only require the keys that need +translation. + +> **Note**: This partial-fallback behaviour applies to `strings/{locale}.json` +> **only**. Translated *content* files (see next section) are always complete +> replacements, not overlays. + +--- + +## Full-Replacement Content Files + +Unlike Plan A's overlay approach, translated content files are **complete** +copies of the pattern JSON with every field in the target language. This +avoids partial-merge edge cases and lets translators work with a self-contained +file. + +```json +// translations/content/pt-BR/language/type-inference-with-var.json +{ + "id": 1, + "slug": "type-inference-with-var", + "title": "Inferência de tipo com var", + "category": "language", + "difficulty": "beginner", + "jdkVersion": "10", + "oldLabel": "Java 8", + "modernLabel": "Java 10+", + "oldApproach": "Tipos explícitos", + "modernApproach": "Palavra-chave var", + "oldCode": "String nome = \"Alice\";\nString saudacao = \"Olá, \" + nome + \"!\";", + "modernCode": "var nome = \"Alice\";\nvar saudacao = \"Olá, %s!\".formatted(nome);", + "summary": "Use var para deixar o compilador inferir o tipo local.", + "explanation": "...", + "whyModernWins": [ + { "icon": "⚡", "title": "Menos ruído", "desc": "..." }, + { "icon": "👁", "title": "Mais legível", "desc": "..." }, + { "icon": "🔒", "title": "Seguro", "desc": "..." } + ], + "support": { + "state": "available", + "description": "Amplamente disponível desde o JDK 10 (março de 2018)" + }, + "prev": "language/...", + "next": "language/...", + "related": ["..."], + "docs": [{ "title": "...", "href": "..." }] +} +``` + +`oldCode` and `modernCode` are always copied from the English file at build +time; translators must not provide them (or they are silently ignored). This +keeps code snippets language-neutral. + +--- + +## `locales.properties` — Supported Locales Registry + +```properties +# html-generators/locales.properties +# format: locale=Display name (first entry is the default/primary locale) +en=English +pt-BR=Português (Brasil) +ja=日本語 +``` + +The generator reads this file to know which locales to build and what label +to show in the language selector. + +--- + +## Generator Changes + +### Resolution Order + +For each pattern the generator: + +1. Loads the English baseline from `content//.json`. +2. Checks if `translations/content///.json` exists. + - If **yes**: use the translated file but override `oldCode`/`modernCode` + with the English values. + - If **no**: use the English file and optionally mark the page with a + banner ("This pattern has not yet been translated"). +3. Loads `translations/strings/.json`, deep-merged over + `translations/strings/en.json`. +4. Renders the template, substituting both content tokens (`{{title}}`, …) + and UI-string tokens (`{{nav.allPatterns}}`, …). +5. Writes the output to `site///.html` + (or `site//.html` for `en`). + +### Untranslated Pattern Banner (optional) + +When falling back to English content for a non-English locale, the generator +can inject a `
` banner: + +```html +
+ This page has not yet been translated into Português (Brasil). + View in English +
+``` + +The banner is suppressed when the locale is `en` or a translation file exists. + +--- + +## Template Changes + +Every hard-coded English string in the templates is replaced with a token. +The token naming convention mirrors the key path in `strings/{locale}.json`: + +| Before | After | +|---|---| +| `Code Comparison` | `{{sections.codeComparison}}` | +| `Why the modern way wins` | `{{sections.whyModernWins}}` | +| `How it works` | `{{sections.howItWorks}}` | +| `← All patterns` | `{{nav.allPatterns}}` | +| `Copy` | `{{copy.copy}}` | +| `Copied!` | `{{copy.copied}}` | +| `Search patterns…` | `{{search.placeholder}}` | + +The `` tag becomes ``. + +--- + +## HTML `` Changes + +`hreflang` alternate links are generated for every supported locale: + +```html + + + +``` + +--- + +## Navigation — Language Selector + +Same user-facing design as Plan A (a ` + + + +``` + +`app.js` path-rewrite logic is identical to Plan A. + +--- + +## `app.js` Changes + +The search index path and the locale picker rewrite must both be locale-aware: + +```js +// Detect current locale from path prefix +const locale = location.pathname.startsWith('/pt-BR/') ? 'pt-BR' + : location.pathname.startsWith('/ja/') ? 'ja' + : 'en'; + +// Load the correct snippets index +const indexPath = locale === 'en' + ? '/data/snippets.json' + : `/${locale}/data/snippets.json`; +``` + +Localised strings needed by JavaScript (search placeholder, "no results" +message, "Copied!") are embedded as a ` +``` + +`app.js` reads from `window.i18n` instead of hard-coded strings. + +--- + +## GitHub Actions Changes + +The deploy workflow builds the English site first, then iterates every entry +in `locales.properties` (skipping `en`) to build locale subtrees: + +```yaml +- name: Build site + run: | + python3 html-generators/generate.py # English + python3 html-generators/generate.py --locale pt-BR + python3 html-generators/generate.py --locale ja +``` + +Or, with a build-all mode added to the generator: + +```yaml +- name: Build site + run: python3 html-generators/generate.py --all-locales +``` + +--- + +## Migration Path + +| Phase | Work | +|---|---| +| 1 | Create `translations/strings/en.json` by extracting every hard-coded string from templates; replace literals with `{{…}}` tokens; verify English output is byte-identical | +| 2 | Add `locales.properties`; extend generator to load strings, support `--locale`, fall back gracefully | +| 3 | Add language selector to nav + `app.js` locale detection and path rewrite | +| 4 | Translate `strings/pt-BR.json` and 2–3 content files as a proof-of-concept; verify fallback banner | +| 5 | Update GitHub Actions; add `hreflang` alternate links | +| 6 | Open translation contribution guide; document `translations/` schema | + +--- + +## Trade-offs + +| Pros | Cons | +|---|---| +| English is a first-class locale — no special-cased code paths | Larger initial refactor (all template strings must be extracted) | +| Complete translation files are easy for translators to understand | Full content files are larger and harder to keep in sync with English originals | +| Untranslated-page fallback is explicit and user-visible | `translations/` directory adds a new top-level location to learn | +| UI strings fall back to English automatically — no silent gaps | `app.js` must be updated to consume `window.i18n` instead of literals | +| Scales cleanly to many locales | Build time grows linearly with locale count × pattern count | + +--- + +## Comparison with Plan A + +| | Plan A | Plan B | +|---|---|---| +| Translation file format | Overlay (partial JSON) | Full replacement JSON | +| UI strings | `content/i18n/{locale}/ui.json` | `translations/strings/{locale}.json` | +| Content location | `content/i18n/{locale}/…` | `translations/content/{locale}/…` | +| English templates | Keep some literals | Extract all literals to `en.json` | +| Fallback behaviour | Silent (English shown without notice) | Optional "not yet translated" banner | +| Generator complexity | Medium — deep-merge algorithm needed | Higher — full resolution pipeline | +| Translator experience | Must understand overlay semantics | Self-contained files, easier to translate | From 318ffb172c1a3fad2565d025fd6ac1db5b70dbf5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:49:51 +0000 Subject: [PATCH 03/13] docs: add i18n plan recommendation for AI-driven translation workflow Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- specs/i18n/recommendation.md | 95 ++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 specs/i18n/recommendation.md diff --git a/specs/i18n/recommendation.md b/specs/i18n/recommendation.md new file mode 100644 index 0000000..55fcb2c --- /dev/null +++ b/specs/i18n/recommendation.md @@ -0,0 +1,95 @@ +# i18n Architecture Recommendation + +## TL;DR — **Plan B is recommended** for an AI-driven translation workflow. + +--- + +## Why Plan B wins when translations are AI-generated + +When a new slug is added and AI generates the translations automatically, the +pipeline becomes: + +``` +New English slug → AI prompt → Translated JSON file → Commit to repo +``` + +### Plan A (overlay) is awkward for AI + +- AI must know *which fields to include* in the overlay and which to omit + (`oldCode`, `modernCode`, `docs`, `related`, `prev`, `next` must all be + absent). +- If the AI accidentally includes any of those fields the build silently uses + the AI value instead of the canonical English one — hard to detect. +- Prompt engineering must explicitly say "only output these seven fields": + `title`, `summary`, `explanation`, `oldApproach`, `modernApproach`, + `whyModernWins`, `support.description`. +- The overlay schema is a non-standard concept; every new contributor (human + or AI) needs to learn it. + +### Plan B (full replacement) is ideal for AI + +- AI receives the full English JSON and outputs a complete translated JSON. + No special field-filtering rules. +- `oldCode` and `modernCode` are simply copied verbatim from the English file + at build time, regardless of what the AI wrote — the generator always + overwrites them. Zero prompt-engineering required to handle this case. +- The output is **self-contained and trivially validatable**: run the same JSON + schema checks as for English files. +- The fallback mechanism is explicit: if the AI-generated file does not yet + exist, the generator falls back to English and can display an "untranslated" + banner — a clear signal rather than a silent gap. +- `translations/strings/{locale}.json` (UI strings) is a simple key-value + file; AI can translate it in one shot with minimal instructions. + +--- + +## Recommended AI automation workflow + +1. **Trigger**: GitHub Actions detects a new `content//.json` commit + (or a workflow dispatch). +2. **AI step**: For each supported locale, call the translation AI with a + structured prompt: + ``` + Translate the following Java pattern JSON from English to {locale}. + - Keep slug, id, category, difficulty, jdkVersion, oldLabel, modernLabel, + oldCode, modernCode, docs, related, prev, next, support.state unchanged. + - Translate: title, summary, explanation, oldApproach, modernApproach, + whyModernWins[*].title, whyModernWins[*].desc, support.description. + - Return valid JSON only. + ``` +3. **Validate**: Run the same JSON schema validation used for English files. +4. **Write**: Commit the translated file to + `translations/content/{locale}//.json`. +5. **Build**: The generator picks it up on the next deployment and removes the + "untranslated" banner automatically. + +> **Note**: Even though Plan B asks translators to provide `oldCode` and +> `modernCode`, the build tooling always overwrites those fields with the +> English values. So AI can safely copy them verbatim — no risk of translated +> or hallucinated code leaking into the site. + +--- + +## Addressing Plan B's main risk + +Plan B's biggest concern is keeping translated content files in sync when the +English original changes (e.g. a corrected explanation). The AI workflow +mitigates this: + +- When a `content//.json` file is **modified**, the same automation + can regenerate the translated file, or flag the diff for human review. +- A simple CI check can compare the `jdkVersion`, `id`, and `slug` in the + English file with the translated file to catch stale translations. + +--- + +## Summary + +| Criterion | Plan A | Plan B | +|---|---|---| +| AI prompt complexity | Higher (must specify excluded fields) | Lower (translate whole file; build ignores code fields) | +| Risk of AI error slipping through | Higher (wrong field inclusion is silent) | Lower (code fields are always overwritten) | +| Validation of AI output | Schema + overlay semantics | Schema only | +| Fallback visibility | Silent (English shown without notice) | Explicit banner | +| Human translator experience | Must understand overlay contract | Self-contained file | +| **Overall for AI workflow** | ⚠️ Workable but fragile | ✅ Recommended | From ce997984c4acb6644bac98117b2f329ef3bff091 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:50:35 +0000 Subject: [PATCH 04/13] docs: remove Plan A, clean up Plan B and recommendation for single-plan focus Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- specs/i18n/plan-a-locale-keyed-content.md | 253 ------------------ ...-externalized-strings-full-translations.md | 18 +- specs/i18n/recommendation.md | 60 ++--- 3 files changed, 17 insertions(+), 314 deletions(-) delete mode 100644 specs/i18n/plan-a-locale-keyed-content.md diff --git a/specs/i18n/plan-a-locale-keyed-content.md b/specs/i18n/plan-a-locale-keyed-content.md deleted file mode 100644 index 97387bb..0000000 --- a/specs/i18n/plan-a-locale-keyed-content.md +++ /dev/null @@ -1,253 +0,0 @@ -# Plan A — Locale-Keyed Content Files - -## Overview - -Introduce locale-specific JSON content directories that mirror the existing -`content/` tree. The generator merges each locale's translations on top of the -English baseline at build time, then emits a fully localised copy of every -page under a locale-prefixed path (e.g. `site/pt-BR/language/slug.html`). - -A language selector in the navigation bar lets users switch locales. The -English site continues to live at `/` (no prefix) so existing links and -search-engine rankings are preserved. - ---- - -## Directory Layout - -``` -content/ # English source of truth (unchanged) - language/ - type-inference-with-var.json - collections/ - ... - -content/i18n/ # Locale overlays - pt-BR/ - language/ - type-inference-with-var.json # Only the fields that differ - ui.json # Navigation / section labels - ja/ - language/ - type-inference-with-var.json - ui.json - ... - -templates/ # Templates unchanged -html-generators/ - categories.properties # English display names - categories.pt-BR.properties # Localised category display names - generate.java / generate.py # Extended to accept a --locale flag - -site/ # Generated output - index.html # English home (no prefix) - language/ - type-inference-with-var.html - data/ - snippets.json # English search index - pt-BR/ # Locale subtree - index.html - language/ - type-inference-with-var.html - data/ - snippets.json - ja/ - ... -``` - ---- - -## JSON Overlay Schema - -Each locale overlay file contains only the fields that change; everything else -is inherited from the English baseline file. - -```json -// content/i18n/pt-BR/language/type-inference-with-var.json -{ - "title": "Inferência de tipo com var", - "summary": "Use var para deixar o compilador inferir o tipo local.", - "explanation": "...", - "oldApproach": "Tipos explícitos", - "modernApproach": "Palavra-chave var", - "whyModernWins": [ - { "icon": "⚡", "title": "Menos ruído", "desc": "..." }, - { "icon": "👁", "title": "Mais legível", "desc": "..." }, - { "icon": "🔒", "title": "Seguro", "desc": "..." } - ] -} -``` - -Fields **not** present in the overlay (`oldCode`, `modernCode`, `jdkVersion`, -`docs`, `related`, `prev`, `next`, `support`) are always taken from the -English baseline — code snippets, links, and navigation stay language-neutral. - -### `ui.json` — UI Strings - -```json -// content/i18n/pt-BR/ui.json -{ - "nav": { - "allPatterns": "← Todos os padrões", - "toggleTheme": "Alternar tema", - "viewOnGitHub": "Ver no GitHub" - }, - "sections": { - "codeComparison": "Comparação de código", - "whyModernWins": "Por que a forma moderna ganha", - "oldApproach": "Abordagem antiga", - "modernApproach": "Abordagem moderna", - "sinceJdk": "Desde o JDK", - "difficulty": "Dificuldade", - "jdkSupport": "Suporte ao JDK", - "howItWorks": "Como funciona", - "relatedDocs": "Documentação relacionada", - "relatedPatterns": "Padrões relacionados" - }, - "filters": { - "show": "Mostrar:", - "all": "Todos", - "difficulty": { - "beginner": "Iniciante", - "intermediate": "Intermediário", - "advanced": "Avançado" - } - }, - "search": { - "placeholder": "Buscar padrões...", - "noResults": "Nenhum resultado encontrado." - } -} -``` - ---- - -## Generator Changes - -1. **Accept `--locale` flag** (e.g. `generate.java --locale pt-BR`). - When omitted the generator builds only the English site (current - behaviour). - -2. **Merge algorithm**: - ``` - merged = deepMerge(english_baseline, locale_overlay) - ``` - Merge happens in memory; neither source file is modified. - -3. **Output path**: all files for locale `` are written to - `site//…` with relative asset paths adjusted - (`../../styles.css`, `../../app.js`, etc.). - -4. **Category display names**: loaded from - `html-generators/categories..properties` when it exists, - falling back to `categories.properties`. - -5. **`data/snippets.json`**: a separate - `site//data/snippets.json` is generated from the merged content - so the locale-specific search index is accurate. - -6. **Build all locales at once**: - ```bash - jbang html-generators/generate.java # English - jbang html-generators/generate.java --locale pt-BR - jbang html-generators/generate.java --locale ja - ``` - Or, when a build-all mode is added: - ```bash - jbang html-generators/generate.java --all-locales - ``` - ---- - -## Template Changes - -Templates (`templates/slug-template.html`, `templates/index.html`) gain -additional `{{…}}` tokens for localised UI strings: - -| Token | Source field | -|---|---| -| `{{labelCodeComparison}}` | `sections.codeComparison` | -| `{{labelWhyModernWins}}` | `sections.whyModernWins` | -| `{{labelHowItWorks}}` | `sections.howItWorks` | -| `{{labelRelatedDocs}}` | `sections.relatedDocs` | -| `{{labelRelatedPatterns}}` | `sections.relatedPatterns` | -| `{{navAllPatterns}}` | `nav.allPatterns` | -| `{{searchPlaceholder}}` | `search.placeholder` | - -English values are hard-coded defaults (matching the current literal strings) -so the English build requires no `ui.json`. - ---- - -## HTML `` Changes - -Each detail page gains `` tags and the `` -attribute is set to the locale: - -```html - - - ... - - - - -``` - ---- - -## Navigation — Language Selector - -A locale pill is added to the nav bar (right-hand side, beside the theme -toggle). It is rendered as a ` - - - - -``` - -JavaScript (`app.js`) listens for changes and navigates to the equivalent -page in the chosen locale by rewriting the path prefix. - ---- - -## GitHub Actions Changes - -The deploy workflow gains a step that loops over every locale directory in -`content/i18n/` and invokes the generator for each: - -```yaml -- name: Generate locales - run: | - jbang html-generators/generate.java - for locale in content/i18n/*/; do - loc=$(basename "$locale") - jbang html-generators/generate.java --locale "$loc" - done -``` - ---- - -## Migration Path - -| Phase | Work | -|---|---| -| 1 | Add `ui.json` English defaults; extract hard-coded label strings from templates into tokens | -| 2 | Extend generator to accept `--locale`, implement merge algorithm | -| 3 | Add language selector to nav; write `app.js` path-rewrite logic | -| 4 | Create first locale overlay (e.g. `pt-BR`) as a proof-of-concept | -| 5 | Update GitHub Actions; add `hreflang` and `` to templates | - ---- - -## Trade-offs - -| Pros | Cons | -|---|---| -| Overlay-only translations mean small locale files | Translators must know the JSON schema | -| English site path unchanged — SEO not affected | Each new locale doubles generated-file count | -| Build-time generation — no runtime i18n library needed | Generator becomes more complex | -| Code snippets stay language-neutral automatically | Navigation (`prev`/`next`) still points to English slugs (cross-locale navigation not addressed) | diff --git a/specs/i18n/plan-b-externalized-strings-full-translations.md b/specs/i18n/plan-b-externalized-strings-full-translations.md index 5637d81..b35825c 100644 --- a/specs/i18n/plan-b-externalized-strings-full-translations.md +++ b/specs/i18n/plan-b-externalized-strings-full-translations.md @@ -290,7 +290,7 @@ The `` tag becomes ``. ## Navigation — Language Selector -Same user-facing design as Plan A (a `` dropdown in the nav bar lets users switch locales. The list of locales is rendered at build time from `locales.properties`: ```html @@ -300,7 +300,7 @@ The list of locales is rendered at build time from `locales.properties`: ``` -`app.js` path-rewrite logic is identical to Plan A. +`app.js` handles path-rewriting when the user picks a different locale. --- @@ -381,17 +381,3 @@ Or, with a build-all mode added to the generator: | Untranslated-page fallback is explicit and user-visible | `translations/` directory adds a new top-level location to learn | | UI strings fall back to English automatically — no silent gaps | `app.js` must be updated to consume `window.i18n` instead of literals | | Scales cleanly to many locales | Build time grows linearly with locale count × pattern count | - ---- - -## Comparison with Plan A - -| | Plan A | Plan B | -|---|---|---| -| Translation file format | Overlay (partial JSON) | Full replacement JSON | -| UI strings | `content/i18n/{locale}/ui.json` | `translations/strings/{locale}.json` | -| Content location | `content/i18n/{locale}/…` | `translations/content/{locale}/…` | -| English templates | Keep some literals | Extract all literals to `en.json` | -| Fallback behaviour | Silent (English shown without notice) | Optional "not yet translated" banner | -| Generator complexity | Medium — deep-merge algorithm needed | Higher — full resolution pipeline | -| Translator experience | Must understand overlay semantics | Self-contained files, easier to translate | diff --git a/specs/i18n/recommendation.md b/specs/i18n/recommendation.md index 55fcb2c..4c85eb0 100644 --- a/specs/i18n/recommendation.md +++ b/specs/i18n/recommendation.md @@ -1,10 +1,10 @@ -# i18n Architecture Recommendation +# i18n Architecture: AI-Driven Translation Workflow -## TL;DR — **Plan B is recommended** for an AI-driven translation workflow. +The i18n architecture is based on **externalized UI strings and full-replacement content files** (see `plan-b-externalized-strings-full-translations.md`). --- -## Why Plan B wins when translations are AI-generated +## Why this approach works well with AI-generated translations When a new slug is added and AI generates the translations automatically, the pipeline becomes: @@ -13,23 +13,10 @@ pipeline becomes: New English slug → AI prompt → Translated JSON file → Commit to repo ``` -### Plan A (overlay) is awkward for AI +Key properties that make this AI-friendly: -- AI must know *which fields to include* in the overlay and which to omit - (`oldCode`, `modernCode`, `docs`, `related`, `prev`, `next` must all be - absent). -- If the AI accidentally includes any of those fields the build silently uses - the AI value instead of the canonical English one — hard to detect. -- Prompt engineering must explicitly say "only output these seven fields": - `title`, `summary`, `explanation`, `oldApproach`, `modernApproach`, - `whyModernWins`, `support.description`. -- The overlay schema is a non-standard concept; every new contributor (human - or AI) needs to learn it. - -### Plan B (full replacement) is ideal for AI - -- AI receives the full English JSON and outputs a complete translated JSON. - No special field-filtering rules. +- AI receives the full English JSON and outputs a complete translated JSON — + no special field-filtering rules in the prompt. - `oldCode` and `modernCode` are simply copied verbatim from the English file at build time, regardless of what the AI wrote — the generator always overwrites them. Zero prompt-engineering required to handle this case. @@ -63,33 +50,16 @@ New English slug → AI prompt → Translated JSON file → Commit to repo 5. **Build**: The generator picks it up on the next deployment and removes the "untranslated" banner automatically. -> **Note**: Even though Plan B asks translators to provide `oldCode` and -> `modernCode`, the build tooling always overwrites those fields with the -> English values. So AI can safely copy them verbatim — no risk of translated -> or hallucinated code leaking into the site. - ---- - -## Addressing Plan B's main risk - -Plan B's biggest concern is keeping translated content files in sync when the -English original changes (e.g. a corrected explanation). The AI workflow -mitigates this: - -- When a `content//.json` file is **modified**, the same automation - can regenerate the translated file, or flag the diff for human review. -- A simple CI check can compare the `jdkVersion`, `id`, and `slug` in the - English file with the translated file to catch stale translations. +> **Note**: Even though the full JSON asks for `oldCode` and `modernCode`, the +> build tooling always overwrites those fields with the English values. So AI +> can safely copy them verbatim — no risk of translated or hallucinated code +> leaking into the site. --- -## Summary +## Keeping translations in sync -| Criterion | Plan A | Plan B | -|---|---|---| -| AI prompt complexity | Higher (must specify excluded fields) | Lower (translate whole file; build ignores code fields) | -| Risk of AI error slipping through | Higher (wrong field inclusion is silent) | Lower (code fields are always overwritten) | -| Validation of AI output | Schema + overlay semantics | Schema only | -| Fallback visibility | Silent (English shown without notice) | Explicit banner | -| Human translator experience | Must understand overlay contract | Self-contained file | -| **Overall for AI workflow** | ⚠️ Workable but fragile | ✅ Recommended | +When an English `content//.json` file is **modified**, the same +automation can regenerate the translated file, or flag the diff for human +review. A simple CI check can compare `jdkVersion`, `id`, and `slug` in the +English file with the translated file to catch stale translations. From d301312bc06a7a20a254e45fff89319355da78a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:09:24 +0000 Subject: [PATCH 05/13] docs: rewrite Plan B as unified Internationalization Specification Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- ...ings-full-translations.md => i18n-spec.md} | 234 ++++++++++-------- specs/i18n/recommendation.md | 65 ----- 2 files changed, 131 insertions(+), 168 deletions(-) rename specs/i18n/{plan-b-externalized-strings-full-translations.md => i18n-spec.md} (53%) delete mode 100644 specs/i18n/recommendation.md diff --git a/specs/i18n/plan-b-externalized-strings-full-translations.md b/specs/i18n/i18n-spec.md similarity index 53% rename from specs/i18n/plan-b-externalized-strings-full-translations.md rename to specs/i18n/i18n-spec.md index b35825c..46eb9d4 100644 --- a/specs/i18n/plan-b-externalized-strings-full-translations.md +++ b/specs/i18n/i18n-spec.md @@ -1,36 +1,37 @@ -# Plan B — Externalized UI Strings + Full Translation Files +# Internationalization Specification ## Overview -Separate concerns into two distinct layers: +This document specifies how java.evolved supports multiple languages. +Internationalization is implemented via two distinct layers: 1. **UI strings layer** — every piece of hard-coded copy in the templates - (labels, button text, nav, footer, etc.) is moved into a per-locale - `strings/{locale}.json` file and injected at build time. + (labels, button text, nav, footer, etc.) is extracted into a per-locale + `translations/strings/{locale}.json` file and injected at build time. -2. **Content translation layer** — translated pattern JSON files are - complete, stand-alone replacements (not overlays) stored next to the - English originals. The generator falls back to the English file for any - pattern that has not yet been translated. +2. **Content translation layer** — translated pattern JSON files are complete, + stand-alone replacements stored under `translations/content/{locale}/`. + The generator falls back to the English file for any pattern that has not yet + been translated. -This approach treats English as just another locale and unifies the build -pipeline so all locales are first-class citizens. +English is a first-class locale. All locales — including English — go through +the same build pipeline. --- ## Directory Layout ``` -content/ # Unchanged English content +content/ # English content (source of truth) language/ type-inference-with-var.json collections/ ... -translations/ # New top-level directory +translations/ # All i18n artefacts strings/ en.json # English UI strings (extracted from templates) - pt-BR.json + pt-BR.json # Partial — missing keys fall back to en.json ja.json content/ pt-BR/ @@ -42,17 +43,17 @@ translations/ # New top-level directory language/ ... -templates/ # Templates gain {{…}} tokens for every - slug-template.html # hard-coded UI string (no literal English left) +templates/ # Templates use {{…}} tokens for every UI string + slug-template.html index.html ... html-generators/ locales.properties # Ordered list of supported locales + display names - generate.java / generate.py # Rewritten to iterate all locales + generate.java / generate.py # Extended to iterate all locales site/ # Generated output - index.html # English home (locale = en, path = /) + index.html # English home (path = /) language/ type-inference-with-var.html data/ @@ -69,9 +70,26 @@ site/ # Generated output --- -## `strings/{locale}.json` Schema +## `locales.properties` — Supported Locales Registry + +```properties +# html-generators/locales.properties +# format: locale=Display name (first entry is the default/primary locale) +en=English +pt-BR=Português (Brasil) +ja=日本語 +``` + +The generator reads this file to know which locales to build and what label +to show in the language selector. + +--- + +## `translations/strings/{locale}.json` Schema -Every user-visible string in the templates is assigned a dot-separated key: +Every user-visible string in the templates is assigned a dot-separated key. +The English file is the complete reference; locale files are partial and only +need to include keys that differ from English. ```json // translations/strings/en.json @@ -130,8 +148,7 @@ Every user-visible string in the templates is assigned a dot-separated key: ``` ```json -// translations/strings/pt-BR.json -// Strings files support partial translation: missing keys fall back to en.json at build time. +// translations/strings/pt-BR.json (partial — only translated keys required) { "site": { "tagline": "O Java evoluiu. Seu código também pode.", @@ -151,22 +168,15 @@ Every user-visible string in the templates is assigned a dot-separated key: } ``` -Missing keys in a locale's strings file fall back to the `en.json` value — -strings files are partial by design and only require the keys that need -translation. - -> **Note**: This partial-fallback behaviour applies to `strings/{locale}.json` -> **only**. Translated *content* files (see next section) are always complete -> replacements, not overlays. +Missing keys fall back to `en.json` at build time. --- -## Full-Replacement Content Files +## Content Translation Files -Unlike Plan A's overlay approach, translated content files are **complete** -copies of the pattern JSON with every field in the target language. This -avoids partial-merge edge cases and lets translators work with a self-contained -file. +Translated content files are **complete** copies of the English pattern JSON +with translatable fields rendered in the target language. This avoids +partial-merge edge cases and makes each file self-contained. ```json // translations/content/pt-BR/language/type-inference-with-var.json @@ -181,8 +191,8 @@ file. "modernLabel": "Java 10+", "oldApproach": "Tipos explícitos", "modernApproach": "Palavra-chave var", - "oldCode": "String nome = \"Alice\";\nString saudacao = \"Olá, \" + nome + \"!\";", - "modernCode": "var nome = \"Alice\";\nvar saudacao = \"Olá, %s!\".formatted(nome);", + "oldCode": "...", + "modernCode": "...", "summary": "Use var para deixar o compilador inferir o tipo local.", "explanation": "...", "whyModernWins": [ @@ -201,50 +211,33 @@ file. } ``` -`oldCode` and `modernCode` are always copied from the English file at build -time; translators must not provide them (or they are silently ignored). This -keeps code snippets language-neutral. +`oldCode` and `modernCode` are **always overwritten** with the English values at +build time, regardless of what appears in the translation file. Translators may +leave those fields empty or copy the English values verbatim — neither causes +any harm. --- -## `locales.properties` — Supported Locales Registry +## Generator — Resolution Order -```properties -# html-generators/locales.properties -# format: locale=Display name (first entry is the default/primary locale) -en=English -pt-BR=Português (Brasil) -ja=日本語 -``` - -The generator reads this file to know which locales to build and what label -to show in the language selector. - ---- - -## Generator Changes - -### Resolution Order - -For each pattern the generator: +For each pattern and locale the generator: 1. Loads the English baseline from `content//.json`. -2. Checks if `translations/content///.json` exists. - - If **yes**: use the translated file but override `oldCode`/`modernCode` +2. Checks whether `translations/content///.json` exists. + - **Yes** → use the translated file, then overwrite `oldCode`/`modernCode` with the English values. - - If **no**: use the English file and optionally mark the page with a - banner ("This pattern has not yet been translated"). -3. Loads `translations/strings/.json`, deep-merged over - `translations/strings/en.json`. -4. Renders the template, substituting both content tokens (`{{title}}`, …) - and UI-string tokens (`{{nav.allPatterns}}`, …). -5. Writes the output to `site///.html` - (or `site//.html` for `en`). + - **No** → use the English file and inject an "untranslated" banner + (see next section). +3. Loads `translations/strings/.json` deep-merged over `en.json`. +4. Renders the template, substituting content tokens (`{{title}}`, …) and + UI-string tokens (`{{nav.allPatterns}}`, …). +5. Writes output to `site///.html` + (or `site//.html` for English). -### Untranslated Pattern Banner (optional) +### Untranslated Pattern Banner When falling back to English content for a non-English locale, the generator -can inject a `
` banner: +injects: ```html
@@ -259,8 +252,8 @@ The banner is suppressed when the locale is `en` or a translation file exists. ## Template Changes -Every hard-coded English string in the templates is replaced with a token. -The token naming convention mirrors the key path in `strings/{locale}.json`: +Every hard-coded English string in the templates is replaced with a token whose +name mirrors the dot-separated key path in `strings/{locale}.json`: | Before | After | |---|---| @@ -272,7 +265,7 @@ The token naming convention mirrors the key path in `strings/{locale}.json`: | `Copied!` | `{{copy.copied}}` | | `Search patterns…` | `{{search.placeholder}}` | -The `` tag becomes ``. +The `` opening tag becomes ``. --- @@ -281,8 +274,8 @@ The `` tag becomes ``. `hreflang` alternate links are generated for every supported locale: ```html - - + + ``` @@ -295,18 +288,19 @@ The list of locales is rendered at build time from `locales.properties`: ```html ``` -`app.js` handles path-rewriting when the user picks a different locale. +`app.js` rewrites the current URL path to the equivalent page in the selected +locale when the user changes the selection. --- ## `app.js` Changes -The search index path and the locale picker rewrite must both be locale-aware: +The search index path and locale picker must both be locale-aware: ```js // Detect current locale from path prefix @@ -320,8 +314,8 @@ const indexPath = locale === 'en' : `/${locale}/data/snippets.json`; ``` -Localised strings needed by JavaScript (search placeholder, "no results" -message, "Copied!") are embedded as a ` ``` -`app.js` reads from `window.i18n` instead of hard-coded strings. +`app.js` reads from `window.i18n` instead of hard-coded literals. --- ## GitHub Actions Changes -The deploy workflow builds the English site first, then iterates every entry -in `locales.properties` (skipping `en`) to build locale subtrees: +The deploy workflow iterates all entries in `locales.properties`: + +```yaml +- name: Build site + run: python3 html-generators/generate.py --all-locales +``` + +Or explicitly, to support incremental locale addition: ```yaml - name: Build site run: | - python3 html-generators/generate.py # English + python3 html-generators/generate.py python3 html-generators/generate.py --locale pt-BR python3 html-generators/generate.py --locale ja ``` -Or, with a build-all mode added to the generator: +--- -```yaml -- name: Build site - run: python3 html-generators/generate.py --all-locales +## AI-Driven Translation Workflow + +When a new slug is added, AI generates translations automatically: + +``` +New English slug → AI prompt → Translated JSON file → Schema validation → Commit ``` +### Why this architecture suits AI translation + +- The AI receives the full English JSON and returns a complete translated JSON — + no special field-filtering rules in the prompt. +- `oldCode`/`modernCode` are overwritten by the build tooling, so AI can copy + them verbatim without risk of hallucinated code shipping to users. +- The translated file passes the same JSON schema validation as English files — + no separate validation logic needed. +- If the AI file does not exist yet, the fallback is an explicit "untranslated" + banner rather than a silent gap. + +### Automation steps + +1. **Trigger** — GitHub Actions detects a new or modified + `content//.json` (push event or workflow dispatch). +2. **Translate** — For each supported locale, call the translation model with: + ``` + Translate the following Java pattern JSON from English to {locale}. + - Keep unchanged: slug, id, category, difficulty, jdkVersion, oldLabel, + modernLabel, oldCode, modernCode, docs, related, prev, next, support.state + - Translate: title, summary, explanation, oldApproach, modernApproach, + whyModernWins[*].title, whyModernWins[*].desc, support.description + - Return valid JSON only. + ``` +3. **Validate** — Run JSON schema validation (same rules as English content). +4. **Commit** — Write the output to + `translations/content/{locale}//.json` and commit. +5. **Deploy** — The generator picks it up on next build; the "untranslated" + banner disappears automatically. + +### Keeping translations in sync + +When an English file is **modified**, the same automation regenerates the +translated file or opens a PR flagging the diff for human review. A CI check +can compare `id`, `slug`, and `jdkVersion` between the English and translated +files to detect stale translations. + --- ## Migration Path | Phase | Work | |---|---| -| 1 | Create `translations/strings/en.json` by extracting every hard-coded string from templates; replace literals with `{{…}}` tokens; verify English output is byte-identical | -| 2 | Add `locales.properties`; extend generator to load strings, support `--locale`, fall back gracefully | -| 3 | Add language selector to nav + `app.js` locale detection and path rewrite | +| 1 | Extract every hard-coded string from templates into `translations/strings/en.json`; replace literals with `{{…}}` tokens; verify English output is unchanged | +| 2 | Add `locales.properties`; extend generator to load strings, support `--locale`, and fall back gracefully | +| 3 | Add language selector to nav; implement `app.js` locale detection and path rewrite | | 4 | Translate `strings/pt-BR.json` and 2–3 content files as a proof-of-concept; verify fallback banner | | 5 | Update GitHub Actions; add `hreflang` alternate links | -| 6 | Open translation contribution guide; document `translations/` schema | - ---- - -## Trade-offs - -| Pros | Cons | -|---|---| -| English is a first-class locale — no special-cased code paths | Larger initial refactor (all template strings must be extracted) | -| Complete translation files are easy for translators to understand | Full content files are larger and harder to keep in sync with English originals | -| Untranslated-page fallback is explicit and user-visible | `translations/` directory adds a new top-level location to learn | -| UI strings fall back to English automatically — no silent gaps | `app.js` must be updated to consume `window.i18n` instead of literals | -| Scales cleanly to many locales | Build time grows linearly with locale count × pattern count | +| 6 | Wire up AI translation automation; add `translations/` schema documentation | diff --git a/specs/i18n/recommendation.md b/specs/i18n/recommendation.md deleted file mode 100644 index 4c85eb0..0000000 --- a/specs/i18n/recommendation.md +++ /dev/null @@ -1,65 +0,0 @@ -# i18n Architecture: AI-Driven Translation Workflow - -The i18n architecture is based on **externalized UI strings and full-replacement content files** (see `plan-b-externalized-strings-full-translations.md`). - ---- - -## Why this approach works well with AI-generated translations - -When a new slug is added and AI generates the translations automatically, the -pipeline becomes: - -``` -New English slug → AI prompt → Translated JSON file → Commit to repo -``` - -Key properties that make this AI-friendly: - -- AI receives the full English JSON and outputs a complete translated JSON — - no special field-filtering rules in the prompt. -- `oldCode` and `modernCode` are simply copied verbatim from the English file - at build time, regardless of what the AI wrote — the generator always - overwrites them. Zero prompt-engineering required to handle this case. -- The output is **self-contained and trivially validatable**: run the same JSON - schema checks as for English files. -- The fallback mechanism is explicit: if the AI-generated file does not yet - exist, the generator falls back to English and can display an "untranslated" - banner — a clear signal rather than a silent gap. -- `translations/strings/{locale}.json` (UI strings) is a simple key-value - file; AI can translate it in one shot with minimal instructions. - ---- - -## Recommended AI automation workflow - -1. **Trigger**: GitHub Actions detects a new `content//.json` commit - (or a workflow dispatch). -2. **AI step**: For each supported locale, call the translation AI with a - structured prompt: - ``` - Translate the following Java pattern JSON from English to {locale}. - - Keep slug, id, category, difficulty, jdkVersion, oldLabel, modernLabel, - oldCode, modernCode, docs, related, prev, next, support.state unchanged. - - Translate: title, summary, explanation, oldApproach, modernApproach, - whyModernWins[*].title, whyModernWins[*].desc, support.description. - - Return valid JSON only. - ``` -3. **Validate**: Run the same JSON schema validation used for English files. -4. **Write**: Commit the translated file to - `translations/content/{locale}//.json`. -5. **Build**: The generator picks it up on the next deployment and removes the - "untranslated" banner automatically. - -> **Note**: Even though the full JSON asks for `oldCode` and `modernCode`, the -> build tooling always overwrites those fields with the English values. So AI -> can safely copy them verbatim — no risk of translated or hallucinated code -> leaking into the site. - ---- - -## Keeping translations in sync - -When an English `content//.json` file is **modified**, the same -automation can regenerate the translated file, or flag the diff for human -review. A simple CI check can compare `jdkVersion`, `id`, and `slug` in the -English file with the translated file to catch stale translations. From 9c2ff5553b7044cf256920cc53acd0aa8ae12fc2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:17:04 +0000 Subject: [PATCH 06/13] docs: replace Python generator references with jbang in i18n spec Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- specs/i18n/i18n-spec.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/i18n/i18n-spec.md b/specs/i18n/i18n-spec.md index 46eb9d4..4cf2aab 100644 --- a/specs/i18n/i18n-spec.md +++ b/specs/i18n/i18n-spec.md @@ -50,7 +50,7 @@ templates/ # Templates use {{…}} tokens for every U html-generators/ locales.properties # Ordered list of supported locales + display names - generate.java / generate.py # Extended to iterate all locales + generate.java # Extended to iterate all locales site/ # Generated output index.html # English home (path = /) @@ -337,7 +337,7 @@ The deploy workflow iterates all entries in `locales.properties`: ```yaml - name: Build site - run: python3 html-generators/generate.py --all-locales + run: jbang html-generators/generate.java --all-locales ``` Or explicitly, to support incremental locale addition: @@ -345,9 +345,9 @@ Or explicitly, to support incremental locale addition: ```yaml - name: Build site run: | - python3 html-generators/generate.py - python3 html-generators/generate.py --locale pt-BR - python3 html-generators/generate.py --locale ja + jbang html-generators/generate.java + jbang html-generators/generate.java --locale pt-BR + jbang html-generators/generate.java --locale ja ``` --- From 4b2330dfa96e46dec969745b5867aa72f3e8ca5b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:54:43 +0000 Subject: [PATCH 07/13] docs: specify per-key English fallback with WARN logging for missing string keys Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- specs/i18n/i18n-spec.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/specs/i18n/i18n-spec.md b/specs/i18n/i18n-spec.md index 4cf2aab..03a03b0 100644 --- a/specs/i18n/i18n-spec.md +++ b/specs/i18n/i18n-spec.md @@ -168,7 +168,15 @@ need to include keys that differ from English. } ``` -Missing keys fall back to `en.json` at build time. +**Key-level fallback rule:** if a key present in `en.json` is absent from a +locale file, the generator uses the English value and emits a build-time warning: + +``` +[WARN] strings/pt-BR.json: missing key "footer.builtWith" — using English fallback +``` + +The page is always rendered completely; no key is ever silently blank. The warning +is purely informational and does **not** abort the build. --- @@ -229,6 +237,9 @@ For each pattern and locale the generator: - **No** → use the English file and inject an "untranslated" banner (see next section). 3. Loads `translations/strings/.json` deep-merged over `en.json`. + Any key present in `en.json` but absent from the locale file falls back to + the English value; the generator logs a `[WARN]` for each missing key and + continues without aborting. 4. Renders the template, substituting content tokens (`{{title}}`, …) and UI-string tokens (`{{nav.allPatterns}}`, …). 5. Writes output to `site///.html` From 8df8d8c7391f955677cdf39dacb40d4b12f57a4d Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 25 Feb 2026 15:38:55 -0500 Subject: [PATCH 08/13] Update specs/i18n/i18n-spec.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- specs/i18n/i18n-spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/i18n/i18n-spec.md b/specs/i18n/i18n-spec.md index 03a03b0..557bfbb 100644 --- a/specs/i18n/i18n-spec.md +++ b/specs/i18n/i18n-spec.md @@ -172,7 +172,7 @@ need to include keys that differ from English. locale file, the generator uses the English value and emits a build-time warning: ``` -[WARN] strings/pt-BR.json: missing key "footer.builtWith" — using English fallback +[WARN] strings/pt-BR.json: missing key "footer.madeWith" — using English fallback ``` The page is always rendered completely; no key is ever silently blank. The warning From 8881175aa57a4e848781f1a55346950498c99843 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 25 Feb 2026 15:39:03 -0500 Subject: [PATCH 09/13] Update specs/i18n/i18n-spec.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- specs/i18n/i18n-spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/i18n/i18n-spec.md b/specs/i18n/i18n-spec.md index 557bfbb..8ef6b2a 100644 --- a/specs/i18n/i18n-spec.md +++ b/specs/i18n/i18n-spec.md @@ -28,7 +28,7 @@ content/ # English content (source of truth) collections/ ... -translations/ # All i18n artefacts +translations/ # All i18n artifacts strings/ en.json # English UI strings (extracted from templates) pt-BR.json # Partial — missing keys fall back to en.json From add7dec9cf3f1edfecf467ff8afdd837e9d6e759 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:41:29 +0000 Subject: [PATCH 10/13] docs: replace select dropdown with globe icon button + dropdown for language selector Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- specs/i18n/i18n-spec.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/specs/i18n/i18n-spec.md b/specs/i18n/i18n-spec.md index 8ef6b2a..4fc3fcf 100644 --- a/specs/i18n/i18n-spec.md +++ b/specs/i18n/i18n-spec.md @@ -294,18 +294,29 @@ The `` opening tag becomes ``. ## Navigation — Language Selector -A ` - - - + +
+ +
    +
  • English
  • +
  • Português (Brasil)
  • +
+
``` -`app.js` rewrites the current URL path to the equivalent page in the selected -locale when the user changes the selection. +When the user selects a locale, `app.js` rewrites the current URL path to the +equivalent page for that locale and persists the choice to `localStorage` so +subsequent page loads open in the correct language automatically. + +The dropdown is hidden by default and toggled via the `.locale-toggle` button. +Clicking outside the dropdown or pressing `Escape` closes it. The currently +active locale's `
  • ` receives `aria-selected="true"` and a visual highlight. --- From 7101fe669a15a396069bbaed164d498aeb9ec756 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 25 Feb 2026 15:49:23 -0500 Subject: [PATCH 11/13] Update specs/i18n/i18n-spec.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- specs/i18n/i18n-spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/i18n/i18n-spec.md b/specs/i18n/i18n-spec.md index 4fc3fcf..aed25fe 100644 --- a/specs/i18n/i18n-spec.md +++ b/specs/i18n/i18n-spec.md @@ -301,7 +301,7 @@ locales. The list is rendered at build time from `locales.properties`. ```html
    -
    • English
    • From 32b6a219168cdaf8ae50a85a4e7021ac8ee06a08 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 25 Feb 2026 15:49:49 -0500 Subject: [PATCH 12/13] Update specs/i18n/i18n-spec.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- specs/i18n/i18n-spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/i18n/i18n-spec.md b/specs/i18n/i18n-spec.md index aed25fe..b806cce 100644 --- a/specs/i18n/i18n-spec.md +++ b/specs/i18n/i18n-spec.md @@ -126,7 +126,7 @@ need to include keys that differ from English. } }, "search": { - "placeholder": "Search patterns…", + "placeholder": "Search snippets…", "noResults": "No results found.", "esc": "ESC" }, From af3884ec4114a5d9673e19044a4c1709ffcc49c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:53:26 +0000 Subject: [PATCH 13/13] docs: add all 11 categories (including enterprise) to i18n-spec directory layout Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- specs/i18n/i18n-spec.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/specs/i18n/i18n-spec.md b/specs/i18n/i18n-spec.md index b806cce..52a8dea 100644 --- a/specs/i18n/i18n-spec.md +++ b/specs/i18n/i18n-spec.md @@ -24,9 +24,16 @@ the same build pipeline. ``` content/ # English content (source of truth) language/ - type-inference-with-var.json collections/ - ... + strings/ + streams/ + concurrency/ + io/ + errors/ + datetime/ + security/ + tooling/ + enterprise/ translations/ # All i18n artifacts strings/ @@ -38,7 +45,15 @@ translations/ # All i18n artifacts language/ type-inference-with-var.json # Full translated JSON (all fields) collections/ - ... + strings/ + streams/ + concurrency/ + io/ + errors/ + datetime/ + security/ + tooling/ + enterprise/ ja/ language/ ...