博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
你想要的——vue-i18n源码分析
阅读量:6082 次
发布时间:2019-06-20

本文共 47094 字,大约阅读时间需要 156 分钟。

大家好,今天给大家带来的干货是vue-i18n(v7.3.0)的源码分析。vue-i18n是用于多语言适配的vue插件,主要用于前端项目的国际化应用

这里是vue-18n的gayhub地址  

首先还是先看看作者给我们的一个简单的例子:

      
getting started

{

{ $t("message.hello") }}

从这个简单的小例子中,我们可以看到vue-i18n的使用非常的简单,我们只需要定义好对应的语言包messages,然后设置一个默认语言类型locale,然后实例化出一个i18n对象并传入我们的vue实例就可以愉快的使用起来

ps:插值表达式中的$t就是vue-i18n暴露给用户的API


接下来,我们就一起看看vue-i18n的源码到底是怎么样的

首先还是先看看目录结构,我们可以看到,源码目录src中有9个文件,而这9个文件便构成了vue-i18n的全部内容

图片描述


我们先看看入口文件 index.js

这个文件定义了并且导出一个名为VueI18n的类,并在类上定义了availabilities,install,version三个静态属性,以便通过类名直接访问

/* @flow */// 导入相应的资源import { install, Vue } from './install'import {  warn,  isNull,  parseArgs,  fetchChoice,  isPlainObject,  isObject,  looseClone,  remove,  canUseDateTimeFormat,  canUseNumberFormat} from './util'import BaseFormatter from './format'import I18nPath from './path'import type { PathValue } from './path'// 定义并且一个VueI18n类export default class VueI18n {  // 定义静态属性,在后面有赋值操作  static install: () => void  static version: string  static availabilities: IntlAvailability  // 私有变量  _vm: any  _formatter: Formatter  _root: ?I18n  _sync: boolean  _fallbackRoot: boolean  _missing: ?MissingHandler  _exist: Function  _watcher: any  _i18nWatcher: Function  _silentTranslationWarn: boolean  _dateTimeFormatters: Object  _numberFormatters: Object  _path: I18nPath  _dataListeners: Array
// 构造函数,默认参数是一个空对象 constructor (options: I18nOptions = {}) { // 局部变量,如果默认参数没有传值,则使用默认值 // locale 用于指定页面使用的语言类型,默认是英文 const locale: Locale = options.locale || 'en-US' // fallbackLocal TODO const fallbackLocale: Locale = options.fallbackLocale || 'en-US' // message 就是用户定义的语言包,默认是一个空对像 const messages: LocaleMessages = options.messages || {} // dateTimeFormats 日期格式 const dateTimeFormats = options.dateTimeFormats || {} // numberFormats 数字格式 const numberFormats = options.numberFormats || {} // _vm 默认的vm对象 this._vm = null // _formatter 可自定义格式化 this._formatter = options.formatter || new BaseFormatter() // missing TODO this._missing = options.missing || null // _root 保存根节点 this._root = options.root || null this._sync = options.sync === undefined ? true : !!options.sync // fallbackRoot 保存中fallback语言包的根节点 this._fallbackRoot = options.fallbackRoot === undefined ? true : !!options.fallbackRoot this._silentTranslationWarn = options.silentTranslationWarn === undefined ? false : !!options.silentTranslationWarn this._dateTimeFormatters = {} this._numberFormatters = {} this._path = new I18nPath() this._dataListeners = [] // _exist方法,用于判断某个key是否存在于这个messages语言包中 // 主要通过path模块的getPathValue方法实现,后面会详细说明,在此只需要知道他的用途 this._exist = (message: Object, key: Path): boolean => { if (!message || !key) { return false } return !isNull(this._path.getPathValue(message, key)) } // 初始化vm this._initVM({ locale, fallbackLocale, messages, dateTimeFormats, numberFormats }) } _initVM (data: { locale: Locale, fallbackLocale: Locale, messages: LocaleMessages, dateTimeFormats: DateTimeFormats, numberFormats: NumberFormats }): void { const silent = Vue.config.silent Vue.config.silent = true // 实例化一个vue对象,将传入的参数变成vue中的响应式数据,大部分vue的插件都使用这种做法,比如vuex this._vm = new Vue({ data }) Vue.config.silent = silent } // 监听vm数据的变化,将每次的vm数据push到监听队列中 subscribeDataChanging (vm: any): void { this._dataListeners.push(vm) } // 取消监听vm数据的变化 unsubscribeDataChanging (vm: any): void { remove(this._dataListeners, vm) } // 监听i18n中定义的数据的变化,如果数据变化了,就强制更新页面 watchI18nData (): Function { const self = this // 利用vue中$watch的api,当this._vm的数据($data)发生改变,就会触发监听队列中所有vm的视图的变化 return this._vm.$watch('$data', () => { let i = self._dataListeners.length // 遍历所有的vm,再利用vue中的$forceUpdate的api,实现强制更新 while (i--) { Vue.nextTick(() => { self._dataListeners[i] && self._dataListeners[i].$forceUpdate() }) } }, { deep: true }) } // 监听根节点locale的变化 watchLocale (): ?Function { /* istanbul ignore if */ if (!this._sync || !this._root) { return null } // 获取当前的vm const target: any = this._vm // 注意:这里是根节点的vm return this._root.vm.$watch('locale', (val) => { // 设置当前vm的locale,并强制更新 target.$set(target, 'locale', val) target.$forceUpdate() }, { immediate: true }) } // 获取当前的vm get vm (): any { return this._vm } // 获取当前vm的messages属性的内容,这里使用了looseClone对js对象进行拷贝,详细会在讲解util.js中分析 get messages (): LocaleMessages { return looseClone(this._getMessages()) } // 获取当前vm的dateTimeFormats属性的内容 get dateTimeFormats (): DateTimeFormats { return looseClone(this._getDateTimeFormats()) } // 获取当前vm的numberFormats属性的内容 get numberFormats (): NumberFormats { return looseClone(this._getNumberFormats()) } // 获取当前vm的locale属性的内容 get locale (): Locale { return this._vm.locale } // 设置当前vm的locale属性的内容 set locale (locale: Locale): void { this._vm.$set(this._vm, 'locale', locale) } // 获取当前vm的fallbackLocale属性的内容 get fallbackLocale (): Locale { return this._vm.fallbackLocale } // 设置当前vm的fallbackLocale属性的内容 set fallbackLocale (locale: Locale): void { this._vm.$set(this._vm, 'fallbackLocale', locale) } // 同上 get missing (): ?MissingHandler { return this._missing } set missing (handler: MissingHandler): void { this._missing = handler } // 同上 get formatter (): Formatter { return this._formatter } set formatter (formatter: Formatter): void { this._formatter = formatter } // 同上 get silentTranslationWarn (): boolean { return this._silentTranslationWarn } set silentTranslationWarn (silent: boolean): void { this._silentTranslationWarn = silent } _getMessages (): LocaleMessages { return this._vm.messages } _getDateTimeFormats (): DateTimeFormats { return this._vm.dateTimeFormats } _getNumberFormats (): NumberFormats { return this._vm.numberFormats } // 提示方法 _warnDefault (locale: Locale, key: Path, result: ?any, vm: ?any): ?string { if (!isNull(result)) { return result } // 如果missing存在,则执行 if (this.missing) { this.missing.apply(null, [locale, key, vm]) } else { if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) { warn( `Cannot translate the value of keypath '${key}'. ` + 'Use the value of keypath as default.' ) } } return key } // 判断是否有回退方案 _isFallbackRoot (val: any): boolean { return !val && !isNull(this._root) && this._fallbackRoot } // 获取message中某个key的值 _interpolate ( locale: Locale, message: LocaleMessageObject, key: Path, host: any, interpolateMode: string, values: any ): any { if (!message) { return null } // 利用getPathValue方法获取message中key的值 const pathRet: PathValue = this._path.getPathValue(message, key) // 如果获取的值是一个数组,则返回这个数组 if (Array.isArray(pathRet)) { return pathRet } let ret: mixed // 如果获取的值是空 if (isNull(pathRet)) { /* istanbul ignore else */ if (isPlainObject(message)) { ret = message[key] if (typeof ret !== 'string') { if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) { warn(`Value of key '${key}' is not a string!`) } return null } } else { return null } } else { /* istanbul ignore else */ if (typeof pathRet === 'string') { // 返回获取的值 ret = pathRet } else { if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) { warn(`Value of key '${key}' is not a string!`) } return null } } // Check for the existance of links within the translated string // 解析message中有@:的情况 if (ret.indexOf('@:') >= 0) { ret = this._link(locale, message, ret, host, interpolateMode, values) } // 如果values为false,则返回ret,否则返回this._render的执行结果 return !values ? ret : this._render(ret, interpolateMode, values) } // @:的情况 _link ( locale: Locale, message: LocaleMessageObject, str: string, host: any, interpolateMode: string, values: any ): any { let ret: string = str // Match all the links within the local // We are going to replace each of // them with its translation // 匹配@:(link) const matches: any = ret.match(/(@:[\w\-_|.]+)/g) // 遍历匹配的数组 for (const idx in matches) { // ie compatible: filter custom array // prototype method if (!matches.hasOwnProperty(idx)) { continue } // 获取每个link const link: string = matches[idx] // Remove the leading @: // 除去头部的 @:,将得到的linkPlaceholder作为_interpolate方法的key继续解析 const linkPlaceholder: string = link.substr(2) // Translate the link let translated: any = this._interpolate( locale, message, linkPlaceholder, host, interpolateMode === 'raw' ? 'string' : interpolateMode, interpolateMode === 'raw' ? undefined : values ) if (this._isFallbackRoot(translated)) { if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) { warn(`Fall back to translate the link placeholder '${linkPlaceholder}' with root locale.`) } /* istanbul ignore if */ if (!this._root) { throw Error('unexpected error') } const root: any = this._root translated = root._translate( root._getMessages(), root.locale, root.fallbackLocale, linkPlaceholder, host, interpolateMode, values ) } // 获取装换的值 translated = this._warnDefault(locale, linkPlaceholder, translated, host) // Replace the link with the translated // 替换数据 ret = !translated ? ret : ret.replace(link, translated) } return ret } // 解析表达式,利用的是this._formatter.interpolate方法 _render (message: string, interpolateMode: string, values: any): any { const ret = this._formatter.interpolate(message, values) // if interpolateMode is **not** 'string' ('row'), // return the compiled data (e.g. ['foo', VNode, 'bar']) with formatter return interpolateMode === 'string' ? ret.join('') : ret } // 翻译语言方法 _translate ( messages: LocaleMessages, locale: Locale, fallback: Locale, key: Path, host: any, interpolateMode: string, args: any ): any { // 通过_interpolate方法获取结果 let res: any = this._interpolate(locale, messages[locale], key, host, interpolateMode, args) if (!isNull(res)) { return res } // 如果获取的结果是null,则使用fallback语言包 res = this._interpolate(fallback, messages[fallback], key, host, interpolateMode, args) if (!isNull(res)) { if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) { warn(`Fall back to translate the keypath '${key}' with '${fallback}' locale.`) } return res } else { return null } } _t (key: Path, _locale: Locale, messages: LocaleMessages, host: any, ...values: any): any { if (!key) { return '' } // 解析传入的values参数 const parsedArgs = parseArgs(...values) // 获取locale const locale: Locale = parsedArgs.locale || _locale // 调用_translate,设置模式为string,并将parsedArgs.params传入 const ret: any = this._translate( messages, locale, this.fallbackLocale, key, host, 'string', parsedArgs.params ) // 判断是否有回退方案,这个适用于当子组件找不到对应的语言包字段的时候,往根节点查找是否有相应的字段 if (this._isFallbackRoot(ret)) { if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) { warn(`Fall back to translate the keypath '${key}' with root locale.`) } /* istanbul ignore if */ if (!this._root) { throw Error('unexpected error') } // 调用根节点的t方法 return this._root.t(key, ...values) } else { return this._warnDefault(locale, key, ret, host) } } t (key: Path, ...values: any): TranslateResult { // 调用_t方法实现 return this._t(key, this.locale, this._getMessages(), null, ...values) } _i (key: Path, locale: Locale, messages: LocaleMessages, host: any, values: Object): any { // 设置interpolateMode为raw const ret: any = this._translate(messages, locale, this.fallbackLocale, key, host, 'raw', values) if (this._isFallbackRoot(ret)) { if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) { warn(`Fall back to interpolate the keypath '${key}' with root locale.`) } if (!this._root) { throw Error('unexpected error') } return this._root.i(key, locale, values) } else { return this._warnDefault(locale, key, ret, host) } } // 处理内置i18n组件逻辑 i (key: Path, locale: Locale, values: Object): TranslateResult { /* istanbul ignore if */ if (!key) { return '' } if (typeof locale !== 'string') { locale = this.locale } return this._i(key, locale, this._getMessages(), null, values) } _tc ( key: Path, _locale: Locale, messages: LocaleMessages, host: any, choice?: number, ...values: any ): any { if (!key) { return '' } if (choice === undefined) { choice = 1 } // 先调用this._t,在使用fetchChoice包装,fetchChoice具体在util中会详细分析 return fetchChoice(this._t(key, _locale, messages, host, ...values), choice) } tc (key: Path, choice?: number, ...values: any): TranslateResult { return this._tc(key, this.locale, this._getMessages(), null, choice, ...values) } _te (key: Path, locale: Locale, messages: LocaleMessages, ...args: any): boolean { const _locale: Locale = parseArgs(...args).locale || locale return this._exist(messages[_locale], key) } te (key: Path, locale?: Locale): boolean { return this._te(key, this.locale, this._getMessages(), locale) } // 获取语言包 getLocaleMessage (locale: Locale): LocaleMessageObject { return looseClone(this._vm.messages[locale] || {}) } // 设置语言包,或者用于热更新:i18n.setLocaleMessage('en', require('./en').default) setLocaleMessage (locale: Locale, message: LocaleMessageObject): void { this._vm.messages[locale] = message } mergeLocaleMessage (locale: Locale, message: LocaleMessageObject): void { this._vm.messages[locale] = Vue.util.extend(this._vm.messages[locale] || {}, message) } getDateTimeFormat (locale: Locale): DateTimeFormat { return looseClone(this._vm.dateTimeFormats[locale] || {}) } setDateTimeFormat (locale: Locale, format: DateTimeFormat): void { this._vm.dateTimeFormats[locale] = format } mergeDateTimeFormat (locale: Locale, format: DateTimeFormat): void { this._vm.dateTimeFormats[locale] = Vue.util.extend(this._vm.dateTimeFormats[locale] || {}, format) } // 本地格式化时间 _localizeDateTime ( value: number | Date, locale: Locale, fallback: Locale, dateTimeFormats: DateTimeFormats, key: string ): ?DateTimeFormatResult { // 获取语言包 & 对应的日期格式对象 let _locale: Locale = locale let formats: DateTimeFormat = dateTimeFormats[_locale] // fallback locale // 判断为空 if (isNull(formats) || isNull(formats[key])) { if (process.env.NODE_ENV !== 'production') { warn(`Fall back to '${fallback}' datetime formats from '${locale} datetime formats.`) } _locale = fallback formats = dateTimeFormats[_locale] } // 判断为空 if (isNull(formats) || isNull(formats[key])) { return null } else { // 如果不为空,本地获取对应key下的format规则 const format: ?DateTimeFormatOptions = formats[key] // 生成相应的id const id = `${_locale}__${key}` // 获取本地_dateTimeFormatters对象的缓存 let formatter = this._dateTimeFormatters[id] // 如果没有的缓存规则,则实例化Intl.DateTimeFormat类,获取对应的规则,并缓存在_dateTimeFormatters对象中 if (!formatter) { formatter = this._dateTimeFormatters[id] = new Intl.DateTimeFormat(_locale, format) } // 调用formatter的format方法,得到格式化后的日期 return formatter.format(value) } } _d (value: number | Date, locale: Locale, key: ?string): DateTimeFormatResult { /* istanbul ignore if */ // 判断是支持Intl.dateTimeFormat方法 if (process.env.NODE_ENV !== 'production' && !VueI18n.availabilities.dateTimeFormat) { warn('Cannot format a Date value due to not support Intl.DateTimeFormat.') return '' } // 如果key为空,则直接实例化Intl.DateTimeFormat类,并调用api:format,得到相应的日期格式 if (!key) { return new Intl.DateTimeFormat(locale).format(value) } // 如果key不为空,则调用本地的格式化规则,_localizeDateTime方法 const ret: ?DateTimeFormatResult = this._localizeDateTime(value, locale, this.fallbackLocale, this._getDateTimeFormats(), key) if (this._isFallbackRoot(ret)) { if (process.env.NODE_ENV !== 'production') { warn(`Fall back to datetime localization of root: key '${key}' .`) } /* istanbul ignore if */ if (!this._root) { throw Error('unexpected error') } return this._root.d(value, key, locale) } else { return ret || '' } } // 日期格式化的入口 d (value: number | Date, ...args: any): DateTimeFormatResult { let locale: Locale = this.locale let key: ?string = null // 如果args的长度唯一,并且值为字符串,则设置为key if (args.length === 1) { if (typeof args[0] === 'string') { key = args[0] } else if (isObject(args[0])) { // 如果值为对象,则解构这个对象 if (args[0].locale) { locale = args[0].locale } if (args[0].key) { key = args[0].key } } } else if (args.length === 2) { // 如果长度为2,则设置key和locale if (typeof args[0] === 'string') { key = args[0] } if (typeof args[1] === 'string') { locale = args[1] } } // 调用_d方法 return this._d(value, locale, key) } getNumberFormat (locale: Locale): NumberFormat { return looseClone(this._vm.numberFormats[locale] || {}) } setNumberFormat (locale: Locale, format: NumberFormat): void { this._vm.numberFormats[locale] = format } mergeNumberFormat (locale: Locale, format: NumberFormat): void { this._vm.numberFormats[locale] = Vue.util.extend(this._vm.numberFormats[locale] || {}, format) } _localizeNumber ( value: number, locale: Locale, fallback: Locale, numberFormats: NumberFormats, key: string ): ?NumberFormatResult { let _locale: Locale = locale let formats: NumberFormat = numberFormats[_locale] // fallback locale // 判断为空 if (isNull(formats) || isNull(formats[key])) { if (process.env.NODE_ENV !== 'production') { warn(`Fall back to '${fallback}' number formats from '${locale} number formats.`) } _locale = fallback formats = numberFormats[_locale] } // 判断为空 if (isNull(formats) || isNull(formats[key])) { return null } else { // 获取对应的key下的数字格式 const format: ?NumberFormatOptions = formats[key] // 生成id const id = `${_locale}__${key}` // 获取相应Id下的缓存 let formatter = this._numberFormatters[id] // 如果没有缓存,则实例化Intl.NumberFormat类,并且缓存在_numberFormatters对象中 if (!formatter) { formatter = this._numberFormatters[id] = new Intl.NumberFormat(_locale, format) } // 根据得到的formatter,调用format方法得到相应的数字 return formatter.format(value) } } _n (value: number, locale: Locale, key: ?string): NumberFormatResult { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !VueI18n.availabilities.numberFormat) { warn('Cannot format a Date value due to not support Intl.NumberFormat.') return '' } // 如果没有key,则直接利用Intl.NumberFormat获取相应的格式 if (!key) { return new Intl.NumberFormat(locale).format(value) } // 如果有key,则调用本地的格式化规则,_localizeNumber方法 const ret: ?NumberFormatResult = this._localizeNumber(value, locale, this.fallbackLocale, this._getNumberFormats(), key) if (this._isFallbackRoot(ret)) { if (process.env.NODE_ENV !== 'production') { warn(`Fall back to number localization of root: key '${key}' .`) } /* istanbul ignore if */ if (!this._root) { throw Error('unexpected error') } return this._root.n(value, key, locale) } else { return ret || '' } } // 数字格式化的入口 n (value: number, ...args: any): NumberFormatResult { let locale: Locale = this.locale let key: ?string = null // 解析参数,与上面的方法d类似 if (args.length === 1) { if (typeof args[0] === 'string') { key = args[0] } else if (isObject(args[0])) { if (args[0].locale) { locale = args[0].locale } if (args[0].key) { key = args[0].key } } } else if (args.length === 2) { if (typeof args[0] === 'string') { key = args[0] } if (typeof args[1] === 'string') { locale = args[1] } } // 调用_n return this._n(value, locale, key) }}VueI18n.availabilities = { dateTimeFormat: canUseDateTimeFormat, numberFormat: canUseNumberFormat}VueI18n.install = installVueI18n.version = '__VERSION__'/* istanbul ignore if */if (typeof window !== 'undefined' && window.Vue) { window.Vue.use(VueI18n)}

