Full Code of Blaspsoft/blasp for AI

main e0a2ea52cbe7 cached
84 files
367.1 KB
98.4k tokens
551 symbols
1 requests
Download .txt
Showing preview only (393K chars total). Download the full file or copy to clipboard to get everything.
Repository: Blaspsoft/blasp
Branch: main
Commit: e0a2ea52cbe7
Files: 84
Total size: 367.1 KB

Directory structure:
gitextract_qpmp_x7w/

├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .styleci.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── config/
│   ├── blasp.php
│   └── languages/
│       ├── english.php
│       ├── french.php
│       ├── german.php
│       └── spanish.php
├── phpunit.xml
├── src/
│   ├── BlaspManager.php
│   ├── BlaspServiceProvider.php
│   ├── Blaspable.php
│   ├── Console/
│   │   ├── ClearCommand.php
│   │   ├── LanguagesCommand.php
│   │   └── TestCommand.php
│   ├── Core/
│   │   ├── Analyzer.php
│   │   ├── Contracts/
│   │   │   ├── DriverInterface.php
│   │   │   └── MaskStrategyInterface.php
│   │   ├── Dictionary.php
│   │   ├── Masking/
│   │   │   ├── CallbackMask.php
│   │   │   ├── CharacterMask.php
│   │   │   └── GrawlixMask.php
│   │   ├── MatchedWord.php
│   │   ├── Matchers/
│   │   │   ├── CompoundWordDetector.php
│   │   │   ├── FalsePositiveFilter.php
│   │   │   ├── PhoneticMatcher.php
│   │   │   └── RegexMatcher.php
│   │   ├── Normalizers/
│   │   │   ├── EnglishNormalizer.php
│   │   │   ├── FrenchNormalizer.php
│   │   │   ├── GermanNormalizer.php
│   │   │   ├── NullNormalizer.php
│   │   │   ├── SpanishNormalizer.php
│   │   │   └── StringNormalizer.php
│   │   ├── Result.php
│   │   └── Score.php
│   ├── Drivers/
│   │   ├── PatternDriver.php
│   │   ├── PhoneticDriver.php
│   │   ├── PipelineDriver.php
│   │   └── RegexDriver.php
│   ├── Enums/
│   │   └── Severity.php
│   ├── Events/
│   │   ├── ContentBlocked.php
│   │   ├── ModelProfanityDetected.php
│   │   └── ProfanityDetected.php
│   ├── Exceptions/
│   │   └── ProfanityRejectedException.php
│   ├── Facades/
│   │   └── Blasp.php
│   ├── Middleware/
│   │   └── CheckProfanity.php
│   ├── PendingCheck.php
│   ├── Rules/
│   │   └── Profanity.php
│   └── Testing/
│       └── BlaspFake.php
└── tests/
    ├── AllLanguagesApiTest.php
    ├── AllLanguagesDetectionTest.php
    ├── BladeDirectiveTest.php
    ├── BlaspCheckTest.php
    ├── BlaspCheckValidationTest.php
    ├── BlaspableTest.php
    ├── BypassVulnerabilityTest.php
    ├── CacheDriverConfigurationTest.php
    ├── ConfigurationLoaderLanguageTest.php
    ├── ConfigurationLoaderTest.php
    ├── CustomMaskCharacterTest.php
    ├── DetectionStrategyRegistryTest.php
    ├── EdgeCaseTest.php
    ├── EmptyInputTest.php
    ├── FrenchStringNormalizerTest.php
    ├── GermanStringNormalizerTest.php
    ├── Issue24Test.php
    ├── Issue32FalsePositiveTest.php
    ├── MiddlewareAliasTest.php
    ├── MultiLanguageDetectionConfigTest.php
    ├── MultiLanguageProfanityTest.php
    ├── PhoneticDriverTest.php
    ├── PipelineDriverTest.php
    ├── ProfanityExpressionGeneratorTest.php
    ├── ResultCachingTest.php
    ├── SeverityMapTest.php
    ├── SpanishStringNormalizerTest.php
    ├── StrMacroTest.php
    ├── TestCase.php
    └── UuidFalsePositiveTest.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/main.yml
================================================
name: Run Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      # Step 1: Check out the repository
      - name: Checkout code
        uses: actions/checkout@v4

      # Step 2: Set up PHP
      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: "8.2" # Set your desired PHP version
          extensions: mbstring, dom, zip

      # Step 3: Install Composer dependencies
      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist

      # Step 4: Run PHPUnit tests
      - name: Run tests
        run: php ./vendor/bin/phpunit


================================================
FILE: .gitignore
================================================
/vendor
composer.lock
.phpunit.result.cache
/.idea

================================================
FILE: .styleci.yml
================================================
preset: laravel

disabled:
  - single_class_element_per_statement


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to `blasp` will be documented in this file

## 3.0.0 - 2025-01-05

### Added
- Custom mask character support with `maskWith()` method
- Simplified API with Laravel facade pattern and method chaining
- Comprehensive multi-language support (Spanish, German, French)
- Expanded test coverage across all languages
- Comprehensive extensibility system with full test coverage
- Basic registry pattern for language normalizers
- Language files publishing to ServiceProvider
- Comprehensive documentation for maskWith() and all chainable methods

### Changed
- Implemented dependency injection and simplified service dependencies
- Extracted expression generation logic to dedicated generator
- Improved substitution detection across all languages
- Updated README with simplified chainable API documentation
- Updated README with comprehensive multi-language support documentation
- Updated README with language files publishing options
- Updated README for v3.0 features

### Fixed
- Resolved language switching not loading correct profanities
- Prevented cross-word-boundary profanity matches

### Removed
- Strategy factory, plugin manager, and default detection strategy
- Domain-specific detection strategies (email, URL, phone)
- Unused strict() and lenient() detection modes
- README duplications and outdated references

## 1.0.0 - 201X-XX-XX

- initial release


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Contributions are **welcome** and will be fully **credited**.

Please read and understand the contribution guide before creating an issue or pull request.

## Etiquette

This project is open source, and as such, the maintainers give their free time to build and maintain the source code
held within. They make the code freely available in the hope that it will be of use to other developers. It would be
extremely unfair for them to suffer abuse or anger for their hard work.

Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
world that developers are civilized and selfless people.

It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.

## Viability

When requesting or submitting new features, first consider whether it might be useful to others. Open
source projects are used by many developers, who may have entirely different needs to your own. Think about
whether or not your feature is likely to be used by other users of the project.

## Procedure

Before filing an issue:

- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
- Check to make sure your feature suggestion isn't already present within the project.
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
- Check the pull requests tab to ensure that the feature isn't already in progress.

Before submitting a pull request:

- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.

## Requirements

If the project maintainer has any additional requirements, you will find them listed here.

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.

**Happy coding**!


================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) Michael Deeming

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
<p align="center">
    <img src="./assets/icon.png" alt="Blasp Icon" width="150" height="150"/>
</p>

> **Official API Available!** This package powers [blasp.app](https://blasp.app/) - a universal profanity filtering REST API that works with any language. Free tier with 1,000 requests/month, multi-language support, and custom word lists.

<p align="center">
    <a href="https://github.com/Blaspsoft/blasp/actions/workflows/main.yml"><img alt="GitHub Workflow Status (main)" src="https://github.com/Blaspsoft/blasp/actions/workflows/main.yml/badge.svg"></a>
    <a href="https://packagist.org/packages/blaspsoft/blasp"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/blaspsoft/blasp"></a>
    <a href="https://packagist.org/packages/blaspsoft/blasp"><img alt="Latest Version" src="https://img.shields.io/packagist/v/blaspsoft/blasp"></a>
    <a href="https://packagist.org/packages/blaspsoft/blasp"><img alt="License" src="https://img.shields.io/packagist/l/blaspsoft/blasp"></a>
</p>

# Blasp - Advanced Profanity Filter for Laravel

Blasp is a powerful, extensible profanity filter for Laravel. Version 4 is a ground-up rewrite with a driver-based architecture, severity scoring, masking strategies, Eloquent model integration, and a clean fluent API.

## Features

- **Driver Architecture** — `regex` (detects obfuscation, substitutions, separators), `pattern` (fast exact matching), `phonetic` (catches sound-alike evasions), or `pipeline` (chains multiple drivers together). Extend with custom drivers.
- **Multi-Language** — English, Spanish, German, French with language-specific normalizers. Check one, many, or all at once.
- **Severity Scoring** — Words categorised as mild/moderate/high/extreme. Filter by minimum severity and get a 0-100 score.
- **Masking Strategies** — Character mask (`*`, `#`), grawlix (`!@#$%`), or a custom callback.
- **Eloquent Integration** — `Blaspable` trait auto-sanitizes or rejects profanity on model save.
- **Middleware** — Reject or sanitize profane request fields with configurable severity.
- **Validation Rules** — Fluent validation rule with language, severity, and score threshold support.
- **Testing Utilities** — `Blasp::fake()` for test doubles with assertions.
- **Events** — `ProfanityDetected`, `ContentBlocked`, and `ModelProfanityDetected`.

## Requirements

- PHP 8.2+
- Laravel 8.0+

## Installation

```bash
composer require blaspsoft/blasp
```

Publish configuration:

```bash
# Everything (config + language files)
php artisan vendor:publish --tag="blasp"

# Config only
php artisan vendor:publish --tag="blasp-config"

# Language files only
php artisan vendor:publish --tag="blasp-languages"
```

## Quick Start

```php
use Blaspsoft\Blasp\Facades\Blasp;

$result = Blasp::check('This is a fucking sentence');

$result->isOffensive();  // true
$result->clean();         // "This is a ******* sentence"
$result->original();      // "This is a fucking sentence"
$result->score();         // 30
$result->count();         // 1
$result->uniqueWords();   // ['fucking']
$result->severity();      // Severity::High
```

## Fluent API

All builder methods return a `PendingCheck` and can be chained:

```php
// Language selection
Blasp::in('spanish')->check($text);
Blasp::in('english', 'french')->check($text);
Blasp::inAllLanguages()->check($text);

// Language shortcuts
Blasp::english()->check($text);
Blasp::spanish()->check($text);
Blasp::german()->check($text);
Blasp::french()->check($text);

// Driver selection
Blasp::driver('regex')->check($text);     // Full obfuscation detection (default)
Blasp::driver('pattern')->check($text);   // Fast exact matching
Blasp::driver('phonetic')->check($text);  // Sound-alike detection (e.g. "phuck", "sheit")
Blasp::driver('pipeline')->check($text);  // Chain multiple drivers (config-based)

// Ad-hoc pipeline — chain any drivers without config
Blasp::pipeline('regex', 'phonetic')->check($text);
Blasp::pipeline('pattern', 'phonetic')->in('english')->mask('#')->check($text);

// Shorthand modes
Blasp::strict()->check($text);   // Forces regex driver
Blasp::lenient()->check($text);  // Forces pattern driver

// Masking
Blasp::mask('*')->check($text);        // Character mask (default)
Blasp::mask('#')->check($text);        // Custom character
Blasp::mask('grawlix')->check($text);  // !@#$% cycling
Blasp::mask(fn($word, $len) => '[CENSORED]')->check($text);  // Callback

// Severity filtering
use Blaspsoft\Blasp\Enums\Severity;
Blasp::withSeverity(Severity::High)->check($text);  // Ignores mild/moderate

// Allow/block lists (merged with config)
Blasp::allow('damn', 'hell')->check($text);
Blasp::block('customword')->check($text);

// Chain everything
Blasp::spanish()
    ->mask('#')
    ->withSeverity(Severity::Moderate)
    ->check($text);

// Batch checking
$results = Blasp::checkMany(['text one', 'text two']);
```

## Result Object

The `Result` object is returned by every `check()` call:

| Method | Returns | Description |
|--------|---------|-------------|
| `isOffensive()` | `bool` | Text contains profanity |
| `isClean()` | `bool` | Text is clean |
| `clean()` | `string` | Text with profanities masked |
| `original()` | `string` | Original unmodified text |
| `score()` | `int` | Severity score (0-100) |
| `count()` | `int` | Total profanity matches |
| `uniqueWords()` | `array` | Unique base words detected |
| `severity()` | `?Severity` | Highest severity in matches |
| `words()` | `Collection` | `MatchedWord` objects with position, length, severity |
| `toArray()` | `array` | Full result as array |
| `toJson()` | `string` | Full result as JSON |

`Result` implements `JsonSerializable`, `Stringable` (returns clean text), and `Countable`.

## Detection Types

The regex driver detects obfuscated profanity:

| Type | Example | Detected As |
|------|---------|-------------|
| Straight match | `fucking` | `fucking` |
| Substitution | `fÛck!ng`, `f4ck` | `fucking`, `fuck` |
| Separators | `f-u-c-k-i-n-g`, `f@ck` | `fucking`, `fuck` |
| Doubled | `ffuucckkiinngg` | `fucking` |
| Combination | `f-uuck!ng` | `fucking` |

> **Separator limit:** The regex driver allows up to 3 separator characters between each letter (e.g., `f--u--c--k`). This covers all realistic obfuscation patterns while keeping regex complexity low enough for PHP-FPM environments.

The pattern driver only detects straight word-boundary matches.

The phonetic driver uses `metaphone()` + Levenshtein distance to catch words that *sound like* profanity but are spelled differently:

| Type | Example | Detected As |
|------|---------|-------------|
| Phonetic spelling | `phuck` | `fuck` |
| Shortened form | `fuk` | `fuck` |
| Sound-alike | `sheit` | `shit` |

Configure sensitivity in `config/blasp.php` under `drivers.phonetic`. A curated false-positive list prevents common words like "fork", "duck", and "beach" from being flagged.

### Pipeline Driver

The pipeline driver chains multiple drivers together so a single `check()` call runs all of them. It uses **union merge** semantics — text is flagged if **any** driver finds a match.

```php
// Config-based: set 'default' => 'pipeline' or use driver('pipeline')
Blasp::driver('pipeline')->check('phuck this sh1t');

// Ad-hoc: pick drivers on the fly (no config needed)
Blasp::pipeline('regex', 'phonetic')->check('phuck this sh1t');
Blasp::pipeline('regex', 'pattern', 'phonetic')->check($text);
```

When multiple drivers detect the same word at the same position, duplicates are removed — only the longest match is kept. Masks are applied from the merged result, and the score is recalculated across all matches.

Configure the default sub-drivers in `config/blasp.php`:

```php
'drivers' => [
    'pipeline' => [
        'drivers' => ['regex', 'phonetic'],  // Drivers to chain
    ],
],
```

## Eloquent Integration

The `Blaspable` trait automatically checks model attributes during save:

```php
use Blaspsoft\Blasp\Blaspable;

class Comment extends Model
{
    use Blaspable;

    protected array $blaspable = ['body', 'title'];
}
```

```php
// Sanitize mode (default) — profanity is masked, model saves
$comment = Comment::create(['body' => 'This is fucking great']);
$comment->body; // "This is ******* great"

// Check what happened
$comment->hadProfanity();            // true
$comment->blaspResults();            // ['body' => Result, 'title' => Result]
$comment->blaspResult('body');       // Result instance
```

### Per-Model Overrides

```php
class Comment extends Model
{
    use Blaspable;

    protected array $blaspable = ['body', 'title'];
    protected string $blaspMode = 'reject';     // 'sanitize' (default) | 'reject'
    protected string $blaspLanguage = 'spanish'; // null = config default
    protected string $blaspMask = '#';           // null = config default
}
```

### Reject Mode

In reject mode, saving a model with profanity throws `ProfanityRejectedException` and the model is not persisted:

```php
use Blaspsoft\Blasp\Exceptions\ProfanityRejectedException;

try {
    $comment = Comment::create(['body' => 'profane text']);
} catch (ProfanityRejectedException $e) {
    $e->attribute; // 'body'
    $e->result;    // Result instance
    $e->model;     // The unsaved model
}
```

### Disabling Checking

```php
Comment::withoutBlaspChecking(function () {
    Comment::create(['body' => 'unchecked content']);
});
```

### Events

A `ModelProfanityDetected` event fires whenever profanity is detected on a model attribute (both sanitize and reject modes):

```php
use Blaspsoft\Blasp\Events\ModelProfanityDetected;

Event::listen(ModelProfanityDetected::class, function ($event) {
    $event->model;     // The model instance
    $event->attribute; // Which attribute had profanity
    $event->result;    // Result instance
});
```

## Middleware

Use `CheckProfanity` to filter incoming request fields. A `blasp` middleware alias is registered automatically:

```php
// Using the short alias (recommended)
Route::post('/comment', CommentController::class)
    ->middleware('blasp');

// With parameters: action, severity
Route::post('/comment', CommentController::class)
    ->middleware('blasp:sanitize,mild');

// Or using the class directly
use Blaspsoft\Blasp\Middleware\CheckProfanity;

Route::post('/comment', CommentController::class)
    ->middleware(CheckProfanity::class);
```

| Action | Behaviour |
|--------|-----------|
| `reject` (default) | Returns 422 JSON with field errors |
| `sanitize` | Replaces profane fields in the request and continues |

Configure which fields to check in `config/blasp.php`:

```php
'middleware' => [
    'action' => 'reject',
    'fields' => ['*'],                            // '*' = all fields
    'except' => ['password', 'email', '_token'],  // Always skipped
    'severity' => 'mild',
],
```

## Validation Rules

### String Rule

```php
$request->validate([
    'comment' => ['required', 'blasp_check'],
    'bio'     => ['required', 'blasp_check:spanish'],
]);
```

### Fluent Rule Object

```php
use Blaspsoft\Blasp\Rules\Profanity;
use Blaspsoft\Blasp\Enums\Severity;

$request->validate([
    'comment' => ['required', Profanity::in('english')],
    'bio'     => ['required', Profanity::severity(Severity::High)],
    'tagline' => ['required', Profanity::maxScore(50)],
]);
```

## Blade Directive

The `@clean` directive sanitizes and escapes text for safe display in views:

```blade
<p>@clean($comment->body)</p>

{{-- Equivalent to: {{ app('blasp')->check($comment->body)->clean() }} --}}
```

Output is HTML-escaped via `e()` for XSS safety.

## Str / Stringable Macros

Blasp registers macros on Laravel's `Str` and `Stringable` classes:

```php
use Illuminate\Support\Str;

// Static methods
Str::isProfane('fuck this');        // true
Str::isProfane('hello');            // false
Str::cleanProfanity('fuck this');   // '**** this'
Str::cleanProfanity('hello');       // 'hello'

// Fluent Stringable methods
Str::of('fuck this')->isProfane();          // true
Str::of('fuck this')->cleanProfanity();     // Stringable('**** this')
Str::of('hello')->cleanProfanity()->upper(); // 'HELLO' (chaining works)
```

## Configuration

Full `config/blasp.php` reference:

```php
return [
    'default'   => env('BLASP_DRIVER', 'regex'),       // 'regex' | 'pattern' | 'phonetic' | 'pipeline'
    'language'  => env('BLASP_LANGUAGE', 'english'),    // Default language
    'mask'      => '*',                                 // Default mask character
    'severity'  => 'mild',                              // Minimum severity
    'events'    => false,                               // Fire ProfanityDetected events

    'cache' => [
        'enabled' => true,
        'driver'  => env('BLASP_CACHE_DRIVER'),
        'ttl'     => 86400,
        'results' => true,          // Cache check() results by content hash
    ],

    'middleware' => [
        'action'   => 'reject',
        'fields'   => ['*'],
        'except'   => ['password', 'email', '_token'],
        'severity' => 'mild',
    ],

    'model' => [
        'mode' => env('BLASP_MODEL_MODE', 'sanitize'),  // 'sanitize' | 'reject'
    ],

    'drivers' => [
        'pipeline' => [
            'drivers' => ['regex', 'phonetic'],    // Sub-drivers to chain
        ],
        'phonetic' => [
            'phonemes' => 4,                       // metaphone code length (2-8)
            'min_word_length' => 3,                // skip short words
            'max_distance_ratio' => 0.6,           // levenshtein threshold (0.3-0.8)
            'supported_languages' => ['english'],  // metaphone is English-oriented
            'false_positives' => ['fork', '...'],  // never flag these words
        ],
    ],

    'allow'  => [],    // Global allow-list
    'block'  => [],    // Global block-list

    'separators'      => [...],  // Characters treated as separators
    'substitutions'   => [...],  // Character leet-speak mappings
    'false_positives' => [...],  // Words that should never be flagged
];
```

## Custom Drivers

Implement `DriverInterface` and register with the manager:

```php
use Blaspsoft\Blasp\Core\Contracts\DriverInterface;
use Blaspsoft\Blasp\Core\Result;
use Blaspsoft\Blasp\Core\Dictionary;
use Blaspsoft\Blasp\Core\Contracts\MaskStrategyInterface;

class MyDriver implements DriverInterface
{
    public function detect(string $text, Dictionary $dictionary, MaskStrategyInterface $mask, array $options = []): Result
    {
        // Your detection logic
    }
}

// Register in a service provider
Blasp::extend('my-driver', fn($app) => new MyDriver());

// Use it
Blasp::driver('my-driver')->check($text);
```

## Caching

Blasp caches `check()` results by default. When the same text is checked with the same configuration (language, driver, severity, allow/block lists), the cached result is returned instantly.

```php
// First call — runs full analysis, caches result
$result = Blasp::check('some text');

// Second call — returns cached result
$result = Blasp::check('some text');
```

Configure caching in `config/blasp.php`:

```php
'cache' => [
    'enabled' => true,                      // Master switch for all caching
    'driver'  => env('BLASP_CACHE_DRIVER'), // null = default cache driver
    'ttl'     => 86400,                     // Cache lifetime in seconds
    'results' => true,                      // Cache check() results (disable independently)
],
```

Result caching is automatically bypassed when using a `CallbackMask` (closures can't be serialized). Clear both dictionary and result caches with:

```bash
php artisan blasp:clear
```

Or programmatically:

```php
Dictionary::clearCache();
```

## Artisan Commands

```bash
# Clear the profanity cache
php artisan blasp:clear

# Test text from the command line
php artisan blasp:test "some text to check" --lang=english --detail

# List available languages with word counts
php artisan blasp:languages
```

## Testing

### Faking

```php
use Blaspsoft\Blasp\Facades\Blasp;
use Blaspsoft\Blasp\Core\Result;

// Replace with a fake — all checks return clean by default
Blasp::fake();

// Pre-configure specific responses
Blasp::fake([
    'bad text'   => Result::withMatches(['fuck']),
    'clean text' => Result::none('clean text'),
]);

$result = Blasp::check('bad text');
$result->isOffensive(); // true

// Assertions
Blasp::assertChecked();
Blasp::assertCheckedTimes(1);
Blasp::assertCheckedWith('bad text');
```

### Disabling Filtering

```php
Blasp::withoutFiltering(function () {
    // All checks return clean results
});
```

## Events

Enable global events with `'events' => true` in config:

| Event | Fired When | Properties |
|-------|------------|------------|
| `ProfanityDetected` | `check()` finds profanity | `result`, `originalText` |
| `ContentBlocked` | Middleware detects profanity | `result`, `request`, `field`, `action` |
| `ModelProfanityDetected` | Blaspable trait detects profanity | `model`, `attribute`, `result` |

`ModelProfanityDetected` always fires (not gated by the `events` config).

## Migrating from v3

### Namespace Changes

| v3 | v4 |
|----|-----|
| `Blaspsoft\Blasp\Facades\Blasp` | `Blaspsoft\Blasp\Facades\Blasp` (unchanged) |
| `Blaspsoft\Blasp\ServiceProvider` | `Blaspsoft\Blasp\BlaspServiceProvider` |

The Laravel auto-discovery handles provider/alias registration automatically. The facade namespace is the same as v3, so no import changes are needed for the facade.

### Config Changes

| v3 Key | v4 Key | Notes |
|--------|--------|-------|
| `default_language` | `language` | `default_language` still works as alias |
| `mask_character` | `mask` | `mask_character` still works as alias |
| `cache_driver` | `cache.driver` | `cache_driver` still works as alias |
| — | `default` | New: driver selection (`regex`/`pattern`) |
| — | `severity` | New: minimum severity level |
| — | `events` | New: enable global events |
| — | `allow` / `block` | New: global allow/block lists |
| — | `middleware` | New: middleware configuration section |
| — | `model` | New: Blaspable trait configuration |

### Result API Changes

| v3 Method | v4 Method |
|-----------|-----------|
| `hasProfanity()` | `isOffensive()` |
| `getCleanString()` | `clean()` |
| `getSourceString()` | `original()` |
| `getProfanitiesCount()` | `count()` |
| `getUniqueProfanitiesFound()` | `uniqueWords()` |

All v3 methods still work as deprecated aliases.

### Builder API Changes

| v3 Method | v4 Method |
|-----------|-----------|
| `maskWith($char)` | `mask($char)` |
| `allLanguages()` | `inAllLanguages()` |
| `language($lang)` | `in($lang)` |
| `configure($profanities, $falsePositives)` | `block(...$words)` / `allow(...$words)` |

All v3 methods still work as deprecated aliases.

### New in v4

- **Driver architecture** — `regex` and `pattern` drivers, custom driver support
- **Severity system** — Mild/Moderate/High/Extreme levels with scoring
- **Masking strategies** — Grawlix and callback masking
- **Blaspable trait** — Automatic Eloquent model profanity checking
- **Middleware** — Request-level profanity filtering
- **Fluent validation rule** — `Profanity::in('spanish')->severity(Severity::High)`
- **Testing utilities** — `Blasp::fake()`, assertions, `withoutFiltering()`
- **Events** — `ProfanityDetected`, `ContentBlocked`, `ModelProfanityDetected`
- **Artisan commands** — `blasp:clear`, `blasp:test`, `blasp:languages`
- **Batch checking** — `Blasp::checkMany([...])`
- **Multi-language in one call** — `Blasp::in('english', 'spanish')->check($text)`

## Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for detailed version history.

## License

Blasp is open-sourced software licensed under the [MIT license](LICENSE).


================================================
FILE: composer.json
================================================
{
    "name": "blaspsoft/blasp",
    "description": "Blasp is a powerful and customisable profanity filter package for Laravel applications",
    "keywords": [
        "blaspsoft",
        "blasp"
    ],
    "homepage": "https://github.com/blaspsoft/blasp",
    "license": "MIT",
    "type": "library",
    "authors": [
        {
            "name": "Michael Deeming",
            "email": "michael.deeming90@gmail.com",
            "role": "Developer"
        }
    ],
    "require": {
        "php": "^8.2",
        "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0"
    },
    "require-dev": {
        "orchestra/testbench": "^10.0|^11.0",
        "phpunit/phpunit": "^11.0|^12.5.12"
    },
    "autoload": {
        "psr-4": {
            "Blaspsoft\\Blasp\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Blaspsoft\\Blasp\\Tests\\": "tests"
        }
    },
    "minimum-stability": "stable",
    "prefer-stable": true,
    "scripts": {
        "test": "vendor/bin/phpunit",
        "test-coverage": "vendor/bin/phpunit --coverage-html coverage"
    },
    "config": {
        "sort-packages": true
    },
    "extra": {
        "laravel": {
            "providers": [
                "Blaspsoft\\Blasp\\BlaspServiceProvider"
            ],
            "aliases": {
                "Blasp": "Blaspsoft\\Blasp\\Facades\\Blasp"
            }
        }
    }
}


================================================
FILE: config/blasp.php
================================================
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Default Driver
    |--------------------------------------------------------------------------
    |
    | The default detection driver. 'regex' provides full obfuscation
    | detection. 'pattern' is faster but only matches exact words.
    |
    */
    'default' => env('BLASP_DRIVER', 'regex'),

    /*
    |--------------------------------------------------------------------------
    | Default Language
    |--------------------------------------------------------------------------
    |
    | The default language to use for profanity detection.
    |
    */
    'language' => env('BLASP_LANGUAGE', 'english'),

    // Backward compat alias
    'default_language' => env('BLASP_LANGUAGE', 'english'),

    /*
    |--------------------------------------------------------------------------
    | Mask Character
    |--------------------------------------------------------------------------
    |
    | The character used to mask detected profanities.
    |
    */
    'mask' => '*',

    // Backward compat alias
    'mask_character' => '*',

    /*
    |--------------------------------------------------------------------------
    | Minimum Severity
    |--------------------------------------------------------------------------
    |
    | The minimum severity level to detect. Words below this severity
    | will be ignored. Options: mild, moderate, high, extreme
    |
    */
    'severity' => 'mild',

    /*
    |--------------------------------------------------------------------------
    | Events
    |--------------------------------------------------------------------------
    |
    | When enabled, ProfanityDetected events will be fired automatically
    | when profanity is found during a check.
    |
    */
    'events' => false,

    /*
    |--------------------------------------------------------------------------
    | Cache Configuration
    |--------------------------------------------------------------------------
    */
    'cache' => [
        'enabled' => true,
        'driver' => env('BLASP_CACHE_DRIVER'),
        'ttl' => 86400,
        'results' => true,
    ],

    // Backward compat alias
    'cache_driver' => env('BLASP_CACHE_DRIVER'),

    /*
    |--------------------------------------------------------------------------
    | Middleware Configuration
    |--------------------------------------------------------------------------
    */
    'middleware' => [
        'action' => 'reject',
        'fields' => ['*'],
        'except' => ['password', 'email', '_token'],
        'severity' => 'mild',
    ],

    /*
    |--------------------------------------------------------------------------
    | Model Configuration
    |--------------------------------------------------------------------------
    |
    | Controls how the Blaspable trait behaves on Eloquent models.
    | 'sanitize' replaces profanity with the mask character.
    | 'reject' throws a ProfanityRejectedException instead of saving.
    |
    */
    'model' => [
        'mode' => env('BLASP_MODEL_MODE', 'sanitize'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Driver-Specific Configuration
    |--------------------------------------------------------------------------
    */
    'drivers' => [
        'pipeline' => [
            'drivers' => ['regex', 'phonetic'],
        ],

        'phonetic' => [
            'phonemes' => 4,              // metaphone code length (2-8, lower=more aggressive)
            'min_word_length' => 3,        // skip words shorter than this
            'max_distance_ratio' => 0.6,   // levenshtein threshold (0.3-0.8, lower=stricter)
            'supported_languages' => ['english'],
            'false_positives' => [
                'fork', 'forked', 'forking',
                'beach', 'beaches',
                'witch', 'witches',
                'sheet', 'sheets',
                'deck', 'decks',
                'count', 'counts', 'counter', 'county',
                'ship', 'shipped', 'shipping',
                'duck', 'ducked', 'ducking',
                'fudge', 'fudging',
                'buck', 'bucks',
                'puck', 'pucks',
                'bass',
                'mass',
                'pass', 'passed',
                'heck',
                'shoot', 'shot',
                'what', 'white', 'while', 'whole',
            ],
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Character Separators
    |--------------------------------------------------------------------------
    */
    'separators' => [
        '@', '#', '%', '&', '_', ';', "'", '"', ',', '~', '`', '|',
        '!', '$', '^', '*', '(', ')', '-', '+', '=', '{', '}',
        '[', ']', ':', '<', '>', '?', '.', '/',
    ],

    /*
    |--------------------------------------------------------------------------
    | Character Substitutions
    |--------------------------------------------------------------------------
    */
    'substitutions' => [
        '/a/' => ['a', '4', '@', '*', 'Á', 'á', 'À', 'Â', 'à', 'Â', 'â', 'Ä', 'ä', 'Ã', 'ã', 'Å', 'å', 'æ', 'Æ', 'α', 'Δ', 'Λ', 'λ'],
        '/b/' => ['b', '8', '\\', '3', '*', 'ß', 'Β', 'β'],
        '/c/' => ['c', '*', 'Ç', 'ç', 'ć', 'Ć', 'č', 'Č', '¢', '€', '<', '(', '{', '©'],
        '/d/' => ['d', '*', '\\', ')', 'Þ', 'þ', 'Ð', 'ð'],
        '/e/' => ['e', '3', '*', '€', 'È', 'è', 'É', 'é', 'Ê', 'ê', 'ë', 'Ë', 'ē', 'Ē', 'ė', 'Ė', 'ę', 'Ę', '∑'],
        '/f/' => ['f', '*', 'ƒ'],
        '/g/' => ['g', '6', '9', '*'],
        '/h/' => ['h', '*', 'Η'],
        '/i/' => ['i', '!', '|', ']', '[', '1', '*', '∫', 'Ì', 'Í', 'Î', 'Ï', 'ì', 'í', 'î', 'ï', 'ī', 'Ī', 'į', 'Į'],
        '/j/' => ['j', '*'],
        '/k/' => ['k', '*', 'Κ', 'κ'],
        '/l/' => ['l', '!', '|', ']', '[', '*', '£', '∫', 'Ì', 'Í', 'Î', 'Ï', 'ł', 'Ł'],
        '/m/' => ['m', '*'],
        '/n/' => ['n', '*', 'η', 'Ν', 'Π', 'ñ', 'Ñ', 'ń', 'Ń'],
        '/o/' => ['o', '0', '*', 'Ο', 'ο', 'Φ', '¤', '°', 'ø', 'ô', 'Ô', 'ö', 'Ö', 'ò', 'Ò', 'ó', 'Ó', 'œ', 'Œ', 'ø', 'Ø', 'ō', 'Ō', 'õ', 'Õ'],
        '/p/' => ['p', '*', 'ρ', 'Ρ', '¶', 'þ'],
        '/q/' => ['q', '*'],
        '/r/' => ['r', '*', '®'],
        '/s/' => ['s', '5', '*', '\$', '§', 'ß', 'Ś', 'ś', 'Š', 'š'],
        '/t/' => ['t', '*', 'Τ', 'τ'],
        '/u/' => ['u', 'υ', 'µ', 'û', 'ü', 'ù', 'ú', 'ū', 'Û', 'Ü', 'Ù', 'Ú', 'Ū', '@', '*'],
        '/v/' => ['v', '*', 'υ', 'ν'],
        '/w/' => ['w', '*', 'ω', 'ψ', 'Ψ'],
        '/x/' => ['x', '*', 'Χ', 'χ'],
        '/y/' => ['y', '*', '¥', 'γ', 'ÿ', 'ý', 'Ÿ', 'Ý'],
        '/z/' => ['z', '*', 'Ζ', 'ž', 'Ž', 'ź', 'Ź', 'ż', 'Ż'],
    ],

    /*
    |--------------------------------------------------------------------------
    | False Positives
    |--------------------------------------------------------------------------
    */
    'false_positives' => [
        'hello', 'scunthorpe', 'cockburn', 'penistone', 'lightwater',
        'assume', 'bass', 'class', 'compass', 'pass',
        'dickinson', 'middlesex', 'cockerel', 'butterscotch', 'blackcock',
        'countryside', 'arsenal', 'flick', 'flicker', 'analyst',
        'cocktail', 'musicals hit', 'is hit', 'blackcocktail', 'its not',
    ],

    /*
    |--------------------------------------------------------------------------
    | Global Allow List
    |--------------------------------------------------------------------------
    |
    | Words in this list will never be flagged as profanity.
    |
    */
    'allow' => [],

    /*
    |--------------------------------------------------------------------------
    | Global Block List
    |--------------------------------------------------------------------------
    |
    | Additional words to always flag as profanity.
    |
    */
    'block' => [],

    /*
    |--------------------------------------------------------------------------
    | Backward Compatibility: Profanities
    |--------------------------------------------------------------------------
    |
    | Basic profanity list for backward compatibility.
    | Full lists are in config/languages/*.php
    |
    */
    'profanities' => [
        'fuck', 'shit', 'damn', 'bitch', 'ass', 'hell',
    ],

];


================================================
FILE: config/languages/english.php
================================================
<?php

return [
    'severity' => [
        'mild' => [
            'damn', 'hell', 'crap', 'arse', 'sucks', 'piss', 'bloody',
            'bollocks', 'bugger', 'crikey', 'darn', 'heck', 'turd',
            'puke', 'puuke', 'puuker', 'shat', 'trots', 'vomit',
            'waysted', 'wuss', 'wuzzie',
        ],
        'moderate' => [
            'ass', 'bitch', 'bastard', 'slut', 'whore', 'douche',
            'douchebag', 'skank', 'slag', 'tramp', 'tosser', 'wanker',
            'wanking', 'prick', 'dick', 'knob', 'bellend', 'minger',
            'git', 'twit', 'dipshit', 'jackass', 'smartass', 'dumbass',
            'asshole', 'arsehole', 'shag', 'shagger', 'shagging',
            'hooker', 'hussy', 'floozy', 'tart', 'sissy', 'pansy',
        ],
        'high' => [
            'fuck', 'shit', 'cock', 'pussy', 'cunt', 'twat', 'tit', 'tits',
            'fucking', 'fucker', 'motherfucker', 'bullshit', 'horseshit',
            'shithead', 'shithole', 'shitface', 'fuckface', 'fuckhead',
            'cocksucker', 'asswipe', 'clusterfuck', 'mindfuck',
            'dumbfuck', 'fuckwit', 'shitbag', 'shitcunt',
            'thundercunt', 'cum', 'jizz', 'dildo', 'blowjob',
            'handjob', 'rimjob', 'fellatio', 'cunnilingus',
        ],
        'extreme' => [
            'nigger', 'nigga', 'niggers', 'niggas', 'coon', 'darkie',
            'kike', 'spic', 'spick', 'wetback', 'chink', 'gook',
            'paki', 'raghead', 'towelhead', 'sandnigger', 'beaner',
            'gringo', 'wop', 'dago', 'polack', 'retard', 'retarded',
            'faggot', 'fag', 'dyke', 'tranny',
        ],
    ],

    'profanities' => [
        'abbo',
        'abortionist',
        'abuser',
        'ahole',
        'alabama hotpocket',
        'alligatorbait',
        'anal',
        'analannie',
        'analsex',
        'areola',
        'arse',
        'arsebagger',
        'arsebandit',
        'arseblaster',
        'arsecowboy',
        'arsefuck',
        'arsefucker',
        'arsehat',
        'arsehole',
        'arseholes',
        'arsehore',
        'arsejockey',
        'arsekiss',
        'arsekisser',
        'arselick',
        'arselicker',
        'arselover',
        'arseman',
        'arsemonkey',
        'arsemunch',
        'arsemuncher',
        'arsepacker',
        'arsepirate',
        'arsepuppies',
        'arseranger',
        'arses',
        'arsewhore',
        'arsewipe',
        'ass',
        'assbag',
        'assbagger',
        'assbandit',
        'assbanger',
        'assbite',
        'assblaster',
        'assclown',
        'asscock',
        'asscowboy',
        'asscracker',
        'asses',
        'assface',
        'assfuck',
        'assfucker',
        'assgoblin',
        'ass-hat',
        'asshat',
        'asshead',
        'asshole',
        'assholes',
        'assholz',
        'asshopper',
        'asshore',
        'ass-jabber',
        'assjacker',
        'assjockey',
        'asskiss',
        'asskisser',
        'assklown',
        'asslick',
        'asslicker',
        'asslover',
        'assman',
        'assmonkey',
        'ass monkey',
        'assmunch',
        'assmuncher',
        'assnigger',
        'asspacker',
        'ass-pirate',
        'asspirate',
        'asspuppies',
        'assranger',
        'assshit',
        'assshole',
        'asssucker',
        'asswad',
        'asswhore',
        'asswipe',
        'axwound',
        'azzhole',
        'backdoorman',
        'badfuck',
        'baldy',
        'ball licker',
        'balllicker',
        'ballsack',
        'bampot',
        'banging',
        'barelylegal',
        'barface',
        'barfface',
        'bassterds',
        'bastard',
        'bastards',
        'bastardz',
        'basterds',
        'basterdz',
        'bazongas',
        'bazooms',
        'beaner',
        'beastality',
        'beastial',
        'beastiality',
        'beat-off',
        'beatoff',
        'beatyourmeat',
        'bestial',
        'bestiality',
        'biatch',
        'bicurious',
        'bigass',
        'bigbastard',
        'bigbutt',
        'bitch',
        'bitchass',
        'bitcher',
        'bitches',
        'bitchez',
        'bitchin',
        'bitching',
        'bitchslap',
        'bitchtits',
        'bitchy',
        'biteme',
        'blow job',
        'blowjob',
        'boffing',
        'bohunk',
        'bollick',
        'bollock',
        'bollocks',
        'bollox',
        'bondage',
        'boner',
        'boob',
        'boobies',
        'boobs',
        'booby',
        'bootycall',
        'bountybar',
        'breastjob',
        'breastlover',
        'breastman',
        'brothel',
        'brotherfucker',
        'bugger',
        'buggered',
        'buggery',
        'bukake',
        'bullcrap',
        'bulldike',
        'bulldyke',
        'bullshit',
        'bumblefuck',
        'bumfuck',
        'bungabunga',
        'bunghole',
        'butchbabes',
        'butchdike',
        'butchdyke',
        'butt-bang',
        'buttbang',
        'buttcheeks',
        'buttface',
        'butt-fuck',
        'buttfuck',
        'buttfucka',
        'butt-fucker',
        'buttfucker',
        'butt-fuckers',
        'buttfuckers',
        'butthead',
        'butthole',
        'buttman',
        'buttmunch',
        'buttmuncher',
        'butt-pirate',
        'buttpirate',
        'butt plug',
        'buttplug',
        'buttstain',
        'buttwipe',
        'byatch',
        'cacker',
        'cameljockey',
        'camel toe',
        'cameltoe',
        'carpet muncher',
        'carpetmuncher',
        'cawk',
        'cawks',
        'chav',
        'cherrypopper',
        'chesticle',
        'chickslick',
        'chinc',
        'chink',
        'choad',
        'chode',
        'clamdigger',
        'clamdiver',
        'clit',
        'clitface',
        'clitfuck',
        'clitoris',
        'clogwog',
        'clunge',
        'clusterfuck',
        'cnts',
        'cntz',
        'cock',
        'cockass',
        'cockbite',
        'cockblock',
        'cockblocker',
        'cockburger',
        'cockcowboy',
        'cockface',
        'cockfight',
        'cockfucker',
        'cock-head',
        'cockhead',
        'cockjockey',
        'cockknob',
        'cockknoker',
        'cocklicker',
        'cocklover',
        'cockmaster',
        'cockmongler',
        'cockmongruel',
        'cockmonkey',
        'cockmuncher',
        'cocknob',
        'cocknose',
        'cocknugget',
        'cockqueen',
        'cockrider',
        'cocks',
        'cockshit',
        'cocksman',
        'cocksmith',
        'cocksmoke',
        'cocksmoker',
        'cocksniffer',
        'cocksucer',
        'cocksuck',
        'cocksucked',
        'cock-sucker',
        'cocksucker',
        'cocksucking',
        'cocktease',
        'cockwaffle',
        'cocky',
        'coitus',
        'cok',
        'commie',
        'coochie',
        'coochy',
        'coon',
        'coondog',
        'cooter',
        'copulate',
        'cracker',
        'crackpipe',
        'crack-whore',
        'crackwhore',
        'crap',
        'crappy',
        'crotchjockey',
        'crotchmonkey',
        'crotchrot',
        'cuck',
        'cum',
        'cumbubble',
        'cumdumpster',
        'cumfest',
        'cumguzzler',
        'cumjockey',
        'cumm',
        'cumquat',
        'cumqueen',
        'cumshot',
        'cumslut',
        'cumtart',
        'cunilingus',
        'cunillingus',
        'cunnie',
        'cunnilingus',
        'cunntt',
        'cunt',
        'cuntass',
        'cunteyed',
        'cuntface',
        'cuntfucker',
        'cunthole',
        'cuntlick',
        'cuntlicker',
        'cuntlicker',
        'cuntlicking',
        'cuntrag',
        'cunts',
        'cuntslut',
        'cuntsucker',
        'cuntz',
        'cybersex',
        'cyberslimer',
        'dago',
        'dammit',
        'damn',
        'damnation',
        'damnit',
        'darkie',
        'darky',
        'datnigga',
        'deapthroat',
        'deepthroat',
        'deggo',
        'dego',
        'devilworshipper',
        'dick',
        'dickbag',
        'dickbeaters',
        'dickbrain',
        'dickface',
        'dickforbrains',
        'dickfuck',
        'dickfucker',
        'dickhead',
        'dickhole',
        'dickjuice',
        'dickless',
        'dicklick',
        'dicklicker',
        'dickmilk',
        'dickmonger',
        'dicks',
        'dickslap',
        'dick-sneeze',
        'dicksucker',
        'dicksucking',
        'dicktickler',
        'dickwad',
        'dickweasel',
        'dickweed',
        'dickwod',
        'dike',
        'dildo',
        'dildos',
        'dilldo',
        'dilldos',
        'dipshit',
        'dipstick',
        'dixiedike',
        'dixiedyke',
        'doggiestyle',
        'doggystyle',
        'dominatricks',
        'dominatrics',
        'dominatrix',
        'doochbag',
        'dookie',
        'douch',
        'douchbag',
        'douche',
        'douchebag',
        'douche-fag',
        'douchewaffle',
        'drag queen',
        'dragqueen',
        'dragqween',
        'dripdick',
        'dumass',
        'dumb ass',
        'dumbass',
        'dumbbitch',
        'dumbfuck',
        'dumbshit',
        'dumshit',
        'dyke',
        'easyslut',
        'eatballs',
        'eatme',
        'eatpussy',
        'ejaculate',
        'ejaculated',
        'ejaculating',
        'ejaculation',
        'enema',
        'excrement',
        'facefucker',
        'facist',
        'faeces',
        'fag',
        'fagbag',
        'faget',
        'fagfucker',
        'fagging',
        'faggit',
        'faggot',
        'faggotcock',
        'faggots',
        'fagit',
        'fagot',
        'fags',
        'fagtard',
        'fagz',
        'faig',
        'faigs',
        'fannyfucker',
        'fark',
        'farted',
        'farting',
        'farty',
        'fastfuck',
        'fatass',
        'fatfuck',
        'fatfucker',
        'fatso',
        'feces',
        'felatio',
        'felch',
        'felcher',
        'felching',
        'fellatio',
        'feltch',
        'feltcher',
        'feltching',
        'fingerfuck',
        'fingerfucked',
        'fingerfucker',
        'fingerfuckers',
        'fingerfucking',
        'fister',
        'fistfuck',
        'fistfucked',
        'fistfucker',
        'fistfucking',
        'fisting',
        'flamer',
        'flasher',
        'flid',
        'flipping the bird',
        'flyd',
        'flydie',
        'flydye',
        'fondle',
        'footaction',
        'footfuck',
        'footfucker',
        'footlicker',
        'fornicate',
        'freakfuck',
        'freakyfucker',
        'freefuck',
        'fubar',
        'fucck',
        'fuck',
        'fucka',
        'fuckable',
        'fuckass',
        'fuckbag',
        'fuckboy',
        'fuckbrain',
        'fuckbuddy',
        'fuckbutt',
        'fuckbutter',
        'fucked',
        'fucker',
        'fuckers',
        'fuckersucker',
        'fuckface',
        'fuckfest',
        'fuckfreak',
        'fuckfriend',
        'fuckhead',
        'fuckhole',
        'fuckin',
        'fuckina',
        'fucking',
        'fuckingbitch',
        'fuckinnuts',
        'fuckinright',
        'fuckit',
        'fuckknob',
        'fuckmonkey',
        'fucknut',
        'fucknutt',
        'fuckoff',
        'fuckpig',
        'fuckstick',
        'fucktard',
        'fucktart',
        'fuckup',
        'fuckwad',
        'fuckwhore',
        'fuckwit',
        'fuckwitt',
        'fuckyou',
        'fudge packer',
        'fudgepacker',
        'Fudge Packer',
        'fugly',
        'fuk',
        'Fukah',
        'Fuken',
        'fuker',
        'Fukin',
        'Fukk',
        'Fukkah',
        'Fukken',
        'Fukker',
        'Fukkin',
        'fuks',
        'funfuck',
        'fuuck',
        'gang bang',
        'gangbang',
        'gangbanged',
        'gangbanger',
        'gatorbait',
        'gayass',
        'gaybob',
        'gayboy',
        'gaydo',
        'gayfuck',
        'gayfuckist',
        'gaygirl',
        'gaylord',
        'gaymuthafuckinwhore',
        'gays',
        'gaysex',
        'gaytard',
        'gaywad',
        'gayz',
        'getiton',
        'givehead',
        'glazeddonut',
        'godammit',
        'goddamit',
        'goddammit',
        'goddamn',
        'goddamned',
        'god-damned',
        'goddamnes',
        'goddamnit',
        'goddamnmuthafucker',
        'goldenshower',
        'gonorrehea',
        'gonzagas',
        'gooch',
        'gook',
        'gotohell',
        'greaseball',
        'gringo',
        'grostulation',
        'guido',
        'gypo',
        'gypp',
        'gyppie',
        'gyppo',
        'gyppy',
        'handjob',
        'hard on',
        'hardon',
        'headfuck',
        'heeb',
        'hell',
        'herpes',
        'hijacker',
        'hijacking',
        'hillbillies',
        'hindoo',
        'hitler',
        'hitlerism',
        'hitlerist',
        'hoar',
        'hobo',
        'hoe',
        'hoes',
        'holestuffer',
        'homo',
        'homobangers',
        'homodumbshit',
        'honger',
        'honkers',
        'honkey',
        'honky',
        'hookers',
        'hoor',
        'hoore',
        'hore',
        'horney',
        'horniest',
        'horny',
        'horseshit',
        'hosejob',
        'hotdamn',
        'hotpussy',
        'hottotrot',
        'humping',
        'hymen',
        'iblowu',
        'idiot',
        'incest',
        'insest',
        'internet wife',
        'inthebuff',
        'jackass',
        'jackoff',
        'jackshit',
        'jagoff',
        'jap',
        'japcrap',
        'japs',
        'jerkass',
        'jerk off',
        'jerk-off',
        'jerkoff',
        'jesuschrist',
        'jigaboo',
        'jiggabo',
        'jihad',
        'jijjiboo',
        'jisim',
        'jism',
        'jiss',
        'jizim',
        'jizjuice',
        'jizm',
        'jizm',
        'jizz',
        'jizzim',
        'jizzum',
        'jubblies',
        'juggalo',
        'jungle bunny',
        'junglebunny',
        'kiddy fiddler',
        'kike',
        'kinky',
        'kissass',
        'knobz',
        'kondum',
        'kooch',
        'kootch',
        'krap',
        'krappy',
        'kraut',
        'kumbubble',
        'kumbullbe',
        'kummer',
        'kumming',
        'kums',
        'kunilingus',
        'kunnilingus',
        'kunt',
        'kunts',
        'kuntz',
        'kyke',
        'labia',
        'lactate',
        'lady boy',
        'ladyboy',
        'lameass',
        'lapdance',
        'lardass',
        'lesbain',
        'lesbayn',
        'lesbian',
        'lesbin',
        'lesbo',
        'lezbe',
        'lezbefriends',
        'lezbo',
        'lezz',
        'lezzer',
        'lezzie',
        'lezzo',
        'libido',
        'lickme',
        'limpdick',
        'lipshits',
        'lipshitz',
        'livesex',
        'lmfao',
        'loadedgun',
        'lovebone',
        'lovegoo',
        'lovegun',
        'lovejuice',
        'lovemuscle',
        'lovepistol',
        'loverocket',
        'low life',
        'lowlife',
        'lubejob',
        'luckycameltoe',
        'manhater',
        'manpaste',
        'masochist',
        'masokist',
        'massterbait',
        'masstrbait',
        'masstrbate',
        'mastabate',
        'mastabater',
        'masterbaiter',
        'masterbate',
        'master bates',
        'masterbates',
        'mastrabator',
        'masturbate',
        'masturbating',
        'mattressprincess',
        'mcfagget',
        'meatbeater',
        'meatrack',
        'mgger',
        'mggor',
        'milf',
        'minge',
        'mofo',
        'molest',
        'molestation',
        'molester',
        'molestor',
        'moneyshot',
        'mooncricket',
        'moron',
        'mothafuck',
        'mothafucka',
        'mothafuckaz',
        'mothafucked',
        'mothafucker',
        'motha fucker',
        'mothafuckin',
        'mothafucking',
        'mothafuckings',
        'motha fuker',
        'motha fukkah',
        'motha fukker',
        'motherfuck',
        'motherfucked',
        'mother-fucker',
        'motherfucker',
        'mother fucker',
        'motherfuckin',
        'motherfucking',
        'motherfuckings',
        'mother fukah',
        'mother fuker',
        'mother fukkah',
        'mother fukker',
        'motherlovebone',
        'muff',
        'muffdive',
        'muffdiver',
        'muffindiver',
        'mufflikcer',
        'muncher',
        'munging',
        'muthafucker',
        'mutha fucker',
        'mutha fukah',
        'mutha fuker',
        'mutha fukkah',
        'mutha fukker',
        'nastt',
        'nastybitch',
        'nastyho',
        'nastyslut',
        'nastywhore',
        'nazi',
        'necro',
        'negro',
        'negroes',
        'negroid',
        'nigaboo',
        'nigga',
        'niggah',
        'niggaracci',
        'niggard',
        'niggarded',
        'niggarding',
        'niggardliness',
        "niggardliness's",
        'niggardly',
        "niggard's",
        'niggards',
        'niggaz',
        'nigger',
        'niggerhead',
        'niggerhole',
        "nigger's",
        'niggers',
        'niggle',
        'niggled',
        'niggles',
        'niggling',
        'nigglings',
        'niggor',
        'niggur',
        'niglet',
        'nignog',
        'nigr',
        'nigra',
        'nigre',
        'nigur',
        'niiger',
        'niigr',
        'nipple',
        'nipplering',
        'nittit',
        'nlgger',
        'nlggor',
        'nofuckingway',
        'nonce',
        'nookey',
        'nookie',
        'nudger',
        'nut case',
        'nutcase',
        'nutfucker',
        'nut sack',
        'nutsack',
        'ontherag',
        'orafis',
        'orgasim',
        'orgasim',
        'orgasm',
        'orgasum',
        'orgies',
        'orgy',
        'oriface',
        'orifice',
        'orifiss',
        'osama bin laden',
        'packi',
        'packie',
        'packy',
        'paedo',
        'paedofile',
        'paedophile',
        'paki',
        'pakie',
        'paky',
        'palesimian',
        'panooch',
        'panti',
        'pearlnecklace',
        'pecker',
        'peckerhead',
        'peckerwood',
        'peedo',
        'peeenus',
        'peeenusss',
        'peehole',
        'peenus',
        'peinus',
        'penas',
        'penile',
        'penisbanger',
        'penis-breath',
        'penises',
        'penisfucker',
        'penispuffer',
        'penus',
        'penuus',
        'perv',
        'perversion',
        'pervert',
        'phonesex',
        'phuc',
        'phuck',
        'phuk',
        'phuked',
        'phuker',
        'phuking',
        'phukked',
        'phukker',
        'phukking',
        'phungky',
        'phuq',
        'pi55',
        'picaninny',
        'piccaninny',
        'pickaninny',
        'pikey',
        'piky',
        'pimper',
        'pimpjuic',
        'pimpjuice',
        'pimpsimp',
        'pindick',
        'piss',
        'pissed',
        'pissed off',
        'pisser',
        'pisses',
        'pissflaps',
        'pisshead',
        'pissin',
        'pissing',
        'pissoff',
        'play boy',
        'playboy',
        'play bunny',
        'playbunny',
        'play girl',
        'playgirl',
        'plumper',
        'pocketpool',
        'polac',
        'polack',
        'polak',
        'polesmoker',
        'pollock',
        'poon',
        'poonani',
        'poonany',
        'poontang',
        'pooperscooper',
        'pooping',
        'poorwhitetrash',
        'poostabber',
        'popimp',
        'porch monkey',
        'porchmonkey',
        'porn',
        'pornflick',
        'pornking',
        'porno',
        'pornprincess',
        'pric',
        'prick',
        'prik',
        'prickhead',
        'prostitute',
        'pu55i',
        'pu55y',
        'pube',
        'pubiclice',
        'puke',
        'punanny',
        'punta',
        'puntang',
        'purinaprincess',
        'pusse',
        'pussee',
        'pussie',
        'pussies',
        'pussy',
        'pussyeater',
        'pussyfucker',
        'pussylicker',
        'pussylicking',
        'pussylips',
        'pussylover',
        'pussypounder',
        'pusy',
        'puto',
        'puuke',
        'puuker',
        'queef',
        'queer',
        'queerbait',
        'queerhole',
        'queers',
        'queerz',
        'quim',
        'qweers',
        'qweerz',
        'qweir',
        'rag head',
        'raghead',
        'raped',
        'rapist',
        'rearend',
        'rearentry',
        'recktum',
        'rectum',
        'redneck',
        'renob',
        'rentafuck',
        'rimjob',
        'rimming',
        'ruski',
        'russki',
        'russkie',
        'sadist',
        'sadom',
        'saeema butt',
        'sandm',
        'sand nigger',
        'sandnigger',
        'scag',
        'scank',
        'scat',
        'schlong',
        'screwing',
        'screwyou',
        'scrote',
        'scrotum',
        'scum',
        'scumbag',
        'seaman staines',
        'semen',
        'sexed',
        'sexfarm',
        'sexhound',
        'sexhouse',
        'sexing',
        'sexkitten',
        'sexpot',
        'sexslave',
        'sextogo',
        'sextoy',
        'sextoys',
        'sexwhore',
        'sexymoma',
        'sexy-slim',
        'seymour butts',
        'shag',
        'shagger',
        'shaggin',
        'shagging',
        'shat',
        'shhit',
        'shit',
        'shitass',
        'shitbag',
        'shitbagger',
        'shitbrains',
        'shitbreath',
        'shitcan',
        'shitcanned',
        'shitcunt',
        'shitdick',
        'shite',
        'shiteater',
        'shited',
        'shiter',
        'shitface',
        'shitfaced',
        'shitfit',
        'shitforbrains',
        'shitfuck',
        'shitfucker',
        'shitfull',
        'shithapens',
        'shithappens',
        'shithead',
        'shithole',
        'shithouse',
        'shiting',
        'shitlist',
        'shitola',
        'shitoutofluck',
        'shits',
        'shitspitter',
        'shitstain',
        'shitted',
        'shitter',
        'shittiest',
        'shitting',
        'shitty',
        'shity',
        'shitz',
        'shiz',
        'shiznit',
        'shortfuck',
        'shyt',
        'shyte',
        'shytty',
        'shyty',
        'sissy',
        'sixsixsix',
        'sixtynine',
        'sixtyniner',
        'skanck',
        'skank',
        'skankbitch',
        'skankee',
        'skankey',
        'skankfuck',
        'skanks',
        'skankwhore',
        'skanky',
        'skankybitch',
        'skankywhore',
        'skeet',
        'skinflute',
        'skullfuck',
        'skum',
        'skumbag',
        'slanteye',
        'slantyeye',
        'slapper',
        'slavedriver',
        'sleezebag',
        'sleezeball',
        'slideitin',
        'slimeball',
        'slimebucket',
        'slopehead',
        'slopey',
        'slopy',
        'slut',
        'slutbag',
        'sluts',
        'slutt',
        'slutting',
        'slutty',
        'slutwear',
        'slutwhore',
        'slutz',
        'smackthemonkey',
        'smeg',
        'smelly',
        'smut',
        'snatch',
        'snatchpatch',
        'snot',
        'snowback',
        'snownigger',
        'sodom',
        'sodomise',
        'sodomite',
        'sodomize',
        'sodomy',
        'son-of-a-bitch',
        'sonofabitch',
        'sonofbitch',
        'spac',
        'spacca',
        'spaghettibender',
        'spaghettinigger',
        'spankthemonkey',
        'spazza',
        'sperm',
        'spermacide',
        'spermbag',
        'spermhearder',
        'spermherder',
        'spic',
        'spick',
        'spig',
        'spigotty',
        'spik',
        'spitter',
        'splittail',
        'splooge',
        'spooge',
        'spook',
        'spreadeagle',
        'squaw',
        'stabber',
        'stiffy',
        'strapon',
        'stripclub',
        'stroking',
        'stupidfuck',
        'stupidfucker',
        'suckass',
        'suckdick',
        'sucker',
        'suckme',
        'suckmyass',
        'suckmydick',
        'suckmytit',
        'suckoff',
        'swastika',
        'tampon',
        'tarbaby',
        'tard',
        'teat',
        'teste',
        'testicle',
        'testicles',
        'thicklips',
        'thicko',
        'thirdeye',
        'thirdleg',
        'threesome',
        'thundercunt',
        'timbernigger',
        'tit',
        'titbitnipply',
        'titfuck',
        'titfucker',
        'titfuckin',
        'titjob',
        'titlicker',
        'titlover',
        'tits',
        'tittie',
        'titties',
        'titty',
        'tittyfuck',
        'tonguethrust',
        'tonguethruster',
        'tonguetramp',
        'torture',
        'tosser',
        'tosspot',
        'towel head',
        'towelhead',
        'trailertrash',
        'tramp',
        'trannie',
        'tranny',
        'trots',
        'trouser snake',
        'tuckahoe',
        'tunneloflove',
        'turd',
        'twat',
        'twatlips',
        'twats',
        'twatwaffle',
        'twink',
        'twinkie',
        'twobitwhore',
        'unclefucker',
        'unfuckable',
        'upskirt',
        'uptheass',
        'upthebutt',
        'urinate',
        'urine',
        'usama bin laden',
        'uterus',
        'vag',
        'vagina',
        'vaginal',
        'vajayjay',
        'vajina',
        'va-j-j',
        'valjina',
        'vibrater',
        'vibrator',
        'vietcong',
        'violate',
        'violation',
        'virginbreaker',
        'vjayjay',
        'vomit',
        'vullva',
        'vulva',
        'wank',
        'wanker',
        'wanking',
        'wankjob',
        'waysted',
        'welcher',
        'wetback',
        'wetspot',
        'whacker',
        'whigger',
        'whiskeydick',
        'whiskydick',
        'whitenigger',
        'whitetrash',
        'whitey',
        'whoor',
        'whop',
        'whore',
        'whorebag',
        'whoreface',
        'whorefucker',
        'whorehouse',
        'wife beater',
        'williewanker',
        'wog',
        'wop',
        'wuss',
        'wuzzie',
        'x-rated',
        'xrated',
        'yellowman',
        'zigabo',
        'zipperhea',
        'zipper head',
        'sucks',
        'bloody',
        'crikey',
        'darn',
        'heck',
        'slag',
        'knob',
        'bellend',
        'minger',
        'git',
        'twit',
        'smartass',
        'hooker',
        'hussy',
        'floozy',
        'tart',
        'pansy',
        'mindfuck',
        'niggas',
        'retard',
        'retarded',
    ],
    
    'false_positives' => [
        'hello',
        'scunthorpe',
        'cockburn',
        'penistone',
        'lightwater',
        'assume',
        'bass',
        'class',
        'compass',
        'pass',
        'dickinson',
        'middlesex',
        'cockerel',
        'butterscotch',
        'blackcock',
        'countryside',
        'arsenal',
        'flick',
        'flicker',
        'analyst',
        'cocktail',
        'musicals hit',
        'is hit',
        'blackcocktail',
        'its not',
        // Common words containing "ass"
        'assignment',
        'assign',
        'assigned',
        'assigns',
        'assigning',
        'assist',
        'assistant',
        'assisted',
        'assists',
        'assistance',
        'associate',
        'associated',
        'associates',
        'association',
        'associations',
        'assemble',
        'assembled',
        'assembles',
        'assembly',
        'assert',
        'asserted',
        'assertion',
        'assertions',
        'asserts',
        'assess',
        'assessed',
        'assesses',
        'assessing',
        'assessment',
        'assessments',
        'asset',
        'assets',
        'assure',
        'assured',
        'assures',
        'assurance',
        'assorted',
        'assortment',
        'assassin',
        'assassins',
        'assassination',
        'assassinated',
        'assault',
        'assaulted',
        'assaults',
        'passion',
        'passionate',
        'passions',
        'passive',
        'passively',
        'passenger',
        'passengers',
        'passage',
        'passages',
        'passing',
        'passed',
        'passes',
        'passport',
        'passports',
        'password',
        'passwords',
        'bypass',
        'bypassed',
        'bypasses',
        'bypassing',
        'classroom',
        'classrooms',
        'classic',
        'classical',
        'classics',
        'classification',
        'classifications',
        'classified',
        'classify',
        'classmate',
        'classmates',
        'classed',
        'classes',
        'classy',
        'mass',
        'masses',
        'massive',
        'massively',
        'massage',
        'massages',
        'massacre',
        'massacres',
        'embassy',
        'embassies',
        'ambassador',
        'ambassadors',
        'embarrass',
        'embarrassed',
        'embarrassing',
        'embarrassment',
        'harass',
        'harassed',
        'harassing',
        'harassment',
        'brass',
        'brassy',
        'crass',
        'glass',
        'glasses',
        'glassy',
        'grass',
        'grasses',
        'grassy',
        'lass',
        'lassie',
        'molasses',
        'morass',
        'sass',
        'sassy',
        'trespass',
        'trespassed',
        'trespassing',
        'surpass',
        'surpassed',
        'surpasses',
        'compassion',
        'compassionate',
        'encompass',
        'encompassed',
        'encompasses',
        'encompassing',
        // Common words containing "tit"
        'title',
        'titles',
        'titled',
        'subtitle',
        'subtitles',
        'entity',
        'entities',
        'identity',
        'identities',
        'quantity',
        'quantities',
        'constitution',
        'constitutional',
        'constitutions',
        'constitute',
        'constitutes',
        'institution',
        'institutional',
        'institutions',
        'petition',
        'petitions',
        'petitioner',
        'competition',
        'competitions',
        'competitive',
        'competitor',
        'competitors',
        'repetition',
        'repetitions',
        'repetitive',
        'appetite',
        'appetites',
        'gratitude',
        'attitude',
        'attitudes',
        'altitude',
        'altitudes',
        'aptitude',
        'multitude',
        'fortitude',
        'latitude',
        'latitudes',
        'partition',
        'partitions',
        'practitioner',
        'practitioners',
        'restitution',
        'prostitution',
        'superstition',
        'superstitions',
        'superstitious',
        'titillate',
        'titan',
        'titans',
        'titanium',
        // Common words containing "cum"
        'document',
        'documents',
        'documentary',
        'documentation',
        'documented',
        'circumstance',
        'circumstances',
        'circumference',
        'accumulate',
        'accumulated',
        'accumulation',
        'cucumber',
        'cucumbers',
        'incumbent',
        'incumbents',
        // Common words containing "ho" / "hoe"
        'shoe',
        'shoes',
        'shoelace',
        'horseshoe',
        // Common words containing "nig"
        'night',
        'nights',
        'nightclub',
        'nightfall',
        'nightlife',
        'nightmare',
        'nightmares',
        'nightstand',
        'nighttime',
        'tonight',
        'overnight',
        'knight',
        'knights',
        // Common words containing "rap"
        'grape',
        'grapes',
        'drape',
        'drapes',
        'scrape',
        'scraped',
        'scraper',
        'therapy',
        'therapies',
        'therapist',
        'therapists',
        // Common words containing "nob"
        'noble',
        'nobles',
        'nobleman',
        'nobility',
        'snob',
        'snobs',
        'snobbish',
        'snobby',
    ],
    
    'substitutions' => [
        '/a/' => ['a', '4', '@', 'Á', 'á', 'À', 'Â', 'à', 'Â', 'â', 'Ä', 'ä', 'Ã', 'ã', 'Å', 'å', 'æ', 'Æ', 'α', 'Δ', 'Λ', 'λ'],
        '/e/' => ['e', '3', '€', 'È', 'è', 'É', 'é', 'Ê', 'ê', 'ë', 'Ë', 'ē', 'Ē', 'ė', 'Ė', 'ę', 'Ę', '∑'],
        '/i/' => ['i', '!', '|', ']', '[', '1', '∫', 'Ì', 'Í', 'Î', 'Ï', 'ì', 'í', 'î', 'ï', 'ī', 'Ī', 'į', 'Į'],
        '/o/' => ['o', '0', 'Ο', 'ο', 'Φ', '¤', '°', 'ø', 'ô', 'Ô', 'ö', 'Ö', 'ò', 'Ò', 'ó', 'Ó', 'œ', 'Œ', 'ø', 'Ø', 'ō', 'Ō', 'õ', 'Õ'],
        '/u/' => ['u', 'υ', 'µ', 'û', 'ü', 'ù', 'ú', 'ū', 'Û', 'Ü', 'Ù', 'Ú', 'Ū', '@', '*'],
    ]
];

================================================
FILE: config/languages/french.php
================================================
<?php

return [
    'severity' => [
        'mild' => [
            'crotte', 'crottes', 'caca', 'cacas', 'zut',
            'punaise',
            'idiot', 'idiots', 'idiote', 'idiotes',
            'bête', 'bete', 'bêtes', 'betes',
            'sot', 'sots', 'sotte', 'sottes',
            'niais', 'niaise', 'niaises',
            'ballot', 'ballots', 'andouille', 'andouilles',
        ],
        'moderate' => [
            'connard', 'connarde', 'con', 'conne',
            'salaud', 'salope', 'garce', 'garces',
            'pétasse', 'petasse', 'pétasses', 'petasses',
            'bâtard', 'batard', 'bâtards', 'batards',
            'bâtarde', 'batarde', 'bâtardes', 'batardes',
            'abruti', 'abrutis', 'abrutie', 'abruties',
            'crétin', 'cretin', 'crétins', 'cretins',
            'crétine', 'cretine', 'crétines', 'cretines',
            'débile', 'debile', 'débiles', 'debiles',
            'imbécile', 'imbecile', 'imbéciles', 'imbeciles',
            'cul', 'culs', 'trou du cul', 'trou de balle',
            'cochon', 'cochons', 'cochonne', 'cochonnes',
        ],
        'high' => [
            'merde', 'putain', 'enculé', 'encule',
            'niquer', 'nique', 'baiser', 'baise',
            'foutre', 'foutu', 'foutue', 'chier',
            'bite', 'pute', 'fils de pute',
        ],
        'extreme' => [
            'pédé', 'pede', 'pédés', 'pedes',
            'pédéraste', 'pederaste', 'pédérastes', 'pederastes',
            'tapette', 'tapettes', 'tantouze', 'tantouzes',
            'fiotte', 'fiottes', 'tarlouze', 'tarlouzes',
            'gouine', 'gouines',
            'attardé', 'attarde', 'attardés', 'attardes',
            'attardée', 'attardee', 'attardées', 'attardees',
        ],
    ],

    'profanities' => [
        // Common French profanities and vulgar expressions
        'merde',
        'putain',
        'connard',
        'connarde',
        'con',
        'conne',
        'salaud',
        'salope',
        'enculé',
        'encule',
        'enculée',
        'enculee',
        'fils de pute',
        'fils de putain',
        'bordel',
        'chier',
        'chiasse',
        'chieur',
        'chieuse',
        'emmerde',
        'emmerder',
        'emmerdeur',
        'emmerdeuse',
        'baiser',
        'baise',
        'baisé',
        'baise',
        'baisée',
        'baisee',
        'foutre',
        'foutue',
        'foutu',
        'niquer',
        'nique',
        'niqué',
        'nique',
        'niquée',
        'niquee',
        'bite',
        'bites',
        'pine',
        'pines',
        'queue',
        'queues',
        'vit',
        'verge',
        'zob',
        'zobs',
        'biroute',
        'biroutes',
        'braquemart',
        'braquemarts',
        'dard',
        'dards',
        'gourdin',
        'gourdins',
        'gland',
        'glands',
        'prépuce',
        'prepuce',
        'prépuces',
        'prepuces',
        'couilles',
        'couille',
        'couillon',
        'couillonne',
        'couillons',
        'couillonnes',
        'roubignoles',
        'roubignole',
        'burnes',
        'burne',
        'roustons',
        'rouston',
        'testicules',
        'testicule',
        'génitoires',
        'genitoires',
        'génitoire',
        'genitoire',
        'chatte',
        'chattes',
        'minou',
        'minous',
        'con',
        'cons',
        'moule',
        'moules',
        'fente',
        'fentes',
        'cramouille',
        'cramouilles',
        'crevasse',
        'crevasses',
        'cyprine',
        'cyprines',
        'foufoune',
        'foufounes',
        'motte',
        'mottes',
        'touffe',
        'touffes',
        'abricot',
        'abricots',
        'nichons',
        'nichon',
        'tétons',
        'teton',
        'téton',
        'tetons',
        'roberts',
        'robert',
        'doudounes',
        'doudoune',
        'lolos',
        'lolo',
        'miches',
        'miche',
        'mamelles',
        'mamelle',
        'seins',
        'sein',
        'nénés',
        'nene',
        'nénée',
        'nenee',
        'roploplos',
        'roploplo',
        'flotteurs',
        'flotteur',
        'amortisseurs',
        'amortisseur',
        'airbags',
        'airbag',
        'cul',
        'culs',
        'fesses',
        'fesse',
        'pétard',
        'petard',
        'pétards',
        'petards',
        'postérieur',
        'posterieur',
        'postérieurs',
        'posterieurs',
        'derrière',
        'derriere',
        'derrières',
        'derrieres',
        'fion',
        'fions',
        'trou du cul',
        'trou de balle',
        'anus',
        'orifice',
        'orifices',
        'rosette',
        'rosettes',
        'rondelle',
        'rondelles',
        'bague',
        'bagues',
        'anneau',
        'anneaux',
        'pédé',
        'pede',
        'pédés',
        'pedes',
        'pédéraste',
        'pederaste',
        'pédérastes',
        'pederastes',
        'tapette',
        'tapettes',
        'tante',
        'tantes',
        'tantouze',
        'tantouzes',
        'fiotte',
        'fiottes',
        'tarlouze',
        'tarlouzes',
        'tafiole',
        'tafioles',
        'gouine',
        'gouines',
        'lesbienne',
        'lesbiennes',
        'tribade',
        'tribades',
        'saphique',
        'saphiques',
        'lesbos',
        'lesbo',
        'garce',
        'garces',
        'pétasse',
        'petasse',
        'pétasses',
        'petasses',
        'traînée',
        'trainee',
        'traînées',
        'trainees',
        'pute',
        'putes',
        'putain',
        'putains',
        'catin',
        'catins',
        'caillera',
        'cailleras',
        'racaille',
        'racailles',
        'voyou',
        'voyous',
        'truand',
        'truands',
        'bandit',
        'bandits',
        'malfrat',
        'malfrats',
        'gangster',
        'gangsters',
        'criminel',
        'criminels',
        'criminelle',
        'criminelles',
        'assassin',
        'assassins',
        'tueur',
        'tueurs',
        'tueuse',
        'tueuses',
        'meurtrier',
        'meurtriers',
        'meurtrière',
        'meurtrieres',
        'bourrin',
        'bourrins',
        'bourrine',
        'bourrines',
        'rustre',
        'rustres',
        'plouc',
        'ploucs',
        'péquenaud',
        'pequenaud',
        'péquenauds',
        'pequenauds',
        'cul-terreux',
        'cul terreux',
        'bouseux',
        'boueuse',
        'bouseux',
        'bouseuses',
        'bâtard',
        'batard',
        'bâtards',
        'batards',
        'bâtarde',
        'batarde',
        'bâtardes',
        'batardes',
        'salopard',
        'salopards',
        'saloparde',
        'salopardes',
        'fumier',
        'fumiers',
        'ordure',
        'ordures',
        'pourriture',
        'pourritures',
        'charogne',
        'charognes',
        'raclure',
        'raclures',
        'déchet',
        'dechet',
        'déchets',
        'dechets',
        'rebut',
        'rebuts',
        'lie',
        'lies',
        'écume',
        'ecume',
        'écumes',
        'ecumes',
        'fange',
        'fanges',
        'boue',
        'boues',
        'vase',
        'vases',
        'crotte',
        'crottes',
        'étron',
        'etron',
        'étrons',
        'etrons',
        'caca',
        'cacas',
        'bouse',
        'bouses',
        'fiente',
        'fientes',
        'colombin',
        'colombins',
        'boudin',
        'boudins',
        'saucisse',
        'saucisses',
        'andouille',
        'andouilles',
        'crétin',
        'cretin',
        'crétins',
        'cretins',
        'crétine',
        'cretine',
        'crétines',
        'cretines',
        'débile',
        'debile',
        'débiles',
        'debiles',
        'attardé',
        'attarde',
        'attardés',
        'attardes',
        'attardée',
        'attardee',
        'attardées',
        'attardees',
        'demeuré',
        'demeure',
        'demeurés',
        'demeures',
        'demeurée',
        'demeuree',
        'demeurées',
        'demeurees',
        'simple',
        'simples',
        'idiot',
        'idiots',
        'idiote',
        'idiotes',
        'imbécile',
        'imbecile',
        'imbéciles',
        'imbeciles',
        'stupide',
        'stupides',
        'bête',
        'bete',
        'bêtes',
        'betes',
        'sot',
        'sots',
        'sotte',
        'sottes',
        'niais',
        'niaise',
        'niaises',
        'nigaud',
        'nigauds',
        'nigaude',
        'nigaudes',
        'benêt',
        'benet',
        'benêts',
        'benets',
        'benête',
        'benete',
        'benêtes',
        'benetes',
        'ballot',
        'ballots',
        'ballotte',
        'ballottes',
        'balourd',
        'balourds',
        'balourde',
        'balourdes',
        'lourdaud',
        'lourdauds',
        'lourdaude',
        'lourdaudes',
        'abruti',
        'abrutis',
        'abrutie',
        'abruties',
        'bourrique',
        'bourriques',
        'âne',
        'ane',
        'ânes',
        'anes',
        'ânesse',
        'anesse',
        'ânesses',
        'anesses',
        'baudet',
        'baudets',
        'bourricot',
        'bourricots',
        'gourde',
        'gourdes',
        'cornichon',
        'cornichons',
        'navet',
        'navets',
        'nouille',
        'nouilles',
        'patate',
        'patates',
        'buse',
        'buses',
        'dinde',
        'dindes',
        'dindon',
        'dindons',
        'oie',
        'oies',
        'jars',
        'bécasse',
        'becasse',
        'bécasses',
        'becasses',
        'bécassine',
        'becassine',
        'bécassines',
        'becassines',
        'poule',
        'poules',
        'poulet',
        'poulets',
        'coquette',
        'coquettes',
        'coq',
        'coqs',
        'chapon',
        'chapons',
        'poularde',
        'poulardes',
        'poussin',
        'poussins',
        'poussinière',
        'poussiniere',
        'poussinières',
        'poussinieres',
        'cochon',
        'cochons',
        'cochonne',
        'cochonnes',
        'porc',
        'porcs',
        'truie',
        'truies',
        'pourceau',
        'pourceaux',
        'goret',
        'gorets',
        'cochonnet',
        'cochonnets',
        'cochonnaille',
        'cochonnailles',
        'verrat',
        'verrats',
        'bauge',
        'bauges',
        'porcherie',
        'porcheries',
        'étable',
        'etable',
        'étables',
        'etables',
        'écurie',
        'ecurie',
        'écuries',
        'ecuries',
        'box',
        'boxs',
        'stalle',
        'stalles',
        'enclos',
        'clos',
        'parc',
        'parcs',
        'paddock',
        'paddocks',
        'pâturage',
        'paturage',
        'pâturages',
        'paturages',
        'prairie',
        'prairies',
        'pré',
        'pre',
        'prés',
        'pres',
        'herbage',
        'herbages',
        'pacage',
        'pacages',
        'pâture',
        'pature',
        'pâtures',
        'patures',
        'fourrage',
        'fourrages',
        'foin',
        'foins',
        'paille',
        'pailles',
        'litière',
        'litiere',
        'litières',
        'litieres',
        'fumier',
        'fumiers',
        'purin',
        'purins',
        'lisier',
        'lisiers',
        'compost',
        'composts',
        'engrais',
        'fertilisant',
        'fertilisants',
        'amendement',
        'amendements',
        'terreau',
        'terreaux',
        'humus',
        'tourbe',
        'tourbes',
        'mousse',
        'mousses',
        'lichen',
        'lichens',
        'algue',
        'algues',
        'varech',
        'varechs',
        'goémon',
        'goemon',
        'goémons',
        'goemons',
        'sargasse',
        'sargasses',
        'zostère',
        'zostere',
        'zostères',
        'zosteres',
        'laminaire',
        'laminaires',
        'fucus',
        'ulve',
        'ulves',
        'spiruline',
        'spirulines',
        'chlorelle',
        'chlorelles',
        'microalgue',
        'microalgues',
        'phytoplancton',
        'phytoplanctons',
        'zooplancton',
        'zooplanctons',
        'plancton',
        'planctons',
        'krill',
        'krills',
        'copépode',
        'copepode',
        'copépodes',
        'copepodes',
        'rotifère',
        'rotifere',
        'rotifères',
        'rotiferes',
        'protozoaire',
        'protozoaires',
        'paramècie',
        'paramecie',
        'paramécies',
        'paramecies',
        'amibe',
        'amibes',
        'euglène',
        'euglene',
        'euglènes',
        'euglenes',
        'volvox',
        'hydre',
        'hydres',
        'méduse',
        'meduse',
        'méduses',
        'meduses',
        'polype',
        'polypes',
        'corail',
        'coraux',
        'anémone',
        'anemone',
        'anémones',
        'anemones',
        'actinie',
        'actinies',
        'éponge',
        'eponge',
        'éponges',
        'eponges',
        'spongieux',
        'spongieuse',
        'spongieuses',
        'poreux',
        'poreuse',
        'poreuses',
        'alvéolé',
        'alveole',
        'alvéolés',
        'alveoles',
        'alvéolée',
        'alveolee',
        'alvéolées',
        'alveolees',
        'cellulaire',
        'cellulaires',
        'cellule',
        'cellules',
        'cytoplasme',
        'cytoplasmes',
        'noyau',
        'noyaux',
        'nucléole',
        'nucleole',
        'nucléoles',
        'nucleoles',
        'chromosome',
        'chromosomes',
        'chromatine',
        'chromatines',
        'gène',
        'gene',
        'gènes',
        'genes',
        'génome',
        'genome',
        'génomes',
        'genomes',
        'génétique',
        'genetique',
        'génétiques',
        'genetiques',
        'héréditaire',
        'hereditaire',
        'héréditaires',
        'hereditaires',
        'hérédité',
        'heredite',
        'hérédités',
        'heredites',
        'descendance',
        'descendances',
        'progéniture',
        'progeniture',
        'progénitures',
        'progenitures',
        'postérité',
        'posterite',
        'postérités',
        'posterites',
        'lignée',
        'lignee',
        'lignées',
        'lignees',
        'dynastie',
        'dynasties',
        'famille',
        'familles',
        'clan',
        'clans',
        'tribu',
        'tribus',
        'peuplade',
        'peuplades',
        'ethnie',
        'ethnies',
        'race',
        'races',
        'espèce',
        'espece',
        'espèces',
        'especes',
        'genre',
        'genres',
        'variété',
        'variete',
        'variétés',
        'varietes',
        'sous-espèce',
        'sous-espece',
        'sous-espèces',
        'sous-especes',
        'subspecies',
        'sous-variété',
        'sous-variete',
        'sous-variétés',
        'sous-varietes',
        'cultivar',
        'cultivars',
        'hybride',
        'hybrides',
        'croisement',
        'croisements',
        'métissage',
        'metissage',
        'métissages',
        'metissages',
        'brassage',
        'brassages',
        'mélange',
        'melange',
        'mélanges',
        'melanges',
        'mixture',
        'mixtures',
        'composition',
        'compositions',
        'formule',
        'formules',
        'recette',
        'recettes',
        'procédé',
        'procede',
        'procédés',
        'procedes',
        'méthode',
        'methode',
        'méthodes',
        'methodes',
        'technique',
        'techniques',
        'procédure',
        'procedure',
        'procédures',
        'procedures',
        'protocole',
        'protocoles',
        'marche',
        'marches',
        'démarche',
        'demarche',
        'démarches',
        'demarches',
        'approche',
        'approches',
        'façon',
        'facon',
        'façons',
        'facons',
        'manière',
        'maniere',
        'manières',
        'manieres',
        'mode',
        'modes',
        'modalité',
        'modalite',
        'modalités',
        'modalites',
        'moyen',
        'moyens',
        'outil',
        'outils',
        'instrument',
        'instruments',
        'ustensile',
        'ustensiles',
        'appareil',
        'appareils',
        'dispositif',
        'dispositifs',
        'mécanisme',
        'mecanisme',
        'mécanismes',
        'mecanismes',
        'machine',
        'machines',
        'engin',
        'engins',
        'équipement',
        'equipement',
        'équipements',
        'equipements',
        'matériel',
        'materiel',
        'matériels',
        'materiels',
        'outillage',
        'outillages',
        'machinerie',
        'machineries',
        'mécanique',
        'mecanique',
        'mécaniques',
        'mecaniques',
        'automatique',
        'automatiques',
        'électrique',
        'electrique',
        'électriques',
        'electriques',
        'électronique',
        'electronique',
        'électroniques',
        'electroniques',
        'numérique',
        'numerique',
        'numériques',
        'numeriques',
        'digital',
        'digitaux',
        'digitale',
        'digitales',
        'informatique',
        'informatiques',
        'ordinateur',
        'ordinateurs',
        'computer',
        'computers',
        'pc',
        'pcs',
        'micro',
        'micros',
        'portable',
        'portables',
        'laptop',
        'laptops',
        'tablette',
        'tablettes',
        'smartphone',
        'smartphones',
        'téléphone',
        'telephone',
        'téléphones',
        'telephones',
        'mobile',
        'mobiles',
        'cellulaire',
        'cellulaires',
        'sans-fil',
        'sans fil',
        'wifi',
        'bluetooth',
        'internet',
        'web',
        'site',
        'sites',
        'page',
        'pages',
        'lien',
        'liens',
        'url',
        'urls',
        'adresse',
        'adresses',
        'email',
        'emails',
        'courriel',
        'courriels',
        'message',
        'messages',
        'texto',
        'textos',
        'sms',
        'mms',
        'chat',
        'chats',
        'forum',
        'forums',
        'blog',
        'blogs',
        'réseau',
        'reseau',
        'réseaux',
        'reseaux',
        'social',
        'sociaux',
        'sociale',
        'sociales',
        'facebook',
        'twitter',
        'instagram',
        'linkedin',
        'youtube',
        'google',
        'yahoo',
        'bing',
        'moteur',
        'moteurs',
        'recherche',
        'recherches',
        'requête',
        'requete',
        'requêtes',
        'requetes',
        'base',
        'bases',
        'donnée',
        'donnee',
        'données',
        'donnees',
        'information',
        'informations',
        'renseignement',
        'renseignements',
        'détail',
        'detail',
        'détails',
        'details',
        'précision',
        'precision',
        'précisions',
        'precisions',
        'exactitude',
        'exactitudes',
        'justesse',
        'justesses',
        'vérité',
        'verite',
        'vérités',
        'verites',
        'réalité',
        'realite',
        'réalités',
        'realites',
        'fait',
        'faits',
        'élément',
        'element',
        'éléments',
        'elements',
        'composant',
        'composants',
        'composante',
        'composantes',
        'partie',
        'parties',
        'portion',
        'portions',
        'section',
        'sections',
        'segment',
        'segments',
        'fragment',
        'fragments',
        'morceau',
        'morceaux',
        'bout',
        'bouts',
        'extrémité',
        'extremite',
        'extrémités',
        'extremites',
        'pointe',
        'pointes',
        'sommet',
        'sommets',
        'pic',
        'pics',
        'cime',
        'cimes',
        'faîte',
        'faite',
        'faîtes',
        'faites',
        'crête',
        'crete',
        'crêtes',
        'cretes',
        'arête',
        'arete',
        'arêtes',
        'aretes',
        'angle',
        'angles',
        'coin',
        'coins',
        'recoin',
        'recoins',
        'recess',
        'alcôve',
        'alcove',
        'alcôves',
        'alcoves',
        'niche',
        'niches',
        'anfractuosité',
        'anfractuosite',
        'anfractuosités',
        'anfractuosites',
        'cavité',
        'cavite',
        'cavités',
        'cavites',
        'trou',
        'trous',
        'creux',
        'orifice',
        'orifices',
        'ouverture',
        'ouvertures',
        'fente',
        'fentes',
        'fissure',
        'fissures',
        'crevasse',
        'crevasses',
        'lézarde',
        'lezarde',
        'lézardes',
        'lezardes',
        'gerçure',
        'gercure',
        'gerçures',
        'gercures',
        'cassure',
        'cassures',
        'fracture',
        'fractures',
        'rupture',
        'ruptures',
        'brisure',
        'brisures',
        'félure',
        'felure',
        'félures',
        'felures',
        'brèche',
        'breche',
        'brèches',
        'breches',
        'trouée',
        'trouee',
        'trouées',
        'trouees',
        'percée',
        'percee',
        'percées',
        'percees',
        'passage',
        'passages',
        'couloir',
        'couloirs',
        'corridor',
        'corridors',
        'galerie',
        'galeries',
        'tunnel',
        'tunnels',
        'souterrain',
        'souterrains',
        'grotte',
        'grottes',
        'caverne',
        'cavernes',
        'antre',
        'antres',
        'tanière',
        'taniere',
        'tanières',
        'tanieres',
        'gîte',
        'gite',
        'gîtes',
        'gites',
        'refuge',
        'refuges',
        'abri',
        'abris',
        'cachette',
        'cachettes',
        'planque',
        'planques',
        'repaire',
        'repaires',
        'retraite',
        'retraites',
        'ermitage',
        'ermitages',
        'solitude',
        'solitudes',
        'isolement',
        'isolements',
        'séparation',
        'separation',
        'séparations',
        'separations',
        'division',
        'divisions',
        'cloison',
        'cloisons',
        'paroi',
        'parois',
        'mur',
        'murs',
        'muraille',
        'murailles',
        'rempart',
        'remparts',
        'fortification',
        'fortifications',
        'défense',
        'defense',
        'défenses',
        'defenses',
        'protection',
        'protections',
        'blindage',
        'blindages',
        'cuirasse',
        'cuirasses',
        'armure',
        'armures',
        'bouclier',
        'boucliers',
        'écu',
        'ecu',
        'écus',
        'ecus',
        'pavois',
        'rondache',
        'rondaches',
        'targe',
        'targes',
        'carapace',
        'carapaces',
        'coquille',
        'coquilles',
        'écaille',
        'ecaille',
        'écailles',
        'ecailles',
        'plaque',
        'plaques',
        'lame',
        'lames',
        'feuille',
        'feuilles',
        'pellicule',
        'pellicules',
        'membrane',
        'membranes',
        'tissu',
        'tissus',
        'étoffe',
        'etoffe',
        'étoffes',
        'etoffes',
        'textile',
        'textiles',
        'fibre',
        'fibres',
        'fil',
        'fils',
        'filament',
        'filaments',
        'brin',
        'brins',
        'corde',
        'cordes',
        'ficelle',
        'ficelles',
        'câble',
        'cable',
        'câbles',
        'cables',
        'chaîne',
        'chaine',
        'chaînes',
        'chaines',
        'maillon',
        'maillons',
        'anneau',
        'anneaux',
        'bague',
        'bagues',
        'alliance',
        'alliances',
        'jonc',
        'joncs',
        'chevalière',
        'chevaliere',
        'chevalières',
        'chevalieres',
        'solitaire',
        'solitaires',
        'diamant',
        'diamants',
        'pierre',
        'pierres',
        'gemme',
        'gemmes',
        'bijou',
        'bijoux',
        'joyau',
        'joyaux',
        'parure',
        'parures',
        'ornement',
        'ornements',
        'décoration',
        'decoration',
        'décorations',
        'decorations',
        'enjolivement',
        'enjolivements',
        'embellissement',
        'embellissements',
        'agrément',
        'agrement',
        'agréments',
        'agrements',
        'atour',
        'atours',
        'apparence',
        'apparences',
        'aspect',
        'aspects',
        'allure',
        'allures',
        'prestance',
        'prestances',
        'élégance',
        'elegance',
        'élégances',
        'elegances',
        'raffinement',
        'raffinements',
        'distinction',
        'distinctions',
        'classe',
        'classes',
        'style',
        'styles',
        'genre',
        'genres',
        'mode',
        'modes',
        'tendance',
        'tendances',
        'fashion',
        'fashions',
        'couture',
        'coutures',
        'prêt-à-porter',
        'pret-a-porter',
        'haute-couture',
        'haute couture',
        'confection',
        'confections',
        'vêtement',
        'vetement',
        'vêtements',
        'vetements',
        'habit',
        'habits',
        'tenue',
        'tenues',
        'costume',
        'costumes',
        'toilette',
        'toilettes',
        'mise',
        'mises',
        'accoutrement',
        'accoutrements',
        'harnachement',
        'harnachements',
        'équipement',
        'equipement',
        'équipements',
        'equipements',
        'attirail',
        'attirails',
        'matériel',
        'materiel',
        'matériels',
        'materiels',
        'outillage',
        'outillages',
        'arsenal',
        'arsenaux',
        'armement',
        'armements',
        'panoplie',
        'panoplies',
        'collection',
        'collections',
        'assortiment',
        'assortiments',
        'gamme',
        'gammes',
        'palette',
        'palettes',
        'éventail',
        'eventail',
        'éventails',
        'eventails',
        'choix',
        'sélection',
        'selection',
        'sélections',
        'selections',
        'tri',
        'tris',
        'triage',
        'triages',
        'criblage',
        'criblages',
        'filtrage',
        'filtrages',
        'épuration',
        'epuration',
        'épurations',
        'epurations',
        'purification',
        'purifications',
        'assainissement',
        'assainissements',
        'nettoyage',
        'nettoyages',
        'lavage',
        'lavages',
        'rinçage',
        'rincage',
        'rinçages',
        'rincages',
        'lessivage',
        'lessivages',
        'blanchiment',
        'blanchiments',
        'dégraissage',
        'degraissage',
        'dégraissages',
        'degraissages',
        'détachage',
        'detachage',
        'détachages',
        'detachages',
        'décrassage',
        'decrassage',
        'décrassages',
        'decrassages',
        'récurage',
        'recurage',
        'récurages',
        'recurages',
        'frottage',
        'frottages',
        'brossage',
        'brossages',
        'polissage',
        'polissages',
        'lustrage',
        'lustrages',
        'cirage',
        'cirages',
        'encaustique',
        'encaustiques',
        'cire',
        'cires',
        'pommade',
        'pommades',
        'baume',
        'baumes',
        'crème',
        'creme',
        'crèmes',
        'cremes',
        'onguent',
        'onguents',
        'liniment',
        'liniments',
        'embrocation',
        'embrocations',
        'friction',
        'frictions',
        'massage',
        'massages',
        'pétrissage',
        'petrissage',
        'pétrissages',
        'petrissages',
        'malaxage',
        'malaxages',
        'manipulation',
        'manipulations',
        'maniement',
        'maniements',
        'manutention',
        'manutentions',
        'transport',
        'transports',
        'acheminement',
        'acheminements',
        'convoyage',
        'convoyages',
        'livraison',
        'livraisons',
        'distribution',
        'distributions',
        'répartition',
        'repartition',
        'répartitions',
        'repartitions',
        'partage',
        'partages',
        'division',
        'divisions',
        'séparation',
        'separation',
        'séparations',
        'separations',
        'scission',
        'scissions',
        'coupure',
        'coupures',
        'découpage',
        'decoupage',
        'découpages',
        'decoupages',
        'sectionnement',
        'sectionnements',
        'segmentation',
        'segmentations',
        'morcellement',
        'morcellements',
        'fragmentation',
        'fragmentations',
        'émiettement',
        'emiettement',
        'émiettements',
        'emiettements',
        'pulvérisation',
        'pulverisation',
        'pulvérisations',
        'pulverisations',
        'atomisation',
        'atomisations',
        'vaporisation',
        'vaporisations',
        'évaporation',
        'evaporation',
        'évaporations',
        'evaporations',
        'sublimation',
        'sublimations',
        'distillation',
        'distillations',
        'condensation',
        'condensations',
        'liquéfaction',
        'liquefaction',
        'liquéfactions',
        'liquefactions',
        'solidification',
        'solidifications',
        'cristallisation',
        'cristallisations',
        'congélation',
        'congelation',
        'congélations',
        'congelations',
        'gel',
        'gels',
        'glaciation',
        'glaciations',
        'refroidissement',
        'refroidissements',
        'réfrigération',
        'refrigeration',
        'réfrigérations',
        'refrigerations',
        'zut',
        'punaise',
    ],
    
    'false_positives' => [
        // Common French words that might be detected as false positives
        'analyse',
        'analyses',
        'classe',
        'classes',
        'passer',
        'passage',
        'passages',
        'expression',
        'expressions',
        'assassin',
        'assassins',
        'assassiner',
        'assassinat',
        'assassinats',
        'entreprise',
        'entreprises',
        'entrepreneur',
        'entrepreneurs',
        'affaire',
        'affaires',
        'travail',
        'travaux',
        'travailler',
        'travailleur',
        'travailleurs',
        'travailleuse',
        'travailleuses',
        'emploi',
        'emplois',
        'employé',
        'employe',
        'employés',
        'employes',
        'employée',
        'employee',
        'employées',
        'employees',
        'employeur',
        'employeurs',
        'bureau',
        'bureaux',
        'ordinateur',
        'ordinateurs',
        'machine',
        'machines',
        'appareil',
        'appareils',
        'dispositif',
        'dispositifs',
        'instrument',
        'instruments',
        'outil',
        'outils',
        'utilité',
        'utilites',
        'fonction',
        'fonctions',
        'fonctionner',
        'fonctionnement',
        'fonctionnements',
        'caractéristique',
        'caracteristique',
        'caractéristiques',
        'caracteristiques',
        'spécialité',
        'specialite',
        'spécialités',
        'specialites',
        'spécialiste',
        'specialiste',
        'spécialistes',
        'specialistes',
        'spécialiser',
        'specialiser',
        'spécialisé',
        'specialise',
        'spécialisée',
        'specialisee',
        'spécialisés',
        'specialises',
        'spécialisées',
        'specialisees',
        'spécialisation',
        'specialisation',
        'spécialisations',
        'specialisations',
        'professionnel',
        'professionnels',
        'professionnelle',
        'professionnelles',
        'profession',
        'professions',
        'professeur',
        'professeurs',
        'enseigner',
        'enseignement',
        'enseignements',
        'éducation',
        'education',
        'éducatif',
        'educatif',
        'éducative',
        'educative',
        'éducatifs',
        'educatifs',
        'éducatives',
        'educatives',
        'éduquer',
        'eduquer',
        'éduqué',
        'eduque',
        'éduquée',
        'eduquee',
        'éduqués',
        'eduques',
        'éduquées',
        'eduquees',
        'éducateur',
        'educateur',
        'éducateurs',
        'educateurs',
        'éducatrice',
        'educatrice',
        'éducatrices',
        'educatrices',
        'étudiant',
        'etudiant',
        'étudiants',
        'etudiants',
        'étudiante',
        'etudiante',
        'étudiantes',
        'etudiantes',
        'étudier',
        'etudier',
        'étude',
        'etude',
        'études',
        'etudes',
        'étudié',
        'etudie',
        'étudiée',
        'etudiee',
        'étudiés',
        'etudies',
        'étudiées',
        'etudiees',
        'recherche',
        'recherches',
        'rechercher',
        'chercheur',
        'chercheurs',
        'chercheuse',
        'chercheuses',
        'scientifique',
        'scientifiques',
        'science',
        'sciences',
        'connaissance',
        'connaissances',
        'connaître',
        'connaitre',
        'connu',
        'connue',
        'connus',
        'connues',
        'savoir',
        'savoirs',
        'su',
        'sue',
        'sus',
        'sues',
        'sagesse',
        'sagesses',
        'sage',
        'sages',
        'intelligence',
        'intelligences',
        'intelligent',
        'intelligents',
        'intelligente',
        'intelligentes',
        'talent',
        'talents',
        'talentueux',
        'talentueuse',
        'talentueuses',
        'habileté',
        'habilete',
        'habiletés',
        'habiletes',
        'habile',
        'habiles',
        'adresse',
        'adresses',
        'adroit',
        'adroits',
        'adroite',
        'adroites',
        'maître',
        'maitre',
        'maîtres',
        'maitres',
        'maîtresse',
        'maitresse',
        'maîtresses',
        'maitresses',
        'maîtrise',
        'maitrise',
        'maîtrises',
        'maitrises',
        'maîtriser',
        'maitriser',
        'maîtrisé',
        'maitrise',
        'maîtrisée',
        'maitrisee',
        'maîtrisés',
        'maitrises',
        'maîtrisées',
        'maitrisees',
        'domaine',
        'domaines',
        'dominer',
        'dominé',
        'domine',
        'dominée',
        'dominee',
        'dominés',
        'domines',
        'dominées',
        'dominees',
        'contrôle',
        'controle',
        'contrôles',
        'controles',
        'contrôler',
        'controler',
        'contrôlé',
        'controle',
        'contrôlée',
        'controlee',
        'contrôlés',
        'controles',
        'contrôlées',
        'controlees',
        'administration',
        'administrations',
        'administrer',
        'administrateur',
        'administrateurs',
        'administratrice',
        'administratrices',
        'gestion',
        'gestions',
        'gérer',
        'gerer',
        'géré',
        'gere',
        'gérée',
        'geree',
        'gérés',
        'geres',
        'gérées',
        'gerees',
        'gestionnaire',
        'gestionnaires',
        'organisation',
        'organisations',
        'organiser',
        'organisé',
        'organise',
        'organisée',
        'organisee',
        'organisés',
        'organises',
        'organisées',
        'organisees',
        'organisateur',
        'organisateurs',
        'organisatrice',
        'organisatrices',
        'système',
        'systeme',
        'systèmes',
        'systemes',
        'systématique',
        'systematique',
        'systématiques',
        'systematiques',
        'méthode',
        'methode',
        'méthodes',
        'methodes',
        'méthodologie',
        'methodologie',
        'méthodologies',
        'methodologies',
        'processus',
        'traiter',
        'traité',
        'traite',
        'traitée',
        'traitee',
        'traités',
        'traites',
        'traitées',
        'traitees',
        'procédure',
        'procedure',
        'procédures',
        'procedures',
        'procéder',
        'proceder',
        'protocole',
        'protocoles',
        'norme',
        'normes',
        'normal',
        'normaux',
        'normale',
        'normales',
        'normalité',
        'normalite',
        'normalités',
        'normalites',
        'normaliser',
        'normalisé',
        'normalise',
        'normalisée',
        'normalisee',
        'normalisés',
        'normalises',
        'normalisées',
        'normalisees',
        'standard',
        'standards',
        'standardiser',
        'standardisé',
        'standardise',
        'standardisée',
        'standardisee',
        'standardisés',
        'standardises',
        'standardisées',
        'standardisees',
        'règle',
        'regle',
        'règles',
        'regles',
        'règlement',
        'reglement',
        'règlements',
        'reglements',
        'réglementer',
        'reglementer',
        'réglementaire',
        'reglementaire',
        'réglementaires',
        'reglementaires',
        'réguler',
        'reguler',
        'régulier',
        'regulier',
        'réguliers',
        'reguliers',
        'régulière',
        'reguliere',
        'régulières',
        'regulieres',
        'régularité',
        'regularite',
        'régularités',
        'regularites',
        'régulariser',
        'regulariser',
        'régularisé',
        'regularise',
        'régularisée',
        'regularisee',
        'régularisés',
        'regularises',
        'régularisées',
        'regularisees',
        'loi',
        'lois',
        'légal',
        'legal',
        'légaux',
        'legaux',
        'légale',
        'legale',
        'légales',
        'legales',
        'légalité',
        'legalite',
        'légalités',
        'legalites',
        'légaliser',
        'legaliser',
        'légalisé',
        'legalise',
        'légalisée',
        'legalisee',
        'légalisés',
        'legalises',
        'légalisées',
        'legalisees',
        'droit',
        'droits',
        'juridique',
        'juridiques',
        'justice',
        'justices',
        'juste',
        'justes',
        'injuste',
        'injustes',
        'injustice',
        'injustices',
        'tribunal',
        'tribunaux',
        'juge',
        'juges',
        'jugement',
        'jugements',
        'juger',
        'jugé',
        'juge',
        'jugée',
        'jugee',
        'jugés',
        'juges',
        'jugées',
        'jugees',
        'sentence',
        'sentences',
        'condamnation',
        'condamnations',
        'condamner',
        'condamné',
        'condamne',
        'condamnée',
        'condamnee',
        'condamnés',
        'condamnes',
        'condamnées',
        'condamnees',
        'punition',
        'punitions',
        'punir',
        'puni',
        'punie',
        'punis',
        'punies',
        'peine',
        'peines',
        'prison',
        'prisons',
        'emprisonner',
        'emprisonné',
        'emprisonne',
        'emprisonnée',
        'emprisonnee',
        'emprisonnés',
        'emprisonnes',
        'emprisonnées',
        'emprisonnees',
        'prisonnier',
        'prisonniers',
        'prisonnière',
        'prisonniere',
        'prisonnières',
        'prisonnieres',
        'détenu',
        'detenu',
        'détenus',
        'detenus',
        'détenue',
        'detenue',
        'détenues',
        'detenues',
        'pénitencier',
        'penitencier',
        'pénitenciers',
        'penitenciers',
        'maison',
        'maisons',
        'arrêt',
        'arret',
        'arrêts',
        'arrets',
    ],
    
    'substitutions' => [
        '/à/' => ['à', 'a', '@', '4'],
        '/â/' => ['â', 'a', '@', '4'],
        '/ä/' => ['ä', 'a', '@', '4'],
        '/á/' => ['á', 'a', '@', '4'],
        '/ã/' => ['ã', 'a', '@', '4'],
        '/å/' => ['å', 'a', '@', '4'],
        '/æ/' => ['æ', 'ae', 'a'],
        '/è/' => ['è', 'e', '3', '€'],
        '/é/' => ['é', 'e', '3', '€'],
        '/ê/' => ['ê', 'e', '3', '€'],
        '/ë/' => ['ë', 'e', '3', '€'],
        '/ì/' => ['ì', 'i', '1', '!', '|'],
        '/í/' => ['í', 'i', '1', '!', '|'],
        '/î/' => ['î', 'i', '1', '!', '|'],
        '/ï/' => ['ï', 'i', '1', '!', '|'],
        '/ò/' => ['ò', 'o', '0', 'ø'],
        '/ó/' => ['ó', 'o', '0', 'ø'],
        '/ô/' => ['ô', 'o', '0', 'ø'],
        '/ö/' => ['ö', 'o', '0', 'ø'],
        '/õ/' => ['õ', 'o', '0', 'ø'],
        '/ø/' => ['ø', 'o', '0'],
        '/œ/' => ['œ', 'oe', 'o'],
        '/ù/' => ['ù', 'u', 'ü'],
        '/ú/' => ['ú', 'u', 'ü'],
        '/û/' => ['û', 'u', 'ü'],
        '/ü/' => ['ü', 'u', 'ù'],
        '/u/' => ['u', 'ù', 'ú', 'û', 'ü', '@', '*'],
        '/ÿ/' => ['ÿ', 'y', 'i'],
        '/ç/' => ['ç', 'c', 's'],
        '/ñ/' => ['ñ', 'n', '~n'],
        '/c/' => ['c', 'k', 'ç', 's'],
        '/k/' => ['k', 'c', 'q'],
        '/ph/' => ['ph', 'f'],
        '/qu/' => ['qu', 'k', 'q'],
        '/x/' => ['x', 'ks', 'gs'],
        '/z/' => ['z', 's'],
        '/j/' => ['j', 'g'],
        '/g/' => ['g', 'j'],
    ]
];

================================================
FILE: config/languages/german.php
================================================
<?php

return [
    'severity' => [
        'mild' => [
            'mist', 'kacke', 'verdammt', 'verdammte', 'verdammter', 'verdammtes',
            'blöd', 'bloed', 'blöde', 'bloede', 'blöder', 'bloeder', 'blödes', 'bloedes',
            'doof', 'doofe', 'doofer', 'doofes',
            'dumm', 'dumme', 'dummer', 'dummes',
            'albern', 'alberne', 'alberner', 'albernes',
            'peinlich', 'peinliche', 'peinlicher', 'peinliches',
        ],
        'moderate' => [
            'arsch', 'arschloch', 'arschlöcher', 'arschlocher',
            'schlampe', 'nutte', 'hure',
            'wichser', 'depp', 'trottel',
            'idiot', 'vollidiot',
            'bescheuert', 'bescheuerte', 'bescheuerter', 'bescheuertes',
            'bekloppt', 'bekloppte', 'bekloppter', 'beklopptes',
            'schwanz', 'pimmel',
            'hintern', 'po', 'popo',
            'schwul', 'schwuler', 'schwule', 'schwules',
        ],
        'high' => [
            'scheiße', 'scheisse', 'ficken', 'fick', 'gefickt',
            'verfickt', 'fotze', 'muschi', 'möse', 'moese',
            'hurensohn', 'hurenkind', 'arschficker',
            'vögeln', 'voegeln', 'bumsen',
        ],
        'extreme' => [
            'tunte', 'tuntig',
            'kampflesbe', 'kampflesben',
            'kanake', 'kanaken',
            'neger', 'negerin',
            'zigeuner', 'zigeunerin',
            'retardiert', 'retardierte', 'retardierter',
        ],
    ],

    'profanities' => [
        // Common German profanities and vulgar expressions
        'scheiße',
        'scheisse',
        'scheiß',
        'scheiss',
        'kacke',
        'mist',
        'arsch',
        'arschloch',
        'arschlöcher',
        'arschlocher',
        'ficken',
        'fick',
        'gefickt',
        'verfickt',
        'verfickte',
        'verfickter',
        'verficktes',
        'verdammt',
        'verdammte',
        'verdammter',
        'verdammtes',
        'hurensohn',
        'hurenkind',
        'hure',
        'nutte',
        'schlampe',
        'fotze',
        'muschi',
        'möse',
        'moese',
        'schwanz',
        'pimmel',
        'dödel',
        'doedel',
        'lümmel',
        'luemmel',
        'rute',
        'zipfel',
        'glied',
        'eier',
        'hoden',
        'klöten',
        'kloeten',
        'sack',
        'hodensack',
        'nüsse',
        'nuesse',
        'kugeln',
        'beutel',
        'titten',
        'brüste',
        'brueste',
        'busen',
        'möpse',
        'moepse',
        'hupen',
        'vorbau',
        'körbchen',
        'koerbchen',
        'milchdrüsen',
        'milchdruesen',
        'warzen',
        'nippel',
        'brustwarzen',
        'zitzen',
        'hintern',
        'po',
        'popo',
        'gesäß',
        'gesaess',
        'kehrseite',
        'vier buchstaben',
        'allerwertester',
        'rückseite',
        'rueckseite',
        'backen',
        'pobacken',
        'arschbacken',
        'speck',
        'hinterteil',
        'schwul',
        'schwuler',
        'schwule',
        'schwules',
        'homo',
        'homos',
        'homosexuell',
        'homosexuelle',
        'homosexueller',
        'homosexuelles',
        'tuntig',
        'tunte',
        'warm',
        'warmer',
        'warme',
        'warmes',
        'lesbe',
        'lesben',
        'lesbisch',
        'lesbische',
        'lesbischer',
        'lesbisches',
        'kampflesbe',
        'kampflesben',
        'butze',
        'butzen',
        'wichser',
        'wichsen',
        'wichst',
        'gewichst',
        'onanieren',
        'onaniert',
        'masturbieren',
        'masturbiert',
        'selbstbefriedigung',
        'handjob',
        'blasen',
        'bläst',
        'blaest',
        'blowjob',
        'oral',
        'lecken',
        'leckt',
        'geleckt',
        'cunnilingus',
        'fellatio',
        'lutschen',
        'lutscht',
        'gelutscht',
        'saugen',
        'saugt',
        'gesaugt',
        'pusten',
        'pustet',
        'gepustet',
        'vögeln',
        'voegeln',
        'vögelt',
        'voegelt',
        'gevögelt',
        'gevoegelt',
        'bumsen',
        'bumst',
        'gebumst',
        'poppen',
        'poppt',
        'gepoppt',
        'knallen',
        'knallt',
        'geknallt',
        'nageln',
        'nagelt',
        'genagelt',
        'rammeln',
        'rammelt',
        'gerammelt',
        'durchnageln',
        'durchnagelt',
        'durchgenagelt',
        'durchficken',
        'durchfickt',
        'durchgefickt',
        'rannehmen',
        'rannimmt',
        'rangenommen',
        'besteigen',
        'besteigt',
        'bestiegen',
        'bespringen',
        'bespringt',
        'besprungen',
        'penis',
        'vagina',
        'vulva',
        'klitoris',
        'kitzler',
        'schamlippen',
        'venushügel',
        'venushuegel',
        'scham',
        'geschlecht',
        'geschlechtsteil',
        'geschlechtsteile',
        'genitalien',
        'intimbereich',
        'unterkörper',
        'unterkoerper',
        'lenden',
        'lendengegend',
        'schritt',
        'schrittbereich',
        'unterleib',
        'becken',
        'beckenboden',
        'damm',
        'perineum',
        'anus',
        'after',
        'poloch',
        'arschloch',
        'rosette',
        'poperze',
        'hintertür',
        'hintertuer',
        'ausgang',
        'darmausgang',
        'enddarm',
        'rektum',
        'mastdarm',
        'analbereich',
        'afterbereich',
        'hinterlader',
        'arschficker',
        'arschficken',
        'arschgefickt',
        'analverkehr',
        'analsex',
        'sodomie',
        'sodomist',
        'sodomistin',
        'pervers',
        'perverse',
        'perverser',
        'perverses',
        'perversling',
        'pervertiert',
        'pervertierte',
        'pervertierter',
        'pervertiertes',
        'versaut',
        'versaute',
        'versauter',
        'versautes',
        'schmutzig',
        'schmutzige',
        'schmutziger',
        'schmutziges',
        'dreckig',
        'dreckige',
        'dreckiger',
        'dreckiges',
        'dreck',
        'unrat',
        'abschaum',
        'pack',
        'gesindel',
        'pöbel',
        'poebel',
        'mob',
        'kanaille',
        'lumpen',
        'lump',
        'schuft',
        'schurke',
        'halunke',
        'gauner',
        'ganove',
        'gangster',
        'verbrecher',
        'kriminell',
        'kriminelle',
        'krimineller',
        'kriminelles',
        'asozial',
        'asoziale',
        'asozialer',
        'asoziales',
        'asi',
        'prollig',
        'prollige',
        'prolliger',
        'prolliges',
        'proll',
        'prolet',
        'unterschicht',
        'prekariat',
        'hartz',
        'hartzer',
        'arbeitslos',
        'arbeitslose',
        'arbeitsloser',
        'arbeitsloses',
        'sozialhilfe',
        'sozialschmarotzer',
        'schmarotzer',
        'parasit',
        'parasiten',
        'ungeziefer',
        'schädling',
        'schaedling',
        'schädlinge',
        'schaedlinge',
        'plage',
        'pest',
        'seuche',
        'krankheit',
        'leiden',
        'übel',
        'uebel',
        'böse',
        'boese',
        'schlecht',
        'schlimm',
        'schrecklich',
        'furchtbar',
        'entsetzlich',
        'grauenhaft',
        'grausam',
        'brutal',
        'roh',
        'primitiv',
        'primitive',
        'primitiver',
        'primitives',
        'barbarisch',
        'barbarische',
        'barbarischer',
        'barbarisches',
        'wild',
        'wilde',
        'wilder',
        'wildes',
        'ungezähmt',
        'ungebildet',
        'ungebildete',
        'ungebildeter',
        'ungebildetes',
        'dumm',
        'dumme',
        'dummer',
        'dummes',
        'doof',
        'doofe',
        'doofer',
        'doofes',
        'blöd',
        'bloed',
        'blöde',
        'bloede',
        'blöder',
        'bloeder',
        'blödes',
        'bloedes',
        'bescheuert',
        'bescheuerte',
        'bescheuerter',
        'bescheuertes',
        'bekloppt',
        'bekloppte',
        'bekloppter',
        'beklopptes',
        'verrückt',
        'verrueckt',
        'verrückte',
        'verrueckte',
        'verrückter',
        'verrueckter',
        'verrücktes',
        'verruecktes',
        'irre',
        'irrer',
        'irres',
        'wahnsinnig',
        'wahnsinnige',
        'wahnsinniger',
        'wahnsinniges',
        'gestört',
        'gestoert',
        'gestörte',
        'gestoerte',
        'gestörter',
        'gestoerter',
        'gestörtes',
        'gestoertes',
        'krank',
        'kranke',
        'kranker',
        'krankes',
        'pathologisch',
        'pathologische',
        'pathologischer',
        'pathologisches',
        'abnormal',
        'abnormale',
        'abnormaler',
        'abnormales',
        'unnormal',
        'unnormale',
        'unnormaler',
        'unnormales',
        'abartig',
        'abartige',
        'abartiger',
        'abartiges',
        'widerlich',
        'widerliche',
        'widerlicher',
        'widerliches',
        'ekelhaft',
        'ekelhafte',
        'ekelhafter',
        'ekelhaftes',
        'eklig',
        'eklige',
        'ekliger',
        'ekliges',
        'widerwertig',
        'widerwertige',
        'widerwertiger',
        'widerwertiges',
        'abstoßend',
        'abstossend',
        'abstoßende',
        'abstossende',
        'abstoßender',
        'abstossender',
        'abstoßendes',
        'abstossendes',
        'absurd',
        'absurde',
        'absurder',
        'absurdes',
        'lächerlich',
        'laecherlich',
        'lächerliche',
        'laecherliche',
        'lächerlicher',
        'laecherlicher',
        'lächerliches',
        'laecherliches',
        'albern',
        'alberne',
        'alberner',
        'albernes',
        'affig',
        'affige',
        'affiger',
        'affiges',
        'närrisch',
        'naerrisch',
        'närrische',
        'naerrische',
        'närrischer',
        'naerrischer',
        'närrisches',
        'naerrisches',
        'töricht',
        'toericht',
        'törichte',
        'toerichte',
        'törichter',
        'toerichter',
        'törichtes',
        'toerichtes',
        'blamabel',
        'blamable',
        'blamabler',
        'blamables',
        'peinlich',
        'peinliche',
        'peinlicher',
        'peinliches',
        'beschämend',
        'beschaemend',
        'beschämende',
        'beschaemende',
        'beschämender',
        'beschaemender',
        'beschämendes',
        'beschaemendes',
        'schmählich',
        'schmaehlich',
        'schmähliche',
        'schmaehliche',
        'schmählicher',
        'schmaehlicher',
        'schmähliches',
        'schmaehliches',
        'schändlich',
        'schaendlich',
        'schändliche',
        'schaendliche',
        'schändlicher',
        'schaendlicher',
        'schändliches',
        'schaendliches',
        'gemein',
        'gemeine',
        'gemeiner',
        'gemeines',
        'niederträchtig',
        'niedertraechtig',
        'niederträchtige',
        'niedertraechtige',
        'niederträchtiger',
        'niedertraechtiger',
        'niederträchtiges',
        'niedertraechtiges',
        'hinterhältig',
        'hinterhaeltig',
        'hinterhältige',
        'hinterhaeltige',
        'hinterhältiger',
        'hinterhaeltiger',
        'hinterhältiges',
        'hinterhaeltiges',
        'heimtückisch',
        'heimtueckisch',
        'heimtückische',
        'heimtueckische',
        'heimtückischer',
        'heimtueckischer',
        'heimtückisches',
        'heimtueckisches',
        'falsch',
        'falsche',
        'falscher',
        'falsches',
        'verlogen',
        'verlogene',
        'verlogener',
        'verlogenes',
        'heuchlerisch',
        'heuchlerische',
        'heuchlerischer',
        'heuchlerisches',
        'scheinheilig',
        'scheinheilige',
        'scheinheiliger',
        'scheinheiliges',
        'doppelzüngig',
        'doppelzuengig',
        'doppelzüngige',
        'doppelzuengige',
        'doppelzüngiger',
        'doppelzuengiger',
        'doppelzüngiges',
        'doppelzuengiges',
        'verlogen',
        'verlogene',
        'verlogener',
        'verlogenes',
        'unaufrichtig',
        'unaufrichtige',
        'unaufrichtiger',
        'unaufrichtiges',
        'unehrlich',
        'unehrliche',
        'unehrlicher',
        'unehrliches',
        'betrügerisch',
        'betruegerisch',
        'betrügerische',
        'betruegerische',
        'betrügerischer',
        'betruegerischer',
        'betrügerisches',
        'betruegerisches',
        'schwindelhaft',
        'schwindlerisch',
        'schwindlerische',
        'schwindlerischer',
        'schwindlerisches',
        'unredlich',
        'unredliche',
        'unredlicher',
        'unredliches',
        'unlauter',
        'unlautere',
        'unlauterer',
        'unlauteres',
        'unseriös',
        'unserioes',
        'unseriöse',
        'unserioes',
        'unseriöser',
        'unserioeser',
        'unseriöses',
        'unserioes',
        'dubios',
        'dubiose',
        'dubioser',
        'dubioses',
        'fragwürdig',
        'fragwuerdig',
        'fragwürdige',
        'fragwuerdige',
        'fragwürdiger',
        'fragwuerdiger',
        'fragwürdiges',
        'fragwuerdiges',
        'zweifelhaft',
        'zweifelhafte',
        'zweifelhafter',
        'zweifelhaftes',
        'suspekt',
        'suspekte',
        'suspekter',
        'suspektes',
        'verdächtig',
        'verdaechtig',
        'verdächtige',
        'verdaechtige',
        'verdächtiger',
        'verdaechtiger',
        'verdächtiges',
        'verdaechtiges',
        'obskur',
        'obskure',
        'obskurer',
        'obskures',
        'dunkel',
        'dunkle',
        'dunkler',
        'dunkles',
        'finster',
        'finstere',
        'finsterer',
        'finsteres',
        'schwarz',
        'schwarze',
        'schwarzer',
        'schwarzes',
        'düster',
        'duester',
        'düstere',
        'duestere',
        'düsterer',
        'duesterer',
        'düsteres',
        'duesteres',
        'trüb',
        'trueb',
        'trübe',
        'truebe',
        'trüber',
        'trueber',
        'trübes',
        'truebes',
        'matt',
        'matte',
        'matter',
        'mattes',
        'fahl',
        'fahle',
        'fahler',
        'fahles',
        'blass',
        'blasse',
        'blasser',
        'blasses',
        'bleich',
        'bleiche',
        'bleicher',
        'bleiches',
        'käsig',
        'kaesig',
        'käsige',
        'kaesige',
        'käsiger',
        'kaesiger',
        'käsiges',
        'kaesiges',
        'kränklich',
        'kraenklich',
        'kränkliche',
        'kraenkliche',
        'kränklicher',
        'kraenklicher',
        'kränkliches',
        'kraenkliches',
        'schwächlich',
        'schwaechlich',
        'schwächliche',
        'schwaechliche',
        'schwächlicher',
        'schwaechlicher',
        'schwächliches',
        'schwaechliches',
        'schwach',
        'schwache',
        'schwacher',
        'schwaches',
        'kraftlos',
        'kraftlose',
        'kraftloser',
        'kraftloses',
        'energielos',
        'energielose',
        'energieloser',
        'energieloses',
        'müde',
        'muede',
        'müder',
        'mueder',
        'müdes',
        'muedes',
        'erschöpft',
        'erschoepft',
        'erschöpfte',
        'erschoepfte',
        'erschöpfter',
        'erschoepfter',
        'erschöpftes',
        'erschoepftes',
        'ausgepowert',
        'ausgepowerte',
        'ausgepower',
        'ausgepowertes',
        'kaputt',
        'kaputte',
        'kaputter',
        'kaputtes',
        'defekt',
        'defekte',
        'defekter',
        'defektes',
        'hinüber',
        'hinueber',
        'im arsch',
        'futsch',
        'dahin',
        'ruiniert',
        'ruinierte',
        'ruinierter',
        'ruiniertes',
        'zerstört',
        'zerstoert',
        'zerstörte',
        'zerstoerte',
        'zerstörter',
        'zerstoerter',
        'zerstörtes',
        'zerstoertes',
        'zerbrochen',
        'zerbrochene',
        'zerbrochener',
        'zerbrochenes',
        'zerschmettert',
        'zerschmetterte',
        'zerschmetterter',
        'zerschmettertes',
        'demoliert',
        'demolierte',
        'demolierter',
        'demoliertes',
        'vernichtet',
        'vernichtete',
        'vernichteter',
        'vernichtetes',
        'ausgelöscht',
        'ausgeloescht',
        'ausgelöschte',
        'ausgeloeschte',
        'ausgelöschter',
        'ausgeloeschter',
        'ausgelöschtes',
        'ausgeloeschtes',
        'eliminiert',
        'eliminierte',
        'eliminierter',
        'eliminiertes',
        'getötet',
        'getoetet',
        'getötete',
        'getoetete',
        'getöteter',
        'getoeteter',
        'getötetes',
        'getoetetes',
        'umgebracht',
        'umgebrachte',
        'umgebrachter',
        'umgebrachtes',
        'ermordet',
        'ermordete',
        'ermordeter',
        'ermordetes',
        'hingerichtet',
        'hingerichtete',
        'hingerichteter',
        'hingerichtetes',
        'exekutiert',
        'exekutierte',
        'exekutierter',
        'exekutiertes',
        'liquidiert',
        'liquidierte',
        'liquidierter',
        'liquidiertes',
        'abgemurkst',
        'abgemurks',
        'abgemurkstes',
        'kaltgemacht',
        'kaltgemachte',
        'kaltgemachter',
        'kaltgemachtes',
        'plattgemacht',
        'plattgemachte',
        'plattgemachter',
        'plattgemachtes',
        'fertiggemacht',
        'fertiggemachte',
        'fertiggemachter',
        'fertiggemachtes',
        'kaputtgemacht',
        'kaputtgemachte',
        'kaputtgemachter',
        'kaputtgemachtes',
        'totgemacht',
        'totgemachte',
        'totgemachter',
        'totgemachtes',
        'totgeschlagen',
        'totgeschlagene',
        'totgeschlagener',
        'totgeschlagenes',
        'totgeprügelt',
        'totgeprügelte',
        'totgeprügelter',
        'totgeprügeltes',
        'totgetrampelt',
        'totgetrampelte',
        'totgetrampelter',
        'totgetrampe',
        'totgefahren',
        'totgefahrene',
        'totgefahrener',
        'totgefahrenes',
        'überfahren',
        'ueberfahren',
        'überfahrene',
        'ueberfahrene',
        'überfahrener',
        'ueberfahrener',
        'überfahrenes',
        'ueberfahrenes',
        'totgefahren',
        'totgefahrene',
        'totgefahrener',
        'totgefahrenes',
        'erstickt',
        'erstickte',
        'erstickter',
        'ersticktes',
        'erwürgt',
        'erwuergt',
        'erwürgte',
        'erwuergte',
        'erwürgter',
        'erwuergter',
        'erwürgtes',
        'erwuergtes',
        'erdrosselt',
        'erdrosselte',
        'erdrosselter',
        'erdrosseltes',
        'stranguliert',
        'strangulierte',
        'strangulierter',
        'stranguliertes',
        'gehängt',
        'gehaengt',
        'gehängte',
        'gehaengte',
        'gehängter',
        'gehaengter',
        'gehängtes',
        'gehaengtes',
        'aufgehängt',
        'aufgehaengt',
        'aufgehängte',
        'aufgehaengte',
        'aufgehängter',
        'aufgehaengter',
        'aufgehängtes',
        'aufgehaengtes',
        'erhängt',
        'erhaengt',
        'erhängte',
        'erhaengte',
        'erhängter',
        'erhaengter',
        'erhängtes',
        'erhaengtes',
        'verbrannt',
        'verbrannte',
        'verbrannter',
        'verbranntes',
        'angezündet',
        'angezuendet',
        'angezündete',
        'angezuendete',
        'angezündeter',
        'angezuendeter',
        'angezündetes',
        'angezuendetes',
        'abgefackelt',
        'abgefackelte',
        'abgefackelter',
        'abgefackeltes',
        'niedergebrannt',
        'niedergebrannte',
        'niedergebrannter',
        'niedergebranntes',
        'eingeäschert',
        'eingeaeschert',
        'eingeäscherte',
        'eingeaescherte',
        'eingeäscherter',
        'eingeaescherter',
        'eingeäschertes',
        'eingeaeschertes',
        'verbrannt',
        'verbrannte',
        'verbrannter',
        'verbranntes',
        'verkohlt',
        'verkohlte',
        'verkohlter',
        'verkohltes',
        'verkocht',
        'verkochte',
        'verkochter',
        'verkochtes',
        'versotten',
        'versottene',
        'versottener',
        'versottenes',
        'versotten',
        'versoffene',
        'versoffener',
        'versoffenes',
        'besoffen',
        'besoffene',
        'besoffener',
        'besoffenes',
        'betrunken',
        'betrunkene',
        'betrunkener',
        'betrunkenes',
        'angetrunken',
        'angetrunkene',
        'angetrunkener',
        'angetrunkenes',
        'alkoholisiert',
        'alkoholisierte',
        'alkoholisierter',
        'alkoholisiertes',
        'breit',
        'breite',
        'breiter',
        'breites',
        'zu',
        'zugedröhnt',
        'zugedroehnt',
        'zugedröhnte',
        'zugedroehnte',
        'zugedröhnter',
        'zugedroeh',
        'zugedröhntes',
        'zugedroeh',
        'dicht',
        'dichte',
        'dichter',
        'dichtes',
        'voll',
        'volle',
        'voller',
        'volles',
        'hinüber',
        'hinueber',
        'weggetreten',
        'weggetretene',
        'weggetretener',
        'weggetretenes',
        'weg',
        'wege',
        'weger',
        'weges',
        'drauf',
        'high',
        'highe',
        'higher',
        'highes',
        'stoned',
        'stoner',
        'stones',
        'bekifft',
        'bekiffte',
        'bekiffter',
        'bekifftes',
        'zugekifft',
        'zugekiffte',
        'zugekiffter',
        'zugekifftes',
        'zugeraucht',
        'zugerauchte',
        'zugerauchter',
        'zugerauchtes',
        'stramm',
        'stramme',
        'strammer',
        'strammes',
        'dicht',
        'dichte',
        'dichter',
        'dichtes',
        'platt',
        'platte',
        'platter',
        'plattes',
        'depp',
        'trottel',
        'idiot',
        'vollidiot',
        'kanake',
        'kanaken',
        'neger',
        'negerin',
        'zigeuner',
        'zigeunerin',
        'retardiert',
        'retardierte',
        'retardierter',
    ],
    
    'false_positives' => [
        // Common German words that might be detected as false positives
        'analyse',
        'analysen',
        'analysieren',
        'analysiert',
        'analysierte',
        'analysierter',
        'analysiertes',
        'klasse',
        'klassen',
        'klassisch',
        'klassische',
        'klassischer',
        'klassisches',
        'passen',
        'passt',
        'gepasst',
        'passage',
        'passagen',
        'ausdruck',
        'ausdrücke',
        'ausdruecke',
        'ausdrücklich',
        'ausdruecklich',
        'ausdrückliche',
        'ausdrueckliche',
        'ausdrücklicher',
        'ausdruecklicher',
        'ausdrückliches',
        'ausdrueckliches',
        'mörder',
        'moerder',
        'morden',
        'mordet',
        'gemordet',
        'mord',
        'morde',
        'mordtat',
        'mordtaten',
        'unternehmen',
        'unternehmens',
        'unternehmung',
        'unternehmungen',
        'unternehmer',
        'unternehmerin',
        'geschäft',
        'geschaeft',
        'geschäfte',
        'geschaefte',
        'geschäftlich',
        'geschaeftlich',
        'geschäftliche',
        'geschaeftliche',
        'geschäftlicher',
        'geschaeftlicher',
        'geschäftliches',
        'geschaeftliches',
        'arbeit',
        'arbeiten',
        'arbeiter',
        'arbeiterin',
        'arbeiterinnen',
        'arbeitsplatz',
        'arbeitsplätze',
        'arbeitsplaetze',
        'anstellung',
        'anstellungen',
        'angestellt',
        'angestellte',
        'angestellter',
        'angestelltes',
        'arbeitgeber',
        'arbeitgeberin',
        'arbeitnehmer',
        'arbeitnehmerin',
        'büro',
        'buero',
        'büros',
        'bueros',
        'computer',
        'computers',
        'rechner',
        'maschine',
        'maschinen',
        'maschinell',
        'maschinelle',
        'maschineller',
        'maschinelles',
        'gerät',
        'geraet',
        'geräte',
        'geraete',
        'apparat',
        'apparate',
        'vorrichtung',
        'vorrichtungen',
        'instrument',
        'instrumente',
        'werkzeug',
        'werkzeuge',
        'hilfsmittel',
        'nutzen',
        'nützen',
        'nuetzen',
        'nützlich',
        'nuetzlich',
        'nützliche',
        'nuetzliche',
        'nützlicher',
        'nuetzlicher',
        'nützliches',
        'nuetzliches',
        'funktion',
        'funktionen',
        'funktionieren',
        'funktioniert',
        'funktionierte',
        'funktioniertes',
        'eigenschaft',
        'eigenschaften',
        'charakteristikum',
        'charakteristika',
        'charakteristisch',
        'charakteristische',
        'charakteristischer',
        'charakteristisches',
        'spezialität',
        'spezialitaet',
        'spezialitäten',
        'spezialitaeten',
        'spezialist',
        'spezialisten',
        'spezialistin',
        'spezialistinnen',
        'spezialisieren',
        'spezialisiert',
        'spezialisierte',
        'spezialisierter',
        'spezialisiertes',
        'spezialisierung',
        'spezialisierungen',
        'beruflich',
        'berufliche',
        'beruflicher',
        'berufliches',
        'beruf',
        'berufe',
        'lehrer',
        'lehrerin',
        'lehrerinnen',
        'unterrichten',
        'unterrichtet',
        'unterrichtete',
        'unterrichtetes',
        'unterricht',
        'lehre',
        'lehren',
        'lehrte',
        'gelehrt',
        'gelehrte',
        'gelehrter',
        'gelehrtes',
        'bildung',
        'bildungen',
        'bildungswesen',
        'ausbildung',
        'ausbildungen',
        'erziehung',
        'erziehen',
        'erzieht',
        'erzogen',
        'erzogene',
        'erzogener',
        'erzogenes',
        'erzieher',
        'erzieherin',
        'erzieherinnen',
        'student',
        'studenten',
        'studentin',
        'studentinnen',
        'studieren',
        'studiert',
        'studierte',
        'studierter',
        'studiertes',
        'studium',
        'studien',
        'studie',
        'forschung',
        'forschungen',
        'forschen',
        'forscht',
        'forschte',
        'geforscht',
        'forscher',
        'forscherin',
        'forscherinnen',
        'wissenschaft',
        'wissenschaften',
        'wissenschaftlich',
        'wissenschaftliche',
        'wissenschaftlicher',
        'wissenschaftliches',
        'wissenschaftler',
        'wissenschaftlerin',
        'wissenschaftlerinnen',
        'wissen',
        'weiss',
        'weiß',
        'gewusst',
        'wusste',
        'wissend',
        'wissende',
        'wissender',
        'wissendes',
        'kenntnis',
        'kenntnisse',
        'kennen',
        'kennt',
        'kannte',
        'gekannt',
        'bekannt',
        'bekannte',
        'bekannter',
        'bekanntes',
        'erkennen',
        'erkennt',
        'erkannte',
        'erkannt',
        'erkannte',
        'erkannter',
        'erkanntes',
        'erkenntnis',
        'erkenntnisse',
        'weisheit',
        'weise',
        'weisen',
        'wies',
        'gewiesen',
        'intelligent',
        'intelligente',
        'intelligenter',
        'intelligentes',
        'intelligenz',
        'talent',
        'talente',
        'talentiert',
        'talentierte',
        'talentierter',
        'talentiertes',
        'fähigkeit',
        'faehigkeit',
        'fähigkeiten',
        'faehigkeiten',
        'fähig',
        'faehig',
        'fähige',
        'faehige',
        'fähiger',
        'faehiger',
        'fähiges',
        'faehiges',
        'geschick',
        'geschickt',
        'geschickte',
        'geschickter',
        'geschicktes',
        'geschicklichkeit',
        'geschicklichkeiten',
        'fertigkeit',
        'fertigkeiten',
        'können',
        'koennen',
        'kann',
        'konnte',
        'gekonnt',
        'meister',
        'meisterin',
        'meisterinnen',
        'meisterschaft',
        'meisterschaften',
        'meistern',
        'meistert',
        'meisterte',
        'gemeistert',
        'bereich',
        'bereiche',
        'gebiet',
        'gebiete',
        'domain',
        'domäne',
        'domaene',
        'domänen',
        'domaenen',
        'beherrschen',
        'beherrscht',
        'beherrschte',
        'beherrschtes',
        'beherrschung',
        'kontrolle',
        'kontrollieren',
        'kontrolliert',
        'kontrollierte',
        'kontrolliertes',
        'verwaltung',
        'verwaltungen',
        'verwalten',
        'verwaltet',
        'verwaltete',
        'verwaltetes',
        'verwalter',
        'verwalterin',
        'verwalterinnen',
        'management',
        'managements',
        'managen',
        'gemanagt',
        'manager',
        'managerin',
        'managerinnen',
        'führung',
        'fuehrung',
        'führungen',
        'fuehrungen',
        'führen',
        'fuehren',
        'führt',
        'fuehrt',
        'führte',
        'fuehrte',
        'geführt',
        'gefuehrt',
        'führer',
        'fuehrer',
        'führerin',
        'fuehrerin',
        'führerinnen',
        'fuehrerinnen',
        'leitung',
        'leitungen',
        'leiten',
        'leitet',
        'leitete',
        'geleitet',
        'leiter',
        'leiterin',
        'leiterinnen',
        'organisation',
        'organisationen',
        'organisieren',
        'organisiert',
        'organisierte',
        'organisierter',
        'organisiertes',
        'system',
        'systeme',
        'systematisch',
        'systematische',
        'systematischer',
        'systematisches',
        'methode',
        'methoden',
        'methodisch',
        'methodische',
        'methodischer',
        'methodisches',
        'verfahren',
        'prozess',
        'prozesse',
        'prozessieren',
        'prozessiert',
        'prozessierte',
        'prozessiertes',
        'ablauf',
        'abläufe',
        'ablaeufe',
        'vorgang',
        'vorgänge',
        'vorgaenge',
        'procedere',
        'protokoll',
        'protokolle',
        'norm',
        'normen',
        'normieren',
        'normiert',
        'normierte',
        'normierter',
        'normiertes',
        'normal',
        'normale',
        'normaler',
        'normales',
        'normalität',
        'normalitaet',
        'standard',
        'standards',
        'standardisieren',
        'standardisiert',
        'standardisierte',
        'standardisierter',
        'standardisiertes',
        'regel',
        'regeln',
        'reglement',
        'reglements',
        'reglementieren',
        'reglementiert',
        'reglementierte',
        'reglementiertes',
        'regulieren',
        'reguliert',
        'regulierte',
        'reguliertes',
        'regular',
        'reguläre',
        'regulaere',
        'regulärer',
        'regulaerer',
        'reguläres',
        'regulaeres',
        'regelmäßig',
        'regelmaessig',
        'regelmäßige',
        'regelmaessige',
        'regelmäßiger',
        'regelmaessiger',
        'regelmäßiges',
        'regelmaessiges',
        'gesetz',
        'gesetze',
        'gesetzlich',
        'gesetzliche',
        'gesetzlicher',
        'gesetzliches',
        'legal',
        'legale',
        'legaler',
        'legales',
        'legalität',
        'legalitaet',
        'legitimität',
        'legitimiatet',
        'legitim',
        'legitime',
        'legitimer',
        'legitimes',
        'legitimieren',
        'legitimiert',
        'legitimierte',
        'legitimiertes',
        'recht',
        'rechte',
        'rechtlich',
        'rechtliche',
        'rechtlicher',
        'rechtliches',
        'rechtmäßig',
        'rechtmaessig',
        'rechtmäßige',
        'rechtmaessige',
        'rechtmäßiger',
        'rechtmaessiger',
        'rechtmäßiges',
        'rechtmaessiges',
        'gerechtigkeit',
        'gerecht',
        'gerechte',
        'gerechter',
        'gerechtes',
        'ungerecht',
        'ungerechte',
        'ungerechter',
        'ungerechtes',
        'ungerechtigkeit',
        'ungerechtigkeiten',
        'gericht',
        'gerichte',
        'richter',
        'richterin',
        'richterinnen',
        'richten',
        'richtet',
        'richtete',
        'gerichtet',
        'urteil',
        'urteile',
        'urteilen',
        'beurteilen',
        'beurteilt',
        'beurteilte',
        'beurteiltes',
        'beurteilung',
        'beurteilungen',
        'verurteilung',
        'verurteilungen',
        'verurteilen',
        'verurteilt',
        'verurteilte',
        'verurteiltes',
        'schuld',
        'schuldig',
        'schuldige',
        'schuldiger',
        'schuldiges',
        'strafe',
        'strafen',
        'bestrafen',
        'bestraft',
        'bestrafte',
        'bestrafter',
        'bestrafftes',
        'bestrafung',
        'bestrafungen',
        'gefängnis',
        'gefaengnis',
        'gefängnisse',
        'gefaengnisse',
        'knast',
        'einsperren',
        'eingesperrt',
        'eingesperrte',
        'eingesperrter',
        'eingesperrtes',
        'häftling',
        'haeftling',
        'häftlinge',
        'haeftlinge',
        'sträfling',
        'straefling',
        'sträflinge',
        'straeflinge',
        'inhaftiert',
        'inhaftierte',
        'inhaftierter',
        'inhaftiertes',
        'inhaftierung',
        'inhaftierungen',
        'festnahme',
        'festnahmen',
        'festnehmen',
        'festgenommen',
        'verhaftung',
        'verhaftungen',
        'verhaften',
        'verhaftet',
        'verhaftete',
        'verhafteter',
        'verhaftetes',
        'arrest',
        'arrestieren',
        'arrestiert',
        'arrestierte',
        'arrestiertes',
    ],
    
    'substitutions' => [
        '/ä/' => ['ä', 'a', 'ae', '@', '4'],
        '/ö/' => ['ö', 'o', 'oe', '0', 'ø'],
        '/ü/' => ['ü', 'u', 'ue'],
        '/ß/' => ['ß', 'ss', 's'],
        '/á/' => ['á', 'a', '@', '4'],
        '/à/' => ['à', 'a', '@', '4'],
        '/â/' => ['â', 'a', '@', '4'],
        '/ã/' => ['ã', 'a', '@', '4'],
        '/å/' => ['å', 'a', '@', '4'],
        '/æ/' => ['æ', 'ae', 'a'],
        '/é/' => ['é', 'e', '3', '€'],
        '/è/' => ['è', 'e', '3', '€'],
        '/ê/' => ['ê', 'e', '3', '€'],
        '/ë/' => ['ë', 'e', '3', '€'],
        '/í/' => ['í', 'i', '1', '!', '|'],
        '/ì/' => ['ì', 'i', '1', '!', '|'],
        '/î/' => ['î', 'i', '1', '!', '|'],
        '/ï/' => ['ï', 'i', '1', '!', '|'],
        '/ó/' => ['ó', 'o', '0', 'ø'],
        '/ò/' => ['ò', 'o', '0', 'ø'],
        '/ô/' => ['ô', 'o', '0', 'ø'],
        '/õ/' => ['õ', 'o', '0', 'ø'],
        '/ø/' => ['ø', 'o', '0'],
        '/ú/' => ['ú', 'u', 'ü'],
        '/ù/' => ['ù', 'u', 'ü'],
        '/û/' => ['û', 'u', 'ü'],
        '/u/' => ['u', 'ü', 'ù', 'ú', 'û', '@', '*'],
        '/c/' => ['c', 'k', 's', 'z'],
        '/k/' => ['k', 'c', 'ck'],
        '/ck/' => ['ck', 'k', 'c'],
        '/z/' => ['z', 's', 'tz'],
        '/tz/' => ['tz', 'z', 's'],
        '/pf/' => ['pf', 'f', 'p'],
        '/ph/' => ['ph', 'f'],
        '/sch/' => ['sch', 'sh', 'ch'],
        '/ch/' => ['ch', 'sh', 'x'],
        '/ie/' => ['ie', 'i', 'y'],
        '/ei/' => ['ei', 'ai', 'ey'],
        '/ai/' => ['ai', 'ei', 'ay'],
        '/au/' => ['au', 'aw', 'ou'],
        '/eu/' => ['eu', 'oi', 'oy'],
        '/äu/' => ['äu', 'aeu', 'oy'],
        '/dt/' => ['dt', 't', 'd'],
        '/st/' => ['st', 's', 't'],
    ]
];

================================================
FILE: config/languages/spanish.php
================================================
<?php

return [
    'severity' => [
        'mild' => [
            'maldito', 'maldita', 'maldición', 'maldicion', 'carajo',
            'hostia', 'hostias', 'jolines', 'joline', 'jobar', 'joroba',
            'caca', 'mear', 'meada', 'peo', 'pedorro', 'pedorra', 'pedos',
            'tonto', 'tonta', 'bobo', 'boba', 'baboso', 'babosa',
            'cursi', 'pesado', 'pesada', 'latoso', 'latosa',
        ],
        'moderate' => [
            'cabrón', 'cabron', 'cabrona', 'cabrones', 'cabronazo',
            'perra', 'zorra', 'gilipollas', 'gilipolla',
            'imbécil', 'imbecil', 'idiota', 'estúpido', 'estupido', 'estúpida', 'estupida',
            'pendejo', 'pendeja', 'mamón', 'mamon',
            'boludo', 'boluda', 'pelotudo', 'pelotuda',
            'culo', 'ojete', 'putilla', 'putita',
            'capullo', 'coñazo', 'conazo', 'putada',
        ],
        'high' => [
            'mierda', 'joder', 'coño', 'puta', 'puto',
            'chingar', 'chingado', 'chingada', 'pinche',
            'verga', 'follar', 'follada', 'follando',
            'hijo de puta', 'hijoputa', 'concha', 'cojones',
        ],
        'extreme' => [
            'maricón', 'maricon', 'marica', 'maricona', 'mariconazo',
            'tortillera', 'bollera',
            'retrasado', 'retrasada', 'retardado', 'retardada',
            'mongoloide', 'subnormal',
        ],
    ],

    'profanities' => [
        // Common Spanish profanities and vulgar expressions
        'mierda',
        'joder',
        'coño',
        'cabrón',
        'cabron',
        'puta',
        'puto',
        'jodido',
        'jodida',
        'hijo de puta',
        'hijoputa',
        'gilipollas',
        'gilipolla',
        'imbécil',
        'imbecil',
        'idiota',
        'estúpido',
        'estupido',
        'pendejo',
        'pendeja',
        'mamón',
        'mamon',
        'mamada',
        'chingar',
        'chingas',
        'chingado',
        'chingada',
        'pinche',
        'verga',
        'carajo',
        'cojones',
        'huevos',
        'huevón',
        'huevon',
        'maricón',
        'maricon',
        'marica',
        'homosexual',
        'tortillera',
        'bollera',
        'follar',
        'folla',
        'follada',
        'follando',
        'culiar',
        'culear',
        'culo',
        'ojete',
        'concha',
        'chocha',
        'chochito',
        'chucha',
        'almeja',
        'zorra',
        'zorro',
        'putilla',
        'putita',
        'perra',
        'perro',
        'cabrona',
        'cabrones',
        'puton',
        'putón',
        'putona',
        'putañero',
        'putanero',
        'polla',
        'picha',
        'rabo',
        'nabo',
        'cipote',
        'chorizo',
        'salchicha',
        'salchichón',
        'salchichon',
        'miembro',
        'pene',
        'pijo',
        'capullo',
        'caput',
        'gusano',
        'rata',
        'caca',
        'mear',
        'meada',
        'orín',
        'orin',
        'orina',
        'orinarse',
        'cagar',
        'cagada',
        'cagarse',
        'cagón',
        'cagon',
        'cagona',
        'culiacan',
        'culiao',
        'culiado',
        'culero',
        'culera',
        'nalgas',
        'trasero',
        'pompis',
        'pompas',
        'tetona',
        'tetuda',
        'tetas',
        'pechos',
        'chichonas',
        'chichona',
        'zángano',
        'zangano',
        'cabronazo',
        'hijoelagranputa',
        'hijoeputa',
        'malparido',
        'malparida',
        'desgraciado',
        'desgraciada',
        'sinvergüenza',
        'sinverguenza',
        'cochino',
        'cochina',
        'guarro',
        'guarra',
        'sucio',
        'sucia',
        'asqueroso',
        'asquerosa',
        'repugnante',
        'vomitivo',
        'vomitiva',
        'nauseabundo',
        'nauseabunda',
        'escoria',
        'basura',
        'porquería',
        'porqueria',
        'maldito',
        'maldita',
        'condenado',
        'condenada',
        'jodón',
        'jodon',
        'jodona',
        'molesto',
        'molesta',
        'fastidioso',
        'fastidiosa',
        'cabronazo',
        'maricona',
        'mariconazo',
        'bolludo',
        'bolluda',
        'boludo',
        'boluda',
        'pelotudo',
        'pelotuda',
        'tarado',
        'tarada',
        'retrasado',
        'retrasada',
        'retardado',
        'retardada',
        'mongoloide',
        'subnormal',
        'anormal',
        'deficiente',
        'tonto',
        'tonta',
        'bobo',
        'boba',
        'baboso',
        'babosa',
        'babas',
        'ñoño',
        'ñona',
        'cursi',
        'ridículo',
        'ridiculo',
        'ridícula',
        'ridicula',
        'estúpida',
        'estupida',
        'gorda',
        'gordo',
        'gordinflas',
        'gordinfla',
        'ballena',
        'vaca',
        'cerda',
        'cerdo',
        'chancho',
        'chancha',
        'marrano',
        'marrana',
        'cochino',
        'cochina',
        'puerco',
        'puerca',
        'animal',
        'bestia',
        'salvaje',
        'bárbaro',
        'barbaro',
        'bárbara',
        'barbara',
        'bruto',
        'bruta',
        'burro',
        'burra',
        'asno',
        'asna',
        'mula',
        'mulo',
        'bestia',
        'fiera',
        'demonio',
        'diablo',
        'diabla',
        'satanás',
        'satanas',
        'lucifer',
        'maldición',
        'maldicion',
        'carajo',
        'hostia',
        'hostias',
        'jolines',
        'joline',
        'jobar',
        'joroba',
        'cojonudo',
        'cojonuda',
        'cojudo',
        'cojuda',
        'acojonante',
        'descojonarse',
        'descojonar',
        'tocapelotas',
        'tocacojones',
        'rompepelotas',
        'rompecojones',
        'pelmazos',
        'pelmazo',
        'pelma',
        'plasta',
        'pesado',
        'pesada',
        'pesao',
        'pesá',
        'latoso',
        'latosa',
        'coñazo',
        'conazo',
        'putada',
        'jodienda',
        'follón',
        'follon',
        'lío',
        'lio',
        'marrón',
        'marron',
        'peo',
        'pedorro',
        'pedorra',
        'pedos',
        'ventosidad',
        'flatulencia',
        'gases',
        'tirarse pedos',
        'echar pedos',
        'soltar pedos',
        'heder',
        'apestar',
        'oler mal',
        'tufo',
        'peste',
        'pestilencia',
        'putrefacción',
        'putrefaccion',
        'putrefacto',
        'putrefacta',
        'podrido',
        'podrida',
        'rancio',
        'rancia',
        'agrio',
        'agria',
        'amargo',
        'amarga',
        'salado',
        'salada',
        'soso',
        'sosa',
        'insípido',
        'insipido',
        'insípida',
        'insipida',
        'desabrido',
        'desabrida',
        'malo',
        'mala',
        'malísimo',
        'malisimo',
        'malísima',
        'malisima',
        'pésimo',
        'pesimo',
        'pésima',
        'pesima',
        'horrible',
        'horroroso',
        'horrorosa',
        'terrorífico',
        'terrorifico',
        'terrorífica',
        'terrorifica',
        'espantoso',
        'espantosa',
        'horripilante',
        'espeluznante',
        'escalofriante',
        'siniestro',
        'siniestra',
        'tenebroso',
        'tenebrosa',
        'lúgubre',
        'lugubre',
        'sombrío',
        'sombrio',
        'sombría',
        'sombria',
        'triste',
        'melancólico',
        'melancolico',
        'melancólica',
        'melancolica',
        'deprimido',
        'deprimida',
        'depresivo',
        'depresiva',
        'suicida',
        'morir',
        'muerte',
        'muerto',
        'muerta',
        'cadáver',
        'cadaver',
        'difunto',
        'difunta',
        'finado',
        'finada',
        'fallecido',
        'fallecida',
        'occiso',
        'occisa',
        'fiambre',
        'estirar la pata',
        'diñar',
        'dinar',
        'palmar',
        'pelar',
        'espichar',
        'fenecer',
        'expirar',
        'perecer',
        'sucumbir',
        'fallecer',
        'óbito',
        'obito',
        'defunción',
        'defuncion',
        'deceso',
        'tránsito',
        'transito',
        'partida',
        'despedida',
        'adiós',
        'adios',
        'hasta la vista',
        'hasta luego',
        'hasta pronto',
        'hasta mañana',
        'hasta manana',
        'chau',
        'chao',
        'bye',
        'goodbye',
    ],
    
    'false_positives' => [
        // Common Spanish words that might be detected as false positives
        'análisis',
        'analisis',
        'clase',
        'clases',
        'paso',
        'pasos',
        'expresión',
        'expresion',
        'expresiones',
        'asesino',
        'asesina',
        'asesinar',
        'asesinato',
        'empresa',
        'empresas',
        'empresario',
        'empresaria',
        'negocio',
        'negocios',
        'trabajo',
        'trabajos',
        'trabajar',
        'trabajador',
        'trabajadora',
        'empleo',
        'empleos',
        'empleado',
        'empleada',
        'empleador',
        'empleadora',
        'oficina',
        'oficinas',
        'oficinista',
        'escritorio',
        'escritorios',
        'computadora',
        'computadoras',
        'computador',
        'computadores',
        'ordenador',
        'ordenadores',
        'máquina',
        'maquina',
        'máquinas',
        'maquinas',
        'aparato',
        'aparatos',
        'dispositivo',
        'dispositivos',
        'instrumento',
        'instrumentos',
        'herramienta',
        'herramientas',
        'útil',
        'util',
        'útiles',
        'utiles',
        'utilidad',
        'utilidades',
        'función',
        'funcion',
        'funciones',
        'funcional',
        'funcionalidad',
        'funcionalidades',
        'característico',
        'caracteristico',
        'característica',
        'caracteristica',
        'características',
        'caracteristicas',
        'especialidad',
        'especialidades',
        'especialista',
        'especialistas',
        'especializar',
        'especializado',
        'especializada',
        'especialización',
        'especializacion',
        'profesional',
        'profesionales',
        'profesión',
        'profesion',
        'profesiones',
        'profesor',
        'profesora',
        'profesores',
        'profesoras',
        'enseñar',
        'ensenar',
        'enseñanza',
        'ensenanza',
        'enseñanzas',
        'ensenanzas',
        'educación',
        'educacion',
        'educativo',
        'educativa',
        'educativos',
        'educativas',
        'educar',
        'educado',
        'educada',
        'educador',
        'educadora',
        'educadores',
        'educadoras',
        'estudiante',
        'estudiantes',
        'estudiar',
        'estudio',
        'estudios',
        'estudiado',
        'estudiada',
        'investigación',
        'investigacion',
        'investigaciones',
        'investigar',
        'investigador',
        'investigadora',
        'investigadores',
        'investigadoras',
        'científico',
        'cientifico',
        'científica',
        'cientifica',
        'científicos',
        'cientificos',
        'científicas',
        'cientificas',
        'ciencia',
        'ciencias',
        'conocimiento',
        'conocimientos',
        'conocer',
        'conocido',
        'conocida',
        'conocidos',
        'conocidas',
        'saber',
        'sabido',
        'sabida',
        'sabidos',
        'sabidas',
        'sabiduría',
        'sabiduria',
        'sabio',
        'sabia',
        'sabios',
        'sabias',
        'inteligente',
        'inteligentes',
        'inteligencia',
        'inteligencias',
        'talento',
        'talentos',
        'talentoso',
        'talentosa',
        'talentosos',
        'talentosas',
        'habilidad',
        'habilidades',
        'hábil',
        'habil',
        'hábiles',
        'habiles',
        'destreza',
        'destrezas',
        'destro',
        'destra',
        'diestro',
        'diestra',
        'diestros',
        'diestras',
        'maestro',
        'maestra',
        'maestros',
        'maestras',
        'maestría',
        'maestria',
        'maestrías',
        'maestrias',
        'dominio',
        'dominios',
        'dominar',
        'dominado',
        'dominada',
        'dominados',
        'dominadas',
        'control',
        'controles',
        'controlar',
        'controlado',
        'controlada',
        'controlados',
        'controladas',
        'administración',
        'administracion',
        'administrar',
        'administrador',
        'administradora',
        'administradores',
        'administradoras',
        'gestión',
        'gestion',
        'gestiones',
        'gestionar',
        'gestor',
        'gestora',
        'gestores',
        'gestoras',
        'organización',
        'organizacion',
        'organizaciones',
        'organizar',
        'organizador',
        'organizadora',
        'organizadores',
        'organizadoras',
        'sistema',
        'sistemas',
        'sistemático',
        'sistematico',
        'sistemática',
        'sistematica',
        'sistemáticos',
        'sistematicos',
        'sistemáticas',
        'sistematicas',
        'método',
        'metodo',
        'métodos',
        'metodos',
        'metodología',
        'metodologia',
        'metodologías',
        'metodologias',
        'proceso',
        'procesos',
        'procesar',
        'procesado',
        'procesada',
        'procesados',
        'procesadas',
        'procedimiento',
        'procedimientos',
        'proceder',
        'protocolo',
        'protocolos',
        'norma',
        'normas',
        'normal',
        'normales',
        'normalidad',
        'normalidades',
        'normalizar',
        'normalizado',
        'normalizada',
        'normalizados',
        'normalizadas',
        'estándar',
        'estandar',
        'estándares',
        'estandares',
        'estandarizar',
        'estandarizado',
        'estandarizada',
        'estandarizados',
        'estandarizadas',
        'regla',
        'reglas',
        'reglamento',
        'reglamentos',
        'reglamentar',
        'reglamentario',
        'reglamentaria',
        'reglamentarios',
        'reglamentarias',
        'regular',
        'regulares',
        'regularidad',
        'regularidades',
        'regularizar',
        'regularizado',
        'regularizada',
        'regularizados',
        'regularizadas',
        'ley',
        'leyes',
        'legal',
        'legales',
        'legalidad',
        'legalidades',
        'legalizar',
        'legalizado',
        'legalizada',
        'legalizados',
        'legalizadas',
        'derecho',
        'derechos',
        'jurídico',
        'juridico',
        'jurídica',
        'juridica',
        'jurídicos',
        'juridicos',
        'jurídicas',
        'juridicas',
        'justicia',
        'justicias',
        'justo',
        'justa',
        'justos',
        'justas',
        'injusto',
        'injusta',
        'injustos',
        'injustas',
        'injusticia',
        'injusticias',
        'tribunal',
        'tribunales',
        'juez',
        'jueces',
        'jueza',
        'juezas',
        'juzgar',
        'juzgado',
        'juzgada',
        'juzgados',
        'juzgadas',
        'sentencia',
        'sentencias',
        'sentenciar',
        'sentenciado',
        'sentenciada',
        'sentenciados',
        'sentenciadas',
        'condena',
        'condenas',
        'condenar',
        'condenado',
        'condenada',
        'condenados',
        'condenadas',
        'castigo',
        'castigos',
        'castigar',
        'castigado',
        'castigada',
        'castigados',
        'castigadas',
        'pena',
        'penas',
        'penar',
        'penado',
        'penada',
        'penados',
        'penadas',
        'prisión',
        'prision',
        'prisiones',
        'cárcel',
        'carcel',
        'cárceles',
        'carceles',
        'encarcelar',
        'encarcelado',
        'encarcelada',
        'encarcelados',
        'encarceladas',
        'preso',
        'presa',
        'presos',
        'presas',
        'presidio',
        'presidios',
        'penitenciaría',
        'penitenciaria',
        'penitenciarías',
        'penitenciarias',
        'reformatorio',
        'reformatorios',
    ],
    
    'substitutions' => [
        '/ñ/' => ['ñ', 'n', '~n', 'ni'],
        '/á/' => ['á', 'a', '@', '4'],
        '/é/' => ['é', 'e', '3', '€'],
        '/í/' => ['í', 'i', '1', '!', '|'],
        '/ó/' => ['ó', 'o', '0', 'ø'],
        '/ú/' => ['ú', 'u', 'ü'],
        '/ü/' => ['ü', 'u', 'ú'],
        '/u/' => ['u', 'ú', 'ü', '@', '*'],
        '/c/' => ['c', 'k', 'ç'],
        '/ll/' => ['ll', 'y', 'i'],
        '/rr/' => ['rr', 'r'],
        '/ch/' => ['ch', 'x'],
        '/z/' => ['z', 's', 'c'],
        '/j/' => ['j', 'x', 'h'],
        '/g/' => ['g', 'j', 'h'],
        '/b/' => ['b', 'v', 'w'],
        '/v/' => ['v', 'b', 'w'],
    ]
];

================================================
FILE: phpunit.xml
================================================
<phpunit bootstrap="vendor/autoload.php" colors="true">
    <testsuites>
        <testsuite name="Application Tests">
            <directory>./tests</directory>
        </testsuite>
    </testsuites>

    <php>
        <env name="APP_ENV" value="testing"/>
    </php>
</phpunit>


================================================
FILE: src/BlaspManager.php
================================================
<?php

namespace Blaspsoft\Blasp;

use Closure;
use Illuminate\Contracts\Foundation\Application;
use Blaspsoft\Blasp\Core\Contracts\DriverInterface;
use Blaspsoft\Blasp\Drivers\RegexDriver;
use Blaspsoft\Blasp\Drivers\PatternDriver;
use Blaspsoft\Blasp\Drivers\PhoneticDriver;
use Blaspsoft\Blasp\Drivers\PipelineDriver;
use InvalidArgumentException;

class BlaspManager
{
    protected Application $app;
    protected array $drivers = [];
    protected array $customCreators = [];

    public function __construct(Application $app)
    {
        $this->app = $app;
    }

    public function driver(?string $driver = null): PendingCheck
    {
        return $this->newPendingCheck()->driver($driver ?? $this->getDefaultDriver());
    }

    public function resolveDriver(string $name): DriverInterface
    {
        if (!isset($this->drivers[$name])) {
            $this->drivers[$name] = $this->createDriver($name);
        }

        return $this->drivers[$name];
    }

    protected function createDriver(string $name): DriverInterface
    {
        if (isset($this->customCreators[$name])) {
            return ($this->customCreators[$name])($this->app);
        }

        $method = 'create' . ucfirst($name) . 'Driver';
        if (method_exists($this, $method)) {
            return $this->$method();
        }

        throw new InvalidArgumentException("Driver [{$name}] not supported.");
    }

    public function createRegexDriver(): DriverInterface
    {
        return new RegexDriver();
    }

    public function createPatternDriver(): DriverInterface
    {
        return new PatternDriver();
    }

    public function createPhoneticDriver(): DriverInterface
    {
        $config = $this->app['config']->get('blasp.drivers.phonetic', []);

        return new PhoneticDriver(
            phonemes: $config['phonemes'] ?? 4,
            minWordLength: $config['min_word_length'] ?? 3,
            maxDistanceRatio: $config['max_distance_ratio'] ?? 0.6,
            phoneticFalsePositives: $config['false_positives'] ?? [],
            supportedLanguages: $config['supported_languages'] ?? ['english'],
        );
    }

    public function createPipelineDriver(): DriverInterface
    {
        $config = $this->app['config']->get('blasp.drivers.pipeline', []);
        $driverNames = $config['drivers'] ?? ['regex', 'phonetic'];

        if (!is_array($driverNames)) {
            throw new InvalidArgumentException('blasp.drivers.pipeline.drivers must be an array of driver names.');
        }

        foreach ($driverNames as $name) {
            if (!is_string($name) || trim($name) === '') {
                throw new InvalidArgumentException('Each pipeline driver name must be a non-empty string.');
            }

            if (strtolower(trim($name)) === 'pipeline') {
                throw new InvalidArgumentException('Pipeline driver cannot contain itself. Remove "pipeline" from blasp.drivers.pipeline.drivers.');
            }
        }

        $resolvedDrivers = array_map(
            fn (string $name) => $this->resolveDriver($name),
            $driverNames,
        );

        return new PipelineDriver($resolvedDrivers);
    }

    public function extend(string $driver, Closure $callback): self
    {
        $this->customCreators[$driver] = $callback;
        return $this;
    }

    public function getDefaultDriver(): string
    {
        return $this->app['config']->get('blasp.default', 'regex');
    }

    public function newPendingCheck(): PendingCheck
    {
        return new PendingCheck($this);
    }

    public function pipeline(string ...$drivers): PendingCheck
    {
        return $this->newPendingCheck()->pipeline(...$drivers);
    }

    // --- Shortcut methods that create PendingCheck ---

    public function check(?string $text): \Blaspsoft\Blasp\Core\Result
    {
        return $this->newPendingCheck()->check($text);
    }

    public function checkMany(array $texts): array
    {
        return $this->newPendingCheck()->checkMany($texts);
    }

    public function __call(string $method, array $parameters): mixed
    {
        return $this->newPendingCheck()->$method(...$parameters);
    }

    public function getApp(): Application
    {
        return $this->app;
    }
}


================================================
FILE: src/BlaspServiceProvider.php
================================================
<?php

namespace Blaspsoft\Blasp;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
class BlaspServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__ . '/../config/blasp.php' => config_path('blasp.php'),
            ], 'blasp-config');

            $this->publishes([
                __DIR__ . '/../config/languages' => config_path('languages'),
            ], 'blasp-languages');

            $this->publishes([
                __DIR__ . '/../config/blasp.php' => config_path('blasp.php'),
                __DIR__ . '/../config/languages' => config_path('languages'),
            ], 'blasp');

            $this->commands([
                Console\ClearCommand::class,
                Console\TestCommand::class,
                Console\LanguagesCommand::class,
            ]);
        }

        $this->registerValidationRule();
        $this->registerMiddlewareAlias();
        $this->registerBladeDirectives();
        $this->registerStringMacros();
    }

    public function register(): void
    {
        $this->mergeConfigFrom(__DIR__ . '/../config/blasp.php', 'blasp');

        $this->app->singleton('blasp', function ($app) {
            return new BlaspManager($app);
        });

        $this->app->alias('blasp', BlaspManager::class);
    }

    protected function registerValidationRule(): void
    {
        $this->app['validator']->extend('blasp_check', function ($attribute, $value, $parameters) {
            if (!is_string($value) || $value === '') {
                return true;
            }

            $language = $parameters[0] ?? config('blasp.language', config('blasp.default_language', 'english'));

            $manager = $this->app->make('blasp');

            $result = $manager->in($language)->check($value);

            return !$result->isOffensive();
        }, 'The :attribute contains profanity.');
    }

    protected function registerMiddlewareAlias(): void
    {
        $this->app['router']->aliasMiddleware('blasp', Middleware\CheckProfanity::class);
    }

    protected function registerBladeDirectives(): void
    {
        Blade::directive('clean', function (string $expression) {
            return "<?php echo e(app('blasp')->check({$expression})->clean()); ?>";
        });
    }

    protected function registerStringMacros(): void
    {
        Str::macro('isProfane', function (string $text): bool {
            return app('blasp')->check($text)->isOffensive();
        });

        Str::macro('cleanProfanity', function (string $text): string {
            return app('blasp')->check($text)->clean();
        });

        Stringable::macro('isProfane', function (): bool {
            return app('blasp')->check((string) $this)->isOffensive();
        });

        Stringable::macro('cleanProfanity', function (): Stringable {
            return new Stringable(app('blasp')->check((string) $this)->clean());
        });
    }
}


