feat(i18n): add Swedish support + include Slovak in auto-fill defaults

Two related changes to the translation infrastructure:

1. Add Swedish (sv) as a supported locale in both the CLI and Monitor
   translation systems. Inspired by PR #121, which arrived before the
   current auto-generated cache layout existed.

2. Include Slovak (sk) in the default auto-fill target set. Guardrail #1
   in build_i18n_messages.py never overwrites a key whose target value
   differs from EN, so Vaso73's curated Slovak strings stay intact —
   auto-translation only fills keys that are still on the English
   fallback. Trade-off accepted: sk users see decent machine translation
   for new keys immediately instead of raw English while human curation
   catches up, and Vaso73 keeps full ownership of the wording via
   follow-up PRs.

Changes:
- AppImage/lib/i18n/languages.ts: add "sv" to LanguageCode + register
  in SUPPORTED_LANGUAGES.
- AppImage/messages/sv/common.json: stub — will be filled by the i18n
  workflow on the local bootstrap.
- .github/scripts/build_translation_cache.py: add "sv" to
  DEFAULT_LANGUAGES.
- .github/scripts/build_i18n_messages.py: add "sk" and "sv" to
  DEFAULT_LANGUAGES; update comments to explain the sk decision.
- .github/workflows/build-i18n-messages.yml: default input includes
  sk + sv; comments updated to match.
