plural-form.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. /*
  5. * The code below is mostly is a slight modification of intl/locale/PluralForm.jsm that
  6. * removes dependencies on chrome privileged APIs. To make maintenance easier, this file
  7. * is kept as close as possible to the original in terms of implementation.
  8. * The modified methods here are
  9. * - makeGetter (remove code adding the caller name to the log)
  10. * - get ruleNum() (rely on LocalizationHelper instead of String.services)
  11. * - log() (rely on console.log)
  12. *
  13. * Disable eslint warnings to preserve original code style.
  14. */
  15. /* eslint-disable */
  16. /**
  17. * This module provides the PluralForm object which contains a method to figure
  18. * out which plural form of a word to use for a given number based on the
  19. * current localization. There is also a makeGetter method that creates a get
  20. * function for the desired plural rule. This is useful for extensions that
  21. * specify their own plural rule instead of relying on the browser default.
  22. * (I.e., the extension hasn't been localized to the browser's locale.)
  23. *
  24. * See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
  25. *
  26. * List of methods:
  27. *
  28. * string pluralForm
  29. * get(int aNum, string aWords)
  30. *
  31. * int numForms
  32. * numForms()
  33. *
  34. * [string pluralForm get(int aNum, string aWords), int numForms numForms()]
  35. * makeGetter(int aRuleNum)
  36. * Note: Basically, makeGetter returns 2 functions that do "get" and "numForm"
  37. */
  38. const {LocalizationHelper} = require("devtools/shared/l10n");
  39. const L10N = new LocalizationHelper("toolkit/locales/intl.properties");
  40. // These are the available plural functions that give the appropriate index
  41. // based on the plural rule number specified. The first element is the number
  42. // of plural forms and the second is the function to figure out the index.
  43. var gFunctions = [
  44. // 0: Chinese
  45. [1, (n) => 0],
  46. // 1: English
  47. [2, (n) => n!=1?1:0],
  48. // 2: French
  49. [2, (n) => n>1?1:0],
  50. // 3: Latvian
  51. [3, (n) => n%10==1&&n%100!=11?1:n!=0?2:0],
  52. // 4: Scottish Gaelic
  53. [4, (n) => n==1||n==11?0:n==2||n==12?1:n>0&&n<20?2:3],
  54. // 5: Romanian
  55. [3, (n) => n==1?0:n==0||n%100>0&&n%100<20?1:2],
  56. // 6: Lithuanian
  57. [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?2:1],
  58. // 7: Russian
  59. [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
  60. // 8: Slovak
  61. [3, (n) => n==1?0:n>=2&&n<=4?1:2],
  62. // 9: Polish
  63. [3, (n) => n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
  64. // 10: Slovenian
  65. [4, (n) => n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3],
  66. // 11: Irish Gaeilge
  67. [5, (n) => n==1?0:n==2?1:n>=3&&n<=6?2:n>=7&&n<=10?3:4],
  68. // 12: Arabic
  69. [6, (n) => n==0?5:n==1?0:n==2?1:n%100>=3&&n%100<=10?2:n%100>=11&&n%100<=99?3:4],
  70. // 13: Maltese
  71. [4, (n) => n==1?0:n==0||n%100>0&&n%100<=10?1:n%100>10&&n%100<20?2:3],
  72. // 14: Macedonian
  73. [3, (n) => n%10==1?0:n%10==2?1:2],
  74. // 15: Icelandic
  75. [2, (n) => n%10==1&&n%100!=11?0:1],
  76. // 16: Breton
  77. [5, (n) => n%10==1&&n%100!=11&&n%100!=71&&n%100!=91?0:n%10==2&&n%100!=12&&n%100!=72&&n%100!=92?1:(n%10==3||n%10==4||n%10==9)&&n%100!=13&&n%100!=14&&n%100!=19&&n%100!=73&&n%100!=74&&n%100!=79&&n%100!=93&&n%100!=94&&n%100!=99?2:n%1000000==0&&n!=0?3:4],
  78. ];
  79. this.PluralForm = {
  80. /**
  81. * Get the correct plural form of a word based on the number
  82. *
  83. * @param aNum
  84. * The number to decide which plural form to use
  85. * @param aWords
  86. * A semi-colon (;) separated string of words to pick the plural form
  87. * @return The appropriate plural form of the word
  88. */
  89. get get()
  90. {
  91. // This method will lazily load to avoid perf when it is first needed and
  92. // creates getPluralForm function. The function it creates is based on the
  93. // value of pluralRule specified in the intl stringbundle.
  94. // See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
  95. // Delete the getters to be overwritten
  96. delete PluralForm.numForms;
  97. delete PluralForm.get;
  98. // Make the plural form get function and set it as the default get
  99. [PluralForm.get, PluralForm.numForms] = PluralForm.makeGetter(PluralForm.ruleNum);
  100. return PluralForm.get;
  101. },
  102. /**
  103. * Create a pair of plural form functions for the given plural rule number.
  104. *
  105. * @param aRuleNum
  106. * The plural rule number to create functions
  107. * @return A pair: [function that gets the right plural form,
  108. * function that returns the number of plural forms]
  109. */
  110. makeGetter: function(aRuleNum)
  111. {
  112. // Default to "all plural" if the value is out of bounds or invalid
  113. if (aRuleNum < 0 || aRuleNum >= gFunctions.length || isNaN(aRuleNum)) {
  114. log(["Invalid rule number: ", aRuleNum, " -- defaulting to 0"]);
  115. aRuleNum = 0;
  116. }
  117. // Get the desired pluralRule function
  118. let [numForms, pluralFunc] = gFunctions[aRuleNum];
  119. // Return functions that give 1) the number of forms and 2) gets the right
  120. // plural form
  121. return [function(aNum, aWords) {
  122. // Figure out which index to use for the semi-colon separated words
  123. let index = pluralFunc(aNum ? Number(aNum) : 0);
  124. let words = aWords ? aWords.split(/;/) : [""];
  125. // Explicitly check bounds to avoid strict warnings
  126. let ret = index < words.length ? words[index] : undefined;
  127. // Check for array out of bounds or empty strings
  128. if ((ret == undefined) || (ret == "")) {
  129. // Display a message in the error console
  130. log(["Index #", index, " of '", aWords, "' for value ", aNum,
  131. " is invalid -- plural rule #", aRuleNum, ";"]);
  132. // Default to the first entry (which might be empty, but not undefined)
  133. ret = words[0];
  134. }
  135. return ret;
  136. }, () => numForms];
  137. },
  138. /**
  139. * Get the number of forms for the current plural rule
  140. *
  141. * @return The number of forms
  142. */
  143. get numForms()
  144. {
  145. // We lazily load numForms, so trigger the init logic with get()
  146. PluralForm.get();
  147. return PluralForm.numForms;
  148. },
  149. /**
  150. * Get the plural rule number from the intl stringbundle
  151. *
  152. * @return The plural rule number
  153. */
  154. get ruleNum()
  155. {
  156. try {
  157. return parseInt(L10N.getStr("pluralRule"), 10);
  158. } catch (e) {
  159. // Fallback to English if the pluralRule property is not available.
  160. return 1;
  161. }
  162. }
  163. };
  164. /**
  165. * Private helper function to log errors to the error console and command line
  166. *
  167. * @param aMsg
  168. * Error message to log or an array of strings to concat
  169. */
  170. function log(aMsg)
  171. {
  172. let msg = "plural-form.js: " + (aMsg.join ? aMsg.join("") : aMsg);
  173. console.log(msg + "\n");
  174. }
  175. exports.PluralForm = this.PluralForm;
  176. /* eslint-ensable */