[译]如何在 Next.js 中实现 i18n

5,475 阅读11分钟

前言

  • 大佬写的这篇文章很不错,我给搬运过来大家看看
  • 您是否希望您的软件/网站仅供懂英语的用户使用?我想不会,您会希望您的应用程序适应各种语言。您不希望您的用户受到语言的限制。语言不应该成为获得 10 亿用户的障碍,因此我将在这里演示如何构建 Nextjs 应用程序以适应各种语言。

国际化

国际化,哇,这是一个 20 个字母的词。打字太长,而且当您必须在文本中多次重复时,时间会更长。这就是为什么它通常缩写为i18n :)。所以在这篇文章的大部分内容中,我将使用 i18n 来代替国际化。

什么是 i18n?

根据维基百科,国际化是设计软件应用程序的过程,以便它可以适应各种语言和地区,而无需进行工程更改。开发人员以他们理解的语言构建软件应用程序内容。中国开发者构建具有中文内容的应用程序,英语开发者构建具有英语的应用程序,同样,它适用于世界上所有语言。应用程序的内容使用开发人员的语言。例如,由西班牙开发人员构建的新闻博客应用程序将以西班牙语发布其新闻内容。

如今,i18n

是软件应用程序中最受欢迎的功能之一。大多数现代软件都支持您可以找到的最流行的语言。支持 i18n 为您在国际上提高了标准,因为您的应用程序不仅适用于特定地区,而且适用于整个世界。根据 Next.js 官方页面:如果网站被翻译,72% 的消费者更有可能留在你的网站上,55% 的消费者表示他们只用母语从电子商务网站购买。

体现 i18n 的应用程序支持多语言。它可以支持尽可能多的语言。这些支持 i18n 的应用程序可以将其内容从一种语言转换为另一种语言。支持的语言通常显示在下拉列表中,供用户选择语言。大多数应用程序也确实通过域支持 i18n。例如,lit.com 网站可以在 es.lit.com 中支持其西班牙语版本。这是 i18n 的另一种形式。

语言环境是 i18n-apps 支持的特定语言。定义语言环境的标准格式是UTS 语言环境标识符,语言环境的一般格式是语言区域脚本。对于法国的法语,它是 fr-FR。美国的英语是en-US,英国的英语是en-EN。

在下一节中,我们将看看如何在 Nextjs 中构建 i18n-apps。

国际化和 Next.js

Next.js 是一个开源 Web 框架,用于使用 React.js 构建服务器端呈现的 Web 应用程序和静态生成的 Web 应用程序。Next.js以来一直支持国际化V10.0.0,它允许我们设置默认语言,目前在用的语言,以及所支持的语言。要在 Next.js 应用程序中设置语言环境,我们首先在应用程序的根目录中创建一个 next.config.js 文件。这是我们定义语言环境的地方:

module.exports = {
i18n: {
/**
* Provide the locales you want to support in your application
* 提供您希望在应用程序中支持的地区
*/
locales: ["en-US", "fr-FR", "es-ES"],
/**
* This is the default locale you want to be used when visiting
* 这是您在访问时希望使用的默认区域设置
* a non-locale prefixed path.
*/
defaultLocale: "en-US",
},
};

i18n 告诉 Next.js 我们要在 Next.js 应用程序中激活/使用 i18n 功能。locales 数组属性是我们添加我们想要支持的语言环境的语言环境代码的地方。defaultLocale 保存应用程序的默认区域设置代码,这是在没有区域设置处于活动状态时将使用的区域设置。Next.js 有两种策略可以在我们的应用程序中处理 i18n。策略是:子路径路由和域路由。

子路径路由

子路径路由策略涉及将语言环境添加为 URL 路径的一部分,而不会在它们呈现时影响组件。

假设我们有这个 pages/users.js 并且我们有语言环境“en-US”、“es-ES”、“fr-FR”,而“en-US”是默认语言环境。

