Technical previewShare feedback

Search

Search documentation

Natural language plugins

Updated Nov 1, 2026

Natural-language plugins parse prose into nlcst and check or rewrite it. The checks report messages on the file and leave the text as it is. Reach them from Markdown through md.toNaturalLanguage or from HTML through html.toNaturalLanguage.

nl.parse

Parses natural language into an nlcst tree of paragraphs, sentences, words and punctuation.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.smartypants())
    .use(nl.stringify())
    .process("\"Hi\" -- she said...")
)
file["value"]
# “Hi” — she said…

Follows retext-english.

OptionTypeDefaultDescription
languageLanguageThe rules sentences and words are found by.

Language

The language nl.parse reads: parse-latin's rules, or parse-english's on top of them.

Takes Literal["latin", "english"].

nl.equality

Warns about insensitive, inconsiderate wording.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.equality())
    .use(nl.stringify())
    .process("Hey guys, the chairman is here.")
)
file["messages"][0]["reason"]
# Unexpected potentially insensitive use of `guys`, in somes cases `people`, ` …

Follows retext-equality.

OptionTypeDefaultDescription
ignoreOptional[List[str]]NonePhrases not to warn about.
binaryOptional[bool]NoneWhether a phrase used next to its counterpart (he or she) is fine.

nl.passive

Warns about the passive voice.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.passive())
    .use(nl.stringify())
    .process("The cake was eaten.")
)
file["messages"][0]["reason"]
# Unexpected use of the passive voice

Follows retext-passive.

OptionTypeDefaultDescription
ignoreOptional[List[str]]NoneParticiples not to warn about.

nl.readability

Warns about sentences too hard to read for a target age.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.readability({"age": 12}))
    .use(nl.stringify())
    .process("The constitutionally mandated intergovernmental reconciliation procedures necessitate comprehensive multilateral deliberation.")
)
file["messages"][0]["reason"]
# Unexpected hard to read sentence, according to 6 out of 7 algorithms

Follows retext-readability.

OptionTypeDefaultDescription
ageOptional[float]NoneThe age of the target audience: 16 by default.
thresholdOptional[float]NoneThe share of algorithms that must find a sentence too hard: 4/7 by default.
minWordsOptional[float]NoneThe fewest words a sentence needs to be checked: 5 by default.

nl.repeatedWords

Warns about a word written twice in a row.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.repeated_words())
    .use(nl.stringify())
    .process("It is the the best.")
)
file["messages"][0]["reason"]
# Unexpected repeated `the`, remove one occurrence

Follows retext-repeated-words.

No options.

nl.sentenceSpacing

Warns about the spaces between sentences: one by default.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.sentence_spacing())
    .use(nl.stringify())
    .process("One.  Two.")
)
file["messages"][0]["reason"]
# Unexpected 2 spaces between sentence, expected 1 space

Follows retext-sentence-spacing.

OptionTypeDefaultDescription
preferredOptional[Spacing]The spacing expected between sentences: 1 or "space" (the default), 2 or "double-space", 0 or "newline".

Spacing

A spacing between sentences, as a count of spaces or by name.

Takes Union[float, str].

nl.stringify

Serializes nlcst back into text.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.stringify())
    .process("Same text out.")
)
file["value"]
# Same text out.

Follows retext-stringify.

No options.

nl.smartypants

Turns straight quotes, dashes, dots and backticks into their typographic forms.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.smartypants())
    .use(nl.stringify())
    .process("\"It's\" -- fine...")
)
file["value"]
# “It’s” — fine…

Follows retext-smartypants.

OptionTypeDefaultDescription
backticksBackticksTurn double backticks and double straight single quotes into double quotes; all turns single ones into single quotes too, and needs quotes: false.
closingQuotesOptional[QuoteCharacters]The closing quotes to use: ” and ’ by default.
dashesDashesTurn two dashes into an em dash; oldschool and inverted treat three too.
ellipsesEllipsesTurn three dots into an ellipsis, with spaces between them or without.
openingQuotesOptional[QuoteCharacters]The opening quotes to use: “ and ‘ by default.
quotesOptional[bool]TrueTurn straight quotes into smart ones.

Backticks

Takes Union[bool, Literal["all"]].

QuoteCharacters

The quote characters to use for double and single quotes.

OptionTypeDefaultDescription
doublestr
singlestr

Dashes

Takes Union[bool, Literal["oldschool", "inverted"]].

Ellipses

Takes Union[bool, Literal["spaced", "unspaced"]].

nl.quotes

Warns about quotes and apostrophes that are not the preferred kind, smart by default.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.quotes())
    .use(nl.stringify())
    .process("He said \"hi\".")
)
file["messages"][0]["reason"]
# Unexpected straight quote `"`, expected `“`

Follows retext-quotes.

OptionTypeDefaultDescription
preferredOptional[QuoteStyle]The style of quotes and apostrophes to prefer: smart by default.
smartOptional[List[str]]NoneSmart quotes by nesting level, each one character or an opening and closing pair: ["“”", "‘’"] by default.
straightOptional[List[str]]NoneStraight quotes by nesting level: ["\"", "'"] by default.

QuoteStyle

A style of quotes: straight (") or smart (“”).

Takes Literal["smart", "straight"].

nl.contractions

Warns about missing or misplaced apostrophes in contractions.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.contractions())
    .use(nl.stringify())
    .process("Dont do that.")
)
file["messages"][0]["reason"]
# Unexpected missing apostrophe in `Dont`, expected `Don’t`

Follows retext-contractions.

OptionTypeDefaultDescription
allowLiteralsOptional[bool]FalseAlso check contractions written as literals, such as “hasnt”.
straightOptional[bool]FalseExpect straight apostrophes (') instead of smart ones (’).

nl.indefiniteArticle

Warns about a and an used before the wrong sound.

from textlane import nl, textlane

file = (
    textlane()
    .use(nl.parse())
    .use(nl.indefinite_article())
    .use(nl.stringify())
    .process("It is a apple.")
)
file["messages"][0]["reason"]
# Unexpected article `a` before `apple`, expected `an`

Follows retext-indefinite-article.

No options.

Created with ❤ and Livemark