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:]}"
PO
powerty
Author
· Staff
Aug. 26, 2026
Aug. 26, 2026
2
Views
0
Likes
3m
Read