# Validate your iOS and Android translations with Locheck

> Does your iOS or Android app ship in multiple languages? Asana wrote & open-sourced Locheck to catch bugs for you. See how it works.

Source: https://asana.com/inside-asana/locheck-open-source

## Validate your iOS and Android translations with Locheck

When mobile apps need to ship in multiple languages, the developer often hires a contractor or external service to translate all the strings. The people doing these translations are usually unfamiliar with the technical details of localization, which makes it easy for them to introduce bugs when a string contains a variable. Even if the app only ships in one language, it’s still possible to write bugs by making subtle mistakes. In order to ship a bug-free app, the developer needs some way of ensuring the format strings in every translation are correct even if they don’t speak the language.

At Asana, where we ship in [13 languages](https://blog.asana.com/2021/06/asana-new-languages-korean-swedish-italian/#:~:text=Asana%20is%20now%20available%20in%20Korean%2C%20Swedish%2C%20and%20Italian,-By%20Amulya%20Uppala&amp;text=Read%20this%20article%20in%20French,Italian%2C%20Polish%2C%20or%20Japanese.), we developed [Locheck](https://github.com/Asana/locheck) to automatically verify that every string in our .strings, .stringsdict, and strings.xml files use consistent arguments and types, and report errors to our CI pipelines. In this post, I’ll cover some challenges with localization and show how Locheck makes sure we don’t ship with bugs.

## **How Locheck catches bugs**

Locheck compares the language you develop into the languages you translate to, and makes sure all their types match. It can catch things like when:
- A string appears in one localization but not another
- An argument is used in a localization but does not appear in the base localization
- An argument has different types in different localizations or different plural variants
- The translation has misspelled a named variable

For _.strings_ and _.strings.xml_ files, this is relatively simple given a fancy enough regular expression and knowledge of the syntax. Locheck parses a string like _"added %d tasks to %3$s"_into a list of Swift structs:

(We are very fortunate that iOS and Android use a close enough format string syntax.)

Locheck then generates a list for each string, and then compares the same string keys across translations, logging a warning or error if they differ. Some issues might cause crashes, for example if your German translation uses _%s_ instead of _%d._

## **The challenges of**_**.stringsdict**_

_.stringsdict_ files are much more complicated. Here’s a shorthand version of the plural rule I showed earlier:

The _%#@tasks@_substring means “recurse into _tasks."_You can even nest these rules:

(There is a simpler way to define this rule, but sometimes nesting is really necessary.)

These rules form a [grammar](https://en.wikipedia.org/wiki/Formal_grammar), defining a set of possible strings. The rules are traversed before the format string is applied. That means in order to really be sure the arguments are correct, every permutation needs to be checked. Here are all the permutations of the _.stringsdict_entry example above:

Given the permutations above, look at how the arguments differ in each permutation. Without explicit positions, the second permutation might mistakenly use the value for tasks in front of milestones, and try to use a number for the string argument at the end. If we add explicit positions, these problems disappear:

Locheck knows how to expand these rules and can log intelligent errors to help you find problems.

## **Deep-dive into a common problem**

Imagine we’re making a task list app with an activity feed.

On Android, there is built-in support for a _plurals_element in _strings.xml_for this:

On iOS, we would add an entry to our Localizable.stringsdict file:

Then in our code, we’d access the string:

And we’d get back whichever variant matched the value of _numTasks_we put in.

Or would we? No, we would not!

If we pass a value of _1_for _numTasks,_the app will actually crash, because after the system substitutes our string value, we’re really doing this:

This kind of mistake is extremely easy to make if you’re not used to thinking about these details, for example if your job is to translate text between different languages rather than write code all day, or if you’re translating to a language like Japanese where the order is often different. Even if you have a developer review every string, it can be very tricky to spot these issues. And as code and teams scale together, tricky-to-spot bugs become guaranteed-to-ship-to-production bugs.

## **How to fix it**

The right thing to do is to add explicit positions to non-consecutive arguments. Instead of writing %s for our third argument, we should write _%3$s_, which makes it always use the third argument.

Best practice would be to use explicit positions 100% of the time, but it can be prohibitively time-consuming to retroactively add explicit positions if your source of truth is an online service like [Transifex](https://transifex.com/), which is true for us at Asana. And you might still get errors if the people doing the translations aren’t perfect at understanding format strings.

Locheck will catch this type of problem automatically, so it’s safe to use implicit positions. There might still be translation errors where two strings are incorrectly swapped and their format specifiers still match, but at least the app won’t crash.

## **How to use Locheck**

You can install Locheck using [Mint](https://github.com/yonaskolb/Mint) or Make:

Locheck emits Xcode-style errors to stderr, as well as a human-readable summary to stdout after all files are examined. It works well as an Xcode Run Script build phase, continuous integration step, or precommit script. Here’s some example output from our demo files:

## **Help us out**

While we’ve run Locheck on our own code and a few open source apps, it’s still early. If you do decide to [try it out](https://github.com/Asana/locheck), please leave feedback as a GitHub issue. Enjoy your new localization-bug-free life!

- [Why Asana is switching to TypeScript](/inside-asana/asana-switching-typescript)

Role spotlights

#### Tech Lead

Single-Page Applications are a powerful way to build rich applications in the browser but as the application grows, so does the code complexity. Asana has a code base with hundre ...

- [What is an Area Tech Lead at Asana?](/inside-asana/what-is-an-area-tech-lead-at-asana)

Role spotlights

Since joining #teamasana 4 months ago, it’s become clear to me that successful areas (groups of engineering teams with a shared goal) tended to have many things in common, includi ...

- [Security and Compliance: Better Together](/inside-asana/security-and-compliance-better-together)

Role spotlights

#### Head of Security

Many security practitioners have been frustrated by bad compliance audits, where an auditor wants something that is impossible, or nonsensical, or simply not worth implementing. O ...

- [What our interns built this summer: Meet our 2021 intern class](/inside-asana/what-our-interns-built-this-summer-meet-our-2021-intern-class)

Role spotlights

Every summer, we host a group of interns on our Design, Data, and Engineering teams, who work alongside full-time Asanas to do everything from contributing to highly visible featu ...

- [Validate your iOS and Android translations with Locheck](/inside-asana/locheck-open-source)

Role spotlights

When mobile apps need to ship in multiple languages, the developer often hires a contractor or external service to translate all the strings. The people doing these translations a ...

- [Role spotlights](/inside-asana/role-spotlights)
