release 1.20.0: domains.conf locale l10n + optional German UI

Add locale column (fr-CH for FP, de-CH elsewhere; ch-FR/ch-DE aliases).
Introduce UI lang=de (tags + sticky) without using de as any stock default.
This commit is contained in:
Hernâni Marques 2026-07-19 11:13:47 +02:00
parent 915e361157
commit c435df9728
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
8 changed files with 408 additions and 163 deletions

View file

@ -203,9 +203,9 @@ Each monitored stack must say **what is bank, exchange, and merchant-backend**.
Edit **[domains.conf](./domains.conf)** (or set `TALER_DOMAINS_CONF=`):
```text
# name bank exchange merchant-backend currency local landing lang [canonical]
taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 0 en
stage.lefrancpaysan.ch stage.bank.… stage.exchange.… stage.monnaie.… TESTPAYSAN 0 1 fr
# name bank exchange merchant currency local landing lang locale [canonical]
taler-ops.ch bank.… exchange.… my.… CHF 0 0 en de-CH
stage.lefrancpaysan.ch stage.bank.… stage.exchange.… stage.monnaie.… TESTPAYSAN 0 1 fr fr-CH
```
| Field | Meaning |
@ -217,7 +217,8 @@ stage.lefrancpaysan.ch stage.bank.… stage.exchange.… stage.monnaie.… T
| **currency** | `GOA` / `KUDOS` / `CHF` / … |
| **local** | `1` = koopa SSH stack; `0` = public only |
| **landing** | `1` = check `/intro` landings + assets; `0` = skip (TOPS / mytops) |
| **lang** | UI default **`en`** or **`fr`** when `TALER_MON_LANG_SET≠1` (source of truth; FP rows use **`fr`**) |
| **lang** | UI language **`en`** / **`fr`** / **`de`** when `TALER_MON_LANG_SET≠1`. Stock profiles use **en** or **fr** only — **`de` is never a default** (opt-in via `--lang de` or an explicit profile edit). |
| **locale** | Regional l10n (BCP 47): **`fr-CH`** for FrancPaysan (Suisse romande); **`de-CH`** for other Swiss stacks. Aliases `ch-FR` / `ch-DE` accepted. Not the same as UI `lang`. |
| **canonical** | optional label after alias (e.g. `my.taler-ops.ch``taler-ops.ch`) |
Workstation `TALER_MON_LANG=en` does **not** override a profile with `lang=fr`. Force English on FP only with `--lang en` / `TALER_MON_LANG_SET=1`.

View file

@ -1 +1 @@
1.19.1
1.20.0

View file

