78

你也许不知道的Vuejs - 最佳实践(3)

 5 years ago
source link: https://yugasun.com/post/you-may-not-know-vuejs-15.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

相信大多数使用 Vue 项目都会面临国际化的问题,而 vue-i18n 便是国际化的不二之选,它用起来非常简单,但是同时也会带来一些问题和挑战。本篇是个人在项目上国际化时一些经验的总结,希望能在国际化的道路上帮到你。

基本使用

vue-i18n 官方文档介绍的很清楚,先在 src/lang 目录下分别创建三个文件 index.jszh.jsen.js

然后在 index.js 中创建 i18n 实例并导出,供 Vue 使用,代码如下:

import Vue from 'vue';
import VueI18n from 'vue-i18n';

import zhMsg from './zh';
import enMsg from './en';

Vue.use(VueI18n);

const messages = {
  zh: {
    ...zhMsg,
  },
  en: {
    ...enMsg,
  },
};

const i18n = new VueI18n({
  locale: 'zh',
  messages,
});

export default i18n;

接着编写语言包文件:

// zh.js
export default {
  index: {
    header: {
      title: 'Vue-I18n 示例',
      subtitle: '你将学会如何使用 vue-i18n',
    },
  },
};

// en.js
export default {
  index: {
    header: {
      title: 'Vue-I18n Demo',
      subtitle: 'You will learn how to use vue-i18n',
    },
  },
};

接下来在入口文件 src/main.js 文件中引入:

// ...
import i18n from './lang';

// ...
new Vue({
  i18n,
  router,
  el: '#app',
  template: '<App/>',
  components: { App },
});

到这里就配置好了,于是我们就可以在组件中使用了,使用方法如下 src/views/index.vue

<h1>{{ $t('index.header.title') }} </h1>
<h3>{{ $t('index.header.subtitle') }} </h3>

看起来是不是很简单,引入 vue-i18n 后,它会在 Vue 实例上挂载它提供的国际化的 api,比如 $t、$tc、$td 等格式化函数。使用时我们只需要在组件中直接调用就行,比如: this.$t

$t 接受两个参数,第一个为我们在语言包中访问的属性路径,各级属性用 . 符号链接,第二个参数可以动态传参,比如我们将 index.header.subtitle 的模板修改为 You will learn how to use {name} ,那么我们可以像下面这样使用:

