While trying to answer this question, I found a strange behaviour.
Text(LocalizedStringKey("Hello \(Image(systemName: "globe"))"))
displays a globe, but
Text(LocalizedStringKey("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))")))
Text(LocalizedStringKey("Hello" + "\(Image(systemName: "globe"))"))
displays "Hello" followed by a jumble of SwiftUI's internal jargon mess.
An even more minimal example would be:
let x = "\(Image(systemName: "globe"))"
print(LocalizedStringKey.init(x))
print(LocalizedStringKey.init("\(Image(systemName: "globe"))"))
The values I'm passing to LocalizedStringKey.init should be the same, both "\(Image(systemName: "globe"))", but The first prints
LocalizedStringKey(key: "%@", hasFormatting: true, arguments: [...])
and the second prints
LocalizedStringKey(key: "Image(provider: SwiftUI.ImageProviderBox<SwiftUI.Image.(unknown context at $7ff91ccb3380).NamedImageProvider>)", hasFormatting: false, arguments: [])
It appears that LocalizedStringKey.init changes its behaviour depends on whether the arguments I pass is an (interpolated) string literal or not.
As far as I can see, the two calls to LocalizedStringKey.init are calling the same initialiser. There is only one parameter-label-less initialiser in LocalizedStringKey, which takes a String.
If there were also an initialiser that takes a LocalizedStringKey, the results would be much more understandable. LocalizedStringKey has custom string interpolation rules, and one specifically for Image, after all. But this is the only initialiser with no parameter labels as far as I know.
It would also be somewhat understandable if the initialiser's parameter is @autoclosure () -> String. If the expression I pass in is lazily evaluated, the method might be able to "peek inside" the closure by some means unknown to me. But the parameter isn't an auto closure.
What seems to be happening here is that the compiler is creating a LocalizedStringKey with key being that same pattern as the interpolation you passed in, even though the parameter is a String!
What is actually going on here? Did I miss a hidden initialiser somewhere?
TL;DR: the behaviour you're seeing comes from
ExpressibleByStringInterpolation. But read on for more fun!LocalizedStringKeybecomes easier to understand if you think of it purely as a convenience to allow SwiftUI interface elements to be localizable "for free" when using string literals. There's only one real time you'd use it directly.Consider
Text. There are two relevant initializers:which will attempt to localize the text passed in, and
which will display the string without altering it.
If you call
Text("Hello"), which initializer is used?String literals conform to
StringProtocol, butLocalizedStringKeyis alsoExpressibleByStringLiteral. The compiler would not know which one to choose.To get "free" localization, the
StringProtocolinitializer is marked with@_disfavoredOverload, which tells the compiler to assume that the string literal is aLocalizableStringKeyrather than aString.Therefore,
Text("Hello")andText(LocalizedStringKey("Hello"))are equivalent.In this case, there is no conflict - the compiler uses the
StringProtocolinitializer and the string is not localized.What does this have to do with your question?
LocalizedStringKeyis alsoExpressibleByStringInterpolation, which is where your "hidden initializer" comes from. But like the examples above, this only comes into play if you are initializing it with a single, interpolated string.You're passing an interpolated string, so the compiler can deal with it and add the image into the interpolation.
Here,
replacingOccurrences(of:is evaluated first, meaning your argument is aString, which is not treated as a LocalizedStringKey expressed-via-string-interpolation. You're essentially seeing the description of the image.A similar thing happens with the example with
+in it. That implicitly makes aString, so you lose the special image interpolation thatLocalizedStringKeygives you.For your last code example:
xis a string containing a description of the image. Remember, onlyLocalizedStringKeyhas the magic power to actually understand and representImage. Any other string interpolation will fall back to the description of the interpolated object.The first initializer is passing a string (which is treated as a key, that's the only time you'd really directly use
LocalizedStringKey, if you were generating keys at run time and wanted to use them for lookup).The second initializer is using
ExpressibleByStringInterpolationand is usingLocalizedStringKey.StringInterpolationto insert images into its internal storage, which can then get rendered byText.