Форматирование валюты в зависимости от выбранного кода валюты независимо от локали устройства (Swift) - PullRequest
2 голосов
/ 07 августа 2020

Я пытаюсь отформатировать валюты в зависимости от валюты, выбранной пользователем. Если валюта не выбрана, для форматирования используется текущая локаль устройства. однако у меня возникают проблемы:

Я использую средство форматирования чисел для форматирования строки double в денежную единицу.

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 - это валюта, которую выбрал пользователь. Однако, если, скажем, пользователь выбирает евро, форматирование во многом такое же, как и для долларов США, что означает, что он не соблюдает выбранную валюту. Я знаю, что мы не можем создать Locale из currencyCode, поскольку EUR используется в 26 разных странах, поэтому невозможно получить правильный языковой стандарт.

Кроме того, поскольку я использую формат, который в основном заполняет позицию десятичных знаков, тогда ЕДИНИЦЫ, десятые и так далее, а некоторые валюты не поддерживают десятичные позиции, например, PKR (пакистанская рупия), так как я могу это сделать? правильно независимо от того, какой языковой стандарт устройства выбран. Если язык моего устройства - доллары США и я создаю список в евро, я бы хотел, чтобы все платежи внутри списка были в формате евро. Итак, если цена в долларах США составляет 3 403,23 доллара в евро, она должна быть 3 403,23 евро.

Есть какие-нибудь советы о том, как go форматировать? Спасибо!

Ответы [ 3 ]

2 голосов
/ 05 сентября 2020

Вкратце

Настройки локали, относящиеся к валюте, бывают двух видов:

  • Зависит от валюты : они связаны с денежной стоимостью и зависят только от валюты и остаются действительными, где бы вы ни использовали эту валюту. Это только международный код ISO и количество десятичных знаков, как определено ISO 4217 .
  • Культурные параметры : они зависят от обычаев и практик, связанных с язык и страна пользователей , а не напрямую к валюте. Как правило, это позиция кода валюты или символа относительно значения, а также десятичный разделитель и разделитель тысяч.

К счастью, 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 (Японская иена), которую в прошлом можно было разделить на субъединицы (сенс) такой небольшой стоимости, что правительство Японии решило больше не использовать их. Вот почему для сумм в JPY больше нет десятичных знаков.

Результаты

Для пользователя будет полезен принцип наименьшего удивления , и вы увидите десятичный разделитель и разделитель тысяч, а также позиционирование, к которому он / она привык.

в моем текущем языковом стандарте (на моем языке код валюты находится справа, десятичные дроби разделяются запятой, а тысячи - с жестким пробелом ) результат будет:

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

Но если вы обычно работаете в среде, говорящей на английском sh, например, в культуре США, вы получите:

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)

Почему вам не следует полностью реализовывать то, что вы хотели

Если вы начнете адаптировать позиционирование, чтобы взять практику языка страны происхождения валюты, вы можете раздражать пользователи, которые больше не видят код валюты там, где они ожидают. К счастью, это не вызовет путаницы.

Пример: евро - валюта стран с очень разными культурами. Поэтому было определено, что правило позиционирования валюты или символа валюты зависит от языка текста, в котором отображается сумма. Официальная ссылка

Теперь, если вы начнете использовать разделители тысяч и десятичные дроби другого языка или страны, потому что это родная страна валюты, это вызовет настоящую путаницу. , особенно для небольших сумм. Более того, это не всегда возможно.

Пример: в Канаде одна и та же сумма в валюте записывается с десятичным разделителем запятой франкоязычными канадцами, а точка десятичного разделителя - англия sh канадцами. Это ясно показывает, что не валюта определяет используемые разделители, а язык пользователя.

Поэтому вы должны уважительно относиться к настройкам пользователя в этом отношении и изменять только настройки c конкретной валюты .

2 голосов
/ 07 августа 2020

Вы можете динамически сопоставить Locale s с кодами валют, поскольку вы можете создать все поддерживаемые Locale s из свойства availableIdentifiers Locale, а затем вы можете проверить их свойство currencyCode на соответствие коду валюты. ваш пользовательский ввод.

extension Locale: CaseIterable {
    public static var allCases: [Locale] {
        availableIdentifiers.map(Locale.init(identifier:))
    }
}

extension Locale {
    static func localeForCurrencyCode(_ currencyCode: String) -> Locale? {
        return allCases.first(where: { $0.currencyCode == currencyCode })
    }
}

Locale.localeForCurrencyCode("EUR") // es_EA
Locale.localeForCurrencyCode("GBP") // kw_GB

Однако, как вы можете видеть, это может вернуть exoti c locales, которые не обязательно могут дать желаемое форматирование.

Я бы предпочел жестко закодировать желаемый Locale для каждого кода валюты, который поддерживает ваше приложение, поэтому вы можете быть на 100% уверены, что форматирование всегда соответствует вашим требованиям. Вы также можете смешать два подхода и жестко запрограммировать Locale s для хорошо известных кодов валют, но использовать подход Dynami c для большего количества кодов валют exoti c, которые у вас нет жестких требований относительно того, как они должны быть отформатированы .

0 голосов
/ 07 августа 2020

Добавление к ответу Давида Пастора .

Если вы не хотите поддерживать десятичные дроби, вы можете использовать:

formatter.maximumFractionDigits = 0

Вы можете найти локаль, которая Включите ли это или добавьте свой чек:

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

Обратите внимание, что если вы решите, что в вашем приложении EUR всегда должен иметь формат € 3 403,23, это может быть неверным для некоторых пользователей. Локаль не универсальна.

...