$t('index.header.subtitle', {name: 'vue-i18n})

小技巧

获取键值集合

相信大家已经知道 vue-i18n 如何使用了,建议通读 官方文档 ,自己动手配置熟悉下。

大多数情况下,我们只需要使用 $t 就可以满足我们的需求了。但是当我们的的语言包层级越来越深时,你会发现属性路径越来越长,而且在某些组件内,需要书写很多遍,比如上面的 index.header.titleindex.header.subtitle ,它们的前缀都是 index.header ,在这里写两遍我们能接受,如果是100遍呢?作为一名懒惰的程序员,怎么能接受同样的事情做3遍呢,更何况是100遍,简直不敢想象!

其实我们可以先获取前缀的对象集合,然后通过这个集合对象来访问,节省臃长的前缀的重复书写,使用的时候我们需要先在 src/views/index.vue 组件的 computed 中定义国际化集合:

export default {
  name: 'Index',
  computed: {
    indexMsg() {
      return this.$t('index.header')
    }
  },
};

接下来修改下模板中的书写方式:

<h1>{{ indexMsg.title }}</h1>
<h3>{{ indexMsg.subtitle }} </h3>

注意:这里必须定义为计算属性,不然在切换语言时,视图国际化将无法自动更新。

多元化的消息

曾经在做一个登录错误信息提示的时候,遇到个需求:用户多次输入错误后,会提示多长时间后重试,但是接口只是返回个剩余秒数,需要根据这个秒数计算出剩余的时分秒,当大于1小时时,提示 请 xx 小时后重试 ,当小于1小时时,提示 请 xx 分 xx 秒后重试 。一般碰到这种需求,我们是肯定需要结合模板变量来实现,最简单的方式就是直接定义两个国际化键值,比如 msg1,msg2 ,然后通过计算出的小时数来做 if 判断就行。

但是当项目越来越庞大,项目中类似的需求越来越多的时候,你会发现你的语言包的键值对越来越多,到了最后,取个属性名就要想半天,不知道大家有没有跟我类似的痛点,所以我对于这种类似需求用 $tc 函数来实现的。

$tc 函数允许我们一个国际化键值可以通过管道符 | 来分割多元化的信息,如下:

export default {
  // ...
  login: {
    tips: '请{minute}分{second}秒后再重试 | 请{hour}小时后再重试',
  },
};

然后在模板中使用:

<p>{{ $tc('login.tips', 1, {minute: 30, second: 29}) }}</p>
<p>{{ $tc('login.tips', 2, {hour: 1}) }}</p>

对于上面的需求,我只需要根据接口返回的秒数,计算下 hour 值,然后写一个三目运算就解决了:

hour >= 1 ? this.$tc('login.tips', 2, {hour: 1}) : this.$tc('login.tips', 1, {minute: 30, second: 29})

可选数组消息

其实对于可选的国际化消息需求,我们还可以通过数组来实现,比如我们的某个订单状态需要国际化, 0-5 分别对应不同的状态,我们只需要定义简单的数组就可以搞定了。

首先定义语言包:

export default {
  //...
  order: {
    status: [
      '待付款',
      '待发货',
      '已发货',
      '已签收',
      '已取消',
    ],
  },
};

然后我们只需要根据不同状态,来读取键值就行:

<span>{{ $t(`order.status[orderStatus]`) }}</span>

<script>
export default {
  name: 'Index',
  data() {
    return {
      orderStatus: 3,
    };
  },
  //...
};
</script>

当然这里也可以通过多元化的方式来实现,大家可以自行尝试下。

模块化

随着项目越来越大,你会发现 src/lang/zh.js 文件越来越臃肿,每次改个国际化文案,需要在上千行的对象中,找半天对应的键值对,而且那深不可测的属性层级,着实让人眼花缭乱。

既然 src/lang/zh.jsjs 文件,我们当然就可以对其进行拆分,然后把不同的模块拆分成独立的 js 文件进行维护,是不是找起来会轻松好多,比如我们尝试将实例中的 zh.js 产分成 src/lang/zh-modules/index.jssrc/lang/zh-modules/login.jssrc/lang/zh-modules/order.js 三个模块文件:

// index.js
export default {
  index: {
    header: {
      title: 'Vue-I18n 示例',
      subtitle: '你将学会如何使用 vue-i18n',
    },
  },
};


// login.js
export default {
  login: {
    tips: '请{minute}分{second}秒后再重试 | 请{hour}小时后再重试',
  },
};

// order.js
export default {
  order: {
    status: [
      '待付款',
      '待发货',
      '已发货',
      '已签收',
      '已取消',
    ],
  },
};

然后在 src/lang/zh.js 文件中一次引入:

import index from './zh-modules/index';
import login from './zh-modules/login';
import order from './zh-modules/order';

export default {
  ...index,
  ...login,
  ...order,
};

这样就变得清晰很多,下次产品需要你修改登录相关的文案,你只需要修改 login.js 模块就行。

但是按模块拆分后,有人就会有疑问了,我这么拆分后,语言模块文件会随着语言种类成倍数增加,应该怎么办?

这里就说说我的解决办法,就是将语言文件按模块合并。所谓 天下大势,合久必分,分久必合 。我们将相同的模块按文件合并起来就可以了,比如 index 模块:

const zh = {
  index: {
    header: {
      title: 'Vue-I18n 示例',
      subtitle: '你将学会如何使用 vue-i18n',
    },
  },
};
const en = {
  index: {
    header: {
      title: 'Vue-I18n Demo',
      subtitle: 'You will learn how to use vue-i18n',
    },
  },
}
export default {
  zh,
  en,
};

然后只需要在 src/lang/index.js 引入,稍作修改就行:

import Vue from 'vue';
import VueI18n from 'vue-i18n';

import indexMsg from './modules/index';
import loginMsg from './modules/login';
import orderMsg from './modules/order';

Vue.use(VueI18n);

const messages = {
  zh: {
    ...indexMsg.zh,
    ...loginMsg.zh,
    ...orderMsg.zh,
  },
  en: {
    ...indexMsg.en,
    ...loginMsg.en,
    ...orderMsg.en,
  },
};

const i18n = new VueI18n({
  locale: 'zh',
  messages,
});

export default i18n;

此时你会发现我们已经不需要 src/lang/zh.jssrc/lang/en.js 文件了。而且这里有个好处是,我们在让翻译帮忙的事后,只需要在同一个文件中对我们的相同键值进行修改和编辑了,是不是方便很多,赶紧动手尝试下吧~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK