Software localization

Introduction to the gettext Tools for Internationalization

The GNU gettext package is a powerful set of tools that can ease i18n and l10n for your applications. Get your skills going with this tutorial.
Software localization blog category featured image | Phrase

When we talk about internationalization (i18n), we refer to the process by which an application, or a set of applications turned into a package, is made aware of and able to support multiple languages. When we talk about localization (l10n) we mean the operation by which, in an application already internationalized, we make it adapt itself to handle its input and output in a fashion that is correct for some native language and cultural habits.

Internationalization is usually taken care of by developers, and localization is mostly performed by linguists.

In this tutorial, we'll have a look at how to use all available gettext tools in practice with a small Python application. Our goal is to get a better understanding of how to best internationalize and localize it so both the developer and linguist can benefit from an efficient workflow.

🔗 Resource » Learn how to localize form validation in an example app and provide valuable error messages for your users in our detailed guide.

Overview of gettext

The GNU gettext package is a set of tools that handle i18n and l10n aspects in computer programs with the ultimate purpose of minimizing the impact of them and hardly noticeable as possible. With gettext we mainly work with PO and MO files and variations of them.

PO files are meant to be read and edited by humans and MO files are meant to be read by programs and are binary in nature. One other characteristic of MO files is that they are often different from system to system; non-portable in nature.

A single PO file is dedicated to a single target language. If a package supports many languages, there is one such PO file per language supported, and each package has its own set of PO files. Files ending with .pot  are kind of base translation files found in distributions, in PO file format.

Before we start with our tutorial, we need to make sure we have installed gettext on our system:

Fedora

$ dnf install gettext-devel intltool

Ubuntu

$ sudo apt-get install gettext

Mac

$ brew install gettext

$ brew link --force gettext

Windows

https://mlocati.github.io/articles/gettext-iconv-windows.html

Example Project

Before we delve into the tooling it's important to start with an example project that we would like to include i18n and l10n support using gettext. We are using a small Python application that resembles an Online Bank with operations such as: Creating a new Bank account, printing your current statements, depositing some money or transferring funds between existing accounts.

1. Create a main.py file and add the following code:

$ touch main.py

File: main.py

import uuid

class BankAccount(object):

    def __init__(self, initial_balance=0):

        self.balance = initial_balance

        self.id = str(uuid.uuid4())

        print("Bank account '{id}' was created with initial Balance: {balance}").format(id=self.id, balance=self.balance))

    def deposit(self, amount):

        self.balance += amount

        print("Deposited {amount} to current balance").format(amount=amount))

    def withdraw(self, amount):

        self.balance -= amount

        print("Withdrawned {amount} from current balance").format(amount=amount))

    def overdrawn(self):

        return self.balance < 0

    def print_balance(self):

        print("Balance for Account '{id}' is: {balance}").

              format(id=self.id, balance=self.balance))

class Bank(object):

    bank_accounts = []

    def create_account(self, initial_balance=0):

        new_account = BankAccount(initial_balance)

        self.bank_accounts.append(new_account)

        return new_account

    def list_accounts(self):

        return self.bank_accounts

    def transfer_balance(self, from_acccount_id, to_account_id, amount):

        from_account = self.get_account_by_id(from_acccount_id)

        to_account = self.get_account_by_id(to_account_id)

        if from_account is None or to_account is None:

            print("One of the Account numbers does not exist")

            return

        from_account.withdraw(amount)

        to_account.deposit(amount)

        print("Successfully transfered {amount} from Account '{from_acccount_id}' to Account: {to_account_id}").

              format(amount=amount, from_acccount_id=from_acccount_id, to_account_id=to_account_id))

    def get_account_by_id(self, id_param):

        accounts = [acc for acc in self.bank_accounts if acc.id == id_param]

        if len(accounts) == 1:

            return accounts[0]

        else:

            return None

if __name__ == '__main__':

    bank = Bank()

    first = bank.create_account(100)

    second = bank.create_account(150)

    bank.transfer_balance(first.id, second.id, 50)

    first.print_balance()

    second.print_balance()

2. Run the program to see the result in the console:

$ python main.py

Bank account '22fc68d4-ac7e-401b-ad24-11d86d09979b' was created with initial Balance: 100

