arabicfmt
Version:
Arabic-first formatting for numbers, currency, dates and bidirectional text across all 22 Arab League countries — with correct handling of the 2025–2026 Unicode currency-symbol transition (Saudi Riyal U+20C1, UAE Dirham U+20C3, Omani Rial U+20C4).
99 lines (95 loc) • 2.63 kB
JavaScript
;
// src/bidi/direction.ts
var LETTER = /\p{L}/u;
var RTL_LETTER = /[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Samaritan}\p{Script=Mandaic}\p{Script=Adlam}\p{Script=Hanifi_Rohingya}]/u;
function charDirection(ch) {
if (!LETTER.test(ch)) return "neutral";
return RTL_LETTER.test(ch) ? "rtl" : "ltr";
}
function detectDirection(text) {
for (const ch of text) {
const dir = charDirection(ch);
if (dir !== "neutral") return dir;
}
return "neutral";
}
function isRTL(text) {
return detectDirection(text) === "rtl";
}
// src/bidi/isolate.ts
var cc = String.fromCharCode;
var LRI = cc(8294);
var RLI = cc(8295);
var FSI = cc(8296);
var PDI = cc(8297);
var LRM = cc(8206);
var RLM = cc(8207);
var ALM = cc(1564);
var BIDI_CONTROLS = new RegExp(
`[${cc(8206)}${cc(8207)}${cc(1564)}${cc(8234)}-${cc(8238)}${cc(8294)}-${cc(8297)}]`,
"g"
);
function isolate(text, dir = "auto") {
const open = dir === "ltr" ? LRI : dir === "rtl" ? RLI : FSI;
return open + text + PDI;
}
function wrapLTR(text) {
return isolate(text, "ltr");
}
function wrapRTL(text) {
return isolate(text, "rtl");
}
function stripBidi(text) {
return text.replace(BIDI_CONTROLS, "");
}
var ASCII_DIGIT = /[0-9]/;
function isolateForeign(text, options = {}) {
const base = options.base ?? (detectDirection(text) === "ltr" ? "ltr" : "rtl");
const wrapDir = base === "ltr" ? "rtl" : "ltr";
const includeNumbers = options.numbers ?? true;
let result = "";
let run = "";
let runHasOpposite = false;
const flush = () => {
if (!run) return;
if (runHasOpposite) {
const lead = run.match(/^\s*/)[0];
const trail = run.match(/\s*$/)[0];
const core = run.slice(lead.length, run.length - trail.length);
result += lead + isolate(core, wrapDir) + trail;
} else {
result += run;
}
run = "";
runHasOpposite = false;
};
for (const ch of text) {
const dir = charDirection(ch);
if (dir === base) {
flush();
result += ch;
} else {
run += ch;
if (dir !== "neutral" && dir !== base || includeNumbers && ASCII_DIGIT.test(ch)) {
runHasOpposite = true;
}
}
}
flush();
return result;
}
exports.ALM = ALM;
exports.FSI = FSI;
exports.LRI = LRI;
exports.LRM = LRM;
exports.PDI = PDI;
exports.RLI = RLI;
exports.RLM = RLM;
exports.charDirection = charDirection;
exports.detectDirection = detectDirection;
exports.isRTL = isRTL;
exports.isolate = isolate;
exports.isolateForeign = isolateForeign;
exports.stripBidi = stripBidi;
exports.wrapLTR = wrapLTR;
exports.wrapRTL = wrapRTL;