commit 49a892dd01e58b8039e9815b861629a6afdb1794
parent 158e32b20064646fbe0fba556968c8e061520cb9
Author: Eemeli Aro <eemeli@mozilla.com>
Date: Thu, 30 Oct 2025 10:57:47 +0000
Bug 1760013 - Inline plural rule selection into devtools/shared/plural-form.js. r=devtools-reviewers,nchevobbe
The messages depending on this should really be migrated to a better format, but this change at least reduces the burden on translators a little bit.
Differential Revision: https://phabricator.services.mozilla.com/D267415
Diffstat:
7 files changed, 145 insertions(+), 196 deletions(-)
diff --git a/devtools/client/shared/test-helpers/jest-fixtures/Services.js b/devtools/client/shared/test-helpers/jest-fixtures/Services.js
@@ -539,6 +539,9 @@ const Services = {
},
appinfo: "",
+ locale: {
+ appLocalesAsLangTags: ["en-US", "en"],
+ },
obs: { addObserver: () => {} },
strings: {
createBundle() {
diff --git a/devtools/shared/plural-form.js b/devtools/shared/plural-form.js
@@ -2,140 +2,24 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/*
- * The code below is mostly is a slight modification of the now removed
- * intl/locale/PluralForm.jsm that removes dependencies on chrome privileged
- * APIs. To make maintenance easier, this file is kept as close as possible to
- * the original in terms of implementation. The modified methods here are
- * - makeGetter (remove code adding the caller name to the log)
- * - get ruleNum() (rely on LocalizationHelper instead of String.services)
- * - log() (rely on console.log)
- *
- * Disable eslint warnings to preserve original code style.
- */
-
+// Disable eslint warnings to preserve original code style.
/* eslint-disable */
/**
* This module provides the PluralForm object which contains a method to figure
* out which plural form of a word to use for a given number based on the
- * current localization. There is also a makeGetter method that creates a get
- * function for the desired plural rule. This is useful for extensions that
- * specify their own plural rule instead of relying on the browser default.
- * (I.e., the extension hasn't been localized to the browser's locale.)
- *
- * See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
- *
- * List of methods:
- *
- * string pluralForm
- * get(int aNum, string aWords)
- *
- * int numForms
- * numForms()
- *
- * [string pluralForm get(int aNum, string aWords), int numForms numForms()]
- * makeGetter(int aRuleNum)
- * Note: Basically, makeGetter returns 2 functions that do "get" and "numForm"
+ * current localization.
*/
-const { LocalizationHelper } = require("resource://devtools/shared/l10n.js");
-const L10N = new LocalizationHelper("toolkit/locales/intl.properties");
-
-// These are the available plural functions that give the appropriate index
-// based on the plural rule number specified. The first element is the number
-// of plural forms and the second is the function to figure out the index.
-const gFunctions = [
- // 0: Chinese
- [1, (n) => 0],
- // 1: English
- [2, (n) => n!=1?1:0],
- // 2: French
- [2, (n) => n>1?1:0],
- // 3: Latvian
- [3, (n) => n%10==1&&n%100!=11?1:n%10==0?0:2],
- // 4: Scottish Gaelic
- [4, (n) => n==1||n==11?0:n==2||n==12?1:n>0&&n<20?2:3],
- // 5: Romanian
- [3, (n) => n==1?0:n==0||n%100>0&&n%100<20?1:2],
- // 6: Lithuanian
- [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?2:1],
- // 7: Russian
- [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
- // 8: Slovak
- [3, (n) => n==1?0:n>=2&&n<=4?1:2],
- // 9: Polish
- [3, (n) => n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
- // 10: Slovenian
- [4, (n) => n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3],
- // 11: Irish Gaeilge
- [5, (n) => n==1?0:n==2?1:n>=3&&n<=6?2:n>=7&&n<=10?3:4],
- // 12: Arabic
- [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],
- // 13: Maltese
- [4, (n) => n==1?0:n==0||n%100>0&&n%100<=10?1:n%100>10&&n%100<20?2:3],
- // 14: Unused
- [3, (n) => n%10==1?0:n%10==2?1:2],
- // 15: Icelandic, Macedonian
- [2, (n) => n%10==1&&n%100!=11?0:1],
- // 16: Breton
- [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],
- // 17: Shuar
- [2, (n) => n!=0?1:0],
- // 18: Welsh
- [6, (n) => n==0?0:n==1?1:n==2?2:n==3?3:n==6?4:5],
- // 19: Bosnian, Croatian, Serbian
- [3, (n) => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2],
-];
-
const PluralForm = {
- /**
- * Get the correct plural form of a word based on the number
- *
- * @param aNum
- * The number to decide which plural form to use
- * @param aWords
- * A semi-colon (;) separated string of words to pick the plural form
- * @return The appropriate plural form of the word
- */
- get get()
+ init()
{
- // This method will lazily load to avoid perf when it is first needed and
- // creates getPluralForm function. The function it creates is based on the
- // value of pluralRule specified in the intl stringbundle.
- // See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
-
- // Delete the getters to be overwritten
delete this.numForms;
delete this.get;
- // Make the plural form get function and set it as the default get
- [this.get, this.numForms] = this.makeGetter(this.ruleNum);
- return this.get;
- },
-
- /**
- * Create a pair of plural form functions for the given plural rule number.
- *
- * @param aRuleNum
- * The plural rule number to create functions
- * @return A pair: [function that gets the right plural form,
- * function that returns the number of plural forms]
- */
- makeGetter: function(aRuleNum)
- {
- // Default to "all plural" if the value is out of bounds or invalid
- if (aRuleNum < 0 || aRuleNum >= gFunctions.length || isNaN(aRuleNum)) {
- log(["Invalid rule number: ", aRuleNum, " -- defaulting to 0"]);
- aRuleNum = 0;
- }
-
- // Get the desired pluralRule function
- let [numForms, pluralFunc] = gFunctions[aRuleNum];
-
- // Return functions that give 1) the number of forms and 2) gets the right
- // plural form
- return [function(aNum, aWords) {
+ let [numForms, pluralFunc] = this.getPluralRule();
+ this.numForms = () => numForms;
+ this.get = (aNum, aWords) => {
// Figure out which index to use for the semi-colon separated words
let index = pluralFunc(aNum ? Number(aNum) : 0);
let words = aWords ? aWords.split(/;/) : [""];
@@ -145,16 +29,29 @@ const PluralForm = {
// Check for array out of bounds or empty strings
if ((ret == undefined) || (ret == "")) {
- // Display a message in the error console
- log(["Index #", index, " of '", aWords, "' for value ", aNum,
- " is invalid -- plural rule #", aRuleNum, ";"]);
+ console.warn(`plural-form.js: Index #${index} of '${aWords}' for value ${aNum} is invalid;\n`);
// Default to the first entry (which might be empty, but not undefined)
ret = words[0];
}
return ret;
- }, () => numForms];
+ };
+ },
+
+ /**
+ * Get the correct plural form of a word based on the number
+ *
+ * @param aNum
+ * The number to decide which plural form to use
+ * @param aWords
+ * A semi-colon (;) separated string of words to pick the plural form
+ * @return The appropriate plural form of the word
+ */
+ get get()
+ {
+ this.init();
+ return this.get;
},
/**
@@ -164,39 +61,92 @@ const PluralForm = {
*/
get numForms()
{
- // We lazily load numForms, so trigger the init logic with get()
- this.get();
+ this.init();
return this.numForms;
},
/**
- * Get the plural rule number from the intl stringbundle
+ * Selects the number of plural categories and the function for selecting between them.
+ *
+ * The default is to use the same plural rules as English, which has "one" and "other" categories.
+ * This is only used for a small number of devtools messages that have a custom format;
+ * Fluent plurals in general rely on Unicode Common Locale Data Repository data.
*
- * @return The plural rule number
+ * @return The available plural function that gives the appropriate index
+ * based on the plural rule number specified. The first element is the number
+ * of plural forms and the second is the function to figure out the index.
*/
- get ruleNum()
+ getPluralRule()
{
- try {
- return parseInt(L10N.getStr("pluralRule"), 10);
- } catch (e) {
- // Fallback to English if the pluralRule property is not available.
- return 1;
+ const appLocales = Services.locale.appLocalesAsLangTags;
+ const locale = new Intl.Locale(appLocales[0]);
+ switch (locale.language) {
+ case "bo":
+ case "id":
+ case "ja":
+ case "km":
+ case "ko":
+ case "lo":
+ case "meh":
+ case "ms":
+ case "my":
+ case "th":
+ case "vi":
+ case "wo":
+ case "zh":
+ return [1, (n) => 0];
+ case "bn":
+ case "fa":
+ case "fr":
+ case "gu":
+ case "hi":
+ case "oc":
+ case "pa":
+ return [2, (n) => n>1?1:0];
+ case "ltg":
+ case "lv":
+ return [3, (n) => n%10==1&&n%100!=11?1:n%10==0?0:2];
+ case "gd":
+ return [4, (n) => n==1||n==11?0:n==2||n==12?1:n>0&&n<20?2:3];
+ case "ro":
+ return [3, (n) => n==1?0:n==0||n%100>0&&n%100<20?1:2];
+ case "lt":
+ return [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?2:1];
+ case "be":
+ case "ru":
+ case "uk":
+ return [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2];
+ case "cs":
+ case "sk":
+ return [3, (n) => n==1?0:n>=2&&n<=4?1:2];
+ case "pl":
+ case "szl":
+ return [3, (n) => n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2];
+ case "dsb":
+ case "hsb":
+ case "sl":
+ return [4, (n) => n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3];
+ case "ga":
+ return [5, (n) => n==1?0:n==2?1:n>=3&&n<=6?2:n>=7&&n<=10?3:4];
+ case "ar":
+ return [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];
+ case "is":
+ case "mk":
+ return [4, (n) => n==1?0:n==0||n%100>0&&n%100<=10?1:n%100>10&&n%100<20?2:3];
+ case "br":
+ return [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];
+ case "cy":
+ return [6, (n) => n==0?0:n==1?1:n==2?2:n==3?3:n==6?4:5];
+ case "bs":
+ case "hr":
+ case "sr":
+ return [3, (n) => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2];
+ default:
+ return [2, (n) => n!=1?1:0];
}
}
};
-/**
- * Private helper function to log errors to the error console and command line
- *
- * @param aMsg
- * Error message to log or an array of strings to concat
- */
-function log(aMsg)
-{
- let msg = "plural-form.js: " + (aMsg.join ? aMsg.join("") : aMsg);
- console.log(msg + "\n");
-}
-
exports.PluralForm = PluralForm;
exports.get = PluralForm.get;
diff --git a/devtools/shared/tests/xpcshell/test_pluralForm-english.js b/devtools/shared/tests/xpcshell/test_pluralForm-english.js
@@ -5,9 +5,7 @@
"use strict";
/**
- * This unit test makes sure the plural form for Irish Gaeilge is working by
- * using the makeGetter method instead of using the default language (by
- * development), English.
+ * This unit test uses the default language (for development), English.
*/
const { PluralForm } = require("resource://devtools/shared/plural-form.js");
diff --git a/devtools/shared/tests/xpcshell/test_pluralForm-irish.js b/devtools/shared/tests/xpcshell/test_pluralForm-irish.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This unit test makes sure the plural form for Irish Gaelic is working.
+ */
+
+const { PluralForm } = require("resource://devtools/shared/plural-form.js");
+
+function run_test() {
+ const origAvLocales = Services.locale.availableLocales;
+ registerCleanupFunction(() => {
+ Services.locale.availableLocales = origAvLocales;
+ });
+
+ Services.locale.availableLocales = ["ga-IE", "en-US"];
+ Services.locale.requestedLocales = ["ga-IE"];
+ PluralForm.init();
+
+ // Irish has 5 plural forms
+ Assert.equal(5, PluralForm.numForms());
+
+ // I don't really know Irish, so I'll stick in some dummy text
+ const words = "is 1;is 2;is 3-6;is 7-10;everything else";
+
+ const test = function (text, low, high) {
+ for (let num = low; num <= high; num++) {
+ Assert.equal(text, PluralForm.get(num, words));
+ }
+ };
+
+ // Make sure for good inputs, things work as expected
+ test("everything else", 0, 0);
+ test("is 1", 1, 1);
+ test("is 2", 2, 2);
+ test("is 3-6", 3, 6);
+ test("is 7-10", 7, 10);
+ test("everything else", 11, 200);
+}
diff --git a/devtools/shared/tests/xpcshell/test_pluralForm-makeGetter.js b/devtools/shared/tests/xpcshell/test_pluralForm-makeGetter.js
@@ -1,38 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-/**
- * This unit test makes sure the plural form for Irish Gaeilge is working by
- * using the makeGetter method instead of using the default language (by
- * development), English.
- */
-
-const { PluralForm } = require("resource://devtools/shared/plural-form.js");
-
-function run_test() {
- // Irish is plural rule #11
- const [get, numForms] = PluralForm.makeGetter(11);
-
- // Irish has 5 plural forms
- Assert.equal(5, numForms());
-
- // I don't really know Irish, so I'll stick in some dummy text
- const words = "is 1;is 2;is 3-6;is 7-10;everything else";
-
- const test = function (text, low, high) {
- for (let num = low; num <= high; num++) {
- Assert.equal(text, get(num, words));
- }
- };
-
- // Make sure for good inputs, things work as expected
- test("everything else", 0, 0);
- test("is 1", 1, 1);
- test("is 2", 2, 2);
- test("is 3-6", 3, 6);
- test("is 7-10", 7, 10);
- test("everything else", 11, 200);
-}
diff --git a/devtools/shared/tests/xpcshell/xpcshell.toml b/devtools/shared/tests/xpcshell/xpcshell.toml
@@ -59,7 +59,7 @@ skip-if = [
["test_pluralForm-english.js"]
-["test_pluralForm-makeGetter.js"]
+["test_pluralForm-irish.js"]
["test_prettifyCSS.js"]
skip-if = [
diff --git a/toolkit/locales/en-US/chrome/global/intl.properties b/toolkit/locales/en-US/chrome/global/intl.properties
@@ -29,9 +29,3 @@ intl.accept_languages=en-US, en
# Set it to the value of one of the menuitems in the "selectLangs" menulist in
# http://searchfox.org/mozilla-central/source/browser/components/preferences/dialogs/fonts.xhtml
font.language.group=x-western
-
-# LOCALIZATION NOTE (pluralRule): Pick the appropriate plural rule for your
-# language. This will determine how many plural forms of a word you will need
-# to provide and in what order.
-# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
-pluralRule=1