Bank account '4a28287c-c07a-4574-a97e-bd749d21605e' was created with initial Balance: 150

Withdrawned 50 from current balance

Deposited 50 to current balance

Successfully transfered 50 from Account '22fc68d4-ac7e-401b-ad24-11d86d09979b' to Account: 4a28287c-c07a-4574-a97e-bd749d21605e

Balance for Account '22fc68d4-ac7e-401b-ad24-11d86d09979b' is: 50

Balance for Account '4a28287c-c07a-4574-a97e-bd749d21605e' is: 200

So far so good.

Let's say now that the upper management requests to add i18n capabilities so we can support multiple locales for the messages printed. They have assigned you to this task, with the need that you make it easy to use by translators and maintainers.

The first thing you need to do is to mark all strings used for gettext translations and make them a bit easier to read. Python and a few programming languages support gettext. We only need to import the relevant library and use the message format function:

3. Import the gettext library and use the relevant format function to all translatable strings:

import uuid

import gettext

_ = gettext.gettext

class BankAccount(object):

    def __init__(self, initial_balance=0):

        self.balance = initial_balance

        self.id = str(uuid.uuid4())

        print(_("Bank account '{id}' was created with initial Balance: {balance}").format(id=self.id, balance=self.balance))

    def deposit(self, amount):

        self.balance += amount

        print(_("Deposited {amount} to current balance").format(amount=amount))

    def withdraw(self, amount):

        self.balance -= amount

        print(_("Withdrawned {amount} from current balance").format(amount=amount))

    def overdrawn(self):

        return self.balance < 0

    def print_balance(self):

        print(_("Balance for Account '{id}' is: {balance}").

              format(id=self.id, balance=self.balance))

class Bank(object):

    bank_accounts = []

    def create_account(self, initial_balance=0):

        new_account = BankAccount(initial_balance)

        self.bank_accounts.append(new_account)

        return new_account

    def list_accounts(self):

        return self.bank_accounts

    def transfer_balance(self, from_acccount_id, to_account_id, amount):

        from_account = self.get_account_by_id(from_acccount_id)

        to_account = self.get_account_by_id(to_account_id)

        if from_account is None or to_account is None:

            print(_("One of the Account numbers does not exist"))

            return

        from_account.withdraw(amount)

        to_account.deposit(amount)

        print(_("Successfully transfered {amount} from Account '{from_acccount_id}' to Account: {to_account_id}").

              format(amount=amount, from_acccount_id=from_acccount_id, to_account_id=to_account_id))

    def get_account_by_id(self, id_param):

        accounts = [acc for acc in self.bank_accounts if acc.id == id_param]

        if len(accounts) == 1:

            return accounts[0]

        else:

            return None

if __name__ == '__main__':

    bank = Bank()

    first = bank.create_account(100)

    second = bank.create_account(150)

    bank.transfer_balance(first.id, second.id, 50)

    first.print_balance()

    second.print_balance()

If you run the same program again you will see that nothing really has changed. You are now able to use the gettext tools to translate the files.

Let's start with creating the  .pot  file

Extracting POT files with xgettext

The xgettext program finds and extract all marked translatable strings, and creates a PO template file out of all these. If you run it without any arguments it will create a file named domainname.po . This file should be used as a basis for all later locale-specific translations as it has all the original program strings. Initially, all strings are empty and they contain only the msgid's, that is the unique message keys.

We would like to rename this file using a .pot  extension (Template PO file) and place it in the locale folder:

$ mkdir locale

$ xgettext main.py -d messages -p locale

$ mv locale/messages.po locale/messages.pot

The xgettext tool can be called with the following format:

$ xgettext [option] [inputfile] …

and in that case, we use the -d flag to specify the language domain and the -p flag to specify the output folder.

Let's inspect the contents of this file:

File: locale/messages.pot

# SOME DESCRIPTIVE TITLE.

# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER

# This file is distributed under the same license as the PACKAGE package.

# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.

#

#, fuzzy

msgid ""

msgstr ""

"Project-Id-Version: PACKAGE VERSION\n"

"Report-Msgid-Bugs-To: \n"

