DEVX-47: feat: add Polish as officially supported language

This commit is contained in:
emil
2026-06-24 23:04:20 +00:00
parent a987b63da7
commit b4350751f1
8 changed files with 253 additions and 55 deletions
+3 -3
View File
@@ -55,7 +55,7 @@ up by bumping their devx dependency.
- **Developer tools** — environment setup, CI tool installation, test speed - **Developer tools** — environment setup, CI tool installation, test speed
enforcement, repository configuration. enforcement, repository configuration.
- **i18n** — built-in translations for English, Bulgarian, German, Russian, - **i18n** — built-in translations for English, Bulgarian, German, Russian,
and Chinese; projects can extend with their own keys. Chinese, and Polish; projects can extend with their own keys.
## Installation ## Installation
@@ -324,7 +324,7 @@ The config system loads `.env` automatically via `python-dotenv`.
| `DEVX_REPO_NAME` | **(none — must be set)** | Repository name (or `owner/repo`) | | `DEVX_REPO_NAME` | **(none — must be set)** | Repository name (or `owner/repo`) |
| `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) | | `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) |
| `DEVX_VIKUNJA_PROJECT_ID` | `6` | Vikunja project ID | | `DEVX_VIKUNJA_PROJECT_ID` | `6` | Vikunja project ID |
| `DEVX_LANG` | `en` | Language for i18n (en, bg, de, ru, zh) | | `DEVX_LANG` | `en` | Language for i18n (en, bg, de, ru, zh, pl) |
| `DEVX_TRANSLATIONS_PATH` | — | Path to a custom JSON translations file | | `DEVX_TRANSLATIONS_PATH` | — | Path to a custom JSON translations file |
| `DEVX_VERSION_FILE` | `src/devx/__init__.py` | Version source file (used by release) | | `DEVX_VERSION_FILE` | `src/devx/__init__.py` | Version source file (used by release) |
| `DEVX_DOCS_DIR` | `docs` | Documentation directory (used by sync_wiki) | | `DEVX_DOCS_DIR` | `docs` | Documentation directory (used by sync_wiki) |
@@ -431,7 +431,7 @@ src/devx/
├── i18n.py # Translation system (gettext-based, translations.json) ├── i18n.py # Translation system (gettext-based, translations.json)
├── exceptions.py # Custom exception types (DevxError, APIError) ├── exceptions.py # Custom exception types (DevxError, APIError)
├── opentofu.py # OpenTofu output helpers ├── opentofu.py # OpenTofu output helpers
├── translations.json # Translation strings (en, bg, de, ru, zh) ├── translations.json # Translation strings (en, bg, de, ru, zh, pl)
├── ci/ # CI/CD automation modules (run by workflows) ├── ci/ # CI/CD automation modules (run by workflows)
├── tools/ # Developer tooling modules (run locally or by CI) ├── tools/ # Developer tooling modules (run locally or by CI)
└── molecule/ # Optional molecule testing helpers (for Ansible projects) └── molecule/ # Optional molecule testing helpers (for Ansible projects)
+2 -2
View File
@@ -42,7 +42,7 @@ via environment variables and `pyproject.toml`, and inherit:
- **Developer tools** — environment setup, CI tool installation, test speed - **Developer tools** — environment setup, CI tool installation, test speed
enforcement, repository configuration. enforcement, repository configuration.
- **i18n** — built-in translations for English, Bulgarian, German, Russian, - **i18n** — built-in translations for English, Bulgarian, German, Russian,
and Chinese; projects can extend with their own keys. Chinese, and Polish; projects can extend with their own keys.
## Installation ## Installation
@@ -148,7 +148,7 @@ fallback. Key variables:
| `DEVX_REPO_OWNER` | **(must be set)** | Repository owner | | `DEVX_REPO_OWNER` | **(must be set)** | Repository owner |
| `DEVX_REPO_NAME` | **(must be set)** | Repository name | | `DEVX_REPO_NAME` | **(must be set)** | Repository name |
| `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) | | `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) |
| `DEVX_LANG` | `en` | Language for i18n (en, bg, de, ru, zh) | | `DEVX_LANG` | `en` | Language for i18n (en, bg, de, ru, zh, pl) |
| `REPO_TOKEN` | — | Gitea API token | | `REPO_TOKEN` | — | Gitea API token |
| `VIKUNJA_TOKEN` | — | Vikunja API token | | `VIKUNJA_TOKEN` | — | Vikunja API token |
+1 -1
View File
@@ -16,7 +16,7 @@ src/devx/
├── i18n.py # Translation system (JSON-based, translations.json) ├── i18n.py # Translation system (JSON-based, translations.json)
├── exceptions.py # Custom exception types (DevxError, APIError) ├── exceptions.py # Custom exception types (DevxError, APIError)
├── opentofu.py # OpenTofu output helpers ├── opentofu.py # OpenTofu output helpers
├── translations.json # Translation strings (en, bg, de, ru, zh) ├── translations.json # Translation strings (en, bg, de, ru, zh, pl)
├── ci/ # CI/CD automation modules (run by workflows) ├── ci/ # CI/CD automation modules (run by workflows)
│ ├── __init__.py │ ├── __init__.py
│ ├── _shared.py # Shared utilities (get_latest_tag) │ ├── _shared.py # Shared utilities (get_latest_tag)
+2 -2
View File
@@ -40,8 +40,8 @@ checks `src/devx/translations.json` against `src/devx/**/*.py`.
Checks performed: Checks performed:
- **Missing keys** — a `_()` call in code has no entry in the translations file - **Missing keys** — a `_()` call in code has no entry in the translations file
- **Dead keys** — a key in the translations file is not used in any code - **Dead keys** — a key in the translations file is not used in any code
- **Missing languages** — a key exists but is missing one of the five - **Missing languages** — a key exists but is missing one of the six
supported languages (en, bg, de, ru, zh) supported languages (en, bg, de, ru, zh, pl)
```bash ```bash
devx ci check-translations devx ci check-translations
+6 -6
View File
@@ -11,8 +11,8 @@ Checks performed (all fail with exit code 1 on error):
- **Missing keys**: a ``_()`` call in code has no entry in the corresponding - **Missing keys**: a ``_()`` call in code has no entry in the corresponding
translations file. translations file.
- **Dead keys**: a key in a translations file is not used in any code. - **Dead keys**: a key in a translations file is not used in any code.
- **Missing languages**: a key exists but is missing one of the 5 supported - **Missing languages**: a key exists but is missing one of the 6 supported
languages (en, bg, de, ru, zh). This is an error — all supported languages languages (en, bg, de, ru, zh, pl). This is an error — all supported languages
must have translations for every key. must have translations for every key.
Usage:: Usage::
@@ -33,7 +33,7 @@ import click
REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent
SUPPORTED_LANGS = ("en", "bg", "de", "ru", "zh") SUPPORTED_LANGS = ("en", "bg", "de", "ru", "zh", "pl")
# Default translation set: devx package itself # Default translation set: devx package itself
DEFAULT_TRANS_FILE = REPO_ROOT / "src" / "devx" / "translations.json" DEFAULT_TRANS_FILE = REPO_ROOT / "src" / "devx" / "translations.json"
@@ -101,9 +101,9 @@ def collect_keys(src_dir: Path) -> set[str]:
if pyfile.name == "i18n.py": if pyfile.name == "i18n.py":
continue continue
keys |= extract_keys(pyfile) keys |= extract_keys(pyfile)
# Add dynamic keys for the default source directory # Dynamic keys are common status strings used via _(variable) that
if src_dir == DEFAULT_SRC_DIR: # can't be detected by AST scanning. Include them for all projects.
keys |= DYNAMIC_KEYS keys |= DYNAMIC_KEYS
return keys return keys
+2 -2
View File
@@ -1,7 +1,7 @@
"""Simple i18n for devx scripts and tools. """Simple i18n for devx scripts and tools.
Set DEVX_LANG environment variable to override the default English. Set DEVX_LANG environment variable to override the default English.
Supported: en, bg, de, ru, zh. Supported: en, bg, de, ru, zh, pl.
Projects can extend translations by setting DEVX_TRANSLATIONS_PATH to a Projects can extend translations by setting DEVX_TRANSLATIONS_PATH to a
JSON file with additional keys. Keys from the project's file are merged JSON file with additional keys. Keys from the project's file are merged
@@ -45,7 +45,7 @@ def _(key: str, **kwargs: object) -> str:
If unset, English is always returned regardless of system locale. If unset, English is always returned regardless of system locale.
""" """
lang = os.getenv("DEVX_LANG", "en") lang = os.getenv("DEVX_LANG", "en")
if lang not in ("en", "bg", "de", "ru", "zh"): if lang not in ("en", "bg", "de", "ru", "zh", "pl"):
lang = "en" lang = "en"
template = TRANSLATIONS.get(key, {}).get(lang, key) template = TRANSLATIONS.get(key, {}).get(lang, key)
return template.format(**kwargs) return template.format(**kwargs)
File diff suppressed because it is too large Load Diff
+29 -11
View File
@@ -41,9 +41,12 @@ class TestCheckTranslationSet:
src_dir.mkdir() src_dir.mkdir()
(src_dir / "mod.py").write_text('_("Hello")\n') (src_dir / "mod.py").write_text('_("Hello")\n')
trans_file = tmp_path / "translations.json" trans_file = tmp_path / "translations.json"
trans_file.write_text( all_langs = {"en": "Hello", "bg": "Здравей", "de": "Hallo", "ru": "Привет", "zh": "你好", "pl": "Cześć"}
json.dumps({"Hello": {"en": "Hello", "bg": "Здравей", "de": "Hallo", "ru": "Привет", "zh": "你好"}}) # Include dynamic keys since collect_keys now adds them for all dirs
) data = {"Hello": all_langs}
for dk in check_translations.DYNAMIC_KEYS:
data[dk] = all_langs
trans_file.write_text(json.dumps(data))
result = check_translations.check_translation_set("test", src_dir, trans_file) result = check_translations.check_translation_set("test", src_dir, trans_file)
assert not result.errors assert not result.errors
@@ -170,9 +173,11 @@ class TestMain:
def test_translations_flag(self, tmp_path: Path) -> None: def test_translations_flag(self, tmp_path: Path) -> None:
"""--translations flag should check a specific file.""" """--translations flag should check a specific file."""
trans_file = tmp_path / "translations.json" trans_file = tmp_path / "translations.json"
trans_file.write_text( all_langs = {"en": "Hello", "bg": "Здравей", "de": "Hallo", "ru": "Привет", "zh": "你好", "pl": "Cześć"}
json.dumps({"Hello": {"en": "Hello", "bg": "Здравей", "de": "Hallo", "ru": "Привет", "zh": "你好"}}) data = {"Hello": all_langs}
) for dk in check_translations.DYNAMIC_KEYS:
data[dk] = all_langs
trans_file.write_text(json.dumps(data))
(tmp_path / "mod.py").write_text('_("Hello")\n') (tmp_path / "mod.py").write_text('_("Hello")\n')
runner = CliRunner() runner = CliRunner()
@@ -251,6 +256,19 @@ class TestDevxI18n:
monkeypatch.delenv("DEVX_LANG", raising=False) monkeypatch.delenv("DEVX_LANG", raising=False)
importlib.reload(devx.i18n) importlib.reload(devx.i18n)
def test_polish_translation(self, monkeypatch: pytest.MonkeyPatch) -> None:
import importlib
monkeypatch.setenv("DEVX_LANG", "pl")
import devx.i18n
importlib.reload(devx.i18n)
result = devx.i18n._("Running tests...")
assert "Uruchamianie testów" in result
monkeypatch.delenv("DEVX_LANG", raising=False)
importlib.reload(devx.i18n)
def test_unsupported_lang_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None: def test_unsupported_lang_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
import importlib import importlib
@@ -290,14 +308,14 @@ class TestCollectKeys:
assert "should_appear" in keys assert "should_appear" in keys
assert "should_not_appear" not in keys assert "should_not_appear" not in keys
def test_non_default_dir_no_dynamic_keys(self, tmp_path: Path) -> None: def test_non_default_dir_includes_dynamic_keys(self, tmp_path: Path) -> None:
"""Non-default source dirs should not include DYNAMIC_KEYS.""" """Non-default source dirs should also include DYNAMIC_KEYS."""
(tmp_path / "mod.py").write_text('_("mykey")\n') (tmp_path / "mod.py").write_text('_("mykey")\n')
keys = check_translations.collect_keys(tmp_path) keys = check_translations.collect_keys(tmp_path)
assert "mykey" in keys assert "mykey" in keys
# Dynamic keys should NOT be present for non-default dirs # Dynamic keys should be present for all dirs
assert "completed" not in keys assert "completed" in keys
assert "pending" not in keys assert "pending" in keys
def test_default_dir_includes_dynamic_keys(self) -> None: def test_default_dir_includes_dynamic_keys(self) -> None:
"""The default source dir should include DYNAMIC_KEYS.""" """The default source dir should include DYNAMIC_KEYS."""