返回介绍

国际化功能

发布于 2024-09-11 01:01:38 字数 13466 浏览 0 评论 0 收藏 0

官网不一定是给一个国家的人看的,可能公司或是团队的业务是针对多个地区的,语言不应该成为价值观传输的阻碍,所以如果是多地区业务线的公司,实现多语言也是很必要的。

安装相关依赖包:

npm install i18next next-i18next react-i18next

next-i18next 包提供了 appWithTranslation 一个高阶组件(HOC),需要用这个高阶组件包装整个应用程序。

pages/_app.tsx

import type { AppProps, AppContext } from 'next/app';
import App from 'next/app';
import Head from 'next/head';
import axios from 'axios';
import ThemeContextProvider from '@/stores/theme';
import UserAgentProvider from '@/stores/userAgent';
import { LOCALDOMAIN, getIsMobile } from '@/utils';
import type { ILayoutProps } from '@/components/layout';
import { appWithTranslation } from 'next-i18next';
import Layout from '@/components/layout';
import '@/styles/globals.css'

const MyApp = (data: AppProps & ILayoutProps & { isMobile: boolean }) => {
  const {
    Component, pageProps, navbarData, footerData, isMobile
  } = data;
  return (
    <div>
      <Head>
        <title>{`A Demo for 官网开发实战 (${
          isMobile ? "移动端" : "pc 端"
        })`}</title>
        <meta
          name="description"
          content="A Demo for 官网开发实战"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <ThemeContextProvider>
        <UserAgentProvider>
          <Layout navbarData={navbarData} footerData={footerData}>
            <Component {...pageProps} />
          </Layout>
        </UserAgentProvider>
      </ThemeContextProvider>
    </div>
  )
}

MyApp.getInitialProps = async (context: AppContext) => {
  const pageProps = await App.getInitialProps(context);
  const { data = {} } = await axios.get(`${LOCALDOMAIN}/api/layout`)
  return {
    ...pageProps,
    ...data,
    isMobile: getIsMobile(context),
  }
}
export default appWithTranslation(MyApp);

现在为 next-i18next 创建一个配置文件,在项目根目录下创建文件 next-i18next.config.js 并添加如下配置。

module.exports = {
  i18n: {
    defaultLocale: 'zh-CN',
    locales: ['en_US', 'zh-CN'],
  },
  ns: ['header', 'main', 'footer', 'common']
}
  • locales: 包含网站上需要的语言环境的数组。
  • defaultLocale: 要显示的默认语言环境。

现在将创建的 i18next 配置导入到 next.config.js 中。

/** @type {import('next').NextConfig} */
const path = require('path');
const semi = require('@douyinfe/semi-next').default({});
const { i18n } = require('./next-i18next.config');

const nextConfig = semi({
  reactStrictMode: true,
  swcMinify: true,
  i18n,
  images: {
    domains: ['127.0.0.1'],
  },
  webpack: (config) => {
    config.resolve.alias = {
      ...config.resolve.alias,
      '@': path.resolve(__dirname),
    };
    return config;
  }
});

module.exports = nextConfig

现在开始在应用程序中添加语言环境,在 public 目录下新建 locales 目录。

public/locales/en_US/main.json

{
  "IpadStyle": "Currently Ipad style",
  "PCStyle": "Currently it is PC style",
  "MobileStyle": "Currently in mobile style"
}

public/locales/zh_CN/main.json

{
  "IpadStyle": "当前是 Ipad 端样式",
  "PCStyle": "当前是 pc 端样式",
  "MobileStyle": "当前是移动端样式"
}

类似于主题化注入,针对语言也先来定义一套注入器(Context),通过缓存的方式统一管理,然后进行全局的注入。

constants/enum

export enum Language {
  ch = "zh-CN",
  en = "en_US",
}

stores/language.tsx

import {createContext, FC, useEffect, useState} from 'react';
import {Language} from '@/constants/enum';

interface ILanguageContextProps {
  language: Language;
  setLanguage: (language: Language) => void;
}

interface ILanguageContextProviderProps {
  children: JSX.Element;
}

export const LanguageContext = createContext<ILanguageContextProps>({} as ILanguageContextProps);

const LanguageContextProvider: FC<ILanguageContextProviderProps> = ({children}) => {
  const [language, setLanguage] = useState<Language>(Language.ch);
  useEffect(() =>  {
    const checkLanguage = () => {
      const item = localStorage.getItem('language') as Language || Language.ch;
      setLanguage(item);
      document.getElementsByTagName('html')[0].lang = item;
    }
    // 初始化先执行一遍
    checkLanguage();
    // 监听浏览器缓存事件
    window.addEventListener('storage', checkLanguage);
    return (): void => {
      // 解绑
      window.removeEventListener('storage', checkLanguage);
    }
  }, []);
  return (
    <LanguageContext.Provider value={{
      language,
      setLanguage: (currentLanguage) => {
        setLanguage(currentLanguage);
        localStorage.setItem('language', currentLanguage);
        document.getElementsByTagName('html')[0].lang = currentLanguage;
      }
    }}>
      {children}
    </LanguageContext.Provider>
  )
}

export default LanguageContextProvider;

导入 serverSideTranslations,在 getServerSideProps 中进行道具传递。

pages/index.tsx

import {useContext, useEffect, useRef, useState} from 'react';
import axios from 'axios';
import type {NextPage} from 'next';
import {Pagination} from '@douyinfe/semi-ui';
import classNames from 'classnames';
import {ThemeContext} from '@/stores/theme';
import {useTranslation} from 'next-i18next';
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import styles from '@/styles/Home.module.scss';
import {LOCALDOMAIN} from "@/utils";
import {IArticleIntroduction} from "@/pages/api/articleIntroduction";
import {LanguageContext} from "@/stores/language";
import {useRouter} from "next/router";

interface IHomeProps {
  title: string;
  description: string;
  articles: {
    total: number;
    list: {
      label: string;
      info: string;
      link: string;
    }[];
  };
}

const Home: NextPage<IHomeProps> = ({
  title, description, articles
}) => {
  const { i18n } = useTranslation();
  const router = useRouter();
  const { locale } = router;
  const [content, setContent] = useState(articles);
  const mainRef = useRef<HTMLDivElement>(null);
  const { theme } = useContext(ThemeContext);
  const { language } = useContext(LanguageContext);
  useEffect(() => {
    mainRef.current?.classList.remove(styles.withAnimation);
    window.requestAnimationFrame(() => {
      mainRef.current?.classList.add(styles.withAnimation);
    });
  }, [theme]);

  useEffect(() => {
    i18n?.changeLanguage(locale);
    console.warn(locale)
  }, [language, locale])
  return (
    <div className={styles.container}>
      <main className={classNames([styles.main, styles.withAnimation])} ref={mainRef}>
        <h1 className={styles.title}>{title}</h1>
        <p className={styles.description}>{description}</p>
        <div className={styles.grid}>
          {
            content?.list?.map((item, index) => {
              return (
                <div key={index} className={styles.card} onClick={(): void => {
                  window.open(
                    item?.link,
                    "blank",
                    "noopener=yes,noreferrer=yes"
                  );
                }}>
                  <h2>{item?.label}</h2>
                  <p>{item?.info}</p>
                </div>
              )
            })
          }
        </div>
        <div className={styles.paginationArea}>
          <Pagination total={content?.total} pageSize={6} onPageChange={pageNo => {
            axios.post(`${LOCALDOMAIN}/api/articleIntroduction`, {
              pageNo,
              pageSize: 6,
            }).then(({ data: {
              total,
              list: listData,
            }}) => {
              setContent({
                list: listData?.map((item: IArticleIntroduction) => ({
                  label: item.label,
                  info: item.info,
                  link: `${LOCALDOMAIN}/article/${item.articleId}`,
                })),
                total,
              })
            })
          }} />
        </div>
      </main>
    </div>
  )
}

export const getServerSideProps = async ({ locale }: { locale: string }) => {
  const {
    data: {
      title, description,
    }
  } = await axios.get(`${LOCALDOMAIN}/api/home`);
  const {
    data: {
      list: listData, total,
    }} = await axios.post(`${LOCALDOMAIN}/api/articleIntroduction`, {
      pageNo: 1,
      pageSize: 6,
    })
  return {
    props: {
      ...(await serverSideTranslations(locale, ['common', 'footer', 'header', 'main'])),
      title,
      description,
      articles: {
        total,
        list: listData?.map((item: IArticleIntroduction) => ({
          label: item.label,
          info: item.info,
          link: `${LOCALDOMAIN}/article/${item.articleId}`,
        }))
      },
    }

  };
}

export default Home;

在 pages/_document.tsx 中进行交互前注入:

import Document, {Html, Head, Main, NextScript, DocumentContext} from 'next/document'
import Script from 'next/script';
import {Language} from '@/constants/enum';

const MyDocument = () => {
  return (
    <Html>
      <Head />
      <body>
      <Main />
      <NextScript />
      <Script id="theme-script" strategy="beforeInteractive">
        {
          `const theme = localStorage.getItem('theme') || 'light';
           localStorage.setItem('theme', theme);
           document.getElementsByTagName('html')[0].dataset.theme = theme;
           const language = localStorage.getItem('language') || 'zh-CN';
           localStorage.setItem('language', language);
           document.getElementsByTagName('html')[0].lang = language;
           `
        }
      </Script>
      </body>
    </Html>
  )
}

export const getServerSideProps = async (context: DocumentContext & {locale: string}) => {
  const initialProps = await Document.getInitialProps(context);
  return { ...initialProps, locale: context?.locale || Language.ch };
}
export default MyDocument;

修改导航组件,添加语言环境切换器。

components/NavBar/index.tsx

import {FC, useContext, useEffect} from 'react';
import Link from "next/link";
import {useTranslation} from 'next-i18next';
import {ThemeContext} from '@/stores/theme';
import {UserAgentContext} from '@/stores/userAgent';
import {Environment, Language, Themes} from '@/constants/enum';
import styles from './index.module.scss';
import {LanguageContext} from "@/stores/language";
import {useRouter} from "next/router";

export interface INavBarProps {}

const NavBar: FC<INavBarProps> = ({}) => {
  const { t } = useTranslation('main');
  const router = useRouter();
  const { locales, locale: activeLocale } = router;
  const otherLocales = locales?.filter(
    (locale) => locale !== activeLocale && locale !== "default"
  );
  const { setTheme } = useContext(ThemeContext);
  const { setLanguage } = useContext(LanguageContext);
  const { userAgent } = useContext(UserAgentContext);
  useEffect(() => {
    setLanguage(router.locale as Language);
  }, [router.locale]);
  return (
    <div className={styles.navBar}>
      <a href="http://localhost:3000/">
        <div className={styles.logoIcon} />
      </a>
      <div className={styles.themeArea}>
        {userAgent === Environment.pc && (
          <span className={styles.text}>{t('PCStyle')}</span>
        )}
        {userAgent === Environment.ipad && (
          <span className={styles.text}>{t('IpadStyle')}</span>
        )}
        {userAgent === Environment.mobile && (
          <span className={styles.text}>{t('MobileStyle')}</span>
        )}
      </div>
      {otherLocales?.map((locale) => {
        const { pathname, query, asPath } = router;
        return (
          <span key={locale}>
            <Link href={{ pathname, query }} as={asPath} locale={locale}>
              {locale}
            </Link>
          </span>
        );
      })}
      <div className={styles.themeIcon} onClick={(): void => {
        setTheme(localStorage.getItem('theme') === Themes.light ? Themes.dark : Themes.light);
      }}/>
    </div>
  )
}

export default NavBar;

在这里获得了 i18next 配置文件中提到的语言环境,然后映射每个区域设置项目并单击每个将链接如下:

<Link href={{ pathname, query }} as={asPath} locale={locale}>

上面的链接会将应用程序的区域设置 URL 更改为选择的相应区域设置。

useTranslation 从 next-i18next 包中导入钩子。

import { useTranslation } from "next-i18next";

现在可以使用一个函数来获取在 locales 目录 t() 中的 locale 文件中添加的语言字符串。

例如,下面的代码将从选择的相应语言环境(en_US 或 zh_CN)中获取字符串。

const { t } = useTranslation();

return (
    <>
      <span className={styles.text}>{t('MobileStyle')}</span>
    </>
  );

实现效果:


如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文