GRM-61: refactor: separate GRM and CI translations with validation

This commit is contained in:
2026-06-22 12:13:58 +00:00
parent 1e04d38d59
commit c9c46e88ac
27 changed files with 1618 additions and 711 deletions
+22 -12
View File
@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up environment
run: make setup
run: make setup-quality
- name: Lint all
run: |
. .venv/bin/activate
@@ -23,15 +23,23 @@ jobs:
. .venv/bin/activate
make pytest-cov
- name: Check unit test speed
env:
PYTHONPATH: .:src
run: |
. .venv/bin/activate
python3 scripts/check_test_speed.py --max-seconds 10
- name: Documentation coverage check
env:
PYTHONPATH: src
PYTHONPATH: .:src
run: |
. .venv/bin/activate
python3 scripts/ci/doc_coverage.py --fail-on-missing
- name: Translation completeness check
env:
PYTHONPATH: .:src
run: |
. .venv/bin/activate
python3 scripts/ci/check_translations.py
- name: Dependency security scan
run: |
. .venv/bin/activate
@@ -60,10 +68,10 @@ jobs:
with:
fetch-depth: 0
- name: Set up environment
run: make setup
run: make setup-release
- name: Release dry-run validation
env:
PYTHONPATH: .
PYTHONPATH: .:src
run: |
. .venv/bin/activate
export PATH="$HOME/.local/bin:$PATH"
@@ -80,11 +88,11 @@ jobs:
with:
fetch-depth: 0
- name: Set up environment
run: make setup
run: make setup-ci
- name: Detect changed paths
id: detect
env:
PYTHONPATH: src
PYTHONPATH: .:src
run: |
. .venv/bin/activate
python3 scripts/ci/classify_changes.py \
@@ -103,13 +111,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up environment
run: make setup
run: make setup-ci
- name: Discover available runners
id: discover
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
MOLECULE_RUNNERS: ${{ vars.MOLECULE_RUNNERS }}
PYTHONPATH: src
PYTHONPATH: .:src
run: |
. .venv/bin/activate
python3 scripts/ci/discover_runners.py \
@@ -128,11 +136,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up environment
run: make setup
run: make setup-molecule
- name: Discover assigned test pairs
env:
RUNNER_INDEX: ${{ matrix.runner-index }}
MAX_RUNNERS: ${{ needs.discover-runners.outputs.runner-count }}
PYTHONPATH: .:src
run: |
. .venv/bin/activate
python3 scripts/ci/distribute_molecule.py \
@@ -153,6 +162,7 @@ jobs:
JOB_NAME: ${{ github.job }}
MATRIX_INDEX: ${{ matrix.runner-index }}
GITEA_REPOSITORY: ${{ github.repository }}
PYTHONPATH: .:src
pr-review:
if: github.event_name == 'pull_request'
@@ -161,11 +171,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up environment
run: make setup
run: make setup-ci
- name: Run automated PR review
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
PYTHONPATH: src
PYTHONPATH: .:src
run: |
set -euo pipefail
. .venv/bin/activate
@@ -192,7 +202,7 @@ jobs:
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
PYTHONPATH: src
PYTHONPATH: .:src
HEAD_REF: ${{ github.head_ref }}
PR_TITLE: ${{ github.event.pull_request.title }}
REPOSITORY: ${{ github.repository }}
+8 -6
View File
@@ -37,6 +37,8 @@ jobs:
fetch-depth: 1
- name: Check if this is a release commit
id: check
env:
PYTHONPATH: .:src
run: python3 scripts/ci/detect_release_commit.py
validate-commit-msg:
@@ -69,14 +71,14 @@ jobs:
fetch-depth: 0
token: ${{ secrets.REPO_TOKEN }}
- name: Set up environment
run: make setup
run: make setup-release
- name: Configure git
run: |
git config user.name "grm-ci-bot"
git config user.email "grm-ci-bot@oblachno.fyi"
- name: Run release
env:
PYTHONPATH: .
PYTHONPATH: .:src
run: |
. .venv/bin/activate
export PATH="$HOME/.local/bin:$PATH"
@@ -104,11 +106,11 @@ jobs:
with:
fetch-depth: 0
- name: Set up environment
run: make setup
run: make setup-ci
- name: Sync documentation to wiki
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
PYTHONPATH: src
PYTHONPATH: .:src
run: |
. .venv/bin/activate
python3 scripts/ci/sync_wiki.py --repo "${{ github.repository }}" --strict
@@ -141,7 +143,7 @@ jobs:
git fetch origin master
git reset --hard origin/master
- name: Set up environment
run: make setup
run: make setup-ci
- name: Generate and push badges
env:
PRE_COMMIT_ALLOW_NO_CONFIG: "1"
@@ -175,7 +177,7 @@ jobs:
- name: Update Vikunja task
env:
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
PYTHONPATH: src
PYTHONPATH: .:src
run: python3 scripts/ci/post_merge.py --git-sha "${{ github.sha }}"
- name: Notify on failure
if: failure()
+2 -2
View File
@@ -3,7 +3,7 @@ repos:
hooks:
- id: validate-commit-msg
name: validate commit message
entry: .venv/bin/python scripts/ci/validate_commit_msg.py
entry: env PYTHONPATH=. .venv/bin/python scripts/ci/validate_commit_msg.py
language: system
stages: [commit-msg]
pass_filenames: true
@@ -67,7 +67,7 @@ repos:
- id: commit-msg
name: validate commit message
entry: .venv/bin/python scripts/ci/validate_commit_msg.py
entry: env PYTHONPATH=. .venv/bin/python scripts/ci/validate_commit_msg.py
language: system
stages: [commit-msg]
pass_filenames: true
+1 -1
View File
@@ -1 +1 @@
GRM-58
GRM-61
+23 -1
View File
@@ -1,4 +1,4 @@
.PHONY: all setup install update lint ansible-lint makefile-lint lint-all test test-unit pytest-cov molecule molecule-all test-all clean workflow-lint workflow-dryrun workflow-check install-tools
.PHONY: all setup setup-ci setup-quality setup-molecule setup-release install update lint ansible-lint makefile-lint lint-all test test-unit pytest-cov molecule molecule-all test-all clean workflow-lint workflow-dryrun workflow-check install-tools
PYTHON := python3
VENV := .venv
@@ -7,10 +7,32 @@ CHECKMAKE := $(shell command -v checkmake 2>/dev/null || echo $(HOME)/go/bin/che
all: setup
# Full setup for local development (all deps, tools, collections, hooks)
setup: $(VENV)/bin/activate .env activate-scripts checkmake install-tools
@export PATH="$(HOME)/.local/bin:$$PATH"; \
$(PYTHON) scripts/setup.py --bin "$(BIN)"
# Lean setup for CI jobs that only need pytest + runtime deps
# (detect-changes, discover-runners, pr-review, sync-wiki, badges)
setup-ci: $(VENV)/bin/activate .env
@$(PYTHON) scripts/setup.py --bin "$(BIN)" --extras ci --no-ansible-collections --no-pre-commit --no-tea-login
# Setup for the quality job (lint + test deps, actionlint tool)
setup-quality: $(VENV)/bin/activate .env install-tools
@export PATH="$(HOME)/.local/bin:$$PATH"; \
$(PYTHON) scripts/setup.py --bin "$(BIN)" --extras "ci,lint" --no-ansible-collections --no-pre-commit --no-tea-login
# Full setup for molecule testing (needs ansible, molecule, collections)
setup-molecule: $(VENV)/bin/activate .env install-tools
@export PATH="$(HOME)/.local/bin:$$PATH"; \
$(PYTHON) scripts/setup.py --bin "$(BIN)" --extras "ci,molecule" --no-pre-commit --no-tea-login
# Setup for release jobs (needs git-cliff and tea tools)
setup-release: $(VENV)/bin/activate .env
@$(PYTHON) scripts/install_tools.py --tool git-cliff --tool tea
@export PATH="$(HOME)/.local/bin:$$PATH"; \
$(PYTHON) scripts/setup.py --bin "$(BIN)" --extras ci --no-ansible-collections --no-pre-commit
.env:
@if [ ! -f .env ]; then \
cp .env.example .env; \
+16 -4
View File
@@ -27,17 +27,29 @@ grm = "gitea_runner_manager.cli:cli"
version = {attr = "gitea_runner_manager.__version__"}
[project.optional-dependencies]
dev = [
# Minimal deps for CI scripts that only need click/dotenv/requests
# (detect-changes, discover-runners, pr-review, sync-wiki, badges, etc.)
ci = [
"pytest>=9.1.0",
"pytest-cov>=7.1.0",
]
# Lint and type-checking tools (quality job)
lint = [
"ruff>=0.15.17",
"pyright>=1.1.410",
"molecule>=26.4.0",
"molecule-docker>=2.1.0",
"ansible-lint>=26.4.0",
"bandit>=1.8.2",
"pip-audit>=2.10",
"pre-commit>=4.6.0",
"ansible-lint>=26.4.0",
]
# Molecule testing (molecule-tests job)
molecule = [
"molecule>=26.4.0",
"molecule-docker>=2.1.0",
]
# Full dev environment (local development, includes everything)
dev = [
"gitea-runner-manager[ci,lint,molecule]",
# Non-Python dev dependency: checkmake (Makefile linter)
# Install via: go install github.com/checkmake/checkmake/cmd/checkmake@latest
]
+1 -1
View File
@@ -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"]
+1 -1
View File
@@ -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+.+")
+198
View File
@@ -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
+1 -1
View File
@@ -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).
+1 -1
View File
@@ -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")
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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+:")
+1 -1
View File
@@ -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)
+31
View File
@@ -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
View File
@@ -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.")
+503
View File
@@ -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."
}
}
+374 -658
View File
@@ -1,38 +1,17 @@
{
"Installing Gitea Runner on {host}": {
"en": "Installing Gitea Runner on {host}",
"bg": "Инсталиране на Gitea Runner на {host}",
"de": "Gitea Runner wird auf {host} installiert",
"ru": "Установка Gitea Runner на {host}",
"zh": "正在 {host} 上安装 Gitea Runner"
"=== Operation Report ===": {
"en": "=== Operation Report ===",
"bg": "=== Отчет за операцията ===",
"de": "=== Operationsbericht ===",
"ru": "=== Отчет об операции ===",
"zh": "=== 操作报告 ==="
},
"Updating Gitea Runner on {host}": {
"en": "Updating Gitea Runner on {host}",
"bg": "Актуализиране на Gitea Runner на {host}",
"de": "Gitea Runner wird auf {host} aktualisiert",
"ru": "Обновление Gitea Runner на {host}",
"zh": "在 {host} 上更新 Gitea Runner"
},
"Running Ansible playbook": {
"en": "Running Ansible playbook",
"bg": "Изпълнение на Ansible playbook",
"de": "Ansible-Playbook wird ausgeführt",
"ru": "Выполнение Ansible playbook",
"zh": "正在运行 Ansible playbook"
},
"Full log: {log_file}": {
"en": "Full log: {log_file}",
"bg": "Пълен лог: {log_file}",
"de": "Vollständiges Log: {log_file}",
"ru": "Полный лог: {log_file}",
"zh": "完整日志: {log_file}"
},
"Done. See full log: {log_file}": {
"en": "Done. See full log: {log_file}",
"bg": "Готово. Вижте пълния лог: {log_file}",
"de": "Fertig. Siehe vollständiges Log: {log_file}",
"ru": "Готово. См. полный лог: {log_file}",
"zh": "完成。查看完整日志: {log_file}"
"Ad-hoc command failed on {host}: {stderr}": {
"en": "Ad-hoc command failed on {host}: {stderr}",
"bg": "Ad-hoc командата неуспешна на {host}: {stderr}",
"de": "Ad-hoc-Befehl auf {host} fehlgeschlagen: {stderr}",
"ru": "Ad-hoc команда не удалась на {host}: {stderr}",
"zh": "Ad-hoc 命令在 {host} 上失败: {stderr}"
},
"Ansible failed with exit code {code}. See full log: {log_file}": {
"en": "Ansible failed with exit code {code}. See full log: {log_file}",
@@ -41,166 +20,12 @@
"ru": "Ansible завершился с кодом {code}. См. лог: {log_file}",
"zh": "Ansible 失败,退出码 {code}。查看日志: {log_file}"
},
"GITEA_REGISTRATION_TOKEN must be set (or pass --token)": {
"en": "GITEA_REGISTRATION_TOKEN must be set (or pass --token)",
"bg": "GITEA_REGISTRATION_TOKEN трябва да е зададен (или подайте --token)",
"de": "GITEA_REGISTRATION_TOKEN muss gesetzt sein (oder --token übergeben)",
"ru": "GITEA_REGISTRATION_TOKEN должен быть задан (или передайте --token)",
"zh": "必须设置 GITEA_REGISTRATION_TOKEN(或传递 --token"
},
"Playbook not found: {playbook}": {
"en": "Playbook not found: {playbook}",
"bg": "Playbook не е намерен: {playbook}",
"de": "Playbook nicht gefunden: {playbook}",
"ru": "Playbook не найден: {playbook}",
"zh": "未找到 Playbook: {playbook}"
},
"GITEA_URL must be set (or pass --url)": {
"en": "GITEA_URL must be set (or pass --url)",
"bg": "GITEA_URL трябва да е зададен (или подайте --url)",
"de": "GITEA_URL muss gesetzt sein (oder --url übergeben)",
"ru": "GITEA_URL должен быть задан (или передайте --url)",
"zh": "必须设置 GITEA_URL(或传递 --url"
},
"Installation failed: {error}": {
"en": "Installation failed: {error}",
"bg": "Инсталацията неуспешна: {error}",
"de": "Installation fehlgeschlagen: {error}",
"ru": "Установка не удалась: {error}",
"zh": "安装失败: {error}"
},
"Update failed: {error}": {
"en": "Update failed: {error}",
"bg": "Актуализацията неуспешна: {error}",
"de": "Update fehlgeschlagen: {error}",
"ru": "Обновление не удалось: {error}",
"zh": "更新失败: {error}"
},
"SSH user": {
"en": "SSH user",
"bg": "SSH потребител",
"de": "SSH-Benutzer",
"ru": "SSH пользователь",
"zh": "SSH 用户"
},
"Path to SSH private key": {
"en": "Path to SSH private key",
"bg": "Път към SSH частен ключ",
"de": "Pfad zum SSH-Private-Key",
"ru": "Путь к SSH приватному ключу",
"zh": "SSH 私钥路径"
},
"Gitea Runner name (default: host)": {
"en": "Gitea Runner name (default: host)",
"bg": "Име на Gitea Runner (по подразбиране: host)",
"de": "Gitea Runner-Name (Standard: host)",
"ru": "Имя Gitea Runner (по умолчанию: host)",
"zh": "Gitea Runner 名称(默认: host"
},
"Registration token (env: GITEA_REGISTRATION_TOKEN)": {
"en": "Registration token (env: GITEA_REGISTRATION_TOKEN)",
"bg": "Регистрационен токен (env: GITEA_REGISTRATION_TOKEN)",
"de": "Registrierungstoken (env: GITEA_REGISTRATION_TOKEN)",
"ru": "Токен регистрации (env: GITEA_REGISTRATION_TOKEN)",
"zh": "注册令牌(环境变量: GITEA_REGISTRATION_TOKEN"
},
"Gitea URL (env: GITEA_URL)": {
"en": "Gitea URL (env: GITEA_URL)",
"bg": "Gitea URL (env: GITEA_URL)",
"de": "Gitea-URL (env: GITEA_URL)",
"ru": "URL Gitea (env: GITEA_URL)",
"zh": "Gitea URL(环境变量: GITEA_URL"
},
"Gitea Runner deployment mode (default: docker)": {
"en": "Gitea Runner deployment mode (default: docker)",
"bg": "Режим на разполагане на Gitea Runner (по подразбиране: docker)",
"de": "Gitea Runner-Bereitstellungsmodus (Standard: docker)",
"ru": "Режим развёртывания Gitea Runner (по умолчанию: docker)",
"zh": "Gitea Runner 部署模式(默认: docker"
},
"Gitea admin API token for integration test (env: REPO_TOKEN)": {
"en": "Gitea admin API token for integration test (env: REPO_TOKEN)",
"bg": "Gitea admin API токен за интеграционен тест (env: REPO_TOKEN)",
"de": "Gitea-Admin-API-Token für Integrationstest (env: REPO_TOKEN)",
"ru": "Токен админ API Gitea для интеграционного теста (env: REPO_TOKEN)",
"zh": "Gitea 管理员 API 令牌,用于集成测试(环境变量: REPO_TOKEN"
},
"Integration test API retries (default: 3, env: GITEA_INTEGRATION_RETRIES)": {
"en": "Integration test API retries (default: 3, env: GITEA_INTEGRATION_RETRIES)",
"bg": "Повторни опити за интеграционен тест API (по подразбиране: 3, env: GITEA_INTEGRATION_RETRIES)",
"de": "API-Wiederholungen für Integrationstest (Standard: 3, env: GITEA_INTEGRATION_RETRIES)",
"ru": "Повторы API интеграционного теста (по умолчанию: 3, env: GITEA_INTEGRATION_RETRIES)",
"zh": "集成测试 API 重试次数(默认: 3,环境变量: GITEA_INTEGRATION_RETRIES"
},
"Runner labels for Gitea Actions (env: GITEA_RUNNER_LABELS). Example: docker:docker://alpine:latest": {
"en": "Runner labels for Gitea Actions (env: GITEA_RUNNER_LABELS). Example: docker:docker://alpine:latest",
"bg": "Етикети на runner за Gitea Actions (env: GITEA_RUNNER_LABELS). Пример: docker:docker://alpine:latest",
"de": "Runner-Labels für Gitea Actions (env: GITEA_RUNNER_LABELS). Beispiel: docker:docker://alpine:latest",
"ru": "Метки runner для Gitea Actions (env: GITEA_RUNNER_LABELS). Пример: docker:docker://alpine:latest",
"zh": "Gitea Actions 的 runner 标签(环境变量: GITEA_RUNNER_LABELS)。示例: docker:docker://alpine:latest"
},
"Prompt for sudo password (default)": {
"en": "Prompt for sudo password (default)",
"bg": "Подканване за sudo парола (по подразбиране)",
"de": "Nach sudo-Passwort fragen (Standard)",
"ru": "Запросить пароль sudo (по умолчанию)",
"zh": "提示输入 sudo 密码(默认)"
},
"Do not prompt for sudo password": {
"en": "Do not prompt for sudo password",
"bg": "Без подканване за sudo парола",
"de": "Nicht nach sudo-Passwort fragen",
"ru": "Не запрашивать пароль sudo",
"zh": "不提示输入 sudo 密码"
},
"Specific Gitea Runner version": {
"en": "Specific Gitea Runner version",
"bg": "Конкретна версия на Gitea Runner",
"de": "Spezifische Gitea Runner-Version",
"ru": "Конкретная версия Gitea Runner",
"zh": "指定 Gitea Runner 版本"
},
"Install and configure a Gitea Runner on a remote host.": {
"en": "Install and configure a Gitea Runner on a remote host.",
"bg": "Инсталиране и конфигуриране на Gitea Runner на отдалечен хост.",
"de": "Gitea Runner auf einem Remote-Host installieren und konfigurieren.",
"ru": "Установить и настроить Gitea Runner на удалённом хосте.",
"zh": "在远程主机上安装并配置 Gitea Runner。"
},
"Update the Gitea Runner binary on a remote host.": {
"en": "Update the Gitea Runner binary on a remote host.",
"bg": "Актуализиране на Gitea Runner двоичния файл на отдалечен хост.",
"de": "Gitea Runner-Binary auf einem Remote-Host aktualisieren.",
"ru": "Обновить бинарный файл Gitea Runner на удалённом хосте.",
"zh": "在远程主机上更新 Gitea Runner 二进制文件。"
},
"Starting Gitea Runner {name} on {host}": {
"en": "Starting Gitea Runner {name} on {host}",
"bg": "Стартиране на Gitea Runner {name} на {host}",
"de": "Starte Gitea Runner {name} auf {host}",
"ru": "Запуск Gitea Runner {name} на {host}",
"zh": "正在 {host} 上启动 Gitea Runner {name}"
},
"Stopping Gitea Runner {name} on {host}": {
"en": "Stopping Gitea Runner {name} on {host}",
"bg": "Спиране на Gitea Runner {name} на {host}",
"de": "Stoppe Gitea Runner {name} auf {host}",
"ru": "Остановка Gitea Runner {name} на {host}",
"zh": "正在 {host} 上停止 Gitea Runner {name}"
},
"Enabling Gitea Runner {name} on {host}": {
"en": "Enabling Gitea Runner {name} on {host}",
"bg": "Активиране на Gitea Runner {name} на {host}",
"de": "Aktiviere Gitea Runner {name} auf {host}",
"ru": "Включение Gitea Runner {name} на {host}",
"zh": "正在 {host} 上启用 Gitea Runner {name}"
},
"Disabling Gitea Runner {name} on {host}": {
"en": "Disabling Gitea Runner {name} on {host}",
"bg": "Деактивиране на Gitea Runner {name} на {host}",
"de": "Deaktiviere Gitea Runner {name} auf {host}",
"ru": "Отключение Gitea Runner {name} на {host}",
"zh": "正在 {host} 上禁用 Gitea Runner {name}"
"Check the status of a registered Gitea Runner.": {
"en": "Check the status of a registered Gitea Runner.",
"bg": "Проверка на състоянието на регистриран Gitea Runner.",
"de": "Status eines registrierten Gitea Runners prüfen.",
"ru": "Проверить состояние зарегистрированного Gitea Runner.",
"zh": "检查已注册的 Gitea Runner 状态。"
},
"Checking status of Gitea Runner {name} on {host}": {
"en": "Checking status of Gitea Runner {name} on {host}",
@@ -216,62 +41,6 @@
"ru": "Проверка состояния Gitea Runner {name} на {host} как {user} (требуется sudo)",
"zh": "正在检查 {host} 上 Gitea Runner {name} 的状态(用户 {user},需要 sudo"
},
"Removing Gitea Runner {name} from {host}": {
"en": "Removing Gitea Runner {name} from {host}",
"bg": "Премахване на Gitea Runner {name} от {host}",
"de": "Entferne Gitea Runner {name} von {host}",
"ru": "Удаление Gitea Runner {name} с {host}",
"zh": "正在从 {host} 移除 Gitea Runner {name}"
},
"Start failed: {error}": {
"en": "Start failed: {error}",
"bg": "Стартирането неуспешно: {error}",
"de": "Start fehlgeschlagen: {error}",
"ru": "Запуск не удался: {error}",
"zh": "启动失败: {error}"
},
"Stop failed: {error}": {
"en": "Stop failed: {error}",
"bg": "Спирането неуспешно: {error}",
"de": "Stop fehlgeschlagen: {error}",
"ru": "Остановка не удалась: {error}",
"zh": "停止失败: {error}"
},
"Enable failed: {error}": {
"en": "Enable failed: {error}",
"bg": "Активирането неуспешно: {error}",
"de": "Aktivierung fehlgeschlagen: {error}",
"ru": "Включение не удалось: {error}",
"zh": "启用失败: {error}"
},
"Disable failed: {error}": {
"en": "Disable failed: {error}",
"bg": "Деактивирането неуспешно: {error}",
"de": "Deaktivierung fehlgeschlagen: {error}",
"ru": "Отключение не удалось: {error}",
"zh": "禁用失败: {error}"
},
"Status check failed: {error}": {
"en": "Status check failed: {error}",
"bg": "Проверката на състоянието неуспешна: {error}",
"de": "Statusprüfung fehlgeschlagen: {error}",
"ru": "Проверка состояния не удалась: {error}",
"zh": "状态检查失败: {error}"
},
"Remove failed: {error}": {
"en": "Remove failed: {error}",
"bg": "Премахването неуспешно: {error}",
"de": "Entfernung fehlgeschlagen: {error}",
"ru": "Удаление не удалось: {error}",
"zh": "移除失败: {error}"
},
"Gitea Runner Manager — manage Gitea Actions runners.": {
"en": "Gitea Runner Manager — manage Gitea Actions runners.",
"bg": "Gitea Runner Manager — управление на Gitea Actions runners.",
"de": "Gitea Runner Manager — Gitea Actions Runner verwalten.",
"ru": "Gitea Runner Manager — управление Gitea Actions runners.",
"zh": "Gitea Runner Manager — 管理 Gitea Actions runners。"
},
"Command: {cmd}": {
"en": "Command: {cmd}",
"bg": "Команда: {cmd}",
@@ -279,27 +48,6 @@
"ru": "Команда: {cmd}",
"zh": "命令: {cmd}"
},
"Start a registered Gitea Runner.": {
"en": "Start a registered Gitea Runner.",
"bg": "Стартиране на регистриран Gitea Runner.",
"de": "Einen registrierten Gitea Runner starten.",
"ru": "Запустить зарегистрированный Gitea Runner.",
"zh": "启动已注册的 Gitea Runner。"
},
"Stop a registered Gitea Runner.": {
"en": "Stop a registered Gitea Runner.",
"bg": "Спиране на регистриран Gitea Runner.",
"de": "Einen registrierten Gitea Runner stoppen.",
"ru": "Остановить зарегистрированный Gitea Runner.",
"zh": "停止已注册的 Gitea Runner。"
},
"Enable a registered Gitea Runner to start on boot.": {
"en": "Enable a registered Gitea Runner to start on boot.",
"bg": "Разрешаване на регистриран Gitea Runner да стартира при зареждане.",
"de": "Einen registrierten Gitea Runner für den Autostart aktivieren.",
"ru": "Включить автозапуск зарегистрированного Gitea Runner.",
"zh": "启用已注册的 Gitea Runner 开机自启。"
},
"Disable a registered Gitea Runner and deregister it.": {
"en": "Disable a registered Gitea Runner and deregister it.",
"bg": "Деактивиране на регистриран Gitea Runner и дерегистриране.",
@@ -307,54 +55,138 @@
"ru": "Отключить и дерегистрировать зарегистрированный Gitea Runner.",
"zh": "禁用并注销已注册的 Gitea Runner。"
},
"Check the status of a registered Gitea Runner.": {
"en": "Check the status of a registered Gitea Runner.",
"bg": "Проверка на състоянието на регистриран Gitea Runner.",
"de": "Status eines registrierten Gitea Runners prüfen.",
"ru": "Проверить состояние зарегистрированного Gitea Runner.",
"zh": "检查已注册的 Gitea Runner 状态。"
"Disable failed: {error}": {
"en": "Disable failed: {error}",
"bg": "Деактивирането неуспешно: {error}",
"de": "Deaktivierung fehlgeschlagen: {error}",
"ru": "Отключение не удалось: {error}",
"zh": "禁用失败: {error}"
},
"Remove a registered Gitea Runner completely.": {
"en": "Remove a registered Gitea Runner completely.",
"bg": "Пълно премахване на регистриран Gitea Runner.",
"de": "Einen registrierten Gitea Runner vollständig entfernen.",
"ru": "Полностью удалить зарегистрированный Gitea Runner.",
"zh": "完全移除已注册的 Gitea Runner"
"Disabling Gitea Runner {name} on {host}": {
"en": "Disabling Gitea Runner {name} on {host}",
"bg": "Деактивиране на Gitea Runner {name} на {host}",
"de": "Deaktiviere Gitea Runner {name} auf {host}",
"ru": "Отключение Gitea Runner {name} на {host}",
"zh": "正在 {host} 上禁用 Gitea Runner {name}"
},
"Override host from registry": {
"en": "Override host from registry",
"bg": "Замяна на хоста от регистъра",
"de": "Host aus Registrierung überschreiben",
"ru": "Переопределить хост из реестра",
"zh": "覆盖注册表中的主机"
"Done. See full log: {log_file}": {
"en": "Done. See full log: {log_file}",
"bg": "Готово. Вижте пълния лог: {log_file}",
"de": "Fertig. Siehe vollständiges Log: {log_file}",
"ru": "Готово. См. полный лог: {log_file}",
"zh": "完成。查看完整日志: {log_file}"
},
"Override user from registry": {
"en": "Override user from registry",
"bg": "Замяна на потребителя от регистъра",
"de": "Benutzer aus Registrierung überschreiben",
"ru": "Переопределить пользователя из реестра",
"zh": "覆盖注册表中的用户"
"Enable a registered Gitea Runner to start on boot.": {
"en": "Enable a registered Gitea Runner to start on boot.",
"bg": "Разрешаване на регистриран Gitea Runner да стартира при зареждане.",
"de": "Einen registrierten Gitea Runner für den Autostart aktivieren.",
"ru": "Включить автозапуск зарегистрированного Gitea Runner.",
"zh": "启用已注册的 Gitea Runner 开机自启。"
},
"Override SSH key from registry": {
"en": "Override SSH key from registry",
"bg": "Замяна на SSH ключа от регистъра",
"de": "SSH-Schlüssel aus Registrierung überschreiben",
"ru": "Переопределить SSH ключ из реестра",
"zh": "覆盖注册表中的 SSH 密钥"
"Enable failed: {error}": {
"en": "Enable failed: {error}",
"bg": "Активирането неуспешно: {error}",
"de": "Aktivierung fehlgeschlagen: {error}",
"ru": "Включение не удалось: {error}",
"zh": "启用失败: {error}"
},
"Override deployment mode from registry": {
"en": "Override deployment mode from registry",
"bg": "Замяна на режима на внедряване от регистъра",
"de": "Bereitstellungsmodus aus Registrierung überschreiben",
"ru": "Переопределить режим развертывания из реестра",
"zh": "覆盖注册表中的部署模式"
"Enabling Gitea Runner {name} on {host}": {
"en": "Enabling Gitea Runner {name} on {host}",
"bg": "Активиране на Gitea Runner {name} на {host}",
"de": "Aktiviere Gitea Runner {name} auf {host}",
"ru": "Включение Gitea Runner {name} на {host}",
"zh": "正在 {host} 上启用 Gitea Runner {name}"
},
"Runner '{name}' not found in registry. Use 'grm install' first or provide --host and --user.": {
"en": "Runner '{name}' not found in registry. Use 'grm install' first or provide --host and --user.",
"bg": "Runner '{name}' не е намерен в регистъра. Използвайте 'grm install' първо или подайте --host и --user.",
"de": "Runner '{name}' nicht in Registrierung gefunden. Verwenden Sie zuerst 'grm install' oder geben Sie --host und --user an.",
"ru": "Runner '{name}' не найден в реестре. Сначала используйте 'grm install' или укажите --host и --user.",
"zh": "注册表中未找到 Runner '{name}'。请先使用 'grm install' 或提供 --host 和 --user。"
"Full log: {log_file}": {
"en": "Full log: {log_file}",
"bg": "Пълен лог: {log_file}",
"de": "Vollständiges Log: {log_file}",
"ru": "Полный лог: {log_file}",
"zh": "完整日志: {log_file}"
},
"GITEA_REGISTRATION_TOKEN must be set (or pass --token)": {
"en": "GITEA_REGISTRATION_TOKEN must be set (or pass --token)",
"bg": "GITEA_REGISTRATION_TOKEN трябва да е зададен (или подайте --token)",
"de": "GITEA_REGISTRATION_TOKEN muss gesetzt sein (oder --token übergeben)",
"ru": "GITEA_REGISTRATION_TOKEN должен быть задан (или передайте --token)",
"zh": "必须设置 GITEA_REGISTRATION_TOKEN(或传递 --token"
},
"GITEA_URL must be set (or pass --url)": {
"en": "GITEA_URL must be set (or pass --url)",
"bg": "GITEA_URL трябва да е зададен (или подайте --url)",
"de": "GITEA_URL muss gesetzt sein (oder --url übergeben)",
"ru": "GITEA_URL должен быть задан (или передайте --url)",
"zh": "必须设置 GITEA_URL(或传递 --url"
},
"Gitea Runner Manager — manage Gitea Actions runners.": {
"en": "Gitea Runner Manager — manage Gitea Actions runners.",
"bg": "Gitea Runner Manager — управление на Gitea Actions runners.",
"de": "Gitea Runner Manager — Gitea Actions Runner verwalten.",
"ru": "Gitea Runner Manager — управление Gitea Actions runners.",
"zh": "Gitea Runner Manager — 管理 Gitea Actions runners。"
},
"Gitea Runner name (default: host)": {
"en": "Gitea Runner name (default: host)",
"bg": "Име на Gitea Runner (по подразбиране: host)",
"de": "Gitea Runner-Name (Standard: host)",
"ru": "Имя Gitea Runner (по умолчанию: host)",
"zh": "Gitea Runner 名称(默认: host"
},
"Gitea URL (env: GITEA_URL)": {
"en": "Gitea URL (env: GITEA_URL)",
"bg": "Gitea URL (env: GITEA_URL)",
"de": "Gitea-URL (env: GITEA_URL)",
"ru": "URL Gitea (env: GITEA_URL)",
"zh": "Gitea URL(环境变量: GITEA_URL"
},
"Gitea admin API token for integration test (env: REPO_TOKEN)": {
"en": "Gitea admin API token for integration test (env: REPO_TOKEN)",
"bg": "Gitea admin API токен за интеграционен тест (env: REPO_TOKEN)",
"de": "Gitea-Admin-API-Token für Integrationstest (env: REPO_TOKEN)",
"ru": "Токен админ API Gitea для интеграционного теста (env: REPO_TOKEN)",
"zh": "Gitea 管理员 API 令牌,用于集成测试(环境变量: REPO_TOKEN"
},
"HOST": {
"en": "HOST",
"bg": "ХОСТ",
"de": "HOST",
"ru": "ХОСТ",
"zh": "主机"
},
"Install and configure a Gitea Runner on a remote host.": {
"en": "Install and configure a Gitea Runner on a remote host.",
"bg": "Инсталиране и конфигуриране на Gitea Runner на отдалечен хост.",
"de": "Gitea Runner auf einem Remote-Host installieren und konfigurieren.",
"ru": "Установить и настроить Gitea Runner на удалённом хосте.",
"zh": "在远程主机上安装并配置 Gitea Runner。"
},
"Installation failed: {error}": {
"en": "Installation failed: {error}",
"bg": "Инсталацията неуспешна: {error}",
"de": "Installation fehlgeschlagen: {error}",
"ru": "Установка не удалась: {error}",
"zh": "安装失败: {error}"
},
"Installing Gitea Runner on {host}": {
"en": "Installing Gitea Runner on {host}",
"bg": "Инсталиране на Gitea Runner на {host}",
"de": "Gitea Runner wird auf {host} installiert",
"ru": "Установка Gitea Runner на {host}",
"zh": "正在 {host} 上安装 Gitea Runner"
},
"Integration test API retries (default: 3, env: GITEA_INTEGRATION_RETRIES)": {
"en": "Integration test API retries (default: 3, env: GITEA_INTEGRATION_RETRIES)",
"bg": "Повторни опити за интеграционен тест API (по подразбиране: 3, env: GITEA_INTEGRATION_RETRIES)",
"de": "API-Wiederholungen für Integrationstest (Standard: 3, env: GITEA_INTEGRATION_RETRIES)",
"ru": "Повторы API интеграционного теста (по умолчанию: 3, env: GITEA_INTEGRATION_RETRIES)",
"zh": "集成测试 API 重试次数(默认: 3,环境变量: GITEA_INTEGRATION_RETRIES"
},
"LABELS": {
"en": "LABELS",
"bg": "ЕТИКЕТИ",
"de": "LABELS",
"ru": "МЕТКИ",
"zh": "标签"
},
"List all registered runners with live status.": {
"en": "List all registered runners with live status.",
@@ -363,13 +195,6 @@
"ru": "Список всех зарегистрированных runners с текущим статусом.",
"zh": "列出所有已注册 runners 的实时状态。"
},
"No runners registered. Use 'grm install' to add one.": {
"en": "No runners registered. Use 'grm install' to add one.",
"bg": "Няма регистрирани runners. Използвайте 'grm install', за да добавите.",
"de": "Keine Runner registriert. Verwenden Sie 'grm install', um einen hinzuzufügen.",
"ru": "Нет зарегистрированных runners. Используйте 'grm install' чтобы добавить.",
"zh": "没有已注册的 runners。使用 'grm install' 添加一个。"
},
"List failed: {error}": {
"en": "List failed: {error}",
"bg": "Списъкът неуспешен: {error}",
@@ -384,103 +209,75 @@
"ru": "ИМЯ",
"zh": "名称"
},
"HOST": {
"en": "HOST",
"bg": "ХОСТ",
"de": "HOST",
"ru": "ХОСТ",
"zh": "主机"
"No runners registered. Use 'grm install' to add one.": {
"en": "No runners registered. Use 'grm install' to add one.",
"bg": "Няма регистрирани runners. Използвайте 'grm install', за да добавите.",
"de": "Keine Runner registriert. Verwenden Sie 'grm install', um einen hinzuzufügen.",
"ru": "Нет зарегистрированных runners. Используйте 'grm install' чтобы добавить.",
"zh": "没有已注册的 runners。使用 'grm install' 添加一个。"
},
"USER": {
"en": "USER",
"bg": "ПОТРЕБИТЕЛ",
"de": "BENUTZER",
"ru": ОЛЬЗОВАТЕЛЬ",
"zh": "用户"
"Override SSH key from registry": {
"en": "Override SSH key from registry",
"bg": "Замяна на SSH ключа от регистъра",
"de": "SSH-Schlüssel aus Registrierung überschreiben",
"ru": ереопределить SSH ключ из реестра",
"zh": "覆盖注册表中的 SSH 密钥"
},
"MODE": {
"en": "MODE",
"bg": "РЕЖИМ",
"de": "MODUS",
"ru": "РЕЖИМ",
"zh": "模式"
"Override host from registry": {
"en": "Override host from registry",
"bg": "Замяна на хоста от регистъра",
"de": "Host aus Registrierung überschreiben",
"ru": "Переопределить хост из реестра",
"zh": "覆盖注册表中的主机"
},
"LABELS": {
"en": "LABELS",
"bg": "ЕТИКЕТИ",
"de": "LABELS",
"ru": "МЕТКИ",
"zh": "标签"
"Override user from registry": {
"en": "Override user from registry",
"bg": "Замяна на потребителя от регистъра",
"de": "Benutzer aus Registrierung überschreiben",
"ru": "Переопределить пользователя из реестра",
"zh": "覆盖注册表中的用户"
},
"STATUS": {
"en": "STATUS",
"bg": "СТАТУС",
"de": "STATUS",
"ru": "СТАТУС",
"zh": "状态"
"Path to SSH private key": {
"en": "Path to SSH private key",
"bg": "Път към SSH частен ключ",
"de": "Pfad zum SSH-Private-Key",
"ru": "Путь к SSH приватному ключу",
"zh": "SSH 私钥路径"
},
"active": {
"en": "active",
"bg": "активен",
"de": "aktiv",
"ru": "активен",
"zh": "活跃"
"Playbook not found: {playbook}": {
"en": "Playbook not found: {playbook}",
"bg": "Playbook не е намерен: {playbook}",
"de": "Playbook nicht gefunden: {playbook}",
"ru": "Playbook не найден: {playbook}",
"zh": "未找到 Playbook: {playbook}"
},
"inactive": {
"en": "inactive",
"bg": "неактивен",
"de": "inaktiv",
"ru": "неактивен",
"zh": "不活跃"
"Prompt for sudo password (default)": {
"en": "Prompt for sudo password (default)",
"bg": "Подканване за sudo парола (по подразбиране)",
"de": "Nach sudo-Passwort fragen (Standard)",
"ru": "Запросить пароль sudo (по умолчанию)",
"zh": "提示输入 sudo 密码(默认)"
},
"failed": {
"en": "failed",
"bg": "неуспешен",
"de": "fehlgeschlagen",
"ru": "сбой",
"zh": "失败"
"Registration token (env: GITEA_REGISTRATION_TOKEN)": {
"en": "Registration token (env: GITEA_REGISTRATION_TOKEN)",
"bg": "Регистрационен токен (env: GITEA_REGISTRATION_TOKEN)",
"de": "Registrierungstoken (env: GITEA_REGISTRATION_TOKEN)",
"ru": "Токен регистрации (env: GITEA_REGISTRATION_TOKEN)",
"zh": "注册令牌(环境变量: GITEA_REGISTRATION_TOKEN"
},
"unknown": {
"en": "unknown",
"bg": "неизвестен",
"de": "unbekannt",
"ru": "неизвестно",
"zh": "未知"
"Remove a registered Gitea Runner completely.": {
"en": "Remove a registered Gitea Runner completely.",
"bg": "Пълно премахване на регистриран Gitea Runner.",
"de": "Einen registrierten Gitea Runner vollständig entfernen.",
"ru": "Полностью удалить зарегистрированный Gitea Runner.",
"zh": "完全移除已注册的 Gitea Runner。"
},
"=== Operation Report ===": {
"en": "=== Operation Report ===",
"bg": "=== Отчет за операцията ===",
"de": "=== Operationsbericht ===",
"ru": "=== Отчет об операции ===",
"zh": "=== 操作报告 ==="
},
"completed": {
"en": "completed",
"bg": "завършено",
"de": "abgeschlossen",
"ru": "завершено",
"zh": "已完成"
},
"pending": {
"en": "pending",
"bg": "чакащо",
"de": "ausstehend",
"ru": "в ожидании",
"zh": "待处理"
},
"in_progress": {
"en": "in progress",
"bg": "в процес",
"de": "in Bearbeitung",
"ru": "в процессе",
"zh": "进行中"
},
"Save runner '{name}' to local registry": {
"en": "Save runner '{name}' to local registry",
"bg": "Запазване на runner '{name}' в локалния регистър",
"de": "Runner '{name}' in lokaler Registrierung speichern",
"ru": "Сохранить runner '{name}' в локальном реестре",
"zh": "将 runner '{name}' 保存到本地注册表"
"Remove failed: {error}": {
"en": "Remove failed: {error}",
"bg": "Премахването неуспешно: {error}",
"de": "Entfernung fehlgeschlagen: {error}",
"ru": "Удаление не удалось: {error}",
"zh": "移除失败: {error}"
},
"Remove runner '{name}' from local registry": {
"en": "Remove runner '{name}' from local registry",
@@ -489,6 +286,58 @@
"ru": "Удалить runner '{name}' из локального реестра",
"zh": "从本地注册表移除 runner '{name}'"
},
"Removing Gitea Runner {name} from {host}": {
"en": "Removing Gitea Runner {name} from {host}",
"bg": "Премахване на Gitea Runner {name} от {host}",
"de": "Entferne Gitea Runner {name} von {host}",
"ru": "Удаление Gitea Runner {name} с {host}",
"zh": "正在从 {host} 移除 Gitea Runner {name}"
},
"Runner '{name}' not found in registry.": {
"en": "Runner '{name}' not found in registry."
},
"Runner '{name}' not found in registry. Use 'grm install' first or provide --host and --user.": {
"en": "Runner '{name}' not found in registry. Use 'grm install' first or provide --host and --user.",
"bg": "Runner '{name}' не е намерен в регистъра. Използвайте 'grm install' първо или подайте --host и --user.",
"de": "Runner '{name}' nicht in Registrierung gefunden. Verwenden Sie zuerst 'grm install' oder geben Sie --host und --user an.",
"ru": "Runner '{name}' не найден в реестре. Сначала используйте 'grm install' или укажите --host и --user.",
"zh": "注册表中未找到 Runner '{name}'。请先使用 'grm install' 或提供 --host 和 --user。"
},
"Runner labels for Gitea Actions (env: GITEA_RUNNER_LABELS). Example: docker:docker://alpine:latest": {
"en": "Runner labels for Gitea Actions (env: GITEA_RUNNER_LABELS). Example: docker:docker://alpine:latest",
"bg": "Етикети на runner за Gitea Actions (env: GITEA_RUNNER_LABELS). Пример: docker:docker://alpine:latest",
"de": "Runner-Labels für Gitea Actions (env: GITEA_RUNNER_LABELS). Beispiel: docker:docker://alpine:latest",
"ru": "Метки runner для Gitea Actions (env: GITEA_RUNNER_LABELS). Пример: docker:docker://alpine:latest",
"zh": "Gitea Actions 的 runner 标签(环境变量: GITEA_RUNNER_LABELS)。示例: docker:docker://alpine:latest"
},
"Running Ansible playbook": {
"en": "Running Ansible playbook",
"bg": "Изпълнение на Ansible playbook",
"de": "Ansible-Playbook wird ausgeführt",
"ru": "Выполнение Ansible playbook",
"zh": "正在运行 Ansible playbook"
},
"SSH user": {
"en": "SSH user",
"bg": "SSH потребител",
"de": "SSH-Benutzer",
"ru": "SSH пользователь",
"zh": "SSH 用户"
},
"STATUS": {
"en": "STATUS",
"bg": "СТАТУС",
"de": "STATUS",
"ru": "СТАТУС",
"zh": "状态"
},
"Save runner '{name}' to local registry": {
"en": "Save runner '{name}' to local registry",
"bg": "Запазване на runner '{name}' в локалния регистър",
"de": "Runner '{name}' in lokaler Registrierung speichern",
"ru": "Сохранить runner '{name}' в локальном реестре",
"zh": "将 runner '{name}' 保存到本地注册表"
},
"Skip remote cleanup and only remove the local registry entry": {
"en": "Skip remote cleanup and only remove the local registry entry",
"bg": "Пропуснете отдалеченото почистване и премахнете само локалния запис",
@@ -496,257 +345,103 @@
"ru": "Пропустить удаленную очистку и удалить только локальную запись реестра",
"zh": "跳过远程清理,仅删除本地注册表条目"
},
"Ad-hoc command failed on {host}: {stderr}": {
"en": "Ad-hoc command failed on {host}: {stderr}",
"bg": "Ad-hoc командата неуспешна на {host}: {stderr}",
"de": "Ad-hoc-Befehl auf {host} fehlgeschlagen: {stderr}",
"ru": "Ad-hoc команда не удалась на {host}: {stderr}",
"zh": "Ad-hoc 命令在 {host} 上失败: {stderr}"
"Specific Gitea Runner version": {
"en": "Specific Gitea Runner version",
"bg": "Конкретна версия на Gitea Runner",
"de": "Spezifische Gitea Runner-Version",
"ru": "Конкретная версия Gitea Runner",
"zh": "指定 Gitea Runner 版本"
},
"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。"
"Start a registered Gitea Runner.": {
"en": "Start a registered Gitea Runner.",
"bg": "Стартиране на регистриран Gitea Runner.",
"de": "Einen registrierten Gitea Runner starten.",
"ru": "Запустить зарегистрированный Gitea Runner.",
"zh": "启动已注册的 Gitea Runner。"
},
"Configuring branch protection for {branch}...": {
"en": "Configuring branch protection for {branch}...",
"bg": "Конфигуриране на защита на клона {branch}...",
"de": "Konfiguriere Branch-Schutz für {branch}...",
"ru": "Настройка защиты ветки {branch}...",
"zh": "正在配置 {branch} 的分支保护..."
"Start failed: {error}": {
"en": "Start failed: {error}",
"bg": "Стартирането неуспешно: {error}",
"de": "Start fehlgeschlagen: {error}",
"ru": "Запуск не удался: {error}",
"zh": "启动失败: {error}"
},
" - Direct pushes: BLOCKED (require PR)": {
"en": " - Direct pushes: BLOCKED (require PR)",
"bg": " - Директни push-ове: БЛОКИРАНИ (изисква PR)",
"de": " - Direkte Pushes: BLOCKIERT (PR erforderlich)",
"ru": " - Прямые push: ЗАБЛОКИРОВАНЫ (требуется PR)",
"zh": " - 直接推送:已阻止(需要 PR"
"Starting Gitea Runner {name} on {host}": {
"en": "Starting Gitea Runner {name} on {host}",
"bg": "Стартиране на Gitea Runner {name} на {host}",
"de": "Starte Gitea Runner {name} auf {host}",
"ru": "Запуск Gitea Runner {name} на {host}",
"zh": "正在 {host} 上启动 Gitea Runner {name}"
},
" - Required approvals: {count}": {
"en": " - Required approvals: {count}",
"bg": " - Необходими одобрения: {count}",
"de": " - Erforderliche Genehmigungen: {count}",
"ru": " - Требуемые одобрения: {count}",
"zh": " - 必需审批数: {count}"
"Status check failed: {error}": {
"en": "Status check failed: {error}",
"bg": "Проверката на състоянието неуспешна: {error}",
"de": "Statusprüfung fehlgeschlagen: {error}",
"ru": "Проверка состояния не удалась: {error}",
"zh": "状态检查失败: {error}"
},
" - Dismiss stale approvals: yes": {
"en": " - Dismiss stale approvals: yes",
"bg": " - Анулиране на остарели одобрения: да",
"de": " - Veraltete Genehmigungen ablehnen: ja",
"ru": " - Отклонять устаревшие одобрения: да",
"zh": " - 忽略过时审批: 是"
"Stop a registered Gitea Runner.": {
"en": "Stop a registered Gitea Runner.",
"bg": "Спиране на регистриран Gitea Runner.",
"de": "Einen registrierten Gitea Runner stoppen.",
"ru": "Остановить зарегистрированный Gitea Runner.",
"zh": "停止已注册的 Gitea Runner。"
},
" - Block outdated branches: yes": {
"en": " - Block outdated branches: yes",
"bg": " - Блокиране на остарели клонове: да",
"de": " - Veraltete Branches blockieren: ja",
"ru": " - Блокировать устаревшие ветки: да",
"zh": " - 阻止过时分支: 是"
"Stop failed: {error}": {
"en": "Stop failed: {error}",
"bg": "Спирането неуспешно: {error}",
"de": "Stop fehlgeschlagen: {error}",
"ru": "Остановка не удалась: {error}",
"zh": "停止失败: {error}"
},
" - Block rejected reviews: yes": {
"en": " - Block rejected reviews: yes",
"bg": " - Блокиране на отхвърлени рецензии: да",
"de": " - Abgelehnte Reviews blockieren: ja",
"ru": " - Блокировать отклонённые ревью: да",
"zh": " - 阻止被拒绝的审查: 是"
"Stopping Gitea Runner {name} on {host}": {
"en": "Stopping Gitea Runner {name} on {host}",
"bg": "Спиране на Gitea Runner {name} на {host}",
"de": "Stoppe Gitea Runner {name} auf {host}",
"ru": "Остановка Gitea Runner {name} на {host}",
"zh": "正在 {host} 上停止 Gitea Runner {name}"
},
" - Required status checks: {checks}": {
"en": " - Required status checks: {checks}",
"bg": " - Необходими проверки на състоянието: {checks}",
"de": " - Erforderliche Status-Checks: {checks}",
"ru": " - Требуемые проверки статуса: {checks}",
"zh": " - 必需状态检查: {checks}"
"USER": {
"en": "USER",
"bg": "ПОТРЕБИТЕЛ",
"de": "BENUTZER",
"ru": "ПОЛЬЗОВАТЕЛЬ",
"zh": "用户"
},
"Creating {label} label...": {
"en": "Creating {label} label...",
"bg": "Създаване на етикет {label}...",
"de": "Erstelle Label {label}...",
"ru": "Создание метки {label}...",
"zh": "正在创建标签 {label}..."
"Update failed: {error}": {
"en": "Update failed: {error}",
"bg": "Актуализацията неуспешна: {error}",
"de": "Update fehlgeschlagen: {error}",
"ru": "Обновление не удалось: {error}",
"zh": "更新失败: {error}"
},
" Label '{label}' already exists.": {
"en": " Label '{label}' already exists.",
"bg": " Етикетът '{label}' вече съществува.",
"de": " Label '{label}' existiert bereits.",
"ru": " Метка '{label}' уже существует.",
"zh": " 标签 '{label}' 已存在。"
"Update the Gitea Runner binary on a remote host.": {
"en": "Update the Gitea Runner binary on a remote host.",
"bg": "Актуализиране на Gitea Runner двоичния файл на отдалечен хост.",
"de": "Gitea Runner-Binary auf einem Remote-Host aktualisieren.",
"ru": "Обновить бинарный файл Gitea Runner на удалённом хосте.",
"zh": "在远程主机上更新 Gitea Runner 二进制文件。"
},
" Label '{label}' created.": {
"en": " Label '{label}' created.",
"bg": " Етикетът '{label}' е създаден.",
"de": " Label '{label}' erstellt.",
"ru": " Метка '{label}' создана.",
"zh": " 标签 '{label}' 已创建。"
"Updating Gitea Runner on {host}": {
"en": "Updating Gitea Runner on {host}",
"bg": "Актуализиране на Gitea Runner на {host}",
"de": "Gitea Runner wird auf {host} aktualisiert",
"ru": "Обновление Gitea Runner на {host}",
"zh": "正在 {host} 上更新 Gitea Runner"
},
"Repository configuration complete.": {
"en": "Repository configuration complete.",
"bg": "Конфигурирането на хранилището е завършено.",
"de": "Repository-Konfiguration abgeschlossen.",
"ru": "Конфигурация репозитория завершена.",
"zh": "仓库配置完成。"
"active": {
"en": "active",
"bg": "активен",
"de": "aktiv",
"ru": "активен",
"zh": "活跃"
},
"Configuring repository settings...": {
"en": "Configuring repository settings...",
"bg": "Конфигуриране на настройките на хранилището...",
"de": "Repository-Einstellungen konfigurieren...",
"ru": "Настройка параметров репозитория...",
"zh": "正在配置仓库设置..."
},
" - Auto-delete branch after merge: yes": {
"en": " - Auto-delete branch after merge: yes",
"bg": " - Автоматично изтриване на клон след сливане: да",
"de": " - Branch nach Merge automatisch löschen: ja",
"ru": " - Автоудаление ветки после слияния: да",
"zh": " - 合并后自动删除分支: 是"
},
"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或者,您可以在 设置 → 分支 中手动配置分支保护。"
},
"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}"
},
"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。"
},
"Oops! PR title must follow conventional commit format.\n Expected: <type>: <description>\n Got: {pr_title}": {
"en": "Oops! PR title must follow conventional commit format.\n Expected: <type>: <description>\n Got: {pr_title}",
"bg": "Опа! Заглавието на PR трябва да следва конвенционален формат.\n Очаква се: <type>: <description>\n Получено: {pr_title}",
"de": "Ups! PR-Titel muss dem konventionellen Commit-Format folgen.\n Erwartet: <type>: <description>\n Erhalten: {pr_title}",
"ru": "Ой! Заголовок PR должен соответствовать формату conventional commit.\n Ожидается: <type>: <description>\n Получено: {pr_title}",
"zh": "哎呀!PR 标题必须遵循 conventional commit 格式。\n 预期格式: <type>: <description>\n 实际: {pr_title}"
},
"Oops! No task ID (GRM-N) found in branch name '{branch}'.": {
"en": "Oops! No task ID (GRM-N) found in branch name '{branch}'.",
"bg": "Опа! Не е намерен идентификатор на задача (GRM-N) в името на клона '{branch}'.",
"de": "Ups! Keine Task-ID (GRM-N) im Branch-Namen '{branch}' gefunden.",
"ru": "Ой! В названии ветки '{branch}' не найден идентификатор задачи (GRM-N).",
"zh": "哎呀!在分支名 '{branch}' 中未找到任务 ID (GRM-N)。"
},
"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 是否准备就绪且您具有合并权限。"
},
"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}"
},
"No task ID in commit message, skipping Vikunja update. All good — nothing to do here!": {
"en": "No task ID in commit message, skipping Vikunja update. All good — nothing to do here!",
"bg": "Няма идентификатор на задача в съобщението за commit, пропускаме обновяването на Vikunja. Всичко е наред — няма какво да правим!",
"de": "Keine Task-ID in der Commit-Nachricht, Vikunja-Update wird übersprungen. Alles gut — nichts zu tun!",
"ru": "В сообщении коммита нет ID задачи, пропускаем обновление Vikunja. Всё в порядке — делать нечего!",
"zh": "提交消息中没有任务 ID,跳过 Vikunja 更新。一切正常 — 无需操作!"
},
"Could not find Vikunja task for {task_id} in project {project_id}.": {
"en": "Could not find Vikunja task for {task_id} in project {project_id}.",
"bg": "Не е намерена задача Vikunja за {task_id} в проект {project_id}.",
"de": "Keine Vikunja-Aufgabe für {task_id} in Projekt {project_id} gefunden.",
"ru": "Не удалось найти задачу Vikunja для {task_id} в проекте {project_id}.",
"zh": "在项目 {project_id} 中找不到 Vikunja 任务 {task_id}。"
},
"Vikunja API error: HTTP {status} — {message}": {
"en": "Vikunja API error: HTTP {status} — {message}",
"bg": "Грешка в API на Vikunja: HTTP {status} — {message}",
"de": "Vikunja API-Fehler: HTTP {status} — {message}",
"ru": "Ошибка API Vikunja: HTTP {status} — {message}",
"zh": "Vikunja API 错误: HTTP {status} — {message}"
},
"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}) 已更新并标记为完成。"
},
"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}"
},
"Published to PyPI.": {
"en": "Published to PyPI.",
"bg": "Публикувано в PyPI.",
"de": "In PyPI veröffentlicht.",
"ru": "Опубликовано в PyPI.",
"zh": "已发布到 PyPI。"
},
"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。"
},
"Release creation failed with HTTP {status}: {message}": {
"en": "Release creation failed with HTTP {status}: {message}",
"bg": "Създаването на release неуспешно с HTTP {status}: {message}",
"de": "Release-Erstellung fehlgeschlagen mit HTTP {status}: {message}",
"ru": "Создание release не удалось: HTTP {status}: {message}",
"zh": "Release 创建失败: HTTP {status}: {message}"
},
"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} 已创建。"
},
"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! 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! 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! 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"
"completed": {
"en": "completed",
"bg": "завършено",
"de": "abgeschlossen",
"ru": "завершено",
"zh": "已完成"
},
"exit code {code}": {
"en": "exit code {code}",
@@ -755,18 +450,39 @@
"ru": "код выхода {code}",
"zh": "退出代码 {code}"
},
"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}"
"failed": {
"en": "failed",
"bg": "неуспешен",
"de": "fehlgeschlagen",
"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})"
"in_progress": {
"en": "in progress",
"bg": "в процес",
"de": "in Bearbeitung",
"ru": "в процессе",
"zh": "进行中"
},
"inactive": {
"en": "inactive",
"bg": "неактивен",
"de": "inaktiv",
"ru": "неактивен",
"zh": "不活跃"
},
"pending": {
"en": "pending",
"bg": "чакащо",
"de": "ausstehend",
"ru": "в ожидании",
"zh": "待处理"
},
"unknown": {
"en": "unknown",
"bg": "неизвестен",
"de": "unbekannt",
"ru": "неизвестно",
"zh": "未知"
}
}
}
+273
View File
@@ -0,0 +1,273 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
from click.testing import CliRunner
import scripts.ci.check_translations as check_translations
class TestExtractKeys:
def test_extracts_underscore_calls(self, tmp_path: Path) -> None:
f = tmp_path / "test.py"
f.write_text('from scripts.i18n import _\nprint(_("Hello world"))\n')
keys = check_translations.extract_keys(f)
assert "Hello world" in keys
def test_extracts_wrapper_calls(self, tmp_path: Path) -> None:
f = tmp_path / "test.py"
f.write_text('@_handle_errors("Update failed: {error}")\ndef foo(): pass\n')
keys = check_translations.extract_keys(f)
assert "Update failed: {error}" in keys
def test_ignores_non_string_args(self, tmp_path: Path) -> None:
f = tmp_path / "test.py"
f.write_text('x = "key"\n_(x)\n')
keys = check_translations.extract_keys(f)
assert keys == set()
def test_syntax_error_returns_empty(self, tmp_path: Path) -> None:
f = tmp_path / "test.py"
f.write_text("def broken(:\n")
keys = check_translations.extract_keys(f)
assert keys == set()
class TestCheckTranslationSet:
def test_all_good(self, tmp_path: Path) -> None:
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "mod.py").write_text('_("Hello")\n')
trans_file = tmp_path / "translations.json"
trans_file.write_text(
json.dumps({"Hello": {"en": "Hello", "bg": "Здравей", "de": "Hallo", "ru": "Привет", "zh": "你好"}})
)
result = check_translations.check_translation_set("test", src_dir, trans_file)
assert not result.errors
assert not result.warnings
def test_missing_key(self, tmp_path: Path) -> None:
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "mod.py").write_text('_("Missing")\n')
trans_file = tmp_path / "translations.json"
trans_file.write_text(json.dumps({"Other": {"en": "Other"}}))
result = check_translations.check_translation_set("test", src_dir, trans_file)
assert any("Missing key" in e for e in result.errors)
def test_dead_key(self, tmp_path: Path) -> None:
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "mod.py").write_text('_("Used")\n')
trans_file = tmp_path / "translations.json"
trans_file.write_text(json.dumps({"Used": {"en": "Used"}, "Dead": {"en": "Dead"}}))
result = check_translations.check_translation_set("test", src_dir, trans_file)
assert any("Dead key" in w for w in result.warnings)
def test_missing_language(self, tmp_path: Path) -> None:
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "mod.py").write_text('_("Hello")\n')
trans_file = tmp_path / "translations.json"
trans_file.write_text(json.dumps({"Hello": {"en": "Hello"}}))
result = check_translations.check_translation_set("test", src_dir, trans_file)
assert any("Missing languages" in w for w in result.warnings)
def test_missing_translations_file(self, tmp_path: Path) -> None:
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "mod.py").write_text('_("Hello")\n')
trans_file = tmp_path / "nonexistent.json"
result = check_translations.check_translation_set("test", src_dir, trans_file)
assert any("not found" in e for e in result.errors)
class TestMain:
def test_passes_on_clean_repo(self) -> None:
"""The actual repo should pass (with warnings for missing langs)."""
runner = CliRunner()
result = runner.invoke(check_translations.main, [])
assert result.exit_code == 0
def test_strict_fails_on_warnings(self) -> None:
"""--strict should fail if there are missing language warnings."""
runner = CliRunner()
result = runner.invoke(check_translations.main, ["--strict"])
# The repo has missing language warnings, so --strict should fail
assert result.exit_code == 1
def test_fails_on_errors(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""Should fail with exit code 1 when errors are found."""
error_result = check_translations.TranslationCheckResult(
name="mock",
src_dir=Path("/tmp"),
trans_file=Path("/tmp/t.json"),
errors=["Missing key: 'foo'"],
)
ok_result = check_translations.TranslationCheckResult(
name="mock2",
src_dir=Path("/tmp"),
trans_file=Path("/tmp/t.json"),
used_keys={"a"},
defined_keys={"a"},
)
monkeypatch.setattr(
check_translations,
"check_translation_set",
lambda name, src, trans: error_result if name == "GRM tool" else ok_result,
)
runner = CliRunner()
result = runner.invoke(check_translations.main, [])
assert result.exit_code == 1
assert "FAIL" in result.output
def test_passes_no_warnings(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""Should pass with exit code 0 and 'PASS:' message when no warnings."""
ok_result = check_translations.TranslationCheckResult(
name="mock",
src_dir=Path("/tmp"),
trans_file=Path("/tmp/t.json"),
used_keys={"a"},
defined_keys={"a"},
)
monkeypatch.setattr(
check_translations,
"check_translation_set",
lambda name, src, trans: ok_result,
)
runner = CliRunner()
result = runner.invoke(check_translations.main, [])
assert result.exit_code == 0
assert "PASS: All translations" in result.output
class TestPrintResult:
def test_prints_all_good(self, capsys: pytest.CaptureFixture[str]) -> None:
result = check_translations.TranslationCheckResult(
name="test",
src_dir=Path("/tmp"),
trans_file=Path("/tmp/t.json"),
used_keys={"a"},
defined_keys={"a"},
)
check_translations.print_result(result)
captured = capsys.readouterr()
assert "All good!" in captured.out
def test_prints_errors(self, capsys: pytest.CaptureFixture[str]) -> None:
result = check_translations.TranslationCheckResult(
name="test",
src_dir=Path("/tmp"),
trans_file=Path("/tmp/t.json"),
errors=["Missing key: 'foo'"],
)
check_translations.print_result(result)
captured = capsys.readouterr()
assert "ERROR" in captured.err
def test_prints_warnings(self, capsys: pytest.CaptureFixture[str]) -> None:
result = check_translations.TranslationCheckResult(
name="test",
src_dir=Path("/tmp"),
trans_file=Path("/tmp/t.json"),
warnings=["Dead key: 'bar'"],
)
check_translations.print_result(result)
captured = capsys.readouterr()
assert "WARN" in captured.err
class TestScriptsI18n:
"""Test the scripts/i18n.py module."""
def test_english_default(self) -> None:
from scripts.i18n import _
assert _("Running tests...") == "Running tests..."
def test_format_kwargs(self) -> None:
from scripts.i18n import _
result = _("Comparing {base}..{head} ({count} files changed)", base="a", head="b", count=5)
assert "a..b" in result
assert "5 files" in result
def test_unknown_key_returns_key(self) -> None:
from scripts.i18n import _
assert _("Nonexistent key 12345") == "Nonexistent key 12345"
def test_grm_lang_override(self, monkeypatch: pytest.MonkeyPatch) -> None:
import importlib
monkeypatch.setenv("GRM_LANG", "de")
import scripts.i18n
importlib.reload(scripts.i18n)
# "ERROR: REPO_TOKEN is not set." has a German translation
result = scripts.i18n._("ERROR: REPO_TOKEN is not set.")
assert "FEHLER" in result
# Restore
monkeypatch.delenv("GRM_LANG", raising=False)
importlib.reload(scripts.i18n)
def test_unsupported_lang_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
import importlib
monkeypatch.setenv("GRM_LANG", "xx")
import scripts.i18n
importlib.reload(scripts.i18n)
result = scripts.i18n._("Running tests...")
assert result == "Running tests..." # Falls back to English
monkeypatch.delenv("GRM_LANG", raising=False)
importlib.reload(scripts.i18n)
class TestGrmI18nSeparation:
"""Test that GRM and CI translations are properly separated."""
def test_grm_translations_exist(self) -> None:
trans = Path("src/gitea_runner_manager/translations.json")
assert trans.exists()
data = json.loads(trans.read_text())
assert len(data) > 0
def test_ci_translations_exist(self) -> None:
trans = Path("scripts/translations.json")
assert trans.exists()
data = json.loads(trans.read_text())
assert len(data) > 0
def test_no_overlap_between_sets(self) -> None:
grm_data = json.loads(Path("src/gitea_runner_manager/translations.json").read_text())
ci_data = json.loads(Path("scripts/translations.json").read_text())
grm_keys = set(grm_data)
ci_keys = set(ci_data)
overlap = grm_keys & ci_keys
assert not overlap, f"Keys found in both translation sets: {overlap}"
def test_grm_translations_only_grm_keys(self) -> None:
"""GRM translations should not contain CI-only keys."""
grm_data = json.loads(Path("src/gitea_runner_manager/translations.json").read_text())
# These are CI-only keys that should NOT be in GRM translations
ci_only_keys = {"Running tests...", "Lint passed.", "Published to PyPI."}
for key in ci_only_keys:
assert key not in grm_data, f"CI key {key!r} found in GRM translations"
def test_ci_translations_only_ci_keys(self) -> None:
"""CI translations should not contain GRM-only keys."""
ci_data = json.loads(Path("scripts/translations.json").read_text())
# These are GRM-only keys that should NOT be in CI translations
grm_only_keys = {"Installing Gitea Runner on {host}", "SSH user", "NAME"}
for key in grm_only_keys:
assert key not in ci_data, f"GRM key {key!r} found in CI translations"
+109 -1
View File
@@ -25,11 +25,21 @@ class TestRun:
class TestInstallPythonDeps:
def test_install(self) -> None:
def test_install_default_dev(self) -> None:
with patch("scripts.setup._run") as mock_run:
setup._install_python_deps(".venv/bin")
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[dev]"], ".venv/bin")
def test_install_ci_extras(self) -> None:
with patch("scripts.setup._run") as mock_run:
setup._install_python_deps(".venv/bin", extras="ci")
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci]"], ".venv/bin")
def test_install_lint_extras(self) -> None:
with patch("scripts.setup._run") as mock_run:
setup._install_python_deps(".venv/bin", extras="ci,lint")
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci,lint]"], ".venv/bin")
class TestInstallAnsibleCollections:
def test_install(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
@@ -159,3 +169,101 @@ class TestMain:
result = runner.invoke(setup.main, ["--bin", str(bin_dir)])
assert result.exit_code == 0
assert "Setup complete" in result.output
def test_extras_ci(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""--extras ci should pass 'ci' to _install_python_deps."""
monkeypatch.chdir(tmp_path)
bin_dir = tmp_path / ".venv" / "bin"
bin_dir.mkdir(parents=True)
runner = CliRunner()
with patch("scripts.setup._install_python_deps") as mock_deps:
with patch("scripts.setup._install_ansible_collections"):
with patch("scripts.setup._install_pre_commit_hooks"):
with patch("scripts.setup._configure_tea_login"):
with patch("scripts.setup._verify"):
result = runner.invoke(setup.main, ["--bin", str(bin_dir), "--extras", "ci"])
assert result.exit_code == 0
mock_deps.assert_called_once_with(str(bin_dir), "ci")
def test_no_ansible_collections(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""--no-ansible-collections should skip ansible collection install."""
monkeypatch.chdir(tmp_path)
bin_dir = tmp_path / ".venv" / "bin"
bin_dir.mkdir(parents=True)
runner = CliRunner()
with patch("scripts.setup._install_python_deps"):
with patch("scripts.setup._install_ansible_collections") as mock_ansible:
with patch("scripts.setup._install_pre_commit_hooks"):
with patch("scripts.setup._configure_tea_login"):
with patch("scripts.setup._verify"):
result = runner.invoke(
setup.main,
["--bin", str(bin_dir), "--no-ansible-collections"],
)
assert result.exit_code == 0
mock_ansible.assert_not_called()
def test_no_pre_commit(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""--no-pre-commit should skip pre-commit hook install."""
monkeypatch.chdir(tmp_path)
bin_dir = tmp_path / ".venv" / "bin"
bin_dir.mkdir(parents=True)
runner = CliRunner()
with patch("scripts.setup._install_python_deps"):
with patch("scripts.setup._install_ansible_collections"):
with patch("scripts.setup._install_pre_commit_hooks") as mock_hooks:
with patch("scripts.setup._configure_tea_login"):
with patch("scripts.setup._verify"):
result = runner.invoke(
setup.main,
["--bin", str(bin_dir), "--no-pre-commit"],
)
assert result.exit_code == 0
mock_hooks.assert_not_called()
def test_no_tea_login(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""--no-tea-login should skip tea login configuration."""
monkeypatch.chdir(tmp_path)
bin_dir = tmp_path / ".venv" / "bin"
bin_dir.mkdir(parents=True)
runner = CliRunner()
with patch("scripts.setup._install_python_deps"):
with patch("scripts.setup._install_ansible_collections"):
with patch("scripts.setup._install_pre_commit_hooks"):
with patch("scripts.setup._configure_tea_login") as mock_tea:
with patch("scripts.setup._verify"):
result = runner.invoke(
setup.main,
["--bin", str(bin_dir), "--no-tea-login"],
)
assert result.exit_code == 0
mock_tea.assert_not_called()
def test_lean_ci_setup(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Simulate the setup-ci make target: all skip flags together."""
monkeypatch.chdir(tmp_path)
bin_dir = tmp_path / ".venv" / "bin"
bin_dir.mkdir(parents=True)
runner = CliRunner()
with patch("scripts.setup._install_python_deps") as mock_deps:
with patch("scripts.setup._install_ansible_collections") as mock_ansible:
with patch("scripts.setup._install_pre_commit_hooks") as mock_hooks:
with patch("scripts.setup._configure_tea_login") as mock_tea:
with patch("scripts.setup._verify"):
result = runner.invoke(
setup.main,
[
"--bin",
str(bin_dir),
"--extras",
"ci",
"--no-ansible-collections",
"--no-pre-commit",
"--no-tea-login",
],
)
assert result.exit_code == 0
mock_deps.assert_called_once_with(str(bin_dir), "ci")
mock_ansible.assert_not_called()
mock_hooks.assert_not_called()
mock_tea.assert_not_called()