How to make an app that automatically translates text in the user's language
Making sure your app can speak your user's language makes it more likely they'll engage with the app. Fortunately, translating app content into any language is easy on the Meya platform. Keep reading to learn how.
Flagging content for translation
The first step in the translation process is to identify, or flag, content that needs to be translated.
To translate content that appears within a Python file, import
meya.util.translation and apply the function to the content.
from dataclasses import dataclass from typing import List from meya.component.element import Component from meya.element.field import element_field from meya.entry import Entry from meya.text.event.say import SayEvent from meya.util.translation import gettext as _ @dataclass class WelcomeComponentElement(Component): async def start(self) -> List[Entry]: text = _("Hi!") text_event = SayEvent(text=text) return self.respond(text_event)
There are special cases in Python when you might want the text to appear translated in other contexts, but don't want the Python file to be altered.
One example would be an enum where the values are displayed to the user in other parts of your code, but should have an unchanging value within the enum. In this case, you can use the
gettext_noop function to flag the text as something that requires a translation, but shouldn't be altered in this context.
from enum import Enum from meya.util.translation import gettext_noop as _ class UserType(Enum): ROBOT = _("Robot") HUMAN = _("Human")
There are two ways of flagging text for translation in BFML.
This method can be used for short pieces of text that take up a single line in your BFML code:
steps: - say: (@ _("Hi!") )
Notice that the text needs to be wrapped in double quotes
Using functions in BFML
_is a function, it needs to be wrapped in Jinja2 delimiters:
(% trans %) statement
(% trans %)statement
Multiline strings should be wrapped with the
(% trans %) and
(% endtrans %) statements, like this:
steps: - say: > (% trans %) This is a long string of text. It covers multiple lines. This is the last line. (% endtrans %)
Variable replacement in translation strings
Often your need to render a variable in a string to the user e.g.
- say: Hi, (@ user.name)
If you want to translate this string, you'll need to use a special substitution syntax to account for the variables.
steps: - say: > (% trans name=user.name %) Hi, (@ name)! (% endtrans %)
Here is how you do it in Python:
from dataclasses import dataclass from typing import List from meya.component.element import Component from meya.element.field import element_field from meya.entry import Entry from meya.text.event.say import SayEvent from meya.util.translation import gettext as _ @dataclass class WelcomeComponentElement(Component): async def start(self) -> List[Entry]: text = _("Hi, %(name)!") % dict(name=self.user.name) text_event = SayEvent(text=text) return self.respond(text_event)
Now that you've flagged all of the strings that need to be translated, you can generate files where you'll store the translated content. To do this, you'll be using a tool called PyBabel.
First, let's set up the PyBabel config file. In your app's root folder, create a file called
translation_mapping.ini and copy this code into it:
[ignore: venv/**] [python: **.py] [bfml: **.yaml]
This file states that files with the extension
.py at any level inside the directory should be processed by the
python extraction method, and
.yaml files with the
bfml extraction method. Files that don’t match any of the mapping patterns are ignored.
Next, we'll tell PyBabel to look through our app files and extract text that's been flagged for translation.
Open a terminal in your app's root folder and run these commands:
mkdir translation pybabel extract --omit-header --mapping-file translation_mapping.ini --output-file translation/app.pot .
If you examine the contents of
translation/app.pot, you'll see it contains all of the text that needs to be translated along with the file names and line numbers where the text was found.
Don't put translations directly in the
If the same piece of text appears more than once, it will be consolidated into one entry in your
.potfile. This is really handy for standard text you'll re-use throughout your app (e.g.
Cancel) since it means you'll only need to translate these strings once.
Next, we need to create a translation file for each language we want to translate the content into.
To do this, run this command in your terminal:
pybabel init --domain app --input-file translation/app.pot --output-dir translation --locale LOCALE
LOCALEwith a two-letter language code like
frfor French, or
Only run this once per locale
Running this command again will overwrite any translations you've added to the
If you've updated translated content in your Python and BFML code and want to update your translation files to reflect the changes, run the
pybabel updatecommand shown in the Updating translations section below.
This command will generate a
.po file in the
translations/<LOCALE>/LC_MESSAGES folder. If you open the file, you'll see a number of entries that look similar to this:
#: component/hello_component.py:15 msgid "Hi!" msgstr ""
Replace the empty
msgstr string with the appropriate translation, like this:
#: component/hello_component.py:15 msgid "Hi!" msgstr "Salut!"
Once you've entered all of your translations, save the file, and continue with the Compile section below.
In this last step, we'll create an
.mo file, which compiles all of our translations.
Whenever you update the
.pofile, you'll need to re-compile the
pybabel compile --domain app --directory translation
Create a debug flow
When creating translations, it is helpful to be able to switch between languages easily. You can do that by creating a flow that updates
user.language to a valid language code.
In your app's
flow folder, create a folder called
debug. Inside the new folder, create a file called
language.yaml and copy this code into it:
triggers: - keyword: _language - keyword: _french action: jump: set data: language: fr - keyword: _english action: jump: set data: language: en steps: - ask: Enter a language code - flow_set: language - (set) - user_set: language: (@ flow.language ) - say: (@ _("Language updated") )
In this example, you can switch between English and French by typing
Save the file and push it to the Grid using these commands in your terminal:
meya format meya push
Test it out
In your app's simulator, verify that the default language still works. Then try switching languages with the
language flow created in the previous step.
Awesome! You can now create a multilingual app.
As you update your app code, you may add, modify, or remove content that is flagged for translation. You'll need to update the translation files as well. Here's how:
pybabel extractto update your
.potfile with the latest file names, line numbers, and message IDs.
pybabel extract --omit-header --mapping-file translation_mapping.ini --output-file translation/app.pot .
pybabel updateto update your
pybabel update --domain app --input-file translation/app.pot --output-dir translation --locale LOCALE
LOCALEwith a language code like
frfor French, or
.pofiles with your new translations.
pybabel compileto recompile your
pybabel compile --domain app --directory translation
Updated 6 months ago