Locale — 是 iOS 和 macOS 中的一个 Foundation 类,封装了用户的语言和文化约定:日期、数字、货币和度量单位的格式。根据 Apple Developer Documentation, 2024,Locale 决定了 DateFormatter 如何显示月份(一月或 January)、数字中的小数分隔符(逗号或点号)以及货币符号(人民币、美元或欧元)。每个 Locale 实例都绑定到一个标识符,如 ru_RU 或 en_US,其中第一部分是语言代码(ISO 639-1),第二部分是地区代码(ISO 3166-1)。与 TimeZone 不同,Locale 不影响时间的绝对值,只影响其字符串表示形式。
要点
Locale — 是 Swift 中的值类型(Objective-C 中的 NSLocale),代表一组特定于某种语言和地区的格式化规则。与确定绝对时间偏移量的 TimeZone 不同,Locale 决定了时间、数字和货币在字符串表示中的外观。同一个日期 2024-07-21 在 ru_RU 中显示为"2024年7月21日",在 en_US 中显示为"July 21, 2024"。
每个 Locale 实例由两个组件组成:Language(语言 — 确定月份名称、星期几、词序)和 Region(地区 — 确定数字格式、货币、日历)。这些组件的组合根据 BCP 47 标准在标识符中编码:ru_RU(俄语,俄罗斯)、en_US(英语,美国)、de_DE(德语,德国)。
根据 Unicode CLDR(2024),iOS 中支持的区域设置列表超过 700 种语言-地区组合。Foundation 使用来自 CLDR(通用区域设置数据仓库)的数据 — 这是由 Unicode 联盟支持的最全面的本地化数据存储库。这保证了所有 Apple 设备上格式化的统一性。
DateFormatter 使用 Locale 来选择正确的月份和星期名称,确定日期组件的顺序(日/月/年或月/日/年)和分隔符。如果没有明确指定 Locale,DateFormatter 将使用设备的区域设置 — 这对 UI 是正确的,但对于格式必须固定的服务器数据来说是危险的。
| 组件 | ru_RU | en_US | de_DE |
|---|---|---|---|
| 日期(中等) | 2024年7月21日 | Jul 21, 2024 | 21.07.2024 |
| 数字(1000.5) | 1 000,5 | 1,000.5 | 1.000,5 |
| 货币(100) | 100,00 ₽ | $100.00 | 100,00 € |
| 日历 | 公历 | 公历 | 公历 |
| 列表分隔符 | ; | , | ; |
NumberFormatter 使用 Locale 来确定小数分隔符(逗号或点号)、分组分隔符(空格、逗号、点号)和货币符号。在解析数字时忽略 Locale 是国际化应用程序中常见的错误原因之一:数字"1,5"对于 ru_RU 来说意味着"一点五",但对于 en_US,数字分析器将其读作逗号后的"五"。
重要: 通过 Calendar.current 创建的 Calendar 继承了设备的区域设置。带有明确指定 locale 的 Calendar(identifier: .gregorian) — 推荐的可预测格式化的方法。在使用 ISO 8601 日期时,始终使用 Locale(identifier: "en_US_POSIX") — 一种用于技术格式化的特殊区域设置,不受区域设置的影响。
Locale 标识符由语言代码(ISO 639-1,两个字符)和地区代码(ISO 3166-1,两个字符)组成,用下划线分隔。示例:ru_RU、en_US、fr_FR、zh_Hans_CN(中文,简体,中国)。Foundation 还支持 BCP 47 格式的标识符:ru-RU、en-US,用于 Web 标准。
除了完整的标识符之外,Locale 还可以仅基于语言创建:Locale(identifier: "ru") 返回一个使用俄语和该语言默认地区(通常为俄罗斯)的区域设置。类似地,对于英语:Locale(identifier: "en") 使用美国地区。这种方法对于设置格式化语言而不绑定到特定地区非常有用。
特殊区域设置包括 en_US_POSIX — 一种用于日期和数字机器格式化的技术区域设置,无论用户设置如何都能保证稳定的格式。此区域设置对于解析服务器 API 的日期是强制性的,尤其是 ISO 8601 格式。它使用公历、24 小时时间格式和点号作为小数分隔符。
import Foundation
// 可用的区域设置标识符
let available: [String] = Locale.availableIdentifiers
print("区域设置总数:\(available.count)")
// 过滤俄语区域设置
let russianLocales = available.filter { $0.hasPrefix("ru") }
print("俄语区域设置:\(russianLocales)")
// 区域设置组件
let locale = Locale(identifier: "de_DE")
print("语言:\(locale.languageCode ?? "nil")")
print("地区:\(locale.regionCode ?? "nil")")
print("货币:\(locale.currencyCode ?? "nil")")
print("日历:\(locale.calendar.identifier)")
检查可用的区域设置通过 Locale.availableIdentifiers 返回当前 iOS 版本支持的所有标识符数组。要按地区过滤,请使用 Locale.availableIdentifiers.filter 并检查 regionCode。这对于构建没有硬编码列表的地区选择 UI 非常有用。
Locale.current — 获取用户在 iOS 设置(Settings > General > Language & Region)中设置的设备当前区域设置的主要方法。当在设置中更改语言或地区时,此属性会自动更新,无需重新启动应用程序。但是,它可能与用于显示内容的首选区域设置不一致:用户可以将界面语言设置为英语,但以俄语格式查看日期。
要更准确地确定用户偏好,请使用 Locale.preferredLanguages — 按用户优先级排序的语言数组。第一个元素是主界面语言。此列表对应于 Language & Region 中的设置,包括按偏好顺序拖动语言。通信应用程序(即时通讯工具、电子邮件客户端)在选择内容显示语言时应考虑此顺序。
import Foundation
// 当前系统区域设置
let current = Locale.current
print("当前区域设置:\(current.identifier)")
print("语言:\(current.language?.disjointName ?? "nil")")
// 用户偏好的语言
let preferred = Locale.preferredLanguages
print("偏好的语言:\(preferred)")
// 从当前区域设置获取地区
if let region = current.regionCode {
let regionLocale = Locale(identifier: "en_\(region)")
let countryName = regionLocale.localizedString(
forRegionCode: region
)
print("国家:\(countryName ?? region)")
}
// 检查24小时格式
let uses24h = current.uses24hClock(
for: .dateAndTime
)
print("使用24小时:\(uses24h)")
UI 中的本地化:要以界面语言显示月份和星期几的名称,请使用设置了 locale 的 Calendar。Calendar.current.symbols(for: .month) 返回当前区域设置语言的月份名称。要以用户语言显示国家名称,请使用 Locale.current.localizedString(forRegionCode:)。
日期格式化考虑区域设置 — 向用户显示日期时的关键任务。设置了 locale 的 DateFormatter 会自动为用户所在地区选择正确的日期和时间格式。对于 dateStyle 和 timeStyle 的值 .short、.medium、.long、.full,格式化程序使用区域设置规则来组合日期组件。
import Foundation
let date = Date()
// 使用不同区域设置格式化
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.locale = Locale(identifier: "ru_RU")
print("俄语:\(formatter.string(from: date))")
formatter.locale = Locale(identifier: "en_US")
print("英语:\(formatter.string(from: date))")
formatter.locale = Locale(identifier: "ja_JP")
print("日语:\(formatter.string(from: date))")
// 使用区域设置格式化货币
let numFormatter = NumberFormatter()
numFormatter.numberStyle = .currency
numFormatter.locale = Locale(identifier: "de_DE")
print("德国货币:\(numFormatter.string(from: 1234.56) ?? "nil")")
numFormatter.locale = Locale(identifier: "en_US")
print("美国货币:\(numFormatter.string(from: 1234.56) ?? "nil")")
从服务器 API 解析日期应始终使用 Locale(identifier: "en_US_POSIX") 以获得固定格式。服务器通常以 ISO 8601 格式发送带有英文月份名称的日期,使用设备的当前区域设置可能会导致解析错误,如果用户位于非英语语言区域。en_US_POSIX 保证解析不依赖于设备设置。
import Foundation
// 正确解析服务器日期
let isoFormatter = DateFormatter()
isoFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
isoFormatter.locale = Locale(identifier: "en_US_POSIX")
isoFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let serverDate = "2024-07-21T14:30:00+0000"
if let parsed = isoFormatter.date(from: serverDate) {
print("解析后的日期:\(parsed)")
}
// 本地化货币名称
let usLocale = Locale(identifier: "en_US")
let currencyName = usLocale.localizedString(
forCurrencyCode: "RUB"
)
print("美国区域设置中的俄罗斯卢布:\(currencyName ?? "nil")")
附加功能: Locale 通过 localizedString(forRegionCode:)、localizedString(forLanguageCode:)、localizedString(forCurrencyCode:) 和 localizedString(forCalendarIdentifier:) 方法提供其组件的本地化描述。这些方法返回调用它们的区域设置的语言名称。例如,Locale(identifier: "ru_RU").localizedString(forCountryCode: "DE") 将返回"德国"。
在解析数字时忽略 Locale — 国际化应用程序中的严重错误。没有明确区域设置的 NumberFormatter 会使用设备的当前区域设置。如果俄罗斯的用户输入"1,5",NumberFormatter.number(from: "1,5") 会正确返回 1.5。但是,如果相同的代码在具有 en_US 区域设置的设备上运行,解析将返回 nil,因为对于 en_US,小数分隔符是点号。
缺少用于服务器日期的 en_US_POSIX 会导致难以发现的错误。使用 dateFormat 和 locale = Locale.current 的 DateFormatter 可能会在日期格式与美国不同的地区的用户中出现问题。例如,在德国,DateFormatter 可能期望"21.07.2024",而服务器发送"07/21/2024"。en_US_POSIX 保证了机器解析的固定格式,与用户地区无关。
比较日期字符串而不是使用 Date — 另一个常见错误。开发人员有时会比较来自不同区域设置的日期的字符串表示,得到不正确的结果。Locale 只改变显示,不改变日期的绝对值。始终比较 Date 对象,而不是它们的字符串表示。要比较日期组件,请使用明确指定了 locale 的 Calendar。
根据 WWDC 2023,大约 30% 的应用程序国际化问题与 Locale 配置错误有关。Apple 建议在使用服务器数据时始终为 DateFormatter 和 NumberFormatter 明确设置 locale,并且仅在 UI 中使用 Locale.current。这种简单的做法可以消除大多数与区域设置相关的错误。
常见问题
Locale — 表示文化和语言格式化规则的 Foundation 类:日期、数字、货币和度量单位的格式。与 DateFormatter、NumberFormatter 和 Calendar 一起使用,实现本地化数据展示。
Locale 确定显示格式(语言、地区约定),而 TimeZone 确定相对于 UTC 的绝对时间偏移。Locale 影响字符串表示,TimeZone 影响时间的数值。两者一起使用可以完成日期的完整格式化。
en_US_POSIX — 一种用于技术格式化的特殊区域设置,无论用户设置如何都能保证稳定的格式。对于解析服务器日期(ISO 8601)和使用格式必须可预测的 API 是强制性的。
Locale.availableIdentifiers 返回包含所有支持的区域设置标识符的字符串数组。要按语言过滤,请使用带 hasPrefix 的 filter,要获取地区 — Locale(identifier:).regionCode。
NumberFormatter 使用 Locale 来确定小数分隔符(逗号或点号)、货币符号和分组分隔符。对于固定格式,将 locale 设置为 en_US_POSIX 或明确指定格式化程序的属性。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。