Приводит числа, даты, время, сокращения, римские цифры, символы и латиницу в русские буквы для использования в TTS и NLP:
"В 1980-е годы денег было 25 млн." → "В тысяча девятьсот восьмидесятые годы денег было двадцать пять миллионов."
Ставит слова в правильную грамматическую форму, а не просто делает замены по словарю.
- Гораздо быстрее и дешевле, чем нормализаторы на LLM
- Покрывает больше реальных кейсов, чем dictionary-only решения
- Малое потребление ресурсов. Не требует GPU
Скачать программу ru-normalizr GUI для Windows
Смотри сравнение с другими нормализаторами здесь
English version of README
По умолчанию используется режим safe:
- приводит к словам числа, даты, время и годы
- обрабатывает римские цифры
- раскрывает самые типичные сокращения, где преобразование обычно однозначно
- нормализует единицы измерения и некоторые символы
- исправляет пробелы, пунктуацию, переносы строк и часть склеек в тексте
Режим tts нормализует ближе к "как это надо читать вслух" — рекомендуется для TTS. Кроме всего перечисленного выше, этот режим:
- включает нормализацию CAPS (
ГЛАВА→Глава) - раскрывает инициалы и аббревиатуры в звуки (
ГИБДД→ги бэ дэ дэ) - включает перевод латиницы в кириллицу (
iPhone→айфоун) - читает явные URL вроде
https://example.com/a1ближе к тому, как их произносят вслух - может убирать сноски в скобках, например,
[1]
ru-normalizr умеет ставить верные ударения ТОЛЬКО при переводе латиницы в кириллицу.
Чтоб включить эту функцию, используйте флаг CLI-флаг --with-latin-stress или настройку enable_latinization_stress_marks=True.
Пакет не ставит ударения в русских словах. Для TTS-пайплайна постановку ударений лучше добавлять отдельным этапом, например, через пакет Silero Stress.
pip install ru-normalizrIPA-бэкенд латинизации (latinization_backend="ipa") работает из коробки:
пакет eng_to_ipa входит в зависимости по умолчанию. Если по какой-то причине
он окажется недоступен, запрос IPA-бэкенда один раз выдаёт предупреждение и
автоматически переключается на словарный бэкенд латинизации.
На поддерживаемых версиях CPython пакет по умолчанию использует compiled
DAWG-backend из pymorphy3[fast]. В CI проверяются CPython 3.10–3.13 на Linux
и Windows; для этих интерпретаторов DAWG2 публикует готовые колёса также для
macOS, manylinux и musllinux. Активный backend можно проверить так:
from pymorphy3 import dawg
assert dawg.EXTENSION_AVAILABLEЗначение False на поддерживаемом CPython означает проблему установки или
конфигурации, а не штатный production-режим. Корректность нормализации от
backend не зависит: перед сменой зависимости portable и compiled варианты
сравниваются parity-gate по публичным результатам, морфологическим разборам и
полному набору тестов. На Windows x86-64 с CPython 3.11.15 короткий прогон
10 000 различных слов дал около 12 тыс. слов/с для portable backend и
64 тыс. слов/с для compiled; это описание методики на одной машине, а не
универсальная гарантия производительности.
Для Windows также доступен GUI: Скачать ru-normalizr GUI для Windows
Нормализовать строку (по умолчанию в режиме safe):
ru-normalizr "Глава IV. Встреча в 10:07."Использовать TTS-режим (рекомендуется):
ru-normalizr "Глава IV. Встреча в 10:07." --mode ttsПрочитать текст из файла и сохранить результат в файл:
ru-normalizr --mode tts --file ./sample.txt --output ./sample.normalized.txtЕсли команда ru-normalizr не работает, добавьте перед ней python -m, например:
python -m ru_normalizr "Глава IV. Встреча в 10:07." --mode ttsПолезные флаги:
--mode safe|tts--with-latin-stress— по возможности ставить верное удар+ение при кириллизации латиницы--keep-links— не удалять [1], (2.5) и прочие сноски--check— вывести результат в stdout и завершиться с кодом 1, если он отличается от входа, иначе 0 (нельзя вместе с--output)--version— вывести установленную версиюru-normalizrи выйти
Cамый простой способ — normalize():
from ru_normalizr import normalize
# по умолчанию режим safe
print(normalize("Глава IV. Встреча в 10:07."))
# Глава четвёртая. Встреча в десять, ноль семь.Если вы обрабатываете много текстов с одними и теми же настройками, удобнее создать Normalizer:
from ru_normalizr import NormalizeOptions, Normalizer
# в режиме tts:
normalizer = Normalizer(NormalizeOptions.tts())
print(normalizer.normalize("ГИБДД"))
# ги бэ дэ дэ
print(normalizer.normalize_batch(["Глава IV.", "В 1980-е годы было 25 млн."]))
# "Глава четвёртая.", "В тысяча девятьсот восьмидесятые годы было двадцать пять миллионов."Для более точной настройки используйте NormalizeOptions:
from ru_normalizr import NormalizeOptions, normalize
options = NormalizeOptions.tts(
latinization_backend="ipa",
enable_latinization_stress_marks=True,
letter_vowel_mode="double", # "single" для обычных названий гласных
initials_pause_mode="comma", # "preserve" без дополнительных пауз
)
print(normalize("YouTube в 2024 г.", options))
# +ютуб в две тысячи двадцать четвёртом году.Значение по умолчанию зависит от режима: NormalizeOptions() и
NormalizeOptions.safe() используют колонку safe, NormalizeOptions.tts()
— колонку tts. Любую опцию можно переопределить явно.
| Опция | Тип | safe | tts | Описание |
|---|---|---|---|---|
enable_caps_normalization |
bool |
False |
True |
Нормализация ЗАГЛАВНЫХ заголовков (ГЛАВА → Глава) |
enable_first_word_decap |
bool |
False |
True |
Приведение первого слова предложения к строчным при нормализации капса |
remove_links |
bool |
False |
True |
Удаление скобочных ссылок-сносок вида [1], (2.5) |
enable_url_normalization |
bool |
False |
True |
Озвучивание явных URL (http://, https://, www.) поразрядно и по словам-разделителям; при False такие URL сохраняются дословно |
remove_links_ignore_interval |
tuple[int, int] |
(1000, 2200) |
(1000, 2200) |
Диапазон чисел в скобках, которые считаются годами и не удаляются как сноски |
enable_year_normalization |
bool |
True |
True |
Нормализация годов, десятилетий и диапазонов лет |
enable_roman_normalization |
bool |
True |
True |
Обработка римских цифр |
enable_dates_time_normalization |
bool |
True |
True |
Нормализация дат и времени |
enable_numeral_normalization |
bool |
True |
True |
Нормализация числительных, порядковых форм, дробей и десятичных |
enable_abbreviation_expansion |
bool |
True |
True |
Раскрытие сокращений (ул., и т. д.) |
enable_contextual_abbreviation_expansion |
bool |
True |
True |
Согласование прилагательных-сокращений по контексту (гос. → государственного) |
enable_years_ago_expansion |
bool |
True |
True |
Раскрытие л. н. → лет назад в числовом контексте |
enable_initials_expansion |
bool |
False |
True |
Раскрытие инициалов (Ч. → чэ) |
letter_vowel_mode |
"single" | "double" |
"single" |
"double" |
При double удваивает отдельно произносимые гласные в инициалах, аббревиатурах и буквенных индексах (Е. → ее, SU → эс юю) |
initials_vowel_mode |
"single" | "double" |
алиас | алиас | Совместимое устаревшее имя letter_vowel_mode; разные значения двух имён одновременно запрещены |
initials_pause_mode |
"preserve" | "comma" |
"preserve" |
"comma" |
При comma добавляет паузы-запятые вокруг инициалов в формате «Фамилия И.» |
cleanup_malformed_punctuation |
bool |
False |
True |
В конце конвейера схлопывать мусорную последовательность ., в точку; можно отключить в TTS или явно включить для грязного ввода |
enable_letter_abbreviation_expansion |
bool |
False |
True |
Побуквенное чтение аббревиатур (ГИБДД → ги бэ дэ дэ) |
enable_dictionary_normalization |
bool |
True |
True |
Применение словарных правил из пользовательских .dic |
enable_latinization |
bool |
False |
True |
Транслитерация латиницы в кириллицу |
latinization_backend |
"ipa" | "dictionary" |
"ipa" |
"ipa" |
Бэкенд латинизации (IPA работает по умолчанию) |
enable_latinization_stress_marks |
bool |
False |
False |
Сохранять знаки ударения + при IPA-латинизации |
latin_dictionary_filename |
str |
"latinization_rules.dic" |
"latinization_rules.dic" |
Имя .dic в каталоге latinization/, используемого стадией latinization |
dictionary_include_files |
tuple[str, ...] |
() |
() |
Если задано — загружать только эти .dic (иначе все, кроме latinization/) |
dictionary_exclude_files |
tuple[str, ...] |
() |
() |
Какие .dic исключить из загрузки |
dictionaries_path |
Path | None |
None |
None |
Корневой каталог с .dic (по умолчанию встроенный dictionaries/) |
mode |
"safe" | "tts" | None |
None |
None |
Маркер пресета; задаёт значения по умолчанию для остальных опций |
Важно: стадии годов и обычных числительных управляются независимо.
remove_links_ignore_interval только не даёт удалить скобочное число как
ссылку: например, при стандартном интервале [1500] сохраняется как возможный
год. После этого enable_year_normalization=True всё равно раскроет его как
(одна тысяча пятьсот), даже если enable_numeral_normalization=False. Чтобы
сохранить цифры, отключите обе числовые стадии: enable_year_normalization=False
и enable_numeral_normalization=False.
Стадия dictionary применяет пользовательские правила замены из .dic-файлов.
Важно: по умолчанию она не загружает ни одного встроенного правила —
единственный встроенный .dic относится к каталогу latinization/ и
используется стадией latinization, а не dictionary. То есть стадия
dictionary существует именно для ваших собственных словарей.
Формат .dic показан ниже:
# строки, начинающиеся с #, — комментарии
# одно правило на строку: источник=замена
YouTube=ютуб
OpenAI=оупэнэйай
# `*` — подстановка для последовательности непробельных символов
*Tube=тюб
Open*=оупен
Подключить словари можно двумя способами: положить .dic в каталог
dictionaries/ пакета (загрузятся автоматически, кроме latinization/) или
указать собственный каталог через dictionaries_path:
from pathlib import Path
from ru_normalizr import NormalizeOptions, normalize
# ./my_dicts/brands.dic:
# YouTube=ютуб
# OpenAI=оупэнэйай
options = NormalizeOptions(dictionaries_path=Path("./my_dicts"))
print(normalize("YouTube и OpenAI", options))
# ютуб и оупэнэйайИз каталога можно загрузить только часть файлов через dictionary_include_files
либо исключить отдельные через dictionary_exclude_files (имя файла или путь
относительно dictionaries_path).
ru-normalizr можно использовать не только как один большой black box, но и как модульный пайплайн.
Доступны отдельные стадии:
urls— ранняя нормализация явных URL в TTS-режиме: переводит разделители в слова и читает цифры внутри ссылок поцифрово, оставляя чистую латиницу на более позднюю стадиюlatinizationpreprocess— предварительная очистка текста: пробелы, переносы строк, пунктуация, часть OCR-подобных склеек, базовая подготовка перед остальной нормализациейroman— обработка римских цифрdates_time— нормализация дат и времениyears— нормализация годов, десятилетий и диапазонов летnumerals— нормализация числительных, порядковых форм, дробей, десятичных и других числовых выраженийabbreviations— раскрытие сокращений, инициалов и буквенных аббревиатур в зависимости от выбранного режимаdictionary— применение пользовательских словарных правил (встроенные правила по умолчанию не загружаются; см. Пользовательские словари)latinization— перевод латиницы в кириллицу через словарь или IPA-бэкендfinalize— финальная чистка текста после всех преобразований: нормализация пунктуации, регистра и восстановление абзацев
Это удобно для отладки, тестирования и встраивания только нужных частей пайплайна в собственную систему.
Окружение управляется uv. Требуется Python 3.10 или новее.
uv sync --frozenПроверки, которые обязаны проходить перед коммитом (тот же набор выполняет CI):
uv run ruff check .
uv run pyright
uv run pytest -qОтчёт о покрытии по ветвям:
uv run pytest -q --cov=src/ru_normalizr --cov-branch --cov-report=term-missingПолный цикл проверки выпуска — очистка, сверка версий, линт, типы, тесты, сборка
и twine check:
uv run python scripts/dev.py checkПорядок выпуска на PyPI описан в docs/RELEASING.md, правила версионирования — в VERSIONING.md, соглашения для правок кода — в AGENTS.md и docs/agents/.
Сравнение проводилось на одном компьютере на одних и тех же текстах. Качество можете оценить сами по примерам ниже.
| Нормализатор | Скорость | Качество | Время на книгу |
|---|---|---|---|
| ru-normalizr | ⚡ очень быстро | ✅ высокое | ⚡ 2 секунды |
| RuNorm | 🐢 очень медленно | ❌ с артефактами | 140-700 секунд |
| Demagog | быстро | низкое | 35 секунд |
Также было произведено сравнение с многообещающим russian_text_normalizer. Он показал себя гораздо лучше, чем RuNorm, но он тоже допускает ошибки в нормализации числительных (например, получил приблизительно 2500 голосов.→получил приблизительно двести пятьдесят тысяч голосов.), нормализирует не всё и съедает слова из оригинального текста.
Глава 1
ПОСЛЕДНИЕ СЛОВА ГЕНЕРАЛА
В 1990–2000 гг компания выпустила 15 моделей iPhone XII и продала 1 234 567 устройств в странах СНГ и ЕС.
АВТОМОБИЛЕСТРОЕНИЕ КОНВЕЙЕРНОЕ организовал американский промышленник Р. Э. Олдс – основатель компании «Олдс Мотор Уоркс» (Olds Motor Works) в городе Детройте.
Компания ABC Ltd. протестировала систему на 3.5 TB данных со скоростью 100 Mb/s.
Размер модели: 2.7 GB, точность: 99.5%.
Глава один.
Последние слова генерала.
В тысяча девятьсот девяностых — двухтысячных годах компания выпустила пятнадцать моделей айфоун двенадцать и продала один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь устройств в странах эс эн гэ и е эс.
Автомобилестроение конвейерное организовал американский промышленник эр ээ Олдс — основатель компании "Олдс Мотор Уоркс" (оулдз моутэр вэркс) в городе Детройте.
Компания эй би си элтиди. Протестировала систему на трёх целых пять десятых терабайта данных со скоростью ста мегабайтов в секунду.
Размер модели: две целых семь десятых гигабайта, точность: девяносто девять целых пять десятых процента.
На нормализацию примера ушло 0.24 секунды. Аббревиатуры и инициалы раскрываются в буквы намерено. Это можно отключить.
Реальную книгу (здесь и далее — Дэвид Бергланд, «ЛИБЕРТАРИАНСТВО ЗА ОДИН УРОК» с flibusta — небольшая книга в 274K символов) ru-normalizer обработал за 2 секунды.
Глава первая пэ о эс эл е дэ эн и е эс эл о вэ а гэ е эн е эр а эл а в тысяча девятьсот девяносто первом – две тысячи втором годах компания выпустила пятнадцать моделей айфон двенадцать и продала один миллион двести двадцать четыре тысячи пятьсот семьдесят семь устройств в странах эс эн гэ и е эс. А вэ тэ о эм о бэ и эл е эс тэ эр о е эн и е ка о эн вэ е ий е эр эн о е, на промышленной основе, организовал американский промышленник р. э. олдс – основатель компании «олдс мотор уоркс » ( олдс мотор уоркс ) в городе детройте. Компания эй би си литд. протестировала систему на три целых и пять десятых терабайта данных со скоростью сто мегабайт /секунд. Размер модели : два целых семь десятых гигабайта, точность : девяносто девять целых и пять десятыхпроцент.
На нормализацию примера ушло 15.76 секунды.
Реальную книгу (274K символов) RuNorm Small обработал за 143 секунды, RuNorm Big — за 723 секунды.
RuNorm значительно медленнее и использует значительно больше ресурсов. Качество нормализации у RuNorm низкое и нестабильное. Ломает форматирование.
Глава первая
ПОСЛЕДНИЕ СЛОВА ГЕНЕРАЛА
В одна тысяча девятьсот девяносто–две тысячи компания выпустила пятнадцать моделей ифон двенадцать и продала один двести тридцать четыре пятьсот шестьдесят семь устройств в странах СНГ и ЕС.
АВТОМОБИЛЕСТРОЕНИЕ КОНВЕЙЕРНОЕ организовал американский промышленник Р. Э. Олдс – основатель компании «Олдс Мотор Уоркс» (олдс мото Оркс) в городе Детройте.
Компания эбс лимитэд. протестировала систему на три с половиной тб данных со скоростью сто мегабайтов /с.
Размер модели: два,семь гигабайтов , точность: девяносто девять с половиной%.
Применены популярные Демагог словари 10_REX_числа(chisla).rex, 65_ЛАТИНИЦА@.dic.
На нормализацию примера ушло менее секунды.
Реальную книгу (274K символов) Демагог обработал за 35 секунд.
Качество нормализации хуже, чем у ru-normalizr, особенно в не покрываемых словарём случаях. Функционал ниже.
Упомянута на 4PDA Библиотека ru-normalizr используется каталогом ИИ аудиокниг AIaudiobooks.org
ru-normalizr converts numbers, dates, time, abbreviations, Roman numerals, symbols, and Latin text into Russian words for use in TTS and NLP.
It inflects words into the correct grammatical form instead of relying on simple dictionary replacements.
- Very fast
- Covers more real-world cases than dictionary-only solutions
- More stable and higher quality than LLM-based normalizers
- Lightweight and resource-efficient. No GPU required
See the comparison with other normalizers here
Russian text normalization is difficult because:
- numbers must be inflected
- abbreviations are ambiguous
- Latin words appear in modern text
- dictionary-only solutions fail on unseen cases
ru-normalizr solves this with a rule-based pipeline that produces grammatically correct forms instead of simple replacements.
By default, safe mode is used:
- converts numbers, dates, time, and years into words
- handles Roman numerals
- expands the most common abbreviations where the transformation is usually unambiguous
- normalizes measurement units and some symbols
- fixes spacing, punctuation, line breaks, and some tokenization / merged-text issues
tts mode normalizes text closer to “how it should be read aloud” and is recommended for TTS:
- includes CAPS normalization (
ГЛАВА→Глава) - expands initials and abbreviations into letter names / sounds (
ГИБДД→ги бэ дэ дэ) - includes Latin-to-Cyrillic transliteration (
iPhone→айфоун) - reads explicit URLs such as
https://example.com/a1closer to how they would be spoken aloud - can remove bracketed references such as
[1]
ru-normalizr can add correct stress marks ONLY when converting Latin text to Cyrillic.
To enable this feature, use the CLI flag --with-latin-stress or the setting enable_latinization_stress_marks=True.
The package does not add stress marks to Russian words. For a TTS pipeline, it is better to add stress marks as a separate stage, for example with the Silero Stress package.
pip install ru-normalizrThe IPA latinization backend (latinization_backend="ipa") works out of the
box: the eng_to_ipa package is a default dependency. If it ever becomes
unavailable, requesting the IPA backend emits a one-time warning and
automatically falls back to the dictionary latinization backend.
Normalize a string (uses safe mode by default):
ru-normalizr "Глава IV. Встреча в 10:07."Use TTS mode (recommended):
ru-normalizr "Глава IV. Встреча в 10:07." --mode ttsRead text from a file and save the result to another file:
ru-normalizr --mode tts --file ./sample.txt --output ./sample.normalized.txtIf the ru-normalizr command does not work, prepend it with python -m, for example:
python -m ru_normalizr "Глава IV. Встреча в 10:07." --mode ttsUseful flags:
--mode safe|tts--with-latin-stress— add correct stress marks when possible during Latin-to-Cyrillic conversion--keep-links— do not remove references such as[1],(2.5), etc.--check— print the result to stdout and exit with status 1 if it differs from the input, 0 if unchanged (cannot be combined with--output)--version— print the installedru-normalizrversion and exit
The simplest way is to use normalize():
from ru_normalizr import normalize
# safe mode by default
print(normalize("Глава IV. Встреча в 10:07."))
# Глава четвёртая. Встреча в десять, ноль семь.If you process many texts with the same settings, it is more convenient to create a Normalizer:
from ru_normalizr import NormalizeOptions, Normalizer
# tts mode:
normalizer = Normalizer(NormalizeOptions.tts())
print(normalizer.normalize("ГИБДД"))
# ги бэ дэ дэ
print(normalizer.normalize_batch(["Глава IV.", "В 1980-е годы было 25 млн."]))
# "Глава четвёртая.", "В тысяча девятьсот восьмидесятые годы было двадцать пять миллионов."For finer control, use NormalizeOptions:
from ru_normalizr import NormalizeOptions, normalize
options = NormalizeOptions.tts(
latinization_backend="ipa",
enable_latinization_stress_marks=True,
letter_vowel_mode="double", # use "single" for ordinary vowel names
initials_pause_mode="comma", # use "preserve" for source punctuation
)
print(normalize("YouTube в 2024 г.", options))
# +ютуб в две тысячи двадцать четвёртом году.The default value depends on the mode: NormalizeOptions() and
NormalizeOptions.safe() use the safe column, NormalizeOptions.tts()
uses the tts column. Any option can be overridden explicitly.
| Option | Type | safe | tts | Description |
|---|---|---|---|---|
enable_caps_normalization |
bool |
False |
True |
Normalize ALL-CAPS headings (ГЛАВА → Глава) |
enable_first_word_decap |
bool |
False |
True |
Lowercase the first word of a sentence during caps normalization |
remove_links |
bool |
False |
True |
Remove bracketed reference links such as [1], (2.5) |
enable_url_normalization |
bool |
False |
True |
Read explicit URLs (http://, https://, www.) aloud by separators and digits; when False, preserve them verbatim |
remove_links_ignore_interval |
tuple[int, int] |
(1000, 2200) |
(1000, 2200) |
Range of bracketed numbers treated as years and kept, not removed as references |
enable_year_normalization |
bool |
True |
True |
Normalize years, decades, and year ranges |
enable_roman_normalization |
bool |
True |
True |
Roman numeral handling |
enable_dates_time_normalization |
bool |
True |
True |
Normalize dates and time expressions |
enable_numeral_normalization |
bool |
True |
True |
Normalize cardinals, ordinals, fractions, and decimals |
enable_abbreviation_expansion |
bool |
True |
True |
Expand abbreviations (ул., и т. д.) |
enable_contextual_abbreviation_expansion |
bool |
True |
True |
Inflect adjective-like abbreviations from context (гос. → государственного) |
enable_years_ago_expansion |
bool |
True |
True |
Expand л. н. → лет назад in numeric context |
enable_initials_expansion |
bool |
False |
True |
Expand initials (Ч. → чэ) |
letter_vowel_mode |
"single" | "double" |
"single" |
"double" |
With double, doubles independently pronounced vowels in initials, abbreviations, and letter indices (Е. → ее, SU → эс юю) |
initials_vowel_mode |
"single" | "double" |
alias | alias | Backwards-compatible deprecated name for letter_vowel_mode; conflicting simultaneous values are rejected |
initials_pause_mode |
"preserve" | "comma" |
"preserve" |
"comma" |
With comma, adds comma pauses around initials in "Surname I." order |
cleanup_malformed_punctuation |
bool |
False |
True |
At the end of the pipeline, collapse the noisy ., sequence to a period; it can be disabled in TTS or enabled explicitly for dirty input |
enable_letter_abbreviation_expansion |
bool |
False |
True |
Read abbreviations letter by letter (ГИБДД → ги бэ дэ дэ) |
enable_dictionary_normalization |
bool |
True |
True |
Apply dictionary rewrite rules from user .dic files |
enable_latinization |
bool |
False |
True |
Transliterate Latin words into Cyrillic |
latinization_backend |
"ipa" | "dictionary" |
"ipa" |
"ipa" |
Latinization backend (IPA works by default) |
enable_latinization_stress_marks |
bool |
False |
False |
Keep + stress markers when using IPA latinization |
latin_dictionary_filename |
str |
"latinization_rules.dic" |
"latinization_rules.dic" |
Name of the .dic in latinization/ used by the latinization stage |
dictionary_include_files |
tuple[str, ...] |
() |
() |
If set, load only these .dic files (otherwise all except latinization/) |
dictionary_exclude_files |
tuple[str, ...] |
() |
() |
Which .dic files to skip when loading |
dictionaries_path |
Path | None |
None |
None |
Root directory of .dic files (defaults to the bundled dictionaries/) |
mode |
"safe" | "tts" | None |
None |
None |
Preset marker; sets the defaults for the remaining options |
Important: year and general-numeral stages are controlled independently.
remove_links_ignore_interval only prevents a bracketed number from being
removed as a reference: with the default interval, [1500] is kept as a
possible year. Then enable_year_normalization=True still expands it to
(одна тысяча пятьсот), even when enable_numeral_normalization=False. To keep
the digits, disable both numeric stages: enable_year_normalization=False and
enable_numeral_normalization=False.
The dictionary stage applies user-defined rewrite rules from .dic files.
Note: by default it loads zero bundled rules — the only bundled .dic
belongs to the latinization/ folder and is used by the latinization stage,
not dictionary. In other words, the dictionary stage exists purely for your
own dictionaries.
The .dic format is shown below:
# lines starting with # are comments
# one rule per line: source=replacement
YouTube=ютуб
OpenAI=оупэнэйай
# `*` is a wildcard for a run of non-space characters
*Tube=тюб
Open*=оупен
There are two ways to load dictionaries: drop .dic files into the package's
dictionaries/ folder (loaded automatically, except latinization/), or point
dictionaries_path at your own folder:
from pathlib import Path
from ru_normalizr import NormalizeOptions, normalize
# ./my_dicts/brands.dic:
# YouTube=ютуб
# OpenAI=оупэнэйай
options = NormalizeOptions(dictionaries_path=Path("./my_dicts"))
print(normalize("YouTube и OpenAI", options))
# ютуб и оупэнэйайYou can load only some files from a folder via dictionary_include_files, or
skip individual ones via dictionary_exclude_files (by file name or path
relative to dictionaries_path).
ru-normalizr can be used not only as one big black box, but also as a modular pipeline.
Individual stages are available:
urls— early normalization of explicit URLs in TTS mode: turns separators into spoken words and reads digits inside links digit-by-digit while leaving plain Latin words for the laterlatinizationstagepreprocess— preliminary text cleanup: spaces, line breaks, punctuation, some OCR-like glued text, and other preparation before the main normalization stagesroman— Roman numeral handlingdates_time— normalization of dates and time expressionsyears— normalization of years, decades, and year rangesnumerals— normalization of cardinal numbers, ordinal forms, fractions, decimals, and other numeric expressionsabbreviations— expansion of abbreviations, initials, and letter-by-letter abbreviations depending on the selected modedictionary— application of user-defined dictionary rules (no bundled rules are loaded here by default; see Custom dictionaries)latinization— conversion of Latin words into Cyrillic using either dictionary rules or the IPA backendfinalize— final cleanup after all transformations: punctuation normalization, casing fixes, and paragraph restoration
The environment is managed with uv. Python 3.10 or newer is required.
uv sync --frozenThe checks that must pass before a commit (CI runs the same set):
uv run ruff check .
uv run pyright
uv run pytest -qBranch coverage report:
uv run pytest -q --cov=src/ru_normalizr --cov-branch --cov-report=term-missingThe full release verification cycle — clean, version sync, lint, types, tests,
build, and twine check:
uv run python scripts/dev.py checkThe PyPI release procedure is described in docs/RELEASING.md, the versioning policy in VERSIONING.md, and the code conventions in AGENTS.md and docs/agents/.
The comparison was run on the same machine and the same texts.
| Normalizer | Speed | Quality |
|---|---|---|
| ru-normalizr | ⚡ very fast | ✅ high |
| RuNorm | 🐢 slow | ❌ unstable |
| Demagog | ⚡ fast | ❌ limited |
A comparison was also made with the promising russian_text_normalizer. It performed much better than RuNorm, but it also makes errors in normalizing numerals (for example, получил приблизительно 2500 голосов.→получил приблизительно двести пятьдесят тысяч голосов.), does not normalize everything, and omits words from the original text.
Глава 1
ПОСЛЕДНИЕ СЛОВА ГЕНЕРАЛА
В 1990–2000 гг компания выпустила 15 моделей iPhone XII и продала 1 234 567 устройств в странах СНГ и ЕС.
АВТОМОБИЛЕСТРОЕНИЕ КОНВЕЙЕРНОЕ организовал американский промышленник Р. Э. Олдс – основатель компании «Олдс Мотор Уоркс» (Olds Motor Works) в городе Детройте.
Компания ABC Ltd. протестировала систему на 3.5 TB данных со скоростью 100 Mb/s.
Размер модели: 2.7 GB, точность: 99.5%.
Глава один.
Последние слова генерала.
В тысяча девятьсот девяностых — двухтысячных годах компания выпустила пятнадцать моделей айфоун двенадцать и продала один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь устройств в странах эс эн гэ и е эс.
Автомобилестроение конвейерное организовал американский промышленник эр ээ Олдс — основатель компании "Олдс Мотор Уоркс" (оулдз моутэр вэркс) в городе Детройте.
Компания эй би си элтиди. Протестировала систему на трёх целых пять десятых терабайта данных со скоростью ста мегабайтов в секунду.
Размер модели: две целых семь десятых гигабайта, точность: девяносто девять целых пять десятых процента.
Normalizing this example took 0.24 seconds. Abbreviations and initials are intentionally expanded into letter names. This can be disabled.
A real book (here and below: David Bergland, Libertarianism in One Lesson from Flibusta — 274K characters) was processed by ru-normalizr in 2 seconds.
Глава первая пэ о эс эл е дэ эн и е эс эл о вэ а гэ е эн е эр а эл а в тысяча девятьсот девяносто первом – две тысячи втором годах компания выпустила пятнадцать моделей айфон двенадцать и продала один миллион двести двадцать четыре тысячи пятьсот семьдесят семь устройств в странах эс эн гэ и е эс. А вэ тэ о эм о бэ и эл е эс тэ эр о е эн и е ка о эн вэ е ий е эр эн о е, на промышленной основе, организовал американский промышленник р. э. олдс – основатель компании «олдс мотор уоркс » ( олдс мотор уоркс ) в городе детройте. Компания эй би си литд. протестировала систему на три целых и пять десятых терабайта данных со скоростью сто мегабайт /секунд. Размер модели : два целых семь десятых гигабайта, точность : девяносто девять целых и пять десятыхпроцент.
Normalizing this example took 15.76 seconds.
A real book (274K characters) was processed in 143 seconds by RuNorm Small and in 723 seconds by RuNorm Big.
RuNorm is significantly slower and uses significantly more resources. Its normalization quality is low and unstable. It also breaks formatting.
Глава первая
ПОСЛЕДНИЕ СЛОВА ГЕНЕРАЛА
В одна тысяча девятьсот девяносто–две тысячи компания выпустила пятнадцать моделей ифон двенадцать и продала один двести тридцать четыре пятьсот шестьдесят семь устройств в странах СНГ и ЕС.
АВТОМОБИЛЕСТРОЕНИЕ КОНВЕЙЕРНОЕ организовал американский промышленник Р. Э. Олдс – основатель компании «Олдс Мотор Уоркс» (олдс мото Оркс) в городе Детройте.
Компания эбс лимитэд. протестировала систему на три с половиной тб данных со скоростью сто мегабайтов /с.
Размер модели: два,семь гигабайтов , точность: девяносто девять с половиной%.
Popular Demagog dictionaries 10_REX_числа(chisla).rex and 65_ЛАТИНИЦА@.dic were used.
Normalizing this example took less than one second.
A real book (274K characters) was processed by Demagog in 35 seconds.
Normalization quality is worse than ru-normalizr, especially in cases not covered by the dictionary. Functionality is also more limited.