From 060f331264bb79d8255405bb4e64fadcfe9ef0fc Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 16 Sep 2026 12:19:40 -0300 Subject: [PATCH] fix(i18n): pt-BR review pass over the retranslated leaves (172 corrections) + review-locale script (#13885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer pass (native-speaker prompt) over the 1,865 pt-BR leaves retranslated in #13782: 172 corrections applied; new scripts/i18n/review-locale.mjs with tests. ⚠️ base-red inherited: #12732 --- changelog.d/fixes/13885-pt-br-review.md | 1 + scripts/i18n/review-locale.mjs | 169 ++++++++++++ src/i18n/messages/pt-BR.json | 340 ++++++++++++------------ tests/unit/i18n-review-locale.test.ts | 24 ++ 4 files changed, 364 insertions(+), 170 deletions(-) create mode 100644 changelog.d/fixes/13885-pt-br-review.md create mode 100644 scripts/i18n/review-locale.mjs create mode 100644 tests/unit/i18n-review-locale.test.ts diff --git a/changelog.d/fixes/13885-pt-br-review.md b/changelog.d/fixes/13885-pt-br-review.md new file mode 100644 index 0000000000..0028b99f5d --- /dev/null +++ b/changelog.d/fixes/13885-pt-br-review.md @@ -0,0 +1 @@ +- **fix(i18n):** reviewer pass over the 1,865 pt-BR leaves retranslated in #13782 (172 corrections) via the new `review-locale` script. (#13885) diff --git a/scripts/i18n/review-locale.mjs b/scripts/i18n/review-locale.mjs new file mode 100644 index 0000000000..0738a5b6a0 --- /dev/null +++ b/scripts/i18n/review-locale.mjs @@ -0,0 +1,169 @@ +#!/usr/bin/env node +/** + * OmniRoute — locale review pass. Sends the leaves a locale changed since a git ref to the + * translation backend with a *reviewer* prompt (native speaker, dashboard context, keep + * placeholders/ICU/markup) and applies only the corrections it returns. Produces a markdown + * report so the operator can read what was changed. + * + * Usage: node scripts/i18n/review-locale.mjs --locale=pt-BR --since= [--dry-run] [--batch-size=30] + */ +import { existsSync, readFileSync, promises as fs } from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { execFileSync } from "node:child_process"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { backendConfig, callChat } from "./lib/translate-backend.mjs"; + +// ----- .env loader (same contract as sync-ui-keys.mjs: repo-root .env, already-set vars win) +(function loadDotEnv() { + const envPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", ".env"); + if (!existsSync(envPath)) return; + try { + const raw = readFileSync(envPath, "utf8"); + for (const rawLine of raw.split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line || line.startsWith("#")) continue; + const eq = line.indexOf("="); + if (eq <= 0) continue; + const key = line.slice(0, eq).trim(); + if (!key || process.env[key] !== undefined) continue; + let value = line.slice(eq + 1); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + process.env[key] = value; + } + } catch { + // unreadable .env — the backend config will report the missing variables + } +})(); + +const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(SCRIPT_DIR, "..", ".."); +const MESSAGES_DIR = path.join(ROOT, "src", "i18n", "messages"); + +const flat = (o, p = "", out = {}) => { + for (const [k, v] of Object.entries(o)) { + const d = p ? `${p}.${k}` : k; + if (v && typeof v === "object" && !Array.isArray(v)) flat(v, d, out); + else out[d] = v; + } + return out; +}; + +const setDeep = (o, dotted, value) => { + const parts = dotted.split("."); + let n = o; + for (const p of parts.slice(0, -1)) n = n[p]; + n[parts.at(-1)] = value; +}; + +export function changedLeaves(before, after) { + const out = {}; + for (const [k, v] of Object.entries(after)) { + if (typeof v === "string" && before[k] !== v) out[k] = v; + } + return out; +} + +export function parseReviewResponse(text, ids) { + const m = text.match(/\{[\s\S]*\}/); + if (!m) return {}; + let parsed; + try { + parsed = JSON.parse(m[0]); + } catch { + return {}; + } + const allowed = new Set(ids); + const out = {}; + for (const [id, v] of Object.entries(parsed)) { + if (allowed.has(id) && typeof v === "string" && v.trim() && v.trim() !== "OK") { + out[id] = v.trim(); + } + } + return out; +} + +const REVIEW_SYSTEM = (english, native) => + `You are a senior ${english} (${native}) localization reviewer for a developer-facing dashboard (an LLM proxy/router called OmniRoute). You receive a JSON object mapping ids to {en, current}. For every id, answer "OK" when the current translation is correct, natural and complete, or return the corrected ${english} string. Rules: keep every placeholder ({name}, {count, plural, …}), ICU syntax, HTML/markdown, product names (OmniRoute, Claude, Codex, MCP, A2A) and technical identifiers exactly as in the English; do not translate code; prefer the terminology already used in "current" when it is fine. Reply with ONLY a JSON object {id: "OK" | "corrected string"}.`; + +function parseArgs(argv) { + const o = { locale: null, since: null, dryRun: false, batchSize: 30 }; + for (const a of argv.slice(2)) { + if (a.startsWith("--locale=")) o.locale = a.slice(9); + else if (a.startsWith("--since=")) o.since = a.slice(8); + else if (a === "--dry-run") o.dryRun = true; + else if (a.startsWith("--batch-size=")) o.batchSize = Math.max(1, Number(a.slice(13)) || 30); + } + if (!o.locale || !o.since) throw new Error("--locale= and --since= are required"); + return o; +} + +async function main() { + const o = parseArgs(process.argv); + const file = path.join(MESSAGES_DIR, `${o.locale}.json`); + const rel = path.relative(ROOT, file); + const after = JSON.parse(await fs.readFile(file, "utf8")); + const before = JSON.parse( + execFileSync("git", ["show", `${o.since}:${rel}`], { + cwd: ROOT, + encoding: "utf8", + maxBuffer: 1 << 28, + }) + ); + const en = flat(JSON.parse(await fs.readFile(path.join(MESSAGES_DIR, "en.json"), "utf8"))); + const changed = changedLeaves(flat(before), flat(after)); + const ids = Object.keys(changed); + console.log(`[review] ${o.locale}: ${ids.length} leaves changed since ${o.since}`); + if (o.dryRun) return; + const config = JSON.parse(await fs.readFile(path.join(ROOT, "config", "i18n.json"), "utf8")); + const entry = config.locales.find((l) => l.code === o.locale); + const backend = backendConfig(); + const fixes = {}; + for (let i = 0; i < ids.length; i += o.batchSize) { + const slice = ids.slice(i, i + o.batchSize); + const payload = Object.fromEntries( + slice.map((id) => [id, { en: en[id], current: changed[id] }]) + ); + const text = await callChat( + [ + { role: "system", content: REVIEW_SYSTEM(entry.english ?? entry.name, entry.native) }, + { role: "user", content: JSON.stringify(payload) }, + ], + backend + ); + Object.assign(fixes, parseReviewResponse(text, slice)); + console.log( + `[review] ${Math.min(i + o.batchSize, ids.length)}/${ids.length} reviewed, ${Object.keys(fixes).length} corrections so far` + ); + } + for (const [id, v] of Object.entries(fixes)) setDeep(after, id, v); + await fs.writeFile(file, JSON.stringify(after, null, 2) + "\n", "utf8"); + const reportDir = path.join(ROOT, "_artifacts", "i18n-review"); + await fs.mkdir(reportDir, { recursive: true }); + const report = [ + `# Review ${o.locale} since ${o.since}`, + "", + `${ids.length} leaves reviewed, ${Object.keys(fixes).length} corrected.`, + "", + "| key | en | before | after |", + "| --- | --- | --- | --- |", + ...Object.entries(fixes).map(([id, v]) => `| \`${id}\` | ${en[id]} | ${changed[id]} | ${v} |`), + ].join("\n"); + await fs.writeFile(path.join(reportDir, `${o.locale}.md`), report + "\n", "utf8"); + console.log( + `[review] ${Object.keys(fixes).length} corrections applied; report: _artifacts/i18n-review/${o.locale}.md` + ); +} + +const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; +if (isDirectRun) { + main().catch((e) => { + console.error(`[review] ${e.message}`); + process.exitCode = 1; + }); +} diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index b0b1e863b6..60f7aed952 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -162,7 +162,7 @@ "effortMax": "Máximo", "effortUltra": "Ultra", "reasoningEffort": "Esforço de raciocínio", - "wireApi": "API Wire", + "wireApi": "Wire API", "modelAliases": "Aliases de modelo", "combos": "Combinações", "noModelsFound": "Nenhum modelo encontrado", @@ -191,12 +191,12 @@ "comboCreated": "Combo criado", "swapFormats": "Trocar formatos", "daysAgo": "Dias atrás", - "retries": "Tentativas", + "retries": "Novas tentativas", "errorDuringRestore": "Erro durante a restauração", "eventsAppearHint": "Dica de exibição de eventos", "noLockouts": "Sem bloqueios", "webSearchDesc": "Descrição da pesquisa na web", - "audioProvidersHeading": "Provedores de áudio", + "audioProvidersHeading": "Título de provedores de áudio", "cloudAgentProviders": "Provedores de agentes em nuvem", "minutesAgo": "Minutos atrás", "a": "A", @@ -207,12 +207,12 @@ "failedToLoad": "Falha ao carregar", "categoryMedia": "Categoria de mídia", "enableCloud": "Ativar nuvem", - "expirationBannerExpiringSoon": "Banner de expiração próxima", + "expirationBannerExpiringSoon": "Banner de expiração iminente", "retry": "Tentar novamente", "embeddings": "Embeddings", "errorCount": "Contagem de erros", "backupReasonManual": "Motivo do backup: manual", - "safeSearchModerate": "Pesquisa segura moderada", + "safeSearchModerate": "Pesquisa segura: moderada", "activeLimiters": "Limitadores ativos", "a2aCardTitle": "Título do cartão A2A", "cloudWorkerUnreachable": "Cloud Worker inacessível", @@ -232,7 +232,7 @@ "available": "Disponível", "noBackupYet": "Nenhum backup ainda", "more": "Mais", - "noCompatibleYet": "Nenhum compatível ainda", + "noCompatibleYet": "Nenhum compatível por enquanto", "multiProvider": "Vários provedores", "repairEnvHint": "Dica para reparar o ambiente", "disablingCloud": "Desativando a nuvem", @@ -240,10 +240,10 @@ "valid": "Válido", "cloudBenefitShare": "Compartilhamento de benefícios da nuvem", "mcpCardTitle": "Título do cartão MCP", - "disableConfirm": "Confirmar desativação", + "disableConfirm": "Confirmação de desativação", "filters": "Filtros", - "expirationBannerExpiredDesc": "Descrição do banner de expiração vencida", - "saveComboDefaults": "Salvar padrões da combinação", + "expirationBannerExpiredDesc": "Descrição do banner de validade expirada", + "saveComboDefaults": "Salvar valores padrão da combinação", "cloudConnectedVerified": "Conexão com a nuvem verificada", "uptime": "Tempo de atividade", "compatibleProdPlaceholder": "Espaço reservado para produto compatível", @@ -259,18 +259,18 @@ "mcpCardDescription": "Descrição do cartão MCP", "apiTypeLabel": "Rótulo do tipo de API", "configuredProvidersLabel": "Rótulo de provedores configurados", - "maxRetriesLabel": "Rótulo do máximo de tentativas", + "maxRetriesLabel": "Rótulo do número máximo de tentativas", "title": "Título", "output": "Saída", "prefixHint": "Dica de prefixo", - "skipWizard": "Pular assistente", - "failedCount": "Contagem de falhas", + "skipWizard": "Pular o assistente", + "failedCount": "Número de falhas", "confirmDbImportDesc": "Descrição da confirmação de importação do banco de dados", "entries": "Entradas", "until": "Até", "disableCombo": "Desativar combinação", "liveMonitorDescriptionPrefix": "Prefixo da descrição do monitoramento em tempo real", - "runningCount": "Contagem em execução", + "runningCount": "Número de execuções em andamento", "noActiveConnectionsInGroup": "Nenhuma conexão ativa no grupo", "savedSuccessfully": "Salvo com sucesso", "systemStorage": "Armazenamento do sistema", @@ -285,9 +285,9 @@ "moderationsDesc": "Descrição das moderações", "providerHealthStatusAria": "Status de integridade do provedor (ARIA)", "modelNamePlaceholder": "Placeholder do nome do modelo", - "mcpQuickStartStep2": "Etapa 2 do início rápido do Mcp", + "mcpQuickStartStep2": "Etapa 2 do início rápido do MCP", "quickStart": "Início rápido", - "fullExportFailedWithError": "Falha na exportação completa com erro", + "fullExportFailedWithError": "A exportação completa falhou com erro", "loadingBackups": "Carregando backups", "anthropicBaseUrlPlaceholder": "Placeholder da URL base da Anthropic", "description": "Descrição", @@ -330,7 +330,7 @@ "anthropicCompatibleName": "Nome compatível com Anthropic", "disabling": "Desativando", "failedEnable": "Falha ao ativar", - "categoryUtility": "Categoria Utilitários", + "categoryUtility": "Categoria de utilitários", "customUrlOptional": "URL personalizada (opcional)", "localProviders": "Provedores locais", "comboNamePlaceholder": "Placeholder do nome da combinação", @@ -340,11 +340,11 @@ "nameLabel": "Rótulo do nome", "allOperational": "Todos operacionais", "backupNow": "Fazer backup agora", - "providerMaxRetriesAria": "ARIA do máximo de novas tentativas do provedor", + "providerMaxRetriesAria": "ARIA do número máximo de novas tentativas do provedor", "textToSpeechDesc": "Descrição da conversão de texto em fala", "machineId": "ID da máquina", "globalProxy": "Proxy global", - "hitsMisses": "Acertos e erros", + "hitsMisses": "Acertos e falhas", "testDesc": "Descrição do teste", "chatDesc": "Descrição do chat", "importSuccess": "Importação bem-sucedida", @@ -417,7 +417,7 @@ "usingLocalServer": "Usando servidor local", "globalComboConfig": "Configuração global de combinações", "backupFailed": "Falha no backup", - "tabProtocols": "Protocolos das abas", + "tabProtocols": "Aba Protocolos", "continue": "Continuar", "categorySearch": "Pesquisa de categorias", "rateLimitStatus": "Status do limite de requisições", @@ -427,8 +427,8 @@ "trackMetricsDesc": "Descrição do rastreamento de métricas", "durationSecondsShort": "Duração em segundos (abreviado)", "formatConverterDescription": "Descrição do conversor de formato", - "cloudBenefitAccess": "Acesso aos benefícios da nuvem", - "maxRetries": "Máximo de tentativas", + "cloudBenefitAccess": "Acesso ao benefício da nuvem", + "maxRetries": "Máximo de novas tentativas", "queueTimeout": "Tempo limite da fila", "queueDepth": "Profundidade da Fila", "addProvider": "Adicionar provedor", @@ -453,7 +453,7 @@ "mcpQuickStartStep1": "Etapa 1 do início rápido do MCP", "runTest": "Executar teste", "loadingFallbackChains": "Carregando cadeias de fallback", - "healthy": "Íntegro", + "healthy": "Saudável", "version": "Versão", "saveBlockWeighted": "Salvar bloco ponderado", "zedImportNone": "Nenhum item para importar do Zed", @@ -461,7 +461,7 @@ "noGlobalProxy": "Nenhum proxy global", "noModels": "Nenhum modelo", "stickyLimit": "Limite de persistência", - "passedCount": "Contagem de aprovações", + "passedCount": "Total de aprovações", "zedImportFailed": "Falha ao importar do Zed", "saving": "Salvando", "testAll": "Testar todos", @@ -477,13 +477,13 @@ "modeTest": "Modo de teste", "failedDeleteChain": "Falha ao excluir a cadeia", "providerDesc": "Descrição do provedor", - "templateLoadHint": "Dica de carregamento do modelo", + "templateLoadHint": "Dica para carregar o modelo", "lastFailure": "Última falha", "moveDown": "Mover para baixo", "providerLabel": "Rótulo do provedor", "clearCacheFailed": "Falha ao limpar o cache", "imagesGenerations": "Gerações de imagens", - "repairEnvWorking": "Reparo do ambiente funcionando", + "repairEnvWorking": "Reparo do ambiente em andamento", "millisecondsShort": "ms", "globalLabel": "Rótulo global", "failuresPlural": "Falhas", @@ -492,22 +492,22 @@ "chatPathHint": "Dica do caminho do chat", "defaultStrategyDesc": "Descrição da estratégia padrão", "latencyP95": "Latência P95", - "textToSpeech": "Texto para fala", + "textToSpeech": "Texto para voz", "searchType": "Tipo de pesquisa", "messages": "Mensagens", "aggregatorsGateways": "Agregadores e gateways", "comboStrategyAria": "ARIA da estratégia combinada", "input": "Entrada", - "testingConnection": "Testando conexão", + "testingConnection": "Testando a conexão", "excludeDomains": "Excluir domínios", "webCookieProviders": "Provedores de cookies da web", - "allTestsPassed": "Todos os testes foram aprovados", + "allTestsPassed": "Todos os testes passaram", "openaiCompatibleName": "Nome compatível com OpenAI", "warningCostOptimizedPartialPricing": "Aviso: preços parciais com otimização de custos", - "comboDefaultsGuideTitle": "Título do guia de padrões combinados", + "comboDefaultsGuideTitle": "Título do guia de configurações padrão combinadas", "hoursAgo": "Horas atrás", "notAvailable": "Indisponível", - "skipAndContinue": "Ignorar e continuar", + "skipAndContinue": "Pular e continuar", "moderations": "Moderações", "proxyConfig": "Configuração de proxy", "upstreamProxyProviders": "Provedores de proxy upstream", @@ -533,12 +533,12 @@ "audioTranscription": "Transcrição de áudio", "fallbackChainsDesc": "Descrição das cadeias de fallback", "exampleTemplates": "Modelos de exemplo", - "connectionsCount": "Contagem de conexões", + "connectionsCount": "Número de conexões", "modelsPathLabel": "Rótulo do caminho dos modelos", "activeProvidersHint": "Dica de provedores ativos", "activeLockouts": "Bloqueios ativos", "invalid": "Inválido", - "skipPassword": "Pular senha", + "skipPassword": "Ignorar senha", "moveUp": "Mover para cima", "timeRange": "Intervalo de tempo", "stickyLimitDesc": "Descrição do limite fixo", @@ -569,11 +569,11 @@ "mcpQuickStartStep3": "Etapa 3 do início rápido do MCP", "repairEnvFailed": "Falha ao reparar o ambiente", "zedImportHint": "Dica de importação do Zed", - "modelsAcrossEndpoints": "Modelos entre endpoints", + "modelsAcrossEndpoints": "Modelos em vários endpoints", "autoDisableBannedAccounts": "Desativar automaticamente contas banidas", "securityDesc": "Descrição da segurança", "listModels": "Listar modelos", - "backupRestore": "Backup e restauração", + "backupRestore": "Restauração de backup", "target": "Destino", "zedImportSuccess": "Importação do Zed realizada com sucesso", "lastHeaderUpdate": "Última atualização do cabeçalho", @@ -581,24 +581,24 @@ "noFallbackChains": "Nenhuma cadeia de fallback", "noSearchProviders": "Nenhum provedor de pesquisa", "autoBalance": "Balanceamento automático", - "noModelsYet": "Nenhum modelo ainda", + "noModelsYet": "Ainda não há modelos", "signatureFamily": "Família de assinaturas", "externalApiCalls": "Chamadas de API externas", "providerOverridesDesc": "Descrição das substituições do provedor", "signatureTool": "Ferramenta de assinatura", "connectionFailed": "Falha na conexão", - "activeLimitersPlural": "Limitadores ativos (plural)", - "doneDesc": "Descrição de concluído", + "activeLimitersPlural": "Limitadores ativos", + "doneDesc": "Descrição da conclusão", "down": "Inativo", "noDataYet": "Ainda não há dados", "activeProviders": "Provedores ativos", "nameInvalid": "Nome inválido", - "skip": "Pular", + "skip": "Ignorar", "createChain": "Criar cadeia", "audioSpeech": "Síntese de voz", "cacheCleared": "Cache limpo", "searchTypeNews": "Tipo de pesquisa: notícias", - "durationMillisecondsShort": "Duração em milissegundos (abreviada)", + "durationMillisecondsShort": "Duração em ms", "addOpenAICompatible": "Adicionar compatível com OpenAI", "chatTesterTab": "Chat Tester Tab", "queued": "Na fila", @@ -607,22 +607,22 @@ "verifyingConnection": "Verificando a conexão", "imageProviders": "Provedores de imagens", "protocolToolsLabel": "Rótulo das ferramentas de protocolo", - "queryPlaceholder": "Texto de exemplo da consulta", + "queryPlaceholder": "Placeholder da consulta", "videoGeneration": "Geração de vídeos", "timeRangeWeek": "Intervalo de tempo: semana", "limitExhausted": "Limite esgotado", "failedDisable": "Falha ao desativar", "inMemoryNote": "Observação sobre armazenamento em memória", "compatibleHint": "Dica de compatibilidade", - "errorShort": "Erro resumido", + "errorShort": "Erro abreviado", "advancedHint": "Dica avançada", "restoreFailed": "Falha ao restaurar", - "searchProvidersHeading": "Pesquisar provedores", + "searchProvidersHeading": "Título dos provedores de pesquisa", "comboName": "Nome da combinação", "autoDisableDescription": "Descrição da desativação automática", "embeddingsDesc": "Descrição dos embeddings", "operational": "Operacional", - "testAllApiKey": "Testar todos os provedores de chave de API", + "testAllApiKey": "Testar todas as chaves de API", "backupCreated": "Backup criado", "errorDuringImport": "Erro durante a importação", "comboDefaultsGuideHint2": "Dica 2 do guia de padrões de combinação", @@ -650,7 +650,7 @@ "millisecondsAbbr": "ms", "backupReasonPreRestore": "Motivo do backup antes da restauração", "disableCloud": "Desativar nuvem", - "newProviderNameAria": "Nome do novo provedor Aria", + "newProviderNameAria": "ARIA do nome do novo provedor", "passwordsMismatch": "As senhas não coincidem", "a2aQuickStartStep3": "Etapa 3 do início rápido do A2A", "liveMonitorDescriptionSuffix": "Sufixo da descrição do monitoramento em tempo real", @@ -666,19 +666,19 @@ "providersCommaSeparated": "Provedores separados por vírgulas", "exportDatabase": "Exportar banco de dados", "hitRate": "Taxa de acertos", - "completionsLegacy": "Conclusões legadas", + "completionsLegacy": "Completions legadas", "removeModel": "Remover modelo", "timeRangeDay": "Intervalo de tempo: dia", "cloudRequestFailed": "Falha na solicitação à nuvem", "updatedAt": "Atualizado em", - "monitoredProvidersHint": "Dica de provedores monitorados", + "monitoredProvidersHint": "Dica sobre provedores monitorados", "connectionSuccessful": "Conexão bem-sucedida", "latencyP50": "Latência P50", "logsDeleted": "Logs excluídos", "chatTester": "Testador de chat", "safeSearchOff": "Pesquisa segura desativada", - "nameHint": "Dica de nome", - "debugToggle": "Alternância de depuração", + "nameHint": "Dica sobre o nome", + "debugToggle": "Controle de depuração", "embedding": "Embedding", "issuesLabel": "Rótulo de problemas", "optionAny": "Opção: qualquer", @@ -691,10 +691,10 @@ "signatureCache": "Cache de assinaturas", "modelLockouts": "Bloqueios de modelos", "usingCloudProxy": "Usando o Cloud Proxy", - "loadingHealth": "Carregando integridade", + "loadingHealth": "Carregando status de integridade", "providerOverrides": "Substituições de provedores", "audioTranscriptionDesc": "Descrição da transcrição de áudio", - "learnedFromHeaders": "Aprendido com os cabeçalhos", + "learnedFromHeaders": "Obtido dos cabeçalhos", "totalRequests": "Total de solicitações", "cloudUnstableNote": "Observação sobre instabilidade da nuvem", "gamificationAdmin": "Administrador de gamificação", @@ -992,11 +992,11 @@ "featureFlagExposeCcDiscoveryAliasesDescription": "Divulgar ids espelho claude/<provider>/<model> em /v1/models para que a descoberta de modelos do gateway Claude Code liste modelos não-Claude. Atenção: duplica as entradas do catálogo para todos os clientes quando ativado globalmente.", "featureFlagNoThinkingAliasEnabledDescription": "Controle principal dos aliases de gateway no-think/<provider>/<model>. Ativado (padrão): /v1/models anuncia uma variante sem raciocínio para cada modelo Claude qualificado com capacidade de raciocínio, e um ID no-think/ enviado em uma solicitação é resolvido para o modelo real com o raciocínio suprimido. Desativado: nenhuma variante é anunciada, e um ID no-think/ é tratado como qualquer outro ID de modelo desconhecido. A configuração de ativação/desativação ModelSpec.noThinkingAlias por modelo continua válida enquanto esta opção estiver ativada.", "featureFlagChatVirtualLanesEnabledDescription": "Ative faixas de admissão virtuais adaptativas por tenant para o despacho de provedores (#9654): o pico de um tenant não gera mais 503 para outro. A variável de ambiente OMNIROUTE_CHAT_VIRTUAL_LANES tem precedência sobre esta configuração do painel; as alterações entram em vigor ao reiniciar o servidor.", - "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Páginas de provedores no painel: exibe o selo Grátis apenas nos sinais que o provedor reconhece — descarta a heurística de nome de exibição, campos de gratuidade não booleanos e sufixos :free em provedores registrados sem uma modalidade gratuita documentada. Desativado mantém a regra histórica do selo.", + "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Páginas de provedores no painel: exibe o selo Grátis apenas quando houver sinais reconhecidos pelo provedor — descarta a heurística de nome de exibição, campos de gratuidade não booleanos e sufixos :free em provedores registrados sem uma modalidade gratuita documentada. Quando desativado, mantém a regra histórica do selo.", "featureFlagProxySkipRecentlyFailedDescription": "Pools de proxy e a rotação por conta do opencode deixam de reutilizar um proxy que acabou de falhar (sonda TCP recusada, ou um 429 recebido por ele) por um período por processo que dobra a cada repetição, até um limite. Nenhum status de proxy é gravado; com todos os candidatos deixados de lado, a escolha não muda. Desligado por padrão: a ordem de seleção é exatamente a rotação simples.", "featureFlagProxyPoolEgressObservationDescription": "Mostra, abaixo de um pool de proxy no painel, quantos IPs de saída observados atenderam seus membros nas últimas 24 h, quantas conexões os usaram e o máximo visto atrás de um mesmo IP. Somente leitura, calculado a partir do log de proxy, nunca usado para roteamento. Desligado por padrão: o editor de pool não muda e a rota de observação responde null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "Na varredura de saúde de proxy, permite que uma sonda recusada pelo destino (401/403/429: o proxy retransmitiu, o destino recusou este IP de saída) zere a sequência de falhas consecutivas do proxy, como uma sonda atendida. Desligado por padrão: a recusa continua neutra e mantém a sequência. Um 5xx continua inconclusivo em qualquer caso, e uma recusa nunca remove, desativa ou reativa um proxy.", - "featureFlagXaiOauthLiveModelDiscoveryDescription": "__MISSING__:Fetch the live xAI model catalog for xai-oauth connections from https://api.x.ai/v1/models using the OAuth bearer token, instead of the frozen static seed. Off by default: xai-oauth keeps serving the static seed unchanged. On any resolution error, discovery falls back to the seed.", + "featureFlagXaiOauthLiveModelDiscoveryDescription": "Busca o catálogo atualizado de modelos da xAI para conexões xai-oauth em https://api.x.ai/v1/models usando o token bearer OAuth, em vez do conjunto inicial estático e congelado. Desligado por padrão: xai-oauth continua disponibilizando o conjunto inicial estático sem alterações. Em caso de qualquer erro de resolução, a descoberta recorre ao conjunto inicial.", "sidebar": { "home": "Início", "dashboard": "Painel", @@ -1092,12 +1092,12 @@ "hideHealthLogs": "Ocultar logs de integridade", "customLogo": "Logotipo personalizado", "appearance": "Aparência", - "themeSelectionAria": "ARIA de seleção de tema", + "themeSelectionAria": "ARIA da seleção de tema", "themeCreate": "Criar tema", "customFaviconDesc": "Descrição do favicon personalizado", "logoPreview": "Pré-visualização do logotipo", "themeCustom": "Tema personalizado", - "hideHealthLogsDesc": "Descrição de ocultar logs de integridade", + "hideHealthLogsDesc": "Descrição da opção de ocultar logs de integridade", "faviconPreview": "Pré-visualização do favicon", "changelog": "Registro de alterações", "contextSection": "Contexto e cache", @@ -1132,11 +1132,11 @@ "routingSection": "Roteamento", "protocolsSection": "Protocolos", "agentsAiSection": "Agentes e IA", - "cacheContextSection": "Cache e Contexto", + "cacheContextSection": "Cache e contexto", "analyticsSection": "Análises", "costsSection": "Custos", "monitoringSection": "Monitoramento", - "auditSecuritySection": "Auditoria e Segurança", + "auditSecuritySection": "Auditoria e segurança", "devtoolsSection": "Ferramentas de Desenvolvimento", "configurationSection": "Configuração", "aiFeaturesSection": "Recursos de IA", @@ -1188,14 +1188,14 @@ "globalRouting": "Roteamento Global", "mitmProxy": "Proxy MITM", "oneProxy": "1Proxy", - "agenticFeaturesSection": "Recursos agênticos", + "agenticFeaturesSection": "Recursos de agentes", "otherFeaturesSection": "Outros recursos", "compressionContextGroup": "Compressão de contexto", "gamificationGroup": "Gamificação", "toolsGroup": "Ferramentas", "integrationsGroup": "Integrações", "proxyGroup": "Proxy", - "costsParametersGroup": "Parâmetros de custos", + "costsParametersGroup": "Parâmetros de custo", "auditGroup": "Auditoria", "batchGroup": "Lote", "homeSubtitle": "Visão geral do painel", @@ -1628,8 +1628,8 @@ "auditDescription": "Trilha de auditoria de conformidade do uso de chaves de API, chamadas de ferramentas MCP e eventos de políticas", "webhooksDescription": "Configure endpoints de webhook para receber notificações de eventos em tempo real", "healthDescription": "Visão geral da integridade do sistema: provedores, disjuntores, limites de taxa e banco de dados", - "proxyDescription": "Configure as definições de proxy upstream para conexões de saída com provedores", - "apiEndpointsDescription": "Gerencie configurações personalizadas de endpoints de API e substituições de roteamento", + "proxyDescription": "Configure as configurações de proxy upstream para conexões de saída com provedores", + "apiEndpointsDescription": "Gerencie configurações personalizadas de endpoints de API e regras de substituição de roteamento", "batchFilesDescription": "Navegue e gerencie arquivos de saída e resultados de trabalhos em lote", "analyticsEvalsDescription": "Resultados de avaliações de modelos e benchmarks de desempenho", "analyticsSearchDescription": "Análises de consultas de pesquisa, taxas de acerto do cache e acompanhamento de custos", @@ -1638,7 +1638,7 @@ "analyticsCompressionDescription": "Análises de compactação de contexto e economia de tokens", "costsBudgetDescription": "Limites de orçamento e alertas de gastos por chave de API e provedor", "costsPricingDescription": "Configuração personalizada de preços para cálculos de custo de tokens", - "logsProxyDescription": "Logs de solicitações do proxy upstream e inspeção de tráfego", + "logsProxyDescription": "Logs de solicitações ao proxy upstream e inspeção de tráfego", "logsConsoleDescription": "Saída do console do aplicativo e logs de depuração", "logsActivityDescription": "Trilha de auditoria das ações dos usuários e dos eventos do sistema", "auditMcpDescription": "Trilha de auditoria de invocações de ferramentas MCP e registros de conformidade", @@ -1672,7 +1672,7 @@ "featureFlagsNoResults": "Nenhuma sinalização corresponde à sua pesquisa", "featureFlagsSaved": "Bandeira atualizada", "featureFlagsError": "Falha ao atualizar sinalizador", - "settingsRoutingDescription": "Regras de roteamento, aliases de modelos, padrões de combos e configurações de degradação", + "settingsRoutingDescription": "Regras de roteamento, aliases de modelos, valores padrão dos combos e configurações de degradação", "settingsResilienceDescription": "Configuração de circuit breaker, novas tentativas e fallback", "settingsAdvancedDescription": "Regras avançadas de payload, limites de requisição e configurações da API de proxy", "mitmProxyDescription": "Configure as definições do proxy MITM para inspeção e depuração de tráfego", @@ -1707,7 +1707,7 @@ "logs": "Logs", "auditLog": "Log de Auditoria", "console": "Console", - "logger": "Registrador", + "logger": "Logger", "translator": "Tradutor", "playground": "Área de testes", "add": "Adicionar", @@ -2093,7 +2093,7 @@ "comboHealthPerformance": "Desempenho", "comboHealthPerformanceDescription": "Confiabilidade e throughput para o tráfego de combo roteado.", "comboHealthExecutionTargetsDescription": "Métricas de runtime por etapa e visibilidade de cota para alvos de combo estruturados.", - "comboHealthRequestShort": "{count} req.", + "comboHealthRequestShort": "{count} req", "comboHealthQuotaScope": "Escopo da cota: {scope}", "comboHealthTrendValue": "Tendência: {trend}", "comboHealthFetchFailed": "Falha ao buscar dados de saúde do combo", @@ -2165,7 +2165,7 @@ "providerUtilizationStepAutomatic": "Os dados aparecerão automaticamente conforme os snapshots de cota forem coletados", "statusExhausted": "Esgotado", "statusLow": "Baixo", - "statusHealthy": "Íntegro", + "statusHealthy": "Saudável", "remainingQuota": "Cota restante", "routeTraceTitle": "Visualização de Rastreamento de Rota", "routeTraceDescription": "Inspecione o rastro de solicitação persistido: alvo selecionado, fatores de roteamento, evidências de fallback, replay da pontuação atual, latência, tokens e saúde do alvo.", @@ -2383,7 +2383,7 @@ "allowedCombos": "Combos Permitidos", "allCombosAllowed": "Esta chave pode usar qualquer combo.", "restrictedComboCount": "Restrito a {count, plural, one {# combo} other {# combos}}.", - "preservedComboRules": "{count, plural, one {# entrada armazenada não está} other {# entradas armazenadas não estão}} nesta lista de opções (por exemplo, regras de roteamento) e será mantida como salva.", + "preservedComboRules": "{count, plural, one {# entrada armazenada não está nesta lista de opções (por exemplo, regras de roteamento) e será mantida como salva.} other {# entradas armazenadas não estão nesta lista de opções (por exemplo, regras de roteamento) e serão mantidas como salvas.}}", "preservedComboRuleHint": "Mantida como armazenada. Esta entrada não é uma opção da lista acima, portanto só pode ser alterada por meio da API.", "apiManagerCustomRateLimits": "Limites de taxas personalizadas", "apiManagerCustomRateLimitsDesc": "Substitua os limites padrão globais. Deixe em branco para usar os padrões.", @@ -2543,12 +2543,12 @@ "openrouterSttDescription": "Os modelos de reconhecimento de fala do OpenRouter usam sua conexão OpenRouter configurada. Nenhuma credencial separada é necessária." }, "search": { - "searchQuery": "Consulta de pesquisa", + "searchQuery": "Consulta de busca", "searchResults": "Resultados da pesquisa", "cachedResult": "Em cache", "searchCost": "Custo", "searchTools": "Search Tools", - "searchToolsDesc": "Teste de pesquisa avançada com comparação de provedores", + "searchToolsDesc": "Teste avançado de pesquisa com comparação entre provedores", "compareProviders": "Comparar provedores", "rerankResults": "Reclassificar resultados", "searchHistory": "Histórico de pesquisa", @@ -2579,12 +2579,12 @@ "timeRangeMonth": "Último mês", "timeRangeYear": "Último ano", "domainPlaceholder": "example.com", - "requestTimedOut": "A solicitação expirou ({seconds}s)", + "requestTimedOut": "A solicitação excedeu o tempo limite ({seconds}s)", "networkError": "Erro de rede", "formatted": "Formatado", "rawJson": "JSON", - "cacheMiss": "cache não encontrado", - "cacheHit": "cache encontrado", + "cacheMiss": "falha de cache", + "cacheHit": "acerto de cache", "latency": "Latência", "cost": "Custo", "results": "Resultados", @@ -2665,7 +2665,7 @@ "httpError": "Erro {status}", "failed": "Falhou", "requestFailed": "Falha na requisição", - "embedding": "Incorporação", + "embedding": "Embedding", "embeddingSample": "Olá, mundo!", "image": "Imagem", "imageSample": "Uma paisagem serena com montanhas ao pôr do sol", @@ -2744,7 +2744,7 @@ "effortExtraHigh": "Extra alto", "effortMax": "Máximo", "effortUltra": "Ultra", - "wireApi": "API Wire", + "wireApi": "Wire API", "modelAliases": "Aliases de modelo", "addModel": "Adicionar modelo", "routeModelPlaceholder": "Rotear {model} para...", @@ -2811,7 +2811,7 @@ "low": "Baixo", "medium": "Médio", "high": "Alto", - "xhigh": "Muito alta" + "xhigh": "XHigh" }, "howItWorks": "Como funciona:", "antigravityHowWorksDesc": "O Antigravity envia requisições para o endpoint do Google. O MITM intercepta e redireciona para o OmniRoute.", @@ -2909,7 +2909,7 @@ "continue": "Use ao executar Continue em IDEs e você precisar de uma configuração de provedor portátil baseada em JSON.", "opencode": "Use quando preferir execuções de agentes nativos de terminal e automação com script via OpenCode.", "kiro": "Use ao integrar o Kiro e controlar o roteamento do modelo centralmente no OmniRoute.", - "windsurf": "Use quando quiser um IDE com foco em IA e modelos Codeium/Windsurf roteados pelo OmniRoute.", + "windsurf": "Use quando quiser uma IDE com foco em IA e modelos Codeium/Windsurf roteados pelo OmniRoute.", "antigravity": "Use quando o tráfego Antigravity/Kiro deve ser interceptado através do MITM e roteado para OmniRoute.", "copilot": "Use quando desejar UX no estilo de bate-papo do Copilot enquanto impõe chaves OmniRoute e regras de roteamento.", "amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.", @@ -2929,7 +2929,7 @@ "qwen": "CLI Alibaba Qwen Code", "cursor": "Editor de código com IA Cursor", "continue": "Assistente de IA Continue", - "opencode": "Agente de programação OpenCode AI (Terminal)", + "opencode": "Agente de programação com IA OpenCode (Terminal)", "kiro": "Amazon Kiro — IDE com IA", "windsurf": "Windsurf — Editor de Código com IA", "copilot": "GitHub Copilot — Assistente de IA", @@ -3141,7 +3141,7 @@ "hermesRoleGoalJudgeDesc": "Julgamento da conclusão de objetivos", "hermesRoleCurator": "Curador", "hermesRoleCuratorDesc": "Curadoria de habilidades e memória", - "hermesRoleMonitor": "Monitorar", + "hermesRoleMonitor": "Monitor", "hermesRoleMonitorDesc": "Monitoramento em segundo plano", "hermesRoleBackgroundReview": "Revisão em Segundo Plano", "hermesRoleBackgroundReviewDesc": "Revisão de código em segundo plano", @@ -3464,8 +3464,8 @@ "filterIntelligent": "Inteligente", "filterDeterministic": "Determinístico", "filterEmptyTitle": "Nenhum combo corresponde a este filtro de estratégia.", - "filterEmptyIntelligentDescription": "Crie um combo automático ou LKGP para preencher o painel de roteamento inteligente.", - "filterEmptyDeterministicDescription": "No momento, existem apenas combos automáticos e LKGP. Volte para Todos ou crie um combo determinístico.", + "filterEmptyIntelligentDescription": "Crie um combo auto ou LKGP para preencher o painel de roteamento inteligente.", + "filterEmptyDeterministicDescription": "No momento, existem apenas combos auto e LKGP. Volte para Todos ou crie um combo determinístico.", "readinessTitle": "Pronto para salvar?", "readinessDescription": "Revise a lista de verificação antes de criar ou atualizar este combo.", "readinessCheckName": "O nome do combo é válido", @@ -3517,7 +3517,7 @@ "providerScores": "Pontuações dos provedores", "allProvidersEvaluated": "Nenhum conjunto de candidatos configurado. Todos os provedores ativos são avaliados em tempo de execução.", "excludedProviders": "Provedores excluídos", - "excludedProvidersHint": "Provedores com um disjuntor de circuito ABERTO são temporariamente excluídos do roteamento.", + "excludedProvidersHint": "Provedores com um disjuntor no estado OPEN são temporariamente excluídos do roteamento.", "noExcludedProviders": "Nenhum provedor está excluído no momento.", "cooldownMinutes": "Tempo de espera: {minutes} min", "builderIntelligentTitle": "Configuração de roteamento inteligente", @@ -3543,8 +3543,8 @@ "weightLatencyInv": "Latência", "weightTaskFit": "Adequação à tarefa", "weightStability": "Estabilidade", - "weightTierPriority": "Categoria", - "weightCacheAffinity": "Afinidade de acerto no cache", + "weightTierPriority": "Camada", + "weightCacheAffinity": "Afinidade com acertos de cache", "weightSessionAvailability": "Disponibilidade da Sessão", "reviewIntelligentTitle": "Configuração de roteamento inteligente", "strategyRecommendations": { @@ -3667,13 +3667,13 @@ "wizardStep4Desc": "Revise sua configuração e ative a combinação", "usageGuideInvokeTitle": "Como chamar este combo", "usageGuideInvokeDesc": "Envie o nome exato do combo como o modelo, por exemplo, model: \"my-combo\" (ou combo/my-combo).", - "usageGuideInvokeAutoNote": "auto e auto/* são um roteador separado com configuração zero que não usa seus combos (a menos que um combo tenha literalmente o nome auto).", - "usageGuideInvokeOpenrouterNote": "openrouter/auto é um produto pago real da OpenRouter (Auto Best Available), não um alias do OmniRoute — exclua-o em Configurações → Roteamento → Ocultar modelos pagos.", + "usageGuideInvokeAutoNote": "auto e auto/* correspondem a um roteador separado com configuração zero que não usa seus combos (a menos que um combo tenha literalmente o nome auto).", + "usageGuideInvokeOpenrouterNote": "openrouter/auto é um produto real e pago da OpenRouter (Auto Best Available), não um alias do OmniRoute — exclua-o em Configurações → Roteamento → Ocultar modelos pagos.", "emailVisibilityStateOn": "Ativado", "emailVisibilityStateOff": "Desativado", "reorderHandle": "Arraste para reordenar", "failedReorder": "Falha ao reordenar os modelos", - "builderFlowTitle": "Fluxo do Criador de Combos", + "builderFlowTitle": "Fluxo de criação de combos", "builderStage": { "basics": { "label": "Informações básicas", @@ -3688,7 +3688,7 @@ "description": "Comportamento de roteamento e configurações avançadas" }, "intelligent": { - "label": "Roteamento Inteligente", + "label": "Roteamento inteligente", "description": "Conjunto de candidatos, predefinições e pontuação do roteamento automático" }, "review": { @@ -3701,7 +3701,7 @@ "builderStagePending": "Pendente", "builderStageLocked": "Bloqueado — conclua primeiro a etapa anterior", "builderTitle": "Criar um Combo", - "builderBrowseCatalog": "Explorar catálogo", + "builderBrowseCatalog": "Explorar o catálogo", "builderProvider": "Provedor", "builderLoadingProviders": "Carregando provedores...", "builderSelectProvider": "Selecionar provedor", @@ -3712,10 +3712,10 @@ "builderPreview": "Pré-visualização", "builderAddStep": "Adicionar etapa", "builderDuplicateExact": "Esta etapa exata de provedor/modelo/conta já está no combo.", - "builderComboRef": "Referência do Combo", + "builderComboRef": "Referência de combo", "builderAddComboRef": "Adicionar referência de combo", "builderComboRefStep": "Adicionar referência de combo", - "builderPinnedAccount": "Conta Fixada", + "builderPinnedAccount": "Conta fixada", "builderLegacyEntry": "Entrada legada", "reviewName": "Nome", "reviewStrategy": "Estratégia", @@ -3781,7 +3781,7 @@ "errorDescription": "Não conseguimos carregar os dados do combo no momento. Verifique sua conexão e tente novamente.", "errorId": "ID de Erro: {id}", "errorRetry": "Tente Novamente", - "comboLabel": "Combinação", + "comboLabel": "Combo", "duplicateAutoComboConfirm": "Criar uma combinação estática de \"{name}\"?", "duplicateAutoComboSnapshotMsg": "Isso irá capturar os provedores/modelos atualmente conectados que correspondem a este modelo em uma combinação editável.", "duplicateAutoComboFailedPrefix": "Falha ao duplicar a combinação automática:", @@ -3806,8 +3806,8 @@ "rangeAll": "Tudo", "spend30d": "Gasto 30D", "activeModels": "Modelos Ativos", - "flatRateEstimateNotice": "Inclui estimativas de preço por token para assinaturas de valor fixo, não o custo cobrado.", - "flatRateEstimateForecast": "A projeção inclui estimativas de valor fixo.", + "flatRateEstimateNotice": "Inclui estimativas de preço por token para assinaturas de preço fixo, não o custo cobrado.", + "flatRateEstimateForecast": "A projeção inclui estimativas de tarifa fixa.", "selectedWindow": "Janela Selecionada", "activeProviders": "Provedores Ativos", "overviewTitle": "Visão Geral de Custos", @@ -3864,7 +3864,7 @@ "groupAccount": "Conta", "groupServiceTier": "Nível de serviço", "serviceTierFast": "Rápido", - "serviceTierFlex": "Flexível", + "serviceTierFlex": "Flex", "serviceTierStandard": "Padrão", "serviceTierBreakdownTitle": "Nível de Serviço", "serviceTierBreakdownSubtitle": "Divisão Rápido / Flex / Padrão", @@ -3985,7 +3985,7 @@ "chat": "Chat", "embedding": "Embedding", "image": "Imagem", - "custom": "personalizado", + "custom": "custom", "modelsCount": "{count, plural, one {# modelo} other {# modelos}}", "sectionTitle": "Superfície de Integração", "sectionDescription": "APIs compatíveis com OpenAI e endpoints operacionais de protocolos", @@ -4014,7 +4014,7 @@ "a2aQuickStartStep2": "Envie requisições JSON-RPC para `POST /a2a` usando `message/send` ou `message/stream`.", "a2aQuickStartStep3": "Acompanhe e controle tarefas com `tasks/get` e `tasks/cancel`.", "completionsLegacy": "Completions (legado)", - "completionsLegacyDesc": "Completions de texto legadas da OpenAI — aceita tanto uma string de prompt quanto o formato de array de mensagens", + "completionsLegacyDesc": "Completions de texto legadas da OpenAI — aceitam tanto uma string de prompt quanto o formato de array de mensagens", "messagesApi": "Mensagens", "messagesApiDesc": "Formato API de mensagens antrópicas nativas para provedores compatíveis com Claude", "imageEdits": "Edições de imagem", @@ -4055,7 +4055,7 @@ "tailscaleInstallTitle": "Instalar Tailscale", "tailscaleInstallIntro": "Instala o Tailscale nesta máquina e prepara o OmniRoute para habilitar o Funnel.", "tailscaleInstallPasswordHint": "No macOS e no Linux, o sudo pode ser necessário para instalar o pacote e iniciar o daemon.", - "tailscaleSudoPlaceholder": "Senha sudo opcional", + "tailscaleSudoPlaceholder": "Senha opcional do sudo", "tailscaleInstalling": "Instalando", "tailscaleSudoLabel": "Senha Sudo (obrigatória em macOS/Linux)", "ngrokTitle": "Túnel ngrok", @@ -4196,7 +4196,7 @@ "notionTokenConfigured": "Token configurado. As ferramentas do Notion estão disponíveis via MCP." }, "endpoints": { - "tabProxy": "Proxy de endpoint", + "tabProxy": "Endpoint de proxy", "tabApiEndpoints": "Endpoints de API", "apiEndpointsTitle": "Endpoints de API", "apiEndpointsDescription": "Endpoints de API backend que podem ser consumidos por outras aplicações e serviços.", @@ -4475,7 +4475,7 @@ "simulate": "Simular", "resultsTitle": "{count} resultado(s)", "resolutionTitle": "Resolução de busca", - "resolutionEmbedding": "Incorporação", + "resolutionEmbedding": "Embedding", "resolutionStore": "Banco de vetores", "resolutionStrategy": "Estratégia usada", "rerankApplied": "Rerank aplicado", @@ -4570,7 +4570,7 @@ "executionsTab": "Execuções", "selectSkillToInspect": "Selecione uma skill à esquerda para inspecionar.", "schemaTab": "Esquema", - "handlerTab": "Manipulador", + "handlerTab": "Handler", "inputSchema": "Schema de Entrada", "outputSchema": "Schema de Saída", "handlerCode": "Código do Handler", @@ -4578,7 +4578,7 @@ "runTestPlaceholder": "Executar teste (placeholder)", "setModeAria": "Definir modo {mode}", "uninstallSkill": "Desinstalar skill", - "sandboxTab": "Ambiente isolado", + "sandboxTab": "Sandbox", "loading": "Carregando skills...", "noSkills": "Nenhuma skill encontrada", "noExecutions": "Nenhuma execução encontrada", @@ -4608,12 +4608,12 @@ "enabledSkills": "Ativadas", "totalExecutions": "Execuções", "successRate": "Taxa de Sucesso", - "marketplaceTab": "Loja", + "marketplaceTab": "Marketplace", "applyFilters": "Aplicar filtros", "popularDefaultsLabel": "Populares por padrão para o provedor selecionado:", "onMode": "LIGADO", "offMode": "DESLIGADO", - "autoMode": "AUTOMÁTICO", + "autoMode": "AUTO", "installSkillButton": "Instalar Habilidade", "installSkillModalTitle": "Instalar Habilidade", "installJsonPlaceholder": "Cole o JSON do manifesto da habilidade aqui...", @@ -4860,7 +4860,7 @@ "dailyLimit": "Limite Diário" }, "logs": { - "title": "Registros", + "title": "Logs", "requestLogs": "Logs de Requisições", "proxyLogs": "Logs do Proxy", "auditLog": "Log de Auditoria", @@ -4895,7 +4895,7 @@ "requestId": "Request ID", "providerWarningDesc": "Esta requisição terminou com um aviso do provedor ou alerta do sanitizador em vez de uma falha dura.", "a": "A", - "offset": "Deslocamento", + "offset": "Offset", "limit": "Limite", "status": "Status", "resourceType": "Tipo de Recurso", @@ -4921,7 +4921,7 @@ "activeStageSendingToProvider": "Enviando para o upstream", "activeStageProviderResponseStarted": "Resposta do upstream iniciada", "count": "Qtd.", - "payloads": "Cargas úteis", + "payloads": "Payloads", "viewPayloads": "Ver Payloads", "activeCount": "{count} ativas", "clientPayload": "Requisição do Cliente", @@ -5309,7 +5309,7 @@ "openaiCompatibleDetails": "Detalhes Compatível OpenAI", "messagesApi": "API de Mensagens", "responsesApi": "API de Respostas", - "embeddings": "Incorporações", + "embeddings": "Embeddings", "audioTranscriptions": "Transcrições de áudio", "audioSpeech": "Síntese de fala", "imagesGenerations": "Geração de imagens", @@ -5548,8 +5548,8 @@ "ccAliasStateOff": "Desativado", "ccAliasAddModelPlaceholder": "ID do modelo (por exemplo, gpt-4o)", "ccAliasAddModelButton": "Adicionar substituição", - "ccAliasLoadError": "Falha ao carregar as configurações de alias de descoberta: {error}", - "ccAliasSaveError": "Falha ao salvar a configuração de alias de descoberta: {error}", + "ccAliasLoadError": "Falha ao carregar as configurações de discovery-alias: {error}", + "ccAliasSaveError": "Falha ao salvar a configuração de discovery-alias: {error}", "compatUpstreamHeadersLabel": "Cabeçalhos upstream adicionais", "compatUpstreamHeadersHint": "Configuração de alto privilégio — tem o mesmo nível de confiança que a edição das credenciais de API do provedor; somente administradores confiáveis devem usá-la. Mesclada após o OmniRoute adicionar a autenticação usando a chave de API do provedor. Se um cabeçalho personalizado usar o mesmo nome de um existente (por exemplo, Authorization), seu valor substituirá completamente o cabeçalho gerado automaticamente (incluindo o token Bearer) — o upstream verá apenas o que você digitou, não a chave das configurações. Uma configuração incorreta pode causar o erro 401 ou interromper a autenticação upstream. Uma linha por cabeçalho (por exemplo, Authentication adicional para alguns gateways). Passe o cursor sobre o valor ou dê foco a ele para visualizar. Salva ao perder o foco, clicar fora ou fechar este painel.", "compatUpstreamHeaderName": "Nome do cabeçalho", @@ -5800,7 +5800,7 @@ "ccCompatibleRedactThinkingDescription": "Adiciona o cabeçalho beta redact-thinking para upstreams compatíveis com CC que exigem fluxos de raciocínio ocultos do Claude.", "ccCompatibleRedactThinkingLabel": "Ativar a versão beta de redact-thinking", "ccCompatibleSummarizeThinkingDescription": "Adiciona `display: \"summarized\"` às solicitações de raciocínio compatíveis com CC para que os modelos Claude compatíveis transmitam deltas de raciocínio visíveis.", - "ccCompatibleSummarizeThinkingLabel": "Ativar exibição resumida do raciocínio", + "ccCompatibleSummarizeThinkingLabel": "Ativar exibição do raciocínio resumido", "ccCompatibleDetailsTitle": "Detalhes CC Compatível", "ccCompatibleLabel": "CC Compatível", "ccCompatibleModelsDescription": "Os modelos disponíveis de CC Compatível espelham a lista do provedor OAuth Claude Code.", @@ -5843,9 +5843,9 @@ "excludedModelsHint": "Padrões wildcard separados por vírgula. Esta conexão será ignorada para modelos correspondentes.", "excludedModelsLabel": "Modelos Excluídos", "excludedModelsPlaceholder": "gpt-5*, claude-opus-*, gemini-*-pro*", - "expirationBannerExpired": "Banner de expiração: expirado", + "expirationBannerExpired": "Banner de expiração expirado", "expirationBannerExpiredDesc": "Ação imediata necessária. Conexões expiradas falharão permanentemente.", - "expirationBannerExpiringSoon": "Banner de expiração: expira em breve", + "expirationBannerExpiringSoon": "Banner de expiração prestes a expirar", "expirationBannerExpiringSoonDesc": "Revise e renove as conexões prestes a expirar para evitar interrupções.", "extraApiKeyMasked": "Chave {index}: {prefix}••••{suffix}", "extraApiKeysHint": "Rotação round-robin — opcional", @@ -5908,7 +5908,7 @@ "rateLimitOverridesSection": "Substituições de Limite de Taxa", "rateLimitOverridesMaxConcurrentHint": "Sobrescrição do máximo de requisições concorrentes para esta conexão. Sobrescreve o limite de nível de conta.", "rateLimitOverridesMaxConcurrentLabel": "Máximo Concorrente (Limite de Taxa)", - "rateLimitOverridesMaxWaitMsHint": "Tempo máximo (ms) que uma solicitação pode aguardar por uma vaga no limite de taxa antes de falhar. Substitui a configuração global de Resiliência somente para esta conexão.", + "rateLimitOverridesMaxWaitMsHint": "Tempo máximo (ms) que uma solicitação pode aguardar por uma vaga de limitação de taxa antes de falhar. Substitui a configuração global de Resiliência somente para esta conexão.", "rateLimitOverridesMaxWaitMsLabel": "Espera máxima na fila (ms)", "rateLimitOverridesMinTimeHint": "Tempo mínimo (ms) entre solicitações. Substitui o atraso padrão do limitador de taxa.", "rateLimitOverridesMinTimeLabel": "Min Intervalo (ms)", @@ -5943,7 +5943,7 @@ "snowflakeBaseUrlHint": "Obrigatório: cole a URL base da conta Snowflake. O app adicionará /api/v2/cortex/inference:complete.", "supportedEndpointAudio": "Áudio", "supportedEndpointChat": "Chat", - "supportedEndpointEmbeddings": "Incorporações", + "supportedEndpointEmbeddings": "Embeddings", "supportedEndpointImages": "Imagens", "supportedEndpointsLabel": "Endpoints Suportados", "tagGroupHint": "Usado para agrupar contas na visão do provedor.", @@ -6126,7 +6126,7 @@ "agentrouter": "Obtenha $200 em créditos gratuitos em https://agentrouter.org/register — sem necessidade de cartão de crédito.", "unorouter": "Crie uma chave de API em https://unorouter.ai e cole aqui como Bearer token.", "agnes": "Obtenha a chave de API em agnes-ai.com", - "agnes-cn": "Connect Agnes AI (China) with an API key issued for api.agnes-ai.cn. Keys from apihub.agnes-ai.com will not work.", + "agnes-cn": "Conecte a Agnes AI (China) com uma chave de API emitida para api.agnes-ai.cn. As chaves de apihub.agnes-ai.com não funcionarão.", "aimlapi": "Nível gratuito pausado (2026) — a AI/ML API agora é apenas pay-as-you-go (recarga mínima de $20); sem créditos gratuitos recorrentes.", "ai21": "$10 em créditos de teste no cadastro (válidos por 3 meses), sem necessidade de cartão de crédito", "alibaba": "Conecte a Alibaba com uma chave de API.", @@ -6314,7 +6314,7 @@ "claude": "Conecte o Claude Code com o fluxo OAuth existente.", "cline": "Conecte o Cline com o fluxo OAuth existente.", "cursor": "Conecte o Cursor IDE com o fluxo OAuth existente.", - "cursor-api": "Conecte o Cursor com uma chave de API de usuário (crsr_...) de cursor.com/dashboard/api; nenhuma sessão do IDE é necessária.", + "cursor-api": "Conecte o Cursor com uma chave de API de usuário (crsr_...) obtida em cursor.com/dashboard/api; não é necessária uma sessão da IDE.", "github": "Conecte o GitHub Copilot com o fluxo OAuth existente.", "gitlab-duo": "Aplicação OAuth com os escopos ai_features + read_user. Configure GITLAB_DUO_OAUTH_CLIENT_ID e, opcionalmente, GITLAB_DUO_OAUTH_CLIENT_SECRET nesta instância do OmniRoute.", "kilocode": "Conecte o Kilo Code com o fluxo OAuth existente.", @@ -6638,7 +6638,7 @@ "modelsDevInfo": "Como funciona", "modelsDevInfoDesc": "models.dev é um banco de dados de código aberto com especificações de modelos de IA, mantido pela equipe do SST/OpenCode. Ele fornece preços, recursos, limites de contexto e modalidades para mais de 4.000 modelos de mais de 100 provedores.", "modelsDevInfoResolution": "Ordem de resolução de preços (da maior para a menor prioridade):", - "modelsDevInfoOrder": "Substituição do usuário → models.dev → LiteLLM → Padrão codificado", + "modelsDevInfoOrder": "Substituição do usuário → models.dev → LiteLLM → Padrão definido no código", "systemTheme": "Tema do Sistema", "debugToggle": "Ativar modo de depuração", "logToolSourcesToggle": "Registrar fontes de ferramentas", @@ -6683,7 +6683,7 @@ "ttlMinutes": "TTL (minutos)", "promptCache": "Cache de Prompt", "strategy": "Estratégia", - "preserveClientCache": "Preservar cache do cliente", + "preserveClientCache": "Preservar o cache do cliente", "enabled": "Ativado", "loading": "Loading...", "saving": "Salvando...", @@ -6700,11 +6700,11 @@ "autoDisableThreshold": "Limite de banimento", "autoDisableThresholdDesc": "Quantidade de sinais consecutivos de banimento antes da desativação permanente.", "autoDisableBannedScope": "Aplicar desativação automática a", - "autoDisableBannedScopeDesc": "As vagas de login (assinaturas pagas e contas gratuitas) podem ser bloqueadas pelo provedor upstream se o OmniRoute continuar tentando novamente após um banimento. Chaves de API pré-pagas não apresentam esse risco — mantenha-as no pool para que o preenchimento prioritário possa usar a próxima chave.", + "autoDisableBannedScopeDesc": "As contas de login (assinaturas pagas e contas gratuitas) podem ser bloqueadas pelo provedor upstream se o OmniRoute continuar tentando após um banimento. Chaves de API pré-pagas não apresentam esse risco — mantenha-as no pool para que o preenchimento prioritário possa usar a próxima chave.", "autoDisableBannedScopeAll": "Todas as conexões", "autoDisableBannedScopeAllDesc": "Desative todas as conexões que retornarem um sinal de banimento permanente, incluindo chaves de API pré-pagas.", "autoDisableBannedScopeSubscription": "Somente contas de login/assinatura", - "autoDisableBannedScopeSubscriptionDesc": "Desative vagas OAuth, de cookie, de sessão e de login pela web (pagas ou gratuitas). As chaves de API pré-pagas permanecem ativas para permitir o failover do roteamento.", + "autoDisableBannedScopeSubscriptionDesc": "Desative contas OAuth, de cookie, de sessão e de login pela web (pagas ou gratuitas). As chaves de API pré-pagas permanecem ativas para permitir o failover do roteamento.", "customBannedSignals": "Palavras-Chave Proibidas", "customBannedSignalsDesc": "Palavras-chave adicionais que acionam a detecção de banimento permanente da conta. Palavras-chave integradas sempre se aplicam.", "customBannedSignalsPlaceholder": "chave da API revogada", @@ -6768,7 +6768,7 @@ "presetMinimalDesc": "Apenas páginas principais", "presetDeveloper": "Desenvolvedor", "presetDeveloperDesc": "Ferramentas de dev & proxy", - "presetAdmin": "Administração", + "presetAdmin": "Administrador", "presetAdminDesc": "Monitoramento & auditoria", "settingsSidebarTitle": "Personalização da Barra Lateral", "settingsSidebarDesc": "Escolha quais itens da barra lateral exibir. Essenciais mantém as ferramentas avançadas pesquisáveis.", @@ -7097,10 +7097,10 @@ "quotaPreflightTitle": "Limite de verificação prévia de cota", "quotaPreflightSubtitle": "Ignore contas sem cota antes de enviar a solicitação ao upstream", "quotaPreflightEnable": "Ativar verificação prévia de cota", - "quotaPreflightEnableDesc": "Antes de cada solicitação, verifique a cota restante da conta (janelas de 5 h/semanais/mensais). Contas no limite ou abaixo dele são ignoradas sem uma chamada ao upstream. Os limites por janela definidos na página Cota do provedor sempre prevalecem sobre o padrão definido aqui.", - "quotaPreflightCutoff": "Limite global (mín. restante em %)", + "quotaPreflightEnableDesc": "Antes de cada solicitação, verifique a cota restante da conta (janelas de 5 h, semanais e mensais). Contas no limite ou abaixo dele são ignoradas sem uma chamada ao upstream. Os limites por janela definidos na página Cota do provedor sempre prevalecem sobre o padrão definido aqui.", + "quotaPreflightCutoff": "Limite global (mín. de cota restante em %)", "quotaPreflightCutoffDesc": "Pare de usar uma conta quando a cota restante cair para esta porcentagem ou abaixo dela, em todas as janelas sem um limite próprio. Corresponde às barras de porcentagem restante na página Cota do provedor.", - "quotaPreflightWindowsHint": "As janelas com limite próprio por janela ou por provedor (definido na janela modal Limite da página Cota do provedor) não são afetadas por este padrão.", + "quotaPreflightWindowsHint": "As janelas com limite próprio por janela ou por provedor (definido na janela modal Limite da página Cota do provedor) não são afetadas por esse padrão.", "quotaPreflightLoadFailed": "Não foi possível carregar as configurações de verificação prévia de cota.", "modelAliases": "Aliases de Modelo", "modelAliasesTitle": "Aliases de Modelo", @@ -7334,7 +7334,7 @@ "configured": "configurado", "none": "Nenhum", "modelOverrideValuePlaceholder": "Valor numérico", - "modelOverrideReasoningEffortsPlaceholder": "Separado por vírgulas em inglês, por exemplo: low, medium, high", + "modelOverrideReasoningEffortsPlaceholder": "Valores em inglês separados por vírgulas, por exemplo: low, medium, high", "addKeyValue": "Adicionar valor da chave", "noModelOverrides": "Nenhuma substituição configurada para este modelo.", "modelOverrideLoadFailed": "Falha ao carregar substituições de modelo", @@ -7538,26 +7538,26 @@ "compressionExclusionsCount": "{count, plural, one {# exclusão configurada} other {# exclusões configuradas}}", "compressionExclusionsEmpty": "Nenhuma exclusão configurada — todo modelo/endpoint é elegível para compressão (comportamento padrão).", "compressionCavemanConfig": "Configuração do mecanismo Caveman", - "compressionCavemanConfigDesc": "Ajuste detalhado do mecanismo de compactação baseado em regras", + "compressionCavemanConfigDesc": "Ajuste fino do mecanismo de compactação baseado em regras", "compressionCavemanPanelHint": "Ativado/desativado e o nível são definidos no painel:", - "compressionRoles": "Compactar funções das mensagens", + "compressionRoles": "Compactar papéis das mensagens", "compressionRoleUser": "Usuário", "compressionRoleAssistant": "Assistente", "compressionRoleSystem": "Sistema", "compressionMinLength": "Tamanho mínimo da mensagem", "compressionSkipRules": "Ignorar regras de compactação", - "compressionSkipRulesDesc": "Clique nas regras para ignorá-las durante a compressão", + "compressionSkipRulesDesc": "Clique nas regras para ignorá-las durante a compactação", "compressionPreservePatterns": "Preservar padrões", - "compressionPreservePatternsDesc": "Padrões de regex que nunca são comprimidos (um por linha)", - "compressionLogTitle": "Log de compressão", - "compressionLogEmpty": "Ainda não há solicitações comprimidas. As estatísticas de compressão aparecerão aqui quando as solicitações forem processadas com a compressão ativada.", + "compressionPreservePatternsDesc": "Padrões de regex que nunca são compactados (um por linha)", + "compressionLogTitle": "Log de compactação", + "compressionLogEmpty": "Ainda não há solicitações compactadas. As estatísticas de compactação aparecerão aqui quando as solicitações forem processadas com a compactação ativada.", "minutes": "minutos", "compressionModeRtk": "RTK", "compressionModeRtkDesc": "Filtragem de saída de ferramentas sensível a comandos", "compressionModeCodexResponses": "Saída de ferramenta do Responses", "compressionModeCodexResponsesDesc": "Compressão conservadora para saídas elegíveis de shell, patch, busca, build e JSON", "compressionModeStacked": "Empilhado", - "compressionModeStackedDesc": "Filtragem de saída de ferramentas do RTK seguida pela compressão de mensagens do Caveman", + "compressionModeStackedDesc": "Filtragem de saída de ferramentas do RTK seguida pela compactação de mensagens do Caveman", "qdrantTitle": "Qdrant (Memoria vetorial)", "qdrantDesc": "Opcional. Indexa memorias semanticas em um banco vetorial externo para busca mais rapida.", "qdrantStatusActive": "Ativo", @@ -7605,7 +7605,7 @@ "resilienceScope": "Escopo:", "resilienceTrigger": "Gatilho:", "resilienceEffect": "Efeito:", - "resilienceRequestQueueTitle": "Fila e limite de solicitações", + "resilienceRequestQueueTitle": "Fila e taxa de solicitações", "resilienceAutoEnableApiKeyProviders": "Ativar automaticamente para provedores com chave de API", "resilienceRequestsPerMinute": "Solicitações por minuto", "resilienceMinTimeBetweenRequests": "Tempo mínimo entre solicitações", @@ -7705,7 +7705,7 @@ "visionBridgePromptHint": "Enviado ao modelo de visão antes que a descrição extraída seja injetada de volta na solicitação original.", "visionBridgeTimeoutMs": "Tempo limite (ms)", "visionBridgeMaxImagesPerRequest": "Máximo de imagens por solicitação", - "resilienceMaxBackoffSteps": "Máximo de etapas de recuo", + "resilienceMaxBackoffSteps": "Máximo de etapas de espera progressiva", "resilienceProviderBreakerTitle": "Disjuntor por provedor", "resilienceFailureThreshold": "Limite de falhas", "resilienceDegradationThreshold": "Limite de degradação", @@ -7766,7 +7766,7 @@ "retentionMcpAudit": "Auditoria MCP (dias)", "retentionA2aEvents": "Eventos A2A (dias)", "retentionCallLogs": "Registros de chamadas (dias)", - "retentionConversationTurnNodes": "Nós de turno de conversa (dias)", + "retentionConversationTurnNodes": "Nós de turnos de conversa (dias)", "retentionUsageHistory": "Histórico de uso (dias)", "retentionMemoryEntries": "Entradas de memória (dias)", "retentionXpAuditLog": "Log de Auditoria de XP (dias)", @@ -7838,9 +7838,9 @@ "compressionDerivedMode": "modo: {mode}", "compressionAdaptiveOff": "Orçamento de contexto adaptativo: desligado (auto-gatilho legado)", "compressionAdaptiveTarget": "Adaptativo ({mode}, política: {policy}) — alvo ≈ {target, number} tokens (para uma janela de {contextLimit, number} tokens)", - "compressionAdaptiveMode": "Orçamento de contexto adaptativo", + "compressionAdaptiveMode": "Orçamento adaptativo de contexto", "compressionAdaptiveModeOff": "Desativado (acionamento automático legado)", - "compressionAdaptiveModeFloor": "Mínimo (sempre garantir o ajuste)", + "compressionAdaptiveModeFloor": "Mínimo (sempre garantir que caiba)", "compressionAdaptiveModeReplace": "Substituir acionamento automático", "compressionAdaptivePolicy": "Política de orçamento", "compressionAdaptivePolicyReserve": "Reservar saída", @@ -7941,10 +7941,10 @@ "cliproxyapiFallback": "substituto CLIProxyAPI", "cliproxyapiEnableFallback": "Habilitar substituto CLIProxyAPI", "cliproxyapiUrl": "URL CLIProxyAPI", - "headroomProxyTitle": "Proxy Headroom", + "headroomProxyTitle": "Proxy do Headroom", "headroomProxyDesc": "URL do proxy opcional Headroom para economizar tokens. Se estiver vazio, usa HEADROOM_URL ou http://localhost:8787.", "headroomProxyUrl": "URL do Headroom", - "headroomProxyUrlHint": "URLs de loopback podem ser iniciadas nesta página. URLs externas são apenas sondadas.", + "headroomProxyUrlHint": "URLs de loopback podem ser iniciadas nesta página. URLs externas são apenas verificadas.", "headroomProxySave": "Salvar", "headroomProxyStart": "Iniciar", "headroomProxyStop": "Parar", @@ -8008,7 +8008,7 @@ "codexFastTierHint": "Quando habilitado, o OmniRoute adiciona service_tier=priority às solicitações de saída do Codex para conexões que ainda não especificam uma camada. O nível de prioridade requer uma chave de API OpenAI Enterprise ou o caminho ChatGPT-auth Codex; outros tipos de chave receberão um erro relacionado ao nível do OpenAI. As configurações por conexão na página do provedor Codex têm precedência.", "codexFastTierSaveError": "Falha ao atualizar a configuração do Codex Fast Tier", "codexAutoPingTitle": "Ping automático da cota do Codex", - "codexAutoPingDesc": "Ativação por conexão: envia uma pequena solicitação logo após a redefinição da janela de sessão do Codex, para que ela não esteja fria quando você precisar usá-la.", + "codexAutoPingDesc": "Ativação por conexão: envia uma pequena solicitação logo após a redefinição da janela de sessão do Codex, para que a sessão não esteja inativa quando você precisar dela.", "codexAutoPingWarning": "Consome uma pequena quantidade da cota real do Codex a cada ping. Desativado por padrão — ative apenas para conexões das quais você depende ativamente.", "codexAutoPingSaveError": "Falha ao atualizar a configuração de ping automático do Codex", "codexAutoPingToggleAria": "Alternar o ping automático da cota do Codex para {connection}", @@ -8225,7 +8225,7 @@ "reasoningTokenBuffer": "Buffer de tokens de raciocínio", "reasoningTokenBufferDesc": "Permita que o roteamento combinado adicione uma margem a max_tokens somente para modelos de raciocínio conhecidos quando o buffer completo couber em um limite de saída conhecido.", "zeroLatencyOptimizations": "Otimizações de latência zero", - "zeroLatencyOptimizationsDesc": "Ative hedging, pulos preditivos de TTFT e compressão proativa de fallback. Deixe desativado para impedir que esses recursos de latência disputem com os destinos ou comprimam solicitações de fallback.", + "zeroLatencyOptimizationsDesc": "Ative hedging, saltos preditivos de TTFT e compressão proativa de fallback. Deixe desativado para impedir que esses recursos de latência façam os destinos competirem entre si ou comprimam solicitações de fallback.", "credentialRedaction": "Redação de Credenciais", "credentialRedactionDesc": "Redija chaves de API, tokens e segredos do contexto enviado para provedores e das respostas.", "enableCredentialRedaction": "Ativar a ocultação de credenciais", @@ -8491,7 +8491,7 @@ "savings": "Economia", "original": "Original", "compressed": "Comprimido", - "diff": "Diferença", + "diff": "Diferenças", "last7Days": "Últimos 7 dias", "noDataYet": "Sem dados ainda", "runs": "Execuções", @@ -8698,7 +8698,7 @@ "pauseReplay": "Pausar replay", "playReplay": "Reproduzir replay", "pause": "Pausar", - "replay": "Repetição", + "replay": "Reexecução", "resetReplay": "Redefinir replay", "stepProgress": "etapa {current}/{total}", "inlineView": "Em linha", @@ -8917,7 +8917,7 @@ "advancedConfigDesc": "Ajustar o comportamento da compactação", "aggressiveSettings": "Configurações agressivas", "aggressiveSettingsDesc": "Compressão máxima com potenciais compensações de qualidade", - "requests": "Solicitações", + "requests": "Requisições", "tokensSaved": "Tokens salvos", "savingsPercent": "Economia %", "avgLatency": "Latencia media", @@ -8997,7 +8997,7 @@ "ideCliIntegrations": "Integrações IDE/CLI", "inMemoryNote": "Nota: os eventos são armazenados em memória e reiniciados quando o servidor reinicia.", "ok": "OK", - "errorShort": "ERRO", + "errorShort": "ERR", "formatConverter": "Conversor de Formato", "formatConverterDescription": "Cole ou digite um corpo de requisição JSON. O tradutor detectará automaticamente o formato de origem e converterá para o formato de destino. Use isso para depurar como o OmniRoute traduz requisições entre formatos (OpenAI ↔ Claude ↔ Gemini ↔ Responses API).", "translationPathHubSpoke": "{source} → OpenAI (intermediário) → {target}", @@ -9049,14 +9049,14 @@ "scenarioMultiTurn": "Multiturno", "scenarioThinking": "Raciocínio", "scenarioSystemPrompt": "Prompt de Sistema", - "scenarioStreaming": "Transmissão", + "scenarioStreaming": "Streaming", "templateNames": { "simple-chat": "Chat Simples", "tool-calling": "Chamada de Ferramenta", "multi-turn": "Multiturno", "thinking": "Raciocínio", "system-prompt": "Prompt de Sistema", - "streaming": "Transmissão", + "streaming": "Streaming", "vision": "Visão", "schema-coercion": "Coerção de esquema" }, @@ -9297,7 +9297,7 @@ "kimiMonthlyLimit": "Limite mensal", "kimiMonthlyLimitUnlimited": "Ilimitado", "kimiAdditionalCredits": "Créditos adicionais", - "loggerTab": "Registrador", + "loggerTab": "Logger", "proxyTab": "Proxy", "budgetManagement": "Gerenciamento de Orçamento", "budgetSaved": "Limites de orçamento salvos", @@ -9346,7 +9346,7 @@ "evaluateStepDescription": "As respostas são comparadas com os critérios esperados. Veja aprovado/reprovado por caso com métricas de latência e feedback detalhado.", "evalsStrategyContainsLabel": "Contém", "evalsStrategyExactLabel": "Correspondência Exata", - "evalsStrategyRegexLabel": "Expressão regular", + "evalsStrategyRegexLabel": "Regex", "evalsStrategyCustomLabel": "Lógica Personalizada", "evalsStrategyContainsDescription": "Verifica se a saída do LLM contém a substring esperada.", "evalsStrategyExactDescription": "Verifica se a saída do LLM corresponde exatamente ao valor esperado.", @@ -9468,9 +9468,9 @@ "noQuotaDataAvailable": "Nenhum dado de cota disponível", "noAccountsForTierFilter": "Nenhuma conta encontrada para o filtro de plano", "tierAll": "Todos", - "tierEnterprise": "Empresarial", + "tierEnterprise": "Corporativo", "tierTeam": "Equipe", - "tierBusiness": "Negócios", + "tierBusiness": "Empresarial", "tierUltra": "Ultra", "tierPro": "Pro", "tierPlus": "Plus", @@ -9561,7 +9561,7 @@ "suiteBuilderDeleteConfirm": "Tem certeza de que deseja excluir esta suíte? Esta ação não pode ser desfeita.", "suiteBuilderNameLabel": "Nome da Suíte", "scorecardSuites": "Suítes", - "evalCompareTarget": "Comparar destino", + "evalCompareTarget": "Alvo de comparação", "suiteBuilderCaseModelPlaceholder": "gpt-4o-mini", "evalControlsHint": "Escolha um modelo ou combo, compare opcionalmente dois alvos e persista o histórico no SQLite.", "recentRunsTitle": "Execuções recentes", @@ -9574,7 +9574,7 @@ "suiteBuilderNamePlaceholder": "Suíte de regressão de suporte ao cliente", "suiteBuilderAddCase": "Adicionar caso", "cancel": "Cancelar", - "evalTarget": "Destino", + "evalTarget": "Alvo", "suiteBuilderCaseNameLabel": "Nome do Caso", "weeklyLimitUsd": "Limite semanal (USD)", "suiteBuilderCaseUserPromptPlaceholder": "Escreva a entrada exata do usuário para repetir durante o eval.", @@ -9603,7 +9603,7 @@ "suiteBuilderCaseTagsPlaceholder": "support, regression, billing", "targetTypeCombo": "Combinado", "scorecardPassed": "Aprovado", - "suiteBuilderCaseTagsLabel": "Etiquetas", + "suiteBuilderCaseTagsLabel": "Tags", "suiteBuilderNewSuite": "Nova suíte", "notifyEvalLoadFailed": "Falha ao carregar os dados do dashboard de evals", "notConfigured": "Não configurado", @@ -9617,7 +9617,7 @@ "edit": "Editar", "monthly": "Mensal", "suiteBuilderCasesRequired": "Adicione pelo menos um caso antes de salvar", - "weeklyLimitSummary": "Resumo do limite do orçamento semanal", + "weeklyLimitSummary": "Resumo do limite de orçamento semanal", "suiteBuilderDescriptionLabel": "Descrição", "targetComparisonHint": "A mesma suíte foi executada contra múltiplos alvos.", "compareCompletedWithScore": "Comparação concluída. A execução principal terminou com {score}% de aprovação", @@ -10127,7 +10127,7 @@ "clientCursorBullet1Suffix": "para modelos do Cursor.", "clientCursorBullet2": "Conexão OAuth - faça login na página de Provedores.", "clientClaudeTitle": "Claude Code / Antigravity", - "clientClaudeBullet1Prefix": "Uso", + "clientClaudeBullet1Prefix": "Use", "clientClaudeBullet1Middle": "(Claude) ou", "clientClaudeBullet1Suffix": "(Antigravity) como prefixo.", "clientWindsurfTitle": "Windsurf", @@ -10564,7 +10564,7 @@ "performance": "Desempenho do cache", "autoRefresh": "Atualização automática a cada {seconds}s", "hits": "Acertos", - "misses": "Erros", + "misses": "Falhas", "total": "Total", "behavior": "Comportamento do cache", "behaviorDeterministic": "Somente solicitações sem streaming com temperature=0 são armazenadas em cache.", @@ -10603,7 +10603,7 @@ "peakCacheRate": "Taxa máxima de cache", "trendHour": "Hora", "activityVolume": "Atividade", - "requestsShort": "solicitações", + "requestsShort": "solic.", "inputShort": "Entrada", "cachedShort": "Em cache", "writeShort": "Gravação", @@ -10617,7 +10617,7 @@ "cachedTokensCol": "Cached", "cacheCreation": "Creation", "trend24h": "Tendência do cache (24 h)", - "peakCached": "Pico em cache", + "peakCached": "Pico armazenado em cache", "cached": "Em cache", "overview": "Visão geral", "tableProvider": "Provedor", @@ -10688,7 +10688,7 @@ "levelKey": "Chave", "levelDirect": "Direto (sem proxy)", "titleGlobal": "Configuração de proxy global", - "titleLevel": "Proxy {level} — {label}", + "titleLevel": "{level} Proxy — {label}", "loading": "Carregando configuração de proxy...", "inheritingFrom": "Herdando de", "source": "Fonte", @@ -10808,7 +10808,7 @@ "readingFromCursor": "Lendo do Cursor IDE ou cursor-agent", "tokensAutoDetected": "Tokens detectados automaticamente com sucesso no Cursor IDE!", "cursorNotDetected": "Cursor IDE não detectado. Cole manualmente seu token.", - "dockerImportHint": "Está usando o Docker? Prefira Entrar com o Cursor. A importação automática do IDE e o cursor-agent geralmente não estão disponíveis dentro do contêiner. Consulte docs/providers/CURSOR-DOCKER.md.", + "dockerImportHint": "Está usando Docker? Prefira Entrar com o Cursor. A importação automática da IDE e o cursor-agent geralmente não estão disponíveis dentro do contêiner. Consulte docs/providers/CURSOR-DOCKER.md.", "accessToken": "Token de acesso", "refreshToken": "Token de atualização", "refreshTokenPlaceholder": "Opcional — permite a atualização automática do token", @@ -10971,9 +10971,9 @@ "strategyLatency": "Otimizado por latência", "poolMembersLabel": "Membros do pool ({count})", "poolNoMembers": "Ainda não há proxies neste pool.", - "poolEgressObservation": "{exits, plural, one {# saída} other {# saídas}} usada(s) por {connections, plural, one {# conexão} other {# conexões}} · até {max} em uma saída · últimas {hours} h", + "poolEgressObservation": "Uso de {exits, plural, one {# saída} other {# saídas}} por {connections, plural, one {# conexão} other {# conexões}} · até {max} em uma única saída · últimas {hours} h", "poolEgressObservationEmpty": "Nenhum tráfego observado nas últimas {hours} h", - "poolEgressObservationHint": "Contado a partir do log de proxy na janela: IPs de saída distintos observados para os membros deste pool e as conexões do OmniRoute que os usaram. \"Em uma saída\" significa ao longo da janela, não ao mesmo tempo. O bloqueio de 429 por IP só pausa conexões da mesma família de provedor.", + "poolEgressObservationHint": "Contabilizado com base no log do proxy durante o período: IPs de saída distintos observados para os membros deste pool e as conexões do OmniRoute que os utilizaram. \"Em uma saída\" significa ao longo do período, não ao mesmo tempo. O bloqueio por IP após um erro 429 pausa apenas as conexões da mesma família de provedores.", "poolRemove": "Remover", "poolRemoveFailed": "Falha ao remover o proxy do pool", "poolAddLabel": "Adicionar um proxy", @@ -11013,7 +11013,7 @@ "endpointOptions": { "chat": "Chat", "responses": "Respostas", - "completions": "Conclusões", + "completions": "Completações", "images": "Images", "embeddings": "Embeddings", "speech": "Speech", @@ -11238,7 +11238,7 @@ "textToSpeech": "Texto para Fala", "videoSample": "Um avião de papel voando sobre uma cidade futurista", "video": "Vídeo", - "webFetch": "Busca na Web", + "webFetch": "Captura da Web", "webSearchSample": "O que é o OmniRoute?", "webSearch": "Pesquisa na Web", "documentUrl": "URL do Documento", @@ -12067,10 +12067,10 @@ "resumeBtn": "Retomar", "clearBtn": "Limpar", "exportHar": "Exportar .har", - "recordSession": "GRAV", + "recordSession": "REC", "stopSession": "Parar", "liveBadge": "ao vivo", - "offlineBadge": "off-line", + "offlineBadge": "offline", "noRequests": "Nenhuma requisição capturada ainda.", "noRequestsDesc": "Certifique-se que o AgentBridge está ativo ou ative outro modo de captura.", "selectRequest": "Selecione uma requisição para inspecioná-la.", @@ -12158,7 +12158,7 @@ "systemPromptHidden": "Prompt de sistema oculto — clique para expandir", "sessionName": "Sessão {id}", "sessions": "Sessões", - "requestCountShort": "{count} solicitações", + "requestCountShort": "{count} reqs", "deleteSession": "Excluir sessão", "saving": "Salvando…", "sensitiveHeaders": "Cabeçalhos sensíveis", @@ -13303,7 +13303,7 @@ "allModels": "todos os modelos", "keepModel": "Manter modelo", "otherModel": "Outro modelo", - "combo": "Combinação", + "combo": "Combo", "priorityShort": "prioridade {value}", "toggleAria": "Ativar {name}", "edit": "Editar", @@ -13321,7 +13321,7 @@ "missing": "Não especificado", "signalOnly": "Sinal de raciocínio não discreto", "requestTags": "Tags da solicitação", - "requestTagsExample": "programação, interno", + "requestTagsExample": "coding, internal", "tagMode": "Correspondência de tags", "effortMode": "Modo de esforço", "targetEffort": "Esforço de destino", @@ -13345,7 +13345,7 @@ "scope": { "global": "Global", "apiKey": "Chave de API", - "combo": "Combinação", + "combo": "Combo", "model": "Modelo", "connection": "Conexão" }, diff --git a/tests/unit/i18n-review-locale.test.ts b/tests/unit/i18n-review-locale.test.ts new file mode 100644 index 0000000000..6c64a5246e --- /dev/null +++ b/tests/unit/i18n-review-locale.test.ts @@ -0,0 +1,24 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { changedLeaves, parseReviewResponse } from "../../scripts/i18n/review-locale.mjs"; + +test("changedLeaves lists new and rewritten leaves only", () => { + const before = { "a.x": "Save", "a.y": "Salvar", "a.z": "Old" }; + const after = { "a.x": "Salvar", "a.y": "Salvar", "a.z": "Novo", "a.w": "Novo também" }; + assert.deepEqual(changedLeaves(before, after), { + "a.x": "Salvar", + "a.z": "Novo", + "a.w": "Novo também", + }); +}); + +test("parseReviewResponse keeps only real corrections for known ids", () => { + const text = 'Here you go:\n{"a.x": "OK", "a.z": "Novo (corrigido)", "ghost": "x", "a.w": ""}'; + assert.deepEqual(parseReviewResponse(text, ["a.x", "a.z", "a.w"]), { + "a.z": "Novo (corrigido)", + }); +}); + +test("parseReviewResponse tolerates a fenced JSON block", () => { + assert.deepEqual(parseReviewResponse('```json\n{"k":"v"}\n```', ["k"]), { k: "v" }); +});