Next.js 会将 fr-FR 和 es-ES 映射到 /users 路径:

  • /fr-fr/用户
  • /es-es/用户 请参阅区域设置以 /users 路径为前缀。两个路由都指向 pages/users.js。默认语言环境不以路径为前缀。然后我们可以通过 Next.js 路由器访问 pages/users.js 中的区域设置信息。使用 useRouter() 钩子,我们可以获取 locale 中当前活动的 locale,locale 中的所有 locale,以及 defaultLocale 中的默认 locale。
import { useRouter } from "next/router";

const { locale, locales, defaultLocale } = useRouter();

看到我们从 useRouter() 重构了 locale、locales 和 defaultLocale。然后我们可以使用 locale 值来了解我们应该呈现的内容的语言版本。

对于静态生成的页面或服务器端呈现的页面,我们可以通过 getServerSideProps 或 getStaticProps 传递区域设置信息。这些方法将道具从包含它们返回的道具键的对象传递给组件。这些 getServerSideProps 或 getStaticProps 方法接受一个上下文参数,该参数是一个包含语言环境、当前活动语言环境、我们应用程序中的所有语言环境和默认语言环境 defaultLocale 的对象。然后我们将此上下文传递给方法返回的 props 对象,以便我们可以从组件的 props 访问它:

export default function Users(props) {
const { locale, locales, defaultLocale } = props;
console.log(locale, locales, defaultLocale);

return (
<div>
<span>Users Component</span>
</div>
);
}

export const getStaticProps = async (context) => {
return {
props: { context },
};
};

所以我们可以从路由器和 getServerSideProps 或 getStaticProps 方法中获取语言环境信息。让我们在下面的部分中演示两者的用法。

通过 useRouter() 钩子

搭建 Next.js 应用程序:

yarn create next-app i18n-next
# OR
npx create-next-app i18n-next

进入目录:cd i18n-next 并运行 yarn dev。我们将创建一个显示新闻列表的新闻页面。新闻内容的语言将根据传递给 URL 路径的区域设置而变化。所以,我们会有不同语言的新闻内容。我们将支持英语(默认)、法语和西班牙语(西班牙语😁)。首先,我们在根文件夹中创建 next.config.js,并添加以下配置:

module.exports = {
i18n: {
/**
* Provide the locales you want to support in your application
* 提供您希望在应用程序中支持的地区
*/
locales: ["en-US", "fr-FR", "es-ES"],
/**
* This is the default locale you want to be used when visiting
* 这是您在访问时希望使用的默认区域设置
* a non-locale prefixed path e.g. `/hello`
*/
defaultLocale: "en-US",
},
};

这会在我们的应用程序中激活 i18n。让我们在 pages 组件中创建一个 news.js 文件并粘贴以下代码:

import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import styles from "../styles/Home.module.css";
import NewsCard from "./../components/newscard";

const newsContent = {
"en-US": {
title: "Your News",
content: [
{
title:
"Otter.ai’s new assistant can automatically transcribe your Zoom meetings",
synopsis:
"A.I.-powered voice transcription service Otter.ai wants to make it even easier for its business users to record their meetings. The company is today introducing a new feature, Otter Assistant, whic...",
imageUrl: "",
},
// ...
],
},
"fr-FR": {
title: "Vos nouvelles",
content: [
{
title:
"Le nouvel assistant d'Otter.ai peut transcrire automatiquement vos réunions Zoom",
synopsis:
"Le service de transcription vocale alimenté par A.I. Otter.ai veut rendre encore plus facile pour ses utilisateurs professionnels l'enregistrement de leurs réunions. La société présente aujourd'hui une nouvelle fonctionnalité, Otter Assistant, qui ...",
imageUrl: "",
},
// ...
],
},
"es-ES": {
title: "Tus noticias",
content: [
{
title:
"El nuevo asistente de Otter.ai puede transcribir automáticamente sus reuniones de Zoom",
synopsis:
"El servicio de transcripción de voz con tecnología de inteligencia artificial Otter.ai quiere facilitar aún más a sus usuarios comerciales la grabación de sus reuniones. La compañía presenta hoy una nueva función, Otter Assistant, que ...",
imageUrl: "",
},
// ...
],
},
};

