Dactyloidae/browser/internaluserscripts/bundled-scripts/intl-segmenter-polyfill.user.js
2026-07-13 08:48:40 -07:00

365 lines
9.3 KiB
JavaScript

// 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/.
// ==UserScript==
// @name Intl.Segmenter Polyfill (minimal)
// @namespace internal-userscripts
// @description Provides a minimal Intl.Segmenter implementation for environments without native support.
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
"use strict";
var global =
(typeof globalThis !== "undefined" && globalThis) ||
(typeof window !== "undefined" && window) ||
(typeof self !== "undefined" && self) ||
this;
if (typeof global.Intl !== "object" || global.Intl == null) {
global.Intl = {};
}
if (typeof global.Intl.Segmenter === "function") {
return;
}
var GRANULARITY_VALUES = { grapheme: true, word: true, sentence: true };
var LOCALE_MATCHER_VALUES = { lookup: true, "best fit": true };
var FALLBACK_WORD_LIKE = /^[A-Za-z0-9_]+$/;
var FALLBACK_WORD_SPLIT = /[A-Za-z0-9_]+|[\s]+|[^\s]/g;
var COMBINING_MARK_RANGES = [
[0x0300, 0x036f],
[0x1ab0, 0x1aff],
[0x1dc0, 0x1dff],
[0x20d0, 0x20ff],
[0xfe20, 0xfe2f],
];
var unicodeGraphemeRegex = null;
var unicodeWordSplit = null;
var unicodeWordLike = null;
try {
unicodeGraphemeRegex = new RegExp(
"(?:\\P{Mark}\\p{Mark}*|\\p{Mark}+)",
"gu",
);
unicodeWordSplit = new RegExp("[\\p{L}\\p{N}\\p{M}_]+|[\\s]+|[^\\s]", "gu");
unicodeWordLike = new RegExp("^[\\p{L}\\p{N}\\p{M}_]+$", "u");
} catch (e) {}
function canonicalizeLocales(locales) {
if (typeof global.Intl.getCanonicalLocales === "function") {
return global.Intl.getCanonicalLocales(locales);
}
if (locales == null) {
return [];
}
if (Array.isArray(locales)) {
return locales.map(function (locale) {
return String(locale);
});
}
return [String(locales)];
}
function defaultLocale() {
try {
if (typeof global.Intl.DateTimeFormat === "function") {
return global.Intl.DateTimeFormat().resolvedOptions().locale;
}
} catch (e) {}
if (typeof navigator !== "undefined" && navigator.language) {
return String(navigator.language);
}
return "en-US";
}
function toStringValue(value) {
if (typeof value === "symbol") {
throw new TypeError("Cannot convert a Symbol value to a string");
}
return String(value);
}
function readCodePoint(str, index) {
var first = str.charCodeAt(index);
if (first >= 0xd800 && first <= 0xdbff && index + 1 < str.length) {
var second = str.charCodeAt(index + 1);
if (second >= 0xdc00 && second <= 0xdfff) {
return {
codePoint: ((first - 0xd800) << 10) + (second - 0xdc00) + 0x10000,
length: 2,
};
}
}
return { codePoint: first, length: 1 };
}
function isCombiningMark(codePoint) {
for (var i = 0; i < COMBINING_MARK_RANGES.length; i++) {
var range = COMBINING_MARK_RANGES[i];
if (codePoint >= range[0] && codePoint <= range[1]) {
return true;
}
}
return false;
}
function segmentGrapheme(string) {
var segments = [];
var i = 0;
if (unicodeGraphemeRegex) {
var match;
unicodeGraphemeRegex.lastIndex = 0;
while ((match = unicodeGraphemeRegex.exec(string))) {
segments.push({
segment: match[0],
index: match.index,
});
}
return segments;
}
while (i < string.length) {
var start = i;
var current = readCodePoint(string, i);
i += current.length;
while (i < string.length) {
var next = readCodePoint(string, i);
if (!isCombiningMark(next.codePoint)) {
break;
}
i += next.length;
}
segments.push({
segment: string.slice(start, i),
index: start,
});
}
return segments;
}
function segmentWord(string) {
var segments = [];
var regex = unicodeWordSplit || FALLBACK_WORD_SPLIT;
var match;
regex.lastIndex = 0;
while ((match = regex.exec(string))) {
segments.push({
segment: match[0],
index: match.index,
});
}
return segments;
}
function isSentenceTerminal(ch) {
return ch === "." || ch === "!" || ch === "?";
}
function isWhitespaceChar(ch) {
return (
ch === " " || ch === "\t" || ch === "\r" || ch === "\n" || ch === "\f"
);
}
function segmentSentence(string) {
var segments = [];
var start = 0;
var i = 0;
if (!string) {
return segments;
}
while (i < string.length) {
if (isSentenceTerminal(string.charAt(i))) {
i++;
while (i < string.length && isSentenceTerminal(string.charAt(i))) {
i++;
}
while (i < string.length && isWhitespaceChar(string.charAt(i))) {
i++;
}
segments.push({
segment: string.slice(start, i),
index: start,
});
start = i;
} else {
i++;
}
}
if (start < string.length) {
segments.push({
segment: string.slice(start),
index: start,
});
}
return segments;
}
function segmentString(string, granularity) {
if (granularity === "word") {
return segmentWord(string);
}
if (granularity === "sentence") {
return segmentSentence(string);
}
return segmentGrapheme(string);
}
function isWordLikeSegment(segment) {
if (unicodeWordLike) {
return unicodeWordLike.test(segment);
}
return FALLBACK_WORD_LIKE.test(segment);
}
function requireSegmenter(value) {
if (
value == null ||
(typeof value !== "object" && typeof value !== "function") ||
!GRANULARITY_VALUES[value._granularity]
) {
throw new TypeError(
"Method Intl.Segmenter.prototype called on incompatible receiver",
);
}
return value;
}
function SegmentIterator(segments) {
this._segments = segments;
this._index = 0;
}
SegmentIterator.prototype.next = function () {
if (this._index >= this._segments.length) {
return { value: undefined, done: true };
}
var value = this._segments[this._index];
this._index += 1;
return { value: value, done: false };
};
if (typeof Symbol !== "undefined" && Symbol.iterator) {
SegmentIterator.prototype[Symbol.iterator] = function () {
return this;
};
}
function Segments(input, segments) {
this._input = input;
this._segments = segments;
}
Segments.prototype.containing = function (index) {
if (index == null) {
return undefined;
}
var position = Number(index);
if (!isFinite(position)) {
return undefined;
}
position = Math.floor(position);
for (var i = 0; i < this._segments.length; i++) {
var segment = this._segments[i];
if (
position >= segment.index &&
position < segment.index + segment.segment.length
) {
return segment;
}
}
return undefined;
};
if (typeof Symbol !== "undefined" && Symbol.iterator) {
Segments.prototype[Symbol.iterator] = function () {
return new SegmentIterator(this._segments);
};
}
function Segmenter(locales, options) {
if (!(this instanceof Segmenter)) {
throw new TypeError("Intl.Segmenter must be called with new");
}
if (options == null) {
options = {};
} else if (typeof options !== "object") {
options = Object(options);
}
var granularity =
options.granularity == null ? "grapheme" : String(options.granularity);
if (!GRANULARITY_VALUES[granularity]) {
throw new RangeError("Invalid granularity");
}
if (
options.localeMatcher != null &&
!LOCALE_MATCHER_VALUES[String(options.localeMatcher)]
) {
throw new RangeError("Invalid localeMatcher");
}
var localeList = canonicalizeLocales(locales);
this._locale = localeList.length ? localeList[0] : defaultLocale();
this._granularity = granularity;
}
Segmenter.supportedLocalesOf = function (locales, options) {
if (options != null && typeof options === "object") {
var localeMatcher = options.localeMatcher;
if (
localeMatcher != null &&
!LOCALE_MATCHER_VALUES[String(localeMatcher)]
) {
throw new RangeError("Invalid localeMatcher");
}
}
return canonicalizeLocales(locales);
};
Segmenter.prototype.resolvedOptions = function () {
var self = requireSegmenter(this);
return {
locale: self._locale,
granularity: self._granularity,
};
};
Segmenter.prototype.segment = function (input) {
var self = requireSegmenter(this);
var string = toStringValue(input);
var entries = segmentString(string, self._granularity);
var segments = [];
for (var i = 0; i < entries.length; i++) {
var part = entries[i];
segments.push({
segment: part.segment,
index: part.index,
input: string,
isWordLike:
self._granularity === "word"
? isWordLikeSegment(part.segment)
: undefined,
});
}
return new Segments(string, segments);
};
global.Intl.Segmenter = Segmenter;
try {
global.__internalUserscriptsIntlSegmenterPolyfill = true;
} catch (e) {}
})();