{"record":{"id":"96deb78a8d84da1a","repo":"mihomo-party-org/clash-party","slug":"translation-not-ready","errorCode":null,"errorMessage":"Translation not ready","messagePattern":"Translation not ready","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"info","filePath":"src/main/utils/init.ts","lineNumber":72,"sourceCode":"import { atomicWriteFile } from './safeFile'\n\nlet isInitBasicCompleted = false\nlet isRuntimeFilesCompleted = false\nlet initBasicPromise: Promise<void> | null = null\nlet runtimeFilesPromise: Promise<void> | null = null\nlet subStoreServicesPromise: Promise<SubStoreServicePorts> | null = null\nlet subStoreServicesStarted = false\n\ninterface SubStoreServicePorts {\n  backendPort?: number\n  frontendPort?: number\n}\n\nexport function safeShowErrorBox(titleKey: string, message: string): void {\n  let title: string\n  try {\n    title = i18next.t(titleKey)\n    if (!title || title === titleKey) throw new Error('Translation not ready')\n  } catch {\n    const isZh = app.getLocale().startsWith('zh')\n    const lang = isZh ? resources['zh-CN'].translation : resources['en-US'].translation\n    title = lang[titleKey] || (isZh ? '错误' : 'Error')\n  }\n  dialog.showErrorBox(title, message)\n}\n\nasync function fixDataDirPermissions(): Promise<void> {\n  if (process.platform !== 'darwin') return\n\n  const dataDirPath = dataDir()\n  if (!existsSync(dataDirPath)) return\n\n  try {\n    const stats = await stat(dataDirPath)\n    const currentUid = process.getuid?.() || 0\n","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/mihomo-party-org/clash-party/blob/911e090537acdf7c50bee1c3aebecc2ef119a8b5/src/main/utils/init.ts#L54-L90","documentation":"safeShowErrorBox resolves an error-dialog title via i18next.t(titleKey). If i18next is not yet initialized (or the key is missing so t() echoes the key back), it deliberately throws the internal sentinel 'Translation not ready' to trigger the catch block, which falls back to a hardcoded zh-CN/en-US translation table. The error never escapes the function — it is a control-flow device to switch to the fallback path.","triggerScenarios":"safeShowErrorBox called (by initCoreWatcher, keepCoreAlive, handleDeepLink, queueLaunchTarget, appConfigPromise, ensureNoHighPrivilegeCore) before await i18next.init() resolves — typically a very early startup error dialog — or with a titleKey absent from the resource bundles.","commonSituations":"App crashes during startup before i18n init completes (config file corrupt, core fails instantly); deep-link activation racing app initialization; typo'd or newly added translation key missing from one locale's resources.","solutions":["Nothing to fix if the fallback title appears — the function already shows the dialog using the built-in zh-CN/en-US strings.","If titles show the raw key, add the missing key to both resources['zh-CN'].translation and resources['en-US'].translation.","Ensure i18next.init() is awaited before any code path that may call safeShowErrorBox (move init earlier in the startup sequence).","Verify the titleKey passed in matches a key in the translation resources exactly (case-sensitive)."],"exampleFix":"// before: dialog title shows raw key because translation missing\ntitle = lang[titleKey] || (isZh ? '错误' : 'Error')\n// after: after adding the key to both bundles\n// resources['en-US'].translation['coreWatchFailed'] = 'Core monitor failed'\n// resources['zh-CN'].translation['coreWatchFailed'] = '内核监控失败'","handlingStrategy":"fallback","validationCode":"// ensure i18n is initialized before any error dialog can fire\nif (!i18next.isInitialized) {\n  await i18next.init({ resources })\n}","typeGuard":"function translationReady(key: string): boolean {\n  const v = i18next.t(key)\n  return typeof v === 'string' && v.length > 0 && v !== key\n}","tryCatchPattern":"// the function already implements the right pattern; mimic it elsewhere:\nlet title: string\ntry {\n  title = i18next.t(key)\n  if (!title || title === key) throw new Error('Translation not ready')\n} catch {\n  title = resources[app.getLocale().startsWith('zh') ? 'zh-CN' : 'en-US'].translation[key] ?? 'Error'\n}","preventionTips":["Await i18next.init() as the first step of app startup, before any watchers or deep-link handlers","Keep zh-CN and en-US resource bundles key-complete; add CI check that both locales have identical key sets","Never display i18next.t() output without checking it isn't the raw key","Ship hardcoded fallback strings for pre-init error dialogs"],"tags":["i18n","initialization","fallback","control-flow"],"backgroundTag":"i18n-not-initialized","analyzedSha":"911e090537acdf7c50bee1c3aebecc2ef119a8b5","analyzedAt":"2026-08-30T13:00:49.174Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}