How to make a multi-language application in C

Learn how to make a multilingual application in C programming language with gnu-gettext for i18n and Localazy.

Typically, when you start developing a new app, its interface is in a specific language, perhaps English, if you want to reach an international audience. By translating your app into other human languages, you can increase the number of people who can install, use, and recommend it.

A few weeks ago, I started learning the C programming language. It is rather challenging to switch the C’s way of thinking about software engineering. While learning how to work on strings, I wondered how to solve the topic of multilingual applications and localization.

Unless stated otherwise, all pictures in the article are by the author.

🎈 Translate GTK app using Gettext and Localazy 🔗

In this article, I will show you how to use Gettext functions to translate a terminal application and a GUI (made using GTK). I will show you how to test this solution using environment variables. And finally, I will show you how to manage multiple translations and even how to help yourself with machine translations using Localazy.

To understand the article, all you need is a basic knowledge of C and possibly some familiarity with the make tool.

Source code

Repository prepared for the article:

https://github.com/fischerbach/C-localization

Every section has its own branch. master branch contains final source codes.

⌨️ Terminal application 🔗

Let’s tackle a simple terminal application first.

The source code is littered with several strings containing messages. The first thought is to collect them into an array.

Now, they could be easily translated and stored in separate variables. Then we could use conditional compilation and pass the appropriate language to the build tool:

This solution has several drawbacks.

  1. Firstly, already at the compilation stage, you have to choose the language version. The resulting binary will only contain one language. In environments with limited resources, e.g., embedded this may make sense. But the end-user then has to download the version that is right for them.
  2. Secondly, this is easy to manage as long as there are only a few fixed strings. But if the software continues to be developed, the number could likely increase quickly. Also, some of the labels may stop being used. Then they occupy memory unnecessarily.
  3. Finally, thirdly, it reduces the readability of the code. What is more readable?

You can try to remedy this by using a hashtable, but this will be a bit of reinventing the wheel and will not solve the previously mentioned limits. Fortunately, there is a ready-made solution — the Gettext library and tools.

📑 Gettext 🔗

GNU gettext is a universal set of tools for producing multi-lingual messages. It provides a framework to support translated message strings with minimal effort. It supports many languages.

In a previous article I described how to use GNU gettext with Python: How to build a multi-language dashboard with Streamlit

The usage pattern is very simple.

First, we need to modify the source code and mark strings to be translated.

Note that we added a preprocessor macro to shorten the gettext function into _. This is a common way to mark localized strings. Next, we generate a file from the application source code containing the strings marked for translation.

xgettext -k_ -o locales/base.pot — language=C do_nothing.c

Normally xgettext seeks for gettext function surrounding texts, so we need to use -k_ option to determine a new name. The output is a template POT (Portable Object Template) file.

It contains every localized text. We use it to create files containing other language versions.

msginit --input=locales/base.pot --locale=pl 
--output=locales/pl/base.po

These are PO (Portable Object) files and contain translated texts.

Finally, to use translations in our application, these PO files are converted to MO (Machine Object) files.

msgfmt --output-file=locales/pl/LC_MESSAGES/base.mo 
locales/pl/base.po

This may sound complicated, but it basically boils down to using a few basic commands, which can then be built into the Makefile.

Note the preservation of the directory structure (especially LC_MESSAGES):

The source code for this section is in the step-1 branch.

As an exercise, fork repository, try to create translation to your native language (and maybe create pull request 😉).


Testing 🔗

To test our solution in the terminal, we need to set locale variables. The UNIX platforms use the environment variables:

  • LANG
  • LANGUAGE
  • LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME

to determine which locale is to be used.

When an application starts, it looks at the value of the LANG environment variable. You can temporarily change the locale in a terminal session by setting the LANG variable before the name of the output executable:

LANG=pl ./do_nothing

As you can see, our application respects the user’s locale settings now.

🖌️ GTK 2.0 🔗

The next application we will look at has a GUI implemented using the GTK+2.0 library. Note that this is a fairly archaic version of GTK. However, our application is simple enough that it will even compile using the newer 3.0 version. Furthermore, in terms of translation, the process will be similar regardless of the library and version used.

The application has similar “functionality” to the previous terminal app, but with some graphical interface.

Some lines may cause difficulties, e.g., HTML formatted label.

Using Gettext with GTK doesn’t differ much.

Glib provides a gi18n header file, which we include. Localized texts are marked exactly the same as in the previous example.

Result:

The fantastic thing is that GTK also automatically translates the labels of built-in dialog window labels!

The source code for this section is in the step-2 branch.


Possible problems 🔗

In the course of my work, I encountered a few problems that may be tricky.

Charset encoding

If your language uses an extended character set, remember to set UTF-8 (or another appropriate charset) in the PO files’ headers.

https://gist.github.com/fischerbach/e4cbe6b4531cb174f192a7df71b83488

Proper locale directories’ structure


It’s important to keep the directory structure understandable to Gettext.

Especially the LC_MESSAGES subdirectory.

Complicated workflow

Remembering all the commands that generate the translation files can be tedious, so in the step-3 branch you will find simple bash scripts and Makefile modifications to automate this process.

Need for regenerating PO files every time

Be careful when re-generating PO files, because you may lose already translated parts. Merging new labels and their translations can be challenging. Therefore, in the next paragraph, you will learn about a web application that makes it as easy as possible to create new language versions.


🚩 Localazy 🔗

Localazy is an awesome piece of software that makes the usually awful translation experience bearable and even almost pleasure. It supports many frameworks and localization file formats and provides CLI tools for build automation. My favorite features are machine suggestions for translations and automagic management of changes in translated files.

So let’s integrate our report with Localazy. First, create a Localazy account and install Localazy CLI. Then, create a new application.

Make sure to set the App Type to Private app. If your app does not contain sensitive data, you can safely leave it Public. Then, select POT files from available file formats.

You will see a template configuration file localazy.json. Copy it to the gui-app main folder. Remember to modify the locales folder path. Now you can upload POT files into Localazy:

localazy upload

Then, go to your app in Localazy and add some new languages. After a while, you will see a list of phrases to translate in each language of your application.

And the cherry on the top, a machine translation suggestions and previously used ones come with each phrase.

Once all the translations have been accepted or created, you can download them into your application and re-generate binary MO files:

localazy download
make locales -B

Result:

I’d say it’s quite acceptable.


✔️ Takeaways 🔗

Providing more language versions of your application not only increases the potential user base, but also contributes to accessibility and equality. At the same time, it can be very difficult to translate each label and keep track of changes as your application evolves.

Gettext and Localazy are flexible solutions to localization problems. Each addresses different sources of workload and they complement each other.

For the C language, the topic of string localization is very broad, so I recommend taking a look at the references below.

Thank you for reading. I hope you enjoyed reading as much as I enjoyed writing this for you.

This article was originally published by Rafał Rybnik on Level Up Coding by gitconnected.com.

📚 References 🔗

GNU Gettext:
https://www.gnu.org/software/gettext/


https://www.gnu.org/prep/standards/html_node/Internationalization.html

https://ptomato.github.io/advanced-gtk-techniques/html/gettext-project.html

https://barrgroup.com/embedded-systems/how-to/firmware-internationalization

https://docs.oracle.com/cd/E23824_01/html/E26033/glmha.html

https://www.labri.fr/perso/fleury/posts/programming/a-quick-gettext-tutorial.html

https://developer.gnome.org/glib/stable/glib-I18N.html

💖 Why developers love Localazy?

Whether you are a single developer or an agency, you can rely on Localazy when it comes to i18n & l10n!

See Testimonials