Censoring
censor returns the text with every match masked. Everything else, including spacing, punctuation and emoji, is left as it was.
censor('you muji'); // 'you ****'
censor('F.U.C.K this Sh1t!'); // '******* this ****!'
censor('मुजीको क्लास'); // '*** क्लास'
censor('Great teacher!'); // 'Great teacher!'Check and censor in one pass
To find out whether text has profanity and get a censored copy, use check. It scans the text once and returns a result you can read and then censor, so you don't pay for a second scan:
final result = check('you muji, F.U.C.K');
result.hasProfanity; // true
result.words; // ['muji', 'fuck']
result.censor(); // 'you ****, *******'Because censor() is a method on the result, you can chain it:
check('you muji').censor(); // 'you ****'
check('you muji').censor(mask: '#'); // 'you ####'A common pattern is to save the censored text and flag the original for a moderator:
final result = check(commentText);
await comments.add({
'body': result.censor(),
'flagged': result.hasProfanity,
'flaggedWords': result.words,
});Calling containsProfanity(text) and then censor(text) gives the same result, but scans the text twice.
Change the mask
mask sets the string used in place of each character of a match. The default is *.
censor('you muji', mask: '#'); // 'you ####'
censor('you muji', mask: '•'); // 'you ••••'Custom replacements
replace receives each match and returns the text to put in its place. It takes precedence over mask.
// A fixed label
censor('you muji', replace: (m) => '[censored]');
// 'you [censored]'
// Keep the first letter
censor('you muji', replace: (m) => m.text[0] + '*' * (m.text.length - 1));
// 'you m***'
// Grawlix
censor('you muji', replace: (m) => ('@#\$%&!' * m.text.length).substring(0, m.text.length));
// 'you @#$%'
// Wrap it for your UI instead of hiding it
censor('you muji', replace: (m) => '<mark>${m.text}</mark>');
// 'you <mark>muji</mark>'The match is the same as the ones returned by findProfanityMatches:
| Field | Example | Meaning |
|---|---|---|
text | 'F.U.C.K' | Exactly what the user typed. |
normalized | 'fuck' | The normalized form that matched. |
start | 0 | Start index in the input. |
end | 7 | End index in the input, exclusive. |
In Flutter, to style matches rather than replace them, build a TextSpan from the positions instead; see Highlight matches.
With filter options
censor and check accept the same languages and strictness options as every other function:
censor('fuck muji', languages: [Language.romanized]); // 'fuck ****'
censor('you idiot', strictness: Strictness.lenient); // 'you idiot'
check('you idiot', strictness: Strictness.lenient).censor(); // 'you idiot'A ProfanityFilter has check and censor too:
final filter = ProfanityFilter(languages: [Language.romanized, Language.devanagari]);
filter.censor('fuck muji'); // 'fuck ****'
filter.check('मुजी').censor(); // '**'What gets masked
- Exactly the characters the user typed. Positions are traced back through normalization, so leetspeak (
Sh1t),!fori(sh!!t), full-width letters (FUCK) and zero-width characters are masked in full. - Spelled-out words, dots included.
F.U.C.Kbecomes*******. - One mask character per visible character. Devanagari is counted by what you see, not by code unit, so
मुजीbecomes**, not****. - Whitespace inside a phrase is kept.
sasto manchebecomes***** ******. - Overlapping matches are merged.
chaak ko pwalis a phrase that contains the wordchaak, and it's masked once, as***** ** ****. - Postpositions are masked with the word.
mujikobecomes******, because the filter reads it as one word.