Apple provides nice tools for internationalization and localization (See official documentation). However, we had several pains that weren't answered out of the box easily:
- Multiple .strings files to maintain:
genstringswill extract NSLocalizedString strings from code, but for each IB document you internationalize - you need to have a separate .strings file, which causes painful maintenance of strings for localization. Also - this way if you already localized a common expression that appears in several files, you'll need to remember to update it for each file separately.
- Localizing a new version is very painful: When you change a view in IB, its .strings file can immediately become invalid and it is very hard to make use of the existing localizations of previous versions. This is also true for the .strings that are extracted by
- NSLocalizedString assumes strings are in main bundle: If you want to use another localization bundle path (e.g. some path of a file you download from your server in runtime), you need to use other more complex macros.
- NSLocalizedString figures out which locale to use automatically: Which is great, but often the user wants to change this preference for your app, without changing the whole device's language.
JTLocalize was written to solve these pains:
- Unified .strings file: Collection of multiple localizable strings from multiple types of resources throughout the project. No need for separate strings file per storyboard/xib - Only one file to maintain without duplicate strings.
- Continuous translation intergration simplified: When the app changes, you don't need to localize everything again.
jtlocalizecommand-line tools will make it easy to just send the diff for translation and merge the translated diff back.
- Configurable location of localization bundle: This allows you to easily decide to use the main bundle as default, and move to another path once you downloaded it from server. As a side effect of this, you can easily use JTLocalize to change all the English versions of your strings from server.
- Configurable preferred locale: This allows you to easily let the user choose a language without relying solely on the language taken from device's settings.
How to internationalize (Preparing your project's strings for the unified .strings file)
Internationalization is done using custom objective-c classes and simple marking mechanisms.
Adding to your project
JTLocalize can be added to an Xcode project using CocoaPods:
Internationalizing UI Elements
Internationalization of UI elements works for both xibs and storyboard files.
In order to internationalize UI element (
Use the corresponding class in the JTLocalize framework (
JTTextField, ...) as the Custom Class of the UI element. These classes use the proper localized string when setting the text.
In interface Builder's Document Outline, set the element's "userLabel" (Document->Label) to a string with the
JTL_prefix. This prefix is respected by our localization command-line tool for string extraction. The rest of the string in the userLabel (after the
JTL_prefix) will be used as the comment of the localization entry in the Localizable.strings files.
Internationalizing DTCoreText attributed labels
DTCoreText elements (
DTAttributedLabel) see the illustration in the example project (DTAttributedLabel+JTLocalizeExtensions, JTAttributedLabelWithLink).
The reason this is only in the example project is to avoid
DTCoreText dependency in the Pod.
If you want to use them (and internationalize them), add a keypath named htmlString with an html string value.
In this html string value, simply put
JTL("Key", "Comment") wherever you would a localized string value.
Internationalizing strings in code
To internationalize strings in the code, simply use the JTLocalizedString() macro (exactly as you would use NSLocalizedString() normally, but with a different macro):
NSString *localizedString = JTLocalizedString("Some string", "The Strings context for translation")
Swift doesn't support C-macros. We currently didn't insert any Swift functions into the Pod itself, because CocoaPods' Swift support is still in beta (as of writing these lines).
Instead, we attached a POC to our example project to show you how you can easily use
JTLocalize in your Swift projects.
How to localize
The JTLocalize framework provides the
jtlocalize command line tool that integrate with the internationalization mechanisms, and simplify the localization flow.
This application requires:
pip install jtlocalize, or download the latest release version:
The standard localization flow
Usually we will use the
merge sub-operations of the
jtlocalize command-line tool, in the following manner:
- Make sure your
JTLocalizable.bundleis ready with directories for all the languages you need (see appendix for more info)
jtlocalize generate /path/to/project /path/to/JTLocalizable.bundle
jtlocalize prepare_diff /path/to/JTLocalizable.bundle
- Translate the
Localizable.strings.pendingfiles in the different languages directories
(convert encoding if needed, see appendix).
- Save the translated file in the proper language directory under
(convert encoding if needed, see appendix).
jtlocalize merge /path/to/JTLocalizable.bundle
Another useful sub-operation that can help you make sure you didn't forget to internationalize any strings in your app.
jtlocalize mock_translate --preset chicken /path/to/Localizable.strings
Will localize the given file so that all translations are "Chicken".
Often we need to send a pending strings file to a 3rd party translation service, which charge us by a word count.
To get an accurate word count of the words that actually needs to be translated, you can run:
jtlocalize word_count /path/to/Localizable.strings.pending
More operations and flags
jtlocalize supports many flags and features for configuring your localization flow.
You can run
jtlocalize --help or
jtlocalize OPERATION --help to gen information about all of them.
Updating localization bundle and preffered locale in runtime
As mentioned above, we wanted to enable easy relocation of the localization bundle out of the main app bundle, to support the use case of updating the bundle from a remote server (and as an awesome side effect - allowing us to change English strings without releasing a new version!). Also, we wanted to allow users to change preferred language.
By default, app will search for localized versions
Localizable.strings in the
JTLocalizable.bundle (see appendix) that is in the app bundle. Also, by default app will choose the preffered locale according to user's preferences in the iOS Settings app.
To change this behavior, you can change the path in the following manner:
[JTLocalize setLocalizationBundleToPath:NEW_PATH stringsTableName:NEW_TABLE_NAME preferredLocale:NEW_LOCALE_IDENTIFIER];
You can pass
NEW_LOCALE_IDENTIFIER to use the defaults (app bundle,
"Localizable", and the locale iOS decides on by default from what's available)
NEW_LOCALE_IDENTIFIER should be in the form of the ISO 639-1 code (e.g. 'en', 'fr', 'zh-Hans', 'pt-PT', etc.) and will essentially redirect the app to take the strings table from the corresponding .lproj subdir of the localization bundle.
If you want to know anywhere in your app which locale is actively beeing used by JTLocalize, you can use
At JoyTunes, we use the bundle version of
JTLocalizable.bundle by default. When app loads - we download an updated version from our servers to the Documents directory, and call
setLocalizationBundleToPath with the path we downloaded it to.
Also, by default we set prefferedLocale to
nil, but once the user decides to change this from a settings screen, we call this method with the updated choice, and cache it using
NSUserDefaults for next sessions.
NSBundles are automatically cached, so making this call without changing the path won't cause the bundle's content to refresh even if it was changed (but will replace the preferredLocale).
JTLocalizable.bundle - The localization bundle
The localize bundle is the directory containing the strings files for the different languages the app is localized to.
| +-- en.lproj
| | +-- Localizable.strings
| +-- ru.lproj
| | +-- Localizable.strings
jtlocalize command_line tools assume en.lproj is the default language directory (will be configurable in future).
Whenever you need to add a new language, just make sure you add an empty LANGUAGE_CODE.lproj directory to the bundle before running
Convert Localizable.strings from/to proper encoding
Notice: The Localizable.strings encoding is UTF16, Little Endian, with line ending set as LF.
The different scripts expect & produce the files in this encoding.
You can use:
iconv -f utf-8 -t utf-16 Localizable.strings > Localizable.new.strings
to convert utf-8 file to the format we use in the scripts.
iconv -f utf-16 -t utf-8 Localizable.strings > Localizable.new.strings
for the opposite effect.