export default function News(props) {
const { locale, locales, defaultLocale, asPath } = useRouter();
const { title, content } = newsContent[locale];
return (
<div className={styles.container}>
<Head>
<title>TV</title>
<link rel="icon" href="/favicon.ico" />
</Head>

      <main className={styles.main}>
        <div className={styles.breadcrumb}>
          <div
            style={{
              padding: "4px",
              marginRight: "4px",
            }}
          >
            <span>Current Language: </span>
            <span
              style={{
                borderRadius: "3px",
                backgroundColor: "blue",
                color: "white",
                padding: "2px",
              }}
            >
              {locale}
            </span>
          </div>
          <Link
            activeClassName={locale === "es-ES"}
            href={asPath}
            locale="es-ES"
          >
            es-ES
          </Link>

          <Link
            activeClassName={locale === "en-US"}
            href={asPath}
            locale="en-US"
          >
            en-US
          </Link>
          <Link
            activeClassName={locale === "fr-FR"}
            href={asPath}
            locale="fr-FR"
          >
            fr-FR
          </Link>
        </div>

        <div className={styles.newscontainer}>
          <div className={styles.yournewscasts}>
            <h3>{title}</h3>
          </div>

          <div>
            {content.map((newsItem, i) => (
              <NewsCard key={i} news={newsItem} />
            ))}
          </div>
        </div>
      </main>
    </div>
);
}

看到我们首先设置我们的新闻内容。newsContent 对象以法语、西班牙语和英语保存我们的新闻内容。它具有“en-US”、“es-ES”和“fr-FR”属性,它们都有“title”和“content”键。标题是我们将在页面顶部显示的内容,它将显示“您的新闻”,因此每个地区都有自己的“您的新闻”语言版本。content 是一个数组,包含我们页面的实际新闻内容。