@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre
| Tag | Date (UTC) | Notes |
|-----|------------|--------|
| **v1.20.0** | 2026-07-19 | **Feature:** domains.conf **`locale`** (l10n) — FP **`fr-CH`**, others **`de-CH`** (aliases ch-FR/ch-DE); UI **`lang=de`** supported (sticky + console tags) but **never** a stock profile default; env `TALER_MON_LOCALE` / `TALER_DOMAIN_LOCALE`. |
| **v1.19.1** | 2026-07-19 | **Bugfix / docs:** DEPENDENCIES — e2e requires `taler-helper-sqlite3` on mon PATH + preflight; README domains.conf documents **`lang`** column. |
| **v1.19.0** | 2026-07-19 | **Feature:** domains.conf **`lang`** column (en\|fr) is the global UI language default per stack; i18n + HTML read `TALER_DOMAIN_LANG` / conf (FP profiles `lang=fr`). |
| **v1.18.9** | 2026-07-19 | **Bugfix:** UI lang — *lefrancpaysan*/*francpaysan* always default **fr** unless `TALER_MON_LANG_SET=1` / `--lang`; workstation `TALER_MON_LANG=en` no longer locks English (host-agent + console_to_html). |

View file

@ -1,52 +1,53 @@
# taler-monitoring domain profiles
#
# When you add a stack, declare the three public endpoints here:
# name bank exchange merchant-backend currency local landing lang [canonical]
# name bank exchange merchant-backend currency local landing lang locale [canonical]
#
# - name: what -d / TALER_DOMAIN matches (aliases allowed as extra lines)
# - bank / exchange / merchant: hostname or full https:// URL
# - currency: GOA | KUDOS | CHF | … (empty or "-" = report only)
# - local: 1 = koopa SSH/inside/e2e stack; 0 = public-only
# - landing: 1 = check /intro landings + assets; 0 = skip (no GOA-style landings)
# - lang: UI language default for this profile (en|fr). Source of truth for i18n
# when TALER_MON_LANG_SET≠1. Workstation env TALER_MON_LANG=en does not
# override a profile that sets fr (e.g. *lefrancpaysan*).
# - lang: UI language for mon console/HTML (en|fr|de). Source of truth when
# TALER_MON_LANG_SET≠1. Workstation TALER_MON_LANG=en does not override.
# **de is supported but must not be used as a profile default** (no row
# sets lang=de unless an operator deliberately chooses it).
# - locale: BCP 47 regional locale for l10n defaults (not the same as UI lang).
# Use **fr-CH** (Suisse romande) for FrancPaysan; **de-CH** (Deutschschweiz)
# for other Swiss / default stacks. Accepts also ch-FR / ch-DE as aliases.
# - canonical: optional TALER_DOMAIN label after alias match
#
# Legacy lines without lang (old 8th field = canonical only) still parse: lang→en.
# CLI overrides: --lang / TALER_MON_LANG_SET=1
# Legacy lines without locale still parse (locale → de-CH except FP heuristic).
# CLI: --lang en|fr|de · TALER_MON_LANG_SET=1 locks UI lang
# Alternate file: TALER_DOMAINS_CONF=/path/to/domains.conf
# Local GOA (koopa) — landings matter
koopa bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 en hacktivism.ch
hacktivism bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 en hacktivism.ch
hacktivism.ch bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 en
# Local GOA (koopa) — landings matter · UI en · locale de-CH
koopa bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 en de-CH hacktivism.ch
hacktivism bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 en de-CH hacktivism.ch
hacktivism.ch bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 en de-CH
# Public demo (KUDOS) — has intro-style pages; keep soft landing checks
taler.net bank.demo.taler.net exchange.demo.taler.net backend.demo.taler.net KUDOS 0 1 en demo.taler.net
demo.taler.net bank.demo.taler.net exchange.demo.taler.net backend.demo.taler.net KUDOS 0 1 en
# Public demo (KUDOS)
taler.net bank.demo.taler.net exchange.demo.taler.net backend.demo.taler.net KUDOS 0 1 en de-CH demo.taler.net
demo.taler.net bank.demo.taler.net exchange.demo.taler.net backend.demo.taler.net KUDOS 0 1 en de-CH
# Public test stack (TESTKUDOS) — no GOA-style landings
test.taler.net bank.test.taler.net exchange.test.taler.net backend.test.taler.net TESTKUDOS 0 0 en
# Public test stack (TESTKUDOS)
test.taler.net bank.test.taler.net exchange.test.taler.net backend.test.taler.net TESTKUDOS 0 0 en de-CH
# TOPS CHF — multi-tenant merchant my.taler-ops.ch; no public GOA landings
taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 0 en
my.taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 0 en taler-ops.ch
# Stage merchant FQDN is stage.my.taler-ops.ch (on betel); mon HTML /monitoring* stage-only
stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch stage.my.taler-ops.ch CHF 0 0 en
stage.my.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch stage.my.taler-ops.ch CHF 0 0 en stage.taler-ops.ch
my.stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch stage.my.taler-ops.ch CHF 0 0 en stage.taler-ops.ch
# TOPS CHF
taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 0 en de-CH
my.taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 0 en de-CH taler-ops.ch
stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch stage.my.taler-ops.ch CHF 0 0 en de-CH
stage.my.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch stage.my.taler-ops.ch CHF 0 0 en de-CH stage.taler-ops.ch
my.stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch stage.my.taler-ops.ch CHF 0 0 en de-CH stage.taler-ops.ch
# Franc Paysan (Infomaniak) — merchant host is monnaie.* (not taler./backend.)
# UI default: French (lang=fr) for all FP prod hostnames
lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 fr
monnaie.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 fr lefrancpaysan.ch
bank.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 fr lefrancpaysan.ch
exchange.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 fr lefrancpaysan.ch
# Franc Paysan — UI fr · regional locale fr-CH (Suisse romande)
lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 fr fr-CH
monnaie.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 fr fr-CH lefrancpaysan.ch
bank.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 fr fr-CH lefrancpaysan.ch
exchange.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 fr fr-CH lefrancpaysan.ch
# Stage TESTPAYSAN — landings on / + farmer shops under stage.monnaie…/shops/
# UI default: French (lang=fr)
stage.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 fr
stage.bank.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 fr stage.lefrancpaysan.ch
stage.exchange.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 fr stage.lefrancpaysan.ch
stage.monnaie.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 fr stage.lefrancpaysan.ch
# Stage TESTPAYSAN — UI fr · locale fr-CH
stage.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 fr fr-CH
stage.bank.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 fr fr-CH stage.lefrancpaysan.ch
stage.exchange.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 fr fr-CH stage.lefrancpaysan.ch
stage.monnaie.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 fr fr-CH stage.lefrancpaysan.ch

135
i18n.sh
View file

@ -1,19 +1,24 @@
#!/usr/bin/env bash
# i18n.sh — language for taler-monitoring console + HTML chrome
# i18n.sh — UI language + regional locale for taler-monitoring console + HTML
#
# TALER_MON_LANG=en|fr
# TALER_MON_LANG_SET=1 → language is explicit (--lang only); do not override.
# Auto (SET≠1), in order:
# 1) TALER_DOMAIN_LANG from domains.conf profile (preferred — source of truth)
# 2) fallback hostname heuristic *lefrancpaysan*/*francpaysan* → fr
# 3) else keep workstation env TALER_MON_LANG or en
# UI language (mon badges / sticky chrome):
# TALER_MON_LANG=en|fr|de
# TALER_MON_LANG_SET=1 → explicit (--lang); do not override.
# Auto (SET≠1): TALER_DOMAIN_LANG from domains.conf `lang` column.
# de is supported but never a stock profile default (no domains.conf row uses lang=de).
#
# domains.conf column `lang` (see suite domains.conf) is the global definition.
# Bare TALER_MON_LANG=en in ~/.config must not override a profile lang=fr.
# Regional locale (l10n defaults, BCP 47 — not the same as UI lang):
# TALER_MON_LOCALE / TALER_DOMAIN_LOCALE e.g. fr-CH, de-CH
# From domains.conf `locale` column (fr-CH for FP, de-CH for others).
# Aliases ch-FR / ch-DE accepted and normalized to fr-CH / de-CH.
#
# domains.conf is the source of truth for both lang and locale.
: "${TALER_MON_LANG:=}"
: "${TALER_MON_LANG_SET:=0}"
: "${TALER_DOMAIN_LANG:=}"
: "${TALER_DOMAIN_LOCALE:=}"
: "${TALER_MON_LOCALE:=}"
i18n_init() {
# Only TALER_MON_LANG_SET=1 is "explicit". Do not promote non-empty TALER_MON_LANG
@ -24,7 +29,6 @@ i18n_init() {
else
case "${TALER_DOMAIN:-}" in
*lefrancpaysan*|*francpaysan*)
# fallback if domain not (yet) in domains.conf
TALER_MON_LANG=fr
;;
*)
@ -37,19 +41,37 @@ i18n_init() {
fi
case "${TALER_MON_LANG}" in
fr|FR|fra|french) TALER_MON_LANG=fr ;;
de|DE|deu|ger|german|deutsch) TALER_MON_LANG=de ;;
*) TALER_MON_LANG=en ;;
esac
# Regional locale from profile (or FP/other fallback)
if [ -n "${TALER_DOMAIN_LOCALE:-}" ]; then
TALER_MON_LOCALE="$TALER_DOMAIN_LOCALE"
elif [ -z "${TALER_MON_LOCALE:-}" ]; then
case "${TALER_DOMAIN:-}" in
*lefrancpaysan*|*francpaysan*) TALER_MON_LOCALE=fr-CH ;;
*) TALER_MON_LOCALE=de-CH ;;
esac
fi
# normalize aliases
case "$(printf '%s' "$TALER_MON_LOCALE" | tr '[:upper:]' '[:lower:]' | tr '_' '-')" in
fr-ch|ch-fr) TALER_MON_LOCALE=fr-CH ;;
de-ch|ch-de) TALER_MON_LOCALE=de-CH ;;
en-ch|ch-en) TALER_MON_LOCALE=en-CH ;;
esac
export TALER_MON_LANG
export TALER_MON_LANG_SET
export TALER_DOMAIN_LANG
export TALER_DOMAIN_LOCALE
export TALER_MON_LOCALE
}
i18n_tag() {
local t="$1"
if [ "${TALER_MON_LANG:-en}" != "fr" ]; then
printf '%s' "$t"
return 0
fi
case "${TALER_MON_LANG:-en}" in
fr)
case "$t" in
OK) printf 'OK' ;;
ERROR) printf 'ERREUR' ;;
@ -61,14 +83,31 @@ i18n_tag() {
PROG|PROGRESS) printf 'PROG' ;;
*) printf '%s' "$t" ;;
esac
;;
de)
case "$t" in
OK) printf 'OK' ;;
ERROR) printf 'FEHLER' ;;
WARN) printf 'WARNUNG' ;;
INFO) printf 'INFO' ;;
BLOCKER) printf 'BLOCKER' ;;
BLOCK) printf 'BLOCK' ;;
SUMMARY) printf 'ZUSAMMENFASSUNG' ;;
PROG|PROGRESS) printf 'PROG' ;;
*) printf '%s' "$t" ;;
esac
;;
*)
printf '%s' "$t"
;;
esac
}
i18n_text() {
local s="$*"
if [ "${TALER_MON_LANG:-en}" != "fr" ] || [ -z "$s" ]; then
printf '%s' "$s"
return 0
fi
[ -z "$s" ] && { printf '%s' "$s"; return 0; }
case "${TALER_MON_LANG:-en}" in
fr)
case "$s" in
"numbered checks this run:") printf 'controles numerotes de cette execution :' ;;
"numbered checks:") printf 'controles numerotes :' ;;
@ -102,24 +141,62 @@ i18n_text() {
*)
local out="$s"
out=${out//shared library missing/bibliotheque partagee manquante}
out=${out//shared library error/erreur de bibliotheque partagee}
out=${out//not active/non actif}
out=${out//after_start/apres_demarrage}
out=${out//catalogued but not reachable/catalogue mais inaccessible}
out=${out//does not resolve/ne resout pas}
out=${out//protocol confirmed/protocole confirme}
out=${out//catalogued service OK/service catalogue OK}
out=${out//from Server header/depuis en-tete Server}
out=${out//OSV clean/OSV sans vulnerabilite}
out=${out//actionable vuln/vuln. actionnable}
out=${out//header version · not Debian pkg/version en-tete · pas paquet Debian}
out=${out//RUN_TIMEOUT exceeded/RUN_TIMEOUT depasse}
out=${out//public HTTPS/HTTPS public}
out=${out//container status/etat du conteneur}
out=${out//remote-only/uniquement distant}
out=${out//no SSH/sans SSH}
out=${out//failed — see list above/echec — voir la liste ci-dessus}
printf '%s' "$out"
;;
esac
;;
de)
case "$s" in
"numbered checks this run:") printf 'Nummerierte Pruefungen dieser Ausfuehrung :' ;;
"numbered checks:") printf 'Nummerierte Pruefungen :' ;;
"(global done/total)") printf '(fertig/gesamt global)' ;;
"global checks:") printf 'Globale Pruefungen :' ;;
"failed — see list above"|"failed - see list above") printf 'fehlgeschlagen — siehe Liste oben' ;;
"BLOCKERS · pay/withdraw cannot finish") printf 'BLOCKER · Zahlung/Abhebung nicht moeglich' ;;
"BLOCKERS (pay/withdraw cannot finish)") printf 'BLOCKER (Zahlung/Abhebung nicht moeglich)' ;;
"ERRORS · failed checks") printf 'FEHLER · fehlgeschlagene Pruefungen' ;;
"ERRORS (failed checks)") printf 'FEHLER (fehlgeschlagene Pruefungen)' ;;
"pay/withdraw cannot finish") printf 'Zahlung/Abhebung kann nicht beendet werden' ;;
"SUMMARY") printf 'ZUSAMMENFASSUNG' ;;
"totals:") printf 'Summen :' ;;
"all clear") printf 'alles in Ordnung' ;;
"warnings only (no hard fail)") printf 'nur Warnungen (kein harter Fehler)' ;;
"monitoring") printf 'Ueberwachung' ;;
"surface") printf 'Surface' ;;
"aptdeploy") printf 'aptdeploy' ;;
"container") printf 'Container' ;;
"disk") printf 'Disk' ;;
"package") printf 'Paket' ;;
"version") printf 'Version' ;;
"reachability") printf 'Erreichbarkeit' ;;
"server-header") printf 'Server-Header' ;;
"inventory") printf 'Inventar' ;;
"flags") printf 'Flags' ;;
"target domain") printf 'Zieldomaene' ;;
"currency") printf 'Waehrung' ;;
"phases") printf 'Phasen' ;;
"access") printf 'Zugang' ;;
*)
local outd="$s"
outd=${outd//shared library missing/gemeinsame Bibliothek fehlt}
outd=${outd//not active/nicht aktiv}
outd=${outd//does not resolve/loest nicht auf}
outd=${outd//from Server header/aus Server-Header}
outd=${outd//public HTTPS/oeffentliches HTTPS}
outd=${outd//no SSH/ohne SSH}
outd=${outd//failed — see list above/fehlgeschlagen — siehe Liste oben}
printf '%s' "$outd"
;;
esac
;;
*)
printf '%s' "$s"
;;
esac
}

70
lib.sh
View file

@ -166,24 +166,53 @@ set_taler_stack() {
fi
}
# Normalize domains.conf lang token → en|fr (empty if not a lang token).
# Normalize domains.conf UI lang token → en|fr|de (empty if not a lang token).
# Note: de is valid for --lang / explicit profile, but no stock profile defaults to de.
_domain_lang_token() {
case "${1:-}" in
en|EN|eng|english) printf 'en' ;;
fr|FR|fra|french) printf 'fr' ;;
de|DE|deu|ger|german|deutsch) printf 'de' ;;
*) printf '' ;;
esac
}
# Normalize regional locale → BCP 47 (fr-CH, de-CH, en-CH, …). Empty if unknown.
# Accepts fr-CH, fr_CH, ch-FR / de-CH, ch-DE (user-friendly aliases).
_domain_locale_token() {
local t="${1:-}"
t=$(printf '%s' "$t" | tr '[:upper:]' '[:lower:]' | tr '_' '-')
case "$t" in
fr-ch|ch-fr|fr_ch) printf 'fr-CH' ;;
de-ch|ch-de|de_ch) printf 'de-CH' ;;
en-ch|ch-en|en_ch) printf 'en-CH' ;;
en-us|en-gb|fr-fr|de-de|it-ch|rm-ch)
# pass through other xx-YY
printf '%s' "$t" | awk -F- '{printf "%s-%s", tolower($1), toupper($2)}'
;;
*)
# bare xx-YY pattern
if printf '%s' "$t" | grep -qE '^[a-z]{2}-[a-z]{2}$'; then
printf '%s' "$t" | awk -F- '{printf "%s-%s", tolower($1), toupper($2)}'
else
printf ''
fi
;;
esac
}
# Load first matching profile from domains.conf.
# Fields (whitespace-separated; # comments; blank lines ignored):
# name bank exchange merchant currency local[0|1] landing[0|1] lang[en|fr] [canonical]
# Legacy: 8th field may be canonical only (no lang) — lang defaults to en.
# Sets TALER_DOMAIN_LANG from the profile (i18n source of truth when SET≠1).
# name bank exchange merchant currency local landing lang locale [canonical]
# Legacy:
# … landing lang [canonical] → locale defaulted
# … landing [canonical] → lang=en, locale defaulted
# Sets TALER_DOMAIN_LANG + TALER_DOMAIN_LOCALE from the profile.
# Returns 0 if found, 1 if not.
load_domain_profile() {
local want="$1" conf="${TALER_DOMAINS_CONF:-}"
local line name bank exchange merchant currency local_stack landing canon profile_lang f8 f9
local line name bank exchange merchant currency local_stack landing canon
local profile_lang profile_locale f8 f9 f10
[ -n "$want" ] || return 1
[ -n "$conf" ] && [ -f "$conf" ] || return 1
@ -202,28 +231,53 @@ load_domain_profile() {
landing="${7:-}"
f8="${8:-}"
f9="${9:-}"
f10="${10:-}"
canon="$name"
profile_lang=""
profile_locale=""
# Backward compat: old 7th field was canonical domain (contains a dot)
if [ -n "$landing" ] && [[ "$landing" == *.* && "$landing" != "0" && "$landing" != "1" ]]; then
canon="$landing"
landing=""
f8="${7:-}"
f9="${8:-}"
f10="${9:-}"
fi
if [ -n "$f8" ]; then
profile_lang=$(_domain_lang_token "$f8")
if [ -n "$profile_lang" ]; then
canon="${f9:-$name}"
# modern: lang [locale] [canonical]
profile_locale=$(_domain_locale_token "$f9")
if [ -n "$profile_locale" ]; then
canon="${f10:-$name}"
else
# legacy: field 8 = canonical, optional field 9 = lang
# lang + canonical (no locale column)
if [ -n "$f9" ] && [ -z "$(_domain_lang_token "$f9")" ]; then
canon="$f9"
else
canon="${f9:-$name}"
fi
profile_locale=$(_domain_locale_token "$f10")
fi
else
# legacy: field 8 = canonical only
canon="$f8"
profile_lang=$(_domain_lang_token "$f9")
profile_locale=$(_domain_locale_token "${f10:-$f9}")
[ -z "$profile_lang" ] && profile_lang=en
fi
fi
[ -z "$profile_lang" ] && profile_lang=en
if [ -z "$profile_locale" ]; then
# default regional locale: FP → fr-CH, else de-CH
case "$name$canon$bank$merchant" in
*lefrancpaysan*|*francpaysan*) profile_locale=fr-CH ;;
*) profile_locale=de-CH ;;
esac
fi
TALER_DOMAIN_LANG="$profile_lang"
export TALER_DOMAIN_LANG
TALER_DOMAIN_LOCALE="$profile_locale"
export TALER_DOMAIN_LANG TALER_DOMAIN_LOCALE
set_taler_stack "$bank" "$exchange" "$merchant" "$currency" "$local_stack" "$landing" "$canon"
return 0
done <"$conf"

