JS 中不同框架都有自己的多语言库,在 Remix 使用多语言,需要安装 remix-i18next 这个库。这个包是基于 i18next 开发的,使用方式可以到官网查看。 Remix-i18next 安装步骤如下:
安装依赖
npm install remix-i18next i18next react-i18next i18next-browser-languagedetector i18next-http-backend i18next-fs-backend
创建语言文件
创建英文和中文的语言文件。
public/locales/en/common.json
{"greeting": "Hello"
}
public/locales/zh/common.json
{"greeting": "你好"
}
i18n 配置文件
app/i18n.ts
export default {// 这是您的应用程序支持的语言列表supportedLngs: ["en", "zh"],// 如果用户的语言不在 supportedLngs 中,// 您希望使用的默认语言fallbackLng: "zh",// i18next 的默认命名空间是 "translation",// 但您可以在这里自定义defaultNS: "common",
};
app/i18next.server.ts
import Backend from "i18next-fs-backend"; // 导入 i18next 文件系统后端
import { resolve } from "node:path"; // 导入 node 的 path 解析函数
import { RemixI18Next } from "remix-i18next/server"; // 导入 Remix 的 i18next 服务器端模块
import i18n from "~/i18n"; // 导入您的 i18n 配置文件let i18next = new RemixI18Next({detection: {supportedLanguages: i18n.supportedLngs, // 支持的语言列表fallbackLanguage: i18n.fallbackLng, // 默认回退语言},// 以下是仅在服务器端翻译消息时使用的 i18next 配置i18next: {...i18n,backend: {loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"), // 指定翻译文件加载路径},},// 您希望 RemixI18next 在 loaders 和 actions 中通过 `i18n.getFixedT` 使用的 i18next 插件。// 例如:文件系统后端插件用于从文件系统加载翻译// 提示:您可以将 `resources` 传递给 `i18next` 配置并在此处避免使用后端plugins: [Backend],
});export default i18next; // 导出 i18next 配置
Remix Client
app/entry.client.tsx
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import i18n from "./i18n";
import i18next from "i18next";
import { I18nextProvider, initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import { getInitialNamespaces } from "remix-i18next/client";async function hydrate() {await i18next.use(initReactI18next) // 告诉 i18next 使用 react-i18next 插件.use(LanguageDetector) // 设置客户端语言检测.use(Backend) // 配置您的后端.init({...i18n, // 展开配置// 此功能检测您的路由在服务端渲染时使用的命名空间ns: getInitialNamespaces(),backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" },detection: {// 这里仅启用 htmlTag 检测, 我们将仅在服务器端使用 remix-i18next 检测语言// 通过使用 `<html lang>` 属性,我们可以通知客户端服务器端检测到的语言order: ["htmlTag"],// 因为我们只使用 htmlTag,没有理由在浏览器上缓存语言,所以我们禁用它caches: [],},});startTransition(() => {hydrateRoot(document,<I18nextProvider i18n={i18next}><StrictMode><RemixBrowser /></StrictMode></I18nextProvider>,);});
}if (window.requestIdleCallback) {window.requestIdleCallback(hydrate);
} else {// Safari 不支持 requestIdleCallback// https://caniuse.com/requestidlecallbackwindow.setTimeout(hydrate, 1);
}
Remix Server
app/entry.server.tsx
import { PassThrough } from "stream";
import {createReadableStreamFromReadable,type EntryContext,
} from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";
import { createInstance } from "i18next";
import i18next from "./i18next.server";
import { I18nextProvider, initReactI18next } from "react-i18next";
import Backend from "i18next-fs-backend";
import i18n from "./i18n"; // 你的 i18n 配置文件
import { resolve } from "node:path";const ABORT_DELAY = 5000; // 终止延迟时间export default async function handleRequest(request: Request,responseStatusCode: number,responseHeaders: Headers,remixContext: EntryContext,
) {let callbackName = isbot(request.headers.get("user-agent"))? "onAllReady" // 当所有内容准备就绪时: "onShellReady"; // 当外壳准备就绪时let instance = createInstance();let lng = await i18next.getLocale(request); // 获取请求的语言环境let ns = i18next.getRouteNamespaces(remixContext); // 获取即将渲染的路由的命名空间await instance.use(initReactI18next) // 告诉我们的实例使用 react-i18next.use(Backend) // 设置我们的后端.init({...i18n, // 展开配置lng, // 我们上面检测到的语言环境ns, // 路由想要使用的命名空间backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json") }, // 加载路径});return new Promise((resolve, reject) => {let didError = false;let { pipe, abort } = renderToPipeableStream(<I18nextProvider i18n={instance}><RemixServer context={remixContext} url={request.url} /></I18nextProvider>,{[callbackName]: () => {let body = new PassThrough();const stream = createReadableStreamFromReadable(body);responseHeaders.set("Content-Type", "text/html"); // 设置内容类型resolve(new Response(stream, {headers: responseHeaders,status: didError ? 500 : responseStatusCode,}),);pipe(body);},onShellError(error: unknown) { // 外壳错误处理reject(error);},onError(error: unknown) { // 错误处理didError = true;console.error(error); // 打印错误信息},},);setTimeout(abort, ABORT_DELAY); // 设置终止时间});
}
修改入口文件
修改 root.tsx 文件,添加 i18n相关配置。
import { useChangeLanguage } from "remix-i18next/react";
import { useTranslation } from "react-i18next";
import i18next from "~/i18next.server";export async function loader({ request }: LoaderArgs) {let locale = await i18next.getLocale(request);return json({ locale });
}export let handle = {// In the handle export, we can add a i18n key with namespaces our route// will need to load. This key can be a single string or an array of strings.// TIP: In most cases, you should set this to your defaultNS from your i18n config// or if you did not set one, set it to the i18next default namespace "translation"i18n: "common",
};export default function Root() {// Get the locale from the loaderlet { locale } = useLoaderData<typeof loader>();let { i18n } = useTranslation();// This hook will change the i18n instance language to the current locale// detected by the loader, this way, when we do something to change the// language, this locale will change and i18next will load the correct// translation filesuseChangeLanguage(locale);return (<html lang={locale} dir={i18n.dir()}><head><Meta /><Links /></head><body><Outlet /><ScrollRestoration /><Scripts /><LiveReload /></body></html>);
}
把 t 方法引进来直接使用即可。
let { i18n, t} = useTranslation();
配置生效
更多的用法可以参考官网,例如在语言文件中添加参数。
#配置文件
en: {translation: {greeting: "Hello, {{name}}! Welcome to our website."}}
# 参数
t 将参数传入
t('greeting', { name: userName });