From bca975cf221f3c676979bcc518b0f4e9f661c704 Mon Sep 17 00:00:00 2001 From: Gabor Kiss-Vamosi Date: Mon, 23 Jun 2025 16:15:57 +0200 Subject: [PATCH] feat(translation): add translation support (#8255) --- .typos.toml | 1 + Kconfig | 4 +- configs/ci/docs/lv_conf_docs.defaults | 1 + configs/ci/examples/lv_conf_examples.defaults | 1 + docs/src/details/auxiliary-modules/index.rst | 1 + .../details/auxiliary-modules/translation.rst | 86 ++++++ .../details/auxiliary-modules/xml/index.rst | 4 +- .../auxiliary-modules/xml/translation.rst | 52 ++++ .../auxiliary-modules/xml/translations.rst | 7 - examples/others/lv_example_others.h | 1 + .../others/snapshot/lv_example_snapshot_1.c | 1 + examples/others/translation/index.rst | 7 + .../translation/lv_example_translation.h | 38 +++ .../translation/lv_example_translation_1.c | 54 ++++ examples/others/xml/lv_example_xml_2.c | 25 +- examples/others/xml/my_card.xml | 7 +- examples/others/xml/translations.xml | 4 + examples/others/xml/view.xml | 24 +- lv_conf_template.h | 32 +- lvgl.h | 2 +- src/core/lv_global.h | 5 + src/lv_conf_internal.h | 38 ++- src/lv_init.c | 9 + src/misc/lv_types.h | 6 + src/others/translation/lv_translation.c | 283 ++++++++++++++++++ src/others/translation/lv_translation.h | 140 +++++++++ .../translation/lv_translation_private.h | 59 ++++ src/others/xml/lv_xml.c | 3 +- src/others/xml/lv_xml.h | 6 +- src/others/xml/lv_xml_translation.c | 180 +++++++++++ src/others/xml/lv_xml_translation.h | 53 ++++ src/others/xml/parsers/lv_xml_label_parser.c | 4 + tests/src/lv_test_conf_full.h | 1 + .../test_cases/xml/test_xml_translations.c | 60 ++++ xmls/lv_buttonmatrix.xml | 2 +- xmls/lv_label.xml | 3 +- xmls/lv_obj.xml | 5 +- 37 files changed, 1134 insertions(+), 75 deletions(-) create mode 100644 docs/src/details/auxiliary-modules/translation.rst create mode 100644 docs/src/details/auxiliary-modules/xml/translation.rst delete mode 100644 docs/src/details/auxiliary-modules/xml/translations.rst create mode 100644 examples/others/translation/index.rst create mode 100644 examples/others/translation/lv_example_translation.h create mode 100644 examples/others/translation/lv_example_translation_1.c create mode 100644 examples/others/xml/translations.xml create mode 100644 src/others/translation/lv_translation.c create mode 100644 src/others/translation/lv_translation.h create mode 100644 src/others/translation/lv_translation_private.h create mode 100644 src/others/xml/lv_xml_translation.c create mode 100644 src/others/xml/lv_xml_translation.h create mode 100644 tests/src/test_cases/xml/test_xml_translations.c diff --git a/.typos.toml b/.typos.toml index 6c48b8d7fd..212cc94c7d 100644 --- a/.typos.toml +++ b/.typos.toml @@ -21,6 +21,7 @@ extend-ignore-re = [ "Rename lv_chart_clear_serie", "rename LV_ROLLER_MODE_INIFINITE", "CARD_INFO_SET\\(&img_multilang_avatar_.*\\)", + "ist", "ACI\\)", ] diff --git a/Kconfig b/Kconfig index 437917f3f8..3254139ead 100644 --- a/Kconfig +++ b/Kconfig @@ -392,7 +392,7 @@ menu "LVGL configuration" bool "Enable VGLite asserts" depends on LV_USE_DRAW_VGLITE default n - + config LV_USE_VGLITE_CHECK_ERROR bool "Enable VGLite error checks" depends on LV_USE_DRAW_VGLITE @@ -1728,6 +1728,8 @@ menu "LVGL configuration" config LV_USE_TEST_SCREENSHOT_COMPARE bool "Enable `lv_test_screenshot_compare`. Requires libpng and a few MB of extra RAM." depends on LV_USE_TEST + config LV_USE_TRANSLATION + bool "Enable text translation support" config LV_USE_XML bool "Enable loading XML UIs runtime" config LV_USE_COLOR_FILTER diff --git a/configs/ci/docs/lv_conf_docs.defaults b/configs/ci/docs/lv_conf_docs.defaults index 29f336ac5e..3e61e2f2ef 100644 --- a/configs/ci/docs/lv_conf_docs.defaults +++ b/configs/ci/docs/lv_conf_docs.defaults @@ -66,6 +66,7 @@ LV_USE_FILE_EXPLORER 1 LV_USE_TEST 1 LV_USE_TEST_SCREENSHOT_COMPARE 1 LV_USE_XML 1 +LV_USE_TRANSLATION 1 LV_USE_SDL 1 diff --git a/configs/ci/examples/lv_conf_examples.defaults b/configs/ci/examples/lv_conf_examples.defaults index 57da88fab4..264bb7a529 100644 --- a/configs/ci/examples/lv_conf_examples.defaults +++ b/configs/ci/examples/lv_conf_examples.defaults @@ -126,4 +126,5 @@ LV_USE_FONT_MANAGER 1 LV_USE_TEST 1 LV_USE_TEST_SCREENSHOT_COMPARE 1 LV_USE_XML 1 +LV_USE_TRANSLATION 1 LV_BUILD_EXAMPLES 1 diff --git a/docs/src/details/auxiliary-modules/index.rst b/docs/src/details/auxiliary-modules/index.rst index 3f39c1d92f..8659e72df6 100644 --- a/docs/src/details/auxiliary-modules/index.rst +++ b/docs/src/details/auxiliary-modules/index.rst @@ -19,4 +19,5 @@ Auxiliary Modules observer/index snapshot test + translation xml/index diff --git a/docs/src/details/auxiliary-modules/translation.rst b/docs/src/details/auxiliary-modules/translation.rst new file mode 100644 index 0000000000..bf640fdd53 --- /dev/null +++ b/docs/src/details/auxiliary-modules/translation.rst @@ -0,0 +1,86 @@ +.. _translation: + +=========== +Translation +=========== + +Overview +******** + +LVGL supports two ways of handling translations: + +- `lv_i18n `_: A comprehensive tool that extracts translatable strings from C files into YAML files, and generates C translation files from them. It also supports plural forms. See its README for details. + +- ``lv_translation``: A simpler yet more flexible solution that allows adding translations statically or dynamically. This is the method documented here. + +Add Translations +**************** + +Static Translations +------------------- + +If most translations are known at compile time, they can be defined using string arrays: + +.. code-block:: c + static const char * languages[] = {"en", "de", "es", NULL}; + static const char * tags[] = {"tiger", "lion", "rabbit", "elephant", NULL}; + static const char * translations[] = { + "The Tiger", "Der Tiger", "El Tigre", + "The Lion", "Der Löwe", "El León", + "The Rabbit", "Das Kaninchen", "El Conejo", + "The Elephant", "Der Elefant", "El Elefante", + }; + + lv_translation_add_static(languages, tags, translations); + +This method uses only a little extra RAM, as only the pointers to the strings are stored. + +Dynamic Translations +-------------------- + +If translations are only available at runtime (e.g., from files, serial ports, or online sources), they can be added dynamically. + +This approach involves memory allocation. See the example at the bottom of this page for reference. + +Select a Language +***************** + +Once translations are registered, use: + +:cpp:expr:`lv_translation_set_language("language")` + +to set the current language. The parameter must match one of the language names provided during registration. + +Translate Strings +***************** + +To retrieve a translation for a given tag, use: + +- :cpp:expr:`lv_translation_get("tag")` +- or the shorthand: :cpp:expr:`lv_tr("tag")` + +These return a translated string which can be used with widgets: + +.. code-block:: c + lv_label_set_text(label, lv_tr("settings")); + lv_dropdown_set_options(dd, lv_tr("color_list")); + +Fallbacks +--------- + +If a tag exists but the translation for the selected language is missing +the tag itself will be returned. + +If the tag is not found at all, the tag itself will be used as a fallback as well. + +.. _lv_translation_example: + +Example +******* + +.. include:: ../../examples/others/translation/index.rst + +.. _lv_translation_api: + +API +*** diff --git a/docs/src/details/auxiliary-modules/xml/index.rst b/docs/src/details/auxiliary-modules/xml/index.rst index b647ded83e..5a54e914da 100644 --- a/docs/src/details/auxiliary-modules/xml/index.rst +++ b/docs/src/details/auxiliary-modules/xml/index.rst @@ -26,5 +26,7 @@ XML - Declarative UI events subjects animations - translations + translation + license + license diff --git a/docs/src/details/auxiliary-modules/xml/translation.rst b/docs/src/details/auxiliary-modules/xml/translation.rst new file mode 100644 index 0000000000..ca69db48ee --- /dev/null +++ b/docs/src/details/auxiliary-modules/xml/translation.rst @@ -0,0 +1,52 @@ +.. _xml_translation: + +============ +Translations +============ + +Overview +******** + +The XML translation module allows defining and using translated strings directly within XML files. + +Usage +***** + +Example XML translation definition: + +.. code-block:: xml + + + + + + +In the root `` tag, the `languages` attribute defines the available languages, +e.g., ``languages="en de hu"``. Language codes are free-form, but ISO-style codes are recommended. + +Each `` defines a `tag`, which acts as the lookup key, and attributes for each language. + +Translations may be omitted—:ref:`Fallbacks ` will be applied when needed. + +To register XML translations: + +- :cpp:expr:`lv_xml_translation_register_from_file("path/to/file.xml")` +- :cpp:expr:`lv_xml_translation_register_from_data(xml_string)` + +Multiple XML sources can be registered; they will be merged and searched collectively. + +Usage in XML +************ + +Some widget properties support a `*-translated` suffix to refer to translation tags. For example: + +.. code-block:: xml + + +This sets the label's text to the translated string for `"dog"`. + +More Details +************ + +For information on selecting the active language, retrieving translations, and fallback behavior, +refer to the general :ref:`LVGL translation module `. diff --git a/docs/src/details/auxiliary-modules/xml/translations.rst b/docs/src/details/auxiliary-modules/xml/translations.rst deleted file mode 100644 index 14cb2355cd..0000000000 --- a/docs/src/details/auxiliary-modules/xml/translations.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _xml_translations: - -============ -Translations -============ - -TODO diff --git a/examples/others/lv_example_others.h b/examples/others/lv_example_others.h index e4f25946c8..ebe0f0dfaa 100644 --- a/examples/others/lv_example_others.h +++ b/examples/others/lv_example_others.h @@ -24,6 +24,7 @@ extern "C" { #include "snapshot/lv_example_snapshot.h" #include "gestures/lv_example_gestures.h" #include "xml/lv_example_xml.h" +#include "translation/lv_example_translation.h" /********************* * DEFINES diff --git a/examples/others/snapshot/lv_example_snapshot_1.c b/examples/others/snapshot/lv_example_snapshot_1.c index 0eea3b42e9..e0297a80ee 100644 --- a/examples/others/snapshot/lv_example_snapshot_1.c +++ b/examples/others/snapshot/lv_example_snapshot_1.c @@ -1,3 +1,4 @@ + #include "../../lv_examples.h" #if LV_USE_SNAPSHOT && LV_BUILD_EXAMPLES diff --git a/examples/others/translation/index.rst b/examples/others/translation/index.rst new file mode 100644 index 0000000000..9e76596435 --- /dev/null +++ b/examples/others/translation/index.rst @@ -0,0 +1,7 @@ +Simple translation example +--------------------------- + +.. lv_example:: others/translation/lv_example_translation_1 + :language: c + + diff --git a/examples/others/translation/lv_example_translation.h b/examples/others/translation/lv_example_translation.h new file mode 100644 index 0000000000..10041737e6 --- /dev/null +++ b/examples/others/translation/lv_example_translation.h @@ -0,0 +1,38 @@ +/** + * @file lv_example_translation.h + * + */ + +#ifndef LV_EXAMPLE_TRANSLATION_H +#define LV_EXAMPLE_TRANSLATION_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_example_translation_1(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EXAMPLE_TRANSLATION_H*/ diff --git a/examples/others/translation/lv_example_translation_1.c b/examples/others/translation/lv_example_translation_1.c new file mode 100644 index 0000000000..9a149fb0d5 --- /dev/null +++ b/examples/others/translation/lv_example_translation_1.c @@ -0,0 +1,54 @@ +#include "../../lv_examples.h" +#if LV_USE_TRANSLATION && LV_BUILD_EXAMPLES + +static void add_static(void) +{ + static const char * languages[] = {"en", "de", "es", NULL}; + static const char * tags[] = {"tiger", "lion", "rabbit", "elephant", NULL}; + static const char * translations[] = { + "The Tiger", "Der Tiger", "El Tigre", + "The Lion", "Der Löwe", "El León", + "The Rabbit", "Das Kaninchen", "El Conejo", + "The Elephant", "Der Elefant", "El Elefante", + }; + + lv_translation_add_static(languages, tags, translations); +} + +static void add_dynamic(void) +{ + lv_translation_pack_t * pack = lv_translation_add_dynamic(); + lv_translation_add_language(pack, "en"); + lv_translation_add_language(pack, "de"); + + lv_translation_tag_dsc_t * tag; + tag = lv_translation_add_tag(pack, "table"); + lv_translation_set_tag_translation(pack, tag, 0, "It's a table"); + lv_translation_set_tag_translation(pack, tag, 1, "Das is ein Tish"); + + tag = lv_translation_add_tag(pack, "chair"); + lv_translation_set_tag_translation(pack, tag, 0, "It's a chair"); + lv_translation_set_tag_translation(pack, tag, 1, "Das ist ein Stuhl"); +} + +/** + * Create and use translations + */ +void lv_example_translation_1(void) +{ + add_static(); + add_dynamic(); + + lv_translation_set_language("de"); + + lv_obj_t * label; + + label = lv_label_create(lv_screen_active()); + lv_label_set_text(label, lv_tr("tiger")); + + label = lv_label_create(lv_screen_active()); + lv_label_set_text(label, lv_tr("chair")); + lv_obj_set_y(label, 50); +} + +#endif /*LV_USE_TRANSLATION && LV_BUILD_EXAMPLES*/ diff --git a/examples/others/xml/lv_example_xml_2.c b/examples/others/xml/lv_example_xml_2.c index 2f23ee2481..22ee818b6e 100644 --- a/examples/others/xml/lv_example_xml_2.c +++ b/examples/others/xml/lv_example_xml_2.c @@ -1,5 +1,5 @@ #include "../../lv_examples.h" -#if LV_BUILD_EXAMPLES && LV_USE_XML +#if LV_BUILD_EXAMPLES && LV_USE_XML && LV_USE_TRANSLATION void lv_example_xml_2(void) { @@ -13,20 +13,27 @@ void lv_example_xml_2(void) } lv_xml_component_register_from_file("A:lvgl/examples/others/xml/my_card.xml"); lv_xml_component_register_from_file("A:lvgl/examples/others/xml/my_button.xml"); + lv_xml_component_register_from_file("A:lvgl/examples/others/xml/view.xml"); + lv_xml_translation_register_from_file("A:lvgl/examples/others/xml/translations.xml"); lv_xml_register_font(NULL, "lv_montserrat_18", &lv_font_montserrat_18); - lv_subject_t s1; - lv_subject_t s2; - static char buf[200]; - lv_subject_init_string(&s1, buf, NULL, 200, "Waaaa"); - lv_subject_init_int(&s2, 25); + lv_translation_set_language("de"); - lv_xml_register_subject(NULL, "s1", &s2); + lv_obj_t * obj = (lv_obj_t *) lv_xml_create(lv_screen_active(), "view", NULL); + lv_obj_set_pos(obj, 10, 10); - lv_xml_test_register_from_file("A:lvgl/examples/others/xml/view.xml", "A:"); + lv_xml_component_unregister("my_button"); - lv_xml_test_run_all(1); + const char * slider_attrs[] = { + "x", "200", + "y", "-15", + "align", "bottom_left", + "value", "30", + NULL, NULL, + }; + lv_obj_t * slider = (lv_obj_t *) lv_xml_create(lv_screen_active(), "lv_slider", slider_attrs); + lv_obj_set_width(slider, 100); } #endif diff --git a/examples/others/xml/my_card.xml b/examples/others/xml/my_card.xml index 31efc94923..3a158b996c 100644 --- a/examples/others/xml/my_card.xml +++ b/examples/others/xml/my_card.xml @@ -25,7 +25,10 @@ - - + + + - - - - - - - - - - -