Translator.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. const has = require('./hasProperty')
  2. /**
  3. * Translates strings with interpolation & pluralization support.
  4. * Extensible with custom dictionaries and pluralization functions.
  5. *
  6. * Borrows heavily from and inspired by Polyglot https://github.com/airbnb/polyglot.js,
  7. * basically a stripped-down version of it. Differences: pluralization functions are not hardcoded
  8. * and can be easily added among with dictionaries, nested objects are used for pluralization
  9. * as opposed to `||||` delimeter
  10. *
  11. * Usage example: `translator.translate('files_chosen', {smart_count: 3})`
  12. */
  13. module.exports = class Translator {
  14. /**
  15. * @param {object|Array<object>} locales - locale or list of locales.
  16. */
  17. constructor (locales) {
  18. this.locale = {
  19. strings: {},
  20. pluralize: function (n) {
  21. if (n === 1) {
  22. return 0
  23. }
  24. return 1
  25. }
  26. }
  27. if (Array.isArray(locales)) {
  28. locales.forEach((locale) => this._apply(locale))
  29. } else {
  30. this._apply(locales)
  31. }
  32. }
  33. _apply (locale) {
  34. if (!locale || !locale.strings) {
  35. return
  36. }
  37. const prevLocale = this.locale
  38. this.locale = Object.assign({}, prevLocale, {
  39. strings: Object.assign({}, prevLocale.strings, locale.strings)
  40. })
  41. this.locale.pluralize = locale.pluralize || prevLocale.pluralize
  42. }
  43. /**
  44. * Takes a string with placeholder variables like `%{smart_count} file selected`
  45. * and replaces it with values from options `{smart_count: 5}`
  46. *
  47. * @license https://github.com/airbnb/polyglot.js/blob/master/LICENSE
  48. * taken from https://github.com/airbnb/polyglot.js/blob/master/lib/polyglot.js#L299
  49. *
  50. * @param {string} phrase that needs interpolation, with placeholders
  51. * @param {object} options with values that will be used to replace placeholders
  52. * @returns {string} interpolated
  53. */
  54. interpolate (phrase, options) {
  55. const { split, replace } = String.prototype
  56. const dollarRegex = /\$/g
  57. const dollarBillsYall = '$$$$'
  58. let interpolated = [phrase]
  59. for (const arg in options) {
  60. if (arg !== '_' && has(options, arg)) {
  61. // Ensure replacement value is escaped to prevent special $-prefixed
  62. // regex replace tokens. the "$$$$" is needed because each "$" needs to
  63. // be escaped with "$" itself, and we need two in the resulting output.
  64. var replacement = options[arg]
  65. if (typeof replacement === 'string') {
  66. replacement = replace.call(options[arg], dollarRegex, dollarBillsYall)
  67. }
  68. // We create a new `RegExp` each time instead of using a more-efficient
  69. // string replace so that the same argument can be replaced multiple times
  70. // in the same phrase.
  71. interpolated = insertReplacement(interpolated, new RegExp('%\\{' + arg + '\\}', 'g'), replacement)
  72. }
  73. }
  74. return interpolated
  75. function insertReplacement (source, rx, replacement) {
  76. const newParts = []
  77. source.forEach((chunk) => {
  78. split.call(chunk, rx).forEach((raw, i, list) => {
  79. if (raw !== '') {
  80. newParts.push(raw)
  81. }
  82. // Interlace with the `replacement` value
  83. if (i < list.length - 1) {
  84. newParts.push(replacement)
  85. }
  86. })
  87. })
  88. return newParts
  89. }
  90. }
  91. /**
  92. * Public translate method
  93. *
  94. * @param {string} key
  95. * @param {object} options with values that will be used later to replace placeholders in string
  96. * @returns {string} translated (and interpolated)
  97. */
  98. translate (key, options) {
  99. return this.translateArray(key, options).join('')
  100. }
  101. /**
  102. * Get a translation and return the translated and interpolated parts as an array.
  103. *
  104. * @param {string} key
  105. * @param {object} options with values that will be used to replace placeholders
  106. * @returns {Array} The translated and interpolated parts, in order.
  107. */
  108. translateArray (key, options) {
  109. if (options && typeof options.smart_count !== 'undefined') {
  110. var plural = this.locale.pluralize(options.smart_count)
  111. return this.interpolate(this.locale.strings[key][plural], options)
  112. }
  113. return this.interpolate(this.locale.strings[key], options)
  114. }
  115. }