mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 18:22:40 +00:00
[feat] implement dynamic imports for locale code splitting (#6076)
## Summary
- Implement dynamic imports for internationalization (i18n) locale files
to reduce initial bundle size
- Only load English locale eagerly as default/fallback, load other
locales on-demand
- Apply code splitting to both main ComfyUI frontend and desktop-ui
applications
## Technical Details
- **Before**: All locale files (main.json, nodeDefs.json, commands.json,
settings.json) for all 9 languages were bundled in the initial
JavaScript bundle
- **After**: Only English locale files are included in initial bundle,
other locales are loaded dynamically when needed
- Implemented `loadLocale()` function that uses dynamic imports with
`Promise.all()` for efficient parallel loading
- Added locale tracking with `loadedLocales` Set to prevent duplicate
loading
- Updated both `src/i18n.ts` and `apps/desktop-ui/src/i18n.ts` with
consistent implementation
## Bundle Size Impact
This change significantly reduces the initial bundle size by removing ~8
languages worth of JSON locale data from the main bundle. Locale files
are now loaded on-demand only when users switch languages.
## Implementation
- Uses dynamic imports: `import('./locales/[locale]/[file].json')`
- Maintains backward compatibility with existing locale switching
mechanism
- Graceful error handling for unsupported locales
- No breaking changes to the public API
## Test plan
- [x] Verify initial load only includes English locale
- [x] Test dynamic locale loading when switching languages in settings
- [x] Confirm fallback behavior for unsupported locales
- [x] Validate both web and desktop-ui applications work correctly
🤖 Generated with [Claude Code](https://claude.ai/code)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6076-feat-implement-dynamic-imports-for-locale-code-splitting-28d6d73d36508189ae0ef060804a5cee)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,67 +1,163 @@
|
|||||||
import arCommands from '@frontend-locales/ar/commands.json' with { type: 'json' }
|
// Import only English locale eagerly as the default/fallback
|
||||||
import ar from '@frontend-locales/ar/main.json' with { type: 'json' }
|
// ESLint cannot statically resolve dynamic imports with path aliases (@frontend-locales/*),
|
||||||
import arNodes from '@frontend-locales/ar/nodeDefs.json' with { type: 'json' }
|
// but these are properly configured in tsconfig.json and resolved by Vite at build time.
|
||||||
import arSettings from '@frontend-locales/ar/settings.json' with { type: 'json' }
|
// eslint-disable-next-line import-x/no-unresolved
|
||||||
import enCommands from '@frontend-locales/en/commands.json' with { type: 'json' }
|
import enCommands from '@frontend-locales/en/commands.json' with { type: 'json' }
|
||||||
|
// eslint-disable-next-line import-x/no-unresolved
|
||||||
import en from '@frontend-locales/en/main.json' with { type: 'json' }
|
import en from '@frontend-locales/en/main.json' with { type: 'json' }
|
||||||
|
// eslint-disable-next-line import-x/no-unresolved
|
||||||
import enNodes from '@frontend-locales/en/nodeDefs.json' with { type: 'json' }
|
import enNodes from '@frontend-locales/en/nodeDefs.json' with { type: 'json' }
|
||||||
|
// eslint-disable-next-line import-x/no-unresolved
|
||||||
import enSettings from '@frontend-locales/en/settings.json' with { type: 'json' }
|
import enSettings from '@frontend-locales/en/settings.json' with { type: 'json' }
|
||||||
import esCommands from '@frontend-locales/es/commands.json' with { type: 'json' }
|
|
||||||
import es from '@frontend-locales/es/main.json' with { type: 'json' }
|
|
||||||
import esNodes from '@frontend-locales/es/nodeDefs.json' with { type: 'json' }
|
|
||||||
import esSettings from '@frontend-locales/es/settings.json' with { type: 'json' }
|
|
||||||
import frCommands from '@frontend-locales/fr/commands.json' with { type: 'json' }
|
|
||||||
import fr from '@frontend-locales/fr/main.json' with { type: 'json' }
|
|
||||||
import frNodes from '@frontend-locales/fr/nodeDefs.json' with { type: 'json' }
|
|
||||||
import frSettings from '@frontend-locales/fr/settings.json' with { type: 'json' }
|
|
||||||
import jaCommands from '@frontend-locales/ja/commands.json' with { type: 'json' }
|
|
||||||
import ja from '@frontend-locales/ja/main.json' with { type: 'json' }
|
|
||||||
import jaNodes from '@frontend-locales/ja/nodeDefs.json' with { type: 'json' }
|
|
||||||
import jaSettings from '@frontend-locales/ja/settings.json' with { type: 'json' }
|
|
||||||
import koCommands from '@frontend-locales/ko/commands.json' with { type: 'json' }
|
|
||||||
import ko from '@frontend-locales/ko/main.json' with { type: 'json' }
|
|
||||||
import koNodes from '@frontend-locales/ko/nodeDefs.json' with { type: 'json' }
|
|
||||||
import koSettings from '@frontend-locales/ko/settings.json' with { type: 'json' }
|
|
||||||
import ruCommands from '@frontend-locales/ru/commands.json' with { type: 'json' }
|
|
||||||
import ru from '@frontend-locales/ru/main.json' with { type: 'json' }
|
|
||||||
import ruNodes from '@frontend-locales/ru/nodeDefs.json' with { type: 'json' }
|
|
||||||
import ruSettings from '@frontend-locales/ru/settings.json' with { type: 'json' }
|
|
||||||
import trCommands from '@frontend-locales/tr/commands.json' with { type: 'json' }
|
|
||||||
import tr from '@frontend-locales/tr/main.json' with { type: 'json' }
|
|
||||||
import trNodes from '@frontend-locales/tr/nodeDefs.json' with { type: 'json' }
|
|
||||||
import trSettings from '@frontend-locales/tr/settings.json' with { type: 'json' }
|
|
||||||
import zhTWCommands from '@frontend-locales/zh-TW/commands.json' with { type: 'json' }
|
|
||||||
import zhTW from '@frontend-locales/zh-TW/main.json' with { type: 'json' }
|
|
||||||
import zhTWNodes from '@frontend-locales/zh-TW/nodeDefs.json' with { type: 'json' }
|
|
||||||
import zhTWSettings from '@frontend-locales/zh-TW/settings.json' with { type: 'json' }
|
|
||||||
import zhCommands from '@frontend-locales/zh/commands.json' with { type: 'json' }
|
|
||||||
import zh from '@frontend-locales/zh/main.json' with { type: 'json' }
|
|
||||||
import zhNodes from '@frontend-locales/zh/nodeDefs.json' with { type: 'json' }
|
|
||||||
import zhSettings from '@frontend-locales/zh/settings.json' with { type: 'json' }
|
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
function buildLocale<M, N, C, S>(main: M, nodes: N, commands: C, settings: S) {
|
function buildLocale<
|
||||||
|
M extends Record<string, unknown>,
|
||||||
|
N extends Record<string, unknown>,
|
||||||
|
C extends Record<string, unknown>,
|
||||||
|
S extends Record<string, unknown>
|
||||||
|
>(main: M, nodes: N, commands: C, settings: S) {
|
||||||
return {
|
return {
|
||||||
...main,
|
...main,
|
||||||
nodeDefs: nodes,
|
nodeDefs: nodes,
|
||||||
commands: commands,
|
commands: commands,
|
||||||
settings: settings
|
settings: settings
|
||||||
}
|
} as M & { nodeDefs: N; commands: C; settings: S }
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = {
|
// Locale loader map - dynamically import locales only when needed
|
||||||
en: buildLocale(en, enNodes, enCommands, enSettings),
|
// ESLint cannot statically resolve these dynamic imports, but they are valid at build time
|
||||||
zh: buildLocale(zh, zhNodes, zhCommands, zhSettings),
|
/* eslint-disable import-x/no-unresolved */
|
||||||
'zh-TW': buildLocale(zhTW, zhTWNodes, zhTWCommands, zhTWSettings),
|
const localeLoaders: Record<
|
||||||
ru: buildLocale(ru, ruNodes, ruCommands, ruSettings),
|
string,
|
||||||
ja: buildLocale(ja, jaNodes, jaCommands, jaSettings),
|
() => Promise<{ default: Record<string, unknown> }>
|
||||||
ko: buildLocale(ko, koNodes, koCommands, koSettings),
|
> = {
|
||||||
fr: buildLocale(fr, frNodes, frCommands, frSettings),
|
ar: () => import('@frontend-locales/ar/main.json'),
|
||||||
es: buildLocale(es, esNodes, esCommands, esSettings),
|
es: () => import('@frontend-locales/es/main.json'),
|
||||||
ar: buildLocale(ar, arNodes, arCommands, arSettings),
|
fr: () => import('@frontend-locales/fr/main.json'),
|
||||||
tr: buildLocale(tr, trNodes, trCommands, trSettings)
|
ja: () => import('@frontend-locales/ja/main.json'),
|
||||||
|
ko: () => import('@frontend-locales/ko/main.json'),
|
||||||
|
ru: () => import('@frontend-locales/ru/main.json'),
|
||||||
|
tr: () => import('@frontend-locales/tr/main.json'),
|
||||||
|
zh: () => import('@frontend-locales/zh/main.json'),
|
||||||
|
'zh-TW': () => import('@frontend-locales/zh-TW/main.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nodeDefsLoaders: Record<
|
||||||
|
string,
|
||||||
|
() => Promise<{ default: Record<string, unknown> }>
|
||||||
|
> = {
|
||||||
|
ar: () => import('@frontend-locales/ar/nodeDefs.json'),
|
||||||
|
es: () => import('@frontend-locales/es/nodeDefs.json'),
|
||||||
|
fr: () => import('@frontend-locales/fr/nodeDefs.json'),
|
||||||
|
ja: () => import('@frontend-locales/ja/nodeDefs.json'),
|
||||||
|
ko: () => import('@frontend-locales/ko/nodeDefs.json'),
|
||||||
|
ru: () => import('@frontend-locales/ru/nodeDefs.json'),
|
||||||
|
tr: () => import('@frontend-locales/tr/nodeDefs.json'),
|
||||||
|
zh: () => import('@frontend-locales/zh/nodeDefs.json'),
|
||||||
|
'zh-TW': () => import('@frontend-locales/zh-TW/nodeDefs.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandsLoaders: Record<
|
||||||
|
string,
|
||||||
|
() => Promise<{ default: Record<string, unknown> }>
|
||||||
|
> = {
|
||||||
|
ar: () => import('@frontend-locales/ar/commands.json'),
|
||||||
|
es: () => import('@frontend-locales/es/commands.json'),
|
||||||
|
fr: () => import('@frontend-locales/fr/commands.json'),
|
||||||
|
ja: () => import('@frontend-locales/ja/commands.json'),
|
||||||
|
ko: () => import('@frontend-locales/ko/commands.json'),
|
||||||
|
ru: () => import('@frontend-locales/ru/commands.json'),
|
||||||
|
tr: () => import('@frontend-locales/tr/commands.json'),
|
||||||
|
zh: () => import('@frontend-locales/zh/commands.json'),
|
||||||
|
'zh-TW': () => import('@frontend-locales/zh-TW/commands.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsLoaders: Record<
|
||||||
|
string,
|
||||||
|
() => Promise<{ default: Record<string, unknown> }>
|
||||||
|
> = {
|
||||||
|
ar: () => import('@frontend-locales/ar/settings.json'),
|
||||||
|
es: () => import('@frontend-locales/es/settings.json'),
|
||||||
|
fr: () => import('@frontend-locales/fr/settings.json'),
|
||||||
|
ja: () => import('@frontend-locales/ja/settings.json'),
|
||||||
|
ko: () => import('@frontend-locales/ko/settings.json'),
|
||||||
|
ru: () => import('@frontend-locales/ru/settings.json'),
|
||||||
|
tr: () => import('@frontend-locales/tr/settings.json'),
|
||||||
|
zh: () => import('@frontend-locales/zh/settings.json'),
|
||||||
|
'zh-TW': () => import('@frontend-locales/zh-TW/settings.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track which locales have been loaded
|
||||||
|
const loadedLocales = new Set<string>(['en'])
|
||||||
|
|
||||||
|
// Track locales currently being loaded to prevent race conditions
|
||||||
|
const loadingLocales = new Map<string, Promise<void>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically load a locale and its associated files (nodeDefs, commands, settings)
|
||||||
|
*/
|
||||||
|
export async function loadLocale(locale: string): Promise<void> {
|
||||||
|
if (loadedLocales.has(locale)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already loading, return the existing promise to prevent duplicate loads
|
||||||
|
const existingLoad = loadingLocales.get(locale)
|
||||||
|
if (existingLoad) {
|
||||||
|
return existingLoad
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = localeLoaders[locale]
|
||||||
|
const nodeDefsLoader = nodeDefsLoaders[locale]
|
||||||
|
const commandsLoader = commandsLoaders[locale]
|
||||||
|
const settingsLoader = settingsLoaders[locale]
|
||||||
|
|
||||||
|
if (!loader || !nodeDefsLoader || !commandsLoader || !settingsLoader) {
|
||||||
|
console.warn(`Locale "${locale}" is not supported`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and track the loading promise
|
||||||
|
const loadPromise = (async () => {
|
||||||
|
try {
|
||||||
|
const [main, nodes, commands, settings] = await Promise.all([
|
||||||
|
loader(),
|
||||||
|
nodeDefsLoader(),
|
||||||
|
commandsLoader(),
|
||||||
|
settingsLoader()
|
||||||
|
])
|
||||||
|
|
||||||
|
const messages = buildLocale(
|
||||||
|
main.default,
|
||||||
|
nodes.default,
|
||||||
|
commands.default,
|
||||||
|
settings.default
|
||||||
|
)
|
||||||
|
|
||||||
|
i18n.global.setLocaleMessage(locale, messages as LocaleMessages)
|
||||||
|
loadedLocales.add(locale)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load locale "${locale}":`, error)
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
// Clean up the loading promise once complete
|
||||||
|
loadingLocales.delete(locale)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
loadingLocales.set(locale, loadPromise)
|
||||||
|
return loadPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only include English in the initial bundle
|
||||||
|
const messages = {
|
||||||
|
en: buildLocale(en, enNodes, enCommands, enSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type for locale messages - inferred from the English locale structure
|
||||||
|
type LocaleMessages = typeof messages.en
|
||||||
|
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
// Must set `false`, as Vue I18n Legacy API is for Vue 2
|
// Must set `false`, as Vue I18n Legacy API is for Vue 2
|
||||||
legacy: false,
|
legacy: false,
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ const config: KnipConfig = {
|
|||||||
],
|
],
|
||||||
project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}']
|
project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}']
|
||||||
},
|
},
|
||||||
|
'apps/desktop-ui': {
|
||||||
|
entry: ['src/main.ts', 'src/i18n.ts'],
|
||||||
|
project: ['src/**/*.{js,ts,vue}', '*.{js,ts,mts}']
|
||||||
|
},
|
||||||
'packages/tailwind-utils': {
|
'packages/tailwind-utils': {
|
||||||
project: ['src/**/*.{js,ts}']
|
project: ['src/**/*.{js,ts}']
|
||||||
},
|
},
|
||||||
|
|||||||
189
src/i18n.ts
189
src/i18n.ts
@@ -1,68 +1,159 @@
|
|||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import arCommands from './locales/ar/commands.json' with { type: 'json' }
|
// ESLint cannot statically resolve dynamic imports with relative paths in template strings,
|
||||||
import ar from './locales/ar/main.json' with { type: 'json' }
|
// but these are valid ES module imports that Vite processes correctly at build time.
|
||||||
import arNodes from './locales/ar/nodeDefs.json' with { type: 'json' }
|
|
||||||
import arSettings from './locales/ar/settings.json' with { type: 'json' }
|
// Import only English locale eagerly as the default/fallback
|
||||||
import enCommands from './locales/en/commands.json' with { type: 'json' }
|
import enCommands from './locales/en/commands.json' with { type: 'json' }
|
||||||
import en from './locales/en/main.json' with { type: 'json' }
|
import en from './locales/en/main.json' with { type: 'json' }
|
||||||
import enNodes from './locales/en/nodeDefs.json' with { type: 'json' }
|
import enNodes from './locales/en/nodeDefs.json' with { type: 'json' }
|
||||||
import enSettings from './locales/en/settings.json' with { type: 'json' }
|
import enSettings from './locales/en/settings.json' with { type: 'json' }
|
||||||
import esCommands from './locales/es/commands.json' with { type: 'json' }
|
|
||||||
import es from './locales/es/main.json' with { type: 'json' }
|
|
||||||
import esNodes from './locales/es/nodeDefs.json' with { type: 'json' }
|
|
||||||
import esSettings from './locales/es/settings.json' with { type: 'json' }
|
|
||||||
import frCommands from './locales/fr/commands.json' with { type: 'json' }
|
|
||||||
import fr from './locales/fr/main.json' with { type: 'json' }
|
|
||||||
import frNodes from './locales/fr/nodeDefs.json' with { type: 'json' }
|
|
||||||
import frSettings from './locales/fr/settings.json' with { type: 'json' }
|
|
||||||
import jaCommands from './locales/ja/commands.json' with { type: 'json' }
|
|
||||||
import ja from './locales/ja/main.json' with { type: 'json' }
|
|
||||||
import jaNodes from './locales/ja/nodeDefs.json' with { type: 'json' }
|
|
||||||
import jaSettings from './locales/ja/settings.json' with { type: 'json' }
|
|
||||||
import koCommands from './locales/ko/commands.json' with { type: 'json' }
|
|
||||||
import ko from './locales/ko/main.json' with { type: 'json' }
|
|
||||||
import koNodes from './locales/ko/nodeDefs.json' with { type: 'json' }
|
|
||||||
import koSettings from './locales/ko/settings.json' with { type: 'json' }
|
|
||||||
import ruCommands from './locales/ru/commands.json' with { type: 'json' }
|
|
||||||
import ru from './locales/ru/main.json' with { type: 'json' }
|
|
||||||
import ruNodes from './locales/ru/nodeDefs.json' with { type: 'json' }
|
|
||||||
import ruSettings from './locales/ru/settings.json' with { type: 'json' }
|
|
||||||
import trCommands from './locales/tr/commands.json' with { type: 'json' }
|
|
||||||
import tr from './locales/tr/main.json' with { type: 'json' }
|
|
||||||
import trNodes from './locales/tr/nodeDefs.json' with { type: 'json' }
|
|
||||||
import trSettings from './locales/tr/settings.json' with { type: 'json' }
|
|
||||||
import zhTWCommands from './locales/zh-TW/commands.json' with { type: 'json' }
|
|
||||||
import zhTW from './locales/zh-TW/main.json' with { type: 'json' }
|
|
||||||
import zhTWNodes from './locales/zh-TW/nodeDefs.json' with { type: 'json' }
|
|
||||||
import zhTWSettings from './locales/zh-TW/settings.json' with { type: 'json' }
|
|
||||||
import zhCommands from './locales/zh/commands.json' with { type: 'json' }
|
|
||||||
import zh from './locales/zh/main.json' with { type: 'json' }
|
|
||||||
import zhNodes from './locales/zh/nodeDefs.json' with { type: 'json' }
|
|
||||||
import zhSettings from './locales/zh/settings.json' with { type: 'json' }
|
|
||||||
|
|
||||||
function buildLocale<M, N, C, S>(main: M, nodes: N, commands: C, settings: S) {
|
function buildLocale<
|
||||||
|
M extends Record<string, unknown>,
|
||||||
|
N extends Record<string, unknown>,
|
||||||
|
C extends Record<string, unknown>,
|
||||||
|
S extends Record<string, unknown>
|
||||||
|
>(main: M, nodes: N, commands: C, settings: S) {
|
||||||
return {
|
return {
|
||||||
...main,
|
...main,
|
||||||
nodeDefs: nodes,
|
nodeDefs: nodes,
|
||||||
commands: commands,
|
commands: commands,
|
||||||
settings: settings
|
settings: settings
|
||||||
}
|
} as M & { nodeDefs: N; commands: C; settings: S }
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = {
|
// Locale loader map - dynamically import locales only when needed
|
||||||
en: buildLocale(en, enNodes, enCommands, enSettings),
|
const localeLoaders: Record<
|
||||||
zh: buildLocale(zh, zhNodes, zhCommands, zhSettings),
|
string,
|
||||||
'zh-TW': buildLocale(zhTW, zhTWNodes, zhTWCommands, zhTWSettings),
|
() => Promise<{ default: Record<string, unknown> }>
|
||||||
ru: buildLocale(ru, ruNodes, ruCommands, ruSettings),
|
> = {
|
||||||
ja: buildLocale(ja, jaNodes, jaCommands, jaSettings),
|
ar: () => import('./locales/ar/main.json'),
|
||||||
ko: buildLocale(ko, koNodes, koCommands, koSettings),
|
es: () => import('./locales/es/main.json'),
|
||||||
fr: buildLocale(fr, frNodes, frCommands, frSettings),
|
fr: () => import('./locales/fr/main.json'),
|
||||||
es: buildLocale(es, esNodes, esCommands, esSettings),
|
ja: () => import('./locales/ja/main.json'),
|
||||||
ar: buildLocale(ar, arNodes, arCommands, arSettings),
|
ko: () => import('./locales/ko/main.json'),
|
||||||
tr: buildLocale(tr, trNodes, trCommands, trSettings)
|
ru: () => import('./locales/ru/main.json'),
|
||||||
|
tr: () => import('./locales/tr/main.json'),
|
||||||
|
zh: () => import('./locales/zh/main.json'),
|
||||||
|
'zh-TW': () => import('./locales/zh-TW/main.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nodeDefsLoaders: Record<
|
||||||
|
string,
|
||||||
|
() => Promise<{ default: Record<string, unknown> }>
|
||||||
|
> = {
|
||||||
|
ar: () => import('./locales/ar/nodeDefs.json'),
|
||||||
|
es: () => import('./locales/es/nodeDefs.json'),
|
||||||
|
fr: () => import('./locales/fr/nodeDefs.json'),
|
||||||
|
ja: () => import('./locales/ja/nodeDefs.json'),
|
||||||
|
ko: () => import('./locales/ko/nodeDefs.json'),
|
||||||
|
ru: () => import('./locales/ru/nodeDefs.json'),
|
||||||
|
tr: () => import('./locales/tr/nodeDefs.json'),
|
||||||
|
zh: () => import('./locales/zh/nodeDefs.json'),
|
||||||
|
'zh-TW': () => import('./locales/zh-TW/nodeDefs.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandsLoaders: Record<
|
||||||
|
string,
|
||||||
|
() => Promise<{ default: Record<string, unknown> }>
|
||||||
|
> = {
|
||||||
|
ar: () => import('./locales/ar/commands.json'),
|
||||||
|
es: () => import('./locales/es/commands.json'),
|
||||||
|
fr: () => import('./locales/fr/commands.json'),
|
||||||
|
ja: () => import('./locales/ja/commands.json'),
|
||||||
|
ko: () => import('./locales/ko/commands.json'),
|
||||||
|
ru: () => import('./locales/ru/commands.json'),
|
||||||
|
tr: () => import('./locales/tr/commands.json'),
|
||||||
|
zh: () => import('./locales/zh/commands.json'),
|
||||||
|
'zh-TW': () => import('./locales/zh-TW/commands.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsLoaders: Record<
|
||||||
|
string,
|
||||||
|
() => Promise<{ default: Record<string, unknown> }>
|
||||||
|
> = {
|
||||||
|
ar: () => import('./locales/ar/settings.json'),
|
||||||
|
es: () => import('./locales/es/settings.json'),
|
||||||
|
fr: () => import('./locales/fr/settings.json'),
|
||||||
|
ja: () => import('./locales/ja/settings.json'),
|
||||||
|
ko: () => import('./locales/ko/settings.json'),
|
||||||
|
ru: () => import('./locales/ru/settings.json'),
|
||||||
|
tr: () => import('./locales/tr/settings.json'),
|
||||||
|
zh: () => import('./locales/zh/settings.json'),
|
||||||
|
'zh-TW': () => import('./locales/zh-TW/settings.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track which locales have been loaded
|
||||||
|
const loadedLocales = new Set<string>(['en'])
|
||||||
|
|
||||||
|
// Track locales currently being loaded to prevent race conditions
|
||||||
|
const loadingLocales = new Map<string, Promise<void>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically load a locale and its associated files (nodeDefs, commands, settings)
|
||||||
|
*/
|
||||||
|
export async function loadLocale(locale: string): Promise<void> {
|
||||||
|
if (loadedLocales.has(locale)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already loading, return the existing promise to prevent duplicate loads
|
||||||
|
const existingLoad = loadingLocales.get(locale)
|
||||||
|
if (existingLoad) {
|
||||||
|
return existingLoad
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = localeLoaders[locale]
|
||||||
|
const nodeDefsLoader = nodeDefsLoaders[locale]
|
||||||
|
const commandsLoader = commandsLoaders[locale]
|
||||||
|
const settingsLoader = settingsLoaders[locale]
|
||||||
|
|
||||||
|
if (!loader || !nodeDefsLoader || !commandsLoader || !settingsLoader) {
|
||||||
|
console.warn(`Locale "${locale}" is not supported`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and track the loading promise
|
||||||
|
const loadPromise = (async () => {
|
||||||
|
try {
|
||||||
|
const [main, nodes, commands, settings] = await Promise.all([
|
||||||
|
loader(),
|
||||||
|
nodeDefsLoader(),
|
||||||
|
commandsLoader(),
|
||||||
|
settingsLoader()
|
||||||
|
])
|
||||||
|
|
||||||
|
const messages = buildLocale(
|
||||||
|
main.default,
|
||||||
|
nodes.default,
|
||||||
|
commands.default,
|
||||||
|
settings.default
|
||||||
|
)
|
||||||
|
|
||||||
|
i18n.global.setLocaleMessage(locale, messages as LocaleMessages)
|
||||||
|
loadedLocales.add(locale)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load locale "${locale}":`, error)
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
// Clean up the loading promise once complete
|
||||||
|
loadingLocales.delete(locale)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
loadingLocales.set(locale, loadPromise)
|
||||||
|
return loadPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only include English in the initial bundle
|
||||||
|
const messages = {
|
||||||
|
en: buildLocale(en, enNodes, enCommands, enSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type for locale messages - inferred from the English locale structure
|
||||||
|
type LocaleMessages = typeof messages.en
|
||||||
|
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
// Must set `false`, as Vue I18n Legacy API is for Vue 2
|
// Must set `false`, as Vue I18n Legacy API is for Vue 2
|
||||||
legacy: false,
|
legacy: false,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import { useCoreCommands } from '@/composables/useCoreCommands'
|
|||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
import { useProgressFavicon } from '@/composables/useProgressFavicon'
|
import { useProgressFavicon } from '@/composables/useProgressFavicon'
|
||||||
import { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig'
|
import { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig'
|
||||||
import { i18n } from '@/i18n'
|
import { i18n, loadLocale } from '@/i18n'
|
||||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||||
import { useFrontendVersionMismatchWarning } from '@/platform/updates/common/useFrontendVersionMismatchWarning'
|
import { useFrontendVersionMismatchWarning } from '@/platform/updates/common/useFrontendVersionMismatchWarning'
|
||||||
import { useVersionCompatibilityStore } from '@/platform/updates/common/versionCompatibilityStore'
|
import { useVersionCompatibilityStore } from '@/platform/updates/common/versionCompatibilityStore'
|
||||||
@@ -145,10 +145,17 @@ watchEffect(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(async () => {
|
||||||
const locale = settingStore.get('Comfy.Locale')
|
const locale = settingStore.get('Comfy.Locale')
|
||||||
if (locale) {
|
if (locale) {
|
||||||
i18n.global.locale.value = locale as 'en' | 'zh' | 'ru' | 'ja'
|
// Load the locale dynamically if not already loaded
|
||||||
|
try {
|
||||||
|
await loadLocale(locale)
|
||||||
|
// Type assertion is safe here as loadLocale validates the locale exists
|
||||||
|
i18n.global.locale.value = locale as typeof i18n.global.locale.value
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to switch to locale "${locale}":`, error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user