i18n.js 3.8 KB

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