diff --git a/.githooks/README.md b/.githooks/README.md new file mode 100644 index 0000000..2086c08 --- /dev/null +++ b/.githooks/README.md @@ -0,0 +1,28 @@ +# 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/it/*.md` or `content/en/*.md` page without a matching sibling in the + other language tree carrying the same `translationKey:` frontmatter value. + 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..35d682f --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# i18n parity gate — block commits where the staged content/it/* and +# content/en/* files don't pair up via translationKey. +# +# Dir-split layout: content/it/foo.md (Italian) and content/en/bar.md +# (English) are paired via the `translationKey:` frontmatter field. Both +# siblings must declare the same key. The hook scans the staged tree and +# fails if a touched page lacks a key or its counterpart key is missing +# from the OTHER language tree. +# +# 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/{it,en}/*.md +mapfile -t staged < <( + git diff --cached --name-only --diff-filter=AMR \ + | grep -E '^content/(it|en)/.*\.md$' || true +) + +[[ ${#staged[@]} -eq 0 ]] && exit 0 + +# extract_key : print translationKey value, empty if missing. +extract_key() { + awk ' + /^---$/ { if (in_fm) exit; in_fm=1; next } + in_fm && /^translationKey:[[:space:]]*/ { + sub(/^translationKey:[[:space:]]*/, ""); gsub(/["'\''`]/, ""); print; exit + } + ' "$1" +} + +# has_key_in : 0 if any content//**/*.md declares translationKey=. +has_key_in() { + local key="$1" lang="$2" f + [[ -d "content/$lang" ]] || return 1 + while IFS= read -r f; do + [[ "$(extract_key "$f")" == "$key" ]] && return 0 + done < <(find "content/$lang" -type f -name '*.md') + return 1 +} + +missing=() +for f in "${staged[@]}"; do + key=$(extract_key "$f") + # No translationKey = page declares no pairing yet; pre-translation + # half-step is allowed (the gate only enforces well-formed pairs, it + # does not force every page to be translated). + [[ -z "$key" ]] && continue + + case "$f" in + content/it/*) other="en" ;; + content/en/*) other="it" ;; + *) continue ;; + esac + + has_key_in "$key" "$other" || \ + missing+=("$f → translationKey '$key' missing in content/$other/") +done + +if [[ ${#missing[@]} -gt 0 ]]; then + echo "✗ i18n parity check failed:" >&2 + printf ' - %s\n' "${missing[@]}" >&2 + echo "" >&2 + echo "Fix: add the missing sibling under content// with the same" >&2 + echo "translationKey, or run 'git commit --no-verify' if you are intentionally" >&2 + echo "staging a half-step (the CI parity check will still gate the PR)." >&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..0fc4ebf --- /dev/null +++ b/.github/workflows/i18n-parity.yml @@ -0,0 +1,86 @@ +name: i18n parity + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + parity: + name: IT/EN parity check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Verify every content/{it,en}/*.md touched in this PR pairs via translationKey + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + + # Delta-based parity gate — mirrors .githooks/pre-commit. Only + # files touched in THIS PR are checked. For each touched + # content/{it,en}/*.md the resulting tree must contain a + # counterpart in the OTHER language tree carrying the same + # `translationKey:` frontmatter value. + if [[ -n "${BASE_SHA:-}" && -n "${HEAD_SHA:-}" ]]; then + range="$BASE_SHA..$HEAD_SHA" + else + range="HEAD^..HEAD" + fi + + mapfile -t touched < <( + git diff --name-only --diff-filter=AMR "$range" \ + | grep -E '^content/(it|en)/.*\.md$' || true + ) + + if [[ ${#touched[@]} -eq 0 ]]; then + echo "✓ no content/{it,en}/*.md touched in this range — parity OK" + exit 0 + fi + + extract_key() { + awk ' + /^---$/ { if (in_fm) exit; in_fm=1; next } + in_fm && /^translationKey:[[:space:]]*/ { + sub(/^translationKey:[[:space:]]*/, ""); gsub(/["'"'"'`]/, ""); print; exit + } + ' "$1" + } + + has_key_in() { + local key="$1" lang="$2" f + [[ -d "content/$lang" ]] || return 1 + while IFS= read -r f; do + [[ "$(extract_key "$f")" == "$key" ]] && return 0 + done < <(find "content/$lang" -type f -name '*.md') + return 1 + } + + missing=() + for f in "${touched[@]}"; do + key=$(extract_key "$f") + # No translationKey = page declares no pairing yet. + [[ -z "$key" ]] && continue + + case "$f" in + content/it/*) other="en" ;; + content/en/*) other="it" ;; + *) continue ;; + esac + + has_key_in "$key" "$other" || \ + missing+=("$f → translationKey '$key' missing in content/$other/") + done + + if [[ ${#missing[@]} -gt 0 ]]; then + echo "::error::i18n parity broken:" + printf ' - %s\n' "${missing[@]}" + exit 1 + fi + + echo "✓ i18n parity OK (${#touched[@]} touched file(s) verified)" diff --git a/content/en/news/_index.md b/content/en/news/_index.md new file mode 100644 index 0000000..1b65593 --- /dev/null +++ b/content/en/news/_index.md @@ -0,0 +1,5 @@ +--- +title: "News Archive" +lead: "All the official news and updates from the Azzurra network." +translationKey: section-news +--- diff --git a/content/en/news/gamebot.md b/content/en/news/gamebot.md new file mode 100644 index 0000000..992fe31 --- /dev/null +++ b/content/en/news/gamebot.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." +translationKey: news-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/content/come-connettersi.md b/content/it/come-connettersi.md similarity index 100% rename from content/come-connettersi.md rename to content/it/come-connettersi.md diff --git a/content/contatti.md b/content/it/contatti.md similarity index 100% rename from content/contatti.md rename to content/it/contatti.md diff --git a/content/faq.md b/content/it/faq.md similarity index 100% rename from content/faq.md rename to content/it/faq.md diff --git a/content/kline.md b/content/it/kline.md similarity index 100% rename from content/kline.md rename to content/it/kline.md diff --git a/content/network.md b/content/it/network.md similarity index 100% rename from content/network.md rename to content/it/network.md diff --git a/content/news/_index.md b/content/it/news/_index.md similarity index 78% rename from content/news/_index.md rename to content/it/news/_index.md index 91e1301..b541c5f 100644 --- a/content/news/_index.md +++ b/content/it/news/_index.md @@ -1,4 +1,5 @@ --- title: "Archivio News" lead: "Tutte le notizie e gli aggiornamenti ufficiali della rete Azzurra." +translationKey: section-news --- diff --git a/content/news/azzurra-rinasce-come-azzurra-chat.md b/content/it/news/azzurra-rinasce-come-azzurra-chat.md similarity index 100% rename from content/news/azzurra-rinasce-come-azzurra-chat.md rename to content/it/news/azzurra-rinasce-come-azzurra-chat.md diff --git a/content/news/gamebot.md b/content/it/news/gamebot.md similarity index 98% rename from content/news/gamebot.md rename to content/it/news/gamebot.md index 781d95f..475e83b 100644 --- a/content/news/gamebot.md +++ b/content/it/news/gamebot.md @@ -4,6 +4,7 @@ date: 2026-05-10 author: "Staff Azzurra" tags: ["annuncio", "bot", "giochi"] summary: "Sfide di carte, poker, quiz e UNO direttamente in canale: GameBot porta i giochi da tavolo su Azzurra." +translationKey: news-gamebot --- ## Una nuova compagnia in chat 🎲 diff --git a/content/news/nickserv-resetpass.md b/content/it/news/nickserv-resetpass.md similarity index 100% rename from content/news/nickserv-resetpass.md rename to content/it/news/nickserv-resetpass.md diff --git a/content/news/nuovi-server-2026.md b/content/it/news/nuovi-server-2026.md similarity index 100% rename from content/news/nuovi-server-2026.md rename to content/it/news/nuovi-server-2026.md diff --git a/content/news/nuovo-sito-2008.md b/content/it/news/nuovo-sito-2008.md similarity index 100% rename from content/news/nuovo-sito-2008.md rename to content/it/news/nuovo-sito-2008.md diff --git a/content/news/nuovo-sito.md b/content/it/news/nuovo-sito.md similarity index 100% rename from content/news/nuovo-sito.md rename to content/it/news/nuovo-sito.md diff --git a/content/news/riorganizzazione-2020.md b/content/it/news/riorganizzazione-2020.md similarity index 100% rename from content/news/riorganizzazione-2020.md rename to content/it/news/riorganizzazione-2020.md diff --git a/content/news/webchat-operativa.md b/content/it/news/webchat-operativa.md similarity index 100% rename from content/news/webchat-operativa.md rename to content/it/news/webchat-operativa.md diff --git a/content/regolamento.md b/content/it/regolamento.md similarity index 100% rename from content/regolamento.md rename to content/it/regolamento.md diff --git a/content/servizi.md b/content/it/servizi.md similarity index 100% rename from content/servizi.md rename to content/it/servizi.md diff --git a/content/storia.md b/content/it/storia.md similarity index 100% rename from content/storia.md rename to content/it/storia.md diff --git a/hugo.toml b/hugo.toml index dc41ad1..ec5dc2e 100644 --- a/hugo.toml +++ b/hugo.toml @@ -1,109 +1,202 @@ -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" + contentDir = "content/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" + contentDir = "content/en" + 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..3afaf2e 100644 --- a/themes/azzurra/layouts/_default/baseof.html +++ b/themes/azzurra/layouts/_default/baseof.html @@ -17,7 +17,7 @@