Slide 1

Slide 1 text

Localization Practicum Ali Rantakari iOS Helsinki Dev Meetup February 9, 2012

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Localization Basics Very Quick Intro

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Musings on Best Practices

Slide 10

Slide 10 text

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):

Slide 11

Slide 11 text

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"

Slide 12

Slide 12 text

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!

Slide 13

Slide 13 text

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!

Slide 14

Slide 14 text

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.”

Slide 15

Slide 15 text

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")

Slide 16

Slide 16 text

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")

Slide 17

Slide 17 text

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]

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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:

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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?

Slide 25

Slide 25 text

Tips and Tricks

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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.

Slide 32

Slide 32 text

Localization Practicum · Ali Rantakari, Futurice · iOS Helsinki Meetup, Feb 9, 2012 NSShowNonLocalizedStrings

Slide 33

Slide 33 text

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 (executable, loaded).

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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]; }

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

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.

Slide 39

Slide 39 text

I’m finished