Lightweight gettext replacement tools for C. Add multi-language support to your embedded projects with ease.
- Mark up the text in your C files as
_("some text")
(singular) and_p("%d item", item_cnt)
(plural) - Create template
yml
files for the translations you are interested in - Run
extract
to fill theyml
files with the texts in_()
and_p()
- Add the translations into the
yml
files - Run
compile
to convert theyml
files to a C and H file. They will contain the translations and all the background functions you need. - Be sure your fonts contain the required characters. See the docs here for more details.
node.js required.
Global install of the last version, execute as "lv_i18n"
npm i lv_i18n -g
Alternatives:
Install from github's repo, master branch
npm i littlevgl/lv_i18n -g
If you wish local install for your project, do in project root:
npm init private
npm i lv_i18n -s
# now available at ./node_modules/.bin/lv_i18n
Then commit package.json
& put /node_modules
into .gitignore
. Next time
use just npm i
to install.
run via npx
node.js
has built-in npx utility to
execute packages "without install":
# run from github master
npx github:littlevgl/lv_i18n -h
# run from npm registry
npx lv_i18n -h
#include "lv_i18n/lv_i18n.h" /*Assuming you have the translations here. (See below)*/
/* Load translations & default locale (usually, done once) */
lv_i18n_init(lv_i18n_language_pack);
/* Set active locale (can be switched anytime) */
lv_i18n_set_locale("ru-RU");
/* The translation of "title1" will be returned according to the selected locale.
* ("title1" is only a unique ID of the text.) Example:
* en-GB: "Main menu"
* ru_RU: "Главное меню"
*/
gui_set_text(label, _("title1"));
/* According to `user_cnt` different text can be returned
* en-GB `user_cnt == 1` : "One user is logged in"
* `user_cnt == 6` : "%d users are logged in"
*/
char buf[64];
sprintf(buf, _p("user_logged_in", user_cnt)), user_cnt); /*E.g. buf == "7 users are logged in"*/
gui_set_text(label, buf);
_
and _p
are normal functions. They just have this short name to enable fast typing of texts.
Rules of getting the translation:
- If the translation is not available on the selected locale then the default language will be used instead
- If the translation is not available on the default locale the text ID ("title1" in the example) will be returned
For each translation, you need to create a yml
file with "language code" name. For example:
- en-GB.yml
- ru-RU.yml
Here is a list of the language and locale codes.
Add the locale-name: ~
line to the yml
files. Replace "locale-name" with the actual language code.
E.g.: en-GB: ~
or simply en: ~
Technically you can have one yml
file where you list all language codes you need but its more modular to separate them.
Run extract
like this (assuming your source files are in the src
folder and the yml
files in the translations folder):
lv_i18n extract -s 'src/**/*.+(c|cpp|h|hpp)' -t 'translations/*.yml'
It will fill the yml
files the texts marked with _
and _p
.
For example:
en-GB:
title1: ~
user_logged_in:
one: ~
other: ~
The naming conventions in the yml
files follow the rules of CLDR so most of the translation offices will know them.
Example:
'en-GB':
title1: Main menu
user_logged_in:
one: One user is logged in
other: '%d users are logged in'
If translators want to know where a message comes from, then use lv_i18n extract --dump-sourceref sr.json ...
to generate the file sr.json
containing file names and line number of each message.
Once you have the translations in the yml
files you only need to run the compile
to generate a C and H files from the yml
files. No other library will be required to get the translation with _()
and _p
.
Running compile
:
lv_i18n compile -t 'translations/*.yml' -o 'src/lv_i18n'
The default locale is en-GB
but you change it with -l 'language-code'
.
You can use --optimize
to generate optimized C code. Without this, finding the corresponding translation by lv_i18n_get_text()
is done by searching through all keys until the right one is found. This can eat up a lot of CPU espcially if the list is long (aka O(n)). Using --optimize
changes this behaviour by using an integer index into the list of strings resulting in an immediate return of the right string (aka O(1)). As the index is computed at compile time, you need a compiler, which is able to evaluate strcmp()
with constants at compile time. All modern compilers, like gcc and clang are able to do this. If you want to check, whether your compiler is able to handle this optimization, you can use the following code to check this:
int main()
{
return strcmp("a", "a");
}
If this compiles without needing #include <string.h>
and nm -u a.out
does not output strcmp
as being undefined, then the compiler optimizes the code and is able to handle --optimize
.
To change a text id in the yml
files use:
lv_i18n rename -t src/i18n/*.yml --from 'Hillo wold' --to 'Hello world!'
Attach generated translations to be used by lv_i18n_get_text()
.
- return - 0 on success, -1 on fail.
Set locale to be used by lv_i18n_get_text()
.
- l_name - locale name (
en-GB
,ru-RU
). - returns - 0 on success, -1 if locale not found.
Mapped to _(...)
or _t(...)
via #define
Get translated text. If not translated, return fallback (try default locale first, then input param if default not exists)
- msg_id - The ID of a text to translate (e.g.
"title1"
) - return - pointer to the traslation
Mapped to _p(...)
or _tp(...)
via #define
Get the plural form of translated text. Use current locale to select plural algorithm. If not translated, fallback to default locale first, then to input param.
- msg_id - The ID of a text to translate (e.g.
"title1"
) - plural - number of items to decide which plural for to use
- return - pointer to the traslation
To understand i18n principles better, you may find useful links below: