localization.js 3.14 KB
Newer Older
1 2
const _ = require('lodash')
const dotize = require('dotize')
3
const i18nMW = require('i18next-express-middleware')
4 5
const i18next = require('i18next')
const Promise = require('bluebird')
Nick's avatar
Nick committed
6 7 8
const fs = require('fs-extra')
const path = require('path')
const yaml = require('js-yaml')
9

10
/* global WIKI */
11 12 13

module.exports = {
  engine: null,
14
  namespaces: [],
15
  init() {
16
    this.namespaces = WIKI.data.localeNamespaces
17
    this.engine = i18next
18
    this.engine.init({
19 20 21 22
      load: 'languageOnly',
      ns: this.namespaces,
      defaultNS: 'common',
      saveMissing: false,
23
      lng: WIKI.config.lang.code,
24
      fallbackLng: 'en'
25
    })
NGPixel's avatar
NGPixel committed
26

27 28
    // Load current language + namespaces
    this.refreshNamespaces(true)
NGPixel's avatar
NGPixel committed
29

30 31
    return this
  },
Nick's avatar
Nick committed
32 33 34 35 36
  /**
   * Attach i18n middleware for Express
   *
   * @param {Object} app Express Instance
   */
37 38 39
  attachMiddleware (app) {
    app.use(i18nMW.handle(this.engine))
  },
Nick's avatar
Nick committed
40 41 42 43 44 45
  /**
   * Get all entries for a specific locale and namespace
   *
   * @param {String} locale Locale code
   * @param {String} namespace Namespace
   */
46
  async getByNamespace(locale, namespace) {
47 48 49 50 51 52 53 54 55 56 57 58
    if (this.engine.hasResourceBundle(locale, namespace)) {
      let data = this.engine.getResourceBundle(locale, namespace)
      return _.map(dotize.convert(data), (value, key) => {
        return {
          key,
          value
        }
      })
    } else {
      throw new Error('Invalid locale or namespace')
    }
  },
Nick's avatar
Nick committed
59 60 61 62 63 64
  /**
   * Load entries from the DB for a single locale
   *
   * @param {String} locale Locale code
   * @param {*} opts Additional options
   */
NGPixel's avatar
NGPixel committed
65
  async loadLocale(locale, opts = { silent: false }) {
66
    const res = await WIKI.models.locales.query().findOne('code', locale)
NGPixel's avatar
NGPixel committed
67 68 69 70 71 72 73 74 75 76
    if (res) {
      if (_.isPlainObject(res.strings)) {
        _.forOwn(res.strings, (data, ns) => {
          this.namespaces.push(ns)
          this.engine.addResourceBundle(locale, ns, data, true, true)
        })
      }
    } else if (!opts.silent) {
      throw new Error('No such locale in local store.')
    }
Nick's avatar
Nick committed
77

78
    // -> Load dev locale files if present
Nick's avatar
Nick committed
79 80
    if (WIKI.IS_DEBUG) {
      try {
81
        const devEntriesRaw = await fs.readFile(path.join(WIKI.SERVERPATH, `locales/${locale}.yml`), 'utf8')
Nick's avatar
Nick committed
82 83 84 85 86 87 88 89 90 91 92 93
        if (devEntriesRaw) {
          const devEntries = yaml.safeLoad(devEntriesRaw)
          _.forOwn(devEntries, (data, ns) => {
            this.namespaces.push(ns)
            this.engine.addResourceBundle(locale, ns, data, true, true)
          })
          WIKI.logger.info(`Loaded dev locales from ${locale}.yml`)
        }
      } catch (err) {
        // ignore
      }
    }
94
  },
Nick's avatar
Nick committed
95 96 97 98 99
  /**
   * Reload all namespaces for all active locales from the DB
   *
   * @param {Boolean} silent No error on fail
   */
100 101 102 103 104 105 106 107
  async refreshNamespaces (silent = false) {
    await this.loadLocale(WIKI.config.lang.code, { silent })
    if (WIKI.config.lang.namespacing) {
      for (let ns of WIKI.config.lang.namespaces) {
        await this.loadLocale(ns, { silent })
      }
    }
  },
Nick's avatar
Nick committed
108 109 110 111 112
  /**
   * Set the active locale
   *
   * @param {String} locale Locale code
   */
113
  async setCurrentLocale(locale) {
114
    await Promise.fromCallback(cb => {
115 116 117 118
      return this.engine.changeLanguage(locale, cb)
    })
  }
}