Search code examples
iphoneioslocalizationinternationalizationlocalizable.strings

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


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?


Solution

  • 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).