Test what trans_choice() really returns
Paste a pluralization line from your lang file, pick a locale and a number. The line runs through
Laravel's own Translator
on our server, so the result is exactly what your app would print, including the surprises.
Replacements (optional, :count is filled in for you)
trans_choice() returns
Note the whitespace inside the quotes: Laravel trims a segment chosen by an explicit condition, but not one chosen by the plural rule.
Form index
Every sample number in
| Number | Index | Picked by | Output |
|---|---|---|---|
How Laravel picks a plural form
trans_choice('cart.items', $count) loads the line, splits it on
| and hands it to
Illuminate\Translation\MessageSelector. The selector works in two passes.
First it looks for an explicit condition at the start of any segment:
{0}, {1},
[2,19] or [20,*]. The first segment
whose condition matches wins, and its text is trimmed. Only when nothing matches does the second pass run:
the conditions are stripped and getPluralIndex() turns the locale and
number into an index, using a table of rules Laravel inherited from Zend Framework. If the line has no
segment at that index, Laravel returns the first one.
After a form is chosen, :count is replaced with the number unless you pass
your own count, and every placeholder also works in
:Name (first letter upper-cased) and :NAME
(all caps) variants. This page builds a Translator around your line and calls
choice(), so none of that is re-implemented: the index column is read
from the same selector.
The hyphen trap: pt_BR works, pt-BR does not
The rule table in MessageSelector is a switch
over locale codes such as pt, pt_BR and
de_DE. Anything it does not list falls to the default branch, which returns
index 0. A hyphenated code like pt-BR or en-US,
the format browsers send in Accept-Language, is not in the table, so every
number gets the first form: "5 maçã" instead of "5 maçãs". There is no exception and no log entry. Try the
pt-BR example above, then change the locale to pt_BR. If your locale comes
from a URL segment or a header, normalise it with str_replace('-', '_', $locale)
before calling App::setLocale(), and name the lang folder to match.
When you silently get the first form
- The locale is unknown to the table: a hyphenated code, a typo, or a language Laravel does not list.
- The line has fewer forms than the language needs. Polish has three under Laravel's rules, so
jabłko|jabłkaprints "jabłko" for 5, 0 and 1000. The tester highlights these rows. - The line has a single segment, which is always returned as-is.
Fractions and explicit ranges
With {0} No apples|{1} One apple|[2,*] :count apples, the number 1.5
matches none of the conditions, so Laravel falls back to the English rule, which returns index 1. Index 1
is the second segment, "One apple", with a leading space because the plural pass does not trim.
If fractional counts are possible, cover them with a range such as [1.01,*]
or round the number before calling trans_choice().
Worked example: Polish apples
Load the Polish example. Laravel's rule for pl returns index 0 for exactly 1,
index 1 when the last digit is 2, 3 or 4 (but not 12, 13 or 14), and index 2 for everything else. So 22 prints
"22 jabłka", while 12, 25 and 0 print "jabłek". Fractions are cast to an integer before the modulo checks,
which puts 1.5 on index 2. Once the table looks right, lock it in with a test so a translator cannot break it later:
it('pluralizes apples in Polish', function () {
expect(trans_choice('shop.apples', 1, [], 'pl'))->toBe('1 jabłko')
->and(trans_choice('shop.apples', 22, [], 'pl'))->toBe('22 jabłka')
->and(trans_choice('shop.apples', 25, [], 'pl'))->toBe('25 jabłek');
});
Laravel's table is not CLDR, so it can disagree with what the Pluralization Tester reports for the same language. The background is in Laravel trans_choice() in depth and in the glossary entry on pluralization. The language guides list the exact forms for Polish, Russian, Arabic, Portuguese, French and Czech.
Frequently asked questions
Why does trans_choice() always return the first form?
Four things cause it. The locale is hyphenated (pt-BR instead of pt_BR), because Laravel's plural table only lists underscored codes and unknown codes get index 0. The locale is missing from the table for another reason, such as a typo. The line has fewer forms than the rule asks for, so the index does not exist and Laravel falls back to the first segment. Or the line has a single segment, which is always returned as-is.
Does Laravel use the CLDR plural rules?
No. trans_choice() uses MessageSelector::getPluralIndex(), a table of rules derived from Zend Framework. It matches CLDR for many languages but not all of them: Polish, for example, resolves to three forms in Laravel. Use this tester to see Laravel's answer and the Pluralization Tester to see the CLDR categories.
How do explicit conditions like {0} and [2,*] work?
Before any plural rule runs, Laravel checks each segment for a leading condition: {n} for an exact number, [from,to] for a range and * for an open end. The first segment whose condition matches wins. If none matches, the conditions are stripped and the locale rule picks a segment by index, which is why 1.5 can land on a {1} segment.
Do I need to pass :count myself?
No. trans_choice() adds the number as the count replacement unless you pass your own count, and like every placeholder it also fills :Count and :COUNT with upper-cased variants. Other placeholders work the same way as in __().
Is the line I paste stored or executed?
Neither. The line is loaded into a Translator that exists only for your request, choice() is called on it, and the result is returned. Nothing is written to a database or a log, and the line is treated as text, never as code.
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/trans-choice-tester">trans_choice() 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.
ICU MessageFormat Tester
Write an ICU message with plural, select and number arguments and render it in any locale with the real ICU library.
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.
Every plural form, for every language, in one place
LangSyncer keeps each language's plural forms with your keys, flags lines with the wrong number of forms, translates the missing ones with AI and publishes the fix without a deploy.