I18n.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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 translation = $('<textarea>').text((prefix + params[0] + postfix)).text();
  95. let args = Array.prototype.slice.call(params);
  96. args.unshift(prefix + '%s' + postfix);
  97. let clean = jsdom();
  98. $('body').html('<div id="i18n"></div>');
  99. args.unshift($('#i18n'));
  100. PrivateBin.I18n.translate.apply(this, args);
  101. const result = $('#i18n').text();
  102. PrivateBin.I18n.reset();
  103. clean();
  104. clean = jsdom();
  105. $('body').html('<div id="i18n"></div>');
  106. args[0] = $('#i18n');
  107. PrivateBin.I18n._.apply(this, args);
  108. const alias = $('#i18n').text();
  109. PrivateBin.I18n.reset();
  110. clean();
  111. return translation === result && translation === alias;
  112. }
  113. );
  114. jsc.property(
  115. 'replaces %s in strings with first given parameter into an element, encoding params only, when a link is part of the messageID inserted',
  116. 'string',
  117. '(small nearray) string',
  118. 'string',
  119. function (prefix, params, postfix) {
  120. prefix = prefix.replace(/%(s|d)/g, '%%').trim();
  121. params[0] = params[0].replace(/%(s|d)/g, '%%').trim();
  122. postfix = postfix.replace(/%(s|d)/g, '%%').trim();
  123. const translation = DOMPurify.sanitize(
  124. prefix + '<a href="' + params[0] + '"></a>' + postfix, {
  125. ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
  126. ALLOWED_TAGS: ['a', 'i', 'span', 'kbd'],
  127. ALLOWED_ATTR: ['href', 'id']
  128. }
  129. );
  130. let args = Array.prototype.slice.call(params);
  131. args.unshift(prefix + '<a href="%s"></a>' + postfix);
  132. let clean = jsdom();
  133. $('body').html('<div id="i18n"></div>');
  134. args.unshift($('#i18n'));
  135. PrivateBin.I18n.translate.apply(this, args);
  136. const result = $('#i18n').html();
  137. PrivateBin.I18n.reset();
  138. clean();
  139. clean = jsdom();
  140. $('body').html('<div id="i18n"></div>');
  141. args[0] = $('#i18n');
  142. PrivateBin.I18n._.apply(this, args);
  143. const alias = $('#i18n').html();
  144. PrivateBin.I18n.reset();
  145. clean();
  146. return translation === result && translation === alias;
  147. }
  148. );
  149. });
  150. describe('getPluralForm', function () {
  151. before(function () {
  152. PrivateBin.I18n.reset();
  153. });
  154. jsc.property(
  155. 'returns valid key for plural form',
  156. common.jscSupportedLanguages(),
  157. 'integer',
  158. function(language, n) {
  159. PrivateBin.I18n.reset(language);
  160. var result = PrivateBin.I18n.getPluralForm(n);
  161. // arabic seems to have the highest plural count with 6 forms
  162. return result >= 0 && result <= 5;
  163. }
  164. );
  165. });
  166. // loading of JSON via AJAX needs to be tested in the browser, this just mocks it
  167. // TODO: This needs to be tested using a browser.
  168. describe('loadTranslations', function () {
  169. this.timeout(30000);
  170. before(function () {
  171. PrivateBin.I18n.reset();
  172. });
  173. jsc.property(
  174. 'downloads and handles any supported language',
  175. common.jscSupportedLanguages(),
  176. function(language) {
  177. // cleanup
  178. var clean = jsdom('', {cookie: ['lang=en']});
  179. PrivateBin.I18n.reset('en');
  180. PrivateBin.I18n.loadTranslations();
  181. clean();
  182. // mock
  183. clean = jsdom('', {cookie: ['lang=' + language]});
  184. // eslint-disable-next-line global-require
  185. PrivateBin.I18n.reset(language, require('../../i18n/' + language + '.json'));
  186. var loadedLang = PrivateBin.I18n.getLanguage(),
  187. result = PrivateBin.I18n.translate('Never'),
  188. alias = PrivateBin.I18n._('Never');
  189. clean();
  190. return language === loadedLang && result === alias;
  191. }
  192. );
  193. jsc.property(
  194. 'should default to en',
  195. function() {
  196. var clean = jsdom('', {url: 'https://privatebin.net/'});
  197. // when navigator.userLanguage is undefined and no default language
  198. // is specified, it would throw an error
  199. [ 'language', 'userLanguage' ].forEach(function (key) {
  200. Object.defineProperty(navigator, key, {
  201. value: undefined,
  202. writeable: false
  203. });
  204. });
  205. PrivateBin.I18n.reset('en');
  206. PrivateBin.I18n.loadTranslations();
  207. var result = PrivateBin.I18n.translate('Never'),
  208. alias = PrivateBin.I18n._('Never');
  209. clean();
  210. return 'Never' === result && 'Never' === alias;
  211. }
  212. );
  213. });
  214. });