DEVX-47: feat: add Polish as officially supported language
This commit is contained in:
@@ -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
@@ -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 |
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
+208
-28
File diff suppressed because it is too large
Load Diff
@@ -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."""
|
||||||
|
|||||||
Reference in New Issue
Block a user