前端多语言加载优化

73 阅读4分钟

背景:站点具有多语言需求,用户访问站点期间需要加载相应的多语言json 文件。

  1. 如何更快响应产品变更词条需求。
  2. 如何更快让用户加载多语言文件是需要考虑的点。
痛点:
  1. 随着多语言JSON文件的词条不断增加,文件体积的增大会导致加载时间延长,进而影响用户体验。因此,优化文件加载效率对于提升用户满意度至关重要。

  2. 以前的多语言文件维护在项目中,每次产品词条更新都需经过CI/CD流程发布,这不仅耗时较长,还难以实现快速响应变更需求。

如何解决:
  1. 基于以上两点问题,全球站多语言需要解决2个问题

  2. 多语言 json文件需要热更新,即产品改动词条之后直接发布,前端不需要CI/CD,用户即可加载最新的多语言文件

  3. 分片加载: 当用户访问特定模块(如KYC页面)时,我们只会加载 kyc 相关的多语言json 文件。其余模块多语言文件并不会被加载 (比如: 用户访问kyc模块,assets 模块多语言就不会被加载)。分片加载带来的好处是: 按需加载,且请求的词条量较小,用户体验更好

项目如何接入:
  1. 初始化拿到所有的 多语言josn文件 在S3的绝对地址,形成一个[国家]+[语言模块]结构。 (类似以下结构)

// 这里的 data 是后端返回的 多语言文件在 S3 绝对地址,只会请求一次
 const data = {
    en_US_asset: 'https://xxx/public/abc.json',    // 英文模式  资产模块    多语言json 文件
    en_US_common: 'https://xxx/public/abcd1.json', // 英文模式  common模块 多语言json 文件
    en_US_kyc: 'https://xxx/public/acb31.json',    // 英文模式  kyc模块    多语言json 文件
    
    zh_CN_asset: 'https://xxx/public/asd1.json',   // 中文简体模式  资产模块     多语言json 文件
    zh_CN_common: 'https://xxx/public/231.json',   // 中文简体模式  common模块  多语言json 文件
    zh_CN_kyc: 'https://xxx/public/12321das.json', // 中文简体模式  kyc模块     多语言json 文件
    
    zh_TW_asset: 'https://xxx/public/1fsa1dsa.json', // 中文繁体模式  资产模块    多语言json 文件
    zh_TW_common: 'https://xxx/public/asds213.json', // 中文繁体模式  common模块 多语言json 文件
    zh_TW_kyc: 'https://xxx/public/sdadasd1.json',   // 中文繁体模式  kyc模块    多语言json 文件
  };
  1. 当用户访问具体某个业务模块的时候,只加载对应模块的多语言json 文件
import i18n from 'i18next';
import HTTPApi from 'i18next-http-backend';
import { awaitWrap } from 'cus-utils';
import localS3Map from './localS3Map';

import {
  getLocalLang,
  EN,
  ZHCN,
  ZHHK,
  LangMapVal,
  NameSpace,
} from '@/utils/utils';
import { getS3LangUrl } from '@/service/common';
import formatS3RemoteLang, {
  type TypeFormatS3RemoteLangUnion,
} from './utils/formatS3RemoteLang';

async function getRemoteS3Url() {
  const [err, s3data] = await awaitWrap(getS3LangUrl());

  const formatS3Data = formatS3RemoteLang(
    s3data?.updateLanguageParameters ?? [],
  );
  // 后续看需要不要加统一错误处理
  if (err) {
    console.error(err, '远端多语言加载失败');
  }

  i18n.use(HTTPApi).init({
    lng: getLocalLang(),
    fallbackLng: {
      zh_CN: [ZHCN],
      zh_HK: [ZHHK],
      en_US: [EN],
      default: [EN],
    },
    ns: 'common',
    fallbackNS: ['default'],
    backend: {
      loadPath: async ([lngs]: LangMapVal[], [namespaces]: NameSpace[]) => {
        const key = (lngs +
          '_' +
          namespaces) as keyof TypeFormatS3RemoteLangUnion;

        // 本地开发模式多语言 json 文件名map
        if (import.meta.env.MODE === 'development') {
          // mock json 文件
          const devModeFilename = localS3Map[key as keyof typeof localS3Map];

          return `${import.meta.env.VITE_STATIC_PUBLICPATH}locales/${devModeFilename}.json?hash=${LANG_HASH}`;
        }

        // 这里控制 多语言按需加载
        const filename = formatS3Data[key].downloadUrl;
        return filename;
      },
      overrideMimeType: 'application/json',
    },
    load: 'currentOnly',
  });
}

getRemoteS3Url();

export default i18n;
  1. 业务代码如何控制多个模块多语言json文件

    1. 方式一:通过 useTranslation 自定义声明 namespace
    2. export default function Page() {
        
        const { t } = useTranslation('kyc');       // 加载 kyc 模块多语言文件
        const { t: t1 } = useTranslation('asset'); // 加载 asset 模块多语言文件
        const { t: t2 } = useTranslation('common');// 加载 common 模块多语言文件
      
        return (
          <>
            <div>kyc词条--- {t('common.overview')}</div>
            <div>asset词条 --- {t1('common.overview')}</div>
            <div>common词条 --- {t2('common.overview')}</div>
          </>
        );
      }
      
    3. 方式二:通过 withTranslation 高阶组件进行 提前注入 namespace
function Page(props) {
  return (
    <>
      <div>kyc词条--- {props.t('common.overview')}</div>
      <div>asset词条 --- {props.t('common.overview', { ns: 'asset' })}</div>
      <div>common词条 --- {props.t('common.overview', { ns: 'common' })}</div>
    </>
  );
}

export default withTranslation(['kyc', 'asset', 'common'])(Page);
效果:
  1. 通过以上配置 i18n文件, 我们可以做到词条维护在 远端S3,实现词条热更新

  2. 用户按需加载多语言词条,用户体验更好

本地开发时如何生成线上多语言:
  1. 方式一: 可以使用代理到线上多语言文件形式
  2. 方式二: 也可以使用shell 拉取线上多语言然后在本地使用, 在 本地 调用 getRemoteLang.sh
#!/bin/bash

### getRemoteLang.sh 内容
response=$(curl -s -X POST  https://xxxx/v1/mix/public/app/init)
updateLanguageParameters=$(echo "$response" | jq -r '.data.updateLanguageParameters')

localS3Map="/* eslint-disable */\nconst localS3Map = {\n"

temp_file=$(mktemp)

echo "$updateLanguageParameters" | jq -c '.[]' | while IFS= read -r item; do
    downloadUrl=$(echo "$item" | jq -r '.downloadUrl')
    languageName=$(echo "$item" | jq -r '.languageName')
    md5=$(echo "$item" | jq -r '.md5')
    nameSpace=$(echo $item | jq -r '.namespace // "common"')

    # 下载文件并保存到本地
    filename="src/locales/${languageName}_${nameSpace}.json"
    curl -s "$downloadUrl" -o "$filename"

    echo "本地生成 ${languageName}_${nameSpace}.json 多语言文件 成功"

    echo ""${languageName}_${nameSpace}": "${languageName}_${nameSpace}"," >> "$temp_file"
done

# cat $temp_file
localS3Map+=$(cat "$temp_file")
rm "$temp_file"

localS3Map+="\n};\nexport default localS3Map;"
echo "$localS3Map" > src/localS3Map.ts
未来优化:
  1. 多语言加载一次之后,缓存在本地,当多语言文件内容 MD5 值明确发生变更之后,再次请求多语言json 文件。即用户请求一次,文件内容没发生变更,直接使用缓存过的多语言文件,避免用户每次刷新都请求多语言文件
  2. 使用 namespace 之后,开发者每次导出组件 都需要引入至少1个namespace,后续考虑使用 AST 进行动态注入每个 组件,尽可能少写 引入 namespace
  3. 目前词条是一个一个录入的,后续考虑在打包编译阶段自动识别到多语言 key,上传到多语言平台,释放开发者录入词条的时间