mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-09 17:28:51 +00:00
fix(i18n): backfill 194 missing pt-BR keys and add key-parity regression test (#6695)
This commit is contained in:
parent
f4fb0d310b
commit
1e2304fd20
3 changed files with 249 additions and 9 deletions
|
|
@ -49,6 +49,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
|
|||
- **fix(api):** editing any existing OpenAI Codex provider connection in the dashboard returned "Invalid request" and the edit could never be saved ([#6562](https://github.com/diegosouzapw/OmniRoute/issues/6562)) — `createProviderConnection()` (`src/lib/db/providers.ts`) auto-increments a new connection's `priority` to `MAX(priority)+1` per provider with no upper bound, and OAuth-imported connections (Codex `codex-auth/import` / `import-bulk`, up to 50 accounts per call, callable repeatedly — the standard Codex bulk-account-rotation workflow) never pass through `createProviderSchema`'s Zod validation at all, so nothing ever capped that value; `EditConnectionModal`'s `handleSubmit` always resends the connection's current `priority` unchanged on every save, and `updateProviderConnectionSchema` capped `priority`/`globalPriority` at `max(100)` — a UI-only ceiling the create path never enforced — so the first edit of any connection whose priority had already grown past 100 (routine once a Codex account count exceeds 100) failed validation regardless of which field the user actually changed. Raised the ceiling to `max(100_000)` on both fields — still bounded (a genuinely out-of-range value is still rejected), just wide enough to accept priorities the app itself already produces. Regression guard: `tests/unit/codex-connection-edit-6562.test.ts` (a Codex OAuth connection whose priority already exceeds the old 100 cap now validates + persists on edit; a control payload with a still-genuinely-invalid priority is still rejected with "Invalid request").
|
||||
- **mimocode**: rotate accounts on MiMoCode's rate-limit-style 400s (body-classified) instead of failing on the first account; malformed 400s still fail fast with the real upstream error (#6648 — thanks @pizzav-xyz)
|
||||
- **fix(cli):** compression CLI REST fallback now reads/writes the canonical `defaultMode` field (surfaced as `strategy`) instead of a nonexistent `engine` key, and table output renders nested objects as JSON instead of `[object Object]` (#6571 — thanks @charleszolot)
|
||||
- **fix(i18n):** pt-BR was missing 194 UI keys present in `en.json` — a real, silent data-sync gap, not covered by any duplicate/mislabeled #6694 (that issue's 9 `providers.*` keys are disjoint, present-but-untranslated sentinels caused by a separate `providerText()` fallback bug) ([#6695](https://github.com/diegosouzapw/OmniRoute/issues/6695)) — `scripts/i18n/sync-ui-keys.mjs` (which mirrors newly-added `en.json` keys into every locale) wasn't re-run after recent `en.json` additions, and the CI `i18n:check-ui-coverage` gate only fails a locale below an 80% threshold, so pt-BR stayed green at 93.8% coverage despite the gap. Backfilled all 194 missing keys into `src/i18n/messages/pt-BR.json` (translated to Brazilian Portuguese, no leftover `__MISSING__` markers) via `npm run i18n:sync-ui -- --locale=pt-BR` + manual translation. Regression guard: `tests/unit/i18n-pt-br.test.ts` (new case asserting full `en.json`→`pt-BR.json` key parity, so a future drift fails a fast unit test instead of silently degrading the coverage percentage).
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"noData": "Nenhum dado disponível",
|
||||
"nothingHere": "Nothing here yet",
|
||||
"configure": "Configurar",
|
||||
"manualConfig": "Configuração manual",
|
||||
"manage": "Gerenciar",
|
||||
"name": "Nome",
|
||||
"actions": "Ações",
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
"model": "Modelo",
|
||||
"models": "modelos",
|
||||
"provider": "Provedor",
|
||||
"unknownProvider": "Provedor desconhecido",
|
||||
"account": "Conta",
|
||||
"time": "Tempo",
|
||||
"details": "Detalhes",
|
||||
|
|
@ -843,6 +845,7 @@
|
|||
"batchActionRetryError": "Falha ao refazer requisições com falha. Tente novamente.",
|
||||
"batchConceptRetentionNote": "Arquivos de resultado e erro ficam retidos por 30 dias (Anthropic: 29 dias)"
|
||||
},
|
||||
"disabled": "Desabilitado",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "__MISSING__:Route budget-exhausted requests to the emergency free fallback provider/model.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "__MISSING__:Enable periodic Arena AI leaderboard ELO sync for model intelligence rankings.",
|
||||
"sidebar": {
|
||||
|
|
@ -1115,7 +1118,9 @@
|
|||
"dragReorderItem": "__MISSING__:Drag to reorder",
|
||||
"cannotHide": "__MISSING__:This item cannot be hidden",
|
||||
"alwaysVisible": "__MISSING__:Always visible",
|
||||
"groupSeparatorLabel": "__MISSING__:Separator"
|
||||
"groupSeparatorLabel": "__MISSING__:Separator",
|
||||
"discovery": "Descoberta",
|
||||
"discoverySubtitle": "Buscar acesso gratuito em provedores"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooks",
|
||||
|
|
@ -1759,6 +1764,8 @@
|
|||
"copyMaskedKey": "Copiar chave mascarada",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# modelo} other {# modelos}}",
|
||||
"devicesCount": "{count, plural, one {# dispositivo} other {# dispositivos}}",
|
||||
"devicesTooltip": "{count, plural, one {# dispositivo distinto (IP/User-Agent) visto com esta chave (últimos 30 min)} other {# dispositivos distintos (IP/User-Agent) vistos com esta chave (últimos 30 min)}}",
|
||||
"lastUsedOn": "Último: {date}",
|
||||
"viewCostsFor": "__MISSING__:View costs for {name}",
|
||||
"editPermissions": "Editar permissões",
|
||||
|
|
@ -1771,6 +1778,8 @@
|
|||
"models": "{count} modelos",
|
||||
"permissionsTitle": "Permissões: {name}",
|
||||
"allowAllDesc": "Esta chave pode acessar todos os modelos disponíveis.",
|
||||
"restrictLoading": "Carregando catálogo de modelos…",
|
||||
"restrictCatalogUnavailable": "Catálogo de modelos indisponível; esta chave tem {selectedCount} restrições de modelo selecionadas.",
|
||||
"restrictDesc": "Esta chave pode acessar {selectedCount} de {totalModels} modelos.",
|
||||
"selectedCount": "{count} selecionados",
|
||||
"maxActiveSessions": "Máximo de sessões ativas",
|
||||
|
|
@ -1854,14 +1863,16 @@
|
|||
"webSearch": "Busca Web",
|
||||
"webFetch": "Web Fetch",
|
||||
"video": "Vídeo",
|
||||
"music": "Música"
|
||||
"music": "Música",
|
||||
"ocr": "OCR"
|
||||
},
|
||||
"noProviders": "Nenhum provedor configurado para este tipo.",
|
||||
"addConnection": "Adicionar Conexão",
|
||||
"backToProviders": "Voltar para Provedores",
|
||||
"connections": "{count} Conexões",
|
||||
"noConnections": "Nenhuma conexão ainda — adicione uma na página do provedor.",
|
||||
"loading": "Carregando..."
|
||||
"loading": "Carregando...",
|
||||
"suggestedModels": "Modelos sugeridos pelo provedor"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
|
|
@ -2389,6 +2400,14 @@
|
|||
"strictRandomDesc": "Baralho embaralhado — usa cada modelo uma vez antes de reembaralhar",
|
||||
"fusion": "__MISSING__:Fusion",
|
||||
"fusionDesc": "__MISSING__:Fans the prompt to every panel model in parallel, then a judge model synthesizes one final answer",
|
||||
"fusionJudgeModel": "Modelo julgador",
|
||||
"fusionJudgeModelHelp": "Modelo que sintetiza as respostas do painel em uma resposta final única. Deixe vazio para usar o primeiro modelo do painel.",
|
||||
"fusionMinPanel": "Painel mínimo",
|
||||
"fusionMinPanelHelp": "Respostas bem-sucedidas do painel necessárias antes que os retardatários recebam uma janela de tolerância (padrão 2).",
|
||||
"fusionStragglerGraceMs": "Tolerância para retardatários (ms)",
|
||||
"fusionStragglerGraceMsHelp": "Quanto tempo esperar por modelos lentos do painel após o quórum ser atingido (padrão 8000).",
|
||||
"fusionPanelHardTimeoutMs": "Timeout máximo do painel (ms)",
|
||||
"fusionPanelHardTimeoutMsHelp": "Limite absoluto para que um modelo travado não bloqueie o painel inteiro (padrão 90000).",
|
||||
"models": "Modelos",
|
||||
"autoBalance": "Auto-balancear",
|
||||
"advancedSettings": "Configurações Avançadas",
|
||||
|
|
@ -2498,7 +2517,8 @@
|
|||
"queueTimeout": "Tempo máximo em fila antes de expirar no round-robin.",
|
||||
"failoverBeforeRetry": "Quando ativado, qualquer erro upstream aciona o failover imediato para o próximo destino combinado, ignorando todas as novas tentativas e URLs substitutos.",
|
||||
"maxSetRetries": "Número de vezes para tentar novamente o destino completo definido quando todos os alvos falharem. 0 = nenhuma nova tentativa de nível definido.",
|
||||
"setRetryDelayMs": "Atraso entre novas tentativas de nível definido, dando tempo para que problemas transitórios sejam resolvidos."
|
||||
"setRetryDelayMs": "Atraso entre novas tentativas de nível definido, dando tempo para que problemas transitórios sejam resolvidos.",
|
||||
"disableSessionStickiness": "Alterna para uma conexão diferente a cada requisição, em vez de fixar toda a conversa em uma conexão pelo hash da primeira mensagem. Sobrescreve o padrão global. Deixe em Herdar para preservar acertos de cache de prompt em conversas com múltiplos turnos."
|
||||
},
|
||||
"templatesTitle": "Templates rápidos",
|
||||
"templatesDescription": "Aplique um perfil inicial e depois ajuste modelos e configuração.",
|
||||
|
|
@ -2792,6 +2812,13 @@
|
|||
"browseLegacyCatalog": "Navegue pelo catálogo combinado legado",
|
||||
"agentFeaturesTitle": "Recursos do agente",
|
||||
"agentFeaturesDescription": "Habilite recursos avançados para agentes usando esta combinação",
|
||||
"responseValidationTitle": "Validação de resposta",
|
||||
"responseValidationHelp": "Faz failover para o próximo alvo quando um corpo 200 OK falha nestas verificações (conteúdo do assistente).",
|
||||
"responseValidationForbidden": "Substrings proibidas (uma por linha)",
|
||||
"responseValidationRequired": "Substrings obrigatórias (uma por linha)",
|
||||
"responseValidationMinLength": "Tamanho mínimo do conteúdo (caracteres)",
|
||||
"responseValidationJsonPaths": "Verificações de JSON-path",
|
||||
"responseValidationAddCheck": "+ Adicionar verificação",
|
||||
"agentFeaturesSystemMessageOverride": "Substituir mensagem do sistema",
|
||||
"agentFeaturesSystemMessagePlaceholder": "Você é um assistente especialista...",
|
||||
"agentFeaturesSystemMessageHint": "Substituição de mensagens do sistema para agentes",
|
||||
|
|
@ -2805,7 +2832,10 @@
|
|||
"agentFeaturesContextLengthErrorInteger": "O comprimento do contexto precisa ser um número inteiro válido",
|
||||
"agentFeaturesContextLengthErrorRange": "O comprimento do contexto precisa estar entre 1.000 e 2.000.000",
|
||||
"compressionOverride": "Substituição de compactação",
|
||||
"modePack": "Pacote de modos"
|
||||
"modePack": "Pacote de modos",
|
||||
"disableSessionStickiness": "Desabilitar fixação de sessão",
|
||||
"sessionStickinessEnabled": "Fixação ativada",
|
||||
"sessionStickinessDisabled": "Fixação desativada"
|
||||
},
|
||||
"costs": {
|
||||
"title": "Custos",
|
||||
|
|
@ -3408,6 +3438,10 @@
|
|||
"description": "Integração opcional com Qdrant para busca semântica escalável",
|
||||
"enableLabel": "Habilitar Qdrant",
|
||||
"enableDesc": "Quando habilitado, Qdrant é usado como vector store principal",
|
||||
"banner": "Armazenamento vetorial Tier 2 — uma alternativa externa e escalável ao sqlite-vec embutido (Tier 1). Habilite apenas se você tiver um conjunto de memória muito grande ou quiser memória compartilhada entre instâncias; a maioria dos usuários fica bem com o sqlite-vec. Quando habilitado, ele se torna o armazenamento primário e volta automaticamente para o sqlite-vec se ficar inacessível.",
|
||||
"hostHelp": "Docker local: http://localhost:6333 · Qdrant Cloud: a URL do seu cluster",
|
||||
"collectionHelp": "Qualquer nome — o OmniRoute o cria no primeiro uso",
|
||||
"embeddingModelHelp": "Define a dimensão do vetor automaticamente no primeiro uso. Memórias existentes não são retroalimentadas, e trocar o modelo depois que já existem dados exige uma coleção nova.",
|
||||
"testConnection": "Testar conexão",
|
||||
"testing": "Testando...",
|
||||
"statusActive": "Ativo",
|
||||
|
|
@ -3484,6 +3518,7 @@
|
|||
"noExecutions": "Nenhuma execução encontrada",
|
||||
"enabled": "Ativado",
|
||||
"disabled": "Desativado",
|
||||
"delete": "Excluir",
|
||||
"version": "Versão",
|
||||
"tableDescription": "Descrição",
|
||||
"skill": "Skill",
|
||||
|
|
@ -4063,6 +4098,8 @@
|
|||
"prefixHint": "Obrigatório. Prefixo único para nomes de modelos.",
|
||||
"nameHint": "Obrigatório. Um rótulo amigável para este nó.",
|
||||
"baseUrlHint": "Obrigatório. URL base da API do provedor.",
|
||||
"iconUrlLabel": "URL do ícone",
|
||||
"iconUrlHint": "Opcional. URL da imagem exibida como ícone deste provedor.",
|
||||
"anthropicPrefixPlaceholder": "ac-prod",
|
||||
"openaiPrefixPlaceholder": "oc-prod",
|
||||
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
|
||||
|
|
@ -4174,6 +4211,8 @@
|
|||
"optional": "Opcional",
|
||||
"anthropicCompatibleName": "Compatível Anthropic",
|
||||
"openaiCompatibleName": "Compatível OpenAI",
|
||||
"compatibleDefaultModelLabel": "Modelo padrão",
|
||||
"compatibleDefaultModelHint": "Digite o ID do modelo exatamente como seu endpoint compatível espera. Este modelo será salvo como o padrão da conexão.",
|
||||
"failedImportModels": "Falha ao importar modelos",
|
||||
"noModelsReturnedFromEndpoint": "Nenhum modelo retornado do endpoint /models.",
|
||||
"importingModelsProgress": "Importando {current} de {total} modelos...",
|
||||
|
|
@ -4280,6 +4319,25 @@
|
|||
"targetFormatAuto": "__MISSING__:Default (auto)",
|
||||
"targetFormatGemini": "__MISSING__:Gemini",
|
||||
"targetFormatAntigravity": "__MISSING__:Antigravity",
|
||||
"compatParamFiltersLabel": "Filtros de parâmetros",
|
||||
"compatBlockedParamsHint": "Parâmetros bloqueados (removidos das requisições)",
|
||||
"compatAllowedParamsHint": "Parâmetros permitidos (readicionados após o bloqueio)",
|
||||
"paramFiltersSectionTitle": "Filtros de parâmetros",
|
||||
"paramFiltersSectionHint": "Remove ou readiciona parâmetros da requisição antes de enviá-la a este provedor. Usado para evitar erros 400 de provedores que rejeitam certos parâmetros (ex.: NVIDIA NIM rejeita <code>thinking</code>).",
|
||||
"paramFiltersBlockedLabel": "Parâmetros bloqueados",
|
||||
"paramFiltersBlockedHint": "Estes parâmetros são removidos das requisições de saída (lista de bloqueio).",
|
||||
"paramFiltersAllowedLabel": "Parâmetros permitidos",
|
||||
"paramFiltersAllowedHint": "Estes parâmetros são readicionados após a remoção pela lista de bloqueio (somente se o cliente os enviou).",
|
||||
"paramFiltersAutoLearnLabel": "Aprender automaticamente com erros 400",
|
||||
"paramFiltersAutoLearnHint": "Quando habilitado, se o upstream retornar 400 com \"Unsupported parameter: X\", o parâmetro é automaticamente adicionado à lista de bloqueio e a requisição é refeita.",
|
||||
"paramFiltersSaving": "Salvando…",
|
||||
"paramFiltersSaveChanges": "Salvar alterações",
|
||||
"paramFiltersResetToDefault": "Restaurar padrão",
|
||||
"paramFiltersLoadError": "Falha ao carregar configuração de filtro de parâmetros: {error}",
|
||||
"paramFiltersSaveSuccess": "Configuração de filtro de parâmetros salva",
|
||||
"paramFiltersSaveError": "Falha ao salvar configuração de filtro de parâmetros: {error}",
|
||||
"paramFiltersResetSuccess": "Configuração de filtro de parâmetros restaurada ao padrão",
|
||||
"paramFiltersResetError": "Falha ao restaurar configuração de filtro de parâmetros: {error}",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
|
|
@ -4476,6 +4534,8 @@
|
|||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"clientIdentityLabel": "Identidade do cliente",
|
||||
"clientIdentityHint": "Opcional. Adiciona cabeçalhos de fingerprint do cliente (ex.: User-Agent) correspondentes a um CLI conhecido, para gateways compatíveis que esperam um.",
|
||||
"statusDeactivated": "Desativado (Manual)",
|
||||
"statusBanned": "Banido / Sandbox Violation",
|
||||
"statusCreditsExhausted": "Saldo Insuficiente",
|
||||
|
|
@ -4598,6 +4658,11 @@
|
|||
"localProviderBaseUrlHint": "Obrigatório: cole a URL base OpenAI-compatible /v1 do seu {provider} (padrão: {baseUrl}). URLs locais/privadas exigem OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true para validação no dashboard.",
|
||||
"localProviders": "Local / Self-Hosted",
|
||||
"maxConcurrentWholeNumberError": "Max concurrent deve ser um número inteiro maior ou igual a 0.",
|
||||
"m365TierLabel": "Nível do Copilot",
|
||||
"m365TierHint": "Escolha qual superfície do Microsoft 365 Copilot esta conexão usa. Individual é o BizChat padrão para consumidores; Educação e Empresarial (trabalho) usam as superfícies do respectivo tenant.",
|
||||
"m365TierIndividualOption": "Individual (padrão)",
|
||||
"m365TierEduOption": "Educação",
|
||||
"m365TierEnterpriseOption": "Empresarial / Trabalho",
|
||||
"museSparkWebCookieHint": "Cole o cookie abra_sess do meta.ai. Um cabeçalho de cookie completo também funciona.",
|
||||
"museSparkWebCookiePlaceholder": "Cole o valor de abra_sess",
|
||||
"oauth": "Oauth",
|
||||
|
|
@ -4781,6 +4846,7 @@
|
|||
"bedrockModelsDescription": "Os modelos Amazon Bedrock têm escopo definido por região da AWS. Importe de /models ou adicione IDs de modelo Bedrock habilitados na região selecionada.",
|
||||
"bedrockModelPlaceholder": "antrópico.claude-sonnet-4-6",
|
||||
"addProviderSessionCookieTitle": "Adicionar cookie de sessão {provider}",
|
||||
"openWebProviderSite": "Abrir {host}",
|
||||
"addProviderWebTokenTitle": "Adicionar token da web {provider}",
|
||||
"addProviderConnectionTitle": "Adicionar conexão {provider}",
|
||||
"webTokenCredentialLabel": "Token de sessão da Web",
|
||||
|
|
@ -4814,7 +4880,10 @@
|
|||
"kimiWebLabel": "__MISSING__:Kimi Web",
|
||||
"kimiWebDesc": "__MISSING__:Moonshot AI consumer chat via www.kimi.com (international, Connect-RPC API)",
|
||||
"doubaoWebLabel": "__MISSING__:Doubao Web",
|
||||
"doubaoWebDesc": "__MISSING__:ByteDance AI chat via doubao.com"
|
||||
"doubaoWebDesc": "__MISSING__:ByteDance AI chat via doubao.com",
|
||||
"overrideBaseUrlAdvanced": "Avançado: sobrescrever URL base",
|
||||
"overrideBaseUrlHint": "Avançado: aponta este provedor embutido para um endpoint personalizado. Deixe em branco para usar o padrão.",
|
||||
"bulkAddFormatHintCloudflare": "Uma chave por linha. Formato: nome|accountId|apiKey (ID de conta Cloudflare + token de API)."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
|
|
@ -4824,6 +4893,10 @@
|
|||
"routing": "Roteamento",
|
||||
"cache": "Cache",
|
||||
"resilience": "Resiliência",
|
||||
"description": "Descrição",
|
||||
"enable": "Habilitar",
|
||||
"disable": "Desabilitar",
|
||||
"update": "Atualizar",
|
||||
"routingSettingsIntro": "Controla como suas solicitações são roteadas, transformadas e enviadas aos provedores de IA.",
|
||||
"routingOpDropParagraphContainsLabel": "Remover parágrafo (contém)",
|
||||
"routingOpDropParagraphStartsWithLabel": "Remover parágrafo (começa com)",
|
||||
|
|
@ -4959,6 +5032,8 @@
|
|||
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
|
||||
"systemTheme": "Tema do Sistema",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"logToolSourcesToggle": "Registrar fontes de ferramentas",
|
||||
"logToolSourcesDescription": "Emite uma linha de log de diagnóstico por requisição resumindo a contagem de ferramentas e a origem MCP/hospedada/cliente.",
|
||||
"homePinProviderQuotaToHome": "Fixar informações na página inicial",
|
||||
"homeProviderQuotaLimits": "Limites de cota dos provedores",
|
||||
"homeProviderQuotaLimitsDesc": "Fixe o painel de status de cotas dos provedores (com botão Atualizar tudo) no topo da página inicial.",
|
||||
|
|
@ -5352,6 +5427,9 @@
|
|||
"providerMaxRetriesAria": "{provider} tentativas máximas",
|
||||
"providerTimeoutAria": "timeout de {provider} em ms",
|
||||
"removeProviderOverrideAria": "Remover sobrescrita de {provider}",
|
||||
"selectProviderPlaceholder": "Selecionar provedor...",
|
||||
"searchProviderPlaceholder": "Buscar provedores...",
|
||||
"searchProviderAria": "Buscar provedores",
|
||||
"newProviderNamePlaceholder": "ex.: google, openai...",
|
||||
"newProviderNameAria": "Nome do novo provedor",
|
||||
"retries": "tentativas",
|
||||
|
|
@ -5360,6 +5438,7 @@
|
|||
"maxNestingDepth": "Profundidade Máx. de Aninhamento",
|
||||
"concurrencyPerModel": "Concorrência / Modelo",
|
||||
"queueTimeout": "Timeout da Fila (ms)",
|
||||
"queueDepth": "Profundidade da fila",
|
||||
"contextRelayHandoffThreshold": "Limite de Handoff",
|
||||
"contextRelayMaxMessages": "Máx. de Mensagens no Resumo",
|
||||
"contextRelaySummaryModel": "Modelo do Resumo",
|
||||
|
|
@ -5513,6 +5592,22 @@
|
|||
"maintenance": "Maintenance",
|
||||
"purgeExpiredLogs": "Purgar Logs Expirados",
|
||||
"purgeLogsFailed": "Falha ao purgar logs",
|
||||
"logsDeleted": "{count, plural, =0 {Nenhum log expirado removido} one {# log expirado removido} other {# logs expirados removidos}}",
|
||||
"resetUsageData": "Redefinir dados de uso",
|
||||
"resetUsageDataDesc": "Selecione quanto tempo no passado você deseja excluir dados de uso. Esta ação não pode ser desfeita.",
|
||||
"resetUsagePeriod_5m": "5 minutos",
|
||||
"resetUsagePeriod_1h": "1 hora",
|
||||
"resetUsagePeriod_3h": "3 horas",
|
||||
"resetUsagePeriod_6h": "6 horas",
|
||||
"resetUsagePeriod_12h": "12 horas",
|
||||
"resetUsagePeriod_1d": "1 dia",
|
||||
"resetUsagePeriod_7d": "7 dias",
|
||||
"resetUsagePeriod_30d": "30 dias",
|
||||
"resetUsagePeriod_all": "Todo o período",
|
||||
"resetUsageSuccess": "{count, plural, =0 {Nenhuma linha de dados de uso excluída} one {Dados de uso redefinidos (# linha excluída)} other {Dados de uso redefinidos (# linhas excluídas)}}",
|
||||
"resetUsageFailed": "Falha ao redefinir dados de uso",
|
||||
"reset": "Redefinir",
|
||||
"resetting": "Redefinindo...",
|
||||
"contextOpt": "Context Optimized",
|
||||
"contextOptDesc": "Routes based on context window requirements and conversation length",
|
||||
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
|
||||
|
|
@ -5613,8 +5708,12 @@
|
|||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
"compressionPreserveSystem": "Preserve System Prompt",
|
||||
"compressionPreserveSystemAlways": "Sempre",
|
||||
"compressionPreserveSystemWhenNoCache": "Quando não há cache",
|
||||
"compressionPreserveSystemNever": "Nunca",
|
||||
"compressionCavemanConfig": "Caveman Engine Configuration",
|
||||
"compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine",
|
||||
"compressionCavemanPanelHint": "Ativado/desativado e o nível são definidos no painel:",
|
||||
"compressionRoles": "Compress Message Roles",
|
||||
"compressionRoleUser": "User",
|
||||
"compressionRoleAssistant": "Assistant",
|
||||
|
|
@ -5821,6 +5920,17 @@
|
|||
"storageUsageTokenBufferDesc": "__MISSING__:Extra tokens added to reported usage to account for system prompt overhead.",
|
||||
"storageUsageTokenBufferHint": "__MISSING__:Set to 0 to report raw provider token counts. Default: 2000.",
|
||||
"storageUsageTokenBufferCurrent": "__MISSING__:Current: {value}",
|
||||
"redisLauncherTitle": "Redis local",
|
||||
"redisLauncherDesc": "Inicie com um clique um contêiner Redis 7 (Podman ou Docker) para cache de resposta, controle de cota e limitação de taxa.",
|
||||
"redisLauncherRefresh": "Atualizar",
|
||||
"redisLauncherStop": "Parar",
|
||||
"redisLauncherLaunching": "Iniciando...",
|
||||
"redisLauncherLaunch": "Iniciar Redis",
|
||||
"redisLauncherContainer": "Contêiner",
|
||||
"redisLauncherRunning": "Em execução",
|
||||
"redisLauncherReachable": "Acessível",
|
||||
"redisLauncherError": "Erro: {message}",
|
||||
"redisLauncherHint": "Equivalente a executar `omniroute redis up`. O contêiner é chamado `omniroute-redis` e escuta em 127.0.0.1:6379.",
|
||||
"compressionSettingsAutoTriggerMode": "Modo de disparo automático",
|
||||
"compressionSettingsMcpDescriptionCompression": "Compressão de descrição MCP",
|
||||
"mcpAccessibilityTitle": "__MISSING__:MCP accessibility output",
|
||||
|
|
@ -5904,6 +6014,12 @@
|
|||
"claudeFastModeModelCheckbox": "Ativar modo rápido para {model}",
|
||||
"claudeFastModeSaveError": "Falha ao atualizar a configuração do modo rápido do Claude",
|
||||
"authz": {
|
||||
"cors": {
|
||||
"wildcard": {
|
||||
"title": "CORS está aberto para qualquer origem (CORS_ALLOW_ALL=true)",
|
||||
"desc": "Qualquer site pode chamar a API deste servidor a partir do navegador de um visitante. Use apenas em redes confiáveis — defina origens explícitas em ALLOWED_ORIGINS e desabilite o CORS_ALLOW_ALL em produção."
|
||||
}
|
||||
},
|
||||
"title": "Inventário de Authz",
|
||||
"description": "Classificação de rotas em 5 níveis com política de bypass em tempo real. A leitura mostra a taxonomia completa; as mutações solicitam a senha de gerenciamento.",
|
||||
"loading": "Carregando inventário…",
|
||||
|
|
@ -5992,6 +6108,14 @@
|
|||
"resilienceEnableServerWaitDesc": "Quando ativado, o OmniRoute aguarda o primeiro resfriamento expirar e tenta novamente automaticamente.",
|
||||
"resilienceMaxAttempts": "Máximo de tentativas",
|
||||
"resilienceMaxWaitPerAttempt": "Espera máxima por tentativa",
|
||||
"resilienceComboCooldownWaitTitle": "Espera de cooldown do combo de cota compartilhada",
|
||||
"resilienceComboCooldownWaitDesc": "Somente para combos de cota compartilhada: aguarda um cooldown transitório curto e reenvia em vez de retornar um 429 imediatamente. Nunca espera em quota_exhausted.",
|
||||
"resilienceComboCooldownWaitToggleDesc": "Somente combos de cota compartilhada; nunca espera em quota_exhausted.",
|
||||
"resilienceComboCooldownMaxWaitMs": "Espera máxima por tentativa",
|
||||
"resilienceComboCooldownBudgetMs": "Orçamento total de espera",
|
||||
"resilienceQuotaShareConcurrencyTitle": "Concorrência por conexão de cota compartilhada",
|
||||
"resilienceQuotaShareConcurrencyDesc": "Somente para combos de cota compartilhada: quando uma conexão define um limite de Concorrência Máxima, serializa as requisições concorrentes para essa conta de assinatura para que nunca seja inundada além do teto. Requisições excedentes esperam na fila em vez de receber um 429. O limite vem do campo Concorrência Máxima de cada conexão; este botão apenas habilita ou desabilita seu respeito.",
|
||||
"resilienceQuotaShareConcurrencyToggleDesc": "Somente combos de cota compartilhada; respeita o limite de Concorrência Máxima de cada conexão.",
|
||||
"resilienceProviderCooldownTitle": "__MISSING__:Provider Cooldown",
|
||||
"resilienceProviderCooldownScope": "__MISSING__:All combo requests",
|
||||
"resilienceProviderCooldownTrigger": "__MISSING__:When a provider/connection fails",
|
||||
|
|
@ -6060,7 +6184,9 @@
|
|||
"modelLockoutExponentialBackoff": "__MISSING__:Exponential Backoff",
|
||||
"modelLockoutExponentialBackoffDescription": "__MISSING__:When enabled, each consecutive failure increases the cooldown duration exponentially.",
|
||||
"modelLockoutMaxBackoffSteps": "__MISSING__:Max Backoff Steps",
|
||||
"modelLockoutMaxBackoffStepsDescription": "__MISSING__:Maximum number of backoff steps before the cooldown stops growing. The Max Cooldown cap is reached first in most configurations, making this a safety ceiling for when Max Cooldown is raised."
|
||||
"modelLockoutMaxBackoffStepsDescription": "__MISSING__:Maximum number of backoff steps before the cooldown stops growing. The Max Cooldown cap is reached first in most configurations, making this a safety ceiling for when Max Cooldown is raised.",
|
||||
"disableSessionStickiness": "Desabilitar fixação de sessão",
|
||||
"disableSessionStickinessDesc": "Combos round-robin e aleatórios alternam para uma conexão diferente a cada requisição, em vez de fixar toda a conversa em uma conexão pelo hash da primeira mensagem. Deixe desativado para preservar acertos de cache de prompt em conversas com múltiplos turnos. Sobrescritas por combo têm prioridade."
|
||||
},
|
||||
"contextRtk": {
|
||||
"title": "Motor RTK",
|
||||
|
|
@ -6633,6 +6759,8 @@
|
|||
"autoRefresh": "Atualização automática",
|
||||
"refreshAll": "Atualizar tudo",
|
||||
"loadingQuotas": "Carregando...",
|
||||
"showMoreQuotas": "Mostrar mais {count}",
|
||||
"showLessQuotas": "Mostrar menos",
|
||||
"account": "Conta",
|
||||
"modelQuotas": "Cotas de Modelo",
|
||||
"lastUsed": "Last Refreshed",
|
||||
|
|
@ -6683,6 +6811,11 @@
|
|||
"resetsIn": "Reseta em",
|
||||
"editCutoffs": "Editar pontos de corte",
|
||||
"forceRefresh": "Atualizar agora",
|
||||
"resetCreditsLabel": "Créditos de redefinição",
|
||||
"redeemResetCredit": "Resgatar redefinição",
|
||||
"confirmRedeemResetCredit": "Resgatar um crédito de redefinição do Codex para esta conta? Isso consome um crédito de redefinição.",
|
||||
"resetCreditRedeemed": "Redefinição resgatada",
|
||||
"resetCreditRedeemFailed": "Falha ao resgatar crédito de redefinição",
|
||||
"suiteBuilderSaveFailed": "Falha ao salvar a suíte customizada",
|
||||
"clone": "Clonar",
|
||||
"exportSuite": "Exportar",
|
||||
|
|
@ -7835,7 +7968,36 @@
|
|||
"bulkImportMaxExceeded": "Máximo de 100 proxies por importação",
|
||||
"bulkImportPreview": "Visualização",
|
||||
"clearAssignment": "(atribuição clara)",
|
||||
"bulkProxyAssignment": "Atribuição de proxy em massa"
|
||||
"bulkProxyAssignment": "Atribuição de proxy em massa",
|
||||
"testAll": "Testar todos",
|
||||
"errorTestFailed": "Falha ao testar proxies",
|
||||
"batchSelectedCount": "{count} selecionado(s)",
|
||||
"batchDeleteSelected": "Excluir {count} selecionado(s)",
|
||||
"batchActivateSelected": "Habilitar {count} selecionado(s)",
|
||||
"testPassed": "✓ OK",
|
||||
"close": "Fechar",
|
||||
"managePool": "Gerenciar pool",
|
||||
"poolTitle": "Pool e rotação de proxies",
|
||||
"poolDescription": "Anexe múltiplos proxies a um escopo e alterne os IPs de saída entre eles. Um escopo com um único proxy se comporta exatamente como uma atribuição simples.",
|
||||
"poolScopeIdLabel": "ID do escopo",
|
||||
"poolScopeIdPlaceholder": "id do provedor / id da conexão / id do combo",
|
||||
"poolScopeIdRequired": "Um ID de escopo é obrigatório para escopo de provedor, conta ou combo.",
|
||||
"poolLoad": "Carregar pool",
|
||||
"poolLoadFailed": "Falha ao carregar o pool de proxies",
|
||||
"poolStrategyLabel": "Estratégia de rotação",
|
||||
"poolStrategyHint": "round-robin percorre os membros em ordem; aleatório escolhe uniformemente; fixo mantém um membro por uma janela antes de avançar.",
|
||||
"poolStrategyFailed": "Falha ao atualizar a estratégia de rotação",
|
||||
"strategyRoundRobin": "Round-robin",
|
||||
"strategyRandom": "Aleatório",
|
||||
"strategySticky": "Fixo",
|
||||
"poolMembersLabel": "Membros do pool ({count})",
|
||||
"poolNoMembers": "Ainda não há proxies neste pool.",
|
||||
"poolRemove": "Remover",
|
||||
"poolRemoveFailed": "Falha ao remover o proxy do pool",
|
||||
"poolAddLabel": "Adicionar um proxy",
|
||||
"poolAddMember": "Adicionar",
|
||||
"poolAddFailed": "Falha ao adicionar o proxy ao pool",
|
||||
"poolSelectProxy": "Selecionar um proxy…"
|
||||
},
|
||||
"playground": {
|
||||
"title": "Title",
|
||||
|
|
@ -7897,6 +8059,10 @@
|
|||
"topP": "Top-p",
|
||||
"presencePenalty": "Penalidade de presença",
|
||||
"frequencyPenalty": "Penalidade de frequência",
|
||||
"reasoningLabel": "Raciocínio",
|
||||
"thinking": "Pensando",
|
||||
"effort": "Esforço",
|
||||
"effortDefault": "Padrão",
|
||||
"seedPlaceholder": "Aleatório (deixar vazio)",
|
||||
"presetsLabel": "Presets",
|
||||
"loadPreset": "Carregar preset",
|
||||
|
|
@ -8374,6 +8540,7 @@
|
|||
"saveMarketplaceUrl": "__MISSING__:Save & Reload",
|
||||
"marketplaceUrlSaved": "__MISSING__:Marketplace URL updated",
|
||||
"marketplaceEmpty": "__MISSING__:No plugins found in marketplace.",
|
||||
"marketplaceInstallComingSoon": "Instalações via marketplace em breve.",
|
||||
"verified": "__MISSING__:Verified",
|
||||
"install": "__MISSING__:Install",
|
||||
"installedFromMarketplace": "__MISSING__:Plugin {name} installed!",
|
||||
|
|
@ -8506,6 +8673,7 @@
|
|||
"restartServer": "Reiniciar",
|
||||
"trustCert": "Confiar Cert",
|
||||
"downloadCert": "Baixar Cert",
|
||||
"certManualTitle": "O certificado não pôde ser instalado automaticamente (ex.: dentro de um contêiner). A ponte ainda pode ser executada — confie na CA manualmente:",
|
||||
"regenerateCert": "Regenerar Cert",
|
||||
"starting": "Iniciando…",
|
||||
"stopping": "Parando…",
|
||||
|
|
@ -8932,6 +9100,38 @@
|
|||
"colScore": "__MISSING__:Score",
|
||||
"colAvgScore": "__MISSING__:Avg Score",
|
||||
"colModels": "__MISSING__:Models",
|
||||
"colType": "__MISSING__:Type"
|
||||
"colType": "__MISSING__:Type",
|
||||
"filterConfiguredOnly": "Somente configurados",
|
||||
"filterAvailableOnly": "Somente disponíveis",
|
||||
"filterAvailableOnlyHelp": "Ocultar provedores cujas conexões estão todas limitadas por taxa ou sem cota.",
|
||||
"configuredOnly": "Somente configurados",
|
||||
"configuredOnlyHint": "Mostrar apenas provedores com conexões ativas",
|
||||
"noConfiguredProviders": "Nenhum provedor configurado encontrado. Adicione uma conexão de provedor primeiro.",
|
||||
"colConfigured": "Status"
|
||||
},
|
||||
"discovery": {
|
||||
"title": "Descoberta de provedores",
|
||||
"subtitle": "Busque métodos de acesso gratuito/ilimitado em provedores e revise os achados. Opcional, somente local.",
|
||||
"scanLabel": "Provedor para buscar",
|
||||
"scanPlaceholder": "ex.: huggingchat",
|
||||
"scan": "Buscar",
|
||||
"scanning": "Buscando…",
|
||||
"scanQueued": "Busca concluída para {provider}.",
|
||||
"scanFailed": "Falha na busca.",
|
||||
"loadFailed": "Falha ao carregar os resultados da busca.",
|
||||
"localOnlyNote": "Esta ferramenta é somente local (loopback). As buscas são executadas nesta máquina e nunca são acessíveis remotamente.",
|
||||
"verify": "Verificar",
|
||||
"verifyFailed": "Falha ao verificar o achado.",
|
||||
"delete": "Excluir",
|
||||
"deleteFailed": "Falha ao excluir o achado.",
|
||||
"deleteTitle": "Excluir resultado de busca",
|
||||
"deleteConfirm": "Excluir o achado de busca para {provider}? Isso não pode ser desfeito.",
|
||||
"emptyTitle": "Ainda não há resultados de busca",
|
||||
"emptyDescription": "Execute uma busca acima para procurar métodos de acesso gratuito em um provedor.",
|
||||
"risk": "Risco",
|
||||
"method": "Método",
|
||||
"auth": "Autenticação",
|
||||
"feasibility": "Viabilidade",
|
||||
"models": "Modelos"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,20 @@ import assert from "node:assert";
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function flatten(obj: Record<string, unknown>, prefix = ""): Record<string, unknown> {
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const k of Object.keys(obj)) {
|
||||
const key = prefix ? `${prefix}.${k}` : k;
|
||||
const v = obj[k];
|
||||
if (v && typeof v === "object" && !Array.isArray(v)) {
|
||||
Object.assign(out, flatten(v as Record<string, unknown>, key));
|
||||
} else {
|
||||
out[key] = v;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
describe("i18n pt-BR integrity", () => {
|
||||
it("should be a valid JSON file", () => {
|
||||
const ptPath = path.resolve("src/i18n/messages/pt-BR.json");
|
||||
|
|
@ -23,4 +37,29 @@ describe("i18n pt-BR integrity", () => {
|
|||
assert.ok(json.cache.loadingCacheAria);
|
||||
assert.ok(json.analytics.usageAnalyticsTitle);
|
||||
});
|
||||
|
||||
// Regression guard for #6695: en.json gained 194 keys that were never
|
||||
// mirrored into pt-BR.json (i18n:sync-ui was not re-run), and the
|
||||
// i18n-ui-coverage CI gate is a percentage threshold (80%) so it stayed
|
||||
// green at 93.8% coverage despite the gap. This asserts full key parity
|
||||
// so a future drift fails loudly instead of silently degrading coverage.
|
||||
it("should contain every key present in en.json (no drift, #6695)", () => {
|
||||
const enPath = path.resolve("src/i18n/messages/en.json");
|
||||
const ptPath = path.resolve("src/i18n/messages/pt-BR.json");
|
||||
const en = JSON.parse(fs.readFileSync(enPath, "utf8"));
|
||||
const pt = JSON.parse(fs.readFileSync(ptPath, "utf8"));
|
||||
|
||||
const enFlat = flatten(en);
|
||||
const ptFlat = flatten(pt);
|
||||
|
||||
const missing = Object.keys(enFlat).filter((k) => !(k in ptFlat));
|
||||
|
||||
assert.strictEqual(
|
||||
missing.length,
|
||||
0,
|
||||
`pt-BR.json is missing ${missing.length} keys present in en.json. Sample: ${missing
|
||||
.slice(0, 10)
|
||||
.join(", ")}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue