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

Localizing Mobile Apps

Localizing Mobile Apps

English Slides for my talk originally given at Developer Week 2014 in Nuremberg, Germany. It covers internationalization and localization of mobile applications, using iOS as an example platform. I look at text, numbers, date and time, images and other resources, using correct plural forms and more.

There is an example application available on GitHub: https://github.com/dschneller/I18N-Example

Daniel Schneller

July 16, 2014
Tweet

More Decks by Daniel Schneller

Other Decks in Technology

Transcript

  1. Agenda I18N vs. L10N Languages and Regions Text Date and

    Time Numbers Images and other Resources https://github.com/dschneller/I18N-Example
  2. Internationalization […] process of designing a software application so that

    it can potentially be adapted to various languages and regions without engineering changes. […]* * Wikipedia
  3. Localization […] the process of adapting internationalized software for a

    specific region or language by adding locale-specific components and translating text. […]* * Wikipedia
  4. Languages and Regions Language ≠ Region 12h time used by

    an American living in Germany „Jänner“ – „Januar“ [German in Austria vs. in Germany for January] Localization ≠ Locale German user might prefer English user interface texts But: 24h time display
  5. NSLocale Encapsulates Region Settings + (id)autoupdatingCurrentLocale
 + (id)currentLocale NSCurrentLocaleDidChangeNotification [[NSNotificationCenter

    defaultCenter]
 addObserver:self
 selector:@selector(localeDidChange:)
 name:NSCurrentLocaleDidChangeNotification
 object:nil] Refresh formatters, caches NSLocale instances Refresh screen content
  6. NSLocale -[NSLocale objectForKey:] NSString* NSLocaleIdentifier NSString* NSLocaleMeasurementSystem NSString* NSLocaleLanguageCode NSString*

    NSLocaleDecimalSeparator NSString* NSLocaleCountryCode NSString* NSLocaleGroupingSeparator NSString* NSLocaleScriptCode NSString* NSLocaleCurrencySymbol NSString* NSLocaleVariantCode NSString* NSLocaleExemplarCharacterSet NSString* NSLocaleCurrencyCode NSString* NSLocaleCollatorIdentifier NSString* NSLocaleCalendar NSString* NSLocaleQuotationBeginDelimiterKey NSString* NSLocaleCollationIdentifier NSString* NSLocaleQuotationEndDelimiterKey NSString* NSLocaleUsesMetricSystem
  7. NSLocale Does not contain the current language! Locale ≠ Localization

    [NSBundle mainBundle].localizations; // NSArray, (all) [NSBundle mainBundle].preferredLocalizations[0]; // (current) Caveats Localizations of InfoPlist.strings determine content of „localizations“ Intersected with user’s region preferences in Settings.app AppleLanguages launch parameter
  8. Text ≠ Text Language of messages, buttons, labels etc. Writing

    direction & alignment Writing systems (Latin, Hebrew, Arabic…) Numbers in text Names & addresses Phrases, idioms and terminology Plurals Sorting …
  9. Text ≠ Text Language of messages, buttons, labels etc. Writing

    direction & alignment Writing systems (Latin, Hebrew, Arabic…) Numbers in text Names & addresses Phrases, idioms and terminology Plurals Sorting …
  10. Base Localization One leading (base) language Add Localization “.strings” files

    genstrings ibtool One copy per language “.lproj” Ordner
  11. Base Localization … /* Class = "IBUILabel"; text = "Loaded

    by SecondViewController"; ObjectID = "NDk-cv-Gan"; */ "NDk-cv-Gan.text" = "Loaded by SecondViewController"; " /* Class = "IBUILabel"; text = "First View"; ObjectID = "KQZ-1w-vlD"; */ "KQZ-1w-vlD.text" = "First View"; …
  12. NSLocalizedString Example [button setTitle:NSLocalizedString(@"reset.counter.button.title", 
 @"Reset Counter action button")
 forState:…];

    genstrings — Localizable.strings /* Reset Counter action button */
 "reset.counter.button.title" = "Zurücksetzen";
  13. .strings Files Updates? Use ibtool every time you update your

    labels and text.
 In the Base.lproj folder:
 
 ibtool ChangedNib.xib --generate-strings- file NewStrings.strings
 
 Open the generated output file and copy all new string entries to ChangedNib.strings in each lproj. #fail
  14. Do not normalize text DRY! — Do Repeat Yourself [NSString

    stringWithFormat:@”…”] Localizable.strings “count” = “Anzahl” “game” = “Spiel” “reset” = “zurücksetzen” “save” = “sparen” “thumbnail” = “Daumennagel”
  15. Do not normalize text DRY! — Do Repeat Yourself [NSString

    stringWithFormat:@”…”] Localizable.strings “count” = “Anzahl” “game” = “Spiel” “reset” = “zurücksetzen” “save” = “sparen” “thumbnail” = “Daumennagel” “reset count” ”zurücksetzen Anzahl” “save game” “sparen Spiel” …
  16. Do not normalize text Context and grammar are lost Structure

    of sentences Declination & conjugation …
  17. Do not normalize text Context and grammar are lost Structure

    of sentences Declination & conjugation … Use dedicated strings per use case
  18. Do not normalize text Context and grammar are lost Structure

    of sentences Declination & conjugation … Use dedicated strings per use case “reset.counter.button” = “Auf 0 stellen”
  19. Do not normalize text Context and grammar are lost Structure

    of sentences Declination & conjugation … Use dedicated strings per use case “reset.counter.button” = “Auf 0 stellen” Context for translators
  20. Do not normalize text Context and grammar are lost Structure

    of sentences Declination & conjugation … Use dedicated strings per use case “reset.counter.button” = “Auf 0 stellen” Context for translators Use self explanatory keys
  21. Do not normalize text Context and grammar are lost Structure

    of sentences Declination & conjugation … Use dedicated strings per use case “reset.counter.button” = “Auf 0 stellen” Context for translators Use self explanatory keys Provide useful comments (button vs. label, approx. length, etc.)
  22. Variables Document parameter order! “Zeige 4 von 12 gesamt” “Total

    12 — Showing 4” /* … Param 1: current page; Param 2: total count */
 "current.page.label" = “Zeige %1d von %2d gesamt”;
 
 /* … Param 1: current page; Param 2: total count */
 "current.page.label" = “Total %2d — Showing %1d”;
  23. Special cases: 0 and 1 Plurals Englisch German 0 No

    books Keine Bücher 1 One book Ein Buch sonstiges 100 books 100 Bücher Localizable.strings “books.0” = “No Books” “books.1” = “One Book” “books.n” = “%1d books” “books.0” = “Keine Bücher” “books.1” = “Ein Buch” “books.n” = “%1d Bücher” If-else-clause in the code: Problem solved.
  24. Englisch Deutsch 0 No books Keine Bücher 1 1 book

    1 Buch Vielleicht nicht ganz… Plural
  25. Englisch Deutsch 0 No books Keine Bücher 1 1 book

    1 Buch 2 2 books 2 Bücher wenige 3 books 3 Bücher viele 11 books 11 Bücher sonstiges 100 books 100 Bücher Vielleicht nicht ganz… Plural
  26. 0 1 2 wenige viele sonstiges Englisch No books 1

    book 2 books 3 books 11 books 100 books German Keine Bücher 1 Buch 2 Bücher 3 Bücher 11 Bücher 100 Bücher Or is it…? Plural
  27. 0 1 2 wenige viele sonstiges Englisch No books 1

    book 2 books 3 books 11 books 100 books German Keine Bücher 1 Buch 2 Bücher 3 Bücher 11 Bücher 100 Bücher Arabic باتك ٠ باتك ناباتك بتك ٣ اًباتك ١١ باتك ١٠٠ Or is it…? Plural
  28. .strings + .stringsdict Available since iOS7 Implements (Unicode*) localization rules

    for Plural Gender [rdar://16670931] * http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar
  29. .strings + .stringsdict Available since iOS7 Implements (Unicode*) localization rules

    for Plural Gender [rdar://16670931] Plist Format Name must match .strings .strings must exist, but may be empty * http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar
  30. .strings + .stringsdict https://developer.apple.com/library/ios/releasenotes/Foundation/↩︎ RN-Foundation/index.html#//apple_ref/doc/uid/TP30000742-CH2-SW56 <dict>
 <key>files.selected.label.%d</key>
 <dict>
 <key>NSStringLocalizedFormatKey</key>
 <string>%#@num_files_are@

    selected</string>
 <key>num_files_are</key>
 <dict>
 <key>NSStringFormatSpecTypeKey</key>
 <string>NSStringPluralRuleType</string>
 <key>NSStringFormatValueTypeKey</key>
 <string>d</string>
 <key>zero</key> <string>No file is</string>
 <key>one</key> <string>A file is</string>
 <key>other</key> <string>%d files are</string>
 </dict>
 </dict>
 </dict>
  31. .stringsdict Categories per language according to Unicode iOS additionally supports

    „zero“ category for all languages Others, e. g. „few“, depend on language
  32. String Tables Localizable.strings Default used by NSLocalizedString() Big projects —

    big file Can be split NSLocalizedStringFromTable(“MyController”, “Key”, “Comment”) MyController.strings Project structure — Context for translators 3rd party code Also works for .stringsdict
  33. Date and Time Transforms between NSDate and NSString +[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:]


    -[NSDateFormatter setLocale:] NSDateFormatterStyle German / Germany Englisch / USA NSDateFormatter
  34. Date and Time Transforms between NSDate and NSString +[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:]


    -[NSDateFormatter setLocale:] NSDateFormatterStyle German / Germany Englisch / USA Short 08.07.14 22:32 7/8/14,10:32 PM NSDateFormatter
  35. Date and Time Transforms between NSDate and NSString +[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:]


    -[NSDateFormatter setLocale:] NSDateFormatterStyle German / Germany Englisch / USA Short 08.07.14 22:32 7/8/14,10:32 PM Medium 08.07.2014 22:32:36 Jul 8, 2014, 10:32:36 PM NSDateFormatter
  36. Date and Time Transforms between NSDate and NSString +[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:]


    -[NSDateFormatter setLocale:] NSDateFormatterStyle German / Germany Englisch / USA Short 08.07.14 22:32 7/8/14,10:32 PM Medium 08.07.2014 22:32:36 Jul 8, 2014, 10:32:36 PM Long 8. Juli 2014 22:32:36 MESZ Jul 8, 2014, 10:32:36 PM GMT+2 NSDateFormatter
  37. Date and Time Transforms between NSDate and NSString +[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:]


    -[NSDateFormatter setLocale:] NSDateFormatterStyle German / Germany Englisch / USA Short 08.07.14 22:32 7/8/14,10:32 PM Medium 08.07.2014 22:32:36 Jul 8, 2014, 10:32:36 PM Long 8. Juli 2014 22:32:36 MESZ Jul 8, 2014, 10:32:36 PM GMT+2 Full Dienstag, 8. Juli 2014 12:32:36 Mitteleuropäische Sommerzeit Tuesday, July 8, 2014 at 12:39:16 PM Central European Summer Time NSDateFormatter
  38. Date and Time Transforms between NSDate and NSString +[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:]


    -[NSDateFormatter setLocale:] NSDateFormatterStyle German / Germany Englisch / USA Short 08.07.14 22:32 7/8/14,10:32 PM Medium 08.07.2014 22:32:36 Jul 8, 2014, 10:32:36 PM Long 8. Juli 2014 22:32:36 MESZ Jul 8, 2014, 10:32:36 PM GMT+2 Full Dienstag, 8. Juli 2014 12:32:36 Mitteleuropäische Sommerzeit Tuesday, July 8, 2014 at 12:39:16 PM Central European Summer Time No - - NSDateFormatter
  39. Date and Time NSDateFormatter.h typedef enum {
 NSDateFormatterNoStyle = …,


    NSDateFormatterShortStyle = …,
 NSDateFormatterMediumStyle = …,
 NSDateFormatterLongStyle = …,
 NSDateFormatterFullStyle = …
 } NSDateFormatterStyle Combine for date and time portions NSDateFormatterNoStyle — only date / time Get updated with the OS NSDateFormatter
  40. Date and Time What if defaults are not suitable? NSDateFormatter

    * http://www.unicode.org/reports/tr35/tr35-dates.html#Contents
  41. Date and Time What if defaults are not suitable? Do

    not use hard coded format strings! NSDateFormatter * http://www.unicode.org/reports/tr35/tr35-dates.html#Contents
  42. Date and Time What if defaults are not suitable? Do

    not use hard coded format strings! Unicode Locale Data Markup Language* NSDateFormatter * http://www.unicode.org/reports/tr35/tr35-dates.html#Contents
  43. Date and Time What if defaults are not suitable? Do

    not use hard coded format strings! Unicode Locale Data Markup Language* [f setDateFormat:[NSDateFormatter
 dateFormatFromTemplate:@"u QQ" 
 options:0
 locale:LOCALE_EN_US]];
 
 NSLog(@“%@", [f stringFromDate:july8th); NSDateFormatter * http://www.unicode.org/reports/tr35/tr35-dates.html#Contents
  44. Date and Time What if defaults are not suitable? Do

    not use hard coded format strings! Unicode Locale Data Markup Language* [f setDateFormat:[NSDateFormatter
 dateFormatFromTemplate:@"u QQ" 
 options:0
 locale:LOCALE_EN_US]];
 
 NSLog(@“%@", [f stringFromDate:july8th); Q3 2014 NSDateFormatter * http://www.unicode.org/reports/tr35/tr35-dates.html#Contents
  45. Relative formatting [formatter setDoesRelativeDateFormatting:YES];
 
 NSLog(@“%@, %@, %@…", [formatter stringFromDate:GESTERN],


    [formatter stringFromDate:HEUTE],
 [formatter stringFromDate:MORGEN]);
 Gestern, Heute, Morgen… [de_DE] Yesterday, Today, Tomorrow… [en_US] Yesterday, Today, Tomorrow…
  46. NSDateComponentsFormatter Formats durations [f stringFromTimeInterval:1234.0]) // seconds About 20 minutes

    remaining NSDateIntervalFormatter Formats time intervals [f stringFromDate:NOW toDate:LATER]) 09.07.14 12:10-13:13 Coming up next: iOS8
  47. Numbers NSNumberFormatter German / Germany English / USA No 1234,56

    1234.56 Decimal 1.234,56 1,234.56 Currency 1.234,56 € $1,234.56 Percent 123.456% 123,456% Scientific 1,23456E+03 1.23456E3 SpellOut eintausendzweihundertvier- unddreißig Komma fünf sechs one thousand two hundred thirty- four point five six
  48. Numbers NSNumberFormatter.h enum {
 NSNumberFormatterNoStyle = …,
 NSNumberFormatterDecimalStyle = …,


    NSNumberFormatterCurrencyStyle = …,
 NSNumberFormatterPercentStyle = …,
 NSNumberFormatterScientificStyle = …,
 NSNumberFormatterSpellOutStyle = …
 }
 typedef NSUInteger NSNumberFormatterStyle
  49. Numbers NSNumberFormatter* f = [[NSNumberFormatter alloc] init];
 f.numberStyle = NSNumberFormatterNoStyle;

    f.locale = LOCALE_DE_AT;
 NSLog(@"NoStyle de_AT: %@", [f stringFromNumber:@(1234.56)]); f.locale = LOCALE_EN_US;
 f.maximumFractionDigits = 2;
 NSLog(@“NoStyle en_US: %@", [f stringFromNumber:@(1234.56)]); NSNumberFormatter
  50. Numbers NSNumberFormatter* f = [[NSNumberFormatter alloc] init];
 f.numberStyle = NSNumberFormatterNoStyle;

    f.locale = LOCALE_DE_AT;
 NSLog(@"NoStyle de_AT: %@", [f stringFromNumber:@(1234.56)]); f.locale = LOCALE_EN_US;
 f.maximumFractionDigits = 2;
 NSLog(@“NoStyle en_US: %@", [f stringFromNumber:@(1234.56)]); NoStyle de_AT: 1235 NoStyle en_US: 1234.56 NSNumberFormatter
  51. Data Volume NSByteCountFormatter File size, amount of memory Picks suitable

    unit automatically Non-numeric display Allow Non-Numeric Zero KB File 1,234.57 GB Memory 1,149.78 GB
  52. Coming up next: iOS8 NSEnergyFormatter Energy in joule, calories etc.

    NSLengthFormatter Distance, in miles, kilometers etc. NSMassFormatter Mass and weight in pounds, kg etc.
  53. Launch Image Disable asset catalogue Launch images in .lproj folders

    Follows usual naming conventions for Retina Orientation iPhone vs. iPad
  54. Cover the Basics Locale based formatting NSDateFormatter NSNumberFormatter NSByteCountFormatter Careful

    and diligent translation Remember resources besides the source code
  55. Next Steps Images resources Launch Images (if relevant) Addresses, names

    Right-to-left support Location based pre-selection of units (miles, km etc.) Pre-selection for pickers, options etc. Color scheme …
  56. Summary Initialer effort can be significant Full translation Code refactoring

    Create workflows and pick tools Ongoing maintenance rather easy Consider relevance of individual measures for your audience