Override locale in a SwiftUI view at runtime
Published on 23 March 2023
Sometimes a user wants to use an app in a different locale than their system locale. I faced this when developing Score Wonders. In the board game 7 Wonders (which the app is a companion for) there are different kinds of buildings/cards the player can build/play. All buildings/cards has a category like "Military", "Civilian", "Commerce" and "Science". The app is currently localized to English and Danish, so I naturally translated these categories to Danish, but it didn't feel right. As a long time player of the game, I have become accustomed to the English names, so I decided to make a setting for using the English names for the categories.
Localizing Xcode Previews
When it came to the implementation I had some ideas on how to do it, but suddenly I realized that SwiftUI has support for it all along, and that I have been using it in my previews in Xcode the whole time. It is done by using the .environment(\.locale, Locale(identifier: "da"))
.
Here is an example of the previews for my icon picker view, which uses my helper view LocalizedPreviews
. This helper lets me easily see previews of my views in the supported locales:
import SwiftUI
struct AppIconPicker_Previews: PreviewProvider {
static var previews: some View {
LocalizedPreviews {
NavigationStack {
AppIconPicker()
.environmentObject(PremiumController())
.environmentObject(SettingsController())
}
}
}
}
struct LocalizedPreviews<Content>: View where Content: View {
var displayName: String?
let content: () -> Content
var body: some View {
ForEach(previewLocales) {
content()
.environment(\.locale, $0.locale)
.previewDisplayName([displayName, $0.name].compactMap { $0 }.joined(separator: " - "))
}
}
}
private let previewLocales: [PreviewLocale] = [
.init(locale: Locale(identifier: "en"), name: "English"),
.init(locale: Locale(identifier: "da"), name: "Danish")
]
private struct PreviewLocale: Identifiable {
var id: String { locale.identifier }
let locale: Locale
let name: String
}
Localizing the categories in the Score calculator
So I took this functionality and added it to my score calculator, where I was showing the category names.
If the setting was enabled I assigned a newly created English locale to the environment for the PointRow
and otherwise just used the current locale:
struct PlayView: View {
...
@Environment(\.locale) private var locale
@EnvironmentObject private var settingsController: SettingsController
var body: some View {
Form {
...
PointRow(title: "Commerce", asset: .systemImage("person.line.dotted.person.fill", .orange), value: $play.commercePoints)
.environment(\.locale, settingsController.useEnglishCategoryNames ? .init(identifier: "en") : locale)
...
}
}
}
Conclusion
SwiftUI has some simple features which makes it a lot easier to do powerful stuff. The .environment(...)
is surely one of these features!
With this approach it was very easy to just override the locale for a small part of the view hierarchy and reach my goal of showing the categories in English.
The .environment(...)
in SwiftUI has a lot more to offer than just the \.locale
. Apple has some documentation of the key paths available in the SwiftUI environment.
Tagged with: