Programming

1. i18n

"""
Column names stay English forever. Only what a human reads gets translated.

Add a language by adding a column here — no other file changes.
"""
from __future__ import annotations

LABELS: dict[str, dict[str, str]] = {
    # sections
    "report_title": {"en": "Retail credit portfolio review", "ru": "Обзор кредитного портфеля"},
    "sec_overview": {"en": "Portfolio at a glance", "ru": "Портфель: ключевые показатели"},
    "sec_origination": {"en": "New lending", "ru": "Новые выдачи"},
    "sec_quality": {"en": "Portfolio quality", "ru": "Качество портфеля"},
    "sec_vintage": {"en": "Vintage performance", "ru": "Винтажный анализ (когорты выдач)"},
    "sec_migration": {"en": "Migration and cure", "ru": "Миграция и восстановление (cure)"},
    "sec_pd": {"en": "Observed default probability", "ru": "Наблюдаемая вероятность дефолта"},
    "sec_bridge": {"en": "NPL bridge", "ru": "Факторный разбор изменения NPL"},
    "sec_client": {"en": "Client structure", "ru": "Структура клиентской базы"},
    "sec_insurance": {"en": "Insurance effect", "ru": "Эффект страхового покрытия"},
    "sec_segments": {"en": "Segment breakdown", "ru": "Разрезы по сегментам"},
    "sec_method": {"en": "Definitions and data quality", "ru": "Определения и качество данных"},

    # metrics
    "exposure": {"en": "Portfolio", "ru": "Портфель"},
    "npl_exposure": {"en": "NPL 90+", "ru": "NPL 90+"},
    "npl_ratio": {"en": "NPL 90+ ratio", "ru": "Доля NPL 90+"},
    "watch_exposure": {"en": "Watch list (31–90 DPD)", "ru": "Зона риска (31–90 дней)"},
    "watch_ratio": {"en": "Watch list share", "ru": "Доля зоны риска"},
    "coverage_ratio": {"en": "Reserve coverage of NPL", "ru": "Покрытие NPL резервами"},
    "n_loans": {"en": "Loans", "ru": "Кредиты"},
    "n_clients": {"en": "Clients", "ru": "Клиенты"},
    "avg_ticket": {"en": "Average ticket", "ru": "Средний чек"},
    "loans_per_client": {"en": "Loans per client", "ru": "Кредитов на клиента"},
    "amount": {"en": "Disbursed", "ru": "Выдано"},
    "n_new_clients": {"en": "New clients", "ru": "Новые клиенты"},
    "repeat_client_share": {"en": "Repeat clients", "ru": "Повторные клиенты"},
    "pd": {"en": "PD (12m, observed)", "ru": "PD (12 мес., фактическая)"},
    "cured": {"en": "Cured", "ru": "Восстановлено (cure)"},
    "inflow": {"en": "New defaults", "ru": "Новые дефолты"},
    "exited": {"en": "Left the book", "ru": "Выбытие с баланса"},
    "residual": {"en": "Amortisation and FX", "ru": "Погашение и валютная переоценка"},
    "opening": {"en": "Opening NPL", "ru": "NPL на начало"},
    "closing": {"en": "Closing NPL", "ru": "NPL на конец"},
    "insurance_npl_relief_pp": {"en": "NPL avoided by insurance, pp",
                                "ru": "NPL, предотвращённый страховкой, п.п."},
    "tcc_proxy": {"en": "Cost of credit (proxy)", "ru": "Стоимость кредита (оценка)"},
    "hhi_filial": {"en": "Branch concentration (HHI)", "ru": "Концентрация по филиалам (HHI)"},
    "top20_share": {"en": "Top-20 client share", "ru": "Доля топ-20 клиентов"},

    # dimensions
    "department": {"en": "Department", "ru": "Департамент"},
    "filial": {"en": "Branch", "ru": "Филиал"},
    "passport": {"en": "Product", "ru": "Продукт (паспорт)"},
    "avto": {"en": "Auto segment", "ru": "Автосегмент"},
    "valuta": {"en": "Currency", "ru": "Валюта"},
    "sud": {"en": "Legal status", "ru": "Судебный статус"},
    "bucket": {"en": "Delinquency bucket", "ru": "Корзина просрочки"},
    "__all__": {"en": "Total", "ru": "Итого"},

    # values
    "non_auto": {"en": "Non-auto", "ru": "Не авто"},
    "pervichka": {"en": "New cars", "ru": "Новые авто"},
    "vtorichka": {"en": "Used cars", "ru": "Авто с пробегом"},
    "norm": {"en": "Performing", "ru": "Без суда"},
    "official": {"en": "In court", "ru": "В суде"},
    "neofficial": {"en": "Pre-litigation", "ru": "Досудебная подготовка"},
    "CUR": {"en": "Current", "ru": "Без просрочки"},
    "B01_30": {"en": "1–30 DPD", "ru": "1–30 дн."},
    "B31_60": {"en": "31–60 DPD", "ru": "31–60 дн."},
    "B61_90": {"en": "61–90 DPD", "ru": "61–90 дн."},
    "B91_180": {"en": "91–180 DPD", "ru": "91–180 дн."},
    "B181_360": {"en": "181–360 DPD", "ru": "181–360 дн."},
    "B360P": {"en": "360+ DPD", "ru": "360+ дн."},
    "EXIT": {"en": "Left the book", "ru": "Выбыл"},
    "cured_out": {"en": "Cured", "ru": "Вылечен"},
    "improved": {"en": "Improved", "ru": "Улучшение"},
    "flat": {"en": "Unchanged", "ru": "Без изменений"},
    "worsened": {"en": "Worsened", "ru": "Ухудшение"},
    "exit": {"en": "Left the book", "ru": "Выбыл"},
    "closed_clean": {"en": "Closed performing", "ru": "Закрыт без дефолта"},
    "closed_bad": {"en": "Closed in default", "ru": "Закрыт в дефолте"},

    # units and chrome
    "unit_mlrd": {"en": "bn UZS", "ru": "млрд сум"},
    "unit_mln": {"en": "mn UZS", "ru": "млн сум"},
    "unit_pct": {"en": "%", "ru": "%"},
    "as_of": {"en": "Data as of", "ru": "Данные на"},
    "generated": {"en": "Generated", "ru": "Сформировано"},
    "methodology": {"en": "Methodology", "ru": "Методология"},
    "caveat": {"en": "Caveat", "ru": "Ограничение"},
}

MONTHS_RU = ["янв", "фев", "мар", "апр", "май", "июн",
             "июл", "авг", "сен", "окт", "ноя", "дек"]
MONTHS_EN = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]


def t(key: str, lang: str = "ru") -> str:
    entry = LABELS.get(str(key))
    if entry is None:
        return str(key)
    return entry.get(lang) or entry.get("en") or str(key)


def period_label(period: int, lang: str = "ru") -> str:
    y, m = divmod(int(period), 100)
    months = MONTHS_RU if lang == "ru" else MONTHS_EN
    return f"{months[m - 1]} {str(y)[2:]}"
Helpful? Dislike 0 Log in to react