Localizewithformat and Variadic Arguments in Swift

localizeWithFormat and variadic arguments in Swift

This should be pretty simple just change your parameters as follow:

extension String {
func localizeWithFormat(name:String,age:Int, comment:String = "") -> String {
return String.localizedStringWithFormat( NSLocalizedString(self, comment: comment), name, age)
}
}

"My name is %@. I am %d years old".localizeWithFormat("John", age: 30) // "My name is John. I am 30 years old"

init(format:locale:arguments:)

extension String {
func localizeWithFormat(args: CVarArgType...) -> String {
return String(format: self, locale: nil, arguments: args)
}
func localizeWithFormat(local:NSLocale?, args: CVarArgType...) -> String {
return String(format: self, locale: local, arguments: args)
}
}
let myTest1 = "My name is %@. I am %d years old".localizeWithFormat(NSLocale.currentLocale(), args: "John",30)
let myTest2 = "My name is %@. I am %d years old".localizeWithFormat("John",30)

How to properly use VarArgs for localizing strings?

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";

passing variadic arguments to 'print' doesn't produce desired output

First you call LogUtil.d("tag", "param1", "param2"). Since the items parameter is declared as variadic Any..., the local variable items inside d has type [Any], and has the value ["tag", "param1", "param2"].

Then d calls p, passing its own items as the first argument for p's items parameter. Since p's items parameter is also variadic, d could pass more arguments for p's items. That is, d could call p(LogLevel.DEBUG, items: items, "more", "words"). It doesn't, but it could.

Since p's items parameter is variadic Any..., the local variable items inside p is also of type [Any], and its value is [["tag", "param1", "param2"]]. It is an array of three elements inside an array of one element.

What you need to do is make p, or some new function, take a non-variadic items parameter, and call that from d and all your other variadic functions.

Also, __FILE__ and __LINE___ are deprecated. If you've upgraded to Xcode 7.3, you should use the new #file and #line special forms instead.

But you still have a problem: #file and #line will return the file and line where they are used inside function p. That's probably not what you want. You probably want the file and line where the program calls d or i or whatever.

To do that, you pass the file and line arguments to d (and the other functions), with default values of #file and #line. The defaults will be expanded at the call site, so you'll get the file and line number of the call to d or i or whatever.

So ultimately you want one function that knows how to take an [Any] (with only one level of array wrapping) and turn it into a space-separated string. Here's how you do that:

struct LogUtil {

enum LogLevel: String {
case Debug
case Info
case Warning
case Error
case Fatal
}

// The low-level function that all the others call.
private static func log(level level: LogLevel, file: String, line: Int, items: [Any]) {
let itemString = items.map { String($0) }.joinWithSeparator(" ")
print("\(level) - \(file)(\(line)): \(itemString)")
}

Note that log(level:file:line:items:) is not variadic, so it won't add another array wrapper around its items.

Then you define all your user-visible functions to call the log function:

    static func p(level: LogLevel, items: Any..., file: String = #file, line: Int = #line) {
log(level: level, file: file, line: line, items: items)
}

static func d(items: Any..., file: String = #file, line: Int = #line) {
log(level: .Debug, file: file, line: line, items: items)
}

static func i(items: Any..., file: String = #file, line: Int = #line) {
log(level: .Info, file: file, line: line, items: items)
}

// other level-specific functions here...

}

When you call LogUtil.d("tag", "foo", "bar"), the output looks like you want:

Debug - /var/folders/kn/<snip>/playground282.swift(33): tag foo bar

NSLocalizedString with format specifiers in Swift yields garbage

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)
}

How to use NSLocalizedString function with variables in Swift?

You can use the sprintf format parameters within NSLocalizedString, so your example can look like this:

let myString = String(format: NSLocalizedString(" - %d Notifica", comment: "sottotitolo prescrizione per le notifiche al singolare"), count)

String with format from localized string

localizedStringWithFormat does not take an array of arguments, it takes a variable list of arguments. So when you pass args, it treats that array as only one argument. The %@ format specifier then converts the array to a string which results in the parentheses.

You should use the String initializer that takes the format arguments as an array.

func displayLocalizedMessage(key: String, args: [CVarArg]) {
someLabel.text = String(format: NSLocalizedString(key, comment: ""), locale: Locale.current, arguments: args)
}

How to format localised strings in Swift?

You can use %@ in Swift's String(format:...), it can be substituted
by a Swift String or any instance of a NSObject subclass.
For example, if the Localizable.strings file contains the definition

"From %@, %@" = "从 %@, %@ 得出";

then

let x = 1.2
let y = 2.4
let text = String(format: NSLocalizedString("From %@, %@", comment: ""), "\(x)", "\(y)")
// Or alternatively:
let text = String(format: NSLocalizedString("From %@, %@", comment: ""), NSNumber(double: x), NSNumber(double: y))

produces "从 1.2, 2.4 得出". Another option would be to use the
%f format for double floating point numbers:

"From %f, %f" = "从 %f, %f 得出";

with

let text = String(format: NSLocalizedString("From %f, %f", comment: ""), x, y)

See Niklas' answer
for an even better solution which localizes the number representation
as well.

how to create macro as function with swift programming language

Try using a String extension (the answer for which I found here):

extension String {
func localizedStringWithVariables(value: String, vars: CVarArgType...) -> String {
return String(format: NSLocalizedString(self, tableName: nil, bundle: NSBundle.mainBundle(), value: value, comment: ""), arguments: vars)
}
}

So then you can do something like:

"KeyNameHere".localizedStringWithVariables("some default value", vars: [])

p.s. the empty array in this vars example should be fine if you have no format arguments in the key/value

Formatting Localized Strings with Multiple Values

String.localizedStringWithFormat() works with “positional arguments”
%n$. In your case

"text_key" = "Out of a possible %2$d items you collected %1$d";

would do the trick.

These are documented in fprintf:

Conversions can be applied to the nth argument after the format in the argument list, rather than to the next unused argument. In this case, the conversion specifier character % (see below) is replaced by the sequence "%n$", where n is a decimal integer in the range [1,{NL_ARGMAX}], giving the position of the argument in the argument list.



Related Topics



Leave a reply



Submit