You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 13:52:58