"POT-Creation-Date: 2018-05-28 16:05+0100\n"

"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"

"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"

"Language-Team: LANGUAGE <LL@li.org>\n"

"Language: \n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=CHARSET\n"

"Content-Transfer-Encoding: 8bit\n"

#: main.py:15

#, python-brace-format

msgid "Bank account '{id}' was created with initial Balance: {balance}"

msgstr ""

#: main.py:19

#, python-brace-format

msgid "Deposited {amount} to current balance"

msgstr ""

#: main.py:23

#, python-brace-format

msgid "Withdrawned {amount} from current balance"

msgstr ""

#: main.py:29

#, python-brace-format

msgid "Balance for Account '{id}' is: {balance}"

msgstr ""

#: main.py:50

msgid "One of the Account numbers does not exist"

msgstr ""

#: main.py:56

#, python-brace-format

msgid ""

"Successfully transfered {amount} from Account '{from_acccount_id}' to "

"Account: {to_account_id}"

msgstr ""

We won't be touching this file for now as for new translations we need to make a copy of it and fill the initial metadata strings. However as you can see the program was able to add extra information about the specific placeholder format using the #, python-brace-format comment.

Let's see now how can a translator can use that .pot file to provide translations for a new language.

Creating PO files with msginit

A new translator comes in and wants to start a new translation for a specific locale. The first thing we need do is to copy that .pot file that we created earlier and change the metadata to show the specific locale info.

The easiest way to do that is with the help of msginit program. For example:

$ msginit -i locale/messages.pot --locale=el_GR -o locale/el/LC_MESSAGES/messages.po

The first time you invoke that command you will have to specify an email address for giving feedback about the translations.

In that case, we use the -i flag to reference the .pot file that we created earlier as base translation file, the --locale  to specify the target locale and the -o  flag to specify the output file.

Let's inspect the contents of this file:

File: locale/el/LC_MESSAGES/messages.po

# Greek translations for PACKAGE package.

# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER

# This file is distributed under the same license as the PACKAGE package.

# IT Spare <itspare@ccc.com>, 2018.

#

msgid ""

msgstr ""

"Project-Id-Version: PACKAGE VERSION\n"

"Report-Msgid-Bugs-To: \n"

"POT-Creation-Date: 2018-05-28 16:05+0100\n"

"PO-Revision-Date: 2018-05-28 16:21+0100\n"

"Last-Translator: IT Spare <itspare@ccc.com>\n"

"Language-Team: Greek\n"

"Language: el\n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=UTF-8\n"

"Content-Transfer-Encoding: 8bit\n"

"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: main.py:15

#, python-brace-format

msgid "Bank account '{id}' was created with initial Balance: {balance}"

msgstr ""

#: main.py:19

#, python-brace-format

msgid "Deposited {amount} to current balance"

msgstr ""

#: main.py:23

#, python-brace-format

msgid "Withdrawned {amount} from current balance"

msgstr ""

#: main.py:29

#, python-brace-format

msgid "Balance for Account '{id}' is: {balance}"

msgstr ""

#: main.py:50

msgid "One of the Account numbers does not exist"

msgstr ""

#: main.py:56

#, python-brace-format

msgid ""

"Successfully transfered {amount} from Account '{from_acccount_id}' to "

"Account: {to_account_id}"

msgstr ""

As you can see the program added a few locale-specific information about the target language and copied all the strings from the .pot  input file. You could give more info but in most cases, you are good to go. Just provide the relevant translations:

...

#: main.py:12

#, python-brace-format

msgid "Bank account '{id}' was created with initial Balance: {balance}"

msgstr "Τραπεζικός λογαριασμός με κωδικό '{id}' δημιουργήθηκε με αρχικό κεφάλαιο: {balance}"

#: main.py:16

#, python-brace-format

msgid "Deposited {amount} to current balance"

msgstr "Το ποσό των {amount} κατατέθηκε στο τρέχων κεφάλαιο"

#: main.py:20

#, python-brace-format

msgid "Withdrawned {amount} from current balance"

msgstr "Το ποσό των {amount} αποσύρθηκε από το τρέχων κεφάλαιο"

#: main.py:26

#, python-brace-format