我们将使用 useRouter() 钩子中的语言环境来获取特定语言的新闻内容。如果语言环境是 fr-Fr,我们将从 newsContent 对象中引用 fr-FR,就像这样 newsContent["fr-FR]。这就是我们在 News 组件中所做的。我们调用了 useRouter() 钩子并解构了语言环境和 asPath来自它。就像我们已经知道的那样,locale 保存当前活动的语言环境。asPath 为我们提供了关于当前语言环境的路由。例如,如果当前语言环境是 en-US,则 asPath 是 /en-US/news。

接下来,我们通过执行以下操作从 newsContent 对象中检索特定语言对象:newsContent[locale]。我们从中解构了标题和内容。现在,我们在 UI 上渲染它们。在新闻组件 UI 中,我们显示当前语言环境:

<div
  style={
    {
      // ...
    }
  }
>
  <span>Current Language: </span>
  <span
    style={
      {
        //...
      }
    }
  >
    {locale}
  </span>
</div>

接下来,我们设置语言环境之间的转换:

<Link
    activeClassName={locale === "es-ES"}
    href={asPath}
    locale="es-ES"
>
    es-ES
</Link>

<Link
    activeClassName={locale === "en-US"}
    href={asPath}
    locale="en-US"
>
    en-US
</Link>
<Link
    activeClassName={locale === "fr-FR"}
    href={asPath}
    locale="fr-FR"
>
    fr-FR
</Link>

这将显示带有文本 es-ES、en-US 和 fr-FR 的链接,分别带有指向 /en-US/news、/news 和 /fr-FR/news 的 href 链接。如果您点击 es-ES,它将加载路由 es-ES/news,当前语言环境变为 es-ES,我们的组件将为 es-ES 加载新闻内容。因此,上述链接用于更改我们应用程序的语言。单击其中任何一个都会将应用程序的内容翻译成单击的语言。最后,显示内容数组:

<div>
  {content.map((newsItem, i) => (
    <NewsCard key={i} news={newsItem} />
  ))}
</div>

NewsCard 组件呈现每个新闻项目的新闻。我们的应用程序将如下所示:

image.png 我们的英文新闻网站

image.png 我们在西班牙的新闻网站(西班牙语)

image.png 我们的法语新闻网站 我们点击了链接来更改语言。如果我们决定以编程方式使用 next/router 导航我们的路线:

const router = useRouter();

router.push("/news", "/news");

我们必须在第三个参数的对象中传递一个语言环境选项,以导航到语言环境路由:

const router = useRouter();

router.push("/news", "/news", { locale: "es-ES" });

这将加载 /fr-FR/news 路由,因此语言环境将为法语。

通过 getServerSideProps 或 getStaticProps 现在我们使用 getServerSideProps 或 getStaticProps 演示 i18n。这将是我们新闻组件中的一个小编辑:

// ...
export default function News(props) {
const { locale, locales, defaultLocale } = props.context;
const { title, content } = newsContent[locale];
return (
<div className={styles.container}>
<Head>
<title>TV</title>
<link rel="icon" href="/favicon.ico" />
</Head>

      <main className={styles.main}>
        <div className={styles.breadcrumb}>
          <div
            style={{
              padding: "4px",
              marginRight: "4px",
            }}
          >
            <span>Current Language: </span>
            <span
              style={{
                borderRadius: "3px",
                backgroundColor: "blue",
                color: "white",
                padding: "2px",
              }}
            >
              {locale}
            </span>
          </div>
          <Link
            activeClassName={locale === "es-ES"}
            href={`/es-ES/news`}
            locale="es-ES"
          >
            es-ES
          </Link>

          <Link
            activeClassName={locale === "en-US"}
            href={`/en-US/news`}
            locale="en-US"
          >
            en-US
          </Link>
          <Link
            activeClassName={locale === "fr-FR"}
            href={`/fr-FR/news`}
            locale="fr-FR"
          >
            fr-FR
          </Link>
        </div>

        <div className={styles.newscontainer}>
          <div className={styles.yournewscasts}>
            <h3>{title}</h3>
          </div>

          <div>
            {content.map((newsItem, i) => (
              <NewsCard key={i} news={newsItem} />
            ))}
          </div>
        </div>
      </main>
    </div>
);
}

export async function getStaticProps(context) {
return {
props: {
context,
},
};
}

我们从 news.js 文件中导出了一个 getStaticProps 方法,这将使 News 组件在构建时静态生成。如果我们使用 getServerSideProps 而不是 getStaticProps,页面将从服务器生成。

这里的主要内容是我们将上下文参数传递给 props 对象并从 getStaticProps 方法返回该对象。现在,我们可以通过它的 props 参数访问 News 组件中的上下文;道具.上下文。然后我们从中解构语言环境。其他一切都成立,我们只需要重构我们的转换链接,因为 pros.context 没有 asPath,所以我们手动设置它们。

我们可以以一种有效的方式以编程方式完成此操作,我将其留给读者以使上述内容变得更好。😁 所以我们的应用程序的运行方式与我们使用 useRouter() 时相同。

域路由

域路由策略涉及将语言环境映射到顶级域。例如,我们设置了语言环境:

module.exports = {
i18n: {
locales: ["en-US", "fr-FR", "es-ES"],
defaultLocale: "en-US",
},
};

我们可以根据域设置要提供的语言环境。


module.exports = {
  i18n: {
    locales: ["en-US", "fr-FR", "es-ES"],
    defaultLocale: "en-US",
    domains: [
      {
        domain: "mynews.com",
        defaultLocale: "en-US",
      },
      {
        domain: "mynews.es",
        defaultLocale: "es-ES",
      },
      {
        domain: "mynews.fr",
        defaultLocale: "fr-FR",
      },
    ],
  },
};

Next.js 将根据我们在上面的域数组属性中设置的域创建区域设置 URL 路径。现在,我们网站 mynews.com/news 的法语版将是 mynews.fr/news。西班牙语版本将是 mynews.es/news。

其他 i18n 实现

结论

Next.js 使 i18n 变得非常容易和简单。您所要做的就是在 next.config.js 文件和多语言数据中设置您的语言环境,您就可以开始了。您只需从 useRouter() 挂钩或通过 getStaticProps 或 getServerSideProps 方法获取区域设置信息。

源代码

在此处查看完整代码