Documentation

quote-punctuation

The opt-in quote-punctuation rule moves a comma or full stop from after a closing quotation mark to before it, for the locale whose convention puts it there, and declines wherever the placement depends on meaning.

Updated · spec 1.22.0

quote-punctuation moves a comma or a full stop from after a closing quotation mark to before it: “hello”, becomes “hello,”. It applies only to locales whose cited convention puts these two marks inside the quotation mark. In the shipped locale data that is en-US (the Chicago Manual of Style); every other locale is set to none, and the rule does nothing there.

This rule is off by default. It changes where a character the author typed sits, not just which glyph it is. Turn it on with rules: { "quote-punctuation": true }.

Why it is opt-in #

Where a comma or full stop goes relative to a closing quotation mark is a convention, and in most locales it depends on meaning: a mark that belongs to the quoted words goes inside, one that belongs to the surrounding sentence goes outside. "Stop." and "most popular", look alike on the surface, and a rule that scans code points cannot tell them apart. So the rule is defined only where the convention is positional, a comma or full stop goes inside whatever the sense, and the locale says none everywhere else.

Even for en-US the move is not always right. In technical text a quoted literal absorbs the punctuation: set it to "strict". becomes set it to “strict.”. Code spans and code blocks are skipped in the html and markdown modes, so a literal written as code is safe. That is why the rule is an opt-in.

How it works #

The rule looks at each closing primary quotation mark of the locale (for en-US, U+201D) and moves the comma or full stop that follows it to before it, when all of these hold:

  1. A letter comes before the quotation mark. A digit, another punctuation mark, a bracket or another quotation mark does not qualify, so “Route 66”., “Stop!”., “e.g.”, and the nested “He said ‘hi’”. are left alone.
  2. The mark is a comma or a full stop. Semicolons, colons, question marks and exclamation marks are never moved.
  3. A space, a line break or the end of the text comes next. A letter, a bracket, another dot or a quotation mark does not qualify, so “file”.txt, (“hi”.) and “wait”… are left alone.

Nothing is inserted or deleted, so a call keeps its length, and the output is a fixed point: run again, nothing more moves.

Modes #

A boundary between the quotation mark and the punctuation, or immediately before or after the pair, blocks the move. In html and markdown that means an element or an emphasis delimiter: <em>“hello”</em>, and *“hello”*, keep the comma where it was. In text mode a range given to protect counts as a boundary in the same way. The end of a document is not a boundary.

Locale data #

The rule reads quotes.punctuationPlacement, one of inside or none.

ValueLocales
insideen-US
noneevery other locale

none means the rule does nothing. For en-GB and de-DE the cited convention places these marks by meaning (the University of Oxford Style Guide (2014) and the Amtliches Regelwerk of 2024), so there is nothing a scan can apply. For the others no convention has been verified.

Options #

Enable the rule explicitly:

transform(text, { locale: 'en-US', rules: { 'quote-punctuation': true } })

false is the default. There is no other setting.

Edge cases #

  • Only the closing primary quotation mark is read. The secondary close, which in en-US is also the apostrophe, is never a trigger: the dogs’, bowls is untouched.
  • A duplicate is not removed: “Stop!”. keeps both marks.
  • An unpaired closing quotation mark that the author typed in curly form moves its comma too. The rule trusts the glyph, not the pairing.
  • A quotation after a number (“Route 66”.) and a nested quotation keep the punctuation outside.

Normative text: spec/rules/quote-punctuation.md.

Already using polytypo? Add our badge

Two lines: a script tag and a span. No per-embed network call. Pick a language and a theme, then copy the code into your footer or credits. Embedding notes cover Content Security Policy. Sites with the badge are listed on Sites using polytypo.

<script async src="https://polytypo.dev/badge.js"></script>
<span data-polytypo-lang="en" data-polytypo-theme="light"></span>

Search runs in your browser. Nothing is sent anywhere.