Skip to content

i18n: scaffold IT/EN (Phase 1+2+3 + sample + parity gate)#11

Merged
abonforti merged 4 commits into
azzurra:masterfrom
vjt:i18n/scaffold
May 10, 2026
Merged

i18n: scaffold IT/EN (Phase 1+2+3 + sample + parity gate)#11
abonforti merged 4 commits into
azzurra:masterfrom
vjt:i18n/scaffold

Conversation

@vjt
Copy link
Copy Markdown
Contributor

@vjt vjt commented May 10, 2026

Scope (Phases 1+2+3 + sample + parity gate)

This is PR-1 of N in the multilingual rollout. It lays the entire scaffold so every subsequent PR is just a content drop.

Phase 1 — config split

  • hugo.toml restructured into [languages.it] (default, served at /) and [languages.en] (served at /en/).
  • defaultContentLanguageInSubdir = false keeps Italian URLs unchanged.
  • Stats labels, menu names and meta description translated per-language.
  • EN main/footer menus point to translated slugs (/en/history, /en/connect, etc.). Those URLs 404 on purpose until Phase 4 lands the translated content — see "Deferred" below.

Phase 2 — theme i18n extraction

  • New themes/azzurra/i18n/{it,en}.toml (~22 keys: nav, footer, breadcrumbs, hero, CTAs, news section, archive count, post meta, info-box default).
  • All hardcoded UI strings in _default/{baseof,list,single}.html, index.html, shortcodes/info.html replaced with {{ i18n "key" }}.
  • relURLrelLangURL for internal anchors so /en/ pages keep their language prefix.

Phase 3 — date format localization

  • dateFormat key per language, rendered with time.Format (i18n "dateFormat") .Date.
  • Honors the page's languageCode10 maggio 2026 on /, May 10, 2026 on /en/.

Sample translation

  • content/news/gamebot.en.md (sibling of gamebot.md, same slug). Proves the filename-suffix layout end-to-end.

