Document home

Plurals

Patterns: Grammatical Numbers, Genders and Select

Sometimes we need to have two or more variants of the same string to have grammatically correct sentences. This is where we use multi-pattern strings.

Grammatical Numbers

Most languages have singular and plural forms. For example, we say, "I have one car," and "I have two cars." Note that the second sentence contains a car in plural form. However, this is not a case with every language. Most Asian languages do not have plural at all. They only have a singular form. Most Slavic languages have three forms: singular, plural, and between them (dual or paucal). Sometimes zero is handled as plural, sometimes as singular. Some languages have four different forms. Arabic has six different forms.

This is why you cannot use the standard string format function if you want to create grammatically correct dynamic messages that contain counts. Some platform contains built-in API to handle plurals. For those that do not have, we have implemented our open source API that you can use. It works a little bit like iOS's localizedStringWithFormat function. You pass a combined pattern and pluralized parameters. The API calculates what pattern to use based on the passed pluralized parameter and uses that pattern. In English and most Western languages, this is very simple because there are only two items in the combined pattern: singular and plural. If the count is one, then the singular pattern is used. If the count is other, the plural pattern is used. Let's have an example. You might have something like this

str = Format("%d files", fileCount);

The technique is called composite format strings or interpolated string. All platforms have some support for them. However, the standard format function is not a plural or gender aware. If you want to localize it correctly convert the code to

str = MultiPatternFormat("{, plular, one {%d files} other {%d files}}", fileCount, fileCount);

As you can see, instead of a single pattern, the new pattern contains two parts: singular (green) and plural (blue). This is because English uses two patterns. Start each pattern with plural form code. If your pattern contains {, } or \ character, you must escape it.

"One {\} Two %d" -> "One \{\\\} Two %d"

The following table shows possible plural form codes:

Name Code Description
Nullar zero Typically 0
Singular one Typically 1
Dual two Typically 2
Paucal few Depends on the language but is typicall few (2-4).
Many many Depends on the language but is typically many.
Plural other Plural or universal form. In general, you should always include this part, no matter what is the language you use.

You can also use fixed patterns. The format is

=<number> {pattern}

For example, "=3 {Three files}" is a pattern for the case where there are three files. If the multi-pattern string contains a fixed pattern, it will be used if the pattern matches the numeric value. Are "zero" and "=0" the same? Not necessary. "zero" is used if the numeric value matches the nullar rule of the language. "=0" is used only when the numeric value is 0 (even in the case that the language does not have a nullar form).

English and most Western languages use two forms: singular and plural. So include singular and plural parts into your patterns. For example, if you write your software in English, include one and other parts. When your translator translates a string to Polish, Soluling lets her enter three parts: singular, paucal, and plural.

Finnish
Polish
Japanese

When original strings are scanned, the format is converted from platform-specific pattern format into Soluling's platform-independent pattern format that protects the placeholder by just showing the location that can be moved. Learn more about patterns.

If you want to handle one or more numeric values in a different way, you can add extra patterns. For example instead of having "{, plural, one {%d file} other {%d files}}" you can add a zero pattern: "{, plural, =0 {No files} one {%d file} other {%d files}}".

When you place the combined pattern into resource string, remember to end the name of the resource string with Plural (e.g., MyMessagePlural). That way, Soluling can automatically identify the pattern as a multi-pattern string, and it can enable advanced plural translation editor that makes entering the translation for plural enabled row very easy.

Multiple parameters

In the above sample, we had only one count related parameter. We could have any number of other parameters such as strings etc. What about if we have two parameters that contain numbers. Let's have an example. We want to show how many steps we have completed. For that, we might have a line like this.

str = Format("I have completed %d ateps out of %d steps", completed, total);

The pattern contains two integer parameters that both are counts, and the following noun (step or run) should be using the proper form. We could split the pattern into two parts:

str = Format("I have completed %d", completed) + Format("out of %d steps", total);

This would work as long as the completed count is written before the total count but would fail of the required word order is different. Another issue is that the sentence has been split into two parts, and that would make translations much harder. One solution would enhance the Format function to accept two or more count parameters. However, this would make the multi-pattern pattern complicated. When we have only one parameter, we would need only the variations required for the language rule (e.g., 2 for English, 3 for Polish, etc.). Having two parameters would require the variations to be squared (e.g., 4 for English, 9 for Polish). Having three parameters would require the third power of variations (e.g., 8 for English, 27 for Polish). As you can see, this would be very complicated and impractical. A better solution is to split the pattern into several parts that each contain only one parameter and then chain the pattern together.

