UNPKG

glin-profanity

Version:

Glin-Profanity is a lightweight and efficient npm package designed to detect and filter profane language in text inputs across multiple languages. Whether you’re building a chat application, a comment section, or any platform where user-generated content

6,395 lines • 156 kB
'use strict';

// ../../shared/dictionaries/arabic.json
var arabic_default = {
  words: [
    "\u0633\u0643\u0633",
    "\u0637\u064A\u0632",
    "\u0634\u0631\u062C",
    "\u0644\u0639\u0642",
    "\u0644\u062D\u0633",
    "\u062A\u0645\u0635",
    "\u0628\u064A\u0636\u0627\u0646",
    "\u062B\u062F\u064A",
    "\u0628\u0632",
    "\u062D\u0644\u0645\u0629",
    "\u0645\u0641\u0644\u0642\u0633\u0629",
    "\u0628\u0638\u0631",
    "\u0643\u0633",
    "\u0645\u0628\u0627\u062F\u0644",
    "\u0639\u0627\u0647\u0631\u0629",
    "\u062C\u0645\u0627\u0639",
    "\u0642\u0636\u064A\u0628",
    "\u0632\u0628",
    "\u0644\u0648\u0637\u064A",
    "\u0644\u0648\u0627\u0637",
    "\u0633\u062D\u0627\u0642",
    "\u0627\u063A\u062A\u0635\u0627\u0628",
    "\u062E\u0646\u062B\u064A",
    "\u0627\u062D\u062A\u0644\u0627\u0645",
    "\u0646\u064A\u0643",
    "\u0645\u062A\u0646\u0627\u0643",
    "\u0634\u0631\u0645\u0648\u0637\u0629",
    "\u0639\u0631\u0635",
    "\u062E\u0648\u0644",
    "\u0642\u062D\u0628\u0629",
    "\u0644\u0628\u0648\u0629",
    "\u0648\u0633\u062E",
    "\u0639\u0644\u0642",
    "\u0646\u0627\u0643",
    "\u0645\u0646\u064A\u0643",
    "\u0634\u0631\u0645\u0637",
    "\u0642\u062D\u0628",
    "\u0634\u0631\u0645\u0648\u0637",
    "\u062E\u0648\u0644",
    "\u0647\u0646\u064A\u0643",
    "\u0641\u062D\u0644",
    "\u062F\u064A\u0648\u062B",
    "\u0645\u062A\u0646\u0627\u0643",
    "\u0647\u0627\u064A\u062C",
    "\u0632\u0628\u0631",
    "bdsm",
    "\u0632\u0627\u0646",
    "\u0633\u0627\u062F\u064A",
    "\u0633\u0627\u062F\u0649",
    "\u0643\u0633\u0647\u0627",
    "\u0643\u0633\u064A",
    "\u0643\u0633\u0643",
    "\u0643\u0633\u0645",
    "\u0628\u0646\u062A\u0627\u0643",
    "\u0628\u062A\u0646\u0627\u0643",
    "\u0646\u064A\u0643\u0646\u064A",
    "\u062F\u064A\u0648\u062B",
    "\u0646\u064A\u0627\u0643",
    "\u0641\u0634\u0627\u062E",
    "\u0641\u0634\u062E",
    "\u0647\u0641\u0634\u062E\u0643",
    "\u0634\u0630\u0648\u0630",
    "\u0644\u0648\u0627\u0637",
    "\u0644\u0648\u0637\u064A",
    "\u0644\u0648\u0637\u0649",
    "\u0639\u0627\u0647\u0631",
    "\u0644\u062D\u0633",
    "\u0645\u062A\u0639",
    "\u062D\u0645\u0627\u0645",
    "\u0647\u0631\u0628\u0637",
    "\u062F\u064A\u0627\u062B",
    "\u0633\u062D\u0627\u0642",
    "\u062C\u0646\u0633",
    "\u062C\u0646\u0633",
    "\u062C\u0648\u0632\u064A",
    "\u0645\u0631\u0627\u062A\u064A",
    "\u0645\u0631\u0627\u062A\u0649",
    "\u0645\u0631\u0627\u062A",
    "\u0648\u0631\u0627",
    "\u0634\u0639\u0631",
    "\u0645\u0634\u0639\u0631",
    "\u0627\u0631\u0643\u0628",
    "\u0641\u0631\u0633",
    "\u062C\u0627\u062F",
    "\u0645\u0646\u064A\u0648\u0643",
    "\u0632\u0648\u0628\u0631",
    "\u0637\u064A\u0627\u0632",
    "\u0634\u0631\u0627\u0645\u064A\u0637",
    "\u0641\u0627\u062C\u0631",
    "\u0645\u062D\u0631\u0648\u0645",
    "\u0648\u0627\u0642\u0641",
    "\u0628\u064A\u062D\u0643",
    "\u0645\u0645\u062D\u0648\u0646\u0629",
    "\u0644\u0628\u0646",
    "\u0644\u0628\u0648",
    "\u0648\u0627\u0642\u0641",
    "\u0634\u0631\u0642\u0627\u0646",
    "\u0627\u0641\u0636\u064A",
    "\u062F\u0647\u0648\u0644\u062A",
    "bzaz",
    "nek",
    "nik",
    "mtnak",
    "zeb",
    "zep",
    "zobr",
    "zopr",
    "kos",
    "a7a",
    "hayg",
    "\u0644\u0628\u0648\u0647",
    "\u0644\u0628\u0648\u0629",
    "\u0644\u0628\u0648\u062A",
    "sharma",
    "banoty",
    "\u0628\u0646\u0648\u062A\u064A",
    "\u0628\u0646\u0648\u062A\u0649",
    "\u0633\u0644\u0628",
    "\u0639\u0627\u064A\u0632",
    "\u062F\u0643\u0631",
    "\u0646\u062A\u0627\u064A\u0629",
    "\u0646\u062A\u0627\u064A\u0647",
    "\u0632\u0646\u062C",
    "\u0645\u0645\u062D\u0648\u0646",
    "\u062A\u0639\u0628\u0627\u0646",
    "\u0627\u0642\u0639\u062F",
    "\u0628\u0632\u0627\u0632",
    "\u0647\u064A\u062C\u0627\u0646",
    "\u0633\u0627\u0641\u0644",
    "\u0645\u0635\u064A\u0647\u0648\u0644\u064A",
    "\u0647\u062A\u0645\u0635",
    "\u0645\u0635\u064A",
    "\u0628\u0632",
    "hayg",
    "\u0643\u0644\u0648\u062A",
    "\u0627\u0646\u062F\u0631",
    "\u0633\u0646\u062A\u064A\u0627",
    "\u0628\u0648\u0643\u0633\u0631",
    "\u0628\u062A\u0627\u0639",
    "\u0645\u0634\u062A\u0647\u064A",
    "\u0632\u0628\u0649",
    "\u0646\u0643\u062A\u0647",
    "\u0627\u062A\u0646\u0627\u0643",
    "nayak",
    "nyak",
    "neeek",
    "\u0627\u063A\u062A\u0635\u0627",
    "\u0632\u0628",
    "\u0643\u0633",
    "\u0628\u0644\u0643\u064A\u0634\u0646",
    "\u0643\u064A\u0634\u0646",
    "\u0644\u064A\u0633\u0628\u064A\u0627\u0646",
    "\u0633\u0628\u064A\u0627\u0646"
  ]
};

// ../../shared/dictionaries/chinese.json
var chinese_default = {
  words: [
    "13.",
    "13\u70B9",
    "\u4E09\u7EA7\u7247",
    "\u4E0B\u4E09\u70C2",
    "\u4E0B\u8D31",
    "\u4E2A\u8001\u5B50\u7684",
    "\u4E5D\u6E38",
    "\u4E73",
    "\u4E73\u4EA4",
    "\u4E73\u5934",
    "\u4E73\u623F",
    "\u4E73\u6CE2\u81C0\u6D6A",
    "\u4EA4\u914D",
    "\u4EC6\u8857",
    "\u4ED6\u5976\u5976",
    "\u4ED6\u5976\u5976\u7684",
    "\u4ED6\u5976\u5A18\u7684",
    "\u4ED6\u5988",
    "\u4ED6\u5988\u3109\u738B\u516B\u86CB",
    "\u4ED6\u5988\u5730",
    "\u4ED6\u5988\u7684",
    "\u4ED6\u5A18",
    "\u4ED6\u9A6C\u7684",
    "\u4F60\u4E2A\u50BB\u6BD4",
    "\u4F60\u4ED6\u9A6C\u7684",
    "\u4F60\u5168\u5BB6",
    "\u4F60\u5976\u5976\u7684",
    "\u4F60\u5979\u9A6C\u7684",
    "\u4F60\u5988",
    "\u4F60\u5988\u7684",
    "\u4F60\u5A18",
    "\u4F60\u5A18\u5361\u597D",
    "\u4F60\u5A18\u54A7",
    "\u4F60\u5B83\u5988\u7684",
    "\u4F60\u5B83\u9A6C\u7684",
    "\u4F60\u662F\u9E21",
    "\u4F60\u662F\u9E2D",
    "\u4F60\u9A6C\u7684",
    "\u505A\u7231",
    "\u50BB\u6BD4",
    "\u50BB\u903C",
    "\u518C\u90A3",
    "\u519B\u5993",
    "\u51E0\u516B",
    "\u51E0\u53ED",
    "\u51E0\u5DF4",
    "\u51E0\u82AD",
    "\u521A\u5EA6",
    "\u521A\u762A\u4E09",
    "\u5305\u76AE",
    "\u5341\u4E09\u70B9",
    "\u5356B",
    "\u5356\u6BD4",
    "\u5356\u6DEB",
    "\u5375",
    "\u5375\u5B50",
    "\u53CC\u5CF0\u5FAE\u98A4",
    "\u53E3\u4EA4",
    "\u53E3\u80AF",
    "\u53EB\u5E8A",
    "\u5403\u5C4E",
    "\u540E\u5EAD",
    "\u5439\u7BAB",
    "\u585E\u4F60\u516C",
    "\u585E\u4F60\u5A18",
    "\u585E\u4F60\u6BCD",
    "\u585E\u4F60\u7238",
    "\u585E\u4F60\u8001\u5E08",
    "\u585E\u4F60\u8001\u6BCD",
    "\u5904\u5973",
    "\u5916\u9634",
    "\u5927\u5375\u5B50",
    "\u5927\u5375\u6CE1",
    "\u5927\u9E21\u5DF4",
    "\u5976",
    "\u5976\u5976\u7684\u718A",
    "\u5976\u5B50",
    "\u5978",
    "\u5978\u4F60",
    "\u5979\u5988\u5730",
    "\u5979\u5988\u7684",
    "\u5979\u9A6C\u7684",
    "\u5988B",
    "\u5988\u4E2AB",
    "\u5988\u4E2A\u6BD4",
    "\u5988\u4E2A\u8001\u6BD4",
    "\u5988\u5988\u7684",
    "\u5988\u6BD4",
    "\u5988\u7684",
    "\u5988\u7684B",
    "\u5988\u903C",
    "\u5993",
    "\u5993\u5973",
    "\u5993\u9662",
    "\u59B3\u5979\u5988\u7684",
    "\u59B3\u5988\u7684",
    "\u59B3\u5A18\u7684",
    "\u59B3\u8001\u6BCD\u7684",
    "\u59B3\u9A6C\u7684",
    "\u59D8\u5934",
    "\u59E3\u897F",
    "\u59E6",
    "\u5A18\u4E2A\u6BD4",
    "\u5A18\u7684",
    "\u5A4A\u5B50",
    "\u5A4A\u5B50\u517B\u7684",
    "\u5AD6\u5A3C",
    "\u5AD6\u5BA2",
    "\u5B83\u5988\u5730",
    "\u5B83\u5988\u7684",
    "\u5BC6\u6D1E",
    "\u5C04\u4F60",
    "\u5C04\u7CBE",
    "\u5C0F\u4E73\u5934",
    "\u5C0F\u5375\u5B50",
    "\u5C0F\u5375\u6CE1",
    "\u5C0F\u762A\u4E09",
    "\u5C0F\u8089\u7C92",
    "\u5C0F\u9A9A\u6BD4",
    "\u5C0F\u9A9A\u8D27",
    "\u5C0F\u9E21\u5DF4",
    "\u5C0F\u9E21\u9E21",
    "\u5C41\u773C",
    "\u5C41\u80A1",
    "\u5C44",
    "\u5C4C",
    "\u5DE8\u4E73",
    "\u5E72x\u5A18",
    "\u5E72\u4E03\u516B",
    "\u5E72\u4F60",
    "\u5E72\u4F60\u5988",
    "\u5E72\u4F60\u5A18",
    "\u5E72\u4F60\u8001\u6BCD",
    "\u5E72\u4F60\u826F",
    "\u5E72\u59B3\u5988",
    "\u5E72\u59B3\u5A18",
    "\u5E72\u59B3\u8001\u6BCD",
    "\u5E72\u59B3\u9A6C",
    "\u5E72\u60A8\u5A18",
    "\u5E72\u673A\u63B0",
    "\u5E72\u6B7BCS",
    "\u5E72\u6B7BGM",
    "\u5E72\u6B7B\u4F60",
    "\u5E72\u6B7B\u5BA2\u670D",
    "\u5E79",
    "\u5F3A\u5978",
    "\u5F3A\u5978\u4F60",
    "\u6027",
    "\u6027\u4EA4",
    "\u6027\u5668",
    "\u6027\u65E0\u80FD",
    "\u6027\u7231",
    "\u60C5\u8272",
    "\u60F3\u4E0A\u4F60",
    "\u61C6\u60A8\u5988",
    "\u61C6\u60A8\u5A18",
    "\u61D28",
    "\u61D2\u516B",
    "\u61D2\u53EB",
    "\u61D2\u6559",
    "\u6210\u4EBA",
    "\u6211\u64CD\u4F60\u7956\u5B97\u5341\u516B\u4EE3",
    "\u6252\u5149",
    "\u6253\u70AE",
    "\u6253\u98DE\u673A",
    "\u62BD\u63D2",
    "\u62DB\u5993",
    "\u63D2\u4F60",
    "\u63D2\u6B7B\u4F60",
    "\u6492\u5C3F",
    "\u64CD\u4F60",
    "\u64CD\u4F60\u5168\u5BB6",
    "\u64CD\u4F60\u5976\u5976",
    "\u64CD\u4F60\u5988",
    "\u64CD\u4F60\u5A18",
    "\u64CD\u4F60\u7956\u5B97",
    "\u64CD\u4F60\u8001\u5988",
    "\u64CD\u4F60\u8001\u6BCD",
    "\u64CD\u59B3",
    "\u64CD\u59B3\u5168\u5BB6",
    "\u64CD\u59B3\u5988",
    "\u64CD\u59B3\u5A18",
    "\u64CD\u59B3\u7956\u5B97",
    "\u64CD\u673A\u63B0",
    "\u64CD\u6BD4",
    "\u64CD\u903C",
    "\u653E\u8361",
    "\u65E5\u4ED6\u5A18",
    "\u65E5\u4F60",
    "\u65E5\u4F60\u5988",
    "\u65E5\u4F60\u8001\u5A18",
    "\u65E5\u4F60\u8001\u6BCD",
    "\u65E5\u6279",
    "\u6708\u7ECF",
    "\u673A\u516B",
    "\u673A\u5DF4",
    "\u673A\u673A\u6B6A\u6B6A",
    "\u6742\u79CD",
    "\u6D6A\u53EB",
    "\u6DEB",
    "\u6DEB\u4E71",
    "\u6DEB\u5987",
    "\u6DEB\u68CD",
    "\u6DEB\u6C34",
    "\u6DEB\u79FD",
    "\u6DEB\u8361",
    "\u6DEB\u897F",
    "\u6E7F\u900F\u7684\u5185\u88E4",
    "\u6FC0\u60C5",
    "\u7068\u4F60\u5A18",
    "\u70C2\u8D27",
    "\u70C2\u903C",
    "\u721B",
    "\u72D7\u5C41",
    "\u72D7\u65E5",
    "\u72D7\u72FC\u517B\u7684",
    "\u7389\u6775",
    "\u738B\u516B\u86CB",
    "\u74DC\u5A03\u5B50",
    "\u74DC\u5A46\u5A18",
    "\u74DC\u6279",
    "\u762A\u4E09",
    "\u767D\u70C2",
    "\u767D\u75F4",
    "\u767D\u7661",
    "\u7956\u5B97",
    "\u79C1\u670D",
    "\u7B28\u86CB",
    "\u7CBE\u5B50",
    "\u8001\u4E8C",
    "\u8001\u5473",
    "\u8001\u6BCD",
    "\u8001\u762A\u4E09",
    "\u8001\u9A9A\u6BD4",
    "\u8001\u9A9A\u8D27",
    "\u8089\u58C1",
    "\u8089\u68CD\u5B50",
    "\u8089\u68D2",
    "\u8089\u7F1D",
    "\u808F",
    "\u809B\u4EA4",
    "\u80A5\u897F",
    "\u8272\u60C5",
    "\u82B1\u67F3",
    "\u8361\u5987",
    "\u8CE4",
    "\u8D1D\u8089",
    "\u8D31B",
    "\u8D31\u4EBA",
    "\u8D31\u8D27",
    "\u8D3C\u4F60\u5988",
    "\u8D5B\u4F60\u8001\u6BCD",
    "\u8D5B\u59B3\u963F\u6BCD",
    "\u8D63\u60A8\u5A18",
    "\u8F6E\u5978",
    "\u8FF7\u836F",
    "\u903C",
    "\u903C\u6837",
    "\u91CE\u9E21",
    "\u9633\u5177",
    "\u9633\u840E",
    "\u9634\u5507",
    "\u9634\u6237",
    "\u9634\u6838",
    "\u9634\u6BDB",
    "\u9634\u830E",
    "\u9634\u9053",
    "\u9634\u90E8",
    "\u96DE\u5DF4",
    "\u9760\u5317",
    "\u9760\u6BCD",
    "\u9760\u7238",
    "\u9760\u80CC",
    "\u9760\u8170",
    "\u9A76\u4F60\u516C",
    "\u9A76\u4F60\u5A18",
    "\u9A76\u4F60\u6BCD",
    "\u9A76\u4F60\u7238",
    "\u9A76\u4F60\u8001\u5E08",
    "\u9A76\u4F60\u8001\u6BCD",
    "\u9A9A\u6BD4",
    "\u9A9A\u8D27",
    "\u9A9A\u903C",
    "\u9B3C\u516C",
    "\u9E218",
    "\u9E21\u516B",
    "\u9E21\u53ED",
    "\u9E21\u5427",
    "\u9E21\u5978",
    "\u9E21\u5DF4",
    "\u9E21\u82AD",
    "\u9E21\u9E21",
    "\u9F9F\u513F\u5B50",
    "\u9F9F\u5934"
  ]
};

// ../../shared/dictionaries/czech.json
var czech_default = {
  words: [
    "bordel",
    "buzna",
    "\u010Dum\u011Bt",
    "\u010Dur\xE1k",
    "debil",
    "do pi\u010De",
    "do prdele",
    "dr\u0161\u0165ka",
    "dr\u017Eka",
    "flundra",
    "hajzl",
    "hovno",
    "chcanky",
    "chuj",
    "jebat",
    "kokot",
    "kokotina",
    "ko\u0148omrd",
    "kunda",
    "kurva",
    "mamrd",
    "mrdat",
    "mrdka",
    "mrdn\xEDk",
    "oslo\u0161oust",
    "pi\u010Da",
    "p\xED\u010Dus",
    "p\xEDchat",
    "pizda",
    "prcat",
    "prdel",
    "prdelka",
    "sra\u010Dka",
    "sr\xE1t",
    "\u0161oustat",
    "\u0161ulin",
    "vyp\xED\u010Denec",
    "zkurvit",
    "zkurvysyn",
    "zmrd",
    "\u017Er\xE1t"
  ]
};

// ../../shared/dictionaries/danish.json
var danish_default = {
  words: [
    "anus",
    "b\xF8sser\xF8v",
    "cock",
    "fisse",
    "fisseh\xE5r",
    "fuck",
    "hestepik",
    "kussekryller",
    "lort",
    "luder",
    "pik",
    "pikh\xE5r",
    "pikslugeri",
    "piksutteri",
    "pis",
    "r\xF8v",
    "r\xF8vhul",
    "r\xF8vsk\xE6g",
    "r\xF8vspr\xE6ke",
    "shit"
  ]
};

// ../../shared/dictionaries/dutch.json
var dutch_default = {
  words: [
    "aardappels afgieten",
    "achter het raam zitten",
    "afberen",
    "aflebberen",
    "afrossen",
    "afrukken",
    "aftrekken",
    "afwerkplaats",
    "afzeiken",
    "afzuigen",
    "anderhalve man en een paardekop",
    "anita",
    "asbak",
    "aso",
    "bagger schijten",
    "balen",
    "bedonderen",
    "befborstel",
    "beffen",
    "bekken",
    "belazeren",
    "besodemieterd zijn",
    "besodemieteren",
    "beurt",
    "boemelen",
    "boerelul",
    "boerenpummel",
    "bokkelul",
    "botergeil",
    "broekhoesten",
    "brugpieper",
    "buffelen",
    "buiten de pot piesen",
    "da's kloten van de bok",
    "de ballen",
    "de hoer spelen",
    "de hond uitlaten",
    "de koffer induiken",
    "del",
    "de pijp aan maarten geven",
    "de pijp uitgaan",
    "dombo",
    "draaikont",
    "driehoog achter wonen",
    "drol",
    "drooggeiler",
    "droogkloot",
    "een beurt geven",
    "een nummertje maken",
    "een wip maken",
    "eikel",
    "engerd",
    "flamoes",
    "flikken",
    "flikker",
    "gadverdamme",
    "galbak",
    "gat",
    "gedoogzone",
    "geilneef",
    "gesodemieter",
    "godverdomme",
    "graftak",
    "gras maaien",
    "gratenkut",
    "greppeldel",
    "griet",
    "hoempert",
    "hoer",
    "hoerenbuurt",
    "hoerenloper",
    "hoerig",
    "hol",
    "hufter",
    "huisdealer",
    "johny",
    "kanen",
    "kettingzeug",
    "klaarkomen",
    "klerebeer",
    "klojo",
    "klooien",
    "klootjesvolk",
    "klootoog",
    "klootzak",
    "kloten",
    "knor",
    "kont",
    "kontneuken",
    "krentekakker",
    "kut",
    "kuttelikkertje",
    "kwakkie",
    "liefdesgrot",
    "lul",
    "lul-de-behanger",
    "lulhannes",
    "lummel",
    "mafketel",
    "matennaaier",
    "matje",
    "mof",
    "muts",
    "naaien",
    "naakt",
    "neuken",
    "neukstier",
    "nicht",
    "oetlul",
    "opgeilen",
    "opkankeren",
    "oprotten",
    "opsodemieteren",
    "op z'n hondjes",
    "op z'n sodemieter geven",
    "opzouten",
    "ouwehoer",
    "ouwehoeren",
    "ouwe rukker",
    "paal",
    "paardelul",
    "palen",
    "penoze",
    "piesen",
    "pijpbekkie",
    "pijpen",
    "pik",
    "pleurislaaier",
    "poep",
    "poepen",
    "poot",
    "portiekslet",
    "pot",
    "potverdorie",
    "publiciteitsgeil",
    "raaskallen",
    "reet",
    "reetridder",
    "reet trappen, voor zijn",
    "remsporen",
    "reutelen",
    "rothoer",
    "rotzak",
    "rukhond",
    "rukken",
    "schatje",
    "schijt",
    "schijten",
    "schoft",
    "schuinsmarcheerder",
    "shit",
    "slempen",
    "slet",
    "sletterig",
    "slik mijn zaad",
    "snol",
    "spuiten",
    "standje",
    "standje-69",
    "stoephoer",
    "stootje",
    "stront",
    "sufferd",
    "tapijtnek",
    "teef",
    "temeier",
    "teringlijer",
    "toeter",
    "tongzoen",
    "triootje",
    "trottoir prostituee",
    "trottoirteef",
    "vergallen",
    "verkloten",
    "verneuken",
    "viespeuk",
    "vingeren",
    "vleesroos",
    "voor jan lul",
    "voor jan-met-de-korte-achternaam",
    "watje",
    "welzijnsmafia",
    "wijf",
    "wippen",
    "wuftje",
    "zaadje",
    "zakkenwasser",
    "zeiken",
    "zeiker",
    "zuigen",
    "zuiplap"
  ]
};

// ../../shared/dictionaries/esperanto.json
var esperanto_default = {
  words: [
    "bugren",
    "bugri",
    "bugru",
    "\u0109iesulino",
    "\u0109iesulo",
    "diofek",
    "diofeka",
    "fek",
    "feken",
    "fekfikanto",
    "feklekulo",
    "fekulo",
    "fik",
    "fikado",
    "fikema",
    "fikfek",
    "fiki",
    "fiki\u011Di",
    "fiki\u011Du",
    "fikilo",
    "fikkla\u016Dno",
    "fikota",
    "fiku",
    "forfiki",
    "forfiki\u011Du",
    "forfiku",
    "forfurzu",
    "forpisi",
    "forpisu",
    "furzulo",
    "kacen",
    "kaco",
    "kacsu\u0109ulo",
    "kojono",
    "pi\u0109en",
    "pi\u0109o",
    "zamenfek"
  ]
};

// ../../shared/dictionaries/english.json
var english_default = {
  words: [
    "2 girls 1 cup",
    "2g1c",
    "a$$",
    "@ss",
    "4ss",
    "acrotomophilia",
    "alabama hot pocket",
    "alaskan pipeline",
    "anal",
    "anilingus",
    "anus",
    "apeshit",
    "arsehole",
    "ass",
    "asshat",
    "asshole",
    "assmunch",
    "asswipe",
    "auto erotic",
    "autoerotic",
    "b!tch",
    "babeland",
    "baby batter",
    "baby juice",
    "ball gag",
    "ball gravy",
    "ball kicking",
    "ball licking",
    "ball sack",
    "ball sucking",
    "bangbros",
    "bareback",
    "barely legal",
    "barenaked",
    "bastard",
    "bastardized",
    "bastardo",
    "bastinado",
    "bbw",
    "bdsm",
    "beaner",
    "beaners",
    "beaver cleaver",
    "beaver lips",
    "bestiality",
    "big black",
    "big breasts",
    "big knockers",
    "big tits",
    "bimbos",
    "birdlock",
    "bitch",
    "bitches",
    "black cock",
    "blonde action",
    "blonde on blonde action",
    "blow job",
    "blow your load",
    "blowjob",
    "blue waffle",
    "blumpkin",
    "bollocks",
    "bondage",
    "boner",
    "boob",
    "boobs",
    "booty call",
    "brown showers",
    "brunette action",
    "bs",
    "bukkake",
    "bulldyke",
    "bullet vibe",
    "bullshit",
    "bung hole",
    "bunghole",
    "busty",
    "butt",
    "buttcheeks",
    "butthole",
    "camel toe",
    "camgirl",
    "camslut",
    "camwhore",
    "carpet muncher",
    "carpetmuncher",
    "chocolate rosebuds",
    "circlejerk",
    "cleveland steamer",
    "clit",
    "clitoris",
    "clover clamps",
    "clusterfuck",
    "cock",
    "cocks",
    "cocksucker",
    "coon",
    "coons",
    "coprolagnia",
    "coprophilia",
    "cornhole",
    "creampie",
    "cum",
    "cumming",
    "cunnilingus",
    "cunt",
    "darkie",
    "date rape",
    "daterape",
    "deep throat",
    "deepthroat",
    "dendrophilia",
    "dick",
    "dildo",
    "dingleberries",
    "dingleberry",
    "dipshit",
    "dirty pillows",
    "dirty sanchez",
    "dog style",
    "doggie style",
    "doggiestyle",
    "doggy style",
    "doggystyle",
    "dolcett",
    "domination",
    "dominatrix",
    "dommes",
    "donkey punch",
    "double dong",
    "double penetration",
    "dp action",
    "dry hump",
    "dumbass",
    "dvda",
    "eat my ass",
    "ecchi",
    "ejaculation",
    "erotic",
    "erotism",
    "escort",
    "eunuch",
    "f*ck",
    "f@ck",
    "f4ck",
    "fvck",
    "phuck",
    "fuk",
    "faggot",
    "fecal",
    "felch",
    "fellatio",
    "feltch",
    "female squirting",
    "femdom",
    "figging",
    "fingerbang",
    "fingering",
    "fisting",
    "foot fetish",
    "footjob",
    "frotting",
    "fuck",
    "fuck buttons",
    "fucker",
    "fuckface",
    "fuckin",
    "fucking",
    "fucktards",
    "fudge packer",
    "fudgepacker",
    "fuq",
    "futanari",
    "g-spot",
    "gang bang",
    "gay sex",
    "genitals",
    "giant cock",
    "girl on",
    "girl on top",
    "girls gone wild",
    "goatcx",
    "goatse",
    "god damn",
    "gokkun",
    "golden shower",
    "goo girl",
    "goodpoop",
    "goregasm",
    "grope",
    "group sex",
    "guro",
    "hand job",
    "handjob",
    "hard core",
    "hardcore",
    "hentai",
    "hoe",
    "homoerotic",
    "honkey",
    "hooker",
    "hot carl",
    "hot chick",
    "how to kill",
    "how to murder",
    "huge fat",
    "humping",
    "incest",
    "intercourse",
    "jack off",
    "jackass",
    "jail bait",
    "jailbait",
    "jelly donut",
    "jerk off",
    "jigaboo",
    "jiggaboo",
    "jiggerboo",
    "jizz",
    "juggs",
    "kike",
    "kinbaku",
    "kinkster",
    "kinky",
    "knobbing",
    "leather restraint",
    "leather straight jacket",
    "lemon party",
    "lmfao",
    "lolita",
    "lovemaking",
    "make me come",
    "male squirting",
    "masturbate",
    "menage a trois",
    "mf",
    "milf",
    "missionary position",
    "motherfucker",
    "mound of venus",
    "mr hands",
    "muff diver",
    "muffdiving",
    "nambla",
    "nawashi",
    "negro",
    "neonazi",
    "nig nog",
    "nigga",
    "nigger",
    "nimphomania",
    "nipple",
    "nipples",
    "nsfw images",
    "nude",
    "nudity",
    "nympho",
    "nymphomania",
    "octopussy",
    "omfg",
    "omorashi",
    "one cup two girls",
    "one guy one jar",
    "orgasm",
    "orgy",
    "paedophile",
    "paki",
    "panties",
    "panty",
    "pedobear",
    "pedophile",
    "pegging",
    "penis",
    "phone sex",
    "piece of shit",
    "piss pig",
    "pissed",
    "pissing",
    "pisspig",
    "playboy",
    "pleasure chest",
    "pole smoker",
    "ponyplay",
    "poof",
    "poon",
    "poontang",
    "poop chute",
    "poopchute",
    "porn",
    "porno",
    "pornography",
    "prince albert piercing",
    "pthc",
    "pubes",
    "punany",
    "pussy",
    "queaf",
    "queef",
    "quim",
    "raghead",
    "raging boner",
    "rape",
    "raping",
    "rapist",
    "rectum",
    "retard",
    "retarded",
    "reverse cowgirl",
    "rimjob",
    "rimming",
    "rosy palm",
    "rosy palm and her 5 sisters",
    "rusty trombone",
    "s&m",
    "sadism",
    "santorum",
    "scat",
    "schlong",
    "scissoring",
    "semen",
    "sex",
    "sexo",
    "sexy",
    "sh!t",
    "shaved beaver",
    "shaved pussy",
    "shemale",
    "shibari",
    "shit",
    "shite",
    "sh1t",
    "$hit",
    "$h!t",
    "shitblimp",
    "shithead",
    "shitshow",
    "shitty",
    "shota",
    "shrimping",
    "simp",
    "skeet",
    "slanteye",
    "slut",
    "smut",
    "snatch",
    "snowballing",
    "sodomize",
    "sodomy",
    "spic",
    "splooge",
    "splooge moose",
    "spooge",
    "spread legs",
    "spunk",
    "stfu",
    "strap on",
    "strapon",
    "strappado",
    "strip club",
    "style doggy",
    "suck",
    "sucks",
    "suicide girls",
    "sultry women",
    "swastika",
    "swinger",
    "tainted love",
    "taste my",
    "tea bagging",
    "testbad01",
    "threesome",
    "throating",
    "tied up",
    "tight white",
    "tit",
    "tits",
    "titties",
    "titty",
    "tongue in a",
    "topless",
    "tosser",
    "towelhead",
    "tranny",
    "tribadism",
    "tub girl",
    "tubgirl",
    "tushy",
    "twat",
    "twink",
    "twinkie",
    "two girls one cup",
    "undressing",
    "upskirt",
    "urethra play",
    "urophilia",
    "vagina",
    "venus mound",
    "vibrator",
    "violet wand",
    "vorarephilia",
    "voyeur",
    "vulva",
    "wank",
    "wet dream",
    "wetback",
    "white power",
    "wrapping men",
    "wrinkled starfish",
    "wtf",
    "xx",
    "xxx",
    "yaoi",
    "yellow showers",
    "yiffy",
    "zoophilia",
    "\u{1F595}"
  ]
};

// ../../shared/dictionaries/finnish.json
var finnish_default = {
  words: [
    "alfred nussi",
    "bylsi\xE4",
    "haahka",
    "haista paska",
    "haista vittu",
    "hatullinen",
    "helvetisti",
    "hevonkuusi",
    "hevonpaska",
    "hevonperse",
    "hevonvittu",
    "hevonvitunperse",
    "hitosti",
    "hitto",
    "huorata",
    "h\xE4ssi\xE4",
    "juosten kustu",
    "jutku",
    "jutsku",
    "j\xE4tk\xE4",
    "kananpaska",
    "koiranpaska",
    "kuin esterin perseest\xE4",
    "kulli",
    "kullinluikaus",
    "kuppainen",
    "kusaista",
    "kuseksia",
    "kusettaa",
    "kusi",
    "kusip\xE4\xE4",
    "kusta",
    "kyrpiintynyt",
    "kyrpiinty\xE4",
    "kyrpi\xE4",
    "kyrp\xE4",
    "kyrp\xE4naama",
    "kyrvitys",
    "lahtari",
    "lutka",
    "molo",
    "molop\xE4\xE4",
    "mulkero",
    "mulkku",
    "mulkvisti",
    "muna",
    "munap\xE4\xE4",
    "munaton",
    "mutakuono",
    "mutiainen",
    "naida",
    "nainti",
    "narttu",
    "neekeri",
    "nekru",
    "nuolla persett\xE4",
    "nussia",
    "nussija",
    "nussinta",
    "paljaalla",
    "palli",
    "pallit",
    "paneskella",
    "panettaa",
    "panna",
    "pano",
    "pantava",
    "paska",
    "paskainen",
    "paskamainen",
    "paskanmarjat",
    "paskantaa",
    "paskapuhe",
    "paskap\xE4\xE4",
    "paskattaa",
    "paskiainen",
    "paskoa",
    "pehko",
    "pentele",
    "perkele",
    "perkeleesti",
    "persaukinen",
    "perse",
    "perseennuolija",
    "perseet olalla",
    "persereik\xE4",
    "perse\xE4\xE4li\xF6",
    "persl\xE4pi",
    "perspano",
    "persvako",
    "pilkunnussija",
    "pillu",
    "pillut",
    "pipari",
    "piru",
    "pist\xE4\xE4",
    "pyllyvako",
    "reik\xE4",
    "reva",
    "ripsipiirakka",
    "runkata",
    "runkkari",
    "runkkaus",
    "runkku",
    "ryss\xE4",
    "r\xE4ttip\xE4\xE4",
    "saatanasti",
    "suklaaosasto",
    "tavara",
    "toosa",
    "tuhkaluukku",
    "tumputtaa",
    "turpasauna",
    "tussu",
    "tussukka",
    "tussut",
    "vakipano",
    "vet\xE4\xE4 k\xE4teen",
    "viiksi",
    "vittu",
    "vittuilla",
    "vittuilu",
    "vittumainen",
    "vittuuntua",
    "vittuuntunut",
    "vitun",
    "vitusti",
    "vituttaa",
    "vitutus",
    "\xE4p\xE4r\xE4"
  ]
};

// ../../shared/dictionaries/german.json
var german_default = {
  words: [
    "analritter",
    "arsch",
    "arschficker",
    "arschlecker",
    "arschloch",
    "bimbo",
    "bratze",
    "bumsen",
    "bonze",
    "d\xF6del",
    "fick",
    "ficken",
    "flittchen",
    "fotze",
    "fratze",
    "hackfresse",
    "hure",
    "hurensohn",
    "ische",
    "kackbratze",
    "kacke",
    "kacken",
    "kackwurst",
    "kampflesbe",
    "kanake",
    "kimme",
    "l\xFCmmel",
    "MILF",
    "m\xF6pse",
    "morgenlatte",
    "m\xF6se",
    "mufti",
    "muschi",
    "nackt",
    "neger",
    "nigger",
    "nippel",
    "nutte",
    "onanieren",
    "orgasmus",
    "pimmel",
    "pimpern",
    "pinkeln",
    "pissen",
    "pisser",
    "popel",
    "poppen",
    "porno",
    "reudig",
    "rosette",
    "schabracke",
    "schlampe",
    "schei\xDFe",
    "scheisser",
    "schiesser",
    "schnackeln",
    "schwanzlutscher",
    "schwuchtel",
    "tittchen",
    "titten",
    "v\xF6geln",
    "vollpfosten",
    "wichse",
    "wichsen",
    "wichser"
  ]
};

// ../../shared/dictionaries/hindi.json
var hindi_default = {
  words: [
    "aand",
    "aandu",
    "balatkar",
    "beti chod",
    "bhadva",
    "bhadve",
    "bhandve",
    "bhootni ke",
    "bhosad",
    "bhosadi ke",
    "boobe",
    "chakke",
    "chinaal",
    "chinki",
    "chod",
    "chodu",
    "chodu bhagat",
    "chooche",
    "choochi",
    "choot",
    "choot ke baal",
    "chootia",
    "chootiya",
    "chuche",
    "chuchi",
    "chudai khanaa",
    "chudan chudai",
    "chut",
    "chut ke baal",
    "chut ke dhakkan",
    "chut maarli",
    "chutad",
    "chutadd",
    "chutan",
    "chutia",
    "chutiya",
    "gaand",
    "gaandfat",
    "gaandmasti",
    "gaandufad",
    "gandu",
    "gashti",
    "gasti",
    "ghassa",
    "ghasti",
    "harami",
    "haramzade",
    "hawas",
    "hawas ke pujari",
    "hijda",
    "hijra",
    "jhant",
    "jhant chaatu",
    "jhant ke baal",
    "jhantu",
    "kamine",
    "kaminey",
    "kanjar",
    "kutta",
    "kutta kamina",
    "kutte ki aulad",
    "kutte ki jat",
    "kuttiya",
    "loda",
    "lodu",
    "lund",
    "lund choos",
    "lund khajoor",
    "lundtopi",
    "lundure",
    "maa ki chut",
    "maal",
    "madar chod",
    "mooh mein le",
    "mutth",
    "najayaz",
    "najayaz aulaad",
    "najayaz paidaish",
    "paki",
    "pataka",
    "patakha",
    "raand",
    "randi",
    "saala",
    "saala kutta",
    "saali kutti",
    "saali randi",
    "suar",
    "suar ki aulad",
    "tatte",
    "tatti",
    "teri maa ka bhosada",
    "teri maa ka boba chusu",
    "teri maa ki chut",
    "tharak",
    "tharki"
  ]
};

// ../../shared/dictionaries/hungarian.json
var hungarian_default = {
  words: [
    "balfasz",
    "balfaszok",
    "balfaszokat",
    "balfaszt",
    "barmok",
    "barmokat",
    "barmot",
    "barom",
    "baszik",
    "bazmeg",
    "buksza",
    "buksz\xE1k",
    "buksz\xE1kat",
    "buksz\xE1t",
    "b\xFAr",
    "b\xFArok",
    "cs\xF6cs",
    "cs\xF6cs\xF6k",
    "cs\xF6cs\xF6ket",
    "cs\xF6cs\xF6t",
    "fasz",
    "faszfej",
    "faszfejek",
    "faszfejeket",
    "faszfejet",
    "faszok",
    "faszokat",
    "faszt",
    "fing",
    "fingok",
    "fingokat",
    "fingot",
    "franc",
    "francok",
    "francokat",
    "francot",
    "geci",
    "gecibb",
    "gecik",
    "geciket",
    "gecit",
    "kibaszott",
    "kibaszottabb",
    "k\xFAr",
    "kurafi",
    "kurafik",
    "kurafikat",
    "kurafit",
    "kurva",
    "kurv\xE1k",
    "kurv\xE1kat",
    "kurv\xE1t",
    "leggecibb",
    "legkibaszottabb",
    "legszarabb",
    "marha",
    "marh\xE1k",
    "marh\xE1kat",
    "marh\xE1t",
    "megd\xF6glik",
    "pele",
    "pel\xE9k",
    "picsa",
    "pics\xE1kat",
    "pics\xE1t",
    "pina",
    "pin\xE1k",
    "pin\xE1kat",
    "pin\xE1t",
    "pofa",
    "pof\xE1kat",
    "pof\xE1t",
    "p\xF6cs",
    "p\xF6cs\xF6k",
    "p\xF6cs\xF6ket",
    "p\xF6cs\xF6t",
    "punci",
    "puncik",
    "segg",
    "seggek",
    "seggeket",
    "segget",
    "seggfej",
    "seggfejek",
    "seggfejeket",
    "seggfejet",
    "szajha",
    "szajh\xE1k",
    "szajh\xE1kat",
    "szajh\xE1t",
    "szar",
    "szarabb",
    "szarik",
    "szarok",
    "szarokat",
    "szart"
  ]
};

// ../../shared/dictionaries/korean.json
var korean_default = {
  words: [
    "\uAC15\uAC04",
    "\uAC1C\uC0C8\uB07C",
    "\uAC1C\uC790\uC2DD",
    "\uAC1C\uC886",
    "\uAC1C\uCC28\uBC18",
    "\uAC70\uC720",
    "\uACC4\uC9D1\uB144",
    "\uACE0\uC790",
    "\uADFC\uCE5C",
    "\uB178\uBAA8",
    "\uB2C8\uAE30\uBBF8",
    "\uB4A4\uC9C8\uB798",
    "\uB538\uB538\uC774",
    "\uB54C\uC539",
    "\uB610\uB77C\uC774",
    "\uB664\uB188",
    "\uB85C\uB9AC\uD0C0",
    "\uB9DD\uAC00",
    "\uBAB0\uCE74",
    "\uBBF8\uCE5C",
    "\uBBF8\uCE5C\uC0C8\uB07C",
    "\uBC14\uBC14\uB9AC\uB9E8",
    "\uBCC0\uD0DC",
    "\uBCD1\uC2E0",
    "\uBCF4\uC9C0",
    "\uBD88\uC54C",
    "\uBE60\uAD6C\uB9AC",
    "\uC0AC\uAE4C\uC2DC",
    "\uC139\uC2A4",
    "\uC2A4\uC640\uD551",
    "\uC30D\uB188",
    "\uC528\uBC1C",
    "\uC528\uBC1C\uB188",
    "\uC528\uD314",
    "\uC539",
    "\uC539\uBB3C",
    "\uC539\uBE68",
    "\uC539\uC0C8\uB07C",
    "\uC539\uC54C",
    "\uC539\uCC3D",
    "\uC539\uD314",
    "\uC554\uCE90",
    "\uC560\uC790",
    "\uC57C\uB3D9",
    "\uC57C\uC0AC",
    "\uC57C\uC560\uB2C8",
    "\uC5C4\uCC3D",
    "\uC5D0\uB85C",
    "\uC5FC\uBCD1",
    "\uC618\uBCD1",
    "\uC720\uBAA8",
    "\uC721\uAC11",
    "\uC740\uAF34",
    "\uC790\uC704",
    "\uC790\uC9C0",
    "\uC7A1\uB144",
    "\uC885\uAC04\uB098",
    "\uC886",
    "\uC886\uB9CC",
    "\uC8FD\uC77C\uB144",
    "\uC950\uC886",
    "\uC9C1\uCD2C",
    "\uC9F1\uAE68",
    "\uCABD\uBC14\uB9AC",
    "\uCC3D\uB140",
    "\uD3EC\uB974\uB178",
    "\uD558\uB4DC\uCF54\uC5B4",
    "\uD638\uB85C",
    "\uD654\uB0E5\uB144",
    "\uD6C4\uB808\uC544\uB4E4",
    "\uD6C4\uC7A5",
    "\uD76C\uCB48\uADF8\uB9AC"
  ]
};

// ../../shared/dictionaries/norwegian.json
var norwegian_default = {
  words: [
    "drittsekk",
    "faen i helvete",
    "fitte",
    "j\xE6vla",
    "kuk",
    "kukene",
    "kuker",
    "nigger",
    "pikk",
    "sotr\xF8r",
    "st\xE5pikk",
    "st\xE5pikkene",
    "st\xE5pikker"
  ]
};

// ../../shared/dictionaries/persian.json
var persian_default = {
  words: [
    "\u0622\u0628 \u06A9\u06CC\u0631",
    "\u0627\u0631\u06AF\u0627\u0633\u0645",
    "\u0628\u0631\u0647\u0646\u0647",
    "\u067E\u0648\u0631\u0646",
    "\u067E\u0648\u0631\u0646\u0648",
    "\u062A\u062C\u0627\u0648\u0632",
    "\u062A\u062E\u0645\u06CC",
    "\u062C\u0642",
    "\u062C\u0642\u06CC",
    "\u062C\u0644\u0642",
    "\u062C\u0646\u062F\u0647",
    "\u0686\u0648\u0686\u0648\u0644",
    "\u062D\u0634\u0631",
    "\u062D\u0634\u0631\u06CC",
    "\u062F\u0627\u0641",
    "\u062F\u0648\u062F\u0648\u0644",
    "\u0633\u0627\u06A9 \u0632\u062F\u0646",
    "\u0633\u06A9\u0633",
    "\u0633\u06A9\u0633 \u06A9\u0631\u062F\u0646",
    "\u0633\u06A9\u0633\u06CC",
    "\u0633\u0648\u067E\u0631",
    "\u0634\u0642 \u06A9\u0631\u062F\u0646",
    "\u0634\u0647\u0648\u062A",
    "\u0634\u0647\u0648\u062A\u06CC",
    "\u0634\u0648\u0646\u0628\u0648\u0644",
    "\u0641\u06CC\u0644\u0645 \u0633\u0648\u067E\u0631",
    "\u06A9\u0633",
    "\u06A9\u0633 \u062F\u0627\u062F\u0646",
    "\u06A9\u0633 \u06A9\u0631\u062F\u0646",
    "\u06A9\u0633\u06A9\u0634",
    "\u06A9\u0648\u0633",
    "\u06A9\u0648\u0646",
    "\u06A9\u0648\u0646 \u062F\u0627\u062F\u0646",
    "\u06A9\u0648\u0646 \u06A9\u0631\u062F\u0646",
    "\u06A9\u0648\u0646\u06A9\u0634",
    "\u06A9\u0648\u0646\u06CC",
    "\u06A9\u06CC\u0631",
    "\u06A9\u06CC\u0631\u06CC",
    "\u0644\u0627\u067E\u0627",
    "\u0644\u0627\u067E\u0627\u06CC\u06CC",
    "\u0644\u0627\u0634\u06CC",
    "\u0644\u062E\u062A",
    "\u0644\u0634",
    "\u0645\u0646\u06CC",
    "\u0647\u0631\u0632\u0647"
  ]
};

// ../../shared/dictionaries/polish.json
var polish_default = {
  words: [
    "burdel",
    "burdelmama",
    "chuj",
    "chujnia",
    "ciota",
    "cipa",
    "cyc",
    "debil",
    "dmucha\u0107",
    "do kurwy n\u0119dzy",
    "dupa",
    "dupek",
    "duperele",
    "dziwka",
    "fiut",
    "g\xF3wno",
    "g\xF3wno prawda",
    "huj",
    "jajco",
    "jajeczko",
    "jajko",
    "jajo",
    "ja pierdol\u0119",
    "jeba\u0107",
    "jebany",
    "kurwa",
    "kurwy",
    "kutafon",
    "kutas",
    "liza\u0107 pa\u0142\u0119",
    "obci\u0105ga\u0107 chuja",
    "obci\u0105ga\u0107 fiuta",
    "obci\u0105ga\u0107 loda",
    "pieprzy\u0107",
    "pierdolec",
    "pierdoli\u0107",
    "pierdolni\u0119ty",
    "pierdo\u0142a",
    "pierdzie\u0107",
    "pizda",
    "pojeb",
    "popierdolony",
    "robic loda",
    "robi\u0107 loda",
    "rucha\u0107",
    "rzyga\u0107",
    "skurwysyn",
    "sraczka",
    "sra\u0107",
    "suka",
    "syf",
    "wkurwia\u0107",
    "zajebisty"
  ]
};

