top of page

i18n: How to Write Good App Translations

By Dawid Wojda, Senior Frontend Engineer

The tips below are more generic rules for writing good translations rather than technical instructions to an i18n (internationalization) framework. I believe you can find the best suited i18n solution for your project by yourself, since each of them has a slightly different API, and must align well with your tech stack. Nevertheless, when you combine it with good practices, I'm sure it will allow you to provide the best experience for your users and developers.

Don't Use Codes

I've seen people creating special codes like `page.home.title` or `abc_test_something` for translations, but sadly this solution has many cons. Codes are static, when in modern applications we need more flexibility (more about it in the next section). Also, in code, they're hard to maintain and introduce unnecessary complexity. If you want to change or add translation, you would first need to know or create code, assign text to the code, and then use code in the translation function.

Another important thing is that you need to wait to load the default translation at the application bootstrap (since you have only codes), which means there is a possibility that the user will see the app with your special codes instead of texts, especially if the user has a slow internet connection.

So, what can we do about it? Just use the default language in translations.

It can be any language you or your user base is familiar with, and of course, the most popular choice is English.

Instead of using codes, you would use raw English directly in the translation function, like this:

t('page.home.title'); // WRONG
t('My super page title'); // OK

It gives you more context right in the place where you work, so while developing you can easily change text without leaving the current file. Also, you don't have to wait on an app bootstrap to load a default translation file, because default translations are right there in the app code. Even if something wrong happened, and the user set a different language which doesn't load, he will see the fallback language, and not the messy codes which could look like your app scored a solid error.

Don't manipulate translations beside their own template system

t(`Product ${productName} created`); // Really BAD
`${t('Product')} ${productName} ${t('created')}`; // Slightly better, but still WRONG

The first example is the worst, because it will generate different text for each product name, and each of them will need to be translated separately. The second one is slightly better, but some languages may have different sentence structures.

For example, the Polish translation for sentence "Product ABC created" will look like "Utworzono produkt ABC". See what happened here? We assumed that every language has the same grammar and sentence sequence. What's worse, we even hard-coded it in the application's code!

Now, when the client asks you to add Polish translations, you need to make changes in the application's code, instead of simply putting another file in the translations' directory.

In the second example, the words are too generic. Those words can be used anywhere and in different languages, and based on their usage context in a sentence, they can be written differently. By the way, we lack the context in this example as we have no idea (on a translation file level) that those are used with a product name between them.

How do you prevent it?

The answer is very simple - just use what the creators of your translation solution prepared for you 😉.

Every translation system has a built-in interpolation (string template) mechanism, like JavaScript's i18next library.

Checkout the below example:

t('Product {productName} created', { productName: 'ABC' });

Now, the person who will work on translations knows in which context all words are used, and what variables are available to use.

Also, in the translation file, we can now change the sentence structure without messing with the code.

If we put together all of the rules above, it quickly comes out that for translations we can use just a simple JSON object containing English texts as keys, and translations as values. Just like the below example of Polish translation file:

	"Product {productName} created": "Utworzono produkt {productName}"

Using the Translation Management System

When the application grows, and the user base grows, we often face the need to outsource some translating work to real, human translators who will make sure that translations are well done and readable by end users. It may be hard for non-tech people to dig into raw JSON or PO files. There is also the possibility that some translations are "dangling" only as 't' function calls, without being present in the translation file, so our translators will not be able to find them by themselves.

Thankfully, the TMS system comes with help. Many modern TMS solutions can be run as a side system, somewhere in the dev or staging environment, and provide a nice UI to interact with translations.

What else can TMS give you?

  • Some of them can look automatically for places with dangling translations

  • TMS can change your app into a WYSIWYG editor to give translators better context when they work on texts and navigate through the application

  • Commit changes into the application's git repo, directly to the translation files

  • Integrate with modern AI translators and propose translations to accept without the need to write them from scratch

There are many TMS solutions, both paid and free. Some examples of open-source solutions I can mention are: Traduora or Tolgee.

Both available on GitHub.


We know why we don't use codes as translations IDs, but instead write pure English texts directly in "t" function calls. We learned about grammar differences between languages and how to handle them. Furthermore, we also know that well-designed translations can be put into the TMS system, which will give us a nice UI to work on them.

With this knowledge, I'm sure you will be able to build solid translations for your Application!


bottom of page