Remix+i18next生产环境元数据翻译失效问题求助
问题描述
使用i18next和remix-i18next开发Remix应用时,开发环境下包括元数据在内的翻译均正常,但生产环境中会显示原始键(如"meta.title"、"meta.description")。
版本信息
- Remix: 2.14.0
- remix-i18next: 6.4.1
- i18next: 23.16.5
- react-i18next: 15.1.1
- i18next-browser-languagedetector: 8.0.0
- i18next-fs-backend: 2.3.2
- i18next-http-backend: 2.6.2
配置代码
app/utils/getLocale.ts
import i18next from "~/i18next.server"; import { getLanguagePreference } from "~/services/session.server"; export async function getLocale(request: Request): Promise<string> { const sessionLocale = await getLanguagePreference(request); const locale = sessionLocale || (await i18next.getLocale(request)); if (!locale) { return "en"; } return locale; }
app/i18n.ts
export default { supportedLngs: ["en", "es", "fr"], fallbackLng: "en", defaultNS: "common", ns: ["common"], react: { useSuspense: false }, interpolation: { escapeValue: false, } };
app/i18next.server.ts
import Backend from "i18next-fs-backend"; import { resolve } from "node:path"; import { RemixI18Next } from "remix-i18next/server"; import i18n from "~/i18n"; let i18next = new RemixI18Next({ detection: { supportedLanguages: i18n.supportedLngs, fallbackLanguage: i18n.fallbackLng, }, i18next: { ...i18n, backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"), }, }, plugins: [Backend], }); export default i18next;
app/routes/_index.tsx
import { json } from "@remix-run/react"; import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { LoaderData } from "~types/*"; import i18next from "~/i18next.server"; import { getLocale } from "~/utils/getLocale"; export const handle = { i18n: "home" }; export async function loader({ request }: LoaderFunctionArgs) { const locale = await getLocale(request); const t = await i18next.getFixedT(locale, "home", { keyPrefix: "meta" }); const title = t("title") const description = t("description") console.log({ title, description }) return json<LoaderData>({ translations: { locale, title: t("title"), description: t("description"), ogTitle: t("ogTitle"), ogDescription: t("ogDescription"), twitterTitle: t("twitterTitle"), twitterDescription: t("twitterDescription"), }, }); } export const meta: MetaFunction<typeof loader> = ({ data }) => { const baseUrl = "https://example.com"; console.log({Translation: data?.translations }); if (!data?.translations) { console.error("No translations data available"); return [{ title: "Fallback Title" }]; } return [ { title: data?.translations.title }, { name: "description", content: data?.translations.description, }, { property: "og:title", content: data?.translations.ogTitle }, { property: "og:description", content: data?.translations.ogDescription, }, { property: "og:image", content: "/images/banner.jpg" }, { property: "og:url", content: "https://example.com" }, { name: "twitter:card", content: "summary_large_image" }, { name: "twitter:title", content: data?.translations.twitterTitle, }, { name: "twitter:description", content: data?.translations.twitterDescription, }, { name: "twitter:image", content: "/banners/banner.jpg" }, { name: "robots", content: "index, follow, max-image-preview:large" }, { name: "language", content: data?.translations.locale }, { name: "revisit-after", content: "1 day" }, { tagName: "link", rel: "canonical", href: baseUrl }, ]; }; // the rest of the code
已尝试操作
- 本地构建后运行正常
- 生产环境部署后显示原始键
目录结构
public/ locales/ en/ common.json home.json es/ ... fr/ ...
示例翻译文件(public/locales/en/common.json)
{ "meta": { "title": "Example Title", "description": "Example Description" // ... other translations } }
可能原因与解决方案
1. 生产环境翻译文件路径错误
i18next-fs-backend的loadPath在生产环境可能因部署路径变化无法找到文件。本地开发时resolve("./public/locales/...")指向项目根目录的public,但生产环境中Remix构建后的文件结构或部署路径可能不同。
解决:
使用process.cwd()确保路径指向项目根目录:
// app/i18next.server.ts backend: { loadPath: resolve(process.cwd(), "public/locales/{{lng}}/{{ns}}.json"), },
同时检查生产环境中public/locales目录是否完整部署,文件权限是否正常。
2. 命名空间未正确加载
app/i18n.ts中ns仅配置了["common"],但首页使用了home命名空间(getFixedT(locale, "home")),生产环境可能未预加载该命名空间。
解决:
在app/i18n.ts中添加home到命名空间列表:
export default { // ...其他配置 ns: ["common", "home"], };
3. 生产环境i18next初始化异步问题
生产环境服务器启动时可能未完成翻译文件加载,导致首次请求无法获取翻译值,返回原始键。
解决:
在服务器启动时等待i18next初始化完成:
// app/i18next.server.ts await i18next.initPromise; // 确保初始化完成后再处理请求
4. 语言检测在生产环境失效
生产环境中i18next.getLocale(request)可能无法正确检测语言,导致使用未配置的语言,从而显示原始键。
解决:
在getLocale函数中强制校验语言是否在支持列表中:
// app/utils/getLocale.ts import i18n from "~/i18n"; // 引入配置 export async function getLocale(request: Request): Promise<string> { const sessionLocale = await getLanguagePreference(request); let locale = sessionLocale || (await i18next.getLocale(request)); // 确保locale在支持的语言列表内 if (!i18n.supportedLngs.includes(locale)) { locale = "en"; } return locale; }
5. 构建配置问题
Remix生产构建时可能优化资源路径,导致翻译文件访问路径变化。检查remix.config.js中的publicPath是否设置正确,确保public/locales目录被完整复制到构建产物中。
解决:
确认remix.config.js中publicPath为默认值"/",若自定义路径需同步调整i18next的加载路径。同时检查构建命令是否正确复制了public目录。
内容的提问来源于stack exchange,提问作者Abubalo

