Software localization

iOS I18n Testing Explained: Checklist, Examples, and Tips

Uncover the power of iOS i18n testing to make your app shine across languages and regions, winning over a global audience and boosting user love for your app. 🌍
iOS localization blog post featured image | Phrase

The world has become a global village now more than ever. The App Store is available in 175 regions and 40 languages to help apps reach a wider audience so no one is left out. Apple has seen the importance of localization early and has been encouraging app makers to support multiple languages, eventually attracting more users and increasing user adoption.

Now, if you thought you were done only by localizing your application, think twice. Have you double-checked if your implementation works as it should all the time? This is exactly the question that i18n testing seeks to answer. Testing is the process of evaluating and verifying that a software product or application does what it is supposed to do.

With i18n testing, you can run tests for multiple locales and quickly identify any issue. It’s also a great way to do regression testing to make sure everything still works as planned after introducing new features. It helps you identify i18n bugs like wrong date formats, untranslated strings, wrong layout directions, and more. Let’s cover these and many more in this guide.

Basic setup

To get started we’ll create a SwiftUI application named “BuyDumbbell.” This is a simple app that highlights the common use cases in i18n and helps us test and ship a great international product. The base language will be English and will support Arabic so we get a chance to test Right to Left scenarios.

Let’s create our project by going to File ➞ New ➞ Project ➞ App ➞ Enter the name of your application (in our case “BuyDumbbell”) ➞ Check Include Tests ➞ click Next to create project. After creating the project, from the project navigator, select the project name, and click on the Info tab. Under Localizations, click the plus (‘+’) symbol to add the languages and regions you want to support, in our case, Arabic.

🗒 Note » Don’t forget to check the Include Tests checkbox when creating your application.

App project set up screenshot | Phrase

Now our application supports both English and Arabic but we are not quite done yet. We will need to create a Strings file that will house all our localized strings. Choose File ➞ New ➞ File …, select Strings File under Resources → click Next. Name it Localizable, then click on Create.

Click on the newly created Localizable file and click on Localize in the Inspectors Navigator, and then on the alert that shows click on Localize to create the localization file for English. On the Inspectors Navigator, check all the other supported localizations for your application under the Localization section, to create localization strings for those languages.

App ready for localization | Phrase

Now your app is ready for localization.

🔗 Resource » We’re focusing on testing in this article, so we’re not going to cover how to localize an iOS app. Our SwiftUI Tutorial on Localization is a good place to start if you want to learn iOS localization.

The application

‘BuyDumbbell’ is an application that sells, as you most likely guessed, Dumbbells. It has a greetings message that welcomes the user with the current date beneath. The weight of the Dumbbell follows an image and a picker to change the type of Dumbbell we want to buy. There is a description of the product with an ‘add to cart’ button. In this small application we are covering a lot of the areas to look out for when i18n your application.

  • Basic string translations
  • Date formatting, which depends on locale
  • Measurement unit: different regions use different measurement systems
  • Right to Left: the direction of content can depend on the language
  • Currency

Buy Dumbbell app in English | Phrase

Testing

Now let’s get to the fun part, Testing. We will start with the most basic for tests which is unit testing. Unit testing is essential for i18n testing because it’s responsible for testing the smallest parts of your application. It can be an individual function, method, procedure, module, or object. It will help you know if you content is displaying the right content for the right locale.

Manual testing

Buy Dumbbell app in English | Phrase Buy Dumbbell app in Arabic | Phrase

We can visually inspect our application to see if it’s properly localized. A few questions to answer for your manual tests:

  • Are strings being translated properly?
  • Do the strings represent the meaning depending on the locale?
  • Is all content completely visible regardless of the locale? e.g. when the weight of the dumbbell changes, does the measurement unit still remain localized?
  • Are strings that shouldn’t be localized translated? e.g PowerBlock Sport Series is a brand name and doesn’t need to be translated
  • Are the date formats localized correctly?
  • Is the correct local measuring unit displayed?
  • When content changes does it still remain localized to the right locale?
  • Is the currency reflecting the locale?
  • Are RTL languages properly oriented?

Strategies

To manually test localizations you can use preview testing directly in Xcode, or run the app in a physical device or simulator for a locale under test.

Preview testing

In SwiftUI you can test your application while developing with the use of localized previews. You can see your preview in different locales and optimize as you develop. As you can see from the screen shot, it’s not perfect. But it is suitable for simple cases like string translations.

Localized previews of Dumbbell app | Phrase

Running the app

The best way to manually test your application is by running it and doing the inspection. This way you can carefully go through all your test cases to make sure they pass. You can run your application using different locales: Option + Click on the Start active scheme button → Run → Options → App Language → Change to a supported language you want to test → App Region to the region of the language you are testing. This helps with things like currency.

For testers on simulators or physical devices, you can change the system language to test our localizations in their natural habitat. This is achieved by going to Settings → General → iPhone Language on a physical iPhone or simulator.

Manual test parameters | Phrase

Automated testing

Our project already has default testing files because we enabled testing when creating our project. We will use these files to test our application. You should have two test folders i.e BuyDumbbellTests which will be for unit tests and BuyDumbbellUITests which will be for UI tests.

Unit testing

To unit test your application you have to model your code to make it easily testable. We will go through a few unit tests for our application and see how this helps us prepare for i18n.

Helpful extension for string localization and test. This will help us write unit tests with different locales.

//Utils.swift

extension String {
    func localizefor(languageCode: String, bundle: Bundle? = nil) -> String {
        if let bundle = bundle {
            // use bundle for localisation
            return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
        } else {
            // use current locale for localisation
            return NSLocalizedString(self, comment: "")
        }
    }
}Code language: Swift (swift)

Open the BuyDumbbellTests file and you will see some sample tests. We are going to write a unit test to verify that ‘Hello’ is properly translated between English and Arabic. To run your test, click on the Run button beside the test function or on the class. The test will run and a prompt will show if it passed or failed.

//  BuyDumbbellTests.swift

import XCTest
@testable import BuyDumbbell

class BuyDumbbellTests: XCTestCase {

    func testHelloLocalization() throws {
        if let enPath = Bundle.main.path(forResource: "en", ofType: "lproj"), let enBundle = Bundle(path: enPath) {
            XCTAssertEqual("hello".localizefor(languageCode: "en", bundle: enBundle), "Hello")
        } else {
            XCTFail("Application not translated in English")
        }
        
        if let arPath = Bundle.main.path(forResource: "ar", ofType: "lproj"), let arBundle = Bundle(path: arPath) {
            XCTAssertEqual("hello".localizefor(languageCode: "ar", bundle: arBundle), "مرحبًا")
        } else {
            XCTFail("Application not translated in Arabic")
        }
    }
}Code language: Swift (swift)

Unit hello test | Phrase

You should write as many unit tests as necessary to fully test all the units of your application. We will add one more example for testing date localization.

//  Utils.swift

extension Date {
    func localizeDateFor(format: String = "dd MMM, yyyy", locale: Locale = Locale.current) -> String {
        let formatter = DateFormatter()
        formatter.locale = locale
        formatter.dateFormat = format
        return formatter.string(from: self)
    }
}Code language: Swift (swift)
//  BuyDumbbellTests.swift

class BuyDumbbellTests: XCTestCase {
		func testDateFormatting() throws {
	        var dateComponents = DateComponents()
	        dateComponents.year = 2022
	        dateComponents.month = 6
	        dateComponents.day = 26
	
	        // Create date from components
	        let userCalendar = Calendar(identifier: .gregorian)
	        if let testDate = userCalendar.date(from: dateComponents) {
	            XCTAssertEqual(testDate.localizeDateFor(locale: Locale(identifier: "en")), "26 Jun, 2022")
	            XCTAssertEqual(testDate.localizeDateFor(locale: Locale(identifier: "ar")), "٢٦ يونيو, ٢٠٢٢")
	        } else {
	            XCTFail("Invalid date");
	        }
	    }
}Code language: Swift (swift)

UI tests

To ensure your UI is ready for the international market, you need to make sure that it meets standards on all devices and locales. UI testing helps us to test this faster than manual testing. SwiftUI, for all it’s great features, doesn’t provide a UI testing framework so we’ll be make use of a great framework called ViewInspector.

Import the ViewInspector library by following the steps provided by Apple.

🗒 Note » Make sure to add ViewInspector to your test project.

View inspector | Phrase

Running UI tests

ViewInspector will allow us to run assertions on our views to verify that our localizations are working correctly. We will run a test to verify that a description is showing on the screen and is localized. A few things to note when running UI Tests with ViewInspector:

  • All files that will be used in testing, like ContentView ,for example, should be added to the testing target
  • Import ViewInspector package
  • The view must conform to Inspectable
  • Test function should throw an error

After these you can write your tests like the example below and it should pass.

//  BuyDumbbellUITests.swift

import XCTest
import ViewInspector // <-- Step 1
@testable import BuyDumbbell

extension ContentView: Inspectable { }  // <-- Step 2

class BuyDumbbellUITests: XCTestCase {
    
    func testingDescription() throws {  // <-- Step 3
        let languageCode = Locale.current.languageCode ?? "en"
        let sut = ContentView()
        let localizedDescription = "description".localizefor(languageCode: languageCode)
        let value = try sut.inspect().find(text: localizedDescription)
        XCTAssertEqual(try value.string(), localizedDescription)
    }
}Code language: Swift (swift)

Advanced UI testing

Let’s have a look at some more advanced user interface testing methods.

Snapshot testing

In our previous UI test, the test runs so fast that it’s barely visible. Snapshot testing helps you visualize the tests being run and validates it so you can easily spot errors. It has the added benefit of running your tests against different parameters like devices, orientations, locales etc. This is a mixture of automated and manual testing. We are going to use another great framework to help us do this called Swift Snapshot Testing.

