Using a pluralized string and a number formatter at the same time

Viewed 514

I want to show a localized string in iOS that supports the following outputs:

1 mile

1.5 miles

2 miles

1,5 miles

I can use a .stringsdict file to make the pluralization work in a way that is localizable. However, I want the number passed in to have a decimal part and I only want to show 0 or 1 decimal places. Furthermore, the number should be formatted based on locale (i.e. show the correct decimal separator).

It occurs to me the solution may take one of two forms, but I can't get either working:

  1. pass in a number representation to stringsdict alongside a string representation
  2. format the number inside the stringsdict using the IEEE printf specification
2 Answers

Okay, I actually got (1) working in the end, but it feels kind of hacky, so if anyone knows a better answer, I would still like to hear it.

let miles: Double = 1.5

let formatter = NumberFormatter()
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 1
let milesString = formatter.string(from: NSNumber(value: miles))!

let formatString = NSLocalizedString("key", comment: "")
let string = String(format: formatString, arguments: [miles, milesString])

And the stringsdict looks like:

<dict>
        <key>key</key>
        <dict>
                <key>NSStringLocalizedFormatKey</key>
                <string>%1$#@miles@</string>
                <key>miles</key>
                <dict>
                        <key>NSStringFormatSpecTypeKey</key>
                        <string>NSStringPluralRuleType</string>
                        <key>NSStringFormatValueTypeKey</key>
                        <string>f</string>
                        <key>one</key>
                        <string>1 mile</string>
                        <key>other</key>
                        <string>%2$@ miles</string>
                </dict>
        </dict>
</dict>

MeasurementFormatter is exactly made for this kind of thing. You can use:

let formatter = MeasurementFormatter()
formatter.unitStyle = .long
formatter.unitOptions = .providedUnit
let string = formatter.string(from: Measurement(value: 1.5, unit: UnitLength.miles))

MeasurementFormatter will handle all the plural and localisation for you.

If you only want to localise the number, but not the unit, you can set the formatter.locale to an English locale, and set formatter.numberFormatter.locale to the local locale:

formatter.locale = Locale(identifier: "en")
formatter.numberFormatter.locale = Locale.current
Related