聊聊官网国际化的解决方案(next.js 和 h5)

3,142 阅读3分钟

在很多官网项目中经常会遇到要做双语的需求,我总结了一些经验和大家一起学习交流

使用 Next.js

next.js 的服务端渲染 ssr、静态化 ssg 以及静态化增量渲染 isr 可以完美的将动态数据适配 seo,本身又是 react 技术栈开发方式 前端er都很熟悉,所以目前在大型官网项目中作为首选框架是很合适的

国际化在 next.js 中很容易实现,它自带国际化路由,比如 /detail 要跳转英文版就是 /en/detail,会在路由之前添加 /en语言路径。首先在配置文件 next.config.js 里设置 i18n 语言选项

这里就用 next.js v13 来做示例

// next.config.js
module.exports = {
  // ...
  i18n: {
    locales: ['zh-CN', 'en'],
    defaultLocale: 'zh-CN', // 默认语言
  },
  // ...
}

locales 中添加了语言列表(中/英),默认语言 defaultLocale 设为中文,默认语言的页面路由不需要添加语言路径前缀,比如中文的新闻页路由直接为 /news 就行,英文版就是 /en/news

我们通常需要在网站头部加一个语言切换的组件,让用户去手动点击切换。

创建一个 LanguageSwitcher 组件:

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

const LanguageSwitcher: React.FC = () => {
  const { locales, locale, pathname, query, asPath } = useRouter()

  return (
    <div className="locale-switcher">
      {locales?.map((item) => (
        <Link
          href={{ pathname, query }}
          as={asPath}
          locale={item}
          key={item}
        >
          <div className="switcher-item">{item}</div>
        </Link>
      ))}
    </div>
  )
}

通过 useRouter hook 可以拿到语言列表 locales、当前页面语言 locale,所有路由的属性 pathname, query, asPath<Link />locale 属性可以传入将要访问的语言,这样用户点击跳转后 会自动在路由路径前加上语言路径

在页面同样可以通过 useRouterlocale 做判断进行条件渲染,这里只是简单示例:

// 页面
import { useRouter } from 'next/router'
import type { NextPage } from 'next'
import css from './page.module.scss'

const HomePage: NextPage = () => {
  const { locale } = useRouter
  
  return (
    <div className={css.page_wrap}>
      <h1>{locale === 'en' ? 'English page' : '中文页面'}</h1>
    </div>
  )
}

当然,大家肯定还有其他的方案,但我觉得国际化路由是最简易可行的

在 h5 普通静态网页中

普通静态网页实现国际化方案非常灵活,大家可自定义规则,一般做两个 html 比如一个中文的一个英文的 news.html news-en.html 有些需求要在不同语言展示不同布局的页面。我会在 html 文件名加 - 横杠语言后缀

同样,在网页头部也有个切换语言的地方:

<div class="language-switch">
  <a data-language-item="zh_CN" href="javascript:">中文</a>
  <a data-language-item="en" href="javascript:">English</a>
</div>

用自定义data属性 data-language-item 来设置语言值,方便在 js 中可以监听点击事件拿到将访问的语言,原理很简单 就是链接到对应语言的 html 页面

创建语言列表数组常量,第一项就为默认语言

// 语言列表
const LANGUAGE_LIST = [
  'zh_CN', // 第一项为默认语言,默认语言的html名不带后缀
  'en',
]

要点击跳转到对应语言页面,就要先知道当前页面的语言是什么,以及不带语言后缀的页面名称,用函数 getCurrentLanguage 来封装下

函数 getFileName 用正则获取浏览器 url 中的 html

但是 url 会出现两种都要考虑到的情况,比如 http://xx.com/news-en.htmlhttp://xx.com/news-en,后者可能是 nginx 中设置的和 html 文件名一致的路由,配置代码很简单,我会在文末贴出来

// 获取文件名
function getFileName(filePath) {
  return filePath.replace(/(.*\/)*([^.]+).*/ig,'$2')
}

// 从浏览器url中获取当前语言、是否为默认语言、没有语言后面的页面名 purePageName
function getCurrentLanguage() {
  const pageName = getFileName(window.location.href)
  let curLanguage = LANGUAGE_LIST[0]

  for (let i = 0; i < LANGUAGE_LIST.length; i++) {
    let _wArr = pageName.split('-')
    if (_wArr[_wArr.length - 1] === LANGUAGE_LIST[i]) {
      curLanguage = LANGUAGE_LIST[i]
      break
    }
  }

  const isDefaultLanguage = curLanguage === LANGUAGE_LIST[0]
  const purePageName = isDefaultLanguage ? pageName : pageName.replace(`-${curLanguage}`, '')

  return { curLanguage, isDefaultLanguage, purePageName }
}

函数 getCurrentLanguage 中先设置当前语言默认为数组 LANGUAGE_LIST 的第一项,然后遍历 LANGUAGE_LIST 通过截取 url 末尾的 - 拿到当前语言,默认语言的页面 url 是不带语言后缀的

调用 getCurrentLanguage() 能返回 curLanguage isDefaultLanguage purePageName,最终需要监听有 data-language-item 属性的 dom 节点的 click 事件跳转到相应的语言页面

const languageItemEls = document.querySelectorAll('[data-language-item]')
const { curLanguage, purePageName } = getCurrentLanguage()

Array.from(languageItemEls).forEach((el) => {
  if (el.getAttribute('data-language-item') === curLanguage) {
    el.classList.add('active')
  } else {
    el.classList.remove('active')
  }

  el.addEventListener(event.click, (e) => {
    const originalUrl = window.location.href
    const hasParams = originalUrl.indexOf('?') >= 0
    // 如果url中有参数就存为后缀
    //(?id=1&name=jack&backUrl=test.html)暂不支持参数中值为html地址还另外带参数的,只有微信公众号回跳地址会出现这种情况 用vue/react来做
    const suffix = hasParams ? `?${originalUrl.split('?')[1]}` : ''
    const pageUrl = hasParams ? originalUrl.split('?')[0] : originalUrl

    const pageName = getFileName(pageUrl)

    const targetLanguage = el.getAttribute('data-language-item') ?? LANGUAGE_LIST[0]

    if (curLanguage !== targetLanguage) {
      window.location.href = targetLanguage === LANGUAGE_LIST[0]
        ? pageUrl.replace(pageName, purePageName) + suffix
        : pageUrl.replace(pageName, `${purePageName}-${targetLanguage}`) + suffix
    }
  })
})

Nginx 配置中设置路由url去掉.html后缀(/name.html -> /name):

location / {
  if (!-e $request_filename) {
    rewrite ^(.*)$ /$1.html last;
    break;
  }
}