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

开始

  • 如果使用模块系统 (例如通过 vue-cli),则需要导入 Vue 和 VueI18n ,然后调用 Vue.use(VueI18n)。

格式化

  • 在某些情况下,你可能希望将翻译呈现为 HTML 信息而不是静态字符串。
    • 在你的网站上动态插入任意 HTML 可能非常危险,因为它很容易导致 XSS 攻击。
const messages = {
  en: {
    message: {
      hello: 'hello <br> world'
    }
  }
}
  • ICU 信息语法
    • 注释:一种用于格式化和解析文本的语法
    • 注释:例如,{gender, select, male {He} female {She} other {They}} will respond shortly. 这个信息可以根据gender的值选择不同的代词,如He will respond shortly.或She will respond shortly.
  • 可以使用实现格式化接口的自定义格式化函数来实现
// 实现自定义格式
class CustomFormatter {
     constructor (options) {
       // ...
     }

     //
     // 插值
     //
     // @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() 而不是 $t()
const messages = {
  en: {
    car: 'car | cars',
    apple: 'no apples | one apple | {count} apples'
  }
}

<p>{{ $tc('car', 1) }}</p>
<p>{{ $tc('car', 2) }}</p>

<p>{{ $tc('apple', 0) }}</p>
<p>{{ $tc('apple', 1) }}</p>
<p>{{ $tc('apple', 10, { count: 10 }) }}</p>
  • 无需明确指定复数的数字。可以通过预定义的命名参数 {count} 和/或 {n} 在语言环境信息中访问该数字。如有必要,你可以覆盖这些预定义的命名参数。
    • 注释:$tc 会自动把第二个参数作为具名参数 count 或 n 传入语言环境信息
    • 注释:$ts 提供了第3个,使用方式跟具名参数一致,优先级高于预定义的参数
const messages = {
  en: {
    apple: 'no apples | one apple | {count} apples',
    banana: 'no bananas | {n} banana | {n} bananas'
  }
}

<p>{{ $tc('banana', 1) }}</p>
<p>{{ $tc('banana', 100, { n: 'too many' }) }}</p>

<p>1 banana</p>
<p>too many bananas</p>
  • 注释:默认规则
    • 如果语言环境信息传入 2 个值,当传入 1 时采用第 1 个值,当传入大于 1 的值时采用第 2 个
    • 如果语言环境信息传入 3 个值,当传入 0 时采用第 1 个值,当传入 1 时采用第 2 个值,其余采用第 3 个值
    • $tc 的第 3 个参数优先级高于第 2 个参数
  • 这种多元化并不适用于所有语言(例如,斯拉夫语言具有不同的多元化规则)
    • 为了实现这些规则,您可以将可选的 pluralizationRules 对象传递给VueI18n 构造函数选项。
new VueI18n({
  // Key - 在这种情况下,用于规则 `'ru'` 的语言
  // Value - 选择正确的复数形式的功能
  pluralizationRules: {
    /**
     * @param choice {number} 输入给$的选择索引 $tc:`$tc('path.to.rule', choiceIndex)`
     * @param choicesLength {number} 可用选择总数
     * @returns 最终选择索引以选择复数单词
     */
    'ru': function(choice, choicesLength) {
      // this === VueI18n 实例,因此本地属性也存在于此

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

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

      if (choicesLength < 4) {
        return (!teen && endsWithOne) ? 1 : 2;
      }
      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: 'long',
      day: 'numeric',
      weekday: 'long',
      hour: 'numeric',
      minute: 'numeric'
    }
  },
  'ja-JP': {
    short: {
      year: 'numeric',
      month: 'short',
      day: 'numeric'
    },
    long: {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      weekday: 'long',
      hour: 'numeric',
      minute: 'numeric',
      hour12: true
    }
  }
}

const i18n = new VueI18n({
  dateTimeFormats
})

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

<div id="app">
  <p>Jan 18, 2021</p>
  <p>2021年1月18日日曜日 午前5:47</p>
</div>
  • 你可以定义具名的 (例如:short、long 等) 日期时间格式,并需要使用 ECMA-402 Intl.DateTimeFormat 的选项
    • 注释:Intl 对象是 ECMAScript 国际化 API 的一个命名空间,它提供了精确的字符串对比、数字格式化,和日期时间格式化。
    • 注释:不同语言对字母的大小写敏感程度不同,所以字符串对比需要国际化,英语中字母A和a是大小写不敏感的,即A == a,但是德语中字母A和a是大小写敏感的,即A != a
    • 注释:不同语言对数字的格式标识不同,法语中数字123456.78用空格作为千分位分隔符,用逗号作为小数点

数字本地化

const numberFormats = {
  'en-US': {
    currency: {
      style: 'currency',
      currency: 'USD'
    }
  },
  'ja-JP': {
    currency: {
      style: 'currency',
      currency: 'JPY',
      currencyDisplay: 'symbol'
    }
  }
}
const i18n = new VueI18n({
  numberFormats
})

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

<div id="app">
  <p>$100.00</p>
  <p>¥100</p>
</div>
  • $n 方法返回的结果字符串带有完全格式化的数字,该数字只能作为整体使用。 在需要格式化格式化数字的某些部分(例如小数位)的情况下,$n 是不够的。 在这种情况下,<i18n-n> 功能组件将有所帮助。
    • 假设需要用较粗的字体呈现数字的整数部分。 这可以通过指定 integer 作用域的插槽元素来实现:
    • 可以通过指定 tag 属性来选择根容器的节点类型。 如果省略,则默认为 'span'。 您也可以将其设置为布尔值 false 以直接插入子节点,而无需创建根元素
<i18n-n :value="1234" :format="{ key: 'currency', currency: 'EUR' }" locale="ja-JP">
  <span v-slot:currency="slotProps" styles="color: green">{{ slotProps.currency }}</span>
  <span v-slot:integer="slotProps" styles="font-weight: bold">{{ slotProps.integer }}</span>
  <span v-slot:group="slotProps" styles="font-weight: bold">{{ slotProps.group }}</span>
  <span v-slot:fraction="slotProps" styles="font-size: small">{{ slotProps.fraction }}</span>
</i18n-n>

语言环境信息的语法

{
  "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
    // ...
  }
}
  • 如果有一个翻译关键字总是与另一个具有相同的具体文本,你可以链接到它。要链接到另一个翻译关键字,你所要做的就是在其内容前加上一个 @: 符号后跟完整的翻译键名,包括你要链接到的命名空间。
const messages = {
  en: {
    message: {
      the_world: 'the world',
      dio: 'DIO:',
      linked: '@:message.dio @:message.the_world !!!!'
    }
  }
}
  • 如果语言区分字符大小写,则可能需要控制链接的语言环境消息的大小写。 链接的消息可以用修饰符 @.modifier:key 格式化。
    • upper: 链接消息中的所有字符均大写
    • lower: 小写链接消息中的所有字符
    • capitalize: 大写链接消息中的第一个字符
const messages = {
  en: {
    message: {
      homeAddress: 'Home address',
      missingHomeAddress: 'Please provide @.lower:message.homeAddress'
    }
  }
}
  • 可以添加修饰符或覆盖将 modifiers 选项传递给 VueI18n 构造函数的现有修饰符。
const i18n = new VueI18n({
  locale: 'en',
  modifiers: {
    snakeCase: (str) => str.split(' ').join('-')
  },
  messages: {
    // ...
  },
})
  • 链接到的语言环境信息的键名也可以形如 @:(message.foo.bar.baz),其中链接到另一段翻译的键名在括号 () 里
    • 如果链接 @:message.something 后紧跟着一个点 .,则此选项非常有用,因为它本不该成为但却成为了链接的一部分
const messages = {
  en: {
    message: {
      dio: 'DIO',
      linked: 'There\'s a reason, you lost, @:(message.dio).'
    }
  }
}
  • 由于复杂的语言语法,您确实需要JavaScript的全部编程功能。 因此,您可以使用 message function 来代替基于字符串的消息。
const messages = {
  en: {
    greeting: (ctx) => `hello, ${ctx.named('name')}!`
  }
}

<p>{{ $t('greeting', { name: 'DIO' }) }}</p>
  • 列表格式的使用类似于上述命名格式
const messages = {
  en: {
    greeting: (ctx) => `hello, ${ctx.list(0)}!`
  }
}

<p>{{ $t('greeting', ['DIO']) }}</p>
  • 在消息功能中,以下基于字符串提供的功能无法通过消息上下文使用:
    • 链接的区域设置消息
    • 复数

回退本地化

