--- a/intl/l10n/L10nRegistry.jsm
+++ b/intl/l10n/L10nRegistry.jsm
@@ -239,16 +239,62 @@ const MSG_CONTEXT_OPTIONS = {
return "macos";
default:
return "other";
}
}
}
};
+const ACCENTED_MAP = {
+ // ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ
+ "caps": [550, 385, 391, 7698, 7702, 401, 403, 294, 298, 308, 310, 319, 7742, 544, 510, 420, 586, 344, 350, 358, 364, 7804, 7814, 7818, 7822, 7824],
+ // ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ
+ "small": [551, 384, 392, 7699, 7703, 402, 608, 295, 299, 309, 311, 320, 7743, 414, 511, 421, 587, 345, 351, 359, 365, 7805, 7815, 7819, 7823, 7825],
+};
+
+const FLIPPED_MAP = {
+ // ∀ԐↃᗡƎℲ⅁HIſӼ⅂WNOԀÒᴚS⊥∩ɅMX⅄Z
+ "caps": [8704, 1296, 8579, 5601, 398, 8498, 8513, 72, 73, 383, 1276, 8514, 87, 78, 79, 1280, 210, 7450, 83, 8869, 8745, 581, 77, 88, 8516, 90],
+ // ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz
+ "small": [592, 113, 596, 112, 477, 607, 387, 613, 305, 638, 670, 645, 623, 117, 111, 100, 98, 633, 115, 647, 110, 652, 653, 120, 654, 122],
+};
+
+function transformString(map, prefix = "", postfix = "", msg) {
+ // XML entities (‪) and XML tags.
+ const reExcluded = /(&[#\w]+;|<\s*.+?\s*>)/;
+
+ const parts = msg.split(reExcluded);
+ const modified = parts.map((part) => {
+ if (reExcluded.test(part)) {
+ return part;
+ }
+ return prefix + part.replace(/[a-z]/ig, (ch) => {
+ let cc = ch.charCodeAt(0);
+ if (cc >= 97 && cc <= 122) {
+ if (ch === "a" || ch === "e" || ch === "o" || ch === "u") {
+ const newChar = String.fromCodePoint(map.small[cc - 97]);
+ return newChar + newChar;
+ }
+ return String.fromCodePoint(map.small[cc - 97]);
+ }
+ if (cc >= 65 && cc <= 90) {
+ return String.fromCodePoint(map.caps[cc - 65]);
+ }
+ return ch;
+ }) + postfix;
+ });
+ return modified.join("");
+}
+
+const PSEUDO_STRATEGIES = {
+ "accented": transformString.bind(null, ACCENTED_MAP, "", ""),
+ "bidi": transformString.bind(null, FLIPPED_MAP, "\u202e", "\u202c"),
+};
+
/**
* Generates a single MessageContext by loading all resources
* from the listed sources for a given locale.
*
* The function casts all error cases into a Promise that resolves with
* value `null`.
* This allows the caller to be an async generator without using
* try/catch clauses.
@@ -265,17 +311,21 @@ function generateContext(locale, sources
}
const fetchPromises = resourceIds.map((resourceId, i) => {
return L10nRegistry.sources.get(sourcesOrder[i]).fetchFile(locale, resourceId);
});
const ctxPromise = Promise.all(fetchPromises).then(
dataSets => {
- const ctx = new MessageContext(locale, MSG_CONTEXT_OPTIONS);
+ const pseudoNameFromPref = Services.prefs.getStringPref("intl.l10n.pseudo", "");
+ const ctx = new MessageContext(locale, {
+ ...MSG_CONTEXT_OPTIONS,
+ transform: PSEUDO_STRATEGIES[pseudoNameFromPref],
+ });
for (const data of dataSets) {
if (data === null) {
return null;
}
ctx.addMessages(data);
}
return ctx;
},