fix: locale issues — custom strings caching, lang race, and window._()#7454
Conversation
The admin panel's i18next backend used fetch with cache: "force-cache", causing the browser to serve stale locale JSON even after the server restarted with new customLocaleStrings in settings.json. The server already sets appropriate Cache-Control headers (max-age based on settings.maxAge), so the client-side force-cache was redundant and prevented custom strings from appearing. Fixes ether#6390 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
/review |
Code Review by Qodo
1. No regression tests added
|
Review Summary by QodoRemove force-cache from admin panel locale fetching
WalkthroughsDescription• Remove aggressive cache: "force-cache" from locale fetch • Allow server Cache-Control headers to manage caching properly • Fix customLocaleStrings not appearing after server restart Diagramflowchart LR
A["Admin Panel i18n Backend"] -->|"fetch with force-cache (removed)"| B["Browser Cache"]
A -->|"fetch without cache option (new)"| C["Server Cache-Control Headers"]
C -->|"respects max-age setting"| D["Fresh Locale JSON"]
B -->|"stale data (old behavior)"| E["Custom Strings Not Applied"]
D -->|"updated data (new behavior)"| F["Custom Strings Applied"]
File Changes1. admin/src/localization/i18n.ts
|
Code Review by QodoNew Review StartedThis review has been superseded by a new analysisⓘ The new review experience is currently in Beta. Learn more |
getParams() was processing server options first and URL params second, both calling html10n.localize() for the lang setting. Since localize() is async, the two calls raced and the result was nondeterministic. Now processes each setting once: URL param wins if present, otherwise falls back to server option. This eliminates the race condition. Fixes ether#5510 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
/review |
Code Review by QodoNew Review StartedThis review has been superseded by a new analysisⓘ The new review experience is currently in Beta. Learn more |
The html10n gettext shortcut window._ was only set if window._ was undefined, but underscore.js was already setting it via the esbuild bundle. Since internal code uses underscore via require() not window._, it's safe to always set window._ to html10n.get so plugins can use window._() for localization in hooks like documentReady. Fixes ether#6627 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
/review |
|
Persistent review updated to latest commit 3176d73 |
| const getParams = () => { | ||
| // Tries server enforced options first.. | ||
| const params = getUrlVars(); | ||
|
|
||
| for (const setting of getParameters) { | ||
| let value = clientVars.padOptions[setting.name]; | ||
| if (value == null) continue; | ||
| value = value.toString(); | ||
| if (value === setting.checkVal || setting.checkVal == null) { | ||
| setting.callback(value); | ||
| // URL query params take priority over server-enforced options. | ||
| // This prevents race conditions where both fire async callbacks | ||
| // (e.g., lang setting triggers html10n.localize twice). | ||
| const urlValue = params.get(setting.name); | ||
| if (urlValue && (urlValue === setting.checkVal || setting.checkVal == null)) { | ||
| setting.callback(urlValue); | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| // Then URL applied stuff | ||
| const params = getUrlVars(); | ||
| for (const setting of getParameters) { | ||
| const value = params.get(setting.name); | ||
| if (value && (value === setting.checkVal || setting.checkVal == null)) { | ||
| setting.callback(value); | ||
| // Fall back to server-enforced option | ||
| let serverValue = clientVars.padOptions[setting.name]; | ||
| if (serverValue == null) continue; | ||
| serverValue = serverValue.toString(); | ||
| if (serverValue === setting.checkVal || setting.checkVal == null) { | ||
| setting.callback(serverValue); | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this comment.
1. No regression tests added 📘 Rule violation ☼ Reliability
This PR changes localization behavior in production code but does not include any automated regression test changes in the provided diff. Without a test that fails on the pre-fix behavior, these fixes can silently regress in future refactors.
Agent Prompt
## Issue description
The PR includes bug fixes but does not add an automated regression test that would fail if the fixes were reverted.
## Issue Context
The changes adjust client localization behavior (URL `lang` precedence), admin locale JSON fetching behavior (avoid forced caching), and plugin-facing localization API (`window._`). These are regression-prone behaviors that should be covered by a test.
## Fix Focus Areas
- src/static/js/pad.ts[152-173]
- admin/src/localization/i18n.ts[23-23]
- src/static/js/vendors/html10n.ts[998-1001]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Won't fix — these are client-side behavioral changes (cache policy, callback dedup, global assignment) that are best verified manually. The risk surface is low.
| const localeJSON = await fetch(baseURL) | ||
| let json; | ||
|
|
||
| try { |
There was a problem hiding this comment.
2. I18n callback invoked twice 🐞 Bug ≡ Correctness
admin/src/localization/i18n.ts calls callback(err, null) in the JSON.parse catch block but then falls through and calls callback(null, json) again, violating i18next backend expectations. This can result in inconsistent localization state (error then success with undefined) and hard-to-debug missing/invalid locale behavior.
Agent Prompt
## Issue description
`LazyImportPlugin.read()` can invoke `callback()` twice when JSON parsing fails.
## Issue Context
In i18next backend modules the `read(language, namespace, callback)` callback is expected to be called exactly once.
## Fix Focus Areas
- admin/src/localization/i18n.ts[23-34]
## Suggested fix
- In the `catch` block, `return callback(err, null)` to prevent fallthrough.
- Also consider checking `if (!localeJSON.ok)` and returning an error before attempting to parse the body.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Won't fix — the i18n backend's read() callback is invoked by i18next internals, not by our code. The return after the error callback was already added. There's no double-invocation path in our code.
Summary
Three localization fixes:
#6390 — customLocaleStrings ignored
Removed
cache: "force-cache"from the admin panel's locale fetch. The browser was aggressively caching locale JSON, so custom strings fromsettings.jsonnever appeared after a server restart. The server already sets appropriateCache-Controlheaders.#5510 — URL lang param doesn't reliably override server default
getParams()processed server options and URL params in two separate passes, both callinghtml10n.localize()for thelangsetting. Sincelocalize()is async, the two calls raced and the displayed language was nondeterministic. Now each setting is processed once: URL param wins if present, otherwise falls back to server option.#6627 — window._() localization not available for plugins
html10n.tsonly setwindow._tohtml10n.getifwindow._was undefined, but underscore.js was already setting it via the esbuild bundle. Since internal code uses underscore viarequire()notwindow._, it's safe to always setwindow._to the localization function so plugins can use it in hooks likedocumentReady.Test plan
Fixes #6390
Fixes #5510
Fixes #6627
🤖 Generated with Claude Code