9

I have a String extension that helps me internationalise.

public extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }

    func localized(args:CVarArg...) -> String{
        return NSString.localizedStringWithFormat(self.localized as NSString, args) as String
    }
}

This way I can easily use "hello_world".localized anywhere in the app and it works nicely.

Now I want to have the same functionality, but also want to be able to pass arguments. However passing the 'CVarArg...' doesn't seem to work as I'd expect it to.

"grant_gps_access".localized("MyApp")

Expected result: "Please grant MyApp GPS access"

Actual result: "Please grant (\n MyApp\n) GPS access"

What am I missing here?

fancy
  • 2,077
  • 2
  • 23
  • 45

1 Answers1

16

You cannot pass a variable argument list to another function, you have to pass a CVaListPointer instead (the Swift equivalent of va_list in C):

public extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }

    func localized(args: CVarArg...) -> String {
        return withVaList(args) {
            NSString(format: self.localized, locale: Locale.current, arguments: $0) as String
        }
    }
}

Since NSString.localizedStringWithFormat has no variant taking a VAListPointer, the equivalent NSString(format:, locale:, arguments:) with the current locale is used.

Even simpler (attribution goes to @OOPer): Use String.init(format:locale:arguments:) which takes a [CVarArg] argument:

    func localized(args: CVarArg...) -> String {
        return String(format: self.localized, locale: Locale.current, arguments: args)
    }

Now

"grant_gps_access".localized(args: "MyApp")

should work as expected, assuming that the strings file contains the entry

"grant_gps_access" =  "Please grant %@ GPS access";
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • You can call `String.init(format:locale:arguments:)` without using `withVaList`. – OOPer Feb 25 '17 at 15:20
  • @OOPer: You are right, that is simpler because it takes a `[CVarArg]` argument, I missed that! – Martin R Feb 25 '17 at 15:24
  • 1
    @MartinR I tried your above solution, however, I am having the problem mentioned in this post https://stackoverflow.com/questions/42428504/swift-3-issue-with-cvararg-being-passed-multiple-times Also passing the CVaListPointer to the arguments isn't being allowed I get a compiler error. Know a workaround this? – Serj Jul 25 '18 at 19:28
  • I just tried the "simpler" solution and was still getting the same output (i.e. "Please grant (\n MyApp\n) access"). @Martin R's initial solution is the only one working for me. – elight May 05 '20 at 18:29