Multi 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.
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 the 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 to create grammatically correct dynamic messages containing counts. Some platform contains built-in API to handle plurals. For those that do not have one, 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. This is very simple in English and most Western languages 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 strings. All platforms have some support for them. However, the standard format function is no 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 a 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 language's nullar rule. "=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 the 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 an advanced plural translation editor that makes entering the translation for plural enabled row very easy.
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 if 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 language rule variations (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 split 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.
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 a gender form code. Then separate parts with the semicolon (;) 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.
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 enabled 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.
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.