Parity gate

  • .githooks/pre-commit blocks any commit that adds/modifies a content/*.md file without a sibling .en.md (excludes _index.md and *.en.md itself).
  • .github/workflows/i18n-parity.yml mirrors the check server-side as the non-bypassable gate. --no-verify only skips the local fast-iteration hook.
  • Activate locally per-clone: git config core.hooksPath .githooks (one-time, see .githooks/README.md).

Smoke build

hugomods/hugo:exts-0.145.0 clean run on this branch:

                   | IT | EN
-------------------+----+-----
  Pages            | 42 | 16
  Static files     |  5 |  5
  Aliases          | 12 |  6
  Cleaned          |  0 |  0

Total in 91 ms — zero i18n warnings

What's deferred

  • Phase 4 — translate the remaining 15 content files (storia.en.md, come-connettersi.en.md, etc.). Will land as small PRs grouped by section, each unblocking the EN URL it covers. The pre-commit gate enforces this incrementally.
  • Phase 5 — add aliases: on translated EN pages if external links to old IT-slug-under-/en/ paths show up in analytics.
  • Live 404 handling — Hugo has no built-in "fallback to IT for missing EN page". If we want that we'd add a small render hook or accept the 404s until Phase 4 fills them.

Decision points (already answered before scaffolding)

  1. EN fallback — ok, but kept lightweight (no synthetic redirects yet).
  2. Slug strategy — translated (/en/history, /en/connect).
  3. News scope — translate all 8 posts.
  4. Translation source — LLM-pre-translate + human review.
  5. Branch strategy — PR per phase, this is PR-1.

Plus Hypnotize's pre-commit ask landed in this PR (.githooks/pre-commit + CI mirror).

Phase 1 (config split):
- hugo.toml restructured into [languages.it] (default, served at /) and
  [languages.en] (served at /en/)
- defaultContentLanguageMissing left at "fallback" implicitly so missing
  EN pages render the IT version instead of 404
- Stats labels and menu names translated per-language

Phase 2 (theme i18n extraction):
- themes/azzurra/i18n/{it,en}.toml with ~22 keys
- All hardcoded UI strings in baseof / index / list / single / info
  shortcode replaced with {{ i18n "key" }}
- Internal links use relLangURL so /en/ pages keep their language prefix

Phase 3 (date format):
- dateFormat key per language, rendered via time.Format which honors the
  page's languageCode → "10 maggio 2026" on /it, "May 10, 2026" on /en

Sample translation:
- content/news/gamebot.en.md as proof-of-concept; same slug across langs

Parity gate:
- .githooks/pre-commit blocks any commit that adds/modifies content/*.md
  without a sibling .en.md (excludes _index.md and *.en.md itself)
- .github/workflows/i18n-parity.yml mirrors the check server-side as the
  non-bypassable gate; --no-verify only skips the local fast-iteration hook

Smoke build (hugomods/hugo:exts-0.145.0): clean, IT+EN sites render,
58 pages total, zero i18n warnings.
Comment thread .githooks/pre-commit Outdated
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# i18n parity gate — block commits that add/modify an IT content file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be also the opposite way, an EN content without the IT counterpart

<span>© 1997–{{ now.Year }} Azzurra IRC Network. Tutti i diritti riservati.</span>
<span>Staff: <a href="mailto:hypnotize@azzurra.chat">Hypnotize</a> · <a href="mailto:sonic@azzurra.chat">Sonic</a> · <a href="mailto:joep@azzurra.chat">joep</a> · <a href="mailto:mezmerize@azzurra.chat">Mezmerize</a> · <a href="mailto:scorpion@azzurra.chat">Scorpion</a></span>
<span>{{ i18n "footerCopyright" (dict "Year" now.Year) }}</span>
<span>{{ i18n "footerStaffPrefix" }} <a href="mailto:hypnotize@azzurra.chat">Hypnotize</a> · <a href="mailto:sonic@azzurra.chat">Sonic</a> · <a href="mailto:joep@azzurra.chat">joep</a> · <a href="mailto:mezmerize@azzurra.chat">Mezmerize</a> · <a href="mailto:scorpion@azzurra.chat">Scorpion</a></span>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add as well vjt (vjt@azzurra.chat)

Hypnotize requested:
1. Parity gate must work both ways: an EN content file without an IT
   counterpart should also block. Refactored .githooks/pre-commit and
   .github/workflows/i18n-parity.yml to enforce sibling existence in
   either direction.
2. Add vjt (vjt@azzurra.chat) to the staff mailto block in baseof.

Bonus: workflow trigger branch fixed master (was main).

Smoke build still green: IT 42 + EN 16 pages, zero warnings.
vjt added 2 commits May 10, 2026 21:40
The original full-tree scan blocked PR#11 because the scaffold ships only
one sample .en.md while the rest of content/ is still IT-only — exactly
the state PR#12 is meant to fix. Bidirectional enforcement still applies,
just only to files touched in the current PR (mirrors the delta-based
.githooks/pre-commit hook). The incremental scaffold → content → aliases
landing pattern now works without each PR having to ship a complete set.
Replaces the filename-suffix layout (content/foo.md + content/foo.en.md
with slug overrides) with explicit directory split. Translated pages now
live under their own language tree with English filenames matching the
public URL, so reviewers see content/en/news/gamebot.md rather than
content/news/gamebot.en.md with slug=gamebot — the github file URL no
longer contradicts the language of the content.

Pairing across languages is declared via the translationKey frontmatter
field rather than inferred from a shared filename stem. The scaffold
sample (news/gamebot) ships paired with translationKey: news-gamebot on
both sides; PR#12 adds the matching keys on the other pages as their EN
counterparts land.

hugo.toml: per-language contentDir lets Hugo route each tree
independently. defaultContentLanguage=it keeps IT URLs at the root
(no /it/ prefix), EN remains at /en/.

Parity gate: the pre-commit hook and the GitHub Action workflow both
switch from filename-stem matching to translationKey matching, using
identical extraction logic. Pages without a translationKey are treated
as not-yet-paired (free pass) — the gate enforces well-formed pairs, it
does not force every page to be translated. Only declared keys must
resolve in the other language tree.

Smoke build: hugo 0.145.0+extended → IT 42 + EN 16 pages, zero warnings,
gamebot sample renders at /en/news/gamebot/ (filename = slug).
@abonforti abonforti marked this pull request as ready for review May 10, 2026 22:00
@Essency Essency self-requested a review May 10, 2026 22:04
@abonforti abonforti merged commit b357c68 into azzurra:master May 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants