A Quick Recap
In a previous post, Chris explained how to get started with
localizing an iOS application. In this post, we’re going to talk about a more
advanced localization topic which will help us fix a grammatical error in the
previous post’s example. To recap, here’s the basic mechanism by
which you can obtain a localized string which contains variable elements defined
by your code:
let msg = String.localizedStringWithFormat(
NSLocalizedString(
"There are %d oranges in the box",
comment: "There are {number} oranges in the box"
),
orangeCount
)
This has the obvious shortcoming that if you have 1 orange, the message will be
“There are 1 oranges in the box”, which is not grammatically correct (at least
in English). In the past, I have been guilty of “fixing” this by using a
different format string if the count value is plural or not:
let fmt: String
if orangeCount != 1 {
fmt = NSLocalizedString(
"There are %d oranges in the box",
comment: "There are {number} oranges in the box
)
} else {
fmt = NSLocalizedString(
"There is %d orange in the box",
comment: "There is 1 orange in the box
)
}
let msg = String.localizedStringWithFormat(fmt, orangeCount)
This is better, but some other languages have more than two plural categories.
Arabic, for example, has six plural categories! The variations between
languages would be extremely difficult to manage
yourself, so Apple has taken care of this for us with the stringsdict
file
mechanism.
iOS Pluralization Support
The stringsdict
file lets you provide formats for the following plural cases:
none
, one
, two
, few
, many
, and other
(English only uses one
and
other
). Add a new file to your project called Localizable.stringsdict
and
check the language that the file applies to in the Localization section of the
File Inspector. It is also required to have a Localizable.strings
file present
(even if it is empty) so that the Localizable.stringsdict
file will be used by
the iOS frameworks. Below is what the Localizable.stringsdict
file needs to
look like to make our box of oranges example work:
<plist version="1.0">
<dict>
<key>orange_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@oranges@</string>
<key>oranges</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>There is one orange in the box</string>
<key>other</key>
<string>There are %1$d oranges in the box</string>
</dict>
</dict>
</dict>
</plist>
The top-level <key></key>
is the key of the localized string passed to
NSLocalizedString
. I prefer to use a short description of what the string is
about for the key instead of the default English text. That way, if the english
translation changes, it is not necessary to change the string key in all of the
Localizable.strings
and Localizable.stringsdict
files. In this example, I’ll
name the string orange_count
. If you use a key that is not the default text,
make sure that you also have an entry in your base Localizable.strings
file
which specifies the default text.
The next step is to name the placeholders so that their plural forms can be
specified later. The value %1$#@oranges@
for the NSStringLocalizedFormatKey
key specifies that the first placeholder (1$
) is called “oranges”
(@oranges@
). It is very important to use position indexes (like 1$
) for
strings with multiple placeholders because the format for another language may
switch positions of the placeholders. The position indicies specify where the
positional arguments to String.locallizedStringWithFormat()
should be placed
within our localized string. If no position is given, the arguments are placed
into the string in the order that they are given, which may be incorrect.
A sub-dictionary for each named placeholder must also be given. This dictionary
contains information about the language rule applied to the placeholder and the
format type of the placeholder. NSStringFormatSpecTypeKey
specifies the
language rule applied and the only option is NSStringPluralRuleType
.
NSStringFormatValueTypeKey
specifies the format of the number using a
standard string format specifier. For an integer number,
we’ll use d
(which formats as a signed integer).
The plural forms of the placeholder are then specified with the none
, one
,
two
, few
, many
, and other
keys. Though the only required key is
other
, you may need to specify additional keys depending on the rules of the
languages you are targeting. In the example above, I’ve included both other
and one
for the English example above, because those are the only two forms
used in English.
let msg = String.localizedStringWithFormat(
NSLocalizedString("orange_count", comment: "Count of oranges in the box"),
orangeCount
)
// Result:
// For 0 oranges: "There are 0 oranges in the box"
// For 1 orange: "There is one orange in the box"
// For 10 oranges: "There are 10 oranges in the box"
<span class="c1">// Result:</span>
<span class="c1">// For 0 oranges: "There are 0 oranges in the box"</span>
<span class="c1">// For 1 orange: "There is one orange in the box"</span>
<span class="c1">// For 10 oranges: "There are 10 oranges in the box"</span>
Android Pluralization Support
Localizing plural forms in Android is even easier than iOS! Android uses
plurals
resources, which are very similar to strings
resources. Plural and string resources both live in your strings.xml
file (and
its localized variants) and use the same quantity keys as iOS:
<plurals name="orange_count">
<item quantity="one">There is one orange in the box</item>
<item quantity="other">There are %1$d oranges in the box</item>
</plurals>
You can then easily generate a formatted result for a given quantity using
getQuantityString()
:
val msg = resources.getQuantityString(
R.plurals.orange_count,
orangeCount, // Used to determine which form of the quantity string to use
orangeCount // The quantity value to be inserted into the format string
)
// Result:
// For 0 oranges: "There are 0 oranges in the box"
// For 1 orange: "There is one orange in the box"
// For 10 oranges: "There are 10 oranges in the box"
Thats it! With just a little bit of extra work, your app can be localized
correctly and you can clean up some conditional logic in your source code!