swiftxcodeswiftuilocalizationswift-package-manager

How to Resolve Localization Issues in Swift Packages when working with Strings with SwiftUI?


I'm wondering how to fix problems when working with String Localization in Swift Packages in SwiftUI?

Currently facing problems:

  1. When importing the package into another project, the localized strings from the Swift Package are not showing as localized.
  2. Inside the package, strings are only localized in the preview when set directly, like this:
    Text("String Example", bundle: .module)
    
    However, when strings are called from variables, they are not localized.

To demonstrate the problems, I created a package named TestPackage.

The TestPackage code is as follows:

import PackageDescription

let package = Package(
    name: "TestPackage",
    defaultLocalization: "en",
    platforms: [
        .iOS(.v16)
    ],
    products: [
        .library(
            name: "TestPackage",
            targets: ["TestPackage"]
        ),
    ],
    targets: [
        .target(
            name: "TestPackage",
            resources: [.process("Localizable.xcstrings")] // also tried [.copy("Localizable.xcstrings")]
        )
    ]
)

After that, in my Sources > TestPackage folder, I created a SwiftUIViewForPreviews.swift file to test the localized strings and a Strings struct for holding an example string:

import SwiftUI

struct SwiftUIViewForPreviews: View {
    
    let stringFromCurrentView = "String Example"
    
    var body: some View {
        VStack {
            Text("String Example", bundle: .module) // Works in Preview
            Text(stringFromCurrentView) // Localization doesn't work
            Text(Strings.stringOneLocalized) // Localization doesn't work
        }
    }
}

#Preview {
    SwiftUIViewForPreviews().environment(\.locale, .init(identifier: "de"))
}

public struct Strings {
    public static let stringOneLocalized = String(localized: "String Example", bundle: .module)
}

When running the preview, I only see the Text("String Example", bundle: .module) string localized. For Text(stringFromCurrentView) and Text(Strings.stringOneLocalized), localization does not work.

TestPackage Xcode Screenshot

I also tried to set up strings the old way using .lproj instead of using a Localizable Strings Catalog, but I faced the same problems.

To test how our package will work inside of a project, I created a project named ProjectForLocalizationPackageTesting and attached the TestPackage to it. I created a ContentView to test how the localized string will show up in German localization, but in the preview, the string Text("String Example") was not localized. I also tested it in the simulator with App Language and App Region set to German/Germany, but it still displayed the English variant in a simulator.

import SwiftUI
import TestPackage

struct ContentView: View {
    var body: some View {
        VStack {
            Text("String Example") // Localization doesn't work
            Text(Strings.stringOneLocalized) // Localization doesn't work
            Text("String Example", bundle: .module) // Call doesn't work due to internal protection level
        }
    }
}

#Preview {
    ContentView()
        .environment(\.locale, .init(identifier: "de"))
}

ProjectForLocalizationPackageTesting Screenshot

LocalizedStringKey

I tried the approach of adding a special extension and when adding it to a Package SwiftUIViewForPreviews View, in a Preview we start to see our strings localised, :

extension Text {
  init(_ key: LocalizedStringKey, comment: String = "") {
    self.init(key, bundle: .module)
  }
}

But when we import our package to another project, our strings are shown again as not localised, even with the extension added to our Package.

Adding this code into our ContentView in our Project also didn't help

extension Text {
    init(_ key: LocalizedStringKey, comment: String = "") {
        #if SWIFT_PACKAGE
        self.init(key, bundle: .module) // For Swift Package
        #else
        self.init(key, bundle: Bundle.main) // For regular Xcode project
        #endif
    }
}

The questions are:

  1. How to correctly set up a Swift Package to support string localization to be able to see correct localisations for strings?
  2. How can we localize strings declared outside of a Text view in SwiftUI?
  3. How do I correctly configure a Swift Package in a project to display localized strings from the package where we have strings Localized?

The question is specific to working with swift packages, Dynamic Text LocalizedStringKey approach Text(LocalizedStringKey(stringFromCurrentView)) didn't help to solve the problem. Any help appreciated.

Xcode version: Version 16.0, Swift, SwiftUI


Solution

  • Question 1

    1. How to correctly set up a Swift Package to support string localization to be able to see correct localisations for strings?

    Your description of the package setup is the correct way to add localization to the package.

    When the package is built, the resources from the packes will be collected into a Bundle that will be accessible within the package by using Bundle.module.

    Anywhere you want to use the string in the package, you need to pass Bundle.module otherwise it will default to looking in Bundle.main which is the bundle for app, not the package.

    Question 2

    1. How can we localize strings declared outside of a Text view in SwiftUI?
    let stringFromCurrentView = "String Example"
    Text(stringFromCurrentView) // Localization doesn't work
    

    That code doesn't localize the string because stringFromCurrentView is of type String, so when passing to the Text it'll call the init that takes a string and does no localization

    That's opposed to creating Text directly with a string literal, which will call the Text.init that takes a LocalizedStringKey which can be initialized with a string literal.

    If the code above was update so the property was a LocalizedStringKey, it should work.

    let stringFromCurrentView: LocalizedStringKey = "String Example"
    Text(stringFromCurrentView, bundle: .module)
    

    String(localized:...) should work when being run in an app. It doesn't work in the preview because the view modifier .environment(\.locale, ...) is only used by other SwiftUI code. String(localized:...) is defined in Foundation and won't use the environment value since it doesn't known about SwiftUI.

    Question 3

    1. How do I correctly configure a Swift Package in a project to display localized strings from the package where we have strings Localized?

    Correct is a matter of personal preference. I've used the approach similar to the struct Strings containing properties or functions if the string can take values. That's also what the code from tools like SwiftGen generate.

    The important thing to remember for any solution is that it's the combination of the string key and bundle (also tableName but it's not applicable here) that allows a localized string to be looked up.