Reputation: 81
I have defined a Strings Catalog for my iOS Framework Project, not an iOS App Project. And it didn't seem to perform any translations nor providing error feedbacks if something went wrong. The project is currently using an app-level localization, not using system's so I have to maintain UserDefaults
to persist the last used locale identifier.
First, here's a helper class I defined as ObservableObject
to help with locale switching, also including an extenstion to Locale
class to provide shortcut to String
init with Locale
. The examples here are for testing English and Arabic translations.
class LanguageSettings: ObservableObject {
init() {
var localeId = UserDefaults.standard.value(forKey: "locale") as? String
if localeId == nil {
localeId = Locale.current.identifier
}
switch localeId?.lowercased() {
case "en","en-us":
break
case "ar":
locale = LanguageSettings.arabic
break
default:
break
}
}
init(localeTest: Locale) {
locale = localeTest
}
@Published var locale: Locale = Locale(identifier: "EN-US") {
didSet {
switch locale.identifier.lowercased() {
case "en", "en-us":
break
case "ar":
break
default:
locale = oldValue
break
}
updateUserDefaults()
}
}
private func updateUserDefaults() {
UserDefaults.standard.setValue(locale.identifier, forKey: "locale")
}
static let english = Locale(identifier: "EN-US")
static let arabic = Locale(identifier: "AR")
static let availableLocales = [
english, arabic
]
}
extension Locale {
func string(localized: String.LocalizationValue, comment: StaticString? = nil) -> String {
let localizedString = String(localized: localized, bundle: Bundle.main, locale: self, comment: comment)
print("Value for \(localized) for \(identifier) is \(localizedString)")
return localizedString
}
}
Then here's a SwiftUI View consuming it.
import Foundation
import SwiftUI
struct ErrorDisplay: View {
@EnvironmentObject private var languageSettings: LanguageSettings
@Environment(\.locale) private var locale: Locale
var body: some View {
let localeIdToName: [String: String] = [
"en": "English",
"en-us": "English",
"ar": "Arabic"
]
// let locale = languageSettings.locale
GeometryReader { _ in
VStack {
Image(.exclamationTriangleSolid).foregroundColor(.white)
.padding(.top, 10)
Text(locale.identifier)
Divider().background(.white)
HStack {
Text(localeIdToName[locale.identifier.lowercased()] ?? "-")
.padding(.all, 5)
.foregroundColor(.black)
Image(systemName: "chevron.down").foregroundColor(.black)
.padding(.trailing, 5)
}
.background(.white)
.padding(.all, 8)
.containerShape(Rectangle())
.onTapGesture {
withAnimation {
showLocalePicker = true
}
}
Text(message ?? "An error has occurred")
.multilineTextAlignment(.center)
.font(.system(size: 16))
.foregroundColor(.white)
Button(
action: onReload
) {
Text(locale.string(localized: "Reload Video")
}
.padding(.vertical, 10)
}
}
.sheet(isPresented: $showLocalePicker) {
Text(
locale.string(localized: "Select Locale")
).padding()
Text("Select Locale")
ForEach(0..<availableLocales.count) { i in
let locale = availableLocales[i]
let name = localeIdToName[locale.identifier.lowercased()]
Button(
action: {
withAnimation {
showLocalePicker = false
}
if name != nil {
languageSettings.locale = locale
}
}
) {
Text(name ?? "-")
}.padding()
}
Button(
role: .destructive,
action: {
withAnimation {
showLocalePicker = false
}
}
) {
Text(locale.string(localized: "Cancel"))
}.padding()
}
}
var message: String? = nil
var availableLocales: [Locale] = LanguageSettings.availableLocales
var onReload: () -> Void
@State private var showLocalePicker = false
}
My Strings Catalog is already at 100% Translation Progress as indicated below. .
I also tried previewing the View like this.
#Preview {
ErrorDisplay(
onReload: {}
)
.environment(\.locale, LanguageSettings.arabic
)
.environmentObject(
LanguageSettings(localeTest: LanguageSettings.arabic)
)
}
Then I did confirm that injected @Environment
level Locale
was indeed correct. I also defined an extension to Locale
class to help with tracing.
Every time I changed the locale, it did trigger the @Environment
locale to rebuild the View so it kept printing for each time I changed the locale. But on the print result indicated that it always returned the Localization Key String instead.
I have looked at similar thread here and no answers provided there yet.
Also to clarify, this is a Framework Project which I intend to use, build and distribute as .xcframework
format. And I do not use Swift Package Manager nor Podfile here.
Upvotes: 0
Views: 458
Reputation: 81
Answering my own question after some trial-and-errors & reading past posts, I replaced the Strings Catalog with the Legacy Strings File and generated the .lproj
files, referring to this old answer, after being referred from a specific Apple forum post.
So editing on my existing code from this
// Before
extension Locale {
func string(localized: String.LocalizationValue, comment: StaticString? = nil) -> String {
return String(localized: localized, bundle: bundle, locale: self, comment: comment)
}
}
Into this
// After
extension Locale {
func string(localized: String, comment: String = "") -> String {
// Fetch my custom framework's bundle, defaults to Bundle.main
var bundle = Bundle(identifier: "com.framework.personal") ?? Bundle.main
// Look for lproj file path inside the bundle then load the localized bundle
if let bundlePath = bundle.path(forResource: languageCodeFromIdentifier, ofType: "lproj") {
bundle = Bundle(path: bundlePath) ?? bundle
}
return NSLocalizedString(localized, bundle: bundle, value: localized, comment: comment)
}
// Helper getter to fetch language code from the Locale as reading from language?.languageCode is not reliable after some tests.
var languageCodeFromIdentifier: String {
get {
let languageCode = identifier.split(separator: "_").first?.lowercased()
return languageCode ?? "en"
}
}
}
This worked for me right now for my use case where the string has to be localized by app defined Locale instead of Locale.current
.
This should be a quick workaround, but not clean enough yet, which I will optimize on my own project.
Upvotes: 0