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..90ff33098875 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": "保存" + } }