PHP code example of monkeyscloud / monkeyslegion-i18n

1. Go to this page and download the library: Download monkeyscloud/monkeyslegion-i18n library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

monkeyscloud / monkeyslegion-i18n example snippets


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"

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"

// 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"

// 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"

// 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

$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"

$translator->setTrackMissing(true);

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

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

$translator->clearMissingTranslations();

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

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

$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

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

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

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' => '[email protected]']);
// → "Email: [email protected]"

// 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"

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

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

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

use MonkeysLegion\I18n\NumberFormatter;

$nf = new NumberFormatter();

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"

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

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"

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"

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

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"

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

use MonkeysLegion\I18n\DateFormatter;

$df = new DateFormatter();

$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');

$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"

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

$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"

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

use MonkeysLegion\I18n\LocaleManager;

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

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

$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"]

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

use MonkeysLegion\I18n\Support\LocaleInfo;

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'); // → "中文"

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

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

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

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

use MonkeysLegion\I18n\Loaders\DatabaseLoader;

// Works with any PDO connection — auto-detects driver
$loader = new DatabaseLoader($pdo, 'translations');
$messages = $loader->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

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

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

use MonkeysLegion\I18n\Loaders\MlcLoader;

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

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

use MonkeysLegion\I18n\Middleware\LocaleUrlMiddleware;

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

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

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

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"'

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;
}

use MonkeysLegion\I18n\Attribute\Locale;

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

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.
});

use MonkeysLegion\I18n\Template\I18nDirectives;

$directives = new I18nDirectives($translator);

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

// 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"

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();

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');

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');

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

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

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

// All queries use parameterized statements

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

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

// 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

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

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

// 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',
    // ...
];
diff
// 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