  • 如果给出的 locale 包含领土和可选的方言,则隐式回退将自动激活
    • 对于 de-DE-bavarian,以下内容将被视为备用:
    • de-DE-Bavarian
    • de-DE
    • de
    • 要禁止自动回退,请添加后缀感叹号 !,例如 de-DE!
  • 默认情况下回退到 fallbackLocale 会产生两个控制台警告:
    • 为了避免这些警告 (同时保留那些完全没有翻译给定关键字的警告),需初始化 VueI18n 实例时设置 silentFallbackWarn:true
    • 注释:旧版本 silentTranslationWarn: true 用于关闭警告
const i18n = new VueI18n({
  locale: 'ja',
  fallbackLocale: 'en',
  messages
})
  • 由于翻译的键值是字符串,因此也可以作为翻译的值:
    • 为了实现此功能,可以通过设置formatFallbackMessages为true
    • 注释:同样支持具名格式,感觉应该也支持链接
    • fallbackRoot的优先级高于formatFallbackMessages
    • 注释:fallbackRoot 在组件本地化中,当本地化失败时是否回退到根级别 (全局) 本地化。如 locale: ‘en’ 表示使用 messages.en 作为根级别本地化
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>

<p>Здравствуйте John</p>
<p>The weather today is sunny!</p>

基于组件的本地化