其实index.js就已经包含了vue-i18n的主要内容了,看明白了这个文件就基本明白了vue-i18n的大体思路了

vue-i18n还有其他优秀的地方,比如它提供了组件和指令,可以供使用者更加方便和灵活的调用

接下来先看看component.js的代码,看看i18n组件是怎么实现的

/* @flow */// 定义i18n组件import { warn } from './util'export default {  name: 'i18n',  functional: true,  props: {    tag: {      type: String,      default: 'span'    },    path: {      type: String,      required: true    },    locale: {      type: String    },    places: {      type: [Array, Object]    }  },  render (h: Function, { props, data, children, parent }: Object) {    // 获取父组件i18n实例    const i18n = parent.$i18n    // 收集子组件    children = (children || []).filter(child => {      return child.tag || (child.text = child.text.trim())    })    if (!i18n) {      if (process.env.NODE_ENV !== 'production') {        warn('Cannot find VueI18n instance!')      }      return children    }    // 获取路径,语言包,其他参数    const path: Path = props.path    const locale: ?Locale = props.locale    const params: Object = {}    const places: Array
| Object = props.places || {} // 判断是否有places占位符 const hasPlaces: boolean = Array.isArray(places) ? places.length > 0 : Object.keys(places).length > 0 const everyPlace: boolean = children.every(child => { if (child.data && child.data.attrs) { const place = child.data.attrs.place return (typeof place !== 'undefined') && place !== '' } }) if (hasPlaces && children.length > 0 && !everyPlace) { warn('If places prop is set, all child elements must have place prop set.') } // 提取组件本身的place if (Array.isArray(places)) { places.forEach((el, i) => { params[i] = el }) } else { Object.keys(places).forEach(key => { params[key] = places[key] }) } // 提取子组件的place children.forEach((child, i: number) => { const key: string = everyPlace ? `${child.data.attrs.place}` : `${i}` params[key] = child }) // 将参数作为createElement方法的参数传入 return h(props.tag, data, i18n.i(path, locale, params)) }}

i18组件不仅仅提供了vue组件的基本功能,还提供了place占位符,比较灵活

vue-i18n提供了v-t指令,主要就是实现了一个vue组件

/* @flow */// 指令功能,用户可通过指令v-t来进行多语言操作import { warn, isPlainObject, looseEqual } from './util'// 定义了vue指令中的bind方法export function bind (el: any, binding: Object, vnode: any): void {  t(el, binding, vnode)}// 定义了vue指令中的update方法export function update (el: any, binding: Object, vnode: any, oldVNode: any): void {  if (looseEqual(binding.value, binding.oldValue)) { return }  t(el, binding, vnode)}function t (el: any, binding: Object, vnode: any): void {  // 解析参数  const value: any = binding.value  // 获取路径,语言类型以及其他参数  const { path, locale, args } = parseValue(value)  if (!path && !locale && !args) {    warn('not support value type')    return  }  // 获取当前的vm  const vm: any = vnode.context  if (!vm) {    warn('not exist Vue instance in VNode context')    return  }  if (!vm.$i18n) {    warn('not exist VueI18n instance in Vue instance')    return  }  if (!path) {    warn('required `path` in v-t directive')    return  }  // 最后调用vm实例上的t方法,然后进行赋值  el._vt = el.textContent = vm.$i18n.t(path, ...makeParams(locale, args))}// 解析参数function parseValue (value: any): Object {  let path: ?string  let locale: ?Locale  let args: any  // 参数只允许是字符串或是对象  if (typeof value === 'string') {    path = value  } else if (isPlainObject(value)) {    path = value.path    locale = value.locale    args = value.args  }  return { path, locale, args }}// 对参数进行调整function makeParams (locale: Locale, args: any): Array
{ const params: Array
= [] locale && params.push(locale) if (args && (Array.isArray(args) || isPlainObject(args))) { params.push(args) } return params}

