feat(translation): add translation support (#8255)

This commit is contained in:
Gabor Kiss-Vamosi
2025-06-23 16:15:57 +02:00
committed by GitHub
parent cbe1f1eeca
commit bca975cf22
37 changed files with 1134 additions and 75 deletions
+1
View File
@@ -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\\)",
]
+3 -1
View File
@@ -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
+1
View File
@@ -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
@@ -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
@@ -19,4 +19,5 @@ Auxiliary Modules
observer/index
snapshot
test
translation
xml/index
@@ -0,0 +1,86 @@
.. _translation:
===========
Translation
===========
Overview
********
LVGL supports two ways of handling translations:
- `lv_i18n <https://github.com/lvgl/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
***
@@ -26,5 +26,7 @@ XML - Declarative UI
events
subjects
animations
translations
translation
license
license
@@ -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
<translations languages="en de hu">
<translation tag="dog" en="The dog" de="Der Hund" hu="A kutya"/>
<translation tag="cat" en="The cat" de="Die Katze" hu="A cica"/>
<translation tag="snake" en="A snake" de="Eine Schlange" hu="A kígyó"/>
</translations>
In the root `<translations>` 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 `<translation>` defines a `tag`, which acts as the lookup key, and attributes for each language.
Translations may be omitted—:ref:`Fallbacks <xml_translations_fallback>` 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
<lv_label text-translated="dog"/>
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 <translation>`.
@@ -1,7 +0,0 @@
.. _xml_translations:
============
Translations
============
TODO
+1
View File
@@ -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
@@ -1,3 +1,4 @@
#include "../../lv_examples.h"
#if LV_USE_SNAPSHOT && LV_BUILD_EXAMPLES
+7
View File
@@ -0,0 +1,7 @@
Simple translation example
---------------------------
.. lv_example:: others/translation/lv_example_translation_1
:language: c
@@ -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*/
@@ -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*/
+16 -9
View File
@@ -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
+5 -2
View File
@@ -25,7 +25,10 @@
</styles>
<view extends="lv_obj" style_radius="3" width="#size" height="content" styles="gray" style_bg_color="$bg_color" >
<lv_label text="$title" align="left_mid"/>
<my_button styles="$btn_rel_style $btn_pr_style:pressed" btn_text="$action" align="right_mid"/>
<lv_label text-translated="$title" align="left_mid"/>
<my_button btn_text="$action" align="right_mid">
<style name="$btn_rel_style"/>
<style name="$btn_pr_style" selector="pressed"/>
</my_button>
</view>
</component>
+4
View File
@@ -0,0 +1,4 @@
<translations languages="en de hu">
<translation tag="Card 1" en="Card 1" de="Karte 1" hu="Egy kártya"/>
<translation tag="No title" en="No title" de="Kein Title"/>
</translations>
+5 -19
View File
@@ -7,30 +7,16 @@
<styles>
<style name="btn_style" bg_color="#dark_blue" bg_opa="150"/>
<style name="btn_pr_style" bg_opa="255"/>
<style name="red_border" border_width="2" border_side="full" border_color="0xff0000"></style>
</styles>
<subjects>
<int name="subject1" value="10"/>
</subjects>
<view extends="lv_obj" flex_flow="column" name="main" width="280" height="content" style_bg_color="#light_blue">
<lv_label bind_text="subject1" styles="btn_style:disabled red_border:checked">
<bind_flag_if_gt subject="subject1" flag="hidden" ref_value="60"/>
<bind_state_if_lt subject="subject1" state="checked" ref_value="40"/>
<bind_state_if_lt subject="subject1" state="disabled" ref_value="20"/>
<style name="btn_style" selector="disabled"/>
<style name="red_border" selector="knob checked"/>
</lv_label>
<lv_slider bind_value="subject1" />
<my_card title="Card 1" name="card1"
y="100"
styles="red_border"
<view extends="lv_obj" name="main" width="280" height="content" style_bg_color="#light_blue">
<lv_label text-translated="tiger"/>
<my_card title="Card 1" name="card1"
y="30"
btn_rel_style="btn_style"
btn_pr_style="btn_pr_style"/>
<my_card y="185"
<my_card y="125"
bg_color="0xffaaaa"
action="Apply"
btn_rel_style="btn_style"
+18 -14
View File
@@ -1165,8 +1165,12 @@
/** Enable loading XML UIs runtime */
#define LV_USE_XML 0
/** 1: Enable text translation support */
#define LV_USE_TRANSLATION 0
/*1: Enable color filter style*/
#define LV_USE_COLOR_FILTER 0
/*==================
* DEVICES
*==================*/
@@ -1340,10 +1344,10 @@
#if LV_BUILD_DEMOS
/** Show some widgets. This might be required to increase `LV_MEM_SIZE`. */
#define LV_USE_DEMO_WIDGETS 0
/** Demonstrate usage of encoder and keyboard. */
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
/** Benchmark your system */
#define LV_USE_DEMO_BENCHMARK 0
@@ -1355,10 +1359,10 @@
/** Render test for each primitive.
* - Requires at least 480x272 display. */
#define LV_USE_DEMO_RENDER 0
/** Stress test for LVGL */
#define LV_USE_DEMO_STRESS 0
/** Music player demo */
#define LV_USE_DEMO_MUSIC 0
#if LV_USE_DEMO_MUSIC
@@ -1368,38 +1372,38 @@
#define LV_DEMO_MUSIC_LARGE 0
#define LV_DEMO_MUSIC_AUTO_PLAY 0
#endif
/** Vector graphic demo */
#define LV_USE_DEMO_VECTOR_GRAPHIC 0
/*---------------------------
* Demos from lvgl/lv_demos
---------------------------*/
/** Flex layout demo */
#define LV_USE_DEMO_FLEX_LAYOUT 0
/** Smart-phone like multi-language demo */
#define LV_USE_DEMO_MULTILANG 0
/** Widget transformation demo */
#define LV_USE_DEMO_TRANSFORM 0
/** Demonstrate scroll settings */
#define LV_USE_DEMO_SCROLL 0
/*E-bike demo with Lottie animations (if LV_USE_LOTTIE is enabled)*/
#define LV_USE_DEMO_EBIKE 0
#if LV_USE_DEMO_EBIKE
#define LV_DEMO_EBIKE_PORTRAIT 0 /*0: for 480x270..480x320, 1: for 480x800..720x1280*/
#endif
/** High-resolution demo */
#define LV_USE_DEMO_HIGH_RES 0
/* Smart watch demo */
#define LV_USE_DEMO_SMARTWATCH 0
#endif /* LV_BUILD_DEMOS */
#endif /* LV_BUILD_DEMOS */
/*--END OF LV_CONF_H--*/
+1 -1
View File
@@ -96,8 +96,8 @@ extern "C" {
#include "src/others/ime/lv_ime_pinyin.h"
#include "src/others/file_explorer/lv_file_explorer.h"
#include "src/others/font_manager/lv_font_manager.h"
#include "src/others/translation/lv_translation.h"
#include "src/others/xml/lv_xml.h"
#include "src/others/xml/lv_xml_component.h"
#include "src/others/test/lv_test.h"
#include "src/libs/barcode/lv_barcode.h"
+5
View File
@@ -228,6 +228,11 @@ typedef struct _lv_global_t {
lv_test_state_t test_state;
#endif
#if LV_USE_TRANSLATION
lv_ll_t translation_packs_ll;
const char * translation_selected_lang;
#endif
#if LV_USE_NUTTX
struct _lv_nuttx_ctx_t * nuttx_ctx;
#endif
+24 -14
View File
@@ -3710,6 +3710,15 @@
#endif
#endif
/** 1: Enable text translation support */
#ifndef LV_USE_TRANSLATION
#ifdef CONFIG_LV_USE_TRANSLATION
#define LV_USE_TRANSLATION CONFIG_LV_USE_TRANSLATION
#else
#define LV_USE_TRANSLATION 0
#endif
#endif
/*1: Enable color filter style*/
#ifndef LV_USE_COLOR_FILTER
#ifdef CONFIG_LV_USE_COLOR_FILTER
@@ -3718,6 +3727,7 @@
#define LV_USE_COLOR_FILTER 0
#endif
#endif
/*==================
* DEVICES
*==================*/
@@ -4339,7 +4349,7 @@
#define LV_USE_DEMO_WIDGETS 0
#endif
#endif
/** Demonstrate usage of encoder and keyboard. */
#ifndef LV_USE_DEMO_KEYPAD_AND_ENCODER
#ifdef CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER
@@ -4348,7 +4358,7 @@
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
#endif
#endif
/** Benchmark your system */
#ifndef LV_USE_DEMO_BENCHMARK
#ifdef CONFIG_LV_USE_DEMO_BENCHMARK
@@ -4378,7 +4388,7 @@
#define LV_USE_DEMO_RENDER 0
#endif
#endif
/** Stress test for LVGL */
#ifndef LV_USE_DEMO_STRESS
#ifdef CONFIG_LV_USE_DEMO_STRESS
@@ -4387,7 +4397,7 @@
#define LV_USE_DEMO_STRESS 0
#endif
#endif
/** Music player demo */
#ifndef LV_USE_DEMO_MUSIC
#ifdef CONFIG_LV_USE_DEMO_MUSIC
@@ -4433,7 +4443,7 @@
#endif
#endif
#endif
/** Vector graphic demo */
#ifndef LV_USE_DEMO_VECTOR_GRAPHIC
#ifdef CONFIG_LV_USE_DEMO_VECTOR_GRAPHIC
@@ -4442,11 +4452,11 @@
#define LV_USE_DEMO_VECTOR_GRAPHIC 0
#endif
#endif
/*---------------------------
* Demos from lvgl/lv_demos
---------------------------*/
/** Flex layout demo */
#ifndef LV_USE_DEMO_FLEX_LAYOUT
#ifdef CONFIG_LV_USE_DEMO_FLEX_LAYOUT
@@ -4455,7 +4465,7 @@
#define LV_USE_DEMO_FLEX_LAYOUT 0
#endif
#endif
/** Smart-phone like multi-language demo */
#ifndef LV_USE_DEMO_MULTILANG
#ifdef CONFIG_LV_USE_DEMO_MULTILANG
@@ -4464,7 +4474,7 @@
#define LV_USE_DEMO_MULTILANG 0
#endif
#endif
/** Widget transformation demo */
#ifndef LV_USE_DEMO_TRANSFORM
#ifdef CONFIG_LV_USE_DEMO_TRANSFORM
@@ -4473,7 +4483,7 @@
#define LV_USE_DEMO_TRANSFORM 0
#endif
#endif
/** Demonstrate scroll settings */
#ifndef LV_USE_DEMO_SCROLL
#ifdef CONFIG_LV_USE_DEMO_SCROLL
@@ -4482,7 +4492,7 @@
#define LV_USE_DEMO_SCROLL 0
#endif
#endif
/*E-bike demo with Lottie animations (if LV_USE_LOTTIE is enabled)*/
#ifndef LV_USE_DEMO_EBIKE
#ifdef CONFIG_LV_USE_DEMO_EBIKE
@@ -4500,7 +4510,7 @@
#endif
#endif
#endif
/** High-resolution demo */
#ifndef LV_USE_DEMO_HIGH_RES
#ifdef CONFIG_LV_USE_DEMO_HIGH_RES
@@ -4509,7 +4519,7 @@
#define LV_USE_DEMO_HIGH_RES 0
#endif
#endif
/* Smart watch demo */
#ifndef LV_USE_DEMO_SMARTWATCH
#ifdef CONFIG_LV_USE_DEMO_SMARTWATCH
@@ -4518,7 +4528,7 @@
#define LV_USE_DEMO_SMARTWATCH 0
#endif
#endif
#endif /* LV_BUILD_DEMOS */
#endif /* LV_BUILD_DEMOS */
+9
View File
@@ -39,6 +39,7 @@
#include "misc/lv_fs.h"
#include "osal/lv_os_private.h"
#include "others/sysmon/lv_sysmon_private.h"
#include "others/translation/lv_translation.h"
#include "others/xml/lv_xml.h"
#if LV_USE_SVG
@@ -393,6 +394,10 @@ void lv_init(void)
lv_svg_decoder_init();
#endif
#if LV_USE_TRANSLATION
lv_translation_init();
#endif
#if LV_USE_XML
lv_xml_init();
#endif
@@ -510,6 +515,10 @@ void lv_deinit(void)
lv_xml_test_unregister();
#endif
#if LV_USE_TRANSLATION
lv_translation_deinit();
#endif
lv_mem_deinit();
lv_initialized = false;
+6
View File
@@ -383,6 +383,12 @@ typedef struct _lv_xml_parser_state_t lv_xml_parser_state_t;
typedef struct _lv_evdev_discovery_t lv_evdev_discovery_t;
#endif
#if LV_USE_TRANSLATION
typedef struct _lv_translation_tag_dsc_t lv_translation_tag_dsc_t;
typedef struct _lv_translation_pack_t lv_translation_pack_t;
#endif
#endif /*__ASSEMBLY__*/
/**********************
+283
View File
@@ -0,0 +1,283 @@
/**
* @file lv_translation.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_translation.h"
#if LV_USE_TRANSLATION
#include "lv_translation_private.h"
#include "../../misc/lv_ll.h"
#include "../../stdlib/lv_mem.h"
#include "../../stdlib/lv_string.h"
#include "../../misc/lv_log.h"
#include "../../misc/lv_assert.h"
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#define packs_ll (LV_GLOBAL_DEFAULT()->translation_packs_ll)
#define selected_lang (LV_GLOBAL_DEFAULT()->translation_selected_lang)
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_translation_init(void)
{
lv_ll_init(&packs_ll, sizeof(lv_translation_pack_t));
selected_lang = NULL;
}
void lv_translation_deinit(void)
{
lv_translation_pack_t * pack;
LV_LL_READ(&packs_ll, pack) {
if(pack->is_static == false) {
size_t i;
size_t trans_cnt = lv_array_size(&pack->translation_array);
for(i = 0; i < trans_cnt; i++) {
lv_translation_tag_dsc_t * tag = lv_array_at(&pack->translation_array, i);
lv_free((void *)tag->tag);
size_t j;
for(j = 0; j < pack->language_cnt; j++) {
lv_free((void *)tag->translations[j]); /*Free each translation of the tag*/
}
lv_free(tag->translations);
}
lv_array_deinit(&pack->translation_array);
for(i = 0; i < pack->language_cnt; i++) {
lv_free((void *)pack->languages[i]);
}
lv_free(pack->languages);
}
}
lv_ll_clear(&packs_ll);
lv_free((void *)selected_lang);
}
lv_translation_pack_t * lv_translation_add_static(const char * languages[], const char * tags[],
const char * translations[])
{
LV_ASSERT_NULL(languages);
LV_ASSERT_NULL(tags);
LV_ASSERT_NULL(translations);
lv_translation_pack_t * pack = lv_ll_ins_head(&packs_ll);
LV_ASSERT_MALLOC(pack);
if(pack == NULL) return NULL;
lv_memzero(pack, sizeof(lv_translation_pack_t));
pack->is_static = 1;
/*Count the languages*/
while(languages[pack->language_cnt]) {
pack->language_cnt++;
}
pack->languages = languages;
pack->tag_p = tags;
pack->translation_p = translations;
return pack;
}
lv_translation_pack_t * lv_translation_add_dynamic(void)
{
lv_translation_pack_t * pack = lv_ll_ins_head(&packs_ll);
LV_ASSERT_MALLOC(pack);
if(pack == NULL) return NULL;
lv_memzero(pack, sizeof(lv_translation_pack_t));
pack->is_static = 0;
lv_array_init(&pack->translation_array, 16, sizeof(lv_translation_tag_dsc_t));
return pack;
}
void lv_translation_set_language(const char * lang)
{
if(selected_lang) lv_free((void *)selected_lang);
selected_lang = lv_strdup(lang);
}
const char * lv_translation_get(const char * tag)
{
if(selected_lang == NULL) {
LV_LOG_WARN("No language is selected to get the translation of `%s`", tag);
return tag;
}
lv_translation_pack_t * pack;
bool lang_found = false;
LV_LL_READ(&packs_ll, pack) {
uint32_t lang;
for(lang = 0; lang < pack->language_cnt; lang++) {
/*Does this pack contains the language?*/
if(lv_streq(pack->languages[lang], selected_lang)) {
lang_found = true;
/*Find the tag*/
if(pack->is_static) {
uint32_t t;
for(t = 0; pack->tag_p[t]; t++) {
if(lv_streq(pack->tag_p[t], tag)) {
/*Find the "row" of the tag */
const char ** tr_row = pack->translation_p + pack->language_cnt * t;
const char * tr = tr_row[lang];
if(tr) return tr; /*Found directly*/
LV_LOG_WARN("`%s` tag is not found. Using the tag as translation.", tag);
return tag; /*Return the tag as a fall back*/
}
}
}
else {
size_t trans_cnt = lv_array_size(&pack->translation_array);
size_t i;
for(i = 0; i < trans_cnt; i++) {
lv_translation_tag_dsc_t * tag_dsc = lv_array_at(&pack->translation_array, i);
if(lv_streq(tag_dsc->tag, tag)) {
const char * tr = tag_dsc->translations[lang];
if(tr) return tr; /*Found directly*/
LV_LOG_WARN("`%s` tag is not found. Using the tag as translation.", tag);
return tag; /*Return the tag as a worst case option*/
}
}
}
}
}
}
if(lang_found) {
LV_LOG_WARN("`%s` tag is not found, using the tag as translation.", tag);
}
else {
LV_LOG_WARN("`%s` language is not found, using the `%s` as translation.", selected_lang, tag);
}
return tag;
}
lv_result_t lv_translation_add_language(lv_translation_pack_t * pack, const char * lang)
{
if(pack->is_static) {
LV_LOG_WARN("Can't add language `%s` to static translation pack `%p`", lang, (void *)pack);
return LV_RESULT_INVALID;
}
pack->language_cnt++;
pack->languages = lv_realloc(pack->languages, sizeof(const char *) * pack->language_cnt);
LV_ASSERT_MALLOC(pack->languages);
if(pack->languages == NULL) {
LV_LOG_WARN("Couldn't allocate languages in `%p`", (void *)pack);
return LV_RESULT_INVALID;
}
pack->languages[pack->language_cnt - 1] = lv_strdup(lang);
LV_ASSERT_MALLOC(pack->languages[pack->language_cnt - 1]);
if(pack->languages[pack->language_cnt - 1] == NULL) {
LV_LOG_WARN("Couldn't allocate the new language in `%p`", (void *)pack);
return LV_RESULT_INVALID;
}
return LV_RESULT_OK;
}
int32_t lv_translation_get_language_index(lv_translation_pack_t * pack, const char * lang_name)
{
uint32_t i;
for(i = 0; i < pack->language_cnt; i++) {
if(lv_streq(pack->languages[i], lang_name)) return (int32_t)i;
}
return -1;
}
lv_translation_tag_dsc_t * lv_translation_add_tag(lv_translation_pack_t * pack, const char * tag_name)
{
if(pack->is_static) {
LV_LOG_WARN("Can't add tag `%s` to static translation pack `%p`", tag_name, (void *)pack);
return LV_RESULT_INVALID;
}
lv_translation_tag_dsc_t tag;
tag.tag = lv_strdup(tag_name);
LV_ASSERT_MALLOC(tag.tag);
tag.translations = lv_zalloc(pack->language_cnt * sizeof(const char *));
LV_ASSERT_MALLOC(tag.translations);
if(tag.tag == NULL || tag.translations == NULL) {
LV_LOG_WARN("Couldn't allocate memory for the tag's data in `%p`", (void *)pack);
lv_free((void *)tag.tag);
lv_free((void *)tag.translations);
return NULL;
}
lv_result_t res = lv_array_push_back(&pack->translation_array, &tag);
if(res != LV_RESULT_OK) {
LV_LOG_WARN("Couldn't add the tag in `%p`", (void *)pack);
lv_free((void *)tag.tag);
lv_free((void *)tag.translations);
return NULL;
}
return lv_array_back(&pack->translation_array);
}
lv_result_t lv_translation_set_tag_translation(lv_translation_pack_t * pack, lv_translation_tag_dsc_t * tag,
uint32_t lang_idx, const char * trans)
{
if(pack->is_static) {
LV_LOG_WARN("Can't set tag translation`%s` in static translation pack `%p`", trans, (void *)pack);
return LV_RESULT_INVALID;
}
if(lang_idx >= pack->language_cnt) {
LV_LOG_WARN("Can't set the translation for language %" LV_PRIu32 " as there are only %" LV_PRIu32
" languages defined in %p",
lang_idx, pack->language_cnt, (void *)pack);
return LV_RESULT_INVALID;
}
lv_free((void *)tag->translations[lang_idx]); /*Free the earlier set language if any*/
tag->translations[lang_idx] = lv_strdup(trans);
if(tag->translations[lang_idx] == NULL) {
LV_LOG_WARN("Couldn't allocate the new translation in tag `%p` in pack `%p`", (void *)tag, (void *) pack);
return LV_RESULT_INVALID;
}
return LV_RESULT_OK;
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_TRANSLATION*/
+140
View File
@@ -0,0 +1,140 @@
/**
* @file lv_translation.h
*
*/
#ifndef LV_TRANSLATION_H
#define LV_TRANSLATION_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_TRANSLATION
#include LV_STDINT_INCLUDE
#include "../../misc/lv_array.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Initialize the translation module
*/
void lv_translation_init(void);
/**
* De-initialize the translation module and free all allocated translations
*/
void lv_translation_deinit(void);
/**
* Register a translation pack from static arrays.
* All the pointers need to be static, that is to live while they are used
* @param languages List of languages. E.g. `{"en", "de", NULL}`
* @param tags Tags that are using in the UI. E.g. `{"dog", "cat", NULL}`
* @param translations List of translations. E.g. `{"Dog", "Cat", "Hund", "Katze"}`
* @return The created pack
*/
lv_translation_pack_t * lv_translation_add_static(const char * languages[], const char * tags[],
const char * translations[]);
/**
* Add a pack to which translations can be added dynamically.
* `pack->languages` needs to be a malloc-ed array where each language is also malloc-ed as an element.
* `pack->translation_array` stores the translation having `lv_translation_tag_dsc_t` items
* In each array element `tag` is a malloced string, `translations` is a malloc-ed array
* with malloc-ed array for each element.
* @return the created pack to which data can be added manually.
*/
lv_translation_pack_t * lv_translation_add_dynamic(void);
/**
* Select the current language
* @param lang a string from the defined languages. E.g. "en" or "de"
*/
void lv_translation_set_language(const char * lang);
/**
* Get the translated version of a tag on the selected language
* @param tag the tag to translate
* @return the translation
* @note fallback rules:
* - if the tag is found on the selected language return it
* - if the tag is not found on the selected language, use the fist language
* - if the tag is not found on the first language, return the tag
*/
const char * lv_translation_get(const char * tag);
/**
* Shorthand of lv_translation_set_language
* @param tag the tag to translate
* @return the translation
*/
static inline const char * lv_tr(const char * tag)
{
return lv_translation_get(tag);
}
/**
* Add a new language to a dynamic language pack.
* All languages should be added before adding tags
* @param pack pointer to a dynamic translation pack
* @param lang language to add, e.g. "en", or "de"
* @return LV_RESULT_OK: success, LV_RESULT_INVALID: failed
*/
lv_result_t lv_translation_add_language(lv_translation_pack_t * pack, const char * lang);
/**
* Get the index of a language in a pack.
* @param pack pointer to a static or dynamic language pack
* @param lang_name name of the language to find
* @return index of the language or -1 if not found.
*/
int32_t lv_translation_get_language_index(lv_translation_pack_t * pack, const char * lang_name);
/**
* Add a new tag to a dynamic language pack.
* Once the tag is added the translations for each language can be added too by using
* `lv_translation_set_tag_translation`
* @param pack pointer to a dynamic translation pack
* @param tag_name name of the tag, e.g. "dog", or "house"
* @return pointer to the allocated tag descriptor
*/
lv_translation_tag_dsc_t * lv_translation_add_tag(lv_translation_pack_t * pack, const char * tag_name);
/**
* Add a translation to a tag in a dynamic translation pack
* @param pack pointer to a dynamic translation pack
* @param tag return value of `lv_translation_add_tag`
* @param lang_idx index of the language for which translation should be set
* @param trans the translation on the given language
* @return LV_RESULT_OK: success, LV_RESULT_INVALID: failed
*/
lv_result_t lv_translation_set_tag_translation(lv_translation_pack_t * pack, lv_translation_tag_dsc_t * tag,
uint32_t lang_idx, const char * trans);
/**********************
* MACROS
**********************/
#endif /*LV_USE_TRANSLATION*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* LV_TRANSLATION_H */
@@ -0,0 +1,59 @@
/**
* @file lv_translation_private.h
*
*/
#ifndef LV_TRANSLATION_PRIVATE_H
#define LV_TRANSLATION_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_TRANSLATION
#include LV_STDINT_INCLUDE
#include "../../misc/lv_array.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
struct _lv_translation_tag_dsc_t {
const char * tag;
const char ** translations; /**< Translations for each language*/
};
struct _lv_translation_pack_t {
const char ** languages;
uint32_t language_cnt;
uint32_t is_static; /*In the union translations_p is used*/
const char ** tag_p;
const char ** translation_p; /*E.g. {{"a", "b"}, {"c", "d"}}*/
lv_array_t translation_array;
};
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#endif /*LV_USE_TRANSLATION*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* LV_TRANSLATION_PRIVATE_H */
+2 -1
View File
@@ -10,13 +10,14 @@
#include "lv_xml.h"
#if LV_USE_XML
#include "lv_xml.h"
#include "lv_xml_base_types.h"
#include "lv_xml_parser.h"
#include "lv_xml_component.h"
#include "lv_xml_component_private.h"
#include "lv_xml_widget.h"
#include "lv_xml_style.h"
#include "lv_xml.h"
#include "lv_xml_translation.h"
#include "lv_xml_utils.h"
#include "lv_xml_private.h"
#include "parsers/lv_xml_obj_parser.h"
+4 -2
View File
@@ -14,11 +14,13 @@ extern "C" {
* INCLUDES
*********************/
#include "../../misc/lv_types.h"
#include "../../misc/lv_event.h"
#include "../../others/observer/lv_observer.h"
#if LV_USE_XML
#include "../../misc/lv_event.h"
#include "../../others/observer/lv_observer.h"
#include "lv_xml_test.h"
#include "lv_xml_translation.h"
#include "lv_xml_component.h"
/*********************
* DEFINES
+180
View File
@@ -0,0 +1,180 @@
/**
* @file lv_xml_translation.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../lvgl.h"
#if LV_USE_XML && LV_USE_TRANSLATION
#include "../translation/lv_translation_private.h"
#include "lv_xml_widget.h"
#include "lv_xml_parser.h"
#include "../../others/translation/lv_translation.h"
#include "../../libs/expat/expat.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void start_handler(void * user_data, const char * name, const char ** attrs);
static void end_handler(void * user_data, const char * name);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_result_t lv_xml_translation_register_from_file(const char * path)
{
lv_fs_res_t fs_res;
lv_fs_file_t f;
fs_res = lv_fs_open(&f, path, LV_FS_MODE_RD);
if(fs_res != LV_FS_RES_OK) {
LV_LOG_WARN("Couldn't open %s", path);
return LV_RESULT_INVALID;
}
/* Determine file size */
lv_fs_seek(&f, 0, LV_FS_SEEK_END);
uint32_t file_size = 0;
lv_fs_tell(&f, &file_size);
lv_fs_seek(&f, 0, LV_FS_SEEK_SET);
/* Create the buffer */
char * xml_buf = lv_malloc(file_size + 1);
LV_ASSERT_MALLOC(xml_buf);
if(xml_buf == NULL) {
LV_LOG_WARN("Memory allocation failed for file %s (%d bytes)", path, file_size + 1);
lv_fs_close(&f);
return LV_RESULT_INVALID;
}
/* Read the file content */
uint32_t rn;
lv_fs_read(&f, xml_buf, file_size, &rn);
if(rn != file_size) {
LV_LOG_WARN("Couldn't read %s fully", path);
lv_free(xml_buf);
lv_fs_close(&f);
return LV_RESULT_INVALID;
}
/* Null-terminate the buffer */
xml_buf[rn] = '\0';
/* Register the component */
lv_result_t res = lv_xml_translation_register_from_data(xml_buf);
/* Housekeeping */
lv_free(xml_buf);
lv_fs_close(&f);
return res;
}
lv_result_t lv_xml_translation_register_from_data(const char * xml_def)
{
lv_translation_pack_t * pack = lv_translation_add_dynamic();
/* Parse the XML to extract metadata */
XML_Parser parser = XML_ParserCreate(NULL);
XML_SetUserData(parser, pack);
XML_SetElementHandler(parser, start_handler, end_handler);
if(XML_Parse(parser, xml_def, lv_strlen(xml_def), XML_TRUE) == XML_STATUS_ERROR) {
LV_LOG_ERROR("XML parsing error: %s on line %lu",
XML_ErrorString(XML_GetErrorCode(parser)),
(unsigned long)XML_GetCurrentLineNumber(parser));
XML_ParserFree(parser);
return LV_RESULT_INVALID;
}
XML_ParserFree(parser);
return LV_RESULT_OK;
}
/**********************
* STATIC FUNCTIONS
**********************/
static void start_handler(void * user_data, const char * name, const char ** attrs)
{
lv_translation_pack_t * pack = user_data;
if(lv_streq(name, "translations")) {
const char * languages = lv_xml_get_value_of(attrs, "languages");
if(languages == NULL) {
LV_LOG_WARN("`languages` are not set in `translations`");
return;
}
char buf[512];
char * bufp = buf;
lv_strlcpy(buf, languages, sizeof(buf));
bufp = buf;
lv_result_t res = LV_RESULT_OK;
while(bufp[0]) {
const char * lang = lv_xml_split_str(&bufp, ' ');
res = lv_translation_add_language(pack, lang);
if(res != LV_RESULT_OK) {
LV_LOG_WARN("Couldn't add language `%s`", lang);
return;
}
}
}
else if(lv_streq(name, "translation")) {
if(pack->language_cnt == 0 || pack->languages == NULL) {
LV_LOG_WARN("`No languages were found, <translations languages=\"...\" was not set>`");
return;
}
const char * tag_name = lv_xml_get_value_of(attrs, "tag");
if(tag_name == NULL) {
LV_LOG_WARN("`tag` is missing from the translation");
return;
}
lv_translation_tag_dsc_t * tag = lv_translation_add_tag(pack, tag_name);
LV_ASSERT_NULL(tag);
if(tag == NULL) {
LV_LOG_WARN("Couldn't add tag `%s`", tag_name);
return;
}
uint32_t i;
for(i = 0; i < pack->language_cnt; i++) {
const char * trans = lv_xml_get_value_of(attrs, pack->languages[i]);
if(trans == NULL) {
LV_LOG_WARN("`%s` language is missing from tag `%s`", pack->languages[i], tag_name);
continue;
}
lv_result_t res = lv_translation_set_tag_translation(pack, tag, i, trans);
if(res != LV_RESULT_OK) {
LV_LOG_WARN("Couldn't set translation `%s` in tag `%s`", trans, tag_name);
return;
}
}
}
}
static void end_handler(void * user_data, const char * name)
{
LV_UNUSED(user_data);
LV_UNUSED(name);
}
#endif /* LV_USE_XML */
+53
View File
@@ -0,0 +1,53 @@
/**
* @file lv_xml_translation.h
*
*/
#ifndef LV_XML_TRANSLATION_H
#define LV_XML_TRANSLATION_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../misc/lv_types.h"
#if LV_USE_XML && LV_USE_TRANSLATION
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Register translations from an XML file
* @param path path to an XML file (staring with a driver letter)
* @return LV_RES_OK: no error
*/
lv_result_t lv_xml_translation_register_from_file(const char * path);
/**
* Register translations from an XML string
* @param xml_def the XML definition as a string
* @return LV_RES_OK: no error
*/
lv_result_t lv_xml_translation_register_from_data(const char * xml_def);
/**********************
* MACROS
**********************/
#endif /* LV_USE_XML */
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_XML_TRANSLATION_H*/
@@ -58,6 +58,10 @@ void lv_xml_label_apply(lv_xml_parser_state_t * state, const char ** attrs)
if(lv_streq("text", name)) lv_label_set_text(item, value);
else if(lv_streq("long_mode", name)) lv_label_set_long_mode(item, long_mode_text_to_enum_value(value));
#if LV_USE_TRANSLATION
if(lv_streq("text-translated", name)) lv_label_set_text(item, lv_tr(value));
#endif
if(lv_streq("long_mode", name)) lv_label_set_long_mode(item, long_mode_text_to_enum_value(value));
else if(lv_streq("bind_text", name)) {
lv_subject_t * subject = lv_xml_get_subject(&state->scope, value);
if(subject == NULL) {
+1
View File
@@ -110,6 +110,7 @@
#define LV_PROFILER_INCLUDE "lv_profiler_builtin.h"
#define LV_USE_GRIDNAV 1
#define LV_USE_XML 1
#define LV_USE_TRANSLATION 1
#define LV_USE_TEST 1
#define LV_USE_TEST_SCREENSHOT_COMPARE 1
@@ -0,0 +1,60 @@
#if LV_BUILD_TEST
#include "../lvgl.h"
#include "unity/unity.h"
void setUp(void)
{
/* Function run before every test */
}
void tearDown(void)
{
/* Function run after every test */
lv_obj_clean(lv_screen_active());
}
void test_xml_translation(void)
{
const char * translations_xml = {
"<translations languages=\"en de hu\">"
" <translation tag=\"dog\" en=\"The dog\" de=\"Der Hund\" hu=\"A kutya\"/>"
" <translation tag=\"cat\" en=\"The cat\" hu=\"A cica\"/>"
" <translation tag=\"snake\" en=\"A snake\" de=\"Eine Schlange\" hu=\"A kígyó\"/>"
"</translations>"
};
lv_xml_translation_register_from_data(translations_xml);
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_pack_t * pack = lv_translation_add_static(languages, tags, translations);
TEST_ASSERT_EQUAL_INT(1, lv_translation_get_language_index(pack, "de"));
TEST_ASSERT_EQUAL_INT(-1, lv_translation_get_language_index(pack, "none"));
lv_translation_set_language("de");
TEST_ASSERT_EQUAL_STRING("Der Hund", lv_translation_get("dog"));
TEST_ASSERT_EQUAL_STRING("Der Löwe", lv_translation_get("lion"));
/*The tag the fallback if not defined for the selected language*/
TEST_ASSERT_EQUAL_STRING("cat", lv_translation_get("cat"));
/*Use the tag if the tag is not found*/
TEST_ASSERT_EQUAL_STRING("foo", lv_tr("foo"));
lv_translation_set_language("es");
TEST_ASSERT_EQUAL_STRING("El Conejo", lv_tr("rabbit"));
}
#endif
+1 -1
View File
@@ -5,7 +5,7 @@ Example
<widget>
<api>
<enumdef name="lv_buttonamtrix_ctrl" multi="true" help="control flags for the buttons">
<enumdef name="lv_buttonmatrix_ctrl" multi="true" help="control flags for the buttons">
<enum name="none" help="No special control"/>
<enum name="width_1" help="Relative width is 1 in its row"/>
<enum name="width_2" help="Relative width is 2 in its row"/>
+2 -1
View File
@@ -13,7 +13,8 @@ Example
<enum name="dots" help=""/>
</enumdef>
<prop name="text" type="string" />
<prop name="text" type="string" />
<prop name="text-translated" type="string" />
<prop name="long_mode" type="enum:lv_label_long_mode" />
<prop name="bind_text">
<param name="bind_text" type="subject" />
+3 -2
View File
@@ -26,7 +26,7 @@ Example
<enum name="adv_hittest" help="Allow performing more accurate hit (click) test. E.g. consider rounded corners."/>
<enum name="ignore_layout" help="Make the object not positioned by the layouts"/>
<enum name="floating" help="Do not scroll the object when the parent scrolls and ignore layout"/>
<enum name="send_draw_task_evenTS" help="Send `LV_EVENT_DRAW_TASK_ADDED` events"/>
<enum name="send_draw_task_events" help="Send `LV_EVENT_DRAW_TASK_ADDED` events"/>
<enum name="overflow_visible" help="Do not clip the children to the parent's ext draw size"/>
<enum name="flex_in_new_track" help="Start a new flex track on this item"/>
<enum name="layout_1" help="Custom flag, free to use by layouts"/>
@@ -61,7 +61,8 @@ Example
<element name="event_cb" access="add">
<arg name="name" type="string"/>
<arg name="trigger" type="lv_event" default="clicked"/>
<arg name="cb" type="event_cb"/>
<arg name="callback" type="event_cb"/>
<arg name="user_data" type="string" default="NULL"/>
</element>
<element name="screen_load_event" access="add">