使用vite和vue-i18n,实现部署后更新多语言功能

28 篇文章 0 订阅
10 篇文章 0 订阅

使用vite和vue-i18n,实现部署后更新多语言功能

vue-i18n是Vue.js的国际化插件,用于实现多语言功能。但是vue-i18n文档中提供的示例用法仅仅是开发时才可以添加/修改多语言。如果希望在打包部署后实现多语言的修改甚至增加语言,不需要修改源码或者重新打包,类似于我们常见的多语言包扩展,又该如何实现呢?

功能和工程结构

工程结构

为了方便后续说明,首先提供一下我这边整个项目的目录结构。目录结构中省略了与本次说明不相关的文件。这里使用的是Vue3,但是方法对vue2也适用。

|-- app
    |-- package.json
    |-- tsconfig.json
    |-- tsconfig.node.json
    |-- vite.config.ts
    |-- plugins
    |   |-- rollup-plugin-i18n-build
    |       |-- index.ts
    |-- src
        |-- main.ts
        |-- i18n
        |   |-- ar-SA.yaml
        |   |-- de-DE.yml
        |   |-- en_US.json
        |   |-- es-ES.json5
        |   |-- index.ts
        |   |-- zh_CN.json
        |-- pages
            |-- subject.vue

代码中的多语言

开发时多语言默认存放在src/i18n文件夹中,也可以存放到其他位置。文件夹中存放各个语言的语言文件,以语言key命名。支持的文件格式有:

  • json
  • yaml
  • yml
  • json5

代码会自动查找上述后缀的文件作为语言文件。非上述后缀的文件,例如.ts .js多语言生成时会被忽略。

文件格式如下,使用vue-i18n接受的格式即可。可以嵌套结构,也可以平铺。

{
  "abc": {
    "def": "你好"
  },
  "def": "你好js",
  "common": {
    "delete": "删除"
  }
}
abc.def: hello
def: hello js
common.delete: delete

构建包(dist)中的多语言目录

为了统一后端寻址,生成的的多语言文件默认统一放置在dist/assets/i18n,也可以存放到其他位置。生成后多语言的文件统一为平铺的json格式,方便同事进行翻译。

|-- dist
    |-- index.html
    |-- assets
        |-- vite.svg
        |-- i18n
            |-- ar-SA.json
            |-- de-DE.json
            |-- en_US.json
            |-- es-ES.json
            |-- zh_CN.json

如果希望增加/修改多语言,就在构建包的多语言目录中增加/修改多语言文件即可,不需要修改代码或重新打包。

获取多语言的实现

代码执行时,多语言这边分为两种获取方式,开发模式从代码中的多语言中获取,生产版本从dist中获取。

开发模式获取多语言

// src/i18n/index.ts

import { createI18n } from 'vue-i18n'
import axios from 'axios'
import jsyaml from 'js-yaml'
import json5 from 'json5'

interface DeepRecord {
  [key: string]: DeepRecord | string
}

// 根据不同格式转换文件内容
function transFile(fileData: string, suffix: string): DeepRecord {
  switch (suffix) {
    case 'json':
    case 'json5':
      return json5.parse(fileData) as DeepRecord
    case 'yaml':
    case 'yml':
      return jsyaml.load(fileData) as DeepRecord
  }
  return {}
}

function getDevLanguages() {
  const reg = /.*i18n\/(.*)\.(json5?|yaml|yml)/
  const messages: Record<string, any> = {}
  const components = import.meta.glob(
    ['@/i18n/*.json5', '@/i18n/*.json', '@/i18n/*.yaml', '@/i18n/*.yml'],
    {
      as: 'raw',
      eager: true,
    },
  )
  Object.keys(components).forEach((key: string) => {
    const regMatch = key.match(reg)
    if (!regMatch) return
    const langKey = regMatch[1] || ''
    const suffix = regMatch[2] || ''
    if (!langKey || !suffix) return
    messages[langKey] = transFile(components[key], suffix)
  })
  return messages
}

// 默认多语言
const i18n = createI18n({
  locale: 'zh_CN',
  legacy: false,
  messages: getDevLanguages(),
})

export default i18n

