Discovering functionality of localizedStringWithFormat(_:_:) method

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 localizedStringWithFormat(_:_:).

In this post I would like to present functionality specific to localizedStringWithFormat(_:_:) method.

Reading Documentation

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 localizedStringWithFormat(_:_:) method?

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 description(withLocale:) instead of description() where necessary.

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:

  1. It provides localized form of numbers for variables used during %d and %f substitutions. 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

  2. It uses description(withLocale:) instead of 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 localizedStringWithFormat(_:_:)

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:

Categories of Unicode Characters as presented during "Session 232: What's New in International User Interfaces" from WWDC 2016

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:

  1. Directionality of the text surrounded by these characters doesn’t influence the directionality of the rest of the string
  2. 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 localizedStringWithFormat(_:_:) method:

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 localizedStringWithFormat(_:_:).

These’re the sessions I’ve watched:

  1. Session 201 (WWDC 2016): Internationalization Best Practices
  2. Session 232 (WWDC 2016): What’s New in International User Interfaces
  3. Session 227 (WWDC 2015): What’s New in Internationalization
  4. Session 222 (WWDC 2015): New UIKit Support for International User Interfaces
  5. Session 201 (WWDC 2014): Advanced Topics in Internationalization

Please let me know if you happen to know anything more about discussed topic.

Conclusion

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 localizedStringWithFormat(_:_:) over 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.


  1. Look for ‘Now, under the hood, ‘localized string with format’ is using NS number formatter.’ string to jump straight to the moment at which they talk about it.
  2. Although it is really rare case when working with strings that are supposed to be localized.