From 7472d9a1cca868e63fda819c8ab76b6e5e9935c8 Mon Sep 17 00:00:00 2001 From: Evgenii Date: Mon, 12 Oct 2026 22:09:17 +0700 Subject: [PATCH 1/2] fix(i18n): nest the catalog keys that were written with dots in their names Twelve entries in en.json sat at the root under a name that spells out the whole path ("settings.agents.save") instead of inside the nested settings object. Eleven of them, the host agent-settings strings from #26479, were flat in es, fr, ja, ko and zh as well. The twelfth duplicated the nested settings.appearance.chat.title. Built-in locales rendered them anyway, because i18next falls back to a flat key when the nested path is missing. A language pack cannot use that shape: the pack parser rejects any key that contains a dot, and one rejected key rejects the whole catalog. The eleven strings now live in settings.agents in all six catalogs and the duplicate is gone. Every path and value is unchanged, so no call site moves. The catalog verifier now fails on a key with a dot in its name, in en.json and in the target catalogs. --- .../scripts/verify-localization-catalog.mjs | 31 +++++++++++++++++++ .../verify-localization-catalog.test.mjs | 30 ++++++++++++++++++ src/renderer/src/i18n/locales/en.json | 25 +++++++-------- src/renderer/src/i18n/locales/es.json | 26 ++++++++-------- src/renderer/src/i18n/locales/fr.json | 26 ++++++++-------- src/renderer/src/i18n/locales/ja.json | 26 ++++++++-------- src/renderer/src/i18n/locales/ko.json | 26 ++++++++-------- src/renderer/src/i18n/locales/zh.json | 26 ++++++++-------- 8 files changed, 138 insertions(+), 78 deletions(-) diff --git a/config/scripts/verify-localization-catalog.mjs b/config/scripts/verify-localization-catalog.mjs index a155e8741d3a..3b2fad9d2b3c 100644 --- a/config/scripts/verify-localization-catalog.mjs +++ b/config/scripts/verify-localization-catalog.mjs @@ -151,6 +151,31 @@ function formatMissingKeys(label, keys) { return keys.map((key) => `${label}: ${key}`).join('\n') } +// Why: i18next resolves a flat "a.b" leaf, so built-in locales pass, but a language pack with a dotted key is rejected whole. +function collectDottedCatalogKeys(value, prefix = '') { + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + return [] + } + return Object.entries(value).flatMap(([key, child]) => { + const keyPath = prefix ? `${prefix}.${key}` : key + return [...(key.includes('.') ? [keyPath] : []), ...collectDottedCatalogKeys(child, keyPath)] + }) +} + +function reportDottedCatalogKeys(fileName, catalog) { + const dottedKeys = collectDottedCatalogKeys(catalog) + if (dottedKeys.length === 0) { + return 0 + } + console.error(`${fileName} has keys with a dot in the name. Write them as nested objects.`) + console.error('') + console.error(formatMissingKeys('dotted', dottedKeys.slice(0, 20))) + if (dottedKeys.length > 20) { + console.error(`...and ${dottedKeys.length - 20} more dotted keys`) + } + return 1 +} + function normalizeInterpolationVariables(value) { return collectInterpolationVariables(value) .map((variable) => variable.slice(2, -2)) @@ -480,6 +505,9 @@ export async function main( } return 0 } + if (reportDottedCatalogKeys('en.json', catalog) !== 0) { + return 1 + } let catalogKeys = new Set(flattenCatalogKeys(catalog)) const references = sharedReferences ?? (await collectLocalizationReferences(root)) @@ -539,6 +567,9 @@ export async function main( const localeName = fileName.replace(/\.json$/, '') const localeCatalogPath = path.join(localesDir, fileName) const localeCatalog = JSON.parse(await fs.readFile(localeCatalogPath, 'utf8')) + if (reportDottedCatalogKeys(fileName, localeCatalog) !== 0) { + return 1 + } const exitCode = verifyLocaleCatalog(catalog, localeName, localeCatalog) if (exitCode !== 0) { console.error('') diff --git a/config/scripts/verify-localization-catalog.test.mjs b/config/scripts/verify-localization-catalog.test.mjs index 4623f345b795..103af30a05b9 100644 --- a/config/scripts/verify-localization-catalog.test.mjs +++ b/config/scripts/verify-localization-catalog.test.mjs @@ -141,6 +141,36 @@ describe('verify-localization-catalog', () => { } }) + // Why: i18next reads a flat "a.b" leaf, so only language packs, which reject dotted keys, noticed. + it('rejects a key that carries its path in a dotted name', async () => { + const sourceText = + "import { translate } from '@/i18n/i18n'\nexport const label = translate('auto.example.greeting', 'Hello')\n" + const nested = { auto: { example: { greeting: 'Hello' } } } + const dottedEnglish = makeProject({ + sourceText, + enCatalog: { 'auto.example.greeting': 'Hello' } + }) + const dottedTarget = makeProject({ + sourceText, + enCatalog: nested, + esCatalog: { auto: { 'example.greeting': 'Hola' } } + }) + const report = vi.spyOn(console, 'error').mockImplementation(() => undefined) + + try { + await expect(verifyLocalizationCatalog(dottedEnglish.root, { fix: true })).resolves.toBe(1) + expect(report).toHaveBeenCalledWith('dotted: auto.example.greeting') + expect(readJson(path.join(dottedEnglish.localesDir, 'en.json'))).toEqual({ + 'auto.example.greeting': 'Hello' + }) + + await expect(verifyLocalizationCatalog(dottedTarget.root, { fix: false })).resolves.toBe(1) + expect(report).toHaveBeenCalledWith(expect.stringContaining('es.json has keys with a dot')) + } finally { + report.mockRestore() + } + }) + // Why: #12113 — parity checks passed while the repair policy rewrote translated terms to English. it('flags catalog values the repair policy would rewrite back to English', () => { const enEntries = new Map([['auto.example.commitLabel', 'Commit message']]) diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 4d51de70365b..71f6a35a82e9 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -317,7 +317,18 @@ "codexSharedServerWarning": { "title": "Warn when a Codex tab shares a server", "description": "Shows a notice on a Codex you started yourself when it shares a server with other tabs, because its agent status may be wrong." - } + }, + "environmentName": "Environment variable name", + "environmentValue": "Value for {{name}}", + "savedEnvironment": "Saved value · enter to replace", + "newEnvironment": "Value", + "removeEnvironment": "Remove", + "loadingHost": "Loading agent settings…", + "updateHost": "Update this server to edit its agent launch settings.", + "readFailed": "Couldn’t read agent settings. Check the host connection and try again.", + "saveFailed": "Couldn’t save agent settings. Check the host connection and try again.", + "hostDefaults": "Defaults for new agents on the selected host.", + "save": "Save" }, "followSymlinkedDirectoriesKeywords": { "symlink": "symlink", @@ -19670,7 +19681,6 @@ "projectField": { "updateFailed": "Failed to update project field" }, - "settings.appearance.chat.title": "Chat", "mediaPreview": { "unavailable": "Unable to play this media file. The file may be unavailable or use an unsupported codec.", "openFailed": "Could not open this file in the default app.", @@ -19751,17 +19761,6 @@ "updateRuntimeSafely": "Update the remote runtime to safely change workspace links" } }, - "settings.agents.environmentName": "Environment variable name", - "settings.agents.environmentValue": "Value for {{name}}", - "settings.agents.savedEnvironment": "Saved value · enter to replace", - "settings.agents.newEnvironment": "Value", - "settings.agents.removeEnvironment": "Remove", - "settings.agents.loadingHost": "Loading agent settings…", - "settings.agents.updateHost": "Update this server to edit its agent launch settings.", - "settings.agents.readFailed": "Couldn’t read agent settings. Check the host connection and try again.", - "settings.agents.saveFailed": "Couldn’t save agent settings. Check the host connection and try again.", - "settings.agents.hostDefaults": "Defaults for new agents on the selected host.", - "settings.agents.save": "Save", "tabHoverCard": { "browser": "Browser", "editor": "Editor", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 4b92605e734a..371853cf2b48 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -149,7 +149,18 @@ "codexSharedServerWarning": { "title": "Avisar cuando una pestaña de Codex comparte un servidor", "description": "Muestra un aviso en un Codex que usted mismo inició cuando comparte un servidor con otras pestañas, porque su estado de agente puede ser incorrecto." - } + }, + "environmentName": "Nombre de la variable de entorno", + "environmentValue": "Valor de {{name}}", + "savedEnvironment": "Valor guardado · escribe para reemplazarlo", + "newEnvironment": "Valor", + "removeEnvironment": "Eliminar", + "loadingHost": "Cargando la configuración de agentes…", + "updateHost": "Actualiza este servidor para editar su configuración de inicio de agentes.", + "readFailed": "No se pudo leer la configuración de agentes. Comprueba la conexión con el host e inténtalo de nuevo.", + "saveFailed": "No se pudo guardar la configuración de agentes. Comprueba la conexión con el host e inténtalo de nuevo.", + "hostDefaults": "Valores predeterminados para nuevos agentes en el host seleccionado.", + "save": "Guardar" }, "followSymlinkedDirectoriesKeywords": { "symlink": "enlace simbólico", @@ -19518,16 +19529,5 @@ }, "projectField": { "updateFailed": "No se pudo actualizar el campo del proyecto" - }, - "settings.agents.environmentName": "Nombre de la variable de entorno", - "settings.agents.environmentValue": "Valor de {{name}}", - "settings.agents.savedEnvironment": "Valor guardado · escribe para reemplazarlo", - "settings.agents.newEnvironment": "Valor", - "settings.agents.removeEnvironment": "Eliminar", - "settings.agents.loadingHost": "Cargando la configuración de agentes…", - "settings.agents.updateHost": "Actualiza este servidor para editar su configuración de inicio de agentes.", - "settings.agents.readFailed": "No se pudo leer la configuración de agentes. Comprueba la conexión con el host e inténtalo de nuevo.", - "settings.agents.saveFailed": "No se pudo guardar la configuración de agentes. Comprueba la conexión con el host e inténtalo de nuevo.", - "settings.agents.hostDefaults": "Valores predeterminados para nuevos agentes en el host seleccionado.", - "settings.agents.save": "Guardar" + } } diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index a9eae06bb86d..599af9858711 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -250,7 +250,18 @@ "codexSharedServerWarning": { "title": "Avertir lorsqu'un onglet Codex partage un serveur", "description": "Affiche un avis sur un Codex que vous avez démarré vous-même lorsqu'il partage un serveur avec d'autres onglets, car son statut d'agent peut être erroné." - } + }, + "environmentName": "Nom de la variable d’environnement", + "environmentValue": "Valeur de {{name}}", + "savedEnvironment": "Valeur enregistrée · saisir pour remplacer", + "newEnvironment": "Valeur", + "removeEnvironment": "Supprimer", + "loadingHost": "Chargement des paramètres des agents…", + "updateHost": "Mettez ce serveur à jour pour modifier ses paramètres de lancement des agents.", + "readFailed": "Impossible de lire les paramètres des agents. Vérifiez la connexion à l’hôte et réessayez.", + "saveFailed": "Impossible d’enregistrer les paramètres des agents. Vérifiez la connexion à l’hôte et réessayez.", + "hostDefaults": "Paramètres par défaut des nouveaux agents sur l’hôte sélectionné.", + "save": "Enregistrer" }, "followSymlinkedDirectoriesKeywords": { "symlink": "lien symbolique", @@ -19519,16 +19530,5 @@ }, "projectField": { "updateFailed": "Échec de la mise à jour du champ du projet" - }, - "settings.agents.environmentName": "Nom de la variable d’environnement", - "settings.agents.environmentValue": "Valeur de {{name}}", - "settings.agents.savedEnvironment": "Valeur enregistrée · saisir pour remplacer", - "settings.agents.newEnvironment": "Valeur", - "settings.agents.removeEnvironment": "Supprimer", - "settings.agents.loadingHost": "Chargement des paramètres des agents…", - "settings.agents.updateHost": "Mettez ce serveur à jour pour modifier ses paramètres de lancement des agents.", - "settings.agents.readFailed": "Impossible de lire les paramètres des agents. Vérifiez la connexion à l’hôte et réessayez.", - "settings.agents.saveFailed": "Impossible d’enregistrer les paramètres des agents. Vérifiez la connexion à l’hôte et réessayez.", - "settings.agents.hostDefaults": "Paramètres par défaut des nouveaux agents sur l’hôte sélectionné.", - "settings.agents.save": "Enregistrer" + } } diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index ae613af9533f..0206964925bf 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -149,7 +149,18 @@ "codexSharedServerWarning": { "title": "Codex タブがサーバーを共有する場合に警告する", "description": "自分で開始した Codex が他のタブとサーバーを共有している場合、Agent のステータスが間違っている可能性があるため、その Codex に関する通知が表示されます。" - } + }, + "environmentName": "環境変数名", + "environmentValue": "{{name}} の値", + "savedEnvironment": "保存済みの値 · 置き換えるには入力", + "newEnvironment": "値", + "removeEnvironment": "削除", + "loadingHost": "Agent設定を読み込み中…", + "updateHost": "Agentの起動設定を編集するには、このサーバーを更新してください。", + "readFailed": "Agent設定を読み込めませんでした。ホストへの接続を確認して再試行してください。", + "saveFailed": "Agent設定を保存できませんでした。ホストへの接続を確認して再試行してください。", + "hostDefaults": "選択したホストで起動する新しいAgentの既定値。", + "save": "保存" }, "followSymlinkedDirectoriesKeywords": { "symlink": "シンボリックリンク", @@ -19518,16 +19529,5 @@ }, "projectField": { "updateFailed": "プロジェクトフィールドを更新できませんでした" - }, - "settings.agents.environmentName": "環境変数名", - "settings.agents.environmentValue": "{{name}} の値", - "settings.agents.savedEnvironment": "保存済みの値 · 置き換えるには入力", - "settings.agents.newEnvironment": "値", - "settings.agents.removeEnvironment": "削除", - "settings.agents.loadingHost": "Agent設定を読み込み中…", - "settings.agents.updateHost": "Agentの起動設定を編集するには、このサーバーを更新してください。", - "settings.agents.readFailed": "Agent設定を読み込めませんでした。ホストへの接続を確認して再試行してください。", - "settings.agents.saveFailed": "Agent設定を保存できませんでした。ホストへの接続を確認して再試行してください。", - "settings.agents.hostDefaults": "選択したホストで起動する新しいAgentの既定値。", - "settings.agents.save": "保存" + } } diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 673eb9c6c06b..f3fb8eed28d2 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -149,7 +149,18 @@ "codexSharedServerWarning": { "title": "Codex 탭이 서버를 공유할 때 경고", "description": "에이전트 상태가 잘못되었을 수 있으므로 서버를 다른 탭과 공유할 때 직접 시작한 Codex에 대한 알림을 표시합니다." - } + }, + "environmentName": "환경 변수 이름", + "environmentValue": "{{name}} 값", + "savedEnvironment": "저장된 값 · 바꾸려면 입력", + "newEnvironment": "값", + "removeEnvironment": "삭제", + "loadingHost": "에이전트 설정 불러오는 중…", + "updateHost": "에이전트 실행 설정을 편집하려면 이 서버를 업데이트하세요.", + "readFailed": "에이전트 설정을 읽을 수 없습니다. 호스트 연결을 확인하고 다시 시도하세요.", + "saveFailed": "에이전트 설정을 저장할 수 없습니다. 호스트 연결을 확인하고 다시 시도하세요.", + "hostDefaults": "선택한 호스트의 새 에이전트 기본값입니다.", + "save": "저장" }, "followSymlinkedDirectoriesKeywords": { "symlink": "심볼릭 링크", @@ -19518,16 +19529,5 @@ }, "projectField": { "updateFailed": "프로젝트 필드를 업데이트하지 못했습니다" - }, - "settings.agents.environmentName": "환경 변수 이름", - "settings.agents.environmentValue": "{{name}} 값", - "settings.agents.savedEnvironment": "저장된 값 · 바꾸려면 입력", - "settings.agents.newEnvironment": "값", - "settings.agents.removeEnvironment": "삭제", - "settings.agents.loadingHost": "에이전트 설정 불러오는 중…", - "settings.agents.updateHost": "에이전트 실행 설정을 편집하려면 이 서버를 업데이트하세요.", - "settings.agents.readFailed": "에이전트 설정을 읽을 수 없습니다. 호스트 연결을 확인하고 다시 시도하세요.", - "settings.agents.saveFailed": "에이전트 설정을 저장할 수 없습니다. 호스트 연결을 확인하고 다시 시도하세요.", - "settings.agents.hostDefaults": "선택한 호스트의 새 에이전트 기본값입니다.", - "settings.agents.save": "저장" + } } diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 5920c46f849d..38bc127fe967 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -149,7 +149,18 @@ "codexSharedServerWarning": { "title": "当 Codex 选项卡共享服务器时发出警告", "description": "当您自己启动的 Codex 与其他选项卡共享服务器时,在您自己启动的 Codex 上显示一条通知,因为它的智能体状态可能是错误的。" - } + }, + "environmentName": "环境变量名称", + "environmentValue": "{{name}} 的值", + "savedEnvironment": "已保存的值 · 输入以替换", + "newEnvironment": "值", + "removeEnvironment": "删除", + "loadingHost": "正在加载代理设置…", + "updateHost": "请更新此服务器以编辑其代理启动设置。", + "readFailed": "无法读取代理设置。请检查主机连接并重试。", + "saveFailed": "无法保存代理设置。请检查主机连接并重试。", + "hostDefaults": "所选主机上新代理的默认设置。", + "save": "保存" }, "followSymlinkedDirectoriesKeywords": { "symlink": "符号链接", @@ -19532,16 +19543,5 @@ }, "projectField": { "updateFailed": "无法更新项目字段" - }, - "settings.agents.environmentName": "环境变量名称", - "settings.agents.environmentValue": "{{name}} 的值", - "settings.agents.savedEnvironment": "已保存的值 · 输入以替换", - "settings.agents.newEnvironment": "值", - "settings.agents.removeEnvironment": "删除", - "settings.agents.loadingHost": "正在加载代理设置…", - "settings.agents.updateHost": "请更新此服务器以编辑其代理启动设置。", - "settings.agents.readFailed": "无法读取代理设置。请检查主机连接并重试。", - "settings.agents.saveFailed": "无法保存代理设置。请检查主机连接并重试。", - "settings.agents.hostDefaults": "所选主机上新代理的默认设置。", - "settings.agents.save": "保存" + } } From fa09fa13cf38b9292c1b10243aff29d6f790bbc0 Mon Sep 17 00:00:00 2001 From: Evgenii Date: Mon, 12 Oct 2026 23:08:04 +0700 Subject: [PATCH 2/2] fix(i18n): use the catalog's word for agent in five Chinese agent-settings strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five of the eleven strings this branch nests said 代理 for "agent" in zh.json. The rest of the Chinese catalog calls an agent 智能体 and keeps 代理 for proxy and user-agent copy, so 代理设置 read as "proxy settings". The new wording is what the catalog's own repair policy (locale-phrase-fixes.mjs) produces for these five strings. Raised by CodeRabbit in review. Only zh.json changes, and only these five values. --- src/renderer/src/i18n/locales/zh.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 38bc127fe967..90ff33098875 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -155,11 +155,11 @@ "savedEnvironment": "已保存的值 · 输入以替换", "newEnvironment": "值", "removeEnvironment": "删除", - "loadingHost": "正在加载代理设置…", - "updateHost": "请更新此服务器以编辑其代理启动设置。", - "readFailed": "无法读取代理设置。请检查主机连接并重试。", - "saveFailed": "无法保存代理设置。请检查主机连接并重试。", - "hostDefaults": "所选主机上新代理的默认设置。", + "loadingHost": "正在加载智能体设置…", + "updateHost": "请更新此服务器以编辑其智能体启动设置。", + "readFailed": "无法读取智能体设置。请检查主机连接并重试。", + "saveFailed": "无法保存智能体设置。请检查主机连接并重试。", + "hostDefaults": "所选主机上新智能体的默认设置。", "save": "保存" }, "followSymlinkedDirectoriesKeywords": {