DASHBOARD_USER/DASHBOARD_PASSWORD in the environment.",
- "auth.updated_title": "Credentials updated",
- "auth.updated_body": "Your new password is now in effect. The browser is still holding the "
- "previous one, so sign in again to continue.",
- "auth.updated_link": "Back to the dashboard",
- "auth.required": "Authentication required.",
- "auth.login_intro": "Sign in to see the panel.",
- "auth.user": "User",
- "auth.password": "Password",
- "auth.enter": "Sign in",
- "auth.login_failed": "Wrong user or password.",
- "auth.too_many": "Too many attempts",
- "auth.too_many_body": "Wait {seconds}s before trying again.",
- "auth.challenge": "Security verification",
- "auth.challenge_prompt": "Select the {item} to confirm you are human:",
- "auth.item_key": "Key",
- "auth.item_shield": "Shield",
- "auth.item_lock": "Lock",
- "auth.item_star": "Star",
- "auth.item_heart": "Heart",
- "auth.item_bell": "Bell",
- "auth.item_lightning": "Lightning",
- "auth.item_gear": "Gear",
- "auth.logout": "Sign out",
- "auth.required_body": "This dashboard is private. Sign in to continue.",
- "gateway.title": "Gateway connection",
- "gateway.db_summary": "Operational ({connections} connections, {combos} combos)",
- "gateway.db_missing": "Database not found",
- "gateway.gateway": "Gateway",
- "gateway.status": "Status",
- "gateway.latency": "Latency",
- "gateway.database": "SQLite database",
- "gateway.offline": "OFFLINE",
- "gateway.no_response": "no response",
- # Vocabulário do ciclo: o trabalho que este agendador faz muda com o
- # gateway, e o texto acompanha.
- "cron.title": "Scheduler",
- "cron.run_now": "Run now",
- "cron.active": "Active · every {interval}s",
- "cron.disabled": "Disabled (CRON_ENABLED=0)",
- "cron.last_run": "Last run",
- "cron.next_run": "Next run",
- "cron.total_runs": "Total cycles",
- "cron.total_findings": "Findings so far",
- "cron.total_renewals": "Tokens renewed",
- "cron.last_result": "Last result",
- "cron.no_runs": "No cycle has run yet",
- # Vocabulário do ciclo: o trabalho que este agendador faz muda com o
- # gateway, e o texto acompanha.
- "cron.result_line": "{inspected} · ({duration}ms)",
- "cron.unavailable": "The scheduler is not available on this instance.",
- "cron.failed": "The cycle could not be completed: {error}",
- "connections.title": "Monitored connections",
- "connections.empty": "No connection registered on the gateway.",
- "connections.empty_hint": f"In {NOME_DO_GATEWAY} a connection is the upstream endpoint behind the "
- "registered models; none of them declares one yet.",
- "connections.lifecycle_note": "The upstream endpoint has no expiry of its own: what expires "
- "is the credential at the provider, outside this gateway.",
- "connections.served_models": "Models served",
- "table.provider": "Provider",
- "table.name": "Name",
- "table.type": "Type",
- "table.status": "Status",
- "table.remaining": "Time remaining",
- "table.diagnosis": "Renewal diagnosis",
- "table.details": "Details",
- "table.expires_at": "Expires at",
- "table.rpm_limit": "RPM limit",
- "table.tpm_limit": "TPM limit",
- "table.max_budget": "Budget ceiling",
- "table.api_base": "API base",
- "table.models": "models",
- "reason.local_ok": "Local instance answered with {count} model(s)",
- "reason.local_unreachable": "Local instance did not answer the model catalog",
- "reason.local_empty": "Local instance answered, but has no model installed yet",
- "egress.bound": "own egress",
- "egress.shared": "shares the gateway address with {count} accounts",
- "egress.single": "gateway address (only account)",
- "egress.unknown": "egress unknown",
- "egress.title": "Network egress",
- "table.combo": "Combo",
- "table.cascade": "Model cascade",
- "combos.title": "Resilience combos",
- "combos.empty": "No fallback combo registered.",
- "combos.empty_hint": f"In {NOME_DO_GATEWAY} the combo is the router fallback "
- "(router_settings.fallbacks); none is declared.",
- "combos.kind_context_window": "context window",
- "combos.kind_content_policy": "content policy",
- "keys.title": "Virtual keys",
- "keys.empty": "No virtual key issued by the gateway.",
- "table.alias": "Alias",
- "table.team": "Team",
- "table.spend": "Spend",
- "keys.enabled": "Accepted",
- "keys.disabled": "Deactivated",
- "keys.access_all": "All models",
- "keys.access_restricted": "Restricted to the listed models",
- "keys.revoked": "Revoked",
- "keys.banned": "Banned",
- "models.title": "Registered models",
- "models.empty": "The gateway answered with an empty model catalogue.",
- "credential.env": "Kept in the proxy environment",
- "credential.named": "Named credential",
- "credential.inline": "Declared on the model",
- "credential.absent": "Not exposed by the gateway",
- "limits.title": "Limit coherence",
- "limits.ok": "Every limit respects the level above it.",
- "models.no_key": "The gateway issues no active key, and it only hands the model "
- "catalogue to a key it issued itself.",
- "models.unreachable": "The gateway did not answer the model catalogue.",
- "models.inherited": "Status, validity and last renewal come from the connection that serves this model.",
- "models.showing": "Showing {shown} of {total} models — open the gateway for the full list.",
- "table.connection": "Connection",
- "table.issued_at": "Issued at",
- "table.last_used": "Last used",
- "table.scopes": "Scopes",
- "table.key_state": "Key state",
- "table.model_access": "Model access",
- "table.not_declared": "Not declared",
- "table.source": "Origin",
- "table.context_limit": "Input limit",
- "table.output_limit": "Output limit",
- "table.endpoints": "Endpoints",
- "table.description": "Description",
- "type.oauth": "OAuth 2.0",
- "type.api_key": "API key",
- "type.local": "Local",
- "type.virtual_key": "Virtual key",
- "type.synced_model": "Synced model",
- "health.active": "Active",
- "health.expiring_soon": "Expiring",
- "health.expired": "Expired",
- "health.rate_limited": "Rate limited",
- "health.no_expiration": "No expiry",
- "health.unknown": "Unknown",
- "health.invalid": "Rejected",
- "health.unreachable": "Unreachable",
- "health.not_checked": "Not checked",
- "health.blocked": "Blocked",
- "health.over_budget": "Budget exhausted",
- "health.valid": "Accepted",
- "metric.virtual_keys": "Virtual keys",
- "metric.teams": "Teams",
- "metric.expiring": "Expiring / expired",
- "metric.limit_findings": "Limit findings",
- "gateway.diagnostics": "Diagnostics",
- "gateway.diag_ok": "Gateway and SQLite database fully operational",
- "gateway.diag_db_failed": "Gateway online, database unreadable",
- "gateway.diag_gateway_failed": "Gateway unreachable",
- "credential.title": "Credential",
- "credential.checked_at": "Checked at",
- "credential.never": "Never validated",
- "action.validate": "Validate credentials",
- "duration.unknown_expiry": "Expiry unknown",
- "duration.no_expiry": "No expiry (static key)",
- "duration.no_expiry_short": "No expiry",
- "table.last_refresh": "Last renewal",
- "table.never_refreshed": "Never renewed",
- "table.time_ago": "{elapsed} ago",
- "security.cross_origin": "Request rejected: it did not come from this dashboard. Reload the page and try again.",
- "action.refreshed": "Page reloaded with fresh data.",
- "action.cron_ran": "Cycle ran in {duration}ms: {inspected} inspected, {findings} findings.",
- "password.policy": "At least 6 characters, with uppercase, lowercase, a number and a special character.",
- "password.too_short": "Password must have at least 6 characters.",
- "password.needs_upper": "Password must contain an uppercase letter.",
- "password.needs_lower": "Password must contain a lowercase letter.",
- "password.needs_digit": "Password must contain a number.",
- "password.needs_special": "Password must contain a special character.",
- "duration.unlimited": "Unlimited / N/A",
- "duration.expired": "Expired",
- "reason.api_key": "Static key: never expires, nothing to renew",
- "reason.no_expiry": "No expiry recorded: will be renewed on the next sweep",
- "reason.expired": "Token expired: renewal will be attempted on the next sweep",
- "reason.inside_margin": "Within the {margin} min margin: will be renewed on the next sweep",
- "reason.outside_margin": "Outside the {margin} min margin: renewal expected in ~{eta}",
- "auth.title": "Dashboard credentials",
- "auth.new_password": "New password",
- "auth.min_chars": "Minimum of 4 characters.",
- "auth.save_failed": "The new password could not be stored.",
- "auth.env_managed": "Credentials for this panel are managed outside it. Change them where the service is configured, then restart it.",
- "pagination.range": "{inicio}-{fim} of {total}",
- "pagination.label": "Pagination",
- "footer.signed_in": "Signed in as",
- "footer.generated": "Data rendered on the server at",
- "language.save_failed": "Could not save the language preference: the panel storage is not writable.",
- "language.label": "Language",
- "action.settings": "Settings",
- "sso.title": "Single sign-on (SSO)",
- "sso.intro": "Optional. The local user and password form never leaves the screen, so a provider outage does not lock you out.",
- "sso.tab_oidc": "OpenID Connect",
- "sso.tab_saml": "SAML 2.0",
- "sso.provider": "Active provider",
- "sso.provider_none": "Off (password only)",
- "sso.provider_oidc": "OpenID Connect",
- "sso.provider_saml": "SAML 2.0 (unavailable)",
- "sso.enabled_help": "One provider at a time. Two enabled at once is what makes a response "
- "from one acceptable as if it came from the other.",
- "sso.enabled_off": "Disabled (password only)",
- "sso.enabled_oidc": "OIDC",
- "sso.base_url": "Public panel address",
- "sso.base_url_hint": "The exact origin the browser uses, with no trailing slash. It has "
- "to be FIXED: a quick tunnel changes address on every start and "
- "every return address registered at the provider stops matching, so "
- "single sign-on needs a named tunnel or Tailscale.",
- "sso.base_url_help": "Exact public origin, no trailing slash. The return address is built from this value and never from the request headers.",
- "sso.redirect_uri": "Return address to register with the provider",
- "sso.callback_url": "Redirect URI to register at the provider",
- "sso.callback_help": "Copy this exact string into the provider. A single character of "
- "difference and the provider refuses the exchange.",
- "sso.issuer": "Issuer",
- "sso.issuer_help": "The issuer published in the discovery document. It must match "
- "character for character.",
- "sso.client_id": "Client ID",
- "sso.client_secret": "Client secret",
- "sso.secret_stored": "A secret is stored. Leave the field blank to keep it.",
- "sso.secret_absent": "No secret stored yet.",
- "sso.secret_missing": "No secret stored yet. Without one, SSO stays off.",
- "sso.secret_from_env": "The secret comes from the OIDC_CLIENT_SECRET environment variable. Change it there and restart the service.",
- "sso.secret_keep_help": "It is never shown again. Leave this field empty to keep the "
- "current one.",
- "sso.secret_failed": "Could not store the client secret: the panel storage is not writable.",
- "sso.scopes": "Scopes",
- "sso.scopes_help": "Space separated. The default covers the e-mail address and the profile.",
- "sso.allowed_domains": "Allowed domains",
- "sso.allowed_emails": "Allowed e-mail addresses",
- "sso.allowlist_hint": "Comma separated, and it cannot be empty: without it every account "
- "at the provider would get in.",
- "sso.allowlist_help": "Comma separated, and at least one of the two. An empty list means every account at the provider gets in, so the panel refuses to turn SSO on without it.",
- "sso.local_password": "Your current panel password",
- "sso.local_password_help": "Required here on top of the session: whoever steals an eight-hour session could otherwise point the panel at a hostile provider and add themselves to the allowed list.",
- "sso.current_password": "Current panel password",
- "sso.confirm_hint": "Saving asks for the local password again: whoever steals a session "
- "must not be able to point the panel at a hostile provider and put "
- "themselves on the list.",
- "sso.current_password_help": "Required on top of the session: a stolen cookie must not be "
- "enough to point the panel at a hostile provider.",
- "sso.save": "Save SSO settings",
- "sso.saved": "Single sign-on settings saved.",
- "sso.turned_off": "Single sign-on is off. The local form keeps working.",
- "sso.save_refused_password": "Wrong panel password: nothing was changed.",
- "sso.save_refused_allowlist": "Add at least one domain or address: an empty list would "
- "let every account at the provider in.",
- "sso.save_refused_fields": "Fill in every field of the chosen provider, including the "
- "public address of this panel.",
- "sso.save_refused_secret": "The client secret could not be written to disk, so single "
- "sign-on was not turned on.",
- "sso.save_refused_saml": "SAML 2.0 is not available in this image.",
- "sso.save_failed": "Could not save the settings.",
- "sso.wrong_password": "Wrong panel password: nothing was changed.",
- "sso.allowlist_required": "Fill in at least one allowed domain or e-mail address. SSO without an allowed list lets in every account at the provider.",
- "sso.incomplete": "Fill in the public address, the issuer and the client ID before turning SSO on.",
- "sso.no_secret": "No client secret: set one here or in the OIDC_CLIENT_SECRET environment variable.",
- "sso.saml_refused": "SAML 2.0 cannot be turned on in this image.",
- "sso.disabled_by_env": "Single sign-on is switched off by the SSO_DISABLED environment variable. The settings below are kept, but no SSO route answers.",
- "sso.login_button": "Sign in with {provider}",
- "sso.need_base_url": "The public panel address must be a scheme and a host, with no path, "
- "and https outside the loopback.",
- "sso.need_issuer": "The issuer is required and must use https outside the loopback.",
- "sso.need_client_id": "The client ID is required.",
- "sso.need_secret": "The client secret is required.",
- "sso.need_allowlist": "Fill in at least one allowed domain or e-mail.",
- "sso.tunnel_warning": "A quick tunnel gets a new address on every start, and every return "
- "address registered at the provider stops matching. Single sign-on "
- "needs a named tunnel or Tailscale, with a fixed address.",
- "sso.sign_in_with": "Sign in with {provider}",
- "sso.unavailable": "The identity provider did not answer. Sign in with your user and password.",
- "sso.or": "or",
- "sso.failed": "Could not sign in through the identity provider. Try again, or use your user and password.",
- "sso.entering": "Signing in",
- "sso.entering_body": "Single sign-on accepted. Opening the panel.",
- "sso.status_on": "On · {provider}",
- "sso.status_off": "Off",
- "sso.signing_in": "Signing in",
- "sso.signing_in_body": "The identity provider confirmed who you are. Taking you to the panel.",
- "sso.logout_note": "Signing out clears the panel session only. The session at the identity provider stays open, so the next click on the SSO button may not ask for a password again.",
- "sso.landing_title": "Signing in...",
- "sso.landing_body": "The session has been created. Taking you to the dashboard.",
- "sso.idp_entity_id": "Identity provider entity ID",
- "sso.idp_sso_url": "Identity provider sign-on URL",
- "sso.idp_cert": "Identity provider X.509 certificate",
- "sso.metadata_hint": "Once saved, download the service description at {url} while signed "
- "in and hand it to the identity provider.",
- "sso.idp_cert_help": "Public certificate, safe to store next to the rest of the settings.",
- "sso.saml_unavailable": "SAML 2.0 is not available in this image. It needs a library that signs and verifies XML, and doing it by hand would accept forged assertions in silence. The fields are here so the settings are ready when the image ships with it.",
- "sso.saml_pending": "The SAML2 library is installed, but this version of the panel only "
- "signs in through OIDC. SAML2 sign-in is the next phase.",
- },
- "pt": {
- "app.subtitle": f"{NOME_DO_GATEWAY} Universal Token & Connection Synchronizer",
- "app.gateway_unset": "gateway não configurado",
- "action.refresh": "Atualizar",
- "action.access": "Acesso",
- "action.sync_now": "Sincronizar agora",
- "action.run_now": "Executar agora",
- "action.test_connection": "Testar conexão",
- "action.save_credentials": "Salvar credenciais",
- "action.change_credentials": "Alterar credenciais",
- "action.close": "Fechar",
- "action.refresh_title": "Recarregar os dados do servidor",
- "metric.total_connections": "Total de conexões",
- "metric.oauth_accounts": "Contas OAuth",
- "metric.api_keys": "Chaves de API",
- "metric.combos": "Combos registrados",
- "security.title": "Atenção de segurança:",
- "security.body": "nenhuma senha foi definida para este painel ainda, então o acesso "
- "ainda depende da credencial de recuperação. Defina a sua agora, ou "
- "configure DASHBOARD_USER/DASHBOARD_PASSWORD no ambiente.",
- "auth.updated_title": "Credenciais atualizadas",
- "auth.updated_body": "A nova senha já está em vigor. O navegador ainda guarda a anterior, "
- "então autentique-se de novo para continuar.",
- "auth.updated_link": "Voltar ao painel",
- "auth.required": "Autenticação requerida.",
- "auth.login_intro": "Entre para ver o painel.",
- "auth.user": "Usuário",
- "auth.password": "Senha",
- "auth.enter": "Entrar",
- "auth.login_failed": "Usuário ou senha incorretos.",
- "auth.too_many": "Tentativas demais",
- "auth.too_many_body": "Aguarde {seconds}s antes de tentar de novo.",
- "auth.challenge": "Verificação de segurança",
- "auth.challenge_prompt": "Selecione o(a) {item} para confirmar que é humano:",
- "auth.item_key": "Chave",
- "auth.item_shield": "Escudo",
- "auth.item_lock": "Cadeado",
- "auth.item_star": "Estrela",
- "auth.item_heart": "Coração",
- "auth.item_bell": "Sino",
- "auth.item_lightning": "Raio",
- "auth.item_gear": "Engrenagem",
- "auth.logout": "Sair",
- "auth.required_body": "Este painel é privado. Autentique-se para continuar.",
- "gateway.title": "Conexão com o gateway",
- "gateway.db_summary": "Operacional ({connections} conexões, {combos} combos)",
- "gateway.db_missing": "Banco não encontrado",
- "gateway.gateway": "Gateway",
- "gateway.status": "Status",
- "gateway.latency": "Latência",
- "gateway.database": "Banco SQLite",
- "gateway.offline": "OFFLINE",
- "gateway.no_response": "sem resposta",
- # Vocabulário do ciclo: o trabalho que este agendador faz muda com o
- # gateway, e o texto acompanha.
- "cron.title": "Agendador",
- "cron.run_now": "Executar agora",
- "cron.active": "Ativo · a cada {interval}s",
- "cron.disabled": "Desativado (CRON_ENABLED=0)",
- "cron.last_run": "Última execução",
- "cron.next_run": "Próxima execução",
- "cron.total_runs": "Ciclos totais",
- "cron.total_findings": "Achados até agora",
- "cron.total_renewals": "Tokens renovados",
- "cron.last_result": "Último resultado",
- "cron.no_runs": "Nenhum ciclo executado ainda",
- # Vocabulário do ciclo: o trabalho que este agendador faz muda com o
- # gateway, e o texto acompanha.
- "cron.result_line": "{inspected} · ({duration}ms)",
- "cron.unavailable": "O agendador não está disponível nesta instância.",
- "cron.failed": "O ciclo não pôde ser concluído: {error}",
- "connections.title": "Conexões monitoradas",
- "connections.empty": "Nenhuma conexão registrada no gateway.",
- "connections.empty_hint": f"No {NOME_DO_GATEWAY} a conexão é o destino por trás dos modelos "
- "cadastrados; nenhum deles declara um destino ainda.",
- "connections.lifecycle_note": "O destino não tem validade própria: o que expira é a "
- "credencial no provedor, fora deste gateway.",
- "connections.served_models": "Modelos servidos",
- "table.provider": "Provedor",
- "table.name": "Nome",
- "table.type": "Tipo",
- "table.status": "Status",
- "table.remaining": "Validade restante",
- "table.diagnosis": "Diagnóstico da renovação",
- "table.details": "Detalhes",
- "table.expires_at": "Expira em",
- "table.rpm_limit": "Limite RPM",
- "table.tpm_limit": "Limite TPM",
- "table.max_budget": "Teto de orçamento",
- "table.api_base": "Base da API",
- "table.models": "modelos",
- "reason.local_ok": "Instância local respondeu com {count} modelo(s)",
- "reason.local_unreachable": "Instância local não respondeu ao catálogo de modelos",
- "reason.local_empty": "Instância local respondeu, mas ainda não tem nenhum modelo instalado",
- "egress.bound": "saída própria",
- "egress.shared": "divide o endereço do gateway com {count} contas",
- "egress.single": "endereço do gateway (única conta)",
- "egress.unknown": "saída desconhecida",
- "egress.title": "Saída de rede",
- "table.combo": "Combo",
- "table.cascade": "Cascata de modelos",
- "combos.title": "Combos de resiliência",
- "combos.empty": "Nenhum combo de fallback registrado.",
- "combos.empty_hint": f"No {NOME_DO_GATEWAY} o combo é o fallback do roteador "
- "(router_settings.fallbacks); nenhum está declarado.",
- "combos.kind_context_window": "janela de contexto",
- "combos.kind_content_policy": "política de conteúdo",
- "keys.title": "Chaves virtuais",
- "keys.empty": "Nenhuma chave virtual emitida pelo gateway.",
- "table.alias": "Apelido",
- "table.team": "Time",
- "table.spend": "Gasto",
- "keys.enabled": "Aceita",
- "keys.disabled": "Desativada",
- "keys.access_all": "Todos os modelos",
- "keys.access_restricted": "Restrita aos modelos listados",
- "keys.revoked": "Revogada",
- "keys.banned": "Banida",
- "models.title": "Modelos cadastrados",
- "models.empty": "O gateway respondeu com o catálogo de modelos vazio.",
- "credential.env": "Mantida no ambiente do proxy",
- "credential.named": "Credencial nomeada",
- "credential.inline": "Declarada no modelo",
- "credential.absent": "Não exposta pelo gateway",
- "limits.title": "Coerência dos limites",
- "limits.ok": "Todo limite respeita o nível acima.",
- "models.no_key": "O gateway não tem nenhuma chave ativa emitida, e ele só entrega o "
- "catálogo de modelos a uma chave que ele mesmo emitiu.",
- "models.unreachable": "O gateway não respondeu ao catálogo de modelos.",
- "models.inherited": "Status, validade e última renovação vêm da conexão que serve este modelo.",
- "models.showing": "Mostrando {shown} de {total} modelos — a lista completa está no gateway.",
- "table.connection": "Conexão",
- "table.issued_at": "Emitida em",
- "table.last_used": "Último uso",
- "table.scopes": "Escopos",
- "table.key_state": "Estado da chave",
- "table.model_access": "Acesso a modelos",
- "table.not_declared": "Não declarado",
- "table.source": "Origem",
- "table.context_limit": "Limite de entrada",
- "table.output_limit": "Limite de saída",
- "table.endpoints": "Endpoints",
- "table.description": "Descrição",
- "type.oauth": "OAuth 2.0",
- "type.api_key": "Chave de API",
- "type.local": "Local",
- "type.virtual_key": "Chave virtual",
- "type.synced_model": "Modelo sincronizado",
- "health.active": "Ativo",
- "health.expiring_soon": "Expirando",
- "health.expired": "Expirado",
- "health.rate_limited": "Rate limit",
- "health.no_expiration": "Sem expiração",
- "health.unknown": "Desconhecido",
- "health.invalid": "Recusada",
- "health.unreachable": "Inacessível",
- "health.not_checked": "Não verificada",
- "health.blocked": "Bloqueada",
- "health.over_budget": "Orçamento esgotado",
- "health.valid": "Aceita",
- "metric.virtual_keys": "Chaves virtuais",
- "metric.teams": "Times",
- "metric.expiring": "Vencendo / vencidas",
- "metric.limit_findings": "Incoerências de limite",
- "gateway.diagnostics": "Diagnóstico",
- "gateway.diag_ok": "Gateway e banco SQLite totalmente operacionais",
- "gateway.diag_db_failed": "Gateway online, banco ilegível",
- "gateway.diag_gateway_failed": "Gateway inacessível",
- "credential.title": "Credencial",
- "credential.checked_at": "Verificada em",
- "credential.never": "Nunca validada",
- "action.validate": "Validar credenciais",
- "duration.unknown_expiry": "Validade desconhecida",
- "duration.no_expiry": "Sem expiração (chave estática)",
- "duration.no_expiry_short": "Sem expiração",
- "table.last_refresh": "Última renovação",
- "table.never_refreshed": "Nunca renovada",
- "table.time_ago": "há {elapsed}",
- "security.cross_origin": "Requisição recusada: ela não veio deste painel. Recarregue a página e tente de novo.",
- "action.refreshed": "Página recarregada com dados atualizados.",
- "action.cron_ran": "Ciclo executado em {duration}ms: {inspected} inspecionados, {findings} achados.",
- "password.policy": "Mínimo de 6 caracteres, com maiúscula, minúscula, número e caractere especial.",
- "password.too_short": "A senha precisa ter ao menos 6 caracteres.",
- "password.needs_upper": "A senha precisa conter uma letra maiúscula.",
- "password.needs_lower": "A senha precisa conter uma letra minúscula.",
- "password.needs_digit": "A senha precisa conter um número.",
- "password.needs_special": "A senha precisa conter um caractere especial.",
- "duration.unlimited": "Ilimitado / N/A",
- "duration.expired": "Expirado",
- "reason.api_key": "Chave estática: não expira, nada a renovar",
- "reason.no_expiry": "Sem expiração registrada: será renovada na próxima varredura",
- "reason.expired": "Token expirado: renovação será tentada na próxima varredura",
- "reason.inside_margin": "Dentro da margem de {margin} min: será renovada na próxima varredura",
- "reason.outside_margin": "Fora da margem de {margin} min: renovação prevista em ~{eta}",
- "auth.title": "Credenciais do painel",
- "auth.new_password": "Nova senha",
- "auth.min_chars": "Mínimo de 4 caracteres.",
- "auth.save_failed": "Não foi possível gravar a nova senha.",
- "auth.env_managed": "As credenciais deste painel são gerenciadas fora dele. Altere-as onde o serviço é configurado e reinicie-o.",
- "pagination.range": "{inicio}-{fim} de {total}",
- "pagination.label": "Paginação",
- "footer.signed_in": "Autenticado como",
- "footer.generated": "Dados gerados no servidor em",
- "language.save_failed": "Nao foi possivel gravar o idioma: o armazenamento do painel nao aceita escrita.",
- "language.label": "Idioma",
- "action.settings": "Configurações",
- "sso.title": "Entrada federada (SSO)",
- "sso.intro": "Opcional. O formulário de usuário e senha nunca sai da tela, então o provedor cair não tranca ninguém do lado de fora.",
- "sso.tab_oidc": "OpenID Connect",
- "sso.tab_saml": "SAML 2.0",
- "sso.provider": "Provedor ativo",
- "sso.provider_none": "Desligado (só senha)",
- "sso.provider_oidc": "OpenID Connect",
- "sso.provider_saml": "SAML 2.0 (indisponível)",
- "sso.enabled_help": "Um provedor por vez. Dois ligados ao mesmo tempo é o que faz a "
- "resposta de um ser aceita como se fosse a do outro.",
- "sso.enabled_off": "Desligado (somente senha)",
- "sso.enabled_oidc": "OIDC",
- "sso.base_url": "Endereço público do painel",
- "sso.base_url_hint": "A origem exata que o navegador usa, sem barra no fim. Tem de ser "
- "FIXA: um túnel rápido troca de endereço a cada subida e todo "
- "endereço de retorno registrado no provedor deixa de bater, então o "
- "acesso federado exige túnel nomeado ou Tailscale.",
- "sso.base_url_help": "Origem pública exata, sem barra no fim. O endereço de retorno nasce deste valor, e nunca dos cabeçalhos da requisição.",
- "sso.redirect_uri": "Endereço de retorno a registrar no provedor",
- "sso.callback_url": "Endereço de retorno a cadastrar no provedor",
- "sso.callback_help": "Copie exatamente esta linha para o provedor. Um caractere de "
- "diferença e ele recusa a troca.",
- "sso.issuer": "Emissor (issuer)",
- "sso.issuer_help": "O emissor publicado no documento de descoberta. Ele tem de bater "
- "caractere a caractere.",
- "sso.client_id": "Identificador do cliente",
- "sso.client_secret": "Segredo do cliente",
- "sso.secret_stored": "Há um segredo guardado. Deixe o campo em branco para mantê-lo.",
- "sso.secret_absent": "Ainda não há segredo guardado.",
- "sso.secret_missing": "Nenhum segredo guardado ainda. Sem ele, o SSO fica desligado.",
- "sso.secret_from_env": "O segredo vem da variável de ambiente OIDC_CLIENT_SECRET. Altere-o lá e reinicie o serviço.",
- "sso.secret_keep_help": "Ele nunca é exibido de volta. Deixe este campo em branco para "
- "manter o atual.",
- "sso.secret_failed": "Não foi possível gravar o segredo do cliente: o armazenamento do "
- "painel não aceita escrita.",
- "sso.scopes": "Escopos",
- "sso.scopes_help": "Separados por espaço. O padrão cobre o endereço de e-mail e o perfil.",
- "sso.allowed_domains": "Domínios autorizados",
- "sso.allowed_emails": "E-mails autorizados",
- "sso.allowlist_hint": "Separados por vírgula, e a lista não pode ficar vazia: sem ela "
- "toda conta do provedor entraria.",
- "sso.allowlist_help": "Separados por vírgula, e ao menos um dos dois. Lista vazia significa que toda conta do provedor entra, então o painel recusa ligar o SSO sem ela.",
- "sso.local_password": "Sua senha atual do painel",
- "sso.local_password_help": "Exigida aqui além da sessão: quem sequestrar uma sessão de oito horas poderia, sem isso, apontar o painel para um provedor hostil e se colocar na lista de autorizados.",
- "sso.current_password": "Senha atual do painel",
- "sso.confirm_hint": "Salvar pede a senha local de novo: quem roubar uma sessão não pode "
- "apontar o painel para um provedor hostil e se colocar na lista.",
- "sso.current_password_help": "Exigida além da sessão: um cookie roubado não pode bastar "
- "para apontar o painel a um provedor hostil.",
- "sso.save": "Salvar configuração de SSO",
- "sso.saved": "Configuração de entrada federada salva.",
- "sso.turned_off": "Acesso federado desligado. O formulário local continua funcionando.",
- "sso.save_refused_password": "Senha do painel incorreta: nada foi alterado.",
- "sso.save_refused_allowlist": "Informe ao menos um domínio ou endereço: uma lista vazia "
- "deixaria entrar toda conta do provedor.",
- "sso.save_refused_fields": "Preencha todos os campos do provedor escolhido, inclusive o "
- "endereço público deste painel.",
- "sso.save_refused_secret": "Não foi possível gravar o segredo do cliente em disco, então "
- "o acesso federado não foi ligado.",
- "sso.save_refused_saml": "SAML 2.0 não está disponível nesta imagem.",
- "sso.save_failed": "Não foi possível salvar a configuração.",
- "sso.wrong_password": "Senha do painel incorreta: nada foi alterado.",
- "sso.allowlist_required": "Preencha ao menos um domínio ou e-mail autorizado. SSO sem lista de autorizados deixa entrar toda conta do provedor.",
- "sso.incomplete": "Preencha o endereço público, o issuer e o ID do cliente antes de ligar o SSO.",
- "sso.no_secret": "Sem segredo do cliente: defina um aqui ou na variável de ambiente OIDC_CLIENT_SECRET.",
- "sso.saml_refused": "SAML 2.0 não pode ser ligado nesta imagem.",
- "sso.disabled_by_env": "A entrada federada está desligada pela variável de ambiente SSO_DISABLED. A configuração abaixo é mantida, mas nenhuma rota de SSO responde.",
- "sso.login_button": "Entrar com {provider}",
- "sso.need_base_url": "O endereço público do painel precisa ser esquema e host, sem "
- "caminho, e https fora do loopback.",
- "sso.need_issuer": "O emissor é obrigatório e precisa usar https fora do loopback.",
- "sso.need_client_id": "O identificador do cliente é obrigatório.",
- "sso.need_secret": "O segredo do cliente é obrigatório.",
- "sso.need_allowlist": "Preencha ao menos um domínio ou e-mail autorizado.",
- "sso.tunnel_warning": "O túnel rápido troca de endereço a cada subida, e todo endereço de "
- "retorno cadastrado no provedor deixa de bater. O SSO exige túnel "
- "nomeado ou Tailscale, com endereço fixo.",
- "sso.sign_in_with": "Entrar com {provider}",
- "sso.unavailable": "O provedor de identidade não respondeu. Entre com usuário e senha.",
- "sso.or": "ou",
- "sso.failed": "Não foi possível entrar pelo provedor de identidade. Tente de novo ou use usuário e senha.",
- "sso.entering": "Entrando",
- "sso.entering_body": "Acesso federado aceito. Abrindo o painel.",
- "sso.status_on": "Ligado · {provider}",
- "sso.status_off": "Desligado",
- "sso.signing_in": "Entrando",
- "sso.signing_in_body": "O provedor de identidade confirmou quem você é. Levando você ao painel.",
- "sso.logout_note": "Sair apaga apenas a sessão do painel. A sessão no provedor de identidade continua aberta, então o clique seguinte no botão de SSO pode não pedir senha de novo.",
- "sso.landing_title": "Entrando...",
- "sso.landing_body": "A sessão foi criada. Levando você ao painel.",
- "sso.idp_entity_id": "Identificador do provedor de identidade",
- "sso.idp_sso_url": "Endereço de entrada do provedor de identidade",
- "sso.idp_cert": "Certificado X.509 do provedor de identidade",
- "sso.metadata_hint": "Depois de salvar, baixe a descrição do serviço em {url} já "
- "autenticado e entregue-a ao provedor de identidade.",
- "sso.idp_cert_help": "Certificado público, que pode ficar ao lado do resto da configuração.",
- "sso.saml_unavailable": "SAML 2.0 não está disponível nesta imagem. Ele exige uma biblioteca que assina e confere XML, e fazer isso à mão aceitaria asserção forjada em silêncio. Os campos ficam aqui para que a configuração já esteja pronta quando a imagem trouxer a biblioteca.",
- "sso.saml_pending": "A biblioteca de SAML2 está instalada, mas esta versão do painel só "
- "entra por OIDC. A entrada por SAML2 é a próxima fase.",
- },
- "es": {
- "app.subtitle": f"{NOME_DO_GATEWAY} Universal Token & Connection Synchronizer",
- "app.gateway_unset": "gateway no configurado",
- "action.refresh": "Actualizar",
- "action.access": "Acceso",
- "action.sync_now": "Sincronizar ahora",
- "action.run_now": "Ejecutar ahora",
- "action.test_connection": "Probar conexión",
- "action.save_credentials": "Guardar credenciales",
- "action.change_credentials": "Cambiar credenciales",
- "action.close": "Cerrar",
- "action.refresh_title": "Recargar los datos del servidor",
- "metric.total_connections": "Conexiones totales",
- "metric.oauth_accounts": "Cuentas OAuth",
- "metric.api_keys": "Claves de API",
- "metric.combos": "Combos registrados",
- "security.title": "Aviso de seguridad:",
- "security.body": "todavía no se ha definido una contraseña para este panel, por lo que "
- "el acceso aún depende de la credencial de recuperación. Defina la suya "
- "ahora, o configure DASHBOARD_USER/DASHBOARD_PASSWORD en el entorno.",
- "auth.updated_title": "Credenciales actualizadas",
- "auth.updated_body": "La nueva contraseña ya está vigente. El navegador todavía guarda la "
- "anterior, así que vuelva a autenticarse para continuar.",
- "auth.updated_link": "Volver al panel",
- "auth.required": "Autenticación requerida.",
- "auth.login_intro": "Entre para ver el panel.",
- "auth.user": "Usuario",
- "auth.password": "Contraseña",
- "auth.enter": "Entrar",
- "auth.login_failed": "Usuario o contraseña incorrectos.",
- "auth.too_many": "Demasiados intentos",
- "auth.too_many_body": "Espere {seconds}s antes de intentarlo de nuevo.",
- "auth.challenge": "Verificación de seguridad",
- "auth.challenge_prompt": "Selecciona el/la {item} para confirmar que eres humano:",
- "auth.item_key": "Llave",
- "auth.item_shield": "Escudo",
- "auth.item_lock": "Candado",
- "auth.item_star": "Estrella",
- "auth.item_heart": "Corazón",
- "auth.item_bell": "Campana",
- "auth.item_lightning": "Rayo",
- "auth.item_gear": "Engranaje",
- "auth.logout": "Salir",
- "auth.required_body": "Este panel es privado. Autentíquese para continuar.",
- "gateway.title": "Conexión con el gateway",
- "gateway.db_summary": "Operativo ({connections} conexiones, {combos} combos)",
- "gateway.db_missing": "Base de datos no encontrada",
- "gateway.gateway": "Gateway",
- "gateway.status": "Estado",
- "gateway.latency": "Latencia",
- "gateway.database": "Base de datos SQLite",
- "gateway.offline": "DESCONECTADO",
- "gateway.no_response": "sin respuesta",
- # Vocabulário do ciclo: o trabalho que este agendador faz muda com o
- # gateway, e o texto acompanha.
- "cron.title": "Programador",
- "cron.run_now": "Ejecutar ahora",
- "cron.active": "Activo · cada {interval}s",
- "cron.disabled": "Desactivado (CRON_ENABLED=0)",
- "cron.last_run": "Última ejecución",
- "cron.next_run": "Próxima ejecución",
- "cron.total_runs": "Ciclos totales",
- "cron.total_findings": "Hallazgos hasta ahora",
- "cron.total_renewals": "Tokens renovados",
- "cron.last_result": "Último resultado",
- "cron.no_runs": "Aún no se ejecutó ningún ciclo",
- # Vocabulário do ciclo: o trabalho que este agendador faz muda com o
- # gateway, e o texto acompanha.
- "cron.result_line": "{inspected} · ({duration}ms)",
- "cron.unavailable": "El programador no está disponible en esta instancia.",
- "cron.failed": "No se pudo completar el ciclo: {error}",
- "connections.title": "Conexiones monitoreadas",
- "connections.empty": "No hay conexiones registradas en el gateway.",
- "connections.empty_hint": f"En {NOME_DO_GATEWAY} la conexión es el destino detrás de los modelos "
- "registrados; ninguno declara uno todavía.",
- "connections.lifecycle_note": "El destino no tiene validez propia: lo que expira es la "
- "credencial en el proveedor, fuera de este gateway.",
- "connections.served_models": "Modelos servidos",
- "table.provider": "Proveedor",
- "table.name": "Nombre",
- "table.type": "Tipo",
- "table.status": "Estado",
- "table.remaining": "Validez restante",
- "table.diagnosis": "Diagnóstico de la renovación",
- "table.details": "Detalles",
- "table.expires_at": "Expira el",
- "table.rpm_limit": "Límite RPM",
- "table.tpm_limit": "Límite TPM",
- "table.max_budget": "Tope de presupuesto",
- "table.api_base": "Base de la API",
- "table.models": "modelos",
- "reason.local_ok": "La instancia local respondió con {count} modelo(s)",
- "reason.local_unreachable": "La instancia local no respondió al catálogo de modelos",
- "reason.local_empty": "La instancia local respondió, pero aún no tiene ningún modelo instalado",
- "egress.bound": "salida propia",
- "egress.shared": "comparte la dirección del gateway con {count} cuentas",
- "egress.single": "dirección del gateway (única cuenta)",
- "egress.unknown": "salida desconocida",
- "egress.title": "Salida de red",
- "table.combo": "Combo",
- "table.cascade": "Cascada de modelos",
- "combos.title": "Combos de resiliencia",
- "combos.empty": "No hay combos de respaldo registrados.",
- "combos.empty_hint": f"En {NOME_DO_GATEWAY} el combo es el fallback del enrutador "
- "(router_settings.fallbacks); no hay ninguno declarado.",
- "combos.kind_context_window": "ventana de contexto",
- "combos.kind_content_policy": "política de contenido",
- "keys.title": "Claves virtuales",
- "keys.empty": "El gateway no ha emitido ninguna clave virtual.",
- "table.alias": "Alias",
- "table.team": "Equipo",
- "table.spend": "Gasto",
- "keys.enabled": "Aceptada",
- "keys.disabled": "Desactivada",
- "keys.access_all": "Todos los modelos",
- "keys.access_restricted": "Restringida a los modelos listados",
- "keys.revoked": "Revocada",
- "keys.banned": "Bloqueada",
- "models.title": "Modelos registrados",
- "models.empty": "El gateway respondió con el catálogo de modelos vacío.",
- "credential.env": "Guardada en el entorno del proxy",
- "credential.named": "Credencial con nombre",
- "credential.inline": "Declarada en el modelo",
- "credential.absent": "No expuesta por la pasarela",
- "limits.title": "Coherencia de los límites",
- "limits.ok": "Todo límite respeta el nivel superior.",
- "models.no_key": "El gateway no tiene ninguna clave activa emitida, y solo entrega el "
- "catálogo de modelos a una clave emitida por él mismo.",
- "models.unreachable": "El gateway no respondió al catálogo de modelos.",
- "models.inherited": "El estado, la validez y la última renovación vienen de la conexión que sirve este modelo.",
- "models.showing": "Mostrando {shown} de {total} modelos — la lista completa está en el gateway.",
- "table.connection": "Conexión",
- "table.issued_at": "Emitida el",
- "table.last_used": "Último uso",
- "table.scopes": "Ámbitos",
- "table.key_state": "Estado de la clave",
- "table.model_access": "Acceso a modelos",
- "table.not_declared": "No declarado",
- "table.source": "Origen",
- "table.context_limit": "Límite de entrada",
- "table.output_limit": "Límite de salida",
- "table.endpoints": "Endpoints",
- "table.description": "Descripción",
- "type.oauth": "OAuth 2.0",
- "type.api_key": "Clave de API",
- "type.local": "Local",
- "type.virtual_key": "Clave virtual",
- "type.synced_model": "Modelo sincronizado",
- "health.active": "Activo",
- "health.expiring_soon": "Por expirar",
- "health.expired": "Expirado",
- "health.rate_limited": "Límite de tasa",
- "health.no_expiration": "Sin expiración",
- "health.unknown": "Desconocido",
- "health.invalid": "Rechazada",
- "health.unreachable": "Inaccesible",
- "health.not_checked": "Sin verificar",
- "health.blocked": "Bloqueada",
- "health.over_budget": "Presupuesto agotado",
- "health.valid": "Aceptada",
- "metric.virtual_keys": "Claves virtuales",
- "metric.teams": "Equipos",
- "metric.expiring": "Por vencer / vencidas",
- "metric.limit_findings": "Incoherencias de límite",
- "gateway.diagnostics": "Diagnóstico",
- "gateway.diag_ok": "Gateway y base SQLite totalmente operativos",
- "gateway.diag_db_failed": "Gateway en línea, base ilegible",
- "gateway.diag_gateway_failed": "Gateway inaccesible",
- "credential.title": "Credencial",
- "credential.checked_at": "Verificada el",
- "credential.never": "Nunca validada",
- "action.validate": "Validar credenciales",
- "duration.unknown_expiry": "Validez desconocida",
- "duration.no_expiry": "Sin expiración (clave estática)",
- "duration.no_expiry_short": "Sin expiración",
- "table.last_refresh": "Última renovación",
- "table.never_refreshed": "Nunca renovada",
- "table.time_ago": "hace {elapsed}",
- "security.cross_origin": "Solicitud rechazada: no provino de este panel. Recargue la página e inténtelo de nuevo.",
- "action.refreshed": "Página recargada con datos actualizados.",
- "action.cron_ran": "Ciclo ejecutado en {duration}ms: {inspected} inspeccionados, {findings} hallazgos.",
- "password.policy": "Mínimo de 6 caracteres, con mayúscula, minúscula, número y carácter especial.",
- "password.too_short": "La contraseña necesita al menos 6 caracteres.",
- "password.needs_upper": "La contraseña necesita una letra mayúscula.",
- "password.needs_lower": "La contraseña necesita una letra minúscula.",
- "password.needs_digit": "La contraseña necesita un número.",
- "password.needs_special": "La contraseña necesita un carácter especial.",
- "duration.unlimited": "Ilimitado / N/D",
- "duration.expired": "Expirado",
- "reason.api_key": "Clave estática: no expira, nada que renovar",
- "reason.no_expiry": "Sin expiración registrada: se renovará en el próximo barrido",
- "reason.expired": "Token expirado: se intentará renovar en el próximo barrido",
- "reason.inside_margin": "Dentro del margen de {margin} min: se renovará en el próximo barrido",
- "reason.outside_margin": "Fuera del margen de {margin} min: renovación prevista en ~{eta}",
- "auth.title": "Credenciales del panel",
- "auth.new_password": "Nueva contraseña",
- "auth.min_chars": "Mínimo de 4 caracteres.",
- "auth.save_failed": "No se pudo guardar la nueva contraseña.",
- "auth.env_managed": "Las credenciales de este panel se gestionan fuera de él. Cámbielas donde se configura el servicio y reinícielo.",
- "pagination.range": "{inicio}-{fim} de {total}",
- "pagination.label": "Paginación",
- "footer.signed_in": "Autenticado como",
- "footer.generated": "Datos generados en el servidor a las",
- "language.save_failed": "No se pudo guardar el idioma: el almacenamiento del panel no acepta escritura.",
- "language.label": "Idioma",
- "action.settings": "Configuración",
- "sso.title": "Inicio de sesión federado (SSO)",
- "sso.intro": "Opcional. El formulario de usuario y contraseña nunca sale de la pantalla, así que una caída del proveedor no deja a nadie fuera.",
- "sso.tab_oidc": "OpenID Connect",
- "sso.tab_saml": "SAML 2.0",
- "sso.provider": "Proveedor activo",
- "sso.provider_none": "Apagado (solo contraseña)",
- "sso.provider_oidc": "OpenID Connect",
- "sso.provider_saml": "SAML 2.0 (no disponible)",
- "sso.enabled_help": "Un proveedor a la vez. Dos activos al mismo tiempo es lo que hace que "
- "la respuesta de uno se acepte como si fuera la del otro.",
- "sso.enabled_off": "Desactivado (solo contraseña)",
- "sso.enabled_oidc": "OIDC",
- "sso.base_url": "Dirección pública del panel",
- "sso.base_url_hint": "El origen exacto que usa el navegador, sin barra al final. Tiene "
- "que ser FIJO: un túnel rápido cambia de dirección en cada arranque "
- "y toda dirección de retorno registrada en el proveedor deja de "
- "coincidir, así que el acceso federado exige túnel con nombre o "
- "Tailscale.",
- "sso.base_url_help": "Origen público exacto, sin barra final. La dirección de retorno nace de este valor, y nunca de las cabeceras de la petición.",
- "sso.redirect_uri": "Dirección de retorno que se registra en el proveedor",
- "sso.callback_url": "Dirección de retorno a registrar en el proveedor",
- "sso.callback_help": "Copie exactamente esta línea en el proveedor. Un carácter de "
- "diferencia y rechaza el intercambio.",
- "sso.issuer": "Emisor (issuer)",
- "sso.issuer_help": "El emisor publicado en el documento de descubrimiento. Debe coincidir "
- "carácter por carácter.",
- "sso.client_id": "Identificador del cliente",
- "sso.client_secret": "Secreto del cliente",
- "sso.secret_stored": "Hay un secreto guardado. Deje el campo en blanco para conservarlo.",
- "sso.secret_absent": "Todavía no hay secreto guardado.",
- "sso.secret_missing": "Todavía no hay secreto guardado. Sin él, el SSO permanece apagado.",
- "sso.secret_from_env": "El secreto viene de la variable de entorno OIDC_CLIENT_SECRET. Cámbielo allí y reinicie el servicio.",
- "sso.secret_keep_help": "Nunca se vuelve a mostrar. Deje este campo vacío para conservar "
- "el actual.",
- "sso.secret_failed": "No se pudo guardar el secreto del cliente: el almacenamiento del "
- "panel no acepta escritura.",
- "sso.scopes": "Ámbitos",
- "sso.scopes_help": "Separados por espacios. El valor por omisión cubre el correo y el perfil.",
- "sso.allowed_domains": "Dominios autorizados",
- "sso.allowed_emails": "Correos autorizados",
- "sso.allowlist_hint": "Separados por coma, y la lista no puede quedar vacía: sin ella "
- "entraría toda cuenta del proveedor.",
- "sso.allowlist_help": "Separados por comas, y al menos uno de los dos. Una lista vacía significa que entra cualquier cuenta del proveedor, por eso el panel se niega a encender el SSO sin ella.",
- "sso.local_password": "Su contraseña actual del panel",
- "sso.local_password_help": "Se exige además de la sesión: quien secuestre una sesión de ocho horas podría, si no, apuntar el panel a un proveedor hostil y agregarse a la lista de autorizados.",
- "sso.current_password": "Contraseña actual del panel",
- "sso.confirm_hint": "Guardar pide la contraseña local otra vez: quien robe una sesión no "
- "puede apuntar el panel a un proveedor hostil y ponerse en la lista.",
- "sso.current_password_help": "Exigida además de la sesión: una cookie robada no puede "
- "bastar para apuntar el panel a un proveedor hostil.",
- "sso.save": "Guardar configuración de SSO",
- "sso.saved": "Configuración de inicio de sesión federado guardada.",
- "sso.turned_off": "Acceso federado apagado. El formulario local sigue funcionando.",
- "sso.save_refused_password": "Contraseña del panel incorrecta: no se cambió nada.",
- "sso.save_refused_allowlist": "Indique al menos un dominio o dirección: una lista vacía "
- "dejaría entrar a toda cuenta del proveedor.",
- "sso.save_refused_fields": "Complete todos los campos del proveedor elegido, incluida la "
- "dirección pública de este panel.",
- "sso.save_refused_secret": "No se pudo guardar el secreto del cliente en disco, así que "
- "el acceso federado no se activó.",
- "sso.save_refused_saml": "SAML 2.0 no está disponible en esta imagen.",
- "sso.save_failed": "No se pudo guardar la configuración.",
- "sso.wrong_password": "Contraseña del panel incorrecta: no se cambió nada.",
- "sso.allowlist_required": "Complete al menos un dominio o correo autorizado. SSO sin lista de autorizados deja entrar a cualquier cuenta del proveedor.",
- "sso.incomplete": "Complete la dirección pública, el issuer y el ID de cliente antes de encender el SSO.",
- "sso.no_secret": "Sin secreto de cliente: defina uno aquí o en la variable de entorno OIDC_CLIENT_SECRET.",
- "sso.saml_refused": "SAML 2.0 no se puede encender en esta imagen.",
- "sso.disabled_by_env": "El inicio de sesión federado está apagado por la variable de entorno SSO_DISABLED. La configuración de abajo se conserva, pero ninguna ruta de SSO responde.",
- "sso.login_button": "Entrar con {provider}",
- "sso.need_base_url": "La dirección pública del panel debe ser esquema y host, sin ruta, y "
- "https fuera del loopback.",
- "sso.need_issuer": "El emisor es obligatorio y debe usar https fuera del loopback.",
- "sso.need_client_id": "El identificador del cliente es obligatorio.",
- "sso.need_secret": "El secreto del cliente es obligatorio.",
- "sso.need_allowlist": "Complete al menos un dominio o correo autorizado.",
- "sso.tunnel_warning": "El túnel rápido cambia de dirección en cada arranque, y toda "
- "dirección de retorno registrada en el proveedor deja de coincidir. "
- "El SSO exige un túnel con nombre o Tailscale, con dirección fija.",
- "sso.sign_in_with": "Entrar con {provider}",
- "sso.unavailable": "El proveedor de identidad no respondió. Entre con usuario y contraseña.",
- "sso.or": "o",
- "sso.failed": "No se pudo entrar por el proveedor de identidad. Inténtelo de nuevo o use usuario y contraseña.",
- "sso.entering": "Entrando",
- "sso.entering_body": "Acceso federado aceptado. Abriendo el panel.",
- "sso.status_on": "Activo · {provider}",
- "sso.status_off": "Apagado",
- "sso.signing_in": "Entrando",
- "sso.signing_in_body": "El proveedor de identidad confirmó quién es usted. Llevándolo al panel.",
- "sso.logout_note": "Salir borra solo la sesión del panel. La sesión en el proveedor de identidad sigue abierta, así que el siguiente clic en el botón de SSO puede no pedir contraseña otra vez.",
- "sso.landing_title": "Entrando...",
- "sso.landing_body": "La sesión fue creada. Llevándolo al panel.",
- "sso.idp_entity_id": "Identificador del proveedor de identidad",
- "sso.idp_sso_url": "Dirección de entrada del proveedor de identidad",
- "sso.idp_cert": "Certificado X.509 del proveedor de identidad",
- "sso.metadata_hint": "Después de guardar, descargue la descripción del servicio en {url} "
- "ya autenticado y entréguela al proveedor de identidad.",
- "sso.idp_cert_help": "Certificado público, que puede quedar junto al resto de la configuración.",
- "sso.saml_unavailable": "SAML 2.0 no está disponible en esta imagen. Requiere una biblioteca que firme y verifique XML, y hacerlo a mano aceptaría aserciones falsificadas en silencio. Los campos quedan aquí para que la configuración esté lista cuando la imagen traiga la biblioteca.",
- "sso.saml_pending": "La biblioteca de SAML2 está instalada, pero esta versión del panel "
- "solo entra por OIDC. La entrada por SAML2 es la próxima fase.",
- },
-}
+LOCALES_DIR = pathlib.Path(__file__).resolve().parent / "locales"
+
+
+def carrega_catalogo(idioma: str) -> Dict[str, str]:
+ """Carrega o catálogo do arquivo JSON, interpolando o gateway de identidade.py."""
+ caminho = LOCALES_DIR / f"{idioma}.json"
+ if caminho.is_file():
+ try:
+ with open(caminho, "r", encoding="utf-8") as f:
+ bruto = json.load(f)
+ return {
+ k: str(v).replace("{NOME_DO_GATEWAY}", NOME_DO_GATEWAY)
+ for k, v in bruto.items()
+ }
+ except Exception:
+ pass
+ return {}
+
+
+def carrega_todas_traducoes() -> Dict[str, Dict[str, str]]:
+ """Carrega todos os idiomas suportados do disco."""
+ traducoes: Dict[str, Dict[str, str]] = {}
+ for code in LANGUAGES:
+ traducoes[code] = carrega_catalogo(code)
+ return traducoes
+
+
+TRANSLATIONS: Dict[str, Dict[str, str]] = carrega_todas_traducoes()
+
+
+def recarrega_traducoes() -> None:
+ """Recarrega os catálogos JSON do disco em tempo de execução."""
+ global TRANSLATIONS
+ novas = carrega_todas_traducoes()
+ for lang, catalogo in novas.items():
+ if catalogo:
+ TRANSLATIONS[lang] = catalogo
def normalize_language(code: str) -> str:
@@ -962,14 +83,14 @@ def translate(key: str, lang: str = DEFAULT_LANGUAGE, **params) -> str:
lang = normalize_language(lang)
# O rótulo do produto vem primeiro: são as poucas chaves que dependem do que
# ESTE gateway faz, e elas moram em identidade.py justamente para que o
- # catálogo abaixo possa ser o mesmo texto nos três irmãos.
+ # catálogo possa ser o mesmo texto nos três irmãos.
text = ROTULOS_DO_PRODUTO.get(lang, {}).get(key)
if text is None:
text = TRANSLATIONS.get(lang, {}).get(key)
if text is None:
text = ROTULOS_DO_PRODUTO.get(DEFAULT_LANGUAGE, {}).get(key)
if text is None:
- text = TRANSLATIONS[DEFAULT_LANGUAGE].get(key, key)
+ text = TRANSLATIONS.get(DEFAULT_LANGUAGE, {}).get(key, key)
if params:
try:
return text.format(**params)
diff --git a/src/nine_rtksync/locales/en.json b/src/nine_rtksync/locales/en.json
new file mode 100644
index 0000000..dcc1ffa
--- /dev/null
+++ b/src/nine_rtksync/locales/en.json
@@ -0,0 +1,296 @@
+{
+ "action.access": "Access",
+ "action.change_credentials": "Change credentials",
+ "action.close": "Close",
+ "action.cron_ran": "Cycle ran in {duration}ms: {inspected} inspected, {findings} findings.",
+ "action.refresh": "Refresh",
+ "action.refresh_title": "Reload data from the server",
+ "action.refreshed": "Page reloaded with fresh data.",
+ "action.run_now": "Run now",
+ "action.save_credentials": "Save credentials",
+ "action.settings": "Settings",
+ "action.sync_now": "Sync now",
+ "action.test_connection": "Test connection",
+ "action.validate": "Validate credentials",
+ "app.gateway_unset": "gateway not configured",
+ "app.subtitle": "{NOME_DO_GATEWAY} Universal Token & Connection Synchronizer",
+ "auth.challenge": "Security verification",
+ "auth.challenge_failed": "Security challenge failed. Please select the correct item.",
+ "auth.challenge_prompt": "Select the {item} to confirm you are human:",
+ "auth.enter": "Sign in",
+ "auth.env_managed": "Credentials for this panel are managed outside it. Change them where the service is configured, then restart it.",
+ "auth.item_bell": "Bell",
+ "auth.item_gear": "Gear",
+ "auth.item_heart": "Heart",
+ "auth.item_key": "Key",
+ "auth.item_lightning": "Lightning",
+ "auth.item_lock": "Lock",
+ "auth.item_shield": "Shield",
+ "auth.item_star": "Star",
+ "auth.logged_out": "You have signed out successfully.",
+ "auth.login_failed": "Wrong user or password.",
+ "auth.login_intro": "Sign in to see the panel.",
+ "auth.logout": "Sign out",
+ "auth.min_chars": "Minimum of 4 characters.",
+ "auth.new_password": "New password",
+ "auth.password": "Password",
+ "auth.password_disabled": "Local password login is disabled. Please use an enabled SSO provider.",
+ "auth.required": "Authentication required.",
+ "auth.required_body": "This dashboard is private. Sign in to continue.",
+ "auth.save_failed": "The new password could not be stored.",
+ "auth.select_challenge": "Please select the requested security item.",
+ "auth.title": "Dashboard credentials",
+ "auth.too_many": "Too many attempts",
+ "auth.too_many_body": "Wait {seconds}s before trying again.",
+ "auth.updated_body": "Your new password is now in effect. The browser is still holding the previous one, so sign in again to continue.",
+ "auth.updated_link": "Back to the dashboard",
+ "auth.updated_title": "Credentials updated",
+ "auth.user": "User",
+ "combos.empty": "No fallback combo registered.",
+ "combos.empty_hint": "In {NOME_DO_GATEWAY} the combo is the router fallback (router_settings.fallbacks); none is declared.",
+ "combos.kind_content_policy": "content policy",
+ "combos.kind_context_window": "context window",
+ "combos.title": "Resilience combos",
+ "connections.empty": "No connection registered on the gateway.",
+ "connections.empty_hint": "In {NOME_DO_GATEWAY} a connection is the upstream endpoint behind the registered models; none of them declares one yet.",
+ "connections.lifecycle_note": "The upstream endpoint has no expiry of its own: what expires is the credential at the provider, outside this gateway.",
+ "connections.served_models": "Models served",
+ "connections.title": "Monitored connections",
+ "credential.absent": "Not exposed by the gateway",
+ "credential.checked_at": "Checked at",
+ "credential.env": "Kept in the proxy environment",
+ "credential.inline": "Declared on the model",
+ "credential.named": "Named credential",
+ "credential.never": "Never validated",
+ "credential.title": "Credential",
+ "cron.active": "Active · every {interval}s",
+ "cron.disabled": "Disabled (CRON_ENABLED=0)",
+ "cron.failed": "The cycle could not be completed: {error}",
+ "cron.last_result": "Last result",
+ "cron.last_run": "Last run",
+ "cron.next_run": "Next run",
+ "cron.no_runs": "No cycle has run yet",
+ "cron.result_line": "{inspected} · ({duration}ms)",
+ "cron.run_now": "Run now",
+ "cron.title": "Scheduler",
+ "cron.total_findings": "Findings so far",
+ "cron.total_renewals": "Tokens renewed",
+ "cron.total_runs": "Total cycles",
+ "cron.unavailable": "The scheduler is not available on this instance.",
+ "duration.expired": "Expired",
+ "duration.no_expiry": "No expiry (static key)",
+ "duration.no_expiry_short": "No expiry",
+ "duration.unknown_expiry": "Expiry unknown",
+ "duration.unlimited": "Unlimited / N/A",
+ "egress.bound": "own egress",
+ "egress.shared": "shares the gateway address with {count} accounts",
+ "egress.single": "gateway address (only account)",
+ "egress.title": "Network egress",
+ "egress.unknown": "egress unknown",
+ "footer.generated": "Data rendered on the server at",
+ "footer.signed_in": "Signed in as",
+ "gateway.database": "SQLite database",
+ "gateway.db_missing": "Database not found",
+ "gateway.db_summary": "Operational ({connections} connections, {combos} combos)",
+ "gateway.diag_db_failed": "Gateway online, database unreadable",
+ "gateway.diag_gateway_failed": "Gateway unreachable",
+ "gateway.diag_ok": "Gateway and SQLite database fully operational",
+ "gateway.diagnostics": "Diagnostics",
+ "gateway.gateway": "Gateway",
+ "gateway.latency": "Latency",
+ "gateway.no_response": "no response",
+ "gateway.offline": "OFFLINE",
+ "gateway.status": "Status",
+ "gateway.title": "Gateway connection",
+ "health.active": "Active",
+ "health.blocked": "Blocked",
+ "health.expired": "Expired",
+ "health.expiring_soon": "Expiring",
+ "health.invalid": "Rejected",
+ "health.no_expiration": "No expiry",
+ "health.not_checked": "Not checked",
+ "health.over_budget": "Budget exhausted",
+ "health.rate_limited": "Rate limited",
+ "health.unknown": "Unknown",
+ "health.unreachable": "Unreachable",
+ "health.valid": "Accepted",
+ "keys.access_all": "All models",
+ "keys.access_restricted": "Restricted to the listed models",
+ "keys.banned": "Banned",
+ "keys.disabled": "Deactivated",
+ "keys.empty": "No virtual key issued by the gateway.",
+ "keys.enabled": "Accepted",
+ "keys.revoked": "Revoked",
+ "keys.title": "Virtual keys",
+ "language.label": "Language",
+ "language.save_failed": "Could not save the language preference: the panel storage is not writable.",
+ "limits.ok": "Every limit respects the level above it.",
+ "limits.title": "Limit coherence",
+ "metric.api_keys": "API keys",
+ "metric.combos": "Registered combos",
+ "metric.expiring": "Expiring / expired",
+ "metric.limit_findings": "Limit findings",
+ "metric.oauth_accounts": "OAuth accounts",
+ "metric.teams": "Teams",
+ "metric.total_connections": "Total connections",
+ "metric.virtual_keys": "Virtual keys",
+ "models.empty": "The gateway answered with an empty model catalogue.",
+ "models.inherited": "Status, validity and last renewal come from the connection that serves this model.",
+ "models.no_key": "The gateway issues no active key, and it only hands the model catalogue to a key it issued itself.",
+ "models.showing": "Showing {shown} of {total} models — open the gateway for the full list.",
+ "models.title": "Registered models",
+ "models.unreachable": "The gateway did not answer the model catalogue.",
+ "pagination.label": "Pagination",
+ "pagination.range": "{inicio}-{fim} of {total}",
+ "password.needs_digit": "Password must contain a number.",
+ "password.needs_lower": "Password must contain a lowercase letter.",
+ "password.needs_special": "Password must contain a special character.",
+ "password.needs_upper": "Password must contain an uppercase letter.",
+ "password.policy": "At least 6 characters, with uppercase, lowercase, a number and a special character.",
+ "password.too_short": "Password must have at least 6 characters.",
+ "reason.api_key": "Static key: never expires, nothing to renew",
+ "reason.expired": "Token expired: renewal will be attempted on the next sweep",
+ "reason.inside_margin": "Within the {margin} min margin: will be renewed on the next sweep",
+ "reason.local_empty": "Local instance answered, but has no model installed yet",
+ "reason.local_ok": "Local instance answered with {count} model(s)",
+ "reason.local_unreachable": "Local instance did not answer the model catalog",
+ "reason.no_expiry": "No expiry recorded: will be renewed on the next sweep",
+ "reason.outside_margin": "Outside the {margin} min margin: renewal expected in ~{eta}",
+ "security.body": "no password has been set for this dashboard yet, so access still depends on the one-time recovery credential. Set your own now, or define DASHBOARD_USER/DASHBOARD_PASSWORD in the environment.",
+ "security.cross_origin": "Request rejected: it did not come from this dashboard. Reload the page and try again.",
+ "security.title": "Security warning:",
+ "sso.allowed_domains": "Allowed domains",
+ "sso.allowed_emails": "Allowed e-mail addresses",
+ "sso.allowlist_help": "Comma separated, and at least one of the two. An empty list means every account at the provider gets in, so the panel refuses to turn SSO on without it.",
+ "sso.allowlist_hint": "Comma separated, and it cannot be empty: without it every account at the provider would get in.",
+ "sso.allowlist_required": "Fill in at least one allowed domain or e-mail address. SSO without an allowed list lets in every account at the provider.",
+ "sso.at_least_one_auth": "At least one authentication method must remain enabled (Password, OIDC or SAML2).",
+ "sso.base_url": "Public panel address",
+ "sso.base_url_help": "Exact public origin, no trailing slash. The return address is built from this value and never from the request headers.",
+ "sso.base_url_hint": "The exact origin the browser uses, with no trailing slash. It has to be FIXED: a quick tunnel changes address on every start and every return address registered at the provider stops matching, so single sign-on needs a named tunnel or Tailscale.",
+ "sso.callback_help": "Copy this exact string into the provider. A single character of difference and the provider refuses the exchange.",
+ "sso.callback_url": "Redirect URI to register at the provider",
+ "sso.client_id": "Client ID",
+ "sso.client_secret": "Client secret",
+ "sso.confirm_hint": "Saving asks for the local password again: whoever steals a session must not be able to point the panel at a hostile provider and put themselves on the list.",
+ "sso.current_password": "Current panel password",
+ "sso.current_password_help": "Required on top of the session: a stolen cookie must not be enough to point the panel at a hostile provider.",
+ "sso.disabled_by_env": "Single sign-on is switched off by the SSO_DISABLED environment variable. The settings below are kept, but no SSO route answers.",
+ "sso.enable_oidc": "Enable OpenID Connect (OIDC)",
+ "sso.enable_password": "Local login (Username and password)",
+ "sso.enable_password_help": "Enables login with local panel username and password. Turn off only if you have at least one SSO provider configured and working.",
+ "sso.enable_saml": "Enable SAML 2.0",
+ "sso.enabled_help": "One provider at a time. Two enabled at once is what makes a response from one acceptable as if it came from the other.",
+ "sso.enabled_off": "Disabled (password only)",
+ "sso.enabled_oidc": "OIDC",
+ "sso.entering": "Signing in",
+ "sso.entering_body": "Single sign-on accepted. Opening the panel.",
+ "sso.failed": "Could not sign in through the identity provider. Try again, or use your user and password.",
+ "sso.idp_cert": "Identity provider X.509 certificate",
+ "sso.idp_cert_help": "Public certificate, safe to store next to the rest of the settings.",
+ "sso.idp_entity_id": "Identity provider entity ID",
+ "sso.idp_sso_url": "Identity provider sign-on URL",
+ "sso.incomplete": "Fill in the public address, the issuer and the client ID before turning SSO on.",
+ "sso.intro": "Optional. The local user and password form never leaves the screen, so a provider outage does not lock you out.",
+ "sso.issuer": "Issuer",
+ "sso.issuer_help": "The issuer published in the discovery document. It must match character for character.",
+ "sso.landing_body": "The session has been created. Taking you to the dashboard.",
+ "sso.landing_title": "Signing in...",
+ "sso.local_password": "Your current panel password",
+ "sso.local_password_help": "Required here on top of the session: whoever steals an eight-hour session could otherwise point the panel at a hostile provider and add themselves to the allowed list.",
+ "sso.login_button": "Sign in with {provider}",
+ "sso.logout_note": "Signing out clears the panel session only. The session at the identity provider stays open, so the next click on the SSO button may not ask for a password again.",
+ "sso.metadata_hint": "Once saved, download the service description at {url} while signed in and hand it to the identity provider.",
+ "sso.need_allowlist": "Fill in at least one allowed domain or e-mail.",
+ "sso.need_base_url": "The public panel address must be a scheme and a host, with no path, and https outside the loopback.",
+ "sso.need_client_id": "The client ID is required.",
+ "sso.need_issuer": "The issuer is required and must use https outside the loopback.",
+ "sso.need_secret": "The client secret is required.",
+ "sso.no_secret": "No client secret: set one here or in the OIDC_CLIENT_SECRET environment variable.",
+ "sso.or": "or",
+ "sso.provider": "Active provider",
+ "sso.provider_none": "Off (password only)",
+ "sso.provider_oidc": "OpenID Connect",
+ "sso.provider_saml": "SAML 2.0 (unavailable)",
+ "sso.redirect_uri": "Return address to register with the provider",
+ "sso.saml_acs_help": "Callback URL where the Identity Provider delivers SAML 2.0 assertions.",
+ "sso.saml_acs_url": "ACS URL (Assertion Consumer Service)",
+ "sso.saml_active_help": "SAML 2.0 is active and ready. Configure the URLs below in your Identity Provider (IdP).",
+ "sso.saml_metadata_help": "Service Provider identifier and metadata XML endpoint to register with your IdP.",
+ "sso.saml_metadata_url": "SP Metadata URL (Entity ID)",
+ "sso.saml_pending": "The SAML2 library is installed, but this version of the panel only signs in through OIDC. SAML2 sign-in is the next phase.",
+ "sso.saml_refused": "SAML 2.0 cannot be turned on in this image.",
+ "sso.saml_unavailable": "SAML 2.0 is not available in this image. It needs a library that signs and verifies XML, and doing it by hand would accept forged assertions in silence. The fields are here so the settings are ready when the image ships with it.",
+ "sso.save": "Save SSO settings",
+ "sso.save_failed": "Could not save the settings.",
+ "sso.save_refused_allowlist": "Add at least one domain or address: an empty list would let every account at the provider in.",
+ "sso.save_refused_fields": "Fill in every field of the chosen provider, including the public address of this panel.",
+ "sso.save_refused_password": "Wrong panel password: nothing was changed.",
+ "sso.save_refused_saml": "SAML 2.0 is not available in this image.",
+ "sso.save_refused_secret": "The client secret could not be written to disk, so single sign-on was not turned on.",
+ "sso.saved": "Single sign-on settings saved.",
+ "sso.scopes": "Scopes",
+ "sso.scopes_help": "Space separated. The default covers the e-mail address and the profile.",
+ "sso.secret_absent": "No secret stored yet.",
+ "sso.secret_failed": "Could not store the client secret: the panel storage is not writable.",
+ "sso.secret_from_env": "The secret comes from the OIDC_CLIENT_SECRET environment variable. Change it there and restart the service.",
+ "sso.secret_keep_help": "It is never shown again. Leave this field empty to keep the current one.",
+ "sso.secret_missing": "No secret stored yet. Without one, SSO stays off.",
+ "sso.secret_stored": "A secret is stored. Leave the field blank to keep it.",
+ "sso.sign_in_with": "Sign in with {provider}",
+ "sso.signing_in": "Signing in",
+ "sso.signing_in_body": "The identity provider confirmed who you are. Taking you to the panel.",
+ "sso.status_off": "Off",
+ "sso.status_on": "On · {provider}",
+ "sso.tab_oidc": "OpenID Connect",
+ "sso.tab_saml": "SAML 2.0",
+ "sso.test_failed": "Connection test failed",
+ "sso.test_oidc": "Test OIDC connection",
+ "sso.test_saml": "Test SAML2 connection",
+ "sso.test_success": "Connection tested successfully!",
+ "sso.testing": "Testing connection...",
+ "sso.title": "Single sign-on (SSO)",
+ "sso.tunnel_warning": "A quick tunnel gets a new address on every start, and every return address registered at the provider stops matching. Single sign-on needs a named tunnel or Tailscale, with a fixed address.",
+ "sso.turned_off": "Single sign-on is off. The local form keeps working.",
+ "sso.unavailable": "The identity provider did not answer. Sign in with your user and password.",
+ "sso.wrong_password": "Wrong panel password: nothing was changed.",
+ "table.alias": "Alias",
+ "table.api_base": "API base",
+ "table.cascade": "Model cascade",
+ "table.combo": "Combo",
+ "table.connection": "Connection",
+ "table.context_limit": "Input limit",
+ "table.description": "Description",
+ "table.details": "Details",
+ "table.diagnosis": "Renewal diagnosis",
+ "table.endpoints": "Endpoints",
+ "table.expires_at": "Expires at",
+ "table.issued_at": "Issued at",
+ "table.key_state": "Key state",
+ "table.last_refresh": "Last renewal",
+ "table.last_used": "Last used",
+ "table.max_budget": "Budget ceiling",
+ "table.model_access": "Model access",
+ "table.models": "models",
+ "table.name": "Name",
+ "table.never_refreshed": "Never renewed",
+ "table.not_declared": "Not declared",
+ "table.output_limit": "Output limit",
+ "table.provider": "Provider",
+ "table.remaining": "Time remaining",
+ "table.rpm_limit": "RPM limit",
+ "table.scopes": "Scopes",
+ "table.source": "Origin",
+ "table.spend": "Spend",
+ "table.status": "Status",
+ "table.team": "Team",
+ "table.time_ago": "{elapsed} ago",
+ "table.tpm_limit": "TPM limit",
+ "table.type": "Type",
+ "type.api_key": "API key",
+ "type.local": "Local",
+ "type.oauth": "OAuth 2.0",
+ "type.synced_model": "Synced model",
+ "type.virtual_key": "Virtual key",
+ "auth.authentication": "Authentication"
+}
diff --git a/src/nine_rtksync/locales/es.json b/src/nine_rtksync/locales/es.json
new file mode 100644
index 0000000..6b7be5a
--- /dev/null
+++ b/src/nine_rtksync/locales/es.json
@@ -0,0 +1,296 @@
+{
+ "action.access": "Acceso",
+ "action.change_credentials": "Cambiar credenciales",
+ "action.close": "Cerrar",
+ "action.cron_ran": "Ciclo ejecutado en {duration}ms: {inspected} inspeccionados, {findings} hallazgos.",
+ "action.refresh": "Actualizar",
+ "action.refresh_title": "Recargar los datos del servidor",
+ "action.refreshed": "Página recargada con datos actualizados.",
+ "action.run_now": "Ejecutar ahora",
+ "action.save_credentials": "Guardar credenciales",
+ "action.settings": "Configuración",
+ "action.sync_now": "Sincronizar ahora",
+ "action.test_connection": "Probar conexión",
+ "action.validate": "Validar credenciales",
+ "app.gateway_unset": "gateway no configurado",
+ "app.subtitle": "{NOME_DO_GATEWAY} Universal Token & Connection Synchronizer",
+ "auth.challenge": "Verificación de seguridad",
+ "auth.challenge_failed": "Fallo en el desafío de seguridad. Por favor, seleccione el elemento correcto.",
+ "auth.challenge_prompt": "Selecciona el/la {item} para confirmar que eres humano:",
+ "auth.enter": "Entrar",
+ "auth.env_managed": "Las credenciales de este panel se gestionan fuera de él. Cámbielas donde se configura el servicio y reinícielo.",
+ "auth.item_bell": "Campana",
+ "auth.item_gear": "Engranaje",
+ "auth.item_heart": "Corazón",
+ "auth.item_key": "Llave",
+ "auth.item_lightning": "Rayo",
+ "auth.item_lock": "Candado",
+ "auth.item_shield": "Escudo",
+ "auth.item_star": "Estrella",
+ "auth.logged_out": "Sesión cerrada con éxito.",
+ "auth.login_failed": "Usuario o contraseña incorrectos.",
+ "auth.login_intro": "Entre para ver el panel.",
+ "auth.logout": "Salir",
+ "auth.min_chars": "Mínimo de 4 caracteres.",
+ "auth.new_password": "Nueva contraseña",
+ "auth.password": "Contraseña",
+ "auth.password_disabled": "El inicio por contraseña está desactivado. Use un proveedor SSO activo.",
+ "auth.required": "Autenticación requerida.",
+ "auth.required_body": "Este panel es privado. Autentíquese para continuar.",
+ "auth.save_failed": "No se pudo guardar la nueva contraseña.",
+ "auth.select_challenge": "Por favor, seleccione el elemento de seguridad solicitado.",
+ "auth.title": "Credenciales del panel",
+ "auth.too_many": "Demasiados intentos",
+ "auth.too_many_body": "Espere {seconds}s antes de intentarlo de nuevo.",
+ "auth.updated_body": "La nueva contraseña ya está vigente. El navegador todavía guarda la anterior, así que vuelva a autenticarse para continuar.",
+ "auth.updated_link": "Volver al panel",
+ "auth.updated_title": "Credenciales actualizadas",
+ "auth.user": "Usuario",
+ "combos.empty": "No hay combos de respaldo registrados.",
+ "combos.empty_hint": "En {NOME_DO_GATEWAY} el combo es el fallback del enrutador (router_settings.fallbacks); no hay ninguno declarado.",
+ "combos.kind_content_policy": "política de contenido",
+ "combos.kind_context_window": "ventana de contexto",
+ "combos.title": "Combos de resiliencia",
+ "connections.empty": "No hay conexiones registradas en el gateway.",
+ "connections.empty_hint": "En {NOME_DO_GATEWAY} la conexión es el destino detrás de los modelos registrados; ninguno declara uno todavía.",
+ "connections.lifecycle_note": "El destino no tiene validez propia: lo que expira es la credencial en el proveedor, fuera de este gateway.",
+ "connections.served_models": "Modelos servidos",
+ "connections.title": "Conexiones monitoreadas",
+ "credential.absent": "No expuesta por la pasarela",
+ "credential.checked_at": "Verificada el",
+ "credential.env": "Guardada en el entorno del proxy",
+ "credential.inline": "Declarada en el modelo",
+ "credential.named": "Credencial con nombre",
+ "credential.never": "Nunca validada",
+ "credential.title": "Credencial",
+ "cron.active": "Activo · cada {interval}s",
+ "cron.disabled": "Desactivado (CRON_ENABLED=0)",
+ "cron.failed": "No se pudo completar el ciclo: {error}",
+ "cron.last_result": "Último resultado",
+ "cron.last_run": "Última ejecución",
+ "cron.next_run": "Próxima ejecución",
+ "cron.no_runs": "Aún no se ejecutó ningún ciclo",
+ "cron.result_line": "{inspected} · ({duration}ms)",
+ "cron.run_now": "Ejecutar ahora",
+ "cron.title": "Programador",
+ "cron.total_findings": "Hallazgos hasta ahora",
+ "cron.total_renewals": "Tokens renovados",
+ "cron.total_runs": "Ciclos totales",
+ "cron.unavailable": "El programador no está disponible en esta instancia.",
+ "duration.expired": "Expirado",
+ "duration.no_expiry": "Sin expiración (clave estática)",
+ "duration.no_expiry_short": "Sin expiración",
+ "duration.unknown_expiry": "Validez desconocida",
+ "duration.unlimited": "Ilimitado / N/D",
+ "egress.bound": "salida propia",
+ "egress.shared": "comparte la dirección del gateway con {count} cuentas",
+ "egress.single": "dirección del gateway (única cuenta)",
+ "egress.title": "Salida de red",
+ "egress.unknown": "salida desconocida",
+ "footer.generated": "Datos generados en el servidor a las",
+ "footer.signed_in": "Autenticado como",
+ "gateway.database": "Base de datos SQLite",
+ "gateway.db_missing": "Base de datos no encontrada",
+ "gateway.db_summary": "Operativo ({connections} conexiones, {combos} combos)",
+ "gateway.diag_db_failed": "Gateway en línea, base ilegible",
+ "gateway.diag_gateway_failed": "Gateway inaccesible",
+ "gateway.diag_ok": "Gateway y base SQLite totalmente operativos",
+ "gateway.diagnostics": "Diagnóstico",
+ "gateway.gateway": "Gateway",
+ "gateway.latency": "Latencia",
+ "gateway.no_response": "sin respuesta",
+ "gateway.offline": "DESCONECTADO",
+ "gateway.status": "Estado",
+ "gateway.title": "Conexión con el gateway",
+ "health.active": "Activo",
+ "health.blocked": "Bloqueada",
+ "health.expired": "Expirado",
+ "health.expiring_soon": "Por expirar",
+ "health.invalid": "Rechazada",
+ "health.no_expiration": "Sin expiración",
+ "health.not_checked": "Sin verificar",
+ "health.over_budget": "Presupuesto agotado",
+ "health.rate_limited": "Límite de tasa",
+ "health.unknown": "Desconocido",
+ "health.unreachable": "Inaccesible",
+ "health.valid": "Aceptada",
+ "keys.access_all": "Todos los modelos",
+ "keys.access_restricted": "Restringida a los modelos listados",
+ "keys.banned": "Bloqueada",
+ "keys.disabled": "Desactivada",
+ "keys.empty": "El gateway no ha emitido ninguna clave virtual.",
+ "keys.enabled": "Aceptada",
+ "keys.revoked": "Revocada",
+ "keys.title": "Claves virtuales",
+ "language.label": "Idioma",
+ "language.save_failed": "No se pudo guardar el idioma: el almacenamiento del panel no acepta escritura.",
+ "limits.ok": "Todo límite respeta el nivel superior.",
+ "limits.title": "Coherencia de los límites",
+ "metric.api_keys": "Claves de API",
+ "metric.combos": "Combos registrados",
+ "metric.expiring": "Por vencer / vencidas",
+ "metric.limit_findings": "Incoherencias de límite",
+ "metric.oauth_accounts": "Cuentas OAuth",
+ "metric.teams": "Equipos",
+ "metric.total_connections": "Conexiones totales",
+ "metric.virtual_keys": "Claves virtuales",
+ "models.empty": "El gateway respondió con el catálogo de modelos vacío.",
+ "models.inherited": "El estado, la validez y la última renovación vienen de la conexión que sirve este modelo.",
+ "models.no_key": "El gateway no tiene ninguna clave activa emitida, y solo entrega el catálogo de modelos a una clave emitida por él mismo.",
+ "models.showing": "Mostrando {shown} de {total} modelos — la lista completa está en el gateway.",
+ "models.title": "Modelos registrados",
+ "models.unreachable": "El gateway no respondió al catálogo de modelos.",
+ "pagination.label": "Paginación",
+ "pagination.range": "{inicio}-{fim} de {total}",
+ "password.needs_digit": "La contraseña necesita un número.",
+ "password.needs_lower": "La contraseña necesita una letra minúscula.",
+ "password.needs_special": "La contraseña necesita un carácter especial.",
+ "password.needs_upper": "La contraseña necesita una letra mayúscula.",
+ "password.policy": "Mínimo de 6 caracteres, con mayúscula, minúscula, número y carácter especial.",
+ "password.too_short": "La contraseña necesita al menos 6 caracteres.",
+ "reason.api_key": "Clave estática: no expira, nada que renovar",
+ "reason.expired": "Token expirado: se intentará renovar en el próximo barrido",
+ "reason.inside_margin": "Dentro del margen de {margin} min: se renovará en el próximo barrido",
+ "reason.local_empty": "La instancia local respondió, pero aún no tiene ningún modelo instalado",
+ "reason.local_ok": "La instancia local respondió con {count} modelo(s)",
+ "reason.local_unreachable": "La instancia local no respondió al catálogo de modelos",
+ "reason.no_expiry": "Sin expiración registrada: se renovará en el próximo barrido",
+ "reason.outside_margin": "Fuera del margen de {margin} min: renovación prevista en ~{eta}",
+ "security.body": "todavía no se ha definido una contraseña para este panel, por lo que el acceso aún depende de la credencial de recuperación. Defina la suya ahora, o configure DASHBOARD_USER/DASHBOARD_PASSWORD en el entorno.",
+ "security.cross_origin": "Solicitud rechazada: no provino de este panel. Recargue la página e inténtelo de nuevo.",
+ "security.title": "Aviso de seguridad:",
+ "sso.allowed_domains": "Dominios autorizados",
+ "sso.allowed_emails": "Correos autorizados",
+ "sso.allowlist_help": "Separados por comas, y al menos uno de los dos. Una lista vacía significa que entra cualquier cuenta del proveedor, por eso el panel se niega a encender el SSO sin ella.",
+ "sso.allowlist_hint": "Separados por coma, y la lista no puede quedar vacía: sin ella entraría toda cuenta del proveedor.",
+ "sso.allowlist_required": "Complete al menos un dominio o correo autorizado. SSO sin lista de autorizados deja entrar a cualquier cuenta del proveedor.",
+ "sso.at_least_one_auth": "Es necesario mantener al menos un método de autenticación activo (Contraseña, OIDC o SAML2).",
+ "sso.base_url": "Dirección pública del panel",
+ "sso.base_url_help": "Origen público exacto, sin barra final. La dirección de retorno nace de este valor, y nunca de las cabeceras de la petición.",
+ "sso.base_url_hint": "El origen exacto que usa el navegador, sin barra al final. Tiene que ser FIJO: un túnel rápido cambia de dirección en cada arranque y toda dirección de retorno registrada en el proveedor deja de coincidir, así que el acceso federado exige túnel con nombre o Tailscale.",
+ "sso.callback_help": "Copie exactamente esta línea en el proveedor. Un carácter de diferencia y rechaza el intercambio.",
+ "sso.callback_url": "Dirección de retorno a registrar en el proveedor",
+ "sso.client_id": "Identificador del cliente",
+ "sso.client_secret": "Secreto del cliente",
+ "sso.confirm_hint": "Guardar pide la contraseña local otra vez: quien robe una sesión no puede apuntar el panel a un proveedor hostil y ponerse en la lista.",
+ "sso.current_password": "Contraseña actual del panel",
+ "sso.current_password_help": "Exigida además de la sesión: una cookie robada no puede bastar para apuntar el panel a un proveedor hostil.",
+ "sso.disabled_by_env": "El inicio de sesión federado está apagado por la variable de entorno SSO_DISABLED. La configuración de abajo se conserva, pero ninguna ruta de SSO responde.",
+ "sso.enable_oidc": "Habilitar OpenID Connect (OIDC)",
+ "sso.enable_password": "Inicio local (Usuario y contraseña)",
+ "sso.enable_password_help": "Habilita el inicio con usuario y contraseña locales. Desactívelo solo si tiene al menos un proveedor SSO configurado y activo.",
+ "sso.enable_saml": "Habilitar SAML 2.0",
+ "sso.enabled_help": "Un proveedor a la vez. Dos activos al mismo tiempo es lo que hace que la respuesta de uno se acepte como si fuera la del otro.",
+ "sso.enabled_off": "Desactivado (solo contraseña)",
+ "sso.enabled_oidc": "OIDC",
+ "sso.entering": "Entrando",
+ "sso.entering_body": "Acceso federado aceptado. Abriendo el panel.",
+ "sso.failed": "No se pudo entrar por el proveedor de identidad. Inténtelo de nuevo o use usuario y contraseña.",
+ "sso.idp_cert": "Certificado X.509 del proveedor de identidad",
+ "sso.idp_cert_help": "Certificado público, que puede quedar junto al resto de la configuración.",
+ "sso.idp_entity_id": "Identificador del proveedor de identidad",
+ "sso.idp_sso_url": "Dirección de entrada del proveedor de identidad",
+ "sso.incomplete": "Complete la dirección pública, el issuer y el ID de cliente antes de encender el SSO.",
+ "sso.intro": "Opcional. El formulario de usuario y contraseña nunca sale de la pantalla, así que una caída del proveedor no deja a nadie fuera.",
+ "sso.issuer": "Emisor (issuer)",
+ "sso.issuer_help": "El emisor publicado en el documento de descubrimiento. Debe coincidir carácter por carácter.",
+ "sso.landing_body": "La sesión fue creada. Llevándolo al panel.",
+ "sso.landing_title": "Entrando...",
+ "sso.local_password": "Su contraseña actual del panel",
+ "sso.local_password_help": "Se exige además de la sesión: quien secuestre una sesión de ocho horas podría, si no, apuntar el panel a un proveedor hostil y agregarse a la lista de autorizados.",
+ "sso.login_button": "Entrar con {provider}",
+ "sso.logout_note": "Salir borra solo la sesión del panel. La sesión en el proveedor de identidad sigue abierta, así que el siguiente clic en el botón de SSO puede no pedir contraseña otra vez.",
+ "sso.metadata_hint": "Después de guardar, descargue la descripción del servicio en {url} ya autenticado y entréguela al proveedor de identidad.",
+ "sso.need_allowlist": "Complete al menos un dominio o correo autorizado.",
+ "sso.need_base_url": "La dirección pública del panel debe ser esquema y host, sin ruta, y https fuera del loopback.",
+ "sso.need_client_id": "El identificador del cliente es obligatorio.",
+ "sso.need_issuer": "El emisor es obligatorio y debe usar https fuera del loopback.",
+ "sso.need_secret": "El secreto del cliente es obligatorio.",
+ "sso.no_secret": "Sin secreto de cliente: defina uno aquí o en la variable de entorno OIDC_CLIENT_SECRET.",
+ "sso.or": "o",
+ "sso.provider": "Proveedor activo",
+ "sso.provider_none": "Apagado (solo contraseña)",
+ "sso.provider_oidc": "OpenID Connect",
+ "sso.provider_saml": "SAML 2.0 (no disponible)",
+ "sso.redirect_uri": "Dirección de retorno que se registra en el proveedor",
+ "sso.saml_acs_help": "URL de retorno donde el proveedor de identidad entrega las aserciones SAML 2.0.",
+ "sso.saml_acs_url": "URL de ACS (Assertion Consumer Service)",
+ "sso.saml_active_help": "SAML 2.0 está activo y listo. Configure las URL siguientes en su proveedor de identidad (IdP).",
+ "sso.saml_metadata_help": "Identificador del SP y dirección de metadatos XML para registrar en su IdP.",
+ "sso.saml_metadata_url": "URL de Metadatos del SP (Entity ID)",
+ "sso.saml_pending": "La biblioteca de SAML2 está instalada, pero esta versión del panel solo entra por OIDC. La entrada por SAML2 es la próxima fase.",
+ "sso.saml_refused": "SAML 2.0 no se puede encender en esta imagen.",
+ "sso.saml_unavailable": "SAML 2.0 no está disponible en esta imagen. Requiere una biblioteca que firme y verifique XML, y hacerlo a mano aceptaría aserciones falsificadas en silencio. Los campos quedan aquí para que la configuración esté lista cuando la imagen traiga la biblioteca.",
+ "sso.save": "Guardar configuración de SSO",
+ "sso.save_failed": "No se pudo guardar la configuración.",
+ "sso.save_refused_allowlist": "Indique al menos un dominio o dirección: una lista vacía dejaría entrar a toda cuenta del proveedor.",
+ "sso.save_refused_fields": "Complete todos los campos del proveedor elegido, incluida la dirección pública de este panel.",
+ "sso.save_refused_password": "Contraseña del panel incorrecta: no se cambió nada.",
+ "sso.save_refused_saml": "SAML 2.0 no está disponible en esta imagen.",
+ "sso.save_refused_secret": "No se pudo guardar el secreto del cliente en disco, así que el acceso federado no se activó.",
+ "sso.saved": "Configuración de inicio de sesión federado guardada.",
+ "sso.scopes": "Ámbitos",
+ "sso.scopes_help": "Separados por espacios. El valor por omisión cubre el correo y el perfil.",
+ "sso.secret_absent": "Todavía no hay secreto guardado.",
+ "sso.secret_failed": "No se pudo guardar el secreto del cliente: el almacenamiento del panel no acepta escritura.",
+ "sso.secret_from_env": "El secreto viene de la variable de entorno OIDC_CLIENT_SECRET. Cámbielo allí y reinicie el servicio.",
+ "sso.secret_keep_help": "Nunca se vuelve a mostrar. Deje este campo vacío para conservar el actual.",
+ "sso.secret_missing": "Todavía no hay secreto guardado. Sin él, el SSO permanece apagado.",
+ "sso.secret_stored": "Hay un secreto guardado. Deje el campo en blanco para conservarlo.",
+ "sso.sign_in_with": "Entrar con {provider}",
+ "sso.signing_in": "Entrando",
+ "sso.signing_in_body": "El proveedor de identidad confirmó quién es usted. Llevándolo al panel.",
+ "sso.status_off": "Apagado",
+ "sso.status_on": "Activo · {provider}",
+ "sso.tab_oidc": "OpenID Connect",
+ "sso.tab_saml": "SAML 2.0",
+ "sso.test_failed": "Fallo en la prueba de conexión",
+ "sso.test_oidc": "Probar conexión OIDC",
+ "sso.test_saml": "Probar conexión SAML2",
+ "sso.test_success": "¡Conexión probada con éxito!",
+ "sso.testing": "Probando conexión...",
+ "sso.title": "Inicio de sesión federado (SSO)",
+ "sso.tunnel_warning": "El túnel rápido cambia de dirección en cada arranque, y toda dirección de retorno registrada en el proveedor deja de coincidir. El SSO exige un túnel con nombre o Tailscale, con dirección fija.",
+ "sso.turned_off": "Acceso federado apagado. El formulario local sigue funcionando.",
+ "sso.unavailable": "El proveedor de identidad no respondió. Entre con usuario y contraseña.",
+ "sso.wrong_password": "Contraseña del panel incorrecta: no se cambió nada.",
+ "table.alias": "Alias",
+ "table.api_base": "Base de la API",
+ "table.cascade": "Cascada de modelos",
+ "table.combo": "Combo",
+ "table.connection": "Conexión",
+ "table.context_limit": "Límite de entrada",
+ "table.description": "Descripción",
+ "table.details": "Detalles",
+ "table.diagnosis": "Diagnóstico de la renovación",
+ "table.endpoints": "Endpoints",
+ "table.expires_at": "Expira el",
+ "table.issued_at": "Emitida el",
+ "table.key_state": "Estado de la clave",
+ "table.last_refresh": "Última renovación",
+ "table.last_used": "Último uso",
+ "table.max_budget": "Tope de presupuesto",
+ "table.model_access": "Acceso a modelos",
+ "table.models": "modelos",
+ "table.name": "Nombre",
+ "table.never_refreshed": "Nunca renovada",
+ "table.not_declared": "No declarado",
+ "table.output_limit": "Límite de salida",
+ "table.provider": "Proveedor",
+ "table.remaining": "Validez restante",
+ "table.rpm_limit": "Límite RPM",
+ "table.scopes": "Ámbitos",
+ "table.source": "Origen",
+ "table.spend": "Gasto",
+ "table.status": "Estado",
+ "table.team": "Equipo",
+ "table.time_ago": "hace {elapsed}",
+ "table.tpm_limit": "Límite TPM",
+ "table.type": "Tipo",
+ "type.api_key": "Clave de API",
+ "type.local": "Local",
+ "type.oauth": "OAuth 2.0",
+ "type.synced_model": "Modelo sincronizado",
+ "type.virtual_key": "Clave virtual",
+ "auth.authentication": "Autenticación"
+}
diff --git a/src/nine_rtksync/locales/pt.json b/src/nine_rtksync/locales/pt.json
new file mode 100644
index 0000000..ae899b1
--- /dev/null
+++ b/src/nine_rtksync/locales/pt.json
@@ -0,0 +1,296 @@
+{
+ "action.access": "Acesso",
+ "action.change_credentials": "Alterar credenciais",
+ "action.close": "Fechar",
+ "action.cron_ran": "Ciclo executado em {duration}ms: {inspected} inspecionados, {findings} achados.",
+ "action.refresh": "Atualizar",
+ "action.refresh_title": "Recarregar os dados do servidor",
+ "action.refreshed": "Página recarregada com dados atualizados.",
+ "action.run_now": "Executar agora",
+ "action.save_credentials": "Salvar credenciais",
+ "action.settings": "Configurações",
+ "action.sync_now": "Sincronizar agora",
+ "action.test_connection": "Testar conexão",
+ "action.validate": "Validar credenciais",
+ "app.gateway_unset": "gateway não configurado",
+ "app.subtitle": "{NOME_DO_GATEWAY} Universal Token & Connection Synchronizer",
+ "auth.challenge": "Verificação de segurança",
+ "auth.challenge_failed": "Falha no desafio de segurança. Por favor, selecione o item correto.",
+ "auth.challenge_prompt": "Selecione o(a) {item} para confirmar que é humano:",
+ "auth.enter": "Entrar",
+ "auth.env_managed": "As credenciais deste painel são gerenciadas fora dele. Altere-as onde o serviço é configurado e reinicie-o.",
+ "auth.item_bell": "Sino",
+ "auth.item_gear": "Engrenagem",
+ "auth.item_heart": "Coração",
+ "auth.item_key": "Chave",
+ "auth.item_lightning": "Raio",
+ "auth.item_lock": "Cadeado",
+ "auth.item_shield": "Escudo",
+ "auth.item_star": "Estrela",
+ "auth.logged_out": "Sessão encerrada com sucesso.",
+ "auth.login_failed": "Usuário ou senha incorretos.",
+ "auth.login_intro": "Entre para ver o painel.",
+ "auth.logout": "Sair",
+ "auth.min_chars": "Mínimo de 4 caracteres.",
+ "auth.new_password": "Nova senha",
+ "auth.password": "Senha",
+ "auth.password_disabled": "Login local por senha está desativado. Use um provedor SSO ativo.",
+ "auth.required": "Autenticação requerida.",
+ "auth.required_body": "Este painel é privado. Autentique-se para continuar.",
+ "auth.save_failed": "Não foi possível gravar a nova senha.",
+ "auth.select_challenge": "Por favor, selecione o item de segurança solicitado.",
+ "auth.title": "Credenciais do painel",
+ "auth.too_many": "Tentativas demais",
+ "auth.too_many_body": "Aguarde {seconds}s antes de tentar de novo.",
+ "auth.updated_body": "A nova senha já está em vigor. O navegador ainda guarda a anterior, então autentique-se de novo para continuar.",
+ "auth.updated_link": "Voltar ao painel",
+ "auth.updated_title": "Credenciais atualizadas",
+ "auth.user": "Usuário",
+ "combos.empty": "Nenhum combo de fallback registrado.",
+ "combos.empty_hint": "No {NOME_DO_GATEWAY} o combo é o fallback do roteador (router_settings.fallbacks); nenhum está declarado.",
+ "combos.kind_content_policy": "política de conteúdo",
+ "combos.kind_context_window": "janela de contexto",
+ "combos.title": "Combos de resiliência",
+ "connections.empty": "Nenhuma conexão registrada no gateway.",
+ "connections.empty_hint": "No {NOME_DO_GATEWAY} a conexão é o destino por trás dos modelos cadastrados; nenhum deles declara um destino ainda.",
+ "connections.lifecycle_note": "O destino não tem validade própria: o que expira é a credencial no provedor, fora deste gateway.",
+ "connections.served_models": "Modelos servidos",
+ "connections.title": "Conexões monitoradas",
+ "credential.absent": "Não exposta pelo gateway",
+ "credential.checked_at": "Verificada em",
+ "credential.env": "Mantida no ambiente do proxy",
+ "credential.inline": "Declarada no modelo",
+ "credential.named": "Credencial nomeada",
+ "credential.never": "Nunca validada",
+ "credential.title": "Credencial",
+ "cron.active": "Ativo · a cada {interval}s",
+ "cron.disabled": "Desativado (CRON_ENABLED=0)",
+ "cron.failed": "O ciclo não pôde ser concluído: {error}",
+ "cron.last_result": "Último resultado",
+ "cron.last_run": "Última execução",
+ "cron.next_run": "Próxima execução",
+ "cron.no_runs": "Nenhum ciclo executado ainda",
+ "cron.result_line": "{inspected} · ({duration}ms)",
+ "cron.run_now": "Executar agora",
+ "cron.title": "Agendador",
+ "cron.total_findings": "Achados até agora",
+ "cron.total_renewals": "Tokens renovados",
+ "cron.total_runs": "Ciclos totais",
+ "cron.unavailable": "O agendador não está disponível nesta instância.",
+ "duration.expired": "Expirado",
+ "duration.no_expiry": "Sem expiração (chave estática)",
+ "duration.no_expiry_short": "Sem expiração",
+ "duration.unknown_expiry": "Validade desconhecida",
+ "duration.unlimited": "Ilimitado / N/A",
+ "egress.bound": "saída própria",
+ "egress.shared": "divide o endereço do gateway com {count} contas",
+ "egress.single": "endereço do gateway (única conta)",
+ "egress.title": "Saída de rede",
+ "egress.unknown": "saída desconhecida",
+ "footer.generated": "Dados gerados no servidor em",
+ "footer.signed_in": "Autenticado como",
+ "gateway.database": "Banco SQLite",
+ "gateway.db_missing": "Banco não encontrado",
+ "gateway.db_summary": "Operacional ({connections} conexões, {combos} combos)",
+ "gateway.diag_db_failed": "Gateway online, banco ilegível",
+ "gateway.diag_gateway_failed": "Gateway inacessível",
+ "gateway.diag_ok": "Gateway e banco SQLite totalmente operacionais",
+ "gateway.diagnostics": "Diagnóstico",
+ "gateway.gateway": "Gateway",
+ "gateway.latency": "Latência",
+ "gateway.no_response": "sem resposta",
+ "gateway.offline": "OFFLINE",
+ "gateway.status": "Status",
+ "gateway.title": "Conexão com o gateway",
+ "health.active": "Ativo",
+ "health.blocked": "Bloqueada",
+ "health.expired": "Expirado",
+ "health.expiring_soon": "Expirando",
+ "health.invalid": "Recusada",
+ "health.no_expiration": "Sem expiração",
+ "health.not_checked": "Não verificada",
+ "health.over_budget": "Orçamento esgotado",
+ "health.rate_limited": "Rate limit",
+ "health.unknown": "Desconhecido",
+ "health.unreachable": "Inacessível",
+ "health.valid": "Aceita",
+ "keys.access_all": "Todos os modelos",
+ "keys.access_restricted": "Restrita aos modelos listados",
+ "keys.banned": "Banida",
+ "keys.disabled": "Desativada",
+ "keys.empty": "Nenhuma chave virtual emitida pelo gateway.",
+ "keys.enabled": "Aceita",
+ "keys.revoked": "Revogada",
+ "keys.title": "Chaves virtuais",
+ "language.label": "Idioma",
+ "language.save_failed": "Nao foi possivel gravar o idioma: o armazenamento do painel nao aceita escrita.",
+ "limits.ok": "Todo limite respeita o nível acima.",
+ "limits.title": "Coerência dos limites",
+ "metric.api_keys": "Chaves de API",
+ "metric.combos": "Combos registrados",
+ "metric.expiring": "Vencendo / vencidas",
+ "metric.limit_findings": "Incoerências de limite",
+ "metric.oauth_accounts": "Contas OAuth",
+ "metric.teams": "Times",
+ "metric.total_connections": "Total de conexões",
+ "metric.virtual_keys": "Chaves virtuais",
+ "models.empty": "O gateway respondeu com o catálogo de modelos vazio.",
+ "models.inherited": "Status, validade e última renovação vêm da conexão que serve este modelo.",
+ "models.no_key": "O gateway não tem nenhuma chave ativa emitida, e ele só entrega o catálogo de modelos a uma chave que ele mesmo emitiu.",
+ "models.showing": "Mostrando {shown} de {total} modelos — a lista completa está no gateway.",
+ "models.title": "Modelos cadastrados",
+ "models.unreachable": "O gateway não respondeu ao catálogo de modelos.",
+ "pagination.label": "Paginação",
+ "pagination.range": "{inicio}-{fim} de {total}",
+ "password.needs_digit": "A senha precisa conter um número.",
+ "password.needs_lower": "A senha precisa conter uma letra minúscula.",
+ "password.needs_special": "A senha precisa conter um caractere especial.",
+ "password.needs_upper": "A senha precisa conter uma letra maiúscula.",
+ "password.policy": "Mínimo de 6 caracteres, com maiúscula, minúscula, número e caractere especial.",
+ "password.too_short": "A senha precisa ter ao menos 6 caracteres.",
+ "reason.api_key": "Chave estática: não expira, nada a renovar",
+ "reason.expired": "Token expirado: renovação será tentada na próxima varredura",
+ "reason.inside_margin": "Dentro da margem de {margin} min: será renovada na próxima varredura",
+ "reason.local_empty": "Instância local respondeu, mas ainda não tem nenhum modelo instalado",
+ "reason.local_ok": "Instância local respondeu com {count} modelo(s)",
+ "reason.local_unreachable": "Instância local não respondeu ao catálogo de modelos",
+ "reason.no_expiry": "Sem expiração registrada: será renovada na próxima varredura",
+ "reason.outside_margin": "Fora da margem de {margin} min: renovação prevista em ~{eta}",
+ "security.body": "nenhuma senha foi definida para este painel ainda, então o acesso ainda depende da credencial de recuperação. Defina a sua agora, ou configure DASHBOARD_USER/DASHBOARD_PASSWORD no ambiente.",
+ "security.cross_origin": "Requisição recusada: ela não veio deste painel. Recarregue a página e tente de novo.",
+ "security.title": "Atenção de segurança:",
+ "sso.allowed_domains": "Domínios autorizados",
+ "sso.allowed_emails": "E-mails autorizados",
+ "sso.allowlist_help": "Separados por vírgula, e ao menos um dos dois. Lista vazia significa que toda conta do provedor entra, então o painel recusa ligar o SSO sem ela.",
+ "sso.allowlist_hint": "Separados por vírgula, e a lista não pode ficar vazia: sem ela toda conta do provedor entraria.",
+ "sso.allowlist_required": "Preencha ao menos um domínio ou e-mail autorizado. SSO sem lista de autorizados deixa entrar toda conta do provedor.",
+ "sso.at_least_one_auth": "É necessário manter pelo menos uma forma de autenticação ativa (Senha, OIDC ou SAML2).",
+ "sso.base_url": "Endereço público do painel",
+ "sso.base_url_help": "Origem pública exata, sem barra no fim. O endereço de retorno nasce deste valor, e nunca dos cabeçalhos da requisição.",
+ "sso.base_url_hint": "A origem exata que o navegador usa, sem barra no fim. Tem de ser FIXA: um túnel rápido troca de endereço a cada subida e todo endereço de retorno registrado no provedor deixa de bater, então o acesso federado exige túnel nomeado ou Tailscale.",
+ "sso.callback_help": "Copie exatamente esta linha para o provedor. Um caractere de diferença e ele recusa a troca.",
+ "sso.callback_url": "Endereço de retorno a cadastrar no provedor",
+ "sso.client_id": "Identificador do cliente",
+ "sso.client_secret": "Segredo do cliente",
+ "sso.confirm_hint": "Salvar pede a senha local de novo: quem roubar uma sessão não pode apontar o painel para um provedor hostil e se colocar na lista.",
+ "sso.current_password": "Senha atual do painel",
+ "sso.current_password_help": "Exigida além da sessão: um cookie roubado não pode bastar para apontar o painel a um provedor hostil.",
+ "sso.disabled_by_env": "A entrada federada está desligada pela variável de ambiente SSO_DISABLED. A configuração abaixo é mantida, mas nenhuma rota de SSO responde.",
+ "sso.enable_oidc": "Habilitar OpenID Connect (OIDC)",
+ "sso.enable_password": "Login local (Usuário e senha)",
+ "sso.enable_password_help": "Habilita o login com usuário e senha locais do painel. Desative apenas se tiver pelo menos um provedor SSO configurado e ativo.",
+ "sso.enable_saml": "Habilitar SAML 2.0",
+ "sso.enabled_help": "Um provedor por vez. Dois ligados ao mesmo tempo é o que faz a resposta de um ser aceita como se fosse a do outro.",
+ "sso.enabled_off": "Desligado (somente senha)",
+ "sso.enabled_oidc": "OIDC",
+ "sso.entering": "Entrando",
+ "sso.entering_body": "Acesso federado aceito. Abrindo o painel.",
+ "sso.failed": "Não foi possível entrar pelo provedor de identidade. Tente de novo ou use usuário e senha.",
+ "sso.idp_cert": "Certificado X.509 do provedor de identidade",
+ "sso.idp_cert_help": "Certificado público, que pode ficar ao lado do resto da configuração.",
+ "sso.idp_entity_id": "Identificador do provedor de identidade",
+ "sso.idp_sso_url": "Endereço de entrada do provedor de identidade",
+ "sso.incomplete": "Preencha o endereço público, o issuer e o ID do cliente antes de ligar o SSO.",
+ "sso.intro": "Opcional. O formulário de usuário e senha nunca sai da tela, então o provedor cair não tranca ninguém do lado de fora.",
+ "sso.issuer": "Emissor (issuer)",
+ "sso.issuer_help": "O emissor publicado no documento de descoberta. Ele tem de bater caractere a caractere.",
+ "sso.landing_body": "A sessão foi criada. Levando você ao painel.",
+ "sso.landing_title": "Entrando...",
+ "sso.local_password": "Sua senha atual do painel",
+ "sso.local_password_help": "Exigida aqui além da sessão: quem sequestrar uma sessão de oito horas poderia, sem isso, apontar o painel para um provedor hostil e se colocar na lista de autorizados.",
+ "sso.login_button": "Entrar com {provider}",
+ "sso.logout_note": "Sair apaga apenas a sessão do painel. A sessão no provedor de identidade continua aberta, então o clique seguinte no botão de SSO pode não pedir senha de novo.",
+ "sso.metadata_hint": "Depois de salvar, baixe a descrição do serviço em {url} já autenticado e entregue-a ao provedor de identidade.",
+ "sso.need_allowlist": "Preencha ao menos um domínio ou e-mail autorizado.",
+ "sso.need_base_url": "O endereço público do painel precisa ser esquema e host, sem caminho, e https fora do loopback.",
+ "sso.need_client_id": "O identificador do cliente é obrigatório.",
+ "sso.need_issuer": "O emissor é obrigatório e precisa usar https fora do loopback.",
+ "sso.need_secret": "O segredo do cliente é obrigatório.",
+ "sso.no_secret": "Sem segredo do cliente: defina um aqui ou na variável de ambiente OIDC_CLIENT_SECRET.",
+ "sso.or": "ou",
+ "sso.provider": "Provedor ativo",
+ "sso.provider_none": "Desligado (só senha)",
+ "sso.provider_oidc": "OpenID Connect",
+ "sso.provider_saml": "SAML 2.0 (indisponível)",
+ "sso.redirect_uri": "Endereço de retorno a registrar no provedor",
+ "sso.saml_acs_help": "URL de retorno onde o provedor de identidade entrega as asserções SAML 2.0.",
+ "sso.saml_acs_url": "URL do ACS (Assertion Consumer Service)",
+ "sso.saml_active_help": "SAML 2.0 está ativo e pronto. Configure as URLs abaixo no seu provedor de identidade (IdP).",
+ "sso.saml_metadata_help": "Identificador do SP e endereço de metadados XML para cadastrar no seu IdP.",
+ "sso.saml_metadata_url": "URL de Metadados do SP (Entity ID)",
+ "sso.saml_pending": "A biblioteca de SAML2 está instalada, mas esta versão do painel só entra por OIDC. A entrada por SAML2 é a próxima fase.",
+ "sso.saml_refused": "SAML 2.0 não pode ser ligado nesta imagem.",
+ "sso.saml_unavailable": "SAML 2.0 não está disponível nesta imagem. Ele exige uma biblioteca que assina e confere XML, e fazer isso à mão aceitaria asserção forjada em silêncio. Os campos ficam aqui para que a configuração já esteja pronta quando a imagem trouxer a biblioteca.",
+ "sso.save": "Salvar configuração de SSO",
+ "sso.save_failed": "Não foi possível salvar a configuração.",
+ "sso.save_refused_allowlist": "Informe ao menos um domínio ou endereço: uma lista vazia deixaria entrar toda conta do provedor.",
+ "sso.save_refused_fields": "Preencha todos os campos do provedor escolhido, inclusive o endereço público deste painel.",
+ "sso.save_refused_password": "Senha do painel incorreta: nada foi alterado.",
+ "sso.save_refused_saml": "SAML 2.0 não está disponível nesta imagem.",
+ "sso.save_refused_secret": "Não foi possível gravar o segredo do cliente em disco, então o acesso federado não foi ligado.",
+ "sso.saved": "Configuração de entrada federada salva.",
+ "sso.scopes": "Escopos",
+ "sso.scopes_help": "Separados por espaço. O padrão cobre o endereço de e-mail e o perfil.",
+ "sso.secret_absent": "Ainda não há segredo guardado.",
+ "sso.secret_failed": "Não foi possível gravar o segredo do cliente: o armazenamento do painel não aceita escrita.",
+ "sso.secret_from_env": "O segredo vem da variável de ambiente OIDC_CLIENT_SECRET. Altere-o lá e reinicie o serviço.",
+ "sso.secret_keep_help": "Ele nunca é exibido de volta. Deixe este campo em branco para manter o atual.",
+ "sso.secret_missing": "Nenhum segredo guardado ainda. Sem ele, o SSO fica desligado.",
+ "sso.secret_stored": "Há um segredo guardado. Deixe o campo em branco para mantê-lo.",
+ "sso.sign_in_with": "Entrar com {provider}",
+ "sso.signing_in": "Entrando",
+ "sso.signing_in_body": "O provedor de identidade confirmou quem você é. Levando você ao painel.",
+ "sso.status_off": "Desligado",
+ "sso.status_on": "Ligado · {provider}",
+ "sso.tab_oidc": "OpenID Connect",
+ "sso.tab_saml": "SAML 2.0",
+ "sso.test_failed": "Falha no teste de conexão",
+ "sso.test_oidc": "Testar conexão OIDC",
+ "sso.test_saml": "Testar conexão SAML2",
+ "sso.test_success": "Conexão testada com sucesso!",
+ "sso.testing": "Testando conexão...",
+ "sso.title": "Entrada federada (SSO)",
+ "sso.tunnel_warning": "O túnel rápido troca de endereço a cada subida, e todo endereço de retorno cadastrado no provedor deixa de bater. O SSO exige túnel nomeado ou Tailscale, com endereço fixo.",
+ "sso.turned_off": "Acesso federado desligado. O formulário local continua funcionando.",
+ "sso.unavailable": "O provedor de identidade não respondeu. Entre com usuário e senha.",
+ "sso.wrong_password": "Senha do painel incorreta: nada foi alterado.",
+ "table.alias": "Apelido",
+ "table.api_base": "Base da API",
+ "table.cascade": "Cascata de modelos",
+ "table.combo": "Combo",
+ "table.connection": "Conexão",
+ "table.context_limit": "Limite de entrada",
+ "table.description": "Descrição",
+ "table.details": "Detalhes",
+ "table.diagnosis": "Diagnóstico da renovação",
+ "table.endpoints": "Endpoints",
+ "table.expires_at": "Expira em",
+ "table.issued_at": "Emitida em",
+ "table.key_state": "Estado da chave",
+ "table.last_refresh": "Última renovação",
+ "table.last_used": "Último uso",
+ "table.max_budget": "Teto de orçamento",
+ "table.model_access": "Acesso a modelos",
+ "table.models": "modelos",
+ "table.name": "Nome",
+ "table.never_refreshed": "Nunca renovada",
+ "table.not_declared": "Não declarado",
+ "table.output_limit": "Limite de saída",
+ "table.provider": "Provedor",
+ "table.remaining": "Validade restante",
+ "table.rpm_limit": "Limite RPM",
+ "table.scopes": "Escopos",
+ "table.source": "Origem",
+ "table.spend": "Gasto",
+ "table.status": "Status",
+ "table.team": "Time",
+ "table.time_ago": "há {elapsed}",
+ "table.tpm_limit": "Limite TPM",
+ "table.type": "Tipo",
+ "type.api_key": "Chave de API",
+ "type.local": "Local",
+ "type.oauth": "OAuth 2.0",
+ "type.synced_model": "Modelo sincronizado",
+ "type.virtual_key": "Chave virtual",
+ "auth.authentication": "Autenticação"
+}
diff --git a/src/nine_rtksync/render.py b/src/nine_rtksync/render.py
index b8f7e6f..c78fb09 100644
--- a/src/nine_rtksync/render.py
+++ b/src/nine_rtksync/render.py
@@ -34,6 +34,7 @@
COR_DO_FAVICON,
GLIFO_DO_FAVICON,
ICONE_DO_PRODUTO,
+ NOME_DO_GATEWAY,
NOME_DO_PRODUTO,
PALETA,
PROVEDOR_DO_GATEWAY,
@@ -82,9 +83,10 @@
# isso fica aqui e não na identidade.
COR_DO_TEXTO = "#e6e8ee"
-# Papéis que as páginas servidas antes do login precisam: login e erro têm
-# cartão, borda e um botão, e mais nada.
-PAPEIS_ANTES_DO_LOGIN = ("--bg", "--surface", "--line", "--accent")
+PAPEIS_ANTES_DO_LOGIN = (
+ "--bg", "--surface", "--surface-2", "--line", "--accent", "--accent-2",
+ "--brand-a", "--brand-b", "--text-dim"
+)
def tokens_do_tema(recuo: str = " ") -> str:
@@ -189,7 +191,38 @@ def format_timestamp_curto(value: Optional[str]) -> str:
return momento.strftime("%d/%m %H:%M")
-def rodape_da_pathbit() -> str:
+def script_de_idioma() -> str:
+ """Script inline para sincronizar localStorage, sessionStorage e cookies."""
+ return """
+ """
+
+
+def rodape_da_pathbit(lang: str = DEFAULT_LANGUAGE, com_seletor: bool = False) -> str:
"""A assinatura da casa, igual nos três painéis e em TODA tela.
Fora do catálogo de tradução de propósito: é nome próprio e assinatura de
@@ -200,10 +233,38 @@ def rodape_da_pathbit() -> str:
O coração é `bi-heart-fill`, e não o emoji: o cabeçalho deste módulo fixa
"Bootstrap Icons, nunca emoji", e emoji muda de desenho conforme o sistema.
"""
+ ano = datetime.now().year
+ if com_seletor:
+ lang = normalize_language(lang)
+ botoes = []
+ for code, (label, flag) in LANGUAGES.items():
+ active = " active" if code == lang else ""
+ botoes.append(
+ f''
+ f'{esc(label)}'
+ )
+ grade = "".join(botoes)
+ return f"""
+ """
return f"""
-