================================================
FILE: src/Blaspable.php
================================================
<?php

namespace Blaspsoft\Blasp;

use Closure;
use Blaspsoft\Blasp\Core\Result;
use Blaspsoft\Blasp\Events\ModelProfanityDetected;
use Blaspsoft\Blasp\Exceptions\ProfanityRejectedException;
use Illuminate\Database\Eloquent\Model;

/**
 * @mixin \Illuminate\Database\Eloquent\Model
 *
 * @property array $blaspable
 * @property string $blaspMode
 * @property string|null $blaspLanguage
 * @property string|null $blaspMask
 */
trait Blaspable
{
    protected static bool $blaspCheckingDisabled = false;

    /** @var array<string, Result> */
    protected array $blaspResultsCache = [];

    public static function bootBlaspable(): void
    {
        static::saving(function (Model $model) {
            if (static::$blaspCheckingDisabled) {
                return;
            }

            $model->blaspResultsCache = [];

            $attributes = $model->blaspable ?? [];
            $dirty = $model->getDirty();
            $mode = $model->blaspMode ?? config('blasp.model.mode', 'sanitize');

            foreach ($attributes as $attr) {
                if (!isset($dirty[$attr]) || !is_string($dirty[$attr])) {
                    continue;
                }

                /** @var PendingCheck $check */
                $check = app('blasp')->newPendingCheck();

                if ($lang = ($model->blaspLanguage ?? null)) {
                    $check = $check->in($lang);
                }

                if ($mask = ($model->blaspMask ?? null)) {
                    $check = $check->mask($mask);
                }

                $result = $check->check($dirty[$attr]);
                $model->blaspResultsCache[$attr] = $result;

                if ($result->isOffensive()) {
                    event(new ModelProfanityDetected($model, $attr, $result));

                    if ($mode === 'reject') {
                        throw ProfanityRejectedException::forModel($model, $attr, $result);
                    }

                    $model->setAttribute($attr, $result->clean());
                }
            }
        });
    }

    public function hadProfanity(): bool
    {
        foreach ($this->blaspResultsCache as $result) {
            if ($result->isOffensive()) {
                return true;
            }
        }

        return false;
    }

    /** @return array<string, Result> */
    public function blaspResults(): array
    {
        return $this->blaspResultsCache;
    }

    public function blaspResult(string $attribute): ?Result
    {
        return $this->blaspResultsCache[$attribute] ?? null;
    }

    public static function withoutBlaspChecking(Closure $callback): mixed
    {
        $previousState = static::$blaspCheckingDisabled;
        static::$blaspCheckingDisabled = true;

        try {
            return $callback();
        } finally {
            static::$blaspCheckingDisabled = $previousState;
        }
    }
}


================================================
FILE: src/Console/ClearCommand.php
================================================
<?php

namespace Blaspsoft\Blasp\Console;

use Illuminate\Console\Command;
use Blaspsoft\Blasp\Core\Dictionary;

class ClearCommand extends Command
{
    protected $signature = 'blasp:clear';
    protected $description = 'Clear the Blasp profanity cache';

    public function handle(): void
    {
        Dictionary::clearCache();
        $this->info('Blasp cache cleared successfully!');
    }
}


================================================
FILE: src/Console/LanguagesCommand.php
================================================
<?php

namespace Blaspsoft\Blasp\Console;

use Illuminate\Console\Command;
use Blaspsoft\Blasp\Core\Dictionary;

class LanguagesCommand extends Command
{
    protected $signature = 'blasp:languages';
    protected $description = 'List available languages and their word counts';

    public function handle(): void
    {
        $languages = Dictionary::getAvailableLanguages();

        $rows = [];
        foreach ($languages as $language) {
            $config = Dictionary::loadLanguageConfig($language);
            $profanityCount = count($config['profanities'] ?? []);
            $falsePositiveCount = count($config['false_positives'] ?? []);
            $hasSeverity = isset($config['severity']) ? 'Yes' : 'No';

            $rows[] = [
                ucfirst($language),
                $profanityCount,
                $falsePositiveCount,
                $hasSeverity,
            ];
        }

        $this->table(['Language', 'Profanities', 'False Positives', 'Severity Map'], $rows);
    }
}


================================================
FILE: src/Console/TestCommand.php
================================================
<?php

namespace Blaspsoft\Blasp\Console;

use Illuminate\Console\Command;

class TestCommand extends Command
{
    protected $signature = 'blasp:test {text} {--lang= : Language to check against} {--detail}';
    protected $description = 'Test profanity detection on a given text';

    public function handle(): void
    {
        $text = $this->argument('text');
        $language = $this->option('lang') ?? config('blasp.language', config('blasp.default_language', 'english'));

        $manager = app('blasp');
        $result = $manager->in($language)->check($text);

        $this->info("Input: {$text}");
        $this->info("Language: {$language}");
        $this->newLine();

        if ($result->isOffensive()) {
            $this->error('Profanity detected!');
            $this->table(
                ['Property', 'Value'],
                [
                    ['Clean text', $result->clean()],
                    ['Score', $result->score()],
                    ['Count', $result->count()],
                    ['Severity', $result->severity()?->value ?? 'n/a'],
                    ['Unique words', implode(', ', $result->uniqueWords())],
                ]
            );

            if ($this->option('detail')) {
                $this->newLine();
                $this->info('Matched words:');
                $rows = [];
                foreach ($result->words() as $word) {
                    $rows[] = [
                        $word->text,
                        $word->base,
                        $word->severity->value,
                        $word->position,
                        $word->length,
                    ];
                }
                $this->table(['Text', 'Base', 'Severity', 'Position', 'Length'], $rows);
            }
        } else {
            $this->info('No profanity detected. Text is clean.');
        }
    }
}


================================================
FILE: src/Core/Analyzer.php
================================================
<?php

namespace Blaspsoft\Blasp\Core;

use Blaspsoft\Blasp\Core\Contracts\DriverInterface;
use Blaspsoft\Blasp\Core\Contracts\MaskStrategyInterface;
use Blaspsoft\Blasp\Core\Masking\CharacterMask;

class Analyzer
{
    public function analyze(
        string $text,
        DriverInterface $driver,
        Dictionary $dictionary,
        ?MaskStrategyInterface $mask = null,
        array $options = [],
    ): Result {
        $mask = $mask ?? new CharacterMask(config('blasp.mask', config('blasp.mask_character', '*')));

        // Strip invisible Unicode format characters (zero-width spaces, invisible separators, etc.)
        // before any driver sees the text, ensuring consistent positions across pipeline drivers
        $text = preg_replace('/\p{Cf}/u', '', $text) ?? $text;

        return $driver->detect($text, $dictionary, $mask, $options);
    }
}


================================================
FILE: src/Core/Contracts/DriverInterface.php
================================================
<?php

namespace Blaspsoft\Blasp\Core\Contracts;

use Blaspsoft\Blasp\Core\Dictionary;
use Blaspsoft\Blasp\Core\Result;

interface DriverInterface
{
    public function detect(string $text, Dictionary $dictionary, MaskStrategyInterface $mask, array $options = []): Result;
}


================================================
FILE: src/Core/Contracts/MaskStrategyInterface.php
================================================
<?php

namespace Blaspsoft\Blasp\Core\Contracts;

interface MaskStrategyInterface
{
    public function mask(string $word, int $length): string;
}


================================================
FILE: src/Core/Dictionary.php
================================================
<?php

namespace Blaspsoft\Blasp\Core;

use Blaspsoft\Blasp\Enums\Severity;
use Blaspsoft\Blasp\Core\Matchers\RegexMatcher;
use Blaspsoft\Blasp\Core\Normalizers\StringNormalizer;
use Blaspsoft\Blasp\Core\Normalizers\EnglishNormalizer;
use Blaspsoft\Blasp\Core\Normalizers\SpanishNormalizer;
use Blaspsoft\Blasp\Core\Normalizers\GermanNormalizer;
use Blaspsoft\Blasp\Core\Normalizers\FrenchNormalizer;
use Illuminate\Support\Facades\Cache;

class Dictionary
{
    private const CACHE_TTL = 86400;

    private array $profanities;
    private array $falsePositives;
    private array $separators;
    private array $substitutions;
    private array $severityMap;
    private array $profanityExpressions;
    private StringNormalizer $normalizer;
    private array $allowList;
    private array $blockList;
    private string $language;

    private static array $normalizers = [];

    public function __construct(
        array $profanities,
        array $falsePositives,
        array $separators,
        array $substitutions,
        array $severityMap,
        StringNormalizer $normalizer,
        array $allowList = [],
        array $blockList = [],
        string $language = 'english',
        ?array $profanityExpressions = null,
    ) {
        $this->profanities = $profanities;
        $this->falsePositives = $falsePositives;
        $this->separators = $separators;
        $this->substitutions = $substitutions;
        $this->severityMap = $severityMap;
        $this->normalizer = $normalizer;
        $this->allowList = array_map('strtolower', $allowList);
        $this->blockList = array_map('strtolower', $blockList);
        $this->language = $language;

        // Apply block list — add extra words to profanities
        foreach ($this->blockList as $word) {
            if (!in_array($word, $this->profanities)) {
                $this->profanities[] = $word;
                $this->severityMap[$word] = Severity::High;
            }
        }

        // Remove allow-listed words
        if (!empty($this->allowList)) {
            $this->profanities = array_values(array_filter(
                $this->profanities,
                fn($p) => !in_array(strtolower($p), $this->allowList)
            ));
        }

        if ($profanityExpressions !== null) {
            $this->profanityExpressions = $profanityExpressions;
        } else {
            $this->profanityExpressions = (new RegexMatcher())->generateExpressions(
                $this->profanities,
                $this->separators,
                $this->substitutions
            );
        }
    }

    public static function forLanguage(string $language, array $options = []): self
    {
        if (!preg_match('/^[a-zA-Z0-9_-]+$/', $language)) {
            return new self(
                profanities: [],
                falsePositives: [],
                separators: [],
                substitutions: [],
                severityMap: [],
                normalizer: new EnglishNormalizer(),
                language: $language,
            );
        }

        $config = self::loadLanguageConfig($language);
        $globalConfig = self::loadGlobalConfig();

        $profanities = $config['profanities'] ?? [];
        $falsePositives = $config['false_positives'] ?? [];
        $severityMap = self::buildSeverityMap($config);

        $substitutions = $globalConfig['substitutions'] ?? [];
        if (isset($config['substitutions']) && is_array($config['substitutions'])) {
            foreach ($config['substitutions'] as $pattern => $values) {
                if (is_array($values)) {
                    $substitutions[$pattern] = array_values(array_unique(array_merge(
                        $substitutions[$pattern] ?? [],
                        $values
                    )));
                }
            }
        }

        return new self(
            profanities: $profanities,
            falsePositives: $falsePositives,
            separators: $globalConfig['separators'] ?? [],
            substitutions: $substitutions,
            severityMap: $severityMap,
            normalizer: self::getNormalizerForLanguage($language),
            allowList: $options['allow'] ?? [],
            blockList: $options['block'] ?? [],
            language: $language,
        );
    }

    public static function forLanguages(array $languages, array $options = []): self
    {
        $allProfanities = [];
        $allFalsePositives = [];
        $allSeverityMap = [];
        $globalConfig = self::loadGlobalConfig();
        $substitutions = $globalConfig['substitutions'] ?? [];

        foreach ($languages as $language) {
            if (!preg_match('/^[a-zA-Z0-9_-]+$/', $language)) {
                continue;
            }
            $config = self::loadLanguageConfig($language);
            $allProfanities = array_merge($allProfanities, $config['profanities'] ?? []);
            $allFalsePositives = array_merge($allFalsePositives, $config['false_positives'] ?? []);
            $allSeverityMap = array_merge($allSeverityMap, self::buildSeverityMap($config));

            // Merge accent/diacritic substitutions only
            if (isset($config['substitutions']) && is_array($config['substitutions'])) {
                foreach ($config['substitutions'] as $pattern => $values) {
                    if (is_array($values)) {
                        $plainKey = trim($pattern, '/');
                        if (mb_strlen($plainKey, 'UTF-8') > 1 || preg_match('/^[a-zA-Z]$/', $plainKey)) {
                            continue;
                        }
                        $substitutions[$pattern] = array_values(array_unique(array_merge(
                            $substitutions[$pattern] ?? [],
                            $values
                        )));
                    }
                }
            }
        }

        return new self(
            profanities: array_values(array_unique($allProfanities)),
            falsePositives: array_values(array_unique($allFalsePositives)),
            separators: $globalConfig['separators'] ?? [],
            substitutions: $substitutions,
            severityMap: $allSeverityMap,
            normalizer: self::getNormalizerForLanguage('english'),
            allowList: $options['allow'] ?? [],
            blockList: $options['block'] ?? [],
            language: implode(',', $languages),
        );
    }

    public static function forAllLanguages(array $options = []): self
    {
        $languages = self::getAvailableLanguages();
        return self::forLanguages($languages, $options);
    }

    public function getProfanities(): array
    {
        return $this->profanities;
    }

    public function getFalsePositives(): array
    {
        return $this->falsePositives;
    }

    public function getProfanityExpressions(): array
    {
        return $this->profanityExpressions;
    }

    public function getSeverity(string $word): Severity
    {
        $lower = strtolower($word);
        return $this->severityMap[$lower] ?? Severity::High;
    }

    public function getNormalizer(): StringNormalizer
    {
        return $this->normalizer;
    }

    public function getLanguage(): string
    {
        return $this->language;
    }

    public function getSeparators(): array
    {
        return $this->separators;
    }

    public function getSubstitutions(): array
    {
        return $this->substitutions;
    }

    // --- Static helpers ---

    public static function getAvailableLanguages(): array
    {
        $possiblePaths = [
            config_path('languages'),
            __DIR__ . '/../../config/languages',
            realpath(__DIR__ . '/../../config/languages'),
        ];

        $languagesPath = null;
        foreach ($possiblePaths as $path) {
            if ($path && is_dir($path)) {
                $languagesPath = $path;
                break;
            }
        }

        if (!$languagesPath) {
            return ['english'];
        }

        $languageFiles = glob($languagesPath . '/*.php');
        $languages = [];

        foreach ($languageFiles as $languageFile) {
            $languages[] = basename($languageFile, '.php');
        }

        return empty($languages) ? ['english'] : $languages;
    }

    public static function loadLanguageConfig(string $language): array
    {
        if (!preg_match('/^[a-zA-Z0-9_-]+$/', $language)) {
            return ['profanities' => [], 'false_positives' => []];
        }

        $possiblePaths = [
            config_path("languages/{$language}.php"),
            __DIR__ . "/../../config/languages/{$language}.php",
            realpath(__DIR__ . "/../../config/languages/{$language}.php"),
        ];

        $languageFile = null;
        foreach ($possiblePaths as $path) {
            if ($path && file_exists($path)) {
                $languageFile = $path;
                break;
            }
        }

        if (!$languageFile) {
            return ['profanities' => [], 'false_positives' => []];
        }

        $config = require $languageFile;

        if (!is_array($config) || !isset($config['profanities'])) {
            return ['profanities' => [], 'false_positives' => []];
        }

        return $config;
    }

    private static function loadGlobalConfig(): array
    {
        return [
            'separators' => config('blasp.separators', config('blasp.drivers.regex.separators', [])),
            'substitutions' => config('blasp.substitutions', config('blasp.drivers.regex.substitutions', [])),
            'false_positives' => config('blasp.false_positives', []),
        ];
    }

    private static function buildSeverityMap(array $config): array
    {
        $map = [];

        if (isset($config['severity']) && is_array($config['severity'])) {
            foreach ($config['severity'] as $level => $words) {
                $severity = Severity::tryFrom($level) ?? Severity::High;
                foreach ($words as $word) {
                    $map[strtolower($word)] = $severity;
                }
            }
        }

        // Words only in profanities (not in severity map) default to High
        if (isset($config['profanities'])) {
            foreach ($config['profanities'] as $word) {
                $lower = strtolower($word);
                if (!isset($map[$lower])) {
                    $map[$lower] = Severity::High;
                }
            }
        }

        return $map;
    }

    public static function getNormalizerForLanguage(string $language): StringNormalizer
    {
        if (!isset(self::$normalizers[$language])) {
            self::$normalizers[$language] = match (strtolower($language)) {
                'english' => new EnglishNormalizer(),
                'spanish' => new SpanishNormalizer(),
                'german' => new GermanNormalizer(),
                'french' => new FrenchNormalizer(),
                default => new EnglishNormalizer(),
            };
        }

        return self::$normalizers[$language];
    }

    // --- Caching ---

    public static function clearCache(): void
    {
        $cache = self::getCache();
        $keys = $cache->get('blasp_cache_keys', []);

        foreach ($keys as $key) {
            $cache->forget($key);
        }

        $cache->forget('blasp_cache_keys');

        // Also clear result cache keys
        $resultKeys = $cache->get('blasp_result_cache_keys', []);

        foreach ($resultKeys as $key) {
            $cache->forget($key);
        }

        $cache->forget('blasp_result_cache_keys');
    }

    private static function getCache(): \Illuminate\Contracts\Cache\Repository
    {
        $driver = config('blasp.cache.driver', config('blasp.cache_driver'));

        return $driver !== null ? Cache::store($driver) : Cache::store();
    }
}


================================================
FILE: src/Core/Masking/CallbackMask.php
================================================
<?php

namespace Blaspsoft\Blasp\Core\Masking;

use Closure;
use Blaspsoft\Blasp\Core\Contracts\MaskStrategyInterface;

class CallbackMask implements MaskStrategyInterface
{
    public function __construct(
        private Closure $callback
    ) {}

    public function mask(string $word, int $length): string
    {
        return ($this->callback)($word, $length);
    }
}


================================================
FILE: src/Core/Masking/CharacterMask.php
================================================
<?php

namespace Blaspsoft\Blasp\Core\Masking;

use Blaspsoft\Blasp\Core\Contracts\MaskStrategyInterface;

class CharacterMask implements MaskStrategyInterface
{
    public function __construct(
        private string $character = '*'
    ) {
        $this->character = mb_substr($character, 0, 1);
    }

    public function mask(string $word, int $length): string
    {
        return str_repeat($this->character, $length);
    }
}


================================================
FILE: src/Core/Masking/GrawlixMask.php
================================================
<?php

namespace Blaspsoft\Blasp\Core\Masking;

use Blaspsoft\Blasp\Core\Contracts\MaskStrategyInterface;

class GrawlixMask implements MaskStrategyInterface
{
    private const CHARS = ['!', '@', '#', '$', '%'];

    public function mask(string $word, int $length): string
    {
        $result = '';
        for ($i = 0; $i < $length; $i++) {
            $result .= self::CHARS[$i % count(self::CHARS)];
        }
        return $result;
    }
}


================================================
FILE: src/Core/MatchedWord.php
================================================
<?php

namespace Blaspsoft\Blasp\Core;

use Blaspsoft\Blasp\Enums\Severity;
use JsonSerializable;

