2020-03-20:vue-i18n

  • 基础
// 结构
{
  "en": {  // 'en' Locale
    "key1": "this is message1", // 基本的 <p>{{ $t('key1') }}</p>
    "nested": { // 嵌套 <p>{{ $t('nested.message1') }}</p>
      "message1": "this is nested message1" 
    },
    "errors": [ // 数组 <p>{{ $t('errors[0]') }}</p>
      "this is 0 error code message",
      {  // 数组嵌套对象 <p>{{ $t('errors[1].internal1') }}</p>
        "internal1": "this is internal 1 error message"
      },
      [  // 数组嵌套数组 <p>{{ $t('errors[2][0]') }}</p>
        "this is nested array error 1"
      ]
    ]
  }
}

// 基于组件的本地化
const Component1 = {
  template: `
    <div class="container">
     <p>Component1 locale messages: {{ $t("message.hello") }}</p>
   </div>`,
  i18n: { // `i18n` 选项,为组件设置语言环境信息
    messages: {
      en: { message: { hello: 'hello component1' } },
    }
  }
}

// 组件插值
const messages = {
  en: {
    term: 'I accept xxx {0}.'
  }
}
<i18n path="term" tag="a" url="/123">
  <a :href="url" target="_blank">插入到{0}中的a标签</a>
</i18n>
// 具名格式
const messages = {
  en: {
    info: 'You can {action} until {limit} minutes from departure.'
  }
}
<i18n path="info" tag="p" :places="{ action:'插入到{action}中的字符串' }">
  <span place="limit">插入到{limit}中</span>
</i18n>


// 具名格式
hello: '{msg} world'
<p>{{ $t('hello', { msg: 'hello' }) }}</p>

// 列表格式
hello: '{0} world'
<p>{{ $t('hello', ['hello']) }}</p>
<p>{{ $t('hello', {'0': 'hello'}) }}</p>

// HTML 格式化
hello: 'hello <br> world'
<p v-html="$t('hello')"></p>

// 复数($tc)
const messages = {
  en: {
    car: 'car | cars',
    apple: 'no apples | one apple | {count} apples'
  }
}
<p>{{ $tc('car', 1) }}</p> // <p>car</p>
<p>{{ $tc('car', 2) }}</p> // <p>cars</p>
<p>{{ $tc('apple', 0) }}</p> // <p>no apples</p>
<p>{{ $tc('apple', 1) }}</p> // <p>one apple</p>
<p>{{ $tc('apple', 10, { count: 10 }) }}</p> // <p>10 apples</p>
<p>{{ $tc('apple', 10) }}</p>

// 日期时间本地化($d),参数参考 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
const dateTimeFormats = {
  'en-US': {
    short: {
      year: 'numeric', month: 'short', day: 'numeric'
    }
  }
}
const i18n = new VueI18n({
  dateTimeFormats
})
<div >
  <p>{{ $d(new Date(), 'short') }}</p>
  <p>{{ $d(new Date(), 'long', 'ja-JP') }}</p>
</div>

// 数字本地化($n),参数请参考 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
const numberFormats = {
  'en-US': {
    currency: { 
      style: 'currency', // 货币格式
      currency: 'USD' // 在货币格式化中使用的货币符号
    }
  }
}
const i18n = new VueI18n({
  numberFormats
})
<div >
  <p>{{ $n(100, 'currency') }}</p>
  <p>{{ $n(100, 'currency', 'ja-JP') }}</p>
</div>

// 链接到另一个翻译关键字(@:(...))
const messages = {
  en: {
    message: {
      dio: 'DIO',
      linked: 'There\'s a reason, you lost, @:(message.dio).'
    }
  }
}
  • 自定义指令本地化
    • 当 v-t 指令应用于 <transition> 组件内的元素时,你可能会注意到过渡动画之后的翻译过的信息会消失。这与 <transition> 组件实现的方式有关——在过渡开始之前 ,<transition> 组件内消失元素中的所有指令都将被销毁。此行为可能导致内容在短过渡时闪烁,但在长过渡时最明显。为了确保在转换期间指令内容不会被触及,只需将.preserve 修饰符添加到 v-t 指令定义中。
    • 也可以在 VueI18n 实例本身设置全局设置,这将对没有修饰符的所有 v-t 指令产生影响。preserveDirectiveContent: true
