twig/intl-extra
Twig Intl Extension adds locale-aware filters to Twig: look up country, currency, language, locale, and timezone names, get country timezones, and format currencies, numbers, dates, times, and lists using PHP's Intl extension.
Architecture fit
twig/intl-extra is an official Twig extra, not a Laravel package. It supplies a single IntlExtension that registers ICU-backed filters (format_date, format_time, format_datetime, format_number, format_currency, plus name lookups such as country_name, currency_name, currency_symbol, language_name, locale_name, timezone_name, and country_timezones). Architectural fit is therefore conditional on one fact: is Twig in the rendering path?
Illuminate\Support\Number (currency, format, percentage, file sizes) and Carbon's localized date formatting, and __() / trans_choice() cover translation and pluralization. The marginal capability this package adds over native Laravel is mostly the ISO name-lookup filters and ICU pattern control inside Twig templates.Verdict for a TPM: treat this as a capability for an existing Twig surface, not as an i18n strategy for Laravel. If the organization is Blade-only, the correct decision is usually "no" to the package and "yes" to a thin IntlFormatter service plus Blade directives using NumberFormatter / IntlDateFormatter directly.
Integration feasibility Two viable paths, with materially different costs:
composer require twig/twig twig/intl-extra:^3.30, instantiate or obtain the Twig environment, addExtension(new \Twig\Extra\Intl\IntlExtension()), bind the locale from app()->getLocale() via a Twig global or function, and configure a filesystem template cache. Effort is roughly 2–5 developer-days for one surface plus locale tests. Main dependency risk is the Twig-to-Laravel bridge layer, which is community-maintained and tends to lag Laravel majors; that bridge, not intl-extra, is the real integration liability.intl helpers.Non-negotiable prerequisites: PHP 8.1+, the intl extension enabled (not guaranteed in slim php:*-cli images unless explicitly installed), Twig 3.x version alignment, and a compiled-template cache. New in this review: pin ^3.30, which carries both the CVE-2026-46629 fix (from v3.26.0) and the v3.30.0 formatter-prototype correctness fix.
Technical risk
App::setLocale(). Without a single source of truth, templates will format with inconsistent locales across requests and jobs.v3.30.0 fixes IntlExtension inheriting values that ICU derived from a date formatter prototype, and now compares the prototype pattern with the derived pattern once per pattern. Practically: where templates pass custom ICU date patterns rather than named styles (short/medium/long), earlier 3.x releases could produce subtly wrong output for locales whose derived pattern diverges from the prototype. This is a correctness bug, not a security one, and it argues for upgrading the floor to ^3.30 rather than staying on ^3.26. It also strengthens the case for per-pattern snapshot tests — the regression class is silent wrong output, not an exception.intl behavior depends on the ICU library bundled with the PHP build. Dev laptop, CI image, and production container can produce different output (notably for currency spacing, timezone names, and date patterns). Pin the PHP base image and test the locale matrix in CI.v3.26.0 fixed CVE-2026-46629 (unbounded memoization of IntlDateFormatter / NumberFormatter). This matters specifically for long-lived workers — Laravel Octane, Horizon, queue workers rendering templates — where unbounded formatter caches become memory growth. Constrain to ^3.30 (includes v3.26.0) and run composer audit in CI.v3.30.0 adds a per-pattern comparison in the prototype path — negligible, but it does mean pattern keys should be low-cardinality, consistent with the existing caching guidance.Key questions
Number/Carbon/intl in Blade?format_date/format_datetime, and if so, are their outputs snapshot-tested across the locale matrix? (Directly scopes exposure to the v3.30.0 prototype-derivation fix.)intl + ICU version pinned across dev/CI/prod?Stack fit Target only Twig-rendered surfaces: transactional email, PDF/invoice rendering, exported reports, and any admin templates already on Twig. Do not target Blade views, Livewire components, or Inertia/Vue/React front ends — the extension cannot reach them. If the estate already runs a Twig bridge for Symfony interoperability, this is a near-zero-friction addition.
Migration path
php -m | grep intl, PHP 8.1+, Twig 3.x, and a compiled template cache in every environment.composer require twig/intl-extra:^3.30, then register IntlExtension on the Twig environment in a service provider. Do not floor at ^3.26 — that version line predates the v3.30.0 prototype-derivation fix.app()->getLocale() and app()->getFallbackLocale() to templates; forbid hardcoded locale strings in templates by convention and lint rule.format_currency, format_datetime, and one name lookup. Include at least one custom-pattern date if the surface uses one, so the v3.30.0 fix is exercised and locked in by test.en_US, de_DE, fr_FR, ja_JP, plus one unsupported locale) asserting exact output snapshots. Where custom patterns exist, snapshot the derived output per pattern, not just the input pattern string.Compatibility
PHP 8.1+, Twig 3.x, ext-intl, Laravel 10/11/12, twig/intl-extra ^3.30. Verify the chosen Twig bridge against the current Laravel major before committing; if the bridge is unmaintained, that is a stop condition. Confirm the Twig cache driver is configured (filesystem or Redis), since uncached compilation in production is a latency and disk risk.
Sequencing
^3.30), extension registration, locale injection, cache config, ICU/PHP image pinning.Maintenance
Vendor maintenance is low and credible: MIT-licensed, maintained under the Twig organization, with a steady patch cadence (v3.29.0 → v3.30.0 is a routine minor with one bug fix and one micro-optimization, no breaking changes, no deprecations). The maintenance burden you actually own is the integration layer — the Twig bridge, locale synchronization, and the locale regression suite. Expect to touch this at every Laravel major and every Twig major. Pin ^3.30 or later, run composer audit and Dependabot/Renovate in CI, and define a patch SLA (e.g., 7 days for security releases, next scheduled release for correctness fixes such as the prototype-derivation bug in v3.30.0). Because v3.30.0 changes only internal formatter-prototype handling, it is a low-risk in-place upgrade — schedule it as a normal dependency bump, but re-run locale snapshot tests since output for custom patterns may legitimately change.
Support
There is no Laravel-specific documentation and little Laravel-specific community answer surface; support relies on Twig and PHP intl docs. A Blade-only team will need ramp-up on Twig syntax ({% %} vs @), escaping semantics, and error types (Twig\Error\LoaderError, SyntaxError, RuntimeError). Publish an internal one-pager mapping each supported filter to its intended use and the approved locale argument convention so on-call engineers can diagnose template failures without Twig expertise. Add a note that silent date-format differences after an intl-extra upgrade are an expected symptom class, resolved by checking the changelog and re-baselining snapshots.
Scaling
The extension is stateless and adds no concurrency concerns; compiled-template caching keeps render overhead low. Two scaling caveats: (1) high-cardinality locale/pattern inputs to IntlDateFormatter/NumberFormatter can grow internal caches — keep patterns static and locale sets small, and validate memory ceilings on Octane/Horizon workers; the v3.30.0 per-pattern comparison reinforces keeping the pattern set constant and finite; (2) formatting inside large loops is CPU-bound, so pre-format in PHP for list/report rendering. ICU data adds tens of MB to container images — account for that in image size budgets.
Failure modes
| Failure | Impact | Mitigation |
|---|---|---|
ext-intl absent in an image |
All filters fatal; emails/PDFs break | Assert extension in CI and container healthcheck |
| Locale unset or unsupported | Silent wrong output or exception | Central locale injection; explicit fallback locale; matrix tests |
| ICU version differs dev vs prod | Inconsistent currency/date output | Pin PHP base image; snapshot tests in CI |
Pre-3.30 release retained with custom date patterns |
Subtly wrong dates from inherited prototype-derived values, no exception raised | Floor at ^3.30; snapshot custom-pattern output per locale |
Vulnerable intl-extra retained |
Memory growth in long-lived workers (CVE-2026-46629) | Constrain ^3.30, automated dependency updates |
| Invalid ICU pattern in template | 500 on render | Validate patterns in code review; catch and fall back |
| Twig major / bridge drift after Laravel upgrade | Template rendering failure at deploy | Pin versions; include Twig render smoke test in release pipeline |
| Cache misconfiguration | Slow renders, disk pressure | Configure and monitor Twig cache; clear on deploy |
Ramp-up
A developer already fluent in Twig reaches productivity in 1–2 days. A Blade-only team introducing Twig spends 1–2 sprints on the bridge, conventions, and locale testing before the first production template — which is precisely why the recommendation is to adopt this package only where Twig already exists, and otherwise to invest the equivalent effort in a small native intl formatting service that the whole team already knows how to operate.
How can I help you explore Laravel packages today?