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