Localize Interpolated String: Why Only String Value Work?

I found this thread by @Daniel_Hopfl
from 2016: Localization support for string interpolation

Using the example there, I can localize the interpolated string in SwiftUI.Text(), but only if the interpolated value is a String. Change to Int doesn't work.

The example:

let quote = "Never trust the internet!"
let person = "Albert Einstein"
SwiftUI.Text("<\(quote)> by <\(person)>")

should look up the key:

"<%@> by <%@>" = "%2$@ said: “%1$@”";

But if I simply change quote to an Int:

let quote = 1234

It stops working. It's as if the key doesn't match, no translation found.

I'm quite sure somehow this should work according to WWDC 2019 "What's New in Swift" https://developer.apple.com/videos/play/wwdc2019/402/

at about 13:00 she talks about localizing interpolated string....

Anyone know something about this?

Edit: She actually start talking about localizing interpolated string at 22:15

I believe the reason is that %@ is the specifier for an object, of which String is one. You need a different specifier for ints.

Yes, you are right. For Int she uses %lld. But how to indicate position in the translation? Just Something simple:

"number \(n), name \(s)"

.strings:

"number %lld, name %@" = "數字%lld、名字%@"; <-- works
"number %lld, name %@" = "名字%@、數字%lld"; <-- don't work

So @Daniel_Hopfl proposal and Apple's work not that same at all, no relationship? In @Daniel_Hopfl's sample implementation at https://github.com/dhoepfl/DHLocalizedString, at the very end:

In your Localizable.strings file, replace all interpolations with %@ , regardless of the type of the interpolation.

This is better to me because the result of any interpolation is String, right?

I cannot test it but I guess the following should work:

"number %lld, name %@" = "名字%2$@、數字%1$lld"

(Note 2$ and 1$)

Actually I had to do it that way because back then I did not find any way to get the type of the interpolation. Interpolation got some big changes with Swift 5, so one can get the type now. In turn, this allows to have things like “%7.3lld” as translated format. And string dictionary need to get the raw value, IIRC (“No files“, “One file”, “%lld files”, …).

On the other hand, I’m not sure what happens if you compile for 32 bit: Is Int translated to “%lld”, “%ld”, or “%d”? Do we need different translation entries for each native word size?

1 Like

Yes, it works. I found the specifiers and positional indicators describe here: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html

Good to know...

I wonder why for SwiftUI.Text(), does Apple still use %lld, %f specifier? Could they not just let the "interpolator" output localized formatted string value? Is it because they need to pass thing down so they can do singular/plural case and the like? Seems with so many "Formatter" available, we can use them in our ExpressibleByStringInterpolation to do our localize formatting and not have to deal with %lld, %f , just use %@, seem more Swift this way.

I'm sure I haven't thought of all the requirements for this...things many not work as I imagine.

Thank you for helping me...

Edit: after thinking a little more. I believe Apple did it their way just to use existing localization mechanism.

SwiftUI needs to generate a string with format specifier so that it does work with the current localization mechanisms that exist on Apple's platforms and it does so by leveraging ExpressibleByStringInterpolation.

This is especially important for the interoperability story because you might have an existing localized string file and you want to use it in SwiftUI; but it's also very much important to use the tools that allows you to produce correct localization in multiple languages; for example, by supporting plural rules (via .stringdict) but also different positions in the sentence for the "variable"s. These are features that already exists and work well.

When you're interpolating a numeric value SwiftUI will choose a format specifier appropriate for that type. You have the ability to force a different format specifier:

Text("Guests: \(party.guests.count, specifier: "%d")").

Additionally, SwiftUI also supports formatters directly in the interpolation:

Text("Party Date: \(party.startDate, formatter: partyDateFormatter)").

This will run the localized string look up and then apply the formatter.

4 Likes

Thank you Luca!

What's the use case for specifying specifier? Wouldn't the type of the value determine what the specifier be? As your example:

Text("Guests: \(party.guests.count, specifier: "%d")").

count is a 64-bit Int so the specifier cannot be %d, should be %lld. Wonder why would we want to make specifier to something else?

Think of printing in hexadecimal or octal. Or specifying the number of decimal places for a floating point.

1 Like

It could also be useful as a general escape hatch, for when you want to represent things that Text doesn't yet have a high-level interface for, but which the underlying localization implementation supports.

1 Like

Can you give me an example? In looking at the source of LocalizedStringKey.StringInterpolation:

public mutating func appendInterpolation(_ string: String)

public mutating func appendInterpolation<Subject>(_ subject: Subject, formatter: Formatter? = nil) where Subject : ReferenceConvertible

public mutating func appendInterpolation<Subject>(_ subject: Subject, formatter: Formatter? = nil) where Subject : NSObject

public mutating func appendInterpolation<T>(_ value: T) where T : _FormatSpecifiable

public mutating func appendInterpolation<T>(_ value: T, specifier: String) where T : _FormatSpecifiable

So only _FormatSpecifiable can supply a specifier and _FormatSpecifiable is not public, these _FormatSpecifiable must be fixed, so regular user cannot add their own. Aren't they just whatever NSLocalizedString allow?

Terms of Service

Privacy Policy

Cookie Policy