1
0
Fork 0
firefox/toolkit/components/mozintl/mozIntl.sys.mjs
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

1110 lines
21 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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/. */
const mozIntlHelper = Cc["@mozilla.org/mozintlhelper;1"].getService(
Ci.mozIMozIntlHelper
);
const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
Ci.mozIOSPreferences
);
/**
* RegExp used to parse variant subtags from a BCP47 language tag.
* For example: ca-valencia
*/
const variantSubtagsMatch = /(?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))+$/;
function getDateTimePatternStyle(option) {
switch (option) {
case "full":
return osPrefs.dateTimeFormatStyleFull;
case "long":
return osPrefs.dateTimeFormatStyleLong;
case "medium":
return osPrefs.dateTimeFormatStyleMedium;
case "short":
return osPrefs.dateTimeFormatStyleShort;
default:
return osPrefs.dateTimeFormatStyleNone;
}
}
/**
* Number of milliseconds in other time units.
*
* This is used by relative time format best unit
* calculations.
*/
const second = 1e3;
const minute = 6e4;
const hour = 36e5;
const day = 864e5;
/**
* Use by RelativeTimeFormat.
*
* Allows for defining a cached getter to perform
* calculations only once.
*
* @param {Object} obj - Object to place the getter on.
* @param {String} prop - Name of the property.
* @param {Function} get - Function that will be used as a getter.
*/
function defineCachedGetter(obj, prop, get) {
defineGetter(obj, prop, function () {
if (!this._[prop]) {
this._[prop] = get.call(this);
}
return this._[prop];
});
}
/**
* Used by RelativeTimeFormat.
*
* Defines a getter on an object
*
* @param {Object} obj - Object to place the getter on.
* @param {String} prop - Name of the property.
* @param {Function} get - Function that will be used as a getter.
*/
function defineGetter(obj, prop, get) {
Object.defineProperty(obj, prop, { get, enumerable: true });
}
/**
* Used by RelativeTimeFormat.
*
* Allows for calculation of the beginning of
* a period for discrete distances.
*
* @param {Date} date - Date of which we're looking to find a start of.
* @param {String} unit - Period to calculate the start of.
*
* @returns {Date}
*/
function startOf(date, unit) {
date = new Date(date.getTime());
switch (unit) {
case "year":
date.setMonth(0);
// falls through
case "month":
date.setDate(1);
// falls through
case "day":
date.setHours(0);
// falls through
case "hour":
date.setMinutes(0);
// falls through
case "minute":
date.setSeconds(0);
// falls through
case "second":
date.setMilliseconds(0);
}
return date;
}
/**
* Used by RelativeTimeFormat.
*
* Calculates the best fit unit to use for an absolute diff distance based
* on thresholds.
*
* @param {Object} absDiff - Object with absolute diff per unit calculated.
*
* @returns {String}
*/
function bestFit(absDiff) {
switch (true) {
case absDiff.years > 0 && absDiff.months > threshold.month:
return "year";
case absDiff.months > 0 && absDiff.weeks > threshold.week:
return "month";
case absDiff.weeks > 0 && absDiff.days > threshold.day:
return "week";
case absDiff.days > 0 && absDiff.hours > threshold.hour:
return "day";
case absDiff.hours > 0 && absDiff.minutes > threshold.minute:
return "hour";
case absDiff.minutes > 0 && absDiff.seconds > threshold.second:
return "minute";
default:
return "second";
}
}
/**
* Used by RelativeTimeFormat.
*
* Thresholds to use for calculating the best unit for relative time fromatting.
*/
const threshold = {
month: 11, // at least 11 months before using year.
week: 3, // at least 3 weeks before using month.
day: 6, // at least 6 days before using week.
hour: 6, // at least 6 hours before using day.
minute: 59, // at least 59 minutes before using hour.
second: 59, // at least 59 seconds before using minute.
};
/**
* Notice: If you're updating this list, you should also
* update the list in
* languageNames.ftl and regionNames.ftl.
*/
const availableLocaleDisplayNames = {
region: new Set([
"ad",
"ae",
"af",
"ag",
"ai",
"al",
"am",
"ao",
"aq",
"ar",
"as",
"at",
"au",
"aw",
"az",
"ba",
"bb",
"bd",
"be",
"bf",
"bg",
"bh",
"bi",
"bj",
"bl",
"bm",
"bn",
"bo",
"bq",
"br",
"bs",
"bt",
"bv",
"bw",
"by",
"bz",
"ca",
"cc",
"cd",
"cf",
"cg",
"ch",
"ci",
"ck",
"cl",
"cm",
"cn",
"co",
"cp",
"cr",
"cu",
"cv",
"cw",
"cx",
"cy",
"cz",
"de",
"dg",
"dj",
"dk",
"dm",
"do",
"dz",
"ec",
"ee",
"eg",
"eh",
"er",
"es",
"et",
"fi",
"fj",
"fk",
"fm",
"fo",
"fr",
"ga",
"gb",
"gd",
"ge",
"gf",
"gg",
"gh",
"gi",
"gl",
"gm",
"gn",
"gp",
"gq",
"gr",
"gs",
"gt",
"gu",
"gw",
"gy",
"hk",
"hm",
"hn",
"hr",
"ht",
"hu",
"id",
"ie",
"il",
"im",
"in",
"io",
"iq",
"ir",
"is",
"it",
"je",
"jm",
"jo",
"jp",
"ke",
"kg",
"kh",
"ki",
"km",
"kn",
"kp",
"kr",
"kw",
"ky",
"kz",
"la",
"lb",
"lc",
"li",
"lk",
"lr",
"ls",
"lt",
"lu",
"lv",
"ly",
"ma",
"mc",
"md",
"me",
"mf",
"mg",
"mh",
"mk",
"ml",
"mm",
"mn",
"mo",
"mp",
"mq",
"mr",
"ms",
"mt",
"mu",
"mv",
"mw",
"mx",
"my",
"mz",
"na",
"nc",
"ne",
"nf",
"ng",
"ni",
"nl",
"no",
"np",
"nr",
"nu",
"nz",
"om",
"pa",
"pe",
"pf",
"pg",
"ph",
"pk",
"pl",
"pm",
"pn",
"pr",
"pt",
"pw",
"py",
"qa",
"qm",
"qs",
"qu",
"qw",
"qx",
"qz",
"re",
"ro",
"rs",
"ru",
"rw",
"sa",
"sb",
"sc",
"sd",
"se",
"sg",
"sh",
"si",
"sk",
"sl",
"sm",
"sn",
"so",
"sr",
"ss",
"st",
"sv",
"sx",
"sy",
"sz",
"tc",
"td",
"tf",
"tg",
"th",
"tj",
"tk",
"tl",
"tm",
"tn",
"to",
"tr",
"tt",
"tv",
"tw",
"tz",
"ua",
"ug",
"us",
"uy",
"uz",
"va",
"vc",
"ve",
"vg",
"vi",
"vn",
"vu",
"wf",
"ws",
"xa",
"xb",
"xc",
"xd",
"xe",
"xg",
"xh",
"xj",
"xk",
"xl",
"xm",
"xp",
"xq",
"xr",
"xs",
"xt",
"xu",
"xv",
"xw",
"ye",
"yt",
"za",
"zm",
"zw",
]),
language: new Set([
"aa",
"ab",
"ach",
"ae",
"af",
"ak",
"am",
"an",
"ar",
"as",
"ast",
"av",
"ay",
"az",
"ba",
"be",
"bg",
"bh",
"bi",
"bm",
"bn",
"bo",
"br",
"bs",
"ca",
"cak",
"ce",
"ch",
"co",
"cr",
"crh",
"cs",
"csb",
"cu",
"cv",
"cy",
"da",
"de",
"dsb",
"dv",
"dz",
"ee",
"el",
"en",
"eo",
"es",
"et",
"eu",
"fa",
"ff",
"fi",
"fj",
"fo",
"fr",
"fur",
"fy",
"ga",
"gd",
"gl",
"gn",
"gu",
"gv",
"ha",
"haw",
"he",
"hi",
"hil",
"ho",
"hr",
"hsb",
"ht",
"hu",
"hy",
"hz",
"ia",
"id",
"ie",
"ig",
"ii",
"ik",
"io",
"is",
"it",
"iu",
"ja",
"jv",
"ka",
"kab",
"kg",
"ki",
"kj",
"kk",
"kl",
"km",
"kn",
"ko",
"kok",
"kr",
"ks",
"ku",
"kv",
"kw",
"ky",
"la",
"lb",
"lg",
"li",
"lij",
"ln",
"lo",
"lt",
"ltg",
"lu",
"lv",
"mai",
"meh",
"mg",
"mh",
"mi",
"mix",
"mk",
"ml",
"mn",
"mr",
"ms",
"mt",
"my",
"na",
"nb",
"nd",
"ne",
"ng",
"nl",
"nn",
"no",
"nr",
"nso",
"nv",
"ny",
"oc",
"oj",
"om",
"or",
"os",
"pa",
"pi",
"pl",
"ps",
"pt",
"qu",
"rm",
"rn",
"ro",
"ru",
"rw",
"sa",
"sat",
"sc",
"sco",
"sd",
"se",
"sg",
"si",
"sk",
"skr",
"sl",
"sm",
"sn",
"so",
"son",
"sq",
"sr",
"ss",
"st",
"su",
"sv",
"sw",
"szl",
"ta",
"te",
"tg",
"th",
"ti",
"tig",
"tk",
"tl",
"tlh",
"tn",
"to",
"tr",
"trs",
"ts",
"tt",
"tw",
"ty",
"ug",
"uk",
"ur",
"uz",
"ve",
"vi",
"vo",
"wa",
"wen",
"wo",
"xh",
"yi",
"yo",
"za",
"zam",
"zh",
"zu",
]),
};
/**
* Notice: If you're updating these names, you should also update the data
* in python/mozbuild/mozbuild/action/langpack_localeNames.json.
*/
const nativeLocaleNames = new Map(
Object.entries({
ach: "Acholi",
af: "Afrikaans",
an: "Aragonés",
ar: "العربية",
ast: "Asturianu",
az: "Azərbaycanca",
be: "Беларуская",
bg: "Български",
bn: "বাংলা",
bo: "བོད་སྐད",
br: "Brezhoneg",
brx: "बड़ो",
bs: "Bosanski",
ca: "Català",
"ca-valencia": "Català (Valencià)",
cak: "Kaqchikel",
cs: "Čeština",
cy: "Cymraeg",
da: "Dansk",
de: "Deutsch",
dsb: "Dolnoserbšćina",
el: "Ελληνικά",
"en-CA": "English (CA)",
"en-GB": "English (GB)",
"en-US": "English (US)",
eo: "Esperanto",
"es-AR": "Español (AR)",
"es-CL": "Español (CL)",
"es-ES": "Español (ES)",
"es-MX": "Español (MX)",
et: "Eesti",
eu: "Euskara",
fa: "فارسی",
ff: "Pulaar",
fi: "Suomi",
fr: "Français",
fur: "Furlan",
"fy-NL": "Frysk",
"ga-IE": "Gaeilge",
gd: "Gàidhlig",
gl: "Galego",
gn: "Guarani",
"gu-IN": "ગુજરાતી",
he: "עברית",
"hi-IN": "हिन्दी",
hr: "Hrvatski",
hsb: "Hornjoserbšćina",
hu: "Magyar",
"hy-AM": "հայերեն",
ia: "Interlingua",
id: "Indonesia",
is: "Islenska",
it: "Italiano",
ja: "日本語",
"ja-JP-mac": "日本語",
ka: "ქართული",
kab: "Taqbaylit",
kk: "қазақ тілі",
km: "ខ្មែរ",
kn: "ಕನ್ನಡ",
ko: "한국어",
lij: "Ligure",
lo: "ລາວ",
lt: "Lietuvių",
ltg: "Latgalīšu",
lv: "Latviešu",
mk: "македонски",
ml: "മലയാളം",
mr: "मराठी",
ms: "Melayu",
my: "မြန်မာ",
"nb-NO": "Norsk Bokmål",
"ne-NP": "नेपाली",
nl: "Nederlands",
"nn-NO": "Nynorsk",
oc: "Occitan",
or: "ଓଡ଼ିଆ",
"pa-IN": "ਪੰਜਾਬੀ",
pl: "Polski",
"pt-BR": "Português (BR)",
"pt-PT": "Português (PT)",
rm: "Rumantsch",
ro: "Română",
ru: "Русский",
sat: "ᱥᱟᱱᱛᱟᱲᱤ",
sc: "Sardu",
sco: "Scots",
si: "සිංහල",
sk: "Slovenčina",
skr: "سرائیکی",
sl: "Slovenščina",
son: "Soŋay",
sq: "Shqip",
sr: "Српски",
"sv-SE": "Svenska",
szl: "Ślōnsko",
ta: "தமிழ்",
te: "తెలుగు",
tg: "Тоҷикӣ",
th: "ไทย",
tl: "Tagalog",
tr: "Türkçe",
trs: "Triqui",
uk: "Українська",
ur: "اردو",
uz: "Ozbek",
vi: "Tiếng Việt",
wo: "Wolof",
xh: "IsiXhosa",
"zh-CN": "简体中文",
"zh-TW": "正體中文",
})
);
class MozRelativeTimeFormat extends Intl.RelativeTimeFormat {
constructor(locales, options = {}, ...args) {
// If someone is asking for MozRelativeTimeFormat, it's likely they'll want
// to use `formatBestUnit` which works better with `auto`
if (options.numeric === undefined) {
options.numeric = "auto";
}
super(locales, options, ...args);
}
formatBestUnit(date, { now = new Date() } = {}) {
const diff = {
_: {},
ms: date.getTime() - now.getTime(),
years: date.getFullYear() - now.getFullYear(),
};
defineCachedGetter(diff, "months", function () {
return this.years * 12 + date.getMonth() - now.getMonth();
});
defineCachedGetter(diff, "weeks", function () {
return Math.trunc(this.days / 7);
});
defineCachedGetter(diff, "days", function () {
return Math.trunc((startOf(date, "day") - startOf(now, "day")) / day);
});
defineCachedGetter(diff, "hours", function () {
return Math.trunc((startOf(date, "hour") - startOf(now, "hour")) / hour);
});
defineCachedGetter(diff, "minutes", function () {
return Math.trunc(
(startOf(date, "minute") - startOf(now, "minute")) / minute
);
});
defineCachedGetter(diff, "seconds", function () {
return Math.trunc(
(startOf(date, "second") - startOf(now, "second")) / second
);
});
const absDiff = {
_: {},
};
for (let prop of Object.keys(diff)) {
defineGetter(absDiff, prop, function () {
return Math.abs(diff[prop]);
});
}
const unit = bestFit(absDiff);
switch (unit) {
case "year":
return this.format(diff.years, unit);
case "month":
return this.format(diff.months, unit);
case "week":
return this.format(diff.weeks, unit);
case "day":
return this.format(diff.days, unit);
case "hour":
return this.format(diff.hours, unit);
case "minute":
return this.format(diff.minutes, unit);
default:
if (unit !== "second") {
throw new TypeError(`Unsupported unit "${unit}"`);
}
return this.format(diff.seconds, unit);
}
}
}
export class MozIntl {
Collator = Intl.Collator;
ListFormat = Intl.ListFormat;
Locale = Intl.Locale;
NumberFormat = Intl.NumberFormat;
PluralRules = Intl.PluralRules;
RelativeTimeFormat = MozRelativeTimeFormat;
constructor() {
this._cache = {};
Services.obs.addObserver(this, "intl:app-locales-changed", true);
}
observe() {
// Clear cache when things change.
this._cache = {};
}
getCalendarInfo(locales, ...args) {
if (!this._cache.hasOwnProperty("getCalendarInfo")) {
mozIntlHelper.addGetCalendarInfo(this._cache);
}
return this._cache.getCalendarInfo(locales, ...args);
}
getDisplayNamesDeprecated(locales, options = {}) {
// Helper for IntlUtils.webidl, will be removed once Intl.DisplayNames is
// available in non-privileged code.
let { type, style, calendar, keys = [] } = options;
let dn = new this.DisplayNames(locales, { type, style, calendar });
let {
locale: resolvedLocale,
type: resolvedType,
style: resolvedStyle,
calendar: resolvedCalendar,
} = dn.resolvedOptions();
let values = keys.map(key => dn.of(key));
return {
locale: resolvedLocale,
type: resolvedType,
style: resolvedStyle,
calendar: resolvedCalendar,
values,
};
}
getAvailableLocaleDisplayNames(type) {
if (availableLocaleDisplayNames.hasOwnProperty(type)) {
return Array.from(availableLocaleDisplayNames[type]);
}
return new Error("Unimplemented!");
}
getLanguageDisplayNames(locales, langCodes) {
if (locales !== undefined) {
throw new Error("First argument support not implemented yet");
}
if (!this._cache.hasOwnProperty("languageLocalization")) {
const loc = new Localization(["toolkit/intl/languageNames.ftl"], true);
this._cache.languageLocalization = loc;
}
const loc = this._cache.languageLocalization;
return langCodes.map(langCode => {
if (typeof langCode !== "string") {
throw new TypeError("All language codes must be strings.");
}
let lcLangCode = langCode.toLowerCase();
if (availableLocaleDisplayNames.language.has(lcLangCode)) {
const value = loc.formatValueSync(`language-name-${lcLangCode}`);
if (value !== null) {
return value;
}
}
return lcLangCode;
});
}
getRegionDisplayNames(locales, regionCodes) {
if (locales !== undefined) {
throw new Error("First argument support not implemented yet");
}
if (!this._cache.hasOwnProperty("regionLocalization")) {
const loc = new Localization(["toolkit/intl/regionNames.ftl"], true);
this._cache.regionLocalization = loc;
}
const loc = this._cache.regionLocalization;
return regionCodes.map(regionCode => {
if (typeof regionCode !== "string") {
throw new TypeError("All region codes must be strings.");
}
let lcRegionCode = regionCode.toLowerCase();
if (availableLocaleDisplayNames.region.has(lcRegionCode)) {
let regionID;
// Allow changing names over time for specific regions
switch (lcRegionCode) {
case "bq":
regionID = "region-name-bq-2018";
break;
case "cv":
regionID = "region-name-cv-2020";
break;
case "cz":
regionID = "region-name-cz-2019";
break;
case "mk":
regionID = "region-name-mk-2019";
break;
case "sz":
regionID = "region-name-sz-2019";
break;
default:
regionID = `region-name-${lcRegionCode}`;
}
const value = loc.formatValueSync(regionID);
if (value !== null) {
return value;
}
}
return regionCode.toUpperCase();
});
}
getLocaleDisplayNames(locales, localeCodes, options = {}) {
const { preferNative = false } = options;
if (locales !== undefined) {
throw new Error("First argument support not implemented yet");
}
// Patterns hardcoded from CLDR 33 english.
// We can later look into fetching them from CLDR directly.
const localePattern = "{0} ({1})";
const localeSeparator = ", ";
return localeCodes.map(localeCode => {
if (typeof localeCode !== "string") {
throw new TypeError("All locale codes must be strings.");
}
if (preferNative && nativeLocaleNames.has(localeCode)) {
return nativeLocaleNames.get(localeCode);
}
let locale;
try {
locale = new Intl.Locale(localeCode.replaceAll("_", "-"));
} catch {
return localeCode;
}
const {
language: languageSubtag,
script: scriptSubtag,
region: regionSubtag,
} = locale;
const variantSubtags = locale.baseName.match(variantSubtagsMatch);
const displayName = [
this.getLanguageDisplayNames(locales, [languageSubtag])[0],
];
if (scriptSubtag) {
displayName.push(scriptSubtag);
}
if (regionSubtag) {
displayName.push(
this.getRegionDisplayNames(locales, [regionSubtag])[0]
);
}
if (variantSubtags) {
displayName.push(...variantSubtags[0].substr(1).split("-")); // Collapse multiple variants.
}
let modifiers;
if (displayName.length === 1) {
return displayName[0];
} else if (displayName.length > 2) {
modifiers = displayName.slice(1).join(localeSeparator);
} else {
modifiers = displayName[1];
}
return localePattern
.replace("{0}", displayName[0])
.replace("{1}", modifiers);
});
}
getScriptDirection(locale) {
// This is a crude implementation until Bug 1693576 lands.
// See justification in toolkit/components/mozintl/mozIMozIntl.idl
const { language } = new Intl.Locale(locale);
if (
language == "ar" ||
language == "ckb" ||
language == "fa" ||
language == "he" ||
language == "ur"
) {
return "rtl";
}
return "ltr";
}
stringHasRTLChars(str) {
return mozIntlHelper.stringHasRTLChars(str);
}
get DateTimeFormat() {
if (!this._cache.hasOwnProperty("DateTimeFormat")) {
mozIntlHelper.addDateTimeFormatConstructor(this._cache);
const DateTimeFormat = this._cache.DateTimeFormat;
class MozDateTimeFormat extends DateTimeFormat {
constructor(locales, options, ...args) {
let resolvedLocales = DateTimeFormat.supportedLocalesOf(locales);
if (options) {
if (options.dateStyle || options.timeStyle) {
options.pattern = osPrefs.getDateTimePattern(
getDateTimePatternStyle(options.dateStyle),
getDateTimePatternStyle(options.timeStyle),
resolvedLocales[0]
);
} else {
// make sure that user doesn't pass a pattern explicitly
options.pattern = undefined;
}
}
super(resolvedLocales, options, ...args);
}
}
this._cache.MozDateTimeFormat = MozDateTimeFormat;
}
return this._cache.MozDateTimeFormat;
}
get DisplayNames() {
if (!this._cache.hasOwnProperty("DisplayNames")) {
mozIntlHelper.addDisplayNamesConstructor(this._cache);
}
return this._cache.DisplayNames;
}
}
MozIntl.prototype.classID = Components.ID(
"{35ec195a-e8d0-4300-83af-c8a2cc84b4a3}"
);
MozIntl.prototype.QueryInterface = ChromeUtils.generateQI([
"mozIMozIntl",
"nsIObserver",
"nsISupportsWeakReference",
]);