Localize your iOS applications with Transifex Native: A How-To Guide

Written by transifex | Published 2021/07/27
Tech Story Tags: localization | l10n | localizing-ios-app | ios | ios-apps | approach-to-localization | software-localization | good-company

TLDR Transifex Native is a powerful technology that allows you to make localization a seamless part of your development lifecycle by completely redefining the localization stack. The technology relies on 3 different parts: the SDKs, the Content Delivery Service (CDS) and the platform itself. CDS is a service that can serve content fast to multiple clients, such as mobile devices. The SDK and the CDS take care of the content format, so you don’t need to care at all about how the content is structured.via the TL;DR App

Transifex Native is a powerful technology that allows you to make localization a seamless part of your development lifecycle by completely redefining the localization stack.
From the perspective of developers, it comes in the form of open-source SDKs that you can integrate directly into your code, literally within a few minutes. And this gives you a vastly automated localization process that revolves around ease of use and the removal of unnecessary, cumbersome actions that otherwise take a lot of time and resources.

Table of Contents

Introduction

Transifex Native Architecture

The technology of Transifex Native relies on 3 different parts: the SDKs, the Content Delivery Service (CDS) and the Transifex platform itself.
Each SDK, such as the iOS SDK, is a thin layer over your applications. Their main purpose is to provide a way for managing localization hooks inside your applications, push content from your application to CDS and retrieve translated content from CDS in order to display it in your application.
CDS is a service that can serve content fast to multiple clients, such as mobile devices. It acts as an intermediary between the SDKs and Transifex. You have the option of using the one hosted by Transifex, or you can easily host it on your infrastructure, using the open-source server.

SDK Philosophy

The iOS Native SDK is built with sensible defaults and an architecture that allows great extensibility. Not all applications have the same needs. So, the SDK lets you provide custom implementations of many core components such as the caching mechanism or the handling of missing translations, or use the default values that cover the most common cases. Even for custom implementations, the SDK provides a rich API and a multitude of ready classes, so that you can construct sophisticated behaviors.
All of them are open source so that you can see in detail exactly what dependency you are adding to your application, and also take advantage of the community or submit your own updates to the SDKs.

Fileless Data Exchange

One of the biggest advantages of Transifex Native is that it doesn’t rely on localization files. On the side of Transifex, Native works with FILELESS resources, not Android XML files or iOS .strings files.
CDS uses the Transifex API to send and receive content to and from Transifex as FILELESS payloads. The SDKs use the CDS APIs to send and receive content to and from CDS as JSON payloads.
Both CDS and the SDKs take care of the content format, so you don’t need to care at all about how the content is structured and how it will reach Transifex. This makes the whole workflow low-maintenance and effortless.

Transifex Setup

Before you begin integrating the SDK, you need to create a project on Transifex.

Create Transifex Project

Transifex Native works with a special type of Transifex projects. Head to www.transifex.com/<myorganization>/add/ to create a new project and make sure you choose “Transifex Native” as the project type.
Then click on “Generate Native Credentials Now” and copy the token and the secret you see in the popup, in a safe place. Now you can continue integrating Transifex Native in your iOS project.
TIP: if for any reason you lose the secret, you can generate a new pair of token/secret from the same page as above.

Basic iOS Setup for Localization Transifex Native

We’ll be using Swift for all code examples throughout this post. Transifex Native also supports Objective-C out of the box; check the README for details.
NOTE: The minimum requirements for the SDK are Swift 5.3, Xcode 12.3 and MacOS 10.13.

Setting up XCode Project

