phpdot/i18n

Internationalization with ICU MessageFormat, pluggable loaders, PSR-16 caching.

Maintainers

Package info

github.com/phpdot/i18n

Issues

pkg:composer/phpdot/i18n

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

v0.2.0 2026-08-19 02:16 UTC

This package is auto-updated.

Last update: 2026-08-19 03:09:50 UTC


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.2
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.