preflow / i18n
Preflow i18n — translations, locale detection, pluralization
Requires
- php: >=8.4
- preflow/core: ^0.1 || @dev
Requires (Dev)
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v0.33.0
- v0.32.0
- v0.31.0
- v0.30.0
- v0.29.0
- v0.28.0
- v0.27.1
- v0.27.0
- v0.26.0
- v0.25.1
- v0.25.0
- v0.24.1
- v0.24.0
- v0.23.0
- v0.22.1
- v0.22.0
- v0.21.0
- v0.20.0
- v0.19.2
- v0.19.1
- v0.19.0
- v0.18.1
- v0.18.0
- v0.17.2
- v0.17.1
- v0.17.0
- v0.16.2
- v0.16.1
- v0.16.0
- v0.15.0
- v0.14.2
- v0.14.1
- v0.14.0
- v0.13.1
- v0.13.0
- v0.12.0
- v0.11.4
- v0.11.3
- v0.11.2
- v0.11.1
- v0.11.0
- v0.10.4
- v0.10.3
- v0.10.2
- v0.10.1
- v0.10.0
- v0.9.1
- v0.9.0
- v0.8.0
- v0.7.0
- v0.6.0
- v0.5.0
- v0.4.1
- v0.4.0
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.1
- v0.1.0
This package is auto-updated.
Last update: 2026-10-25 14:12:42 UTC
README
Translations, locale detection, and pluralization for Preflow applications.
Installation
composer require preflow/i18n
What it does
- Loads PHP translation files from
lang/{locale}/{group}.php - Resolves dot-notation keys with
:paramreplacement and locale fallback - ICU-style pluralization with exact, range, and simple two-form rules
- PSR-15 middleware for locale detection (URL prefix → cookie → Accept-Language)
- Twig functions for global and component-scoped translations
Configuration
config/i18n.php:
return [ 'default' => 'en', 'available' => ['en', 'de'], 'fallback' => 'en', 'url_strategy' => 'prefix', // 'prefix' | 'none' ];
Translation files
lang/en/blog.php:
return [ 'title' => 'My Blog', 'published' => 'Published on :date', 'post_count' => '{0} No posts|{1} One post|[2,*] :count posts', ];
API
Translator
$translator = new Translator( langPath: __DIR__ . '/lang', locale: 'en', fallbackLocale: 'en', ); $translator->get('blog.title'); // "My Blog" $translator->get('blog.published', ['date' => '2026-01-01']); // "Published on 2026-01-01" $translator->choice('blog.post_count', 5); // "5 posts" $translator->setLocale('de'); $translator->getLocale(); // "de" $translator->get('blog.title', [], 'fr'); // in another locale, the current one unchanged
A package ships its own strings for a group and registers them with
$translator->addDefaults('members', __DIR__ . '/../lang') (files lang/{locale}/members.php).
The site's lang/{locale}/members.php is laid over them key by key, so a site overrides single
phrases without copying the whole file.
PluralResolver
Supports three formats:
{0} No posts|{1} One post|[2,*] :count posts // exact + range
[2,10] Some|[11,*] Many // range only
One post|:count posts // simple two-form (1 = first, else second)
LocaleMiddleware
PSR-15 middleware. Detects locale in priority order: URL prefix → cookie → Accept-Language header → configured default. Strips the locale prefix from the request path. A locale cookie is set only when enabled ('cookie' => true in config/i18n.php; the default is off for the prefix strategy, on for none) and only when the locale changed.
Template helpers: t()/tc() (parameters are HTML-escaped), current_locale(), available_locales() and locale_url(path, locale = current), which returns /about for the default locale and /de/about otherwise.
$middleware = new LocaleMiddleware( translator: $translator, availableLocales: ['en', 'de'], defaultLocale: 'en', urlStrategy: 'prefix', // request to /de/blog strips /de, sets locale to "de" );
TranslationExtension (Twig)
Register with your Twig environment:
$twig->addExtension(new TranslationExtension($translator));
{# Simple key #} {{ t('blog.title') }} {# With parameters #} {{ t('blog.published', { date: '2026-01-01' }) }} {# Pluralization — third arg is the count #} {{ t('blog.post_count', { count: 5 }, 5) }} {# Component-scoped — resolves "my-component.label" #} {{ tc('label', 'MyComponent') }}
tc() converts the component name from PascalCase to kebab-case and uses it as the translation group.