2025-08-03 12:48:22 -04:00
|
|
|
|
const verbsHelper = require("english-verbs-helper");
|
|
|
|
|
|
const verbsIrregular = require("english-verbs-irregular/dist/verbs.json");
|
|
|
|
|
|
const verbsGerunds = require("english-verbs-gerunds/dist/gerunds.json");
|
|
|
|
|
|
const verbsData = verbsHelper.mergeVerbsData(verbsIrregular, verbsGerunds);
|
|
|
|
|
|
const articleHelper = require("indefinite");
|
|
|
|
|
|
|
|
|
|
|
|
/* Replaces MetamorPOV markers with English according to the options */
|
|
|
|
|
|
exports.translate = function(input, options) {
|
|
|
|
|
|
let request = {
|
|
|
|
|
|
input: input,
|
|
|
|
|
|
options: options,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:30:18 -04:00
|
|
|
|
try {
|
|
|
|
|
|
if (typeof(input) == "object" && input instanceof Node) {
|
|
|
|
|
|
request.loop = loopNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (typeof(input) == "string") {
|
|
|
|
|
|
request.loop = loopString;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
console.error("Input of type " + typeof(input) + " is unsupported");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 12:48:22 -04:00
|
|
|
|
}
|
2025-08-14 23:30:18 -04:00
|
|
|
|
catch {
|
|
|
|
|
|
console.error("Translation of Node objects is unsupported outside of a browser context");
|
2025-08-03 12:48:22 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isNameSet = !(options.name === undefined || options.name == "");
|
|
|
|
|
|
const isPronounsSet = !(options.preset === undefined || options.preset == "");
|
|
|
|
|
|
const isPovSet = !(options.pov === undefined || options.pov == "");
|
|
|
|
|
|
const isPovLegal = isPovSet && (options.pov != "third" || (isNameSet && isPronounsSet));
|
|
|
|
|
|
|
|
|
|
|
|
if (isPovLegal) {
|
|
|
|
|
|
vrb(request);
|
|
|
|
|
|
pov(request);
|
|
|
|
|
|
plv(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isNameSet) {
|
|
|
|
|
|
yn(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isPronounsSet) {
|
|
|
|
|
|
prn(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isAlsoSet = !(options.also === undefined);
|
|
|
|
|
|
if (isAlsoSet) {
|
|
|
|
|
|
also(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isAllSet = isNameSet && isPronounsSet && isPovLegal;
|
|
|
|
|
|
if (isAllSet) {
|
|
|
|
|
|
alt(request);
|
|
|
|
|
|
ife(request);
|
|
|
|
|
|
cut(request);
|
|
|
|
|
|
cap(request);
|
|
|
|
|
|
mrr(request);
|
|
|
|
|
|
aan(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return request.input;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Iterator for HTML Node elements */
|
|
|
|
|
|
function loopNode(request, params, method) {
|
|
|
|
|
|
const node = request.input;
|
|
|
|
|
|
if (node.contentEditable == "true" || node.type == "textarea" || node.type == "input") { return; }
|
|
|
|
|
|
let child, next;
|
|
|
|
|
|
switch (node.nodeType) {
|
|
|
|
|
|
case 1: /* ELEMENT_NODE */
|
|
|
|
|
|
case 9: /* DOCUMENT_NODE */
|
|
|
|
|
|
case 11: /* DOCUMENT_FRAGMENT_NODE */
|
|
|
|
|
|
child = node.firstChild;
|
|
|
|
|
|
while (child) {
|
|
|
|
|
|
next = child.nextSibling;
|
2025-08-14 23:30:18 -04:00
|
|
|
|
loopNode({
|
|
|
|
|
|
input: child,
|
|
|
|
|
|
options: request.options
|
|
|
|
|
|
}, params, method);
|
2025-08-03 12:48:22 -04:00
|
|
|
|
child = next;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 3: /* TEXT_NODE */
|
2025-08-14 23:30:18 -04:00
|
|
|
|
loopNodeValue({
|
|
|
|
|
|
input: node,
|
|
|
|
|
|
options: request.options
|
|
|
|
|
|
}, params, method);
|
2025-08-03 12:48:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:30:18 -04:00
|
|
|
|
/* Iterator for strings belonging to HTML Node elements */
|
|
|
|
|
|
function loopNodeValue(request, params, method) {
|
2025-08-18 14:32:24 -04:00
|
|
|
|
if (params.looping == false) {
|
|
|
|
|
|
params.looping = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-14 23:30:18 -04:00
|
|
|
|
if (request.input.nodeValue.match(params.regexp) == null) { return; }
|
|
|
|
|
|
request.input.nodeValue = method(request.input.nodeValue, params);
|
|
|
|
|
|
loopNodeValue(request, params, method);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 12:48:22 -04:00
|
|
|
|
/* Iterator for string elements */
|
|
|
|
|
|
function loopString(request, params, method) {
|
2025-08-18 14:32:24 -04:00
|
|
|
|
if (params.looping == false) {
|
|
|
|
|
|
params.looping = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 12:48:22 -04:00
|
|
|
|
if (request.input.match(params.regexp) == null) { return; }
|
|
|
|
|
|
request.input = method(request.input, params);
|
|
|
|
|
|
loopString(request, params, method);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* vrb/be/ becomes "were" in second-person and "was" in third-person POV */
|
|
|
|
|
|
function vrb(request) {
|
|
|
|
|
|
const regexp = new RegExp([
|
|
|
|
|
|
/(?<firstLetter>v)r/,
|
|
|
|
|
|
/(?<category>[nb])\//,
|
2025-08-03 19:35:56 -04:00
|
|
|
|
/(?:(?<tense>(?:(?:simple|progressive|perfect|perfect[ _]progressive|participle(?![ _]future))[ _])*(?:past|present|future))\/)*/,
|
|
|
|
|
|
/(?<verb>[\w\s]+)\//
|
|
|
|
|
|
].map(r => r.source).join(''), 'i');
|
2025-08-03 12:48:22 -04:00
|
|
|
|
|
|
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: regexp,
|
|
|
|
|
|
options: request.options
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const options = params.options;
|
|
|
|
|
|
const vars = input.match(params.regexp).groups;
|
|
|
|
|
|
let tense = vars.tense;
|
|
|
|
|
|
if (tense == null) {
|
|
|
|
|
|
tense = "PAST";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
tense = tense.toUpperCase().replaceAll(" ", "_");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let povIndex;
|
|
|
|
|
|
switch (vars.category) {
|
|
|
|
|
|
case "B": /* B and b vary by POV, capitalized to indicate a named subject */
|
|
|
|
|
|
if (options.pov == "third") {
|
|
|
|
|
|
povIndex = getPovIndex("third-singular", null);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
povIndex = getPovIndex(options.pov, options);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "b":
|
|
|
|
|
|
povIndex = getPovIndex(options.pov, options);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "N": /* N and n are always third-person */
|
|
|
|
|
|
povIndex = getPovIndex("third-singular", null);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "n":
|
|
|
|
|
|
povIndex = getPovIndex("third", options);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let replacement = verbsHelper.getConjugation(verbsData, vars.verb, tense, povIndex);
|
|
|
|
|
|
const isCapital = /V/.test(vars.firstLetter);
|
|
|
|
|
|
if (isCapital) {
|
|
|
|
|
|
replacement = capitalize(replacement);
|
|
|
|
|
|
}
|
|
|
|
|
|
return input.replace(params.regexp, replacement);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* pov/s becomes "I" or "you" or "she" by POV */
|
|
|
|
|
|
function pov(request) {
|
|
|
|
|
|
const options = request.options;
|
|
|
|
|
|
let pronouns, params;
|
|
|
|
|
|
if (request.options.pov == "third") {
|
|
|
|
|
|
pronouns = getPronouns(options.preset, options.other);
|
|
|
|
|
|
params = [
|
|
|
|
|
|
{
|
|
|
|
|
|
regexp: /([Pp])ov\/S/,
|
|
|
|
|
|
pronoun: options.name
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/O/,
|
|
|
|
|
|
pronoun: options.name
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/P/,
|
|
|
|
|
|
pronoun: options.name + "’s"
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/A/,
|
|
|
|
|
|
pronoun: options.name + "’s"
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/R/,
|
|
|
|
|
|
pronoun: options.name + "’s self"
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
pronouns = getPronouns(options.pov, null);
|
|
|
|
|
|
params = [
|
|
|
|
|
|
{
|
|
|
|
|
|
regexp: /([Pp])ov\/S/,
|
|
|
|
|
|
pronoun: pronouns["subjective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/O/,
|
|
|
|
|
|
pronoun: pronouns["objective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/P/,
|
|
|
|
|
|
pronoun: pronouns["possessive"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/A/,
|
|
|
|
|
|
pronoun: pronouns["adjective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/R/,
|
|
|
|
|
|
pronoun: pronouns["reflexive"]
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
params.push(
|
|
|
|
|
|
{
|
|
|
|
|
|
regexp: /([Pp])ov\/s/,
|
|
|
|
|
|
pronoun: pronouns["subjective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/o/,
|
|
|
|
|
|
pronoun: pronouns["objective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/p/,
|
|
|
|
|
|
pronoun: pronouns["possessive"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/a/,
|
|
|
|
|
|
pronoun: pronouns["adjective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])ov\/r/,
|
|
|
|
|
|
pronoun: pronouns["reflexive"]
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
params.forEach((pronoun) => request.loop(request, pronoun, prnHelper));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* prn/s becomes "we" or "they" by POV */
|
|
|
|
|
|
function plv(request) {
|
|
|
|
|
|
const pronouns = getPronouns(request.options.pov + "-plural", null);
|
|
|
|
|
|
const params = [
|
|
|
|
|
|
{
|
|
|
|
|
|
regexp: /([Pp])lv\/s/,
|
|
|
|
|
|
pronoun: pronouns["subjective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])lv\/o/,
|
|
|
|
|
|
pronoun: pronouns["objective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])lv\/p/,
|
|
|
|
|
|
pronoun: pronouns["possessive"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])lv\/a/,
|
|
|
|
|
|
pronoun: pronouns["adjective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])lv\/r/,
|
|
|
|
|
|
pronoun: pronouns["reflexive"]
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
params.forEach((pronoun) => request.loop(request, pronoun, prnHelper));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Y/n becomes the name indicated in the options */
|
|
|
|
|
|
function yn(request) {
|
|
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: /\by\/n\b|\(y\/n\)|\[y\/n\]/ig,
|
2025-08-18 14:32:24 -04:00
|
|
|
|
replacement: request.options["name"],
|
|
|
|
|
|
looping: true
|
2025-08-03 12:48:22 -04:00
|
|
|
|
}, (input, params) => {
|
2025-08-18 14:32:24 -04:00
|
|
|
|
params.looping = false;
|
2025-08-03 12:48:22 -04:00
|
|
|
|
return input.replaceAll(params.regexp, params.replacement);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* prn/s becomes "he" or "she" and are always third-person */
|
|
|
|
|
|
function prn(request) {
|
|
|
|
|
|
const pronouns = getPronouns(request.options.preset, request.options.other);
|
|
|
|
|
|
const params = [
|
|
|
|
|
|
{
|
|
|
|
|
|
regexp: /([Pp])rn\/s/,
|
|
|
|
|
|
pronoun: pronouns["subjective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/o/,
|
|
|
|
|
|
pronoun: pronouns["objective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/p/,
|
|
|
|
|
|
pronoun: pronouns["possessive"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/a/,
|
|
|
|
|
|
pronoun: pronouns["adjective"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/r/,
|
|
|
|
|
|
pronoun: pronouns["reflexive"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/H/,
|
|
|
|
|
|
pronoun: pronouns["honorific-abbr"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/h/,
|
|
|
|
|
|
pronoun: pronouns["honorific"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/N/,
|
|
|
|
|
|
pronoun: pronouns["adult"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/n/,
|
|
|
|
|
|
pronoun: pronouns["youth"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/F/,
|
|
|
|
|
|
pronoun: pronouns["parent"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/f/,
|
|
|
|
|
|
pronoun: pronouns["child"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/k/,
|
|
|
|
|
|
pronoun: pronouns["sibling"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/m/,
|
|
|
|
|
|
pronoun: pronouns["married"]
|
|
|
|
|
|
}, {
|
|
|
|
|
|
regexp: /([Pp])rn\/d/,
|
|
|
|
|
|
pronoun: pronouns["dating"]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
params.forEach((pronoun) => request.loop(request, pronoun, prnHelper));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Replaces specific words indicated in the options */
|
|
|
|
|
|
function also(request) {
|
2025-08-03 19:35:56 -04:00
|
|
|
|
Object.entries(request.options.also).forEach(([key, replacement]) => {
|
2025-08-18 14:32:24 -04:00
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: new RegExp(RegExp.escape(key), "gi"),
|
|
|
|
|
|
replacement: replacement,
|
|
|
|
|
|
looping: true
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
params.looping = false;
|
|
|
|
|
|
return input.replaceAll(params.regexp, params.replacement);
|
|
|
|
|
|
})
|
2025-08-03 12:48:22 -04:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* alt/first and second or third/word1/word2/ becomes "word1" in first and second-person POV but "word2" in third-person */
|
|
|
|
|
|
function alt(request) {
|
|
|
|
|
|
const regexp1 = new RegExp([
|
2025-08-03 19:35:56 -04:00
|
|
|
|
/alt\/(?<targetPov1>first|second|third)/,
|
|
|
|
|
|
/(?: and (?!\1)(?<targetPov2>first|second|third))?/,
|
2025-08-03 12:48:22 -04:00
|
|
|
|
/\/(?<replacement>[^\/]*)\//
|
2025-08-03 19:35:56 -04:00
|
|
|
|
].map(r => r.source).join(''), 'i');
|
2025-08-03 12:48:22 -04:00
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: regexp1,
|
|
|
|
|
|
pov: request.options.pov
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const vars = input.match(params.regexp).groups;
|
2025-08-03 19:35:56 -04:00
|
|
|
|
if (vars.targetPov1.toLowerCase() == params.pov) {
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement);
|
|
|
|
|
|
} else if (vars.targetPov2 == null) {
|
|
|
|
|
|
return input.replace(params.regexp, "");
|
|
|
|
|
|
} else if (vars.targetPov2.toLowerCase() == params.pov) {
|
2025-08-03 12:48:22 -04:00
|
|
|
|
return input.replace(params.regexp, vars.replacement);
|
|
|
|
|
|
} else {
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, "");
|
2025-08-03 12:48:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const regexp2 = new RegExp([
|
|
|
|
|
|
/alt\/(?<targetPov1>first|second|third) /,
|
|
|
|
|
|
/(?<conjunction1>and|or) /,
|
|
|
|
|
|
/(?!\1)(?<targetPov2>first|second|third)/,
|
|
|
|
|
|
/(?: (?!\2)(?<conjunction2>and|or) (?!\1|\3)(?:first|second|third))?/,
|
|
|
|
|
|
/\/(?<replacement1>[^\/]*)\//,
|
|
|
|
|
|
/(?<replacement2>[^\/]*)\//
|
2025-08-03 19:35:56 -04:00
|
|
|
|
].map(r => r.source).join(''), 'i');
|
2025-08-03 12:48:22 -04:00
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: regexp2,
|
|
|
|
|
|
pov: request.options.pov
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const vars = input.match(params.regexp).groups;
|
2025-08-03 19:35:56 -04:00
|
|
|
|
if (vars.targetPov1.toLowerCase() == params.pov) {
|
2025-08-03 12:48:22 -04:00
|
|
|
|
return input.replace(params.regexp, vars.replacement1);
|
2025-08-03 19:35:56 -04:00
|
|
|
|
} else if (vars.targetPov2.toLowerCase() == params.pov) {
|
|
|
|
|
|
if (vars.conjunction1.toLowerCase() == "and") {
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement2);
|
|
|
|
|
|
}
|
2025-08-03 12:48:22 -04:00
|
|
|
|
} else if (vars.conjunction2 == null) {
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, "");
|
2025-08-03 12:48:22 -04:00
|
|
|
|
} else {
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement2);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const regexp3 = new RegExp([
|
2025-08-03 19:35:56 -04:00
|
|
|
|
/alt\/(?:(first|second|third) or (?!\1)(first|second|third) or (?!\1|\2)(?:first|second|third)\/)?/,
|
|
|
|
|
|
/(?<replacement1>[^\/]*)\//,
|
2025-08-03 12:48:22 -04:00
|
|
|
|
/(?<replacement2>[^\/]*)\//,
|
|
|
|
|
|
/(?<replacement3>[^\/]*)\//
|
2025-08-03 19:35:56 -04:00
|
|
|
|
].map(r => r.source).join(''), 'i');
|
2025-08-03 12:48:22 -04:00
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: regexp3,
|
|
|
|
|
|
pov: request.options.pov
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const vars = input.match(params.regexp).groups;
|
|
|
|
|
|
switch (params.pov) {
|
|
|
|
|
|
case "first":
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement1);
|
|
|
|
|
|
case "second":
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement2);
|
|
|
|
|
|
case "third":
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement3);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* if/a is a/word/ becomes "word" because the condition is met */
|
|
|
|
|
|
function ife(request) {
|
|
|
|
|
|
const regexp1 = new RegExp([
|
|
|
|
|
|
/if\/(?<lhs>[^\/]*) is /,
|
|
|
|
|
|
/(?<rhs>[^\/]*)\//,
|
|
|
|
|
|
/(?<replacement>[^\/]*)\//
|
2025-08-03 19:35:56 -04:00
|
|
|
|
].map(r => r.source).join(''), 'i');
|
2025-08-03 12:48:22 -04:00
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: regexp1
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const vars = input.match(params.regexp).groups;
|
|
|
|
|
|
if (vars.lhs === vars.rhs) {
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement);
|
|
|
|
|
|
} else {
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, "");
|
2025-08-03 12:48:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const regexp2 = new RegExp([
|
|
|
|
|
|
/ife\/(?<lhs>[^\/]*) is /,
|
|
|
|
|
|
/(?<rhs>[^\/]*)\//,
|
|
|
|
|
|
/(?<replacement>[^\/]*)\//,
|
|
|
|
|
|
/(?<else>[^\/]*)\//
|
2025-08-03 19:35:56 -04:00
|
|
|
|
].map(r => r.source).join(''), 'i');
|
2025-08-03 12:48:22 -04:00
|
|
|
|
request.loop(request, {
|
2025-08-03 19:35:56 -04:00
|
|
|
|
regexp: regexp2
|
2025-08-03 12:48:22 -04:00
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const vars = input.match(params.regexp).groups;
|
|
|
|
|
|
if (vars.lhs === vars.rhs) {
|
|
|
|
|
|
return input.replace(params.regexp, vars.replacement);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return input.replace(params.regexp, vars.else);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* cut/off first 1/word/ shortens into "ord" */
|
|
|
|
|
|
function cut(request) {
|
|
|
|
|
|
const regexp = new RegExp([
|
2025-08-03 19:35:56 -04:00
|
|
|
|
/cut\/(?<offonly>off|only) /,
|
2025-08-03 12:48:22 -04:00
|
|
|
|
/(?<firstlast>first|last) /,
|
2025-08-03 19:35:56 -04:00
|
|
|
|
/(?<index>[1-9][0-9]*)\//,
|
2025-08-18 09:54:22 -04:00
|
|
|
|
/(?<target>(?!cut\/)[^\/]+)\//
|
2025-08-03 19:35:56 -04:00
|
|
|
|
].map(r => r.source).join(''), 'i');
|
2025-08-03 12:48:22 -04:00
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: regexp
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const vars = input.match(params.regexp).groups;
|
|
|
|
|
|
const length = vars.target.length;
|
2025-08-18 14:32:24 -04:00
|
|
|
|
if (length == 1) {
|
|
|
|
|
|
return input.replace(params.regexp, vars.target);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 12:48:22 -04:00
|
|
|
|
let start, end;
|
2025-08-03 19:35:56 -04:00
|
|
|
|
if (vars.offonly.toLowerCase() == "only") {
|
|
|
|
|
|
if (vars.firstlast.toLowerCase() == "first") { // only first n
|
2025-08-03 12:48:22 -04:00
|
|
|
|
start = 0;
|
|
|
|
|
|
end = Math.min(vars.index, length - 1);
|
|
|
|
|
|
} else { // only last n
|
|
|
|
|
|
start = Math.max(length - vars.index, 1);
|
|
|
|
|
|
end = length;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-08-03 19:35:56 -04:00
|
|
|
|
if (vars.firstlast.toLowerCase() == "first") { // off first n
|
2025-08-03 12:48:22 -04:00
|
|
|
|
start = Math.min(vars.index, length - 1);;
|
|
|
|
|
|
end = length;
|
|
|
|
|
|
} else { // off last n
|
|
|
|
|
|
start = 0;
|
|
|
|
|
|
end = Math.max(length - vars.index, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, vars.target.slice(start, end));
|
2025-08-03 12:48:22 -04:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* cap/WORD/ changes the case to "word" (Cap/word/ for "Word" and CAP/word/ for "WORD") */
|
|
|
|
|
|
function cap(request) {
|
|
|
|
|
|
const regexp = new RegExp([
|
|
|
|
|
|
/(?<style>cap|Cap|CAP)\//,
|
|
|
|
|
|
/(?<target>[^\/]+)\//
|
2025-08-03 19:35:56 -04:00
|
|
|
|
].map(r => r.source).join(''));
|
2025-08-03 12:48:22 -04:00
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: regexp
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const vars = input.match(params.regexp).groups;
|
|
|
|
|
|
const isLowerCase = /c/.test(vars.style.slice(0,1)); // cap
|
|
|
|
|
|
if (isLowerCase) {
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, vars.target.toLowerCase());
|
2025-08-03 12:48:22 -04:00
|
|
|
|
} else {
|
|
|
|
|
|
const isStartCase = /a/.test(vars.style.slice(1,2)); // Cap
|
|
|
|
|
|
if (isStartCase) {
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, capitalize(vars.target));
|
2025-08-03 12:48:22 -04:00
|
|
|
|
} else { // CAP
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, vars.target.toUpperCase());
|
2025-08-03 12:48:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* mrr/word/ flips into "dorw" */
|
|
|
|
|
|
function mrr(request) {
|
|
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: /mrr\/(?<target>[^\/]+)\//i
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const target = input.match(params.regexp).groups.target;
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, target.split('').reverse().join(''));
|
2025-08-03 12:48:22 -04:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* a/an determines "a" or "an" by the pronunciation of the following word */
|
|
|
|
|
|
function aan(request) {
|
|
|
|
|
|
request.loop(request, {
|
|
|
|
|
|
regexp: /a\/an (?<target>\w+)/i
|
|
|
|
|
|
}, (input, params) => {
|
|
|
|
|
|
const target = input.match(params.regexp).groups.target;
|
2025-08-03 19:35:56 -04:00
|
|
|
|
return input.replace(params.regexp, articleHelper(target, { numbers: 'colloquial' }));
|
2025-08-03 12:48:22 -04:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Retrieves an index used by the verbs library for conjugation */
|
|
|
|
|
|
function getPovIndex(pov, options) {
|
|
|
|
|
|
switch (pov) {
|
|
|
|
|
|
case "first":
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
case "second":
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
case "third-singular":
|
|
|
|
|
|
return 2;
|
|
|
|
|
|
case "third":
|
|
|
|
|
|
if (getPronouns(options.preset, options.other).plurality == "singular") {
|
|
|
|
|
|
return 2;
|
|
|
|
|
|
} else { /* Plurality is plural, "they go" */
|
|
|
|
|
|
return 5;
|
|
|
|
|
|
}
|
|
|
|
|
|
return getThirdIndex(options);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Returns the input word, capitalized */
|
|
|
|
|
|
function capitalize(word) {
|
|
|
|
|
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Returns pronouns by preset */
|
|
|
|
|
|
function getPronouns(key, other) {
|
|
|
|
|
|
switch (key) {
|
|
|
|
|
|
case "she":
|
|
|
|
|
|
return {
|
|
|
|
|
|
"subjective": "she",
|
|
|
|
|
|
"objective": "her",
|
|
|
|
|
|
"possessive": "her",
|
|
|
|
|
|
"adjective": "hers",
|
|
|
|
|
|
"reflexive": "herself",
|
|
|
|
|
|
"honorific-abbr": "Ms.",
|
|
|
|
|
|
"honorific": "miss",
|
|
|
|
|
|
"adult": "woman",
|
|
|
|
|
|
"youth": "girl",
|
|
|
|
|
|
"parent": "mother",
|
|
|
|
|
|
"child": "daughter",
|
|
|
|
|
|
"sibling": "sister",
|
|
|
|
|
|
"married": "wife",
|
|
|
|
|
|
"dating": "girlfriend",
|
|
|
|
|
|
"plurality": "singular"
|
|
|
|
|
|
};
|
|
|
|
|
|
case "he":
|
|
|
|
|
|
return {
|
|
|
|
|
|
"subjective": "he",
|
|
|
|
|
|
"objective": "him",
|
|
|
|
|
|
"possessive": "his",
|
|
|
|
|
|
"adjective": "his",
|
|
|
|
|
|
"reflexive": "himself",
|
|
|
|
|
|
"honorific-abbr": "Mr.",
|
|
|
|
|
|
"honorific": "mister",
|
|
|
|
|
|
"adult": "man",
|
|
|
|
|
|
"youth": "boy",
|
|
|
|
|
|
"parent": "father",
|
|
|
|
|
|
"child": "son",
|
|
|
|
|
|
"sibling": "brother",
|
|
|
|
|
|
"married": "husband",
|
|
|
|
|
|
"dating": "boyfriend",
|
|
|
|
|
|
"plurality": "singular"
|
|
|
|
|
|
};
|
|
|
|
|
|
case "they":
|
|
|
|
|
|
return {
|
|
|
|
|
|
"subjective": "they",
|
|
|
|
|
|
"objective": "them",
|
|
|
|
|
|
"possessive": "their",
|
|
|
|
|
|
"adjective": "theirs",
|
|
|
|
|
|
"reflexive": "themself",
|
|
|
|
|
|
"honorific-abbr": "Mx.",
|
|
|
|
|
|
"honorific": "mix",
|
|
|
|
|
|
"adult": "person",
|
|
|
|
|
|
"youth": "kid",
|
|
|
|
|
|
"parent": "parent",
|
|
|
|
|
|
"child": "child",
|
|
|
|
|
|
"sibling": "sibling",
|
|
|
|
|
|
"married": "spouse",
|
|
|
|
|
|
"dating": "partner",
|
|
|
|
|
|
"plurality": "plural"
|
|
|
|
|
|
};
|
|
|
|
|
|
case "first":
|
|
|
|
|
|
return {
|
|
|
|
|
|
"subjective": "I",
|
|
|
|
|
|
"objective": "me",
|
|
|
|
|
|
"possessive": "my",
|
|
|
|
|
|
"adjective": "mine",
|
|
|
|
|
|
"reflexive": "myself"
|
|
|
|
|
|
};
|
|
|
|
|
|
case "second":
|
|
|
|
|
|
return {
|
|
|
|
|
|
"subjective": "you",
|
|
|
|
|
|
"objective": "you",
|
|
|
|
|
|
"possessive": "your",
|
|
|
|
|
|
"adjective": "yours",
|
|
|
|
|
|
"reflexive": "yourself"
|
|
|
|
|
|
};
|
|
|
|
|
|
case "first-plural":
|
|
|
|
|
|
return {
|
|
|
|
|
|
"subjective": "we",
|
|
|
|
|
|
"objective": "us",
|
|
|
|
|
|
"possessive": "our",
|
|
|
|
|
|
"adjective": "ours",
|
|
|
|
|
|
"reflexive": "ourselves"
|
|
|
|
|
|
};
|
|
|
|
|
|
case "second-plural":
|
|
|
|
|
|
return {
|
|
|
|
|
|
"subjective": "you",
|
|
|
|
|
|
"objective": "you",
|
|
|
|
|
|
"possessive": "your",
|
|
|
|
|
|
"adjective": "yours",
|
|
|
|
|
|
"reflexive": "yourselves"
|
|
|
|
|
|
};
|
|
|
|
|
|
case "third-plural":
|
|
|
|
|
|
return {
|
|
|
|
|
|
"subjective": "they",
|
|
|
|
|
|
"objective": "them",
|
|
|
|
|
|
"possessive": "their",
|
|
|
|
|
|
"adjective": "theirs",
|
|
|
|
|
|
"reflexive": "themselves"
|
|
|
|
|
|
};
|
|
|
|
|
|
case "other":
|
|
|
|
|
|
return other;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Handles capitalization and replacement for pronouns */
|
|
|
|
|
|
function prnHelper(input, params) {
|
|
|
|
|
|
let replacement = params.pronoun;
|
|
|
|
|
|
const isCapital = /P/.test(input.match(params.regexp)[1]);
|
|
|
|
|
|
if (isCapital) {
|
|
|
|
|
|
replacement = capitalize(replacement);
|
|
|
|
|
|
}
|
|
|
|
|
|
return input.replace(params.regexp, replacement);
|
|
|
|
|
|
}
|