str = MultiPatternFormat("I have completed {completed, plural, one {%d step} other {%d steps}} {total, plural, one {out of %d step} other {out of %d steps}}", completed, total);

The first pattern is the start pattern that does not contain any pluralized parameters but placeholders for each sub-patterns. The next two patterns are for the completed count. The next two patterns are for the total count. Each pattern contains only one pluralized parameter.

Even you have to slipt multi-parameter patterns into several parts Soluling editor shows them together, so it is easy to see the whole message at once.

Some platforms, such as Angular, use nested patterns that use a tree structure instead of a linked list structure described above.

Genders

Some languages use genders. For example, in English, there are two genders: male and female. We would say, "John brings his bicycle." Because John is a male name, we use "his." The female version would be "Jill brings her bicycle." This is why we cannot use the normal Format function like this.

str = Format("%s brings his bicycle", name);

but we need to use a gender enabled multi pattern MultiPatternFormat.

str = MultiPatternFormat("{gender, male {%s brings his bicycle} female {%s brings her bicycle}}", gender, name);

As you can see, instead of a single pattern, the new pattern contains two parts: male (green) and female (blue). Start each pattern with gender form code. Separate parts with ; character.

English and most Western languages use two forms: male and female. So include both male and female parts into your patterns. For example if you write your software in English, include male and female parts. When your translator translates a string, for example, to Finnish (that does not use genders), Soluling lets her enter only one part: neutral.

Like plural rules, also gender rules depend on the language. Some languages use genders some others do not.

Select

Sometimes you might have two or more variants that are not related to grammar (plural or gender) but a value. For example, if we want to show who is the best player in various sports we could use if statements:

if (sport == "soccer")
  str = Format("%s is the best soccer player", player);
else if sport = 'hockey' then
  str = Format("%s is the best ice hockey player", player);
else if sport = 'basketball' then
  str = Format("%s is the best basketball player", player);

This works, but it also requires you to modify the code each time you add a new sport. If we use select rnabled Format, the above code would be

str = MultiPatternFormat("{select, soccer {%s is the best soccer player} hockey {%s is the best ice hockey player} basketball {%s is the best basketball player}}', sport, player);

In the case of select, all languages will have the same variants as the original string.

Platforms

Some platforms contain built-in support for patterns. Some platformd require that you use our custom API. The following table shows how patterns are supported by various platforms:

Platforms Plural Gender Select Multiple/​nested params
APIs Samples
.NET Standard yes yes yes yes <data-dir>\Library\​NET\Standard\Pattern.cs <data-dir>\Samples\​WindowsForms\Patterns
<data-dir>\Samples\​WPF\Patterns
<data-dir>\Samples\​UWP\Patterns
<data-dir>\Samples\​ASP.NET\Core\Patterns
Android built-in - - -   <data-dir>\Samples\​Android\Plural
Angular built-in built-in built-in built-in   <data-dir>\Samples\​Angular\Patterns
Delphi yes yes yes yes <data-dir>\Library\​Delphi\NtPattern.pas <data-dir>\Samples\​Delphi\VCL\Patterns
<data-dir>\Samples\​Delphi\FMX\Patterns
gettext/po built-in - - -   <data-dir>\Samples\​PO\Plural
See also PHP or Python samples
iOS built-in built-in - built-in iOS 7 or later <data-dir>\Samples\​iOS\Plural
Java yes yes yes yes <data-dir>\Library\​Java\Soluling\Pattern.java <data-dir>\Samples\​Java\Patterns
JavaScript yes yes yes yes <data-dir>\Library\​JavaScript\pattern.js <data-dir>\Samples\​HTML\Patterns
OS X built-in built-in - built-in OS Mavericks (version 10.9) or later See iOS sampels
PHP built-in - - -   <data-dir>\Samples\​PHP\Plural
Python built-in - - -   <data-dir>\Samples\​Python\Plural
Qt built-in - - -   -
TypeScript yes yes yes yes <data-dir>\Library\​TypeScript\pattern.ts <data-dir>\Samples\​HTML\Patterns

yes means that we have implemented an open-source API. To use the open-source API, include the plural API file into your project and start using the API. To see how to use the API, see the provided plural samples at <data-dir>\Samples\<platform>\Pattern directory.

built-in means that the standard API of the platform supports the feature. Only Angular has a built-in solution that matches Soluling API. Other platforms provide built-in support for plural parameters and/or gender but do not have a solution for multiple parameters or select. If your platform does not have built-in support and Soluling's API does not support it, you can easily write your own by viewing the implementation of one of the APIs we provide.