View file

@ -49,44 +49,61 @@ def _norm_lang(v: str) -> str:
v = (v or "").strip().lower()
if v in ("fr", "fra", "french"):
return "fr"
if v in ("de", "deu", "ger", "german", "deutsch"):
return "de"
if v in ("en", "eng", "english"):
return "en"
return ""
def domain_lang_from_conf(host_hint: str = "") -> str:
"""Look up UI lang from domains.conf (suite profile `lang` column).
def _norm_locale(v: str) -> str:
"""BCP 47 regional locale; aliases ch-FR/ch-DE → fr-CH/de-CH."""
t = (v or "").strip().lower().replace("_", "-")
aliases = {
"fr-ch": "fr-CH",
"ch-fr": "fr-CH",
"de-ch": "de-CH",
"ch-de": "de-CH",
"en-ch": "en-CH",
"ch-en": "en-CH",
}
if t in aliases:
return aliases[t]
m = re.fullmatch(r"([a-z]{2})-([a-z]{2})", t)
if m:
return f"{m.group(1)}-{m.group(2).upper()}"
return ""
Matches host_hint against profile name, bank, exchange, merchant, or
canonical fields. Returns '' if no profile matches.
"""
def _domains_conf_path() -> "object | None":
import os
from pathlib import Path
hint = (host_hint or "").lower()
if not hint.strip():
return ""
conf = os.environ.get("TALER_DOMAINS_CONF") or ""
candidates = []
if conf:
candidates.append(Path(conf))
# suite root next to site-gen/
here = Path(__file__).resolve().parent
candidates.append(here.parent / "domains.conf")
# cwd / mon root
candidates.append(Path("domains.conf"))
return next((p for p in candidates if p.is_file()), None)
path = next((p for p in candidates if p.is_file()), None)
def domain_profile_from_conf(host_hint: str = "") -> tuple[str, str]:
"""Return (lang, locale) from domains.conf for host_hint; ('','') if none."""
hint = (host_hint or "").lower()
if not hint.strip():
return "", ""
path = _domains_conf_path()
if path is None:
return ""
best = ""
return "", ""
try:
text = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return ""
return "", ""
best_lang, best_loc = "", ""
for raw in text.splitlines():
line = raw.split("#", 1)[0].strip()
if not line:
@ -95,41 +112,62 @@ def domain_lang_from_conf(host_hint: str = "") -> str:
if len(parts) < 5:
continue
name, bank, exchange, merchant = parts[0], parts[1], parts[2], parts[3]
# fields: … local landing lang [canonical]
lang = ""
canon = name
lang, locale, canon = "", "", name
if len(parts) >= 8:
tok = _norm_lang(parts[7])
if tok:
lang = tok
if len(parts) >= 9:
canon = parts[8]
loc = _norm_locale(parts[8])
if loc:
locale = loc
if len(parts) >= 10:
canon = parts[9]
else:
# lang + canonical (no locale)
canon = parts[8]
if len(parts) >= 10:
locale = _norm_locale(parts[9])
else:
# legacy: field 8 = canonical
canon = parts[7]
if len(parts) >= 9:
lang = _norm_lang(parts[8])
if len(parts) >= 10:
locale = _norm_locale(parts[9])
elif len(parts) >= 9:
locale = _norm_locale(parts[8])
if not lang:
lang = "en"
if not locale:
blob = f"{name}{canon}{bank}{merchant}".lower()
locale = "fr-CH" if ("lefrancpaysan" in blob or "francpaysan" in blob) else "de-CH"
hosts = {name.lower(), bank.lower(), exchange.lower(), merchant.lower(), canon.lower()}
# strip scheme if present
hosts = {re.sub(r"^https?://", "", h).split("/")[0] for h in hosts}
if any(h and h in hint for h in hosts):
# prefer longer / more specific name match later — keep last match
# that appears in conf order; FP aliases all share fr.
best = lang
best_lang, best_loc = lang, locale
if name.lower() in hint or canon.lower() in hint:
return lang, locale
return best_lang, best_loc
def domain_lang_from_conf(host_hint: str = "") -> str:
"""Look up UI lang from domains.conf (suite profile `lang` column)."""
lang, _ = domain_profile_from_conf(host_hint)
return lang
return best
def domain_locale_from_conf(host_hint: str = "") -> str:
"""Look up regional locale from domains.conf (`locale` column)."""
_, loc = domain_profile_from_conf(host_hint)
return loc
def ui_lang(explicit: str = "", host_hint: str = "") -> str:
"""en (default) or fr.
"""en | fr | de (de never a stock profile default).
- TALER_MON_LANG_SET=1 honour TALER_MON_LANG / explicit only.
- Else TALER_DOMAIN_LANG env (set by load_domain_profile).
- Else domains.conf `lang` for host_hint.
- Else fallback *lefrancpaysan*/*francpaysan* fr.
- Else TALER_DOMAIN_LANG / domains.conf `lang`.
- Else fallback *lefrancpaysan* fr.
- Else explicit / env / en.
"""
import os
@ -151,8 +189,24 @@ def ui_lang(explicit: str = "", host_hint: str = "") -> str:
return _norm_lang(lang) or "en"
def ui_locale(host_hint: str = "") -> str:
"""Regional locale fr-CH / de-CH from env or domains.conf."""
import os
for key in ("TALER_MON_LOCALE", "TALER_DOMAIN_LOCALE"):
loc = _norm_locale(os.environ.get(key) or "")
if loc:
return loc
conf = domain_locale_from_conf(host_hint)
if conf:
return conf
if re.search(r"lefrancpaysan|francpaysan", host_hint or "", re.I):
return "fr-CH"
return "de-CH"
def ui(lang: str, key: str, **kwargs) -> str:
"""UI chrome strings for sticky bar (English default; French for FrancPaysan)."""
"""UI chrome strings (en default; fr for FP; de available via --lang de, never stock default)."""
en = {
"errors": "ERRORS",
"warnings": "WARNINGS",
@ -373,7 +427,60 @@ def ui(lang: str, key: str, **kwargs) -> str:
"ago_d": "il y a {n} j",
"age_title": "âge de la page · mise à jour chaque seconde",
}
table = fr if lang == "fr" else en
# German sticky chrome only (full pack falls back to en for missing keys).
# Not used as any domains.conf default — operators may pass --lang de.
de = {
"errors": "FEHLER",
"warnings": "WARNUNGEN",
"ok": "OK",
"error_one": "{n} Fehler",
"error_many": "{n} Fehler",
"warn_one": "{n} Warnung",
"warn_many": "{n} Warnungen",
"jump_first_error": "Zum ersten Fehler",
"jump_first_warn": "Zur ersten Warnung",
"filter_errors": "Filter: Fehler + Kontext",
"filter_warns": "Filter: Warnungen + Kontext",
"filter_infos": "Filter: Info + Kontext",
"filter_oks": "Filter: OK + Kontext",
"filter_clear": "Filter loeschen · alle Zeilen",
"filter_active_err": "Filter: Fehler + Kontext",
"filter_active_warn": "Filter: Warnungen + Kontext",
"filter_active_info": "Filter: Info + Kontext",
"filter_active_ok": "Filter: nur OK + Kontext",
"filter_show_all": "Alle anzeigen",
"info_one": "{n} Info",
"info_many": "{n} Info",
"ok_one": "{n} ok",
"ok_many": "{n} ok",
"env_context": "Monitoring-Umgebung",
"env_context_title": "Aufklappen: Agent-Env, Suite-Pin, Domain-Hosts und Flags",
"env_context_empty": "Kein Lauf-Header im Log",
"generated": "erzeugt",
"version": "Version",
"version_tree_link": "Git-Baum dieses Laufs oeffnen (Commit)",
"suite_version_title": "taler-monitoring Suite-Version · genauer Lauf-Baum",
"source": "Quelle",
"what_monitors": "Was diese Seite ueberwacht",
"what_monitors_title": "Aufklappen: was diese Seite prueft",
"stack_title": "Stack-Monitoring · {host}",
"stack_summary": "{phases} · public + inside",
"mode_ok": "OK",
"mode_err": "ERR",
"mode_redirect": "REDIR",
"footer": "Konsolen-Darstellung von taler-monitoring. Sticky: gruen = OK · gelb = Warnungen · rot = Fehler.",
"ago_s": "vor {n}s",
"ago_m": "vor {n} Min.",
"ago_h": "vor {n} Std.",
"ago_d": "vor {n} T.",
"age_title": "Seitenalter · jede Sekunde aktualisiert",
}
if lang == "fr":
table = fr
elif lang == "de":
table = de
else:
table = en
s = table.get(key) or en.get(key) or key
try:
return s.format(**kwargs)
@ -2489,7 +2596,7 @@ def main() -> None:
ap.add_argument(
"--lang",
default="",
help="UI language en|fr (default: env TALER_MON_LANG or auto)",
help="UI language en|fr|de (default: domains.conf lang / env; de never stock default)",
)
ap.add_argument(
"--page-label",

View file

@ -122,7 +122,7 @@ Options:
--merchant URL merchant-backend base — overrides profile
--currency CODE expected currency (GOA, KUDOS, CHF, …); empty = report only
--no-probe do not probe alternate hosts (unknown domains only)
--lang en|fr UI/console language (sticky bar + badges)
--lang en|fr|de UI/console language (sticky bar + badges; de available, not a profile default)
overrides auto (FrancPaysan→fr, else en)
--ver, --version, -V print suite version, git commit, and repo URLs
-h, --help
@ -165,7 +165,8 @@ Env (same meaning):
SURFACE_CVE_LEVEL=warn|error bare Server-header versions default warn
(Debian package versions default error)
SURFACE_CATALOG=path override surface-catalog.conf
TALER_MON_LANG=en|fr same as --lang (default en; auto fr for *lefrancpaysan*)
TALER_MON_LANG=en|fr|de same as --lang (profile lang from domains.conf; de never stock default)
TALER_MON_LOCALE=fr-CH|de-CH regional l10n from domains.conf locale column
Full load (GOA / hacktivism):
./taler-monitoring.sh -d hacktivism.ch full
@ -225,12 +226,13 @@ NO_PROBE=0
while [ $# -gt 0 ]; do
case "$1" in
--lang)
[ $# -ge 2 ] || { echo "error: --lang needs en|fr" >&2; exit 2; }
[ $# -ge 2 ] || { echo "error: --lang needs en|fr|de" >&2; exit 2; }
_lang="$2"
case "$_lang" in
fr|FR|fra|french) TALER_MON_LANG=fr ;;
de|DE|deu|ger|german|deutsch) TALER_MON_LANG=de ;;
en|EN|eng|english) TALER_MON_LANG=en ;;
*) echo "error: unknown language '$_lang' (use en or fr)" >&2; exit 2 ;;
*) echo "error: unknown language '$_lang' (use en, fr, or de)" >&2; exit 2 ;;
esac
TALER_MON_LANG_SET=1
export TALER_MON_LANG TALER_MON_LANG_SET
@ -547,6 +549,8 @@ elif [ "${LOCAL_STACK:-0}" = "1" ] && command -v podman >/dev/null 2>&1 \
fi
printf 'target domain=%s\n' "${TALER_DOMAIN}"
printf ' ui_lang %s\n' "${TALER_MON_LANG:-en}"
printf ' locale %s\n' "${TALER_MON_LOCALE:-${TALER_DOMAIN_LOCALE:-?}}"
printf ' bank %s\n' "$BANK_PUBLIC"
printf ' exchange %s\n' "$EXCHANGE_PUBLIC"
printf ' merchant %s\n' "$MERCHANT_PUBLIC"