i18n.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. const {defaultSettings} = require('../util/default.json');
  2. const {escapeText} = require('./util.js');
  3. const i18n = {
  4. en: require('./i18n/en.json'),
  5. bn: require('./i18n/bn.json'),
  6. de: require('./i18n/de.json'),
  7. es: require('./i18n/es.json'),
  8. fr: require('./i18n/fr.json'),
  9. hi: require('./i18n/hi.json'),
  10. it: require('./i18n/it.json'),
  11. ja: require('./i18n/ja.json'),
  12. ko: require('./i18n/ko.json'),
  13. nl: require('./i18n/nl.json'),
  14. pl: require('./i18n/pl.json'),
  15. pt: require('./i18n/pt-br.json'),
  16. 'pt-br': require('./i18n/pt-br.json'),
  17. ru: require('./i18n/ru.json'),
  18. th: require('./i18n/th.json'),
  19. tr: require('./i18n/tr.json'),
  20. zh: require('./i18n/zh-hans.json'),
  21. 'zh-hans': require('./i18n/zh-hans.json'),
  22. 'zh-hant': require('./i18n/zh-hant.json')
  23. };
  24. /**
  25. * A language.
  26. * @class
  27. */
  28. class Lang {
  29. /**
  30. * Creates a new language.
  31. * @param {String} [lang] - The language code.
  32. * @param {String} [namespace] - The namespace for the language.
  33. * @constructs Lang
  34. */
  35. constructor(lang = defaultSettings.lang, namespace = '') {
  36. if ( !( typeof lang === 'string' && lang in i18n ) ) lang = defaultSettings.lang;
  37. this.lang = lang;
  38. this.namespace = namespace;
  39. this.fallback = ( i18n?.[lang]?.fallback.slice() || [defaultSettings.lang] ).filter( fb => fb.trim() );
  40. }
  41. /**
  42. * Get a localized message.
  43. * @param {String} message - Name of the message.
  44. * @param {Boolean} escaped - If the message should be HTML escaped.
  45. * @param {(String|import('cheerio'))[]} args - Arguments for the message.
  46. * @returns {String}
  47. */
  48. get(message = '', escaped = false, ...args) {
  49. if ( this.namespace.length ) message = this.namespace + '.' + message;
  50. let keys = ( message.length ? message.split('.') : [] );
  51. let lang = this.lang;
  52. let text = i18n?.[lang];
  53. let fallback = 0;
  54. for (let n = 0; n < keys.length; n++) {
  55. if ( text ) {
  56. text = text?.[keys[n]];
  57. if ( typeof text === 'string' ) text = text.trim()
  58. }
  59. if ( !text ) {
  60. if ( fallback < this.fallback.length ) {
  61. lang = this.fallback[fallback];
  62. fallback++;
  63. text = i18n?.[lang];
  64. n = -1;
  65. }
  66. else {
  67. n = keys.length;
  68. }
  69. }
  70. }
  71. if ( typeof text === 'string' ) {
  72. if ( escaped ) text = escapeText(text);
  73. args.forEach( (arg, i) => {
  74. if ( escaped && typeof arg !== 'string' ) {
  75. text = text.replaceSave( new RegExp( `\\[([^\\]]+)\\]\\(\\$${i + 1}\\)`, 'g' ), (m, linkText) => {
  76. return arg.html(linkText);
  77. } );
  78. }
  79. text = text.replaceSave( new RegExp( `\\$${i + 1}`, 'g' ), arg );
  80. } );
  81. if ( text.includes( 'PLURAL:' ) ) text = text.replace( /{{\s*PLURAL:\s*[+-]?(\d+)\s*\|\s*([^\{\}]*?)\s*}}/g, (m, number, cases) => {
  82. return plural(lang, parseInt(number, 10), cases.split(/\s*\|\s*/));
  83. } );
  84. }
  85. return ( text || '⧼' + message + ( isDebug && args.length ? ': ' + args.join(', ') : '' ) + '⧽' );
  86. }
  87. }
  88. /**
  89. * Parse plural text.
  90. * @param {String} lang - The language code.
  91. * @param {Number} number - The amount.
  92. * @param {String[]} args - The possible text.
  93. * @returns {String}
  94. */
  95. function plural(lang, number, args) {
  96. // https://translatewiki.net/wiki/Plural/Mediawiki_plural_rules
  97. var text = args[args.length - 1];
  98. switch ( lang ) {
  99. case 'fr':
  100. case 'hi':
  101. if ( number <= 1 ) text = getArg(args, 0);
  102. else text = getArg(args, 1);
  103. break;
  104. case 'pl':
  105. if ( number === 1 ) text = getArg(args, 0);
  106. else if ( [2,3,4].includes( number % 10 ) && ![12,13,14].includes( number % 100 ) ) {
  107. text = getArg(args, 1);
  108. }
  109. else text = getArg(args, 2);
  110. break;
  111. case 'ru':
  112. if ( args.length > 2 ) {
  113. if ( number % 10 === 1 && number % 100 !== 11 ) text = getArg(args, 0);
  114. else if ( [2,3,4].includes( number % 10 ) && ![12,13,14].includes( number % 100 ) ) {
  115. text = getArg(args, 1);
  116. }
  117. else text = getArg(args, 2);
  118. }
  119. else {
  120. if ( number === 1 ) text = getArg(args, 0);
  121. else text = getArg(args, 1);
  122. }
  123. break;
  124. case 'bn':
  125. case 'de':
  126. case 'en':
  127. case 'es':
  128. case 'ja':
  129. case 'nl':
  130. case 'pt-br':
  131. case 'th':
  132. case 'tr':
  133. case 'ja':
  134. case 'ko':
  135. case 'zh-hans':
  136. case 'zh-hant':
  137. default:
  138. if ( number === 1 ) text = getArg(args, 0);
  139. else text = getArg(args, 1);
  140. }
  141. return text;
  142. }
  143. /**
  144. * Get text option.
  145. * @param {String[]} args - The list of options.
  146. * @param {Number} index - The preferred option.
  147. * @returns {String}
  148. */
  149. function getArg(args, index) {
  150. return ( args.length > index ? args[index] : args[args.length - 1] );
  151. }
  152. module.exports = Lang;