Localizing Mobile AppsDaniel Schneller, CenterDevice GmbH
AgendaI18N vs. L10N
Languages and Regions
Text
Date and Time
Numbers
Images and other Resources
https://github.com/dschneller/I18N-Example
I18N vs. L10N
I18N
L10N
I — 18 Characters — N
L-10 Characters-N
InternationalizationI — 18 Characters — N
LocalizationL-10 Characters-N
Internationalization
Internationalization[…] process of designing a software application so that it
can potentially be adapted to various languages andregions without engineering changes. […]*
* Wikipedia
Localization
Localization[…] the process of adapting internationalized software fora specific region or language by adding locale-specific
components and translating text. […]*
* Wikipedia
I18N L10NApp LocalizedApp
!"#$%&!
! 🌐 🌐 !"#
$%&
Languages and Regions
Languages and RegionsLanguage ≠ 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
Languages and Regions
Languages and Regions
Languages and Regions
Languages and RegionsScheme Launch Arguments
-AppleLanguages (ar,de,fr)
Order determines preference
-AppleLocale en_US
NSLocaleEncapsulates 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
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
NSLocaleDoes 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
Text
Text ≠ TextLanguage of messages, buttons, labels etc. Writing direction & alignment Writing systems (Latin, Hebrew, Arabic…) Numbers in text Names & addresses Phrases, idioms and terminology Plurals Sorting …
Text ≠ TextLanguage of messages, buttons, labels etc. Writing direction & alignment Writing systems (Latin, Hebrew, Arabic…) Numbers in text Names & addresses Phrases, idioms and terminology Plurals Sorting …
Base Localization
Base LocalizationOne leading (base) language
Base LocalizationOne leading (base) language
Add Localization
Base LocalizationOne leading (base) language
Add Localization
Base LocalizationOne leading (base) language
Add Localization
“.strings” files
Base LocalizationOne leading (base) language
Add Localization
“.strings” filesgenstrings
Base LocalizationOne leading (base) language
Add Localization
“.strings” filesgenstrings
ibtool
Base LocalizationOne leading (base) language
Add Localization
“.strings” filesgenstrings
ibtool
One copy per language
Base LocalizationOne leading (base) language
Add Localization
“.strings” filesgenstrings
ibtool
One copy per language“.lproj” Ordner
Base LocalizationEine Leitsprache (Base)
“.strings” Files genstrings
ibtool
Eine Kopie pro Sprache “.lproj” Ordner
Base LocalizationEine Leitsprache (Base)
“.strings” Files genstrings
ibtool
Eine Kopie pro Sprache “.lproj” Ordner
Base LocalizationEine Leitsprache (Base)
“.strings” Files genstrings
ibtool
Eine Kopie pro Sprache “.lproj” Ordner
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";
…
Base LocalizationPreview in Assistant Editor
Xcode 6: Pick Language
Double-Length Pseudo-Language
Auto Layout
NSLocalizedStringFamily of macros
NSLocalizedStringNSLocalizedStringFromTableNSLocalizedStringFromTableInBundleNSLocalizedStringWithDefaultValue
Access “.strings” files
Utilize NSBundle -[[NSBundle mainBundle] localizedStringForKey:value:table:]
NSLocalizedStringExample
[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";
.strings FilesUpdates?
.strings FilesUpdates?
Use ibtool every time you update your labels and text.In the Base.lproj folder:ibtool ChangedNib.xib --generate-strings-file NewStrings.stringsOpen the generated output file and copy all new string entries to ChangedNib.strings in each lproj.
#fail
Do not normalize text
Do not normalize textDRY! — Do Repeat Yourself
[NSString stringWithFormat:@”…”]
Do not normalize textDRY! — Do Repeat Yourself
[NSString stringWithFormat:@”…”]
Localizable.strings“count” = “Anzahl”
“game” = “Spiel”
“reset” = “zurücksetzen”
“save” = “sparen”
“thumbnail” = “Daumennagel”
Do not normalize textDRY! — 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”
…
Do not normalize text
Do not normalize textContext and grammar are lost
Do not normalize textContext and grammar are lost
Structure of sentences
Do not normalize textContext and grammar are lost
Structure of sentences
Declination & conjugation …
Do not normalize textContext and grammar are lost
Structure of sentences
Declination & conjugation …
Use dedicated strings per use case
Do not normalize textContext and grammar are lost
Structure of sentences
Declination & conjugation …
Use dedicated strings per use case“reset.counter.button” = “Auf 0 stellen”
Do not normalize textContext and grammar are lost
Structure of sentences
Declination & conjugation …
Use dedicated strings per use case“reset.counter.button” = “Auf 0 stellen”
Context for translators
Do not normalize textContext and grammar are lost
Structure of sentences
Declination & conjugation …
Use dedicated strings per use case“reset.counter.button” = “Auf 0 stellen”
Context for translatorsUse self explanatory keys
Do not normalize textContext and grammar are lost
Structure of sentences
Declination & conjugation …
Use dedicated strings per use case“reset.counter.button” = “Auf 0 stellen”
Context for translatorsUse self explanatory keys
Provide useful comments (button vs. label, approx. length, etc.)
VariablesParameters
[NSString stringWithFormat: NSLocalizedString(@"click.counter.label", @"P1: Current Click Count."), self.clickCount]
"
/* P1: Current Click Count. */"click.counter.label" = "%1d x geklickt”;
VariablesDocument 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”;
Special cases: 0 and 1
Plurals
Englisch German
0 No books Keine Bücher1 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.
Englisch Deutsch
0 No books Keine Bücher1 1 book 1 Buch
Vielleicht nicht ganz…
Plural
Englisch Deutsch
0 No books Keine Bücher1 1 book 1 Buch2 2 books 2 Bücher
wenige 3 books 3 Bücherviele 11 books 11 Bücher
sonstiges 100 books 100 Bücher
Vielleicht nicht ganz…
Plural
0
1
2
wenige
viele
sonstiges
Englisch
No books1 book2 books3 books11 books100 books
German
Keine Bücher1 Buch
2 Bücher3 Bücher
11 Bücher100 Bücher
Or is it…?
Plural
0
1
2
wenige
viele
sonstiges
Englisch
No books1 book2 books3 books11 books100 books
German
Keine Bücher1 Buch
2 Bücher3 Bücher
11 Bücher100 Bücher
Arabic
٠ كتابكتابكتابان
٣ كتب١١ كتابًا١٠٠ كتاب
Or is it…?
Plural
.strings + .stringsdict
* http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar
.strings + .stringsdictAvailable since iOS7
* http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar
.strings + .stringsdictAvailable since iOS7
Implements (Unicode*) localization rules forPlural
Gender [rdar://16670931]
* http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar
.strings + .stringsdictAvailable since iOS7
Implements (Unicode*) localization rules forPlural
Gender [rdar://16670931]
Plist FormatName must match .strings
.strings must exist, but may be empty
* http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar
.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>
.stringsdictCategories per language according to Unicode
iOS additionally supports „zero“ category for all languages
Others, e. g. „few“, depend on language
String TablesLocalizable.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
Date and Time
Date and Time NSDateFormatter
Date and TimeTransforms between NSDate and NSString
+[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:] -[NSDateFormatter setLocale:]
NSDateFormatter
Date and TimeTransforms between NSDate and NSString
+[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:] -[NSDateFormatter setLocale:]
NSDateFormatterStyle
NSDateFormatter
Date and TimeTransforms between NSDate and NSString
+[NSDateFormatter localizedStringFromDate:dateStyle:timeStyle:] -[NSDateFormatter setLocale:]
NSDateFormatterStyle
German / Germany Englisch / USA
NSDateFormatter
Date and TimeTransforms 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
Date and TimeTransforms 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
Date and TimeTransforms 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
Date and TimeTransforms 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
Date and TimeTransforms 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
Date and TimeNSDateFormatter.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
Date and Time NSDateFormatter
* http://www.unicode.org/reports/tr35/tr35-dates.html#Contents
Date and TimeWhat if defaults are not suitable?
NSDateFormatter
* http://www.unicode.org/reports/tr35/tr35-dates.html#Contents
Date and TimeWhat if defaults are not suitable?
Do not use hard coded format strings!
NSDateFormatter
* http://www.unicode.org/reports/tr35/tr35-dates.html#Contents
Date and TimeWhat 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
Date and TimeWhat 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
Date and TimeWhat 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
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…
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
Numbers
Numbers
Numbers
“Plain” Numbers
Numbers
MassDistance
LengthCurrency
PercentageData Volume
WeightEnergy
Spelled Out
“Plain” Numbers
NumbersDecimal separators
Grouping characters
Currency symbols
…
"
NSNumberFormatter API similar to NSDateFormatter
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
NumbersNSNumberFormatter.h
enum { NSNumberFormatterNoStyle = …, NSNumberFormatterDecimalStyle = …, NSNumberFormatterCurrencyStyle = …, NSNumberFormatterPercentStyle = …, NSNumberFormatterScientificStyle = …, NSNumberFormatterSpellOutStyle = …} typedef NSUInteger NSNumberFormatterStyle
Numbers NSNumberFormatter
NumbersNSNumberFormatter* 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
NumbersNSNumberFormatter* 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
NumbersMany more customization options
maximumFractionDigits
usesSignificantDigits
minimum/maximumSignificantDigits
paddingCharacter
roundingMode, roundingIncrement
…
NSNumberFormatter
Data VolumeNSByteCountFormatter
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
Coming up next: iOS8NSEnergyFormatter
Energy in joule, calories etc.
NSLengthFormatter Distance, in miles, kilometers etc.
NSMassFormatter Mass and weight in pounds, kg etc.
Images and other Resources
ImagesButtons
UI Elements
UI Elements-[UIImage imageNamed:@"myImage"];
Put image files into .lproj folders
Does not work for asset catalogues
Launch ImageDisable asset catalogue
Launch images in .lproj folders
Follows usual naming conventions for Retina
Orientation
iPhone vs. iPad
Other RessourcesSame as images
Lookup via -[[NSBundle mainBundle] pathForResource:ofType:]
Use cases HTML
Text files
App NameInfo.plist
Application has localized Display Name
<key>LSHasLocalizedDisplayName</key><true />
InfoPlist.strings "CFBundleDisplayName" = "国际范例";
Wrap up
Cover the BasicsLocale based formatting
NSDateFormatter
NSNumberFormatter
NSByteCountFormatter
Careful and diligent translation Remember resources besides the source code
Next StepsImages 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
…
SummaryInitialer 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
Merci!
Hvala!Vielen Dank!
Thank You!
Grazie!