Compare commits

...
2 Commits
Author SHA1 Message Date
devx-ci-bot 9a60009d29 release: v0.7.0 [skip ci] 2026-06-23 18:26:54 +02:00
emil c20dfd185a DEVX-13: feat: add per-test timing quality gate to check_test_speed
Post-merge / detect-type (push) Successful in 12s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / configure-repo (push) Successful in 26s
Post-merge / release (push) Successful in 43s
Post-merge / vikunja (push) Successful in 16s
Post-merge / sync-wiki (push) Successful in 48s
Post-merge / badges (push) Successful in 58s
2026-06-23 16:25:50 +00:00
11 changed files with 1495 additions and 1226 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
PYTHONPATH: src PYTHONPATH: src
run: | run: |
. .venv/bin/activate . .venv/bin/activate
python3 -m devx.tools.check_test_speed --max-seconds 60 python3 -m devx.tools.check_test_speed --max-seconds 60 --max-single-seconds 2.0
- name: Documentation coverage check - name: Documentation coverage check
env: env:
PYTHONPATH: src PYTHONPATH: src
+1 -1
View File
@@ -1 +1 @@
DEVX-12 DEVX-13
+6
View File
@@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.7.0] - 2026-06-23
### Features
- Add per-test timing quality gate to check_test_speed
## [0.6.0] - 2026-06-23 ## [0.6.0] - 2026-06-23
### Features ### Features
+7 -1
View File
@@ -76,7 +76,13 @@ Validate commit messages for conventional commit format.
### `devx tools check-test-speed` ### `devx tools check-test-speed`
Run unit tests and enforce a maximum execution-time budget. Run unit tests and enforce execution-time budgets:
- **Total suite time** must not exceed `--max-seconds` (default: 10s).
- **Per-test time** — no individual test may exceed `--max-single-seconds` (default: 0.5s, 0 to disable).
```bash
python3 -m devx.tools.check_test_speed --max-seconds 10 --max-single-seconds 0.5
```
### `devx tools configure-repo` ### `devx tools configure-repo`
+4 -3
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# pre-commit hook: fail if unit tests take longer than 10 seconds. # pre-commit hook: fail if unit tests are too slow.
# Aligned with CI timeout (ci.yml uses --max-seconds 10). # Checks both total suite time (10s) and per-test time (0.5s).
# Aligned with CI (ci.yml uses same thresholds).
set -e set -e
export PYTHONPATH=src export PYTHONPATH=src
python3 -m devx.tools.check_test_speed --max-seconds 10 python3 -m devx.tools.check_test_speed --max-seconds 10 --max-single-seconds 0.5
+1 -1
View File
@@ -1,3 +1,3 @@
"""devx — reusable development and CI/CD tools for oblachno-oss projects.""" """devx — reusable development and CI/CD tools for oblachno-oss projects."""
__version__ = "0.6.0" __version__ = "0.7.0"
+95 -11
View File
@@ -1,12 +1,21 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Run unit tests and enforce a maximum execution-time budget. """Run unit tests and enforce execution-time budgets.
Checks two quality gates:
1. **Total suite time** must not exceed ``--max-seconds``.
2. **Per-test time** — no individual test may exceed ``--max-single-seconds``.
Usage: Usage:
python3 -m devx.tools.check_test_speed [--max-seconds N] python3 -m devx.tools.check_test_speed [--max-seconds N] [--max-single-seconds S]
The module runs ``make test-unit`` with ``PYTEST_ADDOPTS=--durations=0`` so
that pytest emits per-test timing lines alongside the summary. Both the
total wall-clock time and individual test durations are parsed and validated.
""" """
from __future__ import annotations from __future__ import annotations
import os
import re import re
import subprocess # nosec B404 import subprocess # nosec B404
@@ -14,18 +23,32 @@ import click
from devx.i18n import _ from devx.i18n import _
DEFAULT_MAX_SECONDS = 2.0 DEFAULT_MAX_SECONDS = 10.0
DEFAULT_MAX_SINGLE_SECONDS = 0.5
TEST_COMMAND = ["make", "test-unit"] TEST_COMMAND = ["make", "test-unit"]
# Matches pytest summary line: "234 passed in 0.70s"
_TIMING_RE = re.compile(r"(\d+) passed.* in ([0-9.]+)s") _TIMING_RE = re.compile(r"(\d+) passed.* in ([0-9.]+)s")
# Matches per-test duration lines from --durations=0:
# 0.51s call tests/test_foo.py::test_bar
_DURATION_LINE_RE = re.compile(r"^(\d+\.?\d*)s\s+(?:setup|call|teardown)\s+(.+)$")
def run_tests() -> tuple[str, str]: def run_tests() -> tuple[str, str]:
"""Execute the unit-test suite and return (stdout, stderr).""" """Execute the unit-test suite and return (stdout, stderr).
Sets ``PYTEST_ADDOPTS=--durations=0`` so pytest emits per-test timings.
"""
env = os.environ.copy()
existing = env.get("PYTEST_ADDOPTS", "")
env["PYTEST_ADDOPTS"] = f"--durations=0 {existing}".strip()
result = subprocess.run( # nosec B603 result = subprocess.run( # nosec B603
TEST_COMMAND, TEST_COMMAND,
capture_output=True, capture_output=True,
text=True, text=True,
check=False, check=False,
env=env,
) )
return result.stdout, result.stderr return result.stdout, result.stderr
@@ -43,8 +66,23 @@ def parse_duration(output: str) -> float:
raise click.ClickException(_("Could not parse test execution time from output.")) raise click.ClickException(_("Could not parse test execution time from output."))
def parse_per_test_durations(output: str) -> list[tuple[str, float]]:
"""Extract per-test timings from ``--durations=0`` output.
Returns a list of ``(test_name, seconds)`` tuples sorted by duration
(slowest first).
"""
durations: list[tuple[str, float]] = []
for line in output.splitlines():
match = _DURATION_LINE_RE.match(line.strip())
if match:
durations.append((match.group(2).strip(), float(match.group(1))))
durations.sort(key=lambda x: x[1], reverse=True)
return durations
def check_speed(duration: float, max_seconds: float) -> None: def check_speed(duration: float, max_seconds: float) -> None:
"""Validate duration is within budget; raise on violation.""" """Validate total duration is within budget; raise on violation."""
if duration > max_seconds: if duration > max_seconds:
raise click.ClickException( raise click.ClickException(
_( _(
@@ -57,19 +95,58 @@ def check_speed(duration: float, max_seconds: float) -> None:
) )
def main(max_seconds: float) -> None: def check_per_test_speed(
"""Run tests, parse timing, and enforce the budget.""" durations: list[tuple[str, float]],
max_single_seconds: float,
) -> list[str]:
"""Return a list of violation messages for tests exceeding the per-test limit.
An empty list means all tests are within budget.
"""
violations: list[str] = []
for name, elapsed in durations:
if elapsed > max_single_seconds:
violations.append(
_(
"Test '{name}' took {elapsed:.2f}s (limit: {limit}s). "
"Optimise: use lighter fixtures, reduce I/O, or mock external calls.",
name=name,
elapsed=elapsed,
limit=max_single_seconds,
)
)
return violations
def main(max_seconds: float, max_single_seconds: float) -> None:
"""Run tests, parse timings, and enforce both budgets."""
stdout, stderr = run_tests() stdout, stderr = run_tests()
combined = stdout + "\n" + stderr combined = stdout + "\n" + stderr
click.echo(combined, err=False) click.echo(combined, err=False)
duration = parse_duration(combined) duration = parse_duration(combined)
check_speed(duration, max_seconds) check_speed(duration, max_seconds)
if max_single_seconds > 0:
per_test = parse_per_test_durations(combined)
violations = check_per_test_speed(per_test, max_single_seconds)
if violations:
msg = _(
"Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.",
count=len(violations),
limit=max_single_seconds,
)
click.echo(f"\n{msg}", err=True)
for v in violations:
click.echo(f" - {v}", err=True)
raise click.ClickException(msg)
click.echo( click.echo(
_( _(
"Unit tests passed in {duration:.2f}s (under {max}s limit).", "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).",
duration=duration, duration=duration,
max=max_seconds, max=max_seconds,
single=max_single_seconds,
) )
) )
@@ -80,10 +157,17 @@ def main(max_seconds: float) -> None:
type=float, type=float,
default=DEFAULT_MAX_SECONDS, default=DEFAULT_MAX_SECONDS,
show_default=True, show_default=True,
help="Maximum allowed execution time in seconds.", help="Maximum allowed total execution time in seconds.",
) )
def cli(max_seconds: float) -> None: @click.option(
main(max_seconds) "--max-single-seconds",
type=float,
default=DEFAULT_MAX_SINGLE_SECONDS,
show_default=True,
help="Maximum allowed per-test time in seconds (0 to disable).",
)
def cli(max_seconds: float, max_single_seconds: float) -> None:
main(max_seconds, max_single_seconds)
if __name__ == "__main__": # pragma: no cover if __name__ == "__main__": # pragma: no cover
+1211 -1197
View File
@@ -1,1199 +1,1213 @@
{ {
"\n=== Summary ===": { "\n=== Summary ===": {
"en": "\n=== Summary ===", "en": "\n=== Summary ===",
"bg": "\n=== Summary ===", "bg": "\n=== Summary ===",
"de": "\n=== Summary ===", "de": "\n=== Summary ===",
"ru": "\n=== Summary ===", "ru": "\n=== Summary ===",
"zh": "\n=== Summary ===" "zh": "\n=== Summary ==="
}, },
"\nAll documentation coverage checks passed!": { "\nAll documentation coverage checks passed!": {
"en": "\nAll documentation coverage checks passed!", "en": "\nAll documentation coverage checks passed!",
"bg": "\nAll documentation coverage checks passed!", "bg": "\nAll documentation coverage checks passed!",
"de": "\nAll documentation coverage checks passed!", "de": "\nAll documentation coverage checks passed!",
"ru": "\nAll documentation coverage checks passed!", "ru": "\nAll documentation coverage checks passed!",
"zh": "\nAll documentation coverage checks passed!" "zh": "\nAll documentation coverage checks passed!"
}, },
"\nCHANGELOG version ordering:": { "\nCHANGELOG version ordering:": {
"en": "\nCHANGELOG version ordering:", "en": "\nCHANGELOG version ordering:",
"bg": "\nCHANGELOG version ordering:", "bg": "\nCHANGELOG version ordering:",
"de": "\nCHANGELOG version ordering:", "de": "\nCHANGELOG version ordering:",
"ru": "\nCHANGELOG version ordering:", "ru": "\nCHANGELOG version ordering:",
"zh": "\nCHANGELOG version ordering:" "zh": "\nCHANGELOG version ordering:"
}, },
"\nChecking CI script documentation in ci-cd-workflow.md...": { "\nChecking CI script documentation in ci-cd-workflow.md...": {
"en": "\nChecking CI script documentation in ci-cd-workflow.md...", "en": "\nChecking CI script documentation in ci-cd-workflow.md...",
"bg": "\nChecking CI script documentation in ci-cd-workflow.md...", "bg": "\nChecking CI script documentation in ci-cd-workflow.md...",
"de": "\nChecking CI script documentation in ci-cd-workflow.md...", "de": "\nChecking CI script documentation in ci-cd-workflow.md...",
"ru": "\nChecking CI script documentation in ci-cd-workflow.md...", "ru": "\nChecking CI script documentation in ci-cd-workflow.md...",
"zh": "\nChecking CI script documentation in ci-cd-workflow.md..." "zh": "\nChecking CI script documentation in ci-cd-workflow.md..."
}, },
"\nChecking module documentation in architecture.md...": { "\nChecking module documentation in architecture.md...": {
"en": "\nChecking module documentation in architecture.md...", "en": "\nChecking module documentation in architecture.md...",
"bg": "\nChecking module documentation in architecture.md...", "bg": "\nChecking module documentation in architecture.md...",
"de": "\nChecking module documentation in architecture.md...", "de": "\nChecking module documentation in architecture.md...",
"ru": "\nChecking module documentation in architecture.md...", "ru": "\nChecking module documentation in architecture.md...",
"zh": "\nChecking module documentation in architecture.md..." "zh": "\nChecking module documentation in architecture.md..."
}, },
"\nDoc coverage: {covered}/{total} ({pct}%)": { "\nDoc coverage: {covered}/{total} ({pct}%)": {
"en": "\nDoc coverage: {covered}/{total} ({pct}%)", "en": "\nDoc coverage: {covered}/{total} ({pct}%)",
"bg": "\nDoc coverage: {covered}/{total} ({pct}%)", "bg": "\nDoc coverage: {covered}/{total} ({pct}%)",
"de": "\nDoc coverage: {covered}/{total} ({pct}%)", "de": "\nDoc coverage: {covered}/{total} ({pct}%)",
"ru": "\nDoc coverage: {covered}/{total} ({pct}%)", "ru": "\nDoc coverage: {covered}/{total} ({pct}%)",
"zh": "\nDoc coverage: {covered}/{total} ({pct}%)" "zh": "\nDoc coverage: {covered}/{total} ({pct}%)"
}, },
"\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}": { "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}": {
"en": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}", "en": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}",
"bg": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}", "bg": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}",
"de": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}", "de": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}",
"ru": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}", "ru": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}",
"zh": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}" "zh": "\nDone! Created: {created}, Updated: {updated}, Skipped: {skipped}"
}, },
"\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.": { "\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.", "en": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.",
"bg": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.", "bg": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.",
"de": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.", "de": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.",
"ru": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.", "ru": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce.",
"zh": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce." "zh": "\nERROR: Documentation coverage is not 100%. Use --fail-on-missing to enforce."
}, },
"\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.": { "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.": {
"en": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.", "en": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.",
"bg": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.", "bg": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.",
"de": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.", "de": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.",
"ru": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.", "ru": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report.",
"zh": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report." "zh": "\nFix the misaligned tags before creating new releases. Run 'python3 -m devx.ci.release --verify' for a full report."
}, },
"\nIntegrity check FAILED ({count} issues):": { "\nIntegrity check FAILED ({count} issues):": {
"en": "\nIntegrity check FAILED ({count} issues):", "en": "\nIntegrity check FAILED ({count} issues):",
"bg": "\nIntegrity check FAILED ({count} issues):", "bg": "\nIntegrity check FAILED ({count} issues):",
"de": "\nIntegrity check FAILED ({count} issues):", "de": "\nIntegrity check FAILED ({count} issues):",
"ru": "\nIntegrity check FAILED ({count} issues):", "ru": "\nIntegrity check FAILED ({count} issues):",
"zh": "\nIntegrity check FAILED ({count} issues):" "zh": "\nIntegrity check FAILED ({count} issues):"
}, },
"\nIntegrity check passed — all {count} pages verified.": { "\nIntegrity check passed — all {count} pages verified.": {
"en": "\nIntegrity check passed — all {count} pages verified.", "en": "\nIntegrity check passed — all {count} pages verified.",
"bg": "\nIntegrity check passed — all {count} pages verified.", "bg": "\nIntegrity check passed — all {count} pages verified.",
"de": "\nIntegrity check passed — all {count} pages verified.", "de": "\nIntegrity check passed — all {count} pages verified.",
"ru": "\nIntegrity check passed — all {count} pages verified.", "ru": "\nIntegrity check passed — all {count} pages verified.",
"zh": "\nIntegrity check passed — all {count} pages verified." "zh": "\nIntegrity check passed — all {count} pages verified."
}, },
"\nLatest tag: {tag}": { "\nLatest tag: {tag}": {
"en": "\nLatest tag: {tag}", "en": "\nLatest tag: {tag}",
"bg": "\nLatest tag: {tag}", "bg": "\nLatest tag: {tag}",
"de": "\nLatest tag: {tag}", "de": "\nLatest tag: {tag}",
"ru": "\nLatest tag: {tag}", "ru": "\nLatest tag: {tag}",
"zh": "\nLatest tag: {tag}" "zh": "\nLatest tag: {tag}"
}, },
"\nMissing documentation:": { "\nMissing documentation:": {
"en": "\nMissing documentation:", "en": "\nMissing documentation:",
"bg": "\nMissing documentation:", "bg": "\nMissing documentation:",
"de": "\nMissing documentation:", "de": "\nMissing documentation:",
"ru": "\nMissing documentation:", "ru": "\nMissing documentation:",
"zh": "\nMissing documentation:" "zh": "\nMissing documentation:"
}, },
"\nResult: {status}": { "\nResult: {status}": {
"en": "\nResult: {status}", "en": "\nResult: {status}",
"bg": "\nResult: {status}", "bg": "\nResult: {status}",
"de": "\nResult: {status}", "de": "\nResult: {status}",
"ru": "\nResult: {status}", "ru": "\nResult: {status}",
"zh": "\nResult: {status}" "zh": "\nResult: {status}"
}, },
"\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).": { "\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).", "en": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).",
"bg": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).", "bg": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).",
"de": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).", "de": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).",
"ru": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).", "ru": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).",
"zh": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments)." "zh": "\nReview #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments)."
}, },
"\nRunning full wiki integrity check...": { "\nRunning full wiki integrity check...": {
"en": "\nRunning full wiki integrity check...", "en": "\nRunning full wiki integrity check...",
"bg": "\nRunning full wiki integrity check...", "bg": "\nRunning full wiki integrity check...",
"de": "\nRunning full wiki integrity check...", "de": "\nRunning full wiki integrity check...",
"ru": "\nRunning full wiki integrity check...", "ru": "\nRunning full wiki integrity check...",
"zh": "\nRunning full wiki integrity check..." "zh": "\nRunning full wiki integrity check..."
}, },
"\nTag → Commit alignment:": { "\nTag → Commit alignment:": {
"en": "\nTag → Commit alignment:", "en": "\nTag → Commit alignment:",
"bg": "\nTag → Commit alignment:", "bg": "\nTag → Commit alignment:",
"de": "\nTag → Commit alignment:", "de": "\nTag → Commit alignment:",
"ru": "\nTag → Commit alignment:", "ru": "\nTag → Commit alignment:",
"zh": "\nTag → Commit alignment:" "zh": "\nTag → Commit alignment:"
}, },
"\nUntagged release commits:": { "\nUntagged release commits:": {
"en": "\nUntagged release commits:", "en": "\nUntagged release commits:",
"bg": "\nUntagged release commits:", "bg": "\nUntagged release commits:",
"de": "\nUntagged release commits:", "de": "\nUntagged release commits:",
"ru": "\nUntagged release commits:", "ru": "\nUntagged release commits:",
"zh": "\nUntagged release commits:" "zh": "\nUntagged release commits:"
}, },
"\nUser-facing changes ({count}):": { "\nUser-facing changes ({count}):": {
"en": "\nUser-facing changes ({count}):", "en": "\nUser-facing changes ({count}):",
"bg": "\nUser-facing changes ({count}):", "bg": "\nUser-facing changes ({count}):",
"de": "\nUser-facing changes ({count}):", "de": "\nUser-facing changes ({count}):",
"ru": "\nUser-facing changes ({count}):", "ru": "\nUser-facing changes ({count}):",
"zh": "\nUser-facing changes ({count}):" "zh": "\nUser-facing changes ({count}):"
}, },
"\nVerification FAILED: {failures} page(s) have empty or mismatched content!": { "\nVerification FAILED: {failures} page(s) have empty or mismatched content!": {
"en": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!", "en": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!",
"bg": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!", "bg": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!",
"de": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!", "de": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!",
"ru": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!", "ru": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!",
"zh": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!" "zh": "\nVerification FAILED: {failures} page(s) have empty or mismatched content!"
}, },
"\nVerification passed — all wiki pages have correct content.": { "\nVerification passed — all wiki pages have correct content.": {
"en": "\nVerification passed — all wiki pages have correct content.", "en": "\nVerification passed — all wiki pages have correct content.",
"bg": "\nVerification passed — all wiki pages have correct content.", "bg": "\nVerification passed — all wiki pages have correct content.",
"de": "\nVerification passed — all wiki pages have correct content.", "de": "\nVerification passed — all wiki pages have correct content.",
"ru": "\nVerification passed — all wiki pages have correct content.", "ru": "\nVerification passed — all wiki pages have correct content.",
"zh": "\nVerification passed — all wiki pages have correct content." "zh": "\nVerification passed — all wiki pages have correct content."
}, },
"\nVerifying wiki pages have content...": { "\nVerifying wiki pages have content...": {
"en": "\nVerifying wiki pages have content...", "en": "\nVerifying wiki pages have content...",
"bg": "\nVerifying wiki pages have content...", "bg": "\nVerifying wiki pages have content...",
"de": "\nVerifying wiki pages have content...", "de": "\nVerifying wiki pages have content...",
"ru": "\nVerifying wiki pages have content...", "ru": "\nVerifying wiki pages have content...",
"zh": "\nVerifying wiki pages have content..." "zh": "\nVerifying wiki pages have content..."
}, },
"\nWorkflow-only changes ({count}):": { "\nWorkflow-only changes ({count}):": {
"en": "\nWorkflow-only changes ({count}):", "en": "\nWorkflow-only changes ({count}):",
"bg": "\nWorkflow-only changes ({count}):", "bg": "\nWorkflow-only changes ({count}):",
"de": "\nWorkflow-only changes ({count}):", "de": "\nWorkflow-only changes ({count}):",
"ru": "\nWorkflow-only changes ({count}):", "ru": "\nWorkflow-only changes ({count}):",
"zh": "\nWorkflow-only changes ({count}):" "zh": "\nWorkflow-only changes ({count}):"
}, },
"\n[dry-run] Changelog:\n{changelog}": { "\n[dry-run] Changelog:\n{changelog}": {
"en": "\n[dry-run] Changelog:\n{changelog}", "en": "\n[dry-run] Changelog:\n{changelog}",
"bg": "\n[dry-run] Changelog:\n{changelog}", "bg": "\n[dry-run] Changelog:\n{changelog}",
"de": "\n[dry-run] Changelog:\n{changelog}", "de": "\n[dry-run] Changelog:\n{changelog}",
"ru": "\n[dry-run] Changelog:\n{changelog}", "ru": "\n[dry-run] Changelog:\n{changelog}",
"zh": "\n[dry-run] Changelog:\n{changelog}" "zh": "\n[dry-run] Changelog:\n{changelog}"
}, },
"\n{label} files changed ({count}):": { "\n{label} files changed ({count}):": {
"en": "\n{label} files changed ({count}):", "en": "\n{label} files changed ({count}):",
"bg": "\n{label} files changed ({count}):", "bg": "\n{label} files changed ({count}):",
"de": "\n{label} files changed ({count}):", "de": "\n{label} files changed ({count}):",
"ru": "\n{label} files changed ({count}):", "ru": "\n{label} files changed ({count}):",
"zh": "\n{label} files changed ({count}):" "zh": "\n{label} files changed ({count}):"
}, },
"\n{tag} files ({count}):": { "\n{tag} files ({count}):": {
"en": "\n{tag} files ({count}):", "en": "\n{tag} files ({count}):",
"bg": "\n{tag} files ({count}):", "bg": "\n{tag} files ({count}):",
"de": "\n{tag} files ({count}):", "de": "\n{tag} files ({count}):",
"ru": "\n{tag} files ({count}):", "ru": "\n{tag} files ({count}):",
"zh": "\n{tag} files ({count}):" "zh": "\n{tag} files ({count}):"
}, },
" - Auto-delete branch after merge: yes": { " - Auto-delete branch after merge: yes": {
"en": " - Auto-delete branch after merge: yes", "en": " - Auto-delete branch after merge: yes",
"bg": " - Автоматично изтриване на клон след сливане: да", "bg": " - Автоматично изтриване на клон след сливане: да",
"de": " - Branch nach Merge automatisch löschen: ja", "de": " - Branch nach Merge automatisch löschen: ja",
"ru": " - Автоудаление ветки после слияния: да", "ru": " - Автоудаление ветки после слияния: да",
"zh": " - 合并后自动删除分支: 是" "zh": " - 合并后自动删除分支: 是"
}, },
" - Block outdated branches: yes": { " - Block outdated branches: yes": {
"en": " - Block outdated branches: yes", "en": " - Block outdated branches: yes",
"bg": " - Блокиране на остарели клонове: да", "bg": " - Блокиране на остарели клонове: да",
"de": " - Veraltete Branches blockieren: ja", "de": " - Veraltete Branches blockieren: ja",
"ru": " - Блокировать устаревшие ветки: да", "ru": " - Блокировать устаревшие ветки: да",
"zh": " - 阻止过时分支: 是" "zh": " - 阻止过时分支: 是"
}, },
" - Block rejected reviews: yes": { " - Block rejected reviews: yes": {
"en": " - Block rejected reviews: yes", "en": " - Block rejected reviews: yes",
"bg": " - Блокиране на отхвърлени рецензии: да", "bg": " - Блокиране на отхвърлени рецензии: да",
"de": " - Abgelehnte Reviews blockieren: ja", "de": " - Abgelehnte Reviews blockieren: ja",
"ru": " - Блокировать отклонённые ревью: да", "ru": " - Блокировать отклонённые ревью: да",
"zh": " - 阻止被拒绝的审查: 是" "zh": " - 阻止被拒绝的审查: 是"
}, },
" - Direct pushes: BLOCKED (require PR, whitelisted users can push)": { " - Direct pushes: BLOCKED (require PR, whitelisted users can push)": {
"en": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", "en": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)",
"bg": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", "bg": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)",
"de": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", "de": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)",
"ru": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)", "ru": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)",
"zh": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)" "zh": " - Direct pushes: BLOCKED (require PR, whitelisted users can push)"
}, },
" - Dismiss stale approvals: yes": { " - Dismiss stale approvals: yes": {
"en": " - Dismiss stale approvals: yes", "en": " - Dismiss stale approvals: yes",
"bg": " - Анулиране на остарели одобрения: да", "bg": " - Анулиране на остарели одобрения: да",
"de": " - Veraltete Genehmigungen ablehnen: ja", "de": " - Veraltete Genehmigungen ablehnen: ja",
"ru": " - Отклонять устаревшие одобрения: да", "ru": " - Отклонять устаревшие одобрения: да",
"zh": " - 忽略过时审批: 是" "zh": " - 忽略过时审批: 是"
}, },
" - Required approvals: {count}": { " - Required approvals: {count}": {
"en": " - Required approvals: {count}", "en": " - Required approvals: {count}",
"bg": " - Необходими одобрения: {count}", "bg": " - Необходими одобрения: {count}",
"de": " - Erforderliche Genehmigungen: {count}", "de": " - Erforderliche Genehmigungen: {count}",
"ru": " - Требуемые одобрения: {count}", "ru": " - Требуемые одобрения: {count}",
"zh": " - 必需审批数: {count}" "zh": " - 必需审批数: {count}"
}, },
" - Required status checks: {checks}": { " - Required status checks: {checks}": {
"en": " - Required status checks: {checks}", "en": " - Required status checks: {checks}",
"bg": " - Необходими проверки на състоянието: {checks}", "bg": " - Необходими проверки на състоянието: {checks}",
"de": " - Erforderliche Status-Checks: {checks}", "de": " - Erforderliche Status-Checks: {checks}",
"ru": " - Требуемые проверки статуса: {checks}", "ru": " - Требуемые проверки статуса: {checks}",
"zh": " - 必需状态检查: {checks}" "zh": " - 必需状态检查: {checks}"
}, },
" Created: {title}": { " Created: {title}": {
"en": " Created: {title}", "en": " Created: {title}",
"bg": " Created: {title}", "bg": " Created: {title}",
"de": " Created: {title}", "de": " Created: {title}",
"ru": " Created: {title}", "ru": " Created: {title}",
"zh": " Created: {title}" "zh": " Created: {title}"
}, },
" FAIL: {title} — content mismatch or empty!": { " FAIL: {title} — content mismatch or empty!": {
"en": " FAIL: {title} — content mismatch or empty!", "en": " FAIL: {title} — content mismatch or empty!",
"bg": " FAIL: {title} — content mismatch or empty!", "bg": " FAIL: {title} — content mismatch or empty!",
"de": " FAIL: {title} — content mismatch or empty!", "de": " FAIL: {title} — content mismatch or empty!",
"ru": " FAIL: {title} — content mismatch or empty!", "ru": " FAIL: {title} — content mismatch or empty!",
"zh": " FAIL: {title} — content mismatch or empty!" "zh": " FAIL: {title} — content mismatch or empty!"
}, },
" MISSING: devx {cmd}": { " MISSING: devx {cmd}": {
"en": " MISSING: devx {cmd}", "en": " MISSING: devx {cmd}",
"bg": " ЛИПСВА: devx {cmd}", "bg": " ЛИПСВА: devx {cmd}",
"de": " FEHLT: devx {cmd}", "de": " FEHLT: devx {cmd}",
"ru": " ОТСУТСТВУЕТ: devx {cmd}", "ru": " ОТСУТСТВУЕТ: devx {cmd}",
"zh": " 缺失: devx {cmd}" "zh": " 缺失: devx {cmd}"
}, },
" MISSING: {module}": { " MISSING: {module}": {
"en": " MISSING: {module}", "en": " MISSING: {module}",
"bg": " MISSING: {module}", "bg": " MISSING: {module}",
"de": " MISSING: {module}", "de": " MISSING: {module}",
"ru": " MISSING: {module}", "ru": " MISSING: {module}",
"zh": " MISSING: {module}" "zh": " MISSING: {module}"
}, },
" MISSING: {script}": { " MISSING: {script}": {
"en": " MISSING: {script}", "en": " MISSING: {script}",
"bg": " MISSING: {script}", "bg": " MISSING: {script}",
"de": " MISSING: {script}", "de": " MISSING: {script}",
"ru": " MISSING: {script}", "ru": " MISSING: {script}",
"zh": " MISSING: {script}" "zh": " MISSING: {script}"
}, },
" OK: devx {cmd}": { " OK: devx {cmd}": {
"en": " OK: devx {cmd}", "en": " OK: devx {cmd}",
"bg": " ОК: devx {cmd}", "bg": " ОК: devx {cmd}",
"de": " OK: devx {cmd}", "de": " OK: devx {cmd}",
"ru": " ОК: devx {cmd}", "ru": " ОК: devx {cmd}",
"zh": " 正常: devx {cmd}" "zh": " 正常: devx {cmd}"
}, },
" OK: {module}": { " OK: {module}": {
"en": " OK: {module}", "en": " OK: {module}",
"bg": " OK: {module}", "bg": " OK: {module}",
"de": " OK: {module}", "de": " OK: {module}",
"ru": " OK: {module}", "ru": " OK: {module}",
"zh": " OK: {module}" "zh": " OK: {module}"
}, },
" OK: {script}": { " OK: {script}": {
"en": " OK: {script}", "en": " OK: {script}",
"bg": " OK: {script}", "bg": " OK: {script}",
"de": " OK: {script}", "de": " OK: {script}",
"ru": " OK: {script}", "ru": " OK: {script}",
"zh": " OK: {script}" "zh": " OK: {script}"
}, },
" OK: {title} ({chars} chars)": { " OK: {title} ({chars} chars)": {
"en": " OK: {title} ({chars} chars)", "en": " OK: {title} ({chars} chars)",
"bg": " OK: {title} ({chars} chars)", "bg": " OK: {title} ({chars} chars)",
"de": " OK: {title} ({chars} chars)", "de": " OK: {title} ({chars} chars)",
"ru": " OK: {title} ({chars} chars)", "ru": " OK: {title} ({chars} chars)",
"zh": " OK: {title} ({chars} chars)" "zh": " OK: {title} ({chars} chars)"
}, },
" Updated: {title}": { " Updated: {title}": {
"en": " Updated: {title}", "en": " Updated: {title}",
"bg": " Updated: {title}", "bg": " Updated: {title}",
"de": " Updated: {title}", "de": " Updated: {title}",
"ru": " Updated: {title}", "ru": " Updated: {title}",
"zh": " Updated: {title}" "zh": " Updated: {title}"
}, },
"=== Release Alignment Verification ===\n": { "=== Release Alignment Verification ===\n": {
"en": "=== Release Alignment Verification ===\n", "en": "=== Release Alignment Verification ===\n",
"bg": "=== Release Alignment Verification ===\n", "bg": "=== Release Alignment Verification ===\n",
"de": "=== Release Alignment Verification ===\n", "de": "=== Release Alignment Verification ===\n",
"ru": "=== Release Alignment Verification ===\n", "ru": "=== Release Alignment Verification ===\n",
"zh": "=== Release Alignment Verification ===\n" "zh": "=== Release Alignment Verification ===\n"
}, },
"API poll warning: {exc}": { "API poll warning: {exc}": {
"en": "API poll warning: {exc}", "en": "API poll warning: {exc}",
"bg": "API poll warning: {exc}", "bg": "API poll warning: {exc}",
"de": "API poll warning: {exc}", "de": "API poll warning: {exc}",
"ru": "API poll warning: {exc}", "ru": "API poll warning: {exc}",
"zh": "API poll warning: {exc}" "zh": "API poll warning: {exc}"
}, },
"All molecule tests passed.": { "All molecule tests passed.": {
"en": "All molecule tests passed.", "en": "All molecule tests passed.",
"bg": "All molecule tests passed.", "bg": "All molecule tests passed.",
"de": "All molecule tests passed.", "de": "All molecule tests passed.",
"ru": "All molecule tests passed.", "ru": "All molecule tests passed.",
"zh": "All molecule tests passed." "zh": "All molecule tests passed."
}, },
"Another molecule runner failed. Stopping this runner early.": { "Another molecule runner failed. Stopping this runner early.": {
"en": "Another molecule runner failed. Stopping this runner early.", "en": "Another molecule runner failed. Stopping this runner early.",
"bg": "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.", "de": "Another molecule runner failed. Stopping this runner early.",
"ru": "Another molecule runner failed. Stopping this runner early.", "ru": "Another molecule runner failed. Stopping this runner early.",
"zh": "Another molecule runner failed. Stopping this runner early." "zh": "Another molecule runner failed. Stopping this runner early."
}, },
"Bumping version: {current} -> v{new_version}": { "Bumping version: {current} -> v{new_version}": {
"en": "Bumping version: {current} -> v{new_version}", "en": "Bumping version: {current} -> v{new_version}",
"bg": "Bumping version: {current} -> v{new_version}", "bg": "Bumping version: {current} -> v{new_version}",
"de": "Bumping version: {current} -> v{new_version}", "de": "Bumping version: {current} -> v{new_version}",
"ru": "Bumping version: {current} -> v{new_version}", "ru": "Bumping version: {current} -> v{new_version}",
"zh": "Bumping version: {current} -> v{new_version}" "zh": "Bumping version: {current} -> v{new_version}"
}, },
"Checking CLI command documentation...": { "Checking CLI command documentation...": {
"en": "Checking CLI command documentation...", "en": "Checking CLI command documentation...",
"bg": "Checking CLI command documentation...", "bg": "Checking CLI command documentation...",
"de": "Checking CLI command documentation...", "de": "Checking CLI command documentation...",
"ru": "Checking CLI command documentation...", "ru": "Checking CLI command documentation...",
"zh": "Checking CLI command documentation..." "zh": "Checking CLI command documentation..."
}, },
"Command failed ({cmd}): {stderr}": { "Command failed ({cmd}): {stderr}": {
"en": "Command failed ({cmd}): {stderr}", "en": "Command failed ({cmd}): {stderr}",
"bg": "Command failed ({cmd}): {stderr}", "bg": "Command failed ({cmd}): {stderr}",
"de": "Command failed ({cmd}): {stderr}", "de": "Command failed ({cmd}): {stderr}",
"ru": "Command failed ({cmd}): {stderr}", "ru": "Command failed ({cmd}): {stderr}",
"zh": "Command failed ({cmd}): {stderr}" "zh": "Command failed ({cmd}): {stderr}"
}, },
"Comparing {base}..{head} ({count} files changed)": { "Comparing {base}..{head} ({count} files changed)": {
"en": "Comparing {base}..{head} ({count} files changed)", "en": "Comparing {base}..{head} ({count} files changed)",
"bg": "Comparing {base}..{head} ({count} files changed)", "bg": "Comparing {base}..{head} ({count} files changed)",
"de": "Comparing {base}..{head} ({count} files changed)", "de": "Comparing {base}..{head} ({count} files changed)",
"ru": "Comparing {base}..{head} ({count} files changed)", "ru": "Comparing {base}..{head} ({count} files changed)",
"zh": "Comparing {base}..{head} ({count} files changed)" "zh": "Comparing {base}..{head} ({count} files changed)"
}, },
"Configuring branch protection for {branch}...": { "Configuring branch protection for {branch}...": {
"en": "Configuring branch protection for {branch}...", "en": "Configuring branch protection for {branch}...",
"bg": "Конфигуриране на защита на клона {branch}...", "bg": "Конфигуриране на защита на клона {branch}...",
"de": "Konfiguriere Branch-Schutz für {branch}...", "de": "Konfiguriere Branch-Schutz für {branch}...",
"ru": "Настройка защиты ветки {branch}...", "ru": "Настройка защиты ветки {branch}...",
"zh": "正在配置 {branch} 的分支保护..." "zh": "正在配置 {branch} 的分支保护..."
}, },
"Configuring repository settings...": { "Configuring repository settings...": {
"en": "Configuring repository settings...", "en": "Configuring repository settings...",
"bg": "Конфигуриране на настройките на хранилището...", "bg": "Конфигуриране на настройките на хранилището...",
"de": "Repository-Einstellungen konfigurieren...", "de": "Repository-Einstellungen konfigurieren...",
"ru": "Настройка параметров репозитория...", "ru": "Настройка параметров репозитория...",
"zh": "正在配置仓库设置..." "zh": "正在配置仓库设置..."
}, },
"Could not extract conventional commit message from PR commits.": { "Could not extract conventional commit message from PR commits.": {
"en": "Could not extract conventional commit message from PR commits.", "en": "Could not extract conventional commit message from PR commits.",
"bg": "Could not extract conventional commit message from PR commits.", "bg": "Could not extract conventional commit message from PR commits.",
"de": "Could not extract conventional commit message from PR commits.", "de": "Could not extract conventional commit message from PR commits.",
"ru": "Could not extract conventional commit message from PR commits.", "ru": "Could not extract conventional commit message from PR commits.",
"zh": "Could not extract conventional commit message from PR commits." "zh": "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.": { "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.", "en": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.",
"bg": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", "bg": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.",
"de": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", "de": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.",
"ru": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.", "ru": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task.",
"zh": "Could not find Vikunja task {task_id} in project {project_id}. Every PR must have a corresponding Vikunja task." "zh": "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}": { "Could not find __version__ in {file}": {
"en": "Could not find __version__ in {file}", "en": "Could not find __version__ in {file}",
"bg": "Could not find __version__ in {file}", "bg": "Could not find __version__ in {file}",
"de": "Could not find __version__ in {file}", "de": "Could not find __version__ in {file}",
"ru": "Could not find __version__ in {file}", "ru": "Could not find __version__ in {file}",
"zh": "Could not find __version__ in {file}" "zh": "Could not find __version__ in {file}"
}, },
"Could not parse test execution time from output.": { "Could not parse test execution time from output.": {
"en": "Could not parse test execution time from output.", "en": "Could not parse test execution time from output.",
"bg": "Could not parse test execution time from output.", "bg": "Could not parse test execution time from output.",
"de": "Could not parse test execution time from output.", "de": "Could not parse test execution time from output.",
"ru": "Could not parse test execution time from output.", "ru": "Could not parse test execution time from output.",
"zh": "Could not parse test execution time from output." "zh": "Could not parse test execution time from output."
}, },
"Created issue #{issue_id}: {title}": { "Created issue #{issue_id}: {title}": {
"en": "Created issue #{issue_id}: {title}", "en": "Created issue #{issue_id}: {title}",
"bg": "Created issue #{issue_id}: {title}", "bg": "Created issue #{issue_id}: {title}",
"de": "Created issue #{issue_id}: {title}", "de": "Created issue #{issue_id}: {title}",
"ru": "Created issue #{issue_id}: {title}", "ru": "Created issue #{issue_id}: {title}",
"zh": "Created issue #{issue_id}: {title}" "zh": "Created issue #{issue_id}: {title}"
}, },
"Created release commit.": { "Created release commit.": {
"en": "Created release commit.", "en": "Created release commit.",
"bg": "Created release commit.", "bg": "Created release commit.",
"de": "Created release commit.", "de": "Created release commit.",
"ru": "Created release commit.", "ru": "Created release commit.",
"zh": "Created release commit." "zh": "Created release commit."
}, },
"Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.": { "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.", "en": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.",
"bg": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", "bg": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.",
"de": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", "de": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.",
"ru": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.", "ru": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently.",
"zh": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently." "zh": "Dry-run mode: on branch '{branch}' (not master). Some checks may behave differently."
}, },
"ERROR: REPO_TOKEN is not set.": { "ERROR: REPO_TOKEN is not set.": {
"en": "ERROR: REPO_TOKEN is not set.", "en": "ERROR: REPO_TOKEN is not set.",
"bg": "ГРЕШКА: REPO_TOKEN не е зададен.", "bg": "ГРЕШКА: REPO_TOKEN не е зададен.",
"de": "FEHLER: REPO_TOKEN ist nicht gesetzt.", "de": "FEHLER: REPO_TOKEN ist nicht gesetzt.",
"ru": "ОШИБКА: REPO_TOKEN не задан.", "ru": "ОШИБКА: REPO_TOKEN не задан.",
"zh": "错误:未设置 REPO_TOKEN。" "zh": "错误:未设置 REPO_TOKEN。"
}, },
"ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.": { "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.": {
"en": "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.", "en": "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.",
"bg": "ГРЕШКА: Името на хранилището не е указано. Използвайте --repo или задайте DEVX_REPO_NAME.", "bg": "ГРЕШКА: Името на хранилището не е указано. Използвайте --repo или задайте DEVX_REPO_NAME.",
"de": "FEHLER: Repository-Name nicht angegeben. Verwenden Sie --repo oder setzen Sie DEVX_REPO_NAME.", "de": "FEHLER: Repository-Name nicht angegeben. Verwenden Sie --repo oder setzen Sie DEVX_REPO_NAME.",
"ru": "ОШИБКА: Имя репозитория не указано. Используйте --repo или задайте DEVX_REPO_NAME.", "ru": "ОШИБКА: Имя репозитория не указано. Используйте --repo или задайте DEVX_REPO_NAME.",
"zh": "错误:未指定仓库名称。请使用 --repo 或设置 DEVX_REPO_NAME。" "zh": "错误:未指定仓库名称。请使用 --repo 或设置 DEVX_REPO_NAME。"
}, },
"ERROR: Tag consistency check failed. Existing tags are misaligned:": { "ERROR: Tag consistency check failed. Existing tags are misaligned:": {
"en": "ERROR: Tag consistency check failed. Existing tags are misaligned:", "en": "ERROR: Tag consistency check failed. Existing tags are misaligned:",
"bg": "ERROR: Tag consistency check failed. Existing tags are misaligned:", "bg": "ERROR: Tag consistency check failed. Existing tags are misaligned:",
"de": "ERROR: Tag consistency check failed. Existing tags are misaligned:", "de": "ERROR: Tag consistency check failed. Existing tags are misaligned:",
"ru": "ERROR: Tag consistency check failed. Existing tags are misaligned:", "ru": "ERROR: Tag consistency check failed. Existing tags are misaligned:",
"zh": "ERROR: Tag consistency check failed. Existing tags are misaligned:" "zh": "ERROR: Tag consistency check failed. Existing tags are misaligned:"
}, },
"ERROR: VIKUNJA_TOKEN is not set.": { "ERROR: VIKUNJA_TOKEN is not set.": {
"en": "ERROR: VIKUNJA_TOKEN is not set.", "en": "ERROR: VIKUNJA_TOKEN is not set.",
"bg": "ГРЕШКА: VIKUNJA_TOKEN не е зададен.", "bg": "ГРЕШКА: VIKUNJA_TOKEN не е зададен.",
"de": "FEHLER: VIKUNJA_TOKEN ist nicht gesetzt.", "de": "FEHLER: VIKUNJA_TOKEN ist nicht gesetzt.",
"ru": "ОШИБКА: VIKUNJA_TOKEN не задан.", "ru": "ОШИБКА: VIKUNJA_TOKEN не задан.",
"zh": "错误:未设置 VIKUNJA_TOKEN。" "zh": "错误:未设置 VIKUNJA_TOKEN。"
}, },
"ERROR: mapping.json not found at {path}": { "ERROR: mapping.json not found at {path}": {
"en": "ERROR: mapping.json not found at {path}", "en": "ERROR: mapping.json not found at {path}",
"bg": "ERROR: mapping.json not found at {path}", "bg": "ERROR: mapping.json not found at {path}",
"de": "ERROR: mapping.json not found at {path}", "de": "ERROR: mapping.json not found at {path}",
"ru": "ERROR: mapping.json not found at {path}", "ru": "ERROR: mapping.json not found at {path}",
"zh": "ERROR: mapping.json not found at {path}" "zh": "ERROR: mapping.json not found at {path}"
}, },
"FAILED: {pair} exited with code {code}": { "FAILED: {pair} exited with code {code}": {
"en": "FAILED: {pair} exited with code {code}", "en": "FAILED: {pair} exited with code {code}",
"bg": "FAILED: {pair} exited with code {code}", "bg": "FAILED: {pair} exited with code {code}",
"de": "FAILED: {pair} exited with code {code}", "de": "FAILED: {pair} exited with code {code}",
"ru": "FAILED: {pair} exited with code {code}", "ru": "FAILED: {pair} exited with code {code}",
"zh": "FAILED: {pair} exited with code {code}" "zh": "FAILED: {pair} exited with code {code}"
}, },
"Failed to create issue via tea: {error}": { "Failed to create issue via tea: {error}": {
"en": "Failed to create issue via tea: {error}", "en": "Failed to create issue via tea: {error}",
"bg": "Failed to create issue via tea: {error}", "bg": "Failed to create issue via tea: {error}",
"de": "Failed to create issue via tea: {error}", "de": "Failed to create issue via tea: {error}",
"ru": "Failed to create issue via tea: {error}", "ru": "Failed to create issue via tea: {error}",
"zh": "Failed to create issue via tea: {error}" "zh": "Failed to create issue via tea: {error}"
}, },
"Found {count} existing wiki pages.": { "Found {count} existing wiki pages.": {
"en": "Found {count} existing wiki pages.", "en": "Found {count} existing wiki pages.",
"bg": "Found {count} existing wiki pages.", "bg": "Found {count} existing wiki pages.",
"de": "Found {count} existing wiki pages.", "de": "Found {count} existing wiki pages.",
"ru": "Found {count} existing wiki pages.", "ru": "Found {count} existing wiki pages.",
"zh": "Found {count} existing wiki pages." "zh": "Found {count} existing wiki pages."
}, },
"GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.": { "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.", "en": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.",
"bg": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.", "bg": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.",
"de": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.", "de": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.",
"ru": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.", "ru": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation.",
"zh": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation." "zh": "GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation."
}, },
"Generated {file} with prefix '{prefix}'.": { "Generated {file} with prefix '{prefix}'.": {
"en": "Generated {file} with prefix '{prefix}'.", "en": "Generated {file} with prefix '{prefix}'.",
"bg": "Generated {file} with prefix '{prefix}'.", "bg": "Generated {file} with prefix '{prefix}'.",
"de": "Generated {file} with prefix '{prefix}'.", "de": "Generated {file} with prefix '{prefix}'.",
"ru": "Generated {file} with prefix '{prefix}'.", "ru": "Generated {file} with prefix '{prefix}'.",
"zh": "Generated {file} with prefix '{prefix}'." "zh": "Generated {file} with prefix '{prefix}'."
}, },
"HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.": { "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.": {
"en": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", "en": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.",
"bg": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", "bg": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.",
"de": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", "de": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.",
"ru": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.", "ru": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.",
"zh": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag." "zh": "HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag."
}, },
"HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.": { "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.": {
"en": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", "en": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.",
"bg": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", "bg": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.",
"de": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", "de": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.",
"ru": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.", "ru": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment.",
"zh": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment." "zh": "HEAD is a release commit for v{version} but tag {tag} points to a different commit ({tag_commit} vs HEAD {head_commit}). This indicates a tag/commit misalignment."
}, },
"HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.": { "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.": {
"en": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", "en": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.",
"bg": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", "bg": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.",
"de": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", "de": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.",
"ru": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.", "ru": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping.",
"zh": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping." "zh": "HEAD is already a release commit ('{msg}') and tag {tag} points to HEAD. Skipping."
}, },
"HTTP error: {status} — {message}": { "HTTP error: {status} — {message}": {
"en": "HTTP error: {status} — {message}", "en": "HTTP error: {status} — {message}",
"bg": "HTTP грешка: {status} — {message}", "bg": "HTTP грешка: {status} — {message}",
"de": "HTTP-Fehler: {status} — {message}", "de": "HTTP-Fehler: {status} — {message}",
"ru": "Ошибка HTTP: {status} — {message}", "ru": "Ошибка HTTP: {status} — {message}",
"zh": "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.": { "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.", "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Алтернативно, конфигурирайте защитата на клона ръчно в Настройки → Клонове.", "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.", "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Либо настройте защиту ветки вручную в разделе Настройки → Ветки.", "ru": "HTTP {status} Запрещено — у вашего токена нет прав администратора.\nУбедитесь, что токен принадлежит владельцу репозитория или администратору организации.\nЛибо настройте защиту ветки вручную в разделе Настройки → Ветки.",
"zh": "HTTP {status} 禁止访问 — 您的令牌缺少管理员权限。\n请确保令牌属于仓库所有者或组织管理员。\n或者,您可以在 设置 → 分支 中手动配置分支保护。" "zh": "HTTP {status} 禁止访问 — 您的令牌缺少管理员权限。\n请确保令牌属于仓库所有者或组织管理员。\n或者,您可以在 设置 → 分支 中手动配置分支保护。"
}, },
"Head branch is behind master. Pulling and rebasing...": { "Head branch is behind master. Pulling and rebasing...": {
"en": "Head branch is behind master. Pulling and rebasing...", "en": "Head branch is behind master. Pulling and rebasing...",
"bg": "Head branch is behind master. Pulling and rebasing...", "bg": "Head branch is behind master. Pulling and rebasing...",
"de": "Head branch is behind master. Pulling and rebasing...", "de": "Head branch is behind master. Pulling and rebasing...",
"ru": "Head branch is behind master. Pulling and rebasing...", "ru": "Head branch is behind master. Pulling and rebasing...",
"zh": "Head branch is behind master. Pulling and rebasing..." "zh": "Head branch is behind master. Pulling and rebasing..."
}, },
"Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}": { "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}": {
"en": "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}", "en": "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}",
"bg": "Инфраструктурен commit (без идентификатор на задача DEVX-N), пропускаме обновяването на Vikunja: {msg}", "bg": "Инфраструктурен commit (без идентификатор на задача DEVX-N), пропускаме обновяването на Vikunja: {msg}",
"de": "Infrastruktur-Commit (keine DEVX-N Task-ID), Vikunja-Update wird übersprungen: {msg}", "de": "Infrastruktur-Commit (keine DEVX-N Task-ID), Vikunja-Update wird übersprungen: {msg}",
"ru": "Инфраструктурный коммит (без ID задачи DEVX-N), пропуск обновления Vikunja: {msg}", "ru": "Инфраструктурный коммит (без ID задачи DEVX-N), пропуск обновления Vikunja: {msg}",
"zh": "基础设施提交(无 DEVX-N 任务 ID),跳过 Vikunja 更新: {msg}" "zh": "基础设施提交(无 DEVX-N 任务 ID),跳过 Vikunja 更新: {msg}"
}, },
"Lint failed — refusing to release. Fix lint errors first.\n{stderr}": { "Lint failed — refusing to release. Fix lint errors first.\n{stderr}": {
"en": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", "en": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}",
"bg": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", "bg": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}",
"de": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", "de": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}",
"ru": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}", "ru": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}",
"zh": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}" "zh": "Lint failed — refusing to release. Fix lint errors first.\n{stderr}"
}, },
"Lint passed.": { "Lint passed.": {
"en": "Lint passed.", "en": "Lint passed.",
"bg": "Lint passed.", "bg": "Lint passed.",
"de": "Lint passed.", "de": "Lint passed.",
"ru": "Lint passed.", "ru": "Lint passed.",
"zh": "Lint passed." "zh": "Lint passed."
}, },
"Mapped file {file} is empty. Update the content or remove from mapping.json.": { "Mapped file {file} is empty. Update the content or remove from mapping.json.": {
"en": "Mapped file {file} is empty. Update the content or remove from mapping.json.", "en": "Mapped file {file} is empty. Update the content or remove from mapping.json.",
"bg": "Mapped file {file} is empty. Update the content or remove from mapping.json.", "bg": "Mapped file {file} is empty. Update the content or remove from mapping.json.",
"de": "Mapped file {file} is empty. Update the content or remove from mapping.json.", "de": "Mapped file {file} is empty. Update the content or remove from mapping.json.",
"ru": "Mapped file {file} is empty. Update the content or remove from mapping.json.", "ru": "Mapped file {file} is empty. Update the content or remove from mapping.json.",
"zh": "Mapped file {file} is empty. Update the content or remove from mapping.json." "zh": "Mapped file {file} is empty. Update the content or remove from mapping.json."
}, },
"Mapped file {file} not found. Update mapping.json or create the file.": { "Mapped file {file} not found. Update mapping.json or create the file.": {
"en": "Mapped file {file} not found. Update mapping.json or create the file.", "en": "Mapped file {file} not found. Update mapping.json or create the file.",
"bg": "Mapped file {file} not found. Update mapping.json or create the file.", "bg": "Mapped file {file} not found. Update mapping.json or create the file.",
"de": "Mapped file {file} not found. Update mapping.json or create the file.", "de": "Mapped file {file} not found. Update mapping.json or create the file.",
"ru": "Mapped file {file} not found. Update mapping.json or create the file.", "ru": "Mapped file {file} not found. Update mapping.json or create the file.",
"zh": "Mapped file {file} not found. Update mapping.json or create the file." "zh": "Mapped file {file} not found. Update mapping.json or create the file."
}, },
"Merge failed after rebase retry: {error}\nPlease rebase the PR manually.": { "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.": {
"en": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.", "en": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.",
"bg": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.", "bg": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.",
"de": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.", "de": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.",
"ru": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.", "ru": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually.",
"zh": "Merge failed after rebase retry: {error}\nPlease rebase the PR manually." "zh": "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.": { "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.", "en": "Merge failed with HTTP {status}: {message}\nPlease check the PR is ready and you have merge rights.",
"bg": "Сливането неуспешно с HTTP {status}: {message}\nПроверете дали PR е готов и имате права за сливане.", "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.", "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 готов и у вас есть права на слияние.", "ru": "Слияние не удалось: HTTP {status}: {message}\nПроверьте, что PR готов и у вас есть права на слияние.",
"zh": "合并失败: HTTP {status}: {message}\n请检查 PR 是否准备就绪且您具有合并权限。" "zh": "合并失败: HTTP {status}: {message}\n请检查 PR 是否准备就绪且您具有合并权限。"
}, },
"Module {mod} has no main() function": { "Module {mod} has no main() function": {
"en": "Module {mod} has no main() function", "en": "Module {mod} has no main() function",
"bg": "Модул {mod} няма функция main()", "bg": "Модул {mod} няма функция main()",
"de": "Modul {mod} hat keine main()-Funktion", "de": "Modul {mod} hat keine main()-Funktion",
"ru": "Модуль {mod} не имеет функции main()", "ru": "Модуль {mod} не имеет функции main()",
"zh": "模块 {mod} 没有 main() 函数" "zh": "模块 {mod} 没有 main() 函数"
}, },
"Molecule directory not found: {path}": { "Molecule directory not found: {path}": {
"en": "Molecule directory not found: {path}", "en": "Molecule directory not found: {path}",
"bg": "Директорията на molecule не е намерена: {path}", "bg": "Директорията на molecule не е намерена: {path}",
"de": "Molecule-Verzeichnis nicht gefunden: {path}", "de": "Molecule-Verzeichnis nicht gefunden: {path}",
"ru": "Директория molecule не найдена: {path}", "ru": "Директория molecule не найдена: {path}",
"zh": "未找到 molecule 目录: {path}" "zh": "未找到 molecule 目录: {path}"
}, },
"Nice! Gitea release {tag} created.": { "Nice! Gitea release {tag} created.": {
"en": "Nice! Gitea release {tag} created.", "en": "Nice! Gitea release {tag} created.",
"bg": "Отлично! Gitea release {tag} е създаден.", "bg": "Отлично! Gitea release {tag} е създаден.",
"de": "Prima! Gitea-Release {tag} erstellt.", "de": "Prima! Gitea-Release {tag} erstellt.",
"ru": "Отлично! Gitea release {tag} создан.", "ru": "Отлично! Gitea release {tag} создан.",
"zh": "不错!Gitea release {tag} 已创建。" "zh": "不错!Gitea release {tag} 已创建。"
}, },
"Nice! PR #{pr_number} squash-merged with title: {merge_title}": { "Nice! PR #{pr_number} squash-merged with title: {merge_title}": {
"en": "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}", "bg": "Отлично! PR #{pr_number} е squash-merge-нат със заглавие: {merge_title}",
"de": "Prima! PR #{pr_number} wurde mit Titel {merge_title} squash-gemergt.", "de": "Prima! PR #{pr_number} wurde mit Titel {merge_title} squash-gemergt.",
"ru": "Отлично! PR #{pr_number} squash-merge с заголовком: {merge_title}", "ru": "Отлично! PR #{pr_number} squash-merge с заголовком: {merge_title}",
"zh": "不错!PR #{pr_number} 已 squash 合并,标题: {merge_title}" "zh": "不错!PR #{pr_number} 已 squash 合并,标题: {merge_title}"
}, },
"Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.": { "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.", "en": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.",
"bg": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", "bg": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.",
"de": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", "de": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.",
"ru": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.", "ru": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered.",
"zh": "Nice! Release v{version} tagged and pushed. The publish workflow will be triggered." "zh": "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.": { "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.", "en": "Nice! Vikunja task {task_id} (ID {vikunja_id}) updated and marked done.",
"bg": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) е обновена и маркирана като готова.", "bg": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) е обновена и маркирана като готова.",
"de": "Prima! Vikunja-Aufgabe {task_id} (ID {vikunja_id}) aktualisiert und als erledigt markiert.", "de": "Prima! Vikunja-Aufgabe {task_id} (ID {vikunja_id}) aktualisiert und als erledigt markiert.",
"ru": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) обновлена и отмечена как выполненная.", "ru": "Отлично! Задача Vikunja {task_id} (ID {vikunja_id}) обновлена и отмечена как выполненная.",
"zh": "不错!Vikunja 任务 {task_id} (ID {vikunja_id}) 已更新并标记为完成。" "zh": "不错!Vikunja 任务 {task_id} (ID {vikunja_id}) 已更新并标记为完成。"
}, },
"No changes between {base} and {head}.": { "No changes between {base} and {head}.": {
"en": "No changes between {base} and {head}.", "en": "No changes between {base} and {head}.",
"bg": "No changes between {base} and {head}.", "bg": "No changes between {base} and {head}.",
"de": "No changes between {base} and {head}.", "de": "No changes between {base} and {head}.",
"ru": "No changes between {base} and {head}.", "ru": "No changes between {base} and {head}.",
"zh": "No changes between {base} and {head}." "zh": "No changes between {base} and {head}."
}, },
"No staged changes — version and changelog already up to date.": { "No staged changes — version and changelog already up to date.": {
"en": "No staged changes — version and changelog already up to date.", "en": "No staged changes — version and changelog already up to date.",
"bg": "No staged changes — version and changelog already up to date.", "bg": "No staged changes — version and changelog already up to date.",
"de": "No staged changes — version and changelog already up to date.", "de": "No staged changes — version and changelog already up to date.",
"ru": "No staged changes — version and changelog already up to date.", "ru": "No staged changes — version and changelog already up to date.",
"zh": "No staged changes — version and changelog already up to date." "zh": "No staged changes — version and changelog already up to date."
}, },
"No tags found — treating all changes as user-facing.": { "No tags found — treating all changes as user-facing.": {
"en": "No tags found — treating all changes as user-facing.", "en": "No tags found — treating all changes as user-facing.",
"bg": "No tags found — treating all changes as user-facing.", "bg": "No tags found — treating all changes as user-facing.",
"de": "No tags found — treating all changes as user-facing.", "de": "No tags found — treating all changes as user-facing.",
"ru": "No tags found — treating all changes as user-facing.", "ru": "No tags found — treating all changes as user-facing.",
"zh": "No tags found — treating all changes as user-facing." "zh": "No tags found — treating all changes as user-facing."
}, },
"No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.": { "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.": {
"en": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", "en": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.",
"bg": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", "bg": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.",
"de": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", "de": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.",
"ru": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.", "ru": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID.",
"zh": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID." "zh": "No task ID ({prefix}-N) found in commit message: {msg}. Every non-infrastructure commit must have a task ID."
}, },
"No unreleased changes found. Nothing to release.": { "No unreleased changes found. Nothing to release.": {
"en": "No unreleased changes found. Nothing to release.", "en": "No unreleased changes found. Nothing to release.",
"bg": "No unreleased changes found. Nothing to release.", "bg": "No unreleased changes found. Nothing to release.",
"de": "No unreleased changes found. Nothing to release.", "de": "No unreleased changes found. Nothing to release.",
"ru": "No unreleased changes found. Nothing to release.", "ru": "No unreleased changes found. Nothing to release.",
"zh": "No unreleased changes found. Nothing to release." "zh": "No unreleased changes found. Nothing to release."
}, },
"No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping 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.", "en": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.",
"bg": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", "bg": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.",
"de": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", "de": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.",
"ru": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.", "ru": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release.",
"zh": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release." "zh": "No user-facing changes since {tag} — only workflow/infrastructure files changed. Skipping release."
}, },
"Note: Self-approval not allowed. Posting COMMENT instead.": { "Note: Self-approval not allowed. Posting COMMENT instead.": {
"en": "Note: Self-approval not allowed. Posting COMMENT instead.", "en": "Note: Self-approval not allowed. Posting COMMENT instead.",
"bg": "Note: Self-approval not allowed. Posting COMMENT instead.", "bg": "Note: Self-approval not allowed. Posting COMMENT instead.",
"de": "Note: Self-approval not allowed. Posting COMMENT instead.", "de": "Note: Self-approval not allowed. Posting COMMENT instead.",
"ru": "Note: Self-approval not allowed. Posting COMMENT instead.", "ru": "Note: Self-approval not allowed. Posting COMMENT instead.",
"zh": "Note: Self-approval not allowed. Posting COMMENT instead." "zh": "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": { "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", "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", "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", "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", "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" "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 ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": { "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": {
"en": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", "en": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
"bg": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", "bg": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
"de": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", "de": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
"ru": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.", "ru": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
"zh": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI." "zh": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI."
}, },
"Oops! Gitea PyPI registry publish failed:\n{stderr}": { "Oops! Gitea PyPI registry publish failed:\n{stderr}": {
"en": "Oops! Gitea PyPI registry publish failed:\n{stderr}", "en": "Oops! Gitea PyPI registry publish failed:\n{stderr}",
"bg": "Опа! Публикуването в Gitea PyPI registry неуспешно:\n{stderr}", "bg": "Опа! Публикуването в Gitea PyPI registry неуспешно:\n{stderr}",
"de": "Ups! Veröffentlichung in der Gitea PyPI-Registry fehlgeschlagen:\n{stderr}", "de": "Ups! Veröffentlichung in der Gitea PyPI-Registry fehlgeschlagen:\n{stderr}",
"ru": "Ой! Публикация в Gitea PyPI registry не удалась:\n{stderr}", "ru": "Ой! Публикация в Gitea PyPI registry не удалась:\n{stderr}",
"zh": "哎呀!Gitea PyPI registry 发布失败:\n{stderr}" "zh": "哎呀!Gitea PyPI registry 发布失败:\n{stderr}"
}, },
"Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}": { "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}": {
"en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", "en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}",
"bg": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", "bg": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}",
"de": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", "de": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}",
"ru": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}", "ru": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}",
"zh": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}" "zh": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}"
}, },
"Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}": { "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}": {
"en": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", "en": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}",
"bg": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", "bg": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}",
"de": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", "de": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}",
"ru": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}", "ru": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}",
"zh": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}" "zh": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}"
}, },
"Oops! No task ID found in .taskid file or branch name '{branch}'.": { "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}'.", "en": "Oops! No task ID found in .taskid file or branch name '{branch}'.",
"bg": "Oops! No task ID found in .taskid file or branch name '{branch}'.", "bg": "Oops! No task ID found in .taskid file or branch name '{branch}'.",
"de": "Oops! No task ID found in .taskid file or branch name '{branch}'.", "de": "Oops! No task ID found in .taskid file or branch name '{branch}'.",
"ru": "Oops! No task ID found in .taskid file or branch name '{branch}'.", "ru": "Oops! No task ID found in .taskid file or branch name '{branch}'.",
"zh": "Oops! No task ID found in .taskid file or branch name '{branch}'." "zh": "Oops! No task ID found in .taskid file or branch name '{branch}'."
}, },
"Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": { "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": {
"en": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", "en": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}",
"bg": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", "bg": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}",
"de": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", "de": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}",
"ru": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}", "ru": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}",
"zh": "Oops! PR title must follow format '{prefix}-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}" "zh": "Oops! PR title must follow format '{prefix}-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}": { "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}", "en": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}",
"bg": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", "bg": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}",
"de": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", "de": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}",
"ru": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}", "ru": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}",
"zh": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}" "zh": "Oops! PR title task ID mismatch.\n Branch task ID: {task_id}\n PR title: {pr_title}"
}, },
"Oops! Package build failed:\n{stderr}": { "Oops! Package build failed:\n{stderr}": {
"en": "Oops! Package build failed:\n{stderr}", "en": "Oops! Package build failed:\n{stderr}",
"bg": "Опа! Сборката на пакета неуспешна:\n{stderr}", "bg": "Опа! Сборката на пакета неуспешна:\n{stderr}",
"de": "Ups! Paket-Build fehlgeschlagen:\n{stderr}", "de": "Ups! Paket-Build fehlgeschlagen:\n{stderr}",
"ru": "Ой! Сборка пакета не удалась:\n{stderr}", "ru": "Ой! Сборка пакета не удалась:\n{stderr}",
"zh": "哎呀!包构建失败:\n{stderr}" "zh": "哎呀!包构建失败:\n{stderr}"
}, },
"Oops! PyPI publish failed:\n{stderr}": { "Oops! PyPI publish failed:\n{stderr}": {
"en": "Oops! PyPI publish failed:\n{stderr}", "en": "Oops! PyPI publish failed:\n{stderr}",
"bg": "Опа! Публикуването в PyPI неуспешно:\n{stderr}", "bg": "Опа! Публикуването в PyPI неуспешно:\n{stderr}",
"de": "Ups! PyPI-Veröffentlichung fehlgeschlagen:\n{stderr}", "de": "Ups! PyPI-Veröffentlichung fehlgeschlagen:\n{stderr}",
"ru": "Ой! Публикация в PyPI не удалась:\n{stderr}", "ru": "Ой! Публикация в PyPI не удалась:\n{stderr}",
"zh": "哎呀!PyPI 发布失败:\n{stderr}" "zh": "哎呀!PyPI 发布失败:\n{stderr}"
}, },
"PASSED: {pair}": { "PASSED: {pair}": {
"en": "PASSED: {pair}", "en": "PASSED: {pair}",
"bg": "PASSED: {pair}", "bg": "PASSED: {pair}",
"de": "PASSED: {pair}", "de": "PASSED: {pair}",
"ru": "PASSED: {pair}", "ru": "PASSED: {pair}",
"zh": "PASSED: {pair}" "zh": "PASSED: {pair}"
}, },
"PR number must be an integer, got: {pr_number}": { "PR number must be an integer, got: {pr_number}": {
"en": "PR number must be an integer, got: {pr_number}", "en": "PR number must be an integer, got: {pr_number}",
"bg": "PR number must be an integer, got: {pr_number}", "bg": "PR number must be an integer, got: {pr_number}",
"de": "PR number must be an integer, got: {pr_number}", "de": "PR number must be an integer, got: {pr_number}",
"ru": "PR number must be an integer, got: {pr_number}", "ru": "PR number must be an integer, got: {pr_number}",
"zh": "PR number must be an integer, got: {pr_number}" "zh": "PR number must be an integer, got: {pr_number}"
}, },
"PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}": { "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}", "en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}",
"bg": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", "bg": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}",
"de": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", "de": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}",
"ru": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}", "ru": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}",
"zh": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}" "zh": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}"
}, },
"PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.": { "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.": {
"en": "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.", "en": "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.",
"bg": "PYPI_TOKEN не е зададен и няма конфигуриран URL на registry — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.", "bg": "PYPI_TOKEN не е зададен и няма конфигуриран URL на registry — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.",
"de": "PYPI_TOKEN nicht gesetzt und keine Registry-URL konfiguriert — PyPI-Veröffentlichung wird übersprungen. Keine Sorge, wir erstellen einfach das Gitea-Release.", "de": "PYPI_TOKEN nicht gesetzt und keine Registry-URL konfiguriert — PyPI-Veröffentlichung wird übersprungen. Keine Sorge, wir erstellen einfach das Gitea-Release.",
"ru": "PYPI_TOKEN не задан и URL registry не настроен — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.", "ru": "PYPI_TOKEN не задан и URL registry не настроен — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.",
"zh": "未设置 PYPI_TOKEN 且未配置 registry URL — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。" "zh": "未设置 PYPI_TOKEN 且未配置 registry URL — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。"
}, },
"Published to Gitea PyPI registry.": { "Published to Gitea PyPI registry.": {
"en": "Published to Gitea PyPI registry.", "en": "Published to Gitea PyPI registry.",
"bg": "Публикувано в Gitea PyPI registry.", "bg": "Публикувано в Gitea PyPI registry.",
"de": "In der Gitea PyPI-Registry veröffentlicht.", "de": "In der Gitea PyPI-Registry veröffentlicht.",
"ru": "Опубликовано в Gitea PyPI registry.", "ru": "Опубликовано в Gitea PyPI registry.",
"zh": "已发布到 Gitea PyPI registry。" "zh": "已发布到 Gitea PyPI registry。"
}, },
"Published to PyPI.": { "Published to PyPI.": {
"en": "Published to PyPI.", "en": "Published to PyPI.",
"bg": "Публикувано в PyPI.", "bg": "Публикувано в PyPI.",
"de": "In PyPI veröffentlicht.", "de": "In PyPI veröffentlicht.",
"ru": "Опубликовано в PyPI.", "ru": "Опубликовано в PyPI.",
"zh": "已发布到 PyPI。" "zh": "已发布到 PyPI。"
}, },
"Pushed release commit to master.": { "Pushed release commit to master.": {
"en": "Pushed release commit to master.", "en": "Pushed release commit to master.",
"bg": "Pushed release commit to master.", "bg": "Pushed release commit to master.",
"de": "Pushed release commit to master.", "de": "Pushed release commit to master.",
"ru": "Pushed release commit to master.", "ru": "Pushed release commit to master.",
"zh": "Pushed release commit to master." "zh": "Pushed release commit to master."
}, },
"Rebased and pushed. Retrying merge...": { "Rebased and pushed. Retrying merge...": {
"en": "Rebased and pushed. Retrying merge...", "en": "Rebased and pushed. Retrying merge...",
"bg": "Rebased and pushed. Retrying merge...", "bg": "Rebased and pushed. Retrying merge...",
"de": "Rebased and pushed. Retrying merge...", "de": "Rebased and pushed. Retrying merge...",
"ru": "Rebased and pushed. Retrying merge...", "ru": "Rebased and pushed. Retrying merge...",
"zh": "Rebased and pushed. Retrying merge..." "zh": "Rebased and pushed. Retrying merge..."
}, },
"Release creation failed: {error}": { "Release creation failed: {error}": {
"en": "Release creation failed: {error}", "en": "Release creation failed: {error}",
"bg": "Release creation failed: {error}", "bg": "Release creation failed: {error}",
"de": "Release creation failed: {error}", "de": "Release creation failed: {error}",
"ru": "Release creation failed: {error}", "ru": "Release creation failed: {error}",
"zh": "Release creation failed: {error}" "zh": "Release creation failed: {error}"
}, },
"Release must be run on master, currently on '{branch}'.": { "Release must be run on master, currently on '{branch}'.": {
"en": "Release must be run on master, currently on '{branch}'.", "en": "Release must be run on master, currently on '{branch}'.",
"bg": "Release must be run on master, currently on '{branch}'.", "bg": "Release must be run on master, currently on '{branch}'.",
"de": "Release must be run on master, currently on '{branch}'.", "de": "Release must be run on master, currently on '{branch}'.",
"ru": "Release must be run on master, currently on '{branch}'.", "ru": "Release must be run on master, currently on '{branch}'.",
"zh": "Release must be run on master, currently on '{branch}'." "zh": "Release must be run on master, currently on '{branch}'."
}, },
"Repo must be in 'owner/name' format, got: {repo}": { "Repo must be in 'owner/name' format, got: {repo}": {
"en": "Repo must be in 'owner/name' format, got: {repo}", "en": "Repo must be in 'owner/name' format, got: {repo}",
"bg": "Repo must be in 'owner/name' format, got: {repo}", "bg": "Repo must be in 'owner/name' format, got: {repo}",
"de": "Repo must be in 'owner/name' format, got: {repo}", "de": "Repo must be in 'owner/name' format, got: {repo}",
"ru": "Repo must be in 'owner/name' format, got: {repo}", "ru": "Repo must be in 'owner/name' format, got: {repo}",
"zh": "Repo must be in 'owner/name' format, got: {repo}" "zh": "Repo must be in 'owner/name' format, got: {repo}"
}, },
"Repository configuration complete.": { "Repository configuration complete.": {
"en": "Repository configuration complete.", "en": "Repository configuration complete.",
"bg": "Конфигурирането на хранилището е завършено.", "bg": "Конфигурирането на хранилището е завършено.",
"de": "Repository-Konfiguration abgeschlossen.", "de": "Repository-Konfiguration abgeschlossen.",
"ru": "Конфигурация репозитория завершена.", "ru": "Конфигурация репозитория завершена.",
"zh": "仓库配置完成。" "zh": "仓库配置完成。"
}, },
"Runner index {index} out of range (0..{max})": { "Runner index {index} out of range (0..{max})": {
"en": "Runner index {index} out of range (0..{max})", "en": "Runner index {index} out of range (0..{max})",
"bg": "Индексът на runner {index} е извън диапазона (0..{max})", "bg": "Индексът на runner {index} е извън диапазона (0..{max})",
"de": "Runner-Index {index} außerhalb des Bereichs (0..{max})", "de": "Runner-Index {index} außerhalb des Bereichs (0..{max})",
"ru": "Индекс runner {index} вне диапазона (0..{max})", "ru": "Индекс runner {index} вне диапазона (0..{max})",
"zh": "Runner 索引 {index} 超出范围 (0..{max})" "zh": "Runner 索引 {index} 超出范围 (0..{max})"
}, },
"Running lint checks...": { "Running lint checks...": {
"en": "Running lint checks...", "en": "Running lint checks...",
"bg": "Running lint checks...", "bg": "Running lint checks...",
"de": "Running lint checks...", "de": "Running lint checks...",
"ru": "Running lint checks...", "ru": "Running lint checks...",
"zh": "Running lint checks..." "zh": "Running lint checks..."
}, },
"Running tests...": { "Running tests...": {
"en": "Running tests...", "en": "Running tests...",
"bg": "Running tests...", "bg": "Running tests...",
"de": "Running tests...", "de": "Running tests...",
"ru": "Running tests...", "ru": "Running tests...",
"zh": "Running tests..." "zh": "Running tests..."
}, },
"Running: {scenario} on {platform}": { "Running: {scenario} on {platform}": {
"en": "Running: {scenario} on {platform}", "en": "Running: {scenario} on {platform}",
"bg": "Running: {scenario} on {platform}", "bg": "Running: {scenario} on {platform}",
"de": "Running: {scenario} on {platform}", "de": "Running: {scenario} on {platform}",
"ru": "Running: {scenario} on {platform}", "ru": "Running: {scenario} on {platform}",
"zh": "Running: {scenario} on {platform}" "zh": "Running: {scenario} on {platform}"
}, },
"Skipping commit push — no staged changes.": { "Skipping commit push — no staged changes.": {
"en": "Skipping commit push — no staged changes.", "en": "Skipping commit push — no staged changes.",
"bg": "Skipping commit push — no staged changes.", "bg": "Skipping commit push — no staged changes.",
"de": "Skipping commit push — no staged changes.", "de": "Skipping commit push — no staged changes.",
"ru": "Skipping commit push — no staged changes.", "ru": "Skipping commit push — no staged changes.",
"zh": "Skipping commit push — no staged changes." "zh": "Skipping commit push — no staged changes."
}, },
"Syncing {count} documentation pages to wiki...": { "Syncing {count} documentation pages to wiki...": {
"en": "Syncing {count} documentation pages to wiki...", "en": "Syncing {count} documentation pages to wiki...",
"bg": "Syncing {count} documentation pages to wiki...", "bg": "Syncing {count} documentation pages to wiki...",
"de": "Syncing {count} documentation pages to wiki...", "de": "Syncing {count} documentation pages to wiki...",
"ru": "Syncing {count} documentation pages to wiki...", "ru": "Syncing {count} documentation pages to wiki...",
"zh": "Syncing {count} documentation pages to wiki..." "zh": "Syncing {count} documentation pages to wiki..."
}, },
"Tag consistency check failed.": { "Tag consistency check failed.": {
"en": "Tag consistency check failed.", "en": "Tag consistency check failed.",
"bg": "Tag consistency check failed.", "bg": "Tag consistency check failed.",
"de": "Tag consistency check failed.", "de": "Tag consistency check failed.",
"ru": "Tag consistency check failed.", "ru": "Tag consistency check failed.",
"zh": "Tag consistency check failed." "zh": "Tag consistency check failed."
}, },
"Tag v{version} already existed. Publish workflow should already have been triggered.": { "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.", "en": "Tag v{version} already existed. Publish workflow should already have been triggered.",
"bg": "Tag v{version} already existed. Publish workflow should already have been triggered.", "bg": "Tag v{version} already existed. Publish workflow should already have been triggered.",
"de": "Tag v{version} already existed. Publish workflow should already have been triggered.", "de": "Tag v{version} already existed. Publish workflow should already have been triggered.",
"ru": "Tag v{version} already existed. Publish workflow should already have been triggered.", "ru": "Tag v{version} already existed. Publish workflow should already have been triggered.",
"zh": "Tag v{version} already existed. Publish workflow should already have been triggered." "zh": "Tag v{version} already existed. Publish workflow should already have been triggered."
}, },
"Tag {tag} already exists and points to HEAD. Skipping creation.": { "Tag {tag} already exists and points to HEAD. Skipping creation.": {
"en": "Tag {tag} already exists and points to HEAD. Skipping creation.", "en": "Tag {tag} already exists and points to HEAD. Skipping creation.",
"bg": "Tag {tag} already exists and points to HEAD. Skipping creation.", "bg": "Tag {tag} already exists and points to HEAD. Skipping creation.",
"de": "Tag {tag} already exists and points to HEAD. Skipping creation.", "de": "Tag {tag} already exists and points to HEAD. Skipping creation.",
"ru": "Tag {tag} already exists and points to HEAD. Skipping creation.", "ru": "Tag {tag} already exists and points to HEAD. Skipping creation.",
"zh": "Tag {tag} already exists and points to HEAD. Skipping creation." "zh": "Tag {tag} already exists and points to HEAD. Skipping creation."
}, },
"Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.": { "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.": {
"en": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", "en": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.",
"bg": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", "bg": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.",
"de": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", "de": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.",
"ru": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.", "ru": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details.",
"zh": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details." "zh": "Tag {tag} already exists but points to {tag_commit} (expected HEAD {head_commit}). This indicates a tag/commit misalignment. Run 'python3 -m devx.ci.release --verify' for details."
}, },
"Task ID: {task_id}": { "Task ID: {task_id}": {
"en": "Task ID: {task_id}", "en": "Task ID: {task_id}",
"bg": "Task ID: {task_id}", "bg": "Task ID: {task_id}",
"de": "Task ID: {task_id}", "de": "Task ID: {task_id}",
"ru": "Task ID: {task_id}", "ru": "Task ID: {task_id}",
"zh": "Task ID: {task_id}" "zh": "Task ID: {task_id}"
}, },
"Tests failed — refusing to release. Fix test failures first.\n{stderr}": { "Tests failed — refusing to release. Fix test failures first.\n{stderr}": {
"en": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", "en": "Tests failed — refusing to release. Fix test failures first.\n{stderr}",
"bg": "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}", "de": "Tests failed — refusing to release. Fix test failures first.\n{stderr}",
"ru": "Tests failed — refusing to release. Fix test failures first.\n{stderr}", "ru": "Tests failed — refusing to release. Fix test failures first.\n{stderr}",
"zh": "Tests failed — refusing to release. Fix test failures first.\n{stderr}" "zh": "Tests failed — refusing to release. Fix test failures first.\n{stderr}"
}, },
"Tests passed.": { "Tests passed.": {
"en": "Tests passed.", "en": "Tests passed.",
"bg": "Tests passed.", "bg": "Tests passed.",
"de": "Tests passed.", "de": "Tests passed.",
"ru": "Tests passed.", "ru": "Tests passed.",
"zh": "Tests passed." "zh": "Tests passed."
}, },
"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 passed in {duration:.2f}s (under {max}s limit).", "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.",
"bg": "Unit tests passed in {duration:.2f}s (under {max}s limit).", "bg": "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.",
"de": "Unit tests passed in {duration:.2f}s (under {max}s limit).", "de": "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.",
"ru": "Unit tests passed in {duration:.2f}s (under {max}s limit).", "ru": "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.",
"zh": "Unit tests passed in {duration:.2f}s (under {max}s limit)." "zh": "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."
}, },
"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.": { "Unknown check category '{check}'. Available: all, user-facing{tags}": {
"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.", "en": "Unknown check category '{check}'. Available: all, user-facing{tags}",
"bg": "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.", "bg": "Unknown check category '{check}'. Available: all, user-facing{tags}",
"de": "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.", "de": "Unknown check category '{check}'. Available: all, user-facing{tags}",
"ru": "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.", "ru": "Unknown check category '{check}'. Available: all, user-facing{tags}",
"zh": "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." "zh": "Unknown check category '{check}'. Available: all, user-facing{tags}"
}, },
"Unknown check category '{check}'. Available: all, user-facing{tags}": { "Updated version in {init}": {
"en": "Unknown check category '{check}'. Available: all, user-facing{tags}", "en": "Updated version in {init}",
"bg": "Unknown check category '{check}'. Available: all, user-facing{tags}", "bg": "Updated version in {init}",
"de": "Unknown check category '{check}'. Available: all, user-facing{tags}", "de": "Updated version in {init}",
"ru": "Unknown check category '{check}'. Available: all, user-facing{tags}", "ru": "Updated version in {init}",
"zh": "Unknown check category '{check}'. Available: all, user-facing{tags}" "zh": "Updated version in {init}"
}, },
"Updated version in {init}": { "Updated {changelog_file}": {
"en": "Updated version in {init}", "en": "Updated {changelog_file}",
"bg": "Updated version in {init}", "bg": "Updated {changelog_file}",
"de": "Updated version in {init}", "de": "Updated {changelog_file}",
"ru": "Updated version in {init}", "ru": "Updated {changelog_file}",
"zh": "Updated version in {init}" "zh": "Updated {changelog_file}"
}, },
"Updated {changelog_file}": { "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.": {
"en": "Updated {changelog_file}", "en": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.",
"bg": "Updated {changelog_file}", "bg": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.",
"de": "Updated {changelog_file}", "de": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.",
"ru": "Updated {changelog_file}", "ru": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.",
"zh": "Updated {changelog_file}" "zh": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles."
}, },
"VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.": { "Version file: {file}": {
"en": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", "en": "Version file: {file}",
"bg": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", "bg": "Version file: {file}",
"de": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", "de": "Version file: {file}",
"ru": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles.", "ru": "Version file: {file}",
"zh": "VIKUNJA_TOKEN is not set. This is required in CI to validate PR titles." "zh": "Version file: {file}"
}, },
"Version file: {file}": { "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.": {
"en": "Version file: {file}", "en": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.",
"bg": "Version file: {file}", "bg": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.",
"de": "Version file: {file}", "de": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.",
"ru": "Version file: {file}", "ru": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.",
"zh": "Version file: {file}" "zh": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update."
}, },
"Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.": { "WARNING: --skip-tests passed — skipping test verification.": {
"en": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", "en": "WARNING: --skip-tests passed — skipping test verification.",
"bg": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", "bg": "WARNING: --skip-tests passed — skipping test verification.",
"de": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", "de": "WARNING: --skip-tests passed — skipping test verification.",
"ru": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update.", "ru": "WARNING: --skip-tests passed — skipping test verification.",
"zh": "Vikunja API error (HTTP {status}): {message}. Task {task_id} was NOT updated. The merge succeeded but the Vikunja task needs manual update." "zh": "WARNING: --skip-tests passed — skipping test verification."
}, },
"WARNING: --skip-tests passed — skipping test verification.": { "Warning: could not fetch tags from origin.": {
"en": "WARNING: --skip-tests passed — skipping test verification.", "en": "Warning: could not fetch tags from origin.",
"bg": "WARNING: --skip-tests passed — skipping test verification.", "bg": "Warning: could not fetch tags from origin.",
"de": "WARNING: --skip-tests passed — skipping test verification.", "de": "Warning: could not fetch tags from origin.",
"ru": "WARNING: --skip-tests passed — skipping test verification.", "ru": "Warning: could not fetch tags from origin.",
"zh": "WARNING: --skip-tests passed — skipping test verification." "zh": "Warning: could not fetch tags from origin."
}, },
"Warning: could not fetch tags from origin.": { "Wiki integrity check failed — {count} issue(s)": {
"en": "Warning: could not fetch tags from origin.", "en": "Wiki integrity check failed — {count} issue(s)",
"bg": "Warning: could not fetch tags from origin.", "bg": "Wiki integrity check failed — {count} issue(s)",
"de": "Warning: could not fetch tags from origin.", "de": "Wiki integrity check failed — {count} issue(s)",
"ru": "Warning: could not fetch tags from origin.", "ru": "Wiki integrity check failed — {count} issue(s)",
"zh": "Warning: could not fetch tags from origin." "zh": "Wiki integrity check failed — {count} issue(s)"
}, },
"Wiki integrity check failed — {count} issue(s)": { "Wiki verification failed — {failures} page(s) empty or mismatched": {
"en": "Wiki integrity check failed — {count} issue(s)", "en": "Wiki verification failed — {failures} page(s) empty or mismatched",
"bg": "Wiki integrity check failed — {count} issue(s)", "bg": "Wiki verification failed — {failures} page(s) empty or mismatched",
"de": "Wiki integrity check failed — {count} issue(s)", "de": "Wiki verification failed — {failures} page(s) empty or mismatched",
"ru": "Wiki integrity check failed — {count} issue(s)", "ru": "Wiki verification failed — {failures} page(s) empty or mismatched",
"zh": "Wiki integrity check failed — {count} issue(s)" "zh": "Wiki verification failed — {failures} page(s) empty or mismatched"
}, },
"Wiki verification failed — {failures} page(s) empty or mismatched": { "[dry-run] Would commit: release: v{version}": {
"en": "Wiki verification failed — {failures} page(s) empty or mismatched", "en": "[dry-run] Would commit: release: v{version}",
"bg": "Wiki verification failed — {failures} page(s) empty or mismatched", "bg": "[dry-run] Would commit: release: v{version}",
"de": "Wiki verification failed — {failures} page(s) empty or mismatched", "de": "[dry-run] Would commit: release: v{version}",
"ru": "Wiki verification failed — {failures} page(s) empty or mismatched", "ru": "[dry-run] Would commit: release: v{version}",
"zh": "Wiki verification failed — {failures} page(s) empty or mismatched" "zh": "[dry-run] Would commit: release: v{version}"
}, },
"[dry-run] Would commit: release: v{version}": { "[dry-run] Would create tag: v{version}": {
"en": "[dry-run] Would commit: release: v{version}", "en": "[dry-run] Would create tag: v{version}",
"bg": "[dry-run] Would commit: release: v{version}", "bg": "[dry-run] Would create tag: v{version}",
"de": "[dry-run] Would commit: release: v{version}", "de": "[dry-run] Would create tag: v{version}",
"ru": "[dry-run] Would commit: release: v{version}", "ru": "[dry-run] Would create tag: v{version}",
"zh": "[dry-run] Would commit: release: v{version}" "zh": "[dry-run] Would create tag: v{version}"
}, },
"[dry-run] Would create tag: v{version}": { "[dry-run] Would create tag: {tag}": {
"en": "[dry-run] Would create tag: v{version}", "en": "[dry-run] Would create tag: {tag}",
"bg": "[dry-run] Would create tag: v{version}", "bg": "[dry-run] Would create tag: {tag}",
"de": "[dry-run] Would create tag: v{version}", "de": "[dry-run] Would create tag: {tag}",
"ru": "[dry-run] Would create tag: v{version}", "ru": "[dry-run] Would create tag: {tag}",
"zh": "[dry-run] Would create tag: v{version}" "zh": "[dry-run] Would create tag: {tag}"
}, },
"[dry-run] Would create tag: {tag}": { "[dry-run] Would push commit to master": {
"en": "[dry-run] Would create tag: {tag}", "en": "[dry-run] Would push commit to master",
"bg": "[dry-run] Would create tag: {tag}", "bg": "[dry-run] Would push commit to master",
"de": "[dry-run] Would create tag: {tag}", "de": "[dry-run] Would push commit to master",
"ru": "[dry-run] Would create tag: {tag}", "ru": "[dry-run] Would push commit to master",
"zh": "[dry-run] Would create tag: {tag}" "zh": "[dry-run] Would push commit to master"
}, },
"[dry-run] Would push commit to master": { "[dry-run] Would sync page: {title} ({chars} chars)": {
"en": "[dry-run] Would push commit to master", "en": "[dry-run] Would sync page: {title} ({chars} chars)",
"bg": "[dry-run] Would push commit to master", "bg": "[dry-run] Would sync page: {title} ({chars} chars)",
"de": "[dry-run] Would push commit to master", "de": "[dry-run] Would sync page: {title} ({chars} chars)",
"ru": "[dry-run] Would push commit to master", "ru": "[dry-run] Would sync page: {title} ({chars} chars)",
"zh": "[dry-run] Would push commit to master" "zh": "[dry-run] Would sync page: {title} ({chars} chars)"
}, },
"[dry-run] Would sync page: {title} ({chars} chars)": { "[dry-run] Would update {changelog_file}": {
"en": "[dry-run] Would sync page: {title} ({chars} chars)", "en": "[dry-run] Would update {changelog_file}",
"bg": "[dry-run] Would sync page: {title} ({chars} chars)", "bg": "[dry-run] Would update {changelog_file}",
"de": "[dry-run] Would sync page: {title} ({chars} chars)", "de": "[dry-run] Would update {changelog_file}",
"ru": "[dry-run] Would sync page: {title} ({chars} chars)", "ru": "[dry-run] Would update {changelog_file}",
"zh": "[dry-run] Would sync page: {title} ({chars} chars)" "zh": "[dry-run] Would update {changelog_file}"
}, },
"[dry-run] Would update {changelog_file}": { "[dry-run] Would update {init}": {
"en": "[dry-run] Would update {changelog_file}", "en": "[dry-run] Would update {init}",
"bg": "[dry-run] Would update {changelog_file}", "bg": "[dry-run] Would update {init}",
"de": "[dry-run] Would update {changelog_file}", "de": "[dry-run] Would update {init}",
"ru": "[dry-run] Would update {changelog_file}", "ru": "[dry-run] Would update {init}",
"zh": "[dry-run] Would update {changelog_file}" "zh": "[dry-run] Would update {init}"
}, },
"[dry-run] Would update {init}": { "active": {
"en": "[dry-run] Would update {init}", "en": "active",
"bg": "[dry-run] Would update {init}", "bg": "активен",
"de": "[dry-run] Would update {init}", "de": "aktiv",
"ru": "[dry-run] Would update {init}", "ru": "активен",
"zh": "[dry-run] Would update {init}" "zh": "活跃"
}, },
"active": { "completed": {
"en": "active", "en": "completed",
"bg": "активен", "bg": "завършен",
"de": "aktiv", "de": "abgeschlossen",
"ru": "активен", "ru": "завершён",
"zh": "活跃" "zh": "已完成"
}, },
"completed": { "failed": {
"en": "completed", "en": "failed",
"bg": "завършен", "bg": "неуспешен",
"de": "abgeschlossen", "de": "fehlgeschlagen",
"ru": "завершён", "ru": "неудачный",
"zh": "已完成" "zh": "失败"
}, },
"failed": { "git command failed ({cmd}): {stderr}": {
"en": "failed", "en": "git command failed ({cmd}): {stderr}",
"bg": "неуспешен", "bg": "git command failed ({cmd}): {stderr}",
"de": "fehlgeschlagen", "de": "git command failed ({cmd}): {stderr}",
"ru": "неудачный", "ru": "git command failed ({cmd}): {stderr}",
"zh": "失败" "zh": "git command failed ({cmd}): {stderr}"
}, },
"git command failed ({cmd}): {stderr}": { "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.": {
"en": "git command failed ({cmd}): {stderr}", "en": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.",
"bg": "git command failed ({cmd}): {stderr}", "bg": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.",
"de": "git command failed ({cmd}): {stderr}", "de": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.",
"ru": "git command failed ({cmd}): {stderr}", "ru": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.",
"zh": "git command failed ({cmd}): {stderr}" "zh": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history."
}, },
"git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.": { "git-cliff returned empty version.": {
"en": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", "en": "git-cliff returned empty version.",
"bg": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", "bg": "git-cliff returned empty version.",
"de": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", "de": "git-cliff returned empty version.",
"ru": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history.", "ru": "git-cliff returned empty version.",
"zh": "git-cliff generated empty changelog for v{version}. Check cliff.toml and commit history." "zh": "git-cliff returned empty version."
}, },
"git-cliff returned empty version.": { "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).": {
"en": "git-cliff returned empty version.", "en": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).",
"bg": "git-cliff returned empty version.", "bg": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).",
"de": "git-cliff returned empty version.", "de": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).",
"ru": "git-cliff returned empty version.", "ru": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).",
"zh": "git-cliff returned empty version." "zh": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1)."
}, },
"git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).": { "in_progress": {
"en": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", "en": "in progress",
"bg": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", "bg": "в процес",
"de": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", "de": "in Bearbeitung",
"ru": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).", "ru": "в процессе",
"zh": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1)." "zh": "进行中"
}, },
"in_progress": { "inactive": {
"en": "in progress", "en": "inactive",
"bg": "в процес", "bg": "неактивен",
"de": "in Bearbeitung", "de": "inaktiv",
"ru": "в процессе", "ru": "неактивен",
"zh": "进行中" "zh": "未激活"
}, },
"inactive": { "mapping.json keys and values must be strings, got {k}={v}": {
"en": "inactive", "en": "mapping.json keys and values must be strings, got {k}={v}",
"bg": "неактивен", "bg": "mapping.json keys and values must be strings, got {k}={v}",
"de": "inaktiv", "de": "mapping.json keys and values must be strings, got {k}={v}",
"ru": "неактивен", "ru": "mapping.json keys and values must be strings, got {k}={v}",
"zh": "未激活" "zh": "mapping.json keys and values must be strings, got {k}={v}"
}, },
"mapping.json keys and values must be strings, got {k}={v}": { "mapping.json must be a dict of file-path -> page-title, got {type}": {
"en": "mapping.json keys and values must be strings, got {k}={v}", "en": "mapping.json must be a dict of file-path -> page-title, got {type}",
"bg": "mapping.json keys and values must be strings, got {k}={v}", "bg": "mapping.json must be a dict of file-path -> page-title, got {type}",
"de": "mapping.json keys and values must be strings, got {k}={v}", "de": "mapping.json must be a dict of file-path -> page-title, got {type}",
"ru": "mapping.json keys and values must be strings, got {k}={v}", "ru": "mapping.json must be a dict of file-path -> page-title, got {type}",
"zh": "mapping.json keys and values must be strings, got {k}={v}" "zh": "mapping.json must be a dict of file-path -> page-title, got {type}"
}, },
"mapping.json must be a dict of file-path -> page-title, got {type}": { "pending": {
"en": "mapping.json must be a dict of file-path -> page-title, got {type}", "en": "pending",
"bg": "mapping.json must be a dict of file-path -> page-title, got {type}", "bg": "в очакване",
"de": "mapping.json must be a dict of file-path -> page-title, got {type}", "de": "ausstehend",
"ru": "mapping.json must be a dict of file-path -> page-title, got {type}", "ru": "ожидает",
"zh": "mapping.json must be a dict of file-path -> page-title, got {type}" "zh": "待处理"
}, },
"pending": { "unknown": {
"en": "pending", "en": "unknown",
"bg": "в очакване", "bg": "неизвестен",
"de": "ausstehend", "de": "unbekannt",
"ru": "ожидает", "ru": "неизвестно",
"zh": "待处理" "zh": "未知"
}, },
"unknown": { "{file} already exists. Use --force to overwrite.": {
"en": "unknown", "en": "{file} already exists. Use --force to overwrite.",
"bg": "неизвестен", "bg": "{file} already exists. Use --force to overwrite.",
"de": "unbekannt", "de": "{file} already exists. Use --force to overwrite.",
"ru": "неизвестно", "ru": "{file} already exists. Use --force to overwrite.",
"zh": "未知" "zh": "{file} already exists. Use --force to overwrite."
}, },
"{file} already exists. Use --force to overwrite.": { "--skip-build: skipping package build and PyPI publish.": {
"en": "{file} already exists. Use --force to overwrite.", "en": "--skip-build: skipping package build and PyPI publish.",
"bg": "{file} already exists. Use --force to overwrite.", "bg": "--skip-build: skipping package build and PyPI publish.",
"de": "{file} already exists. Use --force to overwrite.", "de": "--skip-build: skipping package build and PyPI publish.",
"ru": "{file} already exists. Use --force to overwrite.", "ru": "--skip-build: skipping package build and PyPI publish.",
"zh": "{file} already exists. Use --force to overwrite." "zh": "--skip-build: skipping package build and PyPI publish."
}, },
"--skip-build: skipping package build and PyPI publish.": { "Integration tests cancelled — another runner failed.": {
"en": "--skip-build: skipping package build and PyPI publish.", "en": "Integration tests cancelled — another runner failed.",
"bg": "--skip-build: skipping package build and PyPI publish.", "bg": "Integration tests cancelled — another runner failed.",
"de": "--skip-build: skipping package build and PyPI publish.", "de": "Integration tests cancelled — another runner failed.",
"ru": "--skip-build: skipping package build and PyPI publish.", "ru": "Integration tests cancelled — another runner failed.",
"zh": "--skip-build: skipping package build and PyPI publish." "zh": "Integration tests cancelled — another runner failed."
}, },
"Integration tests cancelled — another runner failed.": { "Integration tests failed with exit code {code}": {
"en": "Integration tests cancelled — another runner failed.", "en": "Integration tests failed with exit code {code}",
"bg": "Integration tests cancelled — another runner failed.", "bg": "Integration tests failed with exit code {code}",
"de": "Integration tests cancelled — another runner failed.", "de": "Integration tests failed with exit code {code}",
"ru": "Integration tests cancelled — another runner failed.", "ru": "Integration tests failed with exit code {code}",
"zh": "Integration tests cancelled — another runner failed." "zh": "Integration tests failed with exit code {code}"
}, },
"Integration tests failed with exit code {code}": { "Integration tests passed.": {
"en": "Integration tests failed with exit code {code}", "en": "Integration tests passed.",
"bg": "Integration tests failed with exit code {code}", "bg": "Integration tests passed.",
"de": "Integration tests failed with exit code {code}", "de": "Integration tests passed.",
"ru": "Integration tests failed with exit code {code}", "ru": "Integration tests passed.",
"zh": "Integration tests failed with exit code {code}" "zh": "Integration tests passed."
}, },
"Integration tests passed.": { "Merged {count} reports: {tests} tests, {failures} failures → {output}": {
"en": "Integration tests passed.", "en": "Merged {count} reports: {tests} tests, {failures} failures → {output}",
"bg": "Integration tests passed.", "bg": "Merged {count} reports: {tests} tests, {failures} failures → {output}",
"de": "Integration tests passed.", "de": "Merged {count} reports: {tests} tests, {failures} failures → {output}",
"ru": "Integration tests passed.", "ru": "Merged {count} reports: {tests} tests, {failures} failures → {output}",
"zh": "Integration tests passed." "zh": "Merged {count} reports: {tests} tests, {failures} failures → {output}"
}, },
"Merged {count} reports: {tests} tests, {failures} failures → {output}": { "No JUnit reports found matching {pattern} — skipping merge.": {
"en": "Merged {count} reports: {tests} tests, {failures} failures → {output}", "en": "No JUnit reports found matching {pattern} — skipping merge.",
"bg": "Merged {count} reports: {tests} tests, {failures} failures → {output}", "bg": "No JUnit reports found matching {pattern} — skipping merge.",
"de": "Merged {count} reports: {tests} tests, {failures} failures → {output}", "de": "No JUnit reports found matching {pattern} — skipping merge.",
"ru": "Merged {count} reports: {tests} tests, {failures} failures → {output}", "ru": "No JUnit reports found matching {pattern} — skipping merge.",
"zh": "Merged {count} reports: {tests} tests, {failures} failures → {output}" "zh": "No JUnit reports found matching {pattern} — skipping merge."
}, },
"No JUnit reports found matching {pattern} — skipping merge.": { "Roles directory not found: {path}": {
"en": "No JUnit reports found matching {pattern} — skipping merge.", "en": "Roles directory not found: {path}",
"bg": "No JUnit reports found matching {pattern} — skipping merge.", "bg": "Roles directory not found: {path}",
"de": "No JUnit reports found matching {pattern} — skipping merge.", "de": "Roles directory not found: {path}",
"ru": "No JUnit reports found matching {pattern} — skipping merge.", "ru": "Roles directory not found: {path}",
"zh": "No JUnit reports found matching {pattern} — skipping merge." "zh": "Roles directory not found: {path}"
}, },
"Roles directory not found: {path}": { "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.": {
"en": "Roles directory not found: {path}", "en": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.",
"bg": "Roles directory not found: {path}", "bg": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.",
"de": "Roles directory not found: {path}", "de": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.",
"ru": "Roles directory not found: {path}", "ru": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit.",
"zh": "Roles directory not found: {path}" "zh": "Per-test speed check FAILED: {count} test(s) exceed {limit}s limit."
} },
"Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.": {
"en": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.",
"bg": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.",
"de": "Test '{name}' took {elapsed:.2f}s (limit: {limit}s). Optimise: use lighter fixtures, reduce I/O, or mock external calls.",
"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."
},
"Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).": {
"en": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).",
"bg": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).",
"de": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).",
"ru": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit).",
"zh": "Unit tests passed in {duration:.2f}s (under {max}s limit, all tests under {single}s per-test limit)."
}
} }
+2 -1
View File
@@ -566,7 +566,8 @@ class TestVikunjaClient:
json={"done": True}, json={"done": True},
) )
def test_http_error_raises_api_error(self) -> None: @patch("devx.api_clients.time.sleep")
def test_http_error_raises_api_error(self, mock_sleep: MagicMock) -> None:
client = VikunjaClient("https://work.example.com", "tok") client = VikunjaClient("https://work.example.com", "tok")
mock_resp = MagicMock() mock_resp = MagicMock()
mock_resp.raise_for_status.side_effect = _mock_http_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, "server error") mock_resp.raise_for_status.side_effect = _mock_http_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, "server error")
+164 -10
View File
@@ -1,4 +1,4 @@
"""Unit tests for scripts/check_test_speed.py.""" """Unit tests for devx.tools.check_test_speed."""
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@@ -8,10 +8,13 @@ from click.testing import CliRunner
from devx.tools.check_test_speed import ( from devx.tools.check_test_speed import (
DEFAULT_MAX_SECONDS, DEFAULT_MAX_SECONDS,
DEFAULT_MAX_SINGLE_SECONDS,
TEST_COMMAND, TEST_COMMAND,
check_per_test_speed,
check_speed, check_speed,
cli, cli,
parse_duration, parse_duration,
parse_per_test_durations,
run_tests, run_tests,
) )
@@ -23,12 +26,23 @@ class TestRunTests:
stdout, stderr = run_tests() stdout, stderr = run_tests()
assert stdout == "out" assert stdout == "out"
assert stderr == "err" assert stderr == "err"
mock_run.assert_called_once_with( mock_run.assert_called_once()
TEST_COMMAND, call_kwargs = mock_run.call_args
capture_output=True, assert call_kwargs.args[0] == TEST_COMMAND
text=True, assert call_kwargs.kwargs["capture_output"] is True
check=False, assert call_kwargs.kwargs["text"] is True
) assert call_kwargs.kwargs["check"] is False
env = call_kwargs.kwargs["env"]
assert "--durations=0" in env["PYTEST_ADDOPTS"]
@patch("devx.tools.check_test_speed.subprocess.run")
def test_run_tests_preserves_existing_pytest_addopts(self, mock_run: MagicMock) -> None:
mock_run.return_value = MagicMock(stdout="out", stderr="err", returncode=0)
with patch.dict("os.environ", {"PYTEST_ADDOPTS": "-x"}, clear=False):
run_tests()
env = mock_run.call_args.kwargs["env"]
assert "--durations=0" in env["PYTEST_ADDOPTS"]
assert "-x" in env["PYTEST_ADDOPTS"]
class TestParseDuration: class TestParseDuration:
@@ -48,6 +62,38 @@ class TestParseDuration:
assert "Could not parse" in str(exc.value) assert "Could not parse" in str(exc.value)
class TestParsePerTestDurations:
def test_parses_call_lines(self) -> None:
output = "0.01s call tests/test_foo.py::test_bar\n"
durations = parse_per_test_durations(output)
assert len(durations) == 1
assert durations[0] == ("tests/test_foo.py::test_bar", 0.01)
def test_parses_setup_and_teardown(self) -> None:
output = (
"0.02s setup tests/test_foo.py::test_bar\n"
"0.01s call tests/test_foo.py::test_bar\n"
"0.00s teardown tests/test_foo.py::test_bar\n"
)
durations = parse_per_test_durations(output)
assert len(durations) == 3
names = [d[0] for d in durations]
assert "tests/test_foo.py::test_bar" in names
def test_sorted_slowest_first(self) -> None:
output = "0.01s call tests/test_a.py::test_slow\n0.50s call tests/test_b.py::test_fast\n"
durations = parse_per_test_durations(output)
assert durations[0][1] >= durations[1][1]
assert durations[0][1] == 0.50
def test_empty_output(self) -> None:
assert parse_per_test_durations("") == []
def test_ignores_non_duration_lines(self) -> None:
output = "Some random line\n234 passed in 0.70s\n"
assert parse_per_test_durations(output) == []
class TestCheckSpeed: class TestCheckSpeed:
def test_under_budget_passes(self) -> None: def test_under_budget_passes(self) -> None:
check_speed(1.0, 2.0) # should not raise check_speed(1.0, 2.0) # should not raise
@@ -64,6 +110,31 @@ class TestCheckSpeed:
assert "max allowed: 2.0s" in msg assert "max allowed: 2.0s" in msg
class TestCheckPerTestSpeed:
def test_no_violations_when_all_fast(self) -> None:
durations = [("test_a", 0.1), ("test_b", 0.2)]
assert check_per_test_speed(durations, 0.5) == []
def test_violation_when_test_exceeds_limit(self) -> None:
durations = [("test_slow", 0.6), ("test_fast", 0.1)]
violations = check_per_test_speed(durations, 0.5)
assert len(violations) == 1
assert "test_slow" in violations[0]
assert "0.60s" in violations[0]
def test_multiple_violations(self) -> None:
durations = [("test_a", 0.7), ("test_b", 0.6), ("test_c", 0.1)]
violations = check_per_test_speed(durations, 0.5)
assert len(violations) == 2
def test_exact_limit_passes(self) -> None:
durations = [("test_a", 0.5)]
assert check_per_test_speed(durations, 0.5) == []
def test_empty_durations(self) -> None:
assert check_per_test_speed([], 0.5) == []
def test_main_module_block() -> None: def test_main_module_block() -> None:
import devx.tools.check_test_speed as cts import devx.tools.check_test_speed as cts
@@ -77,39 +148,71 @@ class TestMain:
@patch("devx.tools.check_test_speed.run_tests") @patch("devx.tools.check_test_speed.run_tests")
@patch("devx.tools.check_test_speed.parse_duration") @patch("devx.tools.check_test_speed.parse_duration")
@patch("devx.tools.check_test_speed.check_speed") @patch("devx.tools.check_test_speed.check_speed")
@patch("devx.tools.check_test_speed.parse_per_test_durations")
@patch("devx.tools.check_test_speed.check_per_test_speed")
def test_successful_run( def test_successful_run(
self, self,
mock_check_per: MagicMock,
mock_parse_per: MagicMock,
mock_check: MagicMock, mock_check: MagicMock,
mock_parse: MagicMock, mock_parse: MagicMock,
mock_run: MagicMock, mock_run: MagicMock,
) -> None: ) -> None:
mock_run.return_value = ("stdout\n", "stderr\n") mock_run.return_value = ("stdout\n", "stderr\n")
mock_parse.return_value = 1.5 mock_parse.return_value = 1.5
mock_parse_per.return_value = []
mock_check_per.return_value = []
runner = CliRunner() runner = CliRunner()
result = runner.invoke(cli, []) result = runner.invoke(cli, [])
assert result.exit_code == 0 assert result.exit_code == 0
assert "1.50s" in result.output assert "1.50s" in result.output
assert "under 2.0s limit" in result.output assert "under 10.0s limit" in result.output
mock_run.assert_called_once() mock_run.assert_called_once()
mock_parse.assert_called_once_with("stdout\n\nstderr\n") mock_parse.assert_called_once_with("stdout\n\nstderr\n")
mock_check.assert_called_once_with(1.5, DEFAULT_MAX_SECONDS) mock_check.assert_called_once_with(1.5, DEFAULT_MAX_SECONDS)
mock_parse_per.assert_called_once()
mock_check_per.assert_called_once_with([], DEFAULT_MAX_SINGLE_SECONDS)
@patch("devx.tools.check_test_speed.run_tests") @patch("devx.tools.check_test_speed.run_tests")
@patch("devx.tools.check_test_speed.parse_duration") @patch("devx.tools.check_test_speed.parse_duration")
def test_slow_tests_exit( def test_slow_total_exits(
self, self,
mock_parse: MagicMock, mock_parse: MagicMock,
mock_run: MagicMock, mock_run: MagicMock,
) -> None: ) -> None:
mock_run.return_value = ("out\n", "err\n") mock_run.return_value = ("out\n", "err\n")
mock_parse.return_value = 3.0 mock_parse.return_value = 15.0
runner = CliRunner() runner = CliRunner()
result = runner.invoke(cli, []) result = runner.invoke(cli, [])
assert result.exit_code == 1 assert result.exit_code == 1
assert "too slow" in result.output.lower() assert "too slow" in result.output.lower()
@patch("devx.tools.check_test_speed.run_tests")
@patch("devx.tools.check_test_speed.parse_duration")
@patch("devx.tools.check_test_speed.check_speed")
@patch("devx.tools.check_test_speed.parse_per_test_durations")
@patch("devx.tools.check_test_speed.check_per_test_speed")
def test_per_test_violation_exits(
self,
mock_check_per: MagicMock,
mock_parse_per: MagicMock,
mock_check: MagicMock,
mock_parse: MagicMock,
mock_run: MagicMock,
) -> None:
mock_run.return_value = ("out\n", "err\n")
mock_parse.return_value = 3.0
mock_parse_per.return_value = [("test_slow", 0.8)]
mock_check_per.return_value = ["Test 'test_slow' took 0.80s (limit: 0.5s)."]
runner = CliRunner()
result = runner.invoke(cli, [])
assert result.exit_code == 1
assert "Per-test speed check FAILED" in result.output
assert "test_slow" in result.output
@patch("devx.tools.check_test_speed.run_tests") @patch("devx.tools.check_test_speed.run_tests")
def test_parse_failure_exits( def test_parse_failure_exits(
self, self,
@@ -125,16 +228,67 @@ class TestMain:
@patch("devx.tools.check_test_speed.run_tests") @patch("devx.tools.check_test_speed.run_tests")
@patch("devx.tools.check_test_speed.parse_duration") @patch("devx.tools.check_test_speed.parse_duration")
@patch("devx.tools.check_test_speed.check_speed") @patch("devx.tools.check_test_speed.check_speed")
@patch("devx.tools.check_test_speed.parse_per_test_durations")
@patch("devx.tools.check_test_speed.check_per_test_speed")
def test_custom_max_seconds( def test_custom_max_seconds(
self, self,
mock_check_per: MagicMock,
mock_parse_per: MagicMock,
mock_check: MagicMock, mock_check: MagicMock,
mock_parse: MagicMock, mock_parse: MagicMock,
mock_run: MagicMock, mock_run: MagicMock,
) -> None: ) -> None:
mock_run.return_value = ("out\n", "err\n") mock_run.return_value = ("out\n", "err\n")
mock_parse.return_value = 0.5 mock_parse.return_value = 0.5
mock_parse_per.return_value = []
mock_check_per.return_value = []
runner = CliRunner() runner = CliRunner()
result = runner.invoke(cli, ["--max-seconds", "1.5"]) result = runner.invoke(cli, ["--max-seconds", "1.5"])
assert result.exit_code == 0 assert result.exit_code == 0
mock_check.assert_called_once_with(0.5, 1.5) mock_check.assert_called_once_with(0.5, 1.5)
@patch("devx.tools.check_test_speed.run_tests")
@patch("devx.tools.check_test_speed.parse_duration")
@patch("devx.tools.check_test_speed.check_speed")
@patch("devx.tools.check_test_speed.parse_per_test_durations")
@patch("devx.tools.check_test_speed.check_per_test_speed")
def test_disable_per_test_check(
self,
mock_check_per: MagicMock,
mock_parse_per: MagicMock,
mock_check: MagicMock,
mock_parse: MagicMock,
mock_run: MagicMock,
) -> None:
mock_run.return_value = ("out\n", "err\n")
mock_parse.return_value = 1.0
runner = CliRunner()
result = runner.invoke(cli, ["--max-single-seconds", "0"])
assert result.exit_code == 0
mock_parse_per.assert_not_called()
mock_check_per.assert_not_called()
@patch("devx.tools.check_test_speed.run_tests")
@patch("devx.tools.check_test_speed.parse_duration")
@patch("devx.tools.check_test_speed.check_speed")
@patch("devx.tools.check_test_speed.parse_per_test_durations")
@patch("devx.tools.check_test_speed.check_per_test_speed")
def test_custom_max_single_seconds(
self,
mock_check_per: MagicMock,
mock_parse_per: MagicMock,
mock_check: MagicMock,
mock_parse: MagicMock,
mock_run: MagicMock,
) -> None:
mock_run.return_value = ("out\n", "err\n")
mock_parse.return_value = 1.0
mock_parse_per.return_value = []
mock_check_per.return_value = []
runner = CliRunner()
result = runner.invoke(cli, ["--max-single-seconds", "1.0"])
assert result.exit_code == 0
mock_check_per.assert_called_once_with([], 1.0)
+3
View File
@@ -131,6 +131,7 @@ class TestCli:
clear=True, clear=True,
), ),
patch("devx.ci.integration_guard.POLL_INTERVAL", 0.01), patch("devx.ci.integration_guard.POLL_INTERVAL", 0.01),
patch("devx.molecule.molecule_ci_guard.POLL_INTERVAL", 0.01),
patch("devx.ci.integration_guard.subprocess.Popen") as mock_popen, patch("devx.ci.integration_guard.subprocess.Popen") as mock_popen,
patch("devx.molecule.molecule_ci_guard.get_running_jobs", side_effect=get_jobs_side_effect), patch("devx.molecule.molecule_ci_guard.get_running_jobs", side_effect=get_jobs_side_effect),
patch("os.killpg") as mock_killpg, patch("os.killpg") as mock_killpg,
@@ -177,6 +178,7 @@ class TestCli:
clear=True, clear=True,
), ),
patch("devx.ci.integration_guard.POLL_INTERVAL", 0.01), patch("devx.ci.integration_guard.POLL_INTERVAL", 0.01),
patch("devx.molecule.molecule_ci_guard.POLL_INTERVAL", 0.01),
patch("devx.ci.integration_guard.subprocess.Popen") as mock_popen, patch("devx.ci.integration_guard.subprocess.Popen") as mock_popen,
patch("devx.molecule.molecule_ci_guard.get_running_jobs", side_effect=get_jobs_side_effect), patch("devx.molecule.molecule_ci_guard.get_running_jobs", side_effect=get_jobs_side_effect),
patch("os.killpg", side_effect=ProcessLookupError("no such process")), patch("os.killpg", side_effect=ProcessLookupError("no such process")),
@@ -221,6 +223,7 @@ class TestCli:
clear=True, clear=True,
), ),
patch("devx.ci.integration_guard.POLL_INTERVAL", 0.01), patch("devx.ci.integration_guard.POLL_INTERVAL", 0.01),
patch("devx.molecule.molecule_ci_guard.POLL_INTERVAL", 0.01),
patch("devx.ci.integration_guard.subprocess.Popen") as mock_popen, patch("devx.ci.integration_guard.subprocess.Popen") as mock_popen,
patch("devx.molecule.molecule_ci_guard.get_running_jobs", side_effect=get_jobs_side_effect), patch("devx.molecule.molecule_ci_guard.get_running_jobs", side_effect=get_jobs_side_effect),
patch("os.killpg") as mock_killpg, patch("os.killpg") as mock_killpg,