NextJS i18n多国语言的实现代码

前端开发Next.js 691

NextJS i18n多国语言的实现代码

前面简单提供了的一些资源,由于网上的示例代码都有些小问题,于是踩坑之后决定把完整的实现代码提供一下。

其中主要依赖next-i18next这个包,但是在按照官方的教程实现过程中发现了两个小问题并得以修复:

  1. 语言的切换调用只支持两个,示例代码的写法是:locale={router.locale === 'en' ? 'de' : 'en'},而我修复了这一点,单独写了个componet用做任意语言切换和当前语言的显示
  2. 由于在文件next-i18next.config.js中localePath目录配置的缺失,应用运行时会导致namespace错误

内容均在官方原来的示例代码中修改,下面直接上文件

目录结构

components
----Footer.js
----Header.js
----LocaleSwitcher.js
pages
----_app.js
----index.js
----second-page.js
public
----static
--------locales
------------en
----------------common.json
----------------footer.json
------------fr
----------------common.json
----------------footer.json
------------zh-CN
----------------common.json
----------------footer.json
next-i18next.config.js
next.config.js

另外需要注意的是我使用了tailwindcss框架,不需要的删除相应的引入即可,以免报错

配置文件

/next-i18next.config.js

module.exports = {
    i18n: {
      defaultLocale: 'en',
      locales: ['en', 'fr', 'zh-CN'],
      defaultNS: 'common',
      //fallbackLng: 'en',
    },
    localePath: 'public/static/locales',
};

/next.config.js

/** @type {import('next').NextConfig} */
const { i18n } = require('./next-i18next.config');
const nextConfig = {
  i18n,
  reactStrictMode: true,
  swcMinify: true,

}

module.exports = nextConfig

Pages目录

/pages/_app.js

import '../styles/globals.css';
import { appWithTranslation } from 'next-i18next';

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default appWithTranslation(MyApp);

/pages/index.js

import Head from 'next/head'
import Image from 'next/image'

import styles from '../styles/Home.module.css'

import Link from 'next/link'
import { useRouter } from 'next/router'

import { useTranslation, Trans } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'

import { Header } from '../components/Header'
import { Footer } from '../components/Footer'

const Homepage = () => {

  const router = useRouter()
  const { t } = useTranslation('common')
  const { locale, locales, defaultLocale } = router
  return (
    <>
      <main>
        <Header heading={t('h1')} title={t('title')} />
        <p>Current locale: {locale}</p>
        <p>Default locale: {defaultLocale}</p>
        <p>Configured locales: {JSON.stringify(locales)}</p>        
        
        <div style={{ display: 'inline-flex', width: '90%' }}>
          <div style={{ width: '50%' }}>
            <h3 style={{ minHeight: 70 }}>{t('blog.optimized.question')}</h3>
            <p>
              <Trans i18nKey='blog.optimized.answer'>
                Then you may have a look at <a href='https://locize.com/blog/next-i18next/'>this blog post</a>.
              </Trans>
            </p>
            <a href='https://locize.com/blog/next-i18next/'>
              <img style={{ width: '50%' }} src='https://locize.com/blog/next-i18next/next-i18next.jpg' />
            </a>
          </div>
          <div style={{ width: '50%' }}>
            <h3 style={{ minHeight: 70 }}>{t('blog.ssg.question')}</h3>
            <p>
              <Trans i18nKey='blog.ssg.answer'>
                Then you may have a look at <a href='https://locize.com/blog/next-i18n-static/'>this blog post</a>.
              </Trans>
            </p>
            <a href='https://locize.com/blog/next-i18n-static/'>
              <img style={{ width: '50%' }} src='https://locize.com/blog/next-i18n-static/title.jpg' />
            </a>
          </div>
        </div>
        <hr style={{ marginTop: 20, width: '90%' }} />
        <div>

          <Link href='/second-page'>
            <button
              type='button'
            >
              {t('to-second-page')}
            </button>
          </Link>
        </div>
      </main>
      <Footer />
    </>
  )
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...await serverSideTranslations(locale, ['common', 'footer']),
  },
})

export default Homepage

/pages/second-page.js

import Link from 'next/link'

import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'

import { Header } from '../components/Header'
import { Footer } from '../components/Footer'

const SecondPage = () => {

  const { t } = useTranslation('second-page')

  return (
    <>
      <main>
        <Header heading={t('h1')} title={t('title')} />
        <Link href='/'>
          <button
            type='button'
          >
            {t('back-to-home')}
          </button>
        </Link>
      </main>
      <Footer />
    </>
  )
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...await serverSideTranslations(locale, ['second-page', 'footer']),
  },
})

export default SecondPage

components目录

/components/Footer.js

import { useTranslation } from 'next-i18next';

export const Footer = () => {
  const { t } = useTranslation('footer');

  return (
    <footer>
      <p>{t('description')}</p>
    </footer>
  );
};

/components/Header.js

import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'

import { useTranslation, Trans } from 'next-i18next'
import LocaleSwitcher from '../components/LocaleSwitcher'

export const Header = ({ heading, title }) => {

  const router = useRouter()
  const { t } = useTranslation('common')
  return (
    <>
      <Head>
        <title>{title}</title>
      </Head>
      <h2>
        <Trans i18nKey='website.name'>
          test <a href='https://www.saucouncil.com'>this blog post</a>
        </Trans>
        <hr />
      </h2>
      <LocaleSwitcher />
    </>
  )
}

/components/LocaleSwitcher.js

import Link from 'next/link'
import { useRouter } from 'next/router'

export default function LocaleSwitcher() {
  const router = useRouter()
  const { locales, locale: activeLocale } = router
  const otherLocales = locales.filter((locale) => locale !== activeLocale)

  return (
    <div>
      <p>Locale switcher:</p>
      <ul>
        <li>Current locale: {activeLocale}</li>
        {otherLocales.map((locale) => {
          const { pathname, query, asPath } = router
          return (
            <li key={locale}>
              <Link href={{ pathname, query }} as={asPath} locale={locale}>
                <a>{locale}</a>
              </Link>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

public目录

注意此处的翻译文件都是乱写的,自行修改

/public/locales/en/common.json

{
    "website": {
        "name": "MYWEBSITE <1>http</1>"
    },
    "h1": "A simple example",
    "change-locale": "Change locale to \"{{changeTo}}\"",
    "to-second-page": "To second page",
    "error-with-status": "A {{statusCode}} error occurred on server",
    "error-without-status": "An error occurred on the server",
    "title": "Home | next-i18next",
    "blog": {
      "optimized": {
        "question": "Do you like to unleash some super powers to have all side optimized translations?",
        "answer": "Then you may have a look at 66666<1>this blog post</1>."
      },
      "ssg": {
        "question": "Do you want to use SSG (next export)?",
        "answer": "Then you may have a look at11111111 <1>this blog post</1>."
      }
    }
  }

/public/locales/en/footer.json

{
    "description": "This is a non-page component that requires its own namespace"
}

/public/locales/fr/common.json

{
    "website": {
        "name": "SAUCouncil <1>fr http</1>"
    },
    "h1": "Ein einfaches Beispiel",
    "change-locale": "Sprache wechseln zu \"{{changeTo}}\"",
    "to-second-page": "Zur zweiten Seite",
    "error-with-status": "Auf dem Server ist ein Fehler ({{statusCode}}) aufgetreten",
    "error-without-status": "Auf dem Server ist ein Fehler aufgetreten",
    "title": "Hauptseite | next-i18next",
    "blog": {
      "optimized": {
        "question": "Möchtest du einige Superkräfte entfesseln, um für alle Seiten optimierte Übersetzungen zu haben?",
        "answer": "Dann schaue dir vielleicht 3333333333<1>diesen Blogbeitrag</1> an."
      },
      "ssg": {
        "question": "Möchtest du SSG (next export) verwenden?",
        "answer": "Dann schaue dir vielleicht 2222222<1>diesen Blogbeitrag</1> an."
      }
    }
  }

/public/locales/fr/footer.json

{
    "description": "Dies ist eine Nicht-Seitenkomponente, die einen eigenen Namespace erfordert"
}

/public/locales/zh-CN/common.json

{
    "website": {
        "name": "我的网站 <1>http</1>"
    },
    "h1": "示例代码",
    "change-locale": "Change locale to \"{{changeTo}}\"",
    "to-second-page": "To second page",
    "error-with-status": "A {{statusCode}} error occurred on server",
    "error-without-status": "An error occurred on the server",
    "title": "Home | next-i18next",
    "blog": {
      "optimized": {
        "question": "Do you like to unleash some super powers to have all side optimized translations?",
        "answer": "Then you may have a look at 66666<1>this blog post</1>."
      },
      "ssg": {
        "question": "Do you want to use SSG (next export)?",
        "answer": "Then you may have a look at11111111 <1>this blog post</1>."
      }
    }
  }

/public/locales/zh-CN/footer.json

{
    "description": "页面底部描述"
}

Post Comment