from decimal import Decimal LABELS = { "en": { "cost_report_title": "Capex and Costs {year}", "cost_report_subtitle": "{dept} · cost budget: {prior} actual vs. {year} with adjustment", "total_across": "Total across {n} categories", "by_category": "Budget by category", "by_category_sub": "Total cost per category, {prior} vs. {year}", "change_by_category": "Change by category", "change_by_category_sub": "Percentage change from {prior} actual to {year} with adjustment", "change_legend": "Orange = cost increase · Blue = cost reduction", "prior_actual": "{prior} actual", "adjusted": "{year} with adjustment", "base": "{year} without adjustment", "change_eur": "Change (€)", "change_pct": "Change (%)", "cost_lines": "{n} cost lines", "cost_line": "1 cost line", "summary": "Summary", "summary_sub": "All categories, {prior} actual vs. {year} with adjustment", "category": "Category", "line": "Line", "site": "Site", "group": "Group", "total": "Total", "factor": "Factor", "reason": "Reason", "remarks": "Remarks", "budget_title": "Budget {year} · {dept}", "budget_sub": "Monthly budget by category", "revenue_total": "Sub total revenue", "sub_total": "Sub total {name}", "total_costs": "Total costs", "status": "Status", "exported": "Exported", "draft": "Draft", "in_review": "In review", "approved": "Approved", "inputs": "Inputs", "months": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], "no_data": "No cost lines yet.", "site_filter": "Site: {site}", }, "pt": { "cost_report_title": "Capex e Custos para {year}", "cost_report_subtitle": "{dept} · orçamento de custos: real {prior} vs. {year} com ajuste", "total_across": "Total de {n} categorias", "by_category": "Orçamento por categoria", "by_category_sub": "Custo total por categoria, {prior} vs. {year}", "change_by_category": "Variação por categoria", "change_by_category_sub": "Variação percentual do real {prior} para {year} com ajuste", "change_legend": "Laranja = aumento de custo · Azul = redução de custo", "prior_actual": "{prior} real", "adjusted": "{year} com ajuste", "base": "{year} sem ajuste", "change_eur": "Variação (€)", "change_pct": "Variação (%)", "cost_lines": "{n} linhas de custo", "cost_line": "1 linha de custo", "summary": "Resumo", "summary_sub": "Todas as categorias, real {prior} vs. {year} com ajuste", "category": "Categoria", "line": "Linha", "site": "Unidade", "group": "Grupo", "total": "Total", "factor": "Fator", "reason": "Motivo", "remarks": "Observações", "budget_title": "Orçamento {year} · {dept}", "budget_sub": "Orçamento mensal por categoria", "revenue_total": "Subtotal receitas", "sub_total": "Subtotal {name}", "total_costs": "Total de custos", "status": "Estado", "exported": "Exportado", "draft": "Rascunho", "in_review": "Em revisão", "approved": "Aprovado", "inputs": "Dados", "months": ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"], "no_data": "Ainda não há linhas de custo.", "site_filter": "Unidade: {site}", }, } class T: def __init__(self, lang: str, year: int, dept: str = ""): self.lang = lang if lang in LABELS else "pt" self.labels = LABELS[self.lang] self.ctx = {"year": year, "prior": year - 1, "dept": dept} def __call__(self, key: str, **kw): value = self.labels[key] return value.format(**{**self.ctx, **kw}) if isinstance(value, str) else value def _group(self, value: Decimal, decimals: int) -> str: q = Decimal(1).scaleb(-decimals) if decimals else Decimal(1) text = f"{abs(Decimal(value)).quantize(q):,.{decimals}f}" whole, _, frac = text.partition(".") whole = whole.replace(",", " ") if not frac: return whole return whole + ("," if self.lang == "pt" else ".") + frac def eur(self, value, decimals: int = 2, signed: bool = False) -> str: value = Decimal(value or 0) sign = ("+" if value > 0 else "−" if value < 0 else "") if signed else ("−" if value < 0 else "") body = self._group(value, decimals) return f"{sign}{body} €" if self.lang == "pt" else f"{sign}€{body}" def num(self, value, decimals: int = 2) -> str: value = Decimal(value or 0) return ("\u2212" if value < 0 else "") + self._group(value, decimals) def pct(self, value, signed: bool = True) -> str: if value is None: return "—" value = Decimal(value) sign = ("+" if value > 0 else "−" if value < 0 else "") if signed else "" return f"{sign}{self._group(value, 2)}%" def short_eur(self, value: float) -> str: v = abs(value) sign = "−" if value < 0 else "" if v >= 1_000_000: body = f"{v / 1_000_000:g}M" elif v >= 1000: body = f"{v / 1000:g}k" else: body = f"{v:g}" if self.lang == "pt": return f"{sign}{body.replace('.', ',')} €" return f"{sign}€{body}"