Documentation

Hyphen

The hyphen rule binds the hyphen in listed Russian and Ukrainian forms with a non-breaking hyphen so a line break cannot split the word.

Updated · spec 1.12.0

The hyphen rule replaces a hyphen-minus (U+002D) with a non-breaking hyphen (U+2011) inside a closed list of forms, so that a line-breaking algorithm cannot split the word at the hyphen. It exists for Russian and Ukrainian, where из-под, кое-что and сделал-таки are single words and a line ending in из- is a typographic fault.

It is a data-driven rule. It does nothing except where a locale has listed a literal form. It changes one code point to another of the same meaning: it never inserts, deletes or moves anything and never touches spacing. Two of the nineteen locales, ru and uk, carry lists. For the other seventeen the rule is a no-op, and the output is identical byte for byte.

It is on by default, runs after dashes and before quotes, and works in all four modes.

How it works #

A locale lists forms in three groups:

  • compounds: whole hyphenated words, such as из-под
  • prefixes: word-initial forms ending in a hyphen, such as кое-
  • suffixes: word-final forms starting with a hyphen, such as -таки

At each position the rule picks the longest listed form that matches there, trying compounds before prefixes before suffixes on a tie. It then checks the word boundaries for that group. If the check passes, the hyphen becomes U+2011 and the scan continues after the match. If it fails, nothing is changed at that position, and no shorter form is tried there.

GroupThe form mustAnd the boundary check requires
compoundbe a whole wordno letter, digit or hyphen directly before or after it
prefixstart a wordno letter, digit or hyphen before it, and a letter right after the hyphen
suffixend a worda letter right before the hyphen, and no letter, digit or hyphen after the form

The matching is exact with two exceptions. A hyphen in a listed form matches either a hyphen-minus or a non-breaking hyphen in the text, which is what makes the rule idempotent. And the first letter of a form also matches its capital, so a form at the start of a sentence is found. Nothing else is case-folded.

Because dashes runs first and never converts a hyphen with a letter on each side, every hyphen this rule sees has already been left alone as an ordinary compound hyphen.

Examples #

A compound, in the middle of a sentence and at the start of one. The capital И matches because the first letter of a form is case-lenient:

transform(input, { locale: "ru" })

Input

Достал из-под стола. Из-за дождя ушёл.

Output

Достал из‑под стола. Из‑за дождя ушёл.

Prefixes and suffixes. A prefix has to be followed by a letter, and a suffix has to follow one:

transform(input, { locale: "ru" })

Input

кое-что и кой-кого, что-нибудь или что-либо, он сделал-таки это, скажи-ка

Output

кое‑что и кой‑кого, что‑нибудь или что‑либо, он сделал‑таки это, скажи‑ка

The same hyphen is also left alone where the form is not complete. Here the first кое- is followed by a space, so it does not qualify as a prefix, while the second one does:

transform(input, { locale: "ru" })

Input

кое- и кое-что

Output

кое- и кое‑что

A listed form inside a longer word does not match, and neither does a hyphenated word that is not on the list. научно-технический is an ordinary compound adjective, and the rule has no morphology of its own, only the list:

transform(input, { locale: "ru" })

Input

квазииз-подный и научно-технический

Output

квазииз-подный и научно-технический

A suffix at the start of a line has no letter before it, so it is not a suffix:

transform(input, { locale: "ru" })

Input

-таки в начале

Output

-таки в начале

Ukrainian #

Ukrainian lists more forms, including abbreviations and compounds with з-:

transform(input, { locale: "uk" })

Input

будь-який, з-під столу, хтозна-що, ін-т, гр-н

Output

будь‑який, з‑під столу, хтозна‑що, ін‑т, гр‑н

Locales without lists #

English, German, French and every other locale outside ru and uk have empty lists. The hyphens below stay hyphen-minus characters:

transform(input, { locale: "en-US" })

Input

A well-known e-mail address, COVID-19, Jean-Luc

Output

A well-known e-mail address, COVID-19, Jean-Luc

Locale data #

The rule reads three required fields from the hyphen object of the locale, each a list of lower-case literal strings that may be empty:

  • hyphen.prefixes
  • hyphen.suffixes
  • hyphen.compounds

Each entry must contain at least one hyphen-minus; an entry without one is malformed locale data and raises POLYTYPO_MALFORMED_LOCALE_DATA. The lists are exactly what the locale file says:

LocalePrefixesSuffixesCompounds
ruкое-, кой--то, -либо, -нибудь, -таки, -каиз-за, из-под
ukбудь-, казна-, хтозна-, бозна--бо, -но, -от, -то, -таки, -будь, -небудьnineteen forms, among them з-під, із-за, ін-т, р-н

Already bound, and idempotency #

A form that already carries a non-breaking hyphen matches the list in the same way and is left unchanged. Running the rule twice, or on text that was already typeset, changes nothing more:

transform(input, { locale: "ru" })

Input

Достал из‑под стола

Output

Достал из‑под стола

Edge cases #

  • All capitals are not matched. ИЗ-ПОД СТОЛА keeps its ordinary hyphen. Only the first letter of a form is case-lenient. Из-под matches, but a heading set in capitals, or a form such as Из-Под with a capital inside it, does not.
transform(input, { locale: "ru" })

Input

ИЗ-ПОД СТОЛА

Output

ИЗ-ПОД СТОЛА
  • Lists are blunt. -то is a particle in кто-то but a different word in other positions. The rule binds any listed suffix that follows a letter, so a locale lists only forms whose binding is unconditional.
  • It does not fix spelling. The rule binds a hyphen that is already there. It does not rewrite the morphology around one, so a Russian ordinal such as 10-ый is left as typed.
  • Other dash-like characters are never read or written: U+2010, the soft hyphen U+00AD, U+2012, U+2013, U+2014 and U+2212. A soft hyphen inside из-под is the author’s deliberate break hint.
  • Rendering. U+2011 forbids a break at the hyphen. Some older fonts lack the glyph and fall back visibly. A renderer’s own automatic hyphenation may still break the word elsewhere at a syllable boundary.

Modes and skipped regions #

The rule runs on prose in every mode. In markdown and html the mode keeps URLs, code spans, fenced blocks and attributes out of its reach, so a slug such as /из-под is safe and the word next to it is still bound:

transform(input, { locale: "ru", mode: "markdown", dialect: "commonmark" })

Input

См. https://example.com/из-под и из-под стола.

Output

См. https://example.com/из-под и из‑под стола.

In text mode everything is prose. The rule has no URL awareness of its own.

Turning it off #

import { transform } from 'polytypo';

transform('Достал из-под стола', {
  locale: 'ru',
  rules: { hyphen: false },
});
transform(input, { locale: "ru", rules: { hyphen: false } })

Input

Достал из-под стола

Output

Достал из-под стола

Normative text: spec/rules/hyphen.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.