mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 20:02:45 +03:00
fix(i18n): retranslate the verbatim-English leaves in all 65 catalogs; ratio gate now blocking (#13782)
PR-4 of the locale-expansion plan. 215,363 strings retranslated across the 65 catalogs with the new `sync-ui-keys --retranslate-identical`; the share of leaves still identical to English drops from a mean of 18.3 % to 1.8 % (Spanish 56 → 2.1). No `__MISSING__` marker or missing key is left; zh glossary normalised; pinned product/flag names kept English and allowlisted. Baseline tightened and the CI step `i18n real-translation ratio` is blocking from here on. ⚠️ base-red inherited: #12732
This commit is contained in:
committed by
GitHub
parent
8f55d85d22
commit
cde49c9372
@@ -93,12 +93,14 @@ function parseArgs(argv) {
|
||||
locales: null,
|
||||
dryRun: false,
|
||||
translateMarkers: false,
|
||||
retranslateIdentical: false,
|
||||
concurrency: null,
|
||||
batchSize: 1,
|
||||
};
|
||||
for (const arg of argv.slice(2)) {
|
||||
if (arg === "--dry-run" || arg === "--dryrun") opts.dryRun = true;
|
||||
else if (arg === "--translate-markers") opts.translateMarkers = true;
|
||||
else if (arg === "--retranslate-identical") opts.retranslateIdentical = true;
|
||||
else if (arg.startsWith("--locale=")) {
|
||||
opts.locales = arg
|
||||
.slice(9)
|
||||
@@ -123,6 +125,9 @@ function parseArgs(argv) {
|
||||
"Usage: node scripts/i18n/sync-ui-keys.mjs [options]",
|
||||
"",
|
||||
" --locale=<csv> Target locales (default: all except `en`)",
|
||||
" --retranslate-identical Flag leaves still identical to English (outside",
|
||||
" untranslatable-keys.json) as __MISSING__ so they get",
|
||||
" retranslated — only meaningful with --translate-markers",
|
||||
" --dry-run Report what would change, write nothing",
|
||||
" --translate-markers Call the translation backend to translate every",
|
||||
" __MISSING__:<en> placeholder",
|
||||
@@ -156,6 +161,34 @@ function isPlainObject(value) {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* PR-4 of the locale expansion: a leaf whose value is byte-identical to the English source is
|
||||
* an untranslated copy unless `untranslatable-keys.json` says otherwise. Swap each one for a
|
||||
* `__MISSING__:<en>` placeholder so `translatePlaceholders` picks it up, and return how many
|
||||
* were flagged. Existing placeholders, real translations, empty strings and shape mismatches
|
||||
* are left untouched. Mutates `merged` in place.
|
||||
*/
|
||||
export function markIdenticalAsMissing(merged, source, untranslatable, prefix = "") {
|
||||
let count = 0;
|
||||
for (const [key, sourceValue] of Object.entries(source)) {
|
||||
if (FORBIDDEN_KEYS.has(key)) continue;
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
const targetValue = merged[key];
|
||||
if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
|
||||
count += markIdenticalAsMissing(targetValue, sourceValue, untranslatable, fullKey);
|
||||
} else if (
|
||||
typeof sourceValue === "string" &&
|
||||
sourceValue !== "" &&
|
||||
targetValue === sourceValue &&
|
||||
!untranslatable.has(fullKey)
|
||||
) {
|
||||
merged[key] = `${PLACEHOLDER_PREFIX}${sourceValue}`;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Defensive: reject any key that could traverse into the object prototype
|
||||
// chain when we copy/merge values across JSON trees. Our inputs are
|
||||
// authored JSON we already control, but excluding these keys is a cheap
|
||||
@@ -357,6 +390,13 @@ async function processLocale(locale, source, config, opts, backend) {
|
||||
}
|
||||
|
||||
const { merged, addedPaths } = mergeMissing(source, target);
|
||||
if (opts.retranslateIdentical && locale !== SOURCE_LOCALE) {
|
||||
const allow = new Set(
|
||||
(await loadJson(path.join(SCRIPT_DIR, "untranslatable-keys.json"))).keys ?? []
|
||||
);
|
||||
const flagged = markIdenticalAsMissing(merged, source, allow);
|
||||
logInfo(`${locale}: ${flagged} English leaves flagged for retranslation`);
|
||||
}
|
||||
const placeholderCountBefore = countPlaceholders(merged);
|
||||
|
||||
let translateStats = { translated: 0, failed: 0 };
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"description": "Keys that should remain untranslated (technical terms, brand names, ICU formats, placeholders, etc.)",
|
||||
"keys": [
|
||||
"apiManager.modelsCount",
|
||||
"a2aDashboard.metadata",
|
||||
"a2aDashboard.ok",
|
||||
"a2aDashboard.smokeSendSuccess",
|
||||
"a2aDashboard.url",
|
||||
"apiManager.modelsCount",
|
||||
"cache.behaviorTwoTier",
|
||||
"cache.dbEntriesSub",
|
||||
"cache.model",
|
||||
@@ -64,6 +64,7 @@
|
||||
"endpoint.cloudflaredTitle",
|
||||
"endpoint.mcpCardTitle",
|
||||
"endpoint.rerank",
|
||||
"featureFlags.definitions.SERVER_OWNED_TOOL_LOOP_ENABLED.label",
|
||||
"header.a2a",
|
||||
"header.mcp",
|
||||
"health.cpu",
|
||||
@@ -87,29 +88,29 @@
|
||||
"landing.github",
|
||||
"landing.npm",
|
||||
"legal.listSeparator",
|
||||
"legal.terms",
|
||||
"legal.privacy",
|
||||
"legal.terms",
|
||||
"loggers.inputTokens",
|
||||
"loggers.modelAZ",
|
||||
"loggers.modelZA",
|
||||
"loggers.outputTokens",
|
||||
"loggers.proxy",
|
||||
"logs.notAvailable",
|
||||
"logs.endpoint",
|
||||
"logs.proxy",
|
||||
"logs.console",
|
||||
"logs.request",
|
||||
"logs.audit",
|
||||
"logs.console",
|
||||
"logs.endpoint",
|
||||
"logs.notAvailable",
|
||||
"logs.proxy",
|
||||
"logs.request",
|
||||
"mcpDashboard.apiKeyIdPlaceholder",
|
||||
"mcpDashboard.offline",
|
||||
"mcpDashboard.online",
|
||||
"mcpDashboard.pid",
|
||||
"mcpDashboard.tableAudit",
|
||||
"media.interpolation",
|
||||
"media.model",
|
||||
"media.prompt",
|
||||
"media.interpolation",
|
||||
"media.upscale",
|
||||
"media.samples",
|
||||
"media.upscale",
|
||||
"modals.awsBuilderId",
|
||||
"modals.awsIamIdentity",
|
||||
"modals.awsRegion",
|
||||
@@ -126,8 +127,8 @@
|
||||
"providers.chatPathPlaceholder",
|
||||
"providers.codexAuthAppliedLocal",
|
||||
"providers.codexAuthApplyFailed",
|
||||
"providers.codexAuthExported",
|
||||
"providers.codexAuthExportFailed",
|
||||
"providers.codexAuthExported",
|
||||
"providers.compatProtocolClaude",
|
||||
"providers.compatProtocolOpenAI",
|
||||
"providers.compatProtocolOpenAIResponses",
|
||||
@@ -136,17 +137,17 @@
|
||||
"providers.messagesApi",
|
||||
"providers.messagesPath",
|
||||
"providers.millisecondsAbbr",
|
||||
"providers.modelsPathPlaceholder",
|
||||
"providers.modeTest",
|
||||
"providers.oauthLabel",
|
||||
"providers.modelsPathPlaceholder",
|
||||
"providers.oauth2Label",
|
||||
"providers.oauthLabel",
|
||||
"providers.okShort",
|
||||
"providers.openRouterModelPlaceholder",
|
||||
"providers.openai",
|
||||
"providers.openaiBaseUrlPlaceholder",
|
||||
"providers.openaiCompatibleModelPlaceholder",
|
||||
"providers.openaiCompatibleName",
|
||||
"providers.openaiPrefixPlaceholder",
|
||||
"providers.openRouterModelPlaceholder",
|
||||
"providers.prefixLabel",
|
||||
"providers.proxy",
|
||||
"providers.proxyConfiguredBySource",
|
||||
@@ -154,50 +155,59 @@
|
||||
"providers.responsesApi",
|
||||
"providers.responsesPath",
|
||||
"search.domainPlaceholder",
|
||||
"search.fileSearch",
|
||||
"search.rawJson",
|
||||
"search.rerank",
|
||||
"search.searchTypeWeb",
|
||||
"search.search",
|
||||
"search.searchTools",
|
||||
"search.searchTypeWeb",
|
||||
"search.webSearch",
|
||||
"search.fileSearch",
|
||||
"settings.ai",
|
||||
"settings.aliasPatternPlaceholder",
|
||||
"settings.aliasTargetPlaceholder",
|
||||
"settings.auto",
|
||||
"settings.currency",
|
||||
"settings.language",
|
||||
"settings.model",
|
||||
"settings.modelNamePlaceholder",
|
||||
"settings.ms",
|
||||
"settings.p2c",
|
||||
"settings.providersCommaSeparatedPlaceholder",
|
||||
"settings.proxy",
|
||||
"settings.p2c",
|
||||
"settings.reasoning",
|
||||
"settings.roundRobin",
|
||||
"settings.rpm",
|
||||
"settings.status",
|
||||
"settings.whitelabeling",
|
||||
"settings.theme",
|
||||
"settings.language",
|
||||
"settings.currency",
|
||||
"settings.timezone",
|
||||
"settings.whitelabeling",
|
||||
"sidebar.cache",
|
||||
"sidebar.cacheShort",
|
||||
"sidebar.cliCode",
|
||||
"sidebar.cliSection",
|
||||
"sidebar.compressionStudio",
|
||||
"sidebar.contextAggressive",
|
||||
"sidebar.contextHeadroom",
|
||||
"sidebar.contextLite",
|
||||
"sidebar.contextSessionDedup",
|
||||
"sidebar.debug",
|
||||
"sidebar.debugSection",
|
||||
"sidebar.helpSection",
|
||||
"sidebar.primarySection",
|
||||
"sidebar.systemSection",
|
||||
"sidebar.trafficInspector",
|
||||
"sidebar.version",
|
||||
"stats.errors",
|
||||
"stats.latency",
|
||||
"stats.requests",
|
||||
"stats.tokens",
|
||||
"stats.latency",
|
||||
"stats.errors",
|
||||
"themesPage.dark",
|
||||
"themesPage.light",
|
||||
"themesPage.system",
|
||||
"translator.auto",
|
||||
"translator.chatTester",
|
||||
"translator.detect",
|
||||
"translator.detectedLanguage",
|
||||
"translator.millisecondsShort",
|
||||
"translator.model",
|
||||
"translator.notAvailableSymbol",
|
||||
@@ -208,21 +218,25 @@
|
||||
"translator.translate",
|
||||
"translator.translateFrom",
|
||||
"translator.translateTo",
|
||||
"translator.detect",
|
||||
"translator.detectedLanguage",
|
||||
"usage.cacheReadTokens",
|
||||
"usage.cacheWriteTokens",
|
||||
"usage.columnModel",
|
||||
"usage.columnStatus",
|
||||
"usage.completionTokens",
|
||||
"usage.detailsRegex",
|
||||
"usage.durationHoursShort",
|
||||
"usage.durationMillisecondsShort",
|
||||
"usage.durationMinutesShort",
|
||||
"usage.durationSecondsShort",
|
||||
"usage.inputTokens",
|
||||
"usage.latencyP50",
|
||||
"usage.latencyP95",
|
||||
"usage.latencyP99",
|
||||
"usage.notApplicable",
|
||||
"usage.notAvailableSymbol",
|
||||
"usage.outputTokens",
|
||||
"usage.passSuffix",
|
||||
"usage.promptTokens",
|
||||
"usage.proxyTab",
|
||||
"usage.reasonSeparator",
|
||||
"usage.tierPlus",
|
||||
@@ -230,12 +244,6 @@
|
||||
"usage.tierUltra",
|
||||
"usage.totalRequests",
|
||||
"usage.totalTokens",
|
||||
"usage.inputTokens",
|
||||
"usage.outputTokens",
|
||||
"usage.promptTokens",
|
||||
"usage.completionTokens",
|
||||
"usage.cacheReadTokens",
|
||||
"usage.cacheWriteTokens",
|
||||
"usage.warningThresholdPlaceholder"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user