From 5184b4dc8eb082bf7e757cef625281625c051bb4 Mon Sep 17 00:00:00 2001 From: Marcello Barnaba Date: Sun, 10 May 2026 21:07:34 +0000 Subject: [PATCH 1/6] i18n scaffolding: phases 1+2+3 + sample EN page + parity gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .githooks/README.md | 27 ++ .githooks/pre-commit | 51 ++++ .github/workflows/i18n-parity.yml | 42 +++ content/news/gamebot.en.md | 39 +++ hugo.toml | 291 +++++++++++++------- themes/azzurra/i18n/en.toml | 80 ++++++ themes/azzurra/i18n/it.toml | 80 ++++++ themes/azzurra/layouts/_default/baseof.html | 17 +- themes/azzurra/layouts/_default/list.html | 8 +- themes/azzurra/layouts/_default/single.html | 6 +- themes/azzurra/layouts/index.html | 26 +- themes/azzurra/layouts/shortcodes/info.html | 2 +- 12 files changed, 539 insertions(+), 130 deletions(-) create mode 100644 .githooks/README.md create mode 100755 .githooks/pre-commit create mode 100644 .github/workflows/i18n-parity.yml create mode 100644 content/news/gamebot.en.md create mode 100644 themes/azzurra/i18n/en.toml create mode 100644 themes/azzurra/i18n/it.toml diff --git a/.githooks/README.md b/.githooks/README.md new file mode 100644 index 0000000..18666de --- /dev/null +++ b/.githooks/README.md @@ -0,0 +1,27 @@ +# Repo git hooks + +This directory holds repo-tracked git hooks. They are **not** active by default +on a fresh clone — git looks at `.git/hooks/` first, which is local and untracked. + +## One-time activation per clone + +Run from the repo root: + +```bash +git config core.hooksPath .githooks +``` + +That's it. From now on, hooks in this directory will fire on the appropriate +events. + +## What's here + +- **`pre-commit`** — i18n parity gate. Blocks any commit that adds/modifies a + `content/*.md` file without a matching `.en.md` sibling. Mirrors the + server-side `.github/workflows/i18n-parity.yml` check. + +## Bypassing + +In a real emergency: `git commit --no-verify`. The server-side workflow is the +non-bypassable gate, so the only thing `--no-verify` buys you is faster local +iteration before push. diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..36a1fd2 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# i18n parity gate — block commits that add/modify an IT content file +# without a matching .en.md sibling. +# +# Pairing rule: foo.md (Italian, no language suffix) requires foo.en.md. +# Excluded: hidden/dot files, _index.md (treated as section index, optional EN), +# anything already a .en.md, anything outside content/. +# +# Bypass for emergencies: git commit --no-verify (CI parity check is the +# non-bypassable gate — see .github/workflows/i18n-parity.yml). + +set -euo pipefail + +# Files staged for commit (Added/Modified/Renamed), filtered to content/*.md +mapfile -t staged < <( + git diff --cached --name-only --diff-filter=AMR \ + | grep -E '^content/.*\.md$' || true +) + +[[ ${#staged[@]} -eq 0 ]] && exit 0 + +missing=() +for f in "${staged[@]}"; do + base=$(basename "$f") + + # Skip already-EN files + [[ "$base" == *.en.md ]] && continue + + # Skip section indexes (Hugo accepts IT-only _index.md) + [[ "$base" == _index.md ]] && continue + + dir=$(dirname "$f") + stem="${base%.md}" + en_path="${dir}/${stem}.en.md" + + # Either the .en.md exists in the working tree OR is also being staged + if [[ ! -f "$en_path" ]] && ! printf '%s\n' "${staged[@]}" | grep -qx "$en_path"; then + missing+=("$f → expected $en_path") + fi +done + +if [[ ${#missing[@]} -gt 0 ]]; then + echo "✗ i18n parity check failed: missing English translations" >&2 + printf ' - %s\n' "${missing[@]}" >&2 + echo >&2 + echo "Add the matching .en.md file(s) before committing." >&2 + echo "Bypass (last resort): git commit --no-verify" >&2 + exit 1 +fi + +exit 0 diff --git a/.github/workflows/i18n-parity.yml b/.github/workflows/i18n-parity.yml new file mode 100644 index 0000000..91f748f --- /dev/null +++ b/.github/workflows/i18n-parity.yml @@ -0,0 +1,42 @@ +name: i18n parity + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + parity: + name: IT/EN parity check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Verify every content/*.md has a sibling .en.md + run: | + set -euo pipefail + missing=() + # Find every .md under content/ that is NOT already an .en.md + # and NOT a section _index.md (those are allowed IT-only). + while IFS= read -r f; do + base=$(basename "$f") + [[ "$base" == *.en.md ]] && continue + [[ "$base" == _index.md ]] && continue + + dir=$(dirname "$f") + stem="${base%.md}" + en_path="${dir}/${stem}.en.md" + + if [[ ! -f "$en_path" ]]; then + missing+=("$f → expected $en_path") + fi + done < <(find content -type f -name '*.md' | sort) + + if [[ ${#missing[@]} -gt 0 ]]; then + echo "::error::i18n parity broken — missing English translations:" + printf ' - %s\n' "${missing[@]}" + exit 1 + fi + + echo "✓ i18n parity OK" diff --git a/content/news/gamebot.en.md b/content/news/gamebot.en.md new file mode 100644 index 0000000..732dced --- /dev/null +++ b/content/news/gamebot.en.md @@ -0,0 +1,39 @@ +--- +title: "GameBot has landed on Azzurra!" +date: 2026-05-10 +author: "Azzurra Staff" +tags: ["announcement", "bot", "games"] +summary: "Card games, poker, trivia and UNO right inside your channel: GameBot brings tabletop fun to Azzurra." +slug: gamebot +--- + +## A new companion in chat 🎲 +We're happy to introduce **GameBot**, the new official Azzurra bot dedicated to in-channel games. Built to liven up evenings and recreate that virtual-tabletop atmosphere that's always been part of IRC communities, GameBot brings four timeless classics into our rooms. + +## Available games +- 🃏 **Sette e Mezzo**: the classic Italian card game where luck and steady nerves make the difference. Get as close to seven-and-a-half as you can without busting. +- ♠️ **Texas Hold'em**: the world's most popular poker, channel edition. Bluff, raise and all-in with friends — no real bets, but all the ego on the line. +- 🧠 **Trivia**: rapid-fire questions on general knowledge, cinema, music, sports and much more. +- 🎴 **UNO**: the card game that needs no introduction. Draw two, skip a turn, swap colors — and never forget to call "UNO!". + +## How to start playing +To get GameBot in your channel, just invite it: + +``` +/invite GameBot #yourchannel +``` + +Once it joins, the full list of available games is one command away: + +``` +!listgames +``` + +From there, every match is started with the dedicated commands GameBot will explain on the fly. + +## Why GameBot +We believe IRC is much more than a chat: it's a place to socialise. GameBot was born to give you one more reason to stay connected, challenge your friends and make new acquaintances around a virtual table. No app to install, no extra accounts: just the commands you already know. + +Have fun, and may the best player win! + +See you in chat 👋 diff --git a/hugo.toml b/hugo.toml index dc41ad1..9a2292f 100644 --- a/hugo.toml +++ b/hugo.toml @@ -1,109 +1,200 @@ -baseURL = "https://azzurra.github.io" -languageCode = "it-IT" -title = "Azzurra IRC Network" -theme = "azzurra" +baseURL = "https://azzurra.github.io" +title = "Azzurra IRC Network" +theme = "azzurra" + +defaultContentLanguage = "it" +defaultContentLanguageInSubdir = false [pagination] pagerSize = 10 [params] - description = "La prima e più longeva rete IRC italiana, dal 1997. Connettiti su irc.azzurra.chat." - webchatURL = "https://webchat.azzurra.chat" - - [[params.stats]] - value = "1997" - label = "Fondata" - [[params.stats]] - value = "27+" - label = "Anni online" - [[params.stats]] - value = "100+" - label = "Canali attivi" - [[params.stats]] - value = "24/7" - label = "Uptime" - -# Main navigation -[[menus.main]] - name = "Home" - url = "/" - weight = 1 - -[[menus.main]] - name = "La Storia" - url = "/storia" - weight = 2 - -[[menus.main]] - name = "Come Connettersi" - url = "/come-connettersi" - weight = 3 - -[[menus.main]] - name = "Il Network" - url = "/network" - weight = 4 - -[[menus.main]] - name = "FAQ" - url = "/faq" - weight = 5 - -[[menus.main]] - name = "Contatti" - url = "/contatti" - weight = 6 - -# Footer menus -[[menus.footer]] - name = "Notizie" - url = "/news" - weight = 1 - [menus.footer.params] - col = "rete" - -[[menus.footer]] - name = "I Server" - url = "/network" - weight = 2 - [menus.footer.params] - col = "rete" - -[[menus.footer]] - name = "I Servizi" - url = "/servizi" - weight = 3 - [menus.footer.params] - col = "rete" - -[[menus.footer]] - name = "La Storia" - url = "/storia" - weight = 1 - [menus.footer.params] - col = "info" - -[[menus.footer]] - name = "Regolamento" - url = "/regolamento" - weight = 2 - [menus.footer.params] - col = "info" - -[[menus.footer]] - name = "FAQ" - url = "/faq" - weight = 3 - [menus.footer.params] - col = "info" - -[[menus.footer]] - name = "Contatti" - url = "/contatti" - weight = 4 - [menus.footer.params] - col = "info" + webchatURL = "https://webchat.azzurra.chat" [caches] [caches.images] dir = ':cacheDir/images' + +# ───────────────────────────────────────────────────────────────────── +# Italian (default — served at /) +# ───────────────────────────────────────────────────────────────────── +[languages.it] + languageName = "Italiano" + languageCode = "it-IT" + weight = 1 + + [languages.it.params] + description = "La prima e più longeva rete IRC italiana, dal 1997. Connettiti su irc.azzurra.chat." + + [[languages.it.params.stats]] + value = "1997" + label = "Fondata" + [[languages.it.params.stats]] + value = "27+" + label = "Anni online" + [[languages.it.params.stats]] + value = "100+" + label = "Canali attivi" + [[languages.it.params.stats]] + value = "24/7" + label = "Uptime" + + [[languages.it.menus.main]] + name = "Home" + url = "/" + weight = 1 + [[languages.it.menus.main]] + name = "La Storia" + url = "/storia" + weight = 2 + [[languages.it.menus.main]] + name = "Come Connettersi" + url = "/come-connettersi" + weight = 3 + [[languages.it.menus.main]] + name = "Il Network" + url = "/network" + weight = 4 + [[languages.it.menus.main]] + name = "FAQ" + url = "/faq" + weight = 5 + [[languages.it.menus.main]] + name = "Contatti" + url = "/contatti" + weight = 6 + + [[languages.it.menus.footer]] + name = "Notizie" + url = "/news" + weight = 1 + [languages.it.menus.footer.params] + col = "rete" + [[languages.it.menus.footer]] + name = "I Server" + url = "/network" + weight = 2 + [languages.it.menus.footer.params] + col = "rete" + [[languages.it.menus.footer]] + name = "I Servizi" + url = "/servizi" + weight = 3 + [languages.it.menus.footer.params] + col = "rete" + [[languages.it.menus.footer]] + name = "La Storia" + url = "/storia" + weight = 1 + [languages.it.menus.footer.params] + col = "info" + [[languages.it.menus.footer]] + name = "Regolamento" + url = "/regolamento" + weight = 2 + [languages.it.menus.footer.params] + col = "info" + [[languages.it.menus.footer]] + name = "FAQ" + url = "/faq" + weight = 3 + [languages.it.menus.footer.params] + col = "info" + [[languages.it.menus.footer]] + name = "Contatti" + url = "/contatti" + weight = 4 + [languages.it.menus.footer.params] + col = "info" + +# ───────────────────────────────────────────────────────────────────── +# English (served at /en/) +# ───────────────────────────────────────────────────────────────────── +[languages.en] + languageName = "English" + languageCode = "en-US" + weight = 2 + + [languages.en.params] + description = "Italy's longest-running IRC community, since 1997. Connect at irc.azzurra.chat." + + [[languages.en.params.stats]] + value = "1997" + label = "Founded" + [[languages.en.params.stats]] + value = "27+" + label = "Years online" + [[languages.en.params.stats]] + value = "100+" + label = "Active channels" + [[languages.en.params.stats]] + value = "24/7" + label = "Uptime" + + [[languages.en.menus.main]] + name = "Home" + url = "/en/" + weight = 1 + [[languages.en.menus.main]] + name = "History" + url = "/en/history" + weight = 2 + [[languages.en.menus.main]] + name = "Connect" + url = "/en/connect" + weight = 3 + [[languages.en.menus.main]] + name = "Network" + url = "/en/network" + weight = 4 + [[languages.en.menus.main]] + name = "FAQ" + url = "/en/faq" + weight = 5 + [[languages.en.menus.main]] + name = "Contact" + url = "/en/contact" + weight = 6 + + [[languages.en.menus.footer]] + name = "News" + url = "/en/news" + weight = 1 + [languages.en.menus.footer.params] + col = "rete" + [[languages.en.menus.footer]] + name = "Servers" + url = "/en/network" + weight = 2 + [languages.en.menus.footer.params] + col = "rete" + [[languages.en.menus.footer]] + name = "Services" + url = "/en/services" + weight = 3 + [languages.en.menus.footer.params] + col = "rete" + [[languages.en.menus.footer]] + name = "History" + url = "/en/history" + weight = 1 + [languages.en.menus.footer.params] + col = "info" + [[languages.en.menus.footer]] + name = "Rules" + url = "/en/rules" + weight = 2 + [languages.en.menus.footer.params] + col = "info" + [[languages.en.menus.footer]] + name = "FAQ" + url = "/en/faq" + weight = 3 + [languages.en.menus.footer.params] + col = "info" + [[languages.en.menus.footer]] + name = "Contact" + url = "/en/contact" + weight = 4 + [languages.en.menus.footer.params] + col = "info" diff --git a/themes/azzurra/i18n/en.toml b/themes/azzurra/i18n/en.toml new file mode 100644 index 0000000..e643ccd --- /dev/null +++ b/themes/azzurra/i18n/en.toml @@ -0,0 +1,80 @@ +# Azzurra theme — English strings + +# Date format (used by time.Format with site language) +[dateFormat] +other = "January 02, 2006" + +# Header / nav +[navWebChat] +other = "Web Chat ↗" + +# Footer +[footerNetworkColumn] +other = "Network" + +[footerInfoColumn] +other = "Information" + +[footerBrandTagline] +other = "Italy's longest-running IRC community, since 1997." + +[footerConnectLine] +other = "Connect at irc.azzurra.chat on port 6667 (SSL: 6697)." + +[footerCopyright] +other = "© 1997–{{ .Year }} Azzurra IRC Network. All rights reserved." + +[footerStaffPrefix] +other = "Staff:" + +# Home (index.html) +[heroBadge] +other = "🟢 Online since 1997" + +[heroTitle] +other = "The Italian
IRC Community" + +[heroSubtitle] +other = "Azzurra is Italy's longest-running IRC network. Join thousands of channels, talk to the community, make new friends." + +[heroCTAConnect] +other = "💬 Connect now" + +[heroCTAHowto] +other = "How to connect" + +[newsTitle] +other = "📰 Latest news" + +[newsArchive] +other = "News archive →" + +[newsEmpty] +other = "No news available." + +[ctaConnectTitle] +other = "Ready to connect?" + +[ctaConnectSubtitle] +other = "Use your favorite IRC client, or jump in directly from the browser." + +[ctaConnectButton] +other = "Open Web Chat" + +# List page +[breadcrumbHome] +other = "Home" + +[archiveCount] +other = "{{ .Count }} articles in archive" + +[readMore] +other = "Read more →" + +# Single (post) +[postByPrefix] +other = "by" + +# Shortcode +[infoBoxDefaultTitle] +other = "ℹ️ Note" diff --git a/themes/azzurra/i18n/it.toml b/themes/azzurra/i18n/it.toml new file mode 100644 index 0000000..ea48b0d --- /dev/null +++ b/themes/azzurra/i18n/it.toml @@ -0,0 +1,80 @@ +# Azzurra theme — Italian strings + +# Date format (used by time.Format with site language) +[dateFormat] +other = "02 January 2006" + +# Header / nav +[navWebChat] +other = "Web Chat ↗" + +# Footer +[footerNetworkColumn] +other = "Rete" + +[footerInfoColumn] +other = "Informazioni" + +[footerBrandTagline] +other = "La prima community IRC italiana, dal 1997." + +[footerConnectLine] +other = "Connettiti su irc.azzurra.chat porta 6667 (SSL: 6697)." + +[footerCopyright] +other = "© 1997–{{ .Year }} Azzurra IRC Network. Tutti i diritti riservati." + +[footerStaffPrefix] +other = "Staff:" + +# Home (index.html) +[heroBadge] +other = "🟢 Online dal 1997" + +[heroTitle] +other = "La Community IRC
Italiana" + +[heroSubtitle] +other = "Azzurra è la più longeva rete IRC italiana. Entra in migliaia di canali, parla con la community, fai nuove amicizie." + +[heroCTAConnect] +other = "💬 Connettiti ora" + +[heroCTAHowto] +other = "Come connettersi" + +[newsTitle] +other = "📰 Ultime notizie" + +[newsArchive] +other = "Archivio news →" + +[newsEmpty] +other = "Nessuna notizia disponibile." + +[ctaConnectTitle] +other = "Pronto a connetterti?" + +[ctaConnectSubtitle] +other = "Usa il tuo client IRC preferito oppure accedi direttamente dal browser." + +[ctaConnectButton] +other = "Apri Web Chat" + +# List page +[breadcrumbHome] +other = "Home" + +[archiveCount] +other = "{{ .Count }} articoli in archivio" + +[readMore] +other = "Leggi tutto →" + +# Single (post) +[postByPrefix] +other = "di" + +# Shortcode +[infoBoxDefaultTitle] +other = "ℹ️ Nota" diff --git a/themes/azzurra/layouts/_default/baseof.html b/themes/azzurra/layouts/_default/baseof.html index 04212ca..56a83b7 100644 --- a/themes/azzurra/layouts/_default/baseof.html +++ b/themes/azzurra/layouts/_default/baseof.html @@ -17,7 +17,7 @@