开发模式下使用import.meta.glob自动查找src/i18n下的所有多语言文件。根据不同的文件后缀对文件内容解析为Object,createI18n时作为messages提供。

生产版本获取多语言

生产版本下,把多语言文件做为静态资源,使用接口获取。

// src/i18n/index.ts
const distPath = `${import.meta.env.VITE_NAMESPACE}/assets/i18n/`

async function getLanguage(lang: string) {
  const reqUrl = `${distPath}${lang}.json`
  const res = await axios.get(reqUrl)
  return res.data
}

// 默认多语言 代码上面已经提供了
const i18n = createI18n('...')

// build后从静态资源中获取
export async function renderI18n(langKey = 'zh_CN') {
  if (import.meta.env.DEV) {
    i18n.global.locale.value = langKey
  } else {
    const message = await getLanguage(langKey)
    i18n.global.locale.value = langKey
    i18n.global.setLocaleMessage(langKey, message)
  }
}
renderI18n()

设置多语言时调用renderI18n函数即可。入参是多语言key,把静态资源作为接口请求,获取到多语言文件,设置i18n对象为所需要的语言。

rollup插件生成构建包(dist)多语言

虽然标题写了vite(因为vite对于Vue开发者更熟悉),但插件本身并没有使用vite特性,所以它是一个同时支持vite和rollup的插件。

调用方式

