Translator.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. "use strict";
  2. function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
  3. var id = 0;
  4. function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
  5. const has = require("./hasProperty.js");
  6. function insertReplacement(source, rx, replacement) {
  7. const newParts = [];
  8. source.forEach(chunk => {
  9. // When the source contains multiple placeholders for interpolation,
  10. // we should ignore chunks that are not strings, because those
  11. // can be JSX objects and will be otherwise incorrectly turned into strings.
  12. // Without this condition we’d get this: [object Object] hello [object Object] my <button>
  13. if (typeof chunk !== 'string') {
  14. return newParts.push(chunk);
  15. }
  16. return rx[Symbol.split](chunk).forEach((raw, i, list) => {
  17. if (raw !== '') {
  18. newParts.push(raw);
  19. } // Interlace with the `replacement` value
  20. if (i < list.length - 1) {
  21. newParts.push(replacement);
  22. }
  23. });
  24. });
  25. return newParts;
  26. }
  27. /**
  28. * Takes a string with placeholder variables like `%{smart_count} file selected`
  29. * and replaces it with values from options `{smart_count: 5}`
  30. *
  31. * @license https://github.com/airbnb/polyglot.js/blob/master/LICENSE
  32. * taken from https://github.com/airbnb/polyglot.js/blob/master/lib/polyglot.js#L299
  33. *
  34. * @param {string} phrase that needs interpolation, with placeholders
  35. * @param {object} options with values that will be used to replace placeholders
  36. * @returns {any[]} interpolated
  37. */
  38. function interpolate(phrase, options) {
  39. const dollarRegex = /\$/g;
  40. const dollarBillsYall = '$$$$';
  41. let interpolated = [phrase];
  42. if (options == null) return interpolated;
  43. for (const arg of Object.keys(options)) {
  44. if (arg !== '_') {
  45. // Ensure replacement value is escaped to prevent special $-prefixed
  46. // regex replace tokens. the "$$$$" is needed because each "$" needs to
  47. // be escaped with "$" itself, and we need two in the resulting output.
  48. let replacement = options[arg];
  49. if (typeof replacement === 'string') {
  50. replacement = dollarRegex[Symbol.replace](replacement, dollarBillsYall);
  51. } // We create a new `RegExp` each time instead of using a more-efficient
  52. // string replace so that the same argument can be replaced multiple times
  53. // in the same phrase.
  54. interpolated = insertReplacement(interpolated, new RegExp(`%\\{${arg}\\}`, 'g'), replacement);
  55. }
  56. }
  57. return interpolated;
  58. }
  59. /**
  60. * Translates strings with interpolation & pluralization support.
  61. * Extensible with custom dictionaries and pluralization functions.
  62. *
  63. * Borrows heavily from and inspired by Polyglot https://github.com/airbnb/polyglot.js,
  64. * basically a stripped-down version of it. Differences: pluralization functions are not hardcoded
  65. * and can be easily added among with dictionaries, nested objects are used for pluralization
  66. * as opposed to `||||` delimeter
  67. *
  68. * Usage example: `translator.translate('files_chosen', {smart_count: 3})`
  69. */
  70. var _apply = /*#__PURE__*/_classPrivateFieldLooseKey("apply");
  71. class Translator {
  72. /**
  73. * @param {object|Array<object>} locales - locale or list of locales.
  74. */
  75. constructor(locales) {
  76. Object.defineProperty(this, _apply, {
  77. value: _apply2
  78. });
  79. this.locale = {
  80. strings: {},
  81. pluralize(n) {
  82. if (n === 1) {
  83. return 0;
  84. }
  85. return 1;
  86. }
  87. };
  88. if (Array.isArray(locales)) {
  89. locales.forEach(_classPrivateFieldLooseBase(this, _apply)[_apply], this);
  90. } else {
  91. _classPrivateFieldLooseBase(this, _apply)[_apply](locales);
  92. }
  93. }
  94. /**
  95. * Public translate method
  96. *
  97. * @param {string} key
  98. * @param {object} options with values that will be used later to replace placeholders in string
  99. * @returns {string} translated (and interpolated)
  100. */
  101. translate(key, options) {
  102. return this.translateArray(key, options).join('');
  103. }
  104. /**
  105. * Get a translation and return the translated and interpolated parts as an array.
  106. *
  107. * @param {string} key
  108. * @param {object} options with values that will be used to replace placeholders
  109. * @returns {Array} The translated and interpolated parts, in order.
  110. */
  111. translateArray(key, options) {
  112. if (!has(this.locale.strings, key)) {
  113. throw new Error(`missing string: ${key}`);
  114. }
  115. const string = this.locale.strings[key];
  116. const hasPluralForms = typeof string === 'object';
  117. if (hasPluralForms) {
  118. if (options && typeof options.smart_count !== 'undefined') {
  119. const plural = this.locale.pluralize(options.smart_count);
  120. return interpolate(string[plural], options);
  121. }
  122. throw new Error('Attempted to use a string with plural forms, but no value was given for %{smart_count}');
  123. }
  124. return interpolate(string, options);
  125. }
  126. }
  127. function _apply2(locale) {
  128. if (!(locale != null && locale.strings)) {
  129. return;
  130. }
  131. const prevLocale = this.locale;
  132. this.locale = { ...prevLocale,
  133. strings: { ...prevLocale.strings,
  134. ...locale.strings
  135. }
  136. };
  137. this.locale.pluralize = locale.pluralize || prevLocale.pluralize;
  138. }
  139. module.exports = Translator;