-
Notifications
You must be signed in to change notification settings - Fork 449
fix: preserve custom nodes i18n data when locales are lazily loaded #7214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| import { beforeEach, describe, expect, it, vi } from 'vitest' | ||
| const { i18n, loadLocale, mergeCustomNodesI18n } = await import('./i18n') | ||
|
|
||
| // Mock the JSON imports before importing i18n module | ||
| vi.mock('./locales/en/main.json', () => ({ default: { welcome: 'Welcome' } })) | ||
| vi.mock('./locales/en/nodeDefs.json', () => ({ | ||
| default: { testNode: 'Test Node' } | ||
| })) | ||
| vi.mock('./locales/en/commands.json', () => ({ | ||
| default: { save: 'Save' } | ||
| })) | ||
| vi.mock('./locales/en/settings.json', () => ({ | ||
| default: { theme: 'Theme' } | ||
| })) | ||
|
|
||
| // Mock lazy-loaded locales | ||
| vi.mock('./locales/zh/main.json', () => ({ default: { welcome: '欢迎' } })) | ||
| vi.mock('./locales/zh/nodeDefs.json', () => ({ | ||
| default: { testNode: '测试节点' } | ||
| })) | ||
| vi.mock('./locales/zh/commands.json', () => ({ default: { save: '保存' } })) | ||
| vi.mock('./locales/zh/settings.json', () => ({ default: { theme: '主题' } })) | ||
|
|
||
| describe('i18n', () => { | ||
| beforeEach(async () => { | ||
| vi.resetModules() | ||
| }) | ||
|
|
||
| describe('mergeCustomNodesI18n', () => { | ||
| it('should immediately merge data for already loaded locales (en)', async () => { | ||
| // English is pre-loaded, so merge should work immediately | ||
| mergeCustomNodesI18n({ | ||
| en: { | ||
| customNode: { | ||
| title: 'Custom Node Title' | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| // Verify the custom node data was merged | ||
| const messages = i18n.global.getLocaleMessage('en') as Record< | ||
| string, | ||
| unknown | ||
| > | ||
| expect(messages.customNode).toEqual({ title: 'Custom Node Title' }) | ||
| }) | ||
|
|
||
| it('should store data for not-yet-loaded locales', async () => { | ||
| const { i18n, mergeCustomNodesI18n } = await import('./i18n') | ||
|
|
||
| // Chinese is not pre-loaded, data should be stored but not merged yet | ||
| mergeCustomNodesI18n({ | ||
| zh: { | ||
| customNode: { | ||
| title: '自定义节点标题' | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| // zh locale should not exist yet (not loaded) | ||
| const zhMessages = i18n.global.getLocaleMessage('zh') as Record< | ||
| string, | ||
| unknown | ||
| > | ||
| // Either empty or doesn't have our custom data merged directly | ||
| // (since zh wasn't loaded yet, mergeLocaleMessage on non-existent locale | ||
| // may create an empty locale or do nothing useful) | ||
| expect(zhMessages.customNode).toBeUndefined() | ||
| }) | ||
|
|
||
| it('should merge stored data when locale is lazily loaded', async () => { | ||
| // First, store custom nodes i18n data (before locale is loaded) | ||
| mergeCustomNodesI18n({ | ||
| zh: { | ||
| customNode: { | ||
| title: '自定义节点标题' | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| await loadLocale('zh') | ||
|
|
||
| // Verify both the base locale data and custom node data are present | ||
| const zhMessages = i18n.global.getLocaleMessage('zh') as Record< | ||
| string, | ||
| unknown | ||
| > | ||
| expect(zhMessages.welcome).toBe('欢迎') | ||
| expect(zhMessages.customNode).toEqual({ title: '自定义节点标题' }) | ||
| }) | ||
|
|
||
| it('should preserve custom node data when locale is loaded after merge', async () => { | ||
| // Simulate the real scenario: | ||
| // 1. Custom nodes i18n is loaded first | ||
| mergeCustomNodesI18n({ | ||
| zh: { | ||
| customNode: { | ||
| title: '自定义节点标题' | ||
| }, | ||
| settingsCategories: { | ||
| Hotkeys: '快捷键' | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| // 2. Then locale is lazily loaded (this would previously overwrite custom data) | ||
| await loadLocale('zh') | ||
|
|
||
| // 3. Verify custom node data is still present | ||
| const zhMessages = i18n.global.getLocaleMessage('zh') as Record< | ||
| string, | ||
| unknown | ||
| > | ||
| expect(zhMessages.customNode).toEqual({ title: '自定义节点标题' }) | ||
| expect(zhMessages.settingsCategories).toEqual({ Hotkeys: '快捷键' }) | ||
|
|
||
| // 4. Also verify base locale data is present | ||
| expect(zhMessages.welcome).toBe('欢迎') | ||
| expect(zhMessages.nodeDefs).toEqual({ testNode: '测试节点' }) | ||
| }) | ||
|
|
||
| it('should handle multiple locales in custom nodes i18n data', async () => { | ||
| // Merge data for multiple locales | ||
| mergeCustomNodesI18n({ | ||
| en: { | ||
| customPlugin: { name: 'Easy Use' } | ||
| }, | ||
| zh: { | ||
| customPlugin: { name: '简单使用' } | ||
| } | ||
| }) | ||
|
|
||
| // English should be merged immediately (pre-loaded) | ||
| const enMessages = i18n.global.getLocaleMessage('en') as Record< | ||
| string, | ||
| unknown | ||
| > | ||
| expect(enMessages.customPlugin).toEqual({ name: 'Easy Use' }) | ||
|
|
||
| await loadLocale('zh') | ||
| const zhMessages = i18n.global.getLocaleMessage('zh') as Record< | ||
| string, | ||
| unknown | ||
| > | ||
| expect(zhMessages.customPlugin).toEqual({ name: '简单使用' }) | ||
| }) | ||
|
|
||
| it('should handle calling mergeCustomNodesI18n multiple times', async () => { | ||
| mergeCustomNodesI18n({ | ||
| zh: { plugin1: { name: '插件1' } } | ||
| }) | ||
|
|
||
| mergeCustomNodesI18n({ | ||
| zh: { plugin2: { name: '插件2' } } | ||
| }) | ||
|
|
||
| await loadLocale('zh') | ||
|
|
||
| const zhMessages = i18n.global.getLocaleMessage('zh') as Record< | ||
| string, | ||
| unknown | ||
| > | ||
| // Only the second call's data should be present | ||
| expect(zhMessages.plugin2).toEqual({ name: '插件2' }) | ||
| // First call's data is overwritten | ||
| expect(zhMessages.plugin1).toBeUndefined() | ||
|
Check failure on line 166 in src/i18n.test.ts
|
||
| }) | ||
| }) | ||
|
|
||
| describe('loadLocale', () => { | ||
| it('should not reload already loaded locale', async () => { | ||
| await loadLocale('zh') | ||
| await loadLocale('zh') | ||
|
|
||
| // Should complete without error (second call returns early) | ||
| }) | ||
|
|
||
| it('should warn for unsupported locale', async () => { | ||
| const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) | ||
|
|
||
| await loadLocale('unsupported-locale') | ||
|
|
||
| expect(consoleSpy).toHaveBeenCalledWith( | ||
| 'Locale "unsupported-locale" is not supported' | ||
| ) | ||
| consoleSpy.mockRestore() | ||
| }) | ||
|
|
||
| it('should handle concurrent load requests for same locale', async () => { | ||
| // Start multiple loads concurrently | ||
| const promises = [loadLocale('zh'), loadLocale('zh'), loadLocale('zh')] | ||
|
|
||
| await Promise.all(promises) | ||
| }) | ||
| }) | ||
| }) | ||
Uh oh!
There was an error while loading. Please reload this page.