Vue 3源码极客之:`Vue`的`i18n`:国际化插件的底层实现与语言包的动态加载。

大家好,我是老码农,今天咱们来聊聊Vue 3里一个挺有意思的玩意儿:i18n,也就是国际化。这玩意儿听起来高大上,其实说白了,就是让你的网站或者App能说各国语言,让来自五湖四海的朋友们都能看得懂。

咱们今天不光要讲怎么用,更要扒一扒它的底层实现,看看它怎么做到动态加载语言包的。保证让你听完之后,下次面试官问你“Vue的i18n怎么实现的?”的时候,你能自信地说:“老子不仅会用,还会造!”

一、i18n是个啥?为啥要用它?

咱们先来聊聊i18n是个啥。i18n是Internationalization的缩写,中间省略了18个字母,所以就叫i18n。还有个相关的概念叫l10n,是Localization的缩写,中间省略了10个字母。简单来说,i18n是指让你的应用具备国际化的能力,而l10n是指针对特定语言和地区进行适配。

为啥要用它呢?你想啊,你的App如果只支持中文,那老外来了不是抓瞎?所以,为了让更多人能用你的产品,国际化是必不可少的。

二、vue-i18n:Vue官方推荐的国际化插件

在Vue的世界里,vue-i18n是官方推荐的国际化插件。它提供了一套简单易用的API,可以让你轻松地实现多语言支持。

  1. 安装

    npm install vue-i18n@next
    # 或者
    yarn add vue-i18n@next

    注意:这里用的是@next,因为我们要用Vue 3的版本。

  2. 基本使用

    // main.js
    import { createApp } from 'vue'
    import { createI18n } from 'vue-i18n'
    import App from './App.vue'
    
    // 语言包
    const messages = {
     en: {
       message: {
         hello: 'hello world'
       }
     },
     zh: {
       message: {
         hello: '你好世界'
       }
     }
    }
    
    const i18n = createI18n({
     locale: 'zh', // 默认语言
     fallbackLocale: 'en', // 备用语言
     messages
    })
    
    const app = createApp(App)
    app.use(i18n)
    app.mount('#app')
    <!-- App.vue -->
    <template>
     <p>{{ $t('message.hello') }}</p>
     <button @click="changeLocale('en')">切换到英文</button>
     <button @click="changeLocale('zh')">切换到中文</button>
    </template>
    
    <script>
    import { useI18n } from 'vue-i18n'
    
    export default {
     setup() {
       const { t, locale } = useI18n()
    
       const changeLocale = (lang) => {
         locale.value = lang
       }
    
       return {
         t,
         changeLocale
       }
     }
    }
    </script>

    这段代码很简单,首先我们定义了一个messages对象,里面包含了英文和中文的语言包。然后,我们用createI18n创建了一个i18n实例,并将其挂载到Vue应用上。

    在组件中,我们使用useI18n hook来获取t函数和locale变量。t函数用于翻译文本,locale变量用于切换语言。

  3. $t 是个啥?

    $t 是一个全局函数,它接收一个字符串作为参数,这个字符串就是你在语言包里定义的key。vue-i18n会根据当前的locale,在语言包里找到对应的文本,并将其返回。

    例如,$t('message.hello') 会根据当前的locale,在messages对象里找到 message.hello 对应的文本。

  4. locale 又是啥?

    locale 指的是当前的语言环境。比如,zh 代表中文,en 代表英文。vue-i18n 会根据当前的 locale 来选择使用哪个语言包。

三、动态加载语言包:让你的应用更灵活

上面的例子里,我们将所有的语言包都放在了 main.js 里。这样做虽然简单,但是如果你的应用支持的语言很多,那 main.js 就会变得非常臃肿。而且,每次新增一种语言,都需要修改 main.js,重新打包发布。