// vite.config.ts
import { defineConfig, ConfigEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import i18nBuildPlugin from './plugins/rollup-plugin-i18n-build'

export default ({ mode }: ConfigEnv) => {
  return defineConfig({
    plugins: [vue(), i18nBuildPlugin(mode)],
    ...['其它vite配置']
  })
}

插件入参

  • mode 模式,只在生产模式production时执行插件
  • srcPath 代码中多语言目录,默认src/i18n
  • distPath 构建包(dist)中的多语言目录,默认dist/assets/i18n

代码实现

// plugins/rollup-plugin-i18n-build/index.ts
import fs from 'fs'
import path from 'path'
import jsyaml from 'js-yaml'
import json5 from 'json5'

interface DeepRecord {
  [key: string]: DeepRecord | string
}

// 递归展平
function objDeepEachKey(obj: DeepRecord, keyPre: string): Record<string, string> {
  const i18nJson: Record<string, string> = {}
  Object.keys(obj).forEach((key: string) => {
    if (Object.prototype.toString.call(obj[key]) === '[object Object]') {
      Object.assign(i18nJson, objDeepEachKey(obj[key] as DeepRecord, keyPre + key + '.'))
    } else {
      //@ts-ignore
      i18nJson[keyPre + key] = obj[key]
    }
  })
  return i18nJson
}

// 把嵌套的多语言字符串展平
function i18nFlat(obj: DeepRecord): string {
  const i18nJson: Record<string, string> = objDeepEachKey(obj, '')
  return JSON.stringify(i18nJson)
}

// 根据不同格式转换文件内容
function transFile(fileData: string, suffix: string): DeepRecord {
  switch (suffix) {
    case 'json':
    case 'json5':
      return json5.parse(fileData) as DeepRecord
    case 'yaml':
    case 'yml':
      return jsyaml.load(fileData) as DeepRecord
  }
  return {}
}

const reg = /(.*)\.(json5?|yaml|yml)$/
// 把dev模式下的多语言文件保存在dist下
function setDevLangs(srcPath: string, distPath: string) {
  const dir = fs.readdirSync(srcPath)
  dir.forEach(async (name: string) => {
    const regMatch = name.match(reg)
    if (!regMatch) return
    const langKey = regMatch[1] || ''
    const suffix = regMatch[2] || ''
    if (!langKey || !suffix) return
    const data = fs.readFileSync(path.join(srcPath, name), 'utf8')
    const mes = i18nFlat(transFile(data, suffix))
    fs.writeFileSync(path.join(distPath, langKey + '.json'), mes)
  })
}

export default function i18nBuildPlugin(
  mode: string,
  srcPath = path.join('src', 'i18n'),
  distPath = path.join('dist', 'assets', 'i18n'),
) {
  return {
    name: 'i18nBuildPlugin',
    async closeBundle() {
      if (mode !== 'production') {
        return
      }
      fs.mkdirSync(distPath)
      setDevLangs(srcPath, distPath)
    },
  }
}

实现说明

  1. 插件在使用closeBundle钩子,是rollup钩子中的最后一步。rollup钩子说明。触发closeBundle钩子的时候,打包已经结束,dist目录中已经已经有了打包后的文件。选择钩子时,注意必须在新的dist文件生成之后才能执行。
  2. 这时候setDevLangs函数srcPath中的多语言文件。这里的代码是打包时执行,是node环境,不是浏览器环境,不能使用import.meta.glob,因此使用fs读取文件。
  3. 读取后根据后缀名读取文件内容。这部分的代码基本和src/i18n/index.ts中开发时获取多语言的方式一致。
  4. objDeepEachKey函数递归展平多语言key。开发时为了方便,多语言key是嵌套的,但是展平的key对后续翻译和其他地方归档更方便。
  5. 生成的多语言对象作为json存储在distPath中。

注意示项和问题

Vue3和Vue2的实现差异

  • 实现方式从Vue3改为Vue2非常简单:
// src/i18n/index.ts
// vue3实现
i18n.global.locale.value = langKey
i18n.global.setLocaleMessage(langKey, message)

// vue2实现
i18n.locale = langKey
i18n.setLocaleMessage(langKey, message)

这两句是vue-i18n切换语言和设置message的方法。

同一种语言没有使用多个文件描述

其实一开始我是想每一种多语言一个文件夹,文件夹中可以有多个描述文件,用index.ts作为导出接口。

// 一开始设想的实现方式
src
├── i18n
│    ├── zh_CN
│    │  ├── index.ts
│    │  ├── abc.ts
│    ├── en_US
│    │  ├── index.ts
│    │  ├── abc.ts
│    ├── index.ts

这样方便开发人员查找多语言,提交代码也不容易冲突。但这在实现上有一些问题:

  • 代码多语言目录中同一份代码文件既在浏览器端执行,又在插件中的node端执行。tsconfig.jsontsconfig.node.json中的include不能添加相同的的目录。
  • js/ts文件中的内容是不可控的,可能用户在代码中添加了一些浏览器端中的特性,比如import.meta.glob,作为插件没办法控制,只能报错。
  • 插件的代码中可以使用静态路径的import或者require导入代码多语言目录的js/ts文件,而且js/ts文件中包含的import也能正常导入。我可以先使用fs获取目录列表,然后在单个导入。但是,插件中的import或者require导入只能静态解析,不能写动态路径,例如src/i18n/${key}。如果使用fs读取js/ts文件,则需要自己解析执行,如果遇到import还需要自己查找依赖再用fs引入。这样越来越复杂了,遂放弃。
  • 如果在多语言目录中多一个i18n.ts文件,对每个语言目录中的index.ts手动引入(无法使用import.meta.glob这种自动查找方法),在插件中再手动引入i18n.ts,这样就能避免上一个问题。但是这样实现只能说太不优雅了(虽然目前也并不优雅…)。
  • 后来,我看到了vitessevite-plugin-vue-i18n。发现这里面的插件也是每个语言只有一个文件。我想开了,一个就一个吧。

直接使用静态资源目录存放

  • 如果每个语言一个文件,其实有更好的实现方式: 既然我们用的是json/yaml等配置文件,直接在public文件(或者专门设置一个静态资源目录)中放置语言文件就行了。这样开发模式和生产模式都去解析静态资源即可,这样也不用写插件,甚至也不用区分开发模式和生产模式,也同样能实现部署后更新多语言功能。那么上面的方法相比静态资源目录存放有什么优势呢?
  • 自动识别转换语言文件格式为json
  • 自动展平多语言key
  • distPath任意指定。例如是使用静态资源目录,又想存放到assets/i18n/中,我们的静态资源目录需要是public/assets/i18n/,这样写起来不太好看。

参考

### 回答1: vite-plugin-vue-i18n是一个为Vue应用提供国际化(i18n)支持的Vite插件。Vite是一个现代化的前端构建工具,而vue-i18n是Vue官方推荐的国际化解决方案之一。 vite-plugin-vue-i18n主要提供了以下功能: 1. 支持多语言:插件可以帮助我们将Vue应用中的文本内容翻译成多种语言,使得应用能够根据用户的地理位置或语言偏好来显示相应的语言版本。 2. 动态加载:插件可以根据需要动态加载所需的语言包,避免了一次性加载所有语言资源的性能问题,提高了应用的加载速度。 3. 热重载:插件支持在开发模式下的热重载,即当我们修改了语言资源文件时,页面会自动刷新以显示最新的翻译内容,提高了开发效率。 4. 构建优化:插件可以使用Vite的优化能力,将应用中的多语言资源进行压缩和合并,减少网络请求,从而提高应用的性能。 使用vite-plugin-vue-i18n,我们可以在Vue应用中轻松地实现国际化需求。通过定义一系列的翻译文件,我们可以将应用中的文本内容翻译成多种语言。在Vue组件中,我们可以使用特定的指令或方法来访问和显示对应的翻译内容。 总之,vite-plugin-vue-i18n为我们提供了一种简洁高效的方式来实现Vue应用的国际化需求,帮助我们更好地面向全球用户开发应用。 ### 回答2: vite-plugin-vue-i18n 是一个用于在 Vite.js 项目中支持国际化的插件。Vite.js 是一个基于浏览器原生 ES 模块导入的前端构建工具,vite-plugin-vue-i18n 则是为了在 Vite.js 项目中实现国际化而开发的插件。 这个插件的主要功能是将 Vue 应用程序中的文本国际化,使开发人员能够轻松地在应用中切换和管理多种语言。它能够处理不同语言之间的文本翻译,提供了一种方式来管理和导入不同语言的翻译文件。同时,它还提供了一些工具和指令,使开发人员能够更方便地在应用程序中使用和显示多语言文本。 使用 vite-plugin-vue-i18n,开发人员可以在 Vue 组件中使用特定的指令来标记需要翻译的文本,并通过导入翻译文件来实现文本的动态切换。它支持多种文件格式,如 JSON、YAML、PO 等,可以灵活选择适合自己项目的翻译文件格式。开发人员还可以通过 API 来访问、处理和修改翻译文本。 这个插件还提供了一些其他功能,如支持 Vue 3 的 Composition API、语言切换的路由钩子、翻译文件的合并和拆分等。它还支持在开发环境和生产环境下的不同配置,并具有 TypeScript 类型支持。此外,它还可以与其他插件和构建工具无缝集成,如 Vuex、Vue Router、Vuepress 等。 总的来说,vite-plugin-vue-i18n 是一个功能强大且易于使用的插件,可以帮助开发人员快速实现 Vue 应用程序的国际化。无论是小型项目还是大型项目,都可以借助这个插件来管理多语言文本,提供更好的用户体验。 ### 回答3: vite-plugin-vue-i18n是一个适用于Vite构建工具的插件,用于集成和管理Vue.js应用程序的国际化(i18n)功能。它提供了一种简单的方式来处理多语言翻译和本地化,以便应用程序可以方便地在不同的语言环境中展示。 使用vite-plugin-vue-i18n,我们可以在Vue应用程序中轻松地定义多个语言版本的翻译文件,并在运行时根据需要加载适当的翻译信息。我们可以在单个文件中定义所有语言的翻译内容,或者根据语言划分翻译成多个文件进行管理。 该插件提供了一个全局的翻译函数$t,可以在Vue组件中使用它来获取当前语言的翻译文本。我们可以通过在模板中使用`{{ $t('key') }}`的方式来引用翻译内容。而且,我们还可以根据具体的语言环境,动态切换显示的语言版本。 vite-plugin-vue-i18n还支持自动提取模板中的翻译文本,并生成对应的翻译文件。这样我们就可以通过一个命令将应用程序中的所有翻译文本提取出来,然后交给翻译团队进行翻译,最后再将翻译结果导入到应用程序中。 总之,vite-plugin-vue-i18n是一个方便易用的工具,使得开发多语言应用程序变得更加简单和高效。它提供了一种集成且高度可定制的方式来实现应用程序的国际化需求,可以帮助我们轻松地处理不同语言环境下的文本翻译和本地化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值