diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eabec4a --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +extract: + lv_i18n extract -s 'src/**/*.+(c|cpp|h|hpp)' -t 'translations/*.yml' + +compile: + lv_i18n compile -t 'translations/*.yml' -o 'src/lv_i18n' diff --git a/src/lv_i18n/lv_i18n.c b/src/lv_i18n/lv_i18n.c new file mode 100644 index 0000000..529298c --- /dev/null +++ b/src/lv_i18n/lv_i18n.c @@ -0,0 +1,221 @@ +#include "./lv_i18n.h" + + +//////////////////////////////////////////////////////////////////////////////// +// Define plural operands +// http://unicode.org/reports/tr35/tr35-numbers.html#Operands + +// Integer version, simplified + +#define UNUSED(x) (void)(x) + +static inline uint32_t op_n(int32_t val) { return (uint32_t)(val < 0 ? -val : val); } +static inline uint32_t op_i(uint32_t val) { return val; } +// always zero, when decimal part not exists. +static inline uint32_t op_v(uint32_t val) { UNUSED(val); return 0;} +static inline uint32_t op_w(uint32_t val) { UNUSED(val); return 0; } +static inline uint32_t op_f(uint32_t val) { UNUSED(val); return 0; } +static inline uint32_t op_t(uint32_t val) { UNUSED(val); return 0; } + +static lv_i18n_phrase_t en_gb_singulars[] = { + {"appName", "OS-Railway EN"}, + {NULL, NULL} // End mark +}; + + + +static uint8_t en_gb_plural_fn(int32_t num) +{ + uint32_t n = op_n(num); UNUSED(n); + uint32_t i = op_i(n); UNUSED(i); + uint32_t v = op_v(n); UNUSED(v); + + if ((i == 1 && v == 0)) return LV_I18N_PLURAL_TYPE_ONE; + return LV_I18N_PLURAL_TYPE_OTHER; +} + +static const lv_i18n_lang_t en_gb_lang = { + .locale_name = "en-GB", + .singulars = en_gb_singulars, + + .locale_plural_fn = en_gb_plural_fn +}; + +static lv_i18n_phrase_t de_de_singulars[] = { + {"appName", "OS-Railway DE"}, + {NULL, NULL} // End mark +}; + + + +static uint8_t de_de_plural_fn(int32_t num) +{ + uint32_t n = op_n(num); UNUSED(n); + uint32_t i = op_i(n); UNUSED(i); + uint32_t v = op_v(n); UNUSED(v); + + if ((i == 1 && v == 0)) return LV_I18N_PLURAL_TYPE_ONE; + return LV_I18N_PLURAL_TYPE_OTHER; +} + +static const lv_i18n_lang_t de_de_lang = { + .locale_name = "de-DE", + .singulars = de_de_singulars, + + .locale_plural_fn = de_de_plural_fn +}; + +const lv_i18n_language_pack_t lv_i18n_language_pack[] = { + &en_gb_lang, + &de_de_lang, + NULL // End mark +}; + +//////////////////////////////////////////////////////////////////////////////// + + +// Internal state +static const lv_i18n_language_pack_t * current_lang_pack; +static const lv_i18n_lang_t * current_lang; + + +/** + * Reset internal state. For testing. + */ +void __lv_i18n_reset(void) +{ + current_lang_pack = NULL; + current_lang = NULL; +} + +/** + * Set the languages for internationalization + * @param langs pointer to the array of languages. (Last element has to be `NULL`) + */ +int lv_i18n_init(const lv_i18n_language_pack_t * langs) +{ + if(langs == NULL) return -1; + if(langs[0] == NULL) return -1; + + current_lang_pack = langs; + current_lang = langs[0]; /*Automatically select the first language*/ + return 0; +} + +/** + * Change the localization (language) + * @param l_name name of the translation locale to use. E.g. "en-GB" + */ +int lv_i18n_set_locale(const char * l_name) +{ + if(current_lang_pack == NULL) return -1; + + uint16_t i; + + for(i = 0; current_lang_pack[i] != NULL; i++) { + // Found -> finish + if(strcmp(current_lang_pack[i]->locale_name, l_name) == 0) { + current_lang = current_lang_pack[i]; + return 0; + } + } + + return -1; +} + + +static const char * __lv_i18n_get_text_core(lv_i18n_phrase_t * trans, const char * msg_id) +{ + uint16_t i; + for(i = 0; trans[i].msg_id != NULL; i++) { + if(strcmp(trans[i].msg_id, msg_id) == 0) { + /*The msg_id has found. Check the translation*/ + if(trans[i].translation) return trans[i].translation; + } + } + + return NULL; +} + + +/** + * Get the translation from a message ID + * @param msg_id message ID + * @return the translation of `msg_id` on the set local + */ +const char * lv_i18n_get_text(const char * msg_id) +{ + if(current_lang == NULL) return msg_id; + + const lv_i18n_lang_t * lang = current_lang; + const void * txt; + + // Search in current locale + if(lang->singulars != NULL) { + txt = __lv_i18n_get_text_core(lang->singulars, msg_id); + if (txt != NULL) return txt; + } + + // Try to fallback + if(lang == current_lang_pack[0]) return msg_id; + lang = current_lang_pack[0]; + + // Repeat search for default locale + if(lang->singulars != NULL) { + txt = __lv_i18n_get_text_core(lang->singulars, msg_id); + if (txt != NULL) return txt; + } + + return msg_id; +} + +/** + * Get the translation from a message ID and apply the language's plural rule to get correct form + * @param msg_id message ID + * @param num an integer to select the correct plural form + * @return the translation of `msg_id` on the set local + */ +const char * lv_i18n_get_text_plural(const char * msg_id, int32_t num) +{ + if(current_lang == NULL) return msg_id; + + const lv_i18n_lang_t * lang = current_lang; + const void * txt; + lv_i18n_plural_type_t ptype; + + // Search in current locale + if(lang->locale_plural_fn != NULL) { + ptype = lang->locale_plural_fn(num); + + if(lang->plurals[ptype] != NULL) { + txt = __lv_i18n_get_text_core(lang->plurals[ptype], msg_id); + if (txt != NULL) return txt; + } + } + + // Try to fallback + if(lang == current_lang_pack[0]) return msg_id; + lang = current_lang_pack[0]; + + // Repeat search for default locale + if(lang->locale_plural_fn != NULL) { + ptype = lang->locale_plural_fn(num); + + if(lang->plurals[ptype] != NULL) { + txt = __lv_i18n_get_text_core(lang->plurals[ptype], msg_id); + if (txt != NULL) return txt; + } + } + + return msg_id; +} + +/** + * Get the name of the currently used locale. + * @return name of the currently used locale. E.g. "en-GB" + */ +const char * lv_i18n_get_current_locale(void) +{ + if(!current_lang) return NULL; + return current_lang->locale_name; +} diff --git a/src/lv_i18n/lv_i18n.h b/src/lv_i18n/lv_i18n.h new file mode 100644 index 0000000..9e80353 --- /dev/null +++ b/src/lv_i18n/lv_i18n.h @@ -0,0 +1,85 @@ +#ifndef LV_I18N_H +#define LV_I18N_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef enum { + LV_I18N_PLURAL_TYPE_ZERO, + LV_I18N_PLURAL_TYPE_ONE, + LV_I18N_PLURAL_TYPE_TWO, + LV_I18N_PLURAL_TYPE_FEW, + LV_I18N_PLURAL_TYPE_MANY, + LV_I18N_PLURAL_TYPE_OTHER, + _LV_I18N_PLURAL_TYPE_NUM, +} lv_i18n_plural_type_t; + +typedef struct { + const char * msg_id; + const char * translation; +} lv_i18n_phrase_t; + +typedef struct { + const char * locale_name; + lv_i18n_phrase_t * singulars; + lv_i18n_phrase_t * plurals[_LV_I18N_PLURAL_TYPE_NUM]; + uint8_t (*locale_plural_fn)(int32_t num); +} lv_i18n_lang_t; + +// Null-terminated list of languages. First one used as default. +typedef const lv_i18n_lang_t * lv_i18n_language_pack_t; + + +extern const lv_i18n_language_pack_t lv_i18n_language_pack[]; + + +/** + * Set the languages for internationalization + * @param langs pointer to the array of languages. (Last element has to be `NULL`) + */ +int lv_i18n_init(const lv_i18n_language_pack_t * langs); + +/** + * Change the localization (language) + * @param l_name name of the translation locale to use. E.g. "en_GB" + */ +int lv_i18n_set_locale(const char * l_name); + +/** + * Get the translation from a message ID + * @param msg_id message ID + * @return the translation of `msg_id` on the set local + */ +const char * lv_i18n_get_text(const char * msg_id); + +/** + * Get the translation from a message ID and apply the language's plural rule to get correct form + * @param msg_id message ID + * @param num an integer to select the correct plural form + * @return the translation of `msg_id` on the set local + */ +const char * lv_i18n_get_text_plural(const char * msg_id, int32_t num); + +/** + * Get the name of the currently used localization. + * @return name of the currently used localization. E.g. "en_GB" + */ +const char * lv_i18n_get_current_locale(void); + + +void __lv_i18n_reset(void); + + +#define _(text) lv_i18n_get_text(text) +#define _p(text, num) lv_i18n_get_text_plural(text, num) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_LANG_H*/ diff --git a/src/main.cpp b/src/main.cpp index 90f7c7d..375a6e3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,10 +19,14 @@ #include #include "gfx/lv_setup.h" +#include "lv_i18n/lv_i18n.h" #include "ui/launch_screen.h" void setup() { + lv_i18n_init(lv_i18n_language_pack); + lv_i18n_set_locale("de-DE"); + lv_begin(); ui_LaunchScreen_open(); } diff --git a/src/ui/launch_screen.cpp b/src/ui/launch_screen.cpp index cd8ad6c..3960b0e 100644 --- a/src/ui/launch_screen.cpp +++ b/src/ui/launch_screen.cpp @@ -1,6 +1,7 @@ #include "ui/launch_screen.h" #include +#include "lv_i18n/lv_i18n.h" void ui_LaunchScreen_open() { @@ -14,7 +15,7 @@ void ui_LaunchScreen_open() lv_obj_align(img, LV_ALIGN_CENTER, 0, 0); auto *label = lv_label_create(ui_SplashScreen); - lv_label_set_text(label, "OS-Railway"); + lv_label_set_text(label, _("appName")); lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, -25); lv_disp_load_scr(ui_SplashScreen); diff --git a/translations/de-DE.yml b/translations/de-DE.yml new file mode 100644 index 0000000..10aae10 --- /dev/null +++ b/translations/de-DE.yml @@ -0,0 +1,2 @@ +de-DE: + appName: OS-Railway DE diff --git a/translations/en-GB.yml b/translations/en-GB.yml new file mode 100644 index 0000000..297e918 --- /dev/null +++ b/translations/en-GB.yml @@ -0,0 +1,2 @@ +en-GB: + appName: OS-Railway EN