Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Intl Format Laravel Package

senseexception/intl-format

Locale-aware formatting for Laravel/PHP apps using PHP’s Intl extension. senseexception/intl-format simplifies displaying numbers, currencies, percentages, dates, and times with consistent localized output and minimal configuration.

Deep Wiki
Context7

Getting Started

1. Install and verify the environment

composer require senseexception/intl-format
php -m | grep intl   # must output "intl"

The package is a thin wrapper over PHP's intl extension (NumberFormatter, IntlDateFormatter, MessageFormatter). If ext-intl is not compiled in, you get a hard fatal error the first time you format anything — check this before wiring it into a service container.

2. Where to look first

  • The README.md usage block — it is short, so read it once and then read the tests.
  • The src/ directory: identify the main message formatter class and any value-formatter classes it delegates to (number, currency, date).
  • The test suite (tests/) — with a package this small, the tests are the most reliable specification of the placeholder syntax and constructor signatures.

3. First use case: localized values inside a human message

The canonical day-one use is a message that mixes prose with a locale-aware number or date, which sprintf cannot do correctly across locales:

<?php
require 'vendor/autoload.php';

use SenseException\IntlFormat\MessageFormatter;

$formatter = new MessageFormatter('de_DE');

echo $formatter->format(
    'Ihr Gesamtbetrag beträgt {0, number, currency}.',
    [1234.5]
);
// Ihr Gesamtbetrag beträgt 1.234,50 €.

Note the locale does the work: the same call with en_US produces 1,234.50 $. Do not hand-roll number_format() in templates — that is exactly what this package replaces.

Implementation Patterns

Wrap the formatter as a service, not a static helper

Inject the formatter (or a factory that produces one per locale) so the locale is resolved per request rather than hard-coded:

final class PriceMessage
{
    public function __construct(private MessageFormatter $formatter) {}

    public function render(float $amount): string
    {
        return $this->formatter->format(
            'Total: {0, number, currency}',
            [$amount]
        );
    }
}

Resolve locale from user context, cache per locale

Create one formatter per locale and reuse it; constructing NumberFormatter/IntlDateFormatter instances is comparatively expensive.

$formatters = [];
$formatter = $formatters[$locale] ??= new MessageFormatter($locale);

Pull the locale from the authenticated user's preference, falling back to the request's accepted languages, then to a configured default.

Symfony integration

Register the formatter as a service and, if the package exposes an IntlFormatterInterface implementation, tag it so the translator uses it:

services:
    SenseException\IntlFormat\MessageFormatter:
        arguments: ['%kernel.default_locale%']

Compose with translation, not against it

Order matters. Either translate the message template first and then format the placeholders, or keep message templates locale-neutral and let the formatter own the value rendering. Do not run sprintf and ICU formatting on the same string — pick one layer.

Custom/domain formatters

If the package allows registering named value formatters (check the extension points in src/), add domain-specific ones for things like file sizes or durations and keep the call sites declarative:

$formatter->format('Upload: {0, fileSize}', [$bytes]);

Testing pattern

Pin the locale explicitly in each test and use a data provider for the locales you actually ship:

/**
 * @dataProvider locales
 */
public function testCurrency(string $locale, string $expected): void
{
    $f = new MessageFormatter($locale);
    self::assertSame($expected, $f->format('{0, number, currency}', [1234.5]));
}

Gotchas and Tips

ICU quoting will bite you. In ICU message syntax an apostrophe starts a literal-quoted section. Text like L'utilisateur can silently swallow placeholders unless you double it: L''utilisateur. When in doubt, quote the whole prose segment or escape every literal apostrophe.

Percent values are ratios, not percentages. Formatting 0.15 with percent yields 15%. If you already multiplied by 100, you will render 1500%. Decide once, in the calling code, whether inputs are fractions or percentages.

Currency needs both a locale and a currency code. The locale controls placement and symbol; the currency determines the code. Passing a number without setting the currency can fall back to the locale's default, so be explicit if you format multiple currencies.

Dates need a timezone. IntlDateFormatter defaults to UTC (or the system timezone depending on PHP version). Always pass the user's or application's timezone explicitly, or you will produce off-by-hours dates in production that never appear in UTC-based tests.

Invalid locales and patterns fail quietly. intl formatters frequently return false and set an error code rather than throwing. Check the error state after construction and after formatting, and log the raw message plus locale — this is the fastest way to debug a broken placeholder.

Version drift across environments. ICU data differs between OS packages and PHP builds. A pattern that parses on your laptop can behave differently in CI or on Alpine. Test in the same base image you deploy, or accept small differences and assert on structure rather than exact strings.

Long-running workers (Swoole, RoadRunner, Octane). Formatter instances carry locale and pattern state. If you reuse a singleton across requests with different locales, one request's locale leaks into the next. Recreate per request or key the cache by locale and reset it at request boundaries.

Rounding and precision. The default NumberFormatter rounding is not the same as round(). If monetary amounts must match an accounting rule, set precision/rounding attributes explicitly (or format from integer minor units) instead of trusting defaults.

Literal braces in prose. A message that legitimately contains { or } must escape them, or the pattern parser treats them as placeholder boundaries and errors out.

Prefer the package's extension points over subclassing internals. With a 12-star package, the internals are not a stable contract. If you need new behavior, register a value formatter or wrap the call site; do not extend private classes, or upgrades will break you.

Read the tests before the README. Documentation is minimal; the test files enumerate supported placeholder types, constructor arguments, and edge cases far more completely than the README does.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package