Swift: Simple Localisation

Rich Mucha
5 min readNov 7, 2019

--

If you read one of my previous tutorials, Swift: Working with Constants, I mentioned that I would provide a tutorial on simple localisation in you applications.

The Apple route to creating localised strings is via a Localizable.strings file, this is great if you want to keep updating your application to support another language. Wouldn’t it be better to have an application that doesn’t require an update so release another language, also have you been in that situation where you have pushed a spelling mistake to the App Store:

We have all got those emails!

All of this can be solved very simply, so lets get started by creating a LocalizationService.

LocalizationService

public class LocalizationService {

// MARK: Properties
static
var localisation: [String: Any]?
// MARK: Helpers
class
func getString(with key: String) -> String {
let missingError = UIDevice.isProduction ? "": String(format: "Missing string for key: %@", key)

guard let localisation = shared.localisation as NSDictionary? else { return missingError }
let localisedString = localisation.value(forKeyPath: key) as? String
return localisedString ?? missingError
}
}public extension String {

var localised: String {
return LocalizationService.getStringForKey(with: self)
}
}

In this service you will have a localisation property that will contain your dictionary of strings that the app will require, we will come back to this later.

Below that there is a helper method that takes the key of the string required and returns the string from the localisation dictionary, this uses NSString value(forKeyPath....

There is also a String extension that provides you with a quick helper to request a localised string.

Note: If the key does not exist in the dictionary the helper will return the key, you might want to change this to an empty string if you don’t want weird strings being presented to the users.

Now that is set up we can take a look at our strings, so lets take the example from the previous tutorial:

extension String {      // MARK: HomeScreen
static
let hsHelloWorld: String = "Hello world"
}

What we want to do now is update this line to

  1. Create a key
  2. Use the String extension localised.
static let hsHelloWorld: String = "content.view.hello_world".localised

You will need to add this key to a json file that you can use later.

{
"content": {
"view": {
"hello_world": "Hello world"
}
}
}

To ensure that we are ready to support different languages we need to wrap this with the langauge:

{
"en": {
"content": {
"view": {
"hello_world": "Hello world"
}
}
},
"fr": {
"content": {
"view": {
"hello_world": "Bonjour le monde"
}
}
}
}

Live Data

Now I mentioned about having localised strings updatable without the need to update the app, I do this by using a database with Firebase. With a small service in the app you can request the language the device needs and listen to any changes that might occur (meaning you can fix that spelling mistake).

If you have used Firebase before, great; if you haven’t then you can find some quick tutorials available here.

In the Firebase console upload your dictionary:

NOTE: If you do want to have a secure connection then you will need to follow Firebase documentation to set this up, for this tutorial we are going to switch this off.

In the rules tab add this rule:

{
"rules": {
".read": true,
".write": false
}
}

Ensure you have downloaded the latest GoogleService-Info.plist and attached to your target.

FirebaseService

Next, let’s create a new service that will handle requesting the database information that we need for the app. This can also handle other services that Firebase provide, so it’s useful to have it in it’s own service.

import Firebaseclass FirebaseService: NSObject {   // MARK: Properties
var
languageRef: DatabaseReference?
// MARK: Singleton
static
let shared = FirebaseService()
private override init() {
super.init()
}
// MARK: Properties
static
func setup() {
FirebaseApp.configure()
Database.database().isPersistenceEnabled = true
monitor()
}
static func monitor() {
shared.languageRef = Database.database().reference(withPath: "localisation")
if let ref = shared.languageRef {
ref.keepSynced(true)
ref.observe(.value, with: { snapshot in
if
let
postDict = snapshot.value as? [String: Any],
let langageRegion = NSLocale.preferredLanguages.first {
let languageDic = NSLocale.components(fromLocaleIdentifier: langageRegion)
let language = languageDic[NSLocale.Key.languageCode.rawValue]
var languageStrings = postDict[language ?? "en"] as? [String: Any] if languageStrings == nil {
languageStrings = postDict["en"] as? [String: Any]
}
LocalizationService.localisation = languageStrings
}
}, withCancel: { error in
debugPrint(error.localizedDescription)
})
}
}
}

This will create a connection with Firebase, take the devices language and create a listener to the database on that particular language. This setup is persisted so it will always have that latest copy that it received until it is updated. With this in mind you will need to capture not having a localised dictionary by either:

  1. During the first launch wait until you have received a response from Firebase before you display anything to the user.
  2. Or, if you do not want to rely on the devices connection, then you can preinstall the dictionary by adding the .json file to the bundles resources and preload on app startup.

By adding FirebaseService.setup() during your app startup, and everything is communicating as expected, the app will fetch the strings that you have uploaded.

Next time your app requests .hsHelloWorld string you will get the string that you uploaded to Firebase.

Super simple! Once you have this setup, as long as your app has a intuitive UI setup you should be able to add new languages without even updating the app. Make sure you do this in a test environment before so you can check first.

Thanks for reading and let me know your thoughts, share your thoughts and send to friends/collegues. Also check out my other tutorials that will make your life much simpler 😁.

Happy dev’ing 😀

--

--