Laravel String Pluralization

November 30, 2016 —John Koster

Since Laravel's translator is build on top of Symfony's translator component, the syntax rules used by Symfony's pluralization system will work with Laravel. Pluralization translation lines follow the ISO 31-11 standard for numeric intervals, such as specific numbers and ranges. The syntax also allows for "tags", which behave like comments for different plural translation messages.

#Message Tags

Message tags act as a sort of comment to help explain the various translation messages when dealing with pluralization. Tags appear at the beginning of a translation message, contain no spaces and end with one colon character (:). The following code example highlights the usage of tags:

1<?php
2 
3return [
4 
5 'books' => 'first_message_tag: There is one book.|'.
6 'second_message_tag: There are :count books.',
7];

Message tags are ignored when returning the appropriate message for a given plural form of a message. This can be observed in the following code example (the result of the function call will appear above the function call as a comment):

1<?php
2 
3// There is one book.
4trans_choice('plural.books', 1);
5 
6// There are 4 books.
7trans_choice('plural.books', 4, ['count' => 4]);

#Handling Specific Numbers

Sometimes it is useful to associate a given message number with exactly one number. All of the previous examples have not dealt with the pluralization case when there is no items (when the $number is exactly 0).

Passing 0 to the previous translation messages would produce the following result:

1There are 0 books.

However, it is sometimes desirable to handle the case explicitly and return a more appropriate message:

1<?php
2 
3// Sorry! No books here.
4trans_choice('{0} Sorry! No books here.
5 |There is one book.
6 |There are :count books.', 0);
7 
8// There is one book.
9trans_choice('{0} Sorry! No books here.
10 |There is one book.
11 |There are :count books.', 1);
12 
13// There are 2 books.
14trans_choice('{0} Sorry! No books here.
15 |There is one book.
16 |There are :count books.', 2);

It can be seen in the last call to the trans_choice function in the above code example that Laravel will automatically replace the :count placeholder with the $number of items. Because of this, the two function calls are identical in their result:

1<?php
2 
3// There are 2 books.
4trans_choice('{0} Sorry! No books here.
5 |There is one book.
6 |There are :count books.', 2);
7 
8// There are 2 books.
9trans_choice('{0} Sorry! No books here.
10 |There is one book.
11 |There are :count books.', 2,
12 [
13 'count' => 2
14 ]);

#Examples of Specific Numbers

The following table will show more examples of handling specific numbers when dealing with pluralization translation. The table will use the following translation message:

1{0} There are no books!
2|{1,2,3} You have one, two or three books.
3|{4} Four is a great number of books!
4| The number of books you have is uncountable!
Count Result
0 There are no books!
1 You have one, two or three books.
2 You have one, two or three books.
3 You have one, two or three books.
4 Four is a great number of books!
5 Raises an error

It would seem that passing a $number of 5 would result in the message The number of books you have is uncountable! to be returned. Instead, an instance of InvalidArgumentException will be thrown. The exception message will explain that it was unable to choose a translation. However, a very small fix will allow this to work.

To get the desired result, prefix the entire translation message with a single pipe (|) character:

1|{0} There are no books!
2|{1,2,3} You have one, two or three books.
3|{4} Four is a great number of books!
4| The number of books you have is uncountable!

Now, the following function call will return the expected result:

1<?php
2 
3$message = '|{0} There are no books!
4 |{1,2,3} You have one, two or three books.
5 |{4} Four is a great number of books!
6 | The number of books you have is uncountable!';
7 
8// The number of books you have is uncountable!
9trans_choice($message, 5);
10 
11// The number of books you have is uncountable!
12trans_choice($message, 50);
13 
14// The number of books you have is uncountable!
15trans_choice($message, 100);
16 
17// The number of books you have is uncountable!
18trans_choice($message, M_EULER);
19 
20// The number of books you have is uncountable!
21trans_choice($message, INF);

#Intervals

Specifying every number required for a given translation message can be tedious, or even impossible. However, intervals can be specified to make working with large groups of numbers easier. The syntax for intervals comes directly from the ISO 31-11 standard. Intervals are written as a pair of numbers between two delimiters (a delimiter for both the left and right).

There are two types of delimiters: inclusive and exclusive. Inclusive delimiters include the number that it is associated with and an exclusive delimiter excludes the number that it is associated with. The style of writing inclusive and exclusive delimiters depends on whether or not the delimiter appears on the left or the right of an interval.

Delimiter Position Inclusive Form Exclusive Form
Left [ ]
Right ] [

The following code example demonstrates how to write an interval for all numbers between one and ten, including numbers one and ten:

1[1,10]

The following example would not allow the numbers one and ten:

1]1,10[

As shown in the above examples, the endpoints (the two numbers) of the interval are separated by a comma ,. The standard technically allows the endpoints to be separated by a semicolon, but the implementation used by the translator does not support this.

The following table contains various intervals and the numbers that they match:

Interval Matching Values
[1,5] 1 2 3 4 5
]1,5[ 2 3 4
]5,10] 6 7 8 9 10
[4,9[ 4 5 6 7 8
[-Inf,+Inf] All numbers
]-Inf,+Inf[ All numbers
[20,+Inf] All numbers twenty and above
[-Inf,20] All numbers twenty and below

#Using Intervals with Translation Message

When using intervals with translation messages, the interval appears as the beginning of translation message. Message tags cannot be used when using intervals. The following is an example translation message using intervals:

1|{0} There are no books!
2|[1,3] You have one, two or three books.
3|{4} Four is a great number of books!
4|[5,+Inf] The number of books you have is uncountable!

THe following code example will demonstrate the value returned by the trans_choice helper function using the above translation message. The result will appear above the function call as a comment.

1<?php
2 
3$message = '|{0} There are no books!
4 |[1,3] You have one, two or three books.
5 |{4} Four is a great number of books!
6 |[5,+Inf]The number of books you have is uncountable!';
7 
8// There are no books!
9trans_choice($message, 0);
10 
11// You have one, two or three books.
12trans_choice($message, 2);
13 
14// Four is a great number of books!
15trans_choice($message, 4);
16 
17// The number of books you have is uncountable!
18trans_choice($message, 5);
19 
20// The number of books you have is uncountable!
21trans_choice($message, 500);
22 
23// The number of books you have is uncountable!
24trans_choice($message, 20000);
25 
26// The number of books you have is uncountable!
27trans_choice($message, 1000000);
28 
29// The number of books you have is uncountable!
30trans_choice($message, 154141162141166145154);

#Notes on Decimals

In the previous example, trying to match the number 3.4 will throw an instance of InvalidArgumentException. To match any possible number, including decimals, for the previous translation messages, the explicit number groups must be replaced with intervals (to have the example make sense contextually, it will use USD currency instead):

1<?php
2 
3$message = '|{0} You have no money.
4 |]0,1[ You have a few cents.
5 |[1,3[ You have a dollar, and some cents.
6 |[2,3[ You have a few dollars.
7 |[3,+Inf] Keep it up!';
8 
9// You have no money.
10trans_choice($message, 0);
11 
12// You have a few cents.
13trans_choice($message, 0.23);
14 
15// You have a dollar, and some cents.
16trans_choice($message, 1.23);
17 
18// You have a few dollars.
19trans_choice($message, 2.32);
20 
21// Keep it up!
22trans_choice($message, 3);

Some absolutely amazing
people

The following amazing people help support this site and my open source projects ♥️
If you're interesting in supporting my work and want to show up on this list, check out my GitHub Sponsors Profile.