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)

Examples:

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łka prints "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

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.

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