msgid "Balance for Account '{id}' is: {balance}"

msgstr "Το τρέχων κεφάλαιο του τραπεζικόυ λογαριασμού με κωδικό '{id}' είναι: {balance}"

#: main.py:47

msgid "One of the Account numbers does not exist"

msgstr "Αυτός ο τραπεζικός λογαριασμός δέν υπάρχει"

#: main.py:53

#, python-brace-format

msgid ""

"Successfully transfered {amount} from Account '{from_acccount_id}' to "

"Account: {to_account_id}"

msgstr "Μεταφέρθηκε το ποσό των {amount} απο τον λογαριασμό με κωδικό '{from_acccount_id}' στον λογαριασμό με με κωδικό '{to_account_id}'"

Now add the following 2 lines in the main.py to activate the Greek translations:

File: main.py

import uuid

import gettext

el = gettext.translation('messages', localedir='locale', languages=['el:en'])

el.install()

_ = el.gettext

...

Now before we can actually run this program we need to generate the .mo  files from the .po  files as this is the only way that our little Python program can do to recognize our translations.

Turning PO files into MO files with msgfmt

As we mentioned earlier we need .mo  files to use our translations and there is a tool for that:

The msgfmt program generates a binary message catalog from a message catalog.

The calling format of this program is:

$ msgfmt [option] filename.po

Let's use it now to generate that file from the Greek translations:

$ msgfmt locale/el/LC_MESSAGES/messages.po -o locale/el/LC_MESSAGES/messages.mo

In the command above we used the -o flag to specify the output file.

Note: There is also a tool that performs the inverse operation. The msgunfmt  program attempts to convert a binary message catalog back to a .po  file.

Now we are ready to see the correct translations. Execute the program to see the results:

$ python main.py

Τραπεζικός λογαριασμός με κωδικό '8a26b9d5-b26d-4a33-bd94-5893225cd5d0' δημιουργήθηκε με αρχικό κεφάλαιο: 100

Τραπεζικός λογαριασμός με κωδικό 'f9120696-3fcb-4575-af47-489d14d4207b' δημιουργήθηκε με αρχικό κεφάλαιο: 150

Το ποσό των 50 αποσύρθηκε

Το ποσό των 50 κατατέθηκε

Μεταφέρθηκε το ποσό των 50 απο τον λογαριασμό με κωδικό '8a26b9d5-b26d-4a33-bd94-5893225cd5d0' στον λογαριασμό με με κωδικό 'f9120696-3fcb-4575-af47-489d14d4207b'

Το τρέχων κεφάλαιο του τραπεζικόυ λογαριασμού με κωδικό '8a26b9d5-b26d-4a33-bd94-5893225cd5d0' είναι: 50

Το τρέχων κεφάλαιο του τραπεζικόυ λογαριασμού με κωδικό 'f9120696-3fcb-4575-af47-489d14d4207b' είναι: 200

As the application now evolves we need to intervene from time to time as new untranslated entries are added or removed and strings become not relevant.

Let's see now how can a translator can use the msgmerge tool to handle those translation needs.

Updating PO files with msgmerge

The msgmerge tool is mainly used for existing .po  files and especially existing translations. If our application updates its base extracted messages from the .pot  file, we need to be able to update the relevant entries in the .po  files we have.

Let's simulate that now to see how this tool does that in practice.

1. Modify the main.py  and add a new message string then remove some of them and change one of them:

File: main.py

import uuid

import gettext

el = gettext.translation('messages', localedir='locale', languages=['el:en'])

el.install()

_ = el.gettext

class BankAccount(object):

    def __init__(self, initial_balance=0):

        self.balance = initial_balance

        self.id = str(uuid.uuid4())

        print(_("Bank account '{id}' was created and the initial balance is: {balance}").format(id=self.id, balance=self.balance))

    def deposit(self, amount):

        self.balance += amount

    def withdraw(self, amount):

        self.balance -= amount

    def overdrawn(self):

        return self.balance < 0

    def print_balance(self):

        print(_("Balance for Account '{id}' is: {balance}").

              format(id=self.id, balance=self.balance))

