from __future__ import annotations from urllib.parse import urlparse from starlette.requests import Request from starlette.responses import RedirectResponse LOCALES = ("pt", "en") DEFAULT = "pt" COOKIE = "lang" COOKIE_MAX_AGE = 60 * 60 * 24 * 365 _PT = { "tagline": "Observatório público de trackers BitTorrent", "nav_trackers": "Trackers", "nav_trackers_sub": "lista", "nav_lists": "Criar lista", "nav_lists_sub": "smart", "nav_github": "GitHub", "nav_github_sub": "código", "footer_utc": "medições em UTC", "footer_disclaimer": "Não é um tracker BitTorrent · não indexa torrents", "stats_label": "Resumo", "stats_monitored": "monitorados", "stats_up": "ativos", "stats_degraded": "degradados", "stats_down": "indisponíveis", "stats_unknown": "desconhecidos", "stats_last_import": "última importação", "stats_last_probe": "última medição", "filter_search": "Busca", "filter_search_ph": "hostname ou URL", "filter_protocol": "Protocolo", "filter_any": "qualquer", "filter_status": "Status", "filter_source": "Origem", "filter_sort": "Ordenar", "sort_checked": "última verificação", "sort_latency": "latência", "sort_url": "URL", "sort_status": "status", "sort_protocol": "protocolo", "filter_order": "Ordem", "order_desc": "desc", "order_asc": "asc", "filter_submit": "Filtrar", "col_tracker": "Tracker", "col_protocol": "Protocolo", "col_status": "Status", "col_uptime7": "Uptime 7d", "col_latency": "Latência med.", "col_ip": "IPv4 / IPv6", "col_asn": "ASN / rede", "col_sources": "Fontes", "col_aliases": "Aliases", "col_last_check": "Última verificação", "copy": "copiar", "copy_btn": "Copiar", "copied": "copiado", "th_latency": "Latência", "provisional": "provisório", "empty_inventory": "Nenhum tracker no inventário ainda.", "pagination": "Paginação", "prev": "Anterior", "next": "Próxima", "page_of": "Página {page} / {pages} ({total})", "lists_title": "Criar lista", "lists_heading": "Smart List", "lists_blurb": ( "Seleção de trackers estáveis e com diversidade de infraestrutura. " "Classificações das fontes externas não substituem as medições próprias." ), "min_uptime": "Uptime mínimo (0–1)", "max_latency": "Latência máxima (ms)", "filter_ip": "IP", "filter_limit": "Quantidade (1–50)", "min_age": "Idade mínima (dias)", "diversity": "Diversidade de infraestrutura", "diversity_on": "ligada", "diversity_off": "desligada", "generate": "Gerar", "selected_count": "trackers selecionados.", "download_txt": "Baixar TXT", "open_json": "Abrir JSON", "permalink": "URL permanente", "back": "voltar", "score": "pontuação", "score_insufficient": "insuficiente", "uptime_24h": "Uptime 24h", "uptime_7d": "Uptime 7d", "uptime_30d": "Uptime 30d", "latency_med_7d": "Latência med. 7d", "latency_p95": "Latência p95 7d", "valid_rate": "Respostas válidas 7d", "measurements": "Medições", "tracking": "Acompanhamento", "days": "dias", "spark_label": "Latências recentes", "sources": "Origens", "best_at_source": "best na origem", "stable_at_source": "stable na origem", "live_at_source": "live na origem", "seen": "visto", "no_source": "Sem associação de origem", "dns": "DNS", "terminal_cname": "CNAME terminal", "infra_fp": "Fingerprint de infraestrutura", "asn": "ASN", "unknown": "desconhecido", "country": "País", "no_dns": "Sem registros DNS armazenados", "shared_infra": "Infraestrutura compartilhada", "shared_infra_blurb": ( "Outros endpoints com o mesmo fingerprint (sinal, não prova de operador):" ), "recent_history": "Histórico recente", "when": "Quando (UTC)", "valid": "Válida", "error": "Erro", "yes": "sim", "no": "não", "no_probes": "Sem medições ainda", "not_found": "Tracker não encontrado", "bootstrap_empty": ( "Inventário vazio: não foi possível importar fontes na inicialização ({exc}). " "O site está no ar; tente novamente com `python -m app.cli import-sources`." ), "bootstrap_stale": ( "Listas desatualizadas: não foi possível importar fontes na inicialização ({exc}). " "O site está no ar; tente novamente com `python -m app.cli import-sources`." ), } _EN = { "tagline": "Public observatory of BitTorrent trackers", "nav_trackers": "Trackers", "nav_trackers_sub": "list", "nav_lists": "Build a list", "nav_lists_sub": "smart", "nav_github": "GitHub", "nav_github_sub": "code", "footer_utc": "measurements in UTC", "footer_disclaimer": "Not a BitTorrent tracker · does not index torrents", "stats_label": "Overview", "stats_monitored": "watched", "stats_up": "up", "stats_degraded": "degraded", "stats_down": "down", "stats_unknown": "unknown", "stats_last_import": "last import", "stats_last_probe": "last probe", "filter_search": "Search", "filter_search_ph": "hostname or URL", "filter_protocol": "Protocol", "filter_any": "any", "filter_status": "Status", "filter_source": "Source", "filter_sort": "Sort", "sort_checked": "last check", "sort_latency": "latency", "sort_url": "URL", "sort_status": "status", "sort_protocol": "protocol", "filter_order": "Order", "order_desc": "desc", "order_asc": "asc", "filter_submit": "Filter", "col_tracker": "Tracker", "col_protocol": "Protocol", "col_status": "Status", "col_uptime7": "Uptime 7d", "col_latency": "Median latency", "col_ip": "IPv4 / IPv6", "col_asn": "ASN / net", "col_sources": "Sources", "col_aliases": "Aliases", "col_last_check": "Last check", "copy": "copy", "copy_btn": "Copy", "copied": "copied", "th_latency": "Latency", "provisional": "provisional", "empty_inventory": "No trackers in the inventory yet.", "pagination": "Pagination", "prev": "Previous", "next": "Next", "page_of": "Page {page} / {pages} ({total})", "lists_title": "Build a list", "lists_heading": "Smart List", "lists_blurb": ( "Stable trackers with some infrastructure spread. " "Labels from the upstream lists do not replace our own probes." ), "min_uptime": "Min uptime (0–1)", "max_latency": "Max latency (ms)", "filter_ip": "IP", "filter_limit": "Count (1–50)", "min_age": "Min age (days)", "diversity": "Infrastructure diversity", "diversity_on": "on", "diversity_off": "off", "generate": "Build", "selected_count": "trackers selected.", "download_txt": "Download TXT", "open_json": "Open JSON", "permalink": "Permalink", "back": "back", "score": "score", "score_insufficient": "not enough data", "uptime_24h": "Uptime 24h", "uptime_7d": "Uptime 7d", "uptime_30d": "Uptime 30d", "latency_med_7d": "Median latency 7d", "latency_p95": "p95 latency 7d", "valid_rate": "Valid replies 7d", "measurements": "Probes", "tracking": "Watched for", "days": "days", "spark_label": "Recent latencies", "sources": "Sources", "best_at_source": "best at source", "stable_at_source": "stable at source", "live_at_source": "live at source", "seen": "seen", "no_source": "No source association", "dns": "DNS", "terminal_cname": "Terminal CNAME", "infra_fp": "Infrastructure fingerprint", "asn": "ASN", "unknown": "unknown", "country": "Country", "no_dns": "No stored DNS records", "shared_infra": "Shared infrastructure", "shared_infra_blurb": ( "Other endpoints with the same fingerprint (a hint, not proof of the same operator):" ), "recent_history": "Recent history", "when": "When (UTC)", "valid": "Valid", "error": "Error", "yes": "yes", "no": "no", "no_probes": "No probes yet", "not_found": "Tracker not found", "bootstrap_empty": ( "Empty inventory: could not import sources on startup ({exc}). " "The site is up; retry with `python -m app.cli import-sources`." ), "bootstrap_stale": ( "Stale lists: could not import sources on startup ({exc}). " "The site is up; retry with `python -m app.cli import-sources`." ), } STRINGS = {"pt": _PT, "en": _EN} _BOOTSTRAP_KEYS = { "empty_import": "bootstrap_empty", "stale_import": "bootstrap_stale", } def normalize_lang(code: str | None) -> str: if not code: return DEFAULT tag = code.strip().lower().replace("_", "-") if tag in LOCALES: return tag if tag.startswith("en"): return "en" if tag.startswith("pt"): return "pt" return DEFAULT def from_accept_language(header: str | None) -> str: if not header: return DEFAULT for part in header.split(","): tag = part.split(";", 1)[0].strip() if not tag: continue return normalize_lang(tag) return DEFAULT def resolve_lang(request: Request) -> str: cookie = request.cookies.get(COOKIE) if cookie in LOCALES: return cookie return from_accept_language(request.headers.get("accept-language")) def html_lang(lang: str) -> str: return "pt-BR" if lang == "pt" else "en" def translate(lang: str, key: str, **kwargs: object) -> str: table = STRINGS.get(lang) or STRINGS[DEFAULT] text = table.get(key) or STRINGS[DEFAULT].get(key) or key if kwargs: return text.format(**kwargs) return text def translator(lang: str): def _t(key: str, **kwargs: object) -> str: return translate(lang, key, **kwargs) return _t def encode_bootstrap_note(kind: str, exc: object) -> str: return f"{kind}|{exc}" def render_bootstrap_note(lang: str, raw: str | None) -> str: if not raw: return "" kind, sep, detail = raw.partition("|") key = _BOOTSTRAP_KEYS.get(kind) if key and sep: return translate(lang, key, exc=detail) return raw def safe_back(request: Request) -> str: ref = request.headers.get("referer") if not ref: return "/" parsed = urlparse(ref) if parsed.netloc and parsed.netloc != request.url.netloc: return "/" path = parsed.path or "/" if path.startswith("/lang"): return "/" if parsed.query: return f"{path}?{parsed.query}" return path def lang_redirect(request: Request, code: str) -> RedirectResponse: lang = normalize_lang(code) resp = RedirectResponse(safe_back(request), status_code=303) resp.set_cookie( COOKIE, lang, max_age=COOKIE_MAX_AGE, path="/", samesite="lax", httponly=True, ) return resp