Happy new year 🥳🎉🎊

While working on my app (Charabia), I was faced with a specific problem: localizing pluralized strings. From the get-go, I knew this was something I'd need to handle, but as I wanted to get something out the door really quickly, I managed to write a non-scalable code. I show in the navigation header the kid metadata (their name, age, charabias, and favorites number). The original code looked like this:

extension Kid {
  public var metadata: String {
    var string = ""
    if self.charabiasCount == 0 {
      string += "No charabia"
    } else {
      string += self.charabiasCount == 1 ? "1 charabia" : "\(self.charabiasCount) charabias"
    }
    string += " - "
    if self.memoriesCount == 0 {
      string += "No memory"
    } else {
      string += self.memoriesCount == 1 ? "1 memory" : "\(self.memoriesCount) memories"
    }
    return string
  }
}

We can all agree that this won't scale. Not only I'll have to do a lot of ceremonies if I decide to add other metadata, but it's done only to support English; what about the other languages? How can I handle the pluralization of the words like "charabia" and "memory"? Let's see what Apple gives us to achieve this. Adding Languages

Localization 101

Localizing your app is, fortunately, quite simple. Go to your project info, and in the "Localizations" section, add the supported languages for your app. Next, you need to create a .strings file (I like to make many of them, one for each of my packages). Once created, select the file and click the "Localize…" button in the File Inspector. It'll ask you if you want to localize the file, click the "Localize" button and check all the available languages in the "Localization" section of the File Inspector. Adding Languages Localize Button Now you're ready to add some keys to the files and write the proper meaning regarding each language. Here is an excerpt of what a localization file looks like:

# authentication.strings (English)

// Onboarding View
"onboarding.save_gibberish" = "Save the gibberish";
"onboarding.save_gibberish.description" = "Never miss a single gibberish your kid is saying while learning to speak.";
"onboarding.share_the_charabia" = "Share the charabia";
"onboarding.share_the_charabia.description" = "Customize the charabia the way you want and share it to your friends and family.";
"onboarding.save_moments" = "Save their best moments";
"onboarding.save_moments.description" = "Save your kid special moments wheter they know how to speak properly or not.";
"onboarding.get_started" = "Get started";
# authentication.strings (French)

// Onboarding View
"onboarding.save_gibberish" = "Sauvegardez les charabia";
"onboarding.save_gibberish.description" = "Ne ratez plus jamais les charabias que votre enfant dit quand il/elle apprend à parler.";
"onboarding.share_the_charabia" = "Partagez les charabias";
"onboarding.share_the_charabia.description" = "Customisez le charabia comme vous le voulez et partagez le avec la famille ou les amis.";
"onboarding.save_moments" = "Gardez leurs meilleurs moments";
"onboarding.save_moments.description" = "Gardez les moments spéciaux de vos enfants, qu'ils savent parler ou non";
"onboarding.get_started" = "Commencer";

Now, rather than hardcoding the text directly in the code, you need to reference the key instead so that it can show the correct value depending on the phone's primary language. Fortunately, this is quite straightforward in SwiftUI. The Text view accepts by default a key and a tableName. If I want to use the onboarding.save_gibberish, I'll use it this way:

Text("onboarding.save_gibberish", tableName: "authentication")

Here the tableName represents the name of the .strings file. Quite easy, right. But sometimes you will face situations where you can't use the Text view SwiftUI provides. For instance, what about localizing the navigationTitle text. It's super simple to do it as well. The code will look like this:

VStack { 
  Text("onboarding.save_gibberish", tableName: "authentication")
}.navigationTitle(NSLocalizedString("view-title", tableName: "charabia", comment: "New Charabia"))

We use the NSLocalizedString function, which accepts the same arguments as the SwiftUI Text struct. We give the key, the tableName, and a comment. So here it is, localizing some simple words and sentences is straightforward. Now let's see how we can apply these techniques to localize plural phrases.

Localizing Plural Phrases

Pluralization rules vary by language, so it's not as easy as just adding an "s" to the word. Returning to the original problem, how can we properly handle the localization of the kid metadata? The answer is to use a StringsDict file. Let's create one and call it Localizable. You can do the exact same steps as above, where you click on the "Localize…" button and select the available languages in the File Inspector. Inside the file, you'll find a structured format for identifying strings that have pluralization differences. The first thing is to choose a key you'll reference in the code later. As above, it can be anything but unique. I chose charabias-count, memories-count, and favorites-count. I will explain what I have currently on my StringsDict file and explain the meaning of every key. LocalKeys NSStringLocalizedFormatKey has a weird syntax with a %#@variable-name@. You can write anything you want there like: "This kid has %#@variable-name@ charabias added". The only important piece is the variable-name because you'll define next as a key. In this example, I name mine memories, which has its own key as well. If we open it, we'll see some rules there like the NSStringFormatSpecTypeKey, which is set to NSStringPluralRuleType, and that's what we want since we're doing pluralization. We also have NSStringFormatValueTypeKey, which refers to our placeholder. Think of it like String.format("%d bananas", count) where the %d will be substituted by the count value, and by putting the letter d where saying that this is a placeholder that the value'll replace. The rest are rules for pluralization. For zero memory, I say "No memory", which is more English-friendly than saying "0 memory", for one: "1 memory" and for many, I said "%d memories". Note that we can have other keys like two, few, many. I don't use them because they don't make sense for my special use case. After all, I'm using English and French, but for Russian, though, it will make total sense. Remember to do the same for every supported language. We can now reference the key in the code like this:

extension Kid {
  public var memoriesString: String {
    String.localizedStringWithFormat(NSLocalizedString("memory-count", tableName: "plural", comment: "This kid has %d memories"), memoriesCount)
  }
}

The syntax is a little bit weird as we first use String.localizedStringWithFormat, which accepts the key as an argument, but the latter has to be a localized string 😵‍💫, that's why the NSLocalizedString function is called and used as an argument to the String.localizedStringWithFormat function and you can see the number will come from the memoriesCount variable that's going to determine how the string should look like: if we have none, it's going to be "No memory", one: "1 memory", two: "2 memories", etc. If you want to localize many plural phrases, I suggest that you open the file as "Source Code", that'll allow you to quickly duplicate an entry and change accordingly to your specific use case. I find this useful when you also have to provide the localization in other supported languages. Here is an excerpt of my .stringsdict file, which is just an XML file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>charabia-count</key>
	<dict>
		<key>NSStringLocalizedFormatKey</key>
		<string>%#@charabias@</string>
		<key>charabias</key>
		<dict>
			<key>NSStringFormatSpecTypeKey</key>
			<string>NSStringPluralRuleType</string>
			<key>NSStringFormatValueTypeKey</key>
			<string>d</string>
			<key>zero</key>
			<string>No charabia</string>
			<key>one</key>
			<string>1 charabia</string>
			<key>other</key>
			<string>%d charabias</string>
		</dict>
	</dict>
  <key>memory-count</key>
  <dict>
    <key>NSStringLocalizedFormatKey</key>
    <string>%#@memories@</string>
    <key>memories</key>
    <dict>
      <key>NSStringFormatSpecTypeKey</key>
      <string>NSStringPluralRuleType</string>
      <key>NSStringFormatValueTypeKey</key>
      <string>d</string>
      <key>zero</key>
      <string>No memory</string>
      <key>one</key>
      <string>1 memory</string>
      <key>other</key>
      <string>%d memories</string>
    </dict>
  </dict>
</dict>
</plist>

Conclusion

As you can see, localizing phrases is not that difficult, maybe a little bit cumbersome, but that's what Apple provides us for now. Here is a list of resources I suggest if you want to learn more about localization.