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 aVAListPointer
, the equivalent NSString(format:, locale:, arguments:)
with the current locale is used.
Even simpler (attribution goes to @OOPer): UseString.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
Implementing a Drag-And-Drop Zone in Swift
How to Completely Remove Realm Database from iOS
Iso8601 Date JSON Decoding Using Swift4
When to Use [Self] VS [Weak Self] in Swift Blocks
Swift - Seeding Arc4Random_Uniform? or Alternative
Add Switch in Uitableview Cell in Swift
Create Nsmanagedobject Subclass... Make a New Error in My Project
Showing Cells in Demands in Uicollectionview with Vertical Infinite Scroll
How to Import a Swift Function Declared in a Compiled .Swiftmodule into Another Swift File
Cannot Load Underlying Module for Xctest
Passing Data from Tableview to Viewcontroller in Swift
Why to Avoid Forced Unwrapping
Diagnosing Exc_Bad_Instruction in Swift Standard Library