国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

你想要的——vue-i18n源碼分析

Blackjun / 965人閱讀

大家好,今天給大家帶來的干貨是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 = install
VueI18n.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 = 0
const PUSH = 1
const INC_SUB_PATH_DEPTH = 2
const PUSH_SUB_PATH = 3

// states
const BEFORE_PATH = 0
const IN_PATH = 1
const BEFORE_IDENT = 2
const IN_IDENT = 3
const IN_SUB_PATH = 4
const IN_SINGLE_QUOTE = 5
const IN_DOUBLE_QUOTE = 6
const AFTER_PATH = 7
const ERROR = 8

const 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 | null
export 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)+/
// 分析相應的token
export 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.toString
const 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,則返回1
function 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.hasOwnProperty
export 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.toString
const 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,則返回1
function 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.hasOwnProperty
export 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://specialneedsforspecialkids.com/yun/88680.html

相關文章

  • 前端國際化之Vue-i18n源碼分析

    摘要:最近的工作當中有個任務是做國際化。下文有對的源碼進行分析。因為英文的閱讀方向也是從左到右,因此語言展示的方向不予考慮。服務端數據翻譯前端樣式的調整中文轉英文后部分文案過長圖片第三方插件地圖等中文轉英文后肯定會遇到文案過長的情況。 最近的工作當中有個任務是做國際化。這篇文章也是做個簡單的總結。 部分網站的當前解決的方案 不同語言對應不同的頁面。在本地開發的時候就分別打包輸出了不同語言版...

    不知名網友 評論0 收藏0
  • vue,使用vue-i18n實現國際化

    摘要:需求公司項目需要國際化,點擊按鈕切換中文英文安裝注入實例中,項目中實現調用和模板語法語言標識通過切換的值來實現語言切換中文語言包英文語言包最后對應語言包中文語言包首頁概覽公司概述財務報表更多附錄主要財務指標對比分析新聞事件檔案 需求 公司項目需要國際化,點擊按鈕切換中文/英文 1、安裝 npm install vue-i18n --save 2、注入 vue 實例中,項目中實現調用 ...

    jsummer 評論0 收藏0
  • Vue國際化處理 vue-i18n 以及項目自動切換中英文

    摘要:直接上預覽鏈接國際化處理以及項目自動切換中英文環境搭建命令進入項目目錄,執行以下命令安裝國際化插件項目增加國際化翻譯文件在項目的下添加文件夾增加中文翻譯文件以及英文翻譯文件,里面分別存儲項目中需要翻譯的信息。 0. 直接上 預覽鏈接 Vue國際化處理 vue-i18n 以及項目自動切換中英文 1. 環境搭建 命令進入項目目錄,執行以下命令安裝vue 國際化插件vue-i18n...

    wangtdgoodluck 評論0 收藏0
  • 如何讓一個vue項目支持多語言(vue-i18n

    摘要:引入是一個插件,主要作用就是讓項目支持國際化多語言。所以新建一個文件夾,存放所有跟多語言相關的代碼。目前包含三個文件。全局搜索發現一共有多個。 這兩天手頭的一個任務是給一個五六年的老項目添加多語言。這個項目龐大且復雜,早期是用jQuery實現的,兩年前引入Vue并逐漸用組件替換了之前的Mustache風格模板。要添加多語言,不可避免存在很多文本替換的工作,這么龐雜的一個項目,怎么才能使...

    wuyumin 評論0 收藏0
  • vue-i18n結合Element-ui配置

    摘要:官網已經做了詳細介紹,這里依葫蘆畫瓢跟著實現一下為了實現插件的多語言切換按照如上把國際化文件都整合到一起,避免中大段引入相關代碼。 使用方法: 在配合 Element-UI 一起使用時,會有2個問題: ####(1)、頁面刷新后,通過按鈕切換的語言還原成了最初的語言,無法保存 ####(2)、框架內部自帶的提示文字無法更改,比如像時間選擇框內部中的提示文字 關于第一個問題,可以在初始化...

    孫淑建 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<