mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 09:37:28 +00:00
🐛 fix(i18n): preload default language from JSON to avoid Suspense on first render (#12895)
* 🐛 fix(i18n): preload default language from JSON to avoid Suspense on first render - Sync load en-US common/error/chat from locales/en-US/*.json - Use JSON (not locales/default/*.ts) as runtime values - TS source is type-only - Prevents useTranslation from suspending, avoids CLS from 44px skeleton fallback Made-with: Cursor * ✨ feat(i18n): enable partial loading of languages and add tests for dynamic namespace loading Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
4988413d58
commit
874c2dd706
2 changed files with 35 additions and 0 deletions
|
|
@ -4,6 +4,11 @@ import resourcesToBackend from 'i18next-resources-to-backend';
|
|||
import { initReactI18next } from 'react-i18next';
|
||||
import { isRtlLang } from 'rtl-detect';
|
||||
|
||||
// Sync load default language (en-US) from JSON to avoid Suspense on first render.
|
||||
// locales/default/*.ts is for type inference only, not used as runtime values.
|
||||
import chat from '@/../locales/en-US/chat.json';
|
||||
import common from '@/../locales/en-US/common.json';
|
||||
import error from '@/../locales/en-US/error.json';
|
||||
import { DEFAULT_LANG } from '@/const/locale';
|
||||
import { getDebugConfig } from '@/envs/debug';
|
||||
import { normalizeLocale } from '@/locales/resources';
|
||||
|
|
@ -12,6 +17,8 @@ import { unwrapESMModule } from '@/utils/esm/unwrapESMModule';
|
|||
|
||||
import { loadI18nNamespaceModule } from '../utils/i18n/loadI18nNamespaceModule';
|
||||
|
||||
const defaultResources = { chat, common, error };
|
||||
|
||||
const { I18N_DEBUG, I18N_DEBUG_BROWSER, I18N_DEBUG_SERVER } = getDebugConfig();
|
||||
const debugMode = (I18N_DEBUG ?? isOnServerSide) ? I18N_DEBUG_SERVER : I18N_DEBUG_BROWSER;
|
||||
|
||||
|
|
@ -49,6 +56,13 @@ export const createI18nNext = (lang?: string) => {
|
|||
|
||||
initAsync,
|
||||
|
||||
// Preload default language (en-US) synchronously to avoid Suspense on first render
|
||||
resources: {
|
||||
[DEFAULT_LANG]: defaultResources,
|
||||
},
|
||||
// Keep backend loading enabled for namespaces that are not preloaded above.
|
||||
partialBundledLanguages: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
|
|
|
|||
21
tests/i18n/createI18nNext.test.ts
Normal file
21
tests/i18n/createI18nNext.test.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { createI18nNext } from '@/locales/create';
|
||||
|
||||
describe('createI18nNext', () => {
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('dynamically loads missing namespaces after preloading bundled defaults', async () => {
|
||||
const i18n = createI18nNext('en-US');
|
||||
|
||||
await i18n.init({ initAsync: false });
|
||||
expect(i18n.instance.hasResourceBundle('en-US', 'setting')).toBe(false);
|
||||
|
||||
await i18n.instance.loadNamespaces(['setting']);
|
||||
|
||||
expect(i18n.instance.hasResourceBundle('en-US', 'setting')).toBe(true);
|
||||
expect(i18n.instance.t('tab.common', { ns: 'setting' })).toBe('Appearance');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue