国际化 i18n 项目中怎么实现自动化生成 json 数据,而不是通过手动添加翻译字段呢?

通常支持 i18n 的项目,都需要提供一个对应的国际化 json 数据,但是手动维护 json 数据比较麻烦,怎么才能实现自动或者一键生成 json 数据呢?

本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。
阅读 3k
1 个回答

其实有很多的项目在实现这个功能,比如说之前SF社区就有人自推过自己实现的一个 i18n 管理项目 👉 vue-i18n-generator - 一款方便编辑语言包代码的工具
最近也有人分享过文章 对国际化 i18n 项目的一点思考,里面有提到过几种维护的方式,都可以借鉴一下。

其实使用 i18n-ally 开发起来就已经很方便了。

i18n Ally


我自己呢就是偷懒,其实是因为配置 i18n Ally 的时候总是失败,所以就自己写了一个构建脚本,维护的时候是去维护一个数组:

// /locale/modules/common/button.js
import { transferLangConfig } from '@/i18n/config'

const config = {
  // 通用部分
  "search": ['搜索', '檢索', 'Search'],
  "reset": ['重置', '重置', 'Reset'],
  "clear": ['清空', '清空', 'Clear'],
  "add": ['新增', '新增', 'Add'],
  "confirm": ['确认', '確認', 'Confirm'],
  "select": ['选择', '選擇', 'Select'],
  "delete": ['删除', '刪除', 'Delete'],
  "cancel": ['取消', '取消', 'Cancel'],
  "remove": ['移除', '移除', 'Remove'],
  "yes": ['是', '是', 'Yes'],
  "no": ['否', '否', 'No'],
  // ...
}

export default transferLangConfig(config)

最后通过 "i18n-gen": "node -r esm ./locale/i18nGen.js" 这个脚本执行生成对应语言的 json 文件,大概思路如下:

// /locale/i18nGen.js
import { writeFile } from 'fs'
import { languageList } from './config.js'
import localeData from './modules/index.js'

// uni-app 不支持多层JSON数据需要转换成单层 => "common.button.submit":"提交" 这样的形式
function transfSingleLayerJSON(data, fatherKey=''){
  let tempObj = {}
  for(let key in data){
    let prefix = `${fatherKey?fatherKey+'.':''}${key}`
    if(typeof data[key] === 'object')  {
      Object.assign(tempObj, transfSingleLayerJSON(data[key], prefix))
    } else {
      tempObj[prefix] = data[key]
    }
  }
  return tempObj
}

languageList.forEach(lang => {
  const tempLocale = transfSingleLayerJSON(localeData[lang])
  writeFile(`./locale/${lang}.json`, JSON.stringify(tempLocale),()=>{});
})

一些其他的相关JS示例

// /locale/config.js
export const languageList = ['zh-Hans','zh-Hant', 'en']

// 创建一个空i18n对象
const createEmptyLangConfig = () => Object.fromEntries(new Map(languageList.map(key => [key, {}])))

// 配置文件转i18n对象
export const transferLangConfig = (data) => {
  const langConfig = createEmptyLangConfig()
  for (const key in data) {
    languageList.forEach((lang, index) => {
      langConfig[lang][key] = data[key][index]
    })
  }
  return langConfig
}
// /locale/modules/index.js
import system from './system/index.js'
import common from './common/index.js'

export default {
  en:{
    ...components['en'],
    system:system['en'],
    common:common['en'],
    ...
  },
  'zh-Hans': {
    ...components['zh-Hans'],
    system:system['zh-Hans'],
    common:common['zh-Hans'],
    ...
  },
  'zh-Hant': {
    ...components['zh-Hant'],
    system:system['zh-Hant'],
    common:common['zh-Hant'],
    ...
  }
}
// /locale/modules/common/index.js
import { transferLangConfig } from '../../config.js'
import button from './button.js'
...

// 公共部分
const common = transferLangConfig({
  "no-data": ['没有数据', '沒有數據', 'No Data'],
})


export default {
  'zh-Hans': {
    ...common['zh-Hans'],
    button: button['zh-Hans'],
    ...
  },
  'zh-Hant': {
    ...common['zh-Hant'],
    button: button['zh-Hant'],
    ...
  },
  'en': {
    ...common['en'],
    button: button['en'],
    ...
  }
}

至于为什么引入的时候每一次都手动去 xxx['en'] 主要是因为想比较随意的命名。有些时候并不像层级那么深。想要可以很快的使用,又想可以聚合在一起维护。


本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。
推荐问题
宣传栏