skip to content

Translating a Laravel charity site into Spanish and Polish, how do trans_choice() and |-separated or range plural strings pick the right form?

level: middleimportance: should knowfreq 42%

answer

  1. segments split on the pipe
  2. explicit {n} and [a,b] win first
  3. per-locale plural rule picks an index
  4. Polish needs three forms
  5. :count filled automatically

basics

~20 s

trans_choice() splits the line on |, returns the first segment whose {n} or [a,b] condition matches the count, and otherwise uses the locale's plural rule to pick an index. Polish needs three forms, Spanish two.

solid answer

~40 s

A pluralized line lists forms separated by `|`, optionally prefixed with conditions such as `{0}`, `[1,19]` or `[20,*]`. `trans_choice('donations.count', $n)` first checks every segment's condition against the count and returns the first match; if none matches it strips the conditions and asks the locale's plural rule for an index. Spanish uses two forms (one, other); Polish uses three: 1, then 2-4 except 12-14, then everything else, so `:count darowizna|:count darowizny|:count darowizn` is correct. The count is injected as `:count` unless you pass your own, arrays and `Countable` values are counted for you, and Blade has `@choice`. The rule comes from the locale code, so a directory named `pl-PL` instead of `pl` or `pl_PL` silently picks the first form every time.

code

php · 12 lines
php
<?php

use Illuminate\Support\Facades\App;

App::setLocale('pl');

trans_choice('donations.count', 1);   // 1 darowizna
trans_choice('donations.count', 22);  // 22 darowizny
trans_choice('donations.count', 12);  // 12 darowizn

$donors = collect(['Ola', 'Jan', 'Iga']);
trans_choice('donations.count', $donors); // 3 darowizny (Countable is counted)

go deeper

for a junior

Recall the pipe syntax, the trans_choice() helper with a count, and that :count is filled automatically.

for a middle

Explain the two stages: explicit conditions first, then the locale's plural index, and why Polish needs three forms in rule order.

for a senior

Diagnose wrong forms in production: a pl-PL folder hitting the default index, a {0} segment shifting positions, and fallback text chosen with fallback rules.

for a principal

Decide how plural-heavy copy is handed to translators, which locales need grammar review, and when a dedicated message-format library beats the built-in selector.

## The line format A **pluralized translation line** is one string holding several forms separated by a pipe (`|`). Each form may start with a **condition**: - `{0}` or `{1}`: an exact count; - `[2,19]`: an inclusive range; - `[20,*]` or `[*,5]`: an open-ended range. You read it with `trans_choice($key, $count, $replace = [], $locale = null)`, or `@choice` in Blade. ## How a form is selected The translator's `choice()` method hands the line to `MessageSelector::choose()`, which works in two stages: 1. **Explicit conditions first.** Every segment is tested in order; the first whose condition matches the count is returned, trimmed. 2. **Plural rules second.** If no condition matched, the conditions are stripped and `getPluralIndex($locale, $count)` returns a position; that segment is returned. If the line has only one segment, or the index is past the end, the first segment is used. Before either stage, `choice()` counts arrays and `Countable` objects for you and adds `['count' => $count]` to the replacements unless you set `count` yourself. ## Spanish and Polish on a charity site | Locale | Forms | Rule `getPluralIndex` applies | |---|---|---| | `es` | 2 | 1 -> first, everything else -> second | | `pl` | 3 | 1 -> first; ending in 2-4 but not 12-14 -> second; everything else -> third | | `en` | 2 | 1 -> first, everything else -> second | Spanish can use explicit conditions freely, because its forms map neatly onto ranges: ```php <?php // lang/es/donations.php return [ 'count' => '{0} Aún no hay donaciones|{1} Una donación|[2,*] :count donaciones', ]; ``` Polish cannot sensibly be written as ranges, since 22 takes the second form and 25 the third, so it relies on the rule and lists its three forms **in rule order**: ```php <?php // lang/pl/donations.php return [ 'count' => ':count darowizna|:count darowizny|:count darowizn', ]; ``` With that line, 1 gives `1 darowizna`, 3 and 22 give `darowizny`, and 0, 5, 12 and 21 give `darowizn`, which is correct Polish, zero included. ## Traps - **Mixing a condition with rule-based forms.** Stripped segments keep their positions, so in `{0} Brak|:count darowizna|:count darowizny|:count darowizn` the `{0}` text still sits at index 0: a count of 1 would print `Brak`. Either give every segment a condition or none. - **Wrong locale code.** `getPluralIndex` switches on codes like `pl`, `pl_PL`, `es`, `es_ES`; an unknown code such as `pl-PL` falls to the default branch, index 0, and every count shows the singular. - **Fallback text, fallback rules.** `choice()` picks the locale that actually has the key: if the Polish file lacks it, the English line is used *and* English rules choose the form. - **The pluralizer is something else.** `Pluralizer::useLanguage()` changes the language of the word inflector behind `Str::plural()` and Eloquent's table-name guessing; it has no effect on `trans_choice()`. - **Floats.** A count like 1.5 is accepted; the rules cast to integer for their modulo tests, so test fractional amounts in each locale if you display them. ## Placeholders in plural lines A pluralized line can carry other placeholders besides `:count`, filled from the third argument: ```php <?php // lang/es/donations.php return [ 'from' => '{1} Una donación de :name|[2,*] :count donaciones de :name', ]; // trans_choice('donations.from', 3, ['name' => 'Lucía']); // 3 donaciones de Lucía ``` ## Adding a new locale safely 1. Check that `getPluralIndex` knows the locale code you will use (`pl`, `pl_PL`, and so on) and how many forms its rule returns. 2. Write every pluralized line with that number of forms, in the rule's order. 3. Test a spread of counts for each pluralized key, including 0, 1, 2, 5, 12, 22 and 101. 4. Keep explicit conditions for languages whose forms map onto ranges, and rule-based forms for the rest.

  • How would you show a special message for zero donations in Polish?
    Not by prefixing a `{0}` segment to the three rule-based forms, because stripped segments keep their positions and shift every index. Use a separate key for the empty state and branch in the view, or write every Polish segment with an explicit condition only if the ranges really describe the grammar, which for Polish they do not.
  • The Polish file lacks donations.count. Which text and which plural rule does trans_choice use?
    Both come from the fallback locale. `choice()` checks whether the key exists for the requested locale; if not, it takes the fallback locale's line and passes that locale to the selector, so English text is chosen with English one-or-other rules.

saying these in an interview costs you the question

  • Every language needs exactly two forms: singular and plural
  • trans_choice ignores {0} and [a,b] conditions when the locale has plural rules
  • You must always pass ['count' => $n] or :count stays literal
  • Pluralizer::useLanguage('spanish') makes trans_choice use Spanish plural rules
  • Naming the folder pl-PL works the same as pl or pl_PL