This commit is contained in:
MacRimi
2026-08-09 10:35:03 +02:00
parent a2740b6873
commit 5c33852a65
5 changed files with 26 additions and 19 deletions
+14 -10
View File
@@ -12,9 +12,9 @@ Guardrails:
are extracted before translation and restored afterwards, so the are extracted before translation and restored afterwards, so the
interpolation contract stays intact regardless of what the interpolation contract stays intact regardless of what the
translation provider does with the surrounding text. translation provider does with the surrounding text.
- `sk` is skipped by default; override with --languages es,de,fr,it,pt,sk - `sk` IS translated by default too. Guardrail #1 protects every key
if you ever want to include it (which will only fill missing keys, Vaso73 has curated by hand; auto-translation only fills the keys
not overwrite the existing 3632). that are still on the English fallback in sk.
Reuses the same translation providers as build_translation_cache.py so Reuses the same translation providers as build_translation_cache.py so
the CI environment (googletrans pinning, AppImage provider) stays the CI environment (googletrans pinning, AppImage provider) stays
@@ -40,10 +40,14 @@ from build_translation_cache import ( # noqa: E402
) )
# sk is human-curated (Vaso73); default set excludes it so a naive # sk IS included in the default. Guardrail #1 (never overwrite a key
# workflow run cannot accidentally overwrite curated strings. Users can # whose target value differs from EN) protects every string Vaso73 has
# still pass --languages ...,sk if they want auto-fill for missing keys. # already curated by hand — auto-translation only ever touches keys
DEFAULT_LANGUAGES = ("es", "de", "fr", "it", "pt") # that are still on the English fallback in sk. Trade-off accepted:
# users on sk see a decent auto-translation for new keys instead of raw
# English while the human maintainer catches up, and Vaso73 keeps full
# ownership of the wording via follow-up PRs.
DEFAULT_LANGUAGES = ("es", "de", "fr", "it", "pt", "sk", "sv")
DEFAULT_CONTEXT = "Context: Technical UI text for a Proxmox management dashboard. Translate:" DEFAULT_CONTEXT = "Context: Technical UI text for a Proxmox management dashboard. Translate:"
# next-intl / ICU-style placeholders: {name}, {vmid}, {count}, {app_name}. # next-intl / ICU-style placeholders: {name}, {vmid}, {count}, {app_name}.
@@ -169,9 +173,9 @@ def build_arg_parser() -> argparse.ArgumentParser:
"--languages", "--languages",
default=",".join(DEFAULT_LANGUAGES), default=",".join(DEFAULT_LANGUAGES),
help=( help=(
"Comma-separated target locales. Default excludes sk " "Comma-separated target locales. Includes sk by default; "
"(human-curated by Vaso73). Adding sk here only fills " "guardrail #1 never overwrites keys whose sk value differs "
"keys that are still identical to the English fallback." "from EN, so Vaso73's curated translations are safe."
), ),
) )
parser.add_argument( parser.add_argument(
+1 -1
View File
@@ -28,7 +28,7 @@ from urllib.parse import quote
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
DEFAULT_LANGUAGES = ("es", "fr", "de", "it", "pt", "sk") DEFAULT_LANGUAGES = ("es", "fr", "de", "it", "pt", "sk", "sv")
DEFAULT_CONTEXT = "Context: Technical message for Proxmox and IT. Translate:" DEFAULT_CONTEXT = "Context: Technical message for Proxmox and IT. Translate:"
TRANSLATE_CALL_RE = re.compile( TRANSLATE_CALL_RE = re.compile(
r"""translate\s+(?P<quote>["'])(?P<text>(?:\\.|(?! (?P=quote) ).)*?)(?P=quote)""", r"""translate\s+(?P<quote>["'])(?P<text>(?:\\.|(?! (?P=quote) ).)*?)(?P=quote)""",
+6 -7
View File
@@ -10,12 +10,11 @@ name: Build i18n messages
# #
# Guardrails baked into build_i18n_messages.py: # Guardrails baked into build_i18n_messages.py:
# - Never overwrites a key whose target value differs from EN (i.e. # - Never overwrites a key whose target value differs from EN (i.e.
# already translated by a human). # already translated by a human). This is what makes it safe to
# include sk in the default set: Vaso73's curated strings are
# protected end-to-end; auto only fills keys still on the EN
# fallback.
# - `{placeholder}` tokens are protected end-to-end. # - `{placeholder}` tokens are protected end-to-end.
# - `sk` is excluded from the default list — Vaso73's locale is
# human-curated and full. Add it back via workflow_dispatch inputs
# if you ever want auto-fill for the ~200 keys added post his last
# PR (only missing keys will be touched; existing ones are safe).
# #
# Triggers: # Triggers:
# - push to develop touching AppImage/messages/en/common.json # - push to develop touching AppImage/messages/en/common.json
@@ -35,8 +34,8 @@ on:
type: boolean type: boolean
default: false default: false
languages: languages:
description: 'Comma-separated locales. Default: es,de,fr,it,pt (sk skipped).' description: 'Comma-separated locales. Default: es,de,fr,it,pt,sk,sv (guardrail protects Vaso73 sk).'
default: 'es,de,fr,it,pt' default: 'es,de,fr,it,pt,sk,sv'
# Prevent two runs from racing on the same branch and fighting over the # Prevent two runs from racing on the same branch and fighting over the
# auto-commit. cancel-in-progress:false because a full first-run may take # auto-commit. cancel-in-progress:false because a full first-run may take
+2 -1
View File
@@ -1,7 +1,7 @@
export const LANGUAGE_STORAGE_KEY = "proxmenux-ui-language" export const LANGUAGE_STORAGE_KEY = "proxmenux-ui-language"
export const DEFAULT_LANGUAGE = "en" export const DEFAULT_LANGUAGE = "en"
export type LanguageCode = "en" | "es" | "fr" | "de" | "it" | "pt" | "sk" export type LanguageCode = "en" | "es" | "fr" | "de" | "it" | "pt" | "sk" | "sv"
export type LanguageStatus = "complete" | "partial" | "needs-translation" export type LanguageStatus = "complete" | "partial" | "needs-translation"
@@ -20,6 +20,7 @@ export const SUPPORTED_LANGUAGES: SupportedLanguage[] = [
{ code: "de", englishName: "German", nativeName: "Deutsch", status: "needs-translation" }, { code: "de", englishName: "German", nativeName: "Deutsch", status: "needs-translation" },
{ code: "it", englishName: "Italian", nativeName: "Italiano", status: "needs-translation" }, { code: "it", englishName: "Italian", nativeName: "Italiano", status: "needs-translation" },
{ code: "pt", englishName: "Portuguese", nativeName: "Português", status: "needs-translation" }, { code: "pt", englishName: "Portuguese", nativeName: "Português", status: "needs-translation" },
{ code: "sv", englishName: "Swedish", nativeName: "Svenska", status: "needs-translation" },
] ]
export function isSupportedLanguage(value: string | null | undefined): value is LanguageCode { export function isSupportedLanguage(value: string | null | undefined): value is LanguageCode {
+3
View File
@@ -0,0 +1,3 @@
{
"_meta": "Community translation target. Copy keys from ../en/common.json and translate the values. Placeholders like {vmid} must stay unchanged."
}