PHPackages                             monkeyscloud/monkeyslegion-i18n - 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. monkeyscloud/monkeyslegion-i18n

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

monkeyscloud/monkeyslegion-i18n
===============================

Production-ready I18n &amp; localization component for the MonkeysLegion PHP framework — v2

2.1.1(2mo ago)11.9k↓32.1%1MITPHPPHP ^8.4

Since May 24Pushed 2mo agoCompare

[ Source](https://github.com/MonkeysCloud/MonkeysLegion-I18n)[ Packagist](https://packagist.org/packages/monkeyscloud/monkeyslegion-i18n)[ RSS](/packages/monkeyscloud-monkeyslegion-i18n/feed)WikiDiscussions main Synced 3d ago

READMEChangelog (1)Dependencies (10)Versions (9)Used By (1)

MonkeysLegion I18n v2
=====================

[](#monkeyslegion-i18n-v2)

**Production-ready internationalization &amp; localization for the MonkeysLegion PHP framework.**

[![PHP 8.4+](https://camo.githubusercontent.com/bfb98d885e37493cddcc01059ebf02a8872de9da37c12691b8bb6d13fcdca735/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e342532422d626c75652e737667)](https://php.net)![Tests](https://camo.githubusercontent.com/bf14040f141ae49ff8cadb9a999d5c3b666d6e73eba11ee2533d248d35604bf3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f54657374732d31323725323070617373696e672d627269676874677265656e2e737667)[![License](https://camo.githubusercontent.com/784362b26e4b3546254f1893e778ba64616e362bd6ac791991d2c9e880a3a64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e737667)](LICENSE)![v2 Standards](https://camo.githubusercontent.com/b6933c3ffcc1e5138d627f229353d881a561632e408bdbf3236974934a5f080a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d6f6e6b6579734c6567696f6e2d76322532305374616e64617264732d6f72616e67652e737667)

Translate, pluralize, format numbers/dates/currencies, and manage locales with security and performance at the core. Built with PHP 8.4 property hooks and asymmetric visibility.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Translator](#translator)
    - [Basic Translation](#basic-translation)
    - [Parameter Replacement](#parameter-replacement)
    - [Pluralization](#pluralization)
    - [Namespaced Translations](#namespaced-translations)
    - [Fallback Locale](#fallback-locale)
    - [Missing Translation Tracking](#missing-translation-tracking)
    - [Warm-Up](#warm-up)
- [MessageFormatter](#messageformatter)
    - [Parameter Modifiers](#parameter-modifiers)
    - [XSS Protection](#xss-protection)
- [NumberFormatter](#numberformatter)
    - [Decimal Formatting](#decimal-formatting)
    - [Currency Formatting](#currency-formatting)
    - [Compact Notation](#compact-notation)
    - [Ordinals](#ordinals)
    - [File Size](#file-size)
- [DateFormatter](#dateformatter)
    - [Named Formats](#named-formats)
    - [Relative Time](#relative-time)
    - [Diff for Humans](#diff-for-humans)
- [LocaleManager](#localemanager)
    - [Detection Chain](#detection-chain)
    - [Supported Locales](#supported-locales)
- [LocaleInfo](#localeinfo)
    - [Native Names](#native-names)
    - [RTL Detection](#rtl-detection)
    - [Flag Emojis](#flag-emojis)
- [Loaders](#loaders)
    - [FileLoader](#fileloader)
    - [DatabaseLoader](#databaseloader)
    - [CacheLoader](#cacheloader)
    - [CompiledLoader](#compiledloader)
    - [MlcLoader](#mlcloader)
- [Middleware](#middleware)
    - [LocaleMiddleware](#localemiddleware)
    - [LocaleUrlMiddleware](#localeurlmiddleware)
    - [LocaleRedirectMiddleware](#localeredirectmiddleware)
- [Enums](#enums)
    - [PluralCategory](#pluralcategory)
    - [Direction](#direction)
- [Attributes](#attributes)
    - [\#\[Translatable\]](#translatable)
    - [\#\[Locale\]](#locale)
- [Events](#events)
- [Template Directives](#template-directives)
- [Helper Functions](#helper-functions)
- [TranslatorFactory](#translatorfactory)
- [Translation Management](#translation-management)
- [CLI Commands](#cli-commands)
- [Security](#security)
- [Performance](#performance)
- [Migration from v1](#migration-from-v1)
- [Testing](#testing)
- [License](#license)

---

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

[](#installation)

```
composer require monkeyscloud/monkeyslegion-i18n
```

### Requirements

[](#requirements)

- **PHP 8.4+** (property hooks, asymmetric visibility)
- **ext-json** (JSON translation files)
- **ext-mbstring** (Unicode support)

### Optional Extensions

[](#optional-extensions)

```
# Advanced number/currency/date formatting
ext-intl

# For database translations
monkeyscloud/monkeyslegion-database

# For cache-backed loading
monkeyscloud/monkeyslegion-cache
```

---

Quick Start
-----------

[](#quick-start)

```
use MonkeysLegion\I18n\TranslatorFactory;

// One-line setup
$translator = TranslatorFactory::create([
    'locale'   => 'en',
    'fallback' => 'en',
    'path'     => __DIR__ . '/resources/lang',
]);

echo $translator->trans('messages.welcome');
// → "Welcome!"

echo $translator->trans('messages.greeting', ['name' => 'Yorch']);
// → "Hello, Yorch!"

echo $translator->choice('messages.items', 5);
// → "5 items"
```

### Translation File Structure

[](#translation-file-structure)

```
resources/lang/
├── en/
│   ├── messages.json
│   └── validation.json
├── es/
│   ├── messages.json
│   └── validation.json
└── fr/
    └── messages.json

```

### Example `messages.json`

[](#example-messagesjson)

```
{
  "welcome": "Welcome!",
  "greeting": "Hello, :name!",
  "farewell": "Goodbye, :NAME!",
  "items": "{0} No items|{1} One item|[2,*] :count items",
  "nested": {
    "key": "Nested value",
    "deep": {
      "value": "Deep nested value"
    }
  }
}
```

---

Translator
----------

[](#translator)

### Basic Translation

[](#basic-translation)

```
use MonkeysLegion\I18n\Translator;
use MonkeysLegion\I18n\Loaders\FileLoader;

$translator = new Translator('en', 'en');
$translator->addLoader(new FileLoader('/path/to/lang'));

// Simple key
echo $translator->trans('messages.welcome');
// → "Welcome!"

// Nested key
echo $translator->trans('messages.nested.deep.value');
// → "Deep nested value"

// Check if translation exists
if ($translator->has('messages.welcome')) {
    // Key exists
}

// Returns the key itself when not found
echo $translator->trans('messages.nonexistent');
// → "messages.nonexistent"
```

### Parameter Replacement

[](#parameter-replacement)

```
// Lowercase :name → exact value
echo $translator->trans('messages.greeting', ['name' => 'Yorch']);
// → "Hello, Yorch!"

// Uppercase :NAME → UPPERCASED
echo $translator->trans('messages.farewell', ['name' => 'Yorch']);
// → "Goodbye, YORCH!"

// Ucfirst :Name → Capitalized
// Message: "Welcome :Name"
echo $translator->trans('messages.title', ['name' => 'yorch']);
// → "Welcome Yorch"

// Multiple parameters
echo $translator->trans('order.summary', [
    'product' => 'Widget',
    'count'   => 3,
    'total'   => '29.97',
]);
// → "3x Widget — Total: $29.97"
```

### Pluralization

[](#pluralization)

Supports ICU plural categories for 200+ languages.

```
// Explicit count forms
// "{0} No items|{1} One item|[2,*] :count items"
echo $translator->choice('messages.items', 0);  // → "No items"
echo $translator->choice('messages.items', 1);  // → "One item"
echo $translator->choice('messages.items', 42); // → "42 items"

// Range forms
// "[0,3] A few|[4,10] Several|[11,*] Many"
echo $translator->choice('messages.range', 2);  // → "A few"
echo $translator->choice('messages.range', 7);  // → "Several"
echo $translator->choice('messages.range', 50); // → "Many"

// Simple pipe-delimited (singular|plural)
// "apple|apples"
echo $translator->choice('messages.fruit', 1); // → "apple"
echo $translator->choice('messages.fruit', 5); // → "apples"

// With additional replacements
echo $translator->choice('messages.items', 3, ['color' => 'red']);
// "{0} No :color items|{1} One :color item|[2,*] :count :color items"
// → "3 red items"
```

### Namespaced Translations

[](#namespaced-translations)

```
// Register a namespace
$translator->addNamespace('billing', '/path/to/billing/lang');
$translator->addLoader(new FileLoader('/path/to/billing/lang'));

// Use namespace::group.key format
echo $translator->trans('billing::invoices.title');
// → Loads from /path/to/billing/lang/{locale}/invoices.json
```

### Fallback Locale

[](#fallback-locale)

```
$translator = new Translator('fr', 'en');

// If 'fr' translation missing, falls back to 'en'
echo $translator->trans('messages.welcome');
// French translation exists → "Bienvenue!"

echo $translator->trans('messages.rare_key');
// French missing, English fallback → "Rare English Value"
```

### Missing Translation Tracking

[](#missing-translation-tracking)

```
$translator->setTrackMissing(true);

$translator->trans('messages.missing1');
$translator->trans('messages.missing2');

$missing = $translator->getMissingTranslations();
// ["en.messages.missing1", "en.messages.missing2"]

$translator->clearMissingTranslations();
```

### Warm-Up

[](#warm-up)

Pre-load translation groups for production performance:

```
$translator->warmUp('en', ['messages', 'validation', 'auth']);

// Check what's loaded
$groups = $translator->getLoadedGroups();
// ["messages.en", "validation.en", "auth.en"]
```

### Property Hooks (PHP 8.4)

[](#property-hooks-php-84)

```
$translator = new Translator('en');

// Use property directly
echo $translator->locale; // → "en"

// Setter triggers validation + event dispatch
$translator->locale = 'es';

// Invalid locale throws InvalidLocaleException
$translator->locale = '../etc/passwd'; // ❌ InvalidLocaleException
```

### Locale Changed Events

[](#locale-changed-events)

```
$translator->setEventDispatcher(function (LocaleChangedEvent $event): void {
    log("Locale changed: {$event->previousLocale} → {$event->newLocale}");
});

$translator->setLocale('fr'); // Event fires automatically
```

---

MessageFormatter
----------------

[](#messageformatter)

### Parameter Modifiers

[](#parameter-modifiers)

Use braced syntax with modifiers for advanced formatting:

```
use MonkeysLegion\I18n\MessageFormatter;

$formatter = new MessageFormatter();

// Uppercase
echo $formatter->format('Name: {name|upper}', ['name' => 'yorch']);
// → "Name: YORCH"

// Lowercase
echo $formatter->format('Email: {email|lower}', ['email' => 'USER@EXAMPLE.COM']);
// → "Email: user@example.com"

// Title case
echo $formatter->format('Title: {title|title}', ['title' => 'hello world']);
// → "Title: Hello World"

// Capitalize first
echo $formatter->format('{msg|ucfirst}', ['msg' => 'hello']);
// → "Hello"

// Truncate
echo $formatter->format('{desc|truncate:20}', ['desc' => 'Very long description text']);
// → "Very long descriptio..."

// Number formatting
echo $formatter->format('Total: {amount|number:2}', ['amount' => 1234.5]);
// → "Total: 1,234.50"

// Currency
echo $formatter->format('Price: {price|currency:EUR}', ['price' => 42.50]);
// → "Price: €42.50"

// Percentage
echo $formatter->format('Rate: {rate|percent}', ['rate' => 0.156]);
// → "Rate: 15.60%"

// Date formatting
echo $formatter->format('Date: {date|date:medium}', ['date' => '2026-01-15']);
// → "Date: Jan 15, 2026"

// Default fallback
echo $formatter->format('Name: {name|default:Anonymous}', ['name' => '']);
// → "Name: Anonymous"
```

### XSS Protection

[](#xss-protection)

```
// Auto-escape disabled by default for backward compatibility
$formatter = new MessageFormatter();
echo $formatter->format('Hello :name', ['name' => 'alert(1)']);
// → "Hello alert(1)"

// Enable auto-escape for user-facing output
$safe = new MessageFormatter(autoEscape: true);
echo $safe->format('Hello :name', ['name' => 'alert(1)']);
// → "Hello &lt;script&gt;alert(1)&lt;/script&gt;"

// Custom sanitizer
$custom = new MessageFormatter(
    autoEscape: true,
    sanitizer: new MyCustomSanitizer(),
);
```

---

NumberFormatter
---------------

[](#numberformatter)

Locale-aware number formatting with graceful ext-intl fallback.

```
use MonkeysLegion\I18n\NumberFormatter;

$nf = new NumberFormatter();
```

### Decimal Formatting

[](#decimal-formatting)

```
echo $nf->decimal(1234567, 'en');       // → "1,234,567"
echo $nf->decimal(1234567, 'de');       // → "1.234.567" (with ext-intl)
echo $nf->decimal(3.14159, 'en', 2);    // → "3.14"
```

### Currency Formatting

[](#currency-formatting)

```
echo $nf->currency(42.50, 'USD', 'en');  // → "$42.50"
echo $nf->currency(42.50, 'EUR', 'de');  // → "42,50 €" (with ext-intl)
echo $nf->currency(42.50, 'GBP', 'en');  // → "£42.50"
echo $nf->currency(42.50, 'JPY', 'ja');  // → "¥42.50"
echo $nf->currency(42.50, 'BRL', 'pt');  // → "R$42.50"

// 32 built-in currency symbols
// USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, MXN, BRL,
// INR, KRW, RUB, TRY, SEK, NOK, DKK, PLN, CZK, HUF,
// RON, BGN, HRK, THB, PHP, MYR, IDR, VND, ZAR, EGP,
// NGN, KES, ARS, CLP, COP, PEN
```

### Compact Notation

[](#compact-notation)

```
echo $nf->compact(500);           // → "500"
echo $nf->compact(1_234);         // → "1.2K"
echo $nf->compact(1_500_000);     // → "1.5M"
echo $nf->compact(2_345_000_000); // → "2.3B"
echo $nf->compact(-1_234);        // → "-1.2K"
```

### Ordinals

[](#ordinals)

```
echo $nf->ordinal(1);   // → "1st"
echo $nf->ordinal(2);   // → "2nd"
echo $nf->ordinal(3);   // → "3rd"
echo $nf->ordinal(11);  // → "11th"
echo $nf->ordinal(21);  // → "21st"
echo $nf->ordinal(112); // → "112th"
```

### Percentage

[](#percentage)

```
echo $nf->percent(0.156, 'en', 1); // → "15.6%"
echo $nf->percent(0.5, 'en');      // → "50%"
```

### File Size

[](#file-size)

```
echo $nf->fileSize(0);         // → "0 B"
echo $nf->fileSize(1024);      // → "1.00 KB"
echo $nf->fileSize(1_572_864); // → "1.50 MB"
echo $nf->fileSize(5e9);       // → "4.66 GB"
```

### Spell Out

[](#spell-out)

```
echo $nf->spellOut(123, 'en');
// → "one hundred twenty-three" (requires ext-intl)

echo $nf->spellOut(42, 'es');
// → "cuarenta y dos" (requires ext-intl)
```

---

DateFormatter
-------------

[](#dateformatter)

Locale-aware date/time formatting with relative time support.

```
use MonkeysLegion\I18n\DateFormatter;

$df = new DateFormatter();
```

### Named Formats

[](#named-formats)

```
$date = new DateTimeImmutable('2026-01-15');

echo $df->format($date, 'short');    // → "1/15/26"
echo $df->format($date, 'medium');   // → "Jan 15, 2026"
echo $df->format($date, 'long');     // → "January 15, 2026"
echo $df->format($date, 'full');     // → "Thursday, January 15, 2026"
echo $df->format($date, 'iso');      // → "2026-01-15"
echo $df->format($date, 'time');     // → "12:00 AM"
echo $df->format($date, 'datetime'); // → "Jan 15, 2026 12:00 AM"

// Custom format
echo $df->format($date, 'Y/m/d');    // → "2026/01/15"

// From timestamp
echo $df->format(1705276800, 'medium'); // → "Jan 15, 2024"

// From string
echo $df->format('2026-01-15', 'long'); // → "January 15, 2026"

// With timezone
echo $df->format($date, 'datetime', 'en', 'America/New_York');
```

### Relative Time

[](#relative-time)

```
$now = new DateTimeImmutable('2026-01-15 12:00:00');

// Past — English
echo $df->relative('2026-01-15 11:58:00', 'en', $now);
// → "2 minutes ago"

echo $df->relative('2026-01-15 10:00:00', 'en', $now);
// → "2 hours ago"

echo $df->relative('2026-01-14 12:00:00', 'en', $now);
// → "1 day ago"

echo $df->relative('2025-11-15 12:00:00', 'en', $now);
// → "2 months ago"

// Past — Spanish
echo $df->relative('2026-01-15 10:00:00', 'es', $now);
// → "hace 2 horas"

echo $df->relative('2026-01-14 12:00:00', 'es', $now);
// → "hace 1 día"

// Future
echo $df->relative('2026-01-15 14:00:00', 'en', $now);
// → "in 2 hours"

// Just now (< 10 seconds)
echo $df->relative('2026-01-15 11:59:55', 'en', $now);
// → "just now"
```

### Diff for Humans

[](#diff-for-humans)

```
echo $df->diffForHumans(
    '2026-01-15 10:00:00',
    '2026-01-15 12:30:00',
    'en',
);
// → "2 hours ago"
```

### Day and Month Names

[](#day-and-month-names)

```
$date = new DateTimeImmutable('2026-01-15');

echo $df->dayOfWeek($date);             // → "Thursday"
echo $df->dayOfWeek($date, short: true); // → "Thu"

echo $df->monthName($date);             // → "January"
echo $df->monthName($date, short: true); // → "Jan"
```

### ISO 8601

[](#iso-8601)

```
echo $df->iso('2026-01-15 12:00:00');
// → "2026-01-15T12:00:00+00:00"
```

---

LocaleManager
-------------

[](#localemanager)

Manages locale detection, validation, and state.

```
use MonkeysLegion\I18n\LocaleManager;

$manager = new LocaleManager(
    defaultLocale: 'en',
    supportedLocales: ['en', 'es', 'fr', 'de', 'ja'],
    fallbackLocale: 'en',
);
```

### Detection Chain

[](#detection-chain)

```
use MonkeysLegion\I18n\Detectors\{
    UrlDetector,
    QueryDetector,
    SessionDetector,
    CookieDetector,
    HeaderDetector,
    SubdomainDetector,
};

// Priority order (first match wins)
$manager->addDetector(new UrlDetector(segment: 0));      // /es/products
$manager->addDetector(new QueryDetector(paramName: 'lang')); // ?lang=es
$manager->addDetector(new SessionDetector(key: 'locale'));
$manager->addDetector(new CookieDetector(cookieName: 'locale'));
$manager->addDetector(new HeaderDetector());              // Accept-Language
$manager->addDetector(new SubdomainDetector());           // es.example.com

$locale = $manager->detectLocale();
// Tries each detector in order, returns first supported match
```

### Supported Locales

[](#supported-locales)

```
$manager->isSupported('es');     // → true
$manager->isSupported('xx');     // → false

$manager->addSupportedLocale('pt');
$manager->isSupported('pt');     // → true

$manager->setLocale('es');       // ✅ Switches locale
$manager->setLocale('xx');       // ❌ throws InvalidArgumentException

// Asymmetric visibility (PHP 8.4)
echo $manager->defaultLocale;    // → "en" (read-only from outside)
echo $manager->fallbackLocale;   // → "en"
print_r($manager->supportedLocales); // → ["en", "es", "fr", "de", "ja"]
```

### Locale Parsing

[](#locale-parsing)

```
echo $manager->parseLocale('en-US');  // → "en"
echo $manager->parseLocale('en_US');  // → "en"
echo $manager->parseLocale('pt-BR');  // → "pt"
echo $manager->parseLocale('es');     // → "es"
```

---

LocaleInfo
----------

[](#localeinfo)

Static metadata for 50+ locales.

```
use MonkeysLegion\I18n\Support\LocaleInfo;
```

### Native Names

[](#native-names)

```
echo LocaleInfo::name('es');       // → "Spanish"
echo LocaleInfo::nativeName('es'); // → "Español"
echo LocaleInfo::nativeName('ja'); // → "日本語"
echo LocaleInfo::nativeName('ar'); // → "العربية"
echo LocaleInfo::nativeName('ru'); // → "Русский"
echo LocaleInfo::nativeName('hi'); // → "हिन्दी"
echo LocaleInfo::nativeName('ko'); // → "한국어"
echo LocaleInfo::nativeName('zh'); // → "中文"
```

### RTL Detection

[](#rtl-detection)

```
echo LocaleInfo::isRtl('ar'); // → true (Arabic)
echo LocaleInfo::isRtl('he'); // → true (Hebrew)
echo LocaleInfo::isRtl('fa'); // → true (Persian)
echo LocaleInfo::isRtl('ur'); // → true (Urdu)
echo LocaleInfo::isRtl('en'); // → false

echo LocaleInfo::direction('ar'); // → Direction::RTL
echo LocaleInfo::direction('en'); // → Direction::LTR
```

### Flag Emojis

[](#flag-emojis)

```
echo LocaleInfo::flag('en'); // → 🇺🇸
echo LocaleInfo::flag('es'); // → 🇪🇸
echo LocaleInfo::flag('fr'); // → 🇫🇷
echo LocaleInfo::flag('jp'); // → 🇯🇵
echo LocaleInfo::flag('br'); // → 🇧🇷
```

### Script &amp; Knowledge

[](#script--knowledge)

```
echo LocaleInfo::script('en'); // → "Latn"
echo LocaleInfo::script('ar'); // → "Arab"
echo LocaleInfo::script('ru'); // → "Cyrl"
echo LocaleInfo::script('ja'); // → "Jpan"
echo LocaleInfo::script('ko'); // → "Kore"

echo LocaleInfo::isKnown('en'); // → true
echo LocaleInfo::isKnown('xx'); // → false

$allCodes = LocaleInfo::allCodes(); // 50+ locale codes
```

---

Loaders
-------

[](#loaders)

### FileLoader

[](#fileloader)

Loads from JSON and PHP files with security hardening.

```
use MonkeysLegion\I18n\Loaders\FileLoader;

$loader = new FileLoader('/path/to/lang');

// Loads from /path/to/lang/{locale}/{group}.json
$messages = $loader->load('en', 'messages');

// Add namespace path
$loader->addNamespace('billing', '/path/to/billing/lang');

// Security features:
// ✅ Path traversal prevention (realpath validation)
// ✅ Null byte injection prevention
// ✅ Max file size limit (2MB)
// ✅ JSON_THROW_ON_ERROR on all json_decode
// ✅ Symlink resolution and validation
```

### DatabaseLoader

[](#databaseloader)

Load translations from any SQL database — **MySQL, MariaDB, PostgreSQL, and SQLite**.

#### Install the Schema

[](#install-the-schema)

Ready-to-use SQL files are included in `schema/`:

```
# MySQL / MariaDB
mysql -u root -p your_database load('en', 'messages');

// Stack with FileLoader (DB translations override file translations)
$translator = new Translator('en', 'en');
$translator->addLoader(new FileLoader('/path/to/lang'));  // Base
$translator->addLoader($loader);                           // Override

// Or use TranslatorFactory (recommended)
$translator = TranslatorFactory::create([
    'path' => '/path/to/lang',
    'pdo'  => $pdo,           // Enables DatabaseLoader automatically
]);

// Security features:
// ✅ Table name validated against regex pattern
// ✅ All queries use parameterized statements
// ✅ Cross-database UPSERT (ON CONFLICT / ON DUPLICATE KEY)
// ✅ Readonly PDO property
```

### CacheLoader

[](#cacheloader)

Decorator that caches translations from any other loader.

```
use MonkeysLegion\I18n\Loaders\CacheLoader;

$cached = new CacheLoader(
    loader: $fileLoader,
    cache: $psr16Cache,
    ttl: 3600,
    prefix: 'i18n',
);

// Features:
// ✅ TTL jitter (±10%) to prevent thundering herd
// ✅ Selective cache invalidation
// ✅ Flush all cached translations

$cached->forget('en', 'messages');     // Clear specific group
$cached->flush();                       // Clear all
```

### CompiledLoader

[](#compiledloader)

Opcache-friendly compiled PHP files — 10-50x faster than JSON decode.

```
use MonkeysLegion\I18n\Loaders\CompiledLoader;

$compiled = new CompiledLoader(
    sourceLoader: $fileLoader,
    compilePath: '/var/cache/i18n',
);

// Compile all translations for a locale
$compiled->compile('en', '/path/to/lang');
// → Creates /var/cache/i18n/en.compiled.php

// Check if compiled cache is fresh
if (!$compiled->isFresh('en', '/path/to/lang')) {
    $compiled->compile('en', '/path/to/lang');
}

// Invalidate
$compiled->invalidate('en');

// Atomic writes via temp file + rename
// Auto opcache invalidation
```

### MlcLoader

[](#mlcloader)

Zero-dependency alternative to YAML — uses a simple key=value format.

```
use MonkeysLegion\I18n\Loaders\MlcLoader;

$loader = new MlcLoader('/path/to/lang');
$messages = $loader->load('en', 'messages');
// Loads from /path/to/lang/en/messages.mlc
```

**MLC file format (flat — one group per file):**

```
# resources/lang/en/messages.mlc

# Comments start with # or ;
welcome = Welcome!
greeting = Hello, :name!
farewell = "Goodbye, :NAME!"

# Dot notation creates nested arrays
nested.key = Nested value
nested.deep.value = Deep nested value

# Escape sequences in double-quoted values
multiline = "Line one\nLine two"
```

**MLC file format (sectioned — multiple groups per file):**

```
# resources/lang/es.mlc

[messages]
welcome = ¡Bienvenido!
greeting = ¡Hola, :name!

[validation]
required = El campo :field es obligatorio.
email = Ingrese un correo electrónico válido.
```

**Why MLC over YAML?**

- Zero external dependencies (no `symfony/yaml`)
- Simpler syntax, less error-prone
- Translators don't need to learn YAML indentation rules
- Same security hardening as FileLoader

---

Middleware
----------

[](#middleware)

### LocaleMiddleware

[](#localemiddleware)

Auto-detect and set locale for each request.

```
use MonkeysLegion\I18n\Middleware\LocaleMiddleware;

$middleware = new LocaleMiddleware(
    manager: $localeManager,
    translator: $translator,
    setSession: true,
    setCookie: true,
    cookieTtl: 31536000, // 1 year
);

// Security features:
// ✅ SameSite=Lax on cookies
// ✅ HttpOnly flag
// ✅ Secure flag when HTTPS
// ✅ headers_sent() guard
```

### LocaleUrlMiddleware

[](#localeurlmiddleware)

Extract locale from URL path segment.

```
use MonkeysLegion\I18n\Middleware\LocaleUrlMiddleware;

// /es/products → sets locale to "es"
$middleware = new LocaleUrlMiddleware($manager, $translator, segment: 0);
```

### LocaleRedirectMiddleware

[](#localeredirectmiddleware)

Auto-redirect to localized URL if locale prefix is missing.

```
use MonkeysLegion\I18n\Middleware\LocaleRedirectMiddleware;

// /products → 302 → /en/products
$middleware = new LocaleRedirectMiddleware($manager, segment: 0);

// No exit() call — returns redirect response array
// ✅ headers_sent() guard
```

---

Enums
-----

[](#enums)

### PluralCategory

[](#pluralcategory)

```
use MonkeysLegion\I18n\Enum\PluralCategory;

// ICU plural categories
PluralCategory::Zero;   // "zero"
PluralCategory::One;    // "one"
PluralCategory::Two;    // "two"
PluralCategory::Few;    // "few"
PluralCategory::Many;   // "many"
PluralCategory::Other;  // "other"

PluralCategory::Other->isDefault(); // → true
PluralCategory::ordered();          // Ordered list

// Get plural category for a count in a locale
$pluralizer = new Pluralizer();
$cat = $pluralizer->getCategoryForCount(5, 'en');
// → PluralCategory::Other

$cat = $pluralizer->getCategoryForCount(2, 'ar');
// → PluralCategory::Two
```

### Direction

[](#direction)

```
use MonkeysLegion\I18n\Enum\Direction;

Direction::LTR; // "ltr"
Direction::RTL; // "rtl"

Direction::fromLocale('en'); // → Direction::LTR
Direction::fromLocale('ar'); // → Direction::RTL
Direction::fromLocale('he'); // → Direction::RTL
Direction::fromLocale('fa'); // → Direction::RTL

Direction::RTL->cssAttribute(); // → 'dir="rtl"'
```

---

Attributes
----------

[](#attributes)

### \#\[Translatable\]

[](#translatable)

Mark entity properties for automatic translation:

```
use MonkeysLegion\I18n\Attribute\Translatable;

class Product
{
    #[Translatable(group: 'products', keyPrefix: 'title')]
    public string $title;

    #[Translatable(group: 'products', keyPrefix: 'description', fallbackToValue: true)]
    public string $description;
}
```

### \#\[Locale\]

[](#locale)

Auto-inject the detected locale into controller parameters:

```
use MonkeysLegion\I18n\Attribute\Locale;

class ProductController
{
    public function index(#[Locale] string $locale): Response
    {
        // $locale is auto-populated from the detected locale
    }
}
```

---

Events
------

[](#events)

```
use MonkeysLegion\I18n\Event\LocaleChangedEvent;

// Immutable event (readonly class)
$translator->setEventDispatcher(function (LocaleChangedEvent $event): void {
    echo $event->previousLocale; // → "en"
    echo $event->newLocale;      // → "es"

    // Update user preferences, reconfigure formatters, etc.
});
```

---

Template Directives
-------------------

[](#template-directives)

For use with MonkeysLegion-Template engine:

```
use MonkeysLegion\I18n\Template\I18nDirectives;

$directives = new I18nDirectives($translator);

// Register all directives
foreach ($directives->getDirectives() as $name => $handler) {
    $engine->directive($name, $handler);
}
```

In templates:

```
{{-- Translation --}}
@lang('welcome.message')
@lang('welcome.user', ['name' => $user->name])

{{-- Pluralization --}}
@choice('messages.count', $count)

{{-- Current locale --}}
@locale

{{-- Formatting --}}
@date($order->created_at, 'long')
@currency($product->price, 'USD')
@number($total, 2)
```

---

Helper Functions
----------------

[](#helper-functions)

Global helper functions for convenience:

```
// Translation
echo trans('messages.welcome');
echo trans('messages.greeting', ['name' => 'Yorch']);

// Shorthand alias (__)
echo __('messages.welcome');
echo __('messages.greeting', ['name' => 'Yorch']);

// Pluralization
echo trans_choice('messages.items', 5);
echo trans_choice('messages.items', 1, ['color' => 'red']);

// Get/set locale
echo lang();        // → "en"
lang('es');          // Sets locale to "es"
echo lang();        // → "es"
```

---

TranslatorFactory
-----------------

[](#translatorfactory)

One-line creation with all features:

```
use MonkeysLegion\I18n\TranslatorFactory;

// Basic
$translator = TranslatorFactory::create([
    'locale'   => 'en',
    'fallback' => 'en',
    'path'     => '/path/to/lang',
]);

// With caching
$translator = TranslatorFactory::create([
    'locale'    => 'en',
    'path'      => '/path/to/lang',
    'cache'     => $psr16Cache,
    'cache_ttl' => 3600,
]);

// With compiled loader (production)
$translator = TranslatorFactory::create([
    'locale'        => 'en',
    'path'          => '/path/to/lang',
    'compiled_path' => '/var/cache/i18n',
]);

// With database
$translator = TranslatorFactory::create([
    'locale'    => 'en',
    'path'      => '/path/to/lang',
    'pdo'       => $pdo,
    'cache'     => $cache,
]);

// With namespaces
$translator = TranslatorFactory::create([
    'locale'     => 'en',
    'path'       => '/path/to/lang',
    'namespaces' => [
        'billing' => '/path/to/billing/lang',
        'email'   => '/path/to/email/lang',
    ],
]);

// Full system (translator + manager)
['translator' => $t, 'manager' => $m] = TranslatorFactory::createSystem([
    'default'   => 'en',
    'supported' => ['en', 'es', 'fr'],
    'path'      => '/path/to/lang',
    'detectors' => ['url', 'session', 'cookie', 'header'],
]);

// Number & Date formatters
$nf = TranslatorFactory::createNumberFormatter();
$df = TranslatorFactory::createDateFormatter();
```

---

Translation Management
----------------------

[](#translation-management)

Full CRUD management for file and database translations:

```
use MonkeysLegion\I18n\Management\TranslationManager;

$manager = new TranslationManager($pdo, $translator, '/path/to/lang');

// CRUD
$manager->set('en', 'messages', 'welcome', 'Hello!');
echo $manager->get('en', 'messages', 'welcome'); // → "Hello!"
$manager->delete('en', 'messages', 'welcome');

// Import/Export
$manager->importFromFile('en', 'messages');
$manager->exportToFile('en', 'messages', 'json');
$manager->importArray('en', 'messages', ['key' => 'value'], overwrite: true);

// Sync
$manager->sync('en', 'messages', 'file_to_db');
$manager->sync('en', 'messages', 'db_to_file');

// Merged (DB overrides file)
$all = $manager->getAllMerged('en', 'messages');

// Search
$results = $manager->search('welcome', locale: 'en');

// Statistics
$stats = $manager->getStats();
// ['total' => 150, 'by_locale' => [...], 'by_group' => [...], 'by_source' => [...]]

// Batch update
$manager->batchUpdate([
    ['locale' => 'en', 'group' => 'messages', 'key' => 'welcome', 'value' => 'Hi!'],
    ['locale' => 'en', 'group' => 'messages', 'key' => 'goodbye', 'value' => 'Bye!'],
]);

// Find missing (file keys not in DB)
$missing = $manager->findMissing('en', 'messages');
```

---

CLI Commands
------------

[](#cli-commands)

```
use MonkeysLegion\I18n\Console\TranslationCommand;

$cmd = new TranslationCommand($translator, '/path/to/lang');

// Extract translation keys from source code
$cmd->extract('/path/to/src', '/path/to/output.json');
// Scans for trans(), __(), @lang(), @choice() calls

// Find missing translations
$cmd->missing('es');
// ✗ Found 5 missing translations:
//   - messages.new_feature
//   - validation.custom_rule

// Compare two locales
$cmd->compare('en', 'es');
// Missing in es (3):
//   - messages.new_key
//   - validation.rule

// Export translations
$cmd->export('en', 'json', '/path/to/export.json');
$cmd->export('en', 'csv', '/path/to/export.csv');
$cmd->export('en', 'php', '/path/to/export.php');
```

---

Security
--------

[](#security)

### Path Traversal Prevention

[](#path-traversal-prevention)

```
// FileLoader validates all path segments
$loader->load('../etc', 'passwd');     // ❌ LoaderException
$loader->load("en\0", 'messages');     // ❌ LoaderException
$loader->load('en/../../', 'msg');     // ❌ LoaderException
```

### Locale Injection Prevention

[](#locale-injection-prevention)

```
// Translator validates locale format: /^[a-z]{2,3}(_[A-Z]{2})?$/
new Translator('../etc/passwd');        // ❌ InvalidLocaleException
new Translator("en\0");                 // ❌ InvalidLocaleException
$translator->locale = '';       // ❌ InvalidLocaleException
```

### SQL Injection Prevention

[](#sql-injection-prevention)

```
// DatabaseLoader validates table names
new DatabaseLoader($pdo, 'DROP TABLE users; --'); // ❌ InvalidArgumentException

// All queries use parameterized statements
```

### XSS Protection

[](#xss-protection-1)

```
// Enable auto-escaping in MessageFormatter
$formatter = new MessageFormatter(autoEscape: true);
// All :param replacements are HTML-escaped via htmlspecialchars()
```

### Cookie Security

[](#cookie-security)

```
// LocaleMiddleware sets secure cookie flags
// SameSite=Lax, HttpOnly, Secure (when HTTPS)
```

---

Performance
-----------

[](#performance)

### Compiled Loader (Production)

[](#compiled-loader-production)

```
// 10-50x faster than JSON decode per request
$compiled = new CompiledLoader($fileLoader, '/var/cache/i18n');
$compiled->compile('en', '/path/to/lang');

// Uses PHP's opcache for near-zero overhead
// Atomic writes (temp file + rename)
// Auto mtime-based freshness checks
```

### Cache with Jitter

[](#cache-with-jitter)

```
// TTL jitter prevents thundering herd (cache stampede)
// ±10% variation: TTL 3600 → random 3240-3960
$cached = new CacheLoader($loader, $cache, ttl: 3600);
```

### Warm-Up

[](#warm-up-1)

```
// Pre-load all groups at boot time
$translator->warmUp('en', ['messages', 'validation', 'auth', 'errors']);
```

### Const Array Pluralizer

[](#const-array-pluralizer)

```
// Locale-to-rule mapping is a const array (PHP 8.4)
// Zero overhead — compiled into opcache
private const array LOCALE_RULES = [
    'pl' => 'polish',
    'ru' => 'russian',
    // ...
];
```

---

Migration from v1
-----------------

[](#migration-from-v1)

### Namespace Changes

[](#namespace-changes)

```
- use MonkeysLegion\I18n\Contracts\LoaderInterface;
+ use MonkeysLegion\I18n\Contract\LoaderInterface;
```

> **Note**: Backward-compatible aliases exist at `src/Contracts/aliases.php`.

### Property Hooks

[](#property-hooks)

```
- $translator->getLocale();
+ $translator->locale;         // Read via property
+ $translator->getLocale();    // Still works (BC)

- $translator->setLocale('es');
+ $translator->locale = 'es';  // Write via property hook
+ $translator->setLocale('es'); // Still works (BC)
```

### Locale Validation

[](#locale-validation)

```
// v1: No validation
$translator = new Translator('../etc/passwd');

// v2: Strict validation
+ $translator = new Translator('../etc/passwd');
+ // ❌ InvalidLocaleException
```

### Middleware Split

[](#middleware-split)

```
// v1: All middleware in single file
- // src/Middleware/LocaleMiddleware.php contained 3 classes

// v2: One class per file
+ src/Middleware/LocaleMiddleware.php
+ src/Middleware/LocaleUrlMiddleware.php
+ src/Middleware/LocaleRedirectMiddleware.php
```

---

Testing
-------

[](#testing)

```
# Run all tests
composer test

# Run with testdox output
vendor/bin/phpunit --testdox

# Run specific test file
vendor/bin/phpunit tests/Unit/I18nV2Test.php

# Run specific test
vendor/bin/phpunit --filter="translator_translates_basic_key"
```

### Test Coverage

[](#test-coverage)

- **139 tests**, **250+ assertions**
- Covers: Translator, Pluralizer, MessageFormatter, NumberFormatter, DateFormatter, LocaleManager, LocaleInfo, FileLoader, MlcLoader, CompiledLoader, Enums, Attributes, Events, Factory, CLI

---

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

**Built with ❤️ by [MonkeysCloud](https://monkeys.cloud)**

###  Health Score

48

—

FairBetter than 93% of packages

Maintenance85

Actively maintained with recent releases

Popularity22

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 88.2% 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 ~66 days

Recently: every ~33 days

Total

6

Last Release

77d ago

Major Versions

1.0.0 → 2.0.x-dev2025-12-07

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/2913369?v=4)[Jorge Peraza](/maintainers/yorchperaza)[@yorchperaza](https://github.com/yorchperaza)

---

Top Contributors

[![yorchperaza](https://avatars.githubusercontent.com/u/2913369?v=4)](https://github.com/yorchperaza "yorchperaza (15 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (2 commits)")

---

Tags

localizationinternationalizationi18ntranslationphp84monkeyslegion

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/monkeyscloud-monkeyslegion-i18n/health.svg)

```
[![Health](https://phpackages.com/badges/monkeyscloud-monkeyslegion-i18n/health.svg)](https://phpackages.com/packages/monkeyscloud-monkeyslegion-i18n)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.9k19.5M1.8k](/packages/cakephp-cakephp)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[moonshine/moonshine

Laravel administration panel

1.3k253.1k81](/packages/moonshine-moonshine)[typo3/cms-core

TYPO3 CMS Core

3713.2M5.1k](/packages/typo3-cms-core)[windwalker/framework

The next generation PHP framework.

25740.3k1](/packages/windwalker-framework)[flarum/core

Delightfully simple forum software.

201.4M2.3k](/packages/flarum-core)

PHPackages © 2026

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