【高阶用法】uniapp的i18n/修复/增强/App无重启更换语言

痛点

在i18n多语言模块使用过程中,发现下面几个问题,需要解决

1)uni-best框架下,$t功能函数无法实时的切换语言,可能跟使用有关

2)uni-best建议的translate方式在vue块外使用太繁琐,希望不用导入,直接书写$t使用。统一逻辑,减少复杂度

3)uniapp默认的多语言模式在APP下会重启,造成的体验不好

目标

需要完成的目标如下

1)将多语言模块放到公共区域,可能会导致原生标题无法正常切换语音。这个无所谓,因为标题栏已经custom定制并组件化了

2)修复无法正常实时切换语言的$t,这个可能跟使用方式有关,anyway,让它能按原模式正常工作

3)在任何地方都可以使用$t功能,无论是template还是script部分

4)不用重启直接刷新界面

实现

uni-best的translate方法代码实现了一个很好的思路,只是无法支持占位符的功能。让我们改进它

TypeScript 复制代码
/**
 * 任意文件使用$t翻译方法,需要在app里全局导入
 * @param { string } localeKey 多语言的key,eg: "app.name"
 */
export const translate = (localeKey: string, opt: Record<string, any> = {}) => {
  if (!localeKey) {
    console.error(`[i18n] Function translate(), localeKey param is required`)
    return ''
  }
  const locale = uni.getLocale()
  const message = messages[locale]
  if (Object.keys(message).includes(localeKey)) {
    const template = message[localeKey]
    // 使用 Object.keys 遍历 params 对象,替换模板中的大括号占位符
    return Object.keys(opt).reduce(
      (acc, key) => acc.replace(new RegExp(`{${key}}`, 'g'), opt[key]),
      template
    )
  }
  return localeKey // 转换不了则原样输出
}

然后在main.ts里把它挂载到全局

TypeScript 复制代码
import { message, alert, confirm, translate } from '@/utils'

...

export function createApp() {
  const app = createSSRApp(App)

...

  app.use(i18n)

  app.config.globalProperties.$t = translate // 覆盖不能正常工作的$t函数
  // #ifdef MP-WEIXIN
  // 由于微信小程序的运行机制问题,需声明如下一行,H5和APP非必填
  app.config.globalProperties._i18n = i18n
  // #endif

  return {
    app
  }
}

至于在任意位置使用,让我们把translate挂载到代码部分的全局

TypeScript 复制代码
// 安装到全局,覆盖不能正常工作的$t
;(function (global) {
  console.log('install')
  ;(global as any).$t = translate
})(this || window || globalThis)

下面是最终完成的i18n.ts模块,添加了语言切换功能的导出。

API.tools.locale.request是后端的语言切换代码,实现前后端语言统一切换,目前只导入了3种语言,需要其它语言可以自行增加

TypeScript 复制代码
/**
 * ccframe i18n模块
 * 注意:由于某种未知的原因,uni-best的$t()翻译方法有无法切换语音以及安卓出错的问题,因此使用导出的translate方法进行动态翻译
 * @Jim 24/09/20
 */

import { createI18n } from 'vue-i18n'

import en from '@/datas/en.json'
import zhHans from '@/datas/zh-Hans.json'
import zhHant from '@/datas/zh-Hant.json'

const messages = {
  en,
  'zh-Hant': zhHant,
  'zh-Hans': zhHans // key 不能乱写,查看截图 screenshots/i18n.png
}

const i18n = createI18n({
  legacy: false, // 解决空白报错问题
  locale: uni.getLocale(), // 获取已设置的语言,fallback 语言需要再 manifest.config.ts 中设置
  messages
})

type LocaleType = 'en' | 'zh-Hant' | 'zh-Hans'

i18n.global.locale.value = import.meta.env.VITE_FALLBACK_LOCALE

/**
 * 任意文件使用$t翻译方法,需要在app里全局导入
 * @param { string } localeKey 多语言的key,eg: "app.name"
 */
export const translate = (localeKey: string, opt: Record<string, any> = {}) => {
  if (!localeKey) {
    console.error(`[i18n] Function translate(), localeKey param is required`)
    return ''
  }
  const locale = uni.getLocale()
  const message = messages[locale]
  if (Object.keys(message).includes(localeKey)) {
    const template = message[localeKey]
    // 使用 Object.keys 遍历 params 对象,替换模板中的大括号占位符
    return Object.keys(opt).reduce(
      (acc, key) => acc.replace(new RegExp(`{${key}}`, 'g'), opt[key]),
      template
    )
  }
  return localeKey // 转换不了则原样输出
}

const langMapper: Record<string, string> = {
  'zh-Hans': 'zh-CN',
  'zh-Hant': 'zh-TW',
  en: 'en-US'
}