class Bank(object):

    bank_accounts = []

    def create_account(self, initial_balance=0):

        new_account = BankAccount(initial_balance)

        self.bank_accounts.append(new_account)

        return new_account

    def list_accounts(self):

        return self.bank_accounts

    def transfer_balance(self, from_acccount_id, to_account_id, amount):

        from_account = self.get_account_by_id(from_acccount_id)

        to_account = self.get_account_by_id(to_account_id)

        if from_account is None or to_account is None:

            print(_("One of the Account numbers does not exist"))

            return

        from_account.withdraw(amount)

        to_account.deposit(amount)

        print(_("Successfully transfered {amount} from Account '{from_acccount_id}' to Account: {to_account_id}").

              format(amount=amount, from_acccount_id=from_acccount_id, to_account_id=to_account_id))

    def get_account_by_id(self, id_param):

        accounts = [acc for acc in self.bank_accounts if acc.id == id_param]

        if len(accounts) == 1:

            return accounts[0]

        else:

            return None

    def print_bank_info(self):

        print(_("Bank has {num_accounts} accounts").

              format(num_accounts=len(self.bank_accounts)))

if __name__ == '__main__':

    bank = Bank()

    first = bank.create_account(100)

    second = bank.create_account(150)

    bank.transfer_balance(first.id, second.id, 50)

    first.print_balance()

    second.print_balance()

    bank.print_bank_info()

We have removed the messages from the deposit and withdraw methods, we added a new message on the print_bank_info and modified the existing message on the BankAccount constructor.

2. Replace the old .pot  file using the xgettext tool

$ xgettext main.py -d messages -p locale

$ mv locale/messages.po locale/messages.pot

3. Use the msgmerge tool to update the relevant .po files using the new .pot  template

$ msgmerge locale/el/LC_MESSAGES/messages.po locale/messages.pot -o locale/el/LC_MESSAGES/messages.po

$ msgmerge locale/en/LC_MESSAGES/messages.po locale/messages.pot -o locale/en/LC_MESSAGES/messages.po

The calling format of this tool is:

$ msgmerge [option] def.po ref.pot

In the example invocation, we used the -o  flag to specify the output file.

Let's inspect the messages.po  file for the Greek translations to see what was changed

File: locale/el/LC_MESSAGES/messages.po

...

#: main.py:15

#, fuzzy, python-brace-format

msgid "Bank account '{id}' was created and the initial balance is: {balance}"

msgstr ""

"Τραπεζικός λογαριασμός με κωδικό '{id}' δημιουργήθηκε με αρχικό κεφάλαιο: "

"{balance}"

#: main.py:27

#, python-brace-format

msgid "Balance for Account '{id}' is: {balance}"

msgstr ""

"Το τρέχων κεφάλαιο του τραπεζικόυ λογαριασμού με κωδικό '{id}' είναι: "

"{balance}"

#: main.py:48

msgid "One of the Account numbers does not exist"

msgstr "Αυτός ο τραπεζικός λογαριασμός δέν υπάρχει"

#: main.py:54

#, python-brace-format

msgid ""

"Successfully transfered {amount} from Account '{from_acccount_id}' to "

"Account: {to_account_id}"

msgstr ""

"Μεταφέρθηκε το ποσό των {amount} απο τον λογαριασμό με κωδικό "

"'{from_acccount_id}' στον λογαριασμό με με κωδικό '{to_account_id}'"

#: main.py:66

#, python-brace-format

msgid "Bank has {num_accounts} accounts"

msgstr ""

#~ msgid "Deposited {amount} to current balance"

#~ msgstr "Το ποσό των {amount} κατατέθηκε στο τρέχων κεφάλαιο"

#~ msgid "Withdrawned {amount} from current balance"

#~ msgstr "Το ποσό των {amount} αποσύρθηκε από το τρέχων κεφάλαιο"

Here is the summary of the changes observed:

  • A  fuzzy comment was added to the entry that was updated. Fuzzy translations or entries account for messages that need revision by the translator as the program cannot decide if the meaning has remained the same or it has changed
  • Deleted entries were commented out. This is to show that those entries are no longer available for display.
  • Newly created entries were just placed in the right spot and based on the line number.

The job of the translator now is much easier as he has a lot of information about the status of the changes and what has to do.

