iOS App Localization with PhraseApp

Lots of locales, lots of translators, and lots of developers – this can lead to a bunch of .strings files flying around everywhere. There's a better, more centralized way to localize iOS apps. Let's see how PhraseApp can help.

At scale, iOS app localization can be quite cumbersome. Translators don’t want to deal with .strings files, and we, developers, don’t want to deal with uploads and downloads that we have to merge into our projects manually. This is where PhraseApp comes in. A professional localization tool for developers and translators, PhraseApp can do the heavy lifting of our localization work, giving us more time to focus on the business logic of our app. Let’s take a look at how we can localize our iOS apps with PhraseApp.

Note » I’m assuming you’re familiar with the basics of iOS internationalization and localization here. If not, check out one of our guides:

Our App

We have a simple demo app that we’ll be working on in this article.


Short Circuit, a curated list of electronic music

Our award-winning, two-screen app, Short Circuit, lists electronic music tracks along with their artists and release dates. To display the tracklist, we’re using a UITableView and connecting it to a hard-coded array of tracks via a UIViewController. Tapping on a track row opens a details screen which shows labeled track info. The app is really quite simple, and we’ll use it as a testbed for our localization work with PhraseApp here.

Note » If you want to work along, you can grab the starter project from Github. The completed project is linked at the bottom of the article.

Setup

To get things started, we’ll add a new project to PhraseApp, install the PhraseApp CLI on our development machine, and add our first locales.

Note » Before you add a project, you’ll need to create a PhraseApp account if you don’t have one. A trial account is free for 14 days, and it only takes a couple of minutes to set up.

Adding the Project in PhraseApp

When we login to our PhraseApp account, we arrive at our Projects tab. Here we can add a new project using the Add Project button near the top-right of the page.


Click Add Project to get started

This opens up the Add Project dialog, which allows us to quickly configure our new project’s initial settings.


We can give our project any name we want

We need to give our project a name, and we can pick any name we want. We’ll use .strings files to do the majority of our localization, so we pick that option as our project’s Main Format. And, of course, we pick iOS as the project’s Main Technology. That’s enough to get us started. We can click the Save button to create the project.

Note » We can change any of these project settings at any time from the Project Settings dialog.

Installing the PhraseApp CLI

To be able to sync our translations with PhraseApp, we’ll need the PhraseApp CLI installed. If we have Homebrew on our Mac, this is pretty straight forward. We just need to run two commands from the command line:

tapping gives access to the PhraseApp Homebrew repository. Once that’s done, we can run:

Et voilà. That should be it. To verify the installation, we can run $ phraseapp from the command line. If all went well, we should see a list of all available commands the PhraseAPP CLI provides.

Note » If you don’t want to use Homebrew, check out the complete CLI installation guide for other installation options.

Adding Our Locales

With our project created, we can go to the Locales tab to add our app’s supported locales. Of course, we can add or delete locales in our project at any time.


Let’s add those locales

In the Locales tab, we should see an Add the first locale button. Let’s click that puppy. When we do, the Add Locale dialog will open.


Couldn’t be simpler

We just give our locale a name and select the actual localization we’re targeting. In my case, I’ll add English. We can then save the locale and repeat the process for any other locales we want to initially support in our app.

Initializing our Client Project

Now let’s take a look at our client side, i.e. our development machine. To initialize our local PhraseApp environment for our project, we need to have an API access token. We also need to generate a local .phraseapp.yml file.

Getting an API Access Token

To create an API access token, we can log in to our PhraseApp account and go to our profile page. The link to our profile page will be under our username in the navigation bar.

 


“Fartknocker2022” is not a reasonable profile name

Once we’re on our profile page, we can go to the Access Tokens tab through the link in the top tab bar.


Find the Access Tokens tab bar item

From the Access Tokens tab, we can click the Generate Token button. This opens the Generate Token dialog.


Just a Note and you’re good to go

We’ll want both read and write access for our project since we will both download and upload translation files. So we can leave that option as it is. We can enter any identifying note for ourselves in the Note field. This will serve as a reminder of the reason we created this token. Clicking Save will generate the token, and we should see the new token along with a button to copy it to our clipboard.

Note » Be sure to copy the token and keep it in a safe place because it will only be revealed to you one time in the PhraseApp console.


Get your token while it’s hot

Creating our Client Config

Now that we have our access token, we can initialize our project on our development machine. From the root directory of our project, we can run the following command from the command line.

Running this command will get the PhraseApp parrot photobombing our terminal. We’ll be asked for our API access token, and we can paste in the one we generated above.


It’s an exuberant little parrot

We’ll then be presented with a list of our PhraseApp projects, and we can select one to link with our local project.

After that, we’ll be asked to select our default localization format. We set the format when we created our project in the PhraseApp web console, so we can simply press Enter to use that same format.

We’ll then be prompted to provide the file path to our Localizable.strings file. We haven’t created that file yet, so we can press Enter to stick with the default path template for now. We’ll edit this value a bit later.

Our .phraseapp.yml file will now be generated. Finally, we’ll be asked if we want to perform an initial upload of our translation files. We can press n to skip this step for now.

Note » It may be a good idea to commit our .phraseapp.yml to source control. This will make it easier for other developers on the project to sync translations.

Adding our First Localization in XCode

With PhraseApp’s initialization complete, it’s time to direct our attention to our iOS app. To make it localizable, we need to add at least one language in XCode. We do this by selecting our project in the navigator, selecting the project itself (not a specific target) in the targets list, and then clicking the ➕ button under Localizations. I’ll select Arabic here. You can add any locale you want.


Click ➕ for 🌍

A dialog will open asking us to choose the files and reference language for our new locale’s Storyboard translations. We can leave Base selected as the reference language. Let’s select Localizable Strings for our file types. This will match the format we’ve set our project to in PhraseApp.

Adding Source Translation Files

By default, XCode will use the strings in our Main.storyboard and LaunchScreen.storyboard as its source translations. In my case, these are in English. It’s good to have these strings in their own Localizable.strings file, so that we can use them as source translations in PhraseApp. Let’s select our Main.storyboard file and check the English checkbox in the File inspector under Localizations.


We’ll want our source translations in PhraseApp

Updating our Client Config

Now that we have our first translation files, we can update our .phraseapp.yml to add their locations, which will allow us to sync them with PhraseApp via the CLI. Once we’ve added our source and target paths, our .phraseapp.yml will look a little something like the following.

This will tell the PhraseApp Client which files to upload when we push (our sources), and which ones to download when we pull (our targets). Pushing is basically uploading, and pulling is downloading.

However, in order to be able to push and pull our translations to and from PhraseApp, we’ll need to add locale IDs to our .phraseapp.yml file. We can get these from the PhraseApp web console. When we navigate to the Locales tab on the console, we’ll find a gear icon next to each of our locales.


Have no fear, click the gear

Clicking that icon will open the Edit Locale dialog. From there, we can navigate to the API tab to get the locale’s ID.


Thar she be, the ID

With our locale IDs in hand, we can update our .phraseapp.yml file.

Alright, now we can do our first push. This will upload our source file to PhraseApp and allow us to create the target translations. Let’s run the following command from the same directory housing our .phraseapp.yml on the command line:

If all went well, we should get an output like the following:

And if we visit our PhraseApp web console, we should see our Locales list now reflects that we have pending translations.


We got some translating to do

Note » We can upload our files to PhraseApp using the web console as well, and there are some edge cases to be mindful of when uploading. Check out the guide, Uploading localization files, for a more in-depth look at uploading.

OK, awesome! We now have our project synced up with PhraseApp. Let’s get to translating.

iOS App Localization with PhraseApp in Action

So far we’ve uploaded our Main.storyboard labels. They’re now ready for our translators to tackle in the PhraseApp web console. Let’s open the console and click through to {Project Name} > Locales > ar to see our pending Arabic (target) translations.

Our translation keys are listed in the left sidebar of the page, each one with its English (source) translation. We can select any of these translations and use the Editor to enter and save its Arabic translation.


So much better than fiddling with .strings files

Pruning Unwanted String Keys

First, however, let’s prune some of these keys. Our app has a few labels that are populated dynamically by our view controllers, so we don’t want to translate those label strings directly. We can add these keys to our blacklist so that PhraseApp will stop managing them. We first navigate to our main project page. Then, in the tab bar, we select More > Blacklisted Keys.