export const setLocale = async (locale: LocaleType) => {
  await API.tools.locale.request({ lang: langMapper[locale] })
  // #ifdef APP-PLUS
  setTimeout(() => {
    // 如果是APP,需要等待重新启动页面
    i18n.global.locale.value = locale
    uni.setLocale(locale)
  }, 300)
  // #endif
  // #ifndef APP-PLUS
  i18n.global.locale.value = locale
  uni.setLocale(locale)
  // #endif

  // currentLang.value = locale
}

// 安装到全局,覆盖不能正常工作的$t
;(function (global) {
  console.log('install')
  ;(global as any).$t = translate
})(this || window || globalThis)

export default i18n

/** 非vue 文件使用 i18n
export const testI18n = () => {
  console.log(t('app.name'))
  // 下面同样生效
  uni.showModal({
    title: 'i18n 测试',
    content: t('app.name')
  })
} */

然后就可以简单愉快的使用多语言功能了。

页面上$t('login.enterPhone')根据语言显示"输入手机号码":

<up-input

fontSize="32rpx"

:placeholder="$t('login.enterPhone')"

border="surround"

v-model="data.userMobile"

style="letter-spacing: 2rpx"

@change="data.mobileErr = ''"

type="number"

maxlength="11"

/>

代码片段,这个是form表单验证公共库里的使用:

required(error?: string): Validator {

this.push({

required: true,

validator: (rule: any, val: any, callback: (error?: Error) => void) => {

// 补充验证模式

return val !== undefined && val !== null && val !== ''

},

message:

error ??

(this.labelText

? $t('utils.validator.required') + this.labelText

: $t('utils.validator.notEmpty')),

trigger: ['change', 'blur']

})

return this

}

$t('utils.validator.required')根据语言输出:请输入

$t('utils.validator.notEmpty')根据语言输出:内容不能为空

完善Typescript类型定义

这样使用起来,还有那么一点不舒服,就是在script中使用$t时,会报错类型找不到红红的一片(实际编译没问题)。对于代码强迫症人会有点一点受不了,那么让这个错误的爆红消失掉:

unibest里原本带了i18n.d.ts文件,把我们挂载到script全局的定义添加进去:

TypeScript 复制代码
/* eslint-disable no-unused-vars */
export {}

declare module 'vue' {
  interface ComponentCustomProperties {
    $t: (key: string, opt?: Record<string, any>) => string
    // $tm: (key: string, opt?: Record<string, any>) => [] | { [p: string]: any }
  }
}

declare global {
  function $t(localeKey: string, opt?: Record<string, any>): string
}

刷新一下vscode,不爆红了,完美~

--------------------------------------- [ 2024/9/21 分界线] ---------------------------------------------

UNIAPP 安卓/IOS模式不用重启界面直接切换语言

经过前面的折腾,发现可以直接绕过uni.getLocale和uni.setLocale来实现语言切换了。这样等于就是解决了uniapp官方框架下必须重启APP才能更换语言的软肋

导出切换语言功能:

setStorageSync是我将语言保存到存储,以便系统启动时按照指定的语言启动

TypeScript 复制代码
export const getLocale = () => i18n.global.locale.value

export const setLocale = async (locale: LocaleType) => {
  await API.tools.locale.request({ lang: langMapper[locale] })
  uni.setStorageSync(Global.STOAGE_LOCALE, locale)
  i18n.global.locale.value = locale
}

调用:

TypeScript 复制代码
const switchLang = async () => {
  // 切换应用语言到 {langName} 吗?应用将重启以更改语言(新技术不用重启)
  if (utils.getLocale() !== 'zh-Hans') {
    await utils.setLocale('zh-Hans')
  } else {
    await utils.setLocale('en')
  }
}

这个方法虽然可以直接切换,但是有个条件,就是官方的组件无法依赖默认的。好在大部分组件都提供了默认占位作为参数可以传入,因此像对话框这类基本组件只需要使用时添加多几个国际化参数即可

效果:

相关推荐
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
Random_index4 小时前
#Uniapp篇:支持纯血鸿蒙&发布&适配&UIUI
uni-app·harmonyos
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
ZwaterZ9 小时前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
周三有雨9 小时前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
ZwaterZ11 小时前
el-table-column自动生成序号&&在序号前插入图标
前端·javascript·c#·vue
初遇你时动了情12 小时前
uniapp 城市选择插件
开发语言·javascript·uni-app
木子七13 小时前
vue2-vuex
前端·vue
小小黑00715 小时前
uniapp+vue3+ts H5端使用Quill富文本插件以及解决上传图片反显的问题
uni-app·vue