Localizing Dynamic Plural Noun messages (e.g. "5 Items Processed") on iPhone using Objective-C

15,381

Solution 1

As of iOS 7, Foundation framework has native support for pluralization. Here is a quick tutorial how to use it:

Create a plist file named as Localizable.stringsdict

English Localization:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>%d tasks waiting for action</key>
        <dict>
            <key>NSStringLocalizedFormatKey</key>
            <string>%#@tasks@ waiting for action</string>
            <key>tasks</key>
            <dict>
                <key>NSStringFormatSpecTypeKey</key>
                <string>NSStringPluralRuleType</string>
                <key>NSStringFormatValueTypeKey</key>
                <string>d</string>
                <key>one</key>
                <string>A task is</string>
                <key>two</key>
                <string>Two tasks are</string>
                <key>other</key>
                <string>%d tasks are</string>
            </dict>
        </dict>
    </dict>
</plist>

Polish Localization:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>%d tasks waiting for action</key>
        <dict>
            <key>NSStringLocalizedFormatKey</key>
            <string>Masz %#@zadanie@ do zrobienia</string>
            <key>zadanie</key>
            <dict>
                <key>NSStringFormatSpecTypeKey</key>
                <string>NSStringPluralRuleType</string>
                <key>NSStringFormatValueTypeKey</key>
                <string>d</string>
                <key>one</key>
                <string>jedno zadanie</string>
                <key>few</key>
                <string>%d zadania</string>
                <key>other</key>
                <string>%d zadań</string>
            </dict>
        </dict>
    </dict>
</plist>

And finally in your implementation file, you can call the dictionary like this:

cell.tasksInfoLabel.text = [NSString localizedStringWithFormat:NSLocalizedString(@"%d tasks waiting for action", @"%d tasks waiting for action"), (long)taskCount];

EDIT: Thanks Zaphod for pointing this out ->: You also need to create the Localizable.strings file alongside the .stringsdict to have the pluralization work (even if it's empty).

Solution 2

My team developed an open source library for handling just this situation, checkout our iOS i18n plural library on github.

The basic premise is that the keys for plural strings are extended to contain their plural form according to the CLDR plural rules and the lookup of the strings does not use the typical NSLocalizedString.

The English file for the example posted would look like this:

"%d Items Processed##{one}"   = "1 Item Processed";    
"%d Items Processed##{other}" = "%d Items Processed";

The lookup would then be done using a SLPluralizedString function

SLPluralizedString(@”%d Items Processed”, numItems, @”Number of items processed”);

At runtime, for English the String "1 Item Processed" or "%d Items Processed" would be returned depending on the value of numItems.

The Russian file would then look like this:

"%d Items Processed##{one}"   = "%d элемент обработан";
"%d Items Processed##{few}"   = "%d элемента обработано";
"%d Items Processed##{many}"  = "%d элементов обработано";
"%d Items Processed##{other}" = "%d элемента обработано";

Your code then to lookup "Items Processed" for Russian or any other language wouldn't have to change and the library would return the correct String according to the CLDR plural rules for that particular language.

Please feel free to share your thoughts on the library, suggestions, improvements, etc.

Solution 3

In English, there are just 2 plural forms, e.g. "1 file" and "5 files". In Russian, there are 3 plural forms (101 файл, 2 файла, 11 файлов), unless you count non-integers. Actually, there can actually be up to 6 plural forms in a language (e.g. Arabic has 6) . There seems to be 3 ways to deal with the problem, just choose whatever is good enough but not too complicated for you:

  1. Try to use plural-neutral messages, e.g. "Number of items processed: %d" instead of "%d item processed | %d items processed ".

  2. Support localizations for each plural form, up to 6.

    "%d Gold Coins##{PluralForm0}" -> "%d золотая монета" // e.g. 1 gold coin
    "%d Gold Coins##{PluralForm1}" -> "%d золотые монеты" // e.g. 2 gold coins
    "%d Gold Coins##{PluralForm2}" -> "%d золотых монет"  // e.g. 5 gold coins
    …
    "%d Gold Coins##{PluralForm5}" -> "%d How did we get here if this is not Arabic???"
    

    Knowing the value of %d and the target language, you app will have to detect the plural form number in the runtime, i.e. implement something like

    unsigned int "NumberToPluralFormNumber(unsigned int number, const std::string& langCode);
    

    method. If you support just 2-5 languages and the numbers in the messages are always non-negative ints, it is actually quite simple to implement it w/o any 3d party lib, you can copy/paste C-compatible one-liners for each language from http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html . Please note that it's valid for non-negative integers only, so the number of plural forms might differ from what unicode.org says.

  3. 3d party libs.
Share:
15,381
Jason
Author by

Jason

Updated on June 26, 2022

Comments

  • Jason
    Jason almost 2 years

    In my current app, I have code that displays a message, e.g. "5 Items Processed." To keep the phrase grammatically correct, i.e. whether or not it should be "5 Item" or "5 Items," I use the following code:

    int numItems = 5;
    NSString *myString = [[NSString alloc] initWithFormat:@"%d Item%@ Processed", numItems, (numItems == 1 ? @"" : @"s")];
    

    This works fine for now. But I'm localizing my app, and want to make sure that the text is grammatically correct in all the languages I am translating the app into. I could do something like this:

    int numItems = 5;
    NSString *myString = (numItems == 1 ? 
    NSLocalizedStringWithTable(@"%d Item Processed", @"myApp", @"singular version") :
    NSLocalizedStringWithTable(@"%d Items Processed", @"myApp", @"plural version"));
    

    However, not all languages have the same rules for how plurals operate! For example, (forgive my very specific example here) in Russian, nouns modified with numbers ending with the last digit 1 (i.e., 21, 31, but not 11) take the nominative case, numbers ending in 2-4 take the genitive singular, and 5+ take the genitive plural case. This would require much more serious logic to handle how to pluralize a particular noun in a grammatically correct fashion, and this logic would not match up to the English logic. Therefore, in theory, I cannot have the grammatical logic in my Objective-C code, but should rather have the grammatical logic in the strings file. Is there a way to do this? How do people translate dynamic text for their apps so that it remains grammatically correct?

  • Jason
    Jason about 13 years
    This is a good suggestion, to simply reduce the number of times that plural forms are even an issue. Of course, it's not possible to always avoid this linguistic possibility. I've posted another question related to this topic, but more focused on Mozilla's solution, PluralForm: stackoverflow.com/questions/5130844/…
  • Nick Snyder
    Nick Snyder over 10 years
    How does this solution interact with genstrings? Do you have to manually create Localizable.stringsdict? Can you have both Localizable.stringsdict and Localizable.strings or must you pick only one?
  • Alp
    Alp over 10 years
    You can have both. I'm using genstrings to create Localizable.string, and manually creating Localizable.stringsdict. (I think it makes sense, because you need to implement the plural logic.)
  • dclowd9901
    dclowd9901 about 10 years
    I believe I implemented this solution correctly, but it doesn't appear to be pluralizing. Could you give a few more details about how this is implemented?
  • Alp
    Alp about 10 years
    I've already listed all the required steps. Create the Localizable.stringsdict plist file, localize it as in example, and use in your code. That's it, no additional steps.
  • dclowd9901
    dclowd9901 about 10 years
    I still haven't gotten this whole pluralization thing to work for myself yet, but it hasn't deterred me from making something to make our lives easier and less XML: github.com/dclowd9901/json-to-stringsdict
  • Zaphod
    Zaphod almost 9 years
    @dclowd9901 You also need to create the Localizable.strings file alongside the .stringsdict to have the pluralization work (even if it's empty). @Alp Maybe you could add it to your answer.