phpdot / i18n
Internationalization with ICU MessageFormat, pluggable loaders, PSR-16 caching.
Requires
- php: >=8.5
- ext-intl: *
- phpdot/contracts: ^0.3
- psr/simple-cache: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpdot/container: ^0.3
- phpstan/phpstan: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^13.0
Suggests
- phpdot/container: Autowires the container-attribute services and #[Config] DTOs declared in src (the attributes stay inert until reflected, so standalone consumers don't need it installed).
Provides
None
Conflicts
None
Replaces
None
README
Internationalization built on ICU MessageFormat (via ext-intl): one syntax for interpolation,
pluralization, and selection. Translations load through pluggable loaders (PHP arrays, JSON, or a chain),
compiled maps are cached through any PSR-16 cache, and the translator implements the
PHPdot\Contracts\I18n\MessageTranslatorInterface so the rest of the ecosystem depends only on the
contract.
Table of Contents
Requirements
| Requirement | Constraint |
|---|---|
| PHP | >= 8.5 |
ext-intl |
* |
phpdot/contracts |
^0.3 |
psr/simple-cache |
^3.0 |
phpdot/container is a dev-only suggestion — the #[Config('i18n')] / binding attributes are inert
until a phpdot application reflects them.
Installation
composer require phpdot/i18n
Usage
use PHPdot\I18n\I18nConfig; use PHPdot\I18n\Translator; use PHPdot\I18n\Loader\PhpArrayLoader; $config = new I18nConfig( default: 'en', supported: ['en', 'ar', 'fr'], paths: ['/app/lang'], ); $translator = new Translator( loader: new PhpArrayLoader($config), cache: $cache, // any PSR-16 implementation config: $config, ); $translator->setLocale('ar_JO'); echo $translator->translate('messages.welcome', ['name' => 'Omar']);
Translation files
Keys are prefixed by filename, so lang/en/messages.php gives messages.welcome. Nesting flattens
onto that with dots, so group however reads best — the two save keys below are identical:
// lang/en/messages.php return [ 'welcome' => 'Welcome, {name}!', 'items' => '{count, plural, one {# item} other {# items}}', 'buttons' => [ 'save' => 'Save', // messages.buttons.save ], 'buttons.save' => 'Save', // the same key, written flat ];
Every configured path is scanned, in order, and a later one wins a duplicate key — which is how an
app overrides a catalog it ships with. JsonLoader reads the same shape from .json files;
ChainLoader layers loaders of different kinds (say PHP files plus database overrides). Every
template is ICU MessageFormat — interpolation, plural, and select all share one syntax — and
ICUValidator can check a template before it ships.
Architecture
Translator resolves a key to its ICU template through the configured LoaderInterface, formats it
with the current locale using ext-intl's MessageFormatter, and caches the compiled per-language map in
a PSR-16 cache. Unknown keys are recorded so missing translations can be audited.
graph TD
APP["Application / Contracts MessageTranslatorInterface"]
TRANSLATOR["Translator<br/><br/>resolve key → ICU template → format for locale"]
LOADER["LoaderInterface<br/><br/>PhpArrayLoader / JsonLoader / ChainLoader"]
CACHE["PSR-16 cache<br/><br/>compiled per-language maps"]
ICU["ext-intl MessageFormatter<br/><br/>interpolation, plural, select"]
APP --> TRANSLATOR
TRANSLATOR --> LOADER
TRANSLATOR --> CACHE
TRANSLATOR --> ICU
Loading
Testing
composer install composer test # PHPUnit composer analyse # PHPStan, level max + strict rules composer cs-check # PHP-CS-Fixer composer check # All three
License
MIT.
This repository is a read-only mirror, generated by CI from phpdot/monorepo. Pull requests and issues belong in the monorepo.