Getting Started

Modern Python packaging

You might be familiar with requirements.txt or setup.py for describing your project dependencies. Since then, a lot has changed in the Python world. At some point, we had multiple tools like poetry, pipenv, dephell, flit, and so on, each with its own file format. Luckily, now everyone is settled on a singe universal approach. There are the standards that set the ground:

  • PEP 517 introduced pyproject.toml as a single configuration file for all Python tools.

  • PEP 518 intorduced a [build-system] section in pyproject.toml that says what tool should be used to buid your project.

  • PEP 621 intorduced a [project] section in pyproject.toml that described all project metadata, like name, version, authors, dependencies.

You don’t have to use all this to work with l10n but if you do, l10n will be better at finding your project metadata. In this section, we will use the modern packaging approach for all examples.

Installation

Let’s start a new project:

mkdir l10n-playground
cd l10n-playground

Now, let’s create a pyproject.toml with your project metadata and add l10n into dependencies. In this example, we will use flit for dependency management.

Add into pyproject.toml:

[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "l10n_playground"
dynamic = ["version", "description"]
requires-python = ">=3.7"
dependencies = ["l10n"]

[project.optional-dependencies]
dev = ["l10n[cli]"]

And into l10n_playground/__init__.py:

"""Experimenting with l10n"""

__version__ = "1.0.0"

Now, you can install your project:

python3 -m pip install -U flit
flit install --deps=develop --symlink

The environment is ready! It’s time to write some code.

Writing code

In l10n, your code is the single source of truth, and translation files are generated from it. So you always start from writing code, and translations go later.

The class Locales provides a runtime catalog of translation for different languages. Create an instance of it (usually, no need to pass any arguments, defaults are good) and use it as mapping where keys are language codes. Sounds vague so far, so let’s get our hands dirty.

Create l10n_playground/example.py:

from argparse import ArgumentParser
from l10n import Locales

locales = Locales()

def say_hello(lang: str = 'en') -> None:
    loc = locales[lang]
    msg = loc.get('Hello, world!')
    print(msg)

def main() -> None:
    parser = ArgumentParser()
    parser.add_argument('--lang', default='en')
    args = parser.parse_args()
    say_hello(lang=args.lang)

And l10n_playground/__main__.py:

from .example import main

main()

If you try to run it, it will fail with KeyError because there is no en language in the catalogue. So, it’s time to make some.

Extracting messages

Now, let’s generate translation files from our code for English and Russian:

python3 -m l10n extract --lang=en
python3 -m l10n extract --lang=ru

These commands will create locales/en.po and locales/ru.po respectively. PO file format is a standard text format for translations introduced in GNU (Linux) and used in many places. it’s kind of a de facto standard for translations, so it’s supported by most of the tools. But since it’s a text format, you can open it in any text editor, it’s very readable. Inside, you’ll see some headers (feel free to edit them) and the text message to be translated extracted from your code. Now, it’s time to translate.

Translating messages

At this point, we have a file to store translations. The file contains the messages from our code (currently, only "Hello, world!") but no actual translation. Usually, this is where you send the file to a translator and forget about it while they are doing their job. But if you don’t have a translator just yet, don’t worry, we’ve got you covered.

English you can translate yourself, just edi en.po file in any text editor:

#: ./l10n_playground/example.py:9
msgid "Hello, world!"
msgstr "Hello, world!"

And for Russian, we’ll use l10n translate command which will automatically translate all messages using Google Translate unofficial API:

python3 -m l10n translate

And this is what you now sohuld see in ru.po:

#: ./l10n_playground/example.py:9
#, fuzzy
msgid "Hello, world!"
msgstr "Привет, мир!"

Now, it’s time to get it back into your app.

Compiling messages

PO file format is a readable text format for humans. For machines, we need mo files which are binary and optimized for machines. The po files are used by you and translators during the app development, mo files are used by your app (or library) to get translations for messages in runtime. So, before we can run the app, we need to compile mo files:

python3 -m l10n compile

The command will create en.mo and ru.mo binary files inside of l10n_playground/locales/.

Running the app

Now, it’s time to run the app!

$ python3 -m l10n_playground
Hello, world!
$ python3 -m l10n_playground --lang=ru
Привет, мир!

It works!

Updating messages

When you update a message or add a new one:

  • Run l10n extract to update po files. No need to specify --lang, by default it will just update all po files you already have.

  • Run l10n translate if you want it to be auto-translated (or translate it manually).

  • Run l10n compile to generate mo files.

Cheat sheet

  • po file is a text file with translations.

  • mo file is a binary file compiled out of mo files.

  • l10n.Locales is a mapping of locales (mo files) with language codes as keys.

  • l10n extract --lang=nl to generate po file for nl langauge.

  • l10n extract to update all po files you have.

  • l10n translate to run Google Translate on all po files you have.

  • l10n compile to generate mo files from po files.

See also the advanced usage to get more out of l10n.