为了解决这个问题,我们可以使用动态加载语言包的方式。

  1. 把语言包拆分到单独的文件里

    首先,我们把语言包拆分到单独的文件里,例如:

    • locales/en.json

      {
       "message": {
         "hello": "hello world"
       }
      }
    • locales/zh.json

      {
       "message": {
         "hello": "你好世界"
       }
      }
  2. 使用 createI18nmessages 选项加载初始语言包

    // main.js
    import { createApp } from 'vue'
    import { createI18n } from 'vue-i18n'
    import App from './App.vue'
    import en from './locales/en.json'
    import zh from './locales/zh.json'
    
    const messages = {
     en: en,
     zh: zh
    }
    
    const i18n = createI18n({
     locale: 'zh', // 默认语言
     fallbackLocale: 'en', // 备用语言
     messages
    })
    
    const app = createApp(App)
    app.use(i18n)
    app.mount('#app')

    注意这里 import en from './locales/en.json' 会将 json 文件解析成对象。

  3. 编写动态加载语言包的函数

    // src/i18n.js
    import { createI18n } from 'vue-i18n'
    import en from './locales/en.json'
    import zh from './locales/zh.json'
    
    const messages = {
     en: en,
     zh: zh
    }
    
    export const i18n = createI18n({
     locale: 'zh', // 默认语言
     fallbackLocale: 'en', // 备用语言
     messages
    })
    
    export async function loadLocaleMessages(locale) {
     // 如果已经加载过了,就直接返回
     if (i18n.global.availableLocales.includes(locale)) {
       return
     }
    
     try {
       // 动态加载语言包
       const messages = await import(`./locales/${locale}.json`)
       // 添加到 i18n 实例中
       i18n.global.setLocaleMessage(locale, messages.default)
       // 设置 locale
       i18n.global.locale.value = locale
     } catch (e) {
       console.error(`Failed to load locale ${locale}`, e)
     }
    }

    这个函数接收一个 locale 作为参数,然后使用 import() 动态加载对应的语言包。加载完成后,我们使用 i18n.global.setLocaleMessage 将语言包添加到 i18n 实例中,并使用 i18n.global.locale.value 设置当前的 locale

    import() 是一个动态导入的语法,它允许你在运行时加载模块。这对于按需加载语言包非常有用。

  4. 在组件中使用动态加载语言包的函数

    <!-- App.vue -->
    <template>
     <p>{{ $t('message.hello') }}</p>
     <button @click="changeLocale('en')">切换到英文</button>
     <button @click="changeLocale('zh')">切换到中文</button>
    </template>
    
    <script>
    import { useI18n } from 'vue-i18n'
    import { loadLocaleMessages } from './i18n'
    
    export default {
     setup() {
       const { t, locale } = useI18n()
    
       const changeLocale = async (lang) => {
         await loadLocaleMessages(lang)
         locale.value = lang
       }
    
       return {
         t,
         changeLocale
       }
     }
    }
    </script>

    在组件中,我们调用 loadLocaleMessages 函数来动态加载语言包。注意,changeLocale 函数需要声明为 async,因为 loadLocaleMessages 函数返回的是一个 Promise。

四、vue-i18n 的底层实现:扒一扒源码

光会用还不够,咱们还得扒一扒 vue-i18n 的底层实现,看看它到底是怎么工作的。

  1. createI18n 函数

    createI18n 函数是 vue-i18n 的入口函数,它接收一个配置对象作为参数,并返回一个 i18n 实例。

    // packages/vue-i18n/src/index.ts
    
    export function createI18n(options: I18nOptions = {}): I18n {
     // ...省略N行代码...
    
     const i18n: I18n = {
       install(app: App): void {
         // ...省略N行代码...
       },
       version: VERSION
     }
    
     return i18n
    }

    createI18n 函数主要做了以下几件事:

    • 创建了一个 i18n 实例。
    • i18n 实例上定义了一些属性,例如 localefallbackLocalemessages 等。
    • 定义了一个 install 方法,用于将 i18n 实例挂载到 Vue 应用上。
  2. install 方法

    install 方法是 Vue 插件的标准方法,它会在 Vue 应用启动时被调用。vue-i18ninstall 方法主要做了以下几件事:

    • $t 函数添加到 Vue 的原型上,这样我们就可以在组件中使用 $t 函数了。
    • i18n 实例添加到 Vue 的全局属性上,这样我们就可以在组件中使用 this.$i18n 来访问 i18n 实例了。
    • 注册一个全局组件 <i18n>,用于在模板中进行国际化。
    • 注册一个全局指令 v-t,用于在 DOM 元素上进行国际化。
  3. $t 函数的实现

    $t 函数的实现比较复杂,它需要根据当前的 locale,在语言包里找到对应的文本,并进行一些处理,例如:

    • 插值:将文本中的变量替换为实际的值。
    • 复数:根据数值的不同,选择不同的文本。
    • 格式化:对文本进行格式化,例如日期格式化、数字格式化等。
    // packages/vue-i18n/src/composer.ts
    
    function translate(key: string, ...values: any[]): string {
     // ...省略N行代码...
    
     // 获取当前 locale 的语言包
     const messages = getLocaleMessages(locale.value)
    
     // 在语言包里查找对应的文本
     const message = lookup(messages, key)
    
     // 如果找不到,就使用 fallback locale 的语言包
     if (!message && fallbackLocale.value) {
       // ...省略N行代码...
     }
    
     // 如果还是找不到,就返回 key 本身
     if (!message) {
       return key
     }
    
     // 进行插值和格式化
     const formattedMessage = format(message, values)
    
     return formattedMessage
    }

    translate 函数主要做了以下几件事:

    • 获取当前 locale 的语言包。
    • 在语言包里查找对应的文本。
    • 如果找不到,就使用 fallbackLocale 的语言包。
    • 如果还是找不到,就返回 key 本身。
    • 对文本进行插值和格式化。
  4. 动态加载语言包的实现

    动态加载语言包的实现主要依赖于 import() 函数和 i18n.global.setLocaleMessage 函数。

    import() 函数用于动态加载语言包,i18n.global.setLocaleMessage 函数用于将语言包添加到 i18n 实例中。

    // src/i18n.js
    
    export async function loadLocaleMessages(locale) {
     // ...省略N行代码...
    
     try {
       // 动态加载语言包
       const messages = await import(`./locales/${locale}.json`)
       // 添加到 i18n 实例中
       i18n.global.setLocaleMessage(locale, messages.default)
       // 设置 locale
       i18n.global.locale.value = locale
     } catch (e) {
       console.error(`Failed to load locale ${locale}`, e)
     }
    }

    loadLocaleMessages 函数主要做了以下几件事:

    • 使用 import() 函数动态加载语言包。
    • 使用 i18n.global.setLocaleMessage 函数将语言包添加到 i18n 实例中。
    • 设置当前的 locale

