feat: status translation overrides via YAML config

report.status_translation in YAML config overrides/extends the builtin
STATUS_TRANSLATION dictionary; without the section behavior is unchanged.

- AppConfig.report_status_translation + _resolve_str_dict (warns and
  skips non-scalar values, resolves ${VAR} references)
- Config.get_status_translation() returns merged copy (lazy import,
  builtin dict never mutated)
- build_grouped_report() accepts optional status_translation parameter
- --init-config template gains commented example, docs/CONFIG.md updated

Closes #65
This commit is contained in:
Кокос Артем Николаевич
2026-07-17 15:18:27 +07:00
parent 09f6062e8c
commit 8614062ecd
7 changed files with 332 additions and 2 deletions

View File

@@ -35,6 +35,7 @@ def build_grouped_report(
issue_hours: List[Tuple[Issue, float, Optional[Dict[str, float]]]],
fill_time: bool = True,
by_activity: bool = False,
status_translation: Optional[Dict[str, str]] = None,
) -> List[ReportRow]:
"""
Преобразует список задач с затраченным временем в плоский список строк отчёта,
@@ -42,6 +43,11 @@ def build_grouped_report(
Предусловие: issue_hours должен быть отсортирован по (project, version).
Функция выполняет сортировку самостоятельно для защиты от несортированного ввода.
status_translation: перевод статусов; None — встроенный STATUS_TRANSLATION.
Переданный словарь ЗАМЕНЯЕТ встроенный полностью (без merge): статус,
отсутствующий в нём, выводится как есть (passthrough); пустой dict —
все статусы выводятся как есть.
"""
# Защитная сортировка -- гарантирует корректную группировку независимо от порядка на входе
@@ -49,6 +55,10 @@ def build_grouped_report(
issue_hours, key=lambda x: (str(x[0].project), get_version(x[0]), x[0].id)
)
translation = (
status_translation if status_translation is not None else STATUS_TRANSLATION
)
rows: List[ReportRow] = []
prev_project: str = ""
prev_version: str = ""
@@ -58,7 +68,7 @@ def build_grouped_report(
project = str(issue.project)
version = get_version(issue)
status_en = str(issue.status)
status_ru = STATUS_TRANSLATION.get(status_en, status_en)
status_ru = translation.get(status_en, status_en)
if fill_time:
if by_activity and activities: