Next.js 配置 react-intl 实现语言国际化

5,044 阅读3分钟

    使用Next.js的主要目的是实现SSR优化SEO,当然在使用过程中遇到过很多问题。非常感谢 luffyZh 分享的文章,让我少走很多弯路。(Tip:刚开始搭建的同学, 建议先去看 luffyZh官方文档 了解基础知识再来看本文章哦 #^.^# )。

    言归正传,antd官方是推荐使用react-intl实现国际化的,下面分享一下我在Next.js中实现国际化的过程,有不对或可优化的欢迎指正哦。

一、npm 加载 react-intl

  • npm i react-intl -S

二、在根目录添加locales文件夹,用于存放不同语言的js文件

示例:

locales/
--| en.js  // 存放语言文本键值对
--| en-US.js // 导出存放语言的对象
--| zh.js  
--| zh-CN.js 

zh.js

tip: 默认对象只能使用"debugObj.hello"这种方式,咱们常用的 debugObj{ hello:'你好' } 在这里使用会导致组件中取不到值。

export default {
  title:'标题',
  dynamicName:" 动态赋值:{val}",
  "debugObj.hello":"你好 Debug对象",
};
  • 优化 - 使用flat将对象扁平化,从而支持debugObj{ hello:'你好' }对象包裹
  1. 加载flat npm 包
    npm i flat -S
    
  2. 修改zh.js结构
    export default {
     home: {
       title:'标题'
     }
    };
    
  3. 在_app.js中引入flat,将语言包对象扁平化传给react-intl message

zh-CN.js

import appLocaleData from 'react-intl/locale-data/zh';
import zhMessages from './zh.js';
import antdZh from 'antd/lib/locale-provider/zh_CN'; //antd语言包

let appLocale = {
  messages: {
    ...zhMessages,
  },
  antd: antdZh,
  locale: 'zh-CN',
  data: appLocaleData,
};

export default appLocale;

三、在/pages/_app.js中引入LocaleProvide(antd组件语言)和IntlProvider(react-intl)组件,在render函数中包裹根组件。

_app.js

import { Fragment } from 'react';
import App, { Container } from 'next/app';
import { LocaleProvider } from 'antd';
import { addLocaleData, IntlProvider } from 'react-intl';

// 处理对象嵌套
// import Flat from 'flat';

//导入中英文对象
import _ZH from '../locales/zh-CN'; 
import _EN from '../locales/en-US';

let appLocale = {
  messages: {
    ...zhMessages,
  },
  antd: antdZh,
  locale: 'zh-CN',
  data: appLocaleData,
};


class PageContainer extends App {

  getLocale(languages){
    const appLocale = this.getLocaleDatas(languages);
    addLocaleData(...appLocale.data);
    return appLocale;
  }
  
  getLocaleDatas(lang) {
    let result = {};
    switch (lang) {
      case 'zh-CN':
        result = _ZH;
        break;
      case 'en-US':
        result = _EN;
        break;
      default:
        result = _ZH;
    }
    return result;
  }
  
  // 该render在F5刷新,服务端执行一次后再到前端客户端执行一次
  render () {
    const { Component, pageProps, router } = this.props;
    
    // router.query.lang当前语言 - 需要通过修改server.js传入query.lang
    // 根据url设置语言
    const languages = router.query.lang || 'zh-CN';
    const appLocale = this.getLocale(languages);

    return (
      <Fragment>
        <Container>
            {/* antd语言 */}
            <LocaleProvider locale={appLocale.antd}>
              {/* 引用语言包 */}
              <IntlProvider 
                locale={appLocale.locale}
                {/* 语言包对象嵌套 */}
                // messages={Flat(appLocale.messages)}
                
                {/* 默认 */}
                messages={appLocale.messages}
                formats={appLocale.formats}
                >
                  <Component {...pageProps} router={router} />
              </IntlProvider>
            </LocaleProvider>
        </Container>
      </Fragment>
    );
  }
}

export default appLocale;

四、修改server.js,通过正则匹配URL语言名称,该名称可以在_app.js的render函数中this.props获取

server.js

const express = require('express');
const cp = require('child_process');
const next = require('next');

const PORT = '3006';
const dev = true;

const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare()
  .then(() => {
    const server = express();

    // 处理语言包
    const LangExp = new RegExp(/^(\/zh-CN)|(\/en-US)/i);
    server.get(LangExp, (req, res) => {
      let path = req.path;
      let lang = path.match(LangExp)[0];
      let url = path.replace(lang, '');
      lang = lang.slice(1, lang.length);
      // 跳转默认主页
      if (!url){
        url = `/home`;
      }
      console.log(url, lang, '----------route---');
      return app.render(req, res, url, { lang });
      
    });

    // 重定向默认路径
    server.get('/', (req, res) => {
      res.redirect("/zh-CN/home");
    });
    
    server.get('*', (req, res) => {
      return handle(req, res);
    });

    server.listen(PORT, err => {
      if (err) throw err;
      const serverUrl = `http://localhost:${PORT}`;
      console.log(`> Ready on ${serverUrl}`);
    });
  });


五、配置完成后,在组件中使用

home.js

//FormattedMessage编译后是一个span标签
import { Component } from 'react';
import {Button} from 'antd';
import Link from 'next/link';
import { FormattedMessage,injectIntl, intlShape  } from 'react-intl'; 
class Home extends Component {
   static propTypes = {
     intl: intlShape.isRequired
   }
    constructor(props){
        super(props);
        this.intl = this.props.intl;
        this.lang = {
          title:{
            id:'title'
          }
        };
     }
    render(){
        return (
          {/* 用法一 */}
          <FormattedMessage id="title"/>
          <br/>
          {/* 对象 */}
          <FormattedMessage id="debugObj.hello"/>
          <br/>
          {/* 动态赋值 */}
          <FormattedMessage id="dynamicName"   values={{val:'888999'}}/>
          <br/>
          {/* 用法二 */}
          <FormattedMessage {...this.lang.title}/>
          <br/>
          {/* 在input placeholder中使用 */}
          <input placeholder={ this.intl.formatMessage({id:'title.title'}) } />

          
          <br/>
          <br/>
          {/* 语言切换 */}
          <Link href={`/zh-CN/home`}>
            <Button type="primary">中文</Button>
          </Link>

          <Link href={`/en-US/home`}>
            <Button type="primary">English</Button>
          </Link>
        );
    }
}

export default injectIntl(Home)