Swift MeasurementFormatter在CI(GitHub Actions)环境中忽略自定义区域设置但本地运行正常——始终使用句点而非逗号作为小数分隔符
Swift MeasurementFormatter在CI(GitHub Actions)环境中忽略自定义区域设置但本地运行正常——始终使用句点而非逗号作为小数分隔符
最近我写了个自定义的距离格式化工具类,既能生成普通字符串,也能生成带样式的富文本字符串,先给大家看看最初的代码:
import Foundation import SwiftUI /// A formatter that converts a distance value (in meters) into a localized, styled `AttributedString`. /// /// This formatter: /// - Automatically switches between metric (km) and imperial (mi) based on the user's locale. /// - Applies custom fonts and colors to the numeric value and unit class DistanceAttributedFormatter: Formatter { private let measurementFormatter = MeasurementFormatter() override init() { super.init() measurementFormatter.numberFormatter.maximumFractionDigits = 1 measurementFormatter.unitOptions = .providedUnit } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func string(from meters: Double) -> String { let distance = Measurement(value: meters, unit: UnitLength.meters) let converted = distance.converted(to: Locale.current.usesMetricSystem ? .kilometers : .miles) return measurementFormatter.string(from: converted) } func attributedString(from meters: Double) -> AttributedString { let plainString = string(from: meters) var attributed = AttributedString(plainString) // 拆分数字和单位,设置不同样式 if let numberRange = attributed.range(of: #"\d+(\.\d+)?"#, options: .regularExpression) { attributed[numberRange].font = .system(size: 18, weight: .bold) attributed[numberRange].foregroundColor = .primary } if let unitRange = attributed.range(of: #"[a-zA-Z]+"#, options: .regularExpression) { attributed[unitRange].font = .system(size: 14, weight: .medium) attributed[unitRange].foregroundColor = .secondary } return attributed } }
本来本地测试一切正常,比如我把系统区域切到de_DE,它会自动用逗号做小数分隔符,比如显示1,5 km。可一放到GitHub Actions的CI环境里跑测试就出问题了——不管我在代码里怎么强制设置measurementFormatter.locale = Locale(identifier: "de_DE"),输出的小数分隔符始终是句点,完全无视我设置的自定义区域。
我折腾了好半天,试了各种办法:
- 全局设置
Locale.current = Locale(identifier: "de_DE"),CI里完全没用 - 检查CI环境的系统区域,确实是默认的
en_US,但按道理代码里指定的locale应该覆盖系统设置才对 - 甚至尝试在CI workflow里提前设置系统区域为
de_DE,还是没效果
后来我琢磨着,可能是CI环境里的Foundation框架在处理MeasurementFormatter的时候,对locale的优先级处理和本地不一样?或者是GitHub Actions的虚拟机里有什么系统级的限制,导致代码里的自定义locale没被正确应用?
最后我放弃了依赖MeasurementFormatter的自动本地化,改成手动处理数字和单位的格式化,这样不管在什么环境下都能精准控制格式:
import Foundation import SwiftUI class DistanceFormatter { private let numberFormatter: NumberFormatter private let targetLocale: Locale init(locale: Locale = .current) { self.targetLocale = locale self.numberFormatter = NumberFormatter() self.numberFormatter.locale = locale self.numberFormatter.numberStyle = .decimal self.numberFormatter.maximumFractionDigits = 1 } func formattedAttributedDistance(meters: Double) -> AttributedString { // 1. 转换单位 let targetUnit: UnitLength = targetLocale.usesMetricSystem ? .kilometers : .miles let convertedDistance = Measurement(value: meters, unit: UnitLength.meters).converted(to: targetUnit) // 2. 用NumberFormatter生成带正确分隔符的数字字符串 guard let numericPart = numberFormatter.string(from: NSNumber(value: convertedDistance.value)) else { return AttributedString("\(String(format: "%.1f", convertedDistance.value)) \(targetUnit.symbol)") } // 3. 拼接单位 let unitPart = targetUnit.symbol let fullString = "\(numericPart) \(unitPart)" var attributedString = AttributedString(fullString) // 4. 设置样式 if let numberRange = attributedString.range(of: numericPart) { attributedString[numberRange].font = .system(size: 18, weight: .bold) attributedString[numberRange].foregroundColor = .primary } if let unitRange = attributedString.range(of: unitPart) { attributedString[unitRange].font = .system(size: 14, weight: .medium) attributedString[unitRange].foregroundColor = .secondary } return attributedString } }
改成这样之后,不管是本地还是CI环境,只要指定了对应的locale,就能输出正确的小数分隔符了——比如设置de_DE的话,就会输出1,5 km,完美解决了CI里的问题。
内容来源于stack exchange
相关产品推荐
相关产品推荐

