mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Update formatting
This commit is contained in:
parent
20325dd69c
commit
a6e2f05b3a
@ -44,7 +44,7 @@ private enum BotCheckoutSection: Int32 {
|
|||||||
enum BotCheckoutEntry: ItemListNodeEntry {
|
enum BotCheckoutEntry: ItemListNodeEntry {
|
||||||
case header(PresentationTheme, TelegramMediaInvoice, String)
|
case header(PresentationTheme, TelegramMediaInvoice, String)
|
||||||
case price(Int, PresentationTheme, String, String, Bool, Bool)
|
case price(Int, PresentationTheme, String, String, Bool, Bool)
|
||||||
case tip(Int, PresentationTheme, String, String, String, Int64, [(String, Int64)])
|
case tip(Int, PresentationTheme, String, String, String, Int64, Int64, [(String, Int64)])
|
||||||
case paymentMethod(PresentationTheme, String, String)
|
case paymentMethod(PresentationTheme, String, String)
|
||||||
case shippingInfo(PresentationTheme, String, String)
|
case shippingInfo(PresentationTheme, String, String)
|
||||||
case shippingMethod(PresentationTheme, String, String)
|
case shippingMethod(PresentationTheme, String, String)
|
||||||
@ -69,7 +69,7 @@ enum BotCheckoutEntry: ItemListNodeEntry {
|
|||||||
return 0
|
return 0
|
||||||
case let .price(index, _, _, _, _, _):
|
case let .price(index, _, _, _, _, _):
|
||||||
return 1 + Int32(index)
|
return 1 + Int32(index)
|
||||||
case let .tip(index, _, _, _, _, _, _):
|
case let .tip(index, _, _, _, _, _, _, _):
|
||||||
return 1 + Int32(index)
|
return 1 + Int32(index)
|
||||||
case .paymentMethod:
|
case .paymentMethod:
|
||||||
return 10000 + 2
|
return 10000 + 2
|
||||||
@ -127,8 +127,8 @@ enum BotCheckoutEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .tip(lhsIndex, lhsTheme, lhsText, lhsCurrency, lhsValue, lhsNumericValue, lhsVariants):
|
case let .tip(lhsIndex, lhsTheme, lhsText, lhsCurrency, lhsValue, lhsNumericValue, lhsMaxValue, lhsVariants):
|
||||||
if case let .tip(rhsIndex, rhsTheme, rhsText, rhsCurrency, rhsValue, rhsNumericValue, rhsVariants) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsText == rhsText, lhsCurrency == rhsCurrency, lhsValue == rhsValue, lhsNumericValue == rhsNumericValue {
|
if case let .tip(rhsIndex, rhsTheme, rhsText, rhsCurrency, rhsValue, rhsNumericValue, rhsMaxValue, rhsVariants) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsText == rhsText, lhsCurrency == rhsCurrency, lhsValue == rhsValue, lhsNumericValue == rhsNumericValue, lhsMaxValue == rhsMaxValue {
|
||||||
if lhsVariants.count != rhsVariants.count {
|
if lhsVariants.count != rhsVariants.count {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -194,8 +194,8 @@ enum BotCheckoutEntry: ItemListNodeEntry {
|
|||||||
return BotCheckoutHeaderItem(account: arguments.account, theme: theme, invoice: invoice, botName: botName, sectionId: self.section)
|
return BotCheckoutHeaderItem(account: arguments.account, theme: theme, invoice: invoice, botName: botName, sectionId: self.section)
|
||||||
case let .price(_, theme, text, value, isFinal, hasSeparator):
|
case let .price(_, theme, text, value, isFinal, hasSeparator):
|
||||||
return BotCheckoutPriceItem(theme: theme, title: text, label: value, isFinal: isFinal, hasSeparator: hasSeparator, sectionId: self.section)
|
return BotCheckoutPriceItem(theme: theme, title: text, label: value, isFinal: isFinal, hasSeparator: hasSeparator, sectionId: self.section)
|
||||||
case let .tip(_, _, text, currency, value, numericValue, variants):
|
case let .tip(_, _, text, currency, value, numericValue, maxValue, variants):
|
||||||
return BotCheckoutTipItem(theme: presentationData.theme, title: text, currency: currency, value: value, numericValue: numericValue, availableVariants: variants, sectionId: self.section, updateValue: { value in
|
return BotCheckoutTipItem(theme: presentationData.theme, strings: presentationData.strings, title: text, currency: currency, value: value, numericValue: numericValue, maxValue: maxValue, availableVariants: variants, sectionId: self.section, updateValue: { value in
|
||||||
arguments.updateTip(value)
|
arguments.updateTip(value)
|
||||||
})
|
})
|
||||||
case let .paymentMethod(_, text, value):
|
case let .paymentMethod(_, text, value):
|
||||||
@ -324,7 +324,7 @@ private func botCheckoutControllerEntries(presentationData: PresentationData, st
|
|||||||
let tipTitle: String
|
let tipTitle: String
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
tipTitle = "Tip (Optional)"
|
tipTitle = "Tip (Optional)"
|
||||||
entries.append(.tip(index, presentationData.theme, tipTitle, paymentForm.invoice.currency, "\(formatCurrencyAmount(currentTip ?? 0, currency: paymentForm.invoice.currency))", currentTip ?? 0, tip.suggested.map { item -> (String, Int64) in
|
entries.append(.tip(index, presentationData.theme, tipTitle, paymentForm.invoice.currency, "\(formatCurrencyAmount(currentTip ?? 0, currency: paymentForm.invoice.currency))", currentTip ?? 0, tip.max, tip.suggested.map { item -> (String, Int64) in
|
||||||
return ("\(formatCurrencyAmount(item, currency: paymentForm.invoice.currency))", item)
|
return ("\(formatCurrencyAmount(item, currency: paymentForm.invoice.currency))", item)
|
||||||
}))
|
}))
|
||||||
index += 1
|
index += 1
|
||||||
|
@ -10,10 +10,12 @@ import TelegramStringFormatting
|
|||||||
|
|
||||||
class BotCheckoutTipItem: ListViewItem, ItemListItem {
|
class BotCheckoutTipItem: ListViewItem, ItemListItem {
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
let strings: PresentationStrings
|
||||||
let title: String
|
let title: String
|
||||||
let currency: String
|
let currency: String
|
||||||
let value: String
|
let value: String
|
||||||
let numericValue: Int64
|
let numericValue: Int64
|
||||||
|
let maxValue: Int64
|
||||||
let availableVariants: [(String, Int64)]
|
let availableVariants: [(String, Int64)]
|
||||||
let updateValue: (Int64) -> Void
|
let updateValue: (Int64) -> Void
|
||||||
|
|
||||||
@ -21,12 +23,14 @@ class BotCheckoutTipItem: ListViewItem, ItemListItem {
|
|||||||
|
|
||||||
let requestsNoInset: Bool = true
|
let requestsNoInset: Bool = true
|
||||||
|
|
||||||
init(theme: PresentationTheme, title: String, currency: String, value: String, numericValue: Int64, availableVariants: [(String, Int64)], sectionId: ItemListSectionId, updateValue: @escaping (Int64) -> Void) {
|
init(theme: PresentationTheme, strings: PresentationStrings, title: String, currency: String, value: String, numericValue: Int64, maxValue: Int64, availableVariants: [(String, Int64)], sectionId: ItemListSectionId, updateValue: @escaping (Int64) -> Void) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
self.title = title
|
self.title = title
|
||||||
self.currency = currency
|
self.currency = currency
|
||||||
self.value = value
|
self.value = value
|
||||||
self.numericValue = numericValue
|
self.numericValue = numericValue
|
||||||
|
self.maxValue = maxValue
|
||||||
self.availableVariants = availableVariants
|
self.availableVariants = availableVariants
|
||||||
self.updateValue = updateValue
|
self.updateValue = updateValue
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
@ -154,6 +158,8 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
let labelNode: TextNode
|
let labelNode: TextNode
|
||||||
private let textNode: TextFieldNode
|
private let textNode: TextFieldNode
|
||||||
|
|
||||||
|
private var formatterDelegate: CurrencyUITextFieldDelegate?
|
||||||
|
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
private var valueNodes: [TipValueNode] = []
|
private var valueNodes: [TipValueNode] = []
|
||||||
|
|
||||||
@ -187,7 +193,6 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
self.addSubnode(self.scrollNode)
|
self.addSubnode(self.scrollNode)
|
||||||
|
|
||||||
self.textNode.clipsToBounds = true
|
self.textNode.clipsToBounds = true
|
||||||
self.textNode.textField.delegate = self
|
|
||||||
self.textNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged)
|
self.textNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged)
|
||||||
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||||
}
|
}
|
||||||
@ -216,6 +221,7 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
//TODO:locali
|
||||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Enter Custom", font: textFont, textColor: textColor.withMultipliedAlpha(0.8)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Enter Custom", font: textFont, textColor: textColor.withMultipliedAlpha(0.8)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||||
@ -241,6 +247,25 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
strongSelf.labelNode.isHidden = !text.isEmpty
|
strongSelf.labelNode.isHidden = !text.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strongSelf.formatterDelegate == nil {
|
||||||
|
strongSelf.formatterDelegate = CurrencyUITextFieldDelegate(formatter: CurrencyFormatter(currency: item.currency, { formatter in
|
||||||
|
formatter.maxValue = currencyToFractionalAmount(value: item.maxValue, currency: item.currency) ?? 10000.0
|
||||||
|
formatter.minValue = 0.0
|
||||||
|
formatter.hasDecimals = true
|
||||||
|
}))
|
||||||
|
strongSelf.formatterDelegate?.passthroughDelegate = strongSelf
|
||||||
|
|
||||||
|
strongSelf.formatterDelegate?.textUpdated = {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.textFieldTextChanged(strongSelf.textNode.textField)
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.textNode.clipsToBounds = true
|
||||||
|
strongSelf.textNode.textField.delegate = strongSelf.formatterDelegate
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.textNode.textField.typingAttributes = [NSAttributedString.Key.font: titleFont]
|
strongSelf.textNode.textField.typingAttributes = [NSAttributedString.Key.font: titleFont]
|
||||||
strongSelf.textNode.textField.font = titleFont
|
strongSelf.textNode.textField.font = titleFont
|
||||||
|
|
||||||
@ -323,11 +348,6 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
guard let item = self.item else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
178
submodules/BotPaymentsUI/Sources/Formatter/Currency.swift
Normal file
178
submodules/BotPaymentsUI/Sources/Formatter/Currency.swift
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
//
|
||||||
|
// CurrencyCode.swift
|
||||||
|
// CurrencyText
|
||||||
|
//
|
||||||
|
// Created by Felipe Lefèvre Marino on 1/26/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Currency wraps all availabe currencies that can represented as formatted monetary values
|
||||||
|
/// A currency code is a three-letter code that is, in most cases,
|
||||||
|
/// composed of a country’s two-character Internet country code plus an extra character
|
||||||
|
/// to denote the currency unit. For example, the currency code for the Australian
|
||||||
|
/// dollar is “AUD”. Currency codes are based on the ISO 4217 standard
|
||||||
|
public enum Currency: String {
|
||||||
|
case afghani = "AFN",
|
||||||
|
algerianDinar = "DZD",
|
||||||
|
argentinePeso = "ARS",
|
||||||
|
armenianDram = "AMD",
|
||||||
|
arubanFlorin = "AWG",
|
||||||
|
australianDollar = "AUD",
|
||||||
|
azerbaijanManat = "AZN",
|
||||||
|
bahamianDollar = "BSD",
|
||||||
|
bahrainiDinar = "BHD",
|
||||||
|
baht = "THB",
|
||||||
|
balboa = "PAB",
|
||||||
|
barbadosDollar = "BBD",
|
||||||
|
belarusianRuble = "BYN",
|
||||||
|
belizeDollar = "BZD",
|
||||||
|
bermudianDollar = "BMD",
|
||||||
|
boliviano = "BOB",
|
||||||
|
bolívar = "VEF",
|
||||||
|
brazilianReal = "BRL",
|
||||||
|
bruneiDollar = "BND",
|
||||||
|
bulgarianLev = "BGN",
|
||||||
|
burundiFranc = "BIF",
|
||||||
|
caboVerdeEscudo = "CVE",
|
||||||
|
canadianDollar = "CAD",
|
||||||
|
caymanIslandsDollar = "KYD",
|
||||||
|
chileanPeso = "CLP",
|
||||||
|
colombianPeso = "COP",
|
||||||
|
comorianFranc = "KMF",
|
||||||
|
congoleseFranc = "CDF",
|
||||||
|
convertibleMark = "BAM",
|
||||||
|
cordobaOro = "NIO",
|
||||||
|
costaRicanColon = "CRC",
|
||||||
|
cubanPeso = "CUP",
|
||||||
|
czechKoruna = "CZK",
|
||||||
|
dalasi = "GMD",
|
||||||
|
danishKrone = "DKK",
|
||||||
|
denar = "MKD",
|
||||||
|
djiboutiFranc = "DJF",
|
||||||
|
dobra = "STN",
|
||||||
|
dollar = "USD",
|
||||||
|
dominicanPeso = "DOP",
|
||||||
|
dong = "VND",
|
||||||
|
eastCaribbeanDollar = "XCD",
|
||||||
|
egyptianPound = "EGP",
|
||||||
|
elSalvadorColon = "SVC",
|
||||||
|
ethiopianBirr = "ETB",
|
||||||
|
euro = "EUR",
|
||||||
|
falklandIslandsPound = "FKP",
|
||||||
|
fijiDollar = "FJD",
|
||||||
|
forint = "HUF",
|
||||||
|
ghanaCedi = "GHS",
|
||||||
|
gibraltarPound = "GIP",
|
||||||
|
gourde = "HTG",
|
||||||
|
guarani = "PYG",
|
||||||
|
guineanFranc = "GNF",
|
||||||
|
guyanaDollar = "GYD",
|
||||||
|
hongKongDollar = "HKD",
|
||||||
|
hryvnia = "UAH",
|
||||||
|
icelandKrona = "ISK",
|
||||||
|
indianRupee = "INR",
|
||||||
|
iranianRial = "IRR",
|
||||||
|
iraqiDinar = "IQD",
|
||||||
|
jamaicanDollar = "JMD",
|
||||||
|
jordanianDinar = "JOD",
|
||||||
|
kenyanShilling = "KES",
|
||||||
|
kina = "PGK",
|
||||||
|
kuna = "HRK",
|
||||||
|
kuwaitiDinar = "KWD",
|
||||||
|
kwanza = "AOA",
|
||||||
|
kyat = "MMK",
|
||||||
|
laoKip = "LAK",
|
||||||
|
lari = "GEL",
|
||||||
|
lebanesePound = "LBP",
|
||||||
|
lek = "ALL",
|
||||||
|
lempira = "HNL",
|
||||||
|
leone = "SLL",
|
||||||
|
liberianDollar = "LRD",
|
||||||
|
libyanDinar = "LYD",
|
||||||
|
lilangeni = "SZL",
|
||||||
|
loti = "LSL",
|
||||||
|
malagasyAriary = "MGA",
|
||||||
|
malawiKwacha = "MWK",
|
||||||
|
malaysianRinggit = "MYR",
|
||||||
|
mauritiusRupee = "MUR",
|
||||||
|
mexicanPeso = "MXN",
|
||||||
|
mexicanUnidadDeInversion = "MXV",
|
||||||
|
moldovanLeu = "MDL",
|
||||||
|
moroccanDirham = "MAD",
|
||||||
|
mozambiqueMetical = "MZN",
|
||||||
|
mvdol = "BOV",
|
||||||
|
naira = "NGN",
|
||||||
|
nakfa = "ERN",
|
||||||
|
namibiaDollar = "NAD",
|
||||||
|
nepaleseRupee = "NPR",
|
||||||
|
netherlandsAntilleanGuilder = "ANG",
|
||||||
|
newIsraeliSheqel = "ILS",
|
||||||
|
newTaiwanDollar = "TWD",
|
||||||
|
newZealandDollar = "NZD",
|
||||||
|
ngultrum = "BTN",
|
||||||
|
northKoreanWon = "KPW",
|
||||||
|
norwegianKrone = "NOK",
|
||||||
|
ouguiya = "MRU",
|
||||||
|
paanga = "TOP",
|
||||||
|
pakistanRupee = "PKR",
|
||||||
|
pataca = "MOP",
|
||||||
|
pesoConvertible = "CUC",
|
||||||
|
pesoUruguayo = "UYU",
|
||||||
|
philippinePiso = "PHP",
|
||||||
|
poundSterling = "GBP",
|
||||||
|
pula = "BWP",
|
||||||
|
qatariRial = "QAR",
|
||||||
|
quetzal = "GTQ",
|
||||||
|
rand = "ZAR",
|
||||||
|
rialOmani = "OMR",
|
||||||
|
riel = "KHR",
|
||||||
|
romanianLeu = "RON",
|
||||||
|
rufiyaa = "MVR",
|
||||||
|
rupiah = "IDR",
|
||||||
|
russianRuble = "RUB",
|
||||||
|
rwandaFranc = "RWF",
|
||||||
|
saintHelenaPound = "SHP",
|
||||||
|
saudiRiyal = "SAR",
|
||||||
|
serbianDinar = "RSD",
|
||||||
|
seychellesRupee = "SCR",
|
||||||
|
singaporeDollar = "SGD",
|
||||||
|
sol = "PEN",
|
||||||
|
solomonIslandsDollar = "SBD",
|
||||||
|
som = "KGS",
|
||||||
|
somaliShilling = "SOS",
|
||||||
|
somoni = "TJS",
|
||||||
|
southSudanesePound = "SSP",
|
||||||
|
sriLankaRupee = "LKR",
|
||||||
|
sudanesePound = "SDG",
|
||||||
|
surinamDollar = "SRD",
|
||||||
|
swedishKrona = "SEK",
|
||||||
|
swissFranc = "CHF",
|
||||||
|
syrianPound = "SYP",
|
||||||
|
taka = "BDT",
|
||||||
|
tala = "WST",
|
||||||
|
tanzanianShilling = "TZS",
|
||||||
|
tenge = "KZT",
|
||||||
|
trinidadAndTobagoDollar = "TTD",
|
||||||
|
tugrik = "MNT",
|
||||||
|
tunisianDinar = "TND",
|
||||||
|
turkishLira = "TRY",
|
||||||
|
turkmenistanNewManat = "TMT",
|
||||||
|
uaeDirham = "AED",
|
||||||
|
ugandaShilling = "UGX",
|
||||||
|
unidadDeFomento = "CLF",
|
||||||
|
unidadDeValorReal = "COU",
|
||||||
|
uruguayPesoEnUnidadesIndexadas = "UYI",
|
||||||
|
uzbekistanSum = "UZS",
|
||||||
|
vatu = "VUV",
|
||||||
|
wirEuro = "CHE",
|
||||||
|
wirFranc = "CHW",
|
||||||
|
won = "KRW",
|
||||||
|
yemeniRial = "YER",
|
||||||
|
yen = "JPY",
|
||||||
|
yuanRenminbi = "CNY",
|
||||||
|
zambianKwacha = "ZMW",
|
||||||
|
zimbabweDollar = "ZWL",
|
||||||
|
zloty = "PLN",
|
||||||
|
none
|
||||||
|
}
|
@ -0,0 +1,345 @@
|
|||||||
|
//
|
||||||
|
// CurrencyFormatter.swift
|
||||||
|
// CurrencyText
|
||||||
|
//
|
||||||
|
// Created by Felipe Lefèvre Marino on 1/27/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
|
// MARK: - Currency protocols
|
||||||
|
|
||||||
|
public protocol CurrencyFormatting {
|
||||||
|
var maxDigitsCount: Int { get }
|
||||||
|
var decimalDigits: Int { get set }
|
||||||
|
var maxValue: Double? { get set }
|
||||||
|
var minValue: Double? { get set }
|
||||||
|
var initialText: String { get }
|
||||||
|
var currencySymbol: String { get set }
|
||||||
|
|
||||||
|
func string(from double: Double) -> String?
|
||||||
|
func unformatted(string: String) -> String?
|
||||||
|
func double(from string: String) -> Double?
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol CurrencyAdjusting {
|
||||||
|
func formattedStringWithAdjustedDecimalSeparator(from string: String) -> String?
|
||||||
|
func formattedStringAdjustedToFitAllowedValues(from string: String) -> String?
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Currency formatter
|
||||||
|
|
||||||
|
public class CurrencyFormatter: CurrencyFormatting {
|
||||||
|
|
||||||
|
/// Set the locale to retrieve the currency from
|
||||||
|
/// You can pass a Swift type Locale or one of the
|
||||||
|
/// Locales enum options - that encapsulates all available locales.
|
||||||
|
public var locale: LocaleConvertible {
|
||||||
|
set { self.numberFormatter.locale = newValue.locale }
|
||||||
|
get { self.numberFormatter.locale }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the desired currency type
|
||||||
|
/// * Note: The currency take effetcs above the displayed currency symbol,
|
||||||
|
/// however details such as decimal separators, grouping separators and others
|
||||||
|
/// will be set based on the defined locale. So for a precise experience, please
|
||||||
|
/// preferarbly setup both, when you are setting a currency that does not match the
|
||||||
|
/// default/current user locale.
|
||||||
|
public var currency: Currency {
|
||||||
|
set { numberFormatter.currencyCode = newValue.rawValue }
|
||||||
|
get { Currency(rawValue: numberFormatter.currencyCode) ?? .dollar }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define if currency symbol should be presented or not.
|
||||||
|
/// Note: when set to false the current currency symbol is removed
|
||||||
|
public var showCurrencySymbol: Bool = true {
|
||||||
|
didSet {
|
||||||
|
numberFormatter.currencySymbol = showCurrencySymbol ? numberFormatter.currencySymbol : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The currency's symbol.
|
||||||
|
/// Can be used to read or set a custom symbol.
|
||||||
|
/// Note: showCurrencySymbol must be set to true for
|
||||||
|
/// the currencySymbol to be correctly changed.
|
||||||
|
public var currencySymbol: String {
|
||||||
|
set {
|
||||||
|
guard showCurrencySymbol else { return }
|
||||||
|
numberFormatter.currencySymbol = newValue
|
||||||
|
}
|
||||||
|
get { numberFormatter.currencySymbol }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The lowest number allowed as input.
|
||||||
|
/// This value is initially set to the text field text
|
||||||
|
/// when defined.
|
||||||
|
public var minValue: Double? {
|
||||||
|
set {
|
||||||
|
guard let newValue = newValue else { return }
|
||||||
|
numberFormatter.minimum = NSNumber(value: newValue)
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
if let minValue = numberFormatter.minimum {
|
||||||
|
return Double(truncating: minValue)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The highest number allowed as input.
|
||||||
|
/// The text field will not allow the user to increase the input
|
||||||
|
/// value beyond it, when defined.
|
||||||
|
public var maxValue: Double? {
|
||||||
|
set {
|
||||||
|
guard let newValue = newValue else { return }
|
||||||
|
numberFormatter.maximum = NSNumber(value: newValue)
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
if let maxValue = numberFormatter.maximum {
|
||||||
|
return Double(truncating: maxValue)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of decimal digits shown.
|
||||||
|
/// default is set to zero.
|
||||||
|
/// * Example: With decimal digits set to 3, if the value to represent is "1",
|
||||||
|
/// the formatted text in the fractions will be ",001".
|
||||||
|
/// Other than that with the value as 1, the formatted text fractions will be ",1".
|
||||||
|
public var decimalDigits: Int {
|
||||||
|
set {
|
||||||
|
numberFormatter.minimumFractionDigits = newValue
|
||||||
|
numberFormatter.maximumFractionDigits = newValue
|
||||||
|
}
|
||||||
|
get { numberFormatter.minimumFractionDigits }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set decimal numbers behavior.
|
||||||
|
/// When set to true decimalDigits are automatically set to 2 (most currencies pattern),
|
||||||
|
/// and the decimal separator is presented. Otherwise decimal digits are not shown and
|
||||||
|
/// the separator gets hidden as well
|
||||||
|
/// When reading it returns the current pattern based on the setup.
|
||||||
|
/// Note: Setting decimal digits after, or alwaysShowsDecimalSeparator can overlap this definitios,
|
||||||
|
/// and should be only done if you need specific cases
|
||||||
|
public var hasDecimals: Bool {
|
||||||
|
set {
|
||||||
|
self.decimalDigits = newValue ? 2 : 0
|
||||||
|
self.numberFormatter.alwaysShowsDecimalSeparator = newValue ? true : false
|
||||||
|
}
|
||||||
|
get { decimalDigits != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the string that is the decimal separator
|
||||||
|
/// Note: only presented when hasDecimals is true OR decimalDigits
|
||||||
|
/// is greater than 0.
|
||||||
|
public var decimalSeparator: String {
|
||||||
|
set { self.numberFormatter.currencyDecimalSeparator = newValue }
|
||||||
|
get { numberFormatter.currencyDecimalSeparator }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can be used to set a custom currency code string
|
||||||
|
public var currencyCode: String {
|
||||||
|
set { self.numberFormatter.currencyCode = newValue }
|
||||||
|
get { numberFormatter.currencyCode }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets if decimal separator should always be presented,
|
||||||
|
/// even when decimal digits are disabled
|
||||||
|
public var alwaysShowsDecimalSeparator: Bool {
|
||||||
|
set { self.numberFormatter.alwaysShowsDecimalSeparator = newValue }
|
||||||
|
get { numberFormatter.alwaysShowsDecimalSeparator }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The amount of grouped numbers. This definition is fixed for at least
|
||||||
|
/// the first non-decimal group of numbers, and is applied to all other
|
||||||
|
/// groups if secondaryGroupingSize does not have another value.
|
||||||
|
public var groupingSize: Int {
|
||||||
|
set { self.numberFormatter.groupingSize = newValue }
|
||||||
|
get { numberFormatter.groupingSize }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The amount of grouped numbers after the first group.
|
||||||
|
/// Example: for the given value of 99999999999, when grouping size
|
||||||
|
/// is set to 3 and secondaryGroupingSize has 4 as value,
|
||||||
|
/// the number is represented as: (9999) (9999) [999].
|
||||||
|
/// Beign [] grouping size and () secondary grouping size.
|
||||||
|
public var secondaryGroupingSize: Int {
|
||||||
|
set { self.numberFormatter.secondaryGroupingSize = newValue }
|
||||||
|
get { numberFormatter.secondaryGroupingSize }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the string that is shown between groups of numbers
|
||||||
|
/// * Example: a monetary value of a thousand (1000) with a grouping
|
||||||
|
/// separator == "." is represented as `1.000` *.
|
||||||
|
/// Note: It automatically sets hasGroupingSeparator to true.
|
||||||
|
public var groupingSeparator: String {
|
||||||
|
set {
|
||||||
|
self.numberFormatter.currencyGroupingSeparator = newValue
|
||||||
|
self.numberFormatter.usesGroupingSeparator = true
|
||||||
|
}
|
||||||
|
get { self.numberFormatter.currencyGroupingSeparator }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets if has separator between all group of numbers.
|
||||||
|
/// * Example: when set to false, a bug number such as a million
|
||||||
|
/// is represented by tight numbers "1000000". Otherwise if set
|
||||||
|
/// to true each group is separated by the defined `groupingSeparator`. *
|
||||||
|
/// Note: When set to true only works by defining a grouping separator.
|
||||||
|
public var hasGroupingSeparator: Bool {
|
||||||
|
set { self.numberFormatter.usesGroupingSeparator = newValue }
|
||||||
|
get { self.numberFormatter.usesGroupingSeparator }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value that will be presented when the text field
|
||||||
|
/// text values matches zero (0)
|
||||||
|
public var zeroSymbol: String? {
|
||||||
|
set { numberFormatter.zeroSymbol = newValue }
|
||||||
|
get { numberFormatter.zeroSymbol }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value that will be presented when the text field
|
||||||
|
/// is empty. The default is "" - empty string
|
||||||
|
public var nilSymbol: String {
|
||||||
|
set { numberFormatter.nilSymbol = newValue }
|
||||||
|
get { return numberFormatter.nilSymbol }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encapsulated Number formatter
|
||||||
|
let numberFormatter: NumberFormatter
|
||||||
|
|
||||||
|
/// Maximum allowed number of integers
|
||||||
|
public var maxIntegers: Int? {
|
||||||
|
set {
|
||||||
|
guard let maxIntegers = newValue else { return }
|
||||||
|
numberFormatter.maximumIntegerDigits = maxIntegers
|
||||||
|
}
|
||||||
|
get { return numberFormatter.maximumIntegerDigits }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the maximum allowed number of numerical characters
|
||||||
|
public var maxDigitsCount: Int {
|
||||||
|
numberFormatter.maximumIntegerDigits + numberFormatter.maximumFractionDigits
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value zero formatted to serve as initial text.
|
||||||
|
public var initialText: String {
|
||||||
|
numberFormatter.string(from: 0) ?? "0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - INIT
|
||||||
|
|
||||||
|
/// Handler to initialize a new style.
|
||||||
|
public typealias InitHandler = ((CurrencyFormatter) -> (Void))
|
||||||
|
|
||||||
|
/// Initialize a new currency formatter with optional configuration handler callback.
|
||||||
|
///
|
||||||
|
/// - Parameter handler: configuration handler callback.
|
||||||
|
|
||||||
|
public init(currency: String, _ handler: InitHandler? = nil) {
|
||||||
|
numberFormatter = setupCurrencyNumberFormatter(currency: currency)
|
||||||
|
|
||||||
|
numberFormatter.alwaysShowsDecimalSeparator = false
|
||||||
|
/*numberFormatter.numberStyle = .currency
|
||||||
|
|
||||||
|
numberFormatter.minimumFractionDigits = 2
|
||||||
|
numberFormatter.maximumFractionDigits = 2
|
||||||
|
numberFormatter.minimumIntegerDigits = 1*/
|
||||||
|
|
||||||
|
handler?(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Format
|
||||||
|
extension CurrencyFormatter {
|
||||||
|
|
||||||
|
/// Returns a currency string from a given double value.
|
||||||
|
///
|
||||||
|
/// - Parameter double: the monetary amount.
|
||||||
|
/// - Returns: formatted currency string.
|
||||||
|
public func string(from double: Double) -> String? {
|
||||||
|
let validValue = valueAdjustedToFitAllowedValues(from: double)
|
||||||
|
return numberFormatter.string(from: validValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a double from a string that represents a numerical value.
|
||||||
|
///
|
||||||
|
/// - Parameter string: string that describes the numerical value.
|
||||||
|
/// - Returns: the value as a Double.
|
||||||
|
public func double(from string: String) -> Double? {
|
||||||
|
Double(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a currency formatted string and returns its
|
||||||
|
/// numerical/unformatted representation.
|
||||||
|
///
|
||||||
|
/// - Parameter string: currency formatted string
|
||||||
|
/// - Returns: numerical representation
|
||||||
|
public func unformatted(string: String) -> String? {
|
||||||
|
string.numeralFormat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Currency adjusting conformance
|
||||||
|
|
||||||
|
extension CurrencyFormatter: CurrencyAdjusting {
|
||||||
|
|
||||||
|
/// Receives a currency formatted String, and returns it with its decimal separator adjusted.
|
||||||
|
///
|
||||||
|
/// _Note_: Useful when appending values to a currency formatted String.
|
||||||
|
/// E.g. "$ 23.24" after users taps an additional number, is equal = "$ 23.247".
|
||||||
|
/// Which gets updated to "$ 232.47".
|
||||||
|
///
|
||||||
|
/// - Parameter string: The currency formatted String
|
||||||
|
/// - Returns: The currency formatted received String with its decimal separator adjusted
|
||||||
|
public func formattedStringWithAdjustedDecimalSeparator(from string: String) -> String? {
|
||||||
|
let adjustedString = numeralStringWithAdjustedDecimalSeparator(from: string)
|
||||||
|
guard let value = double(from: adjustedString) else { return nil }
|
||||||
|
|
||||||
|
return self.numberFormatter.string(from: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a currency formatted String, and returns it to fit the formatter's min and max values, when needed.
|
||||||
|
///
|
||||||
|
/// - Parameter string: The currency formatted String
|
||||||
|
/// - Returns: The currency formatted String, or the formatted version of its closes allowed value, min or max, depending on the closest boundary.
|
||||||
|
public func formattedStringAdjustedToFitAllowedValues(from string: String) -> String? {
|
||||||
|
let adjustedString = numeralStringWithAdjustedDecimalSeparator(from: string)
|
||||||
|
guard let originalValue = double(from: adjustedString) else { return nil }
|
||||||
|
|
||||||
|
return self.string(from: originalValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a currency formatted String, and returns a numeral version of it with its decimal separator adjusted.
|
||||||
|
///
|
||||||
|
/// E.g. "$ 23.24", after users taps an additional number, get equal as "$ 23.247". The returned value would be "232.47".
|
||||||
|
///
|
||||||
|
/// - Parameter string: The currency formatted String
|
||||||
|
/// - Returns: The received String with numeral format and with its decimal separator adjusted
|
||||||
|
private func numeralStringWithAdjustedDecimalSeparator(from string: String) -> String {
|
||||||
|
var updatedString = string.numeralFormat()
|
||||||
|
let isNegative: Bool = string.contains(String.negativeSymbol)
|
||||||
|
|
||||||
|
updatedString = isNegative ? .negativeSymbol + updatedString : updatedString
|
||||||
|
updatedString.updateDecimalSeparator(decimalDigits: decimalDigits)
|
||||||
|
|
||||||
|
return updatedString
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a Double value, and returns it adjusted to fit min and max allowed values, when needed.
|
||||||
|
/// If the value respect number formatter's min and max, it will be returned without changes.
|
||||||
|
///
|
||||||
|
/// - Parameter value: The value to be adjusted if needed
|
||||||
|
/// - Returns: The value updated or not, depending on the formatter's settings
|
||||||
|
private func valueAdjustedToFitAllowedValues(from value: Double) -> Double {
|
||||||
|
if let minValue = minValue, value < minValue {
|
||||||
|
return minValue
|
||||||
|
} else if let maxValue = maxValue, value > maxValue {
|
||||||
|
return maxValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
755
submodules/BotPaymentsUI/Sources/Formatter/CurrencyLocale.swift
Normal file
755
submodules/BotPaymentsUI/Sources/Formatter/CurrencyLocale.swift
Normal file
@ -0,0 +1,755 @@
|
|||||||
|
//
|
||||||
|
// CurrencyLocale.swift
|
||||||
|
// CurrencyText
|
||||||
|
//
|
||||||
|
// Created by Felipe Lefèvre Marino on 1/26/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// All locales were extracted from:
|
||||||
|
/// jacobbubu/ioslocaleidentifiers.csv - https://gist.github.com/jacobbubu/1836273
|
||||||
|
|
||||||
|
/// The LocaleConvertible pattern is inspired in SwiftDate by malcommac
|
||||||
|
/// https://github.com/malcommac/SwiftDate
|
||||||
|
|
||||||
|
/// LocaleConvertible defines the behavior to convert locale info to system Locale type
|
||||||
|
public protocol LocaleConvertible {
|
||||||
|
var locale: Locale { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Locale: LocaleConvertible {
|
||||||
|
public var locale: Locale { return self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines locales available in system
|
||||||
|
public enum CurrencyLocale: String, LocaleConvertible {
|
||||||
|
|
||||||
|
case current = "current"
|
||||||
|
case autoUpdating = "currentAutoUpdating"
|
||||||
|
|
||||||
|
case afrikaans = "af"
|
||||||
|
case afrikaansNamibia = "af_NA"
|
||||||
|
case afrikaansSouthAfrica = "af_ZA"
|
||||||
|
case aghem = "agq"
|
||||||
|
case aghemCameroon = "agq_CM"
|
||||||
|
case akan = "ak"
|
||||||
|
case akanGhana = "ak_GH"
|
||||||
|
case albanian = "sq"
|
||||||
|
case albanianAlbania = "sq_AL"
|
||||||
|
case albanianKosovo = "sq_XK"
|
||||||
|
case albanianMacedonia = "sq_MK"
|
||||||
|
case amharic = "am"
|
||||||
|
case amharicEthiopia = "am_ET"
|
||||||
|
case arabic = "ar"
|
||||||
|
case arabicAlgeria = "ar_DZ"
|
||||||
|
case arabicBahrain = "ar_BH"
|
||||||
|
case arabicChad = "ar_TD"
|
||||||
|
case arabicComoros = "ar_KM"
|
||||||
|
case arabicDjibouti = "ar_DJ"
|
||||||
|
case arabicEgypt = "ar_EG"
|
||||||
|
case arabicEritrea = "ar_ER"
|
||||||
|
case arabicIraq = "ar_IQ"
|
||||||
|
case arabicIsrael = "ar_IL"
|
||||||
|
case arabicJordan = "ar_JO"
|
||||||
|
case arabicKuwait = "ar_KW"
|
||||||
|
case arabicLebanon = "ar_LB"
|
||||||
|
case arabicLibya = "ar_LY"
|
||||||
|
case arabicMauritania = "ar_MR"
|
||||||
|
case arabicMorocco = "ar_MA"
|
||||||
|
case arabicOman = "ar_OM"
|
||||||
|
case arabicPalestinianTerritories = "ar_PS"
|
||||||
|
case arabicQatar = "ar_QA"
|
||||||
|
case arabicSaudiArabia = "ar_SA"
|
||||||
|
case arabicSomalia = "ar_SO"
|
||||||
|
case arabicSouthSudan = "ar_SS"
|
||||||
|
case arabicSudan = "ar_SD"
|
||||||
|
case arabicSyria = "ar_SY"
|
||||||
|
case arabicTunisia = "ar_TN"
|
||||||
|
case arabicUnitedArabEmirates = "ar_AE"
|
||||||
|
case arabicWesternSahara = "ar_EH"
|
||||||
|
case arabicWorld = "ar_001"
|
||||||
|
case arabicYemen = "ar_YE"
|
||||||
|
case armenian = "hy"
|
||||||
|
case armenianArmenia = "hy_AM"
|
||||||
|
case assamese = "as"
|
||||||
|
case assameseIndia = "as_IN"
|
||||||
|
case asu = "asa"
|
||||||
|
case asuTanzania = "asa_TZ"
|
||||||
|
case azerbaijani = "az_Latn"
|
||||||
|
case azerbaijaniAzerbaijan = "az_Latn_AZ"
|
||||||
|
case azerbaijaniCyrillic = "az_Cyrl"
|
||||||
|
case azerbaijaniCyrillicAzerbaijan = "az_Cyrl_AZ"
|
||||||
|
case bafia = "ksf"
|
||||||
|
case bafiaCameroon = "ksf_CM"
|
||||||
|
case bambara = "bm_Latn"
|
||||||
|
case bambaraMali = "bm_Latn_ML"
|
||||||
|
case basaa = "bas"
|
||||||
|
case basaaCameroon = "bas_CM"
|
||||||
|
case basque = "eu"
|
||||||
|
case basqueSpain = "eu_ES"
|
||||||
|
case belarusian = "be"
|
||||||
|
case belarusianBelarus = "be_BY"
|
||||||
|
case bemba = "bem"
|
||||||
|
case bembaZambia = "bem_ZM"
|
||||||
|
case bena = "bez"
|
||||||
|
case benaTanzania = "bez_TZ"
|
||||||
|
case bengali = "bn"
|
||||||
|
case bengaliBangladesh = "bn_BD"
|
||||||
|
case engaliIndia = "bn_IN"
|
||||||
|
case bodo = "brx"
|
||||||
|
case bodoIndia = "brx_IN"
|
||||||
|
case bosnian = "bs_Latn"
|
||||||
|
case bosnianBosniaHerzegovina = "bs_Latn_BA"
|
||||||
|
case bosnianCyrillic = "bs_Cyrl"
|
||||||
|
case bosnianCyrillicBosniaHerzegovina = "bs_Cyrl_BA"
|
||||||
|
case breton = "br"
|
||||||
|
case bretonFrance = "br_FR"
|
||||||
|
case bulgarian = "bg"
|
||||||
|
case bulgarianBulgaria = "bg_BG"
|
||||||
|
case burmese = "my"
|
||||||
|
case burmeseMyanmarBurma = "my_MM"
|
||||||
|
case catalan = "ca"
|
||||||
|
case catalanAndorra = "ca_AD"
|
||||||
|
case catalanFrance = "ca_FR"
|
||||||
|
case catalanItaly = "ca_IT"
|
||||||
|
case catalanSpain = "ca_ES"
|
||||||
|
case centralAtlasTamazight = "tzm_Latn"
|
||||||
|
case centralAtlasTamazightMorocco = "tzm_Latn_MA"
|
||||||
|
case centralKurdish = "ckb"
|
||||||
|
case centralKurdishIran = "ckb_IR"
|
||||||
|
case centralKurdishIraq = "ckb_IQ"
|
||||||
|
case cherokee = "chr"
|
||||||
|
case cherokeeUnitedStates = "chr_US"
|
||||||
|
case chiga = "cgg"
|
||||||
|
case chigaUganda = "cgg_UG"
|
||||||
|
case chinese = "zh"
|
||||||
|
case chineseChina = "zh_Hans_CN"
|
||||||
|
case chineseHongKongSarChina = "zh_Hant_HK"
|
||||||
|
case chineseMacauSarChina = "zh_Hant_MO"
|
||||||
|
case chineseSimplified = "zh_Hans"
|
||||||
|
case chineseSimplifiedHongKongSarChina = "zh_Hans_HK"
|
||||||
|
case chineseSimplifiedMacauSarChina = "zh_Hans_MO"
|
||||||
|
case chineseSingapore = "zh_Hans_SG"
|
||||||
|
case chineseTaiwan = "zh_Hant_TW"
|
||||||
|
case chineseTraditional = "zh_Hant"
|
||||||
|
case colognian = "ksh"
|
||||||
|
case colognianGermany = "ksh_DE"
|
||||||
|
case cornish = "kw"
|
||||||
|
case cornishUnitedKingdom = "kw_GB"
|
||||||
|
case croatian = "hr"
|
||||||
|
case croatianBosniaHerzegovina = "hr_BA"
|
||||||
|
case croatianCroatia = "hr_HR"
|
||||||
|
case czech = "cs"
|
||||||
|
case czechCzechRepublic = "cs_CZ"
|
||||||
|
case danish = "da"
|
||||||
|
case danishDenmark = "da_DK"
|
||||||
|
case danishGreenland = "da_GL"
|
||||||
|
case duala = "dua"
|
||||||
|
case dualaCameroon = "dua_CM"
|
||||||
|
case dutch = "nl"
|
||||||
|
case dutchAruba = "nl_AW"
|
||||||
|
case dutchBelgium = "nl_BE"
|
||||||
|
case dutchCaribbeanNetherlands = "nl_BQ"
|
||||||
|
case dutchCuraao = "nl_CW"
|
||||||
|
case dutchNetherlands = "nl_NL"
|
||||||
|
case dutchSintMaarten = "nl_SX"
|
||||||
|
case dutchSuriname = "nl_SR"
|
||||||
|
case dzongkha = "dz"
|
||||||
|
case dzongkhaBhutan = "dz_BT"
|
||||||
|
case embu = "ebu"
|
||||||
|
case embuKenya = "ebu_KE"
|
||||||
|
case english = "en"
|
||||||
|
case englishAlbania = "en_AL"
|
||||||
|
case englishAmericanSamoa = "en_AS"
|
||||||
|
case englishAndorra = "en_AD"
|
||||||
|
case englishAnguilla = "en_AI"
|
||||||
|
case englishAntiguaBarbuda = "en_AG"
|
||||||
|
case englishAustralia = "en_AU"
|
||||||
|
case englishAustria = "en_AT"
|
||||||
|
case englishBahamas = "en_BS"
|
||||||
|
case englishBarbados = "en_BB"
|
||||||
|
case englishBelgium = "en_BE"
|
||||||
|
case englishBelize = "en_BZ"
|
||||||
|
case englishBermuda = "en_BM"
|
||||||
|
case englishBosniaHerzegovina = "en_BA"
|
||||||
|
case englishBotswana = "en_BW"
|
||||||
|
case englishBritishIndianOceanTerritory = "en_IO"
|
||||||
|
case englishBritishVirginIslands = "en_VG"
|
||||||
|
case englishCameroon = "en_CM"
|
||||||
|
case englishCanada = "en_CA"
|
||||||
|
case englishCaymanIslands = "en_KY"
|
||||||
|
case englishChristmasIsland = "en_CX"
|
||||||
|
case englishCocosKeelingIslands = "en_CC"
|
||||||
|
case englishCookIslands = "en_CK"
|
||||||
|
case englishCroatia = "en_HR"
|
||||||
|
case englishCyprus = "en_CY"
|
||||||
|
case englishCzechRepublic = "en_CZ"
|
||||||
|
case englishDenmark = "en_DK"
|
||||||
|
case englishDiegoGarcia = "en_DG"
|
||||||
|
case englishDominica = "en_DM"
|
||||||
|
case englishEritrea = "en_ER"
|
||||||
|
case englishEstonia = "en_EE"
|
||||||
|
case englishEurope = "en_150"
|
||||||
|
case englishFalklandIslands = "en_FK"
|
||||||
|
case englishFiji = "en_FJ"
|
||||||
|
case englishFinland = "en_FI"
|
||||||
|
case englishFrance = "en_FR"
|
||||||
|
case englishGambia = "en_GM"
|
||||||
|
case englishGermany = "en_DE"
|
||||||
|
case englishGhana = "en_GH"
|
||||||
|
case englishGibraltar = "en_GI"
|
||||||
|
case englishGreece = "en_GR"
|
||||||
|
case englishGrenada = "en_GD"
|
||||||
|
case englishGuam = "en_GU"
|
||||||
|
case englishGuernsey = "en_GG"
|
||||||
|
case englishGuyana = "en_GY"
|
||||||
|
case englishHongKongSarChina = "en_HK"
|
||||||
|
case englishHungary = "en_HU"
|
||||||
|
case englishIceland = "en_IS"
|
||||||
|
case englishIndia = "en_IN"
|
||||||
|
case englishIreland = "en_IE"
|
||||||
|
case englishIsleOfMan = "en_IM"
|
||||||
|
case englishIsrael = "en_IL"
|
||||||
|
case englishItaly = "en_IT"
|
||||||
|
case englishJamaica = "en_JM"
|
||||||
|
case englishJersey = "en_JE"
|
||||||
|
case englishKenya = "en_KE"
|
||||||
|
case englishKiribati = "en_KI"
|
||||||
|
case englishLatvia = "en_LV"
|
||||||
|
case englishLesotho = "en_LS"
|
||||||
|
case englishLiberia = "en_LR"
|
||||||
|
case englishLithuania = "en_LT"
|
||||||
|
case englishLuxembourg = "en_LU"
|
||||||
|
case englishMacauSarChina = "en_MO"
|
||||||
|
case englishMadagascar = "en_MG"
|
||||||
|
case englishMalawi = "en_MW"
|
||||||
|
case englishMalaysia = "en_MY"
|
||||||
|
case englishMalta = "en_MT"
|
||||||
|
case englishMarshallIslands = "en_MH"
|
||||||
|
case englishMauritius = "en_MU"
|
||||||
|
case englishMicronesia = "en_FM"
|
||||||
|
case englishMontenegro = "en_ME"
|
||||||
|
case englishMontserrat = "en_MS"
|
||||||
|
case englishNamibia = "en_NA"
|
||||||
|
case englishNauru = "en_NR"
|
||||||
|
case englishNetherlands = "en_NL"
|
||||||
|
case englishNewZealand = "en_NZ"
|
||||||
|
case englishNigeria = "en_NG"
|
||||||
|
case englishNiue = "en_NU"
|
||||||
|
case englishNorfolkIsland = "en_NF"
|
||||||
|
case englishNorthernMarianaIslands = "en_MP"
|
||||||
|
case englishNorway = "en_NO"
|
||||||
|
case englishPakistan = "en_PK"
|
||||||
|
case englishPalau = "en_PW"
|
||||||
|
case englishPapuaNewGuinea = "en_PG"
|
||||||
|
case englishPhilippines = "en_PH"
|
||||||
|
case englishPitcairnIslands = "en_PN"
|
||||||
|
case englishPoland = "en_PL"
|
||||||
|
case englishPortugal = "en_PT"
|
||||||
|
case englishPuertoRico = "en_PR"
|
||||||
|
case englishRomania = "en_RO"
|
||||||
|
case englishRussia = "en_RU"
|
||||||
|
case englishRwanda = "en_RW"
|
||||||
|
case englishSamoa = "en_WS"
|
||||||
|
case englishSeychelles = "en_SC"
|
||||||
|
case englishSierraLeone = "en_SL"
|
||||||
|
case englishSingapore = "en_SG"
|
||||||
|
case englishSintMaarten = "en_SX"
|
||||||
|
case englishSlovakia = "en_SK"
|
||||||
|
case englishSlovenia = "en_SI"
|
||||||
|
case englishSolomonIslands = "en_SB"
|
||||||
|
case englishSouthAfrica = "en_ZA"
|
||||||
|
case englishSouthSudan = "en_SS"
|
||||||
|
case englishSpain = "en_ES"
|
||||||
|
case englishStHelena = "en_SH"
|
||||||
|
case englishStKittsNevis = "en_KN"
|
||||||
|
case englishStLucia = "en_LC"
|
||||||
|
case englishStVincentGrenadines = "en_VC"
|
||||||
|
case englishSudan = "en_SD"
|
||||||
|
case englishSwaziland = "en_SZ"
|
||||||
|
case englishSweden = "en_SE"
|
||||||
|
case englishSwitzerland = "en_CH"
|
||||||
|
case englishTanzania = "en_TZ"
|
||||||
|
case englishTokelau = "en_TK"
|
||||||
|
case englishTonga = "en_TO"
|
||||||
|
case englishTrinidadTobago = "en_TT"
|
||||||
|
case englishTurkey = "en_TR"
|
||||||
|
case englishTurksCaicosIslands = "en_TC"
|
||||||
|
case englishTuvalu = "en_TV"
|
||||||
|
case englishUSOutlyingIslands = "en_UM"
|
||||||
|
case englishUSVirginIslands = "en_VI"
|
||||||
|
case englishUganda = "en_UG"
|
||||||
|
case englishUnitedKingdom = "en_GB"
|
||||||
|
case englishUnitedStates = "en_US"
|
||||||
|
case englishUnitedStatesComputer = "en_US_POSIX"
|
||||||
|
case englishVanuatu = "en_VU"
|
||||||
|
case englishWorld = "en_001"
|
||||||
|
case englishZambia = "en_ZM"
|
||||||
|
case englishZimbabwe = "en_ZW"
|
||||||
|
case esperanto = "eo"
|
||||||
|
case estonian = "et"
|
||||||
|
case estonianEstonia = "et_EE"
|
||||||
|
case ewe = "ee"
|
||||||
|
case eweGhana = "ee_GH"
|
||||||
|
case eweTogo = "ee_TG"
|
||||||
|
case ewondo = "ewo"
|
||||||
|
case ewondoCameroon = "ewo_CM"
|
||||||
|
case faroese = "fo"
|
||||||
|
case faroeseFaroeIslands = "fo_FO"
|
||||||
|
case filipino = "fil"
|
||||||
|
case filipinoPhilippines = "fil_PH"
|
||||||
|
case finnish = "fi"
|
||||||
|
case finnishFinland = "fi_FI"
|
||||||
|
case french = "fr"
|
||||||
|
case frenchAlgeria = "fr_DZ"
|
||||||
|
case frenchBelgium = "fr_BE"
|
||||||
|
case frenchBenin = "fr_BJ"
|
||||||
|
case frenchBurkinaFaso = "fr_BF"
|
||||||
|
case frenchBurundi = "fr_BI"
|
||||||
|
case frenchCameroon = "fr_CM"
|
||||||
|
case frenchCanada = "fr_CA"
|
||||||
|
case frenchCentralAfricanRepublic = "fr_CF"
|
||||||
|
case frenchChad = "fr_TD"
|
||||||
|
case frenchComoros = "fr_KM"
|
||||||
|
case frenchCongoBrazzaville = "fr_CG"
|
||||||
|
case frenchCongoKinshasa = "fr_CD"
|
||||||
|
case frenchCteDivoire = "fr_CI"
|
||||||
|
case frenchDjibouti = "fr_DJ"
|
||||||
|
case frenchEquatorialGuinea = "fr_GQ"
|
||||||
|
case frenchFrance = "fr_FR"
|
||||||
|
case frenchFrenchGuiana = "fr_GF"
|
||||||
|
case frenchFrenchPolynesia = "fr_PF"
|
||||||
|
case frenchGabon = "fr_GA"
|
||||||
|
case frenchGuadeloupe = "fr_GP"
|
||||||
|
case frenchGuinea = "fr_GN"
|
||||||
|
case frenchHaiti = "fr_HT"
|
||||||
|
case frenchLuxembourg = "fr_LU"
|
||||||
|
case frenchMadagascar = "fr_MG"
|
||||||
|
case frenchMali = "fr_ML"
|
||||||
|
case frenchMartinique = "fr_MQ"
|
||||||
|
case frenchMauritania = "fr_MR"
|
||||||
|
case frenchMauritius = "fr_MU"
|
||||||
|
case frenchMayotte = "fr_YT"
|
||||||
|
case frenchMonaco = "fr_MC"
|
||||||
|
case frenchMorocco = "fr_MA"
|
||||||
|
case frenchNewCaledonia = "fr_NC"
|
||||||
|
case frenchNiger = "fr_NE"
|
||||||
|
case frenchRunion = "fr_RE"
|
||||||
|
case frenchRwanda = "fr_RW"
|
||||||
|
case frenchSenegal = "fr_SN"
|
||||||
|
case frenchSeychelles = "fr_SC"
|
||||||
|
case frenchStBarthlemy = "fr_BL"
|
||||||
|
case frenchStMartin = "fr_MF"
|
||||||
|
case frenchStPierreMiquelon = "fr_PM"
|
||||||
|
case frenchSwitzerland = "fr_CH"
|
||||||
|
case frenchSyria = "fr_SY"
|
||||||
|
case frenchTogo = "fr_TG"
|
||||||
|
case frenchTunisia = "fr_TN"
|
||||||
|
case frenchVanuatu = "fr_VU"
|
||||||
|
case frenchWallisFutuna = "fr_WF"
|
||||||
|
case friulian = "fur"
|
||||||
|
case friulianItaly = "fur_IT"
|
||||||
|
case fulah = "ff"
|
||||||
|
case fulahCameroon = "ff_CM"
|
||||||
|
case fulahGuinea = "ff_GN"
|
||||||
|
case fulahMauritania = "ff_MR"
|
||||||
|
case fulahSenegal = "ff_SN"
|
||||||
|
case galician = "gl"
|
||||||
|
case galicianSpain = "gl_ES"
|
||||||
|
case ganda = "lg"
|
||||||
|
case gandaUganda = "lg_UG"
|
||||||
|
case georgian = "ka"
|
||||||
|
case georgianGeorgia = "ka_GE"
|
||||||
|
case german = "de"
|
||||||
|
case germanAustria = "de_AT"
|
||||||
|
case germanBelgium = "de_BE"
|
||||||
|
case germanGermany = "de_DE"
|
||||||
|
case germanLiechtenstein = "de_LI"
|
||||||
|
case germanLuxembourg = "de_LU"
|
||||||
|
case germanSwitzerland = "de_CH"
|
||||||
|
case greek = "el"
|
||||||
|
case greekCyprus = "el_CY"
|
||||||
|
case greekGreece = "el_GR"
|
||||||
|
case gujarati = "gu"
|
||||||
|
case gujaratiIndia = "gu_IN"
|
||||||
|
case gusii = "guz"
|
||||||
|
case gusiiKenya = "guz_KE"
|
||||||
|
case hausa = "ha_Latn"
|
||||||
|
case hausaGhana = "ha_Latn_GH"
|
||||||
|
case hausaNiger = "ha_Latn_NE"
|
||||||
|
case hausaNigeria = "ha_Latn_NG"
|
||||||
|
case hawaiian = "haw"
|
||||||
|
case hawaiianUnitedStates = "haw_US"
|
||||||
|
case hebrew = "he"
|
||||||
|
case hebrewIsrael = "he_IL"
|
||||||
|
case hindi = "hi"
|
||||||
|
case hindiIndia = "hi_IN"
|
||||||
|
case hungarian = "hu"
|
||||||
|
case hungarianHungary = "hu_HU"
|
||||||
|
case icelandic = "is"
|
||||||
|
case icelandicIceland = "is_IS"
|
||||||
|
case igbo = "ig"
|
||||||
|
case igboNigeria = "ig_NG"
|
||||||
|
case inariSami = "smn"
|
||||||
|
case inariSamiFinland = "smn_FI"
|
||||||
|
case indonesian = "id"
|
||||||
|
case indonesianIndonesia = "id_ID"
|
||||||
|
case inuktitut = "iu"
|
||||||
|
case inuktitutUnifiedCanadianAboriginalSyllabics = "iu_Cans"
|
||||||
|
case inuktitutUnifiedCanadianAboriginalSyllabicsCanada = "iu_Cans_CA"
|
||||||
|
case irish = "ga"
|
||||||
|
case irishIreland = "ga_IE"
|
||||||
|
case italian = "it"
|
||||||
|
case italianItaly = "it_IT"
|
||||||
|
case italianSanMarino = "it_SM"
|
||||||
|
case italianSwitzerland = "it_CH"
|
||||||
|
case japanese = "ja"
|
||||||
|
case japaneseJapan = "ja_JP"
|
||||||
|
case jolaFonyi = "dyo"
|
||||||
|
case jolaFonyiSenegal = "dyo_SN"
|
||||||
|
case kabuverdianu = "kea"
|
||||||
|
case kabuverdianuCapeVerde = "kea_CV"
|
||||||
|
case kabyle = "kab"
|
||||||
|
case kabyleAlgeria = "kab_DZ"
|
||||||
|
case kako = "kkj"
|
||||||
|
case kakoCameroon = "kkj_CM"
|
||||||
|
case kalaallisut = "kl"
|
||||||
|
case kalaallisutGreenland = "kl_GL"
|
||||||
|
case kalenjin = "kln"
|
||||||
|
case kalenjinKenya = "kln_KE"
|
||||||
|
case kamba = "kam"
|
||||||
|
case kambaKenya = "kam_KE"
|
||||||
|
case kannada = "kn"
|
||||||
|
case kannadaIndia = "kn_IN"
|
||||||
|
case kashmiri = "ks"
|
||||||
|
case kashmiriArabic = "ks_Arab"
|
||||||
|
case kashmiriArabicIndia = "ks_Arab_IN"
|
||||||
|
case kazakh = "kk_Cyrl"
|
||||||
|
case kazakhKazakhstan = "kk_Cyrl_KZ"
|
||||||
|
case khmer = "km"
|
||||||
|
case khmerCambodia = "km_KH"
|
||||||
|
case kikuyu = "ki"
|
||||||
|
case kikuyuKenya = "ki_KE"
|
||||||
|
case kinyarwanda = "rw"
|
||||||
|
case kinyarwandaRwanda = "rw_RW"
|
||||||
|
case konkani = "kok"
|
||||||
|
case konkaniIndia = "kok_IN"
|
||||||
|
case korean = "ko"
|
||||||
|
case koreanNorthKorea = "ko_KP"
|
||||||
|
case koreanSouthKorea = "ko_KR"
|
||||||
|
case koyraChiini = "khq"
|
||||||
|
case koyraChiiniMali = "khq_ML"
|
||||||
|
case koyraboroSenni = "ses"
|
||||||
|
case koyraboroSenniMali = "ses_ML"
|
||||||
|
case kwasio = "nmg"
|
||||||
|
case kwasioCameroon = "nmg_CM"
|
||||||
|
case kyrgyz = "ky_Cyrl"
|
||||||
|
case kyrgyzKyrgyzstan = "ky_Cyrl_KG"
|
||||||
|
case lakota = "lkt"
|
||||||
|
case lakotaUnitedStates = "lkt_US"
|
||||||
|
case langi = "lag"
|
||||||
|
case langiTanzania = "lag_TZ"
|
||||||
|
case lao = "lo"
|
||||||
|
case laoLaos = "lo_LA"
|
||||||
|
case latvian = "lv"
|
||||||
|
case latvianLatvia = "lv_LV"
|
||||||
|
case lingala = "ln"
|
||||||
|
case lingalaAngola = "ln_AO"
|
||||||
|
case lingalaCentralAfricanRepublic = "ln_CF"
|
||||||
|
case lingalaCongoBrazzaville = "ln_CG"
|
||||||
|
case lingalaCongoKinshasa = "ln_CD"
|
||||||
|
case lithuanian = "lt"
|
||||||
|
case lithuanianLithuania = "lt_LT"
|
||||||
|
case lowerSorbian = "dsb"
|
||||||
|
case lowerSorbianGermany = "dsb_DE"
|
||||||
|
case lubaKatanga = "lu"
|
||||||
|
case lubaKatangaCongoKinshasa = "lu_CD"
|
||||||
|
case luo = "luo"
|
||||||
|
case luoKenya = "luo_KE"
|
||||||
|
case luxembourgish = "lb"
|
||||||
|
case luxembourgishLuxembourg = "lb_LU"
|
||||||
|
case luyia = "luy"
|
||||||
|
case luyiaKenya = "luy_KE"
|
||||||
|
case macedonian = "mk"
|
||||||
|
case macedonianMacedonia = "mk_MK"
|
||||||
|
case machame = "jmc"
|
||||||
|
case machameTanzania = "jmc_TZ"
|
||||||
|
case makhuwaMeetto = "mgh"
|
||||||
|
case makhuwaMeettoMozambique = "mgh_MZ"
|
||||||
|
case makonde = "kde"
|
||||||
|
case makondeTanzania = "kde_TZ"
|
||||||
|
case malagasy = "mg"
|
||||||
|
case malagasyMadagascar = "mg_MG"
|
||||||
|
case malay = "ms_Latn"
|
||||||
|
case malayArabic = "ms_Arab"
|
||||||
|
case malayArabicBrunei = "ms_Arab_BN"
|
||||||
|
case malayArabicMalaysia = "ms_Arab_MY"
|
||||||
|
case malayBrunei = "ms_Latn_BN"
|
||||||
|
case malayMalaysia = "ms_Latn_MY"
|
||||||
|
case malaySingapore = "ms_Latn_SG"
|
||||||
|
case malayalam = "ml"
|
||||||
|
case malayalamIndia = "ml_IN"
|
||||||
|
case maltese = "mt"
|
||||||
|
case malteseMalta = "mt_MT"
|
||||||
|
case manx = "gv"
|
||||||
|
case manxIsleOfMan = "gv_IM"
|
||||||
|
case marathi = "mr"
|
||||||
|
case marathiIndia = "mr_IN"
|
||||||
|
case masai = "mas"
|
||||||
|
case masaiKenya = "mas_KE"
|
||||||
|
case masaiTanzania = "mas_TZ"
|
||||||
|
case meru = "mer"
|
||||||
|
case meruKenya = "mer_KE"
|
||||||
|
case meta = "mgo"
|
||||||
|
case metaCameroon = "mgo_CM"
|
||||||
|
case mongolian = "mn_Cyrl"
|
||||||
|
case mongolianMongolia = "mn_Cyrl_MN"
|
||||||
|
case morisyen = "mfe"
|
||||||
|
case morisyenMauritius = "mfe_MU"
|
||||||
|
case mundang = "mua"
|
||||||
|
case mundangCameroon = "mua_CM"
|
||||||
|
case nama = "naq"
|
||||||
|
case namaNamibia = "naq_NA"
|
||||||
|
case nepali = "ne"
|
||||||
|
case nepaliIndia = "ne_IN"
|
||||||
|
case nepaliNepal = "ne_NP"
|
||||||
|
case ngiemboon = "nnh"
|
||||||
|
case ngiemboonCameroon = "nnh_CM"
|
||||||
|
case ngomba = "jgo"
|
||||||
|
case ngombaCameroon = "jgo_CM"
|
||||||
|
case northNdebele = "nd"
|
||||||
|
case northNdebeleZimbabwe = "nd_ZW"
|
||||||
|
case northernSami = "se"
|
||||||
|
case northernSamiFinland = "se_FI"
|
||||||
|
case northernSamiNorway = "se_NO"
|
||||||
|
case northernSamiSweden = "se_SE"
|
||||||
|
case norwegianBokml = "nb"
|
||||||
|
case norwegianBokmlNorway = "nb_NO"
|
||||||
|
case norwegianBokmlSvalbardJanMayen = "nb_SJ"
|
||||||
|
case norwegianNynorsk = "nn"
|
||||||
|
case norwegianNynorskNorway = "nn_NO"
|
||||||
|
case nuer = "nus"
|
||||||
|
case nuerSudan = "nus_SD"
|
||||||
|
case nyankole = "nyn"
|
||||||
|
case nyankoleUganda = "nyn_UG"
|
||||||
|
case oriya = "or"
|
||||||
|
case oriyaIndia = "or_IN"
|
||||||
|
case oromo = "om"
|
||||||
|
case oromoEthiopia = "om_ET"
|
||||||
|
case oromoKenya = "om_KE"
|
||||||
|
case ossetic = "os"
|
||||||
|
case osseticGeorgia = "os_GE"
|
||||||
|
case osseticRussia = "os_RU"
|
||||||
|
case pashto = "ps"
|
||||||
|
case pashtoAfghanistan = "ps_AF"
|
||||||
|
case persian = "fa"
|
||||||
|
case persianAfghanistan = "fa_AF"
|
||||||
|
case persianIran = "fa_IR"
|
||||||
|
case polish = "pl"
|
||||||
|
case polishPoland = "pl_PL"
|
||||||
|
case portuguese = "pt"
|
||||||
|
case portugueseAngola = "pt_AO"
|
||||||
|
case portugueseBrazil = "pt_BR"
|
||||||
|
case portugueseCapeVerde = "pt_CV"
|
||||||
|
case portugueseGuineaBissau = "pt_GW"
|
||||||
|
case portugueseMacauSarChina = "pt_MO"
|
||||||
|
case portugueseMozambique = "pt_MZ"
|
||||||
|
case portuguesePortugal = "pt_PT"
|
||||||
|
case portugueseSoTomPrncipe = "pt_ST"
|
||||||
|
case portugueseTimorLeste = "pt_TL"
|
||||||
|
case punjabi = "pa_Guru"
|
||||||
|
case punjabiArabic = "pa_Arab"
|
||||||
|
case punjabiArabicPakistan = "pa_Arab_PK"
|
||||||
|
case punjabiIndia = "pa_Guru_IN"
|
||||||
|
case quechua = "qu"
|
||||||
|
case quechuaBolivia = "qu_BO"
|
||||||
|
case quechuaEcuador = "qu_EC"
|
||||||
|
case quechuaPeru = "qu_PE"
|
||||||
|
case romanian = "ro"
|
||||||
|
case romanianMoldova = "ro_MD"
|
||||||
|
case romanianRomania = "ro_RO"
|
||||||
|
case romansh = "rm"
|
||||||
|
case romanshSwitzerland = "rm_CH"
|
||||||
|
case rombo = "rof"
|
||||||
|
case romboTanzania = "rof_TZ"
|
||||||
|
case rundi = "rn"
|
||||||
|
case rundiBurundi = "rn_BI"
|
||||||
|
case russian = "ru"
|
||||||
|
case russianBelarus = "ru_BY"
|
||||||
|
case russianKazakhstan = "ru_KZ"
|
||||||
|
case russianKyrgyzstan = "ru_KG"
|
||||||
|
case russianMoldova = "ru_MD"
|
||||||
|
case russianRussia = "ru_RU"
|
||||||
|
case russianUkraine = "ru_UA"
|
||||||
|
case rwa = "rwk"
|
||||||
|
case rwaTanzania = "rwk_TZ"
|
||||||
|
case sakha = "sah"
|
||||||
|
case sakhaRussia = "sah_RU"
|
||||||
|
case samburu = "saq"
|
||||||
|
case samburuKenya = "saq_KE"
|
||||||
|
case sango = "sg"
|
||||||
|
case sangoCentralAfricanRepublic = "sg_CF"
|
||||||
|
case sangu = "sbp"
|
||||||
|
case sanguTanzania = "sbp_TZ"
|
||||||
|
case scottishGaelic = "gd"
|
||||||
|
case scottishGaelicUnitedKingdom = "gd_GB"
|
||||||
|
case sena = "seh"
|
||||||
|
case senaMozambique = "seh_MZ"
|
||||||
|
case serbian = "sr_Cyrl"
|
||||||
|
case serbianBosniaHerzegovina = "sr_Cyrl_BA"
|
||||||
|
case serbianKosovo = "sr_Cyrl_XK"
|
||||||
|
case serbianLatin = "sr_Latn"
|
||||||
|
case serbianLatinBosniaHerzegovina = "sr_Latn_BA"
|
||||||
|
case serbianLatinKosovo = "sr_Latn_XK"
|
||||||
|
case serbianLatinMontenegro = "sr_Latn_ME"
|
||||||
|
case serbianLatinSerbia = "sr_Latn_RS"
|
||||||
|
case serbianMontenegro = "sr_Cyrl_ME"
|
||||||
|
case serbianSerbia = "sr_Cyrl_RS"
|
||||||
|
case shambala = "ksb"
|
||||||
|
case shambalaTanzania = "ksb_TZ"
|
||||||
|
case shona = "sn"
|
||||||
|
case shonaZimbabwe = "sn_ZW"
|
||||||
|
case sichuanYi = "ii"
|
||||||
|
case sichuanYiChina = "ii_CN"
|
||||||
|
case sinhala = "si"
|
||||||
|
case sinhalaSriLanka = "si_LK"
|
||||||
|
case slovak = "sk"
|
||||||
|
case slovakSlovakia = "sk_SK"
|
||||||
|
case slovenian = "sl"
|
||||||
|
case slovenianSlovenia = "sl_SI"
|
||||||
|
case soga = "xog"
|
||||||
|
case sogaUganda = "xog_UG"
|
||||||
|
case somali = "so"
|
||||||
|
case somaliDjibouti = "so_DJ"
|
||||||
|
case somaliEthiopia = "so_ET"
|
||||||
|
case somaliKenya = "so_KE"
|
||||||
|
case somaliSomalia = "so_SO"
|
||||||
|
case spanish = "es"
|
||||||
|
case spanishArgentina = "es_AR"
|
||||||
|
case spanishBolivia = "es_BO"
|
||||||
|
case spanishCanaryIslands = "es_IC"
|
||||||
|
case spanishCeutaMelilla = "es_EA"
|
||||||
|
case spanishChile = "es_CL"
|
||||||
|
case spanishColombia = "es_CO"
|
||||||
|
case spanishCostaRica = "es_CR"
|
||||||
|
case spanishCuba = "es_CU"
|
||||||
|
case spanishDominicanRepublic = "es_DO"
|
||||||
|
case spanishEcuador = "es_EC"
|
||||||
|
case spanishElSalvador = "es_SV"
|
||||||
|
case spanishEquatorialGuinea = "es_GQ"
|
||||||
|
case spanishGuatemala = "es_GT"
|
||||||
|
case spanishHonduras = "es_HN"
|
||||||
|
case spanishLatinAmerica = "es_419"
|
||||||
|
case spanishMexico = "es_MX"
|
||||||
|
case spanishNicaragua = "es_NI"
|
||||||
|
case spanishPanama = "es_PA"
|
||||||
|
case spanishParaguay = "es_PY"
|
||||||
|
case spanishPeru = "es_PE"
|
||||||
|
case spanishPhilippines = "es_PH"
|
||||||
|
case spanishPuertoRico = "es_PR"
|
||||||
|
case spanishSpain = "es_ES"
|
||||||
|
case spanishUnitedStates = "es_US"
|
||||||
|
case spanishUruguay = "es_UY"
|
||||||
|
case spanishVenezuela = "es_VE"
|
||||||
|
case standardMoroccanTamazight = "zgh"
|
||||||
|
case standardMoroccanTamazightMorocco = "zgh_MA"
|
||||||
|
case swahili = "sw"
|
||||||
|
case swahiliCongoKinshasa = "sw_CD"
|
||||||
|
case swahiliKenya = "sw_KE"
|
||||||
|
case swahiliTanzania = "sw_TZ"
|
||||||
|
case swahiliUganda = "sw_UG"
|
||||||
|
case swedish = "sv"
|
||||||
|
case swedishlandIslands = "sv_AX"
|
||||||
|
case swedishFinland = "sv_FI"
|
||||||
|
case swedishSweden = "sv_SE"
|
||||||
|
case swissGerman = "gsw"
|
||||||
|
case swissGermanFrance = "gsw_FR"
|
||||||
|
case swissGermanLiechtenstein = "gsw_LI"
|
||||||
|
case swissGermanSwitzerland = "gsw_CH"
|
||||||
|
case tachelhit = "shi_Latn"
|
||||||
|
case tachelhitMorocco = "shi_Latn_MA"
|
||||||
|
case tachelhitTifinagh = "shi_Tfng"
|
||||||
|
case tachelhitTifinaghMorocco = "shi_Tfng_MA"
|
||||||
|
case taita = "dav"
|
||||||
|
case taitaKenya = "dav_KE"
|
||||||
|
case tajik = "tg_Cyrl"
|
||||||
|
case tajikTajikistan = "tg_Cyrl_TJ"
|
||||||
|
case tamil = "ta"
|
||||||
|
case tamilIndia = "ta_IN"
|
||||||
|
case tamilMalaysia = "ta_MY"
|
||||||
|
case tamilSingapore = "ta_SG"
|
||||||
|
case tamilSriLanka = "ta_LK"
|
||||||
|
case tasawaq = "twq"
|
||||||
|
case tasawaqNiger = "twq_NE"
|
||||||
|
case telugu = "te"
|
||||||
|
case teluguIndia = "te_IN"
|
||||||
|
case teso = "teo"
|
||||||
|
case tesoKenya = "teo_KE"
|
||||||
|
case tesoUganda = "teo_UG"
|
||||||
|
case thai = "th"
|
||||||
|
case thaiThailand = "th_TH"
|
||||||
|
case tibetan = "bo"
|
||||||
|
case tibetanChina = "bo_CN"
|
||||||
|
case tibetanIndia = "bo_IN"
|
||||||
|
case tigrinya = "ti"
|
||||||
|
case tigrinyaEritrea = "ti_ER"
|
||||||
|
case tigrinyaEthiopia = "ti_ET"
|
||||||
|
case tongan = "to"
|
||||||
|
case tonganTonga = "to_TO"
|
||||||
|
case turkish = "tr"
|
||||||
|
case turkishCyprus = "tr_CY"
|
||||||
|
case turkishTurkey = "tr_TR"
|
||||||
|
case turkmen = "tk_Latn"
|
||||||
|
case turkmenTurkmenistan = "tk_Latn_TM"
|
||||||
|
case ukrainian = "uk"
|
||||||
|
case ukrainianUkraine = "uk_UA"
|
||||||
|
case upperSorbian = "hsb"
|
||||||
|
case upperSorbianGermany = "hsb_DE"
|
||||||
|
case urdu = "ur"
|
||||||
|
case urduIndia = "ur_IN"
|
||||||
|
case urduPakistan = "ur_PK"
|
||||||
|
case uyghur = "ug"
|
||||||
|
case uyghurArabic = "ug_Arab"
|
||||||
|
case uyghurArabicChina = "ug_Arab_CN"
|
||||||
|
case uzbek = "uz_Cyrl"
|
||||||
|
case uzbekArabic = "uz_Arab"
|
||||||
|
case uzbekArabicAfghanistan = "uz_Arab_AF"
|
||||||
|
case uzbekLatin = "uz_Latn"
|
||||||
|
case uzbekLatinUzbekistan = "uz_Latn_UZ"
|
||||||
|
case uzbekUzbekistan = "uz_Cyrl_UZ"
|
||||||
|
case vai = "vai_Vaii"
|
||||||
|
case vaiLatin = "vai_Latn"
|
||||||
|
case vaiLatinLiberia = "vai_Latn_LR"
|
||||||
|
case vaiLiberia = "vai_Vaii_LR"
|
||||||
|
case vietnamese = "vi"
|
||||||
|
case vietnameseVietnam = "vi_VN"
|
||||||
|
case vunjo = "vun"
|
||||||
|
case vunjoTanzania = "vun_TZ"
|
||||||
|
case walser = "wae"
|
||||||
|
case walserSwitzerland = "wae_CH"
|
||||||
|
case welsh = "cy"
|
||||||
|
case welshUnitedKingdom = "cy_GB"
|
||||||
|
case westernFrisian = "fy"
|
||||||
|
case westernFrisianNetherlands = "fy_NL"
|
||||||
|
case yangben = "yav"
|
||||||
|
case yangbenCameroon = "yav_CM"
|
||||||
|
case yiddish = "yi"
|
||||||
|
case yiddishWorld = "yi_001"
|
||||||
|
case yoruba = "yo"
|
||||||
|
case yorubaBenin = "yo_BJ"
|
||||||
|
case yorubaNigeria = "yo_NG"
|
||||||
|
case zarma = "dje"
|
||||||
|
case zarmaNiger = "dje_NE"
|
||||||
|
case zulu = "zu"
|
||||||
|
case zuluSouthAfrica = "zu_ZA"
|
||||||
|
|
||||||
|
/// Return a valid `Locale` instance from currency locale enum
|
||||||
|
public var locale: Locale {
|
||||||
|
switch self {
|
||||||
|
case .current: return Locale.current
|
||||||
|
case .autoUpdating: return Locale.autoupdatingCurrent
|
||||||
|
default: return Locale(identifier: rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// NumberFormatter.swift
|
||||||
|
// CurrencyText
|
||||||
|
//
|
||||||
|
// Created by Felipe Lefèvre Marino on 12/27/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public extension NumberFormatter {
|
||||||
|
|
||||||
|
func string(from doubleValue: Double?) -> String? {
|
||||||
|
if let doubleValue = doubleValue {
|
||||||
|
return string(from: NSNumber(value: doubleValue))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
69
submodules/BotPaymentsUI/Sources/Formatter/String.swift
Normal file
69
submodules/BotPaymentsUI/Sources/Formatter/String.swift
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
//
|
||||||
|
// String.swift
|
||||||
|
// CurrencyText
|
||||||
|
//
|
||||||
|
// Created by Felipe Lefèvre Marino on 4/3/18.
|
||||||
|
// Copyright © 2018 Felipe Lefèvre Marino. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol CurrencyString {
|
||||||
|
var representsZero: Bool { get }
|
||||||
|
var hasNumbers: Bool { get }
|
||||||
|
var lastNumberOffsetFromEnd: Int? { get }
|
||||||
|
func numeralFormat() -> String
|
||||||
|
mutating func updateDecimalSeparator(decimalDigits: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Currency String Extension
|
||||||
|
extension String: CurrencyString {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
/// Informs with the string represents the value of zero
|
||||||
|
public var representsZero: Bool {
|
||||||
|
return numeralFormat().replacingOccurrences(of: "0", with: "").count == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if the string does have any character that represents numbers
|
||||||
|
public var hasNumbers: Bool {
|
||||||
|
return numeralFormat().count > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The offset from end index to the index _right after_ the last number in the String.
|
||||||
|
/// e.g. For the String "123some", the last number position is 4, because from the _end index_ to the index of _3_
|
||||||
|
/// there is an offset of 4, "e, m, o and s".
|
||||||
|
public var lastNumberOffsetFromEnd: Int? {
|
||||||
|
guard let indexOfLastNumber = lastIndex(where: { $0.isNumber }) else { return nil }
|
||||||
|
let indexAfterLastNumber = index(after: indexOfLastNumber)
|
||||||
|
return distance(from: endIndex, to: indexAfterLastNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
/// Updates a currency string decimal separator position based on
|
||||||
|
/// the amount of decimal digits desired
|
||||||
|
///
|
||||||
|
/// - Parameter decimalDigits: The amount of decimal digits of the currency formatted string
|
||||||
|
public mutating func updateDecimalSeparator(decimalDigits: Int) {
|
||||||
|
guard decimalDigits != 0 && count >= decimalDigits else { return }
|
||||||
|
let decimalsRange = index(endIndex, offsetBy: -decimalDigits)..<endIndex
|
||||||
|
|
||||||
|
let decimalChars = self[decimalsRange]
|
||||||
|
replaceSubrange(decimalsRange, with: "." + decimalChars)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The numeral format of a string - remove all non numerical ocurrences
|
||||||
|
///
|
||||||
|
/// - Returns: itself without the non numerical characters ocurrences
|
||||||
|
public func numeralFormat() -> String {
|
||||||
|
return replacingOccurrences(of:"[^0-9]", with: "", options: .regularExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Static constants
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
public static let negativeSymbol = "-"
|
||||||
|
}
|
@ -0,0 +1,182 @@
|
|||||||
|
//
|
||||||
|
// CurrencyUITextFieldDelegate.swift
|
||||||
|
// CurrencyText
|
||||||
|
//
|
||||||
|
// Created by Felipe Lefèvre Marino on 12/26/18.
|
||||||
|
// Copyright © 2018 Felipe Lefèvre Marino. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/// Custom text field delegate, that formats user inputs based on a given currency formatter.
|
||||||
|
public class CurrencyUITextFieldDelegate: NSObject {
|
||||||
|
|
||||||
|
public var formatter: (CurrencyFormatting & CurrencyAdjusting)!
|
||||||
|
|
||||||
|
public var textUpdated: (() -> Void)?
|
||||||
|
|
||||||
|
/// Text field clears its text when value value is equal to zero.
|
||||||
|
public var clearsWhenValueIsZero: Bool = false
|
||||||
|
|
||||||
|
/// A delegate object to receive and potentially handle `UITextFieldDelegate events` that are sent to `CurrencyUITextFieldDelegate`.
|
||||||
|
///
|
||||||
|
/// Note: Make sure the implementation of this object does not wrongly interfere with currency formatting.
|
||||||
|
///
|
||||||
|
/// By returning `false` on`textField(textField:shouldChangeCharactersIn:replacementString:)` no currency formatting is done.
|
||||||
|
public var passthroughDelegate: UITextFieldDelegate? {
|
||||||
|
get { return _passthroughDelegate }
|
||||||
|
set {
|
||||||
|
guard newValue !== self else { return }
|
||||||
|
_passthroughDelegate = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
weak private(set) var _passthroughDelegate: UITextFieldDelegate?
|
||||||
|
|
||||||
|
public init(formatter: CurrencyFormatter) {
|
||||||
|
self.formatter = formatter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UITextFieldDelegate
|
||||||
|
|
||||||
|
extension CurrencyUITextFieldDelegate: UITextFieldDelegate {
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
open func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
||||||
|
return passthroughDelegate?.textFieldShouldBeginEditing?(textField) ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
|
textField.setInitialSelectedTextRange()
|
||||||
|
passthroughDelegate?.textFieldDidBeginEditing?(textField)
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
|
||||||
|
if let text = textField.text, text.representsZero && clearsWhenValueIsZero {
|
||||||
|
textField.text = ""
|
||||||
|
}
|
||||||
|
else if let text = textField.text, let updated = formatter.formattedStringAdjustedToFitAllowedValues(from: text), updated != text {
|
||||||
|
textField.text = updated
|
||||||
|
}
|
||||||
|
return passthroughDelegate?.textFieldShouldEndEditing?(textField) ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
open func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
|
passthroughDelegate?.textFieldDidEndEditing?(textField)
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
open func textFieldShouldClear(_ textField: UITextField) -> Bool {
|
||||||
|
return passthroughDelegate?.textFieldShouldClear?(textField) ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
open func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
return passthroughDelegate?.textFieldShouldReturn?(textField) ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
let shouldChangeCharactersInRange = passthroughDelegate?.textField?(textField,
|
||||||
|
shouldChangeCharactersIn: range,
|
||||||
|
replacementString: string) ?? true
|
||||||
|
guard shouldChangeCharactersInRange else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store selected text range offset from end, before updating and reformatting the currency string.
|
||||||
|
let lastSelectedTextRangeOffsetFromEnd = textField.selectedTextRangeOffsetFromEnd
|
||||||
|
|
||||||
|
// Before leaving the scope, update selected text range,
|
||||||
|
// respecting previous selected text range offset from end.
|
||||||
|
defer {
|
||||||
|
textField.updateSelectedTextRange(lastOffsetFromEnd: lastSelectedTextRangeOffsetFromEnd)
|
||||||
|
textUpdated?()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard !string.isEmpty else {
|
||||||
|
handleDeletion(in: textField, at: range)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard string.hasNumbers else {
|
||||||
|
addNegativeSymbolIfNeeded(in: textField, at: range, replacementString: string)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormattedText(in: textField, inputString: string, range: range)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
extension CurrencyUITextFieldDelegate {
|
||||||
|
|
||||||
|
/// Verifies if user inputed a negative symbol at the first lowest
|
||||||
|
/// bound of the text field and add it.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - textField: text field that user interacted with
|
||||||
|
/// - range: user input range
|
||||||
|
/// - string: user input string
|
||||||
|
private func addNegativeSymbolIfNeeded(in textField: UITextField, at range: NSRange, replacementString string: String) {
|
||||||
|
guard textField.keyboardType == .numbersAndPunctuation else { return }
|
||||||
|
|
||||||
|
if string == .negativeSymbol && textField.text?.isEmpty == true {
|
||||||
|
textField.text = .negativeSymbol
|
||||||
|
} else if range.lowerBound == 0 && string == .negativeSymbol &&
|
||||||
|
textField.text?.contains(String.negativeSymbol) == false {
|
||||||
|
|
||||||
|
textField.text = .negativeSymbol + (textField.text ?? "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Correctly delete characters when user taps remove key.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - textField: text field that user interacted with
|
||||||
|
/// - range: range to be removed
|
||||||
|
private func handleDeletion(in textField: UITextField, at range: NSRange) {
|
||||||
|
if var text = textField.text {
|
||||||
|
if let textRange = Range(range, in: text) {
|
||||||
|
text.removeSubrange(textRange)
|
||||||
|
} else {
|
||||||
|
text.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.isEmpty {
|
||||||
|
textField.text = text
|
||||||
|
} else {
|
||||||
|
textField.text = formatter.formattedStringWithAdjustedDecimalSeparator(from: text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats text field's text with new input string and changed range
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - textField: text field that user interacted with
|
||||||
|
/// - inputString: typed string
|
||||||
|
/// - range: range where the string should be added
|
||||||
|
private func setFormattedText(in textField: UITextField, inputString: String, range: NSRange) {
|
||||||
|
var updatedText = ""
|
||||||
|
|
||||||
|
if let text = textField.text {
|
||||||
|
if text.isEmpty {
|
||||||
|
updatedText = formatter.initialText + inputString
|
||||||
|
} else if let range = Range(range, in: text) {
|
||||||
|
updatedText = text.replacingCharacters(in: range, with: inputString)
|
||||||
|
} else {
|
||||||
|
updatedText = text.appending(inputString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedText.numeralFormat().count > formatter.maxDigitsCount {
|
||||||
|
updatedText.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
textField.text = formatter.formattedStringWithAdjustedDecimalSeparator(from: updatedText)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// UITextField.swift
|
||||||
|
// CurrencyText
|
||||||
|
//
|
||||||
|
// Created by Felipe Lefèvre Marino on 12/26/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public extension UITextField {
|
||||||
|
|
||||||
|
// MARK: Public
|
||||||
|
|
||||||
|
var selectedTextRangeOffsetFromEnd: Int {
|
||||||
|
return offset(from: endOfDocument, to: selectedTextRange?.end ?? endOfDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the selected text range when the text field is starting to be edited.
|
||||||
|
/// _Should_ be called when text field start to be the first responder.
|
||||||
|
func setInitialSelectedTextRange() {
|
||||||
|
// update selected text range if needed
|
||||||
|
adjustSelectedTextRange(lastOffsetFromEnd: 0) // at the end when first selected
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface to update the selected text range as expected.
|
||||||
|
/// - Parameter lastOffsetFromEnd: The last stored selected text range offset from end. Used to keep it concise with pre-formatting.
|
||||||
|
func updateSelectedTextRange(lastOffsetFromEnd: Int) {
|
||||||
|
adjustSelectedTextRange(lastOffsetFromEnd: lastOffsetFromEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
/// Adjust the selected text range to match the best position.
|
||||||
|
private func adjustSelectedTextRange(lastOffsetFromEnd: Int) {
|
||||||
|
/// If text is empty the offset is set to zero, the selected text range does need to be changed.
|
||||||
|
if let text = text, text.isEmpty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetFromEnd = lastOffsetFromEnd
|
||||||
|
|
||||||
|
/// Adjust offset if needed. When the last number character offset from end is less than the current offset,
|
||||||
|
/// or in other words, is more distant to the end of the string, the offset is readjusted to it,
|
||||||
|
/// so the selected text range is correctly set to the last index with a number.
|
||||||
|
if let lastNumberOffsetFromEnd = text?.lastNumberOffsetFromEnd,
|
||||||
|
case let shouldOffsetBeAdjusted = lastNumberOffsetFromEnd < offsetFromEnd,
|
||||||
|
shouldOffsetBeAdjusted {
|
||||||
|
|
||||||
|
offsetFromEnd = lastNumberOffsetFromEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedTextRange(offsetFromEnd: offsetFromEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the selected text range with given offset from end.
|
||||||
|
private func updateSelectedTextRange(offsetFromEnd: Int) {
|
||||||
|
if let updatedCursorPosition = position(from: endOfDocument, offset: offsetFromEnd) {
|
||||||
|
selectedTextRange = textRange(from: updatedCursorPosition, to: updatedCursorPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,55 @@ private func loadCurrencyFormatterEntries() -> [String: CurrencyFormatterEntry]
|
|||||||
|
|
||||||
private let currencyFormatterEntries = loadCurrencyFormatterEntries()
|
private let currencyFormatterEntries = loadCurrencyFormatterEntries()
|
||||||
|
|
||||||
|
public func setupCurrencyNumberFormatter(currency: String) -> NumberFormatter {
|
||||||
|
guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = ""
|
||||||
|
if entry.symbolOnLeft {
|
||||||
|
result.append("¤")
|
||||||
|
if entry.spaceBetweenAmountAndSymbol {
|
||||||
|
result.append(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append("#")
|
||||||
|
|
||||||
|
result.append(entry.decimalSeparator)
|
||||||
|
|
||||||
|
for _ in 0 ..< entry.decimalDigits {
|
||||||
|
result.append("#")
|
||||||
|
}
|
||||||
|
if entry.decimalDigits != 0 {
|
||||||
|
result.append("0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !entry.symbolOnLeft {
|
||||||
|
if entry.spaceBetweenAmountAndSymbol {
|
||||||
|
result.append(" ")
|
||||||
|
}
|
||||||
|
result.append("¤")
|
||||||
|
}
|
||||||
|
|
||||||
|
let numberFormatter = NumberFormatter()
|
||||||
|
|
||||||
|
numberFormatter.numberStyle = .currency
|
||||||
|
|
||||||
|
numberFormatter.positiveFormat = result
|
||||||
|
numberFormatter.negativeFormat = "-\(result)"
|
||||||
|
|
||||||
|
numberFormatter.currencySymbol = entry.symbol
|
||||||
|
numberFormatter.currencyDecimalSeparator = entry.decimalSeparator
|
||||||
|
numberFormatter.currencyGroupingSeparator = entry.thousandsSeparator
|
||||||
|
|
||||||
|
numberFormatter.minimumFractionDigits = entry.decimalDigits
|
||||||
|
numberFormatter.maximumFractionDigits = entry.decimalDigits
|
||||||
|
numberFormatter.minimumIntegerDigits = 1
|
||||||
|
|
||||||
|
return numberFormatter
|
||||||
|
}
|
||||||
|
|
||||||
public func fractionalToCurrencyAmount(value: Double, currency: String) -> Int64? {
|
public func fractionalToCurrencyAmount(value: Double, currency: String) -> Int64? {
|
||||||
guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
|
guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
|
||||||
return nil
|
return nil
|
||||||
@ -54,7 +103,11 @@ public func fractionalToCurrencyAmount(value: Double, currency: String) -> Int64
|
|||||||
for _ in 0 ..< entry.decimalDigits {
|
for _ in 0 ..< entry.decimalDigits {
|
||||||
factor *= 10.0
|
factor *= 10.0
|
||||||
}
|
}
|
||||||
return Int64(value * factor)
|
if value > Double(Int64.max) / factor {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return Int64(value * factor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func currencyToFractionalAmount(value: Int64, currency: String) -> Double? {
|
public func currencyToFractionalAmount(value: Int64, currency: String) -> Double? {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user