Let's see now how can we find duplicate entries.

Finding duplicate entries with msguniq

Sometimes when merging or manipulating .po  files using the above tools, you may find that some of the messages have the same id string. In order to find and highlight those keys, we can use the msguniq tool.

The calling format of this tool is:

$ msguniq [option] [inputfile]

Let's add a duplicate key and run this tool.

1. Modify the locale/el/LC_MESSAGES/messages2.po  file and add the following lines:

File: locale/el/LC_MESSAGES/messages2.po

...

msgid "Bank account '{id}' was created with initial Balance: {balance}"

msgstr "Τραπεζικός λογαριασμός με κωδικό '{id}' δημιουργήθηκε με αρχικό κεφάλαιο: {balance}"

...

2. Run the msguniq tool and inspect the output

$ msguniq locale/el/LC_MESSAGES/messages.po -d

# Greek translations for PACKAGE package.

# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER

# This file is distributed under the same license as the PACKAGE package.

# IT Spare <itspare@c02pv2x1fvh6>, 2018.

#

msgid ""

msgstr ""

"Project-Id-Version: PACKAGE VERSION\n"

"Report-Msgid-Bugs-To: \n"

"POT-Creation-Date: 2018-05-15 15:46+0100\n"

"PO-Revision-Date: 2018-05-15 15:52+0100\n"

"Last-Translator: IT Spare <itspare@c02pv2x1fvh6>\n"

"Language-Team: Greek\n"

"Language: el\n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=UTF-8\n"

"Content-Transfer-Encoding: 8bit\n"

"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: main.py:12

#, python-brace-format

msgid "Bank account '{id}' was created with initial Balance: {balance}"

msgstr ""

"Τραπεζικός λογαριασμός με κωδικό '{id}' δημιουργήθηκε με αρχικό κεφάλαιο: "

"{balance}"

We used the -d  flag to indicate that we want only the duplicate keys to be printed.

Note: This will not work if you have the same message id but with different placeholder parameters. For example, if you have this msgid:

msgid "Bank account '{idParam}' was created with initial Balance: {balanceParam}"

then it would be considered as having a different key and will not be picked up.

The benefit of this tool is that it gives a nice color output and it can work well with multiple domains.

There is also a similar tool called msgcomm that performs a similar job but with a different perspective. It checks two .po  files and finds their common messages. Either case with those tools we should be able to identify duplicate entries.

The last tool for this article is the msgcat utility, that can help us concatenate 2 or more .po files into a single one.

Concatenating PO Files with msgcat

If we have a set of .po  files in different packages or project and we would like to combine them we can use the msgcat tool. It will perform a search to find the common messages first and remove duplicates before creating the output file so the .po  file that was created will also be valid.

Let's use that in practice.

1. Create a new .po  file in locale/el/LC_MESSAGES folder called messages2.po and move half of the messages from the messages.po file. Make sure also you leave at least one common entry between those 2 files.

$ touch locale/el/LC_MESSAGES/messages2.po

File: locale/el/LC_MESSAGES/messages2.po

# Greek translations for PACKAGE package.

# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER

# This file is distributed under the same license as the PACKAGE package.

# IT Spare <itspare@c02pv2x1fvh6>, 2018.

#

msgid ""

msgstr ""

"Project-Id-Version: PACKAGE VERSION\n"

"Report-Msgid-Bugs-To: \n"

"POT-Creation-Date: 2018-05-15 15:46+0100\n"

"PO-Revision-Date: 2018-05-15 15:52+0100\n"

"Last-Translator: IT Spare <itspare@c02pv2x1fvh6>\n"

"Language-Team: Greek\n"

"Language: el\n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=UTF-8\n"

"Content-Transfer-Encoding: 8bit\n"

"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: main.py:20

#, python-brace-format

msgid "Withdrawned {amount} from current balance"

msgstr "Το ποσό των {amount} αποσύρθηκε από το τρέχων κεφάλαιο"

#: main.py:26

#, python-brace-format

msgid "Balance for Account '{id}' is: {balance}"

msgstr "Το τρέχων κεφάλαιο του τραπεζικόυ λογαριασμού με κωδικό '{id}' είναι: {balance}"

#: main.py:47

