为何SwiftUI Widget中设置环境Locale(ja)仅部分代码生效?
我尝试给Widget设置本地化,添加了.environment(\.locale, Locale(identifier: "ja"))后,大部分代码未生效:
仅生效的代码
Text(entry.date, style: .date)
不生效的代码
Text(Date.now.advanced(by: 60*60*12), style: .relative)if {} else {}条件语句
预期显示效果
こんにちは世界! 2022年12月31日 11時間, 57分
实际显示效果

相关代码
struct SuperWidgetEntryView : View { var entry: Provider.Entry var body: some View { WidgetView() .environment(\.locale, Locale(identifier: "ja")) } var languageCode: String? { Locale.autoupdatingCurrent.language.languageCode?.identifier } var localeCode: String? { Locale.autoupdatingCurrent.identifier } var isJapaneseLanguage: Bool { languageCode == "ja" } var isJapaneseLocale: Bool { localeCode == "ja_JP" } @ViewBuilder func WidgetView() -> some View { VStack { if isJapaneseLocale || isJapaneseLanguage { Text("こんにちは世界!") } else { Text("Hello, world!") } VStack { Text(entry.date, style: .date) Text(Date.now.advanced(by: 60*60*12), style: .relative) } } } }
问题原因与解决方法
1. 条件判断不生效的核心原因
当前的isJapaneseLocale、isJapaneseLanguage是基于Locale.autoupdatingCurrent(系统全局区域设置)判断的,和你通过.environment给View设置的Locale完全无关。就算给View环境指定了日语Locale,这些计算属性依然会读取系统的区域设置,导致条件判断不随环境Locale变化。
2. 相对时间格式不生效的原因
Text(Date, style: .relative)在Widget中的表现和普通App不同,它默认依赖系统区域设置,而非View环境里的Locale。另外,直接在View中使用Date.now不符合Widget的更新机制,应该依赖entry.date来保证时间同步。
具体修复步骤
步骤1:改用环境Locale做条件判断
添加@Environment(\.locale)读取View环境中的Locale,替换原有的系统Locale判断:
@Environment(\.locale) private var locale var isJapaneseLanguage: Bool { locale.language.languageCode?.identifier == "ja" } var isJapaneseLocale: Bool { locale.identifier == "ja_JP" }
步骤2:手动处理相对时间的本地化
用RelativeDateTimeFormatter自定义相对时间格式,指定使用环境中的Locale:
private func relativeDateString(for date: Date) -> String { let formatter = RelativeDateTimeFormatter() formatter.locale = locale formatter.unitsStyle = .full return formatter.localizedString(for: date, relativeTo: entry.date) }
同时把Date.now替换为entry.date.addingTimeInterval,保证时间和Widget Entry同步:
Text(relativeDateString(for: entry.date.addingTimeInterval(60*60*12)))
修复后的完整代码
struct SuperWidgetEntryView : View { var entry: Provider.Entry @Environment(\.locale) private var locale var body: some View { WidgetView() .environment(\.locale, Locale(identifier: "ja")) } var isJapaneseLanguage: Bool { locale.language.languageCode?.identifier == "ja" } var isJapaneseLocale: Bool { locale.identifier == "ja_JP" } private func relativeDateString(for date: Date) -> String { let formatter = RelativeDateTimeFormatter() formatter.locale = locale formatter.unitsStyle = .full return formatter.localizedString(for: date, relativeTo: entry.date) } @ViewBuilder func WidgetView() -> some View { VStack { if isJapaneseLocale || isJapaneseLanguage { Text("こんにちは世界!") } else { Text("Hello, world!") } VStack { Text(entry.date, style: .date) Text(relativeDateString(for: entry.date.addingTimeInterval(60*60*12))) } } } }
额外说明
Widget的环境Locale存在一定限制,部分系统级格式化逻辑不会完全遵循View环境的Locale,因此需要手动通过Formatter控制本地化输出;同时所有依赖Locale的判断逻辑,必须基于View环境中的值,而非系统全局Locale。
内容的提问来源于stack exchange,提问作者user14119170

