npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bekindprofanityfilter

v0.0.15

Published

A multi-language profanity filter with romanization detection, language-aware innocence scoring, leet-speak detection, and cross-language collision handling. Forked from AllProfanity.

Readme

BeKind Profanity Filter

Forked from AllProfanity by Ayush Jadaun. Extended with romanization profanity detection (catches Hinglish, transliterated text), language-aware innocence scoring (ELD + trie-based detection prevents false positives for cross-language collisions like "got" in Turkish), and additional language dictionaries. Licensed under MIT.

⚠️ Early-stage package in progress. Features available in the original AllProfanity are being actively deprecated, adjusted, or replaced. API surface may change without notice. Contributions and suggestions greatly appreciated.

Please be advised: Due to the nature of its purpose, the be-kind repository contains explicit profanity, slurs, hate speech, and other offensive language across its source files, dictionaries, and test suites (sorry!). The inclusion of these words does not reflect the views of the authors or contributors.

A multi-language profanity filter with romanization detection, language-aware innocence scoring, leet-speak detection, and cross-language collision handling.

npm version License: MIT Languages Detection Trie


What This Version Contains

  • Multi-Language Profanity Detection: 34K+ word dictionary across 16 languages with 18-language detection trie
  • Romanization Detection: Catches Hinglish, transliterated Bengali, Tamil, Telugu, and Japanese
  • Cross-Language Innocence Scoring: Handles words like "got" (Turkish: "buttocks") and "fart" (Norwegian: "speed")
  • False Positive Reduction: Common dual-meaning words (groomer, missionary, edibles, tinker, etc.) are excluded from default detection to prevent flagging legitimate content on community platforms
  • Context-Aware Analysis: Booster/reducer patterns detect sexual context, negation, medical usage, and quoted speech
  • Leet-Speak Detection: Catches obfuscated profanity (f#ck, a55hole, sh1t) including digit-based leet (6006s, d1ld0)
  • Word Boundary Detection: Smart whole-word matching prevents flagging "assassin" or "assistance"
  • Multiple Algorithms: Trie (default), Aho-Corasick, or Hybrid modes with optional Bloom filters and result caching

Features

Performance & Speed

  • Multiple Algorithm Options: Choose between Trie (default), Aho-Corasick, or Hybrid modes
  • Fast on Large Texts: Aho-Corasick delivers O(n) multi-pattern matching
  • 123x Speedup with Caching: Result cache perfect for repeated checks (chat, forms, APIs)
  • ~27K ops/sec: Default Trie mode handles short texts incredibly fast
  • Single-Pass Scanning: O(n) complexity regardless of dictionary size
  • Batch Processing Ready: Optimized for high-throughput API endpoints

Accuracy & Detection

  • Word Boundary Matching: Smart whole-word detection prevents false positives like "assassin" or "assistance"
  • Advanced Leet-Speak: Detects obfuscated profanities (f#ck, a55hole, sh1t, etc.)
  • Comprehensive Coverage: Catches profanity while minimizing false flags
  • Configurable Strictness: Tune detection sensitivity to your needs

Multi-Language & Flexibility

  • Multi-Language Support: Built-in profanity dictionaries for 16 languages: English, Hindi, French, German, Spanish, Italian, Brazilian Portuguese, Russian, Arabic, Chinese, Japanese, Korean, Bengali, Tamil, Telugu, Turkish
  • Multiple Scripts: Latin/Roman (Hinglish) and native scripts (Devanagari, Tamil, Telugu, etc.)
  • Custom Dictionaries: Add/remove words or entire language packs at runtime
  • Whitelisting: Exclude safe words from detection
  • Severity Scoring: Assess content offensiveness (MILD, MODERATE, SEVERE, EXTREME)

Developer Experience

  • TypeScript Support: Fully typed API with comprehensive documentation
  • Zero 3rd-Party Dependencies: Only internal code and data
  • Configurable: Tune performance vs accuracy for your use case
  • No Dictionary Exposure: Secure by design - word lists never exposed
  • Universal: Works in Node.js and browsers

Forked from AllProfanity by Ayush Jadaun. Extended with romanization profanity detection (catches Hinglish, transliterated text), language-aware innocence scoring (ELD + trie-based detection prevents false positives for cross-language collisions like "got" in Turkish), and additional language dictionaries. Licensed under MIT.

Installation

npm install bekindprofanityfilter
# or
yarn add bekindprofanityfilter

Generate configuration file (optional):

npx bekindprofanityfilter
# Creates bekindprofanityfilter.config.json and config.schema.json in your project

Quick Start

import profanity from 'bekindprofanityfilter';

// Simple check
profanity.check('This is a clean sentence.');        // false
profanity.check('What the f#ck is this?');           // true (leet-speak detected)
profanity.check('यह एक चूतिया परीक्षण है।');           // true (Hindi)
profanity.check('Ye ek chutiya test hai.');          // true (Hinglish Roman script)

Algorithm Configuration

BeKind v2.2+ offers multiple algorithms optimized for different use cases. You can configure via constructor options or config file.

Configuration Methods

Method 1: Constructor Options (Inline)

import { BeKind } from 'bekindprofanityfilter';

const filter = new BeKind({
  algorithm: { matching: "hybrid" },
  performance: { enableCaching: true }
});

Method 2: Config File (Recommended)

# Generate config files in your project
npx bekindprofanityfilter

# This creates:
# - bekindprofanityfilter.config.json (main config)
# - config.schema.json (for IDE autocomplete)
import { BeKind } from 'bekindprofanityfilter';
import config from './bekindprofanityfilter.config.json';

// Load from generated config file
const filter = BeKind.fromConfig(config);

// Or directly from object (no file needed)
const filter2 = BeKind.fromConfig({
  algorithm: { matching: "hybrid", useContextAnalysis: true },
  performance: { enableCaching: true, cacheSize: 1000 }
});

Example Config File (bekindprofanityfilter.config.json):

{
  "algorithm": {
    "matching": "hybrid",
    "useAhoCorasick": true,
    "useBloomFilter": true
  },
  "profanityDetection": {
    "enableLeetSpeak": true,
    "caseSensitive": false,
    "strictMode": false
  },
  "performance": {
    "enableCaching": true,
    "cacheSize": 1000
  }
}

Config File: Run npx bekindprofanityfilter to generate config files in your project. The JSON schema provides IDE autocomplete and validation.


Quick Configuration Examples

1. Default (Best for General Use)

import { BeKind } from 'bekindprofanityfilter';
const filter = new BeKind();
// Uses optimized Trie - fast and reliable (~27K ops/sec)

2. Large Text Processing (Documents, Articles)

const filter = new BeKind({
  algorithm: { matching: "aho-corasick" }
});
// 664% faster on 1KB+ texts

3. Repeated Checks (Chat, Forms, APIs)

const filter = new BeKind({
  performance: {
    enableCaching: true,
    cacheSize: 1000
  }
});

Alternative Library Comparison

The main strength of be-kind comes from its dictionary and knowledge base. To give a fair comparison, all benchmarks below inject be-kind's full 34K-word dictionary into every alternative library, so the results compare matching engines and detection features, not dictionary coverage.

Benchmarked on a single CPU core (pinned via taskset -c 0). All numbers are ops/second — higher is better.

leo-profanity ships with ~400 English words, bad-words ships with ~400 English words, and glin-profanity loads its own 24-language dictionaries — all receive be-kind's 34K dictionary on top.

| Library | Languages (out-of-the-box) | Leet-speak | Repeat compression | Context-aware | |---------|--------------------------|-----------|-------------------|--------------| | be-kind | 16 profanity dicts + 18-lang detection trie | ✅ | 🚧 planned | ✅ (certainty-delta) | | be-kind (ctx) | same as be-kind | ✅ | 🚧 planned | ✅ (boosters + reducers) | | leo-profanity + dict | 16 (via be-kind dict injection) | ❌ | ❌ | ❌ | | bad-words + dict | 16 (via be-kind dict injection) | ❌ | ❌ | ❌ | | glin-profanity + dict | 24 + be-kind dict | ✅ (3 levels) | ✅ | ✅ (heuristic) |

Speed benchmark — ops/second on a single CPU core (taskset -c 0), higher is better. All competitors have be-kind's 34K dictionary injected:

| Test | be-kind | be-kind (ctx) | leo + dict | bad-words + dict | glin (basic) | glin (enhanced) | |------|--------:|--------------:|-----------:|-----------------:|-------------:|----------------:| | check — clean (short) | 2,625 | 3,007 | 932,597 | 29 | 68 | 68 | | check — profane (short) | 2,556 | 2,251 | 1,424,984 | 27 | 3,602 | 3,333 | | check — leet-speak | 1,407 | 1,324 | 1,540,700 | 26 | 2,791 | 4,350 | | clean — profane (short) | 2,499 | 2,243 | 372,049 | 2 | N/A | N/A | | check — 500-char clean | 409 | 427 | 110,318 | 17 | 21 | 22 | | check — 500-char profane | 357 | 314 | 217,347 | 17 | 828 | 718 | | check — 2,500-char clean | 88 | 90 | 21,727 | 10 | 6 | 6 | | check — 2,500-char profane | 79 | 69 | 47,966 | 9 | 192 | 165 |

Library versions tested: [email protected], [email protected], [email protected]

Notes:

  • All competitors have be-kind's 34K dictionary injected to isolate matching-engine performance from dictionary coverage.
  • be-kind is ~39x faster than glin on clean short text (2,625 vs 68 ops/s) with the same vocabulary. be-kind uses a trie (O(input_length) matching), while glin uses linear scanning (for (const word of this.words.keys()) — O(dict_size * input_length)).
  • be-kind (ctx) adds ~10-15% overhead over default be-kind — context analysis (certainty-delta pattern matching) is cheap.
  • leo + dict is the fastest by a large margin but offers no leet-speak, no context analysis, and no repeat compression — it's a simple substring matcher. Its speed advantage comes from a flat array lookup with no normalization overhead.
  • bad-words + dict demonstrates the regex bottleneck catastrophically: 29 ops/s on clean short text vs 2,625 for be-kind — a ~90x slowdown. bad-words creates a new RegExp per word in a .filter() loop (source) — no short-circuiting, so clean and profane text perform identically (~27 ops/s). clean() drops to 2 ops/s (vs 2,499 for be-kind). This makes bad-words unsuitable for large multilingual dictionaries.
  • glin with dict collapses to 68 ops/s on clean short text (vs 2,625 for be-kind) — a ~39x slowdown — demonstrating the linear-scan bottleneck at scale. glin short-circuits on first match, which explains the ~53x speedup on profane text (3,602 ops/s) vs clean text (68 ops/s).
  • be-kind is the only library with cross-language innocence scoring, romanization support, and context-aware certainty adjustment.

Run the speed benchmark yourself:

taskset -c 0 bun run benchmark:competitors

Accuracy Comparison

Measures TP rate (recall), FP rate, and F1 across eight test categories (225 labeled cases, dataset v6). All alternative libraries have be-kind's 34K dictionary injected. All libraries are tested against all categories — no exemptions. Higher F1 and lower FP rate are better.

Bias disclaimer: This dataset was created by the be-kind team. Non-English cases were likely drawn from or verified against be-kind's own dictionary, which advantages be-kind on those categories. To partially offset this, the dataset includes independent test cases from glin-profanity's upstream test suite and adversarial false-positive cases specifically chosen to expose known be-kind failures. We strongly recommend running this benchmark against your own dataset before drawing conclusions.

Note: be-kind (sensitive) = sensitiveMode: true (flags AMBIVALENT words too). be-kind (ctx) = contextAnalysis.enabled: true. glin (collapsed) + dict = glin (basic) + dict with collapseRepeatedCharacters() pre-processing. All alternative libraries have be-kind's 34K dictionary injected.

Single-language detection — 65 cases (English incl. leetspeak, French, German, Spanish, Hindi)

| Library | Recall | Precision | FP Rate | F1 | |---|---|---|---|---| | be-kind (sensitive) | 100% | 100% | 0% | 1.00 | | bad-words + dict | 88% | 100% | 0% | 0.94 | | glin (enhanced) + dict | 88% | 100% | 0% | 0.94 | | glin (collapsed) + dict | 86% | 100% | 0% | 0.92 | | leo + dict | 82% | 100% | 0% | 0.90 | | be-kind | 80% | 100% | 0% | 0.89 | | be-kind (ctx) | 80% | 100% | 0% | 0.89 |

With be-kind's 34K dictionary injected, all alternatives improve dramatically. bad-words + dict and glin (enhanced) + dict both reach 88% recall (up from 52% and 72% without dict). be-kind in default mode misses mild words (damn, hell); sensitiveMode: true catches these. All libraries achieve 100% precision — when they flag something, it's always correct.

False positives / innocent words — 48 cases (clean only, lower FP rate is better)

Includes adversarial cases (cum laude, Dick Van Dyke, culinary faggots, Turkish got). Recall and F1 are undefined (no profane cases).

| Library | FP Rate | |---|---| | leo + dict | 25% | | be-kind (ctx) | 25% | | be-kind | 27% | | be-kind (sensitive) | 31% | | glin (enhanced) + dict | 31% | | glin (collapsed) + dict | 31% | | bad-words + dict | 33% |

With the full 34K dictionary injected, glin and bad-words now produce more false positives than before — their FP rates rise to 31-33% due to the larger vocabulary. be-kind (ctx) ties with leo + dict for the lowest FP rate (25%) thanks to context-aware certainty adjustment. be-kind's FP rate remains a significant weakness, but context analysis helps.

Multi-language detection — 26 cases (Hinglish, French, German, Spanish, mixed)

| Library | Recall | Precision | FP Rate | F1 | |---|---|---|---|---| | be-kind | 100% | 100% | 0% | 1.00 | | be-kind (sensitive) | 100% | 100% | 0% | 1.00 | | leo + dict | 100% | 100% | 0% | 1.00 | | bad-words + dict | 100% | 100% | 0% | 1.00 | | glin (enhanced) + dict | 100% | 100% | 0% | 1.00 | | be-kind (ctx) | 100% | 100% | 0% | 1.00 | | glin (collapsed) + dict | 100% | 100% | 0% | 1.00 |

With be-kind's 34K dictionary injected, every library achieves 100% recall — proving the dictionary is the sole differentiator for multi-language detection. The matching engine doesn't matter when the vocabulary is comprehensive enough.

Romanization — 30 cases (Hinglish, Bengali, Tamil, Telugu, Japanese)

| Library | Recall | Precision | FP Rate | F1 | |---|---|---|---|---| | glin (enhanced) + dict | 85% | 81% | 40% | 0.83 | | leo + dict | 75% | 94% | 10% | 0.83 | | be-kind | 80% | 84% | 30% | 0.82 | | be-kind (sensitive) | 80% | 84% | 30% | 0.82 | | be-kind (ctx) | 80% | 84% | 30% | 0.82 | | bad-words + dict | 80% | 84% | 30% | 0.82 | | glin (collapsed) + dict | 80% | 84% | 30% | 0.82 |

With dict injection, glin (enhanced) + dict achieves the highest recall (85%) on romanization — glin's leet-speak detection catches additional transliterated variants. However, its FP rate (40%) is also the highest. leo + dict achieves the same F1 (0.83) with much better precision (94%) and lowest FP (10%). be-kind, bad-words + dict, and glin (collapsed) + dict all tie at 80% recall / 30% FP / F1=0.82, showing that the dictionary drives most romanization detection — not the matching engine.

Semantic context — 25 cases

| Library | Recall | Precision | FP Rate | F1 | |---|---|---|---|---| | leo + dict | 100% | 59% | 47% | 0.74 | | bad-words + dict | 100% | 48% | 73% | 0.65 | | be-kind (sensitive) | 100% | 48% | 73% | 0.65 | | glin (enhanced) + dict | 100% | 48% | 73% | 0.65 | | glin (collapsed) + dict | 100% | 48% | 73% | 0.65 | | be-kind (ctx) | 80% | 62% | 47% | 0.64 | | be-kind | 80% | 47% | 60% | 0.59 |

Semantic context is where all libraries struggle — precision drops below 50% for most. Cases include metalinguistic uses, negation, and medical context. With dict injection, bad-words + dict and glin now achieve 100% recall but at the cost of 73% FP rate. be-kind (ctx) trades lower recall (80%) for better precision (62%) and a lower FP rate (47%) via context-aware certainty adjustment — boosters confirm profane intent, reducers detect innocent contexts like proper nouns and medical terms.

Repeated character evasion — 5 cases (elongated profanity)

No clean cases in this category — FP rate is undefined.

| Library | Recall | Precision | |---|---|---| | glin (enhanced) + dict | 100% | 100% | | glin (collapsed) + dict | 40% | 100% | | be-kind | 0% | — | | be-kind (sensitive) | 0% | — | | be-kind (ctx) | 0% | — | | leo + dict | 0% | — | | bad-words + dict | 0% | — |

Concatenated / no-space evasion — 7 cases (profanity embedded in concatenated strings)

| Library | Recall | Precision | FP Rate | F1 | |---|---|---|---|---| | be-kind | 20% | 100% | 0% | 0.33 | | be-kind (sensitive) | 20% | 100% | 0% | 0.33 | | be-kind (ctx) | 20% | 100% | 0% | 0.33 | | bad-words + dict | 20% | 100% | 0% | 0.33 | | glin (enhanced) + dict | 20% | 100% | 0% | 0.33 | | glin (collapsed) + dict | 20% | 100% | 0% | 0.33 | | leo + dict | 0% | — | 0% | — |

Challenge cases — 19 cases (semantic disambiguation, embedded substrings, separator evasion)

Hard problems: cock as rooster, ass as donkey, Turkish got = "buttocks" vs English "got", profanity in concatenated strings, and separator-spaced evasion (f u c k, f_u*c k, a.s.s.h.o.l.e).

| Library | Recall | Precision | FP Rate | F1 | |---|---|---|---|---| | be-kind (ctx) | 60% | 75% | 33% | 0.63 | | be-kind | 60% | 60% | 44% | 0.60 | | be-kind (sensitive) | 60% | 60% | 44% | 0.60 | | glin (enhanced) + dict | 60% | 60% | 44% | 0.60 | | bad-words + dict | 50% | 56% | 44% | 0.53 | | glin (collapsed) + dict | 50% | 56% | 44% | 0.53 | | leo + dict | 20% | 50% | 22% | 0.29 |

be-kind (ctx) achieves the best F1 on challenge cases thanks to context-aware certainty adjustment — recognizing innocent contexts like "cock crowed at dawn" and "wild ass is an equine." With dict injection, glin (enhanced) + dict now matches be-kind's recall (60%) but at higher FP (44% vs 33%). Separator-spaced evasion cases (f u c k, f_u*c k, mixed separators) test features that no alternative library supports. These cases still require semantic understanding that no dictionary-based filter can fully solve — the strongest argument for LLM-assisted moderation as a second pass.

Overall summary — micro-averaged across all 225 cases

All alternative libraries have be-kind's 34K dictionary injected.

| Library | Recall | Precision | FP Rate | F1 | TP | FN | FP | TN | |---|---|---|---|---|---|---|---|---| | be-kind (sensitive) | 86% | 76% | 32% | 0.81 | 104 | 17 | 33 | 71 | | glin (enhanced) + dict | 86% | 75% | 33% | 0.80 | 104 | 17 | 34 | 70 | | glin (collapsed) + dict | 81% | 75% | 32% | 0.78 | 98 | 23 | 33 | 71 | | bad-words + dict | 80% | 74% | 33% | 0.77 | 97 | 24 | 34 | 70 | | leo + dict | 74% | 80% | 21% | 0.77 | 89 | 32 | 22 | 82 | | be-kind (ctx) | 76% | 79% | 24% | 0.77 | 92 | 29 | 25 | 79 | | be-kind | 76% | 76% | 28% | 0.76 | 92 | 29 | 29 | 75 |

Micro-averaged: all 225 cases (121 profane, 104 clean) aggregated into one confusion matrix per library, then recall/precision/F1 computed once. No category weighting artifacts. With be-kind's dictionary injected, glin (enhanced) + dict matches be-kind (sensitive) on recall (86%) and nearly matches on F1 (0.80 vs 0.81) — proving the dictionary is the core differentiator, not the matching engine. leo + dict and be-kind (ctx) tie for best precision (79-80%) and lowest FP rates (21-24%). be-kind (ctx) achieves this through context-aware certainty adjustment; leo achieves it through simpler matching that avoids over-triggering.

Run the accuracy benchmark yourself:

bun run benchmark:accuracy

API Reference & Examples

check(text: string): boolean

Returns true if the text contains any profanity.

profanity.check('This is a clean sentence.');  // false
profanity.check('This is a b*llsh*t sentence.'); // true
profanity.check('What the f#ck is this?'); // true (leet-speak)
profanity.check('यह एक चूतिया परीक्षण है।'); // true (Hindi)

detect(text: string): ProfanityDetectionResult

Returns a detailed result:

  • hasProfanity: boolean
  • detectedWords: string[] (actual matched words)
  • cleanedText: string (character-masked)
  • severity: ProfanitySeverity (MILD, MODERATE, SEVERE, EXTREME)
  • positions: Array<{ word: string, start: number, end: number }>
const result = profanity.detect('This is f**king b*llsh*t and chutiya.');
console.log(result.hasProfanity); // true
console.log(result.detectedWords); // ['f**king', 'b*llsh*t', 'chutiya']
console.log(result.severity); // 3 (SEVERE)
console.log(result.cleanedText); // "This is ******* ******** and ******."
console.log(result.positions); // e.g. [{word: 'fucking', start: 8, end: 15}, ...]

clean(text: string, placeholder?: string): string

Replace each character of profane words with a placeholder (default: *).

profanity.clean('This contains b*llsh*t.'); // "This contains ********."
profanity.clean('This contains b*llsh*t.', '#'); // "This contains ########."
profanity.clean('यह एक चूतिया परीक्षण है।'); // e.g. "यह एक ***** परीक्षण है।"

cleanWithPlaceholder(text: string, placeholder?: string): string

Replace each profane word with a single placeholder (default: ***).
(If the placeholder is omitted, uses ***.)

profanity.cleanWithPlaceholder('This contains b*llsh*t.'); // "This contains ***."
profanity.cleanWithPlaceholder('This contains b*llsh*t.', '[CENSORED]'); // "This contains [CENSORED]."
profanity.cleanWithPlaceholder('यह एक चूतिया परीक्षण है।', '####'); // e.g. "यह एक #### परीक्षण है।"

add(word: string | string[]): void

Add a word or an array of words to the profanity filter.

profanity.add('badword123');
profanity.check('This is badword123.'); // true

profanity.add(['mierda', 'puta']);
profanity.check('Esto es mierda.'); // true (Spanish)
profanity.check('Qué puta situación.'); // true

remove(word: string | string[]): void

Remove a word or an array of words from the profanity filter.

profanity.remove('b*llsh*t');
profanity.check('This is b*llsh*t.'); // false

profanity.remove(['mierda', 'puta']);
profanity.check('Esto es mierda.'); // false

addToWhitelist(words: string[]): void

Whitelist words so they are never flagged as profane.

profanity.addToWhitelist(['f**k', 'idiot','sh*t']);
profanity.check('He is an f**king idiot.'); // false
profanity.check('F**k this sh*t.'); // false
// Remove from whitelist to restore detection
profanity.removeFromWhitelist(['f**k', 'idiot','sh*t']);

removeFromWhitelist(words: string[]): void

Remove words from the whitelist so they can be detected again.

profanity.removeFromWhitelist(['anal']);

setPlaceholder(placeholder: string): void

Set the default placeholder character for clean().

profanity.setPlaceholder('#');
profanity.clean('This is b*llsh*t.'); // "This is ########."
profanity.setPlaceholder('*'); // Reset to default

updateConfig(options: Partial<BeKindOptions>): void

Change configuration at runtime.
Options include: enableLeetSpeak, caseSensitive, strictMode, detectPartialWords, defaultPlaceholder, languages, whitelistWords.

profanity.updateConfig({ caseSensitive: true, enableLeetSpeak: false });
profanity.check('F**K'); // false (if caseSensitive)
profanity.updateConfig({ caseSensitive: false, enableLeetSpeak: true });
profanity.check('f#ck'); // true

loadLanguage(language: string): boolean

Load a built-in language.

profanity.loadLanguage('french');
profanity.check('Ce mot est merde.'); // true

loadLanguages(languages: string[]): number

Load multiple built-in languages at once.

profanity.loadLanguages(['english', 'french', 'german']);
profanity.check('Das ist scheiße.'); // true (German)

loadIndianLanguages(): number

Convenience: Load all major Indian language packs.

profanity.loadIndianLanguages();
profanity.check('यह एक बेंगाली गाली है।'); // true (Bengali)
profanity.check('This is a Tamil profanity: புண்டை'); // true

loadCustomDictionary(name: string, words: string[]): void

Add your own dictionary as an additional language.

profanity.loadCustomDictionary('swedish', ['fan', 'jävla', 'skit']);
profanity.loadLanguage('swedish');
profanity.check('Det här är skit.'); // true

getLoadedLanguages(): string[]

Returns the names of all currently loaded language packs.

console.log(profanity.getLoadedLanguages()); // ['english', 'hindi', ...]

getAvailableLanguages(): string[]

Returns the names of all available built-in language packs.

console.log(profanity.getAvailableLanguages());
// ['english', 'hindi', 'french', 'german', 'spanish', 'bengali', 'tamil', 'telugu', 'brazilian']

clearList(): void

Remove all loaded languages and dynamic words (start with a clean filter).

profanity.clearList();
profanity.check('f**k'); // false
profanity.loadLanguage('english');
profanity.check('f**k'); // true

getConfig(): Partial<BeKindOptions>

Get the current configuration.

console.log(profanity.getConfig());
/*
{
  defaultPlaceholder: '*',
  enableLeetSpeak: true,
  caseSensitive: false,
  strictMode: false,
  detectPartialWords: false,
  languages: [...],
  whitelistWords: [...]
}
*/

Configuration File Structure

BeKind supports JSON-based configuration for easy setup and deployment. The config file structure supports all algorithm and detection options.

Full Configuration Schema

{
  "algorithm": {
    "matching": "trie" | "aho-corasick" | "hybrid",  // Algorithm selection
    "useAhoCorasick": boolean,                        // Enable Aho-Corasick
    "useBloomFilter": boolean                         // Enable Bloom Filter
  },
  "bloomFilter": {
    "enabled": boolean,                               // Enable/disable
    "expectedItems": number,                          // Expected dictionary size (default: 10000)
    "falsePositiveRate": number                       // Acceptable false positive rate (default: 0.01)
  },
  "ahoCorasick": {
    "enabled": boolean,                               // Enable/disable
    "prebuild": boolean                               // Prebuild automaton (default: true)
  },
  "profanityDetection": {
    "enableLeetSpeak": boolean,                       // Detect l33t speak (default: true)
    "caseSensitive": boolean,                         // Case sensitive matching (default: false)
    "strictMode": boolean,                            // Require word boundaries (default: false)
    "detectPartialWords": boolean,                    // Detect within words (default: false)
    "defaultPlaceholder": string                      // Default censoring character (default: "*")
  },
  "performance": {
    "enableCaching": boolean,                         // Enable result cache (default: false)
    "cacheSize": number                               // Cache size limit (default: 1000)
  }
}

Pre-configured Templates

High Performance (Large Texts)

{
  "algorithm": { "matching": "aho-corasick" },
  "ahoCorasick": { "enabled": true, "prebuild": true },
  "profanityDetection": { "enableLeetSpeak": true }
}

Balanced (Production)

{
  "algorithm": {
    "matching": "hybrid",
    "useAhoCorasick": true,
    "useBloomFilter": true
  },
  "profanityDetection": { "enableLeetSpeak": true },
  "performance": { "enableCaching": true, "cacheSize": 1000 }
}

Using Config Files

Step 1: Generate Config Files

# Run this in your project directory
npx bekindprofanityfilter

# Output:
# ✅ BeKind configuration files created!
#
# Created files:
#   📄 bekindprofanityfilter.config.json - Main configuration
#   📄 config.schema.json - JSON schema for IDE autocomplete

Step 2: Load Config in Your Code

// ES Modules / TypeScript
import { BeKind } from 'bekindprofanityfilter';
import config from './bekindprofanityfilter.config.json';

const filter = BeKind.fromConfig(config);
// CommonJS (Node.js)
const { BeKind } = require('bekindprofanityfilter');
const config = require('./bekindprofanityfilter.config.json');

const filter = BeKind.fromConfig(config);

Step 3: Customize Config

Edit bekindprofanityfilter.config.json to enable/disable features. Your IDE will provide autocomplete thanks to the JSON schema!


Cross-Language Innocence Scoring

Many words are profane in one language but perfectly innocent in another. For example, "got" means "buttocks" in Turkish but is an extremely common English word, "fart" means "speed" in Scandinavian languages, and "bite" is a common English word that's vulgar in French. BeKind handles these cross-language collisions automatically using a multi-layer language detection and scoring system.

Language Detection Architecture

BeKind uses a hybrid language detection system with three layers:

1. ELD N-gram Detection (eld/small) We integrate Nito-ELD, a corpus-trained byte-level n-gram language detector supporting 60+ languages. ELD analyzes character sequences (trigrams) and compares them against frequency profiles trained on massive corpora. It provides both per-word scores and full-text Bayesian priors.

Limitation: ELD works on UTF-8 byte patterns, so it struggles with accent-stripped text and frequently confuses closely related languages (Swedish ↔ German, Norwegian ↔ Danish). This is why we don't rely on ELD alone.

2. Trie Vocabulary Detection (18 languages) Per-language tries built from ~200-350 common words each. When a word is looked up, the trie returns a match score (0-1) indicating how strongly the word belongs to that language. Supports accent-tolerant matching (e.g., "gurultu" matches Turkish "gürültü" with a small penalty).

3. Script Detection Unicode codepoint ranges map characters directly to language families (e.g., Cyrillic → Russian, Devanagari → Hindi). This is deterministic and instant, providing strong signal for non-Latin scripts.

The scoreWord() Function

For each word, scoreWord() combines all three layers into a single Record<string, number> mapping language codes to confidence scores:

scoreWord("got") → { en: 0.9, tr: 0.7, de: 0.2, ... }
                      ↑ English trie match (extremely common word)
                           ↑ Turkish trie match (profane in Turkish)
                                ↑ German ELD n-gram signal

Layer weights: Script (1.0) > Trie (0.8) > ELD (0.6) > Suffix (0.3+) > Prefix (0.3+)

The detectLanguages() Function

For full text, detectLanguages() runs scoreWord() on every word and aggregates results into document-level proportions:

detectLanguages("We got the tickets and went to the show")
// → { languages: [{ language: "en", proportion: 0.9 }, { language: "tr", proportion: 0.1 }, ...] }

Note: ELD often classifies Swedish as German due to n-gram similarity. The confusion map (see below) compensates for this.

Two-Layer Signal Combination

When a collision word is detected, we combine word-level and document-level signals using a 1.5:1 weighted average favoring the document signal:

amplified[lang] = (scoreWord[lang] × 1.0 + docSignal[lang] × 1.5) / 2.5

The document signal is favored because it provides broader context — a single word's language score can be ambiguous, but the surrounding text usually makes the language clear.

The Confusion Map

ELD's n-gram model frequently misclassifies Scandinavian languages as German (they share many character patterns). The confusion map treats German signal as partial evidence of Scandinavian:

effectiveAmp["sv"] = max(directAmp["sv"], confusedAmp["de"] × 0.8)

The 0.8 discount prevents over-attribution — German text shouldn't fully count as Swedish, but mostly-German signal in a Scandinavian context should still trigger dampening.

Certainty Adjustment Formula

Once we have the amplified language signals, adjustCertaintyForLanguage() adjusts the word's certainty score:

If innocent language dominates (innocentAmp > profaneAmp):
  adjusted = certainty × (1 - dampeningFactor × innocentAmp)    ← reduces certainty

If profane language dominates (profaneAmp > innocentAmp):
  adjusted = certainty × (1 + dampeningFactor × profaneAmp)     ← increases certainty

Result clamped to [0, 5]

The dampeningFactor (0-1) controls how aggressively the adjustment works per collision word. Words that are genuinely innocent in another language (e.g., "got" in English, df=0.95) get heavy dampening, while dangerous dual-meaning words (e.g., "cock" as rooster, df=0.1) barely adjust.

End-to-End Flow

Text: "All proceeds go to the local food bank"
                      ^^^^^^
                      "go t" bridged → "got" detected (tr: s:4 c:4)

  1. Collision word matched → check innocent-words map
     "got" → innocent in English (meaning: "past tense of get", dampeningFactor: 0.95)

  2. Language detection triggered (lazy — only runs on collision matches)
     Document signal: detectLanguages() → { en: 0.9, tr: 0.05, ... }
     Word signal:     scoreWord("got")  → { en: 0.9, tr: 0.7, ... }

  3. Weighted average (1.5:1 doc:word ratio)
     amplified["en"] = (0.9 × 1.0 + 0.9 × 1.5) / 2.5 = 0.90
     amplified["tr"] = (0.7 × 1.0 + 0.05 × 1.5) / 2.5 = 0.31

  4. Innocent language (en: 0.90) > Profane language (tr: 0.31)?
     → Yes, English signal dominates
     → Certainty dampened: 4 × (1 - 0.95 × 0.90) = 0.58
     → Below flag threshold (s:4 needs c:2+) → NOT FLAGGED ✓

Key Features

  • 29 collision words mapped across 7 languages (English, Swedish, Norwegian, Danish, German, Dutch, French, Spanish)
  • Per-word dampening factors control adjustment strength:
    • 0.95 = heavy dampening (genuinely innocent cross-language, e.g., "got" in English)
    • 0.1 = barely dampens (almost always used as profanity, e.g., "cock" in English)
  • Lazy language detectiondetectLanguages() only runs when a collision word is matched (zero performance cost for non-collision text)
  • Confusion map — handles ELD n-gram detector's known misclassifications (e.g., Swedish often classified as German)
  • Swedish trie vocabulary — ~350 common words for reliable word-level Swedish detection

Collision Words

| Word | Profane In | Innocent In | Meaning | |------|-----------|-------------|---------| | got | Turkish | English | past tense of "get" (df: 0.95) | | slut | English | Swedish, Danish | end/finish | | fart | English | Swedish, Norwegian, Danish | speed | | hell | English | Swedish, Norwegian | luck | | prick | English | Swedish | dot/point | | kock | English | Swedish | chef/cook | | bra | English | Swedish | good | | bite | French | English | to use teeth | | con | French | English, Spanish | prefix/with | | pet | French | English | animal companion | | mist | Dutch/German | English | fog/haze | | hoe | English | Dutch | how | | kant | Dutch | German | edge | | ass | English | English | donkey (df: 0.15) | | cock | English | English | rooster (df: 0.1) |

Full list in src/languages/innocent-words.ts

Same-Language Collisions

Words like "ass" (donkey) and "cock" (rooster) are both profane and innocent in English. Since the profane and innocent language signals are equal, the system cannot disambiguate — these always remain flagged. This is a known limitation that would require semantic context analysis to solve.

Tested Scenarios

The challenge test suite (tests/challenge-tests.test.ts) documents known limitations and unsolved edge cases:

| Category | Tests | Status | Description | |----------|-------|--------|-------------| | Semantic analysis | 4 | skipped | "slut" in Swedish, "ass" as donkey, "cock" as rooster, "git" as VCS tool | | Dual-meaning words (innocent) | 10 | skipped | tinker, edibles, missionary, groomer, puttanesca, 8ball, catfish, knob, redskins, crime statistics | | Dual-meaning words (hateful) | 4 | skipped | Same words used as slurs/dog whistles | | Short common words (innocent) | 18 | skipped | ken, nom, gay, dom, eta, tat, goo, mut, bur, ano, wea, mae, pos, hag, div, bra, bal, gu | | Short common words (profane) | 18 | skipped | Same words in their profane language contexts | | Embedded profanity | 1 | skipped | "urASSHOLEbro" — concatenated evasion | | Digit leet-speak | 4 | passing | "6006s", "b006s", "4ss", "d1ld0" |

Dual-meaning words like "groomer" (dog grooming vs anti-LGBTQ+ slur), "missionary" (religious work vs sexual position), and "edibles" (food vs cannabis) are commented out of the dictionary to prevent false positives on community platforms. The challenge tests document both the innocent and hateful usage patterns — solving these requires context-aware detection that can distinguish legitimate uses from slurs/dog whistles.

Short common words (≤3 chars) collide across languages (e.g., "ken" is French verlan for a slur but also a common English name). These need language-aware context detection before re-enabling.


Severity Levels

Severity reflects the number and variety of detected profanities:

| Level | Enum Value | Description | |-----------|------------|-----------------------------------------| | MILD | 1 | 1 unique/total word | | MODERATE | 2 | 2 unique or total words | | SEVERE | 3 | 3 unique/total words | | EXTREME | 4 | 4+ unique or 5+ total profane words |


Language Support

  • Profanity Dictionaries (15): English, Hindi, French, German, Spanish, Italian, Brazilian Portuguese, Russian, Arabic, Chinese, Japanese, Korean, Bengali, Tamil, Telugu
  • Language Detection Trie (18): All 15 above + Dutch, Turkish, Swedish (used for innocence scoring, not profanity detection)
  • Cross-Language Innocence Scoring: English, Swedish, Norwegian, Danish, German, Dutch, French, Spanish
  • Scripts: Latin/Roman, Devanagari, Tamil, Telugu, Bengali, Cyrillic, Arabic, CJK, etc.
  • Mixed Content: Handles mixed-language and code-switched sentences with language-aware scoring.
profanity.check('This is b*llsh*t and चूतिया.'); // true (mixed English/Hindi)
profanity.check('Ce mot est merde and पागल.');   // true (French/Hindi)
profanity.check('Isso é uma merda.');             // true (Brazilian Portuguese)

Use Exported Wordlists

For sample words in a language (for UIs, admin, etc):

import { englishBadWords, hindiBadWords } from 'bekindprofanityfilter';
console.log(englishBadWords.slice(0, 5)); // ["f**k", "sh*t", ...]

Security

  • No wordlist exposure: There is no .list() function for security and encapsulation. Use exported word arrays for samples.
  • TRIE-based: Scales easily to 50,000+ words.
  • Handles leet-speak: Catches obfuscated variants like f#ck, a55h*le.

Full Example

import profanity, { ProfanitySeverity } from 'bekindprofanityfilter';


// Multi-language detection
profanity.loadLanguages(['english', 'french', 'tamil']);
console.log(profanity.check('Ce mot est merde.')); // true

// Leet-speak detection
console.log(profanity.check('You a f#cking a55hole!')); // true

// Whitelisting
profanity.addToWhitelist(['anal', 'ass']);
console.log(profanity.check('He is an associate professor.')); // false

// Severity
const result = profanity.detect('This is f**king b*llsh*t and chutiya.');
console.log(ProfanitySeverity[result.severity]); // "SEVERE"

// Custom dictionary
profanity.loadCustomDictionary('pirate', ['barnacle-head', 'landlubber']);
profanity.loadLanguage('pirate');
console.log(profanity.check('You barnacle-head!')); // true

// Placeholder configuration
profanity.setPlaceholder('#');
console.log(profanity.clean('This is b*llsh*t.')); // "This is ########."
profanity.setPlaceholder('*'); // Reset

FAQ

Q: How do I see all loaded profanities?
A: For security, the internal word list is not exposed. Use englishBadWords etc. for samples.

Q: How do I reset the filter?
A: Use clearList() and reload languages/dictionaries.

Q: Is this safe for browser and Node.js?
A: Yes! BeKind is universal.


Middleware Examples

Looking for Express.js/Node.js middleware to use BeKind in your API or chat app?
Check the examples/ folder for ready-to-copy middleware and integration samples.


Roadmap

  • ✅ Cross-language innocence scoring (collision word disambiguation)
  • ✅ Multi-language detection trie (18 languages)
  • ✅ Language confusion map for Scandinavian/Germanic disambiguation
  • ✅ Additional language packs (Arabic, Russian, Japanese, Korean, Chinese, Dutch)
  • ✅ Romanization detection (Hinglish and other transliterated scripts)
  • ✅ False positive reduction for dual-meaning words (groomer, missionary, edibles, etc.)
  • ✅ Digit leet-speak detection (6006s, b006s, 4ss, d1ld0)
  • 🚧 Context-aware dual-meaning word disambiguation (distinguish "dog groomer" from "groomer" as slur)
  • 🚧 Norwegian and Danish trie vocabularies (currently covered via confusion map)
  • 🚧 Repeat character compression (normalize elongated words before matching, avoiding the need to enumerate elongations in the dictionary)
  • 🚧 Phonetic matching (sounds-like detection)
  • 🚧 Plugin system for custom detection algorithms

License

MIT — See LICENSE

This project is a fork of AllProfanity by Ayush Jadaun, also licensed under MIT.


Contributing

We welcome contributions! Please see our CONTRIBUTORS.md for:

  • How to add your name to our contributors list
  • Guidelines for adding new languages
  • Test requirements (must include passing test screenshots in PRs)
  • Code of conduct and PR guidelines