Translations
One design, several languages, switched on the device - and told before you flash it which words will not fit and which letters the font cannot draw.
A design starts in one language: the text you typed on its widgets. Add a second on the Languages tab and that text becomes the base language, each text you mark gets a box for every other language, and the export carries all of them. A Set language event on a button switches between them on the panel.
Adding a language and marking text for translation are part of Pro. A design that already has translations opens, edits, previews and exports for anybody - opening one in a free account loses nothing.
Languages
The Languages tab is in the bottom panel, beside Fonts and Icons. Add a language takes a name and a short id; pick a common one from the list and both are filled in.
- The first language is the design itself. When you add your first language the list
gains two entries: the one you asked for, and above it the language the widgets are already
written in. It is called English (
en) to begin with - rename it, and its id, if your design is in something else. It cannot be removed or moved, because it is not a translation: it is the text on the widgets. - The id becomes code.
deisUI_LANG_DEin the firmware and the heading of that language's column in a CSV, so it is a letter followed by letters, digits or underscores, 24 at most, and two ids must differ by more than capitals. Renaming an id moves its translations and every event that names it. - The order matters. It is the order Set language: the next one steps through and the order of the numbers in the generated code. The arrows beside a language move it.
- Removing a language removes its translations. An event that switched to it becomes "the next one" rather than pointing at nothing. Undo brings all of it back.
A design holds up to 32 languages. Each is a complete table of text in the firmware, so the cost is the text itself - a few kilobytes for a typical design, not a copy of the UI.
Marking text
Nothing is translated until you say so. Five kinds of text can be, each through a To be translated box in the widget's settings:
| Widget | What is translated |
|---|---|
| Label | Its text |
| Button | Its caption. An icon on the button stays as it is. |
| Checkbox | The text beside the box |
| Text area | The placeholder. What somebody has typed into it is left alone. |
| Dropdown | The options, one to a line. Keep the same number of lines in every language: the selection is a position in the list, and it is kept when the language changes. |
Mark all on the Languages tab ticks every one of them in the design at once, which is the usual first step on a design drawn before anybody thought of a second language. Leave unmarked what should not change: a unit, a product name, a number.
A text a binding writes is not yours to translate. A label bound to a variable is rewritten on every refresh, which would replace whatever the language set. The Problems tab says so; put the translated caption in a label of its own beside the value.
The table
With two languages and one marked text, the tab becomes a table: a row for each text, a column for each language. The base column is the text on the widget, read only - change it on the widget. Click the name at the start of a row to select that widget on the canvas.
- An empty box shows the base text, greyed, because that is what the device will show. A missing translation is never a blank on the panel.
- Each column says how many it is missing, or complete.
- The filter narrows the rows to Not translated yet or to Will not fit or draw.
- A warning under a box is about that text in that language: too long for its widget, or a letter its font does not have. The same warnings are on the Problems tab.
Translations follow their widget. A duplicated or pasted widget takes its translations with it, and deleting a widget drops them.
CSV: sending it to a translator
Export CSV writes one row for each marked text and one column for each language:
key,where,en,de,fr
w_k3f91a:text,Home / Title,Settings,Einstellungen,
w_k3fa2c:options,Home / Units,"Celsius
Fahrenheit","Celsius
Fahrenheit",
key is what a row is matched on when the file comes back - it does not change when
a widget is renamed or moved, so leave it alone. where is the screen and the widget,
for the person translating, and is never read. The file is UTF-8 with a byte-order mark, which is
what makes Excel open it with its accents intact.
Import CSV reads the file back and says exactly what it did:
- how many translations were set, how many were already the same, and how many cells were empty - an empty cell leaves the translation that is there, so a part-finished file does no harm;
- every row whose key is not a marked text in this design, and every column that is not one of its languages. Nothing is guessed: such a row is reported, not matched to something that looks like it;
- every text whose base wording has changed since the file was written, since its translation may now be of the old sentence.
A file saved with semicolons or tabs is read as it is - a spreadsheet in a locale that writes 1,5 saves "CSV" that way. A file that is not UTF-8 is refused whole, with how to save it properly, because its accented letters are already lost. One import is one undo step.
Seeing a language
Once a design has a second language, a selector with a globe appears in the canvas toolbar. Choosing German draws every screen in German: on the canvas, in preview and in the LCD view. It is a way of looking - nothing in the design changes and nothing is added to undo. A text with no translation shows in the base language, exactly as on the device.
In preview, a Set language event really switches, and the selector follows it.
What is checked
A translated screen fails quietly, and each way it can is on the Problems tab with the widget and the language named and a link that selects the widget:
| Check | What it says |
|---|---|
| Will not fit | German is routinely a third longer than English. A label that wraps to more lines than its box holds, a caption wider than its button, a checkbox or dropdown text cut at its edge - each names the widget, the language, and what the text needs against what the widget has. |
| Missing | How many marked texts each language has no translation for. They will show in the base language. |
| Cannot be drawn | Every character the text's font does not have, by widget and language. On the device each one is an empty box. |
| Dropdown lines | A translation with a different number of options from the base language. |
Widths are measured the way the canvas measures them, which is within a few per cent of the panel rather than exact - a text reported as fitting by a pixel is worth a look in the language selector.
Accents and other alphabets
Accented text needs an imported font. LVGL's built-in Montserrat holds the English letters, digits, punctuation, the degree sign and a bullet - no "ä", "é", "ñ" or "ß", and no Greek, Cyrillic or Chinese. The fonts in the library are converted with their own limited sets. A character the font does not have draws as an empty box on the panel, with no error anywhere.
So that it cannot happen unnoticed:
- the canvas draws the same empty box the panel will, in whichever language is showing;
- the Problems tab and the table name each missing character, the widget and the language.
The cure is a font that has the letters. Import one on the Fonts tab - Montserrat's own full file, Noto Sans, anything under a free licence - and set it on the text under Style settings > Text > Font. From there it is automatic: the characters your translations use are added to the font at export, in every size the design uses, with nothing to tick. Add a Polish translation and "ł" is in the next build.
If the file itself lacks a character - a Latin font asked for Chinese - the warning says the font file does not have it, which is a different problem with a different cure: a font for that script. Beyond the set chosen when it was imported, only the characters your texts use are converted, so a few dozen Chinese words cost a few dozen glyphs. The file you import can be 1.5 MB at most, though, and a complete Chinese, Japanese or Korean font is many times that: import a subset of one that holds the characters you need.
Right-to-left scripts are not supported yet. Arabic and Hebrew need LVGL's bidirectional text and Arabic shaping, which the export does not switch on, so they would draw left to right and unjoined.
Switching on the device
The action is Set language, in the Language group. It takes one of the design's languages, or the next one, which steps through them in the tab's order and goes from the last back to the first - a single button is then a language switch. It works from a widget's event and from an automation.
What a switch does on the panel:
- every marked text on every screen that exists changes at once, and a screen built afterwards (a temporary screen) is built in the language that is current;
- a text is set only when it differs from what the widget shows, so switching to the language already showing redraws nothing;
- a dropdown keeps its selection, though LVGL resets it when the options are replaced;
- a text area keeps what was typed - only its placeholder changes.
In the Arduino export
A design with two or more languages gains ui_i18n.h and ui_i18n.c in
the UI folder, on LVGL 9 and LVGL 8 alike. A design in one language gets neither, and its export
is byte for byte what it was.
typedef enum {
UI_LANG_EN = 0, // English
UI_LANG_DE, // German
UI_LANG_COUNT
} ui_lang_t;
void ui_set_language(ui_lang_t lang);
ui_lang_t ui_get_language(void);
const char * ui_language_name(ui_lang_t lang);
const char * ui_text(ui_text_t id);
Each language is a table of strings in flash, and a text with no translation holds the base text there too - it was filled in when the file was written, so the device has no fallback to look up. From your own code:
ui_set_language(UI_LANG_DE);
lv_label_set_text(my_label, ui_language_name(ui_get_language()));
The choice is not kept across a reboot. To keep it, store
ui_get_language() yourself - in Preferences, say - and call
ui_set_language() with it before ui_init(), so the screens are
built in that language rather than built and then changed. The export's README repeats this with
your own languages' names.
If the export's Code prefix is not ui, the files and functions take it:
hmi_i18n.h, hmi_set_language().
In the MicroPython export
The same thing as a module, ui_i18n.py: a tuple of strings for each language,
set_language(), next_language(), get_language() and
text(), and constants LANG_EN, LANG_DE. The firmware has
Montserrat 12, 14 and 16 built in and nothing else, so a translated design with accents needs an
imported font here too; it is written to /fonts with the characters your
translations use, as on Arduino.
ESPHome
The ESPHome export writes the base language only. ESPHome's LVGL component has no table of texts to switch between, so a design with several languages exports as the one it was drawn in. The Languages tab and the Problems tab both say so, the other languages stay in the design, and Set language is greyed out with the reason beside it. Nothing is lost by keeping them: the same design exported for Arduino or MicroPython writes them all.
What it does not do
- Only the five widgets above. The items of a List and a Roller, a Tabview's tab names and a Calendar's day names are not translated yet.
- Text your own code sets with
lv_label_set_text()is yours: useui_get_language()to choose it. - Pictures, fonts and layout are the same in every language. A caption that only fits in English needs a wider widget, which the fit check is there to tell you.
- No plural rules, and no numbers or dates formatted by locale.