国际化

动态国际化组件

基于 vue-i18n 封装的动态国际化组件

使用步骤

  • 1、dyn-i18n.ts
// dyn-i18n.ts
import { type I18n, type I18nOptions, createI18n } from 'vue-i18n'
import type { WritableComputedRef } from 'vue'

export interface DynI18nOptions {
  langKey?: string
  defaultLang?: string
  resourceUrl?: string
}

export interface LangList {
  name: string
  fileName: string
  isLoaded?: boolean
}

export class DynI18n {
  private _i18n: I18n
  private _langKey = 'lang' // 本地缓存的语言设置字段名
  private _lang = 'zh-CN'
  private _defaultLang = 'zh-CN'
  private _resourceUrl = '/langs'
  private _langs: Array<LangList> = []
  private _isLoadedLang = false

  // step1: 实例化
  constructor(options?: DynI18nOptions, i18nOptions?: I18nOptions) {
    if (options?.langKey) {
      this._langKey = options.langKey
    }
    if (options?.defaultLang) {
      this._defaultLang = options.defaultLang
    }
    if (options?.resourceUrl) {
      this._resourceUrl = options.resourceUrl
    }
    // 首先检查 localStorage 中保存的 lang ,再检查 浏览器首选语言,最后再设置默认语言
    const cacheLang = localStorage.getItem(this._langKey) as string
    if (cacheLang && cacheLang !== 'null') {
      this._lang = cacheLang
    } else {
      const locale = navigator.language
      if (locale) {
        this._lang = locale
        localStorage.setItem(this._langKey, this._lang)
      }
    }
    // 初始化 vue-i18n
    this._i18n = createI18n(
      Object.assign(
        {
          legacy: false,
          globalInjection: true,
          fallbackLocale: this._defaultLang,
          locale: this._lang,
        },
        i18nOptions
      )
    )
  }

  // step2: 初始化获取配置文件和当前语言包
  async init() {
    if (await this._loadLang(this._lang)) {
      // 设置 vue-i18n 语言设置
      ;(this._i18n.global.locale as WritableComputedRef<string>).value =
        this._lang
    }
    // 初始化的时候如果当前语言和默认语言不一致,加载一下默认语言
    if (this._lang !== this._defaultLang) {
      await this._loadLang(this._defaultLang)
    }
    this._isLoadedLang = true
  }

  // step3: 修改语言
  async setLang(val: string) {
    if (val !== this._lang) {
      this._lang = val
      this._isLoadedLang = false
      // 请求语言包
      if (await this._loadLang(val)) {
        this._isLoadedLang = true
        // 设置 vue-i18n 语言设置
        ;(this._i18n.global.locale as WritableComputedRef<string>).value = val
        localStorage.setItem(this._langKey, val)
      }
    }
  }

  get i18n() {
    return this._i18n
  }

  // 获取当前语言
  get lang() {
    return this._lang
  }

  // 获取当前语言列表
  get langs() {
    return this._langs
  }

  // 是否已经加载了语言包
  get isLoaded() {
    return this._isLoadedLang
  }

  // 读取 config.json 获取支持的语言列表
  private async _loadLangs() {
    if (!this._langs.length) {
      const resJSON = await fetch(`${this._resourceUrl}/config.json`)
      if (resJSON) {
        const res = await resJSON.json()
        if (res && res.langs && res.langs.length) {
          this._langs = res.langs
        }
      }
    }
    return this._langs
  }

  // 加载语言包
  private async _loadLang(lang: string) {
    // 先检查语言列表中有没有当前设置的语言
    const list = await this._loadLangs()
    const currentLangItem = list.find((v) => v.fileName === lang)
    if (list && list.length && currentLangItem) {
      if (!currentLangItem.isLoaded) {
        const resJSON = await fetch(`${this._resourceUrl}/${lang}.json`)
        if (resJSON) {
          const res = await resJSON.json()
          if (res && this._i18n) {
            this._i18n.global.mergeLocaleMessage(lang, res)
            currentLangItem.isLoaded = true
          }
        }
      }
      return true
    }
    return false
  }
}

export default DynI18n
  • 2、实例化
// /src/utils/i18n.ts
import DynI18n from '@/utils/dynI18n'
// 默认读取当前项目下 /langs 中的 config.json 和 语言包 文件,默认语言为 zh-CN ,默认存储在 localStorage 中的国际化 key 为 lang
const dynI18n = new DynI18n()
export default dynI18n
  • 3、注册
// /src/main.ts
import dynI18n from '@/utils/i18n'
app.use(dynI18n.i18n)
  • 4、加载语言

在路由拦截中判断 dynI18n.isLoaded 是否已加载,如果已加载直接 next,如果未加载 await 加载语言包。

// router.ts
router.beforeEach(async (to, from, next) => {
  document.title = to.meta.title
  if (!dynI18n.isLoaded) {
    await dynI18n.init()
  }
  next()
})
  • 5、切换语言
import dynI18n from '@/utils/i18n'
await dynI18n.setLang('en-US')

config.json 文件说明

{
  "langs": [
    {
      "name": "中文简体",
      "fileName": "zh-CN"
    },
    {
      "name": "English",
      "fileName": "en-US"
    }
  ]
}

表示当前项目有 2 种语言可选择,分别是 中文简体English,fileName 是语言包在 resourceUrl 下的文件名

推荐一个 i18n 在 vscode 中的辅助插件: i18n-ally

对于 json 文件支持度非常高,可以做到在源码中察看结果、修改语言信息、分析翻译覆盖度等。

// vscode 配置文件 setting.json
"i18n-ally.localesPaths": ["app/public/langs"],
"i18n-ally.extract.autoDetect": true,
"i18n-ally.sourceLanguage": "zh-CN",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.sortKeys": false,
"i18n-ally.enabledParsers": ["ts", "json"],
"i18n-ally.enabledFrameworks": ["vue", "vue-sfc"],
"i18n-ally.keystyle": "nested",
"i18n-ally.ignoreFiles": ["**/config.json"],