Forcing iOS localization at runtime -the right way.
Tags: swif, ios, localization, runtime
Published: 2019-10-21 13:04:08

## iOS localization, and how it should be done

> ***From Apple internalization docs:***
Localization is the process of translating your app into multiple languages.

Sounds simple enough, and most of the time it is. You add resources for the languages you wish to support and iOS will automatically select and use one according to user’s device language.

But what if your app specification requires you to force a specific language at runtime? Here is where things get a bit tricky.

In this short article, I’m going to talk about forcing localization, and the right way to approach this topic.

## So, you need to force a language?

Here are some of the reasons you might need to force a specific language:

1. Allow users to pick a language, and switch it on the fly.

1. The app is designated for a specific country but must be displayed in a different language.

1. The same app needs to be released to different countries and used only with their native tongue.

At [Healthy.io](https://healthy.io) I had a requirement similar to #3, same app with multiple languages. Usually, the simplest way to achieve this is to set a different target for each variation of the app and compile only with the Base language resources.

But, there’s a catch. You need to manage multiple targets and their settings, like Build Phases, Build Rules, etc.
Every change you make to one target’s settings will need to be duplicated to other targets.

An alternative is to keep a single target, with multiple schemes and set the language at runtime.

**Pros:**
- Single target, easy to maintain.
- Extremely easy to add a new app variations with new languages.

**Cons:**
- Depends on your localization resources, it might be bloated since your binary was compiled with all the languages.
- Apple doesn’t really like this approach, but it shouldn’t cause a rejection during the review.

## Forcing a language at runtime

> ***Here is what Apple has to say on the matter: ***
In general, you should not change the iOS system language (via use of the AppleLanguages pref key) from within your application.
This goes against the basic iOS user model for switching languages in the Settings app, and also uses a preference key that is not documented, 
meaning that at some point in the future, the key name could change, which would break your application.

After a short research you’ve definitely seen this:

{% gist https://gist.github.com/eldare/1c86ecd042b62d083c7c529b83ec7f56 %}

While it does work, it’s not a complete solution for one simple reason: 
It requires an app restart. Meaning, the user starts the app for the first time, sees a wrong language, closes the app and starts it again. Great user experience! 😏

## What should you do?

The above line of code is the right direction, but by itself, it’s not enough to provide a good user experience.

> ***From Apple: ***
If you want to switch languages in your application, you can do so via manually loading resource files in your bundle.
You can use NSBundle:pathForResource:ofType:inDirectory:forLocalization: for this purpose, but keep in mind that your application would be responsible for all loading of localized data.

In order to achieve the right language on the first app start, you need to access the desired language bundle yourself:

{% gist https://gist.github.com/eldare/47302fb282109af182aa181323867a88 %}

Great! But we don’t want to deal with this part everywhere: *“keep in mind that your application would be responsible for all loading of localized data”*.
To make it work seamlessly we’ll need to swizzle *localizedString(forKey:value:table:)* to access the desired bundle.

In case you’re not familiar with swizzling, you should read [Method swizzling in iOS swift](https://medium.com/@abhimuralidharan/method-swizzling-in-ios-swift-1f38edaf984f).

> ***From the article:***
Method swizzling is the process of changing the implementation of an existing selector at runtime. Simply speaking, we can change the functionality of a method at runtime.

(Please note, that while I'm against swizzling in general, I think cases like these are a good example of when it's ok to swizzle.)

The complete code:

{% gist https://gist.github.com/eldare/91b8c01514d4d132a9522a44b7950079 %}

And one last thing; To ensure RTL/LTR switch, you need to reload the rootViewController.

## Don’t reinvent the wheel

That's it? Not quite. If you need Storyboard/Nibs support or just don't want to manage this yourself, then I have good news for you! There's a great library called [LanguageManager-iOS](https://github.com/Abedalkareem/LanguageManager-iOS).
It’s small, easy to use, maintained, and supports Carthage, Cocoa-Pods and SPM. Jackpot!!

Quick usage example on how to set the default language on app start with LanguageManager-iOS:

{% gist https://gist.github.com/eldare/b7f117c3e28d36e778ab5ac4ba133772 %}

### IMPORTANT UPDATE: LanguageManager-iOS No longer supports swizzling

The LanguageManager-iOS lib v1.1.4+, no longer swizzles, thus requires you to call String.localiz() yourself. You can find more information in the Github issue: https://github.com/Abedalkareem/LanguageManager-iOS/issues/46

### textAligntment = .natural

*textAlignment = .natural* can’t be trusted on first app start, and needs to be handled manually for *UITextView*, *UILabel*, and *UITextField.*

**Important Note: **Just to be on the safe side, you should set the text before textAligntment is set.

{% gist https://gist.github.com/eldare/11bf3af9c3d5c7c2219a4ea5a30aaf24 %}

### Localized Info.plist

Sadly, Localized Info.plist ignores runtime language set, and so far I was not able to resolve this issue.

## Final thoughts

When you need to stray from Apple’s path, localization becomes a hassle; So I’m really glad someone took the time to make and maintain a lib like [LanguageManager-iOS](https://github.com/Abedalkareem/LanguageManager-iOS).

If you have notes, additional issues/solutions, please don’t keep them to yourself.

Cheers! 🎈<br>
Eldar.