Public Access
DEVX-129: feat: test isolation pytest plugin, shift-left quality gates, dep upgrades
This commit was merged in pull request #195.
This commit is contained in:
+14
-2
@@ -115,7 +115,7 @@ devx-ensure-venv:
|
||||
.PHONY: devx-notify-failure devx-install-hooks devx-activate-scripts devx-venv devx-ensure-venv
|
||||
.PHONY: devx-lint-ruff devx-lint-format devx-typecheck devx-lint-bandit devx-lint-deps devx-lint
|
||||
.PHONY: devx-clean devx-pre-push
|
||||
.PHONY: devx-check-mutable-globals devx-check-dep-docs devx-check-test-coverage devx-check-docs devx-check-test-speed devx-check-doc-versions devx-vale
|
||||
.PHONY: devx-check-mutable-globals devx-check-dep-docs devx-check-test-coverage devx-check-docs devx-check-test-speed devx-check-test-isolation devx-check-translations devx-check-doc-versions devx-vale
|
||||
.PHONY: devx-check-api-identity-checks devx-setup-ssh-key
|
||||
.PHONY: devx-test-unit devx-pytest-cov
|
||||
.PHONY: devx-setup-image devx-lint-dockerfiles
|
||||
@@ -303,7 +303,7 @@ devx-lint-deps:
|
||||
@PIPAPI_PYTHON_LOCATION=$$(pwd)/$(DEVX_VENV)/bin/python \
|
||||
$(DEVX_BIN)/pip-audit --desc --skip-editable 2>&1 || true
|
||||
|
||||
devx-lint: devx-lint-ruff devx-lint-format devx-typecheck devx-lint-bandit
|
||||
devx-lint: devx-lint-ruff devx-lint-format devx-typecheck devx-lint-bandit devx-check-translations devx-check-test-isolation
|
||||
@echo "[devx-lint] Linting checks passed."
|
||||
|
||||
# ── Testing ───────────────────────────────────────────────────────────────────
|
||||
@@ -381,6 +381,18 @@ devx-vale:
|
||||
devx-check-test-speed:
|
||||
@$(DEVX_PYTHON) -m devx.tools.check_test_speed
|
||||
|
||||
# Check test files for un-hermetic patterns (unpatched subprocess, time.sleep, etc.)
|
||||
# This is also automatically enforced by the pytest plugin (pytest11 entry point).
|
||||
# Use this target for CI gates or pre-commit hooks.
|
||||
devx-check-test-isolation:
|
||||
@$(DEVX_PYTHON) -m devx.tools.check_test_isolation --test-path $(DEVX_TEST_PATHS)
|
||||
|
||||
# Check translation files for missing keys, dead keys, and missing languages.
|
||||
# Runs automatically as part of devx-lint to shift-left translation issues
|
||||
# (fail locally instead of in CI).
|
||||
devx-check-translations:
|
||||
@$(DEVX_PYTHON) -m devx.ci.check_translations
|
||||
|
||||
# Scan integration tests for unsafe is True/is False identity checks
|
||||
devx-check-api-identity-checks:
|
||||
@$(DEVX_PYTHON) -m devx.tools.check_api_identity_checks
|
||||
|
||||
@@ -0,0 +1,516 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static analysis to detect un-hermetic test patterns that cause slow or flaky tests.
|
||||
|
||||
This module is used in two ways:
|
||||
|
||||
1. **As a pytest plugin** (automatic — no configuration needed):
|
||||
When devx is installed, pytest auto-discovers this plugin via the
|
||||
``pytest11`` entry point. Every ``pytest`` run statically analyzes
|
||||
test files for patterns that cause slow, non-deterministic, or
|
||||
non-hermetic tests and reports violations as warnings.
|
||||
|
||||
To promote warnings to errors (fail the test run), add to pyproject.toml::
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
filterwarnings = ["error:Test isolation:UserWarning"]
|
||||
|
||||
Or use the ``--strict-test-isolation`` flag on the command line.
|
||||
|
||||
2. **As a standalone CLI** (for CI gates)::
|
||||
|
||||
python3 -m devx.tools.check_test_isolation [--test-path tests/]
|
||||
python3 -m devx.tools.check_test_isolation --strict
|
||||
|
||||
Patterns detected:
|
||||
|
||||
1. **Unpatched subprocess calls** — test functions that call
|
||||
``subprocess.run/call/Popen/check_call/check_output`` without a
|
||||
corresponding ``@patch`` decorator.
|
||||
2. **Unpatched ``time.sleep``** — test functions that call ``time.sleep``
|
||||
without patching it.
|
||||
3. **Unpatched known-subprocess-helpers** — functions known to spawn
|
||||
subprocesses (e.g. ``update_doc_versions``) called without patching.
|
||||
4. **Excessive iteration loops** — ``for _ in range(N)`` where N > 100.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from devx.i18n import _
|
||||
|
||||
# ── Configuration ─────────────────────────────────────────────────────────────
|
||||
|
||||
DEFAULT_MAX_LOOP_ITERATIONS = 100
|
||||
|
||||
# Functions known to spawn subprocesses. When a test calls any of these
|
||||
# without patching them, the real subprocess runs.
|
||||
# Maps function name → human-readable description.
|
||||
KNOWN_SUBPROCESS_HELPERS: dict[str, str] = {
|
||||
"update_doc_versions": "calls subprocess.run to run check_doc_versions --fix",
|
||||
"run_tests": "calls run_cmd to run make lint-ruff and make pytest-cov",
|
||||
"run_cmd": "calls subprocess.run for shell commands",
|
||||
}
|
||||
|
||||
# Transitive dependencies: if a helper calls another helper that is patched,
|
||||
# the call is safe. Maps helper → set of function names it internally calls.
|
||||
# If ANY of these are in the test's patches, the helper call is safe.
|
||||
HELPER_INTERNAL_CALLS: dict[str, set[str]] = {
|
||||
"run_tests": {"run_cmd", "subprocess"},
|
||||
"update_doc_versions": {"subprocess"},
|
||||
"run_cmd": {"subprocess"},
|
||||
}
|
||||
|
||||
|
||||
# ── Data structures ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@dataclass
|
||||
class Violation:
|
||||
"""A single isolation violation found in a test file."""
|
||||
|
||||
file: Path
|
||||
line: int
|
||||
col: int
|
||||
category: str
|
||||
message: str
|
||||
|
||||
def format(self) -> str:
|
||||
try:
|
||||
rel = self.file.relative_to(Path.cwd())
|
||||
except ValueError:
|
||||
rel = self.file
|
||||
return f"{rel}:{self.line}:{self.col}: [{self.category}] {self.message}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestFunctionInfo:
|
||||
"""Information about a test function or method."""
|
||||
|
||||
name: str
|
||||
node: ast.FunctionDef | ast.AsyncFunctionDef
|
||||
patches: set[str] = field(default_factory=set)
|
||||
class_patches: set[str] = field(default_factory=set)
|
||||
is_test: bool = False
|
||||
|
||||
|
||||
# ── AST helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _extract_patch_targets(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> set[str]:
|
||||
"""Extract @patch targets from decorators on a function or class."""
|
||||
targets: set[str] = set()
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Call):
|
||||
func = decorator.func
|
||||
is_patch = (
|
||||
isinstance(func, ast.Name)
|
||||
and func.id == "patch"
|
||||
or isinstance(func, ast.Attribute)
|
||||
and func.attr == "patch"
|
||||
)
|
||||
if is_patch and decorator.args and isinstance(decorator.args[0], ast.Constant):
|
||||
target = decorator.args[0].value
|
||||
if isinstance(target, str):
|
||||
targets.add(target)
|
||||
targets.add(target.rsplit(".", 1)[-1])
|
||||
return targets
|
||||
|
||||
|
||||
def _is_test_function(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
||||
return node.name.startswith("test_")
|
||||
|
||||
|
||||
def _get_called_name(node: ast.Call) -> str | None:
|
||||
func = node.func
|
||||
if isinstance(func, ast.Name):
|
||||
return func.id
|
||||
if isinstance(func, ast.Attribute):
|
||||
return func.attr
|
||||
return None
|
||||
|
||||
|
||||
def _get_full_called_name(node: ast.Call) -> str | None:
|
||||
func = node.func
|
||||
parts: list[str] = []
|
||||
current = func
|
||||
while isinstance(current, ast.Attribute):
|
||||
parts.append(current.attr)
|
||||
current = current.value
|
||||
if isinstance(current, ast.Name):
|
||||
parts.append(current.id)
|
||||
parts.reverse()
|
||||
if not parts:
|
||||
return None
|
||||
return ".".join(parts)
|
||||
|
||||
|
||||
def _get_range_count(node: ast.Call) -> int | None:
|
||||
if not isinstance(node.func, ast.Name) or node.func.id != "range":
|
||||
return None
|
||||
if not node.args:
|
||||
return None
|
||||
# range(N) — single argument
|
||||
if len(node.args) == 1:
|
||||
arg = node.args[0]
|
||||
if isinstance(arg, ast.Constant) and isinstance(arg.value, int):
|
||||
return arg.value
|
||||
return None
|
||||
# range(start, stop) — two or more arguments
|
||||
if len(node.args) >= 2:
|
||||
stop = node.args[1]
|
||||
if not isinstance(stop, ast.Constant) or not isinstance(stop.value, int):
|
||||
return None
|
||||
start = node.args[0]
|
||||
if isinstance(start, ast.Constant) and isinstance(start.value, int):
|
||||
return stop.value - start.value
|
||||
# Non-constant start — assume 0
|
||||
return stop.value
|
||||
return None # pragma: no cover
|
||||
|
||||
|
||||
# ── Analyzers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestIsolationVisitor(ast.NodeVisitor):
|
||||
"""AST visitor that detects un-hermetic test patterns."""
|
||||
|
||||
def __init__(self, file_path: Path, max_loop_iterations: int = DEFAULT_MAX_LOOP_ITERATIONS):
|
||||
self.file_path = file_path
|
||||
self.max_loop_iterations = max_loop_iterations
|
||||
self.violations: list[Violation] = []
|
||||
self._current_function: TestFunctionInfo | None = None
|
||||
self._current_class_patches: set[str] = set()
|
||||
self._in_test_class = False
|
||||
|
||||
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
||||
old_class_patches = self._current_class_patches
|
||||
old_in_test = self._in_test_class
|
||||
self._current_class_patches = _extract_patch_targets(node)
|
||||
self._in_test_class = node.name.startswith("Test")
|
||||
self.generic_visit(node)
|
||||
self._current_class_patches = old_class_patches
|
||||
self._in_test_class = old_in_test
|
||||
|
||||
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
||||
self._visit_function(node)
|
||||
|
||||
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
||||
self._visit_function(node)
|
||||
|
||||
def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
||||
if not _is_test_function(node):
|
||||
self.generic_visit(node)
|
||||
return
|
||||
|
||||
patches = _extract_patch_targets(node)
|
||||
info = TestFunctionInfo(
|
||||
name=node.name,
|
||||
node=node,
|
||||
patches=patches,
|
||||
class_patches=self._current_class_patches,
|
||||
is_test=True,
|
||||
)
|
||||
old_func = self._current_function
|
||||
self._current_function = info
|
||||
self.generic_visit(node)
|
||||
self._current_function = old_func
|
||||
|
||||
def visit_Call(self, node: ast.Call) -> None:
|
||||
if self._current_function is None:
|
||||
self.generic_visit(node)
|
||||
return
|
||||
|
||||
full_name = _get_full_called_name(node)
|
||||
short_name = _get_called_name(node)
|
||||
all_patches = self._current_function.patches | self._current_function.class_patches
|
||||
|
||||
# Check 1: subprocess.run / subprocess.call / subprocess.Popen etc.
|
||||
if full_name and full_name.startswith("subprocess."):
|
||||
method = full_name.split(".", 1)[1]
|
||||
if method in ("run", "call", "Popen", "check_call", "check_output") and not any(
|
||||
"subprocess" in p for p in all_patches
|
||||
):
|
||||
self.violations.append(
|
||||
Violation(
|
||||
file=self.file_path,
|
||||
line=node.lineno,
|
||||
col=node.col_offset,
|
||||
category="unpatched-subprocess",
|
||||
message=_(
|
||||
"{call} called in test '{test}' without @patch — "
|
||||
"this spawns a real subprocess. Add "
|
||||
'@patch("<module>.subprocess.run") or patch the calling function.',
|
||||
call=full_name,
|
||||
test=self._current_function.name,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Check 2: time.sleep
|
||||
if (
|
||||
(full_name == "time.sleep" or (short_name == "sleep" and "sleep" not in all_patches))
|
||||
and "sleep" not in all_patches
|
||||
and "time.sleep" not in all_patches
|
||||
and not any("sleep" in p for p in all_patches)
|
||||
):
|
||||
self.violations.append(
|
||||
Violation(
|
||||
file=self.file_path,
|
||||
line=node.lineno,
|
||||
col=node.col_offset,
|
||||
category="unpatched-sleep",
|
||||
message=_(
|
||||
"time.sleep called in test '{test}' without @patch — "
|
||||
"this causes real wall-clock delays. Add "
|
||||
'@patch("<module>.time.sleep").',
|
||||
test=self._current_function.name,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Check 3: Known subprocess helpers
|
||||
if short_name in KNOWN_SUBPROCESS_HELPERS and not (
|
||||
short_name in all_patches
|
||||
or any("subprocess" in p for p in all_patches)
|
||||
or any(
|
||||
dep in all_patches or any(dep in p for p in all_patches)
|
||||
for dep in HELPER_INTERNAL_CALLS.get(short_name, set())
|
||||
)
|
||||
):
|
||||
self.violations.append(
|
||||
Violation(
|
||||
file=self.file_path,
|
||||
line=node.lineno,
|
||||
col=node.col_offset,
|
||||
category="unpatched-helper",
|
||||
message=_(
|
||||
"{func} called in test '{test}' without @patch — "
|
||||
'this function {desc}. Add @patch("<module>.{func}").',
|
||||
func=short_name,
|
||||
test=self._current_function.name,
|
||||
desc=KNOWN_SUBPROCESS_HELPERS[short_name],
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_For(self, node: ast.For) -> None:
|
||||
if self._current_function is not None and isinstance(node.iter, ast.Call):
|
||||
count = _get_range_count(node.iter)
|
||||
if count is not None and count > self.max_loop_iterations:
|
||||
self.violations.append(
|
||||
Violation(
|
||||
file=self.file_path,
|
||||
line=node.lineno,
|
||||
col=node.col_offset,
|
||||
category="excessive-iterations",
|
||||
message=_(
|
||||
"Loop with {count} iterations in test '{test}' — "
|
||||
"consider property-based testing (hypothesis) or reduce to <= {max} iterations.",
|
||||
count=count,
|
||||
test=self._current_function.name,
|
||||
max=self.max_loop_iterations,
|
||||
),
|
||||
)
|
||||
)
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
# ── File scanning (shared by CLI and pytest plugin) ──────────────────────────
|
||||
|
||||
|
||||
def find_test_files(test_path: Path) -> list[Path]:
|
||||
"""Find all Python test files under the given path."""
|
||||
if test_path.is_file():
|
||||
return [test_path] if test_path.suffix == ".py" else []
|
||||
return sorted(test_path.rglob("test_*.py"))
|
||||
|
||||
|
||||
def analyze_file(file_path: Path, max_loop_iterations: int = DEFAULT_MAX_LOOP_ITERATIONS) -> list[Violation]:
|
||||
"""Analyze a single test file for isolation violations."""
|
||||
try:
|
||||
source = file_path.read_text()
|
||||
tree = ast.parse(source, filename=str(file_path))
|
||||
except SyntaxError as exc:
|
||||
return [
|
||||
Violation(
|
||||
file=file_path,
|
||||
line=exc.lineno or 0,
|
||||
col=exc.offset or 0,
|
||||
category="syntax-error",
|
||||
message=f"Could not parse file: {exc}",
|
||||
)
|
||||
]
|
||||
|
||||
visitor = TestIsolationVisitor(file_path, max_loop_iterations)
|
||||
visitor.visit(tree)
|
||||
return visitor.violations
|
||||
|
||||
|
||||
def analyze_test_files(
|
||||
test_path: Path,
|
||||
max_loop_iterations: int = DEFAULT_MAX_LOOP_ITERATIONS,
|
||||
categories: set[str] | None = None,
|
||||
) -> list[Violation]:
|
||||
"""Analyze all test files under test_path. Returns list of violations."""
|
||||
test_files = find_test_files(test_path)
|
||||
all_violations: list[Violation] = []
|
||||
for file_path in test_files:
|
||||
violations = analyze_file(file_path, max_loop_iterations)
|
||||
if categories:
|
||||
violations = [v for v in violations if v.category in categories]
|
||||
all_violations.extend(violations)
|
||||
return all_violations
|
||||
|
||||
|
||||
# ── Pytest plugin ─────────────────────────────────────────────────────────────
|
||||
#
|
||||
# When devx is installed, pytest auto-discovers this plugin via the
|
||||
# `pytest11` entry point. The plugin runs static analysis on every
|
||||
# test file during collection and emits warnings for violations.
|
||||
# Use --strict-test-isolation to promote warnings to errors.
|
||||
|
||||
|
||||
def pytest_addoption(parser): # type: ignore[no-untyped-def] # pragma: no cover
|
||||
"""Register pytest command-line options."""
|
||||
parser.addoption(
|
||||
"--strict-test-isolation",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Fail the test run if any test isolation violations are found.",
|
||||
)
|
||||
parser.addoption(
|
||||
"--no-test-isolation",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Disable test isolation static analysis.",
|
||||
)
|
||||
parser.addoption(
|
||||
"--test-isolation-max-loop",
|
||||
type=int,
|
||||
default=DEFAULT_MAX_LOOP_ITERATIONS,
|
||||
help=f"Max iterations allowed in a test loop (default: {DEFAULT_MAX_LOOP_ITERATIONS}).",
|
||||
)
|
||||
|
||||
|
||||
def pytest_collection_finish(session): # type: ignore[no-untyped-def] # pragma: no cover
|
||||
"""Run static analysis after all test files are collected."""
|
||||
if session.config.getoption("--no-test-isolation"):
|
||||
return
|
||||
|
||||
strict = session.config.getoption("--strict-test-isolation")
|
||||
max_loop = session.config.getoption("--test-isolation-max-loop")
|
||||
|
||||
# Analyze all collected test files
|
||||
test_files: set[Path] = set()
|
||||
for item in session.items:
|
||||
test_files.add(Path(str(item.fspath)))
|
||||
|
||||
all_violations: list[Violation] = []
|
||||
for file_path in sorted(test_files):
|
||||
violations = analyze_file(file_path, max_loop)
|
||||
all_violations.extend(violations)
|
||||
|
||||
if not all_violations:
|
||||
return
|
||||
|
||||
# Emit warnings
|
||||
import warnings
|
||||
|
||||
for v in sorted(all_violations, key=lambda x: (str(x.file), x.line)):
|
||||
msg = f"Test isolation violation: {v.format()}"
|
||||
warnings.warn(msg, UserWarning, stacklevel=2)
|
||||
|
||||
if strict:
|
||||
count = len(all_violations)
|
||||
files = len({v.file for v in all_violations})
|
||||
click.echo(
|
||||
_(
|
||||
"\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\n"
|
||||
"Fix: add @patch decorators for subprocess/time.sleep calls, "
|
||||
"or patch the calling function.\n",
|
||||
count=count,
|
||||
files=files,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
|
||||
|
||||
# ── Standalone CLI ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--test-path",
|
||||
type=click.Path(exists=True, path_type=Path),
|
||||
default=Path("tests/"),
|
||||
show_default=True,
|
||||
help="Path to test directory or file to analyze.",
|
||||
)
|
||||
@click.option(
|
||||
"--max-loop-iterations",
|
||||
type=int,
|
||||
default=DEFAULT_MAX_LOOP_ITERATIONS,
|
||||
show_default=True,
|
||||
help="Maximum allowed iterations in a single test loop.",
|
||||
)
|
||||
@click.option(
|
||||
"--strict",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Treat warnings as errors (non-zero exit on any violation).",
|
||||
)
|
||||
@click.option(
|
||||
"--categories",
|
||||
type=str,
|
||||
default="",
|
||||
help="Comma-separated list of categories to check (default: all). "
|
||||
"Available: unpatched-subprocess, unpatched-sleep, unpatched-helper, excessive-iterations",
|
||||
)
|
||||
def cli(test_path: Path, max_loop_iterations: int, strict: bool, categories: str) -> None:
|
||||
"""Check test files for un-hermetic patterns that cause slow or flaky tests."""
|
||||
allowed: set[str] | None = None
|
||||
if categories:
|
||||
allowed = {c.strip() for c in categories.split(",")}
|
||||
|
||||
violations = analyze_test_files(test_path, max_loop_iterations, allowed)
|
||||
|
||||
if not violations:
|
||||
file_count = len(find_test_files(test_path))
|
||||
click.echo(
|
||||
_("Test isolation check passed: {count} test files analyzed, no violations found.", count=file_count)
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
click.echo(
|
||||
_(
|
||||
"Test isolation check FAILED: {count} violation(s) found in {files} test file(s).",
|
||||
count=len(violations),
|
||||
files=len({v.file for v in violations}),
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
click.echo("")
|
||||
for v in sorted(violations, key=lambda x: (str(x.file), x.line)):
|
||||
click.echo(f" {v.format()}", err=True)
|
||||
|
||||
click.echo("")
|
||||
click.echo(
|
||||
_(
|
||||
"Fix: add @patch decorators for subprocess/time.sleep calls, "
|
||||
"or patch the calling function. Use property-based testing for statistical tests."
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
cli() # pragma: no cover
|
||||
@@ -35,17 +35,17 @@ TARGET_DIR = Path.home() / ".local" / "bin"
|
||||
|
||||
ACTIONLINT_VERSION = "1.7.12"
|
||||
|
||||
GIT_CLIFF_VERSION = "2.13.0"
|
||||
GIT_CLIFF_VERSION = "2.13.1"
|
||||
|
||||
ACT_RUNNER_VERSION = "0.2.11"
|
||||
|
||||
TEA_VERSION = "0.14.1"
|
||||
TEA_VERSION = "0.14.2"
|
||||
|
||||
HADOLINT_VERSION = "2.12.0"
|
||||
HADOLINT_VERSION = "2.14.0"
|
||||
|
||||
TOFU_VERSION = "1.12.3"
|
||||
|
||||
VALE_VERSION = "3.12.0"
|
||||
VALE_VERSION = "3.15.1"
|
||||
|
||||
|
||||
def _arch() -> str:
|
||||
|
||||
+157
-93
@@ -183,6 +183,14 @@
|
||||
"ru": "\nTag → Commit alignment:",
|
||||
"zh": "\nTag → Commit alignment:"
|
||||
},
|
||||
"\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\nFix: add @patch decorators for subprocess/time.sleep calls, or patch the calling function.\n": {
|
||||
"bg": "\nПроверката за изолация на тестове НЕ ПРЕМИНА: {count} нарушения в {files} файла.\nРешение: добавете @patch декоратори за subprocess/time.sleep извиквания или patch-нете извикващата функция.\n",
|
||||
"de": "\nTestisolationsprüfung FEHLGESCHLAGEN: {count} Verstoß/Verstöße in {files} Datei(en).\nBehebung: @patch-Dekoratoren für subprocess/time.sleep-Aufrufe hinzufügen oder die aufrufende Funktion patchen.\n",
|
||||
"en": "\nTest isolation check FAILED: {count} violation(s) in {files} file(s).\nFix: add @patch decorators for subprocess/time.sleep calls, or patch the calling function.\n",
|
||||
"pl": "\nSprawdzenie izolacji testów NIE ZALICZONE: {count} naruszeń w {files} plikach.\nNaprawa: dodaj dekoratory @patch dla wywołań subprocess/time.sleep lub patchuj wywołującą funkcję.\n",
|
||||
"ru": "\nПроверка изоляции тестов НЕ ПРОЙДЕНА: {count} нарушений в {files} файлах.\nИсправление: добавьте декораторы @patch для вызовов subprocess/time.sleep или patch вызывающую функцию.\n",
|
||||
"zh": "\n测试隔离检查失败:在 {files} 个文件中有 {count} 个违规。\n修复:为 subprocess/time.sleep 调用添加 @patch 装饰器,或 patch 调用函数。\n"
|
||||
},
|
||||
"\nUntagged release commits:": {
|
||||
"bg": "\nUntagged release commits:",
|
||||
"de": "\nUntagged release commits:",
|
||||
@@ -368,9 +376,9 @@
|
||||
"zh": " - {count} standard labels verified"
|
||||
},
|
||||
" -> {dir}": {
|
||||
"en": " -> {dir}",
|
||||
"bg": " -> {dir}",
|
||||
"de": " -> {dir}",
|
||||
"en": " -> {dir}",
|
||||
"pl": " -> {dir}",
|
||||
"ru": " -> {dir}",
|
||||
"zh": " -> {dir}"
|
||||
@@ -552,9 +560,9 @@
|
||||
"zh": " Repo root: {root}"
|
||||
},
|
||||
" Run 'make install-checkmake' to install the Makefile linter.": {
|
||||
"en": " Run 'make install-checkmake' to install the Makefile linter.",
|
||||
"bg": " Изпълнете 'make install-checkmake' за инсталиране на Makefile линтера.",
|
||||
"de": " Führen Sie 'make install-checkmake' aus, um den Makefile-Linter zu installieren.",
|
||||
"en": " Run 'make install-checkmake' to install the Makefile linter.",
|
||||
"pl": " Uruchom 'make install-checkmake', aby zainstalować linter Makefile.",
|
||||
"ru": " Выполните 'make install-checkmake' для установки линтера Makefile.",
|
||||
"zh": " 运行 'make install-checkmake' 来安装 Makefile 检查器。"
|
||||
@@ -704,9 +712,9 @@
|
||||
"zh": " {n} stale docs found (warnings only)"
|
||||
},
|
||||
" {tool}: found at {path}": {
|
||||
"en": " {tool}: found at {path}",
|
||||
"bg": " {tool}: намерен на {path}",
|
||||
"de": " {tool}: gefunden unter {path}",
|
||||
"en": " {tool}: found at {path}",
|
||||
"pl": " {tool}: znaleziono w {path}",
|
||||
"ru": " {tool}: найден в {path}",
|
||||
"zh": " {tool}: 在 {path} 找到"
|
||||
@@ -791,6 +799,14 @@
|
||||
"ru": "All molecule tests passed.",
|
||||
"zh": "All molecule tests passed."
|
||||
},
|
||||
"Allow empty tag (PR mode where SHA is concrete).": {
|
||||
"bg": "Позволи празен таг (PR режим, където SHA е конкретен).",
|
||||
"de": "Leeren Tag zulassen (PR-Modus, in dem SHA konkret ist).",
|
||||
"en": "Allow empty tag (PR mode where SHA is concrete).",
|
||||
"pl": "Zezwalaj na pusty tag (tryb PR, w którym SHA jest konkretne).",
|
||||
"ru": "Разрешить пустой тег (режим PR, где SHA конкретен).",
|
||||
"zh": "允许空标签(SHA 为具体值的 PR 模式)。"
|
||||
},
|
||||
"Another molecule runner failed. Stopping this runner early.": {
|
||||
"bg": "Another molecule runner failed. Stopping this runner early.",
|
||||
"de": "Another molecule runner failed. Stopping this runner early.",
|
||||
@@ -959,14 +975,6 @@
|
||||
"ru": "CI checks failed.",
|
||||
"zh": "CI checks failed."
|
||||
},
|
||||
"Gitea API token not set. Set one of: {names}": {
|
||||
"bg": "Gitea API token not set. Set one of: {names}",
|
||||
"de": "Gitea API token not set. Set one of: {names}",
|
||||
"en": "Gitea API token not set. Set one of: {names}",
|
||||
"pl": "Gitea API token not set. Set one of: {names}",
|
||||
"ru": "Gitea API token not set. Set one of: {names}",
|
||||
"zh": "Gitea API token not set. Set one of: {names}"
|
||||
},
|
||||
"CI_GITEA_TOKEN environment variable required": {
|
||||
"bg": "CI_GITEA_TOKEN environment variable required",
|
||||
"de": "CI_GITEA_TOKEN environment variable required",
|
||||
@@ -1368,9 +1376,9 @@
|
||||
"zh": "Dependencies must have documentation comments."
|
||||
},
|
||||
"Directory to scan (default: tests/integration). Can be repeated.": {
|
||||
"en": "Directory to scan (default: tests/integration). Can be repeated.",
|
||||
"bg": "Директория за сканиране (по подразбиране: tests/integration). Може да се повтаря.",
|
||||
"de": "Zu scannendes Verzeichnis (Standard: tests/integration). Kann wiederholt werden.",
|
||||
"en": "Directory to scan (default: tests/integration). Can be repeated.",
|
||||
"pl": "Katalog do skanowania (domyślnie: tests/integration). Można powtarzać.",
|
||||
"ru": "Директория для сканирования (по умолчанию: tests/integration). Можно повторять.",
|
||||
"zh": "要扫描的目录(默认:tests/integration)。可重复。"
|
||||
@@ -1544,9 +1552,9 @@
|
||||
"zh": "Failed to push release commit after 3 attempts. Manual intervention required."
|
||||
},
|
||||
"Failed to start ssh-agent: {error}": {
|
||||
"en": "Failed to start ssh-agent: {error}",
|
||||
"bg": "Неуспешно стартиране на ssh-agent: {error}",
|
||||
"de": "Starten von ssh-agent fehlgeschlagen: {error}",
|
||||
"en": "Failed to start ssh-agent: {error}",
|
||||
"pl": "Nie udało się uruchomić ssh-agent: {error}",
|
||||
"ru": "Не удалось запустить ssh-agent: {error}",
|
||||
"zh": "启动 ssh-agent 失败: {error}"
|
||||
@@ -1575,6 +1583,14 @@
|
||||
"ru": "Fetching origin/master...",
|
||||
"zh": "Fetching origin/master..."
|
||||
},
|
||||
"Fix: add @patch decorators for subprocess/time.sleep calls, or patch the calling function. Use property-based testing for statistical tests.": {
|
||||
"bg": "Решение: добавете @patch декоратори за subprocess/time.sleep извиквания или patch-нете извикващата функция. Използвайте property-based тестове за статистически тестове.",
|
||||
"de": "Behebung: @patch-Dekoratoren für subprocess/time.sleep-Aufrufe hinzufügen oder die aufrufende Funktion patchen. Property-based testing für statistische Tests verwenden.",
|
||||
"en": "Fix: add @patch decorators for subprocess/time.sleep calls, or patch the calling function. Use property-based testing for statistical tests.",
|
||||
"pl": "Naprawa: dodaj dekoratory @patch dla wywołań subprocess/time.sleep lub patchuj wywołującą funkcję. Użyj testów opartych na właściwościach dla testów statystycznych.",
|
||||
"ru": "Исправление: добавьте декораторы @patch для вызовов subprocess/time.sleep или patch вызывающую функцию. Используйте property-based тестирование для статистических тестов.",
|
||||
"zh": "修复:为 subprocess/time.sleep 调用添加 @patch 装饰器,或 patch 调用函数。对统计测试使用基于属性的测试。"
|
||||
},
|
||||
"Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.": {
|
||||
"bg": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.",
|
||||
"de": "Force-push failed:\n{error}\nThe remote may have unexpected commits. Fetch and try again.",
|
||||
@@ -1608,9 +1624,9 @@
|
||||
"zh": "Found {count} stale documentation reference(s)"
|
||||
},
|
||||
"Found {count} unsafe identity check(s) in integration tests.": {
|
||||
"en": "Found {count} unsafe identity check(s) in integration tests.",
|
||||
"bg": "Намерени са {count} небрежни проверки за идентичност в интеграционните тестове.",
|
||||
"de": "{count} unsichere Identitätsprüfung(en) in Integrationstests gefunden.",
|
||||
"en": "Found {count} unsafe identity check(s) in integration tests.",
|
||||
"pl": "Znaleziono {count} niebezpiecznych sprawdzeń tożsamości w testach integracyjnych.",
|
||||
"ru": "Найдено {count} небезопасных проверок идентичности в интеграционных тестах.",
|
||||
"zh": "在集成测试中发现 {count} 个不安全的身份检查。"
|
||||
@@ -1655,6 +1671,30 @@
|
||||
"ru": "Generating badges in {out}...",
|
||||
"zh": "Generating badges in {out}..."
|
||||
},
|
||||
"Git tag or ref that was deployed": {
|
||||
"bg": "Git таг или референция, която беше разгърната",
|
||||
"de": "Git-Tag oder Ref, der bereitgestellt wurde",
|
||||
"en": "Git tag or ref that was deployed",
|
||||
"pl": "Tag Git lub ref, który został wdrożony",
|
||||
"ru": "Git-тег или ссылка, которые были развёрнуты",
|
||||
"zh": "已部署的 Git 标签或引用"
|
||||
},
|
||||
"Git tag to deploy (e.g. v0.28.1).": {
|
||||
"bg": "Git таг за разгръщане (напр. v0.28.1).",
|
||||
"de": "Git-Tag für Bereitstellung (z.B. v0.28.1).",
|
||||
"en": "Git tag to deploy (e.g. v0.28.1).",
|
||||
"pl": "Tag Git do wdrożenia (np. v0.28.1).",
|
||||
"ru": "Git-тег для развёртывания (напр. v0.28.1).",
|
||||
"zh": "要部署的 Git 标签(例如 v0.28.1)。"
|
||||
},
|
||||
"Gitea API token not set. Set one of: {names}": {
|
||||
"bg": "Gitea API token not set. Set one of: {names}",
|
||||
"de": "Gitea API token not set. Set one of: {names}",
|
||||
"en": "Gitea API token not set. Set one of: {names}",
|
||||
"pl": "Gitea API token not set. Set one of: {names}",
|
||||
"ru": "Gitea API token not set. Set one of: {names}",
|
||||
"zh": "Gitea API token not set. Set one of: {names}"
|
||||
},
|
||||
"Gitea PyPI registry: {tag} already published — continuing.": {
|
||||
"bg": "Gitea PyPI registry: {tag} вече е публикуван — продължава.",
|
||||
"de": "Gitea PyPI-Registry: {tag} bereits veröffentlicht — wird fortgesetzt.",
|
||||
@@ -1840,13 +1880,21 @@
|
||||
"zh": "Linting documentation in {root}..."
|
||||
},
|
||||
"Login to {registry} failed: {error}": {
|
||||
"en": "Login to {registry} failed: {error}",
|
||||
"bg": "Влизането в {registry} не успя: {error}",
|
||||
"de": "Anmeldung bei {registry} fehlgeschlagen: {error}",
|
||||
"en": "Login to {registry} failed: {error}",
|
||||
"pl": "Logowanie do {registry} nie powiodło się: {error}",
|
||||
"ru": "Ошибка входа в {registry}: {error}",
|
||||
"zh": "登录 {registry} 失败: {error}"
|
||||
},
|
||||
"Loop with {count} iterations in test '{test}' — consider property-based testing (hypothesis) or reduce to <= {max} iterations.": {
|
||||
"bg": "Цикъл с {count} итерации в тест '{test}' — използвайте property-based тестове (hypothesis) или намалете до <= {max} итерации.",
|
||||
"de": "Schleife mit {count} Iterationen in Test '{test}' — property-based testing (hypothesis) verwenden oder auf <= {max} Iterationen reduzieren.",
|
||||
"en": "Loop with {count} iterations in test '{test}' — consider property-based testing (hypothesis) or reduce to <= {max} iterations.",
|
||||
"pl": "Pętla z {count} iteracjami w teście '{test}' — rozważ testy oparte na właściwościach (hypothesis) lub zmniejsz do <= {max} iteracji.",
|
||||
"ru": "Цикл с {count} итерациями в тесте '{test}' — используйте property-based тестирование (hypothesis) или уменьшите до <= {max} итераций.",
|
||||
"zh": "测试 '{test}' 中有 {count} 次迭代的循环 — 考虑使用基于属性的测试 (hypothesis) 或减少到 <= {max} 次迭代。"
|
||||
},
|
||||
"Manifest file not found: {path}": {
|
||||
"bg": "Manifest file not found: {path}",
|
||||
"de": "Manifest file not found: {path}",
|
||||
@@ -2103,13 +2151,13 @@
|
||||
"ru": "No workflow runs found for SHA {sha}.",
|
||||
"zh": "No workflow runs found for SHA {sha}."
|
||||
},
|
||||
"Note: Self-approval not allowed. Posting COMMENT instead.": {
|
||||
"bg": "Забележка: Само-одобрението не е разрешено. Публикуване на COMMENT вместо това.",
|
||||
"de": "Hinweis: Selbstgenehmigung nicht erlaubt. COMMENT wird stattdessen gesendet.",
|
||||
"en": "Note: Self-approval not allowed. Posting COMMENT instead.",
|
||||
"pl": "Uwaga: Samo-zatwierdzenie niedozwolone. Publikowanie COMMENT zamiast tego.",
|
||||
"ru": "Примечание: Самоодобрение не разрешено. Публикация COMMENT вместо этого.",
|
||||
"zh": "注意:不允许自我批准。改为发布 COMMENT。"
|
||||
"Note: CI token also cannot approve. Posting COMMENT instead.": {
|
||||
"bg": "Забележка: CI тоукънът също не може да одобри. Публикуване на COMMENT вместо това.",
|
||||
"de": "Hinweis: CI-Token kann ebenfalls nicht genehmigen. COMMENT wird stattdessen gesendet.",
|
||||
"en": "Note: CI token also cannot approve. Posting COMMENT instead.",
|
||||
"pl": "Uwaga: Token CI również nie może zatwierdzić. Publikowanie COMMENT zamiast tego.",
|
||||
"ru": "Примечание: CI токен также не может одобрить. Публикация COMMENT вместо этого.",
|
||||
"zh": "注意:CI 令牌也无法批准。改为发布 COMMENT。"
|
||||
},
|
||||
"Note: Self-approval not allowed with reviewer token. Retrying with CI token.": {
|
||||
"bg": "Забележка: Само-одобрението не е разрешено с тоукън на рецензента. Повторен опит с CI тоукън.",
|
||||
@@ -2119,13 +2167,13 @@
|
||||
"ru": "Примечание: Самоодобрение токеном ревьюера не разрешено. Повторная попытка с CI токеном.",
|
||||
"zh": "注意:不允许使用审阅者令牌进行自我批准。正在使用 CI 令牌重试。"
|
||||
},
|
||||
"Note: CI token also cannot approve. Posting COMMENT instead.": {
|
||||
"bg": "Забележка: CI тоукънът също не може да одобри. Публикуване на COMMENT вместо това.",
|
||||
"de": "Hinweis: CI-Token kann ebenfalls nicht genehmigen. COMMENT wird stattdessen gesendet.",
|
||||
"en": "Note: CI token also cannot approve. Posting COMMENT instead.",
|
||||
"pl": "Uwaga: Token CI również nie może zatwierdzić. Publikowanie COMMENT zamiast tego.",
|
||||
"ru": "Примечание: CI токен также не может одобрить. Публикация COMMENT вместо этого.",
|
||||
"zh": "注意:CI 令牌也无法批准。改为发布 COMMENT。"
|
||||
"Note: Self-approval not allowed. Posting COMMENT instead.": {
|
||||
"bg": "Забележка: Само-одобрението не е разрешено. Публикуване на COMMENT вместо това.",
|
||||
"de": "Hinweis: Selbstgenehmigung nicht erlaubt. COMMENT wird stattdessen gesendet.",
|
||||
"en": "Note: Self-approval not allowed. Posting COMMENT instead.",
|
||||
"pl": "Uwaga: Samo-zatwierdzenie niedozwolone. Publikowanie COMMENT zamiast tego.",
|
||||
"ru": "Примечание: Самоодобрение не разрешено. Публикация COMMENT вместо этого.",
|
||||
"zh": "注意:不允许自我批准。改为发布 COMMENT。"
|
||||
},
|
||||
"Nothing to push.": {
|
||||
"bg": "Nothing to push.",
|
||||
@@ -2624,9 +2672,9 @@
|
||||
"zh": "仓库所有者未设置。使用 --owner 或 DEVX_REPO_OWNER 环境变量。"
|
||||
},
|
||||
"Required tools missing.": {
|
||||
"en": "Required tools missing.",
|
||||
"bg": "Липсват задължителни инструменти.",
|
||||
"de": "Erforderliche Werkzeuge fehlen.",
|
||||
"en": "Required tools missing.",
|
||||
"pl": "Brak wymaganych narzędzi.",
|
||||
"ru": "Отсутствуют обязательные инструменты.",
|
||||
"zh": "缺少必需的工具。"
|
||||
@@ -2720,25 +2768,25 @@
|
||||
"zh": "Running: {scenario} on {platform}"
|
||||
},
|
||||
"SSH key set up successfully": {
|
||||
"en": "SSH key set up successfully",
|
||||
"bg": "SSH ключът е настроен успешно",
|
||||
"de": "SSH-Schlüssel erfolgreich eingerichtet",
|
||||
"en": "SSH key set up successfully",
|
||||
"pl": "Klucz SSH skonfigurowany pomyślnie",
|
||||
"ru": "SSH-ключ успешно настроен",
|
||||
"zh": "SSH 密钥设置成功"
|
||||
},
|
||||
"SSH key setup skipped (no key provided)": {
|
||||
"en": "SSH key setup skipped (no key provided)",
|
||||
"bg": "Настройката на SSH ключ е пропусната (не е предоставен ключ)",
|
||||
"de": "SSH-Schlüssel-Setup übersprungen (kein Schlüssel bereitgestellt)",
|
||||
"en": "SSH key setup skipped (no key provided)",
|
||||
"pl": "Pominięto konfigurację klucza SSH (brak klucza)",
|
||||
"ru": "Настройка SSH-ключа пропущена (ключ не предоставлен)",
|
||||
"zh": "SSH 密钥设置已跳过(未提供密钥)"
|
||||
},
|
||||
"SSH_PRIVATE_KEY not set — skipping SSH key setup": {
|
||||
"en": "SSH_PRIVATE_KEY not set — skipping SSH key setup",
|
||||
"bg": "SSH_PRIVATE_KEY не е зададен — пропускане на SSH ключ настройката",
|
||||
"de": "SSH_PRIVATE_KEY nicht gesetzt — SSH-Schlüssel-Setup übersprungen",
|
||||
"en": "SSH_PRIVATE_KEY not set — skipping SSH key setup",
|
||||
"pl": "SSH_PRIVATE_KEY nie ustawione — pomijanie konfiguracji klucza SSH",
|
||||
"ru": "SSH_PRIVATE_KEY не задан — пропуск настройки SSH-ключа",
|
||||
"zh": "SSH_PRIVATE_KEY 未设置 — 跳过 SSH 密钥设置"
|
||||
@@ -2855,6 +2903,22 @@
|
||||
"ru": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.",
|
||||
"zh": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls."
|
||||
},
|
||||
"Test isolation check FAILED: {count} violation(s) found in {files} test file(s).": {
|
||||
"bg": "Проверката за изолация на тестове НЕ ПРЕМИНА: открити са {count} нарушения в {files} тестови файла.",
|
||||
"de": "Testisolationsprüfung FEHLGESCHLAGEN: {count} Verstoß/Verstöße in {files} Testdatei(en) gefunden.",
|
||||
"en": "Test isolation check FAILED: {count} violation(s) found in {files} test file(s).",
|
||||
"pl": "Sprawdzenie izolacji testów NIE ZALICZONE: znaleziono {count} naruszeń w {files} plikach testowych.",
|
||||
"ru": "Проверка изоляции тестов НЕ ПРОЙДЕНА: найдено {count} нарушений в {files} тестовых файлах.",
|
||||
"zh": "测试隔离检查失败:在 {files} 个测试文件中发现 {count} 个违规。"
|
||||
},
|
||||
"Test isolation check passed: {count} test files analyzed, no violations found.": {
|
||||
"bg": "Проверката за изолация на тестове премина: анализирани са {count} тестови файла, няма нарушения.",
|
||||
"de": "Testisolationsprüfung bestanden: {count} Testdateien analysiert, keine Verstöße gefunden.",
|
||||
"en": "Test isolation check passed: {count} test files analyzed, no violations found.",
|
||||
"pl": "Sprawdzenie izolacji testów zaliczone: przeanalizowano {count} plików testowych, brak naruszeń.",
|
||||
"ru": "Проверка изоляции тестов пройдена: проанализировано {count} тестовых файлов, нарушений не найдено.",
|
||||
"zh": "测试隔离检查通过:已分析 {count} 个测试文件,未发现违规。"
|
||||
},
|
||||
"Tests failed — refusing to release. Fix test failures first.\n{stderr}": {
|
||||
"bg": "Tests failed — refusing to release. Fix test failures first.\n{stderr}",
|
||||
"de": "Tests failed — refusing to release. Fix test failures first.\n{stderr}",
|
||||
@@ -2936,9 +3000,9 @@
|
||||
"zh": "Updated {changelog_file}"
|
||||
},
|
||||
"Use string comparison or _is_truthy()/_is_falsy() helpers instead. Add '{marker}' to suppress individual lines.": {
|
||||
"en": "Use string comparison or _is_truthy()/_is_falsy() helpers instead. Add '{marker}' to suppress individual lines.",
|
||||
"bg": "Използвайте сравнение на низове или _is_truthy()/_is_falsy() помощници. Добавете '{marker}' за потискане на отделни редове.",
|
||||
"de": "Verwenden Sie String-Vergleich oder _is_truthy()/_is_falsy() Hilfsfunktionen. Fügen Sie '{marker}' hinzu, um einzelne Zeilen zu unterdrücken.",
|
||||
"en": "Use string comparison or _is_truthy()/_is_falsy() helpers instead. Add '{marker}' to suppress individual lines.",
|
||||
"pl": "Użyj porównania ciągów lub pomocników _is_truthy()/_is_falsy(). Dodaj '{marker}', aby pominąć pojedyncze linie.",
|
||||
"ru": "Используйте строковое сравнение или помощники _is_truthy()/_is_falsy(). Добавьте '{marker}' для подавления отдельных строк.",
|
||||
"zh": "使用字符串比较或 _is_truthy()/_is_falsy() 辅助函数。添加 '{marker}' 以抑制个别行。"
|
||||
@@ -2991,6 +3055,14 @@
|
||||
"ru": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.",
|
||||
"zh": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update."
|
||||
},
|
||||
"Vikunja task title '{title}' starts with '{prefix}:'. The task title should NOT include the '{prefix}' prefix — it is automatically added to the PR title. Update the Vikunja task title to remove the prefix.": {
|
||||
"bg": "Заглавието на задачата във Vikunja '{title}' започва с '{prefix}:'. Заглавието на задачата НЕ трябва да съдържа префикса '{prefix}' — той се добавя автоматично към заглавието на PR. Актуализирайте заглавието на задачата във Vikunja, за да премахнете префикса.",
|
||||
"de": "Der Vikunja-Aufgabentitel '{title}' beginnt mit '{prefix}:'. Der Aufgabentitel darf NICHT den Präfix '{prefix}' enthalten — er wird automatisch zum PR-Titel hinzugefügt. Aktualisieren Sie den Vikunja-Aufgabentitel, um den Präfix zu entfernen.",
|
||||
"en": "Vikunja task title '{title}' starts with '{prefix}:'. The task title should NOT include the '{prefix}' prefix — it is automatically added to the PR title. Update the Vikunja task title to remove the prefix.",
|
||||
"pl": "Tytuł zadania Vikunja '{title}' zaczyna się od '{prefix}:'. Tytuł zadania nie powinien zawierać prefiksu '{prefix}' — jest on automatycznie dodawany do tytułu PR. Zaktualizuj tytuł zadania Vikunja, aby usunąć prefiks.",
|
||||
"ru": "Заголовок задачи Vikunja '{title}' начинается с '{prefix}:'. Заголовок задачи НЕ должен включать префикс '{prefix}' — он автоматически добавляется к заголовку PR. Обновите заголовок задачи Vikunja, чтобы удалить префикс.",
|
||||
"zh": "Vikunja 任务标题 '{title}' 以 '{prefix}:' 开头。任务标题不应包含 '{prefix}' 前缀 — 它会自动添加到 PR 标题中。请更新 Vikunja 任务标题以删除前缀。"
|
||||
},
|
||||
"Vikunja task {task_id} not found in project {project_id}.\n Create it first:\n python -m devx.tools.create_task --title \"Task title\"\n Or check that the task ID in the branch name is correct.": {
|
||||
"bg": "Vikunja задача {task_id} не е намерена в проект {project_id}.\n Създайте я първо:\n python -m devx.tools.create_task --title \"Заглавие на задача\"\n Или проверете че ID на задачата в името на клона е правилно.",
|
||||
"de": "Vikunja-Task {task_id} in Projekt {project_id} nicht gefunden.\n Zuerst erstellen:\n python -m devx.tools.create_task --title \"Task-Titel\"\n Oder prüfen, ob die Task-ID im Branch-Namen korrekt ist.",
|
||||
@@ -3000,33 +3072,33 @@
|
||||
"zh": "在项目 {project_id} 中找不到 Vikunja 任务 {task_id}。\n 请先创建:\n python -m devx.tools.create_task --title \"任务标题\"\n 或检查分支名称中的任务 ID 是否正确。"
|
||||
},
|
||||
"WARN: .venv has Python {version}, but >={req} is required.": {
|
||||
"en": "WARN: .venv has Python {version}, but >={req} is required.",
|
||||
"bg": "ПРЕДУПРЕЖДЕНИЕ: .venv има Python {version}, но се изисква >={req}.",
|
||||
"de": "WARNUNG: .venv hat Python {version}, aber >={req} ist erforderlich.",
|
||||
"en": "WARN: .venv has Python {version}, but >={req} is required.",
|
||||
"pl": "OSTRZEŻENIE: .venv ma Python {version}, ale wymagane jest >={req}.",
|
||||
"ru": "ПРЕДУПРЕЖДЕНИЕ: в .venv установлен Python {version}, но требуется >={req}.",
|
||||
"zh": "警告: .venv 的 Python 版本为 {version},但要求 >={req}。"
|
||||
},
|
||||
"WARN: .venv not found. Run 'make setup-venv' to create it.": {
|
||||
"en": "WARN: .venv not found. Run 'make setup-venv' to create it.",
|
||||
"bg": "ПРЕДУПРЕЖДЕНИЕ: .venv не е намерен. Изпълнете 'make setup-venv' за създаване.",
|
||||
"de": "WARNUNG: .venv nicht gefunden. Führen Sie 'make setup-venv' aus, um es zu erstellen.",
|
||||
"en": "WARN: .venv not found. Run 'make setup-venv' to create it.",
|
||||
"pl": "OSTRZEŻENIE: Nie znaleziono .venv. Uruchom 'make setup-venv', aby utworzyć.",
|
||||
"ru": "ПРЕДУПРЕЖДЕНИЕ: .venv не найден. Выполните 'make setup-venv' для создания.",
|
||||
"zh": "警告: 未找到 .venv。运行 'make setup-venv' 来创建。"
|
||||
},
|
||||
"WARN: Could not determine Python version in .venv.": {
|
||||
"en": "WARN: Could not determine Python version in .venv.",
|
||||
"bg": "ПРЕДУПРЕЖДЕНИЕ: Не може да се определи версията на Python в .venv.",
|
||||
"de": "WARNUNG: Python-Version in .venv konnte nicht bestimmt werden.",
|
||||
"en": "WARN: Could not determine Python version in .venv.",
|
||||
"pl": "OSTRZEŻENIE: Nie można określić wersji Python w .venv.",
|
||||
"ru": "ПРЕДУПРЕЖДЕНИЕ: Не удалось определить версию Python в .venv.",
|
||||
"zh": "警告: 无法确定 .venv 中的 Python 版本。"
|
||||
},
|
||||
"WARN: Could not parse Python version '{version}'.": {
|
||||
"en": "WARN: Could not parse Python version '{version}'.",
|
||||
"bg": "ПРЕДУПРЕЖДЕНИЕ: Не може да се анализира версията на Python '{version}'.",
|
||||
"de": "WARNUNG: Python-Version '{version}' konnte nicht analysiert werden.",
|
||||
"en": "WARN: Could not parse Python version '{version}'.",
|
||||
"pl": "OSTRZEŻENIE: Nie można przeanalizować wersji Python '{version}'.",
|
||||
"ru": "ПРЕДУПРЕЖДЕНИЕ: Не удалось разобрать версию Python '{version}'.",
|
||||
"zh": "警告: 无法解析 Python 版本 '{version}'。"
|
||||
@@ -3175,6 +3247,14 @@
|
||||
"ru": "",
|
||||
"zh": ""
|
||||
},
|
||||
"Write deploy-ref to $GITHUB_OUTPUT file.": {
|
||||
"bg": "Запиши deploy-ref в $GITHUB_OUTPUT файла.",
|
||||
"de": "Deploy-ref in $GITHUB_OUTPUT-Datei schreiben.",
|
||||
"en": "Write deploy-ref to $GITHUB_OUTPUT file.",
|
||||
"pl": "Zapisz deploy-ref do pliku $GITHUB_OUTPUT.",
|
||||
"ru": "Записать deploy-ref в файл $GITHUB_OUTPUT.",
|
||||
"zh": "将 deploy-ref 写入 $GITHUB_OUTPUT 文件。"
|
||||
},
|
||||
"Wrote tag {tag} to GITHUB_OUTPUT.": {
|
||||
"bg": "Wrote tag {tag} to GITHUB_OUTPUT.",
|
||||
"de": "Wrote tag {tag} to GITHUB_OUTPUT.",
|
||||
@@ -3184,9 +3264,9 @@
|
||||
"zh": "Wrote tag {tag} to GITHUB_OUTPUT."
|
||||
},
|
||||
"[check-api-identity-checks] Passed: no unsafe identity checks found": {
|
||||
"en": "[check-api-identity-checks] Passed: no unsafe identity checks found",
|
||||
"bg": "[check-api-identity-checks] Мина: не са намерени небрежни проверки за идентичност",
|
||||
"de": "[check-api-identity-checks] Bestanden: keine unsicheren Identitätsprüfungen gefunden",
|
||||
"en": "[check-api-identity-checks] Passed: no unsafe identity checks found",
|
||||
"pl": "[check-api-identity-checks] Passed: nie znaleziono niebezpiecznych sprawdzeń tożsamości",
|
||||
"ru": "[check-api-identity-checks] Пройдено: небезопасных проверок идентичности не найдено",
|
||||
"zh": "[check-api-identity-checks] 通过:未发现不安全的身份检查"
|
||||
@@ -3200,25 +3280,25 @@
|
||||
"zh": "[check-dep-docs] Passed: all dependencies are documented"
|
||||
},
|
||||
"[check-deps] All core tools present.": {
|
||||
"en": "[check-deps] All core tools present.",
|
||||
"bg": "[check-deps] Всички основни инструменти са налични.",
|
||||
"de": "[check-deps] Alle Kernwerkzeuge vorhanden.",
|
||||
"en": "[check-deps] All core tools present.",
|
||||
"pl": "[check-deps] Wszystkie podstawowe narzędzia są dostępne.",
|
||||
"ru": "[check-deps] Все основные инструменты доступны.",
|
||||
"zh": "[check-deps] 所有核心工具均已就绪。"
|
||||
},
|
||||
"[check-deps] Verifying tools...": {
|
||||
"en": "[check-deps] Verifying tools...",
|
||||
"bg": "[check-deps] Проверка на инструментите...",
|
||||
"de": "[check-deps] Werkzeuge werden überprüft...",
|
||||
"en": "[check-deps] Verifying tools...",
|
||||
"pl": "[check-deps] Sprawdzanie narzędzi...",
|
||||
"ru": "[check-deps] Проверка инструментов...",
|
||||
"zh": "[check-deps] 正在验证工具..."
|
||||
},
|
||||
"[check-deps] Virtualenv .venv ready (Python {version}).": {
|
||||
"en": "[check-deps] Virtualenv .venv ready (Python {version}).",
|
||||
"bg": "[check-deps] Виртуална среда .venv готова (Python {version}).",
|
||||
"de": "[check-deps] Virtuelle Umgebung .venv bereit (Python {version}).",
|
||||
"en": "[check-deps] Virtualenv .venv ready (Python {version}).",
|
||||
"pl": "[check-deps] Środowisko wirtualne .venv gotowe (Python {version}).",
|
||||
"ru": "[check-deps] Виртуальное окружение .venv готово (Python {version}).",
|
||||
"zh": "[check-deps] 虚拟环境 .venv 已就绪 (Python {version})。"
|
||||
@@ -3248,25 +3328,25 @@
|
||||
"zh": "[check_test_coverage] No changed files to check."
|
||||
},
|
||||
"[docker-login] Logged in to {registry}.": {
|
||||
"en": "[docker-login] Logged in to {registry}.",
|
||||
"bg": "[docker-login] Влязъл в {registry}.",
|
||||
"de": "[docker-login] Angemeldet bei {registry}.",
|
||||
"en": "[docker-login] Logged in to {registry}.",
|
||||
"pl": "[docker-login] Zalogowano do {registry}.",
|
||||
"ru": "[docker-login] Выполнен вход в {registry}.",
|
||||
"zh": "[docker-login] 已登录到 {registry}。"
|
||||
},
|
||||
"[docker-login] Login to {registry} failed (continuing).": {
|
||||
"en": "[docker-login] Login to {registry} failed (continuing).",
|
||||
"bg": "[docker-login] Влизането в {registry} не успя (продължава).",
|
||||
"de": "[docker-login] Anmeldung bei {registry} fehlgeschlagen (wird fortgesetzt).",
|
||||
"en": "[docker-login] Login to {registry} failed (continuing).",
|
||||
"pl": "[docker-login] Logowanie do {registry} nie powiodło się (kontynuowanie).",
|
||||
"ru": "[docker-login] Ошибка входа в {registry} (продолжаем).",
|
||||
"zh": "[docker-login] 登录 {registry} 失败(继续)。"
|
||||
},
|
||||
"[docker-login] Skipping {registry} (token {env} not set).": {
|
||||
"en": "[docker-login] Skipping {registry} (token {env} not set).",
|
||||
"bg": "[docker-login] Пропускане на {registry} (токен {env} не е зададен).",
|
||||
"de": "[docker-login] {registry} übersprungen (Token {env} nicht gesetzt).",
|
||||
"en": "[docker-login] Skipping {registry} (token {env} not set).",
|
||||
"pl": "[docker-login] Pomijanie {registry} (token {env} nie ustawiony).",
|
||||
"ru": "[docker-login] Пропуск {registry} (токен {env} не задан).",
|
||||
"zh": "[docker-login] 跳过 {registry}(未设置令牌 {env})。"
|
||||
@@ -3344,33 +3424,33 @@
|
||||
"zh": "[dry-run] Would update {init}"
|
||||
},
|
||||
"[tofu-init] Done.": {
|
||||
"en": "[tofu-init] Done.",
|
||||
"bg": "[tofu-init] Готово.",
|
||||
"de": "[tofu-init] Fertig.",
|
||||
"en": "[tofu-init] Done.",
|
||||
"pl": "[tofu-init] Gotowe.",
|
||||
"ru": "[tofu-init] Готово.",
|
||||
"zh": "[tofu-init] 完成。"
|
||||
},
|
||||
"[tofu-init] Initializing {dir}...": {
|
||||
"en": "[tofu-init] Initializing {dir}...",
|
||||
"bg": "[tofu-init] Инициализиране на {dir}...",
|
||||
"de": "[tofu-init] Initialisiere {dir}...",
|
||||
"en": "[tofu-init] Initializing {dir}...",
|
||||
"pl": "[tofu-init] Inicjalizacja {dir}...",
|
||||
"ru": "[tofu-init] Инициализация {dir}...",
|
||||
"zh": "[tofu-init] 正在初始化 {dir}..."
|
||||
},
|
||||
"[tofu-{mode}] All configurations valid.": {
|
||||
"en": "[tofu-{mode}] All configurations valid.",
|
||||
"bg": "[tofu-{mode}] Всички конфигурации са валидни.",
|
||||
"de": "[tofu-{mode}] Alle Konfigurationen gültig.",
|
||||
"en": "[tofu-{mode}] All configurations valid.",
|
||||
"pl": "[tofu-{mode}] Wszystkie konfiguracje są poprawne.",
|
||||
"ru": "[tofu-{mode}] Все конфигурации валидны.",
|
||||
"zh": "[tofu-{mode}] 所有配置有效。"
|
||||
},
|
||||
"[tofu-{mode}] Validating OpenTofu configurations...": {
|
||||
"en": "[tofu-{mode}] Validating OpenTofu configurations...",
|
||||
"bg": "[tofu-{mode}] Проверка на OpenTofu конфигурациите...",
|
||||
"de": "[tofu-{mode}] Validiere OpenTofu-Konfigurationen...",
|
||||
"en": "[tofu-{mode}] Validating OpenTofu configurations...",
|
||||
"pl": "[tofu-{mode}] Sprawdzanie konfiguracji OpenTofu...",
|
||||
"ru": "[tofu-{mode}] Проверка конфигураций OpenTofu...",
|
||||
"zh": "[tofu-{mode}] 正在验证 OpenTofu 配置..."
|
||||
@@ -3527,10 +3607,18 @@
|
||||
"ru": "tea not installed — skipping login configuration.",
|
||||
"zh": "tea not installed — skipping login configuration."
|
||||
},
|
||||
"time.sleep called in test '{test}' without @patch — this causes real wall-clock delays. Add @patch(\"<module>.time.sleep\").": {
|
||||
"bg": "time.sleep извикано в тест '{test}' без @patch — това причинява реални забавяния. Добавете @patch(\"<module>.time.sleep\").",
|
||||
"de": "time.sleep in Test '{test}' ohne @patch aufgerufen — dies verursacht echte Wanduhr-Verzögerungen. @patch(\"<module>.time.sleep\") hinzufügen.",
|
||||
"en": "time.sleep called in test '{test}' without @patch — this causes real wall-clock delays. Add @patch(\"<module>.time.sleep\").",
|
||||
"pl": "time.sleep wywołane w teście '{test}' bez @patch — to powoduje rzeczywiste opóźnienia. Dodaj @patch(\"<module>.time.sleep\").",
|
||||
"ru": "time.sleep вызвано в тесте '{test}' без @patch — это вызывает реальные задержки. Добавьте @patch(\"<module>.time.sleep\").",
|
||||
"zh": "time.sleep 在测试 '{test}' 中被调用但没有 @patch — 这会导致真实的挂钟延迟。请添加 @patch(\"<module>.time.sleep\")。"
|
||||
},
|
||||
"tofu command failed in {dir}: {error}": {
|
||||
"en": "tofu command failed in {dir}: {error}",
|
||||
"bg": "командата tofu не успя в {dir}: {error}",
|
||||
"de": "tofu-Befehl fehlgeschlagen in {dir}: {error}",
|
||||
"en": "tofu command failed in {dir}: {error}",
|
||||
"pl": "polecenie tofu nie powiodło się w {dir}: {error}",
|
||||
"ru": "команда tofu не удалась в {dir}: {error}",
|
||||
"zh": "tofu 命令在 {dir} 中失败: {error}"
|
||||
@@ -3543,18 +3631,26 @@
|
||||
"ru": "неизвестно",
|
||||
"zh": "未知"
|
||||
},
|
||||
"{call} called in test '{test}' without @patch — this spawns a real subprocess. Add @patch(\"<module>.subprocess.run\") or patch the calling function.": {
|
||||
"bg": "{call} извикано в тест '{test}' без @patch — това стартира реален subprocess. Добавете @patch(\"<module>.subprocess.run\") или patch-нете извикващата функция.",
|
||||
"de": "{call} in Test '{test}' ohne @patch aufgerufen — dies startet einen echten subprocess. @patch(\"<module>.subprocess.run\") hinzufügen oder die aufrufende Funktion patchen.",
|
||||
"en": "{call} called in test '{test}' without @patch — this spawns a real subprocess. Add @patch(\"<module>.subprocess.run\") or patch the calling function.",
|
||||
"pl": "{call} wywołane w teście '{test}' bez @patch — to uruchamia rzeczywisty subprocess. Dodaj @patch(\"<module>.subprocess.run\") lub patchuj wywołującą funkcję.",
|
||||
"ru": "{call} вызвано в тесте '{test}' без @patch — это запускает реальный subprocess. Добавьте @patch(\"<module>.subprocess.run\") или patch вызывающую функцию.",
|
||||
"zh": "{call} 在测试 '{test}' 中被调用但没有 @patch — 这会启动真实的子进程。请添加 @patch(\"<module>.subprocess.run\") 或 patch 调用函数。"
|
||||
},
|
||||
"{env} is not set. Set it in your .env file or pass it as an environment variable.": {
|
||||
"en": "{env} is not set. Set it in your .env file or pass it as an environment variable.",
|
||||
"bg": "{env} не е зададен. Задайте го във вашия .env файл или го подайте като променлива на средата.",
|
||||
"de": "{env} ist nicht gesetzt. Setzen Sie es in Ihrer .env-Datei oder übergeben Sie es als Umgebungsvariable.",
|
||||
"en": "{env} is not set. Set it in your .env file or pass it as an environment variable.",
|
||||
"pl": "{env} nie jest ustawiony. Ustaw go w pliku .env lub przekaż jako zmienną środowiskową.",
|
||||
"ru": "{env} не задан. Установите его в файле .env или передайте как переменную окружения.",
|
||||
"zh": "{env} 未设置。请在 .env 文件中设置或作为环境变量传递。"
|
||||
},
|
||||
"{env} is not set. Set it in your .env file.": {
|
||||
"en": "{env} is not set. Set it in your .env file.",
|
||||
"bg": "{env} не е зададен. Задайте го във вашия .env файл.",
|
||||
"de": "{env} ist nicht gesetzt. Setzen Sie es in Ihrer .env-Datei.",
|
||||
"en": "{env} is not set. Set it in your .env file.",
|
||||
"pl": "{env} nie jest ustawiony. Ustaw go w pliku .env.",
|
||||
"ru": "{env} не задан. Установите его в файле .env.",
|
||||
"zh": "{env} 未设置。请在 .env 文件中设置。"
|
||||
@@ -3567,10 +3663,18 @@
|
||||
"ru": "{file} already exists. Use --force to overwrite.",
|
||||
"zh": "{file} already exists. Use --force to overwrite."
|
||||
},
|
||||
"{func} called in test '{test}' without @patch — this function {desc}. Add @patch(\"<module>.{func}\").": {
|
||||
"bg": "{func} извикано в тест '{test}' без @patch — тази функция {desc}. Добавете @patch(\"<module>.{func}\").",
|
||||
"de": "{func} in Test '{test}' ohne @patch aufgerufen — diese Funktion {desc}. @patch(\"<module>.{func}\") hinzufügen.",
|
||||
"en": "{func} called in test '{test}' without @patch — this function {desc}. Add @patch(\"<module>.{func}\").",
|
||||
"pl": "{func} wywołane w teście '{test}' bez @patch — ta funkcja {desc}. Dodaj @patch(\"<module>.{func}\").",
|
||||
"ru": "{func} вызвано в тесте '{test}' без @patch — эта функция {desc}. Добавьте @patch(\"<module>.{func}\").",
|
||||
"zh": "{func} 在测试 '{test}' 中被调用但没有 @patch — 此函数 {desc}。请添加 @patch(\"<module>.{func}\")。"
|
||||
},
|
||||
"{level}: {tool} not found.{hint}": {
|
||||
"en": "{level}: {tool} not found.{hint}",
|
||||
"bg": "{level}: {tool} не е намерен.{hint}",
|
||||
"de": "{level}: {tool} nicht gefunden.{hint}",
|
||||
"en": "{level}: {tool} not found.{hint}",
|
||||
"pl": "{level}: {tool} nie znaleziono.{hint}",
|
||||
"ru": "{level}: {tool} не найден.{hint}",
|
||||
"zh": "{level}: 未找到 {tool}。{hint}"
|
||||
@@ -3582,45 +3686,5 @@
|
||||
"pl": "{separator}",
|
||||
"ru": "{separator}",
|
||||
"zh": "{separator}"
|
||||
},
|
||||
"Allow empty tag (PR mode where SHA is concrete).": {
|
||||
"bg": "Позволи празен таг (PR режим, където SHA е конкретен).",
|
||||
"de": "Leeren Tag zulassen (PR-Modus, in dem SHA konkret ist).",
|
||||
"en": "Allow empty tag (PR mode where SHA is concrete).",
|
||||
"pl": "Zezwalaj na pusty tag (tryb PR, w którym SHA jest konkretne).",
|
||||
"ru": "Разрешить пустой тег (режим PR, где SHA конкретен).",
|
||||
"zh": "允许空标签(SHA 为具体值的 PR 模式)。"
|
||||
},
|
||||
"Git tag or ref that was deployed": {
|
||||
"bg": "Git таг или референция, която беше разгърната",
|
||||
"de": "Git-Tag oder Ref, der bereitgestellt wurde",
|
||||
"en": "Git tag or ref that was deployed",
|
||||
"pl": "Tag Git lub ref, który został wdrożony",
|
||||
"ru": "Git-тег или ссылка, которые были развёрнуты",
|
||||
"zh": "已部署的 Git 标签或引用"
|
||||
},
|
||||
"Git tag to deploy (e.g. v0.28.1).": {
|
||||
"bg": "Git таг за разгръщане (напр. v0.28.1).",
|
||||
"de": "Git-Tag für Bereitstellung (z.B. v0.28.1).",
|
||||
"en": "Git tag to deploy (e.g. v0.28.1).",
|
||||
"pl": "Tag Git do wdrożenia (np. v0.28.1).",
|
||||
"ru": "Git-тег для развёртывания (напр. v0.28.1).",
|
||||
"zh": "要部署的 Git 标签(例如 v0.28.1)。"
|
||||
},
|
||||
"Write deploy-ref to $GITHUB_OUTPUT file.": {
|
||||
"bg": "Запиши deploy-ref в $GITHUB_OUTPUT файла.",
|
||||
"de": "Deploy-ref in $GITHUB_OUTPUT-Datei schreiben.",
|
||||
"en": "Write deploy-ref to $GITHUB_OUTPUT file.",
|
||||
"pl": "Zapisz deploy-ref do pliku $GITHUB_OUTPUT.",
|
||||
"ru": "Записать deploy-ref в файл $GITHUB_OUTPUT.",
|
||||
"zh": "将 deploy-ref 写入 $GITHUB_OUTPUT 文件。"
|
||||
},
|
||||
"Vikunja task title '{title}' starts with '{prefix}:'. The task title should NOT include the '{prefix}' prefix — it is automatically added to the PR title. Update the Vikunja task title to remove the prefix.": {
|
||||
"bg": "Заглавието на задачата във Vikunja '{title}' започва с '{prefix}:'. Заглавието на задачата НЕ трябва да съдържа префикса '{prefix}' — той се добавя автоматично към заглавието на PR. Актуализирайте заглавието на задачата във Vikunja, за да премахнете префикса.",
|
||||
"de": "Der Vikunja-Aufgabentitel '{title}' beginnt mit '{prefix}:'. Der Aufgabentitel darf NICHT den Präfix '{prefix}' enthalten — er wird automatisch zum PR-Titel hinzugefügt. Aktualisieren Sie den Vikunja-Aufgabentitel, um den Präfix zu entfernen.",
|
||||
"en": "Vikunja task title '{title}' starts with '{prefix}:'. The task title should NOT include the '{prefix}' prefix — it is automatically added to the PR title. Update the Vikunja task title to remove the prefix.",
|
||||
"pl": "Tytuł zadania Vikunja '{title}' zaczyna się od '{prefix}:'. Tytuł zadania nie powinien zawierać prefiksu '{prefix}' — jest on automatycznie dodawany do tytułu PR. Zaktualizuj tytuł zadania Vikunja, aby usunąć prefiks.",
|
||||
"ru": "Заголовок задачи Vikunja '{title}' начинается с '{prefix}:'. Заголовок задачи НЕ должен включать префикс '{prefix}' — он автоматически добавляется к заголовку PR. Обновите заголовок задачи Vikunja, чтобы удалить префикс.",
|
||||
"zh": "Vikunja 任务标题 '{title}' 以 '{prefix}:' 开头。任务标题不应包含 '{prefix}' 前缀 — 它会自动添加到 PR 标题中。请更新 Vikunja 任务标题以删除前缀。"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user