Reputation: 586
My app is supposed to support language change at runtime. I'm using SwiftGen 5.0. ViewControllers subscribe to language change notification and I've checked that the localisation function fires correctly. My overriden tr
function looks like this:
fileprivate static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
guard let bundle = LanguageManager.shared.bundle else {
fatalError("Cannot find bundle!")
}
let format = NSLocalizedString(key, tableName: table, bundle: bundle, comment: "")
let locale = Locale(identifier: LanguageManager.shared.currentLanguageKey!)
return String(format: format, locale: locale, arguments: args)
}
The bundle
is set like so:
if let path = Bundle.main.path(forResource: currentLanguageKey, ofType: "lproj") {
bundle = Bundle(path: path)
}
However, the tr
function returns mostly previous language strings. Only one out of all labels currently in memory refreshes. Setting a breakpoint inside the function and printing bundle
returns
NSBundle </var/containers/Bundle/Application/ED5A6C7D-1807-4319-8817-45E693BC45E2/MyApp.app/en_US.lproj> (not yet loaded)
which is the correct new language. After app restarts the language is set correctly. Am I doing something wrong?
Upvotes: 9
Views: 3348
Reputation: 686
I experienced the same issue caused by runtime localization/language changes in my Swift app. To address this, I followed Kacper's solution and extended the internal extension as outlined below:
Swift L10n:
extension L10n {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
// Custom Setup
let storedLanguages = ["de", "en"]
if let languages = UserDefaults.standard.array(forKey: "AppleLanguages") as? [String],
let language = languages.first,
storedLanguages.contains(language),
let path = Bundle.main.path(forResource: language, ofType: "lproj"),
let bundle = Bundle(path: path)
{
return bundle.localizedString(forKey: key, value: nil, table: nil)
}
// Default Setup
let format = Bundle.main.localizedString(forKey: key, value: nil, table: table)
return String(format: format, locale: Locale.current, arguments: args)
}
}
Origin Stencil:
// MARK: - Implementation Details
extension {{enumName}} {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
// Default Setup
{% if param.lookupFunction %}
let format = {{ param.lookupFunction }}(key, table)
{% else %}
let format = {{param.bundle|default:"Bundle.main"}}.localizedString(forKey: key, value: nil, table: table)
{% endif %}
return String(format: format, locale: Locale.current, arguments: args)
}
}
Modified Stencil:
// MARK: - Implementation Details
extension {{enumName}} {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
// Custom Setup
let storedLanguages = ["de", "en"]
if let languages = UserDefaults.standard.array(forKey: "AppleLanguages") as? [String],
let language = languages.first,
storedLanguages.contains(language),
{% if param.lookupFunction %}
let path = {{ param.lookupFunction }}.path(forResource: language, ofType: "lproj"),
{% else %}
let path = {{param.bundle|default:"Bundle.main"}}.path(forResource: language, ofType: "lproj"),
{% endif %}
let bundle = Bundle(path: path)
{
return bundle.localizedString(forKey: key, value: nil, table: nil)
}
// Default Setup
{% if param.lookupFunction %}
let format = {{ param.lookupFunction }}(key, table)
{% else %}
let format = {{param.bundle|default:"Bundle.main"}}.localizedString(forKey: key, value: nil, table: table)
{% endif %}
return String(format: format, locale: Locale.current, arguments: args)
}
}
The ideation was to enable the app to immediately utilize the newly addressed localized table without requiring any restart or reopening. I also used Kacper's solution with:
static var label: String {
return L10n.tr("Localizable", "registration_verify.pin_code.label")
}
Origin Stencil:
{{accessModifier}}static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
Modified Stencil:
{{accessModifier}}static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { {{enumName}}.tr("{{table}}", "{{string.key}}") }
Upvotes: 0
Reputation: 86
Now you can config lookupFunction params in swiftgen.yml file
strings:
inputs:
- en.lproj
outputs:
- templateName: structured-swift5
params:
lookupFunction: Localize_Swift_bridge(forKey:table:fallbackValue:)
output: L10n-Constants.swift
in your project you just need implement lookupFunction, your can use use Localize_Swift library
import Localize_Swift;
func Localize_Swift_bridge(forKey:String,table:String,fallbackValue:String)->String {
return forKey.localized(using: table);
}
generated code may like this:
internal enum Localizable {
internal static var baseConfig: String { return
L10n.tr("Localizable", "base config", fallback: #"Base Config"#) }}
extension L10n {
private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String {
let format = Localize_Swift_bridge(forKey:table:fallbackValue:)(key, table, value)
return String(format: format, locale: Locale.current, arguments: args)
}
}
https://github.com/marmelroy/Localize-Swift
Upvotes: 7
Reputation: 586
Okay, I found the problem. The stencil was generating static variables:
static let label = L10n.tr("Localizable", "registration_verify.pin_code.label")
Changing stencil to generate computed properties fixed the behaviour:
static var label: String {
return L10n.tr("Localizable", "registration_verify.pin_code.label")
}
Upvotes: 11