readonly class MatchedWord implements JsonSerializable
{
    public function 
Download .txt
gitextract_qpmp_x7w/

├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .styleci.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── config/
│   ├── blasp.php
│   └── languages/
│       ├── english.php
│       ├── french.php
│       ├── german.php
│       └── spanish.php
├── phpunit.xml
├── src/
│   ├── BlaspManager.php
│   ├── BlaspServiceProvider.php
│   ├── Blaspable.php
│   ├── Console/
│   │   ├── ClearCommand.php
│   │   ├── LanguagesCommand.php
│   │   └── TestCommand.php
│   ├── Core/
│   │   ├── Analyzer.php
│   │   ├── Contracts/
│   │   │   ├── DriverInterface.php
│   │   │   └── MaskStrategyInterface.php
│   │   ├── Dictionary.php
│   │   ├── Masking/
│   │   │   ├── CallbackMask.php
│   │   │   ├── CharacterMask.php
│   │   │   └── GrawlixMask.php
│   │   ├── MatchedWord.php
│   │   ├── Matchers/
│   │   │   ├── CompoundWordDetector.php
│   │   │   ├── FalsePositiveFilter.php
│   │   │   ├── PhoneticMatcher.php
│   │   │   └── RegexMatcher.php
│   │   ├── Normalizers/
│   │   │   ├── EnglishNormalizer.php
│   │   │   ├── FrenchNormalizer.php
│   │   │   ├── GermanNormalizer.php
│   │   │   ├── NullNormalizer.php
│   │   │   ├── SpanishNormalizer.php
│   │   │   └── StringNormalizer.php
│   │   ├── Result.php
│   │   └── Score.php
│   ├── Drivers/
│   │   ├── PatternDriver.php
│   │   ├── PhoneticDriver.php
│   │   ├── PipelineDriver.php
│   │   └── RegexDriver.php
│   ├── Enums/
│   │   └── Severity.php
│   ├── Events/
│   │   ├── ContentBlocked.php
│   │   ├── ModelProfanityDetected.php
│   │   └── ProfanityDetected.php
│   ├── Exceptions/
│   │   └── ProfanityRejectedException.php
│   ├── Facades/
│   │   └── Blasp.php
│   ├── Middleware/
│   │   └── CheckProfanity.php
│   ├── PendingCheck.php
│   ├── Rules/
│   │   └── Profanity.php
│   └── Testing/
│       └── BlaspFake.php
└── tests/
    ├── AllLanguagesApiTest.php
    ├── AllLanguagesDetectionTest.php
    ├── BladeDirectiveTest.php
    ├── BlaspCheckTest.php
    ├── BlaspCheckValidationTest.php
    ├── BlaspableTest.php
    ├── BypassVulnerabilityTest.php
    ├── CacheDriverConfigurationTest.php
    ├── ConfigurationLoaderLanguageTest.php
    ├── ConfigurationLoaderTest.php
    ├── CustomMaskCharacterTest.php
    ├── DetectionStrategyRegistryTest.php
    ├── EdgeCaseTest.php
    ├── EmptyInputTest.php
    ├── FrenchStringNormalizerTest.php
    ├── GermanStringNormalizerTest.php
    ├── Issue24Test.php
    ├── Issue32FalsePositiveTest.php
    ├── MiddlewareAliasTest.php
    ├── MultiLanguageDetectionConfigTest.php
    ├── MultiLanguageProfanityTest.php
    ├── PhoneticDriverTest.php
    ├── PipelineDriverTest.php
    ├── ProfanityExpressionGeneratorTest.php
    ├── ResultCachingTest.php
    ├── SeverityMapTest.php
    ├── SpanishStringNormalizerTest.php
    ├── StrMacroTest.php
    ├── TestCase.php
    └── UuidFalsePositiveTest.php
Download .txt
SYMBOL INDEX (551 symbols across 70 files)

FILE: src/BlaspManager.php
  class BlaspManager (line 14) | class BlaspManager
    method __construct (line 20) | public function __construct(Application $app)
    method driver (line 25) | public function driver(?string $driver = null): PendingCheck
    method resolveDriver (line 30) | public function resolveDriver(string $name): DriverInterface
    method createDriver (line 39) | protected function createDriver(string $name): DriverInterface
    method createRegexDriver (line 53) | public function createRegexDriver(): DriverInterface
    method createPatternDriver (line 58) | public function createPatternDriver(): DriverInterface
    method createPhoneticDriver (line 63) | public function createPhoneticDriver(): DriverInterface
    method createPipelineDriver (line 76) | public function createPipelineDriver(): DriverInterface
    method extend (line 103) | public function extend(string $driver, Closure $callback): self
    method getDefaultDriver (line 109) | public function getDefaultDriver(): string
    method newPendingCheck (line 114) | public function newPendingCheck(): PendingCheck
    method pipeline (line 119) | public function pipeline(string ...$drivers): PendingCheck
    method check (line 126) | public function check(?string $text): \Blaspsoft\Blasp\Core\Result
    method checkMany (line 131) | public function checkMany(array $texts): array
    method __call (line 136) | public function __call(string $method, array $parameters): mixed
    method getApp (line 141) | public function getApp(): Application

FILE: src/BlaspServiceProvider.php
  class BlaspServiceProvider (line 9) | class BlaspServiceProvider extends ServiceProvider
    method boot (line 11) | public function boot(): void
    method register (line 40) | public function register(): void
    method registerValidationRule (line 51) | protected function registerValidationRule(): void
    method registerMiddlewareAlias (line 68) | protected function registerMiddlewareAlias(): void
    method registerBladeDirectives (line 73) | protected function registerBladeDirectives(): void
    method registerStringMacros (line 80) | protected function registerStringMacros(): void

FILE: src/Blaspable.php
  type Blaspable (line 19) | trait Blaspable
    method bootBlaspable (line 26) | public static function bootBlaspable(): void
    method hadProfanity (line 71) | public function hadProfanity(): bool
    method blaspResults (line 83) | public function blaspResults(): array
    method blaspResult (line 88) | public function blaspResult(string $attribute): ?Result
    method withoutBlaspChecking (line 93) | public static function withoutBlaspChecking(Closure $callback): mixed

FILE: src/Console/ClearCommand.php
  class ClearCommand (line 8) | class ClearCommand extends Command
    method handle (line 13) | public function handle(): void

FILE: src/Console/LanguagesCommand.php
  class LanguagesCommand (line 8) | class LanguagesCommand extends Command
    method handle (line 13) | public function handle(): void

FILE: src/Console/TestCommand.php
  class TestCommand (line 7) | class TestCommand extends Command
    method handle (line 12) | public function handle(): void

FILE: src/Core/Analyzer.php
  class Analyzer (line 9) | class Analyzer
    method analyze (line 11) | public function analyze(

FILE: src/Core/Contracts/DriverInterface.php
  type DriverInterface (line 8) | interface DriverInterface
    method detect (line 10) | public function detect(string $text, Dictionary $dictionary, MaskStrat...

FILE: src/Core/Contracts/MaskStrategyInterface.php
  type MaskStrategyInterface (line 5) | interface MaskStrategyInterface
    method mask (line 7) | public function mask(string $word, int $length): string;

FILE: src/Core/Dictionary.php
  class Dictionary (line 14) | class Dictionary
    method __construct (line 31) | public function __construct(
    method forLanguage (line 80) | public static function forLanguage(string $language, array $options = ...
    method forLanguages (line 126) | public static function forLanguages(array $languages, array $options =...
    method forAllLanguages (line 173) | public static function forAllLanguages(array $options = []): self
    method getProfanities (line 179) | public function getProfanities(): array
    method getFalsePositives (line 184) | public function getFalsePositives(): array
    method getProfanityExpressions (line 189) | public function getProfanityExpressions(): array
    method getSeverity (line 194) | public function getSeverity(string $word): Severity
    method getNormalizer (line 200) | public function getNormalizer(): StringNormalizer
    method getLanguage (line 205) | public function getLanguage(): string
    method getSeparators (line 210) | public function getSeparators(): array
    method getSubstitutions (line 215) | public function getSubstitutions(): array
    method getAvailableLanguages (line 222) | public static function getAvailableLanguages(): array
    method loadLanguageConfig (line 252) | public static function loadLanguageConfig(string $language): array
    method loadGlobalConfig (line 285) | private static function loadGlobalConfig(): array
    method buildSeverityMap (line 294) | private static function buildSeverityMap(array $config): array
    method getNormalizerForLanguage (line 320) | public static function getNormalizerForLanguage(string $language): Str...
    method clearCache (line 337) | public static function clearCache(): void
    method getCache (line 358) | private static function getCache(): \Illuminate\Contracts\Cache\Reposi...

FILE: src/Core/Masking/CallbackMask.php
  class CallbackMask (line 8) | class CallbackMask implements MaskStrategyInterface
    method __construct (line 10) | public function __construct(
    method mask (line 14) | public function mask(string $word, int $length): string

FILE: src/Core/Masking/CharacterMask.php
  class CharacterMask (line 7) | class CharacterMask implements MaskStrategyInterface
    method __construct (line 9) | public function __construct(
    method mask (line 15) | public function mask(string $word, int $length): string

FILE: src/Core/Masking/GrawlixMask.php
  class GrawlixMask (line 7) | class GrawlixMask implements MaskStrategyInterface
    method mask (line 11) | public function mask(string $word, int $length): string

FILE: src/Core/MatchedWord.php
  class MatchedWord (line 8) | readonly class MatchedWord implements JsonSerializable
    method __construct (line 10) | public function __construct(
    method toArray (line 19) | public function toArray(): array
    method jsonSerialize (line 31) | public function jsonSerialize(): mixed

FILE: src/Core/Matchers/CompoundWordDetector.php
  class CompoundWordDetector (line 5) | class CompoundWordDetector
    method isPureAlphaSubstring (line 9) | public function isPureAlphaSubstring(string $matchedText, string $full...

FILE: src/Core/Matchers/FalsePositiveFilter.php
  class FalsePositiveFilter (line 5) | class FalsePositiveFilter
    method __construct (line 9) | public function __construct(array $falsePositives)
    method isFalsePositive (line 14) | public function isFalsePositive(string $word): bool
    method isInsideHexToken (line 19) | public function isInsideHexToken(string $string, int $start, int $leng...
    method isSpanningWordBoundary (line 49) | public function isSpanningWordBoundary(string $matchedText, string $fu...
    method getFullWordContext (line 125) | public function getFullWordContext(string $string, int $start, int $le...

FILE: src/Core/Matchers/PhoneticMatcher.php
  class PhoneticMatcher (line 5) | class PhoneticMatcher
    method __construct (line 10) | public function __construct(
    method buildIndex (line 21) | private function buildIndex(array $profanities): void
    method match (line 43) | public function match(string $word): ?string

FILE: src/Core/Matchers/RegexMatcher.php
  class RegexMatcher (line 5) | class RegexMatcher
    method generateExpressions (line 10) | public function generateExpressions(array $profanities, array $separat...
    method generateSeparatorExpression (line 28) | public function generateSeparatorExpression(array $separators): string
    method generateSubstitutionExpressions (line 37) | public function generateSubstitutionExpressions(array $substitutions):...
    method generateProfanityExpression (line 66) | public function generateProfanityExpression(string $profanity, array $...
    method generateEscapedExpression (line 103) | private function generateEscapedExpression(array $characters = [], arr...

FILE: src/Core/Normalizers/EnglishNormalizer.php
  class EnglishNormalizer (line 5) | class EnglishNormalizer implements StringNormalizer
    method normalize (line 7) | public function normalize(string $string): string

FILE: src/Core/Normalizers/FrenchNormalizer.php
  class FrenchNormalizer (line 5) | class FrenchNormalizer implements StringNormalizer
    method normalize (line 7) | public function normalize(string $string): string

FILE: src/Core/Normalizers/GermanNormalizer.php
  class GermanNormalizer (line 5) | class GermanNormalizer implements StringNormalizer
    method normalize (line 7) | public function normalize(string $string): string

FILE: src/Core/Normalizers/NullNormalizer.php
  class NullNormalizer (line 5) | class NullNormalizer implements StringNormalizer
    method normalize (line 7) | public function normalize(string $string): string

FILE: src/Core/Normalizers/SpanishNormalizer.php
  class SpanishNormalizer (line 5) | class SpanishNormalizer implements StringNormalizer
    method normalize (line 7) | public function normalize(string $string): string

FILE: src/Core/Normalizers/StringNormalizer.php
  type StringNormalizer (line 5) | interface StringNormalizer
    method normalize (line 7) | public function normalize(string $string): string;

FILE: src/Core/Result.php
  class Result (line 11) | class Result implements JsonSerializable, Stringable, Countable
    method __construct (line 15) | public function __construct(
    method isClean (line 26) | public function isClean(): bool
    method isOffensive (line 31) | public function isOffensive(): bool
    method clean (line 36) | public function clean(): string
    method original (line 41) | public function original(): string
    method score (line 46) | public function score(): int
    method count (line 51) | public function count(): int
    method uniqueWords (line 56) | public function uniqueWords(): array
    method severity (line 61) | public function severity(): ?Severity
    method words (line 73) | public function words(): Collection
    method hasProfanity (line 81) | public function hasProfanity(): bool
    method getCleanString (line 87) | public function getCleanString(): string
    method getSourceString (line 93) | public function getSourceString(): string
    method getProfanitiesCount (line 99) | public function getProfanitiesCount(): int
    method getUniqueProfanitiesFound (line 105) | public function getUniqueProfanitiesFound(): array
    method none (line 112) | public static function none(string $text): self
    method fromArray (line 117) | public static function fromArray(array $data): self
    method withMatches (line 139) | public static function withMatches(array $words, string $originalText ...
    method toArray (line 164) | public function toArray(): array
    method toJson (line 178) | public function toJson(int $options = 0): string
    method jsonSerialize (line 183) | public function jsonSerialize(): mixed
    method __toString (line 188) | public function __toString(): string

FILE: src/Core/Score.php
  class Score (line 5) | class Score
    method calculate (line 7) | public static function calculate(array $matchedWords, int $totalWordCo...

FILE: src/Drivers/PatternDriver.php
  class PatternDriver (line 13) | class PatternDriver implements DriverInterface
    method detect (line 15) | public function detect(string $text, Dictionary $dictionary, MaskStrat...

FILE: src/Drivers/PhoneticDriver.php
  class PhoneticDriver (line 15) | class PhoneticDriver implements DriverInterface
    method __construct (line 17) | public function __construct(
    method detect (line 25) | public function detect(string $text, Dictionary $dictionary, MaskStrat...

FILE: src/Drivers/PipelineDriver.php
  class PipelineDriver (line 12) | class PipelineDriver implements DriverInterface
    method __construct (line 15) | public function __construct(private array $drivers) {}
    method detect (line 17) | public function detect(string $text, Dictionary $dictionary, MaskStrat...

FILE: src/Drivers/RegexDriver.php
  class RegexDriver (line 15) | class RegexDriver implements DriverInterface
    method detect (line 20) | public function detect(string $text, Dictionary $dictionary, MaskStrat...

FILE: src/Enums/Severity.php
  method weight (line 12) | public function weight(): int
  method isAtLeast (line 22) | public function isAtLeast(self $minimum): bool

FILE: src/Events/ContentBlocked.php
  class ContentBlocked (line 8) | class ContentBlocked
    method __construct (line 10) | public function __construct(

FILE: src/Events/ModelProfanityDetected.php
  class ModelProfanityDetected (line 8) | class ModelProfanityDetected
    method __construct (line 10) | public function __construct(

FILE: src/Events/ProfanityDetected.php
  class ProfanityDetected (line 7) | class ProfanityDetected
    method __construct (line 9) | public function __construct(

FILE: src/Exceptions/ProfanityRejectedException.php
  class ProfanityRejectedException (line 9) | class ProfanityRejectedException extends RuntimeException
    method __construct (line 11) | public function __construct(
    method forModel (line 19) | public static function forModel(Model $model, string $attribute, Resul...

FILE: src/Facades/Blasp.php
  class Blasp (line 38) | class Blasp extends BaseFacade
    method getFacadeAccessor (line 40) | protected static function getFacadeAccessor(): string
    method fake (line 45) | public static function fake(array $responses = []): BlaspFake
    method withoutFiltering (line 52) | public static function withoutFiltering(Closure $callback): mixed
    method assertChecked (line 64) | public static function assertChecked(): void
    method assertCheckedTimes (line 73) | public static function assertCheckedTimes(int $times): void

FILE: src/Middleware/CheckProfanity.php
  class CheckProfanity (line 12) | class CheckProfanity
    method __construct (line 14) | public function __construct(
    method handle (line 18) | public function handle(Request $request, Closure $next, ?string $actio...
    method extractTextFields (line 63) | protected function extractTextFields(array $input): array

FILE: src/PendingCheck.php
  class PendingCheck (line 18) | class PendingCheck
    method __construct (line 32) | public function __construct(BlaspManager $manager)
    method driver (line 39) | public function driver(string $driver): self
    method in (line 45) | public function in(string ...$languages): self
    method inAllLanguages (line 51) | public function inAllLanguages(): self
    method mask (line 57) | public function mask(string|Closure $mask): self
    method allow (line 69) | public function allow(string ...$words): self
    method block (line 75) | public function block(string ...$words): self
    method withSeverity (line 81) | public function withSeverity(Severity $severity): self
    method strict (line 87) | public function strict(): self
    method lenient (line 94) | public function lenient(): self
    method pipeline (line 101) | public function pipeline(string ...$drivers): self
    method maskWith (line 110) | public function maskWith(string $character): self
    method allLanguages (line 116) | public function allLanguages(): self
    method language (line 122) | public function language(string $language): self
    method english (line 129) | public function english(): self
    method spanish (line 134) | public function spanish(): self
    method german (line 139) | public function german(): self
    method french (line 144) | public function french(): self
    method configure (line 151) | public function configure(?array $profanities = null, ?array $falsePos...
    method check (line 161) | public function check(?string $text): Result
    method performCheck (line 186) | protected function performCheck(string $text): Result
    method checkMany (line 208) | public function checkMany(array $texts): array
    method buildDictionary (line 219) | protected function buildDictionary(): Dictionary
    method resolveDriver (line 241) | protected function resolveDriver(): \Blaspsoft\Blasp\Core\Contracts\Dr...
    method resolveMask (line 261) | protected function resolveMask(): MaskStrategyInterface
    method shouldCache (line 273) | protected function shouldCache(): bool
    method buildCacheKey (line 290) | protected function buildCacheKey(string $text): string
    method getCache (line 309) | protected function getCache(): \Illuminate\Contracts\Cache\Repository
    method trackCacheKey (line 316) | protected function trackCacheKey(string $key): void

FILE: src/Rules/Profanity.php
  class Profanity (line 9) | class Profanity implements ValidationRule
    method make (line 15) | public static function make(): self
    method in (line 20) | public function in(string $language): self
    method maxScore (line 26) | public function maxScore(int $score): self
    method severity (line 32) | public function severity(Severity $severity): self
    method __callStatic (line 38) | public static function __callStatic(string $name, array $arguments): self
    method validate (line 43) | public function validate(string $attribute, mixed $value, Closure $fai...

FILE: src/Testing/BlaspFake.php
  class BlaspFake (line 9) | class BlaspFake
    method __construct (line 14) | public function __construct(array $fakeResults = [])
    method check (line 19) | public function check(?string $text): Result
    method checkMany (line 31) | public function checkMany(array $texts): array
    method assertChecked (line 40) | public function assertChecked(): void
    method assertCheckedTimes (line 45) | public function assertCheckedTimes(int $times): void
    method assertCheckedWith (line 54) | public function assertCheckedWith(string $text): void
    method __call (line 60) | public function __call(string $method, array $parameters): self
    method in (line 65) | public function in(string ...$languages): self
    method inAllLanguages (line 70) | public function inAllLanguages(): self
    method allLanguages (line 75) | public function allLanguages(): self
    method english (line 80) | public function english(): self
    method spanish (line 85) | public function spanish(): self
    method german (line 90) | public function german(): self
    method french (line 95) | public function french(): self
    method mask (line 100) | public function mask(string $mask): self
    method maskWith (line 105) | public function maskWith(string $character): self
    method language (line 110) | public function language(string $language): self
    method driver (line 115) | public function driver(string $driver): self
    method configure (line 120) | public function configure(?array $profanities = null, ?array $falsePos...

FILE: tests/AllLanguagesApiTest.php
  class AllLanguagesApiTest (line 7) | class AllLanguagesApiTest extends TestCase
    method test_all_languages_detection (line 9) | public function test_all_languages_detection()
    method test_mixed_language_content (line 28) | public function test_mixed_language_content()
    method test_chainable_all_languages (line 36) | public function test_chainable_all_languages()
    method test_language_shortcuts_vs_all (line 42) | public function test_language_shortcuts_vs_all()
    method test_direct_manager_all_languages (line 58) | public function test_direct_manager_all_languages()
    method test_configure_with_all_languages (line 66) | public function test_configure_with_all_languages()

FILE: tests/AllLanguagesDetectionTest.php
  class AllLanguagesDetectionTest (line 7) | class AllLanguagesDetectionTest extends TestCase
    method test_all_languages_profanity_detection (line 9) | public function test_all_languages_profanity_detection()
    method test_language_variations (line 64) | public function test_language_variations()
    method test_language_normalizers (line 100) | public function test_language_normalizers()

FILE: tests/BladeDirectiveTest.php
  class BladeDirectiveTest (line 7) | class BladeDirectiveTest extends TestCase
    method renderBlade (line 9) | protected function renderBlade(string $template, array $data = []): st...
    method test_clean_directive_masks_profane_text (line 19) | public function test_clean_directive_masks_profane_text()
    method test_clean_directive_passes_clean_text_unchanged (line 27) | public function test_clean_directive_passes_clean_text_unchanged()
    method test_clean_directive_escapes_html_for_xss_safety (line 34) | public function test_clean_directive_escapes_html_for_xss_safety()

FILE: tests/BlaspCheckTest.php
  class BlaspCheckTest (line 7) | class BlaspCheckTest extends TestCase
    method test_real_blasp_service (line 9) | public function test_real_blasp_service()
    method test_straight_match (line 15) | public function test_straight_match()
    method test_substitution_match (line 25) | public function test_substitution_match()
    method test_obscured_match (line 35) | public function test_obscured_match()
    method test_doubled_match (line 45) | public function test_doubled_match()
    method test_combination_match (line 55) | public function test_combination_match()
    method test_multiple_profanities_no_spaces (line 65) | public function test_multiple_profanities_no_spaces()
    method test_multiple_profanities (line 75) | public function test_multiple_profanities()
    method test_scunthorpe_problem (line 84) | public function test_scunthorpe_problem()
    method test_penistone_problem (line 94) | public function test_penistone_problem()
    method test_false_positives (line 104) | public function test_false_positives()
    method test_cuntfuck_fuckcunt (line 122) | public function test_cuntfuck_fuckcunt()
    method test_fucking_shit_cunt_fuck (line 131) | public function test_fucking_shit_cunt_fuck()
    method test_billy_butcher (line 140) | public function test_billy_butcher()
    method test_paragraph (line 149) | public function test_paragraph()
    method test_word_boudary (line 163) | public function test_word_boudary()
    method test_pural_profanity (line 175) | public function test_pural_profanity()
    method test_this_musicals_hit (line 184) | public function test_this_musicals_hit()
    method test_ass_subtitution (line 193) | public function test_ass_subtitution()
    method test_embedded_profanities (line 202) | public function test_embedded_profanities()
    method test_multiple_profanities_with_spaces (line 211) | public function test_multiple_profanities_with_spaces()
    method test_spaced_profanity_with_substitution (line 220) | public function test_spaced_profanity_with_substitution()
    method test_spaced_profanity_without_substitution (line 227) | public function test_spaced_profanity_without_substitution()
    method test_partial_spacing_s_hit (line 233) | public function test_partial_spacing_s_hit()
    method test_partial_spacing_f_uck (line 240) | public function test_partial_spacing_f_uck()
    method test_partial_spacing_t_wat (line 247) | public function test_partial_spacing_t_wat()
    method test_partial_spacing_fu_c_k (line 254) | public function test_partial_spacing_fu_c_k()
    method test_partial_spacing_tw_a_t (line 261) | public function test_partial_spacing_tw_a_t()
    method test_no_false_positive_musicals_hit_embedded (line 268) | public function test_no_false_positive_musicals_hit_embedded()
    method test_no_false_positive_an_alert (line 275) | public function test_no_false_positive_an_alert()
    method test_no_false_positive_has_5_faces (line 282) | public function test_no_false_positive_has_5_faces()
    method test_detects_at_ss_obfuscation (line 289) | public function test_detects_at_ss_obfuscation()
    method test_no_false_positive_space_words (line 295) | public function test_no_false_positive_space_words()

FILE: tests/BlaspCheckValidationTest.php
  class BlaspCheckValidationTest (line 6) | class BlaspCheckValidationTest extends TestCase
    method test_blasp_check_validation_passes_with_clean_text (line 13) | public function test_blasp_check_validation_passes_with_clean_text()
    method test_blasp_check_validation_fails_with_profanity (line 29) | public function test_blasp_check_validation_fails_with_profanity()

FILE: tests/BlaspableTest.php
  class BlaspableTestModel (line 14) | class BlaspableTestModel extends Model
  class BlaspableRejectModel (line 25) | class BlaspableRejectModel extends Model
  class BlaspableSpanishModel (line 37) | class BlaspableSpanishModel extends Model
  class BlaspableCustomMaskModel (line 49) | class BlaspableCustomMaskModel extends Model
  class BlaspableTest (line 61) | class BlaspableTest extends TestCase
    method setUp (line 63) | protected function setUp(): void
    method tearDown (line 75) | protected function tearDown(): void
    method test_sanitize_mode_masks_profanity_on_save (line 81) | public function test_sanitize_mode_masks_profanity_on_save()
    method test_reject_mode_throws_exception (line 94) | public function test_reject_mode_throws_exception()
    method test_reject_mode_does_not_persist_model (line 105) | public function test_reject_mode_does_not_persist_model()
    method test_clean_text_passes_through_untouched (line 118) | public function test_clean_text_passes_through_untouched()
    method test_only_dirty_attributes_are_checked (line 129) | public function test_only_dirty_attributes_are_checked()
    method test_non_blaspable_attributes_are_ignored (line 144) | public function test_non_blaspable_attributes_are_ignored()
    method test_per_model_language_override (line 154) | public function test_per_model_language_override()
    method test_per_model_mask_override (line 164) | public function test_per_model_mask_override()
    method test_had_profanity_returns_true_when_profanity_detected (line 175) | public function test_had_profanity_returns_true_when_profanity_detected()
    method test_had_profanity_returns_false_for_clean_text (line 184) | public function test_had_profanity_returns_false_for_clean_text()
    method test_blasp_results_returns_results_array (line 193) | public function test_blasp_results_returns_results_array()
    method test_blasp_result_returns_single_attribute_result (line 209) | public function test_blasp_result_returns_single_attribute_result()
    method test_blasp_result_returns_null_for_unknown_attribute (line 221) | public function test_blasp_result_returns_null_for_unknown_attribute()
    method test_without_blasp_checking_disables_profanity_check (line 230) | public function test_without_blasp_checking_disables_profanity_check()
    method test_model_profanity_detected_event_fires_in_sanitize_mode (line 242) | public function test_model_profanity_detected_event_fires_in_sanitize_...
    method test_model_profanity_detected_event_fires_in_reject_mode (line 257) | public function test_model_profanity_detected_event_fires_in_reject_mo...
    method test_event_not_fired_for_clean_text (line 274) | public function test_event_not_fired_for_clean_text()
    method test_update_triggers_sanitization (line 285) | public function test_update_triggers_sanitization()
    method test_multiple_profane_attributes_are_sanitized (line 298) | public function test_multiple_profane_attributes_are_sanitized()
    method test_null_attributes_are_skipped (line 310) | public function test_null_attributes_are_skipped()
    method test_profanity_rejected_exception_contains_model_and_attribute (line 321) | public function test_profanity_rejected_exception_contains_model_and_a...

FILE: tests/BypassVulnerabilityTest.php
  class BypassVulnerabilityTest (line 7) | class BypassVulnerabilityTest extends TestCase
    method test_invisible_separator_in_fuck (line 13) | public function test_invisible_separator_in_fuck()
    method test_zero_width_space_in_shit (line 20) | public function test_zero_width_space_in_shit()
    method test_multiple_invisible_chars_in_profanity (line 27) | public function test_multiple_invisible_chars_in_profanity()
    method test_invisible_chars_in_clean_text_no_false_positive (line 34) | public function test_invisible_chars_in_clean_text_no_false_positive()
    method test_invisible_separator_clean_output_masks_profanity (line 40) | public function test_invisible_separator_clean_output_masks_profanity()
    method test_asterisk_censored_fag (line 51) | public function test_asterisk_censored_fag()
    method test_asterisk_censored_fuck (line 58) | public function test_asterisk_censored_fuck()
    method test_asterisk_censored_shit (line 64) | public function test_asterisk_censored_shit()
    method test_asterisk_fully_censored_fuck (line 70) | public function test_asterisk_fully_censored_fuck()
    method test_asterisk_in_non_profane_word_no_false_positive (line 76) | public function test_asterisk_in_non_profane_word_no_false_positive()
    method test_invisible_char_plus_asterisk_censoring (line 86) | public function test_invisible_char_plus_asterisk_censoring()

FILE: tests/CacheDriverConfigurationTest.php
  class CacheDriverConfigurationTest (line 9) | class CacheDriverConfigurationTest extends TestCase
    method setUp (line 11) | public function setUp(): void
    method test_dictionary_can_be_created_without_cache (line 18) | public function test_dictionary_can_be_created_without_cache(): void
    method test_clear_cache_works (line 28) | public function test_clear_cache_works(): void
    method test_dictionary_loads_consistently (line 34) | public function test_dictionary_loads_consistently(): void
    method test_different_languages_have_different_profanities (line 43) | public function test_different_languages_have_different_profanities():...
    method test_clear_cache_with_custom_driver (line 51) | public function test_clear_cache_with_custom_driver(): void

FILE: tests/ConfigurationLoaderLanguageTest.php
  class ConfigurationLoaderLanguageTest (line 11) | class ConfigurationLoaderLanguageTest extends TestCase
    method test_get_available_languages (line 13) | public function test_get_available_languages()
    method test_load_specific_language_english (line 24) | public function test_load_specific_language_english()
    method test_load_specific_language_spanish (line 39) | public function test_load_specific_language_spanish()
    method test_load_specific_language_french (line 56) | public function test_load_specific_language_french()
    method test_load_specific_language_german (line 74) | public function test_load_specific_language_german()
    method test_load_nonexistent_language (line 93) | public function test_load_nonexistent_language()
    method test_normalizer_for_languages (line 99) | public function test_normalizer_for_languages()
    method test_language_substitutions_are_merged (line 107) | public function test_language_substitutions_are_merged()

FILE: tests/ConfigurationLoaderTest.php
  class ConfigurationLoaderTest (line 8) | class ConfigurationLoaderTest extends TestCase
    method setUp (line 10) | public function setUp(): void
    method test_for_language_returns_dictionary (line 17) | public function test_for_language_returns_dictionary()
    method test_dictionary_has_profanity_expressions (line 26) | public function test_dictionary_has_profanity_expressions()
    method test_for_languages_returns_multi_language_dictionary (line 37) | public function test_for_languages_returns_multi_language_dictionary()
    method test_for_all_languages_returns_all_language_dictionary (line 46) | public function test_for_all_languages_returns_all_language_dictionary()
    method test_allow_list_removes_words (line 57) | public function test_allow_list_removes_words()
    method test_block_list_adds_words (line 65) | public function test_block_list_adds_words()
    method test_severity_map_is_populated (line 72) | public function test_severity_map_is_populated()
    method test_clear_cache (line 80) | public function test_clear_cache()
    method test_get_available_languages (line 86) | public function test_get_available_languages()
    method test_load_language_config (line 97) | public function test_load_language_config()
    method test_load_nonexistent_language_config (line 106) | public function test_load_nonexistent_language_config()
    method test_normalizer_is_set (line 114) | public function test_normalizer_is_set()
    method test_separators_and_substitutions_loaded (line 121) | public function test_separators_and_substitutions_loaded()

FILE: tests/CustomMaskCharacterTest.php
  class CustomMaskCharacterTest (line 7) | class CustomMaskCharacterTest extends TestCase
    method test_default_mask_character_is_asterisk (line 9) | public function test_default_mask_character_is_asterisk()
    method test_custom_mask_character_with_hash (line 15) | public function test_custom_mask_character_with_hash()
    method test_custom_mask_character_with_dash (line 21) | public function test_custom_mask_character_with_dash()
    method test_custom_mask_character_with_underscore (line 27) | public function test_custom_mask_character_with_underscore()
    method test_custom_mask_character_with_unicode (line 33) | public function test_custom_mask_character_with_unicode()
    method test_custom_mask_character_only_uses_first_character (line 39) | public function test_custom_mask_character_only_uses_first_character()
    method test_mask_character_can_be_chained_with_language (line 45) | public function test_mask_character_can_be_chained_with_language()
    method test_mask_character_works_with_multiple_profanities (line 51) | public function test_mask_character_works_with_multiple_profanities()
    method test_mask_character_with_block_list (line 58) | public function test_mask_character_with_block_list()
    method test_different_mask_characters_can_be_used_independently (line 64) | public function test_different_mask_characters_can_be_used_independent...

FILE: tests/DetectionStrategyRegistryTest.php
  class DetectionStrategyRegistryTest (line 14) | class DetectionStrategyRegistryTest extends TestCase
    method setUp (line 18) | public function setUp(): void
    method test_default_driver_is_regex (line 24) | public function test_default_driver_is_regex()
    method test_resolve_regex_driver (line 29) | public function test_resolve_regex_driver()
    method test_resolve_pattern_driver (line 35) | public function test_resolve_pattern_driver()
    method test_resolve_unknown_driver_throws_exception (line 41) | public function test_resolve_unknown_driver_throws_exception()
    method test_extend_registers_custom_driver (line 47) | public function test_extend_registers_custom_driver()
    method test_manager_check_returns_result (line 62) | public function test_manager_check_returns_result()
    method test_manager_creates_pending_check (line 69) | public function test_manager_creates_pending_check()
    method test_driver_method_returns_pending_check (line 75) | public function test_driver_method_returns_pending_check()

FILE: tests/EdgeCaseTest.php
  class EdgeCaseTest (line 7) | class EdgeCaseTest extends TestCase
    method test_fuckme_not_detected_across_word_boundaries (line 9) | public function test_fuckme_not_detected_across_word_boundaries()
    method test_removed_compound_profanities_not_detected (line 23) | public function test_removed_compound_profanities_not_detected()
    method test_legitimate_compound_profanities_still_work (line 34) | public function test_legitimate_compound_profanities_still_work()

FILE: tests/EmptyInputTest.php
  class EmptyInputTest (line 7) | class EmptyInputTest extends TestCase
    method test_empty_string_returns_no_profanity (line 9) | public function test_empty_string_returns_no_profanity()
    method test_empty_string_returns_empty_source_and_clean_strings (line 18) | public function test_empty_string_returns_empty_source_and_clean_strin...
    method test_null_returns_no_profanity (line 26) | public function test_null_returns_no_profanity()
    method test_profanity_still_detected_after_empty_check (line 35) | public function test_profanity_still_detected_after_empty_check()

FILE: tests/FrenchStringNormalizerTest.php
  class FrenchStringNormalizerTest (line 7) | class FrenchStringNormalizerTest extends TestCase
    method setUp (line 11) | public function setUp(): void
    method test_normalize_accented_vowels (line 17) | public function test_normalize_accented_vowels()
    method test_normalize_cedilla (line 26) | public function test_normalize_cedilla()
    method test_normalize_ligatures (line 33) | public function test_normalize_ligatures()
    method test_normalize_french_profanity_variants (line 40) | public function test_normalize_french_profanity_variants()
    method test_normalize_circumflex_accent (line 49) | public function test_normalize_circumflex_accent()
    method test_normalize_grave_accent (line 58) | public function test_normalize_grave_accent()
    method test_normalize_acute_accent (line 67) | public function test_normalize_acute_accent()
    method test_normalize_diaeresis (line 76) | public function test_normalize_diaeresis()
    method test_normalize_mixed_case_preservation (line 85) | public function test_normalize_mixed_case_preservation()
    method test_normalize_preserves_non_french_characters (line 93) | public function test_normalize_preserves_non_french_characters()
    method test_normalize_empty_and_special_strings (line 100) | public function test_normalize_empty_and_special_strings()
    method test_normalize_complex_french_text (line 108) | public function test_normalize_complex_french_text()
    method test_normalize_all_french_accents (line 115) | public function test_normalize_all_french_accents()
    method test_normalize_numbers_and_special_chars (line 145) | public function test_normalize_numbers_and_special_chars()
    method test_normalize_french_profanities_from_config (line 152) | public function test_normalize_french_profanities_from_config()

FILE: tests/GermanStringNormalizerTest.php
  class GermanStringNormalizerTest (line 7) | class GermanStringNormalizerTest extends TestCase
    method setUp (line 11) | public function setUp(): void
    method test_normalize_umlauts (line 17) | public function test_normalize_umlauts()
    method test_normalize_eszett (line 27) | public function test_normalize_eszett()
    method test_normalize_sch_combinations (line 35) | public function test_normalize_sch_combinations()
    method test_normalize_german_profanity_variants (line 42) | public function test_normalize_german_profanity_variants()
    method test_normalize_preserves_non_german_characters (line 50) | public function test_normalize_preserves_non_german_characters()
    method test_normalize_mixed_case_preservation (line 56) | public function test_normalize_mixed_case_preservation()
    method test_normalize_empty_and_special_strings (line 63) | public function test_normalize_empty_and_special_strings()

FILE: tests/Issue24Test.php
  class Issue24Test (line 7) | class Issue24Test extends TestCase
    method test_etre_not_flagged_as_profanity (line 9) | public function test_etre_not_flagged_as_profanity()
    method test_are_accent_not_flagged (line 15) | public function test_are_accent_not_flagged()
    method test_tete_not_flagged (line 21) | public function test_tete_not_flagged()
    method test_actual_profanity_still_detected (line 27) | public function test_actual_profanity_still_detected()

FILE: tests/Issue32FalsePositiveTest.php
  class Issue32FalsePositiveTest (line 8) | class Issue32FalsePositiveTest extends TestCase
    method test_legitimate_words_not_flagged (line 10) | #[DataProvider('legitimateWordsProvider')]
    method legitimateWordsProvider (line 20) | public static function legitimateWordsProvider(): array
    method test_actual_profanity_still_detected (line 36) | public function test_actual_profanity_still_detected()

FILE: tests/MiddlewareAliasTest.php
  class MiddlewareAliasTest (line 7) | class MiddlewareAliasTest extends TestCase
    method test_blasp_alias_resolves_to_check_profanity_middleware (line 9) | public function test_blasp_alias_resolves_to_check_profanity_middleware()

FILE: tests/MultiLanguageDetectionConfigTest.php
  class MultiLanguageDetectionConfigTest (line 8) | class MultiLanguageDetectionConfigTest extends TestCase
    method test_for_language_sets_language (line 10) | public function test_for_language_sets_language()
    method test_for_languages_merges_profanities (line 16) | public function test_for_languages_merges_profanities()
    method test_for_all_languages_includes_all (line 25) | public function test_for_all_languages_includes_all()
    method test_profanity_expressions_generated (line 36) | public function test_profanity_expressions_generated()
    method test_severity_map_populated (line 46) | public function test_severity_map_populated()
    method test_false_positives_loaded (line 54) | public function test_false_positives_loaded()
    method test_allow_list_removes_profanities (line 64) | public function test_allow_list_removes_profanities()
    method test_block_list_adds_profanities (line 71) | public function test_block_list_adds_profanities()
    method test_block_list_gets_severity (line 78) | public function test_block_list_gets_severity()
    method test_separators_and_substitutions_present (line 86) | public function test_separators_and_substitutions_present()

FILE: tests/MultiLanguageProfanityTest.php
  class MultiLanguageProfanityTest (line 8) | class MultiLanguageProfanityTest extends TestCase
    method test_english_profanities (line 10) | public function test_english_profanities()
    method test_spanish_profanities (line 26) | public function test_spanish_profanities()
    method test_german_profanities (line 41) | public function test_german_profanities()
    method test_french_profanities (line 57) | public function test_french_profanities()
    method test_profanity_variations (line 72) | public function test_profanity_variations()
    method test_case_insensitivity (line 90) | public function test_case_insensitivity()
    method test_false_positives_not_flagged (line 110) | public function test_false_positives_not_flagged()
    method test_comprehensive_language_coverage (line 123) | public function test_comprehensive_language_coverage()

FILE: tests/PhoneticDriverTest.php
  class PhoneticDriverTest (line 9) | class PhoneticDriverTest extends TestCase
    method setUp (line 11) | protected function setUp(): void
    method test_matcher_exact_profanity_match (line 47) | public function test_matcher_exact_profanity_match()
    method test_matcher_phonetic_variant_detection (line 55) | public function test_matcher_phonetic_variant_detection()
    method test_matcher_short_word_skipping (line 64) | public function test_matcher_short_word_skipping()
    method test_matcher_phonetic_false_positive_respected (line 72) | public function test_matcher_phonetic_false_positive_respected()
    method test_matcher_high_levenshtein_distance_rejection (line 82) | public function test_matcher_high_levenshtein_distance_rejection()
    method test_resolves_from_manager (line 97) | public function test_resolves_from_manager()
    method test_detects_standard_profanity (line 104) | public function test_detects_standard_profanity()
    method test_detects_phonetic_evasion (line 112) | public function test_detects_phonetic_evasion()
    method test_returns_correct_clean_text_with_masking (line 128) | public function test_returns_correct_clean_text_with_masking()
    method test_handles_empty_text (line 136) | public function test_handles_empty_text()
    method test_respects_severity_filter (line 145) | public function test_respects_severity_filter()
    method test_respects_dictionary_false_positives (line 155) | public function test_respects_dictionary_false_positives()
    method test_multiple_profanities_in_one_text (line 162) | public function test_multiple_profanities_in_one_text()
    method test_unsupported_language_returns_clean_result (line 170) | public function test_unsupported_language_returns_clean_result()
    method test_fork_is_not_flagged (line 183) | public function test_fork_is_not_flagged()
    method test_beach_is_not_flagged (line 190) | public function test_beach_is_not_flagged()
    method test_sheet_is_not_flagged (line 197) | public function test_sheet_is_not_flagged()
    method test_duck_is_not_flagged (line 204) | public function test_duck_is_not_flagged()
    method test_count_is_not_flagged (line 211) | public function test_count_is_not_flagged()

FILE: tests/PipelineDriverTest.php
  class PipelineDriverTest (line 9) | class PipelineDriverTest extends TestCase
    method setUp (line 11) | protected function setUp(): void
    method test_resolves_from_manager_via_driver_name (line 50) | public function test_resolves_from_manager_via_driver_name()
    method test_ad_hoc_pipeline_via_facade (line 57) | public function test_ad_hoc_pipeline_via_facade()
    method test_catches_obfuscated_text_via_regex (line 68) | public function test_catches_obfuscated_text_via_regex()
    method test_catches_phonetic_evasion (line 76) | public function test_catches_phonetic_evasion()
    method test_catches_exact_match_via_pattern (line 83) | public function test_catches_exact_match_via_pattern()
    method test_same_word_at_same_position_only_counted_once (line 94) | public function test_same_word_at_same_position_only_counted_once()
    method test_clean_text_masks_applied_correctly (line 108) | public function test_clean_text_masks_applied_correctly()
    method test_clean_text_with_multiple_matches (line 117) | public function test_clean_text_with_multiple_matches()
    method test_score_recalculated_from_merged_matches (line 130) | public function test_score_recalculated_from_merged_matches()
    method test_empty_text_returns_clean_result (line 142) | public function test_empty_text_returns_clean_result()
    method test_single_driver_pipeline_matches_standalone (line 156) | public function test_single_driver_pipeline_matches_standalone()
    method test_severity_filter_applies_across_merged_result (line 170) | public function test_severity_filter_applies_across_merged_result()
    method test_works_with_language_selection (line 184) | public function test_works_with_language_selection()
    method test_works_with_custom_mask_character (line 197) | public function test_works_with_custom_mask_character()
    method test_works_with_allow_list (line 212) | public function test_works_with_allow_list()
    method test_works_with_block_list (line 222) | public function test_works_with_block_list()
    method test_original_text_preserved (line 235) | public function test_original_text_preserved()
    method test_pipeline_with_pattern_and_phonetic (line 247) | public function test_pipeline_with_pattern_and_phonetic()
    method test_clean_input_returns_unchanged (line 262) | public function test_clean_input_returns_unchanged()

FILE: tests/ProfanityExpressionGeneratorTest.php
  class ProfanityExpressionGeneratorTest (line 7) | class ProfanityExpressionGeneratorTest extends TestCase
    method setUp (line 11) | public function setUp(): void
    method test_generate_separator_expression (line 17) | public function test_generate_separator_expression()
    method test_generate_substitution_expressions (line 25) | public function test_generate_substitution_expressions()
    method test_generate_profanity_expression_simple (line 41) | public function test_generate_profanity_expression_simple()
    method test_generate_expressions_full_flow (line 62) | public function test_generate_expressions_full_flow()
    method test_generated_expressions_match_profanities (line 92) | public function test_generated_expressions_match_profanities()
    method test_separator_expression_with_various_chars (line 116) | public function test_separator_expression_with_various_chars()
    method test_generate_expressions_with_multi_char_substitutions (line 133) | public function test_generate_expressions_with_multi_char_substitutions()
    method test_expressions_are_case_insensitive (line 154) | public function test_expressions_are_case_insensitive()
    method test_empty_arrays_handling (line 176) | public function test_empty_arrays_handling()
    method test_complex_profanity_patterns (line 190) | public function test_complex_profanity_patterns()
    method test_circular_substitutions_produce_valid_regex (line 222) | public function test_circular_substitutions_produce_valid_regex()
    method test_basic_profanity_matching (line 238) | public function test_basic_profanity_matching()

FILE: tests/ResultCachingTest.php
  class ResultCachingTest (line 11) | class ResultCachingTest extends TestCase
    method setUp (line 13) | protected function setUp(): void
    method test_results_are_cached (line 23) | public function test_results_are_cached(): void
    method test_cache_key_varies_by_language (line 39) | public function test_cache_key_varies_by_language(): void
    method test_cache_key_varies_by_severity (line 53) | public function test_cache_key_varies_by_severity(): void
    method test_cache_key_varies_by_allow_list (line 63) | public function test_cache_key_varies_by_allow_list(): void
    method test_cache_key_varies_by_block_list (line 72) | public function test_cache_key_varies_by_block_list(): void
    method test_cache_key_varies_by_driver (line 81) | public function test_cache_key_varies_by_driver(): void
    method test_callback_mask_bypasses_cache (line 94) | public function test_callback_mask_bypasses_cache(): void
    method test_clear_cache_wipes_result_cache (line 103) | public function test_clear_cache_wipes_result_cache(): void
    method test_disabling_results_config_skips_caching (line 121) | public function test_disabling_results_config_skips_caching(): void
    method test_disabling_cache_entirely_skips_caching (line 131) | public function test_disabling_cache_entirely_skips_caching(): void
    method test_cached_results_deserialize_correctly (line 141) | public function test_cached_results_deserialize_correctly(): void
    method test_clean_text_is_not_cached_incorrectly (line 157) | public function test_clean_text_is_not_cached_incorrectly(): void

FILE: tests/SeverityMapTest.php
  class SeverityMapTest (line 8) | class SeverityMapTest extends TestCase
    method test_spanish_mild_words_filtered_by_moderate_severity (line 12) | public function test_spanish_mild_words_filtered_by_moderate_severity(...
    method test_spanish_moderate_words_caught_at_moderate_severity (line 19) | public function test_spanish_moderate_words_caught_at_moderate_severit...
    method test_spanish_moderate_words_filtered_by_high_severity (line 26) | public function test_spanish_moderate_words_filtered_by_high_severity(...
    method test_spanish_default_high_words_caught (line 33) | public function test_spanish_default_high_words_caught(): void
    method test_spanish_extreme_words_caught_at_extreme (line 40) | public function test_spanish_extreme_words_caught_at_extreme(): void
    method test_spanish_high_words_filtered_by_extreme (line 46) | public function test_spanish_high_words_filtered_by_extreme(): void
    method test_french_mild_words_filtered_by_moderate_severity (line 55) | public function test_french_mild_words_filtered_by_moderate_severity()...
    method test_french_moderate_words_caught_at_moderate_severity (line 62) | public function test_french_moderate_words_caught_at_moderate_severity...
    method test_french_moderate_words_filtered_by_high_severity (line 69) | public function test_french_moderate_words_filtered_by_high_severity()...
    method test_french_default_high_words_caught (line 76) | public function test_french_default_high_words_caught(): void
    method test_french_extreme_words_caught_at_extreme (line 83) | public function test_french_extreme_words_caught_at_extreme(): void
    method test_french_high_words_filtered_by_extreme (line 89) | public function test_french_high_words_filtered_by_extreme(): void
    method test_german_mild_words_filtered_by_moderate_severity (line 98) | public function test_german_mild_words_filtered_by_moderate_severity()...
    method test_german_moderate_words_caught_at_moderate_severity (line 105) | public function test_german_moderate_words_caught_at_moderate_severity...
    method test_german_moderate_words_filtered_by_high_severity (line 112) | public function test_german_moderate_words_filtered_by_high_severity()...
    method test_german_default_high_words_caught (line 119) | public function test_german_default_high_words_caught(): void
    method test_german_extreme_words_caught_at_extreme (line 126) | public function test_german_extreme_words_caught_at_extreme(): void
    method test_german_high_words_filtered_by_extreme (line 132) | public function test_german_high_words_filtered_by_extreme(): void
    method test_unmapped_words_default_to_high_across_languages (line 141) | public function test_unmapped_words_default_to_high_across_languages()...

FILE: tests/SpanishStringNormalizerTest.php
  class SpanishStringNormalizerTest (line 7) | class SpanishStringNormalizerTest extends TestCase
    method setUp (line 11) | public function setUp(): void
    method test_normalize_accented_vowels (line 17) | public function test_normalize_accented_vowels()
    method test_normalize_enye_character (line 24) | public function test_normalize_enye_character()
    method test_normalize_double_l (line 31) | public function test_normalize_double_l()
    method test_normalize_double_r (line 38) | public function test_normalize_double_r()
    method test_normalize_spanish_profanity_variants (line 45) | public function test_normalize_spanish_profanity_variants()
    method test_normalize_preserves_non_spanish_characters (line 52) | public function test_normalize_preserves_non_spanish_characters()
    method test_normalize_mixed_case_preservation (line 58) | public function test_normalize_mixed_case_preservation()
    method test_normalize_empty_and_special_strings (line 65) | public function test_normalize_empty_and_special_strings()

FILE: tests/StrMacroTest.php
  class StrMacroTest (line 8) | class StrMacroTest extends TestCase
    method test_str_is_profane_returns_true_for_profane_text (line 10) | public function test_str_is_profane_returns_true_for_profane_text()
    method test_str_is_profane_returns_false_for_clean_text (line 15) | public function test_str_is_profane_returns_false_for_clean_text()
    method test_str_clean_profanity_masks_profane_text (line 20) | public function test_str_clean_profanity_masks_profane_text()
    method test_str_clean_profanity_returns_clean_text_unchanged (line 28) | public function test_str_clean_profanity_returns_clean_text_unchanged()
    method test_stringable_is_profane_returns_true_for_profane_text (line 33) | public function test_stringable_is_profane_returns_true_for_profane_te...
    method test_stringable_is_profane_returns_false_for_clean_text (line 38) | public function test_stringable_is_profane_returns_false_for_clean_text()
    method test_stringable_clean_profanity_returns_stringable_instance (line 43) | public function test_stringable_clean_profanity_returns_stringable_ins...
    method test_stringable_clean_profanity_returns_clean_text_unchanged (line 52) | public function test_stringable_clean_profanity_returns_clean_text_unc...

FILE: tests/TestCase.php
  class TestCase (line 9) | abstract class TestCase extends BaseTestCase
    method getPackageProviders (line 12) | protected function getPackageProviders($app)
    method setUp (line 19) | protected function setUp(): void

FILE: tests/UuidFalsePositiveTest.php
  class UuidFalsePositiveTest (line 7) | class UuidFalsePositiveTest extends TestCase
    method test_uuid_not_flagged_as_profanity (line 9) | public function test_uuid_not_flagged_as_profanity()
    method test_hex_string_not_flagged (line 15) | public function test_hex_string_not_flagged()
    method test_profanity_alongside_uuid_still_detected (line 21) | public function test_profanity_alongside_uuid_still_detected()
    method test_standalone_profanity_still_detected (line 28) | public function test_standalone_profanity_still_detected()
    method test_normal_profanity_detection_unaffected (line 34) | public function test_normal_profanity_detection_unaffected()
    method test_uuid_in_sentence_not_flagged (line 40) | public function test_uuid_in_sentence_not_flagged()
    method test_short_hex_does_not_suppress_profanity (line 46) | public function test_short_hex_does_not_suppress_profanity()
    method test_pure_letter_hex_does_not_suppress_profanity (line 52) | public function test_pure_letter_hex_does_not_suppress_profanity()
    method test_md5_hash_not_flagged (line 59) | public function test_md5_hash_not_flagged()
    method test_multiple_uuids_not_flagged (line 65) | public function test_multiple_uuids_not_flagged()
Condensed preview — 84 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (397K chars).
[
  {
    "path": ".github/workflows/main.yml",
    "chars": 647,
    "preview": "name: Run Tests\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    steps:\n      # Step 1: Check ou"
  },
  {
    "path": ".gitignore",
    "chars": 50,
    "preview": "/vendor\ncomposer.lock\n.phpunit.result.cache\n/.idea"
  },
  {
    "path": ".styleci.yml",
    "chars": 66,
    "preview": "preset: laravel\n\ndisabled:\n  - single_class_element_per_statement\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1397,
    "preview": "# Changelog\n\nAll notable changes to `blasp` will be documented in this file\n\n## 3.0.0 - 2025-01-05\n\n### Added\n- Custom m"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2972,
    "preview": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contributi"
  },
  {
    "path": "LICENSE.md",
    "chars": 1066,
    "preview": "MIT License\n\nCopyright (c) Michael Deeming\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 19749,
    "preview": "<p align=\"center\">\n    <img src=\"./assets/icon.png\" alt=\"Blasp Icon\" width=\"150\" height=\"150\"/>\n</p>\n\n> **Official API A"
  },
  {
    "path": "composer.json",
    "chars": 1403,
    "preview": "{\n    \"name\": \"blaspsoft/blasp\",\n    \"description\": \"Blasp is a powerful and customisable profanity filter package for L"
  },
  {
    "path": "config/blasp.php",
    "chars": 8357,
    "preview": "<?php\n\nreturn [\n\n    /*\n    |--------------------------------------------------------------------------\n    | Default Dr"
  },
  {
    "path": "config/languages/english.php",
    "chars": 33810,
    "preview": "<?php\n\nreturn [\n    'severity' => [\n        'mild' => [\n            'damn', 'hell', 'crap', 'arse', 'sucks', 'piss', 'bl"
  },
  {
    "path": "config/languages/french.php",
    "chars": 43244,
    "preview": "<?php\n\nreturn [\n    'severity' => [\n        'mild' => [\n            'crotte', 'crottes', 'caca', 'cacas', 'zut',\n       "
  },
  {
    "path": "config/languages/german.php",
    "chars": 37406,
    "preview": "<?php\n\nreturn [\n    'severity' => [\n        'mild' => [\n            'mist', 'kacke', 'verdammt', 'verdammte', 'verdammte"
  },
  {
    "path": "config/languages/spanish.php",
    "chars": 17854,
    "preview": "<?php\n\nreturn [\n    'severity' => [\n        'mild' => [\n            'maldito', 'maldita', 'maldición', 'maldicion', 'car"
  },
  {
    "path": "phpunit.xml",
    "chars": 279,
    "preview": "<phpunit bootstrap=\"vendor/autoload.php\" colors=\"true\">\n    <testsuites>\n        <testsuite name=\"Application Tests\">\n  "
  },
  {
    "path": "src/BlaspManager.php",
    "chars": 4250,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp;\n\nuse Closure;\nuse Illuminate\\Contracts\\Foundation\\Application;\nuse Blaspsoft\\Blasp\\Cor"
  },
  {
    "path": "src/BlaspServiceProvider.php",
    "chars": 3103,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp;\n\nuse Illuminate\\Support\\Facades\\Blade;\nuse Illuminate\\Support\\ServiceProvider;\nuse Ill"
  },
  {
    "path": "src/Blaspable.php",
    "chars": 2883,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp;\n\nuse Closure;\nuse Blaspsoft\\Blasp\\Core\\Result;\nuse Blaspsoft\\Blasp\\Events\\ModelProfani"
  },
  {
    "path": "src/Console/ClearCommand.php",
    "chars": 398,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Console;\n\nuse Illuminate\\Console\\Command;\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\n\nclass C"
  },
  {
    "path": "src/Console/LanguagesCommand.php",
    "chars": 1009,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Console;\n\nuse Illuminate\\Console\\Command;\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\n\nclass L"
  },
  {
    "path": "src/Console/TestCommand.php",
    "chars": 1875,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Console;\n\nuse Illuminate\\Console\\Command;\n\nclass TestCommand extends Command\n{\n    prot"
  },
  {
    "path": "src/Core/Analyzer.php",
    "chars": 866,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core;\n\nuse Blaspsoft\\Blasp\\Core\\Contracts\\DriverInterface;\nuse Blaspsoft\\Blasp\\Core\\Con"
  },
  {
    "path": "src/Core/Contracts/DriverInterface.php",
    "chars": 275,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Contracts;\n\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\nuse Blaspsoft\\Blasp\\Core\\Result;\n"
  },
  {
    "path": "src/Core/Contracts/MaskStrategyInterface.php",
    "chars": 147,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Contracts;\n\ninterface MaskStrategyInterface\n{\n    public function mask(string $wor"
  },
  {
    "path": "src/Core/Dictionary.php",
    "chars": 11822,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core;\n\nuse Blaspsoft\\Blasp\\Enums\\Severity;\nuse Blaspsoft\\Blasp\\Core\\Matchers\\RegexMatch"
  },
  {
    "path": "src/Core/Masking/CallbackMask.php",
    "chars": 375,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Masking;\n\nuse Closure;\nuse Blaspsoft\\Blasp\\Core\\Contracts\\MaskStrategyInterface;\n\n"
  },
  {
    "path": "src/Core/Masking/CharacterMask.php",
    "chars": 434,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Masking;\n\nuse Blaspsoft\\Blasp\\Core\\Contracts\\MaskStrategyInterface;\n\nclass Charact"
  },
  {
    "path": "src/Core/Masking/GrawlixMask.php",
    "chars": 448,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Masking;\n\nuse Blaspsoft\\Blasp\\Core\\Contracts\\MaskStrategyInterface;\n\nclass Grawlix"
  },
  {
    "path": "src/Core/MatchedWord.php",
    "chars": 808,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core;\n\nuse Blaspsoft\\Blasp\\Enums\\Severity;\nuse JsonSerializable;\n\nreadonly class Matche"
  },
  {
    "path": "src/Core/Matchers/CompoundWordDetector.php",
    "chars": 1389,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Matchers;\n\nclass CompoundWordDetector\n{\n    private const SUFFIXES = ['s', 'es', '"
  },
  {
    "path": "src/Core/Matchers/FalsePositiveFilter.php",
    "chars": 4163,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Matchers;\n\nclass FalsePositiveFilter\n{\n    private array $falsePositivesMap;\n\n    "
  },
  {
    "path": "src/Core/Matchers/PhoneticMatcher.php",
    "chars": 2215,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Matchers;\n\nclass PhoneticMatcher\n{\n    /** @var array<string, array<string>> metap"
  },
  {
    "path": "src/Core/Matchers/RegexMatcher.php",
    "chars": 4006,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Matchers;\n\nclass RegexMatcher\n{\n    private const SEPARATOR_PLACEHOLDER = '{!!}';\n"
  },
  {
    "path": "src/Core/Normalizers/EnglishNormalizer.php",
    "chars": 198,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Normalizers;\n\nclass EnglishNormalizer implements StringNormalizer\n{\n    public fun"
  },
  {
    "path": "src/Core/Normalizers/FrenchNormalizer.php",
    "chars": 1043,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Normalizers;\n\nclass FrenchNormalizer implements StringNormalizer\n{\n    public func"
  },
  {
    "path": "src/Core/Normalizers/GermanNormalizer.php",
    "chars": 715,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Normalizers;\n\nclass GermanNormalizer implements StringNormalizer\n{\n    public func"
  },
  {
    "path": "src/Core/Normalizers/NullNormalizer.php",
    "chars": 195,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Normalizers;\n\nclass NullNormalizer implements StringNormalizer\n{\n    public functi"
  },
  {
    "path": "src/Core/Normalizers/SpanishNormalizer.php",
    "chars": 1105,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Normalizers;\n\nclass SpanishNormalizer implements StringNormalizer\n{\n    public fun"
  },
  {
    "path": "src/Core/Normalizers/StringNormalizer.php",
    "chars": 138,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core\\Normalizers;\n\ninterface StringNormalizer\n{\n    public function normalize(string $s"
  },
  {
    "path": "src/Core/Result.php",
    "chars": 4911,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core;\n\nuse Blaspsoft\\Blasp\\Enums\\Severity;\nuse Illuminate\\Support\\Collection;\nuse JsonS"
  },
  {
    "path": "src/Core/Score.php",
    "chars": 514,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Core;\n\nclass Score\n{\n    public static function calculate(array $matchedWords, int $tot"
  },
  {
    "path": "src/Drivers/PatternDriver.php",
    "chars": 3883,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Drivers;\n\nuse Blaspsoft\\Blasp\\Core\\Contracts\\DriverInterface;\nuse Blaspsoft\\Blasp\\Core\\"
  },
  {
    "path": "src/Drivers/PhoneticDriver.php",
    "chars": 4576,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Drivers;\n\nuse Blaspsoft\\Blasp\\Core\\Contracts\\DriverInterface;\nuse Blaspsoft\\Blasp\\Core\\"
  },
  {
    "path": "src/Drivers/PipelineDriver.php",
    "chars": 2790,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Drivers;\n\nuse Blaspsoft\\Blasp\\Core\\Contracts\\DriverInterface;\nuse Blaspsoft\\Blasp\\Core\\"
  },
  {
    "path": "src/Drivers/RegexDriver.php",
    "chars": 7276,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Drivers;\n\nuse Blaspsoft\\Blasp\\Core\\Contracts\\DriverInterface;\nuse Blaspsoft\\Blasp\\Core\\"
  },
  {
    "path": "src/Enums/Severity.php",
    "chars": 510,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Enums;\n\nenum Severity: string\n{\n    case Mild = 'mild';\n    case Moderate = 'moderate';"
  },
  {
    "path": "src/Events/ContentBlocked.php",
    "chars": 333,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Events;\n\nuse Blaspsoft\\Blasp\\Core\\Result;\nuse Illuminate\\Http\\Request;\n\nclass ContentBl"
  },
  {
    "path": "src/Events/ModelProfanityDetected.php",
    "chars": 312,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Events;\n\nuse Blaspsoft\\Blasp\\Core\\Result;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclas"
  },
  {
    "path": "src/Events/ProfanityDetected.php",
    "chars": 232,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Events;\n\nuse Blaspsoft\\Blasp\\Core\\Result;\n\nclass ProfanityDetected\n{\n    public functio"
  },
  {
    "path": "src/Exceptions/ProfanityRejectedException.php",
    "chars": 645,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Exceptions;\n\nuse Blaspsoft\\Blasp\\Core\\Result;\nuse Illuminate\\Database\\Eloquent\\Model;\nu"
  },
  {
    "path": "src/Facades/Blasp.php",
    "chars": 2685,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Facades;\n\nuse Blaspsoft\\Blasp\\BlaspManager;\nuse Blaspsoft\\Blasp\\Core\\Result;\nuse Blasps"
  },
  {
    "path": "src/Middleware/CheckProfanity.php",
    "chars": 2404,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Symfony\\Component\\HttpFounda"
  },
  {
    "path": "src/PendingCheck.php",
    "chars": 9037,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp;\n\nuse Closure;\nuse Blaspsoft\\Blasp\\Core\\Analyzer;\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\n"
  },
  {
    "path": "src/Rules/Profanity.php",
    "chars": 1744,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Rules;\n\nuse Closure;\nuse Illuminate\\Contracts\\Validation\\ValidationRule;\nuse Blaspsoft\\"
  },
  {
    "path": "src/Testing/BlaspFake.php",
    "chars": 2615,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Testing;\n\nuse Blaspsoft\\Blasp\\Core\\Result;\nuse Blaspsoft\\Blasp\\PendingCheck;\nuse PHPUni"
  },
  {
    "path": "tests/AllLanguagesApiTest.php",
    "chars": 2846,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass AllLanguagesApiTest extends TestCase\n"
  },
  {
    "path": "tests/AllLanguagesDetectionTest.php",
    "chars": 4254,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass AllLanguagesDetectionTest extends Tes"
  },
  {
    "path": "tests/BladeDirectiveTest.php",
    "chars": 1240,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Illuminate\\Support\\Facades\\Blade;\n\nclass BladeDirectiveTest extends TestCas"
  },
  {
    "path": "tests/BlaspCheckTest.php",
    "chars": 11538,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass BlaspCheckTest extends TestCase\n{\n   "
  },
  {
    "path": "tests/BlaspCheckValidationTest.php",
    "chars": 908,
    "preview": "<?php\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Illuminate\\Support\\Facades\\Validator;\n\nclass BlaspCheckValidationTest extend"
  },
  {
    "path": "tests/BlaspableTest.php",
    "chars": 9869,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Result;\nuse Blaspsoft\\Blasp\\Blaspable;\nuse Blaspsoft\\B"
  },
  {
    "path": "tests/BypassVulnerabilityTest.php",
    "chars": 2846,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass BypassVulnerabilityTest extends TestC"
  },
  {
    "path": "tests/CacheDriverConfigurationTest.php",
    "chars": 1737,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\nuse Illuminate\\Support\\Facades\\Cache;\nuse "
  },
  {
    "path": "tests/ConfigurationLoaderLanguageTest.php",
    "chars": 5600,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\nuse Blaspsoft\\Blasp\\Core\\Normalizers\\Engli"
  },
  {
    "path": "tests/ConfigurationLoaderTest.php",
    "chars": 3995,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\nuse Illuminate\\Support\\Facades\\Cache;\n\ncla"
  },
  {
    "path": "tests/CustomMaskCharacterTest.php",
    "chars": 2455,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass CustomMaskCharacterTest extends TestC"
  },
  {
    "path": "tests/DetectionStrategyRegistryTest.php",
    "chars": 2491,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\BlaspManager;\nuse Blaspsoft\\Blasp\\Core\\Contracts\\DriverInte"
  },
  {
    "path": "tests/EdgeCaseTest.php",
    "chars": 1524,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass EdgeCaseTest extends TestCase\n{\n    p"
  },
  {
    "path": "tests/EmptyInputTest.php",
    "chars": 1080,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass EmptyInputTest extends TestCase\n{\n   "
  },
  {
    "path": "tests/FrenchStringNormalizerTest.php",
    "chars": 7298,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Normalizers\\FrenchNormalizer;\n\nclass FrenchStringNorma"
  },
  {
    "path": "tests/GermanStringNormalizerTest.php",
    "chars": 2812,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Normalizers\\GermanNormalizer;\n\nclass GermanStringNorma"
  },
  {
    "path": "tests/Issue24Test.php",
    "chars": 1078,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass Issue24Test extends TestCase\n{\n    pu"
  },
  {
    "path": "tests/Issue32FalsePositiveTest.php",
    "chars": 1221,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Blaspsoft\\Blasp\\Facades\\Blas"
  },
  {
    "path": "tests/MiddlewareAliasTest.php",
    "chars": 435,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Middleware\\CheckProfanity;\n\nclass MiddlewareAliasTest exten"
  },
  {
    "path": "tests/MultiLanguageDetectionConfigTest.php",
    "chars": 2978,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\nuse Blaspsoft\\Blasp\\Enums\\Severity;\n\nclass"
  },
  {
    "path": "tests/MultiLanguageProfanityTest.php",
    "chars": 5088,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\n\nclass "
  },
  {
    "path": "tests/PhoneticDriverTest.php",
    "chars": 6623,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Matchers\\PhoneticMatcher;\nuse Blaspsoft\\Blasp\\Enums\\Se"
  },
  {
    "path": "tests/PipelineDriverTest.php",
    "chars": 9030,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Drivers\\PipelineDriver;\nuse Blaspsoft\\Blasp\\Enums\\Severity;"
  },
  {
    "path": "tests/ProfanityExpressionGeneratorTest.php",
    "chars": 9651,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Matchers\\RegexMatcher;\n\nclass ProfanityExpressionGener"
  },
  {
    "path": "tests/ResultCachingTest.php",
    "chars": 5790,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Dictionary;\nuse Blaspsoft\\Blasp\\Enums\\Severity;\nuse Bl"
  },
  {
    "path": "tests/SeverityMapTest.php",
    "chars": 7368,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\nuse Blaspsoft\\Blasp\\Enums\\Severity;\n\nclass S"
  },
  {
    "path": "tests/SpanishStringNormalizerTest.php",
    "chars": 2735,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Core\\Normalizers\\SpanishNormalizer;\n\nclass SpanishStringNor"
  },
  {
    "path": "tests/StrMacroTest.php",
    "chars": 1653,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Illuminate\\Support\\Str;\nuse Illuminate\\Support\\Stringable;\n\nclass StrMacroT"
  },
  {
    "path": "tests/TestCase.php",
    "chars": 1013,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\BlaspServiceProvider;\nuse Illuminate\\Support\\Facades\\Config"
  },
  {
    "path": "tests/UuidFalsePositiveTest.php",
    "chars": 2177,
    "preview": "<?php\n\nnamespace Blaspsoft\\Blasp\\Tests;\n\nuse Blaspsoft\\Blasp\\Facades\\Blasp;\n\nclass UuidFalsePositiveTest extends TestCas"
  }
]

About this extraction

This page contains the full source code of the Blaspsoft/blasp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 84 files (367.1 KB), approximately 98.4k tokens, and a symbol index with 551 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!