new Vue({
  i18n: new VueI18n({
    locale: 'en',
    messages: {
      en: { hello: 'hi {name} there!' },
      ja: { hello: 'こんにちは!' }
    }
  })
}).$mount('#string-syntax')
// 字符串语法
<p v-t="'hello'"></p>
// 对象语法
<p v-t="{ path: 'hello', locale: 'ja', args: { name: 'kazupon' } }"></p>
  • 自定义翻译(略,搜索:自定义格式)
  • 自定义复数翻译(略,搜索:自定义复数)
  • 回退本地化
    • 回退是指当前语言没有对应的翻译,使用备选语言的翻译,设置 silentFallbackWarn:true 避免回退警告
  • 回退插值
    • 指$t()传入的字符串作为路径找不到对应翻译时已该字符串作为翻译,设置了formatFallbackMessages后会先查找fallbackLocale设置的备用语言得翻译,如果没有才会以$t传入的字符串作为模板
const i18n = new VueI18n({
  locale: 'ru',
  fallbackLocale: 'en',
  formatFallbackMessages: true,
  messages
})
<p>{{ $t('The weather today is {condition}!', { condition: 'sunny' }) }}</p>
  • 当前组件中的i18n选项缺少的项(messages中的),会自动回退到根实例中的选项(new VueI18n传入的参数)。设置 silentFallbackWarn:true 关闭警告
  • 如果你希望在组件语言环境中进行本地化,可以在 i18n 选项中用 sync: false 和 locale (为当前组件定义特殊语言)
  • 函数式组件不支持 i18n 这个选项,可以在 parent 组件中定义供函数式组件使用parent.$t
  • 更改 VueI18n 实例的 locale 属性的值,动态更改语言环境。每个组件都包含一个引用为 $i18n 属性的 VueI18n 实例,该实例也可用于更改语言环境
const i18n = new VueI18n({
  locale: 'ja', // 设置语言环境
  ...
})
i18n.locale = 'en'
  • 可以使用具有多个 i18n 自定义块的语言环境信息
<i18n src="./common/locales.json"></i18n>
<i18n>
  {
    "en": {
      "hello": "hello world!"
    }
  }
</i18n>
  • 热重载,webpack热更新的一种优化,应该是能实现直接替换messages而不造成文件级别的替换

————————————————————————————————————————————————————————————————————————

http://kazupon.github.io/vue-i18n/zh/introduction.html

开始

安装

兼容性说明

直接下载 / CDN

NPM

npm install vue-i18n

Yarn

  • 在一个模块系统中使用它,你必须通过 Vue.use() 明确地安装 vue-i18n
import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

Vue Cli 3.x

  • 注释:在@vue/cli创建的项目中添加i18n插件,vue需要全局安装,未全局安装的话可以通过vue ui来进行插件安装
  • 注释:安装的插件会生成相应的文件和配置文件
vue add i18n

开发版构建

格式化

具名格式

const messages = {
  en: {
    message: {
      hello: '{msg} world'
    }
  }
}
<p>{{ $t('message.hello', { msg: 'hello' }) }}</p>

列表格式

const messages = {
  en: {
    message: {
      hello: '{0} world'
    }
  }
}
<p>{{ $t('message.hello', ['hello']) }}</p>
// 列表格式也接受类似数组的对象:
<p>{{ $t('message.hello', {'0': 'hello'}) }}</p>

HTML 格式化

const messages = {
  en: {
    message: {
      hello: 'hello <br> world'
    }
  }
}
<p v-html="$t('message.hello')"></p>

支持 ruby on rails 的 i18n 格式

  • 注释:ruby on rails 一个使用 Ruby 语言写的开源 Web 应用框架
const messages = {
  en: {
    message: {
      hello: '%{msg} world'
    }
  }
}
<p>{{ $t('message.hello', { msg: 'hello' }) }}</p>

自定义格式

// 实现自定义格式
class CustomFormatter {
     // 注释:options 为实例化时传入的参数
     constructor (options) {
       // ...
     }

