Force a locale from string catalog

I'm writing an iOS app that shares content with buddies. So my app will run in one language but the shared content will use the locale configured for the buddy.

I found this Apple documentation which suggests that locale: (Locale(identifier: is the solution. Apple Documentation

But I can't get it to work. Here's sample code.

struct LocalizationDemoView: View {
    @State var isEnglish = true
    
    var body: some View {
        
        var myLocale: String { isEnglish ? "en": "de" }
        VStack {
            Toggle("Switch language", isOn: $isEnglish).frame(maxWidth: 200)
            HStack {
                Text("\(myLocale): ")
                Text(String(localized: "Hello world!", locale: (Locale(identifier: myLocale)), comment: "To share"))
            }
        }
    }
}

And here's the excerpt from the string catalog:

{
  "sourceLanguage" : "en",
  "strings" : {

"Hello world!" : {
      "comment" : "To share",
      "localizations" : {
        "de" : {
          "stringUnit" : {
            "state" : "translated",
            "value" : "🇩🇪 Moin Welt!"
          }
        },
        "en" : {
          "stringUnit" : {
            "state" : "translated",
            "value" : "🇬🇧 Hello world!"
          }
        }
      }
    }
  }
}

Has Apple reduced support for string catalogs or is my code wrong?


Xcode 16.4 compiled on MacOS 15.6.1, device iOS 18.6.2

Answered by Engineer in 855618022

I understand that the locale parameter is confusing. Additionally there's confusion around which framework ultimately configures the locale to load the string from.

SwiftUI always uses the locale from the environment when localizable strings are resolved, i.e. in Text and LocalizedStringResource. This makes sure that values from the environment always take precedence.

You can either influence what's in the environment, or ask Foundation to resolve a LocalizedStringResource into a localized String yourself – bypassing SwiftUI's environment.

Here's the full code snippet that gives you two options how to load strings in a different language; one for SwiftUI and one for all the other strings.
Please don't try to load a bundle from a file path to an .lproj folder, you will miss out on Foundation's language matching when it comes to language variants and other edge-cases.

struct ContentView: View {
    @State var isOtherLanguage = false

    var myLocale: String { isOtherLanguage ? "de" : "en" }
    var locale: Locale {
        Locale(identifier: myLocale)
    }

    var body: some View {
        VStack {
            Toggle("Switch language", isOn: $isOtherLanguage).frame(maxWidth: 200)

            HStack {
                Text("✅\(myLocale): ")
                Text(LocalizedStringResource(
                    "Hello world!",
                    table: "Localizable",
                    locale: locale
                )).environment(\.locale, Locale(identifier: myLocale))
            }

            HStack {
                Text("✅\(myLocale): ")
                Text(localizedString("Hello world!", locale: locale))
            }
        }
    }

    // Foundation resolving the string:
    func localizedString(_ resource: LocalizedStringResource, locale: Locale) -> String {
        var resource = resource
        resource.locale = locale
        return String(localized: resource)
    }
}

Hello!

You should be able to use LocalizedStringResource to load a given string in a language that is not the one the app is currently running in.

LocalizedStringResource("Hello, world!", table: "Localizable", locale: Locale(identifier: myLocale))

Alternatively, since you're writing SwiftUI, you can configure an entire subtree to use the locale of the recipient you're sharing with:

// your SwiftUI code
.environment(\.locale, Locale(identifier: myLocale))

The locale parameter in the String initializer you shared here only affects how interpolations into that string are handled, and is comparable to String.localizedStringWithFormat.

LocalizedStringResource didn't seem to work. The .environment modifier may work on Views but not on the content of the view ( strings in the view used to build the shared content)

However, the localizedString method suggested by AI did work despite looking messy. A shame there's not a simpler solution.

@State var isOtherLanguage = false
  
    var body: some View {

        var myLocale: String { isOtherLanguage ? "de" : "en" }
        
        VStack {
            Toggle("Switch language", isOn: $isOtherLanguage).frame(maxWidth: 200)

            HStack {
                Text("❌\(myLocale): ")
                Text(
                    LocalizedStringResource(
                        "Hello world!",
                        table: "Localizable",
                        locale: Locale(identifier: myLocale)
                    )
                )
            }
            HStack {
                Text("❌\(myLocale): ")
                Text(
                    String(
                        localized: "Hello world!",
                        locale: (Locale(identifier: myLocale)),
                        comment: "To share"
                    )
                )
                .environment(\.locale, Locale(identifier: myLocale))
            }
            HStack {
                Text("❌\(myLocale): ")
                Text(localizedString)
            }
            HStack {

                Text("✅\(myLocale): ")
                Text("Hello world!")
                    .environment(\.locale, Locale(identifier: myLocale))
            }
            HStack {
                Text("✅\(myLocale): ")
                Text(localizedString("Hello world!", locale: myLocale))
            }
            
        }
    }
    
    func localizedString(_ key: String.LocalizationValue, locale: String) -> String {
        guard let path = Bundle.main.path(forResource: locale, ofType: "lproj"),
              let bundle = Bundle(path: path) else {
            return String(localized: key)
        }
        
        return String(localized: key, bundle: bundle)
    }

Accepted Answer

I understand that the locale parameter is confusing. Additionally there's confusion around which framework ultimately configures the locale to load the string from.

SwiftUI always uses the locale from the environment when localizable strings are resolved, i.e. in Text and LocalizedStringResource. This makes sure that values from the environment always take precedence.

You can either influence what's in the environment, or ask Foundation to resolve a LocalizedStringResource into a localized String yourself – bypassing SwiftUI's environment.

Here's the full code snippet that gives you two options how to load strings in a different language; one for SwiftUI and one for all the other strings.
Please don't try to load a bundle from a file path to an .lproj folder, you will miss out on Foundation's language matching when it comes to language variants and other edge-cases.

struct ContentView: View {
    @State var isOtherLanguage = false

    var myLocale: String { isOtherLanguage ? "de" : "en" }
    var locale: Locale {
        Locale(identifier: myLocale)
    }

    var body: some View {
        VStack {
            Toggle("Switch language", isOn: $isOtherLanguage).frame(maxWidth: 200)

            HStack {
                Text("✅\(myLocale): ")
                Text(LocalizedStringResource(
                    "Hello world!",
                    table: "Localizable",
                    locale: locale
                )).environment(\.locale, Locale(identifier: myLocale))
            }

            HStack {
                Text("✅\(myLocale): ")
                Text(localizedString("Hello world!", locale: locale))
            }
        }
    }

    // Foundation resolving the string:
    func localizedString(_ resource: LocalizedStringResource, locale: Locale) -> String {
        var resource = resource
        resource.locale = locale
        return String(localized: resource)
    }
}

Many thanks Apple Engineer, func localizedString( is exactly what I needed and the fog is clearing :)

Reminder to myself:

func localizedString(_ resource: LocalizedStringResource, _ myLocale: String) -> String {

        var locale: Locale {
            Locale(identifier: myLocale)
        }
        var resource = resource
            
        resource.locale = locale
        return String(localized: resource)
 }
Force a locale from string catalog
 
 
Q