Let’s prune this beastly bush

Once we’re on the Blacklisted Keys page, we can just click the Add blacklisted key button near the top-right of the screen to open a dialog where we enter our offending key.


Remember to add the key, not the value

We just add the key, click Save, and repeat for each key we don’t want our translators to see.

Note » Read more about blacklisting, deleting, and excluding keys in our guide, Working with Keys.

Translating: The Translation Editor

Now we can select each of the remaining translations, add their Arabic translation in the Translation Editor, and click the Save button.


The convenience of the PhraseApp Translation Editor

It’s really that simple. And the cool thing is that the developer working on the app neither has to wait for the translations to be 100% complete, nor for a file share from translators. He or she can download the latest translations at any time using the PhraseApp Client.

Pulling (Downloading) Translations

We can pull current translations at any time by running the following command from the command line.

In our current state, after we run the pull command, we should see a message like:

We should now have all the current Arabic translations in our project. We can verify this by opening our Views/ar.lproj/Main.strings.

PhraseApp has downloaded all our Arabic translations. This is such a time-saver, especially at scale. Imagine teams of developers and translators working on an app with just four or five supported locales. Each locale could have its own translator or translation team, and we would have to manage all of those .strings files going back and forth. PhraseApp is already making our localization workflow so much smoother.

If we run our app in Arabic, we can see that all of our Storyboard UI labels have indeed been translated.


Al salam alykum

Translating Code: Adding a Localizable.strings file

So far we’ve been focused on translating Main.storyboard. This is all good and well, but what about the code? We often have strings embedded in our code that we need to localize.

For example, say we want to add a copyright label at the bottom of our app’s track details screen.


You gotta fight, for your right

First, we’ll add a Localizable.strings file to our XCode project, and make sure it’s localized by selecting the file and clicking Localize in the File inspector. After we do that, we can check the boxes for both Arabic and English under Localizations for the file.

Now we can open the English Localizable.strings and add the following.

This is a formatted string that contains dynamic strings to be interpolated in our Swift code. We’ll want to swap the %@ %@ with our current year and the artist’s name, since these values change dynamically in our app.

Assuming we’ve created the copyright label in our storyboard and linked it to an outlet in our controller, we can then update our controller to look like this:

Now let’s add our Localizable.strings file to our .phraseapp.yml so that we can work with it in PhraseApp.

Just like before, we add the English Localizable.strings file as a source and its Arabic counterpart as a target.

We can then run $ phraseapp push to upload the new translation to PhraseApp. Once that’s done, we can open our PhraseApp web console and navigate to our project’s Arabic translations. When we locate our key, we can use the Translation Editor to translate our formatted string.


That lovely editor again

Notice how PhraseApp is aware of our %@ placeholders. In fact, we can click on these placeholders (or any word for that matter) in the original English source to have it automatically placed in the editor by our cursor. This is very handy for translators, since they don’t have to remember the tricky character sequences that make up our placeholders.

Once we’ve saved our translation, we can $ phraseapp pull on our local machine to get it. After the pull is complete, we can run our app in Arabic to see our update.

Note » When we have multiple targets, PhraseApp will aggregate all translations into one big list and put that list into all of our targets. This effectively means that our targets will be duplicates of each other. As long as we’re not duplicating keys across our .strings files, this shouldn’t really be a problem.


That’s our little app totally translated

Note » You can grab the completed, working XCode project from Github.

Note » For even more time-savings, learn how to automate storyboard localization with PhraseApp by reading Automate iOS Storyboard Localization.

C’est fini

That will do it for our foray into iOS app localization with PhraseApp. You’re hopefully starting to see the time-savings the PhraseApp l10n workflow gives you. And we’ve only covered only a few of the features PhraseApp helps us with. The service was made with developers in mind, so it has a fully-featured CLI and an API that we can wire up to and create custom workflows. PhraseApp also has professional features like Over-the-Air translations, translation versioning, and more. Sign up for a free 14-day trial. And happy coding, amigos and amigas 🙂

iOS App Localization with PhraseApp
5 (100%) 11 votes
Author
Mohammad @Mohammad Ashour
Comments