// ../../shared/dictionaries/portuguese.json
var portuguese_default = {
  words: [
    "aborto",
    "amador",
    "\xE2nus",
    "aranha",
    "ariano",
    "balalao",
    "bastardo",
    "bicha",
    "biscate",
    "bissexual",
    "boceta",
    "boob",
    "bosta",
    "braulio de borracha",
    "bumbum",
    "burro",
    "cabrao",
    "cacete",
    "cagar",
    "camisinha",
    "caralho",
    "cerveja",
    "chochota",
    "chupar",
    "clitoris",
    "coca\xEDna",
    "colhoes",
    "comer",
    "cona",
    "consolo",
    "corno",
    "cu",
    "dar o rabo",
    "dum raio",
    "esporra",
    "fecal",
    "filho da puta",
    "foda",
    "foda-se",
    "foder",
    "frango assado",
    "gozar",
    "grelho",
    "hero\xEDna",
    "heterosexual",
    "homem gay",
    "homoer\xF3tico",
    "homosexual",
    "inferno",
    "l\xE9sbica",
    "lolita",
    "mama",
    "merda",
    "paneleiro",
    "passar um cheque",
    "pau",
    "peidar",
    "p\xEAnis",
    "pinto",
    "porra",
    "puta",
    "puta que pariu",
    "puta que te pariu",
    "queca",
    "sacanagem",
    "saco",
    "torneira",
    "transar",
    "vai-te foder",
    "vai tomar no cu",
    "veado",
    "vibrador",
    "xana",
    "xochota"
  ]
};

// ../../shared/dictionaries/russian.json
var russian_default = {
  words: [
    "bychara",
    "byk",
    "chernozhopyi",
    "dolboy'eb",
    "ebalnik",
    "ebalo",
    "ebalom sch'elkat",
    "gol",
    "mudack",
    "opizdenet",
    "osto'eblo",
    "ostokhuitel'no",
    "ot'ebis",
    "otmudohat",
    "otpizdit",
    "otsosi",
    "padlo",
    "pedik",
    "perdet",
    "petuh",
    "pidar gnoinyj",
    "pizda",
    "pizdato",
    "pizdatyi",
    "piz'det",
    "pizdetc",
    "pizdoi nakryt'sja",
    "pizd'uk",
    "piz`dyulina",
    "podi ku'evo",
    "poeben",
    "po'imat' na konchik",
    "po'iti posrat",
    "po khuy",
    "poluchit pizdy",
    "pososi moyu konfetku",
    "prissat",
    "proebat",
    "promudobl'adsksya pizdopro'ebina",
    "propezdoloch",
    "prosrat",
    "raspeezdeyi",
    "raspizdatyi",
    "raz'yebuy",
    "raz'yoba",
    "s'ebat'sya",
    "shalava",
    "styervo",
    "sukin syn",
    "svodit posrat",
    "svoloch",
    "trakhat'sya",
    "trimandoblydskiy pizdoproyob",
    "ubl'yudok",
    "uboy",
    "u'ebitsche",
    "vafl'a",
    "vafli lovit",
    "v pizdu",
    "vyperdysh",
    "vzdrochennyi",
    "yeb vas",
    "za'ebat",
    "zaebis",
    "zalupa",
    "zalupat",
    "zasranetc",
    "zassat",
    "zlo'ebuchy",
    "\u0431\u0430\u0440\u0434\u0430\u043A",
    "\u0431\u0437\u0434\u0451\u043D\u043E\u043A",
    "\u0431\u043B\u044F\u0434\u043A\u0438",
    "\u0431\u043B\u044F\u0434\u043E\u0432\u0430\u0442\u044C",
    "\u0431\u043B\u044F\u0434\u0441\u0442\u0432\u043E",
    "\u0431\u043B\u044F\u0434\u044C",
    "\u0431\u0443\u0433\u043E\u0440",
    "\u0432\u043E \u043F\u0438\u0437\u0434\u0443",
    "\u0432\u0441\u0442\u0430\u0442\u044C \u0440\u0430\u043A\u043E\u043C",
    "\u0432\u044B\u0451\u0431\u044B\u0432\u0430\u0442\u044C\u0441\u044F",
    "\u0433\u0430\u043D\u0434\u043E\u043D",
    "\u0433\u043E\u0432\u043D\u043E",
    "\u0433\u043E\u0432\u043D\u044E\u043A",
    "\u0433\u043E\u043B\u044B\u0439",
    "\u0434\u0430\u0442\u044C \u043F\u0438\u0437\u0434\u044B",
    "\u0434\u0435\u0440\u044C\u043C\u043E",
    "\u0434\u0440\u043E\u0447\u0438\u0442\u044C",
    "\u0434\u0440\u0443\u0433\u043E\u0439 \u0434\u0440\u0430\u0437\u043D\u0438\u0442\u0441\u044F",
    "\u0451\u0431\u0430\u0440\u044C",
    "\u0435\u0431\u0430\u0442\u044C",
    "\u0435\u0431\u0430\u0442\u044C-\u043A\u043E\u043F\u0430\u0442\u044C",
    "\u0435\u0431\u043B\u043E",
    "\u0435\u0431\u043D\u0443\u0442\u044C",
    "\u0451\u0431 \u0442\u0432\u043E\u044E \u043C\u0430\u0442\u044C",
    "\u0436\u043E\u043F\u0430",
    "\u0436\u043E\u043F\u043E\u043B\u0438\u0437",
    "\u0438\u0433\u0440\u0430\u0442\u044C \u043D\u0430 \u043A\u043E\u0436\u0430\u043D\u043E\u0439 \u0444\u043B\u0435\u0439\u0442\u0435",
    "\u0438\u0437\u043C\u0443\u0434\u043E\u0445\u0430\u0442\u044C",
    "\u043A\u0430\u0436\u0434\u044B\u0439 \u0434\u0440\u043E\u0447\u0438\u0442 \u043A\u0430\u043A \u043E\u043D \u0445\u043E\u0447\u0435\u0442",
    "\u043A\u0430\u043A\u0430\u044F \u0440\u0430\u0437\u043D\u0438\u0446\u0430",
    "\u043A\u0430\u043A \u0434\u0432\u0430 \u043F\u0430\u043B\u044C\u0446\u0430 \u043E\u0431\u043E\u0441\u0441\u0430\u0442\u044C",
    "\u043A\u0443\u0440\u0438\u0442\u0435 \u043C\u043E\u044E \u0442\u0440\u0443\u0431\u043A\u0443",
    "\u043B\u044B\u0441\u043E\u0433\u043E \u0432 \u043A\u0443\u043B\u0430\u043A\u0435 \u0433\u043E\u043D\u044F\u0442\u044C",
    "\u043C\u0430\u043B\u043E\u0444\u044F",
    "\u043C\u0430\u043D\u0434\u0430",
    "\u043C\u0430\u043D\u0434\u0430\u0432\u043E\u0448\u043A\u0430",
    "\u043C\u0435\u043D\u0442",
    "\u043C\u0443\u0434\u0430",
    "\u043C\u0443\u0434\u0438\u043B\u043E",
    "\u043C\u0443\u0434\u043E\u0437\u043C\u043E\u043D",
    "\u043D\u0430\u0435\u0431\u0430\u0442\u044C",
    "\u043D\u0430\u0435\u0431\u0435\u043D\u0438\u0442\u044C\u0441\u044F",
    "\u043D\u0430\u0435\u0431\u043D\u0443\u0442\u044C\u0441\u044F",
    "\u043D\u0430 \u0444\u0438\u0433",
    "\u043D\u0430 \u0445\u0443\u0439",
    "\u043D\u0430 \u0445\u0443\u044E \u0432\u0435\u0440\u0442\u0435\u0442\u044C",
    "\u043D\u0430 \u0445\u0443\u044F",
    "\u043D\u0430\u0445\u0443\u044F\u0447\u0438\u0442\u044C\u0441\u044F",
    "\u043D\u0435\u0432\u0435\u0431\u0435\u043D\u043D\u044B\u0439",
    "\u043D\u0435 \u0435\u0431\u0435\u0442",
    "\u043D\u0438 \u0437\u0430 \u0445\u0443\u0439 \u0441\u043E\u0431\u0430\u0447\u0443",
    "\u043D\u0438 \u0445\u0443\u044F",
    "\u043E\u0431\u043D\u0430\u0436\u0435\u043D\u043D\u044B\u0439",
    "\u043E\u0431\u043E\u0441\u0441\u0430\u0442\u044C\u0441\u044F \u043C\u043E\u0436\u043D\u043E",
    "\u043E\u0434\u0438\u043D \u0435\u0431\u0451\u0442\u0441\u044F",
    "\u043E\u043F\u0435\u0441\u0434\u043E\u043B",
    "\u043E\u0444\u0438\u0433\u0435\u0442\u044C",
    "\u043E\u0445\u0443\u0435\u0442\u044C",
    "\u043E\u0445\u0443\u0439\u0442\u0435\u043B\u044C\u043D\u043E",
    "\u043F\u043E\u043B\u043E\u0432\u043E\u0435 \u0441\u043D\u043E\u0448\u0435\u043D\u0438\u0435",
    "\u0441\u0435\u043A\u0441",
    "\u0441\u0438\u0441\u043A\u0438",
    "\u0441\u043F\u0438\u0437\u0434\u0438\u0442\u044C",
    "\u0441\u0440\u0430\u0442\u044C",
    "\u0441\u0441\u0430\u0442\u044C",
    "\u0442\u0440\u0430x\u0430\u0442\u044C",
    "\u0442\u044B \u043C\u043D\u0435 \u0432\u0430\u043D\u044C\u043A\u0443 \u043D\u0435 \u0432\u0430\u043B\u044F\u0439",
    "\u0444\u0438\u0433\u0430",
    "\u0445\u0430\u043F\u0430\u0442\u044C",
    "\u0445\u0435\u0440 \u0441 \u043D\u0435\u0439",
    "\u0445\u0435\u0440 \u0441 \u043D\u0438\u043C",
    "\u0445\u043E\u0445\u043E\u043B",
    "\u0445\u0440\u0435\u043D",
    "\u0445\u0443\u0451\u0432\u043E",
    "\u0445\u0443\u0451\u0432\u044B\u0439",
    "\u0445\u0443\u0435\u043C \u0433\u0440\u0443\u0448\u0438 \u043E\u043A\u043E\u043B\u0430\u0447\u0438\u0432\u0430\u0442\u044C",
    "\u0445\u0443\u0435\u043F\u043B\u0435\u0442",
    "\u0445\u0443\u0438\u043B\u043E",
    "\u0445\u0443\u0438\u043D\u0435\u0439 \u0441\u0442\u0440\u0430\u0434\u0430\u0442\u044C",
    "\u0445\u0443\u0438\u043D\u044F",
    "\u0445\u0443\u0439",
    "\u0445\u0443\u0439\u043D\u0443\u0442\u044C",
    "\u0445\u0443\u0439 \u043F\u0438\u043D\u0430\u0442\u044C"
  ]
};

// ../../shared/dictionaries/french.json
var french_default = {
  words: [
    "baiser",
    "bander",
    "bigornette",
    "bite",
    "bitte",
    "bloblos",
    "bordel",
    "bosser",
    "bourr\xE9",
    "bourr\xE9e",
    "brackmard",
    "branlage",
    "branler",
    "branlette",
    "branleur",
    "branleuse",
    "brouter le cresson",
    "caca",
    "cailler",
    "chatte",
    "chiasse",
    "chier",
    "chiottes",
    "clito",
    "clitoris",
    "con",
    "connard",
    "connasse",
    "conne",
    "couilles",
    "cramouille",
    "cul",
    "d\xE9conne",
    "d\xE9conner",
    "drague",
    "emmerdant",
    "emmerder",
    "emmerdeur",
    "emmerdeuse",
    "encul\xE9",
    "encul\xE9e",
    "enculeur",
    "enculeurs",
    "enfoir\xE9",
    "enfoir\xE9e",
    "\xE9tron",
    "fille de pute",
    "fils de pute",
    "folle",
    "foutre",
    "gerbe",
    "gerber",
    "gouine",
    "grande folle",
    "grogniasse",
    "gueule",
    "jouir",
    "la putain de ta m\xE8re",
    "MALPT",
    "m\xE9nage \xE0 trois",
    "merde",
    "merdeuse",
    "merdeux",
    "meuf",
    "n\xE8gre",
    "nique ta m\xE8re",
    "nique ta race",
    "palucher",
    "p\xE9dale",
    "p\xE9d\xE9",
    "p\xE9ter",
    "pipi",
    "pisser",
    "pouffiasse",
    "pousse-crotte",
    "putain",
    "pute",
    "ramoner",
    "sac \xE0 foutre",
    "sac \xE0 merde",
    "salaud",
    "salope",
    "suce",
    "tapette",
    "tanche",
    "teuch",
    "teuf",
    "tringler",
    "trique",
    "troncher",
    "trou du cul",
    "turlute",
    "veuve",
    "zigounette",
    "zizi"
  ]
};

// ../../shared/dictionaries/italian.json
var italian_default = {
  words: [
    "allupato",
    "ammucchiata",
    "anale",
    "arrapato",
    "arrusa",
    "arruso",
    "assatanato",
    "bagascia",
    "bagassa",
    "bagnarsi",
    "baldracca",
    "balle",
    "battere",
    "battona",
    "belino",
    "biga",
    "bocchinara",
    "bocchino",
    "bofilo",
    "boiata",
    "bordello",
    "brinca",
    "bucaiolo",
    "budi\xF9lo",
    "buona donna",
    "busone",
    "cacca",
    "caccati in mano e prenditi a schiaffi",
    "caciocappella",
    "cadavere",
    "cagare",
    "cagata",
    "cagna",
    "cammello",
    "cappella",
    "carciofo",
    "carit\xE0",
    "casci",
    "cazzata",
    "cazzimma",
    "cazzo",
    "checca",
    "chiappa",
    "chiavare",
    "chiavata",
    "ciospo",
    "ciucciami il cazzo",
    "coglione",
    "coglioni",
    "cornuto",
    "cozza",
    "culattina",
    "culattone",
    "culo",
    "di merda",
    "ditalino",
    "duro",
    "fare una sega",
    "fava",
    "femminuccia",
    "fica",
    "figa",
    "figlio di buona donna",
    "figlio di puttana",
    "figone",
    "finocchio",
    "fottere",
    "fottersi",
    "fracicone",
    "fregna",
    "frocio",
    "froscio",
    "fuori come un balcone",
    "goldone",
    "grilletto",
    "guanto",
    "guardone",
    "incazzarsi",
    "incoglionirsi",
    "ingoio",
    "l'arte bolognese",
    "leccaculo",
    "lecchino",
    "lofare",
    "loffa",
    "loffare",
    "lumaca",
    "manico",
    "mannaggia",
    "merda",
    "merdata",
    "merdoso",
    "mignotta",
    "minchia",
    "minchione",
    "mona",
    "monta",
    "montare",
    "mussa",
    "nave scuola",
    "nerchia",
    "nudo",
    "padulo",
    "palle",
    "palloso",
    "patacca",
    "patonza",
    "pecorina",
    "pesce",
    "picio",
    "pincare",
    "pipa",
    "pip\xEC",
    "pippone",
    "pirla",
    "pisciare",
    "piscio",
    "pisello",
    "pistola",
    "pistolotto",
    "pomiciare",
    "pompa",
    "pompino",
    "porca",
    "porca madonna",
    "porca miseria",
    "porca puttana",
    "porco due",
    "porco zio",
    "potta",
    "puppami",
    "puttana",
    "quaglia",
    "recchione",
    "regina",
    "rincoglionire",
    "rizzarsi",
    "rompiballe",
    "ruffiano",
    "sbattere",
    "sbattersi",
    "sborra",
    "sborrata",
    "sborrone",
    "sbrodolata",
    "scopare",
    "scopata",
    "scorreggiare",
    "sega",
    "slinguare",
    "slinguata",
    "smandrappata",
    "soccia",
    "socmel",
    "sorca",
    "spagnola",
    "spompinare",
    "sticchio",
    "stronza",
    "stronzata",
    "stronzo",
    "succhiami",
    "sveltina",
    "sverginare",
    "tarzanello",
    "terrone",
    "testa di cazzo",
    "tette",
    "tirare",
    "topa",
    "troia",
    "trombare",
    "uccello",
    "vacca",
    "vaffanculo",
    "vangare",
    "venire",
    "zinne",
    "zio cantante",
    "zoccola"
  ]
};

// ../../shared/dictionaries/japanese.json
var japanese_default = {
  words: [
    "3p",
    "g \u30B9\u30DD\u30C3\u30C8",
    "s \uFF06 m",
    "sm",
    "sm\u5973\u738B",
    "xx",
    "\u30A2\u30B8\u30A2\u306E\u304B\u308F\u3044\u3044\u5973\u306E\u5B50",
    "\u30A2\u30B9\u30DB\u30FC\u30EB",
    "\u30A2\u30CA\u30EA\u30F3\u30B0\u30B9",
    "\u30A2\u30CA\u30EB",
    "\u3044\u305F\u305A\u3089",
    "\u30A4\u30E9\u30DE\u30C1\u30AA",
    "\u30A6\u30A7\u30D6\u30AB\u30E1\u30E9",
    "\u30A8\u30AF\u30B9\u30BF\u30B7\u30FC",
    "\u30A8\u30B9\u30B3\u30FC\u30C8",
    "\u30A8\u30C3\u30C1",
    "\u30A8\u30ED\u30C6\u30A3\u30BA\u30E0",
    "\u30A8\u30ED\u30C6\u30A3\u30C3\u30AF",
    "\u30AA\u30FC\u30AC\u30BA\u30E0",
    "\u30AA\u30AB\u30DE",
    "\u304A\u3057\u3063\u3053",
    "\u304A\u3057\u308A",
    "\u30AA\u30B7\u30EA",
    "\u304A\u3057\u308A\u306E\u3042\u306A",
    "\u304A\u3063\u3071\u3044",
    "\u30AA\u30C3\u30D1\u30A4",
    "\u30AA\u30CA\u30CB\u30FC",
    "\u30AA\u30DE\u30F3\u30B3",
    "\u304A\u3082\u3089\u3057",
    "\u304A\u5C3B",
    "\u30AB\u30FC\u30DE\u30B9\u30FC\u30C8\u30E9",
    "\u30AB\u30F3\u30C8",
    "\u30AF\u30EA\u30C8\u30EA\u30B9",
    "\u30B0\u30EB\u30FC\u30D7\u30FB\u30BB\u30C3\u30AF\u30B9",
    "\u30B0\u30ED",
    "\u30AF\u30F3\u30CB\u30EA\u30F3\u30B0\u30B9",
    "\u30B2\u30A4\u30FB\u30BB\u30C3\u30AF\u30B9",
    "\u30B2\u30A4\u306E\u7537\u6027",
    "\u30B2\u30A4\u30DC\u30FC\u30A4",
    "\u30B4\u30FC\u30EB\u30C7\u30F3\u30B7\u30E3\u30EF\u30FC",
    "\u30B3\u30AB\u30A4\u30F3",
    "\u30B4\u30C3\u30AF\u30F3",
    "\u30B5\u30C7\u30A3\u30BA\u30E0",
    "\u3057\u3070\u308A",
    "\u30B9\u30A6\u30A3\u30F3\u30AC\u30FC",
    "\u30B9\u30AB\u30FC\u30C8\u306E\u4E2D",
    "\u30B9\u30AB\u30C8\u30ED",
    "\u30B9\u30C8\u30E9\u30C3\u30D7\u30AA\u30F3",
    "\u30B9\u30C8\u30EA\u30C3\u30D7\u5287\u5834",
    "\u30B9\u30E9\u30C3\u30C8",
    "\u30B9\u30EA\u30C3\u30C8",
    "\u30BB\u30AF\u30B7\u30FC\u306A",
    "\u30BB\u30AF\u30B7\u30FC\u306A 10 \u4EE3",
    "\u30BB\u30C3\u30AF\u30B9",
    "\u30BD\u30C9\u30DF\u30FC",
    "\u3061\u3093\u3053",
    "\u30C7\u30A3\u30FC\u30D7\u30FB\u30B9\u30ED\u30FC\u30C8",
    "\u30C7\u30A3\u30C3\u30AF",
    "\u30C7\u30A3\u30EB\u30C9",
    "\u30C7\u30FC\u30C8\u30EC\u30A4\u30D7",
    "\u30C7\u30D6",
    "\u30C6\u30EC\u30D5\u30A9\u30F3\u30BB\u30C3\u30AF\u30B9",
    "\u30C9\u30C3\u30B0\u30B9\u30BF\u30A4\u30EB",
    "\u30C8\u30C3\u30D7\u30EC\u30B9",
    "\u306A\u3081",
    "\u30CB\u30AC\u30FC",
    "\u30CC\u30FC\u30C9",
    "\u30CD\u30AA\u30FB\u30CA\u30C1",
    "\u30CF\u30FC\u30C9\u30B3\u30A2",
    "\u30D1\u30A4\u30D1\u30F3",
    "\u30D0\u30A4\u30D6\u30EC\u30FC\u30BF\u30FC",
    "\u30D0\u30C3\u30AF\u30FB\u30B9\u30BF\u30A4\u30EB",
    "\u30D1\u30F3\u30C6\u30A3\u30FC",
    "\u30D3\u30C3\u30C1",
    "\u30D5\u30A1\u30C3\u30AF",
    "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FC",
    "\u30D5\u30A3\u30B9\u30C8",
    "\u30D5\u30A7\u30C6\u30A3\u30C3\u30B7\u30E5",
    "\u30D5\u30A7\u30E9\u30C1\u30AA",
    "\u3075\u305F\u306A\u308A",
    "\u3076\u3063\u304B\u3051",
    "\u30D5\u30C3\u30AF",
    "\u30D7\u30EA\u30F3\u30B9 \u30A2\u30EB\u30D0\u30FC\u30C8 \u30D4\u30A2\u30B9",
    "\u30D7\u30EC\u30A4\u30DC\u30FC\u30A4",
    "\u30D9\u30A2\u30D0\u30C3\u30AF",
    "\u30DA\u30CB\u30B9",
    "\u30DA\u30CB\u30B9\u30D0\u30F3\u30C9",
    "\u30DC\u30FC\u30A4\u30BA\u30E9\u30D6",
    "\u30DC\u30FC\u30EB\u30AE\u30E3\u30B0",
    "\u30DC\u30FC\u30EB\u3092\u8E74\u308B",
    "\u307D\u3063\u3061\u3083\u308A",
    "\u30DB\u30E2",
    "\u30DD\u30EB\u30CE",
    "\u30DD\u30EB\u30CE\u30B0\u30E9\u30D5\u30A3\u30FC",
    "\u30DC\u30F3\u30C6\u30FC\u30B8",
    "\u30DE\u30B6\u30FC\u30FB\u30D5\u30A1\u30C3\u30AB\u30FC",
    "\u30DE\u30B9\u30BF\u30FC\u30D9\u30FC\u30B7\u30E7\u30F3",
    "\u307E\u3093\u3053",
    "\u3084\u304A\u3044",
    "\u3084\u308A\u307E\u3093",
    "\u30E6\u30C0\u30E4\u4EBA",
    "\u30E9\u30C6\u30A3\u30FC\u30CA",
    "\u30E9\u30D0\u30FC",
    "\u30E9\u30F3\u30B8\u30A7\u30EA\u30FC",
    "\u30EC\u30A4\u30D7",
    "\u30EC\u30BA\u30D3\u30A2\u30F3",
    "\u30ED\u30FC\u30BF\u30FC",
    "\u30ED\u30EA\u30FC\u30BF",
    "\u6DEB\u4E71",
    "\u9670\u6BDB",
    "\u9769\u6291\u5236",
    "\u9A0E\u4E0A\u4F4D",
    "\u5DE8\u6839",
    "\u5DE8\u4E73",
    "\u5F37\u59E6\u72AF",
    "\u7389\u306A\u3081",
    "\u7389\u8210\u3081",
    "\u7DCA\u7E1B",
    "\u8FD1\u89AA\u76F8\u59E6",
    "\u5F8C\u80CC\u4F4D",
    "\u5408\u610F\u306E\u6027\u4EA4",
    "\u62F7\u554F",
    "\u6BBA\u3057\u65B9",
    "\u6BBA\u4EBA\u4E8B\u4EF6",
    "\u6BBA\u4EBA\u65B9\u6CD5",
    "\u652F\u914D",
    "\u5150\u7AE5\u6027\u8650\u5F85",
    "\u81EA\u5DF1\u611B\u6027",
    "\u5C04\u7CBE",
    "\u624B\u30B3\u30AD",
    "\u7363\u59E6",
    "\u5973\u738B\u69D8",
    "\u5973\u5B50\u9AD8\u751F",
    "\u5973\u88C5",
    "\u65B0\u3057\u3044\u30DD\u30EB\u30CE",
    "\u4EBA\u59BB",
    "\u4EBA\u7A2E",
    "\u6027\u4EA4",
    "\u6B63\u5E38\u4F4D",
    "\u751F\u6B96\u5668",
    "\u7CBE\u6DB2",
    "\u633F\u5165",
    "\u8DB3\u30D5\u30A7\u30C1",
    "\u8DB3\u3092\u5E83\u3052\u308B",
    "\u5927\u9670\u5507",
    "\u8131\u8863",
    "\u8336\u8272\u306E\u30B7\u30E3\u30EF\u30FC",
    "\u4E2D\u51FA\u3057",
    "\u6F6E\u5439\u304D\u5973",
    "\u6F6E\u5439\u304D\u7537\u6027",
    "\u76F4\u8178",
    "\u5243\u6BDB",
    "\u8C9E\u64CD\u5E2F",
    "\u5974\u96B7",
    "\u4E8C\u7A74",
    "\u4E73\u9996",
    "\u5C3F\u9053\u30D7\u30EC\u30A4",
    "\u8997\u304D",
    "\u58F2\u6625\u5A66",
    "\u7E1B\u308A",
    "\u5674\u51FA",
    "\u7CDE",
    "\u7CDE\u5C3F\u611B\u597D\u75C7",
    "\u7CDE\u4FBF",
    "\u5E73\u624B\u6253\u3061",
    "\u5909\u614B",
    "\u52C3\u8D77\u3059\u308B",
    "\u5922\u7CBE",
    "\u6BDB\u6DF1\u3044",
    "\u8A98\u60D1",
    "\u5E7C\u5150",
    "\u5E7C\u5150\u6027\u611B\u8005",
    "\u88F8",
    "\u88F8\u306E\u5973\u6027",
    "\u4E71\u4EA4",
    "\u4E21\u6027",
    "\u4E21\u6027\u5177\u6709",
    "\u4E21\u5200",
    "\u8F2A\u59E6",
    "\u534D",
    "\u5BA6\u5B98",
    "\u809B\u9580",
    "\u81A3"
  ]
};

// ../../shared/dictionaries/turkish.json
var turkish_default = {
  words: [
    "18+",
    "30+1",
    "31",
    ":poop:",
    "a.g.",
    "a.mk",
    "a.q",
    "a.q.",
    "a.\u0131g",
    "abaza",
    "abazan",
    "adi",
    "ag",
    "ag.",
    "ahmak",
    "ak",
    "akp",
    "al a\u011Fz\u0131na",
    "allahs\u0131z",
    "am",
    "am biti",
    "am.k",
    "amar\u0131m",
    "ambiti",
    "amcik",
    "amck",
    "amckl",
    "amcklama",
    "amcklaryla",
    "amckta",
    "amcktan",
    "amcuk",
    "amc\u0131k",
    "amc\u0131k ho\u015Faf\u0131",
    "amc\u0131ka\u011F\u0131z",
    "amc\u0131klama",
    "amc\u0131kland\u0131",
    "amc\u0131klar\u0131",
    "amc\u0131\u011F\u0131 ",
    "amc\u0131\u011F\u0131n",
    "amc\u0131\u011F\u0131n\u0131",
    "amc\u0131\u011F\u0131n\u0131z\u0131",
    "amed",
    "amg",
    "amin oglu",
    "amina",
    "amina g",
    "amina k",
    "amina koyarim",
    "amina koyayim",
    "amina koyay\u0131m",
    "aminako",
    "aminakoyarim",
    "aminakoyim",
    "aminda",
    "amindan",
    "amindayken",
    "amini",
    "aminiyarraaniskiim",
    "aminoglu",
    "amiyum",
    "amk",
    "amk \xE7ocu\u011Fu",
    "amk.",
    "amkafa",
    "amlarnzn",
    "aml\u0131",
    "amm",
    "ammak",
    "ammna",
    "amm\u0131na",
    "amn",
    "amna",
    "amnda",
    "amndaki",
    "amngtn",
    "amnn",
    "amona",
    "amq",
    "amsiz",
    "amsz",
    "ams\u0131z",
    "amteri",
    "amugaa",
    "amuna",
    "amu\u011Fa",
    "am\u0131g",
    "am\u0131k",
    "am\u0131n",
    "am\u0131n feryad\u0131",
    "am\u0131n oglu",
    "am\u0131n o\u011Flu",
    "am\u0131na",
    "Am\u0131na",
    "am\u0131na g",
    "am\u0131na k",
    "am\u0131na koy",
    "am\u0131na koyar\u0131m",
    "am\u0131na koyay\u0131m",
    "am\u0131na koyim",
    "am\u0131na koyyim",
    "am\u0131na s",
    "am\u0131na sikem",
    "am\u0131na sokam",
    "am\u0131nad\xFCrz\xFC",
    "am\u0131nako",
    "am\u0131nakoyim",
    "am\u0131no\u011Flu",
    "am\u0131n\u0131",
    "am\u0131n\u0131 s",
    "am\u0131s\u0131na",
    "am\u0131s\u0131n\u0131",
    "anaaann",
    "anal",
    "analarn",
    "anam",
    "anamla",
    "anan",
    "anana",
    "anandan",
    "anani",
    "anani sikerim",
    "anani sikeyim",
    "ananin",
    "ananisikerim",
    "ananisikeyim",
    "anann",
    "ananz",
    "anan\u0131",
    "anan\u0131 ",
    "anan\u0131 sikerim",
    "anan\u0131 sikeyim",
    "anan\u0131n",
    "anan\u0131n am",
    "anan\u0131n am\u0131",
    "anan\u0131n d\xF6l\xFC",
    "anan\u0131nki",
    "anan\u0131sikerim",
    "anan\u0131sikeyim",
    "anan\u0131z\u0131",
    "anan\u0131z\u0131n",
    "anan\u0131z\u0131n am",
    "anas",
    "anasi",
    "anasinin",
    "anas\u0131 orospu",
    "anas\u0131n\u0131",
    "anas\u0131n\u0131 avrad\u0131n\u0131",
    "anas\u0131n\u0131n am",
    "anay",
    "anayin",
    "angut",
    "anl\u0131k be\u011Feni",
    "anneni",
    "annenin",
    "annesiz",
    "anuna",
    "aptal",
    "aq",
    "aq.",
    "ass",
    "atkafas\u0131",
    "atm\u0131k",
    "attrrm",
    "att\u0131rd\u0131\u011F\u0131m",
    "auzlu",
    "avon",
    "avrad\u0131n\u0131",
    "avrat",
    "ayklarmalrmsikerim",
    "azd\u0131m",
    "azd\u0131r",
    "azd\u0131r\u0131c\u0131",
    "a\u011Fz\u0131na",
    "a\u011Fz\u0131na s\u0131\xE7ay\u0131m",
    "a\u015Fa\u011Fl\u0131k",
    "a\u015Fa\u011Fl\u0131ksn\u0131z",
    "a\u015Fa\u011F\u0131l\u0131ks\u0131n",
    "a\u015Fa\u011F\u0131l\u0131ks\u0131n\u0131z",
    "b.k",
    "babaannesi ka\u015Far",
    "babafingo",
    "babani",
    "baban\u0131",
    "baban\u0131n",
    "babas\u0131 pezevenk",
    "babas\u0131 tornac\u0131 anas\u0131 orospu",
    "baca\u011F\u0131na s\u0131\xE7ay\u0131m",
    "bacini",
    "bacn",
    "bacndan",
    "bacy",
    "bac\u0131n",
    "bac\u0131na",
    "bac\u0131n\u0131",
    "bac\u0131n\u0131n",
    "bastard",
    "basur",
    "bayanlar",
    "beyinsiz",
    "be\u011Fen",
    "be\u011Fenirmisiniz",
    "bira",
    "biseksuel",
    "bisekuel",
    "bisexual",
    "bisikiverem",
    "bitch",
    "biting",
    "bok",
    "boka",
    "bokbok",
    "bokhu",
    "bokkkumu",
    "boklar",
    "boktan",
    "boku",
    "bokubokuna",
    "bokum",
    "bok\xE7a",
    "bombok",
    "boner",
    "bosalmak",
    "boycot",
    "boycott",
    "boykot",
    "bo\u015Falmak",
    "bo\u015Fluk",
    "browni",
    "b\u0131z\u0131r",
    "ccc",
    "ccc ccc",
    "ccc incisiker ccc",
    "cccincisikerccc",
    "ccs colgate ccc",
    "cenabet",
    "cibiliyetsiz",
    "cibilliyetini",
    "cibilliyetsiz",
    "cif",
    "cikar",
    "cim",
    "cm",
    "dalaks\u0131z",
    "dallama",
    "daltassak",
    "dalyarak",
    "dalyarrak",
    "dangalak",
    "dasak",
    "dassagi",
    "dassak",
    "da\u015Fak",
    "da\u015F\u015Fak",
    "da\u015F\u015Faks\u0131z",
    "dede",
    "destek",
    "destekk",
    "deste\u011Finizi",
    "devsol",
    "dhkp",
    "diktim",
    "dildo",
    "din",
    "dinci",
    "dingil",
    "dingilini",
    "dinsiz",
    "dkerim",
    "domal",
    "domalan",
    "domald\u0131",
    "domald\u0131n",
    "domalmak",
    "domalm\u0131\u015F",
    "domals\u0131n",
    "domalt",
    "domaltarak",
    "domaltip",
    "domaltmak",
    "domalt\u0131p",
    "domalt\u0131r",
    "domalt\u0131r\u0131m",
    "domal\u0131k",
    "domal\u0131yor",
    "domuz yag\u0131",
    "domuz ya\u011F\u0131",
    "domuzyag\u0131",
    "durum",
    "d\xF6l\xFC",
    "d\xF6nek",
    "d\xFCd\xFCk",
    "d\xFCrz\xFC",
    "eben",
    "ebeni",
    "ebenin",
    "ebeninki",
    "ebleh",
    "ecdadini",
    "ecdad\u0131n\u0131",
    "edit",
    "embesil",
    "emek server",
    "emi",
    "ensest",
    "erotik",
    "escinsel",
    "ev han\u0131mlar\u0131",
    "ezik",
    "e\u015Fcinsel",
    "fahise",
    "fahi\u015Fe",
    "farmasi",
    "fasist",
    "fasizm",
    "fa\u015Fist",
    "fa\u015Fizm",
    "feri\u015Ftah",
    "ferre",
    "fuck",
    "fucker",
    "fuckin",
    "fucking",
    "f\u0131rsat",
    "g*t",
    "g*t\xFC",
    "g*t\xFCn",
    "g*t\xFCne",
    "g.t",
    "gasp",
    "gavad",
    "gavat",
    "gavurun d\xF6l\xFC",
    "gay",
    "geber",
    "geberik",
    "gebermek",
    "gebermi\u015F",
    "gebersin",
    "gebertir",
    "geri zekal\u0131",
    "gerizekali",
    "gerizekal\u0131",
    "gerizekal\u0131d\u0131r",
    "gerzek",
    "ger\u0131 zekal\u0131d\u0131r",
    "ger\u0131zekal\u0131",
    "giberim",
    "giberler",
    "gibis",
    "gibi\u015F",
    "gibmek",
    "gibtiler",
    "giren \xE7\u0131kan",
    "goddamn",
    "godo\u015F",
    "godumun",
    "got",
    "gotelek",
    "gotlalesi",
    "gotlu",
    "gotten",
    "gotundeki",
    "gotunden",
    "gotune",
    "gotunu",
    "gotuze",
    "gotveren",
    "goyiim",
    "goyum",
    "goyuyim",
    "goyyim",
    "gtelek",
    "gtn",
    "gtnde",
    "gtnden",
    "gtne",
    "gtten",
    "gtveren",
    "guat",
    "g\xF6t",
    "g\xF6t deli\u011Fi",
    "g\xF6t herif",
    "g\xF6t o\u011Flan\u0131",
    "g\xF6t veren",
    "g\xF6t verir",
    "g\xF6te",
    "g\xF6telek",
    "g\xF6tlalesi",
    "g\xF6tlek",
    "g\xF6to\u011Flan\u0131",
    "g\xF6to\u015F",
    "g\xF6ts\xFCn",
    "g\xF6ts\xFCn\xFCz",
    "g\xF6tten",
    "g\xF6tveren",
    "g\xF6tvern",
    "g\xF6t\xFC",
    "g\xF6t\xFCn",
    "g\xF6t\xFCne",
    "g\xF6t\xFCne koyim",
    "g\xF6t\xFCnekoyim",
    "g\xF6t\xFCn\xFC",
    "g\xF6t\xFCn\xFCze",
    "g\xF6t\xFCyle",
    "g\xF6\u011F\xFCs",
    "hack",
    "han\u0131mlar",
    "has siktir",
    "hasiktir",
    "hasiktr",
    "hassikome",
    "hassiktir",
    "hassittir",
    "hastir",
    "haydar",
    "haysiyetsiz",
    "hayvan",
    "hayvan herif",
    "ha\u015Fgerya",
    "hile",
    "hizbullah",
    "homo",
    "ho\u015Faf\u0131",
    "hristiyan",
    "hsktr",
    "huur",
    "hz.muhammed",
    "hz.\xF6mer",
    "h\xF6d\xFCk",
    "h\u0131rs\u0131z",
    "h\u0131yar",
    "h\u0131yara\u011Fas\u0131",
    "i.ne",
    "ibina",
    "ibine",
    "ibinenin",
    "ibne",
    "ibnedir",
    "ibneler",
    "ibneleri",
    "ibnelik",
    "ibneli\u011Fi",
    "ibnelri",
    "ibneni",
    "ibnenin",
    "ibnerator",
    "ibnesi",
    "idiot",
    "idiyot",
    "igrenc",
    "ilk yorum",
    "imansz",
    "inci siker",
    "incisike",
    "incisiker",
    "ipne",
    "ipneler",
    "irsail",
    "iserim",
    "israil",
    "israli",
    "israyil",
    "isra\u0131l",
    "it",
    "iti",
    "itler",
    "ito\u011Flu it",
    "izrail",
    "i\u011Fren\xE7",
    "i\u015F f\u0131rsat\u0131",
    "i\u015Ferim",
    "k.o.c",
    "k.o.\xE7",
    "kadek",
    "kafam girsin",
    "kafasiz",
    "kafas\u0131z",
    "kahpe",
    "kahpenin",
    "kahpenin feryad\u0131",
    "kaka",
    "kaltak",
    "kancik",
    "kanc\u0131k",
    "kan\u0131",
    "kan\u0131 bozuk",
    "kappe",
    "karhane",
    "kariyer f\u0131rsat\u0131",
    "kavat",
    "kavatn",
    "kaypak",
    "kayyum",
    "kazik",
    "kazma",
    "kaz\u0131k",
    "ka\xE7\u0131rma",
    "ka\u015Far",
    "kerane",
    "kerhane",
    "kerhanelerde",
    "keriz",
    "kerizler",
    "kevase",
    "keva\u015Fe",
    "kevvase",
    "koca g\xF6t",
    "kodumun",
    "kodumunun",
    "koduumun",
    "kodu\u011Fmun",
    "kodu\u011Fmunun",
    "komonist",
    "komunist",
    "komunizm",
    "kom\xFCnist",
    "kom\xFCnizm",
    "kongragel",
    "kopegi",
    "kope\u011Fi",
    "koyarm",
    "koyay\u0131m",
    "koyiim",
    "koyiiym",
    "koyim",
    "koyum",
    "koyyim",
    "krar",
    "kro",
    "kro musunuz",
    "kromusunuz",
    "kuku",
    "kukudaym",
    "kurdistan",
    "k\xF6peginin",
    "k\xF6pekle\u015Fme",
    "k\xFCrdistan",
    "k\u0131ro",
    "k\u0131\xE7",
    "k\u0131\xE7\u0131n\u0131z",
    "k\u0131\xE7\u0131n\u0131za",
    "l.an",
    "laciye boyad\u0131m",
    "lan",
    "lanet",
    "lann",
    "lannn",
    "lavuk",
    "len",
    "leyn",
    "lezle",
    "lezler",
    "libo\u015F",
    "madafaka",
    "mal",
    "malafat",
    "malak",
    "mallar",
    "manda",
    "mankafa",
    "manyak",
    "mcik",
    "meme",
    "memelerini",
    "memi\u015F",
    "mezveleli",
    "minaamc\u0131k",
    "mincikliyim",
    "mk",
    "mna",
    "moloz",
    "monakkoluyum",
    "moron",
    "motherfucker",
    "mudik",
    "musevi",
    "musevi bozuk",
    "m\xFCsl\xFCman",
    "nah",
    "o \xE7",
    "o. \xE7ocu\u011Fu",
    "o.\xE7",
    "o.\xE7.",
    "oc",
    "ocuu",
    "ocuun",
    "odun",
    "okuz",
    "oriflame",
    "orosbu",
    "orosbucocuu",
    "orospu",
    "orospu cocugu",
    "orospu \xE7oc",
    "orospu \xE7ocuklar\u0131",
    "orospu \xE7ocu\u011Fu",
    "orospu \xE7ocu\u011Fudur",
    "orospucocugu",
    "orospudur",
    "orospular",
    "orospunun",
    "orospunun evlad\u0131",
    "orospuydu",
    "orospuyuz",
    "orospu\xE7ocu\u011Fu",
    "orostoban",
    "orostopol",
    "orrospu",
    "oruc",
    "oruspu",
    "oruspu \xE7ocu\u011Fu",
    "oruspu\xE7ocu\u011Fu",
    "oru\xE7",
    "osbir",
    "ossurduum",
    "ossurmak",
    "ossuruk",
    "osur",
    "osuram",
    "osurduu",
    "osuruk",
    "osururum",
    "otusbir",
    "otuzbir",
    "oyna",
    "O\xC7",
    "o\xE7",
    "o\xE7.",
    "o\u011Flan",
    "o\u011Flanc\u0131",
    "o\u011Flu it",
    "p kk",
    "patlak zar",
    "penis",
    "peygamber",
    "pezevek",
    "pezeven",
    "pezeveng",
    "pezevengi",
    "pezevengin evlad\u0131",
    "pezevenk",
    "pezevenkler",
    "pezo",
    "pic",
    "pici",
    "picler",
    "pipi",
    "pipi\u015F",
    "pis",
    "pisliktir",
    "pi\xE7",
    "pi\xE7 kurusu",
    "pi\xE7i",
    "pi\xE7in o\u011Flu",
    "pi\xE7inin",
    "pi\xE7ler",
    "pi\xE7sin",
    "pi\xE7siniz",
    "pkk",
    "pok",
    "pokunu",
    "pompalama",
    "pompalamak",
    "porn",
    "porno",
    "protesto",
    "pussy",
    "putpererst",
    "putperest",
    "pu\u015Ft",
    "pu\u015Fttur",
    "pvp",
    "rahminde",
    "rak",
    "rak\u0131",
    "revizyonist",
    "s.ikerim",
    "s.iktm",
    "s.ktir",
    "s.tir",
    "s1kerim",
    "s1kerm",
    "s1krm",
    "sahtekar",
    "sakso",
    "saksofon",
    "salaak",
    "salak",
    "sana ne",
    "sanane",
    "saxo",
    "sayfa",
    "sekis",
    "seks",
    "serefsiz",
    "serefsz",
    "serefszler",
    "sevgi koyar\u0131m",
    "sevi\u015F",
    "sevi\u015Felim",
    "sevi\u015Fmek",
    "sex",
    "sexs",
    "shit",
    "si.\xE7mak",
    "sicarsin",
    "sicmak",
    "sicti",
    "sie",
    "sigara",
    "sik",
    "sik k\u0131r\u0131\u011F\u0131",
    "sikdi",
    "sikdi\u011Fim",
    "sike",
    "sikecem",
    "sikem",
    "siken",
    "sikenin",
    "siker",
    "sikerim",
    "sikerler",
    "sikersin",
    "sikert",
    "sikertir",
    "sikertirler",
    "sikertmek",
    "sikesen",
    "sikesicenin",
    "sikey",
    "sikeydim",
    "sikeyim",
    "sikeym",
    "siki",
    "sikicem",
    "sikici",
    "sikien",
    "sikienler",
    "sikiiim",
    "sikiiimmm",
    "sikiim",
    "sikiir",
    "sikiirken",
    "sikik",
    "sikil",
    "sikildiini",
    "sikilesice",
    "sikilmi",
    "sikilmie",
    "sikilmis",
    "sikilmi\u015F",
    "sikilsin",
    "sikim",
    "sikimde",
    "sikimden",
    "sikime",
    "sikimi",
    "sikimiin",
    "sikimin",
    "sikimle",
    "sikimsonik",
    "sikimtrak",
    "sikin",
    "sikinde",
    "sikinden",
    "sikine",
    "sikini",
    "sikip",
    "sikis",
    "sikisek",
    "sikisen",
    "sikish",
    "sikismis",
    "sikitiin",
    "sikiyim",
    "sikiym",
    "sikiyorum",
    "siki\u015F",
    "siki\u015Fen",
    "siki\u015Fken",
    "siki\u015Fme",
    "siki\u015Fmek",
    "sikkim",
    "sikko",
    "sikleri",
    "sikleriii",
    "sikli",
    "sikm",
    "sikmek",
    "sikmem",
    "sikmeyi",
    "sikmiler",
    "sikmisligim",
    "siksem",
    "sikseydin",
    "sikseyidin",
    "siksin",
    "siksinbaya",
    "siksinler",
    "siksiz",
    "siksok",
    "siksz",
    "sikt",
    "sikti",
    "siktigimin",
    "siktigiminin",
    "siktii",
    "siktiim",
    "siktiimin",
    "siktiiminin",
    "siktiler",
    "siktim",
    "siktim ",
    "siktimin",
    "siktiminin",
    "siktin",
    "siktir",
    "siktir et",
    "siktir git",
    "siktir lan",
    "siktir ol git",
    "siktirgit",
    "siktirir",
    "siktiririm",
    "siktiriyor",
    "siktirolgit",
    "siktirsin",
    "sikti\u011Fim",
    "sikti\u011Fimin",
    "sikti\u011Fiminin",
    "siktr",
    "siqem",
    "sisme",
    "sittimin",
    "sittir",
    "skcem",
    "skecem",
    "skem",
    "sker",
    "skerim",
    "skerm",
    "skeyim",
    "skiim",
    "skik",
    "skim",
    "skime",
    "skiym",
    "skm",
    "skmek",
    "skrm",
    "sksin",
    "sksn",
    "sksz",
    "sktiimin",
    "sktim",
    "sktir",
    "sktirsin",
    "sktr",
    "sktroradan",
    "sktrr",
    "sktrsn",
    "skyim",
    "slaleni",
    "snane",
    "sokacak",
    "sokam",
    "sokarim",
    "sokarm",
    "sokarmkoduumun",
    "sokar\u0131m",
    "sokaym",
    "sokay\u0131m",
    "sokiim",
    "soktu\u011Fumunun",
    "sokuk",
    "sokum",
    "sokuyum",
    "soku\u015F",
    "soxum",
    "sperm",
    "sulaleni",
    "s\xE7kik",
    "s\xFClaleni",
    "s\xFClalenizi",
    "s\xFCrt\xFCk",
    "s\xFCt\xFC bozuk",
    "s\xFCzme",
    "s\u0131ecem",
    "s\u0131ker\u0131m",
    "s\u0131km",
    "s\u0131kt\u0131r",
    "s\u0131\xE7ar\u0131m",
    "s\u0131\xE7mak",
    "s\u0131\xE7t\u0131\u011F\u0131m",
    "taaklarn",
    "taaklarna",
    "takip",
    "takip et",
    "takma",
    "tarrakimin",
    "tasak",
    "tassak",
    "tayyip",
    "ta\u015Fak",
    "ta\u015F\u015Fak",
    "temas\u0131yla",
    "teror",
    "terorist",
    "ter\xF6r",
    "ter\xF6rist",
    "thumb?",
    "tipini s.k",
    "tipinizi s.keyim",
    "tiyniyat",
    "tokmaklama",
    "tokmaklamak",
    "top",
    "toplarm",
    "topsun",
    "toto\u015F",
    "transeksuel",
    "transeks\xFCel",
    "travesti",
    "ukler",
    "ulan",
    "ulen",
    "ulker",
    "vagina",
    "vajina",
    "vajinan\u0131",
    "va\u015Fina",
    "veled",
    "veled i zina",
    "veledizina",
    "verdiimin",
    "verem",
    "vodka",
    "votka",
    "weled",
    "weledizina",
    "whore",
    "wslik",
    "wslik server",
    "xikeyim",
    "yaaraaa",
    "yahudi",
    "yalaka",
    "yalama",
    "yalarun",
    "yalar\u0131m",
    "yaraaam",
    "yarak",
    "yaraks\u0131z",
    "yaraktr",
    "yaram",
    "yaraminbasi",
    "yaramn",
    "yararmorospunun",
    "yark",
    "yarra",
    "yarraaaa",
    "yarraak",
    "yarraam",
    "yarraam\u0131",
    "yarragi",
    "yarragimi",
    "yarragina",
    "yarragindan",
    "yarragm",
    "yarraimin",
    "yarrak",
    "yarram",
    "yarramin",
    "yarraminba\u015F\u0131",
    "yarramn",
    "yarram\u0131n",
    "yarran",
    "yarrana",
    "yarra\u011F",
    "yarra\u011F\u0131m",
    "yarra\u011F\u0131m\u0131",
    "yarrk",
    "yarro",
    "yarrrak",
    "yaudi",
    "yavak",
    "yavu\u015Fak",
    "yav\u015F",
    "yav\u015Fak",
    "yav\u015Fakt\u0131r",
    "yav\u015Famak",
    "ya\u011Fudi",
    "yilisik",
    "yogurtlayam",
    "yosma",
    "yo\u011Furtlayam",
    "yrak",
    "yrk",
    "yrrak",
    "y\u0131l\u0131\u015F\u0131k",
    "zibidi",
    "zigsin",
    "zikeyim",
    "zikiiim",
    "zikiim",
    "zikik",
    "zikim",
    "ziksiiin",
    "ziksiin",
    "zina",
    "zulliyetini",
    "zviyetini",
    "z\u0131kk\u0131m\u0131m",
    "\xE7al\u0131\u015Fmak istermisiniz?",
    "\xE7\xFCk",
    "\xF6k\xFCz",
    "\xF6l",
    "\xF6\u015Fex",
    "\xFCkler",
    "\xFClker",
    "\xFClk\xFCc\xFC",
    "\u0131bnel\u0131k",
    "\u015Ferefsiz",
    "\u015Ferefsizler",
    "\u015Ferefsizlerin",
    "\u015Ferefsizlik",
    "\u015Firret",
    "\u015Fi\u015Fme",
    "\u015Fi\u015Fme bebek",
    "\u015Fi\u015Fme kad\u0131n",
    "\u015Fi\u015Fme manken",
    "\u015F\u0131ll\u0131k",
    "\u015F\u0131rf\u0131nt\u0131"
  ]
};

// ../../shared/dictionaries/spanish.json
var spanish_default = {
  words: [
    "asesinato",
    "asno",
    "bastardo",
    "Bollera",
    "Cabron",
    "Cabr\xF3n",
    "Caca",
    "Chupada",
    "Chupapollas",
    "Chupet\xF3n",
    "concha",
    "Concha de tu madre",
    "Co\xF1o",
    "Coprofag\xEDa",
    "Culo",
    "Drogas",
    "Esperma",
    "Fiesta de salchichas",
    "Follador",
    "Follar",
    "Gilipichis",
    "Gilipollas",
    "Hacer una paja",
    "Haciendo el amor",
    "Hero\xEDna",
    "Hija de puta",
    "Hijaputa",
    "Hijo de puta",
    "Hijoputa",
    "Idiota",
    "Imb\xE9cil",
    "infierno",
    "Jilipollas",
    "Kapullo",
    "Lameculos",
    "Maciza",
    "Macizorra",
    "maldito",
    "Mamada",
    "Marica",
    "Maric\xF3n",
    "Mariconazo",
    "martillo",
    "Mierda",
    "Nazi",
    "Orina",
    "Pedo",
    "Pervertido",
    "Pez\xF3n",
    "Pinche",
    "Pis",
    "Prostituta",
    "Puta",
    "Racista",
    "Ramera",
    "S\xE1dico",
    "Semen",
    "Sexo",
    "Sexo oral",
    "Soplagaitas",
    "Soplapollas",
    "Tetas grandes",
    "T\xEDa buena",
    "Travesti",
    "Trio",
    "Verga",
    "vete a la mierda",
    "Vulva"
  ]
};

// ../../shared/dictionaries/swedish.json
var swedish_default = {
  words: [
    "arsle",
    "brutta",
    "discofitta",
    "dra \xE5t helvete",
    "fan",
    "fitta",
    "fittig",
    "f\xF6r helvete",
    "helvete",
    "h\xE5rd",
    "j\xE4vlar",
    "knulla",
    "kuk",
    "kuks\xE5s",
    "k\xF6tthuvud",
    "k\xF6ttnacke",
    "moona",
    "moonade",
    "moonar",
    "moonat",
    "mutta",
    "nigger",
    "neger",
    "olla",
    "pippa",
    "pitt",
    "prutt",
    "p\xF6k",
    "runka",
    "r\xF6v",
    "r\xF6vh\xE5l",
    "r\xF6vknulla",
    "satan",
    "skita",
    "skit ner dig",
    "sk\xE4ggbiff",
    "snedfitta",
    "snefitta",
    "stake",
    "subba",
    "s\xE5s",
    "s\xE4tta p\xE5",
    "tusan"
  ]
};

// ../../shared/dictionaries/thai.json
var thai_default = {
  words: [
    "\u0E01\u0E23\u0E30\u0E14\u0E2D",
    "\u0E01\u0E23\u0E30\u0E40\u0E14\u0E49\u0E32",
    "\u0E01\u0E23\u0E30\u0E2B\u0E23\u0E35\u0E48",
    "\u0E01\u0E30\u0E1B\u0E34",
    "\u0E01\u0E39",
    "\u0E02\u0E35\u0E49",
    "\u0E04\u0E27\u0E22",
    "\u0E08\u0E34\u0E4B\u0E21",
    "\u0E08\u0E39\u0E4B",
    "\u0E40\u0E08\u0E4A\u0E01",
    "\u0E40\u0E08\u0E35\u0E4A\u0E22\u0E27",
    "\u0E14\u0E2D\u0E01\u0E17\u0E2D\u0E07",
    "\u0E15\u0E2D\u0E41\u0E2B\u0E25",
    "\u0E15\u0E39\u0E14",
    "\u0E19\u0E49\u0E4D\u0E32\u0E41\u0E15\u0E01",
    "\u0E21\u0E36\u0E07",
    "\u0E41\u0E21\u0E48\u0E07",
    "\u0E40\u0E22\u0E47\u0E14",
    "\u0E23\u0E39\u0E15\u0E39\u0E14",
    "\u0E25\u0E49\u0E32\u0E07\u0E15\u0E39\u0E49\u0E40\u0E22\u0E47\u0E19",
    "\u0E2A\u0E49\u0E19\u0E15\u0E35\u0E19",
    "\u0E2A\u0E31\u0E14",
    "\u0E40\u0E2A\u0E37\u0E2D\u0E01",
    "\u0E2B\u0E0D\u0E34\u0E07\u0E0A\u0E32\u0E15\u0E34\u0E0A\u0E31\u0E48\u0E27",
    "\u0E2B\u0E25\u0E31\u0E48\u0E07",
    "\u0E2B\u0E48\u0E32",
    "\u0E2B\u0E4D\u0E32",
    "\u0E2B\u0E35",
    "\u0E40\u0E2B\u0E35\u0E49\u0E22",
    "\u0E2D\u0E21\u0E19\u0E01\u0E40\u0E02\u0E32",
    "\u0E44\u0E2D\u0E49\u0E04\u0E27\u0E32\u0E22"
  ]
};

// src/data/dictionary.ts
var dictionary_default = {
  arabic: arabic_default.words,
  chinese: chinese_default.words,
  czech: czech_default.words,
  danish: danish_default.words,
  dutch: dutch_default.words,
  english: english_default.words,
  esperanto: esperanto_default.words,
  finnish: finnish_default.words,
  french: french_default.words,
  german: german_default.words,
  hindi: hindi_default.words,
  hungarian: hungarian_default.words,
  italian: italian_default.words,
  japanese: japanese_default.words,
  korean: korean_default.words,
  norwegian: norwegian_default.words,
  persian: persian_default.words,
  polish: polish_default.words,
  portuguese: portuguese_default.words,
  russian: russian_default.words,
  spanish: spanish_default.words,
  turkish: turkish_default.words,
  swedish: swedish_default.words,
  thai: thai_default.words
};

// src/nlp/contextAnalyzer.ts
var POSITIVE_INDICATORS = /* @__PURE__ */ new Set([
  "amazing",
  "awesome",
  "excellent",
  "fantastic",
  "great",
  "love",
  "wonderful",
  "brilliant",
  "perfect",
  "incredible",
  "outstanding",
  "superb",
  "magnificent",
  "marvelous",
  "spectacular",
  "phenomenal",
  "terrific",
  "fabulous",
  "divine",
  "best",
  "good",
  "nice",
  "cool",
  "sweet",
  "rad",
  "sick",
  "dope",
  "fire",
  "lit",
  "epic",
  "legendary",
  "godlike",
  "insane",
  "crazy",
  "wild",
  "beast",
  "movie",
  "film",
  "show",
  "song",
  "music",
  "game",
  "book",
  "restaurant",
  "food",
  "dish",
  "meal",
  "place",
  "spot",
  "location",
  "experience"
]);
var NEGATIVE_INDICATORS = /* @__PURE__ */ new Set([
  "hate",
  "terrible",
  "awful",
  "horrible",
  "disgusting",
  "pathetic",
  "stupid",
  "idiot",
  "moron",
  "loser",
  "worthless",
  "useless",
  "garbage",
  "trash",
  "suck",
  "sucks",
  "worst",
  "bad",
  "ugly",
  "gross",
  "nasty",
  "annoying",
  "irritating",
  "frustrating",
  "disappointing",
  "lame",
  "weak",
  "fail",
  "you",
  "your",
  "yourself",
  "u",
  "ur",
  "ure",
  "youre"
]);
var GAMING_POSITIVE = /* @__PURE__ */ new Set([
  "player",
  "gamer",
  "team",
  "squad",
  "clan",
  "guild",
  "match",
  "game",
  "round",
  "level",
  "boss",
  "raid",
  "quest",
  "achievement",
  "skill",
  "build",
  "loadout",
  "strategy",
  "tactic",
  "play",
  "move",
  "combo"
]);
var GAMING_ACCEPTABLE_WORDS = /* @__PURE__ */ new Set([
  "kill",
  "killer",
  "killed",
  "killing",
  "shoot",
  "shot",
  "shooting",
  "die",
  "dying",
  "died",
  "dead",
  "death",
  "badass",
  "sick",
  "insane",
  "crazy",
  "mad",
  "beast",
  "savage",
  "suck",
  "sucks",
  "wtf",
  "omg",
  "hell",
  "damn",
  "crap"
]);
var POSITIVE_PHRASES = /* @__PURE__ */ new Map([
  ["the bomb", 0.9],
  // "this movie is the bomb"
  ["da bomb", 0.9],
  // slang for "the best"
  ["bomb.com", 0.9],
  // website reference
  ["bomb diggity", 0.9],
  // slang for excellent
  ["photo bomb", 0.8],
  // photography term
  ["bath bomb", 0.8],
  // cosmetic product
  ["bomb squad", 0.7]
  // could be neutral/positive in gaming
]);
var NEGATIVE_PHRASES = /* @__PURE__ */ new Map([
  ["you are", 0.1],
  // "you are [profanity]"
  ["ur a", 0.1],
  // "ur a [profanity]"
  ["such a", 0.2],
  // "such a [profanity]"
  ["fucking", 0.1],
  // intensifier, usually negative
  ["damn", 0.2]
  // mild profanity, context dependent
]);
var ContextAnalyzer = class {
  constructor(config) {
    this.contextWindow = config.contextWindow;
    this.language = config.language;
    this.domainWhitelists = new Set(
      (config.domainWhitelists || []).map((word) => word.toLowerCase())
    );
  }
  /**
   * Analyzes the context around a profanity match to determine if it should be flagged
   */
  analyzeContext(text, matchWord, matchIndex) {
    const tokens = this.tokenizeWithSpans(text);
    const matchWordIndex = this.findWordIndex(tokens, matchIndex);
    if (matchWordIndex === -1) {
      return {
        contextScore: 0.5,
        reason: "Could not locate match in tokenized text",
        isWhitelisted: false
      };
    }
    const startIndex = Math.max(0, matchWordIndex - this.contextWindow);
    const endIndex = Math.min(tokens.length, matchWordIndex + this.contextWindow + 1);
    const contextWords = tokens.slice(startIndex, endIndex).map((token) => token.word);
    const contextText = contextWords.join(" ").toLowerCase();
    const phraseResult = this.checkPhraseContext(contextText, matchWord);
    if (phraseResult) {
      return phraseResult;
    }
    if (this.isDomainWhitelisted(contextWords, matchWord)) {
      return {
        contextScore: 0.8,
        reason: "Domain-specific whitelist match",
        isWhitelisted: true
      };
    }
    const sentimentScore = this.calculateSentimentScore(contextWords, matchWordIndex - startIndex);
    return {
      contextScore: sentimentScore,
      reason: this.generateReason(sentimentScore, contextWords),
      isWhitelisted: false
    };
  }
  checkPhraseContext(contextText, matchWord) {
    for (const [phrase, score] of POSITIVE_PHRASES.entries()) {
      if (phrase.includes(matchWord) && contextText.includes(phrase)) {
        return {
          contextScore: score,
          reason: `Positive phrase detected: "${phrase}"`,
          isWhitelisted: true
        };
      }
    }
    for (const [phrase, score] of NEGATIVE_PHRASES.entries()) {
      if (contextText.includes(phrase)) {
        return {
          contextScore: score,
          reason: `Negative phrase detected: "${phrase}"`,
          isWhitelisted: false
        };
      }
    }
    return null;
  }
  isDomainWhitelisted(contextWords, matchWord) {
    const normalizedMatchWord = matchWord.toLowerCase();
    for (const word of contextWords) {
      if (this.domainWhitelists.has(word)) {
        return true;
      }
      if (GAMING_POSITIVE.has(word)) {
        if (GAMING_ACCEPTABLE_WORDS.has(normalizedMatchWord)) {
          return true;
        }
      }
    }
    return false;
  }
  generateReason(score, contextWords) {
    const foundPositive = Array.from(new Set(contextWords.filter((word) => POSITIVE_INDICATORS.has(word))));
    const foundNegative = Array.from(new Set(contextWords.filter((word) => NEGATIVE_INDICATORS.has(word))));
    if (score >= 0.7) {
      const details = foundPositive.length > 0 ? ` (found: ${foundPositive.join(", ")})` : "";
      return `Positive context detected${details} - likely not profanity`;
    } else if (score <= 0.3) {
      const details = foundNegative.length > 0 ? ` (found: ${foundNegative.join(", ")})` : "";
      return `Negative context detected${details} - likely profanity`;
    } else {
      return "Neutral context - uncertain classification";
    }
  }
  tokenizeWithSpans(text) {
    const tokens = [];
    const pattern = /\S+/g;
    let match;
    while ((match = pattern.exec(text)) !== null) {
      const raw = match[0];
      const normalized = raw.toLowerCase().replace(/[^\p{L}\p{N}_]/gu, "");
      if (normalized.length > 0) {
        tokens.push({
          word: normalized,
          start: match.index,
          end: match.index + raw.length
        });
      }
    }
    return tokens;
  }
  findWordIndex(tokens, charIndex) {
    if (tokens.length === 0) {
      return -1;
    }
    for (let i = 0; i < tokens.length; i++) {
      const token = tokens[i];
      if (charIndex >= token.start && charIndex < token.end) {
        return i;
      }
      if (charIndex < token.start) {
        return Math.max(0, i - 1);
      }
    }
    return tokens.length - 1;
  }
  calculateSentimentScore(contextWords, matchPosition) {
    let positiveCount = 0;
    let negativeCount = 0;
    const totalWords = contextWords.length;
    for (let i = 0; i < contextWords.length; i++) {
      const word = contextWords[i];
      const distance = Math.abs(i - matchPosition);
      const weight = Math.max(0.1, 1 - distance * 0.2);
      if (POSITIVE_INDICATORS.has(word)) {
        positiveCount += weight;
      } else if (NEGATIVE_INDICATORS.has(word)) {
        negativeCount += weight;
      }
    }
    const totalSentiment = positiveCount + negativeCount;
    if (totalSentiment === 0) {
      return 0.5;
    }
    const rawScore = positiveCount / totalSentiment;
    let adjustedScore = rawScore;
    const confidenceMultiplier = Math.min(1, totalWords / 5);
    adjustedScore = 0.5 + (adjustedScore - 0.5) * confidenceMultiplier;
    const hasPersonalPronouns = contextWords.some(
      (word) => ["you", "your", "u", "ur"].includes(word)
    );
    if (hasPersonalPronouns && rawScore < 0.7) {
      adjustedScore *= 0.7;
    }
    const hasObjectReferences = contextWords.some(
      (word) => ["movie", "song", "game", "book", "show", "this", "that", "it"].includes(word)
    );
    if (hasObjectReferences && rawScore > 0.3) {
      adjustedScore = Math.min(1, adjustedScore * 1.3);
    }
    return Math.max(0, Math.min(1, adjustedScore));
  }
  /**
   * Updates the domain whitelist for this analyzer instance
   */
  updateDomainWhitelist(newWhitelist) {
    this.domainWhitelists = new Set(newWhitelist.map((word) => word.toLowerCase()));
  }
  /**
   * Adds words to the domain whitelist
   */
  addToDomainWhitelist(words) {
    words.forEach((word) => this.domainWhitelists.add(word.toLowerCase()));
  }
};

// src/utils/leetspeak.ts
var BASIC_SUBSTITUTIONS = {
  "0": "o",
  "1": "i",
  "3": "e",
  "4": "a",
  "5": "s",
  "7": "t",
  "8": "b",
  "9": "g"
};
var MODERATE_SUBSTITUTIONS = {
  ...BASIC_SUBSTITUTIONS,
  "@": "a",
  "$": "s",
  "!": "i",
  "(": "c",
  "<": "c",
  "{": "c",
  "[": "c",
  "+": "t",
  "\u20AC": "e",
  "&": "e",
  "#": "h",
  "\xA5": "y",
  "\xA7": "s",
  "\u2020": "t",
  "\xAE": "r",
  "\xA9": "c",
  "\xB2": "2",
  "\xB3": "3"
};
var AGGRESSIVE_MULTI_CHAR = [
  // Letter A patterns
  [/\/\\/g, "a"],
  [/\/-\\/g, "a"],
  [/\^/g, "a"],
  // Letter B patterns
  [/\|3/g, "b"],
  [/13/g, "b"],
  [/ß/g, "b"],
  // Letter D patterns
  [/\|\)/g, "d"],
  [/\|>/g, "d"],
  [/\[\)/g, "d"],
  // Letter F patterns
  [/\|=/g, "f"],
  [/ph/gi, "f"],
  // Letter H patterns
  [/\|-\|/g, "h"],
  [/\}\{/g, "h"],
  // Letter K patterns
  [/\|</g, "k"],
  [/\|\{/g, "k"],
  // Letter L patterns
  [/\|_/g, "l"],
  // Letter M patterns
  [/\/\\\/\\/g, "m"],
  [/\|V\|/g, "m"],
  [/\[V\]/g, "m"],
  // Letter N patterns
  [/\/\\\//g, "n"],
  [/\|\\\|/g, "n"],
  // Letter P patterns
  [/\|\*/g, "p"],
  [/\|o/g, "p"],
  // Letter R patterns
  [/\|2/g, "r"],
  [/\|\?/g, "r"],
  // Letter U patterns
  [/\|_\|/g, "u"],
  [/\\_\\/g, "u"],
  [/\/_\//g, "u"],
  // Letter V patterns
  [/\\\//g, "v"],
  // Letter W patterns
  [/\\\/\\\//g, "w"],
  [/vv/gi, "w"],
  // Letter X patterns
  [/></g, "x"],
  // Letter Y patterns
  [/'\//g, "y"],
  // Letter Z patterns
  [/7_/g, "z"]
];
AGGRESSIVE_MULTI_CHAR.sort((a, b) => b[0].source.length - a[0].source.length);
var AGGRESSIVE_SUBSTITUTIONS = {
  ...MODERATE_SUBSTITUTIONS,
  "|": "i",
  "6": "g",
  "2": "z",
  "%": "z"
};
var AMBIGUOUS_AS_L = { "|": "l", "1": "l", "!": "l" };
var SPACED_CHARS_PATTERN = /\b([a-zA-Z0-9@$!#])\s+([a-zA-Z0-9@$!#])(\s+[a-zA-Z0-9@$!#])+\b/g;
var MEASUREMENT_LIKE = /(?<![A-Za-z])\d+[A-Za-z](?![A-Za-z])/g;
function measurementDigitIndices(text) {
  const keep = /* @__PURE__ */ new Set();
  for (const match of text.matchAll(MEASUREMENT_LIKE)) {
    const start = match.index ?? 0;
    for (let i = start; i < start + match[0].length; i++) {
      if (text[i] >= "0" && text[i] <= "9") keep.add(i);
    }
  }
  return keep;
}
function getLeetspeakTables(level, ambiguousAsL = false) {
  return {
    multi: level === "aggressive" ? AGGRESSIVE_MULTI_CHAR : [],
    single: getSubstitutionMap(level, ambiguousAsL)
  };
}
function normalizeLeetspeak(text, options = {}) {
  const {
    level = "moderate",
    collapseRepeated = true,
    maxRepeated = 2,
    removeSpacedChars = true,
    ambiguousAsL = false
  } = options;
  let normalized = text;
  if (removeSpacedChars) {
    normalized = collapseSpacedCharacters(normalized);
  }
  if (level === "aggressive") {
    for (const [pattern, replacement] of AGGRESSIVE_MULTI_CHAR) {
      normalized = normalized.replace(pattern, replacement);
    }
  }
  const substitutions = getSubstitutionMap(level, ambiguousAsL);
  const keepDigits = measurementDigitIndices(normalized);
  normalized = normalized.split("").map((char, index) => keepDigits.has(index) ? char : substitutions[char] || char).join("");
  if (collapseRepeated) {
    normalized = collapseRepeatedCharacters(normalized, maxRepeated);
  }
  return normalized;
}
function getSubstitutionMap(level, ambiguousAsL = false) {
  if (ambiguousAsL) return { ...getSubstitutionMap(level), ...AMBIGUOUS_AS_L };
  switch (level) {
    case "basic":
      return BASIC_SUBSTITUTIONS;
    case "moderate":
      return MODERATE_SUBSTITUTIONS;
    case "aggressive":
      return AGGRESSIVE_SUBSTITUTIONS;
    default:
      return MODERATE_SUBSTITUTIONS;
  }
}
function collapseSpacedCharacters(text) {
  return text.replace(SPACED_CHARS_PATTERN, (match) => {
    return match.replace(/\s+/g, "");
  });
}
function collapseRepeatedCharacters(text, maxRepeated = 2) {
  const pattern = new RegExp(`(.)\\1{${maxRepeated},}`, "gi");
  return text.replace(pattern, (match, char) => char.repeat(maxRepeated));
}

// src/utils/unicode.ts
var HOMOGLYPHS = {
  // Cyrillic homoglyphs (look like Latin)
  "\u0430": "a",
  // Cyrillic small a
  "\u0410": "A",
  // Cyrillic capital A
  "\u0435": "e",
  // Cyrillic small e
  "\u0415": "E",
  // Cyrillic capital E
  "\u043A": "k",
  // Cyrillic small ka
  "\u041A": "K",
  // Cyrillic capital Ka
  "\u043E": "o",
  // Cyrillic small o
  "\u041E": "O",
  // Cyrillic capital O
  "\u0440": "p",
  // Cyrillic small er
  "\u0420": "P",
  // Cyrillic capital Er
  "\u0441": "c",
  // Cyrillic small es
  "\u0421": "C",
  // Cyrillic capital Es
  "\u0443": "u",
  // Cyrillic small u (map to u, not y)
  "\u0423": "U",
  // Cyrillic capital U
  "\u0445": "x",
  // Cyrillic small ha
  "\u0425": "X",
  // Cyrillic capital Ha
  "\u0456": "i",
  // Cyrillic small i (Ukrainian)
  "\u0406": "I",
  // Cyrillic capital I (Ukrainian)
  "\u0458": "j",
  // Cyrillic small je
  "\u0408": "J",
  // Cyrillic capital Je
  "\u0455": "s",
  // Cyrillic small dze
  "\u0405": "S",
  // Cyrillic capital Dze
  // Currency and special symbols that look like letters
  "\xA2": "c",
  // Cent sign
  "\u0192": "f",
  // Latin small f with hook (florin)
  // Greek homoglyphs
  "\u03B1": "a",
  // Greek small alpha
  "\u0391": "A",
  // Greek capital Alpha
  "\u03B2": "b",
  // Greek small beta (sort of)
  "\u0392": "B",
  // Greek capital Beta
  "\u03B5": "e",
  // Greek small epsilon
  "\u0395": "E",
  // Greek capital Epsilon
  "\u03B7": "n",
  // Greek small eta
  "\u0397": "H",
  // Greek capital Eta
  "\u03B9": "i",
  // Greek small iota
  "\u0399": "I",
  // Greek capital Iota
  "\u03BA": "k",
  // Greek small kappa
  "\u039A": "K",
  // Greek capital Kappa
  "\u03BD": "v",
  // Greek small nu (looks like v)
  "\u039D": "N",
  // Greek capital Nu
  "\u03BF": "o",
  // Greek small omicron
  "\u039F": "O",
  // Greek capital Omicron
  "\u03C1": "p",
  // Greek small rho
  "\u03A1": "P",
  // Greek capital Rho
  "\u03C4": "t",
  // Greek small tau
  "\u03A4": "T",
  // Greek capital Tau
  "\u03C5": "u",
  // Greek small upsilon
  "\u054D": "U",
  // Armenian capital seh (looks like U)
  "\u057D": "u",
  // Armenian small seh (looks like u)
  "\u03A5": "Y",
  // Greek capital Upsilon
  "\u03C7": "x",
  // Greek small chi
  "\u03A7": "X",
  // Greek capital Chi
  // Mathematical symbols
  "\u2102": "C",
  // Double-struck capital C
  "\u210D": "H",
  // Double-struck capital H
  "\u2115": "N",
  // Double-struck capital N
  "\u2119": "P",
  // Double-struck capital P
  "\u211A": "Q",
  // Double-struck capital Q
  "\u211D": "R",
  // Double-struck capital R
  "\u2124": "Z",
  // Double-struck capital Z
  // Subscript/superscript
  "\u1D43": "a",
  "\u1D47": "b",
  "\u1D9C": "c",
  "\u1D48": "d",
  "\u1D49": "e",
  "\u1DA0": "f",
  "\u1D4D": "g",
  "\u02B0": "h",
  "\u2071": "i",
  "\u02B2": "j",
  "\u1D4F": "k",
  "\u02E1": "l",
  "\u1D50": "m",
  "\u207F": "n",
  "\u1D52": "o",
  "\u1D56": "p",
  "\u02B3": "r",
  "\u02E2": "s",
  "\u1D57": "t",
  "\u1D58": "u",
  "\u1D5B": "v",
  "\u02B7": "w",
  "\u02E3": "x",
  "\u02B8": "y",
  "\u1DBB": "z",
  // Small caps
  "\u1D00": "A",
  "\u0299": "B",
  "\u1D04": "C",
  "\u1D05": "D",
  "\u1D07": "E",
  "\uA730": "F",
  "\u0262": "G",
  "\u029C": "H",
  "\u026A": "I",
  "\u1D0A": "J",
  "\u1D0B": "K",
  "\u029F": "L",
  "\u1D0D": "M",
  "\u0274": "N",
  "\u1D0F": "O",
  "\u1D18": "P",
  "\u01EB": "Q",
  "\u0280": "R",
  "\uA731": "S",
  "\u1D1B": "T",
  "\u1D1C": "U",
  "\u1D20": "V",
  "\u1D21": "W",
  "\u028F": "Y",
  "\u1D22": "Z",
  // Other lookalikes
  "\u24D0": "a",
  "\u24D1": "b",
  "\u24D2": "c",
  "\u24D3": "d",
  "\u24D4": "e",
  "\u24D5": "f",
  "\u24D6": "g",
  "\u24D7": "h",
  "\u24D8": "i",
  "\u24D9": "j",
  "\u24DA": "k",
  "\u24DB": "l",
  "\u24DC": "m",
  "\u24DD": "n",
  "\u24DE": "o",
  "\u24DF": "p",
  "\u24E0": "q",
  "\u24E1": "r",
  "\u24E2": "s",
  "\u24E3": "t",
  "\u24E4": "u",
  "\u24E5": "v",
  "\u24E6": "w",
  "\u24E7": "x",
  "\u24E8": "y",
  "\u24E9": "z",
  // Fancy Unicode letters
  "\uFF41": "a",
  "\uFF42": "b",
  "\uFF43": "c",
  "\uFF44": "d",
  "\uFF45": "e",
  "\uFF46": "f",
  "\uFF47": "g",
  "\uFF48": "h",
  "\uFF49": "i",
  "\uFF4A": "j",
  "\uFF4B": "k",
  "\uFF4C": "l",
  "\uFF4D": "m",
  "\uFF4E": "n",
  "\uFF4F": "o",
  "\uFF50": "p",
  "\uFF51": "q",
  "\uFF52": "r",
  "\uFF53": "s",
  "\uFF54": "t",
  "\uFF55": "u",
  "\uFF56": "v",
  "\uFF57": "w",
  "\uFF58": "x",
  "\uFF59": "y",
  "\uFF5A": "z",
  // Mirrored/rotated
  "\u0250": "a",
  "\u0254": "c",
  "\u01DD": "e",
  "\u025F": "j",
  "\u0265": "h",
  "\u026F": "m",
  "\u0279": "r",
  "\u0287": "t",
  "\u028C": "v",
  "\u028D": "w",
  // Common lookalikes
  "\u0142": "l",
  "\u0141": "L",
  "\xF8": "o",
  "\xD8": "O",
  "\u0111": "d",
  "\u0110": "D",
  "\u0127": "h",
  "\u0126": "H",
  "\u0131": "i",
  "\u0130": "I",
  "\u0138": "k",
  "\u0140": "l",
  "\u013F": "L",
  "\u014B": "n",
  "\u014A": "N",
  "\u0153": "oe",
  "\u0152": "OE",
  "\u017F": "s",
  "\u0167": "t",
  "\u0166": "T"
};
var ZERO_WIDTH_CHARS = [
  "\u200B",
  // Zero-width space
  "\u200C",
  // Zero-width non-joiner
  "\u200D",
  // Zero-width joiner
  "\u200E",
  // Left-to-right mark
  "\u200F",
  // Right-to-left mark
  "\u2060",
  // Word joiner
  "\u2061",
  // Function application
  "\u2062",
  // Invisible times
  "\u2063",
  // Invisible separator
  "\u2064",
  // Invisible plus
  "\uFEFF",
  // Byte order mark / zero-width no-break space
  "\xAD",
  // Soft hyphen
  "\u034F",
  // Combining grapheme joiner
  "\u061C",
  // Arabic letter mark
  "\u115F",
  // Hangul choseong filler
  "\u1160",
  // Hangul jungseong filler
  "\u17B4",
  // Khmer vowel inherent Aq
  "\u17B5",
  // Khmer vowel inherent Aa
  "\u180E",
  // Mongolian vowel separator
  "\u3164"
  // Hangul filler
];
var KANA_VOICED_SOUND_MARKS = /* @__PURE__ */ new Set(["\u3099", "\u309A"]);
function normalizeUnicode(text, options = {}) {
  const {
    nfkd = true,
    homoglyphs = true,
    fullWidth = true,
    removeDiacritics = true,
    removeZeroWidth = true
  } = options;
  let normalized = text;
  if (removeZeroWidth) {
    normalized = removeZeroWidthCharacters(normalized);
  }
  if (fullWidth) {
    normalized = convertFullWidth(normalized);
  }
  if (homoglyphs) {
    normalized = convertHomoglyphs(normalized);
  }
  if (nfkd || removeDiacritics) {
    normalized = normalizeNFKD(normalized, removeDiacritics);
  }
  return normalized;
}
function removeZeroWidthCharacters(text) {
  const pattern = new RegExp(`[${ZERO_WIDTH_CHARS.join("")}]`, "g");
  return text.replace(pattern, "");
}
function convertFullWidth(text) {
  return text.replace(/[\uFF01-\uFF5E]/g, (char) => {
    return String.fromCharCode(char.charCodeAt(0) - 65248);
  });
}
function homoglyphToAscii(char) {
  return HOMOGLYPHS[char] ?? char;
}
function convertHomoglyphs(text) {
  return text.split("").map((char) => homoglyphToAscii(char)).join("");
}
function normalizeNFKD(text, removeDiacritics = true) {
  let normalized = text.normalize("NFKD");
  if (removeDiacritics) {
    normalized = normalized.replace(
      /\p{Mn}/gu,
      (mark) => KANA_VOICED_SOUND_MARKS.has(mark) ? mark : ""
    );
    normalized = normalized.normalize("NFC");
  }
  return normalized;
}

// src/filters/normalizeText.ts
function normalizeObfuscated(text) {
  const charMap = { "@": "a", $: "s", "!": "i", "1": "i", "*": "" };
  return text.replace(/([a-zA-Z])\1{1,}/g, "$1$1").replace(/[@$!1*]/g, (m) => charMap[m] || m);
}
function normalizeForMatching(text, aggressive, config) {
  let normalized = text;
  if (config.normalizeUnicode) normalized = normalizeUnicode(normalized);
  if (config.detectLeetspeak) {
    normalized = normalizeLeetspeak(normalized, {
      level: config.leetspeakLevel,
      collapseRepeated: true,
      // Keep double letters like "ss" for the normal check, collapse all when aggressive.
      maxRepeated: aggressive ? 1 : 2,
      removeSpacedChars: true
    });
  }
  if (config.allowObfuscatedMatch && !config.detectLeetspeak) {
    normalized = normalizeObfuscated(normalized);
  }
  return normalized;
}

// src/filters/wordEdges.ts
var KATA = "\\u30a1-\\u30fa\\u30fc-\\u30ff\\u31f0-\\u31ff\\uff66-\\uff9f";
var HAN = /^(?:[\u3005\u3100-\u312f\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]|[\u{20000}-\u{2fa1f}])$/u;
var KATAKANA = new RegExp(`^[${KATA}]$`, "u");
var UNAMBIGUOUS_SINGLE_HAN = new Set(Array.from("\u808F\u5C44\u5C4C\u81A3\u59E6\u59D8"));
var NATIVE = /^[\p{L}\p{N}\p{M}]$/u;
function edgeKind(char, wordLength) {
  if (char.charCodeAt(0) < 128 || !NATIVE.test(char)) return "ascii";
  if (KATAKANA.test(char)) return "kata";
  if (!HAN.test(char)) return "native";
  return wordLength >= 2 || UNAMBIGUOUS_SINGLE_HAN.has(char) ? "free" : "native";
}
function wordEdges(word) {
  const chars = Array.from(word);
  if (chars.length === 0) return { head: "ascii", tail: "ascii" };
  return {
    head: edgeKind(chars[0], chars.length),
    tail: edgeKind(chars[chars.length - 1], chars.length)
  };
}
var HEAD = {
  ascii: "\\b",
  native: "(?<![\\p{L}\\p{N}_])",
  free: "",
  kata: `(?<![${KATA}])`
};
var TAIL = {
  ascii: "\\b",
  native: "(?![\\p{L}\\p{N}_])",
  free: "",
  kata: `(?![${KATA}])`
};
function headSource(kind) {
  return HEAD[kind];
}
function tailSource(kind) {
  return TAIL[kind];
}
function boundedPattern(word) {
  const { head, tail } = wordEdges(word);
  return {
    source: `${HEAD[head]}${word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}${TAIL[tail]}`,
    unicode: head !== "ascii" && head !== "free" || tail !== "ascii" && tail !== "free"
  };
}

// src/filters/wordMatcher.ts
var WordMatcher = class {
  constructor(opts) {
    this.opts = opts;
    this.regexCache = /* @__PURE__ */ new Map();
  }
  get wordBoundaries() {
    return this.opts.wordBoundaries;
  }
  clear() {
    this.regexCache.clear();
  }
  /** Returns a shared global regex for the word with lastIndex reset. */
  getRegex(word) {
    const cached = this.regexCache.get(word);
    if (cached) {
      cached.lastIndex = 0;
      return cached;
    }
    const baseFlags = this.opts.caseSensitive ? "g" : "gi";
    let regex;
    if (this.opts.wordBoundaries) {
      const { source, unicode } = boundedPattern(word);
      regex = new RegExp(source, unicode ? `${baseFlags}u` : baseFlags);
    } else {
      regex = new RegExp(word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), baseFlags);
    }
    this.regexCache.set(word, regex);
    return regex;
  }
  isFuzzyToleranceMatch(word, text) {
    const simplifiedText = text.toLowerCase().replace(/[^a-z]/g, "");
    const simplifiedWord = word.toLowerCase();
    let matchCount = 0;
    let index = 0;
    for (let i = 0; i < simplifiedText.length; i++) {
      if (simplifiedText[i] === simplifiedWord[index]) {
        matchCount++;
        index++;
        if (index === simplifiedWord.length) break;
      }
    }
    return matchCount / simplifiedWord.length >= this.opts.fuzzyToleranceLevel;
  }
  evaluateSeverity(word, text) {
    if (this.getRegex(word).test(text)) {
      return 1 /* EXACT */;
    }
    if (!this.opts.wordBoundaries && this.isFuzzyToleranceMatch(word, text)) {
      return 2 /* FUZZY */;
    }
    return void 0;
  }
};

// src/filters/replacement.ts
function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function escapeCharClass(value) {
  return value.replace(/[\\\][^-]/g, "\\$&");
}
function replaceWords(text, words, replaceWith, wordBoundaries) {
  let processed = text;
  for (const word of new Set(words)) {
    const escaped = escapeRegExp(word);
    let pattern;
    if (wordBoundaries) {
      const { source, unicode } = boundedPattern(word);
      pattern = new RegExp(source, unicode ? "giu" : "gi");
    } else {
      pattern = new RegExp(escaped, "gi");
    }
    processed = processed.replace(pattern, replaceWith);
  }
  return processed;
}
function replaceSpans(text, spans, replaceWith) {
  if (spans.length === 0) return text;
  const sorted = [...spans].sort((a, b) => a[0] - b[0]);
  const merged = [];
  for (const span of sorted) {
    const last = merged[merged.length - 1];
    if (last && span[0] <= last[1]) last[1] = Math.max(last[1], span[1]);
    else merged.push([span[0], span[1]]);
  }
  let out = "";
  let cursor = 0;
  for (const [start, end] of merged) {
    out += text.slice(cursor, start) + replaceWith;
    cursor = end;
  }
  return out + text.slice(cursor);
}

// src/filters/redact.ts
function redactText(text, profaneWords, replaceWith, wordBoundaries, hits, safeSpans) {
  if (hits) {
    return replaceSpans(text, hits.map((h) => [h.start, h.end]), replaceWith);
  }
  if (profaneWords.length === 0) return text;
  const mask = (part) => replaceWords(part, profaneWords, replaceWith, wordBoundaries);
  let out = "";
  let cursor = 0;
  for (const [start, end] of safeSpans) {
    out += mask(text.slice(cursor, start)) + text.slice(start, end);
    cursor = end;
  }
  return out + mask(text.slice(cursor));
}

// src/filters/hitSummary.ts
function uniqueSpans(hits) {
  const seen = /* @__PURE__ */ new Set();
  return hits.filter((h) => {
    const key = `${h.word}|${h.start}|${h.end}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}
function summarizeHits(hits) {
  const profaneWords = [];
  const severityMap = {};
  for (const hit of hits) {
    const key = hit.view === "orig" ? hit.text : hit.word;
    if (hit.view === "orig" || !profaneWords.includes(key)) {
      profaneWords.push(key);
      if (severityMap[key] === void 0) severityMap[key] = hit.severity;
    }
  }
  return { profaneWords, severityMap };
}

// src/filters/legacyScan.ts
function legacyIsProfane(ctx, value) {
  const normalizedInput = ctx.normalizeText(value);
  const aggressiveInput = ctx.normalizeText(value, true);
  for (const word of ctx.words.keys()) {
    if (ctx.ignoreWords.has(word.toLowerCase())) continue;
    if (ctx.matcher.evaluateSeverity(word, value) !== void 0) return true;
    if (ctx.matcher.evaluateSeverity(word, normalizedInput) !== void 0) return true;
    if (ctx.matcher.evaluateSeverity(word, aggressiveInput) !== void 0) return true;
  }
  return false;
}
function legacyScan(ctx, text) {
  const originalInput = text.toLowerCase();
  const normalizedInput = ctx.normalizeText(text).toLowerCase();
  const aggressiveInput = ctx.normalizeText(text, true).toLowerCase();
  const profaneWords = [];
  const severityMap = {};
  const record = (word, severity) => {
    if (severityMap[word] === void 0) severityMap[word] = severity;
  };
  for (const dictWord of ctx.words.keys()) {
    if (ctx.ignoreWords.has(dictWord.toLowerCase())) continue;
    let severity = ctx.matcher.evaluateSeverity(dictWord, originalInput);
    if (severity !== void 0) {
      const regex = ctx.matcher.getRegex(dictWord);
      let match;
      while ((match = regex.exec(originalInput)) !== null) {
        profaneWords.push(match[0]);
        record(match[0], severity);
      }
    }
    severity = ctx.matcher.evaluateSeverity(dictWord, normalizedInput);
    if (severity !== void 0) {
      const regex = ctx.matcher.getRegex(dictWord);
      while (regex.exec(normalizedInput) !== null) {
        if (!profaneWords.includes(dictWord)) {
          profaneWords.push(dictWord);
          record(dictWord, severity);
        }
      }
    }
    severity = ctx.matcher.evaluateSeverity(dictWord, aggressiveInput);
    if (severity !== void 0 && !profaneWords.includes(dictWord)) {
      profaneWords.push(dictWord);
      record(dictWord, severity);
    }
  }
  return { profaneWords, severityMap };
}
function legacyContextHits(ctx, text) {
  const views = [
    text.toLowerCase(),
    ctx.normalizeText(text).toLowerCase(),
    ctx.normalizeText(text, true).toLowerCase()
  ];
  const hits = [];
  for (const dictWord of ctx.words.keys()) {
    if (ctx.ignoreWords.has(dictWord.toLowerCase())) continue;
    let found = false;
    for (let v = 0; v < views.length && !found; v++) {
      const severity = ctx.matcher.evaluateSeverity(dictWord, views[v]);
      if (severity === void 0) continue;
      const regex = ctx.matcher.getRegex(dictWord);
      let match;
      while ((match = regex.exec(views[v])) !== null) {
        hits.push({ word: match[0], index: match.index, severity });
        found = true;
      }
      if (!found && v === views.length - 1) hits.push({ word: dictWord, index: 0, severity });
    }
  }
  return hits;
}

// src/filters/pipeline/dictionaryIndex.ts
var NEEDS_NORMALIZING = /[^a-z]|ph|vv|(.)\1\1/;
var LEET_READINGS = {
  "0": "o",
  "1": "il",
  "3": "e",
  "4": "a",
  "5": "s",
  "7": "t",
  "8": "b",
  "9": "g"
};
function squeeze(value) {
  return value.replace(/(.)\1+/gu, "$1");
}
var DictionaryIndex = class {
  constructor(words, options) {
    this.options = options;
    this.exact = /* @__PURE__ */ new Set();
    this.normalized = /* @__PURE__ */ new Set();
    this.byLength = /* @__PURE__ */ new Map();
    this.squeezed = /* @__PURE__ */ new Map();
    this.stretchPatterns = /* @__PURE__ */ new Map();
    let longest = 0;
    for (const raw of words) {
      const word = raw.toLowerCase();
      if (/\s/.test(word)) continue;
      this.exact.add(word);
      longest = Math.max(longest, word.length);
      const norm = this.normalize(word);
      this.normalized.add(norm);
      if (/^\p{L}+$/u.test(word)) {
        const list = this.byLength.get(word.length) ?? [];
        list.push(word);
        this.byLength.set(word.length, list);
      }
      if (/^\p{L}+$/u.test(norm) && norm.length >= 3) {
        const key = squeeze(norm);
        const list = this.squeezed.get(key) ?? [];
        if (!list.includes(norm)) list.push(norm);
        this.squeezed.set(key, list);
      }
    }
    this.maxWordLength = longest;
    this.rankMaskCandidates();
  }
  /**
   * When several words fit a mask, prefer the root other entries build on
   * (fuck over fvck, shit over scat) so reports name the expected word.
   */
  rankMaskCandidates() {
    const roots = /* @__PURE__ */ new Map();
    for (const word of this.exact) {
      for (let len = 3; len < word.length; len++) {
        const prefix = word.slice(0, len);
        roots.set(prefix, (roots.get(prefix) ?? 0) + 1);
      }
    }
    for (const list of this.byLength.values()) {
      list.sort((a, b) => (roots.get(b) ?? 0) - (roots.get(a) ?? 0));
    }
  }
  normalize(value) {
    if (!this.options.leetspeak) return value;
    return normalizeLeetspeak(value, {
      level: this.options.leetspeakLevel,
      maxRepeated: 2,
      removeSpacedChars: false
    }).toLowerCase();
  }
  /** True when the fragment is a dictionary word, directly or after leet decoding. */
  isWord(fragment) {
    const lower = fragment.toLowerCase();
    if (this.exact.has(lower)) return true;
    if (!NEEDS_NORMALIZING.test(lower)) return false;
    return this.normalized.has(this.normalize(lower));
  }
  /**
   * Resolves a token with mask characters (f*ck, sh#t) to a dictionary word of
   * the same length where each mask stands for exactly one letter.
   */
  resolveMasked(token, masks, minVisible) {
    const chars = Array.from(token.toLowerCase());
    const length = chars.length;
    const candidates = this.byLength.get(length);
    if (!candidates) return null;
    let visible = 0;
    let maskCount = 0;
    for (const c of chars) {
      if (masks.has(c)) maskCount++;
      else if (/\p{L}/u.test(c)) visible++;
    }
    if (maskCount === 0 || visible < minVisible) return null;
    if (visible === 1 && !(length >= 4 && !masks.has(chars[0]) && /\p{L}/u.test(chars[0]))) {
      return null;
    }
    if (length <= 3 && visible < 2) return null;
    for (const word of candidates) {
      let ok = true;
      let k = 0;
      for (const w of word) {
        const c = chars[k++];
        if (masks.has(c) || c === w) continue;
        if (LEET_READINGS[c]?.includes(w)) continue;
        ok = false;
        break;
      }
      if (ok) return word;
    }
    return null;
  }
  /**
   * Resolves a stretched token (aaaasshole) to its dictionary word. The token
   * must contain the word's own letter runs, only longer, so "as" never
   * becomes "ass" and legitimate double letters still match.
   */
  resolveStretched(token) {
    const lower = token.toLowerCase();
    const candidates = this.squeezed.get(squeeze(lower));
    if (!candidates) return null;
    for (const word of candidates) {
      if (lower.length <= word.length) continue;
      if (this.stretchPattern(word).test(lower)) return word;
    }
    return null;
  }
  stretchPattern(word) {
    let pattern = this.stretchPatterns.get(word);
    if (!pattern) {
      const runs = word.match(/(.)\1*/gu) ?? [];
      const body = runs.map((run) => {
        const ch = escapeRegExp(Array.from(run)[0]);
        const count = Array.from(run).length;
        return count > 1 ? `${ch}{${count},}` : `${ch}+`;
      }).join("");
      pattern = new RegExp(`^${body}$`, "u");
      this.stretchPatterns.set(word, pattern);
    }
    return pattern;
  }
};

// src/filters/pipeline/mapped.ts
function fromText(text) {
  return { text, starts: null, ends: null };
}
function spanStart(m, index) {
  return m.starts ? m.starts[index] : index;
}
function spanEnd(m, index) {
  return m.ends ? m.ends[index] : index + 1;
}
function toOriginalSpan(m, start, end) {
  if (end <= start) {
    const at = start < m.text.length ? spanStart(m, start) : spanEnd(m, Math.max(0, m.text.length - 1));
    return [at, at];
  }
  return [spanStart(m, start), spanEnd(m, end - 1)];
}
function subsequenceIndices(source, repl) {
  const picked = [];
  let at = 0;
  for (let i = 0; i < repl.length; i++) {
    const found = source.indexOf(repl[i], at);
    if (found < 0) return null;
    picked.push(found);
    at = found + 1;
  }
  return picked;
}
function applyEdits(m, edits) {
  if (edits.length === 0) return m;
  let out = "";
  const starts = [];
  const ends = [];
  let cursor = 0;
  const copy = (from, to) => {
    out += m.text.slice(from, to);
    for (let i = from; i < to; i++) {
      starts.push(spanStart(m, i));
      ends.push(spanEnd(m, i));
    }
  };
  for (const edit of edits) {
    copy(cursor, edit.start);
    const source = m.text.slice(edit.start, edit.end);
    const repl = edit.replacement;
    if (repl.length > 0) {
      const picked = repl.length === source.length ? Array.from({ length: repl.length }, (_, i) => i) : repl.length < source.length ? subsequenceIndices(source, repl) : null;
      if (picked) {
        for (const p of picked) {
          starts.push(spanStart(m, edit.start + p));
          ends.push(spanEnd(m, edit.start + p));
        }
      } else {
        const s = spanStart(m, edit.start);
        const e = spanEnd(m, edit.end - 1);
        for (let i = 0; i < repl.length; i++) {
          starts.push(s);
          ends.push(e);
        }
      }
      out += repl;
    }
    cursor = edit.end;
  }
  copy(cursor, m.text.length);
  return { text: out, starts: Int32Array.from(starts), ends: Int32Array.from(ends) };
}
function replaceRegex(m, pattern, fn) {
  const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
  const edits = [];
  let match;
  while ((match = re.exec(m.text)) !== null) {
    if (match[0].length === 0) {
      re.lastIndex++;
      continue;
    }
    const replacement = fn(match);
    if (replacement !== null && replacement !== match[0]) {
      edits.push({ start: match.index, end: match.index + match[0].length, replacement });
    }
  }
  return applyEdits(m, edits);
}
function mapChars(m, fn) {
  const edits = [];
  const text = m.text;
  for (let i = 0; i < text.length; i++) {
    const next = fn(text[i]);
    if (next !== void 0 && next !== text[i]) {
      edits.push({ start: i, end: i + 1, replacement: next });
    }
  }
  return applyEdits(m, edits);
}
function runPipeline(input, steps) {
  let current = input;
  for (const step of steps) current = step(current);
  return current;
}

// src/filters/pipeline/html.ts
var TAG_PATTERN = /<!--[\s\S]{0,2000}?-->|<\/?[a-zA-Z][a-zA-Z0-9-]*(?:\s[^<>]*)?\/?>/g;
var ENTITY_PATTERN = /&(?:#(\d{1,7})|#[xX]([0-9a-fA-F]{1,6})|([a-zA-Z][a-zA-Z0-9]{1,31}));/g;
var BLOCK_TAG = /^<\/?(?:p|div|li|ul|ol|tr|td|th|table|h[1-6]|section|article|blockquote|pre|hr)\b/i;
var NAMED_ENTITIES = {
  amp: "&",
  lt: "<",
  gt: ">",
  quot: '"',
  apos: "'",
  nbsp: " ",
  ensp: " ",
  emsp: " ",
  thinsp: " ",
  shy: "",
  zwnj: "",
  zwj: "",
  lrm: "",
  rlm: "",
  hellip: "...",
  ndash: "-",
  mdash: "-",
  auml: "\xE4",
  ouml: "\xF6",
  uuml: "\xFC",
  Auml: "\xC4",
  Ouml: "\xD6",
  Uuml: "\xDC",
  szlig: "\xDF",
  eacute: "\xE9",
  egrave: "\xE8",
  ecirc: "\xEA",
  aacute: "\xE1",
  agrave: "\xE0",
  acirc: "\xE2",
  iacute: "\xED",
  oacute: "\xF3",
  uacute: "\xFA",
  ntilde: "\xF1",
  ccedil: "\xE7"
};
var SOFT_HYPHEN = "\xAD";
function decodeCodePoint(code) {
  if (!Number.isInteger(code) || code < 1 || code > 1114111) return null;
  if (code >= 55296 && code <= 57343) return null;
  const ch = String.fromCodePoint(code);
  return ch === SOFT_HYPHEN ? "" : ch;
}
function decodeEntity(match) {
  if (match[1] !== void 0) return decodeCodePoint(parseInt(match[1], 10));
  if (match[2] !== void 0) return decodeCodePoint(parseInt(match[2], 16));
  const named = NAMED_ENTITIES[match[3]];
  return named === void 0 ? null : named;
}
function htmlTransformer(options) {
  return (input) => {
    const text = input.text;
    if (text.indexOf("<") < 0 && text.indexOf("&") < 0) return input;
    let m = replaceRegex(input, TAG_PATTERN, (match) => BLOCK_TAG.test(match[0]) ? " " : "");
    for (let pass = 0; pass < options.decodeDepth; pass++) {
      if (m.text.indexOf("&") < 0) break;
      const next = replaceRegex(m, ENTITY_PATTERN, decodeEntity);
      if (next === m) break;
      m = next;
    }
    return m;
  };
}

// src/filters/pipeline/masking.ts
var EDGE_PUNCTUATION = /* @__PURE__ */ new Set(["?", "!"]);
var OPENER_WINDOW = 200;
var EMPHASIS_CHARS = /* @__PURE__ */ new Set(["*", "_"]);
function splitEmphasis(raw) {
  let lead = 0;
  while (lead < raw.length && EMPHASIS_CHARS.has(raw[lead])) lead++;
  if (lead === raw.length) return [raw, "", ""];
  let end = raw.length;
  while (end > lead && EMPHASIS_CHARS.has(raw[end - 1])) end--;
  return [raw.slice(0, lead), raw.slice(lead, end), raw.slice(end)];
}
function isEmphasis(raw, text, tokenStart, masks) {
  const [lead, inner, trail] = splitEmphasis(raw);
  if (Array.from(inner).some((c) => masks.has(c))) return false;
  if (lead && trail) return true;
  if (!trail) return false;
  const window = text.slice(Math.max(0, tokenStart - OPENER_WINDOW), tokenStart);
  const before = window.slice(window.lastIndexOf("\n") + 1);
  return new RegExp(`(?:^|\\s)${escapeRegExp(trail)}\\S`).test(before);
}
function maskingTransformer(index, options) {
  const masks = new Set(Array.from(options.chars));
  const maskClass = escapeCharClass(options.chars);
  const quick = new RegExp(`[${maskClass}]`);
  const token = new RegExp(`[\\p{L}\\p{N}${maskClass}]+`, "gu");
  return (input) => {
    if (!quick.test(input.text)) return input;
    const edits = [];
    token.lastIndex = 0;
    let match;
    while ((match = token.exec(input.text)) !== null) {
      const raw = match[0];
      if (isEmphasis(raw, input.text, match.index, masks)) continue;
      let lo = 0;
      let hi = raw.length;
      while (lo < hi && EDGE_PUNCTUATION.has(raw[lo])) lo++;
      while (hi > lo && EDGE_PUNCTUATION.has(raw[hi - 1])) hi--;
      const core = raw.slice(lo, hi);
      if (core.length < 3) continue;
      const word = index.resolveMasked(core, masks, options.minVisible);
      if (word && word.length === core.length) {
        edits.push({ start: match.index + lo, end: match.index + hi, replacement: word });
      }
    }
    return applyEdits(input, edits);
  };
}

// src/filters/pipeline/normalizers.ts
var ZERO_WIDTH_PATTERN = new RegExp(`[${ZERO_WIDTH_CHARS.join("")}]`, "g");
var NON_ASCII = /[\u0080-\uffff]/;
var unicodeTransformer = (input) => {
  if (!NON_ASCII.test(input.text)) return input;
  let m = replaceRegex(input, ZERO_WIDTH_PATTERN, () => "");
  m = replaceRegex(
    m,
    /[!-~]/g,
    (match) => String.fromCharCode(match[0].charCodeAt(0) - 65248)
  );
  m = mapChars(m, (ch) => HOMOGLYPHS[ch]);
  return decomposeAndStrip(m);
};
function stripMarks(value) {
  return value.normalize("NFKD").replace(/[̀-ͯ]/g, "");
}
function decomposeAndStrip(m) {
  const target = stripMarks(m.text);
  if (target === m.text) return m;
  const edits = [];
  let joined = "";
  let i = 0;
  for (const ch of m.text) {
    const next = ch.length === 1 && ch.charCodeAt(0) < 128 ? ch : stripMarks(ch);
    if (next !== ch) edits.push({ start: i, end: i + ch.length, replacement: next });
    joined += next;
    i += ch.length;
  }
  if (joined === target) return applyEdits(m, edits);
  return applyEdits(m, [{ start: 0, end: m.text.length, replacement: target }]);
}
function leetTransformer(options) {
  const tables = getLeetspeakTables(options.level, options.ambiguousAsL ?? false);
  const repeated = new RegExp(`(.)\\1{${options.maxRepeated},}`, "gi");
  return (input) => {
    let m = replaceRegex(input, SPACED_CHARS_PATTERN, (match) => match[0].replace(/\s+/g, ""));
    for (const [pattern, replacement] of tables.multi) {
      m = replaceRegex(m, pattern, () => replacement);
    }
    const keepDigits = measurementDigitIndices(m.text);
    let at = -1;
    m = mapChars(m, (ch) => keepDigits.has(++at) ? void 0 : tables.single[ch]);
    return replaceRegex(m, repeated, (match) => match[1].repeat(options.maxRepeated));
  };
}
var legacyObfuscationTransformer = (input) => {
  const charMap = { "@": "a", $: "s", "!": "i", "1": "i", "*": "" };
  const m = replaceRegex(input, /([a-zA-Z])\1{1,}/g, (match) => match[1] + match[1]);
  return replaceRegex(m, /[@$!1*]/g, (match) => charMap[match[0]] || match[0]);
};

// src/filters/pipeline/separators.ts
function separatorTransformer(index, options) {
  const sepClass = escapeCharClass(options.chars);
  const chain = new RegExp(
    `[\\p{L}\\p{N}]+(?:[${sepClass} ][\\p{L}\\p{N}]+)+`,
    "gu"
  );
  const quick = new RegExp(
    `[\\p{L}\\p{N}][${sepClass}][\\p{L}\\p{N}]|\\b\\p{L} \\p{L} \\p{L}\\b`,
    "u"
  );
  const sepSet = new Set(Array.from(options.chars));
  return (input) => {
    if (!quick.test(input.text)) return input;
    const edits = [];
    const taken = /* @__PURE__ */ new Set();
    chain.lastIndex = 0;
    let match;
    while ((match = chain.exec(input.text)) !== null) {
      const pieces = splitPieces(match[0], match.index, sepSet);
      if (pieces.length < 2) continue;
      collectJoins(pieces, input.text, index, options, edits, taken);
    }
    edits.sort((a, b) => a.start - b.start);
    return applyEdits(input, edits);
  };
}
function splitPieces(chainText, offset, seps) {
  const pieces = [];
  let start = 0;
  for (let i = 0; i <= chainText.length; i++) {
    const atEnd = i === chainText.length;
    const isSep = !atEnd && (seps.has(chainText[i]) || chainText[i] === " ");
    if (atEnd || isSep) {
      pieces.push({
        text: chainText.slice(start, i),
        start: offset + start,
        end: offset + i,
        sep: atEnd ? "" : chainText[i]
      });
      start = i + 1;
    }
  }
  return pieces;
}
function collectJoins(pieces, source, index, options, edits, taken) {
  let i = 0;
  while (i < pieces.length) {
    let joinedTo = -1;
    let candidate = "";
    for (let j = i; j < pieces.length; j++) {
      candidate += pieces[j].text;
      if (candidate.length > index.maxWordLength) break;
      if (j > i && acceptsJoin(pieces, i, j, candidate, index, options)) joinedTo = j;
    }
    if (joinedTo < 0) {
      i++;
      continue;
    }
    addEdits(pieces, i, joinedTo, source, edits, taken);
    i = joinedTo + 1;
  }
}
function acceptsJoin(pieces, from, to, candidate, index, options) {
  const span = pieces.slice(from, to + 1);
  if (!span.some((p) => p.text.length === 1)) return false;
  if (span.length === 2 && candidate.length < options.minTwoPieceLength) return false;
  const inner = span.slice(0, -1).map((p) => p.sep);
  const spaces = inner.filter((s) => s === " ").length;
  if (spaces > 0) {
    const others = inner.length - spaces;
    const spelledOut = others === 0 && span.length >= 3 && span.every((p) => p.text.length === 1);
    if (!spelledOut && others < (options.allowSpace ? 1 : 2)) return false;
  }
  return index.isWord(candidate) && endsAtWordEnd(pieces, to, index);
}
var SUFFIXES = /* @__PURE__ */ new Set(["s", "es", "ed", "er", "ers", "ing", "y"]);
function endsAtWordEnd(pieces, to, index) {
  const sep = pieces[to - 1].sep;
  let rest = "";
  let k = to + 1;
  while (k < pieces.length && pieces[k].text.length === 1 && pieces[k - 1].sep === sep && rest.length <= index.maxWordLength) {
    rest += pieces[k].text;
    k++;
  }
  if (rest === "") return true;
  return SUFFIXES.has(rest.toLowerCase()) || index.isWord(rest);
}
function addEdits(pieces, from, to, source, edits, taken) {
  const push = (edit) => {
    if (!taken.has(edit.start)) {
      taken.add(edit.start);
      edits.push(edit);
    }
  };
  for (let k = from; k < to; k++) {
    push({ start: pieces[k].end, end: pieces[k + 1].start, replacement: "" });
  }
  const prev = pieces[from].start - 1;
  if (from > 0 && source[prev] === "_") push({ start: prev, end: prev + 1, replacement: " " });
  const next = pieces[to].end;
  if (to + 1 < pieces.length && source[next] === "_") {
    push({ start: next, end: next + 1, replacement: " " });
  }
}

// src/filters/pipeline/stretch.ts
var TOKEN = /[\p{L}\p{N}]+/gu;
var HAS_REPEAT = /(.)\1/u;
function stretchTransformer(index) {
  return (input) => {
    if (!HAS_REPEAT.test(input.text)) return input;
    const edits = [];
    const re = new RegExp(TOKEN.source, TOKEN.flags);
    let match;
    while ((match = re.exec(input.text)) !== null) {
      if (match[0].length < 4) continue;
      const word = index.resolveStretched(match[0]);
      if (word) {
        edits.push({ start: match.index, end: match.index + match[0].length, replacement: word });
      }
    }
    return applyEdits(input, edits);
  };
}

// src/filters/pipeline/views.ts
var LONG_RUN = /(.)\1\1/u;
var AMBIGUOUS_GLYPH = /[\p{L}@$][|1!]+\p{L}/u;
var ViewFactory = class {
  constructor(cfg, index) {
    this.cfg = cfg;
    const obf = cfg.obfuscation;
    const html = obf.html.enabled ? [htmlTransformer(obf.html)] : [];
    const separators = obf.separators.enabled ? [separatorTransformer(index, obf.separators)] : [];
    const masking = obf.masking.enabled ? [maskingTransformer(index, obf.masking)] : [];
    this.prefix = [...html, ...separators, ...masking];
    this.prefixWithoutSeparators = [...html, ...masking];
    this.stretch = obf.stretch ? stretchTransformer(index) : null;
  }
  normalizeSteps(aggressive, ambiguousAsL = false) {
    const steps = [];
    if (this.cfg.normalizeUnicode) steps.push(unicodeTransformer);
    if (this.cfg.detectLeetspeak) {
      steps.push(
        leetTransformer({
          level: this.cfg.leetspeakLevel,
          maxRepeated: aggressive ? 1 : 2,
          ambiguousAsL
        })
      );
    } else if (this.cfg.allowObfuscatedMatch) {
      steps.push(legacyObfuscationTransformer);
    }
    return steps;
  }
  legacyViews(text) {
    const orig = fromText(text);
    return {
      orig,
      norm: runPipeline(orig, this.normalizeSteps(false)),
      aggr: runPipeline(orig, this.normalizeSteps(true))
    };
  }
  /** Extra views, empty when the text carries no obfuscation signal. */
  enhancedViews(text) {
    const orig = fromText(text);
    const prepared = runPipeline(orig, this.prefix);
    const changed = prepared !== orig;
    const longRun = this.stretch !== null && LONG_RUN.test(prepared.text);
    const ambiguous = this.cfg.obfuscation.ambiguousLeet && this.cfg.detectLeetspeak && AMBIGUOUS_GLYPH.test(text);
    if (!changed && !longRun && !ambiguous) return [];
    const views = [];
    const tail = this.stretch ? [this.stretch] : [];
    if (changed || longRun) {
      views.push(runPipeline(prepared, [...this.normalizeSteps(false), ...tail]));
    }
    if (ambiguous) {
      const steps = [...this.normalizeSteps(false, true), ...tail];
      views.push(runPipeline(prepared, steps));
      if (this.cfg.obfuscation.separators.enabled) {
        const unjoined = runPipeline(orig, this.prefixWithoutSeparators);
        if (unjoined.text !== prepared.text) views.push(runPipeline(unjoined, steps));
      }
    }
    return views;
  }
};

// src/filters/builtinWhitelist.ts
var NOT_A_NAME = "(?!(?:You|Your|Big|Small|Little|Huge|Fat|Stupid|Dumb|Such|What|The|This|That|Fucking|Lazy|Ugly|Total|Complete)\\s)";
var BUILTIN_WHITELIST = [
  // Clean words that a leet-decoded or invisible-character-split view turns into "ass".
  { protect: "(?:cl|gl|gr|m|b|br|l|s|p)ass(?:es)?" },
  { protect: "ass(?:ist|istant|istants|istance|ume|umes|umed|ess|essment|assin|assins|assinate|embly|emble|ociate|ociation|ignment|ert|et|ets)" },
  // Idioms and nouns where the dictionary word is not an insult.
  { before: "(?:summa |magna )?", protect: "cum", after: "\\s+laude" },
  { protect: "pussy", after: "\\s+willows?" },
  { before: "(?:great|blue|coal|marsh|willow)\\s+", protect: "tits?" },
  { protect: "bastard", after: "\\s+files?" },
  { protect: "jackass", after: "\\s+penguins?" },
  { protect: "cock(?:ed|ing)?", after: "\\s+the\\s+(?:hammer|gun|pistol)" },
  { protect: "anal", after: "\\s+fissures?" },
  // Place name, also hit by repeated-letter collapsing.
  { protect: "twatt", after: ",?\\s+(?:orkney|shetland)" },
  // Given name: "Moby Dick", "Philip K. Dick", "Dick Van Dyke".
  {
    before: `${NOT_A_NAME}(?:[A-Z][a-z]+|[A-Z]\\.)\\s+`,
    protect: "Dick",
    after: "(?!\\s+(?:Head|Move|Bag|Face)\\b)",
    caseSensitive: true
  },
  { protect: "Dick", after: "\\s+(?!(?:Head|Move|Bag|Face)\\b)[A-Z][a-z]+", caseSensitive: true }
];

// src/filters/whitelist.ts
var ASCII_WORD = /^[A-Za-z0-9_]$/;
function edgeFor(kind, char) {
  return kind === "ascii" && !ASCII_WORD.test(char) ? "native" : kind;
}
var Whitelist = class {
  constructor(userEntries, useBuiltin) {
    const compiled = [];
    if (useBuiltin) {
      for (const entry of BUILTIN_WHITELIST) compiled.push(compileBuiltin(entry));
    }
    for (const raw of userEntries) {
      const entry = raw.trim();
      if (entry.length > 0) compiled.push(compileUser(entry));
    }
    this.compiled = compiled;
  }
  get isEmpty() {
    return this.compiled.length === 0;
  }
  /** Cheap test on plain strings: could any safe span exist at all? */
  mightMatch(texts) {
    for (const { regex } of this.compiled) {
      for (const text of texts) {
        regex.lastIndex = 0;
        if (regex.test(text)) return true;
      }
    }
    return false;
  }
  /** Protected spans of the view, in original-text offsets. */
  intervals(view) {
    const found = [];
    for (const { regex } of this.compiled) {
      regex.lastIndex = 0;
      let match;
      while ((match = regex.exec(view.text)) !== null) {
        const start = match.index + match[1].length;
        const end = start + match[2].length;
        if (end === start) {
          regex.lastIndex++;
          continue;
        }
        found.push(toOriginalSpan(view, start, end));
      }
    }
    return found;
  }
};
function protectedSpans(whitelist, text) {
  const spans = whitelist.intervals(fromText(text)).sort((a, b) => a[0] - b[0]);
  const merged = [];
  for (const span of spans) {
    const last = merged[merged.length - 1];
    if (last && span[0] <= last[1]) last[1] = Math.max(last[1], span[1]);
    else merged.push([span[0], span[1]]);
  }
  return merged;
}
function blankProtected(text, spans) {
  let out = "";
  let cursor = 0;
  for (const [start, end] of spans) {
    out += text.slice(cursor, start) + " ".repeat(end - start);
    cursor = end;
  }
  return out + text.slice(cursor);
}
function compileBuiltin(entry) {
  const body = `(${entry.before ?? ""})(${entry.protect})(${entry.after ?? ""})`;
  return { regex: new RegExp(`\\b${body}\\b`, entry.caseSensitive ? "g" : "gi") };
}
function compileUser(entry) {
  const chars = Array.from(entry);
  const edges = wordEdges(entry);
  const head = edgeFor(edges.head, chars[0]);
  const tail = edgeFor(edges.tail, chars[chars.length - 1]);
  const protect = escapeRegExp(entry).replace(/\s+/g, "\\s+");
  const source = `${headSource(head)}()(${protect})()${tailSource(tail)}`;
  const unicode = [head, tail].some((kind) => kind !== "ascii");
  return { regex: new RegExp(source, unicode ? "giu" : "gi") };
}
function within(hit, span) {
  return hit[0] >= span[0] && hit[1] <= span[1];
}

// src/filters/scanner.ts
var NON_ASCII2 = /[\u0080-\uffff]/;
var ASCII_WORD2 = /^[a-z0-9_]+$/;
var ASCII_TOKEN = /[a-z0-9_]+/g;
var Scanner = class {
  constructor(cfg) {
    this.cfg = cfg;
    this.activeWords = Array.from(cfg.words.keys()).filter(
      (word) => !cfg.ignoreWords.has(word.toLowerCase())
    );
    this.tokenWords = new Set(this.activeWords.filter((word) => ASCII_WORD2.test(word)));
    this.index = new DictionaryIndex(this.activeWords, {
      leetspeak: cfg.detectLeetspeak,
      leetspeakLevel: cfg.leetspeakLevel
    });
    this.views = new ViewFactory(cfg, this.index);
    this.whitelist = new Whitelist(cfg.whitelist, cfg.obfuscation.builtinWhitelist);
    [this.asciiAny, this.nativeAny] = this.buildPrefilters();
  }
  buildPrefilters() {
    const groups = /* @__PURE__ */ new Map();
    for (const word of this.activeWords) {
      const { head, tail } = wordEdges(word);
      const key = `${head}|${tail}`;
      const group = groups.get(key) ?? { head, tail, words: [] };
      group.words.push(word);
      groups.set(key, group);
    }
    const flags = this.cfg.caseSensitive ? "" : "i";
    const ascii = [];
    const native = [];
    for (const { head, tail, words } of groups.values()) {
      const source = `${headSource(head)}(?:${words.map(escapeRegExp).join("|")})${tailSource(tail)}`;
      if (head === "ascii" && tail === "ascii") ascii.push(new RegExp(source, flags));
      else native.push(new RegExp(source, `${flags}u`));
    }
    return [ascii, native];
  }
  /** Dictionary words that occur as whole ASCII tokens, so other words skip their regex. */
  tokensIn(lower) {
    const found = /* @__PURE__ */ new Set();
    for (const token of lower.match(ASCII_TOKEN) ?? []) {
      if (this.tokenWords.has(token)) found.add(token);
    }
    return found;
  }
  hasCandidate(text) {
    if (this.asciiAny.some((regex) => regex.test(text))) return true;
    return this.nativeAny.length > 0 && NON_ASCII2.test(text) && this.nativeAny.some((regex) => regex.test(text));
  }
  /**
   * Boolean check. Clean text exits after one regex test per view; text with a
   * hit also exits early unless a safe word could overlap it.
   */
  isProfane(text) {
    const norm = this.cfg.normalizeText(text);
    const aggr = this.cfg.normalizeText(text, true);
    const enhanced = this.views.enhancedViews(text);
    const candidate = this.hasCandidate(text) || this.hasCandidate(norm) || this.hasCandidate(aggr) || enhanced.some((view) => this.hasCandidate(view.text));
    if (!candidate) return false;
    if (!this.whitelist.mightMatch([text, norm, aggr, ...enhanced.map((v) => v.text)])) return true;
    return this.scan(text).length > 0;
  }
  /**
   * Returns hits in dictionary order, views in order orig, norm, aggr, enhanced.
   * Every path (plain and context-aware) scans the same views.
   */
  scan(text) {
    const enhanced = this.views.enhancedViews(text);
    const quick = [
      text.toLowerCase(),
      this.cfg.normalizeText(text).toLowerCase(),
      this.cfg.normalizeText(text, true).toLowerCase()
    ];
    if (!quick.some((t) => this.hasCandidate(t)) && !enhanced.some((v) => this.hasCandidate(v.text.toLowerCase()))) {
      return [];
    }
    const legacy = this.views.legacyViews(text);
    const views = [];
    const add = (kind, view) => {
      const lowered = view.text.toLowerCase();
      views.push({ kind, view, lower: lowered.length === view.text.length ? lowered : view.text });
    };
    add("orig", legacy.orig);
    add("norm", legacy.norm);
    add("aggr", legacy.aggr);
    for (const view of enhanced) add("enhanced", view);
    const live = views.filter((v) => this.hasCandidate(v.lower)).map((v) => ({ ...v, tokens: this.tokensIn(v.lower) }));
    const hits = [];
    const seen = /* @__PURE__ */ new Set();
    for (const word of this.activeWords) {
      const isToken = this.tokenWords.has(word);
      for (const { kind, view, lower, tokens } of live) {
        if (isToken && !tokens.has(word)) continue;
        const regex = this.cfg.matcher.getRegex(word);
        let match;
        while ((match = regex.exec(lower)) !== null) {
          if (match[0].length === 0) {
            regex.lastIndex++;
            continue;
          }
          const [start, end] = toOriginalSpan(view, match.index, match.index + match[0].length);
          const key = `${word}|${start}|${end}`;
          if (kind === "enhanced" && seen.has(key)) continue;
          seen.add(key);
          hits.push({
            word,
            text: match[0],
            view: kind,
            start,
            end,
            severity: 1 /* EXACT */
          });
        }
      }
    }
    return this.dropWhitelisted(hits, legacy, enhanced);
  }
  dropWhitelisted(hits, legacy, enhanced) {
    if (hits.length === 0 || this.whitelist.isEmpty) return hits;
    const safe = [];
    const sources = [legacy.orig, legacy.norm, legacy.aggr, ...enhanced];
    for (const view of sources) safe.push(...this.whitelist.intervals(view));
    if (safe.length === 0) return hits;
    return hits.filter((hit) => !safe.some((span) => within([hit.start, hit.end], span)));
  }
};

// src/filters/obfuscation.ts
var DEFAULT_LEVEL = "standard";
var DEFAULT_SEPARATORS = ".,/|_-";
var DEFAULT_MASKS = "*#@$%_?!";
function envInt(name, fallback) {
  const raw = typeof process !== "undefined" ? process.env?.[name] : void 0;
  const parsed = raw === void 0 ? NaN : parseInt(raw, 10);
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
}
function object(value) {
  return typeof value === "object" && value !== null ? value : {};
}
function resolveObfuscation(option) {
  const opts = typeof option === "string" ? { level: option } : option ?? {};
  const level = opts.level ?? DEFAULT_LEVEL;
  const on = level !== "off";
  const mid = level === "standard" || level === "strict";
  const strict = level === "strict";
  const flag = (value, byLevel) => value === void 0 ? byLevel : value !== false;
  const html = object(opts.html);
  const sep = object(opts.separators);
  const mask = object(opts.masking);
  return {
    html: {
      enabled: flag(opts.html, on),
      decodeDepth: html.decodeDepth ?? envInt("GLIN_HTML_DECODE_DEPTH", 2)
    },
    separators: {
      enabled: flag(opts.separators, mid),
      chars: sep.chars ?? DEFAULT_SEPARATORS,
      allowSpace: sep.allowSpace ?? strict,
      minTwoPieceLength: sep.minTwoPieceLength ?? envInt("GLIN_SEPARATOR_MIN_JOIN", strict ? 3 : 4)
    },
    masking: {
      enabled: flag(opts.masking, mid),
      chars: mask.chars ?? DEFAULT_MASKS,
      minVisible: mask.minVisible ?? envInt("GLIN_MASK_MIN_VISIBLE", 1)
    },
    stretch: opts.stretch ?? on,
    ambiguousLeet: opts.ambiguousLeet ?? mid,
    builtinWhitelist: opts.builtinWhitelist ?? on
  };
}
function obfuscationEnabled(resolved) {
  return resolved.html.enabled || resolved.separators.enabled || resolved.masking.enabled || resolved.stretch || resolved.ambiguousLeet || resolved.builtinWhitelist;
}

// src/filters/Filter.ts
var _Filter = class _Filter {
  /**
   * Creates a new Filter instance with the specified configuration.
   *
   * @param config - Filter configuration options
   *
   * @example
   * ```typescript
   * // Basic usage
   * const filter = new Filter({ languages: ['english'] });
   *
   * // With leetspeak detection
   * const filter = new Filter({
   *   languages: ['english'],
   *   detectLeetspeak: true,
   *   leetspeakLevel: 'moderate',
   * });
   *
   * // With all advanced features
   * const filter = new Filter({
   *   languages: ['english', 'spanish'],
   *   detectLeetspeak: true,
   *   normalizeUnicode: true,
   *   cacheResults: true,
   *   enableContextAware: true,
   * });
   * ```
   */
  constructor(config) {
    const defaultLanguage = "english";
    this.enableContextAware = config?.enableContextAware ?? false;
    this.contextWindow = config?.contextWindow ?? 3;
    this.confidenceThreshold = config?.confidenceThreshold ?? 0.7;
    this.primaryLanguage = config?.languages?.[0] || defaultLanguage;
    if (this.enableContextAware) {
      this.contextAnalyzer = new ContextAnalyzer({
        contextWindow: this.contextWindow,
        language: this.primaryLanguage,
        domainWhitelists: config?.domainWhitelists?.[this.primaryLanguage] || []
      });
    }
    this.caseSensitive = config?.caseSensitive ?? false;
    this.allowObfuscatedMatch = config?.allowObfuscatedMatch ?? false;
    this.wordBoundaries = config?.wordBoundaries ?? !this.allowObfuscatedMatch;
    this.replaceWith = config?.replaceWith;
    this.severityLevels = config?.severityLevels ?? false;
    this.ignoreWords = new Set(
      config?.ignoreWords?.map((word) => word.toLowerCase()) || []
    );
    this.logProfanity = config?.logProfanity ?? false;
    this.fuzzyToleranceLevel = config?.fuzzyToleranceLevel ?? 0.8;
    this.detectLeetspeak = config?.detectLeetspeak ?? false;
    this.leetspeakLevel = config?.leetspeakLevel ?? "moderate";
    this.normalizeUnicodeEnabled = config?.normalizeUnicode ?? true;
    this.cacheResults = config?.cacheResults ?? false;
    this.maxCacheSize = config?.maxCacheSize ?? 1e3;
    this.cache = /* @__PURE__ */ new Map();
    this.matcher = new WordMatcher({
      caseSensitive: this.caseSensitive,
      wordBoundaries: this.wordBoundaries,
      fuzzyToleranceLevel: this.fuzzyToleranceLevel
    });
    this.obfuscationOption = config?.obfuscation;
    this.whitelistWords = config?.whitelist ?? [];
    let words = [];
    if (config?.allLanguages) {
      for (const lang in dictionary_default) {
        if (Object.prototype.hasOwnProperty.call(dictionary_default, lang)) {
          words = [...words, ...dictionary_default[lang]];
        }
      }
    } else {
      const languages = config?.languages || ["english"];
      const languagesChecks = new Set(languages);
      languagesChecks.forEach((lang) => {
        words = [...words, ...dictionary_default[lang]];
      });
    }
    if (config?.customWords) {
      words = [...words, ...config.customWords];
    }
    if (this.normalizeUnicodeEnabled) {
      words = _Filter.withAccentFoldedAliases(words);
    }
    this.words = new Map(words.map((word) => [word.toLowerCase(), 1]));
    this.scanner = this.createScanner();
    this.legacyWhitelist = !this.scanner && this.whitelistWords.length > 0 ? new Whitelist(this.whitelistWords, false) : void 0;
  }
  static withAccentFoldedAliases(words) {
    const seen = new Set(words.map((word) => word.toLowerCase()));
    const extra = [];
    for (const word of words) {
      const folded = normalizeUnicode(word);
      const key = folded.toLowerCase();
      if (key === word.toLowerCase() || seen.has(key)) continue;
      if (key.replace(/ /g, "").length < _Filter.ACCENT_ALIAS_MIN_LENGTH) continue;
      seen.add(key);
      extra.push(folded);
    }
    return extra.length > 0 ? [...words, ...extra] : words;
  }
  createScanner() {
    const obfuscation = resolveObfuscation(this.obfuscationOption);
    if (!this.wordBoundaries) return void 0;
    if (!obfuscationEnabled(obfuscation) && this.whitelistWords.length === 0) return void 0;
    return new Scanner({
      words: this.words,
      ignoreWords: this.ignoreWords,
      matcher: this.matcher,
      normalizeText: (text, aggressive) => this.normalizeText(text, aggressive),
      detectLeetspeak: this.detectLeetspeak,
      leetspeakLevel: this.leetspeakLevel,
      normalizeUnicode: this.normalizeUnicodeEnabled,
      allowObfuscatedMatch: this.allowObfuscatedMatch,
      obfuscation,
      whitelist: this.whitelistWords,
      caseSensitive: this.caseSensitive
    });
  }
  debugLog(...args) {
    if (this.logProfanity) {
      console.log("[glin-profanity]", ...args);
    }
  }
  /**
   * Normalizes text with every enabled method (Unicode, leetspeak, legacy
   * obfuscation). `aggressive` collapses repeated characters to one.
   */
  normalizeText(text, aggressive = false) {
    return normalizeForMatching(text, aggressive, {
      normalizeUnicode: this.normalizeUnicodeEnabled,
      detectLeetspeak: this.detectLeetspeak,
      leetspeakLevel: this.leetspeakLevel,
      allowObfuscatedMatch: this.allowObfuscatedMatch
    });
  }
  /**
   * Clears the result cache.
   * Useful when dictionary or configuration changes.
   */
  clearCache() {
    this.cache.clear();
    this.matcher.clear();
  }
  /**
   * Gets the current cache size.
   * @returns Number of cached results
   */
  getCacheSize() {
    return this.cache.size;
  }
  /**
   * Exports the current filter configuration as a JSON-serializable object.
   * Useful for saving configuration to files or sharing between environments.
   *
   * @returns The current filter configuration
   *
   * @example
   * ```typescript
   * const filter = new Filter({
   *   languages: ['english', 'spanish'],
   *   detectLeetspeak: true,
   *   leetspeakLevel: 'aggressive',
   * });
   *
   * const config = filter.getConfig();
   * // Save to file: fs.writeFileSync('filter.config.json', JSON.stringify(config));
   *
   * // Later, restore:
   * // const saved = JSON.parse(fs.readFileSync('filter.config.json'));
   * // const restored = new Filter(saved);
   * ```
   */
  getConfig() {
    return {
      languages: [this.primaryLanguage],
      caseSensitive: this.caseSensitive,
      wordBoundaries: this.wordBoundaries,
      replaceWith: this.replaceWith,
      severityLevels: this.severityLevels,
      ignoreWords: Array.from(this.ignoreWords),
      logProfanity: this.logProfanity,
      allowObfuscatedMatch: this.allowObfuscatedMatch,
      fuzzyToleranceLevel: this.fuzzyToleranceLevel,
      enableContextAware: this.enableContextAware,
      contextWindow: this.contextWindow,
      confidenceThreshold: this.confidenceThreshold,
      detectLeetspeak: this.detectLeetspeak,
      leetspeakLevel: this.leetspeakLevel,
      normalizeUnicode: this.normalizeUnicodeEnabled,
      cacheResults: this.cacheResults,
      maxCacheSize: this.maxCacheSize,
      obfuscation: this.obfuscationOption,
      whitelist: this.whitelistWords
    };
  }
  /**
   * Returns the current word dictionary size.
   * Useful for monitoring and debugging.
   *
   * @returns Number of words in the dictionary
   */
  getWordCount() {
    return this.words.size;
  }
  /**
   * Adds a result to the cache, evicting oldest entries if necessary.
   */
  addToCache(key, result) {
    if (!this.cacheResults) return;
    if (this.cache.size >= this.maxCacheSize) {
      const firstKey = this.cache.keys().next().value;
      if (firstKey) {
        this.cache.delete(firstKey);
      }
    }
    this.cache.set(key, result);
  }
  /**
   * Gets a cached result if available.
   */
  getFromCache(key) {
    if (!this.cacheResults) return void 0;
    return this.cache.get(key);
  }
  redact(text, profaneWords, hits, safeSpans = []) {
    if (!this.replaceWith) return text;
    return redactText(text, profaneWords, this.replaceWith, this.wordBoundaries, hits, safeSpans);
  }
  /** Safe spans of the text on the legacy path, and the text with them blanked. */
  legacySafe(text) {
    if (!this.legacyWhitelist) return { spans: [], text };
    const spans = protectedSpans(this.legacyWhitelist, text);
    return { spans, text: spans.length > 0 ? blankProtected(text, spans) : text };
  }
  legacyContext() {
    return {
      words: this.words,
      ignoreWords: this.ignoreWords,
      matcher: this.matcher,
      normalizeText: (text, aggressive) => this.normalizeText(text, aggressive)
    };
  }
  /**
   * Checks if the given text contains profanity.
   *
   * @param value - The text to check
   * @returns True if the text contains profanity
   *
   * @example
   * ```typescript
   * const filter = new Filter({ detectLeetspeak: true });
   *
   * filter.isProfane('hello');     // false
   * filter.isProfane('fuck');      // true
   * filter.isProfane('f4ck');      // true (leetspeak)
   * filter.isProfane('fυck');      // true (Unicode homoglyph)
   * ```
   */
  isProfane(value) {
    if (this.scanner) return this.scanner.isProfane(value);
    return legacyIsProfane(this.legacyContext(), this.legacySafe(value).text);
  }
  matches(word) {
    return this.isProfane(word);
  }
  /**
   * Performs a comprehensive profanity check on the given text.
   *
   * @param text - The text to check for profanity
   * @returns Result object containing detected profanity information
   *
   * @example
   * ```typescript
   * const filter = new Filter({
   *   languages: ['english'],
   *   detectLeetspeak: true,
   *   normalizeUnicode: true,
   * });
   *
   * const result = filter.checkProfanity('This is f4ck!ng bad');
   * console.log(result.containsProfanity);  // true
   * console.log(result.profaneWords);       // ['fuck']
   *
   * // With caching for repeated checks
   * const filter2 = new Filter({ cacheResults: true });
   * filter2.checkProfanity('same text');  // Computed
   * filter2.checkProfanity('same text');  // Retrieved from cache
   * ```
   */
  checkProfanity(text) {
    const cacheKey = text;
    const cachedResult = this.getFromCache(cacheKey);
    if (cachedResult) {
      this.debugLog("Cache hit for:", text.substring(0, 50));
      return cachedResult;
    }
    if (!this.enableContextAware) {
      const hits = this.scanner ? this.scanner.scan(text) : void 0;
      const safe2 = this.legacySafe(text);
      const { profaneWords: profaneWords2, severityMap: severityMap2 } = hits ? summarizeHits(hits) : legacyScan(this.legacyContext(), safe2.text);
      const result2 = {
        containsProfanity: profaneWords2.length > 0,
        profaneWords: Array.from(new Set(profaneWords2)),
        processedText: this.replaceWith ? this.redact(text, profaneWords2, hits, safe2.spans) : void 0,
        severityMap: this.severityLevels && Object.keys(severityMap2).length > 0 ? severityMap2 : void 0
      };
      this.addToCache(cacheKey, result2);
      return result2;
    }
    const originalText = text;
    const profaneWords = [];
    const severityMap = {};
    const matches = [];
    const safe = this.legacySafe(text);
    const legacySafeText = safe.text;
    const scanHits = this.scanner ? uniqueSpans(this.scanner.scan(text)) : void 0;
    const contextHits = scanHits ? scanHits.map((h) => ({
      word: h.view === "orig" ? h.text : h.word,
      index: h.start,
      severity: h.severity,
      scan: h
    })) : legacyContextHits(this.legacyContext(), legacySafeText).map((h) => ({ ...h, scan: void 0 }));
    const keptHits = [];
    for (const hit of contextHits) {
      const matchObj = { word: hit.word, index: hit.index, severity: hit.severity };
      if (this.contextAnalyzer) {
        const contextResult = this.contextAnalyzer.analyzeContext(
          originalText,
          hit.word,
          hit.index
        );
        matchObj.contextScore = contextResult.contextScore;
        matchObj.reason = contextResult.reason;
        matchObj.isWhitelisted = contextResult.isWhitelisted;
        if (contextResult.isWhitelisted || contextResult.contextScore > this.confidenceThreshold) {
          continue;
        }
      }
      profaneWords.push(hit.word);
      if (hit.scan) keptHits.push(hit.scan);
      if (severityMap[hit.word] === void 0) severityMap[hit.word] = hit.severity;
      matches.push(matchObj);
    }
    if (profaneWords.length > 0) {
      this.debugLog("Detected:", profaneWords);
    }
    const processedText = this.redact(text, profaneWords, scanHits ? keptHits : void 0, safe.spans);
    let contextScore;
    if (this.enableContextAware && matches.length > 0) {
      const totalScore = matches.reduce((sum, match) => sum + (match.contextScore || 0.5), 0);
      contextScore = totalScore / matches.length;
    }
    const result = {
      containsProfanity: profaneWords.length > 0,
      profaneWords: Array.from(new Set(profaneWords)),
      processedText: this.replaceWith ? processedText : void 0,
      severityMap: this.severityLevels && Object.keys(severityMap).length > 0 ? severityMap : void 0,
      matches: matches.length > 0 ? matches : void 0,
      contextScore,
      reason: matches.length > 0 ? `Found ${matches.length} potential profanity matches` : "No profanity detected"
    };
    this.addToCache(cacheKey, result);
    return result;
  }
  /**
   * Checks profanity with minimum severity filtering.
   *
   * @param text - The text to check
   * @param minSeverity - Minimum severity level to include in results
   * @returns Object with filtered words and full result
   */
  checkProfanityWithMinSeverity(text, minSeverity = 1 /* EXACT */) {
    const result = this.checkProfanity(text);
    const filteredWords = result.severityMap && result.profaneWords.length > 0 ? result.profaneWords.filter((word) => {
      const severity = result.severityMap?.[word];
      return typeof severity === "number" && severity >= minSeverity;
    }) : [];
    return { filteredWords, result };
  }
};
/**
 * The obfuscation-aware scanner needs word boundaries; fuzzy matching
 * (wordBoundaries off) and `obfuscation: 'off'` keep the legacy scan.
 */
_Filter.ACCENT_ALIAS_MIN_LENGTH = 4;
var Filter = _Filter;

// src/frameworks/nextjs.ts
function createFilter(config) {
  return new Filter({
    languages: config?.languages || ["english"],
    detectLeetspeak: config?.detectLeetspeak ?? true,
    normalizeUnicode: config?.normalizeUnicode ?? true,
    severityLevels: true,
    cacheResults: true,
    ...config
  });
}
function matchPath(path, pattern) {
  const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
  return new RegExp(`^${regexPattern}$`).test(path);
}
function getNestedValue(obj, path) {
  const parts = path.split(".");
  let current = obj;
  for (const part of parts) {
    if (current === null || current === void 0) return void 0;
    if (typeof current !== "object") return void 0;
    current = current[part];
  }
  return typeof current === "string" ? current : void 0;
}
function createProfanityMiddleware(config = {}) {
  const {
    paths = ["/api/*"],
    excludePaths = [],
    blockProfanity = true,
    addHeaders = true,
    blockedResponse = { status: 400, message: "Request contains inappropriate content" },
    checkFields = ["message", "content", "text", "body", "comment"],
    filterConfig
  } = config;
  const filter = createFilter(filterConfig);
  return async function middleware(request) {
    const url = new URL(request.url);
    const pathname = url.pathname;
    const shouldProcess = paths.some((p) => matchPath(pathname, p));
    const shouldExclude = excludePaths.some((p) => matchPath(pathname, p));
    if (!shouldProcess || shouldExclude) {
      return void 0;
    }
    const method = request.method.toUpperCase();
    if (!["POST", "PUT", "PATCH"].includes(method)) {
      return void 0;
    }
    try {
      const clonedRequest = request.clone();
      const body = await clonedRequest.json();
      const flaggedFields = [];
      const allProfaneWords = [];
      for (const field of checkFields) {
        const value = getNestedValue(body, field);
        if (value) {
          const result = filter.checkProfanity(value);
          if (result.containsProfanity) {
            flaggedFields.push(field);
            allProfaneWords.push(...result.profaneWords);
          }
        }
      }
      const containsProfanity = flaggedFields.length > 0;
      if (containsProfanity && blockProfanity) {
        return new Response(
          JSON.stringify({
            error: blockedResponse.message,
            flaggedFields,
            profaneWords: [...new Set(allProfaneWords)]
          }),
          {
            status: blockedResponse.status || 400,
            headers: { "Content-Type": "application/json" }
          }
        );
      }
      if (addHeaders) {
        const headers = new Headers();
        headers.set("X-Profanity-Detected", containsProfanity.toString());
        if (containsProfanity) {
          headers.set("X-Profanity-Fields", flaggedFields.join(","));
          headers.set("X-Profanity-Words", [...new Set(allProfaneWords)].join(","));
        }
        return new Response(null, { headers });
      }
      return void 0;
    } catch {
      return void 0;
    }
  };
}
function withProfanityCheck(handler, config = {}) {
  const {
    checkFields = ["message", "content", "text", "body"],
    blockProfanity = true,
    filterConfig
  } = config;
  const filter = createFilter(filterConfig);
  return async (request) => {
    try {
      const clonedRequest = request.clone();
      const body = await clonedRequest.json();
      const flaggedFields = [];
      const allProfaneWords = [];
      let lastCheckResult;
      for (const field of checkFields) {
        const value = getNestedValue(body, field);
        if (value) {
          const result = filter.checkProfanity(value);
          lastCheckResult = result;
          if (result.containsProfanity) {
            flaggedFields.push(field);
            allProfaneWords.push(...result.profaneWords);
          }
        }
      }
      const containsProfanity = flaggedFields.length > 0;
      const profanityResult = {
        blocked: containsProfanity && blockProfanity,
        reason: containsProfanity ? `Profanity detected in fields: ${flaggedFields.join(", ")}` : void 0,
        profaneWords: [...new Set(allProfaneWords)],
        flaggedFields,
        checkResult: lastCheckResult
      };
      if (profanityResult.blocked) {
        return new Response(
          JSON.stringify({
            error: "Request contains inappropriate content",
            flaggedFields: profanityResult.flaggedFields
          }),
          {
            status: 400,
            headers: { "Content-Type": "application/json" }
          }
        );
      }
      return handler(request, profanityResult);
    } catch {
      return handler(request, {
        blocked: false,
        profaneWords: [],
        flaggedFields: []
      });
    }
  };
}
function createServerAction(action, config = {}) {
  const {
    checkFields = ["message", "content", "text", "comment"],
    filterConfig,
    onProfanity
  } = config;
  const filter = createFilter(filterConfig);
  return async (data) => {
    const flaggedFields = [];
    const allProfaneWords = [];
    for (const field of checkFields) {
      const value = getNestedValue(data, field);
      if (value) {
        const result = filter.checkProfanity(value);
        if (result.containsProfanity) {
          flaggedFields.push(field);
          allProfaneWords.push(...result.profaneWords);
        }
      }
    }
    if (flaggedFields.length > 0) {
      const profanityResult = {
        blocked: true,
        reason: `Profanity detected in: ${flaggedFields.join(", ")}`,
        profaneWords: [...new Set(allProfaneWords)],
        flaggedFields
      };
      if (onProfanity) {
        return onProfanity(profanityResult);
      }
      throw new Error(profanityResult.reason);
    }
    return action(data);
  };
}
var profanityUtils = {
  /**
   * Quick check for profanity in a single string
   */
  check(text, config) {
    const filter = createFilter(config);
    return filter.checkProfanity(text);
  },
  /**
   * Check and optionally censor text
   */
  censor(text, replacement = "***", config) {
    const filter = createFilter({ ...config, replaceWith: replacement });
    const result = filter.checkProfanity(text);
    return result.processedText || text;
  },
  /**
   * Check multiple fields in an object
   */
  checkObject(obj, fields, config) {
    const filter = createFilter(config);
    const flaggedFields = [];
    const profaneWords = [];
    for (const field of fields) {
      const value = getNestedValue(obj, field);
      if (value) {
        const result = filter.checkProfanity(value);
        if (result.containsProfanity) {
          flaggedFields.push(field);
          profaneWords.push(...result.profaneWords);
        }
      }
    }
    return {
      containsProfanity: flaggedFields.length > 0,
      flaggedFields,
      profaneWords: [...new Set(profaneWords)]
    };
  }
};

exports.createProfanityMiddleware = createProfanityMiddleware;
exports.createServerAction = createServerAction;
exports.profanityUtils = profanityUtils;
exports.withProfanityCheck = withProfanityCheck;