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.

Examples:

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 is other, 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 on other.
  • {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: every plural and select needs an other branch. Without it, ICU refuses the pattern with U_DEFAULT_KEYWORD_MISSING.
  • Unbalanced braces: reported as U_PATTERN_SYNTAX_ERROR with 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 one and other parses fine, but 22 needs few. Set the locale of the first example to pl: 1,234 falls in the few category, which that pattern does not define, so ICU uses other.

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

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.

We'd like to use analytics and marketing cookies to understand how the site is used. They stay off unless you accept them. Cookie Policy