     //
     // 插值
     // 注释:以下为 interpolate 函数接收到的两个参数的说明
     // @param {string} 信息
     //   列表或具名格式的字符串。
     //   例如:
     //   - 具名格式:'Hi {name}'
     //   - 列表格式:'Hi {0}'
     //
     // @param {Object | Array} 值
     //   `message` 插值的值
     //   使用 `$t`, `$tc` 和 `i18n` 函数式组件传递值。
     //   e.g.
     //   - $t('hello', { name: 'kazupon' }) -> 传递值:Object `{ name: 'kazupon' }`
     //   - $t('hello', ['kazupon']) -> 传递值:Array `['kazupon']`
     //   - `i18n` 函数式组件 (组件插值)
     //     <i18n path="hello">
     //       <p>kazupon</p>
     //       <p>how are you?</p>
     //     </i18n>
     //     -> 传递值:Array (included VNode):
     //        `[VNode{ tag: 'p', text: 'kazupon', ...}, VNode{ tag: 'p', text: 'how are you?', ...}]`
     //
     // @return {Array<any>}
     //   插值,你需要返回以下内容:
     //   - 当使用 `$t` 或 `$tc` 数组中应该是字符串。
     //   - 当使用 `i18n` 函数式组件时 数组中应包含 VNode 对象。
     //
     interpolate (message, values) {
       // 在这里实现插值逻辑
       // ...

       // 返回插值数组
       return ['resolved message string']
     }
}

// 注册 `formatter` 选项
const i18n = new VueI18n({
  locale: 'en-US',
  formatter: new CustomFormatter(/* 这里是构造函数选项 */),
  messages: {
    'en-US': {
      // ...
    },
    // ...
  }
})

// 启动!
new Vue({ i18n }).$mount('#app')

复数

  • 注释:这里使用了$tc函数
const messages = {
  en: {
    car: 'car | cars',
    apple: 'no apples | one apple | {count} apples'
  }
}

<p>{{ $tc('car', 1) }}</p> // <p>car</p>
<p>{{ $tc('car', 2) }}</p> // <p>cars</p>
<p>{{ $tc('apple', 0) }}</p> // <p>no apples</p>
<p>{{ $tc('apple', 1) }}</p> // <p>one apple</p>
<p>{{ $tc('apple', 10, { count: 10 }) }}</p> // <p>10 apples</p>
// 注释:等价于,应该是自动作为参数,一种便捷的绑定方式
// 注释:参数2为字符串时会被判定为 0
<p>{{ $tc('apple', 10) }}</p>
  • 注释:参数2传入VueI18n.prototype.getChoiceIndex方法进行判断
  • 注释:默认规律为
    • choicesLength === 2 且 choice <= 1 时才返回 0 否则返回 1
    • 其他:当 choice > 2 时返回 2,其他时候按照 choice 返回
VueI18n.prototype.getChoiceIndex = function getChoiceIndex (choice, choicesLength) {
  // Default (old) getChoiceIndex implementation - english-compatible
  var defaultImpl = function (_choice, _choicesLength) {
    _choice = Math.abs(_choice);

    if (_choicesLength === 2) {
      return _choice
        ? _choice > 1
          ? 1
          : 0
        : 1
    }

    return _choice ? Math.min(_choice, 2) : 0
  };

  if (this.locale in this.pluralizationRules) {
    return this.pluralizationRules[this.locale].apply(this, [choice, choicesLength])
  } else {
    return defaultImpl(choice, choicesLength)
  }
};

通过预定义的参数访问该数字

自定义复数

  • 为了实现不同规则,你可以覆盖 VueI18n.prototype.getChoiceIndex 函数(斯拉夫语言具有不同的复数规则)
/**
 * @param choice {number} 由 $tc 输入的选择索引:`$tc('path.to.rule', choiceIndex)`
 * @param choicesLength {number} 总体可用选择 //注释:message中 | 分割的长度
 * @returns 选择复数单词的最终选择索引
**/
VueI18n.prototype.getChoiceIndex = function (choice, choicesLength) {
  // this === VueI18n 实例,所有语言环境属性也存在于此处
  if (this.locale !== 'ru') {
    // 继续执行默认实现
  }

  if (choice === 0) {
    return 0;
  }

  const teen = choice > 10 && choice < 20;
  const endsWithOne = choice % 10 === 1;

  if (!teen && endsWithOne) {
    return 1;
  }

  if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
    return 2;
  }

  return (choicesLength < 4) ? 2 : 3;
}

日期时间本地化

