You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

127 lines
3.3 KiB

  1. const _ = require('lodash')
  2. const dotize = require('dotize')
  3. const i18nMW = require('i18next-express-middleware')
  4. const i18next = require('i18next')
  5. const Promise = require('bluebird')
  6. const fs = require('fs-extra')
  7. const path = require('path')
  8. const yaml = require('js-yaml')
  9. /* global WIKI */
  10. module.exports = {
  11. engine: null,
  12. namespaces: [],
  13. init() {
  14. this.namespaces = WIKI.data.localeNamespaces
  15. this.engine = i18next
  16. this.engine.init({
  17. load: 'languageOnly',
  18. ns: this.namespaces,
  19. defaultNS: 'common',
  20. saveMissing: false,
  21. lng: WIKI.config.lang.code,
  22. fallbackLng: 'en'
  23. })
  24. // Load current language + namespaces
  25. this.refreshNamespaces(true)
  26. // Listen for localization events
  27. WIKI.events.on('localization', (action) => {
  28. switch (action) {
  29. case 'reload':
  30. this.refreshNamespaces()
  31. break
  32. }
  33. })
  34. return this
  35. },
  36. /**
  37. * Attach i18n middleware for Express
  38. *
  39. * @param {Object} app Express Instance
  40. */
  41. attachMiddleware (app) {
  42. app.use(i18nMW.handle(this.engine))
  43. },
  44. /**
  45. * Get all entries for a specific locale and namespace
  46. *
  47. * @param {String} locale Locale code
  48. * @param {String} namespace Namespace
  49. */
  50. async getByNamespace(locale, namespace) {
  51. if (this.engine.hasResourceBundle(locale, namespace)) {
  52. let data = this.engine.getResourceBundle(locale, namespace)
  53. return _.map(dotize.convert(data), (value, key) => {
  54. return {
  55. key,
  56. value
  57. }
  58. })
  59. } else {
  60. throw new Error('Invalid locale or namespace')
  61. }
  62. },
  63. /**
  64. * Load entries from the DB for a single locale
  65. *
  66. * @param {String} locale Locale code
  67. * @param {*} opts Additional options
  68. */
  69. async loadLocale(locale, opts = { silent: false }) {
  70. const res = await WIKI.models.locales.query().findOne('code', locale)
  71. if (res) {
  72. if (_.isPlainObject(res.strings)) {
  73. _.forOwn(res.strings, (data, ns) => {
  74. this.namespaces.push(ns)
  75. this.engine.addResourceBundle(locale, ns, data, true, true)
  76. })
  77. }
  78. } else if (!opts.silent) {
  79. throw new Error('No such locale in local store.')
  80. }
  81. // -> Load dev locale files if present
  82. if (WIKI.IS_DEBUG) {
  83. try {
  84. const devEntriesRaw = await fs.readFileAsync(path.join(WIKI.SERVERPATH, `locales/${locale}.yml`), 'utf8')
  85. if (devEntriesRaw) {
  86. const devEntries = yaml.safeLoad(devEntriesRaw)
  87. _.forOwn(devEntries, (data, ns) => {
  88. this.namespaces.push(ns)
  89. this.engine.addResourceBundle(locale, ns, data, true, true)
  90. })
  91. WIKI.logger.info(`Loaded dev locales from ${locale}.yml`)
  92. }
  93. } catch (err) {
  94. // ignore
  95. }
  96. }
  97. },
  98. /**
  99. * Reload all namespaces for all active locales from the DB
  100. *
  101. * @param {Boolean} silent No error on fail
  102. */
  103. async refreshNamespaces (silent = false) {
  104. await this.loadLocale(WIKI.config.lang.code, { silent })
  105. if (WIKI.config.lang.namespacing) {
  106. for (let ns of WIKI.config.lang.namespaces) {
  107. await this.loadLocale(ns, { silent })
  108. }
  109. }
  110. },
  111. /**
  112. * Set the active locale
  113. *
  114. * @param {String} locale Locale code
  115. */
  116. async setCurrentLocale(locale) {
  117. await Promise.fromCallback(cb => {
  118. return this.engine.changeLanguage(locale, cb)
  119. })
  120. }
  121. }