msgid "One of the Account numbers does not exist"

msgstr "Αυτός ο τραπεζικός λογαριασμός δέν υπάρχει"

#: main.py:53

#, python-brace-format

msgid ""

"Successfully transfered {amount} from Account '{from_acccount_id}' to "

"Account: {to_account_id}"

msgstr "Μεταφέρθηκε το ποσό των {amount} απο τον λογαριασμό με κωδικό '{from_acccount_id}' στον λογαριασμό με με κωδικό '{to_account_id}'"

2. Use the msgcat tool to concatenate those files together.

$ msgcat locale/el/LC_MESSAGES/messages.po locale/el/LC_MESSAGES/messages2.po -o locale/el/LC_MESSAGES/messages3.po

File: locale/el/LC_MESSAGES/messages3.po

# Greek translations for PACKAGE package.

# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER

# This file is distributed under the same license as the PACKAGE package.

# IT Spare <itspare@c02pv2x1fvh6>, 2018.

#

msgid ""

msgstr ""

"Project-Id-Version: PACKAGE VERSION\n"

"Report-Msgid-Bugs-To: \n"

"POT-Creation-Date: 2018-05-15 15:46+0100\n"

"PO-Revision-Date: 2018-05-15 15:52+0100\n"

"Last-Translator: IT Spare <itspare@c02pv2x1fvh6>\n"

"Language-Team: Greek\n"

"Language: el\n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=UTF-8\n"

"Content-Transfer-Encoding: 8bit\n"

"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: main.py:12

#, python-brace-format

msgid "Bank account '{id}' was created with initial Balance: {balance}"

msgstr ""

"Τραπεζικός λογαριασμός με κωδικό '{id}' δημιουργήθηκε με αρχικό κεφάλαιο: "

"{balance}"

msgid "Bank account '{param}' was created with initial Balance: {balance}"

msgstr ""

"Τραπεζικός λογαριασμός με κωδικό '{param}' δημιουργήθηκε με αρχικό κεφάλαιο: "

"{balance}"

#: main.py:16

#, python-brace-format

msgid "Deposited {amount} to current balance"

msgstr "Το ποσό των {amount} κατατέθηκε στο τρέχων κεφάλαιο"

#: main.py:20

#, python-brace-format

msgid "Withdrawned {amount} from current balance"

msgstr "Το ποσό των {amount} αποσύρθηκε από το τρέχων κεφάλαιο"

#: main.py:26

#, python-brace-format

msgid "Balance for Account '{id}' is: {balance}"

msgstr ""

"Το τρέχων κεφάλαιο του τραπεζικόυ λογαριασμού με κωδικό '{id}' είναι: "

"{balance}"

#: main.py:47

msgid "One of the Account numbers does not exist"

msgstr "Αυτός ο τραπεζικός λογαριασμός δέν υπάρχει"

#: main.py:53

#, python-brace-format

msgid ""

"Successfully transfered {amount} from Account '{from_acccount_id}' to "

"Account: {to_account_id}"

msgstr ""

"Μεταφέρθηκε το ποσό των {amount} απο τον λογαριασμό με κωδικό "

"'{from_acccount_id}' στον λογαριασμό με με κωδικό '{to_account_id}'"

As you can see the tool removed any duplicates and combined the messages in one file correctly.

You can also use the -u  flag to specify that you want to keep only the unique entries, thus it will work just like the msguniq tool.

🔗 Resource » Find out more about alternative ways to combine two PO files together in our compact guide.

Alas, most of the tools that we discussed in this article have myriads of flags and options so each case usage is different. If you want to learn in detail about the full spectrum of this library you can visit the official page here. Hopefully, though this article has shown the best practical applications of each tool and you won't have to invest more time on that.

Use Phrase

Phrase supports many different languages and frameworks, including gettext. It allows you to easily import and export translations data and search for any missing translations, which is really convenient. On top of that, you can collaborate with translators as it is much better to have professionally done localization for your website. If you’d like to learn more about Phrase, refer to the Phrase Localization Platform.

Conclusion

I hope with this tutorial to have tempted your interest enough and given you more practical examples of how to use the gettext collection of programs. Stay put for more future articles related to this topic.