bg

Localization: changing language on the fly in an iOS app

author

Supernerd

/

Developer

6 min read

6 min read

So… you’ve got an iOS app and you want to add the option for your users to change the app’s language while they’re using it. No need for stacking and Googling: we’ve got your back!

Please note, that this guide covers language changing on the fly in an iOS app. Our general recommendation as a mobile app development company is to stick to Apple`s guidelines. That means that you should use the language that the user set up on his phone. However, if you still need to have the changing language in the app itself, here`s the guide on how you can do it. The advantage of the solution we describe here is that you don’t need to use third-party libraries or dive into info.plist. Before you start, I’m assuming you have Swift 5.3 or higher, and Xcode 12.2 or higher as your IDE.

As an example of our bilingual apps, check out our Ayadi project, which we developed in two languages with right-to-left language support.

Step 1: Storyboard

Let’s create an application called LanguageSwitchApp. Rename the default ViewController to MainViewController. In your default storyboard, update the class for the default ViewController in its Identity inspector. Embed your MainViewController in the navigation controller. Throw in a second view controller and create a class for it called SettingsViewController.

Add a label and a button to both screens. In the MainViewController, set the label text to Good Luck!, and the button title to Settings screen. In the SettingsViewController, set the label text to The current language is English and the button title to Change language. Hold the control button on your keyboard, click on the Settings screen button, and drag the push segue to the SettingsViewController . Select Push (never mind that it’s deprecated).

This is what your final layout should look like. Of course, you can apply any style, font, or color you want.

You need to create outlets for the two labels and two buttons you just added. For the Settings screen button, you’ll also need to add an IBAction. Add it and call it changeLanguageButtonTapped.

That’s it, we’re done with the storyboard!

Step 2: Begin Localization

Now we can get right down to the iOS app localization-related stuff. Go to your project settings and click the Info tab. Under Localizations, you’ll see the list of languages used by the app.

Click the plus (+) button at the bottom of the list and select Ukrainian. In the window that appears you’ll see that Xcode has already selected the resource files for Ukrainian (or your chosen language).

Create a group called Resources. In it create a strings file called Localizable. Click Localize in the file inspector for the strings file.

In the window that opens you’ll see a dropdown list with the languages from the project settings. For now, let’s stick with English. Click Localize.

Getting back to your Localizable.strings file: we use these files to write key-value pairs, almost like a dictionary collection in Swift. Write the following pairs in your Localizable.strings file. Some values can be identical to their keys, so in order to avoid confusion use snake case (lower case only and an underline instead of a space). Be careful: don’t forget the semicolon.

Open Resourcesgroup in Finderand you will see there is a newen.lproj group with our Localizable.strings file. Xcode uses the lprojextension for directories with localization files.

Now follow the same steps, but for the Ukrainian language. Create the group uk.lproj in Resources, and create another Localizable.strings file inside it. You can copy over the content of the first one, but this time the values have to be in Ukrainian. Add a checkmark for Ukrainian in the Localization area of the file inspector.

See also: How to Implement Google Maps Marker Clustering for iOS and Stay Sane >

Step 3: Extend the Bundle Class

The first thing we need is a bundle. According to the Apple docs, this is basically a directory with a hierarchical structure holding both code and its resources. It’s also where the localized resources are stored. To access our bundle, we’ll use the class with the same name: Bundle.

Create a group called Extensions, and in it create a swift file called BundleExtension. As the name suggests, you need to create an extension of the Bundle class.


extension Bundle {
    private static var bundle: Bundle!
    
    public static func localizedBundle() -> Bundle {
        if bundle == nil {
            let appLang = UserDefaults.standard.string(forKey: "app_lang") ?? "en"
            let path = Bundle.main.path(forResource: appLang, ofType: "lproj")
            bundle = Bundle(path: path!)
        }
        
        return bundle
    }
}

The localizedBundlefunction returns the same bundle, but with the path to your localization file. If the current language hasn’t been set yet, set the appLang property to en and store it in UserDefaults.

Step 4: Perform the Language Switch

The second function we need to add will actually perform the language switch. Its body is very similar to localizedBundle, but it serves a different purpose.

 


public static func setLanguage(lang: String) {
        UserDefaults.standard.set(lang, forKey: "app_lang")
        let path = Bundle.main.path(forResource: lang, ofType: "lproj")
        bundle = Bundle(path: path!)
    }

 

Strings in our application have to be subjected to the language switch. Create a swift file called StringExtension. To actually use those key-value pairs we created, you’ll have to apply the regular NSLocalizedString function to the string keys. If you look at the input parameters it takes, you’ll see that it takes the string it’s applied to, and a localized bundle (the Bundle extension we’ve just created), and returns the string value from this localized bundle. Simple enough, right?

 


extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.localizedBundle(), value: "", comment: "")
    }
}

 

Step 5: Put It All Together

Now we finally get to use all the tools we’ve created. Create a setStrings function and in it assign the string keys with the localized function to every UI component. Your code should look like this:

 


func setStrings() {
        currentLanguageLabel.text = "current_language".localized
        changeLanguageButton.setTitle("change_language".localized, for: .normal)
        navigationItem.title = "settings".localized
}

 

Move the code that assigns string values to viewDidLoad:

 


override func viewDidLoad(l) {
        super.viewDidLoad()
        
        navigationController?.navigationBar.isHidden = false
        setStrings()
    }

 

To show the language options we suggest using an action sheet with two action buttons (one each for English and Ukrainian) and a Cancel button.

 


func setActionSheet() {
        let alert = UIAlertController(title: "choose_language".localized, message: "", preferredStyle: UIAlertController.Style.actionSheet)
        let englishAction = UIAlertAction(title: "English", style: .default) { _ in
        }
        let ukrainianAction = UIAlertAction(title: "Українська", style: .default) { _ in
        }
        alert.addAction(englishAction)
        alert.addAction(ukrainianAction)
        alert.addAction(UIAlertAction(title: "cancel".localized, style: .cancel, handler: nil))
        self.present(alert, animated: true)
    }

 

What you have to do inside these actions is very simple: save an abbreviation of the selected language to UserDefaults, pass it to Bundle through its setLanguage function, and call the setStrings function.

Your alertActions should look like this:

 


let englishAction = UIAlertAction(title: "English", style: .default) { [weak self] _ in
            let newLanguage = "en"
            UserDefaults.standard.set(newLanguage, forKey: "i18n_language")
            Bundle.setLanguage(lang: newLanguage)
            self?.setStrings()
        }

 let ukrainianAction = UIAlertAction(title: "Українська", style: .default) { [weak self] _ in
            let newLanguage = "uk"
            UserDefaults.standard.set(newLanguage, forKey: "i18n_language")
            Bundle.setLanguage(lang: newLanguage)
            self?.setStrings()
        }

 

Step 6: i18n

What’s i18n and why do we need UserDefaults all of a sudden? Well, i18n stands for “internationalization”: that’s because there are 18 letters between the first and the last letter. We access the property in UserDefaults because our app has to remember the user’s language setting when it launches.

Since we’re using navigation controller, the viewDidLoad method in MainViewController won’t be called when navigation controller pop SettingsViewController. Move the code that assigns string values to viewWillAppear.

 


override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
        
        navigationController?.navigationBar.isHidden = true
        navigationItem.backButtonTitle = ""
        goodLuckLabel.text = "good_luck".localized
        settingsScreenButton.setTitle("settings_screen".localized, for: .normal)
    }

 

Step 7: Save the Set Language for Relaunch

There’s only one more thing left to do: save the set language for the next launch. You need this when adding language to iPhone apps, otherwise, the interface will revert to default at each launch.

Go to AppDelegate and add the following code to the didFinishLaunchingWithOptions function:

 


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        let currentLanguage = UserDefaults.standard.string(forKey: "i18n_language")
        Bundle.setLanguage(lang: "en")
        switch currentLanguage {
        case "uk":
            Bundle.setLanguage(lang: "uk")
        default:
            Bundle.setLanguage(lang: "en")
        }
        return true
    }

 

We’ve set the bundle’s default language to be English. If the user didn’t specifically set any language, our default language will beEnglish.

Using bare keys isn’t very comfortable: you either have to remember them, or jump to Localizable.strings every time you need them. To make things easier, let’s create an enum called  AppStrings of strings that will hold all the keys.



enum AppStrings: String {
    case good_luck = "good_luck"
    case settings_screen = "settings_screen"
    case current_language = "current_language"
    case change_language = "change_language"
    case choose_language = "choose_language"
    case settings = "settings"
    case cancel = "cancel"
    
    var localized: String {
        return self.rawValue.localized
    }
}

 

Now you can go back to the view controllers and access the keys for localization via this enum.


goodLuckLabel.text = AppStrings.good_luck.localized
settingsScreenButton.setTitle(AppStrings.settings_screen.localized, for: .normal)


Step 8: Test The App

Finally, launch your app. As intended, the default language is English. Go to the SettingsViewController, tap the Change languagebutton, and select Ukrainian.

And there you go! All the strings have changed to Ukrainian. The same thing happened in the MainViewController. Terminate the application. Next time you launch the app, it should be in Ukrainian.

And You’re Done!

That’s it! Now you have a working example of how to change language on the fly in an iOS app, and without using any third party libraries or diving into info.plist. You’re welcome to use it and share it, and we hope it’s useful. If you need help with iOS localization, native iOS application software development, bilingual/multilingual app development, right-to-left support, and other challenges, well, that’s what we do for a living at NERDZ LAB. We’re an iOS app development company with a mission to bring to life ideas you’ve never seen before. Come visit us to find out more about how we could help you!

You can also follow our GitHub and LinkedIn for more handy coding tutorials.