const dateTimeFormats = {
  'en-US': {
    short: {
      year: 'numeric', month: 'short', day: 'numeric'
    },
    long: {
      year: 'numeric', month: 'short', day: 'numeric',
      weekday: 'short', hour: 'numeric', minute: 'numeric'
    }
  },
  'ja-JP': {
    short: {
      year: 'numeric', month: 'short', day: 'numeric'
    },
    long: {
      year: 'numeric', month: 'short', day: 'numeric',
      weekday: 'short', hour: 'numeric', minute: 'numeric', hour12: true
    }
  }
}
const i18n = new VueI18n({
  dateTimeFormats
})

<div >
  <p>{{ $d(new Date(), 'short') }}</p>
  <p>{{ $d(new Date(), 'long', 'ja-JP') }}</p>
</div>

数字本地化

const numberFormats = {
  'en-US': {
    currency: { 
      style: 'currency', // 货币格式
      currency: 'USD' // 在货币格式化中使用的货币符号
    }
  },
  'ja-JP': {
    currency: {
      style: 'currency', currency: 'JPY', currencyDisplay: 'symbol'
    }
  }
}
const i18n = new VueI18n({
  numberFormats
})

<div >
  <p>{{ $n(100, 'currency') }}</p>
  <p>{{ $n(100, 'currency', 'ja-JP') }}</p>
</div>

语言环境信息的语法

结构

{
  "en": {  // 'en' Locale
    "key1": "this is message1", // 基本的
    "nested": { // 嵌套
      "message1": "this is nested message1"
    },
    "errors": [ // 数组
      "this is 0 error code message",
      {  // 数组嵌套对象
        "internal1": "this is internal 1 error message"
      },
      [  // 数组嵌套数组
        "this is nested array error 1"
      ]
    ]
  },
  "ja": { // 'ja' Locale
    // ...
  }
}

<div >
  <!-- 基本的 -->
  <p>{{ $t('key1') }}</p>
  <!-- 嵌套 -->
  <p>{{ $t('nested.message1') }}</p>
  <!-- 数组 -->
  <p>{{ $t('errors[0]') }}</p>
  <!-- 数组嵌套对象 -->
  <p>{{ $t('errors[1].internal1') }}</p>
  <!-- 数组嵌套数组 -->
  <p>{{ $t('errors[2][0]') }}</p>
</div>

Linked locale messages

  • 如果有一个翻译关键字总是与另一个具有相同的具体文本,你可以链接到它。要链接到另一个翻译关键字,你所要做的就是在其内容前加上一个 @: 符号后跟完整的翻译键名,包括你要链接到的命名空间。
  • 注释:用来拼接多个翻译关键字,不需要结束标志,直接跟字符串会自动作为字符串添加到变量后
const messages = {
  en: {
    message: {
      the_world: 'the world',
      dio: 'DIO:',
      linked: '@:message.dio @:message.the_world我是文本'
    }
  }
}
<p>{{ $t('message.linked') }}</p> // <p>DIO: the world我是文本</p>

按括号分组

  • 如果链接 @:message.something 后紧跟着一个点 . 会被认为成了链接的一部分。这时链接到另一段翻译的键名应该在括号 () 里
const messages = {
  en: {
    message: {
      dio: 'DIO',
      linked: 'There\'s a reason, you lost, @:(message.dio).'
    }
  }
}

回退本地化

  • 默认情况下回退到 fallbackLocale 会产生两个控制台警告
    • 注释:回退是指当前语言没有对应的翻译,使用备选语言的翻译
  • 为了避免这些警告 (同时保留那些完全没有翻译给定关键字的警告),需初始化 VueI18n 实例时设置 silentFallbackWarn:true

回退插值

  • 如果在message中找不到相应的键值将回退到原本的语言
  • 注释:原本的语言是指$t()传入的字符串回退作为翻译的值
  • 为了实现此功能,可以通过设置formatFallbackMessages为true
  • 注意: fallbackRoot的优先级高于formatFallbackMessages
  • 注释:回退本地化优先于回退插值,即设置了formatFallbackMessages后会先查找fallbackLocale设置的备用语言得翻译,如果没有才会以$t传入的字符串作为模板
const messages = {
  ru: {
    'Hello {name}': 'Здравствуйте {name}'
  }
}
const i18n = new VueI18n({
  locale: 'ru',
  fallbackLocale: 'en',
  formatFallbackMessages: true,
  messages
})

<p>{{ $t('Hello {name}', { name: 'John' }}) }}</p>
<p>{{ $t('The weather today is {condition}!', { condition: 'sunny' }) }}</p>

