i18n: scaffold IT/EN (Phase 1+2+3 + sample + parity gate)#11
Merged
Conversation
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.
abonforti
requested changes
May 10, 2026
| @@ -0,0 +1,51 @@ | |||
| #!/usr/bin/env bash | |||
| # i18n parity gate — block commits that add/modify an IT content file | |||
Contributor
There was a problem hiding this comment.
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> |
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.
This was referenced May 10, 2026
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
approved these changes
May 10, 2026
Essency
approved these changes
May 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.tomlrestructured into[languages.it](default, served at/) and[languages.en](served at/en/).defaultContentLanguageInSubdir = falsekeeps Italian URLs unchanged./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
themes/azzurra/i18n/{it,en}.toml(~22 keys: nav, footer, breadcrumbs, hero, CTAs, news section, archive count, post meta, info-box default)._default/{baseof,list,single}.html,index.html,shortcodes/info.htmlreplaced with{{ i18n "key" }}.relURL→relLangURLfor internal anchors so/en/pages keep their language prefix.Phase 3 — date format localization
dateFormatkey per language, rendered withtime.Format (i18n "dateFormat") .Date.languageCode→10 maggio 2026on/,May 10, 2026on/en/.Sample translation
content/news/gamebot.en.md(sibling ofgamebot.md, same slug). Proves the filename-suffix layout end-to-end.Parity gate
.githooks/pre-commitblocks any commit that adds/modifies acontent/*.mdfile without a sibling.en.md(excludes_index.mdand*.en.mditself)..github/workflows/i18n-parity.ymlmirrors the check server-side as the non-bypassable gate.--no-verifyonly skips the local fast-iteration hook.git config core.hooksPath .githooks(one-time, see.githooks/README.md).Smoke build
hugomods/hugo:exts-0.145.0clean run on this branch:What's deferred
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.aliases:on translated EN pages if external links to old IT-slug-under-/en/ paths show up in analytics.Decision points (already answered before scaffolding)
/en/history,/en/connect).Plus Hypnotize's pre-commit ask landed in this PR (
.githooks/pre-commit+ CI mirror).