PHPackages                             alexivanou/russian-text-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. alexivanou/russian-text-bundle

ActiveSymfony-bundle[Templating &amp; Views](/categories/templating)

alexivanou/russian-text-bundle
==============================

Symfony bundle providing Russian text functions: pluralization, number spelling, name inflection, noun/adjective declension, money spelling, time intervals, and more. Wraps wapmorgan/morphos into Symfony DI services and Twig extensions.

v1.0.3(3d ago)024MITPHP &gt;=7.2

Since Jul 8Compare

[ Source](https://github.com/alexivanou/russian-text-bundle)[ Packagist](https://packagist.org/packages/alexivanou/russian-text-bundle)[ RSS](/packages/alexivanou-russian-text-bundle/feed)WikiDiscussions Synced today

READMEChangelogDependencies (5)Versions (2)Used By (0)

RussianTextBundle
=================

[](#russiantextbundle)

[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](LICENSE)[![Tests](https://github.com/alexivanou/russian-text-bundle/actions/workflows/tests.yml/badge.svg)](https://github.com/alexivanou/russian-text-bundle/actions/workflows/tests.yml)

Symfony bundle providing Russian text functions: pluralization, number spelling, name inflection, noun/adjective declension, geographical name inflection, money spelling, time ago, transliteration, phone formatting, identifier validation, semantic name parsing, date formatting, and text utilities. Built on top of [wapmorgan/morphos](https://github.com/wapmorgan/Morphos).

Also available in [Russian](README.ru.md).

Installation
------------

[](#installation)

```
composer require alexivanou/russian-text-bundle
```

**Symfony Flex** will auto-configure the bundle. If you don't use Flex, add to `bundles.php`:

```
AlexIvanou\RussianTextBundle\AlexIvanouRussianTextBundle::class => ['all' => true],
```

Configuration
-------------

[](#configuration)

Every feature can be toggled on or off. When a feature is disabled, its service definition and autowiring alias are **removed from the container** at compile time — they are never instantiated, never loaded, and never consume memory.

```
# config/packages/russian_text.yaml
russian_text:
    # --- Core services (14 flags) ---
    enable_pluralizer: true
    enable_number_speller: true
    enable_money_speller: true
    enable_name_inflector: true
    enable_noun_decliner: true
    enable_adjective_decliner: true
    enable_geo_inflector: true
    enable_time_speller: true
    enable_transliterator: true
    enable_identifier_validator: true
    enable_phone_formatter: true
    enable_name_parser: true
    enable_russian_date_formatter: true
    enable_text_helper: true

    # --- Twig extensions (7 flags) ---
    enable_twig_plural: true
    enable_twig_numeral: true
    enable_twig_name: true
    enable_twig_declension: true
    enable_twig_time: true
    enable_twig_utility: true
    enable_twig_validation: true
```

All flags default to `true`.

### Why this matters

[](#why-this-matters)

**Memory savings**

Every service is a container object with its own definition, arguments, and tags. Even if a service is never invoked, its definition is loaded into memory during DI container compilation. Disabling unused services:

- Reduces compiled container size (files in `var/cache/{env}/Container*.php`)
- Lowers PHP memory consumption per request in production
- Removes autowiring aliases, speeding up dependency resolution

**Performance**

On each Symfony request the container goes through several compilation phases:

1. **Resolution** — resolving all service arguments. Fewer services = faster resolution.
2. **Dump** — writing the compiled container to disk (only on first request after cache clear).
3. **Autowiring** — searching for a matching service by type. Aliases for disabled services are not registered → faster autowiring.

**Real-world example**

If you only use `Pluralizer` and `TimeSpeller` (no validators, phones, declensions), disabling 15+ flags removes ~20 class definitions + 12 autowiring aliases from the container. Difference in a typical Symfony project:

MetricAll enabledMinimal setLines of compiled container code~3000~800Service definitions~40~18Memory per request~2.8 MB~2.5 MBCompilation time (debug=0)~120 ms~80 msValues are approximate and depend on PHP version and project complexity.

**How it works**

Flags are honest: if `enable_pluralizer: false`, then:

- Service `alexivanou.russian_text.pluralizer` is NOT registered
- Autowiring alias `PluralizerInterface::class` is NOT registered
- Any attempt to inject `PluralizerInterface` throws a clear `ServiceNotFoundException`, not a silent null

This lets IDEs and static analyzers (PHPStan, Psalm) correctly track dependencies.

Services
--------

[](#services)

Service IDInterfaceDescription`alexivanou.russian_text.pluralizer``PluralizerInterface`Pluralization of nouns after numerals`alexivanou.russian_text.number_speller``NumberSpellerInterface`Cardinal and ordinal numerals in words`alexivanou.russian_text.money_speller``MoneySpellerInterface`Money amounts in words (RUB, USD, EUR, UAH, KZT, BYN)`alexivanou.russian_text.name_inflector``NameInflectorInterface`Personal name inflection by cases + gender detection`alexivanou.russian_text.noun_decliner``NounDeclinerInterface`Noun declension by cases + pluralization`alexivanou.russian_text.adjective_decliner``AdjectiveDeclinerInterface`Adjective declension by cases, gender, number`alexivanou.russian_text.geo_inflector``GeographicalNameInflectorInterface`Geographical name inflection by cases`alexivanou.russian_text.time_speller``TimeSpellerInterface`Human-readable time intervals in Russian`alexivanou.russian_text.transliterator``TransliteratorInterface`Cyrillic → Latin transliteration + slug`alexivanou.russian_text.identifier_validator``IdentifierValidatorInterface`INN, SNILS, OGRN, SWIFT, IBAN, etc. validation`alexivanou.russian_text.phone_formatter``PhoneFormatterInterface`Phone number formatting (30 countries)`alexivanou.russian_text.name_parser``NameParserInterface`Semantic FIO parsing`alexivanou.russian_text.russian_date_formatter``RussianDateFormatterInterface`Russian month/day names`alexivanou.russian_text.text_helper``TextHelperInterface`ordinalSuffix, currencySymbol, truncateTwig Usage
----------

[](#twig-usage)

### Pluralization (`pluralize` filter)

[](#pluralization-pluralize-filter)

```
{{ 'дом'|pluralize(5) }}         {# домов #}
{{ 'машина'|pluralize(2) }}      {# машины #}
{{ 'сообщение'|pluralize(10) }}  {# сообщений #}
```

### Number spelling (`cardinal`, `ordinal` filters)

[](#number-spelling-cardinal-ordinal-filters)

```
{{ 123|cardinal }}       {# сто двадцать три #}
{{ 21|ordinal }}         {# двадцать первый #}
{{ 961|ordinal }}        {# девятьсот шестьдесят первый #}
```

### Money spelling (`spell_money` filter)

[](#money-spelling-spell_money-filter)

```
{{ 123.45|spell_money }}                          {# сто двадцать три рубля сорок пять копеек #}
{{ 100|spell_money('USD') }}                      {# сто долларов #}
{{ 123.45|spell_money('RUB', 'short') }}          {# 123 рубля 45 копеек #}
{{ 123.45|spell_money('RUB', 'accounting') }}     {# сто двадцать три рубля 45 копеек #}
```

### Name inflection (`inflect_name` filter, `name_cases`, `detect_gender` functions)

[](#name-inflection-inflect_name-filter-name_cases-detect_gender-functions)

```
{{ 'Иванов Иван'|inflect_name('творительный') }}       {# Ивановым Иваном #}
{{ 'Иванов Иван Иванович'|inflect_name('дательный') }} {# Иванову Ивану Ивановичу #}

{% set cases = name_cases('Иванов Иван') %}
{{ cases.genitive }}  {# Иванова Ивана #}

{{ detect_gender('Иванова Мария') }}  {# w #}
```

### Noun declension (`noun_case`, `noun_plural` filters)

[](#noun-declension-noun_case-noun_plural-filters)

```
{{ 'дом'|noun_case('родительный') }}    {# дома #}
{{ 'машина'|noun_case('творительный') }} {# машиной #}
{{ 'дом'|noun_plural(5) }}              {# домов #}
```

### Adjective declension (`adj_case` filter)

[](#adjective-declension-adj_case-filter)

```
{{ 'новый'|adj_case('родительный') }}       {# нового #}
{{ 'новый'|adj_case('творительный') }}      {# новым #}
{{ 'синий'|adj_case('родительный') }}       {# синего #}
{{ 'синий'|adj_case('дательный', 'f') }}    {# синей (feminine) #}
```

### Geographical name inflection (`geo_case` filter)

[](#geographical-name-inflection-geo_case-filter)

```
{{ 'Москва'|geo_case('родительный') }}     {# Москвы #}
{{ 'Париж'|geo_case('творительный') }}     {# Парижем #}
{{ 'Саратов'|geo_case('родительный') }}    {# Саратова #}
{{ 'Томск'|geo_case('дательный') }}        {# Томску #}
{{ 'Сочи'|geo_case('родительный') }}       {# Сочи (immutable) #}
```

### Time ago (`time_ago`, `distance_of_time` filters)

[](#time-ago-time_ago-distance_of_time-filters)

```
{{ post.createdAt|time_ago }}              {# 5 минут назад #}
{{ event.date|time_ago }}                  {# через 2 часа #}
{{ post.createdAt|distance_of_time }}      {# 5 минут назад #}
```

### Transliteration (`translit`, `slug` filters)

[](#transliteration-translit-slug-filters)

```
{{ 'Привет'|translit }}        {# Privet #}
{{ 'Привет'|slug }}            {# privet #}
{{ 'Привет, мир!'|slug }}     {# privet-mir #}
```

### Validation functions

[](#validation-functions)

```
{{ is_valid_inn('7707083893') }}         {# true #}
{{ is_valid_swift('DEUTDEFF') }}        {# true #}
{{ is_valid_iban('DE44500105175407324931') }}  {# true #}
{{ is_valid_credit_card('4111111111111111') }}  {# true #}
```

### Phone formatting

[](#phone-formatting)

```
{{ '+79031234567'|phone_format }}              {# +7 (903) 123-45-67 #}
{{ phone_is_valid('+79031234567') }}           {# true #}
{{ phone_detect_country('+375291234567') }}    {# BY #}
```

### Name parsing

[](#name-parsing)

```
{% set name = parse_name('Иванов Иван Петрович') %}
{{ name.surname }}         {# Иванов #}
{{ name.firstName }}       {# Иван #}
{{ name.patronymic }}      {# Петрович #}
{{ name.gender }}          {# m #}

{{ name_initials('Иванов Иван Петрович') }}     {# Иванов И. П. #}
{{ name_initials('Иванов Иван Петрович', 'before') }}  {# И. П. Иванов #}
```

### Date formatting

[](#date-formatting)

```
{{ post.createdAt|russian_date('j F Y') }}              {# 15 январь 2026 #}
{{ post.createdAt|russian_date_genitive('j F Y') }}     {# 15 января 2026 #}
{{ russian_month(3) }}                                   {# март #}
{{ russian_month(3, 'genitive') }}                       {# марта #}
{{ russian_day_of_week(post.createdAt) }}                {# вторник #}
```

### Text helpers

[](#text-helpers)

```
{{ 1|ordinal_suffix }}                  {# 1-й #}
{{ 1|ordinal_suffix('f') }}             {# 1-я #}
{{ 'RUB'|currency_symbol }}             {# ₽ #}
{{ 'Очень длинный текст'|truncate(10) }} {# Очень... #}
```

Case Names
----------

[](#case-names)

All case-aware methods accept any of the following formats:

ConstantRussianAbbreviationEnglish`nominative`именительныйиnominative`genitive`родительныйрgenitive`dative`дательныйдdative`accusative`винительныйвaccusative`ablative`творительныйтinstrumental`prepositional`предложныйпprepositionalPHP Usage
---------

[](#php-usage)

```
use AlexIvanou\RussianTextBundle\Service\Pluralizer;
use AlexIvanou\RussianTextBundle\Service\NumberSpeller;
use AlexIvanou\RussianTextBundle\Service\NameInflector;
use AlexIvanou\RussianTextBundle\Service\NounDecliner;
use AlexIvanou\RussianTextBundle\Service\MoneySpeller;
use AlexIvanou\RussianTextBundle\Service\TimeSpeller;

$pluralizer = new Pluralizer();
echo $pluralizer->pluralize('дом', 5); // домов

$numberSpeller = new NumberSpeller();
echo $numberSpeller->cardinal(123); // сто двадцать три

$nameInflector = new NameInflector();
echo $nameInflector->inflect('Иванов Иван', 'творительный'); // Ивановым Иваном

$nounDecliner = new NounDecliner();
echo $nounDecliner->decline('дом', 'родительный'); // дома

$adjDecliner = new AdjectiveDecliner();
echo $adjDecliner->decline('новый', 'родительный'); // нового

$geoInflector = new GeographicalNameInflector();
echo $geoInflector->inflect('Москва', 'родительный'); // Москвы

$moneySpeller = new MoneySpeller();
echo $moneySpeller->spell(123.45, MoneySpeller::RUBLE); // сто двадцать три рубля сорок пять копеек

$timeSpeller = new TimeSpeller();
echo $timeSpeller->timeAgo(new \DateTime('-5 minutes')); // 5 минут назад
```

Architecture
------------

[](#architecture)

Services are split into separate classes (not one monolithic helper) so you can inject only what you need.

Each service implements a corresponding interface (`PluralizerInterface`, `NumberSpellerInterface`, etc.) and is registered with an autowiring alias. You can type-hint the interface in your constructors:

```
use AlexIvanou\RussianTextBundle\Service\PluralizerInterface;

class MyService
{
    public function __construct(PluralizerInterface $pluralizer)
    {
        // ...
    }
}
```

### Validator architecture

[](#validator-architecture)

The `IdentifierValidator` uses tagged services. Each validation rule (INN, KPP, SWIFT, IBAN, etc.) is a separate class implementing `ValidationRuleInterface` tagged with `russian_text.validator`. To add a custom validator:

```
namespace App\Validator;

use AlexIvanou\RussianTextBundle\Service\ValidationRuleInterface;

class PassportUaValidator implements ValidationRuleInterface
{
    public function getType() { return 'passport_ua'; }
    public function isValid($value) { /* ... */ }
}
```

```
# config/services.yaml
services:
    App\Validator\PassportUaValidator:
        tags:
            - { name: russian_text.validator }
```

The `IdentifierValidator` collects all tagged validators via a compiler pass and exposes them through the fluent API.

### Twig extensions

[](#twig-extensions)

Twig extensions are separated by domain:

- **PluralExtension** — pluralization (`pluralize`)
- **NumeralExtension** — numerals + money (`cardinal`, `ordinal`, `spell_money`)
- **NameExtension** — names (`inflect_name`, `name_cases`, `detect_gender`)
- **DeclensionExtension** — nouns (`noun_case`, `noun_plural`), adjectives (`adj_case`), geographical names (`geo_case`)
- **TimeExtension** — time (`time_ago`, `distance_of_time`)
- **UtilityExtension** — transliteration (`translit`, `slug`), text helper (`truncate`, `ordinal_suffix`, `currency_symbol`), date (`russian_date`, `russian_date_genitive`, `russian_month`, `russian_day_of_week`)
- **ValidationExtension** — phone format (`phone_format`, `phone_is_valid`, `phone_detect_country`), identifier validation (`is_valid_inn`, `is_valid_snils`, etc.), name parsing (`parse_name`, `name_initials`)

This design lets you disable unused features via bundle configuration and keeps your DI container lean.

What's covered vs wapmorgan/morphos
-----------------------------------

[](#whats-covered-vs-wapmorganmorphos)

FeaturemorphosThis bundleNoun pluralization✅ `Russian\Plurality`✅ `Pluralizer`Cardinal numerals✅ `Russian\CardinalNumeral`✅ `NumberSpeller`Ordinal numerals✅ `Russian\OrdinalNumeral`✅ `NumberSpeller`Noun declension✅ `Russian\GeneralDeclension`✅ `NounDecliner`Name inflection✅ `Russian\name()`✅ `NameInflector`Gender detection✅ `Russian\detectGender()`✅ `NameInflector`Adjective declension❌✅ `AdjectiveDecliner` (custom)Geographical name inflection❌✅ `GeographicalNameInflector` (custom)Money spelling❌✅ `MoneySpeller` (custom)Time ago❌✅ `TimeSpeller` (custom)Requirements
------------

[](#requirements)

- PHP 7.2+
- Symfony 4.2+
- Twig 2.0+
- ext-mbstring

Testing
-------

[](#testing)

```
vendor/bin/phpunit
```

License
-------

[](#license)

MIT

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance99

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity28

Early-stage or recently created project

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

3d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/c2d938cc33a5da1ed3ea679fd918336c536c9492a57caf5defeb25f3ec6ad643?d=identicon)[alexivanou](/maintainers/alexivanou)

---

Tags

symfonypluraltwiginflectiontextrussianmorphologydeclensionnum2str

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/alexivanou-russian-text-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/alexivanou-russian-text-bundle/health.svg)](https://phpackages.com/packages/alexivanou-russian-text-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.9M389](/packages/easycorp-easyadmin-bundle)[symfony/ux-toolkit

A tool to easily create a design system in your Symfony app with customizable, well-crafted Twig components

16126.1k1](/packages/symfony-ux-toolkit)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M206](/packages/sulu-sulu)[symfony/ux-icons

Renders local and remote SVG icons in your Twig templates.

567.3M143](/packages/symfony-ux-icons)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M582](/packages/shopware-core)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

9421.6k64](/packages/open-dxp-opendxp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
