PHPackages                             matecat/icu-intl - 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. [Localization &amp; i18n](/categories/localization)
4. /
5. matecat/icu-intl

ActiveLibrary[Localization &amp; i18n](/categories/localization)

matecat/icu-intl
================

A PHP port of ICU4J's MessagePattern parser with locale utilities. Parses ICU MessageFormat patterns into an inspectable AST and provides locale data support for internationalization in PHP.

v1.1.22(1mo ago)0640↓29.2%1[1 PRs](https://github.com/matecat/icu-intl/pulls)LGPL-3.0-or-laterPHPPHP &gt;=8.3CI passing

Since Jan 28Pushed 1mo agoCompare

[ Source](https://github.com/matecat/icu-intl)[ Packagist](https://packagist.org/packages/matecat/icu-intl)[ RSS](/packages/matecat-icu-intl/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (7)Dependencies (6)Versions (40)Used By (0)

matecat/icu-intl
================

[](#matecaticu-intl)

[![Quality Gate Status](https://camo.githubusercontent.com/b39b659c2ffa75e6091d0d691b2ea6e1240c5ba03e3b77c8eaff70c00d8a63ca/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6d6174656361745f6963752d696e746c266d65747269633d616c6572745f737461747573)](https://sonarcloud.io/summary/new_code?id=matecat_icu-intl)[![Coverage](https://camo.githubusercontent.com/0c26836e7b2995e4be4357004e1daabc5fc3edc57d6f4c80b5dbaf81a944b282/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6d6174656361745f6963752d696e746c266d65747269633d636f766572616765)](https://sonarcloud.io/summary/new_code?id=matecat_icu-intl)[![Reliability Rating](https://camo.githubusercontent.com/c7f395a2945150315add6df645cb70614b28048fe0ceca60e650646300286a92/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6d6174656361745f6963752d696e746c266d65747269633d72656c696162696c6974795f726174696e67)](https://sonarcloud.io/summary/new_code?id=matecat_icu-intl)[![Maintainability Rating](https://camo.githubusercontent.com/7acda762fe0ec0fe67171b499d5c887087cb98f1a5934441e45892e52005760d/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6d6174656361745f6963752d696e746c266d65747269633d7371616c655f726174696e67)](https://sonarcloud.io/summary/new_code?id=matecat_icu-intl)

---

A PHP port of ICU4J's MessagePattern parser with locale utilities. Parses ICU MessageFormat patterns into an inspectable AST and provides locale data support for internationalization in PHP.

This package focuses on:

- **ICU MessagePattern parser**: Parses ICU MessageFormat patterns into a precise token stream and AST (Abstract Syntax Tree), exposing the internal structure of messages (literals, arguments, selects, plurals, nested sub-messages, offsets, quoted text, etc.).
- **Locale utilities**: Access language data, plural rules, and locale validation for internationalization support.

This package does not provide locale-aware date/number formatting itself — it provides the pattern model and utilities so you can build formatters or validators that interoperate with PHP's intl extension or other formatting libraries.

Contents
--------

[](#contents)

- [Features](#features)
- [Installation](#installation)
- [Namespaces](#namespaces)
- [Quick Usage](#quick-usage)
- [High-level API](#high-level-api)
- [Exceptions](#exceptions)
- [Development](#development)
- [Project Structure](#project-structure)

Authors
-------

[](#authors)

- Domenico Lupinetti — [Ostico](https://github.com/Ostico) —  /

Features
--------

[](#features)

### ICU MessagePattern Parser

[](#icu-messagepattern-parser)

- Full tokenization of ICU MessageFormat patterns (braces, argument names/indexes, type specifiers, selectors, offsets, quoted text, etc.).
- AST representation of a parsed message pattern (message, message parts, argument placeholders, plural/select blocks, and their sub-messages).
- Utilities for traversing, validating, and reconstructing patterns.
- Error reporting with position information for malformed patterns.
- Mirrored behavior of ICU4J MessagePattern parsing logic (same parsing rules and edge-case handling).

### Locale Utilities

[](#locale-utilities)

- **Languages**: Comprehensive language data including localized names, RTL detection, and language code validation.
- **Plural Rules**: CLDR-based plural rules for 140+ languages to determine the correct plural form for any number.
- **Language Domains**: Domain-specific language groupings for translation workflows.

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

[](#installation)

**Install via Composer**

```
composer require matecat/icu-intl
```

### Requirements

[](#requirements)

- PHP 8.3+
- ext-mbstring required
- ext-intl recommended for full formatting integration (not required for parsing)
- Composer for installation and development tasks

Namespaces
----------

[](#namespaces)

NamespaceDescription`Matecat\ICU`ICU MessagePattern parser, validator, and comparator`Matecat\ICU\Tokens`Token/Part model and enums (Part, TokenType, ArgType)`Matecat\ICU\Parsing`Core parsing infrastructure (parser, context, accessor)`Matecat\ICU\Parsing\Style`Style-specific sub-parsers (choice, plural/select, numeric)`Matecat\ICU\Parsing\Utils`Static character/string utility functions`Matecat\ICU\Plurals`CLDR plural rules and compliance validation`Matecat\ICU\Comparator`Comparison result model`Matecat\ICU\Exceptions`Parser and validation exceptions`Matecat\Locales`Language data, plural rules, and locale utilitiesQuick Usage
-----------

[](#quick-usage)

### ICU MessagePattern Parser

[](#icu-messagepattern-parser-1)

#### Basic parse and inspect

[](#basic-parse-and-inspect)

```
use Matecat\ICU\MessagePattern;
use Matecat\ICU\Tokens\TokenType;

$patternText = "You have {num, plural, offset:1 =0{no messages} =1{one message} other{# messages}} in {folder}.";
$pattern = new MessagePattern($patternText);

// Get AST and traverse (Token is a small DTO with type, start, length, value, limit)
$indent = '';
foreach ($pattern as $i => $part) {
    $explanation = '';

    $partString = (string)$part;
    $type = $part->getType();

    if ($type === TokenType::MSG_START) {
        $indent = str_pad('', $part->getValue() * 4, ' ', STR_PAD_LEFT);
    }

    if ($part->getLength() > 0) {
        $explanation .= '="' . $pattern->parts()->getSubstring($part) . '"';
    }

    if ($type->hasNumericValue()) {
        $explanation .= '=' . $pattern->parts()->getNumericValue($part);
    }

    printf("%2d: %s%s%s\n", $i, $indent, $partString, $explanation);

    if ($type === TokenType::MSG_LIMIT) {
        $nestingLevel = $part->getValue();
        if ($nestingLevel > 1) {
            $indent = str_pad('', ($nestingLevel - 1) * 4, ' ', STR_PAD_LEFT);
        } else {
            $indent = '';
        }
    }
}

/*
     0: MSG_START(0)@0
     1: ARG_START(PLURAL)@9="{"
     2: ARG_NAME(0)@10="num"
     3: ARG_INT(1)@30="1"=1
     4: ARG_SELECTOR(0)@32="=0"
     5: ARG_INT(0)@33="0"=0
     6:     MSG_START(1)@34="{"
     7:     MSG_LIMIT(1)@46="}"
     8: ARG_SELECTOR(0)@48="=1"
     9: ARG_INT(1)@49="1"=1
    10:     MSG_START(1)@50="{"
    11:     MSG_LIMIT(1)@62="}"
    12: ARG_SELECTOR(0)@64="other"
    13:     MSG_START(1)@69="{"
    14:     REPLACE_NUMBER(0)@70="#"
    15:     MSG_LIMIT(1)@80="}"
    16: ARG_LIMIT(PLURAL)@81="}"
    17: ARG_START(NONE)@86="{"
    18: ARG_NAME(0)@87="folder"
    19: ARG_LIMIT(NONE)@93="}"
    20: MSG_LIMIT(0)@95
 */
```

#### Simple placeholders

[](#simple-placeholders)

```
$pattern = new MessagePattern("Hello {name}, welcome!");
/*
     0: MSG_START(0)@0
     1: ARG_START(NONE)@6="{"
     2: ARG_NAME(0)@7="name"
     3: ARG_LIMIT(NONE)@11="}"
     4: MSG_LIMIT(0)@22
*/
```

#### Plural example

[](#plural-example)

```
$pattern = new MessagePattern();
$pattern->parse("You have {count, plural, =0{no messages} one{# message} other{# messages}}.");
```

#### Nested selects and plurals

[](#nested-selects-and-plurals)

```
$pattern = MessagePattern::parse("{gender, select, female{{num, plural, one{She has one file} other{She has # files}}} male{{num, plural, one{He has one file} other{He has # files}}} other{{num, plural, one{They have one file} other{They have # files}}}}");
/*
     0: MSG_START(0)@0
     1: ARG_START(SELECT)@0="{"
     2: ARG_NAME(0)@1="gender"
     3: ARG_SELECTOR(0)@17="female"
     4:     MSG_START(1)@23="{"
     5:     ARG_START(PLURAL)@24="{"
     ...
    42: ARG_LIMIT(SELECT)@219="}"
    43: MSG_LIMIT(0)@220
 */
```

### Locale Utilities

[](#locale-utilities-1)

#### Languages

[](#languages)

```
use Matecat\Locales\Languages;

// Get all supported languages
$languages = Languages::getInstance();
$allLanguages = $languages->getLanguages();

// Get a specific language by RFC3066 code
$english = $languages->getLanguage('en-US');
echo $english['name'];        // "English US"
echo $english['localized'];   // "English"
echo $english['direction'];   // "ltr"
echo $english['plurals'];     // 2

// Check if a language is RTL
$isRtl = $languages->isRTL('ar-SA'); // true

// Get the number of plural forms for a language
$pluralCount = Languages::getPluralsCount('ru-RU'); // 3
```

#### Plural Rules

[](#plural-rules)

```
use Matecat\ICU\Plurals\PluralRules;

// Get the plural form index for a number in a specific language
$form = PluralRules::getCardinalFormIndex('en', 1);    // 0 (singular)
$form = PluralRules::getCardinalFormIndex('en', 5);    // 1 (plural)

$form = PluralRules::getCardinalFormIndex('ru', 1);    // 0 (one)
$form = PluralRules::getCardinalFormIndex('ru', 2);    // 1 (few)
$form = PluralRules::getCardinalFormIndex('ru', 5);    // 2 (many)

// Get the CLDR plural category name for a number
$category = PluralRules::getCardinalCategoryName('en', 1);    // "one"
$category = PluralRules::getCardinalCategoryName('en', 5);    // "other"

$category = PluralRules::getCardinalCategoryName('ru', 1);    // "one"
$category = PluralRules::getCardinalCategoryName('ru', 2);    // "few"
$category = PluralRules::getCardinalCategoryName('ru', 5);    // "many"

$category = PluralRules::getCardinalCategoryName('ar', 0);    // "zero"
$category = PluralRules::getCardinalCategoryName('ar', 1);    // "one"
$category = PluralRules::getCardinalCategoryName('ar', 2);    // "two"
$category = PluralRules::getCardinalCategoryName('ar', 5);    // "few"
$category = PluralRules::getCardinalCategoryName('ar', 11);   // "many"
$category = PluralRules::getCardinalCategoryName('ar', 100);  // "other"

// Get all available plural categories for a language
$categories = PluralRules::getCardinalCategories('en');  // ["one", "other"]
$categories = PluralRules::getCardinalCategories('ru');  // ["one", "few", "many"]
$categories = PluralRules::getCardinalCategories('ar');  // ["zero", "one", "two", "few", "many", "other"]

// Use category constants for comparison
if (PluralRules::getCardinalCategoryName('en', $count) === PluralRules::CATEGORY_ONE) {
    echo "Singular form";
}
```

#### Plural Compliance Validation

[](#plural-compliance-validation)

The `MessagePatternValidator` validates that plural/selectordinal selectors comply with CLDR plural categories for a given locale. It provides per-argument warnings for detailed feedback.

##### Simplified API (recommended)

[](#simplified-api-recommended)

The validator can work directly with a pattern string, without needing to create a `MessagePattern` object first:

```
use Matecat\ICU\MessagePatternValidator;
use Matecat\ICU\Plurals\PluralComplianceException;

// Simplified API: just provide locale and pattern string
$validator = new MessagePatternValidator('en', '{count, plural, one{# item} other{# items}}');
$warning = $validator->validatePluralCompliance();

// Returns null when all categories are valid and complete
var_dump($warning); // null - 'one' and 'other' are valid for English

// Fluent API with setPatternString()
$warning = (new MessagePatternValidator('ru'))
    ->setPatternString('{count, plural, one{# item} other{# items}}')
    ->validatePluralCompliance();

// Returns a PluralComplianceWarning - Russian requires one/few/many/other
$warning->getMessagesAsString();           // Human-readable warning message
$warning->getArgumentWarnings();           // Array of PluralArgumentWarning objects
$warning->getAllMissingCategories();       // ['few', 'many']
$warning->getAllWrongLocaleSelectors();    // []

// Check if pattern contains complex syntax (plural, select, choice, selectordinal)
$validator = new MessagePatternValidator('en', '{count, plural, one{# file} other{# files}}');
$validator->containsComplexSyntax(); // true

$validator = new MessagePatternValidator('en', 'Hello {name}.');
$validator->containsComplexSyntax(); // false

// Check if pattern has valid ICU syntax
$validator = new MessagePatternValidator('en', '{count, plural, one{# item} other{# items}}');
$validator->isValidSyntax(); // true

$validator = new MessagePatternValidator('en', '{invalid');
$validator->isValidSyntax(); // false
$validator->getSyntaxException(); // "Unmatched '{' braces in message..."
```

##### Factory Method (with pre-parsed MessagePattern)

[](#factory-method-with-pre-parsed-messagepattern)

Use the `fromPattern()` factory method when you have a pre-parsed `MessagePattern` or want to validate the same pattern against multiple locales:

```
use Matecat\ICU\MessagePattern;
use Matecat\ICU\MessagePatternValidator;

// Parse an ICU message first
$pattern = new MessagePattern();
$pattern->parse('{count, plural, one{# item} other{# items}}');

// Create validator using factory method
$validator = MessagePatternValidator::fromPattern('en', $pattern);
$warning = $validator->validatePluralCompliance();

// Validate same pattern against multiple locales (reuses the parsed pattern)
$enValidator = MessagePatternValidator::fromPattern('en', $pattern);
$ruValidator = MessagePatternValidator::fromPattern('ru', $pattern);
$arValidator = MessagePatternValidator::fromPattern('ar', $pattern);

$enValidator->validatePluralCompliance(); // null - English only needs 'one', 'other'
$ruValidator->validatePluralCompliance(); // warning - Russian needs 'one', 'few', 'many', 'other'
```

##### Working with Warnings

[](#working-with-warnings)

```
use Matecat\ICU\MessagePatternValidator;

// Access per-argument warnings
$validator = new MessagePatternValidator('ru', '{count, plural, one{# item} other{# items}}');
$warning = $validator->validatePluralCompliance();

foreach ($warning->getArgumentWarnings() as $argWarning) {
    echo $argWarning->argumentName;           // 'count'
    echo $argWarning->getArgumentTypeLabel(); // 'plural' or 'selectordinal'
    print_r($argWarning->expectedCategories); // ['one', 'few', 'many', 'other']
    print_r($argWarning->missingCategories);  // ['few', 'many']
    print_r($argWarning->foundSelectors);     // ['one', 'other']
    echo $argWarning->getMessageAsString();   // Detailed message for this argument
}
```

##### Exception Handling

[](#exception-handling)

```
use Matecat\ICU\MessagePatternValidator;
use Matecat\ICU\Plurals\PluralComplianceException;

// Invalid CLDR categories throw an exception
$validator = new MessagePatternValidator('en', '{count, plural, some{# items} other{# items}}');

try {
    $validator->validatePluralCompliance();
} catch (PluralComplianceException $e) {
    echo $e->getMessage();
    // "Invalid selectors found for locale 'en': [some]. Found selectors: [some, other]. Valid CLDR categories are: [zero, one, two, few, many, other]."
    echo $e->locale;                  // 'en'
    print_r($e->invalidSelectors);    // ['some']
    print_r($e->foundSelectors);      // ['some', 'other']
    print_r($e->expectedCategories);  // ['zero', 'one', 'two', 'few', 'many', 'other']
}
```

##### More Examples

[](#more-examples)

```
use Matecat\ICU\MessagePatternValidator;

// Valid CLDR categories wrong for locale return warnings (not exceptions)
$validator = new MessagePatternValidator('en', '{count, plural, one{# item} few{# items} other{# items}}');
$warning = $validator->validatePluralCompliance();

$argWarning = $warning->getArgumentWarnings()[0];
print_r($argWarning->wrongLocaleSelectors); // ['few'] - valid CLDR but not for English

// Explicit numeric selectors (=0, =1, =2) are always valid but don't substitute category keywords
$validator = new MessagePatternValidator('en', '{count, plural, =0{none} =1{one item} other{# items}}');
$warning = $validator->validatePluralCompliance();

$argWarning = $warning->getArgumentWarnings()[0];
print_r($argWarning->numericSelectors);    // ['=0', '=1']
print_r($argWarning->missingCategories);   // ['one'] - =1 doesn't substitute for 'one' keyword

// Nested messages with multiple plural arguments get per-argument validation
$validator = new MessagePatternValidator(
    'en',
    "{gender, select, female{{n, plural, one{her item} other{her items}}} male{{n, plural, one{his item} other{his items}}}}"
);
$warning = $validator->validatePluralCompliance(); // null - all valid

// SelectOrdinal validation uses ordinal rules (different from cardinal)
$validator = new MessagePatternValidator('en', '{rank, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}');
$warning = $validator->validatePluralCompliance(); // null - English ordinal uses one/two/few/other
```

##### Validation Behavior Summary

[](#validation-behavior-summary)

Selector TypeBehaviorNon-existent CLDR category (e.g., 'some')Throws `PluralComplianceException`Valid CLDR category wrong for locale (e.g., 'few' in English)Returns warning in `wrongLocaleSelectors`Missing required category for localeReturns warning in `missingCategories`Explicit numeric selector (=0, =1, etc.)Always valid, tracked in `numericSelectors`'other' categoryAlways valid (ICU requires it as fallback)#### Pattern Comparison for Translations

[](#pattern-comparison-for-translations)

The `MessagePatternComparator` validates that translated ICU patterns maintain the same complex forms (plural, select, choice, selectordinal) as the source pattern. This ensures translations don't accidentally lose required argument structures.

##### Basic Usage

[](#basic-usage)

```
use Matecat\ICU\MessagePatternComparator;
use Matecat\ICU\Exceptions\MissingComplexFormException;

// Compare source and target patterns
$comparator = new MessagePatternComparator(
    'en-US',                                                    // source locale
    'fr-FR',                                                    // target locale
    '{count, plural, one{# item} other{# items}}',              // source pattern
    '{count, plural, one{# article} many{# articles} other{# articles}}'  // target pattern
);

// Validate - throws exception if target is missing complex forms from source
$comparator->validate();

// Optionally validate plural compliance against CLDR rules for source, target, or both
$result = $comparator->validate(validateSource: true, validateTarget: true);

// $result->sourceWarnings — PluralComplianceWarning|null for the source pattern
// $result->targetWarnings — PluralComplianceWarning|null for the target pattern
if ($result->targetWarnings !== null) {
    echo $result->targetWarnings->getMessagesAsString();
}
```

##### Factory Methods

[](#factory-methods)

Use factory methods when you have pre-configured validators or pre-parsed patterns:

```
use Matecat\ICU\MessagePattern;
use Matecat\ICU\MessagePatternComparator;
use Matecat\ICU\MessagePatternValidator;

// From pre-configured validators
$sourceValidator = new MessagePatternValidator('en', '{count, plural, one{# item} other{# items}}');
$targetValidator = new MessagePatternValidator('fr', '{count, plural, one{# article} other{# articles}}');
$comparator = MessagePatternComparator::fromValidators($sourceValidator, $targetValidator);

// From pre-parsed patterns (useful for comparing same patterns against multiple locale pairs)
$sourcePattern = new MessagePattern('{count, plural, one{# item} other{# items}}');
$targetPattern = new MessagePattern('{count, plural, one{# article} other{# articles}}');
$comparator = MessagePatternComparator::fromPatterns('en', 'fr', $sourcePattern, $targetPattern);
```

##### Exception Handling

[](#exception-handling-1)

```
use Matecat\ICU\MessagePatternComparator;
use Matecat\ICU\Exceptions\MissingComplexFormException;

// Missing plural form in target
$comparator = new MessagePatternComparator(
    'en', 'fr',
    '{count, plural, one{# item} other{# items}}',
    'Les articles {count}'  // Missing plural form!
);

try {
    $comparator->validate();
} catch (MissingComplexFormException $e) {
    echo $e->getMessage();
    // "Argument 'count' has complex form 'PLURAL' in source (en) but is missing in target (fr)."

    echo $e->argumentName;     // 'count'
    echo $e->sourceArgType;    // ArgType::PLURAL
    echo $e->targetArgType;    // null (missing)
    echo $e->sourceLocale;     // 'en'
    echo $e->targetLocale;     // 'fr'
}

// Mismatched complex form types
$comparator = new MessagePatternComparator(
    'en', 'fr',
    '{count, plural, one{# item} other{# items}}',
    '{count, select, one{un article} other{des articles}}'  // SELECT instead of PLURAL!
);

try {
    $comparator->validate();
} catch (MissingComplexFormException $e) {
    echo $e->getMessage();
    // "Argument 'count' has complex form 'PLURAL' in source (en) but has 'SELECT' in target (fr)."

    echo $e->targetArgType;    // ArgType::SELECT
}
```

##### Helper Methods

[](#helper-methods)

```
use Matecat\ICU\MessagePatternComparator;

$comparator = new MessagePatternComparator(
    'en', 'fr',
    '{count, plural, one{# item} other{# items}}',
    '{count, plural, one{# article} other{# articles}}'
);

// Check if patterns contain complex syntax
$comparator->sourceContainsComplexSyntax();  // true
$comparator->targetContainsComplexSyntax();  // true

// Get locales
$comparator->getSourceLocale();  // 'en'
$comparator->getTargetLocale();  // 'fr'
```

##### Validation Rules

[](#validation-rules)

ScenarioBehaviorSource has no complex formsValidation passes (nothing to check)Target has same complex forms for same argumentsValidation passesTarget is missing a complex form argumentThrows `MissingComplexFormException`Target has different complex form type for same argumentThrows `MissingComplexFormException`PLURAL vs SELECTORDINALNot interchangeable (different semantics)Target has extra complex formsAllowed (no exception)Plural compliance (`validateSource`/`validateTarget`)Off by default; when enabled, validates selectors against CLDR rulesInvalid CLDR category (e.g., `foo`)Throws `PluralComplianceException` (when compliance validation is enabled)Wrong locale selector (e.g., `few` in English)Returns `PluralComplianceWarning` (when compliance validation is enabled)#### Language Domains

[](#language-domains)

```
use Matecat\Locales\LanguageDomains;

// Get all language domains
$domains = LanguageDomains::getInstance();
$allDomains = $domains->getDomains();

// Get a specific domain
$domain = $domains->getDomain('technical');
```

### Notes about formatting

[](#notes-about-formatting)

This library focuses on parsing and structure. If you want to format values using parsed plural/select patterns:

- Use PHP's Intl MessageFormatter (intl extension) for end-to-end ICU MessageFormat formatting (it accepts message strings and values).
- Or, implement a custom formatter that walks the AST and applies number/date formatting from ext-intl or other libraries.

High-level API
--------------

[](#high-level-api)

Quick reference for the public classes and the most-used methods.

### Matecat\\ICU\\MessagePattern

[](#matecaticumessagepattern)

- `__construct(?string $pattern = null, string $apostropheMode = MessagePattern::APOSTROPHE_DOUBLE_OPTIONAL)`
- `parse(string $pattern): self`
- `parseChoiceStyle(string $pattern): self`
- `parsePluralStyle(string $pattern): self`
- `parseSelectStyle(string $pattern): self`
- `clear(): void`
- `clearPatternAndSetApostropheMode(string $mode): void`
- `getApostropheMode(): string`
- `getPatternString(): string`
- `hasNamedArguments(): bool`
- `hasNumberedArguments(): bool`
- `autoQuoteApostropheDeep(): string`
- `parts(): PartAccessor` — returns the part accessor for querying parsed tokens
- `validateArgumentName(string $name): int` (static helper)
- `appendReducedApostrophes(string $s, int $start, int $limit, string &$out): void` (static helper)
- Implements `Iterator` to iterate parts.

### Matecat\\ICU\\Parsing\\PartAccessor

[](#matecaticuparsingpartaccessor)

Accessed via `$pattern->parts()`:

- `countParts(): int`
- `getPart(int $index): Part`
- `getPartType(int $index): TokenType`
- `getSubstring(Part $part): string`
- `partSubstringMatches(Part $part, string $s): bool`
- `getNumericValue(Part $part): float` (returns `MessagePattern::NO_NUMERIC_VALUE` when not numeric)
- `getPluralOffset(int $pluralStart): float`
- `getPatternIndex(int $partIndex): int`
- `getLimitPartIndex(int $start): int`

### Matecat\\ICU\\Tokens\\Part

[](#matecaticutokenspart)

Represents a parsed token/part with accessors:

- `getType(): TokenType`
- `getIndex(): int`
- `getLength(): int`
- `getValue(): int`
- `getLimit(): int`
- `getArgType(): ?ArgType`
- `Part::MAX_LENGTH`
- `Part::MAX_VALUE`

### Matecat\\ICU\\Tokens\\TokenType (enum)

[](#matecaticutokenstokentype-enum)

Token types used by the parser: `MSG_START`, `MSG_LIMIT`, `ARG_START`, `ARG_NAME`, `ARG_NUMBER`, `ARG_INT`, `ARG_DOUBLE`, `ARG_TYPE`, `ARG_STYLE`, `ARG_SELECTOR`, `ARG_LIMIT`, `INSERT_CHAR`, `REPLACE_NUMBER`, `SKIP_SYNTAX`, etc.

### Matecat\\ICU\\Tokens\\ArgType (enum)

[](#matecaticutokensargtype-enum)

Argument classifications: `NONE`, `SIMPLE`, `CHOICE`, `PLURAL`, `SELECT`, `SELECTORDINAL`.

### Matecat\\ICU\\MessagePatternValidator

[](#matecaticumessagepatternvalidator)

- `__construct(string $language = 'en-US', ?string $patternString = null)` - Creates a validator with the specified locale and optional pattern string
- `static fromPattern(string $language, MessagePattern $pattern): MessagePatternValidator` - Factory method to create a validator from a pre-parsed MessagePattern (useful for validating the same pattern against multiple locales)
- `setPatternString(string $patternString): static` - Sets the pattern string for lazy parsing, resets any stored parsing exception, and clears the internal pattern (fluent interface)
- `getPattern(): MessagePattern` - Returns the parsed MessagePattern instance (triggers parsing if not already done)
- `containsComplexSyntax(): bool` - Returns true if the pattern contains plural, select, choice, or selectordinal
- `isValidSyntax(): bool` - Returns true if the pattern string has valid ICU MessageFormat syntax, false if there were parsing errors
- `getSyntaxException(): ?string` - Returns the parsing exception message if the pattern has invalid syntax, null otherwise
- `validatePluralCompliance(): ?PluralComplianceWarning` - Validates if plural forms comply with the locale's expected categories. Returns null if valid, a warning object if there are issues. Throws `PluralComplianceException` for invalid CLDR categories, or `InvalidArgumentException`/`OutOfBoundsException` for parsing errors.

### Matecat\\ICU\\MessagePatternComparator

[](#matecaticumessagepatterncomparator)

Compares source and target ICU MessageFormat patterns for translation validation. Ensures target patterns maintain the same complex forms (plural, select, choice, selectordinal) as source patterns.

- `__construct(string $sourceLocale, string $targetLocale, string $sourcePattern, string $targetPattern)` - Creates a comparator with source/target locales and pattern strings
- `static fromValidators(MessagePatternValidator $sourceValidator, MessagePatternValidator $targetValidator): MessagePatternComparator` - Factory method to create a comparator from pre-configured validators
- `static fromPatterns(string $sourceLocale, string $targetLocale, MessagePattern $sourcePattern, MessagePattern $targetPattern): MessagePatternComparator` - Factory method to create a comparator from pre-parsed patterns (useful for reusing parsed patterns across multiple locale comparisons)
- `validate(bool $validateSource = false, bool $validateTarget = false): ComparisonResult` - Validates that all complex forms in source exist in target. Optionally validates plural/ordinal compliance against CLDR rules for the source and/or target locale. Returns a `ComparisonResult` with `sourceWarnings` and `targetWarnings` properties (each `PluralComplianceWarning|null`, null if validation was not requested or no issues found). Throws `MissingComplexFormException` if target is missing complex forms or has mismatched types. Throws `PluralComplianceException` if a selector is not a valid CLDR category name.
- `sourceContainsComplexSyntax(): bool` - Returns true if source contains plural, select, choice, or selectordinal
- `targetContainsComplexSyntax(): bool` - Returns true if target contains plural, select, choice, or selectordinal
- `getSourceLocale(): string` - Returns the source locale
- `getTargetLocale(): string` - Returns the target locale
- `getSourceValidator(): MessagePatternValidator` - Returns the source pattern validator
- `getTargetValidator(): MessagePatternValidator` - Returns the target pattern validator

### Matecat\\ICU\\Comparator\\ComparisonResult (readonly)

[](#matecaticucomparatorcomparisonresult-readonly)

Result object returned by `MessagePatternComparator::validate()`. Contains optional plural compliance warnings for source and target patterns.

- `__construct(?PluralComplianceWarning $sourceWarnings = null, ?PluralComplianceWarning $targetWarnings = null)`
- `sourceWarnings: ?PluralComplianceWarning` - Plural compliance warnings for the source pattern, or null if validation was not requested or no issues were found
- `targetWarnings: ?PluralComplianceWarning` - Plural compliance warnings for the target pattern, or null if validation was not requested or no issues were found
- `hasWarnings(): bool` - Returns true if either side has warnings

### Matecat\\ICU\\Plurals\\PluralComplianceWarning (readonly)

[](#matecaticupluralspluralcompliancewarning-readonly)

Returned when plural selectors have compliance issues that don't warrant an exception.

- `__construct(array $argumentWarnings)`
- `getArgumentWarnings(): array` - Get all argument-level warnings
- `getAllMissingCategories(): array` - Get all missing categories across all arguments
- `getAllWrongLocaleSelectors(): array` - Get all wrong locale selectors across all arguments
- `getMessages(): array` - Get all warning messages as an array
- `getMessagesAsString(): string` - Human-readable warning message (joins all messages with newlines)
- Implements `Stringable` interface

### Matecat\\ICU\\Plurals\\PluralArgumentWarning (readonly)

[](#matecaticupluralspluralargumentwarning-readonly)

Detailed warning information for a single plural/selectordinal argument.

- `argumentName: string` - The argument name (e.g., 'count', 'num\_guests')
- `argumentType: ArgType` - The argument type (PLURAL or SELECTORDINAL)
- `expectedCategories: array` - Valid CLDR categories for this argument type and locale
- `foundSelectors: array` - All selectors found in this argument
- `missingCategories: array` - Expected categories not found
- `numericSelectors: array` - Explicit numeric selectors found (e.g., =0, =1)
- `wrongLocaleSelectors: array` - Valid CLDR categories that don't apply to this locale
- `getArgumentTypeLabel(): string` - Returns 'plural' or 'selectordinal'
- `getMessage(): string` - Human-readable message for this argument
- Implements `Stringable` interface

### Matecat\\ICU\\Plurals\\PluralComplianceException

[](#matecaticupluralspluralcomplianceexception)

Thrown when a selector is not a valid CLDR category name (e.g., 'some', 'foo').

- `expectedCategories: array` - Valid CLDR categories
- `foundSelectors: array` - All selectors found in the message
- `invalidSelectors: array` - Non-existent CLDR category names
- `missingCategories: array` - (Always empty, for interface compatibility)

### Matecat\\ICU\\Exceptions\\MissingComplexFormException

[](#matecaticuexceptionsmissingcomplexformexception)

Thrown when a target pattern is missing a complex form that exists in the source pattern, or when the complex form type doesn't match.

- `argumentName: string` - The name of the argument with the missing/mismatched complex form
- `sourceArgType: ArgType` - The argument type in the source pattern (PLURAL, SELECT, CHOICE, SELECTORDINAL)
- `targetArgType: ?ArgType` - The argument type in the target pattern (null if argument is missing entirely)
- `sourceLocale: string` - The source locale
- `targetLocale: string` - The target locale

### Matecat\\Locales\\Languages

[](#matecatlocaleslanguages)

- `getInstance(): Languages` (singleton)
- `getLanguages(): array`
- `getLanguage(string $rfc3066code): ?array`
- `isRTL(string $rfc3066code): bool`
- `getPluralsCount(string $rfc3066code): int` (static)

### Matecat\\ICU\\Plurals\\PluralRules

[](#matecaticupluralspluralrules)

- `getCardinalFormIndex(string $locale, int $n): int` (static) - Returns the plural form index for a number
- `getCardinalCategoryName(string $locale, int $n): string` (static) - Returns the CLDR category name ('zero', 'one', ' two', 'few', 'many', 'other')
- `getCardinalCategories(string $locale): array` (static) - Returns all available cardinal category names for a locale
- `getOrdinalCategories(string $locale): array` (static) - Returns all available ordinal category names for a locale
- `getOrdinalFormIndex(string $locale, int $n): int` (static) - Returns the ordinal form index for a number
- `isValidCategory(string $selector): bool` (static) - Checks if a selector is a valid CLDR category name

#### Constants

[](#constants)

- `CATEGORY_ZERO` = 'zero'
- `CATEGORY_ONE` = 'one'
- `CATEGORY_TWO` = 'two'
- `CATEGORY_FEW` = 'few'
- `CATEGORY_MANY` = 'many'
- `CATEGORY_OTHER` = 'other'
- `VALID_CATEGORIES` = \['zero', 'one', 'two', 'few', 'many', 'other'\]

### Matecat\\Locales\\LanguageDomains

[](#matecatlocaleslanguagedomains)

- `getInstance(): LanguageDomains` (singleton)
- `getDomains(): array`
- `getDomain(string $domainKey): ?array`

Exceptions
----------

[](#exceptions)

- `Matecat\ICU\Exceptions\InvalidArgumentException` — syntax errors and invalid argument patterns
- `Matecat\ICU\Exceptions\OutOfBoundsException` — excessive sizes, nesting, and indexing errors
- `Matecat\ICU\Exceptions\UnmatchedBracesException` — unmatched `{` or `}` in patterns
- `Matecat\ICU\Exceptions\BadChoicePatternSyntaxException` — invalid choice pattern syntax
- `Matecat\ICU\Exceptions\BadPluralSelectPatternSyntaxException` — invalid plural/select pattern syntax
- `Matecat\ICU\Exceptions\InvalidNumericValueException` — bad numeric value syntax in patterns
- `Matecat\ICU\Exceptions\MissingComplexFormException` — missing or mismatched complex forms in pattern comparisons
- `Matecat\ICU\Plurals\PluralComplianceException` — invalid CLDR plural category names
- `Matecat\Locales\InvalidLanguageException` — invalid language codes

Development
-----------

[](#development)

**Run tests**

```
vendor/bin/phpunit
```

or

```
composer test
```

**Run static analysis**

```
vendor/bin/phpstan analyze
```

Project Structure
-----------------

[](#project-structure)

```
src/ICU/
├── MessagePattern.php              # Public facade — parse, iterate, access parts
├── MessagePatternValidator.php     # Syntax & plural compliance validation
├── MessagePatternComparator.php    # Source/target pattern comparison
├── Parsing/
│   ├── MessagePatternParser.php    # Core recursive-descent parser
│   ├── ParseContext.php            # Shared mutable parsing state
│   ├── PartAccessor.php            # Read-only part/token accessor
│   ├── Style/
│   │   ├── ChoiceStyleParser.php   # ChoiceFormat sub-parser
│   │   ├── NumericParser.php       # Numeric value parsing
│   │   └── PluralSelectParser.php  # Plural/Select/SelectOrdinal sub-parser
│   └── Utils/
│       └── CharUtils.php           # Static character/string utilities
├── Tokens/
│   ├── Part.php                    # Token DTO (type, index, length, value)
│   ├── TokenType.php               # Token type enum
│   └── ArgType.php                 # Argument type enum
├── Plurals/
│   ├── PluralRules.php             # CLDR cardinal/ordinal plural rules
│   ├── PluralComplianceWarning.php # Plural compliance warning model
│   ├── PluralArgumentWarning.php   # Per-argument warning detail
│   └── PluralComplianceException.php
├── Comparator/
│   └── ComparisonResult.php        # Comparison result model
└── Exceptions/
    ├── InvalidArgumentException.php
    ├── OutOfBoundsException.php
    ├── UnmatchedBracesException.php
    ├── BadChoicePatternSyntaxException.php
    ├── BadPluralSelectPatternSyntaxException.php
    ├── InvalidNumericValueException.php
    └── MissingComplexFormException.php

src/Locales/
├── Languages.php                   # Language data and validation
├── LanguageDomains.php             # Domain-specific language groupings
└── PluralRulesBuilder.php          # Builds pluralRules.json from CLDR data

src/resources/
├── supported_langs.json            # Language data (runtime)
├── languageDomains.json            # Domain data (runtime)
├── pluralRules.json                # Built plural rules output (runtime)
└── build/                          # Build-time inputs (not needed at runtime)
    ├── cldr49_plural_rules.json    # CLDR 49 plural rules (source of truth)
    ├── cardinal_rules_human.json   # Cardinal rule → human description lookup
    ├── ordinal_rules_human.json    # Ordinal rule → human description lookup
    ├── nonCldrParentMap.json       # Non-CLDR locale → parent locale mapping
    └── pluralRulesOverrides.json   # Per-language overrides

```

License
-------

[](#license)

This project is licensed under the GNU Lesser General Public License v3.0 or later.

- SPDX-License-Identifier: `LGPL-3.0-or-later`
- See the `LICENSE` file for the full license text.
- More info:

### Credits

[](#credits)

This PHP port mirrors ideas from ICU4J MessagePattern.java.

- See `LICENSE` for project license and attribution to ICU4J sources:
- Plural rules based on CLDR (Unicode Common Locale Data Repository):

###  Health Score

48

—

FairBetter than 95% of packages

Maintenance90

Actively maintained with recent releases

Popularity20

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity61

Established project with proven stability

 Bus Factor1

Top contributor holds 83.8% of commits — single point of failure

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

Every ~1 days

Total

36

Last Release

56d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b9a5ee22f08424377759a39efc8bc6db4d0460db2afb97b8d6c950c14a9d5666?d=identicon)[MateCat-bot](/maintainers/MateCat-bot)

---

Top Contributors

[![Ostico](https://avatars.githubusercontent.com/u/8008416?v=4)](https://github.com/Ostico "Ostico (83 commits)")[![mauretto78](https://avatars.githubusercontent.com/u/10035321?v=4)](https://github.com/mauretto78 "mauretto78 (14 commits)")[![leonardotaddeitranslated](https://avatars.githubusercontent.com/u/99892721?v=4)](https://github.com/leonardotaddeitranslated "leonardotaddeitranslated (2 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/matecat-icu-intl/health.svg)

```
[![Health](https://phpackages.com/badges/matecat-icu-intl/health.svg)](https://phpackages.com/packages/matecat-icu-intl)
```

###  Alternatives

[joedixon/laravel-translation

A tool for managing all of your Laravel translations

717911.4k11](/packages/joedixon-laravel-translation)[illuminate/translation

The Illuminate Translation package.

6936.4M495](/packages/illuminate-translation)[lajax/yii2-translate-manager

Translation management extension for Yii 2

227578.8k13](/packages/lajax-yii2-translate-manager)[larswiegers/laravel-translations-checker

Make sure your laravel translations are checked and are included in all languages.

256423.2k2](/packages/larswiegers-laravel-translations-checker)[inpsyde/multilingual-press

Simply THE multisite-based free open source plugin for your multilingual websites.

2414.0k1](/packages/inpsyde-multilingual-press)[statikbe/laravel-chained-translator

The Laravel Chained Translator can combine several translators that can override each others translations.

36149.4k6](/packages/statikbe-laravel-chained-translator)

PHPackages © 2026

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