mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-23 06:10:32 +00:00
## Summary
Sidebar buttons rendered literal i18n keys (e.g.
`sideToolbar.labels.assets`) on a fresh install when the user's
`navigator.language` base tag wasn't one of the 12 shipped locales —
German/Italian/Polish/Dutch/Brazilian-Portuguese users among others.
## Changes
- **What**: Add `resolveSupportedLocale()` that tries the full BCP-47
tag first (preserves `zh-TW`, `pt-BR`), then the base tag, then `'en'`.
Wire through both entry points (`createI18n`'s initial locale,
`Comfy.Locale`'s `defaultValue`) and clamp inside `loadLocale`,
propagating the resolved tag to `GraphView` so a stale stored
`Comfy.Locale='de'` from older builds also recovers.
- **Side benefit**: Brazilian Portuguese users were previously falling
through `pt-BR` → `pt` (unshipped) → broken. The full-tag-first lookup
now correctly lands them on the `pt-BR` bundle.
- **Breaking**: None.
- **Dependencies**: None.
## Root Cause
Three-link chain:
1. `Comfy.Locale`'s default was `() => navigator.language.split('-')[0]
|| 'en'`. German → `'de'` (unshipped).
2. `loadLocale('de')` silently `console.warn`'d and returned without
throwing.
3. `GraphView` then ran `i18n.global.locale.value = 'de'` anyway.
4. `st(key, fallback) = te(key) ? t(key) : fallback`. vue-i18n's `te()`
checks **only** the current locale and ignores `fallbackLocale` — every
key missed → `st()` returned the literal key string.
Two pathways reached the broken state (defaultValue path, and
unset-setting path through `createI18n`'s own `navigator.language`
snapshot); the new helper closes both.
## Review Focus
- `loadLocale` now returns `SupportedLocale` (was `void`). Old `void`
callers continue to compile; the only change is `GraphView` consuming
the return value.
- Unit-tested in `src/i18n.test.ts` (added `resolveSupportedLocale`
block + updated the `loadLocale` unsupported-locale case from "warn" to
"clamp to en").
- Self-reproduced via Playwright with `navigator.language='de-DE'` +
fresh-install state on both `main` (shows the bug) and this branch
(shows the fix). Spec saved at
`temp/scripts/issue-10563-locale-bug.spec.ts`.
Fixes #10563
FE-480 — https://linear.app/comfyorg/issue/FE-480
## Screenshots
**Before** (from #10563, on `main`):
<img width="258" height="399" alt="Sidebar with literal i18n keys"
src="https://github.com/user-attachments/assets/098d1d76-8e89-4237-813f-5f030b34e51e"
/>
**After** (this branch, same `navigator.language='de-DE'`):
<img width="367" height="793" alt="Screenshot 2026-04-28 at 2 07 38 PM"
src="https://github.com/user-attachments/assets/9d279de3-50a8-4774-999f-ab4c3018a9ef"
/>
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11712-fix-i18n-clamp-unsupported-browser-locales-to-a-shipped-tag-3506d73d3650812f89d2f0fe3199de3a)
by [Unito](https://www.unito.io)
102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
|
|
import type { CustomNodesI18n } from '@/schemas/apiSchema'
|
|
import {
|
|
comfyExpect as expect,
|
|
comfyPageFixture as test
|
|
} from '@e2e/fixtures/ComfyPage'
|
|
|
|
const NODE_TYPE = 'DevToolsNodeWithStringInput'
|
|
const LOCALIZED_ZH = '本地化字符串输入 (ZH)'
|
|
const LOCALIZED_ZH_TW = '本地化字串輸入 (ZH-TW)'
|
|
const LOCALIZED_EN = 'Localized String Input (EN)'
|
|
|
|
async function routeCustomNodesI18n(page: Page, body: CustomNodesI18n) {
|
|
await page.route('**/api/i18n', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(body)
|
|
})
|
|
})
|
|
}
|
|
|
|
test.describe(
|
|
'Custom node locales loading',
|
|
{ tag: ['@ui', '@vue-nodes'] },
|
|
() => {
|
|
test.describe('shipped base tag', () => {
|
|
test.use({ initialSettings: { 'Comfy.Locale': 'zh' } })
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await routeCustomNodesI18n(page, {
|
|
zh: { nodeDefs: { [NODE_TYPE]: { display_name: LOCALIZED_ZH } } }
|
|
})
|
|
})
|
|
|
|
// Regression test for PR #7214 (issue #7025): custom-node i18n data was
|
|
// clobbered when a non-English locale was lazily loaded, so nodes from
|
|
// custom packs lost their translated display_name on locale switch.
|
|
test('preserves custom-node /api/i18n translation through lazy locale load', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.nodeOps.addNode(NODE_TYPE)
|
|
|
|
await expect(
|
|
comfyPage.vueNodes.getNodeByTitle(LOCALIZED_ZH)
|
|
).toHaveCount(1)
|
|
})
|
|
})
|
|
|
|
test.describe('unsupported tag clamps to en', () => {
|
|
// Regression test for PR #11712 (issue #10563): when Comfy.Locale holds
|
|
// an unsupported tag, the boundary helper clamps it to 'en'. Custom-node
|
|
// 'en' translations must still merge into the active locale messages.
|
|
test.use({ initialSettings: { 'Comfy.Locale': 'de' } })
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await routeCustomNodesI18n(page, {
|
|
en: { nodeDefs: { [NODE_TYPE]: { display_name: LOCALIZED_EN } } }
|
|
})
|
|
})
|
|
|
|
test('renders en custom-node translation when locale clamps to en', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.nodeOps.addNode(NODE_TYPE)
|
|
|
|
await expect(
|
|
comfyPage.vueNodes.getNodeByTitle(LOCALIZED_EN)
|
|
).toHaveCount(1)
|
|
})
|
|
})
|
|
|
|
test.describe('regional tag preserved', () => {
|
|
// Regression test for PR #11712: full-tag match must beat base-tag
|
|
// fallback, so a shipped regional tag like 'zh-TW' is not collapsed to
|
|
// its base ('zh'). Both keys are present in the payload — the active
|
|
// locale must merge the regional variant.
|
|
test.use({ initialSettings: { 'Comfy.Locale': 'zh-TW' } })
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await routeCustomNodesI18n(page, {
|
|
zh: { nodeDefs: { [NODE_TYPE]: { display_name: LOCALIZED_ZH } } },
|
|
'zh-TW': {
|
|
nodeDefs: { [NODE_TYPE]: { display_name: LOCALIZED_ZH_TW } }
|
|
}
|
|
})
|
|
})
|
|
|
|
test('uses zh-TW custom-node translation, not zh base-tag fallback', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.nodeOps.addNode(NODE_TYPE)
|
|
|
|
await expect(
|
|
comfyPage.vueNodes.getNodeByTitle(LOCALIZED_ZH_TW)
|
|
).toHaveCount(1)
|
|
})
|
|
})
|
|
}
|
|
)
|