  • 面向 Vue 组件的设计,你也可以更方便的分别控制每个组件的语言环境信息
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' } }
    }
  }
}
  • 如果组件没有语言环境信息,它将回退到全局定义的本地化信息。
    • 在默认情况下,回退到根语言环境会在控制台中生成两个警告
    • 为避免以上警告 (同时保留那些完全没有翻译给定关键字的警告) 需初始化 VueI18n 实例时设置 silentFallbackWarn:true
  • 如果你希望在组件语言环境中进行本地化,可以在 i18n 选项中用 sync: false 和 locale
  • 为某些组件导入共享的语言环境消息
    • 如果将 sharedMessages 选项与 messages 选项一起指定,则这些消息将被合并为语言环境消息,并进入目标组件的VueI18n实例
// locales/common.js
export default {
  en: {
    buttons: {
      save: "Save",
      // ...
    }
  },
  ja: {
    buttons: {
      save: "保存",
      // ...
    }
  }
}
import commonMessage from './locales/common' // 导入通用语言环境消息

export default {
  name: 'ServiceModal',
  template: `
    <div class="modal">
      <div class="body">
        <p>This is good service</p>
      </div>
      <div class="footer">
        <button type="button">
          {{ $t('buttons.save') }}
        </button>
      </div>
    </div>
  `,
  i18n: {
    messages: { ... },
    sharedMessages: commonMessages
  }
}
  • 在函数式组件上使用 vue-i18n 时,你必须将 $t 称为 parent.$t

自定义指令本地化

<div id="string-syntax">
  <!-- 字符串 -->
  <p v-t="'hello'"></p>
  <!-- 通过数据进行键名路径绑定 -->
  <p v-t="path"></p>
</div>

<div id="object-syntax">
  <!-- 文字 -->
  <p v-t="{ path: 'hello', locale: 'ja', args: { name: 'kazupon' } }"></p>
  <!-- 通过 `data` 绑定数据 -->
  <p v-t="{ path: path, args: { name: nickName } }"></p>