五、高级用法:更多姿势解锁i18n

  1. 日期和数字的格式化

    vue-i18n 还提供了日期和数字的格式化功能。你可以使用 d 函数和 n 函数来格式化日期和数字。

    <template>
     <p>{{ $d(new Date(), 'short') }}</p>
     <p>{{ $n(1234567.89, 'currency') }}</p>
    </template>

    你需要先定义一些格式:

    // i18n.js
    import { createI18n } from 'vue-i18n'
    
    const i18n = createI18n({
     locale: 'zh',
     fallbackLocale: 'en',
     messages: {
       en: {
         // ...
       },
       zh: {
         // ...
       }
     },
     datetimeFormats: {
       en: {
         short: {
           year: 'numeric',
           month: 'short',
           day: 'numeric'
         },
         long: {
           year: 'numeric',
           month: 'long',
           day: 'numeric',
           weekday: 'long',
           hour: 'numeric',
           minute: 'numeric',
           second: 'numeric',
           timeZoneName: 'short'
         }
       },
       zh: {
         short: {
           year: 'numeric',
           month: 'short',
           day: 'numeric'
         },
         long: {
           year: 'numeric',
           month: 'long',
           day: 'numeric',
           weekday: 'long',
           hour: 'numeric',
           minute: 'numeric',
           second: 'numeric',
           timeZoneName: 'short'
         }
       }
     },
     numberFormats: {
       en: {
         currency: {
           style: 'currency',
           currency: 'USD'
         }
       },
       zh: {
         currency: {
           style: 'currency',
           currency: 'CNY'
         }
       }
     }
    })
    
    export default i18n
  2. 组件的国际化

    vue-i18n 还提供了组件的国际化功能。你可以使用 <i18n> 组件来国际化组件。

    <template>
     <i18n path="message.hello">
       <template #default="{ message }">
         <p>{{ message }}</p>
       </template>
     </i18n>
    </template>

    或者使用具名插槽传递参数:

    <template>
     <i18n path="message.greeting">
       <template #name="{ message }">
         <p>{{ message }}</p>
       </template>
     </i18n>
    </template>
    
    <script>
    export default {
     data() {
       return {
         name: '老码农'
       }
     },
     i18n: {
       messages: {
         en: {
           message: {
             greeting: 'Hello, {name}!'
           }
         },
         zh: {
           message: {
             greeting: '你好,{name}!'
           }
         }
       },
       // 传递参数
       placeholders: {
         name: this.name
       }
     }
    }
    </script>

    注意:组件的国际化需要在组件的 i18n 选项中定义语言包。

  3. 指令的国际化

    vue-i18n 还提供了指令的国际化功能。你可以使用 v-t 指令来国际化 DOM 元素。

    <template>
     <p v-t="'message.hello'"></p>
    </template>

六、总结

今天咱们聊了Vue 3的i18n,从基本使用到动态加载语言包,再到扒源码,最后还介绍了一些高级用法。

希望通过今天的讲解,你能对vue-i18n有一个更深入的了解,下次面试的时候,也能自信地说:“老子不仅会用,还会造!”

最后,记住一点:国际化不仅仅是翻译文本,更重要的是要考虑不同文化之间的差异,让你的应用更贴近用户的需求。

好了,今天的分享就到这里,感谢大家的收听!咱们下次再见!

表格总结:常用 API

API 描述
createI18n 创建 i18n 实例,接收配置对象,例如 localefallbackLocalemessages 等。
$t(key, ...values) 翻译文本,根据当前 locale 在语言包中查找对应的文本,并进行插值和格式化。
locale.value 获取或设置当前的语言环境。
fallbackLocale.value 获取或设置备用语言环境。
setLocaleMessage(locale, messages) 动态设置某个 locale 的语言包,用于动态加载语言包。
$d(date, format) 格式化日期,date 为 Date 对象,format 为预定义的格式名称或自定义格式。
$n(number, format) 格式化数字,number 为数字,format 为预定义的格式名称或自定义格式。
<i18n> 组件,用于在模板中进行国际化,可以在组件内部使用插槽传递参数。
v-t="key" 指令,用于在 DOM 元素上进行国际化,key 为语言包中的键名。
useI18n() Hook,在 setup 函数中使用,返回 i18n 实例的常用方法和属性,例如 tlocale 等。

补充说明:

  • vue-i18n 的配置项非常丰富,可以根据你的实际需求进行配置。
  • 动态加载语言包可以有效地减小应用体积,提高加载速度。
  • 在进行国际化时,要充分考虑不同文化之间的差异,例如日期格式、数字格式、货币符号等。
  • 可以使用一些工具来辅助进行国际化,例如 i18next、PhraseApp 等。

希望这些补充说明能对你有所帮助。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注