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:
parent
51b552d214
commit
c7cecfc6d5
8 changed files with 408 additions and 163 deletions
|
|
@ -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
|
||||
return best
|
||||
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
|
||||
|
||||
|
||||
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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue