In my iPhone app, I need to display object counts which I then localize, since English makes the distinction of singular and plural, I do the following
// pseudocode
I just posted JJPluralForm, an adaptation for Mozilla's PluralForm.
With that, you wouldn't have to worry about doing all the if-else
, switch-case
code to do localizing in your code, which becomes impossible to maintain as your number of localizations grow.
Something like the example you gave could be handled with:
[[JJPluralForm sharedManager] pluralStringForNumber:numberOfObjects
withPluralForms:NSLocalizedString(@"N_OBJECTS_PLURAL_STRING", @"")
localizeNumeral:YES];
Each Localizable.strings file then localize N_OBJECTS_PLURAL_STRING
as a semicolon separated list of plural forms. For English, that would be "%@ object;%@ objects"
.
Check out the project for more details.
NSLocalizedString is going to be reading from a string table in your app bundle. Therefore, the list of languages you need to support is known at compile time. Rather than worry about how to code for every possible language, just support the ones you are supporting.
If your translator comes to you and says that, to support Martian, you need a separate spelling for even and odd numbers, you can adjust your code then, to something like:
if (objectList.count == 1) {
NSLocalizedString(@"ObjectCount1", @"display one");
} else if (objectList.count % 2 == 0) {
NSLocalizedString(@"ObjectCountEven", @"display even");
} else if (objectList.count % 2 == 0) {
NSLocalizedString(@"ObjectCountOdd", @"display odd");
}
en.lproj/Localizable.strings:
ObjectCount1 = "1 object";
ObjectCountEven = "%d objects";
ObjectCountOdd = "%d objects"; // same as ObjectCountEven
mars.lproj/Localizable.strings:
ObjectCount1 = "1 object-o";
ObjectCountEven = "%d object-e";
ObjectCountOdd = "%d object-o"; // same as ObjectCount1
Sorry if this sounds less than ideal, but human languages are messy and irregular, so it's a waste of time to try to find an elegant, unified, common solution for them all. There isn't one.
Smartling has released an open source framework to support plurals following the CLDR standard.
https://github.com/Smartling/ios-i18n
To use it you add a special marker in your keys that follow CLDR style labeling and call SLPluralizedString instead of NSLocalizedString. At runtime the library will pick the right string to use based on the language the app is currently running in.
So your English .strings file would have:
"%@ apples##{one}" = "One apple";
"%@ apples##{other}" = "%@ apples";
The Russian .strings file:
"%@ apples##{one}" = "%@ яблоко";
"%@ apples##{few}" = "%@ яблока";
"%@ apples##{many}" = "%@ яблок";
"%@ apples##{other}" = "%@ яблока";
and the code to call it could look like:
NSString *s2 = [NSString stringWithFormat:SLPluralizedString(@"%@ apples", number, nil), number];
Note this example demonstrates one of the great things about this library - it lets your write your original strings or translations with expressive language that might not even use the format specifier.
At this writing I am a Product Manager with Smartling.
There is an even more powerful solution to this problem. Take a look at the Tr8n library from TranslationExchange.com
https://github.com/tr8n/tr8n_objc_clientsdk
The library uses TranslationExchange's TML (Translation Markup Language), which makes internationalization process very easy. First of all, you don't even need to deal with Strings XML files ever again, really... - the Tr8n SDK will create and manage your String files for you on the fly - you will never look at them again.
Your particular example would simply be:
Tr8nLocalizedStringWithTokens(@"{count || object}", @{@"count": objectList.count})
Tr8n library will automatically pick the right plural form for you in any language. Hah? Yes, it is magical. The full form of the above token example is actually:
{count:number || one: object, other: objects}
That means that "count" token is of a numeric type that is mapped to English plurals using keywords "one" and "other".... But Tr8n is smart enough to not have you type all that. It is also smart enough to map the sequence of parameters to the appropriate rule values as well. And, of course, it knows that "count" is associated with the numeric rules through naming convention. So it simply becomes:
{count || object}
Btw, since you've mentioned Russian, the Russian translation for the above would simply be:
"{count || object}" = "{count || объект, объекта, объектов}"
This example was too easy, let's take a look at a more interesting one:
Tr8nLocalizedStringWithTokens(
@"{user} uploaded {count || photo} to {user | his, her} photo album.",
@{@"user": user, @"count": 5}
)
First of all, good luck translating this sentence using the standard iOS i18n library (or any other library for that matter)... it's a joke - but really, there is no way to do it using anything, but Tr8n.
The above TML translated to Russian would simply be:
@"{user || загрузил, загрузила} {count || фотографию, фотографии, фотографий} в свой фотоальбом."
Here we deal with gender rules the same way we deal with numeric rules. But instead of "one", "few", "other", we have "male", "female", "unknown" - well, each language may have different gender and numeric rules. Tr8n will deal with it, so you don't have to.
Alright, let's take it to the next level. You have decided that you must have the number of photos to be bold. Piece of cake.
Tr8nLocalizedAttributedStringWithTokens(
@"{user} uploaded [bold: {count || photo}] to {user | his, her} photo album.",
@{
@"user": user,
@"count": 5,
@"bold": @{@"font":@{@"name": @"system", @"size": @12, @"type": @"bold"}}
}
)
[bold: ... ] is a decoration token. Did you notice that we switched the macro to the AttributedString version? This macro would actually produce an NSAttributedString using the native decoration mechanism of iOS. Can you guess what the translation in Russian would be?
@"{user || загрузил, загрузила} [bold: {count || фотографию, фотографии, фотографий}] в свой фотоальбом."
Btw, you can predefine all your decoration tokens elsewhere, so you don't have to keep defining them every time.
Let's do just one more final example... Say, you have a newsfeed story in the following form:
Tr8nLocalizedAttributedStringWithTokens(
@"{actor} sent {target} [bold: {count || gift}].",
@{
@"actor": user1,
@"target": user2,
@"count": 5
}
)
It doesn't look interesting in English. But it does in Russian or any other languages that supports language cases. The name of {target}, if it happens to be in Russian, will actually need to use Russian Dative Language Case.
http://en.wikipedia.org/wiki/Dative_case
If you don't speak Russian, you probably don't need to know about it. But your Russian translators should. Let's see the Russian translation then:
@"{actor || подарил, подарила} {target::dat} [bold: {count || подарок, подарка, подарков}].",
Tr8n is smart enough to use its powerful language rules engine and apply the Dative language case to the Russian names passed through the {target} token...
That was a bit of a long answer for a simple numeric question. Thank you for reading it that far. Hope it helps.
Disclaimer: I am the creator of Tr8n framework and TML language. If you have any questions, please ping me and I would love to help you out with all your translation questions.
By the way, starting in iOS 7 and Mac OS X 10.9, there is built-in support for plurals in localization. See the WWDC 2013 video "Making Your App World-Ready" starting at 16:10. Plurals are localized in a .stringsdict plist file, and then you just do
NSLog(@"%@", [NSString localizedStringWithFormat:
NSLocalizedString(@"%d objects", @"display multiple objects"), n]);