Test an ICU message in any locale
Write a pattern with plural,
select and
number arguments,
add the values as JSON, and see it rendered by PHP's MessageFormatter,
which is the ICU library itself (ICU 50.2 on this server).
The pattern syntax is stable across ICU versions, but locale data such as plural rules and number
symbols is updated between releases, so check INTL_ICU_VERSION
on your own server if a detail differs.
Strings like 2026-03-14 or 2026-03-14T09:30 are passed as dates.
ICU error
Rendered in
No value for . ICU prints a missing argument verbatim instead of failing.
The same message in several locales
Numbers, dates and plural categories follow each locale. The words are yours, so they stay in the language you wrote them in.
What ICU MessageFormat is
ICU MessageFormat
is a syntax for messages that change with their arguments. A placeholder is {name};
a formatted one adds a type, such as {size, number} or
{date, date, long}; and the complex arguments,
plural, selectordinal and
select, pick a sub-message. Plural categories (zero, one, two, few, many, other)
come from CLDR,
the same data behind the Pluralization Tester.
Exact matches like =0 are checked before the categories, and
# prints the number formatted for the locale: 1,234 in English, 1.234 in German.
Reading the example, piece by piece
{name}is a simple argument: the value is inserted as-is.{count, plural, =0 {no files} one {# file} other {# files}}checks the exact value 0 first, then asks CLDR for the category. With 1234 that isother, and#becomes "1,234" in English and "1.234" in German.{size, number, ::unit/megabyte unit-width-short}is a number skeleton. ICU picks the unit abbreviation for the locale: "52.5 MB" in English, "52,5 Mo" in French.{gender, select, female {her} male {his} other {their}}chooses by exact string. Any value you did not list, including a missing one, lands onother.{date, date, long}formats a date in the locale's long style: "March 14, 2026" in English, "14. März 2026" in German.
The comparison table runs your pattern unchanged in seven locales. The words do not translate themselves, but everything ICU controls does: digit grouping, decimal separators, unit names, date order and which plural branch is chosen. That makes it a quick way to spot a branch you forgot for one of your target languages.
ICU and Laravel
Laravel's translator does not understand ICU syntax. __() swaps
:placeholders with a plain string replacement, and
trans_choice() picks pipe-separated forms with its own rule table in
MessageSelector, not CLDR. You can see the difference with the
trans_choice() Tester.
If you want ICU messages in a Laravel app, the intl extension gives you the formatter directly:
// lang/en/files.php
'shared' => '{count, plural, =0 {No files} one {# file} other {# files}}',
// anywhere
$message = MessageFormatter::formatMessage(
app()->getLocale(),
__('files.shared'), // no replacements, so Laravel returns the line untouched
['count' => $files->count()],
);
Call __() without a replacement array, so Laravel hands back the raw pattern
and ICU does all the work. Laravel's Number helper already depends on the same
extension: Number::format() and Number::currency()
throw if intl is missing. ICU accepts both pt-BR and
pt_BR, unlike Laravel's plural table.
Mistakes ICU will and will not catch
- Missing
other: everypluralandselectneeds anotherbranch. Without it, ICU refuses the pattern withU_DEFAULT_KEYWORD_MISSING. - Unbalanced braces: reported as
U_PATTERN_SYNTAX_ERRORwith the offset where parsing stopped. - Missing arguments: not an error.
Hi {name}without a name renders as "Hi {name}", which is why this page lists them. - Apostrophes: a single quote before a brace starts a quoted literal, so
'{n}'prints{n}. Write''for a literal quote there. An apostrophe inside a word, as in "I'm", is left alone. - Forgotten categories: a Polish message with only
oneandotherparses fine, but 22 needsfew. Set the locale of the first example topl: 1,234 falls in thefewcategory, which that pattern does not define, so ICU usesother.
Frequently asked questions
Does Laravel support ICU MessageFormat?
Not natively. __() replaces :placeholders and trans_choice() uses its own plural table, so ICU syntax in a lang file is returned as plain text. With the intl extension installed you can pass the raw line to MessageFormatter::formatMessage($locale, __('key'), $arguments) and let ICU do the formatting.
What does U_DEFAULT_KEYWORD_MISSING mean?
A plural, selectordinal or select argument has no other branch. ICU requires other in every one of them, because it is the fallback for any value or category you did not list, so the pattern is rejected until you add it.
What happens if I forget an argument?
ICU does not report an error. The placeholder is printed as written, so Hi {name} without a name renders as "Hi {name}". This tester lists the arguments your pattern uses but your JSON does not provide.
How do I write a literal brace or apostrophe?
Quote it with apostrophes: '{' prints a brace and '{name}' prints {name} instead of the value. Two apostrophes in a row print one. An apostrophe that is not next to a special character, as in "it's", is printed as-is.
Will my message render the same on my server?
The syntax is the same everywhere, but number, date and plural data come from the CLDR release bundled with the ICU version your intl extension links against, so details such as spacing or abbreviations can differ between versions. The page shows which ICU version produced the result.
Useful? Link to it
The tool is free and stays free. Paste this into a README, a wiki page or a forum answer.
<a href="https://langsyncer.com/tools/icu-message-format-tester">ICU MessageFormat Tester</a> by LangSyncer
Other free tools
Pluralization Tester
Pick a language, type a number, see which CLDR plural category applies — live, via your browser's Intl.PluralRules.
trans_choice() Tester
Paste a Laravel pluralization string, pick a locale and a count, and see exactly which form Laravel returns, using Laravel itself.
Carbon Localized Date Preview
See how Carbon formats dates, relative times and month names in any locale before you ship them.
Locale Code Lookup
Check any BCP 47 locale code: language, region, script, text direction, plural forms and the Laravel folder name to use.
Keep every language's messages in sync
LangSyncer imports your Laravel lang files, shows which keys each language is missing, translates them with AI and publishes the result live, without a deploy.