Adding Swift Package Dependency
Open your project on XCode and select File > Swift Packages > Add Package Dependency. Type https://github.com/transifex/transifex-swift/ in the input box and select the suggested latest version.
Adding Languages
Go to the Project navigator, choose your project, and click Info. Then, under Localizations, click the Add button (+), and select a language. Repeat for all the languages you want in your project.
Initializing the SDK
Transifex Native needs to be initialized as soon as possible in the app flow. Update the
application(_:didFinishLaunchingWithOptions:)
function, adding the few lines shown below. 
Now is the time to use the token generated for your Transifex project. Make sure you replace the values of
sourceLocale
,
locales
and
token
with the correct values that correspond to your project.
Import Transifex
import Transifex

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication,
                    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let sourceLocale = "en"
        let locales = ["en", "el", "fr_FR", "es_MX"]
        let token = "1/84d1b54ff99b491a2d4c5831412dee8de6983758"

        let localeState = TXLocaleState(sourceLocale: sourceLocale, appLocales: locales)
        TXNative.initialize(locales: localeState, token: token)
        TXNative.fetchTranslations()

        return true
    }
}
Fetching Translations
Note that we are calling
TXNative.fetchTranslations()
right after initialization. This will start running in the background and will download all available translations for the locales you defined during initialization. 
We recommend that translations are fetched as soon as possible so that they are available on your app when the UI first renders. If, for any reason, you don’t want this to happen right away, you can always call
TXNative.fetchTranslations()
later. Also, you could do this each time the application is brought to the foreground or when the internet connectivity is established
Finally, you can call the fetch method with a completion handler, so that you get notified when translations are downloaded. For example, you could display a loading screen until all your assets, including translations, are ready.
Adding TXNativeExtensions
You will also need to copy the TXNativeExtensions.swift file in your project and include it in all of the targets that call any of the following Swift methods:
  • String.localizedStringWithFormat(format:...)
  • NSString.localizedStringWithFormat(format:...)
If none of your application targets call any of the above methods, then you don’t need to add this file to your project.
Handling Localization Hooks for Localization Transifex Native
Transifex Native for iOS was designed in a way that requires minimal effort for developers. Apart from the initialization code you saw above, there is nothing more to be done in order to designate the proper localization hooks within the code or UI.
The SDK works with the existing hooks provided by the iOS localization framework itself. For example, it works out of the box with the following statements:
let localizedString = NSLocalizedString("Welcome!", comment: "Sign up page")
let localizedMinutes = NSLocalizedString("unit-time.%d-minute(s)", comment: "dminutes")
NSString.localizedStringWithFormat(localizedMinutes as NSString, 3)
String.localizedStringWithFormat(localizedMinutes, 3)
Initial run
If you run your app now, you’ll notice nothing different. However, all translatable content will already be served by Transifex Native. Since the SDK doesn’t know of any translations yet, the source strings will be used for all languages.

Pushing Source Content to Transifex

The most common way to push the source phrases to Transifex is to use the CLI tool we have built.
Install the CLI tool by running the following commands on your Mac:
git clone git@github.com:transifex/transifex-swift-cli.git
cd transifex-swift-cli
swift build -c release
cp .build/release/txios-cli /usr/local/bin/txios-cli.
Now you can call the CLI commands from any path. Go to the root directory of your XCode project and run:
txios-cli push --token <transifex_token> --secret <transifex_secret> --project MyApp.xcodeproj --verbose
Make sure you replace the token and secret with the actual values you have stored previously.
What this command does is to export the base localization of the provided Xcode project, parse the generated XLIFF file, transform the translation units to the format CDS expects and push them to CDS, which in turn pushes them to Transifex.
TIP: if you set the TRANSIFEX_TOKEN and TRANSIFEX_SECRET environment variables, then the push command can be simplified to:
txios-cli push --project MyApp.xcodeproj
The push command supports extra arguments:
  • --dry-run, to extract all strings without actually sending anything to CDS/Transifex
  • --append-tags, to let you define one or more custom tags for the pushed strings on Transifex
NOTE: all strings that are pushed to CDS this way are stored on Transifex, without deleting any previous strings. This means that as time goes by, the strings on Transifex will only increase in number, even if you delete some of them from your iOS project. If you wish to change this behavior, look for the --purge option in the README, but do so with a lot of caution, as you could permanently delete translations from the Transifex platform.

Translating Content on Transifex

At this point, all source strings defined in your project will have reached Transifex. Note that this includes the strings found inside storyboards. Your translation team can now start translating the source strings in any locale. If you are testing things out, you can add a few translations yourself. It’s best if you leave some strings untranslated, so that you can observe how the SDK handles missing translations.

Displaying Translations On Your App

