GRM-61: refactor: separate GRM and CI translations with validation
This commit is contained in:
@@ -12,7 +12,7 @@ import subprocess # nosec B404
|
||||
|
||||
import click
|
||||
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
DEFAULT_MAX_SECONDS = 2.0
|
||||
TEST_COMMAND = ["make", "test-unit"]
|
||||
|
||||
@@ -36,7 +36,7 @@ from gitea_runner_manager.config import (
|
||||
VIKUNJA_PROJECT_ID,
|
||||
)
|
||||
from gitea_runner_manager.exceptions import APIError
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
TASKID_FILE = ".taskid"
|
||||
PR_TITLE_RE = re.compile(r"^GRM-\d+:\s+.+")
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check translation files for gaps, dead keys, and missing languages.
|
||||
|
||||
Validates two separate translation sets:
|
||||
1. GRM tool translations: ``src/gitea_runner_manager/translations.json``
|
||||
— keys used by ``src/gitea_runner_manager/*.py``
|
||||
2. CI/dev tool translations: ``scripts/translations.json``
|
||||
— keys used by ``scripts/**/*.py``
|
||||
|
||||
Checks performed (all fail with exit code 1 on error):
|
||||
- **Missing keys**: a ``_()`` call in code has no entry in the corresponding
|
||||
translations file.
|
||||
- **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
|
||||
languages (en, bg, de, ru, zh). Reported as a warning, not an error.
|
||||
|
||||
Usage::
|
||||
|
||||
python3 scripts/ci/check_translations.py
|
||||
python3 scripts/ci/check_translations.py --strict # warnings are errors
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
SUPPORTED_LANGS = ("en", "bg", "de", "ru", "zh")
|
||||
|
||||
GRM_SRC_DIR = REPO_ROOT / "src" / "gitea_runner_manager"
|
||||
GRM_TRANS_FILE = GRM_SRC_DIR / "translations.json"
|
||||
|
||||
CI_SRC_DIR = REPO_ROOT / "scripts"
|
||||
CI_TRANS_FILE = CI_SRC_DIR / "translations.json"
|
||||
|
||||
# Functions that wrap _() and receive a translation key as first arg.
|
||||
# Their string-literal arguments should be treated as translation keys.
|
||||
_I18N_WRAPPERS = {"_handle_errors"}
|
||||
|
||||
# Known dynamic keys used via _(variable) that can't be detected by AST.
|
||||
# These are status strings set as variable values and passed to _().
|
||||
DYNAMIC_KEYS = {"completed", "pending", "in_progress", "failed", "active", "inactive", "unknown"}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TranslationCheckResult:
|
||||
"""Result of a translation check for one translation set."""
|
||||
|
||||
name: str
|
||||
src_dir: Path
|
||||
trans_file: Path
|
||||
used_keys: set[str] = field(default_factory=set)
|
||||
defined_keys: set[str] = field(default_factory=set)
|
||||
missing_keys: set[str] = field(default_factory=set)
|
||||
dead_keys: set[str] = field(default_factory=set)
|
||||
missing_langs: dict[str, list[str]] = field(default_factory=dict)
|
||||
errors: list[str] = field(default_factory=list)
|
||||
warnings: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def extract_keys(filepath: Path) -> set[str]:
|
||||
"""Extract translation keys from a Python file using AST.
|
||||
|
||||
Detects:
|
||||
- ``_("key")`` calls with string-literal first argument
|
||||
- ``_handle_errors("key")`` and other wrapper calls (see ``_I18N_WRAPPERS``)
|
||||
"""
|
||||
try:
|
||||
tree = ast.parse(filepath.read_text(encoding="utf-8"), filename=str(filepath))
|
||||
except SyntaxError:
|
||||
return set()
|
||||
keys: set[str] = set()
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Call):
|
||||
func = node.func
|
||||
if (
|
||||
isinstance(func, ast.Name)
|
||||
and func.id in ("_", *_I18N_WRAPPERS)
|
||||
and node.args
|
||||
and isinstance(node.args[0], ast.Constant)
|
||||
and isinstance(node.args[0].value, str)
|
||||
):
|
||||
keys.add(node.args[0].value)
|
||||
return keys
|
||||
|
||||
|
||||
def collect_keys(src_dir: Path) -> set[str]:
|
||||
"""Collect all translation keys from .py files in a directory tree.
|
||||
|
||||
Also includes known dynamic keys (see ``DYNAMIC_KEYS``) that are used
|
||||
via ``_(variable)`` and can't be detected by AST scanning.
|
||||
"""
|
||||
keys: set[str] = set()
|
||||
for pyfile in src_dir.rglob("*.py"):
|
||||
if pyfile.name == "i18n.py":
|
||||
continue
|
||||
keys |= extract_keys(pyfile)
|
||||
# Add dynamic keys for the GRM source directory
|
||||
if src_dir == GRM_SRC_DIR:
|
||||
keys |= DYNAMIC_KEYS
|
||||
return keys
|
||||
|
||||
|
||||
def check_translation_set(name: str, src_dir: Path, trans_file: Path) -> TranslationCheckResult:
|
||||
"""Check one translation set for gaps and dead keys."""
|
||||
result = TranslationCheckResult(name=name, src_dir=src_dir, trans_file=trans_file)
|
||||
|
||||
# Collect used keys from source code
|
||||
result.used_keys = collect_keys(src_dir)
|
||||
|
||||
# Load defined keys from translations file
|
||||
if not trans_file.exists():
|
||||
result.errors.append(f"Translations file not found: {trans_file}")
|
||||
return result
|
||||
|
||||
translations = json.loads(trans_file.read_text(encoding="utf-8"))
|
||||
result.defined_keys = set(translations.keys())
|
||||
|
||||
# Check for missing keys (used in code but not in translations)
|
||||
result.missing_keys = result.used_keys - result.defined_keys
|
||||
for key in sorted(result.missing_keys):
|
||||
result.errors.append(f"Missing key in {name}: {key!r}")
|
||||
|
||||
# Check for dead keys (in translations but not used in code)
|
||||
result.dead_keys = result.defined_keys - result.used_keys
|
||||
for key in sorted(result.dead_keys):
|
||||
result.warnings.append(f"Dead key in {name}: {key!r}")
|
||||
|
||||
# Check for missing languages
|
||||
for key, langs in translations.items():
|
||||
missing = [lang for lang in SUPPORTED_LANGS if lang not in langs]
|
||||
if missing:
|
||||
result.missing_langs[key] = missing
|
||||
result.warnings.append(f"Missing languages {missing} for key {key!r} in {name}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def print_result(result: TranslationCheckResult) -> None:
|
||||
"""Print check results in a human-readable format."""
|
||||
click.echo(f"\n=== {result.name} ===")
|
||||
click.echo(f" Source dir: {result.src_dir}")
|
||||
click.echo(f" Translations: {result.trans_file}")
|
||||
click.echo(f" Used keys: {len(result.used_keys)}")
|
||||
click.echo(f" Defined keys: {len(result.defined_keys)}")
|
||||
click.echo(f" Missing keys: {len(result.missing_keys)}")
|
||||
click.echo(f" Dead keys: {len(result.dead_keys)}")
|
||||
click.echo(f" Missing langs: {len(result.missing_langs)} keys")
|
||||
|
||||
for err in result.errors:
|
||||
click.echo(f" ERROR: {err}", err=True)
|
||||
for warn in result.warnings:
|
||||
click.echo(f" WARN: {warn}", err=True)
|
||||
|
||||
if not result.errors and not result.warnings:
|
||||
click.echo(" All good!")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--strict", is_flag=True, default=False, help="Treat warnings as errors.")
|
||||
def main(strict: bool) -> None:
|
||||
"""Check translation files for gaps, dead keys, and missing languages."""
|
||||
results = [
|
||||
check_translation_set("GRM tool", GRM_SRC_DIR, GRM_TRANS_FILE),
|
||||
check_translation_set("CI/dev tools", CI_SRC_DIR, CI_TRANS_FILE),
|
||||
]
|
||||
|
||||
has_errors = False
|
||||
has_warnings = False
|
||||
for result in results:
|
||||
print_result(result)
|
||||
if result.errors:
|
||||
has_errors = True
|
||||
if result.warnings:
|
||||
has_warnings = True
|
||||
|
||||
click.echo()
|
||||
if has_errors:
|
||||
click.echo("FAIL: Translation check found errors.", err=True)
|
||||
sys.exit(1)
|
||||
if strict and has_warnings:
|
||||
click.echo("FAIL: Translation check found warnings (--strict mode).", err=True)
|
||||
sys.exit(1)
|
||||
if has_warnings:
|
||||
click.echo("PASS with warnings: Translation check passed (warnings present).")
|
||||
else:
|
||||
click.echo("PASS: All translations are complete and up to date.")
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main() # pragma: no cover
|
||||
@@ -55,7 +55,7 @@ import sys
|
||||
|
||||
import click
|
||||
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
# Explicit allowlist of workflow-only path patterns.
|
||||
# Anything NOT matching these is treated as user-facing (safe default).
|
||||
|
||||
@@ -24,8 +24,8 @@ from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.ci.platforms import PLATFORMS
|
||||
from scripts.i18n import _
|
||||
|
||||
DEFAULT_MAX_RUNNERS = 3
|
||||
MOLECULE_ROOT = Path("ansible/roles/gitea-runner/molecule")
|
||||
|
||||
@@ -17,7 +17,7 @@ from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
DOCS_DIR = REPO_ROOT / "docs"
|
||||
|
||||
@@ -35,7 +35,7 @@ from pathlib import Path
|
||||
import click
|
||||
import requests
|
||||
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
POLL_INTERVAL = 10
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import click
|
||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||
|
||||
from gitea_runner_manager.config import GITEA_API_URL
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.gitea_cli import TeaCLI, TeaCLIError
|
||||
from scripts.i18n import _
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
||||
from gitea_runner_manager.api_clients import VikunjaClient
|
||||
from gitea_runner_manager.config import DEFAULT_PER_PAGE, TASK_ID_RE, VIKUNJA_API_URL, VIKUNJA_PROJECT_ID
|
||||
from gitea_runner_manager.exceptions import APIError
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
||||
from gitea_runner_manager.api_clients import GiteaClient
|
||||
from gitea_runner_manager.config import GITEA_API_URL
|
||||
from gitea_runner_manager.exceptions import APIError
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import sys
|
||||
import click
|
||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.gitea_cli import TeaCLI, TeaCLIError
|
||||
from scripts.i18n import _
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ import subprocess # nosec B404
|
||||
import click
|
||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.ci.classify_changes import has_user_facing_changes # cross-CI import, needs PYTHONPATH=.
|
||||
from scripts.i18n import _
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
||||
from gitea_runner_manager.api_clients import GiteaClient
|
||||
from gitea_runner_manager.config import GITEA_API_URL
|
||||
from gitea_runner_manager.exceptions import APIError
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import subprocess # nosec B404
|
||||
import click
|
||||
|
||||
from gitea_runner_manager.config import CONVENTIONAL_RE
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
MASTER_TASK_ID_RE = re.compile(r"^GRM-\d+:")
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ from gitea_runner_manager.config import (
|
||||
REPO_SETTINGS_CONFIG,
|
||||
)
|
||||
from gitea_runner_manager.exceptions import APIError
|
||||
from gitea_runner_manager.i18n import _
|
||||
from scripts.i18n import _
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""i18n for CI/dev scripts.
|
||||
|
||||
Separate from the GRM tool's i18n (``gitea_runner_manager.i18n``) so that
|
||||
CI-only translation keys don't bloat the packaged CLI.
|
||||
|
||||
Set GRM_LANG environment variable to override the default English.
|
||||
Supported: en, bg, de, ru, zh.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
TRANSLATIONS: dict[str, dict[str, str]] = json.loads(
|
||||
(Path(__file__).parent / "translations.json").read_text(encoding="utf-8")
|
||||
)
|
||||
|
||||
|
||||
def _(key: str, **kwargs: object) -> str:
|
||||
"""Return a translated string for the given key.
|
||||
|
||||
Translation is opt-in via the ``GRM_LANG`` environment variable.
|
||||
If unset, English is always returned regardless of system locale.
|
||||
"""
|
||||
lang = os.getenv("GRM_LANG", "en")
|
||||
if lang not in ("en", "bg", "de", "ru", "zh"):
|
||||
lang = "en"
|
||||
template = TRANSLATIONS.get(key, {}).get(lang, key)
|
||||
return template.format(**kwargs)
|
||||
+44
-12
@@ -28,10 +28,10 @@ def _run(cmd: list[str], bin_dir: str) -> None:
|
||||
subprocess.run(cmd, check=True) # nosec B603
|
||||
|
||||
|
||||
def _install_python_deps(bin_dir: str) -> None:
|
||||
"""Install the project with dev extras in editable mode."""
|
||||
def _install_python_deps(bin_dir: str, extras: str = "dev") -> None:
|
||||
"""Install the project with the specified extras in editable mode."""
|
||||
pip = str(Path(bin_dir) / "pip")
|
||||
_run([pip, "install", "-e", ".[dev]"], bin_dir)
|
||||
_run([pip, "install", "-e", f".[{extras}]"], bin_dir)
|
||||
|
||||
|
||||
def _install_ansible_collections(bin_dir: str) -> None:
|
||||
@@ -119,22 +119,54 @@ def _verify(bin_dir: str) -> None:
|
||||
|
||||
@click.command()
|
||||
@click.option("--bin", "bin_dir", default=".venv/bin", help="Path to the virtualenv bin directory.")
|
||||
def main(bin_dir: str) -> None:
|
||||
@click.option(
|
||||
"--extras",
|
||||
default="dev",
|
||||
help="Dependency group to install: ci, lint, molecule, or dev (default: dev).",
|
||||
)
|
||||
@click.option(
|
||||
"--no-ansible-collections",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Skip Ansible Galaxy collections installation.",
|
||||
)
|
||||
@click.option(
|
||||
"--no-pre-commit",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Skip pre-commit hook installation.",
|
||||
)
|
||||
@click.option(
|
||||
"--no-tea-login",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Skip tea CLI login configuration.",
|
||||
)
|
||||
def main(
|
||||
bin_dir: str,
|
||||
extras: str,
|
||||
no_ansible_collections: bool,
|
||||
no_pre_commit: bool,
|
||||
no_tea_login: bool,
|
||||
) -> None:
|
||||
"""Install Python deps, Ansible collections, and pre-commit hooks."""
|
||||
if not Path(bin_dir).exists():
|
||||
raise click.ClickException(f"Bin directory not found: {bin_dir}. Run 'python3 -m venv .venv' first.")
|
||||
|
||||
click.echo("Installing Python dependencies...")
|
||||
_install_python_deps(bin_dir)
|
||||
click.echo(f"Installing Python dependencies (extras: {extras})...")
|
||||
_install_python_deps(bin_dir, extras)
|
||||
|
||||
click.echo("Installing Ansible collections...")
|
||||
_install_ansible_collections(bin_dir)
|
||||
if not no_ansible_collections:
|
||||
click.echo("Installing Ansible collections...")
|
||||
_install_ansible_collections(bin_dir)
|
||||
|
||||
click.echo("Installing pre-commit hooks...")
|
||||
_install_pre_commit_hooks(bin_dir)
|
||||
if not no_pre_commit:
|
||||
click.echo("Installing pre-commit hooks...")
|
||||
_install_pre_commit_hooks(bin_dir)
|
||||
|
||||
click.echo("Configuring tea CLI login...")
|
||||
_configure_tea_login()
|
||||
if not no_tea_login:
|
||||
click.echo("Configuring tea CLI login...")
|
||||
_configure_tea_login()
|
||||
|
||||
click.echo("")
|
||||
click.echo("Setup complete.")
|
||||
|
||||
@@ -0,0 +1,503 @@
|
||||
{
|
||||
"\nAll documentation coverage checks passed!": {
|
||||
"en": "\nAll documentation coverage checks passed!"
|
||||
},
|
||||
"\nAnsible files changed ({count}):": {
|
||||
"en": "\nAnsible files changed ({count}):"
|
||||
},
|
||||
"\nChecking CI script documentation in ci-cd-workflow.md...": {
|
||||
"en": "\nChecking CI script documentation in ci-cd-workflow.md..."
|
||||
},
|
||||
"\nChecking module documentation in architecture.md...": {
|
||||
"en": "\nChecking module documentation in architecture.md..."
|
||||
},
|
||||
"\nDoc coverage: {covered}/{total} ({pct}%)": {
|
||||
"en": "\nDoc coverage: {covered}/{total} ({pct}%)"
|
||||
},
|
||||
"\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}": {
|
||||
"en": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}"
|
||||
},
|
||||
"\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.": {
|
||||
"en": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce."
|
||||
},
|
||||
"\nIntegrity check FAILED ({count} issues):": {
|
||||
"en": "\nIntegrity check FAILED ({count} issues):"
|
||||
},
|
||||
"\nIntegrity check passed — all {count} pages verified.": {
|
||||
"en": "\nIntegrity check passed — all {count} pages verified."
|
||||
},
|
||||
"\nMissing documentation:": {
|
||||
"en": "\nMissing documentation:"
|
||||
},
|
||||
"\nResult: {status}": {
|
||||
"en": "\nResult: {status}"
|
||||
},
|
||||
"\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).": {
|
||||
"en": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments)."
|
||||
},
|
||||
"\nRunning full wiki integrity check...": {
|
||||
"en": "\nRunning full wiki integrity check..."
|
||||
},
|
||||
"\nUser-facing changes ({count}):": {
|
||||
"en": "\nUser-facing changes ({count}):"
|
||||
},
|
||||
"\nUser-facing files changed ({count}):": {
|
||||
"en": "\nUser-facing files changed ({count}):"
|
||||
},
|
||||
"\nVerification FAILED: {failures} page(s) have empty or mismatched content!": {
|
||||
"en": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!"
|
||||
},
|
||||
"\nVerification passed — all wiki pages have correct content.": {
|
||||
"en": "\nVerification passed — all wiki pages have correct content."
|
||||
},
|
||||
"\nVerifying wiki pages have content...": {
|
||||
"en": "\nVerifying wiki pages have content..."
|
||||
},
|
||||
"\nWorkflow-only changes ({count}):": {
|
||||
"en": "\nWorkflow-only changes ({count}):"
|
||||
},
|
||||
"\n[dry-run] Changelog:\n{changelog}": {
|
||||
"en": "\n[dry-run] Changelog:\n{changelog}"
|
||||
},
|
||||
" - Auto-delete branch after merge: yes": {
|
||||
"en": " - Auto-delete branch after merge: yes",
|
||||
"bg": " - Автоматично изтриване на клон след сливане: да",
|
||||
"de": " - Branch nach Merge automatisch löschen: ja",
|
||||
"ru": " - Автоудаление ветки после слияния: да",
|
||||
"zh": " - 合并后自动删除分支: 是"
|
||||
},
|
||||
" - Block outdated branches: yes": {
|
||||
"en": " - Block outdated branches: yes",
|
||||
"bg": " - Блокиране на остарели клонове: да",
|
||||
"de": " - Veraltete Branches blockieren: ja",
|
||||
"ru": " - Блокировать устаревшие ветки: да",
|
||||
"zh": " - 阻止过时分支: 是"
|
||||
},
|
||||
" - Block rejected reviews: yes": {
|
||||
"en": " - Block rejected reviews: yes",
|
||||
"bg": " - Блокиране на отхвърлени рецензии: да",
|
||||
"de": " - Abgelehnte Reviews blockieren: ja",
|
||||
"ru": " - Блокировать отклонённые ревью: да",
|
||||
"zh": " - 阻止被拒绝的审查: 是"
|
||||
},
|
||||
" - Direct pushes: BLOCKED (require PR, whitelisted users can push)": {
|
||||
"en": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)"
|
||||
},
|
||||
" - Dismiss stale approvals: yes": {
|
||||
"en": " - Dismiss stale approvals: yes",
|
||||
"bg": " - Анулиране на остарели одобрения: да",
|
||||
"de": " - Veraltete Genehmigungen ablehnen: ja",
|
||||
"ru": " - Отклонять устаревшие одобрения: да",
|
||||
"zh": " - 忽略过时审批: 是"
|
||||
},
|
||||
" - Required approvals: {count}": {
|
||||
"en": " - Required approvals: {count}",
|
||||
"bg": " - Необходими одобрения: {count}",
|
||||
"de": " - Erforderliche Genehmigungen: {count}",
|
||||
"ru": " - Требуемые одобрения: {count}",
|
||||
"zh": " - 必需审批数: {count}"
|
||||
},
|
||||
" - Required status checks: {checks}": {
|
||||
"en": " - Required status checks: {checks}",
|
||||
"bg": " - Необходими проверки на състоянието: {checks}",
|
||||
"de": " - Erforderliche Status-Checks: {checks}",
|
||||
"ru": " - Требуемые проверки статуса: {checks}",
|
||||
"zh": " - 必需状态检查: {checks}"
|
||||
},
|
||||
" Created: {title}": {
|
||||
"en": " Created: {title}"
|
||||
},
|
||||
" FAIL: {title} — content mismatch or empty!": {
|
||||
"en": " FAIL: {title} — content mismatch or empty!"
|
||||
},
|
||||
" MISSING: grm {cmd}": {
|
||||
"en": " MISSING: grm {cmd}"
|
||||
},
|
||||
" MISSING: {module}": {
|
||||
"en": " MISSING: {module}"
|
||||
},
|
||||
" MISSING: {script}": {
|
||||
"en": " MISSING: {script}"
|
||||
},
|
||||
" OK: grm {cmd}": {
|
||||
"en": " OK: grm {cmd}"
|
||||
},
|
||||
" OK: {module}": {
|
||||
"en": " OK: {module}"
|
||||
},
|
||||
" OK: {script}": {
|
||||
"en": " OK: {script}"
|
||||
},
|
||||
" OK: {title} ({chars} chars)": {
|
||||
"en": " OK: {title} ({chars} chars)"
|
||||
},
|
||||
" Updated: {title}": {
|
||||
"en": " Updated: {title}"
|
||||
},
|
||||
"API poll warning: {exc}": {
|
||||
"en": "API poll warning: {exc}"
|
||||
},
|
||||
"All molecule tests passed.": {
|
||||
"en": "All molecule tests passed."
|
||||
},
|
||||
"Another molecule runner failed. Stopping this runner early.": {
|
||||
"en": "Another molecule runner failed. Stopping this runner early."
|
||||
},
|
||||
"Bumping version: {current} -> v{new_version}": {
|
||||
"en": "Bumping version: {current} -> v{new_version}"
|
||||
},
|
||||
"Checking CLI command documentation...": {
|
||||
"en": "Checking CLI command documentation..."
|
||||
},
|
||||
"Command failed ({cmd}): {stderr}": {
|
||||
"en": "Command failed ({cmd}): {stderr}"
|
||||
},
|
||||
"Comparing {base}..{head} ({count} files changed)": {
|
||||
"en": "Comparing {base}..{head} ({count} files changed)"
|
||||
},
|
||||
"Configuring branch protection for {branch}...": {
|
||||
"en": "Configuring branch protection for {branch}...",
|
||||
"bg": "Конфигуриране на защита на клона {branch}...",
|
||||
"de": "Konfiguriere Branch-Schutz für {branch}...",
|
||||
"ru": "Настройка защиты ветки {branch}...",
|
||||
"zh": "正在配置 {branch} 的分支保护..."
|
||||
},
|
||||
"Configuring repository settings...": {
|
||||
"en": "Configuring repository settings...",
|
||||
"bg": "Конфигуриране на настройките на хранилището...",
|
||||
"de": "Repository-Einstellungen konfigurieren...",
|
||||
"ru": "Настройка параметров репозитория...",
|
||||
"zh": "正在配置仓库设置..."
|
||||
},
|
||||
"Could not extract conventional commit message from PR commits.": {
|
||||
"en": "Could not extract conventional commit message from PR commits."
|
||||
},
|
||||
"Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.": {
|
||||
"en": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task."
|
||||
},
|
||||
"Could not find __version__ in {file}": {
|
||||
"en": "Could not find __version__ in {file}"
|
||||
},
|
||||
"Could not parse test execution time from output.": {
|
||||
"en": "Could not parse test execution time from output."
|
||||
},
|
||||
"Created issue #{issue_id}: {title}": {
|
||||
"en": "Created issue #{issue_id}: {title}"
|
||||
},
|
||||
"Created release commit.": {
|
||||
"en": "Created release commit."
|
||||
},
|
||||
"Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.": {
|
||||
"en": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently."
|
||||
},
|
||||
"ERROR: REPO_TOKEN is not set.": {
|
||||
"en": "ERROR: REPO_TOKEN is not set.",
|
||||
"bg": "ГРЕШКА: REPO_TOKEN не е зададен.",
|
||||
"de": "FEHLER: REPO_TOKEN ist nicht gesetzt.",
|
||||
"ru": "ОШИБКА: REPO_TOKEN не задан.",
|
||||
"zh": "错误:未设置 REPO_TOKEN。"
|
||||
},
|
||||
"ERROR: VIKUNJA_TOKEN is not set.": {
|
||||
"en": "ERROR: VIKUNJA_TOKEN is not set.",
|
||||
"bg": "ГРЕШКА: VIKUNJA_TOKEN не е зададен.",
|
||||
"de": "FEHLER: VIKUNJA_TOKEN ist nicht gesetzt.",
|
||||
"ru": "ОШИБКА: VIKUNJA_TOKEN не задан.",
|
||||
"zh": "错误:未设置 VIKUNJA_TOKEN。"
|
||||
},
|
||||
"ERROR: mapping.json not found at {path}": {
|
||||
"en": "ERROR: mapping.json not found at {path}"
|
||||
},
|
||||
"FAILED: {pair} exited with code {code}": {
|
||||
"en": "FAILED: {pair} exited with code {code}"
|
||||
},
|
||||
"Failed to create issue via tea: {error}": {
|
||||
"en": "Failed to create issue via tea: {error}"
|
||||
},
|
||||
"Found {count} existing wiki pages.": {
|
||||
"en": "Found {count} existing wiki pages."
|
||||
},
|
||||
"GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.": {
|
||||
"en": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation."
|
||||
},
|
||||
"HEAD is already a release commit ('{msg}'). Another release may have just completed. Skipping.": {
|
||||
"en": "HEAD is already a release commit ('{msg}'). Another release may have just completed. Skipping."
|
||||
},
|
||||
"HTTP error: {status} — {message}": {
|
||||
"en": "HTTP error: {status} — {message}",
|
||||
"bg": "HTTP грешка: {status} — {message}",
|
||||
"de": "HTTP-Fehler: {status} — {message}",
|
||||
"ru": "Ошибка HTTP: {status} — {message}",
|
||||
"zh": "HTTP 错误: {status} — {message}"
|
||||
},
|
||||
"HTTP {status} Forbidden — your token lacks admin rights.\nMake sure the token belongs to a repo owner or organisation admin.\nAlternatively, configure branch protection manually in Settings → Branches.": {
|
||||
"en": "HTTP {status} Forbidden — your token lacks admin rights.\nMake sure the token belongs to a repo owner or organisation admin.\nAlternatively, configure branch protection manually in Settings → Branches.",
|
||||
"bg": "HTTP {status} Забранено — вашият токен няма администраторски права.\nУверете се, че токенът принадлежи на собственик на хранилище или администратор на организация.\nАлтернативно, конфигурирайте защитата на клона ръчно в Настройки → Клонове.",
|
||||
"de": "HTTP {status} Verboten — Ihr Token hat keine Admin-Rechte.\nStellen Sie sicher, dass das Token einem Repository-Besitzer oder Organisations-Admin gehört.\nAlternativ können Sie den Branch-Schutz manuell unter Einstellungen → Branches konfigurieren.",
|
||||
"ru": "HTTP {status} Запрещено — у вашего токена нет прав администратора.\nУбедитесь, что токен принадлежит владельцу репозитория или администратору организации.\nЛибо настройте защиту ветки вручную в разделе Настройки → Ветки.",
|
||||
"zh": "HTTP {status} 禁止访问 — 您的令牌缺少管理员权限。\n请确保令牌属于仓库所有者或组织管理员。\n或者,您可以在 设置 → 分支 中手动配置分支保护。"
|
||||
},
|
||||
"Head branch is behind master. Pulling and rebasing...": {
|
||||
"en": "Head branch is behind master. Pulling and rebasing..."
|
||||
},
|
||||
"Infrastructure commit (no GRM-N task ID), skipping Vikunja update: {msg}": {
|
||||
"en": "Infrastructure commit (no GRM-N task ID), skipping Vikunja update: {msg}"
|
||||
},
|
||||
"Lint failed — refusing to release. Fix lint errors first.\n{stderr}": {
|
||||
"en": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}"
|
||||
},
|
||||
"Lint passed.": {
|
||||
"en": "Lint passed."
|
||||
},
|
||||
"Merge failed after rebase retry: {error}\nPlease rebase the PR manually.": {
|
||||
"en": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually."
|
||||
},
|
||||
"Merge failed with HTTP {status}: {message}\nPlease check the PR is ready and you have merge rights.": {
|
||||
"en": "Merge failed with HTTP {status}: {message}\nPlease check the PR is ready and you have merge rights.",
|
||||
"bg": "Сливането неуспешно с HTTP {status}: {message}\nПроверете дали PR е готов и имате права за сливане.",
|
||||
"de": "Merge fehlgeschlagen mit HTTP {status}: {message}\nBitte prüfen Sie, ob der PR bereit ist und Sie Merge-Rechte haben.",
|
||||
"ru": "Слияние не удалось: HTTP {status}: {message}\nПроверьте, что PR готов и у вас есть права на слияние.",
|
||||
"zh": "合并失败: HTTP {status}: {message}\n请检查 PR 是否准备就绪且您具有合并权限。"
|
||||
},
|
||||
"Molecule directory not found: {path}": {
|
||||
"en": "Molecule directory not found: {path}",
|
||||
"bg": "Директорията на molecule не е намерена: {path}",
|
||||
"de": "Molecule-Verzeichnis nicht gefunden: {path}",
|
||||
"ru": "Директория molecule не найдена: {path}",
|
||||
"zh": "未找到 molecule 目录: {path}"
|
||||
},
|
||||
"Nice! Gitea release {tag} created.": {
|
||||
"en": "Nice! Gitea release {tag} created.",
|
||||
"bg": "Отлично! Gitea release {tag} е създаден.",
|
||||
"de": "Prima! Gitea-Release {tag} erstellt.",
|
||||
"ru": "Отлично! Gitea release {tag} создан.",
|
||||
"zh": "不错!Gitea release {tag} 已创建。"
|
||||
},
|
||||
"Nice! PR #{pr_number} squash-merged with title: {merge_title}": {
|
||||
"en": "Nice! PR #{pr_number} squash-merged with title: {merge_title}",
|
||||
"bg": "Отлично! PR #{pr_number} е squash-merge-нат със заглавие: {merge_title}",
|
||||
"de": "Prima! PR #{pr_number} wurde mit Titel {merge_title} squash-gemergt.",
|
||||
"ru": "Отлично! PR #{pr_number} squash-merge с заголовком: {merge_title}",
|
||||
"zh": "不错!PR #{pr_number} 已 squash 合并,标题: {merge_title}"
|
||||
},
|
||||
"Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.": {
|
||||
"en": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered."
|
||||
},
|
||||
"Nice! Vikunja task {task_id} (ID {vikunja_id}) updated and marked done.": {
|
||||
"en": "Nice! Vikunja task {task_id} (ID {vikunja_id}) updated and marked done.",
|
||||
"bg": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) е обновена и маркирана като готова.",
|
||||
"de": "Prima! Vikunja-Aufgabe {task_id} (ID {vikunja_id}) aktualisiert und als erledigt markiert.",
|
||||
"ru": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) обновлена и отмечена как выполненная.",
|
||||
"zh": "不错!Vikunja 任务 {task_id} (ID {vikunja_id}) 已更新并标记为完成。"
|
||||
},
|
||||
"No changes between {base} and {head}.": {
|
||||
"en": "No changes between {base} and {head}."
|
||||
},
|
||||
"No staged changes — version and changelog already up to date.": {
|
||||
"en": "No staged changes — version and changelog already up to date."
|
||||
},
|
||||
"No tags found — treating all changes as user-facing.": {
|
||||
"en": "No tags found — treating all changes as user-facing."
|
||||
},
|
||||
"No unreleased changes found. Nothing to release.": {
|
||||
"en": "No unreleased changes found. Nothing to release."
|
||||
},
|
||||
"No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.": {
|
||||
"en": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release."
|
||||
},
|
||||
"Note: Self-approval not allowed. Posting COMMENT instead.": {
|
||||
"en": "Note: Self-approval not allowed. Posting COMMENT instead."
|
||||
},
|
||||
"Oops! Commit message must follow conventional commit format.\n Expected: <type>: <description>\n Got: {subject}\n Allowed types: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE": {
|
||||
"en": "Oops! Commit message must follow conventional commit format.\n Expected: <type>: <description>\n Got: {subject}\n Allowed types: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE",
|
||||
"bg": "Опа! Съобщението за commit трябва да следва конвенционален формат.\n Очаква се: <type>: <description>\n Получено: {subject}\n Разрешени типове: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE",
|
||||
"de": "Ups! Commit-Nachricht muss dem konventionellen Commit-Format folgen.\n Erwartet: <type>: <description>\n Erhalten: {subject}\n Erlaubte Typen: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE",
|
||||
"ru": "Ой! Сообщение коммита должно соответствовать формату conventional commit.\n Ожидается: <type>: <description>\n Получено: {subject}\n Допустимые типы: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE",
|
||||
"zh": "哎呀!提交消息必须遵循 conventional commit 格式。\n 预期格式: <type>: <description>\n 实际: {subject}\n 允许的类型: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE"
|
||||
},
|
||||
"Oops! Do not include task ID (GRM-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": {
|
||||
"en": "Oops! Do not include task ID (GRM-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
|
||||
"bg": "Опа! Не включвайте идентификатор на задача (GRM-N) в commit-и от feature клонове.\n Идентификаторът ще бъде добавен автоматично при сливане чрез CI.",
|
||||
"de": "Ups! Keine Task-ID (GRM-N) in Feature-Branch-Commits einfügen.\n Die Task-ID wird beim Merge automatisch über CI hinzugefügt.",
|
||||
"ru": "Ой! Не включайте ID задачи (GRM-N) в коммиты feature-веток.\n ID задачи будет добавлен автоматически при слиянии через CI.",
|
||||
"zh": "哎呀!不要在 feature 分支的提交中包含任务 ID (GRM-N)。\n 任务 ID 将在通过 CI 合并时自动添加。"
|
||||
},
|
||||
"Oops! Master branch commit must follow conventional format after task ID.\n Expected: GRM-N: <type>: <description>\n Got: {subject}": {
|
||||
"en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: GRM-N: <type>: <description>\n Got: {subject}",
|
||||
"bg": "Опа! Commit-ът в клона master трябва да следва конвенционален формат след идентификатора.\n Очаква се: GRM-N: <type>: <description>\n Получено: {subject}",
|
||||
"de": "Ups! Master-Branch-Commit muss nach der Task-ID dem konventionellen Format folgen.\n Erwartet: GRM-N: <type>: <description>\n Erhalten: {subject}",
|
||||
"ru": "Ой! Коммит в ветку master после ID задачи должен соответствовать conventional формату.\n Ожидается: GRM-N: <type>: <description>\n Получено: {subject}",
|
||||
"zh": "哎呀!master 分支提交在任务 ID 后必须遵循 conventional commit 格式。\n 预期格式: GRM-N: <type>: <description>\n 实际: {subject}"
|
||||
},
|
||||
"Oops! Master branch commits must start with a task ID.\n Expected: GRM-N: <conventional commit message>\n Got: {subject}": {
|
||||
"en": "Oops! Master branch commits must start with a task ID.\n Expected: GRM-N: <conventional commit message>\n Got: {subject}",
|
||||
"bg": "Опа! Commit-ите в клона master трябва да започват с идентификатор на задача.\n Очаква се: GRM-N: <conventional commit message>\n Получено: {subject}",
|
||||
"de": "Ups! Master-Branch-Commits müssen mit einer Task-ID beginnen.\n Erwartet: GRM-N: <conventional commit message>\n Erhalten: {subject}",
|
||||
"ru": "Ой! Коммиты в ветку master должны начинаться с ID задачи.\n Ожидается: GRM-N: <conventional commit message>\n Получено: {subject}",
|
||||
"zh": "哎呀!master 分支的提交必须以任务 ID 开头。\n 预期格式: GRM-N: <conventional commit message>\n 实际: {subject}"
|
||||
},
|
||||
"Oops! No task ID found in .taskid file or branch name '{branch}'.": {
|
||||
"en": "Oops! No task ID found in .taskid file or branch name '{branch}'."
|
||||
},
|
||||
"Oops! PR title must follow format 'GRM-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": {
|
||||
"en": "Oops! PR title must follow format 'GRM-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}"
|
||||
},
|
||||
"Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}": {
|
||||
"en": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}"
|
||||
},
|
||||
"Oops! Package build failed:\n{stderr}": {
|
||||
"en": "Oops! Package build failed:\n{stderr}",
|
||||
"bg": "Опа! Сборката на пакета неуспешна:\n{stderr}",
|
||||
"de": "Ups! Paket-Build fehlgeschlagen:\n{stderr}",
|
||||
"ru": "Ой! Сборка пакета не удалась:\n{stderr}",
|
||||
"zh": "哎呀!包构建失败:\n{stderr}"
|
||||
},
|
||||
"Oops! PyPI publish failed:\n{stderr}": {
|
||||
"en": "Oops! PyPI publish failed:\n{stderr}",
|
||||
"bg": "Опа! Публикуването в PyPI неуспешно:\n{stderr}",
|
||||
"de": "Ups! PyPI-Veröffentlichung fehlgeschlagen:\n{stderr}",
|
||||
"ru": "Ой! Публикация в PyPI не удалась:\n{stderr}",
|
||||
"zh": "哎呀!PyPI 发布失败:\n{stderr}"
|
||||
},
|
||||
"PASSED: {pair}": {
|
||||
"en": "PASSED: {pair}"
|
||||
},
|
||||
"PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}": {
|
||||
"en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}"
|
||||
},
|
||||
"PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release.": {
|
||||
"en": "PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release.",
|
||||
"bg": "PYPI_TOKEN не е зададен — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.",
|
||||
"de": "PYPI_TOKEN nicht gesetzt — PyPI-Veröffentlichung wird übersprungen. Keine Sorge, wir erstellen einfach das Gitea-Release.",
|
||||
"ru": "PYPI_TOKEN не задан — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.",
|
||||
"zh": "未设置 PYPI_TOKEN — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。"
|
||||
},
|
||||
"Published to PyPI.": {
|
||||
"en": "Published to PyPI.",
|
||||
"bg": "Публикувано в PyPI.",
|
||||
"de": "In PyPI veröffentlicht.",
|
||||
"ru": "Опубликовано в PyPI.",
|
||||
"zh": "已发布到 PyPI。"
|
||||
},
|
||||
"Pushed release commit to master.": {
|
||||
"en": "Pushed release commit to master."
|
||||
},
|
||||
"Rebased and pushed. Retrying merge...": {
|
||||
"en": "Rebased and pushed. Retrying merge..."
|
||||
},
|
||||
"Release creation failed: {error}": {
|
||||
"en": "Release creation failed: {error}"
|
||||
},
|
||||
"Release must be run on master, currently on '{branch}'.": {
|
||||
"en": "Release must be run on master, currently on '{branch}'."
|
||||
},
|
||||
"Repository configuration complete.": {
|
||||
"en": "Repository configuration complete.",
|
||||
"bg": "Конфигурирането на хранилището е завършено.",
|
||||
"de": "Repository-Konfiguration abgeschlossen.",
|
||||
"ru": "Конфигурация репозитория завершена.",
|
||||
"zh": "仓库配置完成。"
|
||||
},
|
||||
"Runner index {index} out of range (0..{max})": {
|
||||
"en": "Runner index {index} out of range (0..{max})",
|
||||
"bg": "Индексът на runner {index} е извън диапазона (0..{max})",
|
||||
"de": "Runner-Index {index} außerhalb des Bereichs (0..{max})",
|
||||
"ru": "Индекс runner {index} вне диапазона (0..{max})",
|
||||
"zh": "Runner 索引 {index} 超出范围 (0..{max})"
|
||||
},
|
||||
"Running lint checks...": {
|
||||
"en": "Running lint checks..."
|
||||
},
|
||||
"Running tests...": {
|
||||
"en": "Running tests..."
|
||||
},
|
||||
"Running: {scenario} on {platform}": {
|
||||
"en": "Running: {scenario} on {platform}"
|
||||
},
|
||||
"Skipping commit push — no staged changes.": {
|
||||
"en": "Skipping commit push — no staged changes."
|
||||
},
|
||||
"Syncing {count} documentation pages to wiki...": {
|
||||
"en": "Syncing {count} documentation pages to wiki..."
|
||||
},
|
||||
"Tag v{version} already existed. Publish workflow should already have been triggered.": {
|
||||
"en": "Tag v{version} already existed. Publish workflow should already have been triggered."
|
||||
},
|
||||
"Tag {tag} already exists, skipping creation.": {
|
||||
"en": "Tag {tag} already exists, skipping creation."
|
||||
},
|
||||
"Task ID: {task_id}": {
|
||||
"en": "Task ID: {task_id}"
|
||||
},
|
||||
"Tests failed — refusing to release. Fix test failures first.\n{stderr}": {
|
||||
"en": "Tests failed — refusing to release. Fix test failures first.\n{stderr}"
|
||||
},
|
||||
"Tests passed.": {
|
||||
"en": "Tests passed."
|
||||
},
|
||||
"Unit tests passed in {duration:.2f}s (under {max}s limit).": {
|
||||
"en": "Unit tests passed in {duration:.2f}s (under {max}s limit)."
|
||||
},
|
||||
"Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.": {
|
||||
"en": "Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures."
|
||||
},
|
||||
"Updated version in {init}": {
|
||||
"en": "Updated version in {init}"
|
||||
},
|
||||
"Updated {changelog_file}": {
|
||||
"en": "Updated {changelog_file}"
|
||||
},
|
||||
"WARNING: --skip-tests passed — skipping test verification.": {
|
||||
"en": "WARNING: --skip-tests passed — skipping test verification."
|
||||
},
|
||||
"WARNING: File {file} is empty — skipping.": {
|
||||
"en": "WARNING: File {file} is empty — skipping."
|
||||
},
|
||||
"WARNING: File {file} not found — skipping.": {
|
||||
"en": "WARNING: File {file} not found — skipping."
|
||||
},
|
||||
"Warning: No task ID (GRM-N) found in commit message: {msg}. Skipping Vikunja update.": {
|
||||
"en": "Warning: No task ID (GRM-N) found in commit message: {msg}. Skipping Vikunja update."
|
||||
},
|
||||
"Warning: VIKUNJA_TOKEN not set, skipping title match validation.": {
|
||||
"en": "Warning: VIKUNJA_TOKEN not set, skipping title match validation."
|
||||
},
|
||||
"Warning: Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded — please update the Vikunja task manually.": {
|
||||
"en": "Warning: Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded — please update the Vikunja task manually."
|
||||
},
|
||||
"Warning: git-cliff generated empty changelog.": {
|
||||
"en": "Warning: git-cliff generated empty changelog."
|
||||
},
|
||||
"Wiki integrity check failed — {count} issue(s)": {
|
||||
"en": "Wiki integrity check failed — {count} issue(s)"
|
||||
},
|
||||
"Wiki verification failed — {failures} page(s) empty or mismatched": {
|
||||
"en": "Wiki verification failed — {failures} page(s) empty or mismatched"
|
||||
},
|
||||
"[dry-run] Would commit: release: v{version}": {
|
||||
"en": "[dry-run] Would commit: release: v{version}"
|
||||
},
|
||||
"[dry-run] Would create tag: v{version}": {
|
||||
"en": "[dry-run] Would create tag: v{version}"
|
||||
},
|
||||
"[dry-run] Would create tag: {tag}": {
|
||||
"en": "[dry-run] Would create tag: {tag}"
|
||||
},
|
||||
"[dry-run] Would push commit to master": {
|
||||
"en": "[dry-run] Would push commit to master"
|
||||
},
|
||||
"[dry-run] Would sync page: {title} ({chars} chars)": {
|
||||
"en": "[dry-run] Would sync page: {title} ({chars} chars)"
|
||||
},
|
||||
"[dry-run] Would update {changelog_file}": {
|
||||
"en": "[dry-run] Would update {changelog_file}"
|
||||
},
|
||||
"[dry-run] Would update {init}": {
|
||||
"en": "[dry-run] Would update {init}"
|
||||
},
|
||||
"git command failed ({cmd}): {stderr}": {
|
||||
"en": "git command failed ({cmd}): {stderr}"
|
||||
},
|
||||
"git-cliff returned empty version.": {
|
||||
"en": "git-cliff returned empty version."
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user