I18n.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. 'use strict';
  2. var common = require('../common');
  3. describe('I18n', function () {
  4. describe('translate', function () {
  5. this.timeout(30000);
  6. before(function () {
  7. PrivateBin.I18n.reset();
  8. });
  9. jsc.property(
  10. 'returns message ID unchanged if no translation found',
  11. 'string',
  12. function (messageId) {
  13. messageId = messageId.replace(/%(s|d)/g, '%%');
  14. var plurals = [messageId, messageId + 's'],
  15. fake = [messageId],
  16. result = PrivateBin.I18n.translate(messageId);
  17. PrivateBin.I18n.reset();
  18. var alias = PrivateBin.I18n._(messageId);
  19. PrivateBin.I18n.reset();
  20. var pluralResult = PrivateBin.I18n.translate(plurals);
  21. PrivateBin.I18n.reset();
  22. var pluralAlias = PrivateBin.I18n._(plurals);
  23. PrivateBin.I18n.reset();
  24. var fakeResult = PrivateBin.I18n.translate(fake);
  25. PrivateBin.I18n.reset();
  26. var fakeAlias = PrivateBin.I18n._(fake);
  27. PrivateBin.I18n.reset();
  28. if (messageId.indexOf('<a') === -1) {
  29. messageId = PrivateBin.Helper.htmlEntities(messageId);
  30. } else {
  31. messageId = DOMPurify.sanitize(
  32. messageId, {
  33. ALLOWED_TAGS: ['a', 'i', 'span'],
  34. ALLOWED_ATTR: ['href', 'id']
  35. }
  36. );
  37. }
  38. return messageId === result && messageId === alias &&
  39. messageId === pluralResult && messageId === pluralAlias &&
  40. messageId === fakeResult && messageId === fakeAlias;
  41. }
  42. );
  43. jsc.property(
  44. 'replaces %s in strings with first given parameter, encoding all, when no link is in the messageID',
  45. 'string',
  46. '(small nearray) string',
  47. 'string',
  48. function (prefix, params, postfix) {
  49. prefix = prefix.replace(/%(s|d)/g, '%%').replace(/<a/g, '');
  50. params[0] = params[0].replace(/%(s|d)/g, '%%');
  51. postfix = postfix.replace(/%(s|d)/g, '%%').replace(/<a/g, '');
  52. const translation = PrivateBin.Helper.htmlEntities(prefix + params[0] + postfix);
  53. params.unshift(prefix + '%s' + postfix);
  54. const result = PrivateBin.I18n.translate.apply(this, params);
  55. PrivateBin.I18n.reset();
  56. const alias = PrivateBin.I18n._.apply(this, params);
  57. PrivateBin.I18n.reset();
  58. return translation === result && translation === alias;
  59. }
  60. );
  61. jsc.property(
  62. 'replaces %s in strings with first given parameter, encoding params only, when a link is part of the messageID',
  63. 'string',
  64. '(small nearray) string',
  65. 'string',
  66. function (prefix, params, postfix) {
  67. prefix = prefix.replace(/%(s|d)/g, '%%');
  68. params[0] = params[0].replace(/%(s|d)/g, '%%');
  69. postfix = postfix.replace(/%(s|d)/g, '%%');
  70. const translation = DOMPurify.sanitize(
  71. prefix + '<a href="' + params[0] + '"></a>' + postfix, {
  72. ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
  73. ALLOWED_TAGS: ['a', 'i', 'span', 'kbd'],
  74. ALLOWED_ATTR: ['href', 'id']
  75. }
  76. );
  77. params.unshift(prefix + '<a href="%s"></a>' + postfix);
  78. const result = PrivateBin.I18n.translate.apply(this, params);
  79. PrivateBin.I18n.reset();
  80. const alias = PrivateBin.I18n._.apply(this, params);
  81. PrivateBin.I18n.reset();
  82. return translation === result && translation === alias;
  83. }
  84. );
  85. jsc.property(
  86. 'replaces %s in strings with first given parameter into an element, encoding all, when no link is in the messageID',
  87. 'string',
  88. '(small nearray) string',
  89. 'string',
  90. function (prefix, params, postfix) {
  91. prefix = prefix.replace(/%(s|d)/g, '%%').replace(/<a/g, '');
  92. params[0] = params[0].replace(/%(s|d)/g, '%%');
  93. postfix = postfix.replace(/%(s|d)/g, '%%').replace(/<a/g, '');
  94. const tempDiv = document.createElement('textarea');
  95. tempDiv.textContent = (prefix + params[0] + postfix);
  96. const translation = tempDiv.textContent;
  97. let args = Array.prototype.slice.call(params);
  98. args.unshift(prefix + '%s' + postfix);
  99. let clean = globalThis.cleanup();
  100. document.body.innerHTML = '<div id="i18n"></div>';
  101. const i18nElement = document.getElementById('i18n');
  102. args.unshift(i18nElement);
  103. PrivateBin.I18n.translate.apply(this, args);
  104. const result = i18nElement.textContent;
  105. PrivateBin.I18n.reset();
  106. clean();
  107. clean = globalThis.cleanup();
  108. document.body.innerHTML = '<div id="i18n"></div>';
  109. args[0] = document.getElementById('i18n');
  110. PrivateBin.I18n._.apply(this, args);
  111. const alias = document.getElementById('i18n').textContent;
  112. PrivateBin.I18n.reset();
  113. clean();
  114. return translation === result && translation === alias;
  115. }
  116. );
  117. jsc.property(
  118. 'replaces %s in strings with first given parameter into an element, encoding params only, when a link is part of the messageID inserted',
  119. 'string',
  120. '(small nearray) string',
  121. 'string',
  122. function (prefix, params, postfix) {
  123. prefix = prefix.replace(/%(s|d)/g, '%%').trim();
  124. params[0] = params[0].replace(/%(s|d)/g, '%%').trim();
  125. postfix = postfix.replace(/%(s|d)/g, '%%').trim();
  126. const translation = DOMPurify.sanitize(
  127. prefix + '<a href="' + params[0] + '"></a>' + postfix, {
  128. ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
  129. ALLOWED_TAGS: ['a', 'i', 'span', 'kbd'],
  130. ALLOWED_ATTR: ['href', 'id']
  131. }
  132. );
  133. let args = Array.prototype.slice.call(params);
  134. args.unshift(prefix + '<a href="%s"></a>' + postfix);
  135. let clean = globalThis.cleanup();
  136. document.body.innerHTML = '<div id="i18n"></div>';
  137. const i18nElement2 = document.getElementById('i18n');
  138. args.unshift(i18nElement2);
  139. PrivateBin.I18n.translate.apply(this, args);
  140. const result = i18nElement2.innerHTML;
  141. PrivateBin.I18n.reset();
  142. clean();
  143. clean = globalThis.cleanup();
  144. document.body.innerHTML = '<div id="i18n"></div>';
  145. args[0] = document.getElementById('i18n');
  146. PrivateBin.I18n._.apply(this, args);
  147. const alias = document.getElementById('i18n').innerHTML;
  148. PrivateBin.I18n.reset();
  149. clean();
  150. return translation === result && translation === alias;
  151. }
  152. );
  153. });
  154. describe('getPluralForm', function () {
  155. before(function () {
  156. PrivateBin.I18n.reset();
  157. });
  158. jsc.property(
  159. 'returns valid key for plural form',
  160. common.jscSupportedLanguages(),
  161. 'integer',
  162. function(language, n) {
  163. PrivateBin.I18n.reset(language);
  164. var result = PrivateBin.I18n.getPluralForm(n);
  165. // arabic seems to have the highest plural count with 6 forms
  166. return result >= 0 && result <= 5;
  167. }
  168. );
  169. });
  170. // loading of JSON via AJAX needs to be tested in the browser, this just mocks it
  171. // TODO: This needs to be tested using a browser.
  172. describe('loadTranslations', function () {
  173. this.timeout(30000);
  174. before(function () {
  175. PrivateBin.I18n.reset();
  176. });
  177. jsc.property(
  178. 'downloads and handles any supported language',
  179. common.jscSupportedLanguages(),
  180. function(language) {
  181. // cleanup
  182. var clean = globalThis.cleanup('', {cookie: ['lang=en']});
  183. PrivateBin.I18n.reset('en');
  184. PrivateBin.I18n.loadTranslations();
  185. clean();
  186. // mock
  187. clean = globalThis.cleanup('', {cookie: ['lang=' + language]});
  188. // eslint-disable-next-line global-require
  189. PrivateBin.I18n.reset(language, require('../../i18n/' + language + '.json'));
  190. var loadedLang = PrivateBin.I18n.getLanguage(),
  191. result = PrivateBin.I18n.translate('Never'),
  192. alias = PrivateBin.I18n._('Never');
  193. clean();
  194. return language === loadedLang && result === alias;
  195. }
  196. );
  197. jsc.property(
  198. 'should default to en',
  199. function() {
  200. var clean = globalThis.cleanup('', {url: 'https://privatebin.net/'});
  201. // when navigator.userLanguage is undefined and no default language
  202. // is specified, it would throw an error
  203. [ 'language', 'userLanguage' ].forEach(function (key) {
  204. Object.defineProperty(navigator, key, {
  205. value: undefined,
  206. writeable: false
  207. });
  208. });
  209. PrivateBin.I18n.reset('en');
  210. PrivateBin.I18n.loadTranslations();
  211. var result = PrivateBin.I18n.translate('Never'),
  212. alias = PrivateBin.I18n._('Never');
  213. clean();
  214. return 'Never' === result && 'Never' === alias;
  215. }
  216. );
  217. });
  218. });