Thanks for your interest in contributing! Whether you're adding a single word, reporting a false positive, or building a new language pack — every contribution helps.
git clone https://github.com/badursun/terlik.js.git
cd terlik.js
pnpm installpnpm test # run tests
pnpm test:watch # run tests in watch mode
pnpm test:coverage # run tests with coverage report
pnpm typecheck # TypeScript type checking
pnpm build # build ESM + CJS output
pnpm dev:live # start interactive test server at http://localhost:2026Pre-commit hooks run automatically via Husky to ensure code quality.
The most common contribution. No TypeScript knowledge needed — just edit a JSON file.
src/lang/tr/dictionary.json ← Turkish
src/lang/en/dictionary.json ← English
src/lang/es/dictionary.json ← Spanish
src/lang/de/dictionary.json ← German
{
"root": "newword",
"variants": ["n3ww0rd", "neww0rd"],
"severity": "high",
"category": "insult",
"suffixable": true
}Fields:
root— The base form of the word (lowercase)variants— Alternative spellings the normalizer might not catch (leet speak, phonetic, etc.)severity—"high"(strongest),"medium"(moderate),"low"(mild)category—"sexual","insult","slur","general"suffixable— Set totrueif the word takes grammatical suffixes (e.g.,newwordler,newwording). Keepfalsefor very short roots (3 chars) to avoid false positives — use explicit variants instead.
If your new root could cause false positives on legitimate words, add them to the whitelist array:
"whitelist": ["existingword", "yournewsafeword"]Add a test in the language test file (e.g., tests/profanity.test.ts for Turkish, tests/lang/en.test.ts for English):
it("detects newword", () => {
expect(terlik.containsProfanity("newword")).toBe(true);
});pnpm test # all tests pass
pnpm typecheck # no type errors
pnpm build # builds successfullyOpen an issue with:
- False positive (safe word flagged): Include the word, the language, and why it's legitimate.
- False negative (profane word missed): Include the word (or a sanitized version), evasion technique used, and the language.
If you can, include a fix as a PR — it's the fastest path to resolution.
-
Create
src/lang/xx/folder (wherexxis the BCP-47 language code) -
Add
dictionary.json:{ "version": 1, "suffixes": ["ing", "ed", "s"], "entries": [ { "root": "word", "variants": ["w0rd"], "severity": "high", "category": "general", "suffixable": true } ], "whitelist": ["safeword"] } -
Add
config.ts:import type { LanguageConfig } from "../types.js"; import dictionary from "./dictionary.json"; import { validateDictionary } from "../../dictionary/schema.js"; const data = validateDictionary(dictionary); export const config: LanguageConfig = { locale: "xx", dictionary: data, charMap: { // language-specific character mappings (e.g., ñ → n, ß → ss) }, leetMap: { "0": "o", "1": "i", "3": "e", "4": "a", "5": "s", "7": "t", "@": "a", "$": "s", "!": "i", }, charClasses: { // character classes for regex pattern generation }, };
-
Register in
src/lang/index.ts— add one import and one registry line. -
Add tests in
tests/lang/xx.test.ts. -
Verify:
pnpm test && pnpm typecheck && pnpm build
We use Conventional Commits:
feat(tr): add new root word "example"
fix(en): false positive on "grasshopper"
feat(lang): add Portuguese language pack
test: add edge case tests for suffix engine
docs: update README with new API example
perf: optimize pattern compilation
Common prefixes: feat, fix, test, docs, perf, chore, refactor.
Include the language scope when the change is language-specific (e.g., feat(tr):, fix(en):).
- Fork the repository and create a feature branch from
main - Make your changes with tests
- Ensure all checks pass:
pnpm test # all tests pass pnpm typecheck # clean pnpm build # ESM + CJS + DTS clean
- Submit a PR with a clear description (the PR template will guide you)
- Respond to review feedback
- Severity levels:
high(strongest profanity),medium(moderate),low(mild) - Categories:
sexual,insult,slur,general - Suffixable: Set to
truefor words that commonly take grammatical suffixes. Keepfalsefor very short roots (3 chars) to avoid false positives — use explicit variants instead. - Whitelist: Add legitimate words that might be false positives (e.g., "assassin" contains "ass")
- Precision over recall: When in doubt, leave it out. False positives are worse than false negatives in production chat systems.
- TypeScript strict mode is enforced
- Keep functions focused and small
- Add JSDoc to all public exports
- Write tests for new functionality
- No runtime dependencies