使用 Next.js 和 react-intl 轻松实现国际化

国际化 (i18n) 是现代 Web 开发的一个重要方面。它使我们能够创建可供世界各地用户访问的网站,无论他们的语言或位置如何。在本文中,我们将探讨如何使用 Next.js 和 react-intl 使 i18n 变得简单。

Installation

要在 Next.js 和 react-intl 中开始使用 i18n,我们需要安装一些依赖项。首先,我们需要安装 react-intl 库:

sh 复制代码
npm install react-intl

NextJS Config

这就是我们的 next.config.js 文件所需要的全部内容。

javascript 复制代码
/** @type {import('next').NextConfig} */  
const nextConfig = {  
// instead of wrapping our Component inside <StricMode/> we set this to true.  
reactStrictMode: true,  
    i18n:{  
      locales: ['en', 'de'],  
      defaultLocale: 'en',  
    }  
}  
  
module.exports = nextConfig

Folder structure

这是我们的文件夹结构

lang文件夹包含我们的json文件以及flattenMessages.ts,我们将在后面解释。

pages文件夹是 NextJS 文件夹,我们将把我们的路由放进去

React Itl Config

react-intl 是一个很棒的库,它使内化工作变得非常容易!它提供了超酷的功能(我们今天不会介绍其中的大部分!我们将只关注如何处理基本的内化)

typeScript 复制代码
import type { AppProps } from "next/app";
import { IntlProvider } from "react-intl";
import { useLocale } from "../hooks/useLocale";

export default function App({ Component, pageProps }: AppProps) {
  const { locale, messages } = useLocale();
  console.log(locale);
  return (
    <IntlProvider locale={locale as string} messages={messages}>
      <Component {...pageProps} />
    </IntlProvider>
  );
}

在我们的_app.tsx文件中,我们只是导入IntlProvider并包装我们的Component.

IntlProvider:需要 2 个 props。locale在我们的例子中是 ende &messages: 具有key``value对的对象,因为我们在这里不能有嵌套对象!但是,如果我们看一下我们的[locale].json文件,我们会发现我们的文件包含嵌套的 JSON!

为什么?因为嵌套的 JSON 可以很容易地按屏幕或主题拆分我们的键!因此,它们可以有一个清晰的结构,而简单的 key value JSON 则不会如此。

因此,为了解决这个问题,我们有这个flattenMessages方法,它只接受任何 json 并对其进行flattens以使其成为key value对!

typeScript 复制代码
// This interface is using Indexed signature and Recursive type utility in TS.
// It just means our json might have key and value of type string or nested json.
export interface INestedMessages {
  [key: string]: string | INestedMessages;
}
export const flattenMessages = (
  nestedMessages: INestedMessages,
  prefix = ""
): Record<string, string> => {
  return Object.keys(nestedMessages).reduce(
    (messages: Record<string, string>, key) => {
      const value = nestedMessages[key];
      const prefixedKey = prefix ? `${prefix}.${key}` : key;
      if (typeof value === "string") {
        messages[prefixedKey] = value;
      } else {
        Object.assign(messages, flattenMessages(value, prefixedKey));
      }

      return messages;
    },
    {}
  );
};

flattenMessages(en) 
// output: key value pair
{
  "app.title": "NextJS internalisation",
  "app.locale_switcher.en": "English",
  "app.locale_switcher.de": "German",
  "app.main.description": "This article is trying to explain how we can use react-intl with NextJS and use also Typescript to make our life easy!"
 }

Custom hooks: useLocale

typeScript 复制代码
import { useRouter } from "next/router";
import { useCallback, useMemo } from "react";
import en from "@/../lang/en.json";
import de from "@/../lang/de.json";
import { flattenMessages, INestedMessages } from "../../lang/flattenMessages";

// Union type
export type Locale = "en" | "de";

// a Record is an object wich we can pass union types to it as key.
const messages: Record<Locale, INestedMessages> = {
  en,
  de,
};

export const useLocale = () => {
  const router = useRouter();
  
  const flattenedMessages = useMemo(
    () => flattenMessages(messages[router.locale as keyof typeof messages]),
    [router]
  );

  const switchLocale = useCallback(
    (locale: Locale) => {
      // if we already have /en and we choose english for example we just return!
      if (locale === router.locale) {
        return;
      }

      // This is how we change locale in NextJS.
      const path = router.asPath;
      return router.push(path, path, { locale });
    },
    [router]
  );
  return { locale: router.locale, switchLocale, messages: flattenedMessages };
};

这个定制钩子非常简单!它只是使用 nextjs 中的 useRouter 钩子来获取语言环境并切换语言环境。它还会返回翻译消息的扁平化版本。 它使用起来非常简单,通过这种方式,我们抽象了locale管理的复杂性。

typeScript 复制代码
cosnt {switchLocale, locale, messages} = useLocale()

Custom Hooks: useTranslate

typeScript 复制代码
import type { PrimitiveType } from "react-intl";  
import { useIntl } from "react-intl";  
import type { FormatXMLElementFn } from "intl-messageformat";  
import { useCallback } from "react";  
import { TranslationKey } from "../../lang/flattenMessages";  
  
export const useTranslate = () => {  
const { formatMessage } = useIntl();  
// Define a function called t that takes in a key of type TranslationKey  
(key: TranslationKey ) =>  
// Call formatMessage with an object that has an id property set to the given key  
    [formatMessage]  
);  
  
    return { t };  
};

现在这是我们useTranslate自定义钩子!我们使用的是 react-intl formatMessage 方法中的useIntl。 此方法具有必需的参数MessageDescriptor它只是一个具有id:string的对象。它还接受本文范围之外的其他参数。

TranslationKey 是我们构建的用于检索翻译键的 TS 类型。转换键只是以字符串结尾的所有路径。在我们的例子中,我们的翻译键是app.title | app.locale_switcher.en | app.locale.de | app.main.description.

我们需要提取这种类型,这样我们就不会在尝试翻译字符串时出现错别字。

typeScript 复制代码
// Define a TypeScript type called KeyPaths that takes in an object type T as its generic type parameter.  
// KeyPaths is defined as a mapped type, which produces a new type by iterating over the keys of T.  
type KeyPaths<T extends INestedMessages> = {  
// For each key K in T, create a mapped type that checks whether the value of that key T[K] extends INestedMessages,  
// i.e., whether the value is an object that has string keys and unknown values.  
[K in keyof T]: T[K] extends INestedMessages  
? // If the value of the key is an object, create a string literal type that represents the path to that object.  
// The path is constructed by combining the current key K with a dot separator and the key paths of the nested object T[K].  
// The & string is used to ensure that TypeScript recognizes this string literal as a string.  
  
`${K & string}.${KeyPaths<T[K]> & string}`  
: // If the value of the key is not an object, simply return the key name K.  
K;  
// Finally, index the KeyPaths type by keyof T, which returns a union of all the key paths in the object type T.  
// This means that KeyPaths<T> is a union of all the possible key paths in the object type T.  
}[keyof T];  
  
export type TranslationKey = KeyPaths<typeof en>;

这就是结果!我们的 IDE 将帮助我们避免任何typos,我们不需要记住所有这些路径!如果我们更改翻译文件中的路径,我们也会收到错误,并且在我们使用它的任何地方修复它之前,构建不会通过。

Translated Page

最后,这是我们的页面。

typeScript 复制代码
import { Inter } from "next/font/google";  
import { Locale, useLocale } from "@/hooks/useLocale";  
import { useTranslate } from "@/hooks/useTranslate";  
  
const inter = Inter({ subsets: ["latin"] });  
  
export default function Home() {  
const { switchLocale, locale } = useLocale();  
const { t } = useTranslate();  
return (  
    <main  
    className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}  
    >  
        <div className="flex items-center justify-between w-full">  
            <h1 className="text-3xl">{t("app.title")}</h1>  
            <select  
                onChange={(e) => {  
                switchLocale(e.target.value as Locale);  
            }}  
            className="outline-none rounded-xl text-black px-2 w-32"  
            placeholder="Language"  
            >  
                <option value="en">{t("app.locale_switcher.en")}</option>  
                <option value="de">{t("app.locale_switcher.de")}</option>  
            </select>  
        </div>  
    </main>  
   );  
}

Conclusion

在本文中,我们讨论了将 react-intl 与 NextJs 一起使用来构建一个内部化的 Web 应用程序。

我们使用 TypeScript 为我们的localestranslationKeys.构建类型

我们还使用customHooks来抽象localetranslation管理的所有复杂性。

github链接: github.com/wugaoliang1...

相关推荐
吃杠碰小鸡20 分钟前
commitlint校验git提交信息
前端
虾球xz1 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇1 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员1 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐1 小时前
前端图像处理(一)
前端
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒1 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪2 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背2 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript