All files index.js

100% Statements 32/32
92.86% Branches 13/14
100% Functions 9/9
100% Lines 31/31
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118                  1x               12x 12x 12x       10x   10x 5x 5x               2x             14x     14x   14x 28x   2x       14x                         14x 14x 2x   12x   12x 8x 1x       7x                         8x   7x 1x     6x 6x   6x                       2x 2x      
// @flow
import _ from 'lodash'
import numeral from 'numeral'
import plural from 'plurals-cldr'
import numeralLanguages from './numeralLanguages'
 
type Translations = { [key: string]: any }
type Locales = Array<string>
 
const DEFAULT_LOCALE = 'en'
 
export default class I18n {
  translations: Translations
  supportedLocales: Locales
  locale: string
 
  constructor (translations: Translations, supportedLocales: Locales) {
    this.translations = translations
    this.supportedLocales = supportedLocales
    this.locale = 'en'
  }
 
  setLocale (locale: string): void {
    this.locale = locale
 
    if (numeralLanguages[locale]) {
      numeral.locales[locale] = numeralLanguages[locale]
      numeral.locale(locale)
    }
  }
 
  /**
   * Leverages `numeral` for number formatting
   */
  formatNumber (format: string, number: number): string {
    return numeral(number).format(format)
  }
 
  /**
   * Retrieves a key from the translations object.
   */
  getKey (path: string): mixed | string {
    const result = _.at(this.translations[this.locale], path)[0] ||
      _.at(this.translations[DEFAULT_LOCALE], path)[0]
 
    Eif (process.env.NODE_ENV === 'development') {
      // We check that the translation exists in all current supportedLocales!
      this.supportedLocales.forEach(locale => {
        if (_.at(this.translations[locale], path)[0]) return
 
        console.warn(`Key not found "${path}" with the locale "${locale}"`)
      })
    }
 
    return result
  }
 
  /**
   * Translate a key
   * Interpolate values with %{name}
   *
   * Examples:
   *
   * t('foo.bar') => 'Bar'
   * t('zemba.fleiba', {num: 3 }) => 'Zemba 3 Fleiba!'
   */
  t (path: string, opts?: { [key: string]: mixed }): string {
    const value = this.getKey(path)
    if (typeof value !== 'string') {
      throw new Error(`Key "${path}"is not a leaf`)
    }
    const MATCH = /%\{([^}]+)\}/g
 
    return value.replace(MATCH, (match, subst) => {
      if (!opts) {
        throw new Error(
          `Translation "${path}" has a missing interpolation value "${subst}"`
        )
      }
      return _.escape(String(opts[subst]))
    })
  }
 
  /**
   * Translate singular o plural copies
   * Options must contain `count`
   *
   * Examples:
   *
   * tp('beer', { count: 3 }) => '3 beers'
   */
  tp (path: string, opts: { [key: string]: mixed }): string {
    const num = opts.count
 
    if (typeof num !== 'number') {
      throw new Error('You must have a `count` property')
    }
 
    const form = plural(this.locale, num)
    const pluralPath = `${path}.${form}`
 
    return this.t(pluralPath, opts)
  }
 
  /**
   * Conditional copies, made easy.
   * Provide a path and then options, like classNames
   *
   * Example:
   *
   *   tx('fleiba.zemba', user.get('role'))
   */
  tx (path: string, variable: string, opts?: { [key: string]: mixed }): string {
    const newPath = `${path}.${variable}`
    return this.t(newPath, opts)
  }
}