Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions config/scripts/verify-localization-catalog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,30 @@ function formatMissingKeys(label, keys) {
return keys.map((key) => `${label}: ${key}`).join('\n')
}

// Language packs require nested names even though i18next also accepts flat keys.
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(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))
Expand Down Expand Up @@ -480,6 +504,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))

Expand Down Expand Up @@ -539,6 +566,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('')
Expand Down
24 changes: 24 additions & 0 deletions config/scripts/verify-localization-catalog.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ describe('verify-localization-catalog', () => {
})
})

it.each([
['en', false],
['en', true],
['es', false]
])('rejects dotted %s keys without rewriting them (fix: %s)', async (locale, fix) => {
const nested = { auto: { example: { greeting: 'Hello.world' } } }
const { root, localesDir } = makeProject({
sourceText: 'export {}\n',
enCatalog: locale === 'en' ? { 'auto.example.greeting': 'Hello.world' } : nested,
esCatalog: locale === 'es' ? { auto: { 'example.greeting': 'Hola.world' } } : {}
})
const catalogPath = path.join(localesDir, `${locale}.json`)
const before = readFileSync(catalogPath, 'utf8')
const report = vi.spyOn(console, 'error').mockImplementation(() => undefined)
try {
await expect(verifyLocalizationCatalog(root, { fix })).resolves.toBe(1)
expect(report).toHaveBeenCalledWith(expect.stringContaining(`${locale}.json`))
expect(report).toHaveBeenCalledWith('dotted: auto.example.greeting')
expect(readFileSync(catalogPath, 'utf8')).toBe(before)
} finally {
report.mockRestore()
}
})

it('never overwrites mismatched translations or removes target-only entries', async () => {
const { root, localesDir } = makeProject({
sourceText:
Expand Down
56 changes: 56 additions & 0 deletions src/renderer/src/i18n/catalog-nested-settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import i18next from 'i18next'
import { expect, it } from 'vitest'
import { parsePluginLanguagePackArtifact } from '../../../shared/plugins/plugin-language-pack-artifact'
import en from './locales/en.json'
import es from './locales/es.json'
import fr from './locales/fr.json'
import ja from './locales/ja.json'
import ko from './locales/ko.json'
import zh from './locales/zh.json'

const AGENT_SETTINGS_KEYS = [
'environmentName',
'environmentValue',
'savedEnvironment',
'newEnvironment',
'removeEnvironment',
'loadingHost',
'updateHost',
'readFailed',
'saveFailed',
'hostDefaults',
'save'
] as const

it.each([
['en', en],
['es', es],
['fr', fr],
['ja', ja],
['ko', ko],
['zh', zh]
] as const)(
'keeps %s agent settings usable as nested language-pack translations',
async (locale, catalog) => {
// Bundled security copy is intentionally unavailable to plugin language packs.
expect(
parsePluginLanguagePackArtifact(JSON.stringify({ settings: catalog.settings }))
).toMatchObject({ ok: true })

const instance = i18next.createInstance()
await instance.init({
lng: locale,
fallbackLng: false,
resources: { [locale]: { translation: catalog } },
ignoreJSONStructure: false
})
for (const key of AGENT_SETTINGS_KEYS) {
expect(instance.t(`settings.agents.${key}`, { name: 'PATH' })).toBe(
catalog.settings.agents[key].replace('{{name}}', 'PATH')
)
}
expect(instance.t('settings.appearance.chat.title')).toBe(
catalog.settings.appearance.chat.title
)
}
)
25 changes: 12 additions & 13 deletions src/renderer/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,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",
Expand Down Expand Up @@ -19791,7 +19802,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.",
Expand Down Expand Up @@ -19872,17 +19882,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",
Expand Down
26 changes: 13 additions & 13 deletions src/renderer/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,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",
Expand Down Expand Up @@ -19536,16 +19547,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"
}
}
26 changes: 13 additions & 13 deletions src/renderer/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,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",
Expand Down Expand Up @@ -19537,16 +19548,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"
}
}
26 changes: 13 additions & 13 deletions src/renderer/src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,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": "シンボリックリンク",
Expand Down Expand Up @@ -19536,16 +19547,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": "保存"
}
}
26 changes: 13 additions & 13 deletions src/renderer/src/i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,18 @@
"codexSharedServerWarning": {
"title": "Codex 탭이 서버를 공유할 때 경고",
"description": "에이전트 상태가 잘못되었을 수 있으므로 서버를 다른 탭과 공유할 때 직접 시작한 Codex에 대한 알림을 표시합니다."
}
},
"environmentName": "환경 변수 이름",
"environmentValue": "{{name}} 값",
"savedEnvironment": "저장된 값 · 바꾸려면 입력",
"newEnvironment": "값",
"removeEnvironment": "삭제",
"loadingHost": "에이전트 설정 불러오는 중…",
"updateHost": "에이전트 실행 설정을 편집하려면 이 서버를 업데이트하세요.",
"readFailed": "에이전트 설정을 읽을 수 없습니다. 호스트 연결을 확인하고 다시 시도하세요.",
"saveFailed": "에이전트 설정을 저장할 수 없습니다. 호스트 연결을 확인하고 다시 시도하세요.",
"hostDefaults": "선택한 호스트의 새 에이전트 기본값입니다.",
"save": "저장"
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "심볼릭 링크",
Expand Down Expand Up @@ -19566,16 +19577,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": "저장"
}
}
26 changes: 13 additions & 13 deletions src/renderer/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,18 @@
"codexSharedServerWarning": {
"title": "当 Codex 选项卡共享服务器时发出警告",
"description": "当您自己启动的 Codex 与其他选项卡共享服务器时,在您自己启动的 Codex 上显示一条通知,因为它的智能体状态可能是错误的。"
}
},
"environmentName": "环境变量名称",
"environmentValue": "{{name}} 的值",
"savedEnvironment": "已保存的值 · 输入以替换",
"newEnvironment": "值",
"removeEnvironment": "删除",
"loadingHost": "正在加载代理设置…",
"updateHost": "请更新此服务器以编辑其代理启动设置。",
"readFailed": "无法读取代理设置。请检查主机连接并重试。",
"saveFailed": "无法保存代理设置。请检查主机连接并重试。",
"hostDefaults": "所选主机上新代理的默认设置。",
"save": "保存"
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "符号链接",
Expand Down Expand Up @@ -19550,16 +19561,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": "保存"
}
}
Loading