基于组件的本地化

  • 注释:当前组件中的i18n选项缺少的项(包括messages中的,也包括locale等),会自动回退到根实例中的选项(new VueI18n传入的参数)
  • 回退到根语言环境会在控制台中生成两个警告,为避免以上警告 (同时保留那些完全没有翻译给定关键字的警告) 需初始化 VueI18n 实例时设置 silentFallbackWarn:true(同回退本地化)
  • 如果你希望在组件语言环境中进行本地化,可以在 i18n 选项中用 sync: false 和 locale
const Component1 = {
  template: `
    <div class="container">
     <p>Component1 locale messages: {{ $t("message.hello") }}</p>
     <p>Fallback global locale messages: {{ $t("message.greeting") }}</p>
   </div>`,
  i18n: { // `i18n` 选项,为组件设置语言环境信息
    messages: {
      en: { message: { hello: 'hello component1' } },
      ja: { message: { hello: 'こんにちは、component1' } }
    }
  }
}

函数式组件的翻译

  • 在函数式组件上使用 vue-i18n 时,你必须将 $t 称为 parent.$t
  • 注释:函数式组件也不支持 i18n 这个选项,可以在 parent 组件中定义供函数式组件使用
{
  functional: true,
  render(h, context) {
    return <div>{context.parent.$t("czb")}</div>;
  }
}

自定义指令本地化

  • 7.3 新增
  • 不仅可以使用 $t 方法进行翻译,还可以使用 v-t 自定义指令

字符串语法

  • 注释:注意v-t绑定的是字符串,依然可以使用基于组件的本地化
new Vue({
  i18n: new VueI18n({
    locale: 'en',
    messages: {
      en: { hello: 'hi there!' },
      ja: { hello: 'こんにちは!' }
    }
  })
}).$mount('#string-syntax')

<div >
  <!-- 字符串 -->
  <p v-t="'hello'"></p>
</div>

对象语法

<p v-t="{ path: 'hello', locale: 'ja', args: { name: 'kazupon' } }"></p>
<p v-t="{ path: 'hello', args: { name: nickName } }"></p>

使用翻译

  • 8.7 新增
  • 当 v-t 指令应用于 <transition> 组件内的元素时,你可能会注意到过渡动画之后的翻译过的信息会消失。这与 <transition> 组件实现的方式有关——在过渡开始之前 ,<transition> 组件内消失元素中的所有指令都将被销毁。此行为可能导致内容在短过渡时闪烁,但在长过渡时最明显。为了确保在转换期间指令内容不会被触及,只需将.preserve 修饰符添加到 v-t 指令定义中。
  • 疑问:指令销毁是否指改变创建当前实例的参数?
  • 也可以在 VueI18n 实例本身设置全局设置,这将对没有修饰符的所有 v-t 指令产生影响。
new Vue({
  i18n: new VueI18n({
    locale: 'en',
    messages: {
      en: { preserve: 'with preserve' },
    },
    preserveDirectiveContent: true
  })
}).$mount('#in-transitions')

$t vs v-t

  • $t 在每次重新渲染时都会被执行,因此它确实有翻译成本。
  • v-t 比 $t 方法具有更好的性能,因为在一次翻译时自定义指令会进行缓存。
  • 疑问:自定义指令进行缓存如何实现的?通过判断传入参数是否改变来决定是否修改对应的vnode对象?
  • 此外可以使用由 vue-i18n-extensions 提供的 Vue 编译器模块进行预翻译。
  • 注释:vue-i18n-extensions用来实现服务端渲染,参考https://github.com/intlify/vue-i18n-extensions
  • 注释:i18n下的message整个对象都会被打包到,不会根据当前语言舍弃不需要语言块
  • 带有 v-t 的翻译内容会被插入到元素的 textContent 中(textContent是原生dom的一个属性,指向它的文本内容)
  • 当你使用服务器渲染时,你需要设置自定义指令到 createRenderer 函数的 directives 选项
  • 疑问:v-t已经被注册到vue中,为什么服务端渲染要设置自定义指令呢?

组件插值

基本用法

  • 7.0 新增
  • 注释:类似vue的插槽,path翻译的key,tag当前i18n标签渲染的使用的dom名,for 绑定到该dom上的其他 attribute
<p>I accept xxx <a href="/term">Terms of Service Agreement</a></p>

const messages = {
  en: {
    tos: 'Term of Service',
    term: 'I accept xxx {0}.'
  },
  ja: {
    tos: '利用規約',
    term: '私は xxx の{0}に同意します。'
  }
}
// 把 a 标签插入term的{0}插槽中
<div >
  <!-- ... -->
  <i18n path="term" tag="label" for="tos">
    <a :href="url" target="_blank">{{ $t('tos') }}</a>
  </i18n>
  <!-- ... -->
</div>

高级用法

  • 7.2 新增
  • 在 i18n 组件中,仅包含空格的文本内容将被省略
  • 在 place 特性的帮助下支持具名格式
  • 注释:存在i18n组件实例
<div >
  <!-- ... -->
  <i18n path="info" tag="p">
    <span place="limit">{{ changeLimit }}</span>
    <a place="action" :href="changeUrl">{{ $t('change') }}</a>
  </i18n>
  <!-- ... -->
</div>

const messages = {
  en: {
    info: 'You can {action} until {limit} minutes from departure.',
    change: 'change your flight'
  }
}
  • i18n 组件的所有子项都必须设置 place 属性。否则它将回退到列表格式
  • 注释:列表格式会自动舍去插入元素多余的部分,插入并不存在的位置时该插入元素也会被舍弃
  • 注释:不存在插入位都会被渲染为空字符串,当被回退为列表格式时也一样
  • 如果你仍想在命名格式中插入文本内容,可以在 i18n 组件上定义 places 属性。
<div >
  <!-- ... -->
  <i18n path="info" tag="p" :places="{ limit: refundLimit }">
    <a place="action" :href="refundUrl">{{ $t('refund') }}</a>
  </i18n>
  <!-- ... -->
</div>
{
  data(){
    return {
      refundLimit:"refundLimitText",
      refundUrl:"/refundUrl"
    }
  },
  i18n:{
    messages:{
      en: {
        info: 'You can {action} until {limit} minutes from departure.',
        refund: 'refund the ticket'
      }
    }
  }
}

单文件组件

基本用法

安装 vue-i18n-loader

  • 为了使用 <i18n> 自定义块,你需要安装 vue-loader 和 vue-i18n-loader。如果你使用了单文件组件,vue-loader 很可能已在项目中使用了,那么 vue-i18n-loader 必须另外安装
npm i --save-dev @kazupon/vue-i18n-loader

Webpack

  • 需要对 Webpack 进行以下配置,对于 vue-loader v15 或更高版本
  • 疑问:对模块进行处理时rules是顺序执行的吗?
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        resourceQuery: /blockType=i18n/,
        type: 'javascript/auto',
        loader: '@kazupon/vue-i18n-loader'
      }
      // ...
    ]
  },
  // ...
}
  • 对于 vue-loader v14
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            // 你需要指定 `i18n` 的值为 `vue-i18n-loader`
            i18n: '@kazupon/vue-i18n-loader'
          }
        }
      },
      // ...
    ]
  },
  // ...
}

Vue CLI 3.0

  • Vue CLI 3.0 隐藏了 webpack 配置,因此须在项目的根目录下创建一个 vue.config.js
  • 对于 vue-loader v15 或更高版本
module.exports = {
  chainWebpack: config => {
    config.module
      .rule("i18n")
      .resourceQuery(/blockType=i18n/)
      .type('javascript/auto')
      .use("i18n")
        .loader("@kazupon/vue-i18n-loader")
        .end();
  }
}
  • 对于 vue-loader v14
  • 注释:需要 deepmerge 库用于对象合并
const merge = require('deepmerge')

module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options =>
        merge(options, {
          loaders: {
            i18n: '@kazupon/vue-i18n-loader'
          }
        })
      )
  }
}

Laravel-Mix(略)

  • 注释:位于webpack顶层的一个简洁的配置层

加载 YAML

  • i18n 自定义块需要指定为 JSON 格式,你也可以通过使用 vue-loader 预加载器功能来使用 YAML 格式。
  • 以下是 YAML 格式的 i18n 自定义块:
<i18n>
en:
  hello: "hello world!"
ja:
  hello: "こんにちは、世界!"
</i18n>
  • Webpack 配置如下(略)

多个自定义块

  • 可以使用具有多个 i18n 自定义块的语言环境信息。
  • 这些语言环境信息将合并为组件的语言环境信息
<i18n src="./common/locales.json"></i18n>
<i18n>
  {
    "en": {
      "hello": "hello world!"
    },
    "ja": {
      "hello": "こんにちは、世界!"
    }
  }
</i18n>

Scoped 风格

  • 当使用带有 scoped style vue-i18n 时,重要的是要记住使用深度选择器 来设置嵌套转换的样式。
  • 疑问:并不存在这样的限制,这里的文档好像有问题
...
<template>
  <div class="parent">
    <p>message: {{ $t('hello') }}</p>
  </div>
</template>
...
<!-- 不可行-->
<style scoped>
.parent p {
  color: #42b883;
}
</style>

<!-- 可行 -->
<style>
.parent >>> p {
  color: #42b883;
}
</style>

函数式组件中的自定义块

  • 以下代码无法使用 i18n 自定义块的语言环境信息进行本地化
<i18n>
{
  "en": {
    "hello": "hello world"
  },
  "ja": {
    "hello": "こんにちは、世界"
  }
}
</i18n>

<template functional>
  <!-- 'hello' 的父实例的语言环境信息 -->
  <p>{{ parent.$t('hello') }}</p>
</template>

热重载

  • 可以监视本地化文件中的更改,并将更改热重载到应用程序中
  • 疑问:webpack 属于静态打包,热更新本身就会观察所有加载的模块,使用这种方式是为了提高效率吗?防止文件重新打包,而只是通过setLocaleMessage替换数据?
// 语言环境信息
const messages = {
  // ...
}

// VueI18n 实例
const i18n = new Vuei18n({
  locale: 'en',
  messages
})

// 运行程序
const app = new Vue({
  i18n,
  // ...
}).$mount('#app')

// 热更新
if (module.hot) {
  module.hot.accept(['./en', './ja'], function () {
    i18n.setLocaleMessage('en', require('./en').default)
    i18n.setLocaleMessage('ja', require('./ja').default)
    // 同样可以通过 $i18n 属性进行热更新
    // app.$i18n.setLocaleMessage('en', require('./en').default)
    // app.$i18n.setLocaleMessage('ja', require('./ja').default)
  })
}

语言环境变更

  • 通常,使用 Vue 根实例作为起点,使用 VueI18n 类的 locale 属性作为参考来本地化所有子组件
  • 可以更改 VueI18n 实例的 locale 属性的值,动态更改语言环境
const i18n = new VueI18n({
  locale: 'ja', // 设置语言环境
  ...
})
i18n.locale = 'en'
  • 每个组件都包含一个引用为 $i18n 属性的 VueI18n 实例,该实例也可用于更改语言环境
<template>
  <div class="locale-changer">
    <select v-model="$i18n.locale">
      <option v-for="(lang, i) in langs" :key="`Lang${i}`" :value="lang">{{ lang }}</option>
    </select>
  </div>
</template>

<script>
export default {
  name: 'locale-changer',
  data () {
    return { langs: ['ja', 'en'] }
  }
}
</script>

延迟加载翻译

//i18n-setup.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import messages from '@/lang/en'
import axios from 'axios'

Vue.use(VueI18n)

export const i18n = new VueI18n({
  locale: 'en', // 设置语言环境
  fallbackLocale: 'en',
  messages // 设置语言环境信息
})

const loadedLanguages = ['en'] // 我们的预装默认语言

function setI18nLanguage (lang) {
  i18n.locale = lang
  axios.defaults.headers.common['Accept-Language'] = lang
  document.querySelector('html').setAttribute('lang', lang)
  return lang
}

export function loadLanguageAsync (lang) {
  if (i18n.locale !== lang) {
    if (!loadedLanguages.includes(lang)) {
      return import(/* webpackChunkName: "lang-[request]" */ `@/lang/${lang}`).then(msgs => {
        i18n.setLocaleMessage(lang, msgs.default)
        loadedLanguages.push(lang)
        return setI18nLanguage(lang)
      })
    }
    return Promise.resolve(setI18nLanguage(lang))
  }
  return Promise.resolve(lang)
}

工具

官方工具

Vue Cli 插件

Webpack Loader

ESLint 插件

  • eslint-plugin-vue-i18n 是为 Vue I18n 编写的 ESLint 插件。
  • 疑问:该插件官方页面已经404,是否已经没有存在的意义了?通过vue-cli-plugin-i18n安装的i18n并不支持格式化i18n自定义块,是否有其他解决方案。

Extensions

第三方工具

BabelEdit