为vite-plugin-ssr集成react-i18next:i18next未初始化问题
在vite-plugin-ssr中解决react-i18next未初始化报错并实现预加载翻译
我有一个基于vite-plugin-ssr的React项目,尝试通过react-i18next添加国际化支持。已完成i18n实例配置,并在服务端和客户端渲染代码中通过I18nextProvider提供实例,但使用<Trans>组件时出现以下错误:
i18n: hasLoadedNamespace: i18next was not initialized ['en'] i18next::translator: key "hello" for languages "en" won't get resolved as namespace "translation" was not yet loaded This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!
需要解决两个核心问题:
- 确保i18next完全初始化后再进行SSR渲染(无法使用useEffect等待)
- 构建时预加载翻译资源,避免客户端渲染时的内容闪烁
解决方案
1. 预加载翻译资源(构建时打包)
替换动态加载资源的方式,改用vite的import.meta.glob在构建时预加载所有翻译文件,避免运行时动态加载的延迟。
修改i18n.ts:
// i18n.ts import i18n from 'i18next' import { initReactI18next } from 'react-i18next' import LanguageDetector from 'i18next-browser-languagedetector' // 预加载所有locales下的json文件,eager模式会在构建时加载 const localeFiles = import.meta.glob('../locales/**/*.json', { eager: true }) const resources: Record<string, Record<string, any>> = {} // 解析预加载的文件,整理成i18n需要的resources格式 for (const filePath in localeFiles) { const match = filePath.match(/../locales/([a-z-]+)/([a-z]+)\.json/) if (match) { const [, language, namespace] = match if (!resources[language]) { resources[language] = {} } resources[language][namespace] = localeFiles[filePath].default } } const i18nInstance = i18n .use(initReactI18next) .use(LanguageDetector) .init({ resources, // 直接传入预加载的资源 fallbackLng: 'en', debug: true, interpolation: { escapeValue: false, // React已处理XSS }, }) export default i18nInstance // 导出初始化Promise,供渲染函数等待 export const i18nInitPromise = i18nInstance
2. 服务端渲染:等待初始化并同步语言
在服务端渲染前,必须等待i18n初始化完成,并确保当前语言的翻译资源已加载,同时将初始语言传递给客户端,避免hydrate不匹配。
修改_default.page.server.tsx:
// _default.page.server.tsx import i18n, { i18nInitPromise } from './i18n' import ReactDOMServer from 'react-dom/server' import { I18nextProvider } from 'react-i18next' async function render(pageContext: PageContextServer) { // 等待i18n初始化完成 await i18nInitPromise // 获取当前语言(可根据请求头、路由参数等调整逻辑) const currentLang = pageContext.request?.headers['accept-language']?.split(',')[0] || 'en' // 切换语言并等待对应资源加载完成 await i18n.changeLanguage(currentLang) const { Page, pageProps } = pageContext const pageHtml = ReactDOMServer.renderToString( <PageShell pageContext={pageContext}> <I18nextProvider i18n={i18n}> <Page {...pageProps} /> </I18nextProvider> </PageShell> ) // 将初始语言注入到HTML,供客户端读取 return { documentHtml: ` <!DOCTYPE html> <html lang="${currentLang}"> <head> <meta charset="UTF-8"> <title>Vite SSR I18n</title> </head> <body> <div id="page-view">${pageHtml}</div> <script>window.__INITIAL_LANG__ = "${currentLang}";</script> </body> </html> `, } } export default render
3. 客户端渲染:同步服务端语言并等待初始化
客户端渲染时,先等待i18n初始化,再使用服务端传递的初始语言,避免客户端检测语言导致的内容闪烁和hydrate错误。
修改_default.page.client.tsx:
// _default.page.client.tsx import i18n, { i18nInitPromise } from './i18n' import { hydrateRoot } from 'react-dom/client' import { I18nextProvider } from 'react-i18next' async function render(pageContext: PageContextClient) { // 等待i18n初始化完成 await i18nInitPromise // 使用服务端传递的初始语言,避免客户端重新检测 const initialLang = (window as any).__INITIAL_LANG__ || 'en' await i18n.changeLanguage(initialLang) const { Page, pageProps } = pageContext hydrateRoot( document.getElementById('page-view')!, <PageShell pageContext={pageContext}> <I18nextProvider i18n={i18n}> <Page {...pageProps} /> </I18nextProvider> </PageShell> ) } export default render
关键说明
- 预加载资源:通过
import.meta.glob({ eager: true })让vite在构建时将所有翻译文件打包进代码,彻底避免运行时动态加载的延迟和闪烁。 - 等待初始化:i18n的
init方法返回Promise,必须等待其resolve后才能使用翻译功能,否则会触发未初始化的报错。 - 服务端-客户端语言同步:将服务端确定的语言传递给客户端,确保hydrate时内容一致,同时减少客户端的语言检测步骤。
内容的提问来源于stack exchange,提问作者Malcolm Crum
相关产品推荐
相关产品推荐

