根据所选的货币代码格式化货币,而与设备的区域设置无关(Swift)

时间:2020-08-07 09:10:58

标签: ios swift locale currency numberformatter

我正在尝试根据用户选择的货币来格式化货币。如果未选择任何货币,则使用设备的当前语言环境进行格式化。但是,我遇到了问题:

我正在使用数字格式化程序来格式化双精度货币字符串。

let formatter = NumberFormatter()
    formatter.numberStyle = .currency
    formatter.currencySymbol = ""
        
    if currencyCode != nil {
        formatter.currencyCode = currencyCode
    }
        
    let amount = Double(amt/100) + Double(amt%100)/100
    return formatter.string(from: NSNumber(value: amount))
}

currencyCode基本上是用户选择的货币。但是,如果说用户选择了欧元,其格式与美元的格式几乎相同,这意味着它不尊重所选择的货币。我知道我们不可能用currencyCode创建一个语言环境,因为EUR在26个不同的国家/地区使用过,因此不可能得出正确的语言环境。

此外,由于我使用的是基本填充小数位的格式,因此ONES,Teth等等等以及某些货币不支持小数位,例如PKR(巴基斯坦卢比),那么我该如何满足呢? / p>

所以我的问题是,无论选择哪种设备区域设置,如何正确设置货币格式。 如果我的设备语言环境是USD,并且创建了EUR列表,则希望列表中的所有付款都采用EUR格式。 因此,如果美元价格为3,403.23美元(欧元),则应该为3,403.23欧元。

关于如何格式化的任何建议?谢谢!

3 个答案:

答案 0 :(得分:2)

由于可以从Locale的{​​{1}}属性创建所有受支持的Locale,然后可以检查其{ {1}}属性,以匹配用户输入的货币代码。

availableIdentifiers

但是,如您所见,这可以返回奇特的语言环境,不一定提供所需的格式。

我宁愿建议为应用支持的每种货币代码对所需的Locale进行硬编码,这样您可以100%确保格式始终符合您的要求。您还可以混合使用这两种方法,并为著名的货币代码对currencyCode进行硬编码,但是对于更奇特的货币代码,可以使用动态方法,您对格式应该没有严格的要求。

答案 1 :(得分:2)

简而言之

与货币相关的语言环境设置有两种:

  • 与货币有关:它们与货币价值有关,并且仅取决于货币,并且在您使用该货币的任何地方均保持有效。这只是ISO 4217定义的国际ISO代码和小数位数。
  • 文化设置:这些设置取决于与用户的语言和国家/地区相关的用法和惯例,而不直接取决于货币。通常,它是货币代码或符号相对于值的位置,以及小数点和千位分隔符。

幸运的是,Swift非常出色。这里有一些代码,使您可以适应依赖于货币的设置,而无需接触对用户重要的文化设置。我还将解释为什么您不应该更改所有本地设置。

代码

以下是演示代码,并带有几种代表货币:

let value: Double = 1345.23
for mycur in ["USD", "TND", "EUR", "JPY" ] {
    let myformatter = NumberFormatter()
    myformatter.numberStyle = .currencyISOCode
    let newLocale = "\(Locale.current.identifier)@currency=\(mycur)" // this is it!
    myformatter.locale = Locale(identifier:newLocale)
    print ("currency:\(mycur): min:\(myformatter.minimumFractionDigits) max:\(myformatter.maximumFractionDigits)" 
    print ("result: \(myformatter.string(from: value as NSNumber) ?? "xxx")")
}

对于代表性的演示,我使用过:

  • 美元和欧元,就像大多数货币一样,可以分为100个子单位(美分),
  • TND(突尼斯第纳尔),与其他第纳尔货币一样,可以分为1000个子单位(毫厘),
  • JPY(日元),过去可以分为很小的价值的子单位(sen),日本政府决定不再使用它们。这就是为什么日元金额不再有小数。

结果

对于用户而言,将受益于principle of least astonishment,并看到小数点和千位分隔符以及他/她习惯的位置。

在我当前的语言环境中(在我的语言中,货币代码在右边,小数点之间用逗号分隔,数千个空格之间有空格),结果将是:

cur:USD: min:2 max:2 result: 1  345,23 USD
cur:TND: min:3 max:3 result: 1  345,230 TND
cur:EUR: min:2 max:2 result: 1  345,23 EUR
cur:JPY: min:0 max:0 result: 1  345 JPY

但是,如果您通常在说英语的环境中工作,例如在美国文化中工作,您将会得到:

cur:USD: min:2 max:2 result: USD 1,345.23
cur:TND: min:3 max:3 result: TND 1,345.230
cur:EUR: min:2 max:2 result: EUR 1,345.23
cur:JPY: min:0 max:0 result: JPY 1,345

工作方式:

该代码的窍门是通过更改货币设置来创建新的语言环境,而保留所有其他与国家和语言相关的参数。

    let newLocale = "\(Locale.current.identifier)@currency=\(mycur)" // this is it!
    myformatter.locale = Locale(identifier:newLocale)

为什么你不应该完全实现想要的东西

如果您开始调整位置以采用货币来源国的语言惯例,可能会激怒那些不再在期望的位置看到货币代码的用户。幸运的是,它不会造成真正的混乱。

示例:欧元是具有不同文化背景的国家/地区的货币。因此,关于货币或货币符号的定位规则被定义为取决于出现金额的文本的语言。 Official reference

现在,如果您是采用其他语言或国家/地区的thousand and decimal separators,因为它是该货币的母国,这将会造成真正的混乱,尤其是对于较小的货币。而且,并非总是可能。

示例:在加拿大,法语国家的加拿大人使用逗号分隔小数点,而英语国家的加拿大人则用小数点分隔。这清楚地表明,决定使用分隔符的不是货币,而是用户的语言。

因此,在这方面您应该尊重用户的设置,并且仅调整特定于货币的设置。

答案 2 :(得分:0)

添加到DávidPásztor的answer

如果您不想支持小数,可以使用:

formatter.maximumFractionDigits = 0

您可以找到包含此内容的语言环境,也可以添加自己的支票:

if ["PKR", "some other currency"].contains(currencyCode) {
    formatter.maximumFractionDigits = 0
}

请注意,如果您决定在应用中将EUR始终设置为€ 3 403,23格式,则对于某些用户来说可能是错误的。语言环境不是通用的。