Anmeldung

Nextjs+next-intl Best Practice für mehrsprachige Internationalisierung (APP-Router)

Autor:Neo-Yang Zeit:2024/02/19 Lesen: 13277
Nextjs bietet zwei Router: APP und Page, wobei Page auslaufen wird. Der Autor verwendete zuvor die Page-Router-Internationalisierung, hat jedoch inzwischen die Internationalisierung auf Basis des APP-Routers implementiert. Sie evaluierten mehrere Lösungen und fanden heraus, dass next-intl die einfachste und erfolgreichste ist . Der Beitrag beschreibt die Verzeichnisstruktur, das Routing, die Middleware-Einrichtung, das Laden von Übersetzungsdateien und die Implementierung von Übersetzungen und betont, dass Routing, Dateistruktur und Übersetzungsimplementierung unabhängig von der gewählten Internationalisierungslösung Schlüsselaspekte sind.

Nextjs有两种路由器:APP路由器和Page路由器。

前段时间,在Page路由器下,对nextjs做了国际化,并做了总结:

但Page路由器将会是Nextjs逐渐淘汰的路由器。所以,最近,基于APP路由器,对Nextjs做了国际化。

一、Nextjs国际化解决方案的选择

1、i18next + react-i18next + i18next-resources-to-backend + next-i18n-router

似乎大多数都在用这个,我试了一下,挺复杂,还没成功,所以放弃了。

2、next-i18n-router react-intl

看着也挺复杂,由于有1的阴影,放弃了。

3、next-intl

next-intl 是一个完整的nextjs的国际化方案,无须其它软件包。而且配置相对简单。更重要的是,我配置成功了,没遇到莫名其妙的问题。

所以,就是它了。

二、目录结构

|app
..[locale]
...layout.js
...page.js
|components
..LangSwitcher.js
|public
..locales
...en
....common.json
...es
....common.json
|i18n.js
|navigation.js
|middleware.js
|next.config.js

三、国际化路由

1、novigation.js

这是个配置文件,这个文件中配置的项目会在其它一些文件中调用。

import { createSharedPathnamesNavigation } from 'next-intl/navigation';

export const locales = ['en', 'de', 'es', 'ja','ko','it','pt'];
export const localePrefix = 'as-needed';
export const defaultLocale= "en";
export const localeItems= [
  { name: "English",code: "en", iso: "en-US", dir: "ltr" },
  { name: "español",code: "es", iso: "es-ES", dir: "ltr" },
  { name: "中文",code: "zh_cn", iso: "zh-CN", dir: "ltr" },
  { name: "Deutsch",code: "de", iso: "de-DE", dir: "ltr" },
  { name: "Italiano",code: "it", iso: "it-IT", dir: "ltr" },
  { name: "日本語",code: "ja", iso: "ja-JP", dir: "ltr" },
  { name: "한국인",code: "ko", iso: "ko-KR", dir: "ltr" },
  { name: "Português",code: "pt", iso: "pt-PT", dir: "ltr" },
]

export const { Link, redirect, usePathname, useRouter } =
  createSharedPathnamesNavigation({ locales, localePrefix });

2、[locale]目录

app目录中的页面文件全部放到[locale]目录中。

3、中间件:middleware.js

veranschaulichen:

实现以下样式的URL

默认语言的URL:

首页为:www.xxx.com

内页为:www.xxx.com/about

其它语言的URL:(以西班牙语为例)

首页为:www.xxx.com/es

内页为:www.xxx.com/es/about

另外,如果URL中输入了默认语言,比如默认语言为英语,用户输入URL:www.xxx.com/en

那么,将自动转到www.xxx.com

Code:

import createMiddleware from "next-intl/middleware";
import { defaultLocale, localePrefix, locales } from '@/navigation';
export default createMiddleware({
    locales,
    localePrefix ,
    defaultLocale,
    localeDetection: false,
    
  });
  
  export const config = {
    // Skip all paths that should not be internationalized.
    // This skips the folders "api", "_next" and all files
    // with an extension (e.g. favicon.ico)
    matcher: ["/((?!api|_next|.*\\..*).*)"],
  };

四、加载翻译文件

1、翻译文件

说明

我的翻译文件放在了“public”目录中,但,其实,翻译文件也可以放在其它目录中,只要在i18n.js文件中配置好想应的路径即可。

翻译文件是一个json文件,json文件可以是嵌套格式,也可以不嵌套。这两种,最终,在引用时会稍有不同。

代码(不嵌套)

{
  "aaa": "hi",
}

代码(嵌套)

{
  "bbb":{
        "aaa":"hi",
        } 
}

2、i18n.js文件

veranschaulichen:

这个文件,是导入翻译文件的,关键是配置翻译文件的路径,要和你的翻译文件所在的路径保持一致。

路径中的${locale}表示语言。

Code

import { getRequestConfig } from "next-intl/server";

// Create this configuration once per request and 
// make it available to all Server Components.
export default getRequestConfig(async ({ locale }) => ({
  // Load translations for the active locale.
  messages: (await import(`./public/locales/${locale}/common.json`)).default,
}));

3、next.config.js的配置

/** @type {import('next').NextConfig} */
const nextConfig = {}
const withNextIntl = require("next-intl/plugin")("./i18n.js");

module.exports =withNextIntl( nextConfig)

五、实现翻译

layout.js中的代码:

import "./globals.css";
import { NextIntlClientProvider, useMessages } from 'next-intl';
import { locales } from '@/navigation';
import Header from '@/components/Header'
import Footer from '@/components/Footer'
export default function RootLayout({
  children, params: { locale }
}) {
  if (!locales.includes(locale)) {
    notFound();
  } 
       const messages = useMessages();

  return (
    <html lang="{locale}">
      <nextintlclientprovider locale="{locale}" messages="{messages}">
      <body><header />{Kinder}<footer /></body>
      </nextintlclientprovider>
    </html>
  );
}

page.js中的代码:

参数locale表示当前的语言。

如果翻译文件是不嵌套的方式,则const t = useTranslations();

如果翻译文件是嵌套的方式,则根据文件结构和实际需要,用类似:const t = useTranslations(“bbb“);

import { useTranslations } from 'next-intl';
import { Link } from '@/navigation';
export default function Home({ params: { locale } }) {
  const t = useTranslations();

  return (<>   
<link href="{"/"}"><h1>{t("aaa")}</h1></Link>
</>)

六、语言切换器

1、效果

下拉选择语言,当前页面切换到选择的语言的页面。

2、代码

"use client";
import { useLocale } from "next-intl";
import { localeItems, useRouter, usePathname, defaultLocale } from '@/navigation';
export default function LangSwitcher() {
  const locale = useLocale();
  const router = useRouter();
  const pathname = usePathname();
  console.log(locale)

  const handleChange = (e) => {
    router.push(pathname, { locale: e.target.value });
  };
 
  return (
    <div>
      <select
      value={locale}
        onChange={handleChange}
        className="h-8 m-2 p-1 rounded border-current"
      >
        <option value={locale} > {GetLangData(locale).name}</option>

        {localeItems.map((item) => {

          if (item.code === locale) return null
          return (<option key={item.code} value={item.code}>
            {item.name}
          </option>)
        })}
      </select>
    </div>
  );
}
export function GetLangData(defaultLocale) {
  var res = {}
  {
    localeItems.map(locale => {
      if (locale.code === defaultLocale) {
        console.log(locale)
        res = locale
      }
    })
  }
  return res

}

七、SEO和搜索引擎友好

1、Meta data

nextjs的APP路由器,生成页面的title和Meta data,我尝试了三种方式:

第一种方式:在layout.js中直接使用:expert const metadata={}

第二种方式:在page.js中使用动态生成Meta data

可参考:Optimizing: Metadata | Next.js (nextjs.org)

第这两种方式,对于多语言的title和Meta data,我都失败了。最后用了第三种方式,也是最简单直白的方式。

第三种方式:直接在page.js中写title标签和Meta标签。

所以,page.js的代码改成了这样:

import { useTranslations } from 'next-intl';
import { Link } from '@/navigation';
export default function Home({ params: { locale } }) {
  const t = useTranslations();

  return (<>  
          <title>{t("site-name")}</title>
        <meta name="keyword" content="{t("site-name")}"></meta>
        <meta name="description" content="{t("site-description")}"></meta>
        <meta property="og:type" content="website"></meta>
        <meta property="og:title" content="{t("site-name")}"></meta>
        <meta property="og:description" content="{t("site-description")}"></meta>
        <meta property="og:site_name" content="{t("site-name")}"></meta>
        <meta property="og:image" content="/images/xxx.png"></meta>
        <meta property="og:image:width" content="512"></meta>
        <meta property="og:image:height" content="512"></meta>
        <meta property="og:image:alt" content=""></meta>
 
<link href="{"/"}"><h1>{t("aaa")}</h1></Link>
</>)

2、多语言导航:让搜索引擎更容易发现多语言URL

说明

语言切换器是前端渲染的,所以,我在页面的底部,也就是组件Footer.js中增加了后端渲染的多语言导航。点击相应的语言就会进入此语言的网站首页。

Footer.js代码如下:

import Link from 'next/link'
import { localeItems,defaultLocale } from '@/navigation';


export default function Footer(){


  return (<>
    <div className="divider"></div>
    <footer className="footer footer-center p-10 bg-base-200 text-base-content rounded">
      <nav className="flex flex-wrap gap-4">

     {localeItems.map(locale => {
          if (locale.code === defaultLocale) return(<Link className="link link-hover" href="/"  key={locale.code}>{locale.name}</Link>)
          const thehref="/"+locale.code
          return (<Link className="link link-hover" href={thehref} key={locale.code}> {locale.name}</Link>)
        })} 
  </nav> 
      <aside className='p-6 text-center'>
        <p>Copyright © 2024 - All right reserved by xxx.com</p>
      </aside>
     
    </footer>
  </>
    
  )
}

八、总结

国际化路由、翻译文件的结构和引入、翻译的实现。无论哪种国际化方案,都会涉及到这三个方面。虽然都不叫麻烦,但总的来说,next-intl相对还是简单一些。

九、参考

A Deep Dive into Next.js App Router Localization with next-intl

A complete guide for setting up next-intl with the App Router

Stichworte:


Copyright © www.lyustu.com, alle Rechte vorbehalten.
Thema: TheMoon V3.0. Autor: neo yang