Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Localization Practicum

Ali Rantakari
February 13, 2012

Localization Practicum

Presentation on iOS localization from the HelsinkiOS developer meetup on February 9, 2012 — http://lanyrd.com/2012/helsinkios-february/

You can find the shell script the slides refer to from here: https://github.com/hasseg/cocoa-l10n-tools/blob/master/genstrings.sh

Ali Rantakari

February 13, 2012
Tweet

More Decks by Ali Rantakari

Other Decks in Programming

Transcript

  1. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Why Talk About L10N • Apple’s documentation is a little thin on best practices • No WWDC videos on the topic • Bad early decisions can be (costly|annoying) to fix later
  2. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Not the Final Word! • I’m not an expert on this topic, but I wanted to: • Force myself to more closely evaluate my current approach • Learn more • Encourage discussion on the topic
  3. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Contents • Basics (very quickly) • Musings on best practices • Tips and tricks • Some 3rd-party tools
  4. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Not Contents • Other internationalization topics • See the WWDC 2010 video for more info on I18N
  5. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Quick L10N Intro • Localization bundles (en.lproj etc.) • .strings files, NIBs, images, sounds… • Native development region • Macros: NSLocalizedString and friends • Expand to [[NSBundle mainBundle] localizedStringForKey:value:table:] • The genstrings tool • genstrings -o en.lproj *.m • The ibtool tool • ibtool --generate-strings-file MainView.strings MainView.xib Quick Intro to L10N
  6. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Quick L10N Intro Quick Intro to L10N NSLocalizedString(@"Log in", nil) /* Label for the main page login button */ "Log in" = "Log in" genstrings ibtool .strings files: /* Label for the main page login button */ "Log in" = "Kirjaudu sisään" Translated .strings files --^ ibtool Localized copies of your .xibs: Project locale bundles Translators
  7. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Key = Default Value? NSLocalizedString(@"Log in", nil) If localized string for key is not found, returns key Typical usage according to the intarwebs (including Apple’s documentation):
  8. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Key = Default Value? • Benefits: • It’s easy • It works • Translated .strings files will look like this: instead of this: /* Label for the main page login button */ "Log in" = "Kirjaudu sisään" /* Label for the main page login button */ "LoginButtonLabel" = "Kirjaudu sisään"
  9. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Key ≠ Default Value? • Problems: • Makes all translations dependent on base language strings • Not a problem in practice: when you change a base language string, you most likely also want to change the translations to match!
  10. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Key ≠ Default Value? • Problems: • Breaks down as soon as the same string needs to be translated in two or more different ways in different places /* Weapon that shoots arrows */ /* A tied ribbon */ /* The front of a ship */ "Bow" = "Bow" Translate that, please!
  11. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Key ≠ Default Value? • Homonyms: • “wind” (what you do to a clock) vs. “wind” (moving air) • “ass” (the animal; donkey) vs. “ass” (a person’s posterior) • More specific translations: • “New York on kaupunki. Perähikiä on kaupunki.” • “New York is a city. Perähikiä is a town.”
  12. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Key = Default Value? • Solution #1 • Use explicit, unique keys: NSLocalizedStringWithDefaultValue( @"MainViewGreeting", nil, [NSBundle mainBundle], @"Hello", @"Greeting in main view") NSLocalizedStringWithDefaultValue( @"PackagingOptionBow", nil, [NSBundle mainBundle], @"Bow", @"Bow packaging option in order view") NSLocalizedStringWithDefaultValue( @"ProductWeaponBow", nil, [NSBundle mainBundle], @"Bow", @"Product: weapon that shoots arrows")
  13. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Key = Default Value? • Solution #2 • By default, use keys as default values: • But handle conflicts (homonyms etc.) by using an explicit default value: NSLocalizedString(@"Hello", @"Greeting in main view") NSLocalizedStringWithDefaultValue( @"Bow (tied ribbon)", nil, [NSBundle mainBundle], @"Bow", @"Bow packaging option in order view") NSLocalizedStringWithDefaultValue( @"Bow (weapon)", nil, [NSBundle mainBundle], @"Bow", @"Product: weapon that shoots arrows")
  14. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Use Format Strings • Do not hardcode sentence structure: • “Aaseja oli 13” = “There were 13 donkeys” • Instead of e.g. this: do this: [NSLocalizedString(@"Aaseja oli ", nil) stringByAppendingString:numAsses] [NSString stringWithFormat: NSLocalizedString(@"Aaseja oli %@", nil), numAsses]
  15. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 No Vars in L10N Macros • Unless you have a good reason to do otherwise, use only string literals as arguments to NSLocalizedString and friends • This will work at runtime… • …but genstrings will choke on them. • If you really need to determine a L10N key dynamically, you can add all the possible values as dummy NSLocalizedString entries into your sources • Apple does/did this with TextEdit
  16. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Text in Images? • Unless absolutely necessary for some reason, avoid putting text into images • Maintenance nightmare: “where is that PSD file again…” translator: “I don’t have Photoshop” • Instead just use UILabels or draw the text into the view
  17. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Localize NIBs? • Whatever you do, you do not want to manually maintain localized .xibs! • Maintenance nightmare: Localize app to N languages → every time you change something in a .xib, you now have N other .xibs to make exactly the same changes in! • It is best to have one version of each .xib, with labels sized according to the longest translation
  18. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Localize NIBs? • Options for localizing text in your NIBs: 1. Use ibtool to generate localized copies automatically 2. Just add IBOutlets and localize from code 3. Use Wil Shipley’s NSBundle category* that localizes strings in NIBs on the fly when they are loaded, from .strings files * http://wilshipley.com/blog/ 2009/10/pimp-my-code- part-17-lost-in.html
  19. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Consider Plurals • Most languages have the same three cases to consider: Zero no documents Singular one document Plural %@ documents English 1 file 2 files 5 files Russian 1 fail 2 faila 5 failov • However, many languages have more than one plural form:
  20. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Consider Plurals • In fact, it’s a bit complicated: • Latvian has a specific grammatical number, the nullar, for the "n = 0" case. • Dhivehi, Inuktitut, Irish, Maori, and a few other languages have a dual form for the "n = 2" case. • Czech, Slovak, Lithuanian, and Macedonian have a dual, but they use it according to more complex rules. • Slovenian has a trial in addition to the singular, dual, and plural forms. • Romanian handles the "n >= 20" case differently from the "n < 20" case. • Arabic has six different forms, depending on the value of n. • Chinese, Japanese, Korean, and many other languages don't distinguish between the singular and the plural. Source: http://doc.qt.nokia.com/qq/qq19-plurals.html
  21. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Consider Plurals • Easy solution: cheat (Apple does!) English 1 song %@ songs Russian 1 песня (1 song) песни: %@ (songs: %@) • No turnkey solution for this for Cocoa?
  22. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Verbosity Makes Baby Jesus Cry • This is not so bad: • But this … is: NSLocalizedStringWithDefaultValue(@"LoginButtonLabel", nil, [NSBundle mainBundle], @"Log in", @"Label for the main page login button") NSLocalizedString(@"LoginButtonLabel", @"Label for the main page login button") Fighting Verbosity
  23. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Macros to the Rescue // Localization.h #define LOC NSLocalizedString #define LOC_DEF(__key, __defvalue, __comment) \ NSLocalizedStringWithDefaultValue(\ (__key), nil, [NSBundle mainBundle],\ (__defvalue), (__comment)) #define LOC_F(__key, __comment, ...) \ [NSString stringWithFormat:\ LOC(__key, __comment), __VA_ARGS__] #define LOC_DEF_F(__key, __defvalue, __comment, ...) \ [NSString stringWithFormat:\ LOC_DEF(__key, __defvalue, __comment), __VA_ARGS__] Fighting Verbosity
  24. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Which is Nicer? NSLocalizedStringWithDefaultValue(@"LoginButtonLabel", nil, [NSBundle mainBundle], @"Log in", @"Label for the main page login button") LOC_DEF(@"LoginButtonLabel", @"Log in", @"Label for the main page login button") 59 boilerplate chars 9 boilerplate chars Fighting Verbosity
  25. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Ease of Formatting • LOC_F() helps use localized format strings (LOC_DEF_F() is the same thing, just with an explicit default value): LOC_F(@"Hello %@, today is %@", @"Greeting with name+date", name, date) Fighting Verbosity
  26. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 The Price One Pays • genstrings does not understand these custom macros • Need a helper script to enable it to go through your code: 1. Copies sources (*.{m,mm,h}) to temp dir 2. Expands the LOC macros into NSLocalizedString and friends in the temp copies of the source files 3. Runs genstrings on the modified temp files 4. (would be nice:) merges output with existing .strings file Fighting Verbosity
  27. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 NSShowNonLocalizedStrings Using the user default NSShowNonLocalizedStrings, you can alter the behavior of localizedStringForKey:value:table: to log a message when the method can’t find a localized string. If you set this default to YES (in the global domain or in the application’s domain), then when the method can’t find a localized string in the table, it logs a message to the console and capitalizes key before returning it.
  28. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 NSShowNonLocalizedStrings 2012-02-07 21:31:59.747 LocalizationTest[43897:f803] Localizable string "My View Title" not found in strings table "Localizable" of bundle CFBundle 0x687c8c0 </Users/alir/ Library/Application Support/iPhone Simulator/5.0/ Applications/C8F78D48-8ADA-4AE7-AA23-B88C1858C023/ LocalizationTest.app> (executable, loaded).
  29. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Select Language at Runtime • You might need to set your app’s language during runtime, e.g. based on the response from a back-end server • Setting the user default AppleLanguages is not a good solution, as it is hackish and requires rebooting the app
  30. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 Select Language at Runtime A Solution in a nutshell: static NSString *_languageCode = nil; #define LOC(__key, __descr) \ myPrefix_getLocalizedStringForLanguage((__key), (__key), _languageCode) NSString *myPrefix_getLocalizedStringForLanguage(NSString *key, NSString *defaultValue, NSString *languageCode) { NSBundle *bundle = [NSBundle bundleWithPath: [NSBundle.mainBundle pathForResource:languageCode ofType:@"lproj"]]; if (bundle == nil) return defaultValue; return [bundle localizedStringForKey:key value:defaultValue table:nil]; }
  31. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 3rd Party Tools • Twine (released yesterday!): http://www.mobiata.com/blog/ 2012/02/08/twine-string- management-ios-mac-os-x • Cmd-line tool that generates .strings files from a single text file with all of your translations in one place [yes] en = Yes da = Ja de = Ja es = Sí fr = Oui ja = ͸͍ ko =  [no] en = No da = Nej de = Nein fr = Non ja = ͍͍͑ ko =  [path_not_found_error] en = File '%@' could not be found. comment = An error describing... [network_unavailable_error] en = The network is unavailable. comment = An error describing...
  32. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 3rd Party Tools • diffstrings.py from the Three20 library: https://github.com/facebook/three20/blob/master/diffstrings.py • Cmd-line tool that compares your primary locale with all your other locales to help you determine which new strings need to be translated. It outputs XML files which can be translated, and then merged back into your strings files.
  33. Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup,

    Feb 9, 2012 3rd Party Tools • A bunch of GUI apps (of seemingly varying quality) in the Mac App Store Linguan Localization Helper e.g.