Repository navigation
Codex Desktop falls back to English UI when started offline - i18n gated behind server config enable_i18n (default false) #52471
Description
What version of the Codex App are you using?
26.1002.52244 (package OpenAI.Codex_26.1002.7124.0_x64__2p2nqsd0c76g0, prod channel, installed from Microsoft Store)
What platform is your computer?
Microsoft Windows 11 家庭版 中文版 (10.0.26200, Build 26200) x64
System region: zh-CN, preferred UI language: zh-Hans-CN
What issue are you seeing?
Codex Desktop renders its UI in English when the app is launched without a working connection to OpenAI, even though:
- the UI language is set to 中文(中国)/
zh-CN, - the locale pack is already bundled inside the app (
app.asarcontainszh-CN-b05cfd55c534.js~2.5 MB andzh-Hans-6d9213662c0b.js), - the local config correctly stores the override:
[desktop]
localeOverride = "zh-CN"As soon as the app is launched with a working proxy/VPN (so that it can reach OpenAI at startup), the same installation renders in Chinese. Turning the proxy off and restarting brings the English UI back. No restart-with-network is needed after the initial launch: the language stays Chinese for the lifetime of the process.
What steps can reproduce the bug?
- Settings > General > Language: select 中文(中国)(
zh-CN), confirmconfig.tomlgets[desktop] localeOverride = "zh-CN". - Fully quit Codex (tray > Quit).
- Start Codex with a network path that cannot reach OpenAI (e.g. direct connection without proxy).
- The UI renders in English. Log shows the backend is unreachable, e.g.:
warning [electron-message-handler] sa_server_request_failed ... errorMessage="Workspace routing is unavailable"
warning [AppServerConnection] app_server_connection.transport_connect_failed errorMessage="Sign in to ChatGPT to start a durable thread."
- Quit Codex, enable the proxy/VPN, start it again — the UI is now Chinese.
Expected behavior
The UI should be rendered in the selected language as soon as it is selected, including offline starts, since both the preference and the locale pack are available locally.
Root cause (from inspecting the shipped bundle)
The i18n path is gated behind a dynamically fetched config, and the gate defaults to disabled when the config is unavailable. In app.asar (minified):
function RYs(e){
...
s = En(`72216192`), // server-provided dynamic config
c = s?.get(`enable_i18n`, false); // missing config => false => English UI
...
}The locale resolution logic directly below it would happily use the local override or the system locale, but it never runs when enable_i18n is false.
Workaround
Start the app once with a working proxy/VPN, then keep the process running (e.g. disable the system proxy afterwards). The language persists until the app is restarted.
Suggested fix
Either default enable_i18n to true on desktop, or honor a non-empty localeOverride with the bundled locale packs and no network dependency.
中文摘要(可贴在 issue 末尾或作为补充)
界面语言被一个在线开关 enable_i18n 控制,取不到配置时默认 false,于是离线启动一律回退英文;但语言包本来就在本地(app.asar 内的 zh-CN-*.js),config.toml 里的 localeOverride = "zh-CN" 也已生效。开代理启动一次即恢复中文,说明只差这一个开关。建议桌面端把 enable_i18n 默认设为 true,或在 localeOverride 非空时直接用本地语言包。
Related open issues with the same symptom: #19239, #23815. This report adds the offline/online correlation and the code-level cause (enable_i18n defaulting to false); feel free to consolidate it into those threads if you prefer.
Activity
github-actions commentedon Oct 9, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
en-USin its MSIX manifest, forcing the UI to English despite shipping complete zh-CN resources #51926Powered by Codex Action
gehai46 commentedon Oct 10, 2026
Follow-up with code-level confirmation. I filed the symptom-level report in #51074 before
isolating the cause; the same machine and the same config file produce the A/B below.
Environment
zh-CNOpenAI.Codex_26.1007.2314.0_x64__2p2nqsd0c76g0; release reported in logs26.1007.21434~/.codex/config.tomlcontains[desktop] localeOverride = "zh-CN", and that line is neverremoved or rewritten by either run below
Decisive observation
One installation, one config file, only the network path changes:
browser_use_availability_resolvedavailable=true ... reason=available release=26.1007.21434available=false ... reason=statsig-disabled release=26.1007.21434Nothing else differs. The saved
localeOverrideis present and identical in both runs, so thesymptom is network-triggered rather than version-triggered.
Bundle review of this exact build
The locale provider gates the loader on a server-delivered dynamic config whose default is
false:The same config key defaults to
truein the settings picker:Two different defaults for one key reproduce the symptom in the original report exactly: the
dropdown keeps listing 中文(中国) because the picker defaults to enabled, while the loader that
would actually apply it defaults to disabled. Selection appears to save and never takes effect.
Pointers that may save triage time
POST https://chatgpt.com/backend-api/wham/statsig/bootstrap, issued assafePost('/wham/statsig/bootstrap', { requestBody: e, retry: false, signal: t }). It issingle-shot, so one slow body read degrades the session with no retry.
432 "Workspace routing is unavailable"on/settings/userand/wham/usageis a red herringfor this bug. It is present in the proxied session too, where statsig succeeded and the UI was
Chinese. It does not discriminate between the two states.
Failed to load locale messagesentry, andapp.asarships.vite/build/locales/zh-CN.jsonplus the webviewzh-CN-*.jschunk. Missingresources are not involved.
zh-CNwhile native stringsstay English, which is the "value resolved, value unused" signature reported here.
Separate defect, worth tracking independently
AppxManifest.xmlin26.1007.2314.0still declares a single resource language:That is a real packaging issue (see #51926 and #52800) affecting the system-locale path. It cannot
explain the A/B above, since it does not change with the network path.
Timeline
Logs on this machine only go back to 2026-10-04, so I cannot speak to earlier builds. Across
26.930.31730,26.930.41038,26.930.61225,26.1002.51308,26.1002.52244and26.1007.21434, bothreason=availableandreason=statsig-disabledappear, including differentoutcomes on the same build within the same hour. The outcome is not pinned to any one build.
The report in #50377, observed 2026-10-02 to 10-03, is the earliest occurrence I can point at.
Workaround verified here
Start the app once on a path that can reach
chatgpt.com, then keep the process running. The UIstays in the selected language for the lifetime of the process, and the proxy does not need to
stay on afterwards. Restarting without such a path brings English back.
Requested fix
Honour a non-empty
localeOverrideusing the bundled catalogs with no network dependency, ordefault
enable_i18ntotrueon desktop.中文摘要
同一台机器、同一版本
26.1007.2314.0、同一份config.toml,只更换网络路径即可复现:走代理时reason=available,界面为简体中文;直连时reason=statsig-disabled,界面为英文。原因是语言加载器里
enable_i18n的默认值为false,而设置页语言选择器的同一配置默认值为true,于是选项可见、选中无效。localeOverride与本地语言包都正常,日志中也没有语言包加载失败。另有 MSIX 清单只声明en-US的独立打包缺陷(见 #51926、#52800),与本条成因不同,需分开修复。
welger commentedon Oct 13, 2026
Why "launch once online" does not survive an offline restart: the Statsig evaluations cache never persists (localStorage
QuotaExceededError)The reports above establish that the UI is localized only when
enable_i18nis available at startup. One question was still open: why doesn't a successful online launch carry over to the next offline launch? The Statsig client in the renderer has storage enabled and readsstatsig.cached.evaluations.*before it hits the network, so the last-known values should be served from cache. They aren't, because the cache write always fails.Environment
OpenAI.Codex_26.1007.2314.0_x64__2p2nqsd0c76g0, release26.1007.21434, Chromium 155.0.8059.403.34.0[desktop] localeOverride = "zh-CN"present; no[analytics]override, so renderer storage is enabled (disableStorageis unset on the client)Method
The app was launched in its package context with
--remote-debugging-port(viaInvoke-CommandInDesktopPackage, so the data directory does not change). I inspected__STATSIG__.firstInstancein the primary window, wrappedStorage.prototype.setItemto record every write, and triggered a refresh withupdateUserAsync(currentUser).Result (network reachable)
After the refresh,
localStorageholds onlystatsig.session_id.*,statsig.stable_id.*and an empty ledgerstatsig.last_modified_time.evaluations = {}. No evaluations are ever written to disk.Chromium caps
localStorageat 10 MiB per origin, stored as UTF-16, which is about 5,242,880 characters. The serialized cache record is 5,336,996 characters, about 1.8% over the limit.This is how the SDK's
_writeToCachehandles it (the code is identical in the earlier26.730bundle; only the payload size changed):The last eviction pass runs with limit
0. It removes every entry in the ledger, including any previously cached value under the same key, and leaves the ledger as{}. The error is silent: nothing about it appears in the desktop logs.What fills the payload
dynamic_configslayer_configsfeature_gatesdataThe layer behind localization (
72216192→{ "enable_i18n": true, "locale_source": "IDE" }) is a single small entry insidelayer_configs.Consequence
Every launch without a working path to OpenAI starts with
NoValues. That meansenable_i18nisfalse(English), and every other gate falls back to its default too. On this machine that also brings back the older navigation layout and removes Approve for me from the permissions menu, which matches the symptoms in #50377.Control experiment: the cache-read path works; only the size breaks it
feature_gatesanddynamic_configsemptied andlayer_configsreduced to72216192. The SDK's own_loadFromCachereturned it withsource: Cache.Cache:Recognized. The layout was still the older one, since every other gate was at its default.NoValues.So the offline fallback is already designed to work. It is defeated only because the full evaluations payload no longer fits in
localStorage.Relation to #50377
The same oversized payload is probably also why the post-login bootstrap misses its fixed 10 s deadline on slower links, as reported there. I have not measured that directly.
Possible fixes (from the outside; you will know better)
localStoragequota. The SDK already supports a customstorageProvider(for example IndexedDB, or a file through the main process).dynamic_configsis about 91% of it. Or additionally persist a small allowlist of startup-critical values (locale, navigation layout, permission presets) that always fits.localeOverridefrom the bundled catalogs without depending onenable_i18n, as already requested above.QuotaExceededError. It is currently swallowed, which makes this very hard to diagnose from user reports.On builds from late August we saw the evaluations cache present on disk, and the localized UI persisted offline. I did not measure the payload size on those builds, so I can't say exactly when it crossed the threshold.
中文摘要
「联网启动一次」之所以撑不到下一次断网启动,是因为 Statsig 的开关缓存从来没有写进磁盘。
在
26.1007.2314.0里实测:联网拉取成功,但缓存记录有 5,336,996 个字符,超过 Chromium localStorage 每个源约 5,242,880 字符的上限(超出约 1.8%),连续 5 次QuotaExceededError。SDK 的减半重试最后按上限 0 淘汰,把账本清成{},而且不留任何日志。数据里
dynamic_configs占约 91%。汉化开关72216192只是其中很小的一项。对照实验:断网时只写入裁剪后 2,098 字符的记录,断网冷启动界面就是中文(
Cache:Recognized);联网启动一次,这条记录就被同一个配额循环删掉,再断网又变回英文。这说明离线读缓存的路径本身是好的,只是数据太大存不下。建议的修法:换用不受 localStorage 配额限制的
storageProvider;精简缓存内容,或单独持久化启动所需的少量关键值;localeOverride不依赖enable_i18n;至少把配额错误记进日志。WU928-spec commentedon Oct 15, 2026
Adding a macOS data point to the root-cause side of this, on 26.1007.71326 (build 20398), macOS 14.8.3 arm64, system language
zh-Hans-CN:The UI stays English while the machine has a working route to OpenAI, so this is not only the offline case described above. Confirmed at startup through a working proxy: the app's own processes connect to
chatgpt.com,chat.openai.comandab.chatgpt.com, andapi.openai.comis reachable (401 without a key). Network is not the variable here — the gate still resolves to false.The likely cause is a default mismatch between the two call sites in
Contents/Resources/app.asar:So unless the server explicitly returns
enable_i18n: truefor72216192, the UI renders EnglishdefaultMessages and every local lever (localeOverride = "zh-CN",--lang=zh-CN, changing the picker) is a no-op. The zh-Hans strings are bundled inapp.asar(verified), so it is not a missing-translation problem either.Full macOS repro and the things that were tried are in #19239.
4x25 commentedon Oct 16, 2026
Confirming the same defect on macOS, with a cache angle that lines up with @welger's Windows findings.
Environment: Codex App
26.1013.31256(Chromium base155.0.8059.80), macOSDarwin 27.0.0 arm64. One installation, oneconfig.toml; only the network path changes.A/B (cold starts):
ab.chatgpt.com-> connection reset (curlexit 35),chatgpt.com/backend-api-> connect timeout after 8s. Every cold start renders English; changing the language in Settings writes[desktop] localeOverridebut never changes the UI.127.0.0.1:7890): both hosts answer (403 without auth). The UI localizes a moment after launch (brief English -> localized flash, i.e. the bundled locale chunk loads right after the gate resolves) and stays localized for the process lifetime.Both call sites read the same layer. In this build,
general-settings-fd634ec6c16e.jsimportsOrfromapp-initial-3ccf4886957f.jsas a re-export of the samegetLayerhelper (dWt as oDrin the bundle's export table). So the picker lists languages (?.get('enable_i18n', true)) while the provider that actually injects messages usesfalse, for the exact same72216192layer: the option is always selectable and the write always succeeds even when the value can never be applied.Cache state on this macOS install (matches @welger's conclusion):
statsig.session_id.*,statsig.stable_id.*and an emptystatsig.last_modified_time.evaluationsledger ({}). There is nostatsig.cached.evaluations.*for the current identity, so every launch starts fromNoValues.statsig.cached.evaluations.3217092736withsource: "Network",receivedAt2026-06-01, andlayer_configs["72216192"] = {"enable_i18n": true, "locale_source": "IDE"}. The cache write path used to work on macOS; today nothing survives.dataAdapter.setData(payload)+initializeSync(), and the SDK'ssetDatawrites to its in-memory cache only - nothing hits storage. Combined with the above, an offline cold start always falls back to theenable_i18ndefault.On the 432 noise: agreed it is orthogonal - this machine logged 477
"Workspace routing is unavailable"(status 432) events today, including while the proxy was on and the UI localized.The requested fix (honor a non-empty
localeOverridefrom the bundled catalogs without the gate, and/or defaultenable_i18ntotrueon desktop) resolves every variant above; the workaround (start once on a reachable network path) keeps matching across reports.