Swift Package String Localization

How do you localize a string in a Swift Package?

I wrote about adding resources to Swift packages and localizing them back when Apple added support to Xcode 12. When it comes to localizing strings it’s way too easy to make a mistake. There’s a few critical, not always obvious, steps you have to get right for it to work. Here’s a recap:

Example Swift Package

My example package contains some helper SwiftUI views including a loading view. The minimal implementation has some label view in a vertical stack above a progress view spinner:

public struct LoadingView<Label>: View where Label: View {
  public var label: Label

  public init(@ViewBuilder label: () -> Label) {
    self.label = label()
  }
            
  public var body: some View {
    VStack {
      label
      ProgressView()
    }
  }
}

For the common case, where the label is text, I have a convenient initializer that uses a default text message:

extension LoadingView where Label == Text {
  public init() {
    self.label = Text("Loading...")
  }
}

Loading message above an activity spinner

I want to localize that “Loading…” message.

Localization Prerequisites

There’s a couple of prerequisites you must follow if you want to add localizations to a swift package:

Minimum tools version

Support for resources in packages depends on Swift 5.3. If you’re adding resources to an old package make sure the first line of your package.swift manifest file has a tools version of at least 5.3. I’m using Xcode 14 which ships with Swift 5.7:

// swift-tools-version: 5.7

Add a default localization

Before you can localize a resource you need to add a default localization to your package manifest file. I’m using English (en):

let package = Package(
  name: "HelperViews",
  defaultLocalization: "en",

Adding Languages

For each language you want to support, including the default localization, add an .lproj folder under the target containing a Localizable.strings file:

Folder structure for HelperViews package with en.lproj, es.lproj and it.lproj directories containing Localizable.strings files below Sources/HelperViews.

Make sure you create the language directories under the target. This is probably the most common error I make.

The Swift Package Manager knows how to process .lproj directories containing .strings files so there’s no need to add a resources section to the target in the manifest file just for this localization.

Each of the Localizable.strings files contains the text for that language:

// en.lproj/Localizable.strings
"loadingview.title" = "Loading...";
// it.lproj/Localizable.strings
"loadingview.title" = "Caricando...";

Updating Usage

Update the view code to use the localized string key:

self.label = Text("loadingview.title", bundle: .module)

Note the use of the .module bundle to load the string from the package bundle. The Swift Package Manager creates the Bundle.module extension when it detects you’ve added resources to the package. If Xcode shows you an error that the Bundle type has no member module check you’ve added the language directories under the target.

Previewing A Locale

To check a localization is working you can override the locale in the preview environment:

LoadingView()
.environment(\.locale, Locale(identifier: "it"))

Caricando&hellip; with spinner

Not Working in App?

One issue that caught me out is that I had the localization working in the package but it wouldn’t work in an App target using the package. The reason is explained in this Swift forums post.

I hadn’t yet added any localized resources to the main app bundle. That’s not usually going to be problem. If I want my user interface to be localized for some languages I’m likely to add localized resources for those languages to the app bundle. However, until I do the app bundle ignores the localizations in other bundles like the Swift package.

The workaround, until I localize the app bundle, is to set the CFBundleAllowMixedLocalizations flag in the Info.plist file of the app target.

Info.plist Localized resources can be mixed

That allows the localizations in the Swift package to be used even if those languages are not localized in the app bundle.

In Summary

  1. Make sure your swift tools version is >= 5.3.
  2. Add a defaultLocalization to your Swift manifest file.
  3. Create the .lproj directories under the target directory.
  4. Update any usage in the package to load the localized resource from the .module bundle.
  5. Override the locale environment if you want to preview a localization in the package.
  6. Allow mixed localizations if you want to use a package language that is not supported by the app bundle.