Import the Swift Snapshot Testing library by following the steps provided by Apple.

🗒 Note » Make sure to add Swift Snapshot to your test project.

The tests should fail the first time you run it but will work well the second time. Create a new UI Test Case Class file in the BuyDumbbellUITests directory called BuyDumbellSnapshotTests where we’ll write our snapshot testing code.

//  BuyDumbellSnapshotTests.swift

import XCTest
import SwiftUI
import SnapshotTesting
@testable import BuyDumbbell

class BuyBumbellSnapshotTests: XCTestCase {

    func verifyRendering(for device: ViewImageConfig, style: UIUserInterfaceStyle = .light, testName: String) {
        // Given
        let sut = ContentView()

        // When
        let wrapper = UIHostingController(rootView: sut)
        wrapper.overrideUserInterfaceStyle = style

        // Verify
        assertSnapshot(matching: wrapper, as: .image(on: device), testName: testName)
    }

    func testforEnglishLocaleInLightMode() throws {
        try XCTSkipIf(Locale.current.languageCode != "en")
        verifyRendering(for: .iPhone8,testName: #function)
        verifyRendering(for: .iPhone8Plus, testName: #function)
        verifyRendering(for: .iPhone12ProMax, testName: #function)
        verifyRendering(for: .iPadMini,testName: #function)
        verifyRendering(for: .iPadPro11, testName: #function)
    }
}Code language: Swift (swift)

This will run and create screenshots of the test specified in Project

Directory/{ProjectName}UITests/Snapshots/{ProjectName}SnapshotTests

Screenshots of the test specified | Phrase

Snapshot testing for different locales

You can create different test cases per locale to test with snapshots. Let’s create another snapshot test for Arabic in dark mode. Remember to change the scheme App Language and Region to run tests for specific locales.

🗒 Note » Add a locale-specific skip condition to your snapshot tests. This is because the tests will run with different locales and Locale.current.languageCode could be different from the locale needed for your test.

//  BuyDumbellSnapshotTests.swift

func testforArabicLocaleInDarkMode() throws {
    try XCTSkipIf(Locale.current.languageCode != "ar")
    verifyRendering(for: .iPhone8, style: .dark, testName: #function)
    verifyRendering(for: .iPhone8Plus, style: .dark, testName: #function)
    verifyRendering(for: .iPhone12ProMax, style: .dark, testName: #function)
    verifyRendering(for: .iPadMini, style: .dark, testName: #function)
    verifyRendering(for: .iPadPro11, style: .dark, testName: #function)
}Code language: Swift (swift)

Test plans

So far, our automated tests still need a lot of manual configuration to run different test configurations. Test plans is the solution to this problem. They were introduced in WWDC 2019 with Xcode 11 as a way to organize our test environment.

To create a test plan go to Product -> Scheme → Edit scheme → Test → Info. Disable BuyDumbellTests. Then click Convert to use Test Plans → Create test plan from scheme → Convert → Enter name of test plan (e.g. ‘LocalizationBuyDumbbell’) -> Save to create test plan.

Test plan config | Phrase

Now let’s create plans for our supported locales. Click on the .testplan file that was created then click on Configuration 1 and rename it to ‘English’ and change localization settings to English. To create another configuration, click on the plus (’+’) icon and rename the new configuration that will be created (i.e Configuration 2) to ‘Arabic’.

All setup now, just press Command(⌘) + U to run the test plan. You’ll see more thorough UI tests trying different locales, orientations, and interface styles.

English test plan | Phrase

Arabic test plan | Phrase

Screenshot if you pass all tests | Phrase

If all tests pass you should see a screen similar to this.

Screenshot if you pass all tests | Phrase

Wrapping up iOS i18n testing tutorial

We hope you enjoyed learning with us about iOS testing for internationalization! Now that you have your app ready to conquer the world, you can let Phrase do the heavy lifting.

With its dedicated software localization solution, Phrase Strings, you can manage translation strings for your iOS app more easily and efficiently than ever.

With a robust API to automate translation workflows and integrations with GitHub, GitLab, Bitbucket, and other popular software platforms, Phrase Strings can do the heavy lifting from start to finish so you can stay focused on the code you love.

Phrase Strings comes with a full-featured strings editor where translators can pick up the content for translation you push. As soon as they’re done with translation, you can pull the translated content back into your project automatically.

As your software grows and you want to scale, our integrated suite lets you connect Phrase Strings with a cutting-edge translation management system (TMS) to fully leverage traditional CAT tools and AI-powered machine translation capabilities.

Check out all Phrase features for developers and see for yourself how they can streamline your software localization workflows from the get-go.

String Management UI visual | Phrase

Phrase Strings

Take your web or mobile app global without any hassle

Adapt your software, website, or video game for global audiences with the leanest and most realiable software localization platform.

Explore Phrase Strings