看完了组件和指令的实现,其他的文件都是辅助函数,我们一次来看看各个文件的实现

  • extend.js
/* @flow */    // 主要是往Vue类的原型链扩展方法,调用的都是i18n的实例方法    export default function extend (Vue: any): void {      Vue.prototype.$t = function (key: Path, ...values: any): TranslateResult {        const i18n = this.$i18n        return i18n._t(key, i18n.locale, i18n._getMessages(), this, ...values)      }          Vue.prototype.$tc = function (key: Path, choice?: number, ...values: any): TranslateResult {        const i18n = this.$i18n        return i18n._tc(key, i18n.locale, i18n._getMessages(), this, choice, ...values)      }          Vue.prototype.$te = function (key: Path, locale?: Locale): boolean {        const i18n = this.$i18n        return i18n._te(key, i18n.locale, i18n._getMessages(), locale)      }          Vue.prototype.$d = function (value: number | Date, ...args: any): DateTimeFormatResult {        return this.$i18n.d(value, ...args)      }          Vue.prototype.$n = function (value: number, ...args: any): NumberFormatResult {        return this.$i18n.n(value, ...args)      }    }
  • path.js
/* @flow */import { isObject } from './util'/** *  Path paerser *  - Inspired: *    Vue.js Path parser */// actions// 定义了各种常量const APPEND = 0const PUSH = 1const INC_SUB_PATH_DEPTH = 2const PUSH_SUB_PATH = 3// statesconst BEFORE_PATH = 0const IN_PATH = 1const BEFORE_IDENT = 2const IN_IDENT = 3const IN_SUB_PATH = 4const IN_SINGLE_QUOTE = 5const IN_DOUBLE_QUOTE = 6const AFTER_PATH = 7const ERROR = 8const pathStateMachine: any = []pathStateMachine[BEFORE_PATH] = {  'ws': [BEFORE_PATH],  'ident': [IN_IDENT, APPEND],  '[': [IN_SUB_PATH],  'eof': [AFTER_PATH]}pathStateMachine[IN_PATH] = {  'ws': [IN_PATH],  '.': [BEFORE_IDENT],  '[': [IN_SUB_PATH],  'eof': [AFTER_PATH]}pathStateMachine[BEFORE_IDENT] = {  'ws': [BEFORE_IDENT],  'ident': [IN_IDENT, APPEND],  '0': [IN_IDENT, APPEND],  'number': [IN_IDENT, APPEND]}pathStateMachine[IN_IDENT] = {  'ident': [IN_IDENT, APPEND],  '0': [IN_IDENT, APPEND],  'number': [IN_IDENT, APPEND],  'ws': [IN_PATH, PUSH],  '.': [BEFORE_IDENT, PUSH],  '[': [IN_SUB_PATH, PUSH],  'eof': [AFTER_PATH, PUSH]}pathStateMachine[IN_SUB_PATH] = {  "'": [IN_SINGLE_QUOTE, APPEND],  '"': [IN_DOUBLE_QUOTE, APPEND],  '[': [IN_SUB_PATH, INC_SUB_PATH_DEPTH],  ']': [IN_PATH, PUSH_SUB_PATH],  'eof': ERROR,  'else': [IN_SUB_PATH, APPEND]}pathStateMachine[IN_SINGLE_QUOTE] = {  "'": [IN_SUB_PATH, APPEND],  'eof': ERROR,  'else': [IN_SINGLE_QUOTE, APPEND]}pathStateMachine[IN_DOUBLE_QUOTE] = {  '"': [IN_SUB_PATH, APPEND],  'eof': ERROR,  'else': [IN_DOUBLE_QUOTE, APPEND]}/** * Check if an expression is a literal value. */const literalValueRE: RegExp = /^\s?(true|false|-?[\d.]+|'[^']*'|"[^"]*")\s?$/function isLiteral (exp: string): boolean {  return literalValueRE.test(exp)}/** * Strip quotes from a string */function stripQuotes (str: string): string | boolean {  const a: number = str.charCodeAt(0)  const b: number = str.charCodeAt(str.length - 1)  return a === b && (a === 0x22 || a === 0x27)    ? str.slice(1, -1)    : str}/** * Determine the type of a character in a keypath. */// 获取path的类型function getPathCharType (ch: ?string): string {  if (ch === undefined || ch === null) { return 'eof' }  // 获取charCode,判断各种类型,逻辑很简单,不解释  const code: number = ch.charCodeAt(0)  switch (code) {    case 0x5B: // [    case 0x5D: // ]    case 0x2E: // .    case 0x22: // "    case 0x27: // '    case 0x30: // 0      return ch    case 0x5F: // _    case 0x24: // $    case 0x2D: // -      return 'ident'    case 0x20: // Space    case 0x09: // Tab    case 0x0A: // Newline    case 0x0D: // Return    case 0xA0:  // No-break space    case 0xFEFF:  // Byte Order Mark    case 0x2028:  // Line Separator    case 0x2029:  // Paragraph Separator      return 'ws'  }  // a-z, A-Z  if ((code >= 0x61 && code <= 0x7A) || (code >= 0x41 && code <= 0x5A)) {    return 'ident'  }  // 1-9  if (code >= 0x31 && code <= 0x39) { return 'number' }  return 'else'}/** * Format a subPath, return its plain form if it is * a literal string or number. Otherwise prepend the * dynamic indicator (*). */// 格式化子路径function formatSubPath (path: string): boolean | string {  const trimmed: string = path.trim()  // invalid leading 0  if (path.charAt(0) === '0' && isNaN(path)) { return false }  return isLiteral(trimmed) ? stripQuotes(trimmed) : '*' + trimmed}/** * Parse a string path into an array of segments */// 路径解析function parse (path: Path): ?Array
{ // 初始化变量 const keys: Array
= [] let index: number = -1 let mode: number = BEFORE_PATH let subPathDepth: number = 0 let c: ?string let key: any let newChar: any let type: string let transition: number let action: Function let typeMap: any const actions: Array
= [] // 定义各种actions // 将key添加到keys数组中 actions[PUSH] = function () { if (key !== undefined) { keys.push(key) key = undefined } } // 设置key,如果key不为空,则追加在key后面 actions[APPEND] = function () { if (key === undefined) { key = newChar } else { key += newChar } } actions[INC_SUB_PATH_DEPTH] = function () { actions[APPEND]() subPathDepth++ } actions[PUSH_SUB_PATH] = function () { if (subPathDepth > 0) { subPathDepth-- mode = IN_SUB_PATH actions[APPEND]() } else { subPathDepth = 0 key = formatSubPath(key) if (key === false) { return false } else { actions[PUSH]() } } } // 判断是否为"" 或者 为 '' function maybeUnescapeQuote (): ?boolean { const nextChar: string = path[index + 1] if ((mode === IN_SINGLE_QUOTE && nextChar === "'") || (mode === IN_DOUBLE_QUOTE && nextChar === '"')) { index++ newChar = '\\' + nextChar actions[APPEND]() return true } } // 循环遍历路径,然后进行拆分 while (mode !== null) { index++ c = path[index] // 判断是否为\\ 并且判断是否为双引号或单引号,如果是,就进入下个循环 if (c === '\\' && maybeUnescapeQuote()) { continue } // 获取当前字符的类型 type = getPathCharType(c) // 获取mode类型的映射表 typeMap = pathStateMachine[mode] // 得到相应的transition transition = typeMap[type] || typeMap['else'] || ERROR if (transition === ERROR) { return // parse error } // 重新设置mode mode = transition[0] // 得到相应的action action = actions[transition[1]] if (action) { newChar = transition[2] newChar = newChar === undefined ? c : newChar if (action() === false) { return } } if (mode === AFTER_PATH) { return keys } }}export type PathValue = PathValueObject | PathValueArray | string | number | boolean | nullexport type PathValueObject = { [key: string]: PathValue }export type PathValueArray = Array
function empty (target: any): boolean { /* istanbul ignore else */ if (Array.isArray(target)) { return target.length === 0 } else { return false }}export default class I18nPath { _cache: Object constructor () { this._cache = Object.create(null) } /** * External parse that check for a cache hit first */ // 通过parse解析路径,并缓存在_cache对象上 parsePath (path: Path): Array
{ let hit: ?Array
= this._cache[path] if (!hit) { hit = parse(path) if (hit) { this._cache[path] = hit } } return hit || [] } /** * Get path value from path string */ getPathValue (obj: mixed, path: Path): PathValue { if (!isObject(obj)) { return null } // 得到path路径解析后的数组paths const paths: Array
= this.parsePath(path) if (empty(paths)) { return null } else { const length: number = paths.length let ret: any = null let last: any = obj let i: number = 0 // 遍历查找obj中key对应的值 while (i < length) { const value: any = last[paths[i]] if (value === undefined) { last = null break } last = value i++ } ret = last return ret } }}
  • format.js
/* @flow */import { warn, isObject } from './util'// 对应key的值进行基础格式化export default class BaseFormatter {  _caches: { [key: string]: Array
} constructor () { // 初始化一个缓存对象 this._caches = Object.create(null) } interpolate (message: string, values: any): Array
{ // 先查看缓存中是否有token let tokens: Array
= this._caches[message] // 如果没有,则进一步解析 if (!tokens) { tokens = parse(message) this._caches[message] = tokens } // 得到tokens之后进行编译 return compile(tokens, values) }}type Token = { type: 'text' | 'named' | 'list' | 'unknown', value: string}const RE_TOKEN_LIST_VALUE: RegExp = /^(\d)+/const RE_TOKEN_NAMED_VALUE: RegExp = /^(\w)+/// 分析相应的tokenexport function parse (format: string): Array
{ const tokens: Array
= [] let position: number = 0 let text: string = '' // 将字符串拆分成字符逐个解析 while (position < format.length) { // 获取每个字符 let char: string = format[position++] // 对于符号{,进行特殊处理 if (char === '{') { if (text) { tokens.push({ type: 'text', value: text }) } // 内部循环,直到找到对应的符号} text = '' let sub: string = '' char = format[position++] while (char !== '}') { sub += char char = format[position++] } const type = RE_TOKEN_LIST_VALUE.test(sub) ? 'list' : RE_TOKEN_NAMED_VALUE.test(sub) ? 'named' : 'unknown' tokens.push({ value: sub, type }) } else if (char === '%') { // when found rails i18n syntax, skip text capture if (format[(position)] !== '{') { text += char } } else { text += char } } // 最后生成对应的tokens text && tokens.push({ type: 'text', value: text }) return tokens}// 编译函数export function compile (tokens: Array
, values: Object | Array
): Array
{ const compiled: Array
= [] let index: number = 0 // 获取mode const mode: string = Array.isArray(values) ? 'list' : isObject(values) ? 'named' : 'unknown' if (mode === 'unknown') { return compiled } // 根据token的各种类型进行编译 while (index < tokens.length) { const token: Token = tokens[index] switch (token.type) { case 'text': compiled.push(token.value) break case 'list': compiled.push(values[parseInt(token.value, 10)]) break case 'named': // 如果是named,则将对应的value值push到complied数组中 if (mode === 'named') { compiled.push((values: any)[token.value]) } else { if (process.env.NODE_ENV !== 'production') { warn(`Type of token '${token.type}' and format of value '${mode}' don't match!`) } } break case 'unknown': if (process.env.NODE_ENV !== 'production') { warn(`Detect 'unknown' type of token!`) } break } index++ } return compiled}
  • mixin.js
/* @flow */import VueI18n from './index'import { isPlainObject, warn, merge } from './util'export default {  beforeCreate (): void {    const options: any = this.$options    options.i18n = options.i18n || (options.__i18n ? {} : null)    if (options.i18n) {      if (options.i18n instanceof VueI18n) {        // init locale messages via custom blocks        if (options.__i18n) {          try {            let localeMessages = {}            options.__i18n.forEach(resource => {              localeMessages = merge(localeMessages, JSON.parse(resource))            })            Object.keys(localeMessages).forEach((locale: Locale) => {              options.i18n.mergeLocaleMessage(locale, localeMessages[locale])            })          } catch (e) {            if (process.env.NODE_ENV !== 'production') {              warn(`Cannot parse locale messages via custom blocks.`, e)            }          }        }        this._i18n = options.i18n        this._i18nWatcher = this._i18n.watchI18nData()        this._i18n.subscribeDataChanging(this)        this._subscribing = true      } else if (isPlainObject(options.i18n)) {        // component local i18n        if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {          options.i18n.root = this.$root.$i18n          options.i18n.fallbackLocale = this.$root.$i18n.fallbackLocale          options.i18n.silentTranslationWarn = this.$root.$i18n.silentTranslationWarn        }        // init locale messages via custom blocks        if (options.__i18n) {          try {            let localeMessages = {}            options.__i18n.forEach(resource => {              localeMessages = merge(localeMessages, JSON.parse(resource))            })            options.i18n.messages = localeMessages          } catch (e) {            if (process.env.NODE_ENV !== 'production') {              warn(`Cannot parse locale messages via custom blocks.`, e)            }          }        }        this._i18n = new VueI18n(options.i18n)        this._i18nWatcher = this._i18n.watchI18nData()        this._i18n.subscribeDataChanging(this)        this._subscribing = true        if (options.i18n.sync === undefined || !!options.i18n.sync) {          this._localeWatcher = this.$i18n.watchLocale()        }      } else {        if (process.env.NODE_ENV !== 'production') {          warn(`Cannot be interpreted 'i18n' option.`)        }      }    } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {      // root i18n      this._i18n = this.$root.$i18n      this._i18n.subscribeDataChanging(this)      this._subscribing = true    } else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) {      // parent i18n      this._i18n = options.parent.$i18n      this._i18n.subscribeDataChanging(this)      this._subscribing = true    }  },  beforeDestroy (): void {    if (!this._i18n) { return }    if (this._subscribing) {      this._i18n.unsubscribeDataChanging(this)      delete this._subscribing    }    if (this._i18nWatcher) {      this._i18nWatcher()      delete this._i18nWatcher    }    if (this._localeWatcher) {      this._localeWatcher()      delete this._localeWatcher    }    this._i18n = null  }}
  • util.js
/* @flow *//** * utilites */export function warn (msg: string, err: ?Error): void {  if (typeof console !== 'undefined') {    console.warn('[vue-i18n] ' + msg)    /* istanbul ignore if */    if (err) {      console.warn(err.stack)    }  }}export function isObject (obj: mixed): boolean %checks {  return obj !== null && typeof obj === 'object'}const toString: Function = Object.prototype.toStringconst OBJECT_STRING: string = '[object Object]'// 判断是否为一个对象export function isPlainObject (obj: any): boolean {  return toString.call(obj) === OBJECT_STRING}// 判断是否为空export function isNull (val: mixed): boolean {  return val === null || val === undefined}// 解析参数export function parseArgs (...args: Array
): Object { let locale: ?string = null let params: mixed = null // 分析参数的长度,如果长度为1 if (args.length === 1) { // 如果是对象或是数组,则将该参数设置为params if (isObject(args[0]) || Array.isArray(args[0])) { params = args[0] } else if (typeof args[0] === 'string') { // 如果是字符串,则设置为locale,当做是语言类型 locale = args[0] } } else if (args.length === 2) { // 长度为2时,根据情况设置locale和params if (typeof args[0] === 'string') { locale = args[0] } /* istanbul ignore if */ if (isObject(args[1]) || Array.isArray(args[1])) { params = args[1] } } // 最后返回{ locale, params }对象 return { locale, params }}// 如果索引值大于1,则返回1function getOldChoiceIndexFixed (choice: number): number { return choice ? choice > 1 ? 1 : 0 : 1}// 获取索引的方法function getChoiceIndex (choice: number, choicesLength: number): number { choice = Math.abs(choice) // 如果长度等于2,则调用getOldChoiceIndexFixed if (choicesLength === 2) { return getOldChoiceIndexFixed(choice) } // 确保索引值不大于2,这个很令人费解啊 return choice ? Math.min(choice, 2) : 0}export function fetchChoice (message: string, choice: number): ?string { /* istanbul ignore if */ if (!message && typeof message !== 'string') { return null } // 将字符串分割为数组 const choices: Array
= message.split('|') // 获取索引 choice = getChoiceIndex(choice, choices.length) // 得到数组中特定索引的值 if (!choices[choice]) { return message } // 去掉空格 return choices[choice].trim()}// 利用JSON的api实现对象的深拷贝export function looseClone (obj: Object): Object { return JSON.parse(JSON.stringify(obj))}// 删除数组中的某一项export function remove (arr: Array
, item: any): Array
| void { if (arr.length) { const index = arr.indexOf(item) if (index > -1) { return arr.splice(index, 1) } }}const hasOwnProperty = Object.prototype.hasOwnPropertyexport function hasOwn (obj: Object | Array<*>, key: string): boolean { return hasOwnProperty.call(obj, key)}// 递归合并对象export function merge (target: Object): Object { const output = Object(target) for (let i = 1; i < arguments.length; i++) { const source = arguments[i] if (source !== undefined && source !== null) { let key for (key in source) { if (hasOwn(source, key)) { if (isObject(source[key])) { output[key] = merge(output[key], source[key]) } else { output[key] = source[key] } } } } } return output}// 松散判断是否相等export function looseEqual (a: any, b: any): boolean { // 先判断是否全等 if (a === b) { return true } const isObjectA: boolean = isObject(a) const isObjectB: boolean = isObject(b) // 如果两者都是对象 if (isObjectA && isObjectB) { try { const isArrayA: boolean = Array.isArray(a) const isArrayB: boolean = Array.isArray(b) // 如果两者都是数组 if (isArrayA && isArrayB) { // 如果长度相等,则递归对比数组中的每一项 return a.length === b.length && a.every((e: any, i: number): boolean => { // 递归调用looseEqual return looseEqual(e, b[i]) }) } else if (!isArrayA && !isArrayB) { // 如果不是数组,则当做对象来对比 const keysA: Array
= Object.keys(a) const keysB: Array
= Object.keys(b) // 如果key的数量相等,则递归对比每个key对应的值 return keysA.length === keysB.length && keysA.every((key: string): boolean => { // 递归调用looseEqual return looseEqual(a[key], b[key]) }) } else { /* istanbul ignore next */ return false } } catch (e) { /* istanbul ignore next */ return false } } else if (!isObjectA && !isObjectB) { // 如果不是对象,则强制转为字符串进行对比 return String(a) === String(b) } else { return false }}// 判断是否支持Intl.DateTimeFormat方法export const canUseDateTimeFormat: boolean = typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat !== 'undefined'// 判断是否支持Intl.NumberFormat方法export const canUseNumberFormat: boolean = typeof Intl !== 'undefined' && typeof Intl.NumberFormat !== 'undefined'
  • install.js
/* @flow *//** * utilites */export function warn (msg: string, err: ?Error): void {  if (typeof console !== 'undefined') {    console.warn('[vue-i18n] ' + msg)    /* istanbul ignore if */    if (err) {      console.warn(err.stack)    }  }}export function isObject (obj: mixed): boolean %checks {  return obj !== null && typeof obj === 'object'}const toString: Function = Object.prototype.toStringconst OBJECT_STRING: string = '[object Object]'// 判断是否为一个对象export function isPlainObject (obj: any): boolean {  return toString.call(obj) === OBJECT_STRING}// 判断是否为空export function isNull (val: mixed): boolean {  return val === null || val === undefined}// 解析参数export function parseArgs (...args: Array
): Object { let locale: ?string = null let params: mixed = null // 分析参数的长度,如果长度为1 if (args.length === 1) { // 如果是对象或是数组,则将该参数设置为params if (isObject(args[0]) || Array.isArray(args[0])) { params = args[0] } else if (typeof args[0] === 'string') { // 如果是字符串,则设置为locale,当做是语言类型 locale = args[0] } } else if (args.length === 2) { // 长度为2时,根据情况设置locale和params if (typeof args[0] === 'string') { locale = args[0] } /* istanbul ignore if */ if (isObject(args[1]) || Array.isArray(args[1])) { params = args[1] } } // 最后返回{ locale, params }对象 return { locale, params }}// 如果索引值大于1,则返回1function getOldChoiceIndexFixed (choice: number): number { return choice ? choice > 1 ? 1 : 0 : 1}// 获取索引的方法function getChoiceIndex (choice: number, choicesLength: number): number { choice = Math.abs(choice) // 如果长度等于2,则调用getOldChoiceIndexFixed if (choicesLength === 2) { return getOldChoiceIndexFixed(choice) } // 确保索引值不大于2,这个很令人费解啊 return choice ? Math.min(choice, 2) : 0}export function fetchChoice (message: string, choice: number): ?string { /* istanbul ignore if */ if (!message && typeof message !== 'string') { return null } // 将字符串分割为数组 const choices: Array
= message.split('|') // 获取索引 choice = getChoiceIndex(choice, choices.length) // 得到数组中特定索引的值 if (!choices[choice]) { return message } // 去掉空格 return choices[choice].trim()}// 利用JSON的api实现对象的深拷贝export function looseClone (obj: Object): Object { return JSON.parse(JSON.stringify(obj))}// 删除数组中的某一项export function remove (arr: Array
, item: any): Array
| void { if (arr.length) { const index = arr.indexOf(item) if (index > -1) { return arr.splice(index, 1) } }}const hasOwnProperty = Object.prototype.hasOwnPropertyexport function hasOwn (obj: Object | Array<*>, key: string): boolean { return hasOwnProperty.call(obj, key)}// 递归合并对象export function merge (target: Object): Object { const output = Object(target) for (let i = 1; i < arguments.length; i++) { const source = arguments[i] if (source !== undefined && source !== null) { let key for (key in source) { if (hasOwn(source, key)) { if (isObject(source[key])) { output[key] = merge(output[key], source[key]) } else { output[key] = source[key] } } } } } return output}// 松散判断是否相等export function looseEqual (a: any, b: any): boolean { // 先判断是否全等 if (a === b) { return true } const isObjectA: boolean = isObject(a) const isObjectB: boolean = isObject(b) // 如果两者都是对象 if (isObjectA && isObjectB) { try { const isArrayA: boolean = Array.isArray(a) const isArrayB: boolean = Array.isArray(b) // 如果两者都是数组 if (isArrayA && isArrayB) { // 如果长度相等,则递归对比数组中的每一项 return a.length === b.length && a.every((e: any, i: number): boolean => { // 递归调用looseEqual return looseEqual(e, b[i]) }) } else if (!isArrayA && !isArrayB) { // 如果不是数组,则当做对象来对比 const keysA: Array
= Object.keys(a) const keysB: Array
= Object.keys(b) // 如果key的数量相等,则递归对比每个key对应的值 return keysA.length === keysB.length && keysA.every((key: string): boolean => { // 递归调用looseEqual return looseEqual(a[key], b[key]) }) } else { /* istanbul ignore next */ return false } } catch (e) { /* istanbul ignore next */ return false } } else if (!isObjectA && !isObjectB) { // 如果不是对象,则强制转为字符串进行对比 return String(a) === String(b) } else { return false }}// 判断是否支持Intl.DateTimeFormat方法export const canUseDateTimeFormat: boolean = typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat !== 'undefined'// 判断是否支持Intl.NumberFormat方法export const canUseNumberFormat: boolean = typeof Intl !== 'undefined' && typeof Intl.NumberFormat !== 'undefined'

ok~今天就写到这,希望对大家有所帮助,也欢迎拍砖

转载地址:http://rqkwa.baihongyu.com/

你可能感兴趣的文章
iOS--OCR图片识别
查看>>
Handler和AsyncTask
查看>>
关于ios::sync_with_stdio(false);和 cin.tie(0)加速c++输入输出流
查看>>
浅析微信支付:统一下单接口
查看>>
网络对抗技术_实验一_网络侦查与网络扫描
查看>>
黑板模式分析
查看>>
释放Win8.1 WinSxS冗余更新,微软Dism来解决
查看>>
【BZOJ】2243 [SDOI2011]染色
查看>>
springboot集成springsession利用redis来实现session共享
查看>>
文件上传与下载总结
查看>>
【测试基础】测试用例的设计方法
查看>>
MySQL优化-》执行计划和常见索引
查看>>
ThinkPHP中通过URL重写隐藏应用的入口文件index.php的相关服务器的配置
查看>>
18、图片 & 多媒体
查看>>
第七周进度总结
查看>>
Android任务栈的运行规律
查看>>
批处理通用测试代码
查看>>
uva 10594 Data Flow
查看>>
POJ 3592 Instantaneous Transference
查看>>
redis数据类型(字符串)
查看>>