2

To facilitate easier localizing in a very small app of mine, I have this String extension method:

extension String {
    func localized(with values: Any...) -> String {
        // debug values
        for v in values {
            print("\(type(of: v)): \(v)")
        }

        return String.localizedStringWithFormat(NSLocalizedString(self, comment: ""), values)
    }
}

My German localization of Localizable.strings contains this key/value pair: "WeeksFuture" = "In %d Wochen";

Doing this:

for _ in 0..<5 {
    let localized = "WeeksFuture".localized(with: 3)
    print(localized)
}

while having Xcode set to debug the app in German (although this happens in every other language too) prints this to the output window:

Int: 3
In 151.456 Wochen
Int: 3
In 186.912 Wochen
Int: 3
In 186.880 Wochen
Int: 3
In 187.264 Wochen
Int: 3
In 187.488 Wochen

Obviously, this is all wrong. Why do I first get the correct output of "Int: 3", and then a string with a seemingly random garbage number?

Peter W.
  • 2,323
  • 4
  • 22
  • 42
  • Related: [localizeWithFormat and variadic arguments in Swift](https://stackoverflow.com/questions/27914053/localizewithformat-and-variadic-arguments-in-swift) and [How to properly use VarArgs for localizing strings?](https://stackoverflow.com/questions/42457503/how-to-properly-use-varargs-for-localizing-strings). – Martin R May 29 '17 at 09:28

1 Answers1

4

String.localizedStringWithFormat takes a String and CVarArg... as arguments. You passed in an array of Any - values as the second argument. It is forced to convert an array to a decimal number, resulting in the weird result.

To solve this problem, you just need to find an overload that takes an [CVarArg] instead. Luckily, there is an init overload like that:

 return String.init(format: 
    NSLocalizedString(self, comment: ""), arguments: values)

However, values is an [Any], which is not compatible with the expected [CVarArg]. You should probably change the parameter type.

So your whole extension looks like this:

func localized(with values: CVarArg...) -> String {
    return String.init(format: NSLocalizedString(self, comment: ""), arguments: values)
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Hi Sweeper - so has there been no advance on this? We're not actually calling `String.localizedStringWithFormat`, and there seems no way to do so, as we cannot pass the variadics into the body of the function as a splatted list. I'm happy to use this approach but it feels like we're trying to reinvent an existing wheel. – matt Jun 29 '20 at 17:59
  • @matt I just looked through the new features of Swift 5.3, and it seems you still can't splat the variadic. As long as you can't splat variadics, you can't call `localizedStringWithFormat` with an array... That said, ideally Apple should have added a `FormattableString` type that is `ExpressibleByStringInterpolation`, like C# does... – Sweeper Jun 30 '20 at 00:47
  • Thanks, I agree with your assessment. I suppose it isn't as bad as I think; your `localized` implementation probably does just what `String.localizedStringWithFormat` does. – matt Jun 30 '20 at 00:50