30 Nov, 2020
A closer look at localization with Swift Package Manager
Swift build tools 5.3, released earlier this adds support for Swift Package Manager to bundle resource files along with code. Bundled with this update (no pun intended) it's the possibility to properly support localization in your SPM project.
Although the feature seems simple enough, it is not without its Gotchas. How does it work exactly? how can localization setup this way be used and/or overridden when necessary? How can I preview the changes using SwiftUI? How do I unit test this setup?
TLDR;
- Create a practical example on how to apply localization on an SPM project.
- Learn about
Bundle.module
, a very specific type ofBundle
. - Learn how SPM does not support
xctestplan
. - Learn how
SwiftUI
previews do not support localization of the bundle inside a module included via SPM. - Learn how to inject a specific
Bundle
as an explicit dependency for unit testing or for usingSwiftUI
previews.
The Basics
The manifest
I will start by setting up an SPM module called "WorldGreetings" for the purposes of this article. It will contain different localizations of the world "Hello".
Most of this setup should be familiar but there are 2 key parts here relevant to localization support:
// swift-tools-version:5.3
- Declaring the minimum Swift version required to build this package, which to support localization must be 5.3 (or above, if you are coming from the future)defaultLocalization: "en"
- This is aLanguageTag
declaration stating the default language supported by this package. The value is a RFC5646 compliant value. Broadly defined this standard is composed of smaller ISO 639 language code identifiers such asen
(ISO 639-1 for English) separated by dashes to potentially create more specific identifiers such asen-US
(American English).
Localizable.strings
With the manifest out of the way, it is time to create our .strings
file. The format and location of this resource is the same as you would expect on a regular Xcode project.
The Localizable.strings
files must be placed under the folder Sources/<Target name>/<language tag>.lproj
In my case, I will add localized text for English and Spanish, so I will need two Localizable.strings
files at the locations:
Sources/WorldGreetings/en.lproj/Localizable.strings
Sources/WorldGreetings/es.lproj/Localizable.strings

Usage
With all the setup steps complete, we can go ahead and use our localized strings:
The interesting part here is the usage of Bundle.module
. This specific property is created by SPM to refer to the resource bundle for our specific module, that is, the bundle in which the specific code resides separate from other SPM modules and the host application.
This property is interesting in that Bundle.module
will only be generated by SPM if the resources are placed in the right location in your package.
A common mistake here is to place it under the `Sources` folder directly. If you do this, SPM won't know that your project contains localized resources.
Using the localized package in an App
Using this SPM package in an App, seems easy enough:



And with all of that we should be ready to go.
We have included the WorldGreetings
SPM module and if we run in the simulator modifying the scheme to set "Spanish" as the App Language, our app correctly displays localized text.

SwiftUI Previews
One of the nicest perks of using SwiftUI
is being able to preview our work side by side our editor. This should also be a straight forward process:
Running this preview however does not yield the expected results.

What is happening here? We know the localization is working from running the app in the simulator.
From my testing it seems that SwiftUI
Previews are not smart enough to pass down the locale information to code included using SPM. This only seems to work when the localization come from Bundle.main
, that is, the App's Bundle.
Could there be a way to get around this? I will show how to get that done, but first, let's try unit testing our original SPM package to reveal more weaknesses exposed by SPM now that we can localize our modules.
Unit testing
For verification purposes and to give yourself a safety net in future updates to your code, it's a good idea to unit test your implementation while we are at it.
Unit testing that your strings are correctly pulling their values from the localizable resource file should be straight forward, or is it?
Consider the following test suite for our module:
Normally you would be able to write a simple test for each language and then use xctestplan
to run the suite including or skipping tests for the relevant language(s).
For an example of this, check out WorldGreetings-app-sample on Github

It seems however at the time of writing SPM packages don't support xctestplan
.
This is why a little workaround is necessary if we want to be able to run these kinds of tests.
Overriding the Bundle
To pull this off, we need to be able to have control over which Bundle
is being used to fetch our localized resources. In other words, we want to inject Bundle
as a explicit dependency.
What this extra init(languageCode: String)
provides is a way to specify a language code that we will use to load a specific Bundle
that matches that code. This Bundle
instance is then used as the bundle
parameter in NSLocalizedString(_, bundle: Bundle, comment: String)
inside our WorldGreetings
struct.
Because a Bundle
belonging a specific language code might not exist, this initializer is fallible.
And with that in place, our unit tests can be changed to test multiple localizations in the same XCTestCase
by force loading a specific resource bundle matching a language code:
Back to the SwiftUI previews
Now that we have a way to inject the Bundle
as a dependency, we can modify our code to show previews with specific languages:

Sources and Further reading
- Piterwilson on Github, WorldGreetings-final, https://github.com/piterwilson/WorldGreetings-final
- Piterwilson on Github, WorldGreetings-spm, https://github.com/piterwilson/WorldGreetings-spm
- Piterwilson on Github, WorldGreetings-app-sample, https://github.com/piterwilson/WorldGreetings-app-sample
- Apple, Swift packages: Resources and localization, Retrieved from
https://developer.apple.com/videos/play/wwdc2020/10169/?spm=ata.13261165.0.0.58fb628dEVVzNy) - SwiftLee, SwiftUI Previews: Validating views in different states, https://www.avanderlee.com/swiftui/previews-different-states/