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 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?
Yes, it works. I found the specifiers and positional indicators describe here: String Format Specifiers
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:
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.
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?
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?
Sorry to revive this old post but, in case anyone else ends up here trying to solve the same kind of problem, I'd like to explain something.
_FormatSpecifiable is a public protocol. It is however undocumented. (It's impossible to have a non-public protocol in a public API.)
The actual public protocol is:
public protocol _FormatSpecifiable : Swift.Equatable {
associatedtype _Arg : Swift.CVarArg
var _arg: Self._Arg { get }
var _specifier: Swift.String { get }
}
It's found in SwiftUI's swiftinterface file: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64e-apple-ios.swiftinterface
You can see the public interface for how they have implemented it for stdlib types:
extension Int : SwiftUI._FormatSpecifiable {
public var _arg: Swift.Int64 {
get
}
public var _specifier: Swift.String {
get
}
public typealias _Arg = Swift.Int64
}
Knowing this you could go into a playground and simply:
print(42._arg) // prints 42
print(42._specifier) // prints %lld
print(CGFloat(42)._arg) // prints 42.0
print(CGFloat(42)._specifier) // prints %lf
Etc.
However given that there is no documentation about this and its types have been marked with _ meaning, "for Apple's use only", I would recommend to not conform to this, or if you need to use it, then file developer support ticket and/or a feedback assistant issue to and see if we can ask them to make this public and document it.
I got sick of manually making up the right key. So I made this to get the key out the the Text:
extension Text {
/// print out the LocalizedStringKey generated key
/// for use in Localizable.strings and Localizable.stringsdict
/// This string is the left hand side of a entry in Localizable.strings
/// and top most <key>...</key> entry in .stringsdict
/// Usage:
/// ```
/// Text("My dog \("Paula") ate \(2) and \(0)")
/// .printLocalizedKey()
/// ```
@warn_unqualified_access
func printLocalizedKey() -> Text {
let mirror = Mirror(reflecting: self)
if let key = mirror.descendant("storage", "anyTextStorage", "key", "key") {
print(key as! String)
} else {
print("printLocalizedKey(): no key found!")
}
return self
}
}
But I still have problem: the genstrings command line only recognize Text("string literal"). But these is a whole lot SwiftUI views that take LocalizedStringKey that are localizable. So I don't know of anyway to process my source to get all the localizable strings
Can you show an example of some functions that takes LocalizedStringKey where you're having the problem?
And what is the problem specifically? Is it that the genstrings command doesn't recognize these various places where you are using a custom localized string key as the input to a SwiftUI view?
genstrings is useless for SwiftUI currently. Two problems with the genstrings command:
It generates wrong key. For this source:
Text("My dog \(name) ate \(percent * 100, specifier: "%.2f")% of \(count) bags")
genstrings generates this key:
"My dog %@ ate %@ and %@ bags" = "My dog %1$@ ate %2$@ and %3$@ bags";
SwiftUI.Text expects this key:
My dog %@ ate %.2f%% of %lld bags
There are lots of view init's and accessibility that takes LocalizedStringKey but genstrings doesn't recognize. I've created a test with all of these init's. Every string literals that has LocalizedStringKey in it are LocalizedStringKey.
run genstrings on this input to see the problems.
import SwiftUI
extension Text {
/// print out the LocalizedStringKey generated key
/// for use in Localizable.strings and Localizable.stringsdict
/// This string is the left hand side of an entry in Localizable.strings
/// and top most <key>...</key> entry in .stringsdict
/// Usage:
/// ```
/// Text("My dog \("Paula") ate \(2) and \(0)")
/// .printLocalizedKey()
/// ```
@warn_unqualified_access
func printLocalizedKey() -> Text {
let mirror = Mirror(reflecting: self)
if let key = mirror.descendant("storage", "anyTextStorage", "key", "key") {
print(key as! String)
} else {
print("printLocalizedKey(): no key found!")
}
return self
}
}
// here in contains may things that takes a `LocalizedStringKey` that can be localized
// but genstrings doesn't recognize at all
struct ContentView: View {
let name = "Paula"
let percent = 0.90
let count = 2
@State var color = Color.green
@State var date = Date()
@State var picker = 0
@State var text = ""
@State var flag = true
var body: some View {
VStack {
Group {
// this is the correct key
// My dog %@ ate %.2f%% of %lld bags
// this is genstrings output:
// "My dog %@ ate %@ and %@ bags" = "My dog %1$@ ate %2$@ and %3$@ bags";
Text("My dog \(name) ate \(percent * 100, specifier: "%.2f")% of \(count) bags")
.printLocalizedKey() // look in console to see what the key is for this Text
ColorPicker("ColorPicker LocalizedStringKey", selection: $color)
DatePicker("DatePicker LocalizedStringKey", selection: $date)
DisclosureGroup("DisclosureGroup LocalizedStringKey") {
Text("Some Key")
}
// should genstrings recognize "ABC"?
Image("ABC") // The name of the image resource to lookup, as well as the localization key with which to label the image.
Image("ABC", label: Text("LocalizedStringKey")) // genstrings should recognizes label
Link("Link LocalizedStringKey", destination: URL(string: "https://swift.org")!)
Menu("Menu LocalizedStringKey") {
Button("Some Key") { }
}
}
Text("Some Key")
.accessibilityLabel("accessibilityLabel LocalizedStringKey")
.accessibilityValue("accessibilityValue LocalizedStringKey")
.accessibilityHint("accessibilityHint LocalizedStringKey")
.accessibilityInputLabels(["accessibilityInputLabels LocalizedStringKey"])
.accessibilityAction(named: "accessibilityAction LocalizedStringKey", { })
.help(".help LocalizedStringKey")
Group {
NavigationLink("NavigationLink LocalizedStringKey", destination: Text("somewhere"))
Picker("Picker LocalizedStringKey", selection: $picker) {
Text("Some Key")
}
ProgressView("ProgressView LocalizedStringKey", value: 0.3)
SecureField("SecureField LocalizedStringKey", text: $text)
Stepper("Stepper LocalizedStringKey", onIncrement: nil, onDecrement: nil)
TextField("TextField LocalizedStringKey", text: $text)
Toggle("Toggle LocalizedStringKey", isOn: $flag)
}
}
.navigationTitle("navigationTitle LocalizedStringKey")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}