Creating localized strings using predefined format is a complicated task. There are multiple elements that must be taken into account during this process: order of arguments, directionality of text, the way variables that are substituted into the string that is being created etc. In order to make working with format string easier, Foundation framework implements two convenient methods:
init(format:arguments:) and less popular
In this post I would like to present functionality specific to
The documentation of
init(format:arguments:) method is pretty self-explanatory:
Returns a string created by using a given format string as a template into which the remaining argument values are substituted.
Formatting String Objects and String Format Specifiers guides describe a lot of details regarding the way you can use this method. Overall, strings formatting is flexible and powerful but still provides nice syntax for basic scenarios such as:
print(String.init("Some funny number %d", 1)) //Some funny number 1
What about the description of
This method is equivalent to using
init(format:locale:args:)and passing the current locale as the locale argument.
As an example of formatting, this method replaces the decimal according to the locale in %f and %d substitutions, and calls
Unfortunately, the documentation of
init(format:locale:args:) method doesn’t contain any further details about the way
localizedStringWithFormat(_:_:)method works. Having that said, let’s try to wrap out what documentation says about it:
It provides localized form of numbers for variables used during
%fsubstitutions. In fact, internally this method uses
NSNumberFormatter. That fact is mentioned during Session 227: What’s New in Internotialization from WWDC 2015. The transcript of this video can be found here. 1
description()method when printing objects. As a matter of fact, the same is true for
init(format:arguments:)but this isn’t a documented behavior so I would not advise to rely on it.
Going Beyond Documentation of
I’ve always been wondering whether there are any cases which are not mentioned in the documentation and in which use of
localizedStringWithFormat(_:_:) is more beneficial than
init(format:arguments:) method. What’s New in International User Interfaces session from this year’s WWDC presented one such case.
Consider format like this:
let format = "%@ is a programmer."
Even such a simple scenario becomes tricky when
%@ is substituted for string provided by the user (their name in this case). How is that possible?
Let’s start with a short introduction to the way Unicode characters can be categorized:
All characters are divided into three groups: strong LTR (Left-To-Right), strong RTL (Right-To-Left) and neutral/weak.
It turns out that UIKit determines the direction of the text by looking at the first strong character in the string that is being rendered. In our case we don’t know the character at compile time as it is determined during execution of an application (maybe it is fetched from the server or taken as an input from the user). In other words, the direction of the first strong character in the string provided at runtime determines the direction of the whole string. What are the possible implications of that fact?
Let’s try it:
let format = "%@ is a programmer." print(NSString.init(format: format, "John")) // prints: John is a programmer. print(NSString.init(format: format, "اية")) // prints: .is a programmer اية
As expected, everything works correctly when we use latin characters. The same isn’t true when our
%@ format specifier is substituted with a name starting with a strong character that is intended to be used with right-to-left writing direction. In that case, the whole final string is rendered using this direction which leads to unreadable string.
How can we work around it? To deal with scenarios like these, Unicode specification introduced support for isolates. These are some special characters that allow programmers to isolate parts of the string in the way described below:
- Directionality of the text surrounded by these characters doesn’t influence the directionality of the rest of the string
- Directionality of the text surrounded by these characters is determined by the first strong character within that text
This is exactly what we need. Using isolates we can guarantee that the user name provided at runtime is rendered using its directionality without affecting the directionality of the surrounding text (which should be always left-to-right). That’s great!
But what isolates have in common with
localizedStringWithFormat(_:_:) method? It turns out that this method automatically surrounds substituted format specifiers, for example
%@, with isolates. That way users of this method don’t have to do anything to ensure correct directionality of strings they create using provided format.👌
Knowing that let’s change implementation of our test scenario so that it uses
let format = "%@ is a programmer." print(NSString.localizedStringWithFormat(format: format, "John")) // prints: John is a programmer. print(NSString.localizedStringWithFormat(format: format, "اية")) // prints: اية is a programmer.
It looks good now. 🙂
Notice that the described problem with text directionality exists even if your app isn’t localized at all. As long as your application allows users for text input, they can always use their keyboard of choice to enter characters with different directionalities.
Looking for More
Positively surprised by my discovery in
localizedStringWithFormat(_:_:) in this year’s session I decided to watch more sessions relating to localization and internationalization from WWDC conferences hoping to find more details regarding the mentioned method. I went back to videos dating as far as 2014 year but I haven’t found any new scenarios that would be covered by
These’re the sessions I’ve watched:
- Session 201 (WWDC 2016): Internationalization Best Practices
- Session 232 (WWDC 2016): What’s New in International User Interfaces
- Session 227 (WWDC 2015): What’s New in Internationalization
- Session 222 (WWDC 2015): New UIKit Support for International User Interfaces
- Session 201 (WWDC 2014): Advanced Topics in Internationalization
Please let me know if you happen to know anything more about discussed topic.
Global availability of apps raises new challenges to its authors. The lack of localization doesn’t protect authors of applications from locale related problems. In order to properly handle localization nuances developers should use methods and classes intended to be used with user-facing strings.
When working with strings that are supposed to be localized you should always choose
init(format:arguments:). You should do that even if the differences between documentation of these two methods don’t give you any particular reason to use the former one. Treat
localizedStringWithFormat(_:_:) as your default option and consider
init(format:arguments:) only if you know that you need it 2.