</div>
  • 在过渡开始之前 ,<transition> 组件内消失元素中的所有指令都将被销毁。此行为可能导致内容在短过渡时闪烁,但在长过渡时最明显。
    • 为了确保在转换期间指令内容不会被触及,只需将.preserve 修饰符添加到 v-t 指令定义中
    • 也可以在 VueI18n 实例本身设置全局设置 preserveDirectiveContent: true,这将对没有修饰符的所有 v-t 指令产生影响
<div id="in-transitions">
  <transition name="fade">
    <span v-if="toggle" v-t.preserve="'preserve'"></span>
  </transition>
  <button @click="toggle = !toggle">Toggle</button>
</div>
// 或者
new Vue({
  i18n: new VueI18n({
    locale: 'en',
    messages: {
      en: { preserve: 'with preserve' },
    },
    preserveDirectiveContent: true
  }),
  data: { toggle: true }
}).$mount('#in-transitions')
  • $t 在每次重新渲染时都会被执行,因此它确实有翻译成本。
  • v-t 比 $t 方法具有更好的性能,因为在一次翻译时自定义指令会进行缓存。此外可以使用由 vue-i18n-extensions 提供的 Vue 编译器模块进行预翻译。
    • 注释:vue-i18n-extensions 是一个为vue-i18n提供扩展功能的插件,它主要有两个特点:服务器端渲染(SSR)支持v-t自定义指令。在构建时将语言包中的文本资源翻译成目标语言,并生成新的语言包文件,从而减少运行时的翻译开销和网络请求。
    • 使用服务器渲染时,你需要设置自定义指令到 createRenderer 函数的 directives 选项。
  • v-t 的翻译内容会被插入到元素的 textContent 中

组件插值

  • 使用了 v-html="$t('term')" 进行本地化而存在被 XSS 攻击的可能性
    • 可以使用 i18n 函数式组件来避免它
    • 组件插值遵循列表格式。i18n 函数式组件的子项按其出现顺序进行插值。
<div id="app">
  <!-- ... -->
  <i18n path="term" tag="label" for="tos">
    <a :href="url" target="_blank">{{ $t('tos') }}</a>
  </i18n>
  <!-- ... -->
</div>

const messages = {
  en: {
    tos: 'Term of Service',
    term: 'I accept xxx {0}.'
  },
}

<div id="app">
  <!-- ... -->
  <label for="tos">
    I accept xxx <a href="/term" target="_blank">Term of Service</a>.
  </label>
  <!-- ... -->
</div>

单文件组件

  • 为了使用 <i18n> 自定义块,你需要安装 vue-i18n-loader
    • 注释:vue-i18n-loader 在打包工具的配置方法
    • 注释:Laravel-Mix 一个用于Laravel应用的webpack封装工具,它可以让你用简洁的语法来定义你的前端资源的编译和压缩步骤
    • i18n 自定义块需要指定为 JSON 格式,你也可以通过使用 vue-loader 预加载器功能来使用 YAML 格式。
<i18n>
{
  "en": {
    "hello": "hello world!"
  },
  "ja": {
    "hello": "こんにちは、世界!"
  }
}
</i18n>
  • 可以使用具有多个 i18n 自定义块的语言环境信息。
    • 些语言环境信息将合并为组件的语言环境信息。
<i18n src="./common/locales.json"></i18n>
<i18n>
  {
    "en": {
      "hello": "hello world!"
    },
    "ja": {
      "hello": "こんにちは、世界!"
    }
  }
</i18n>
  • 当使用带有 scoped style vue-i18n 时,重要的是要记住使用深度选择器 (opens new window)来设置嵌套转换的样式。
<i18n>
{
  "en": {
    "hello": "hello<span>world!</span>"
  },
}
</i18n>

<template>
  <div class="parent">
    <p v-html="$t('hello')"></p>
  </div>
</template>

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

  ::v-deep .parent p span {
    color: red;
  }
</style>
  • 如果单个文件组件具有使用函数式组件的模板,并且你已经定义了 i18n 自定义块,请注意你无法使用语言环境信息进行本地化。
<i18n>
{
  "en": {
    "hello": "hello world"
  },
  "ja": {
    "hello": "こんにちは、世界"
  }
}
</i18n>

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

热重载

  • 注释:webpack 需要手动实现热重载

语言环境变更

  • 动态更改语言环境。在这种情况下,你可以更改 VueI18n 实例的 locale 属性的值
    • 每个组件都包含一个引用为 $i18n 属性的 VueI18n 实例,该实例也可用于更改语言环境。
const i18n = new VueI18n({
  locale: 'ja', // 设置语言环境
  ...
})

// 更改为其它的 locale
i18n.locale = 'en'

延迟加载翻译

//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) {
    return Promise.resolve(setI18nLanguage(lang))
  }

  // 如果语言已经加载
  if (loadedLanguages.includes(lang)) {
    return Promise.resolve(setI18nLanguage(lang))
  }

  // 如果尚未加载语言
  return import(/* webpackChunkName: "lang-[request]" */ `@/i18n/messages/${lang}.js`).then(
    messages => {
      i18n.setLocaleMessage(lang, messages.default)
      loadedLanguages.push(lang)
      return setI18nLanguage(lang)
    }
  )
}

工具

  • vue-cli-plugin-i18n是官方提供的 Vue Cli 插件。
  • eslint-plugin-vue-i18n 是为 Vue I18n 编写的 ESLint 插件。
    • 疑问:官方链接丢失
  • BabelEdit可以翻译 json 文件,也可以翻译单文件组件的 i18n 自定义块。
    • 注释:BabelEdit可以让你在一个友好的界面中管理和修改你的翻译文件,无需手动编辑JSON代码
  • i18n Ally是 VSCode 的 i18n 扩展。
  • vue-i18n-extract对基于 vue-i18n 的 Vue.js 项目执行静态分析,并报告以下信息:
    • 注释:一个工具包,提供了命令来实现以下功能
    • 所有 未使用的 vue-i18n 键的列表(在语言文件中找到但在项目中未使用的条目)
    • 所有 缺失键 的列表(在项目中使用但在语言文件中不存在的条目)
    • 可以在控制台中显示输出或将其写入json文件
    • 丢失的键也可以自动添加到给定的语言文件中

——————————

i18n-ally

  • 可以设置当前文件 key 对应显示的语言
  • 悬浮窗显示 key 对应的所有语言的值、自动翻译、编辑、跳到文件
  • 左侧菜单栏统一管理
    • CURRENT FILE:当前文件使用了哪些 key
    • PROGRESS:整个项目每种语言的完成情况
      • 已翻译、未翻译数量
      • 请求更改?
      • 用户建议?
    • TREE:树形结构的语言信息
    • USAGE REPORT:使用情况报告
    • HELP & FEEDABCK:帮助和反馈
  • 可视化编辑器:针对某个 key 所有语言的编辑
    • 如果你更喜欢将此编辑器用于大部分工作,而不是 VS Code 提供的接口,则可以设置"i18n-ally.editor.preferEditor": true
  • 审阅系统
    • 对某个 key 的反应提出修改请求和建议
    • 审阅注释将存储在 .vscode/i18n-ally-reviews.yml 中
    • 默认情况下,您的姓名和电子邮件是从本地 git 设置中读取的。您可以通过以下方式更改它们"i18n-ally.review.user.name": "Your Name","i18n-ally.review.user.email": "hi@example.com"
    • 如果您希望再有一层控制机器翻译的内容,您可以通过以下方式启用翻译候选系统
      • 使用候译版本,您可以在将译文写入区域设置文件之前查看和编辑译文。
      • "i18n-ally.translate.saveAsCandidates": true,
  • 从代码中提取翻译:选择一段文本直接插入语言文件
  • 在 vscode 控制台的问题 tab 报告缺少的翻译
  • 弹窗提供机器翻译
  • 支持 JSON 和 YAML 文件的注释?
  • 支持多根工作区
  • 支持远程开发?vscode支持远程开发
  • 支持众多流行的框架
  • 支持语言信息中进行链接
posted on 2023-09-12 11:45  噬蛇之牙  阅读(443)  评论(0)    收藏  举报