Head to your iOS device or Simulator and change the system locale to one other than the source; for example, fr_FR. Now run your application.
The first time you do that, you’ll notice no difference. However, in the background, the SDK makes a request to CDS, downloads all available translations, and stores them on the device. If you close the app and run it again, you should now see all the translated strings appearing in French. Make sure it’s a fresh session, for example, by stopping the Simulator and running the app again
NOTE: The reason the SDK requires one session to download the fresh content and another session to display it is because it was considered best to not wait for the translations before the app content is shown, and not to update the UI with different translations while the app is running. The former might have introduced an undesirable delay, and the latter might have been confusing to the users. If you want a different behavior than the default, you can easily have that by providing a custom cache during initialization.
Any strings you left untranslated on Transifex should be displayed in English, the source language. This is the behavior of the default “missing policy”, but there are many more options to that.
If you change any of the translations on Transifex and run your app again with a fresh session, the new translations will appear in your app, just like that!
CDS Translation Cache
In order to ensure a great performance, CDS has a caching mechanism that does not get fresh content from Transifex for about 1 hour. If you want to see the new translations appearing immediately on your app, you can invalidate the cache, by running the following command:
txios-cli invalidate --token <transifex_token>

Bundling Translations

It is very common to ship iOS applications with the translations bundled inside, to make sure they are available even if there is no Internet connection the first time the app is launched.
In order to bundle the translations in your app, you can use the CLI tool as follows:
txios-cli pull --token <transifex_token> --translated-locales <translated_locale_list> --output <output_directory>
This will download the translations from CDS for all locales specified and will store them in a txstrings.json file inside the specified output directory. This is the only case where you are going to interact with a file in the whole flow of Transifex Native.
The output directory can be any directory in the local filesystem, ideally the directory where the Xcode project resides.
After downloading the txstrings.json file, you have to drag and drop it to the Xcode project, making sure that the main application target in the ‘Add to targets:’ list is selected and the ‘Copy items if needed’ option is enabled.
The above operation only has to be performed once so that the file can be included in the application bundle. In order to confirm that this is the case, within Xcode navigate to the main application target, then to ‘Build Phases’ and under the ‘Copy Bundle Resources’ section, make sure that the txstrings.json file is included.
If a new version of the txstrings.json file is downloaded, the existing bundled file can be simply updated by overwriting the older version of the file with the new one in the filesystem, or just by specifying the directory of the Xcode project that the bundled txstrings.json resides to the pull command of the txios-cli tool.

Advanced iOS Setup for Localization Transifex Native

By following the steps up to here, you have set up an iOS application that covers all important localization needs, with minimum effort and Over The Air support that works out of the box. If you want to get deeper into the workings of the SDK and see how you can further customize the SDK to fit more advanced needs, keep reading!

Missing Policy

By default, when a string is not available in the selected locale, the source string is displayed instead. However, this behavior is very easy to change. Simply provide a different missing policy when initializing the SDK.
TXNative.initialize(
    locales: TXLocaleState(sourceLocale: "en", 
                           appLocales: ["en", "el", "fr"]),
    token: "<transifex_token>",
    secret: "<transifex_secret>",
    missingPolicy: TXCompositePolicy(
        TXPseudoTranslationPolicy(),
        TXWrappedStringPolicy(start: "[[", end: "]]")
    )
)
With the policy defined above, the source string "The quick brown fox" would become "[[Ťȟê ʠüıċǩ ƀȓøẁñ ƒøẋ]]".
If the existing policies are not enough for you, you can create one of your own very easily. Just subclass TXMissingPolicy and provide a custom implementation that fits your needs.

Error Policy

The last thing you would want from an SDK is to cause your application to crash. The iOS SDK is built in a way that practically makes it impossible to crash your app when rendering strings. 
The default error policy will return the original source string. You can easily create your own policy by subclassing TXErrorPolicy.

Cache

The iOS SDK uses a powerful caching system with extensive functionality, a rich API, and full control over the way it behaves.
The default cache used is defined in TXStandardCache, and it includes the following functionality:
  • In its core, it uses a MemoryCache object, which means that the downloaded translations are stored in the runtime memory.
  • When the app is launched, this memory cache gets populated with any translations found in the bundle. After that, if there are any translations downloaded previously by the SDK (and stored in the sandbox directory of the main app), the memory cache is updated with those. All previous translations that were loaded from the bundle are removed completely, as per the default TXCacheUpdatePolicy used (replaceAll).
  • When the new translations are fetched, they are stored in the app sandbox, but they aren’t stored in the memory cache. This ensures that they will only be available on the next app launch, when the flow above will run again.
Custom Cache
If your app needs a different behavior than the one described above, you can provide a custom cache object. For example, if you want the memory cache to be immediately updated with newly downloaded translations, you can use the following code. It is identical to the default implementation, but removes the TXReadonlyCacheDecorator wrapping (downloadURL, providers and updatePolicy taken from TXStandardCache):
let cache = TXFileOutputCacheDecorator(
    fileURL: downloadURL,
    internalCache: TXProviderBasedCache(
        providers: providers,
        internalCache: TXStringUpdateFilterCache(
            policy: updatePolicy,
            internalCache: TXMemoryCache()
        )
    )
)
TXNative.initialize(locales: localeState, token: token, cache: cache)
Also, there is an additional update policy to choose, called updateUsingTranslated. It updates the existing cache only with the new entries that have a non-empty translation, ignoring the rest.

Source Translations

Something worth mentioning is that for the Native SDK the source locale acts almost exactly like any other locale. This means that your translators can provide new translations for the source locale, and the SDK will download and display them in the app. You, the developer, will not have to care about updating the source strings inside the app; instead, the translators will take care of any necessary updates, away from the code.

SDK Under the Hood

One of the first things discussed in this post is the fact that installing and using the Transifex Native iOS SDK is super easy, and requires no changes in your codebase, other than initializing the SDK.
In order to allow developers to keep the standard iOS localization hooks like NSLocalizedString, String.localizedStringWithFormat(format:...) and NSString.localizedStringWithFormat(format:...), the SDK needed to do some magic. The heart of that magic is the Swizzler.
It contains a SwizzledBundle class that is activated automatically during runtime, that swizzles (i.e. overrides, monkey-patches) all localizedString() calls made either by storyboard files or by the use of NSLocalizedString(). The swizzled localizedString() method delegates everything to the Native SDK, instead of letting it go through the localization framework of iOS.

Why Transifex Native

Transifex offers many different ways to integrate with its platform. So why would you choose the Native SDK over something else to localize your mobile applications?

Install Effortlessly

Getting started with Transifex Native is as easy as it gets, as you can have everything up and running within a few minutes. It’s arguably the most straightforward and less painful way to localize an iOS application. 

Focus On What Really Matters

As you know very well, releasing a new version of your app to the App Store takes a lot of time and effort, and involves a lot of different teams and people. With Transifex Native, you don’t have to worry about changes made to the translations of the app content. Having Over The Air updates allows people outside the Engineering team to fix typos or make content changes together with their translation team, without having to bother you at all; you can just keep working on building awesome features in your app.

Maintain An Effective Workflow

A typical engineering department has multiple teams or multiple people working on the same application, usually by branching off of a main Git branch and doing work in parallel. With Transifex Native, you can keep using feature branches like you already do, and stop worrying about conflicts in the localization files (remember, there are no files with Native!).
Every string you add or even modify in your app will become a new string on Transifex. The push command of the CLI tool will always append strings to Transifex, never replacing existing ones. So each team or individual can do their work without worrying about affecting other people’s work or introducing conflicts.

Publish Earlier & Translate Later

Because you can push new source strings to Transifex as early and as often you like, the localization team can start the translation process as early as they wish. They don’t necessarily have to wait until your pull request is merged, until QA is finished, or even until all strings of a feature have been added to the app; they can start translating whenever they see fit, thus reducing a bottleneck for potential releases.
On the other hand, your team doesn't have to wait until all strings of a certain locale are 100% translated before publishing to the App Store. They can move forward with publishing the app, and the translators can do their work later in the process, as the content will arrive Over The Air.

Expand For Your Needs

The people that built the architecture of the Native SDK wanted to make sure that it allows you to customize all the important parts. There are plenty of ready-made things to choose from, but the SDK’s API lets you easily build your own functionality.
Moreover, the open-source nature and the extensive documentation of Transifex Native allows you to easily dive into its code, make changes and contribute them to the community.

Where To Next

This post should help you get up and running with Transifex Native for iOS. For more information, you can check the extensive documentation. We’d be happy to answer any questions you might have and review your PRs on GitHub!
This post was authored by Dimitrios Bendilas.

Written by transifex | The #1 localization platform for developers.
Published by HackerNoon on 2021/07/27