<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>microcontroller Archives | DMC, Inc.</title>
	<atom:link href="https://static.dmcinfo.com/blog/tag/microcontroller/feed/index.xml" rel="self" type="application/rss+xml" />
	<link></link>
	<description></description>
	<lastBuildDate>Tue, 04 Aug 2026 21:07:05 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>

<image>
	<url>https://static.dmcinfo.com/wp-content/uploads/2025/04/site-icon-150x150.png</url>
	<title>microcontroller Archives | DMC, Inc.</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>LVGL for International GUI Design</title>
		<link>https://static.dmcinfo.com/blog/17764/lvgl-for-international-gui-design/</link>
		
		<dc:creator><![CDATA[Ben Dyer]]></dc:creator>
		<pubDate>Wed, 01 Mar 2023 10:12:27 +0000</pubDate>
				<category><![CDATA[Embedded Development & Programming]]></category>
		<category><![CDATA[User Interface Design]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[embedded]]></category>
		<category><![CDATA[i18n]]></category>
		<category><![CDATA[internationalization]]></category>
		<category><![CDATA[LVGL]]></category>
		<category><![CDATA[microcontroller]]></category>
		<category><![CDATA[multi-language]]></category>
		<category><![CDATA[user interface]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/17764/lvgl-for-international-gui-design/</guid>

					<description><![CDATA[<p>Creating a high-quality user interface that can run on embedded systems is a challenging task, and that’s doubly (or perhaps triply) true if it’s intended for an international user base. As with any difficult job, however, it becomes much more manageable with the right tools. Read on to learn the basics of one such tool, [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/17764/lvgl-for-international-gui-design/">LVGL for International GUI Design</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><!-- Injected  Highlight.js Code --><script type="text/javascript" src="/Providers/HtmlEditorProviders/CKEditor/plugins/codesnippet/lib/highlight/highlight.pack.js?ver=1790905719"></script><link type="text/css" rel="stylesheet" href="/Providers/HtmlEditorProviders/CKEditor/plugins/codesnippet/lib/highlight/styles/default.css?ver=1790905719"/><script type="text/javascript">window.onload = function() {var aCodes = document.getElementsByTagName('pre');for (var i=0; i < aCodes.length;i++){hljs.highlightBlock(aCodes[i]);} };</script></p>


<p class="wp-block-paragraph">Creating a high-quality user interface that can run on embedded systems is a challenging task, and that’s doubly (or perhaps triply) true if it’s intended for an international user base.</p>



<p class="wp-block-paragraph">As with any difficult job, however, it becomes much more manageable with the right tools. Read on to learn the basics of one such tool, LVGL, and its associated utilities.</p>



<h2 id="h-getting-started-with-lvgl" class="wp-block-heading">Getting Started with LVGL</h2>



<p class="wp-block-paragraph">In order to develop with LVGL, it will need&nbsp;to be ported to your platform of choice.</p>



<p class="wp-block-paragraph">First, pull the latest release of LVGL into a project configured for your target and create “lv_conf.h” from the included template. This file contains a variety of parameters for tweaking the library’s behavior, but they can be left at their default values for now. Add a call to <strong>lv_init</strong>&nbsp;to your program, ensuring it’s in a location where it will occur before any other LVGL functions are called.</p>



<p class="wp-block-paragraph">Next, you will need to define drivers for the display showing your UI and any input devices used to interact with it. The specifics of these drivers will vary greatly from project to project, and LVGL already has excellent documentation on porting, so only the general structure will be covered here.</p>



<p class="wp-block-paragraph">In simple cases, you will only need to allocate some memory to act as a draw buffer, define the resolution of the display, and implement a function to send the information LVGL places into the draw buffer to your screen.&nbsp;The example below shows a basic setup, but additional configuration options exist to implement things like double-buffering or screen rotation.</p>



<p class="wp-block-paragraph"></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">C</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>static lv_color_t draw_buffer&#91;SCREEN_HOR_RES * SCREEN_VER_RES / 10&#93;;
static lv_disp_drv_t disp_drv;
static lv_disp_draw_buf_t disp_buf;

void flush_buffer_callback(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p);

lv_disp_t* lvgl_display_port_init()
{
    //configure any platform specific peripherals here

    //set up draw buffer
    lv_disp_draw_buf_init(&amp;disp_buf, draw_buffer, NULL, SCREEN_HOR_RES * SCREEN_VER_RES / 10);

    //initialize display driver
    lv_disp_drv_init(&amp;disp_drv);

    //configure display driver
    disp_drv.draw_buf = &amp;disp_buf;
    disp_drv.hor_res = SCREEN_HOR_RES;
    disp_drv.ver_res = SCREEN_VER_RES;
    disp_drv.flush_cb = flush_buffer_callback;

    //set optional fields to customize display driver here
   
    //finalize driver setup
    return lv_disp_drv_register(&amp;disp_drv);
}

void flush_buffer_callback(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p)
{
    /* copy pixels from 'color_p' to the area of the screen described by 'area' */
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">static</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">lv_color_t</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">draw_buffer</span><span style="color: #D4D4D4">&#91;SCREEN_HOR_RES * SCREEN_VER_RES / </span><span style="color: #B5CEA8">10</span><span style="color: #D4D4D4">&#93;;</span></span>
<span class="line"><span style="color: #569CD6">static</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">lv_disp_drv_t</span><span style="color: #D4D4D4"> disp_drv;</span></span>
<span class="line"><span style="color: #569CD6">static</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">lv_disp_draw_buf_t</span><span style="color: #D4D4D4"> disp_buf;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">void</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">flush_buffer_callback</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">lv_disp_drv_t</span><span style="color: #D4D4D4">* </span><span style="color: #9CDCFE">disp_drv</span><span style="color: #D4D4D4">, </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">lv_area_t</span><span style="color: #D4D4D4">* </span><span style="color: #9CDCFE">area</span><span style="color: #D4D4D4">, </span><span style="color: #4EC9B0">lv_color_t</span><span style="color: #D4D4D4">* </span><span style="color: #9CDCFE">color_p</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #4EC9B0">lv_disp_t</span><span style="color: #D4D4D4">* </span><span style="color: #DCDCAA">lvgl_display_port_init</span><span style="color: #D4D4D4">()</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//configure any platform specific peripherals here</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//set up draw buffer</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">lv_disp_draw_buf_init</span><span style="color: #D4D4D4">(&amp;disp_buf, draw_buffer, </span><span style="color: #569CD6">NULL</span><span style="color: #D4D4D4">, SCREEN_HOR_RES * SCREEN_VER_RES / </span><span style="color: #B5CEA8">10</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//initialize display driver</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">lv_disp_drv_init</span><span style="color: #D4D4D4">(&amp;disp_drv);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//configure display driver</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">disp_drv</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">draw_buf</span><span style="color: #D4D4D4"> = &amp;disp_buf;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">disp_drv</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">hor_res</span><span style="color: #D4D4D4"> = SCREEN_HOR_RES;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">disp_drv</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">ver_res</span><span style="color: #D4D4D4"> = SCREEN_VER_RES;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">disp_drv</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">flush_cb</span><span style="color: #D4D4D4"> = flush_buffer_callback;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//set optional fields to customize display driver here</span></span>
<span class="line"><span style="color: #D4D4D4">   </span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//finalize driver setup</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">lv_disp_drv_register</span><span style="color: #D4D4D4">(&amp;disp_drv);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">void</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">flush_buffer_callback</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">lv_disp_drv_t</span><span style="color: #D4D4D4">* </span><span style="color: #9CDCFE">disp_drv</span><span style="color: #D4D4D4">, </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">lv_area_t</span><span style="color: #D4D4D4">* </span><span style="color: #9CDCFE">area</span><span style="color: #D4D4D4">, </span><span style="color: #4EC9B0">lv_color_t</span><span style="color: #D4D4D4">* </span><span style="color: #9CDCFE">color_p</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #6A9955">    /* copy pixels from &apos;color_p&apos; to the area of the screen described by &apos;area&apos; */</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>


<pre><code class="language-cpp">
</code></pre>


<p class="wp-block-paragraph"><span id="cke_bm_275C" style="display: none;">&nbsp;</span></p>



<p class="wp-block-paragraph">Configuring an input device follows a similar process. Use <strong>lv_indev_drv_init</strong>&nbsp;to set up the driver, define its type,&nbsp;attach a function for reading its value, and register it using <strong>lv_indev_drv_register</strong>.<span id="cke_bm_399C" style="display: none;">&nbsp;</span></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">C</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>static lv_indev_drv_t indev_drv;
void input_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);

void lvgl_indev_port_init(void);
{
    //configure any necessary platform specific peripherals here

    //set up input device driver
    lv_indev_drv_init(&amp;indev_drv);

    //configure input device driver
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = input_read;
    
    //finalize driver setup
    lv_indev_t* touch_indev = lv_indev_drv_register(&amp;indev_drv);
}

void input_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
    /* Read location from input device (i.e. last touch location on a touchscreen) */

    /* Read state from input device (i.e. is a touchscreen currently being touched) */

    /* Store current location (x, y) and state (pressed, released) in 'data' */
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">static</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">lv_indev_drv_t</span><span style="color: #D4D4D4"> indev_drv;</span></span>
<span class="line"><span style="color: #569CD6">void</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">input_read</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">lv_indev_drv_t</span><span style="color: #D4D4D4"> *</span><span style="color: #9CDCFE">indev_drv</span><span style="color: #D4D4D4">, </span><span style="color: #4EC9B0">lv_indev_data_t</span><span style="color: #D4D4D4"> *</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">void</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">lvgl_indev_port_init</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">void</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//configure any necessary platform specific peripherals here</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//set up input device driver</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">lv_indev_drv_init</span><span style="color: #D4D4D4">(&amp;indev_drv);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//configure input device driver</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">indev_drv</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4"> = LV_INDEV_TYPE_POINTER;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">indev_drv</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">read_cb</span><span style="color: #D4D4D4"> = input_read;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">//finalize driver setup</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #4EC9B0">lv_indev_t</span><span style="color: #D4D4D4">* touch_indev = </span><span style="color: #DCDCAA">lv_indev_drv_register</span><span style="color: #D4D4D4">(&amp;indev_drv);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">void</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">input_read</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">lv_indev_drv_t</span><span style="color: #D4D4D4"> *</span><span style="color: #9CDCFE">indev_drv</span><span style="color: #D4D4D4">, </span><span style="color: #4EC9B0">lv_indev_data_t</span><span style="color: #D4D4D4"> *</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #6A9955">    /* Read location from input device (i.e. last touch location on a touchscreen) */</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6A9955">    /* Read state from input device (i.e. is a touchscreen currently being touched) */</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6A9955">    /* Store current location (x, y) and state (pressed, released) in &apos;data&apos; */</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">&nbsp;</p>



<p class="wp-block-paragraph">With the display and input device drivers implemented, the final step is to create an environment for LVGL to run in. In simple applications, this might be an infinite loop at the end of main function, while more complex applications may utilize an RTOS (real-time operating system) and give LVGL its own thread. Many approaches will work as long as <strong>lv_tick_inc</strong>&nbsp;and <strong>lv_timer_handler</strong>&nbsp;are called periodically, with <strong>lv_tick_inc</strong>&nbsp;occurring at least as frequently as <strong>lv_timer_handler</strong>.</p>



<p class="wp-block-paragraph">Note that LVGL functions are not inherently thread-safe or interrupt-safe; if using interrupts or an RTOS, utilize synchronization tools like mutexes or take care to limit calls to LVGL such that they cannot overlap.</p>



<p class="wp-block-paragraph">With all of the above in place, LVGL should be ready to use.</p>



<h2 id="h-internationalization-tools" class="wp-block-heading">Internationalization Tools</h2>



<p class="wp-block-paragraph">In addition to the base library, LVGL has several supplementary tools to aid in development. When creating a UI for an international user base, two such tools stand out as essential:&nbsp;LVGL’s internationalization library, which provides&nbsp;simple tools for text substitution, and LVGL's&nbsp;font converter, which allows a developer to parse glyphs from several fonts into a single file for use with LVGL.</p>



<p class="wp-block-paragraph"><span style="font-family:arial;">For both of these tools, you will need node.js (at least version 14) and the&nbsp;package manager it comes bundled with (called npm). You can</span> <span style="font-family:arial;"><a href="https://nodejs.org/en/download/" type="link" id="https://nodejs.org/en/download/">download them</a>&nbsp;here. Sticking with the default installation options will work for the tools covered here.</span></p>



<p class="wp-block-paragraph"><span style="font-family:arial;">After installing, ensure that both node.js and npm are added to your system PATH. Open command prompt, and install the font converter with </span><strong>npm i lv_font_conv -g</strong><span style="font-family:arial;"> and the internationalization library with <strong>npm i lv_i18n -g</strong> Both tools should now be usable in your project.</span></p>



<h2 id="h-lv-i18n" class="wp-block-heading">lv_i18n</h2>



<p class="wp-block-paragraph"><span style="font-family:arial;">The process described below was adapted from the setup instructions provided with lv_i18n&nbsp;with notes from personal experience. For the original instructions, see the <a href="https://github.com/lvgl/lv_i18n" rel="no follow" target="_blank">lv_i18n github</a>.</span></p>



<p class="wp-block-paragraph">Start off with a simple example, and wrap any text which will be translated in <strong>_( )</strong>. For strings containing plurals, <strong>_p( )</strong>&nbsp;can be used instead to support languages with different pluralization rules from your base language. Include “lv_i18n.h” (this file doesn’t exist yet as&nbsp;it will be generated by the tool later) and, after initializing LVGL, call <code>lv_i18n_init</code>&nbsp;and <code>lv_i18n_set_locale</code>. The locale can be set to any language that you plan to support, and it can be changed any time that it’s safe to call an LVGL API function.</p>



<p class="wp-block-paragraph"><span style="font-family:arial;">Next, create a folder to store translations, add a .yml file for each language you plan to support (including the language you are developing in), and title the files&nbsp;with <a href="https://www.andiamo.co.uk/resources/iso-language-codes/" rel="no follow" target="_blank">language codes</a>. Write the language code, followed by a colon, into the first line of each file (ex: Portuguese would have a file called “pt.yml”, and it’s first line would be “pt:”). Once your example is done, from a command prompt in your project, run the following (without angle brackets):</span></p>



<p class="wp-block-paragraph"><code>lv_i18n extract -s ‘&lt;path to your source files&gt;/*.+(c|cpp|h|hpp)’ -t ‘&lt;path to your yml translations&gt;/*.yml’ </code></p>



<p class="wp-block-paragraph"><span style="font-family:arial;">This will populate the translation files with a list of every string from your project wrapped in <code>_( )</code>. Next to each, add its translation in the language corresponding to the yml file it’s in. To implement these translations, run the following:</span><br>
<br>
<code>lv_i18n compile -t ‘&lt;path to your yml translations&gt;/*.yml’ -o ‘&lt;path where you want lv_i18n.h to go&gt;’</code><br>
<br>
<span style="font-family:arial;">The output location of this command will receive lv_i18n.h and lv_i18n.c, which will contain all of your translations in a form that the tool can reference. It will need to be accessible by any file that calls <code>_( )</code>, so set up its location accordingly. </span></p>



<p class="wp-block-paragraph"><span style="font-family:arial;">Now, your example should be ready to run. Any time that code containing a string wrapped in <code>_( )</code> executes, the current locale will be checked and the string will be substituted for its translation in that locale. This is a simple, literal substitution, so anything referencing the string will be affected, not just LVGL functions. Additionally, if new text is added, you will need to run <code>lv_i18n extract</code> and <code>lv_i18n compile</code> again.</span></p>



<h2 id="h-lv-font-conv" class="wp-block-heading">lv_font_conv</h2>



<p class="wp-block-paragraph"><span style="font-family:arial;">If one of your&nbsp;languages uses&nbsp;a script other than the Latin alphabet, you may notice missing characters when its translation is&nbsp;displayed. This is where the font converter comes in. Most fonts don’t describe a glyph for every Unicode character, so you will need to either find one that has all the characters you need&nbsp;or create one by splicing together a few fonts. </span></p>



<p class="wp-block-paragraph"><span style="font-family:arial;">First, prepare a list of characters needed, or ranges of characters. Even if you have a font which contains all the characters needed, it’s likely that it also contains many that you don’t, and fonts take up a lot of space (especially physically larger ones; by default, LVGL stores them as byte arrays, and more pixels means more bytes). </span></p>



<p class="wp-block-paragraph">Next, parse this list into a font converter call. Broadly, calls to the font converter are structured as a sequential list of font files, which characters to take from them, and then a few parameters to customize the output. Supply a path to the font file with <code>--font</code>, specify which characters to include from it with <code>--range</code>&nbsp;or <code>--symbols</code>, and repeat for each font to be included.</p>



<p class="wp-block-paragraph">When adding characters, <code>--symbols</code>&nbsp;accepts a list of characters. Duplicates are ignored, so you can freely copy and paste everything from one language’s translation into this argument, but be careful to remove any spaces as they may be treated as the end of the argument. <code>--range</code>&nbsp;accepts a single Unicode value or a contiguous range of Unicode values. See <a href="https://jrgraphix.net/research/unicode.php" rel="no follow" target="_blank">Unicode ranges</a> for reference. You can add as many <code>--range</code>&nbsp;and <code>--symbols</code>&nbsp;arguments as needed. Next, add the font size to generate with <code>--size</code>, the format with <code>--format</code>, the level of detail with <code>--bpp</code>, and a file path to output to with <code>-o</code>. The <a href="https://github.com/lvgl/lv_font_conv" rel="no follow" target="_blank">font converter github page</a> has more details on each of these parameters.</p>



<p class="wp-block-paragraph">&nbsp;<span style="font-family:arial;">Altogether, a call might look like the following:</span></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">Bash</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly> lv_font_conv --font ARIALUNI.TTF

  --range 0x0000-0x017F

  --range 0x0400-0x04FF

  --symbols КАСБОЙ

 --font NotoSansArabic-Regulat.ttf

  --range 0x0600-0x06FF

  --range 0xFE70-0xFEFF

 --size 10 --format lvgl --bpp 3 -o examplefont.c</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">lv_font_conv</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">--font</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">ARIALUNI.TTF</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">--range</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">0x0000</span><span style="color: #CE9178">-0x017F</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">--range</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">0x0400</span><span style="color: #CE9178">-0x04FF</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">--symbols</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">КАСБОЙ</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">--font</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">NotoSansArabic-Regulat.ttf</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">--range</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">0x0600</span><span style="color: #CE9178">-0x06FF</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">--range</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">0xFE70</span><span style="color: #CE9178">-0xFEFF</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">--size</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">10</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">--format</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">lvgl</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">--bpp</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">3</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">-o</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">examplefont.c</span></span></code></pre></div>



<pre class="wp-block-code"><code> </code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"><span style="font-family:arial;">Note that newlines and indentation are included for clarity, the only separator should be spaces since this is invoked from a command line. To that end, it’s helpful to store arguments in a text file, then parse them into calls in a script with something like bash’s <code>mapfile</code>&nbsp;command.</span></p>



<p class="wp-block-paragraph">Once a font has been generated, it can be included in the project, declared with <code>LV_FONT_DECLARE</code>, and used the same as LVGL’s default fonts.</p>



<h2 id="h-common-challenges" class="wp-block-heading">Common Challenges</h2>



<p class="wp-block-paragraph">What follows is a collection of some common challenges one might encounter when creating internationalized UIs, and some tips as to how they can be handled with&nbsp;the tools discussed here.</p>



<p class="wp-block-paragraph"><u><strong>Base Direction</strong></u></p>



<p class="wp-block-paragraph">Languages vary in their base directions; English, for example, is a left-to-right (LTR) language, while languages using the Arabic script are right-to-left languages (RTL). LVGL has some built-in handling for different base directions, namely the <code>base_dir</code>&nbsp;style property.</p>



<p class="wp-block-paragraph">When creating an object, it can be given a base direction with <code>lv_obj_set_style_base_dir</code>, or attaching a style with the property already set (by <code>lv_style_set_base_dir</code>). This will work in most cases, but, with strings containing both LTR and RTL languages, you may need to specify text direction manually. For this, one can use Unicode directional indicators. Including the following in your project will allow you to change direction within a string.</p>



<p class="wp-block-paragraph"><code>#define LRI "\xE2\x81\xA6"</code></p>



<p class="wp-block-paragraph"><code>#define RLI "\xE2\x81\xA7"</code></p>



<p class="wp-block-paragraph"><code>#define PDI "\xE2\x81\xA9"</code></p>



<p class="wp-block-paragraph">LRI indicates left-to-right, RLI the opposite, and PDI (pop directional indicator) undoes the most recent indicator. Wrapping chunks of text in LRI/RLI … PDI can be thought of as splitting the overall string into substrings, with the base direction of the overall string governing the order these substrings will appear. As an example, if the following string were to be placed in a label with RTL base direction:</p>



<p class="wp-block-paragraph">“&lt;LRI&gt;123&lt;PDI&gt; &lt;RLI&gt;456&lt;PDI&gt; &lt;LRI&gt;789&lt;PDI&gt;”</p>



<p class="wp-block-paragraph">(note: angle brackets included for clarity), it would appear on screen as</p>



<p class="wp-block-paragraph">“789 654 123”.</p>



<p class="wp-block-paragraph">Note also that LVGL may output warnings for missing glyph descriptions when rendering strings containing these characters. They are zero-width characters and function correctly regardless of whether they are included in the font, so these warnings can be safely ignored&nbsp;or suppressed in lv_conf.h (though missing glyph warnings are very helpful when working with custom fonts).</p>



<p class="wp-block-paragraph"><u><strong>Information Density</strong></u></p>



<p class="wp-block-paragraph">Languages vary in information density; three Chinese characters might convey the same information as twenty Latin ones in English or thirty in Spanish. As such, it’s helpful to leave extra space in screen layouts for languages less dense than the one being used for development.</p>



<p class="wp-block-paragraph">When this isn't possible, however, one can also use LVGL’s long text handling. Calling <code>lv_label_set_long_mode</code>&nbsp;will allow the developer to specify whether a label with text that does not fit in its width scrolls, wraps, or is cut off (optionally ending the displayed text with “…”) . Make sure to set the width of the label <u>after</u> calling this function.</p>



<p class="wp-block-paragraph">In cases where built-in long modes are not desirable, another option is to decrease font size. Using <code>lv_txt_get_size</code>&nbsp;to check whether a string will fit in a space with a given font, it’s possible to implement automatic font scaling&nbsp;(though the exact process will vary greatly from UI to UI depending on the desired behavior).</p>



<p class="wp-block-paragraph"><u><strong>Inserting Variables into Translated Text</strong></u></p>



<p class="wp-block-paragraph">One common use of text in a UI is to provide context for numerical information, which is complicated by the fact that translations need to be compiled beforehand. Thankfully, there’s a simple solution. As mentioned before, lv_i18n’s translation function&nbsp;<code>_( )</code> is a simple, direct replacement. This means that the translated string can be used with basic C text functions, namely <code>snprintf</code>. Including “%s” (or other format codes) in both the initial string and its translations will allow values to be inserted into the string by code after translation. When using this method, take care to allocate enough space in the buffer passed to <code>snprintf</code>&nbsp;for your longest translation.</p>



<p class="wp-block-paragraph">When a string contains multiple variable values, they can appear in different orders from language to language. This can be solved with either clever translation to keep fields in the same order, or by tracking which language is active and supplying arguments to <code>sprintf</code>&nbsp;in the corresponding order.</p>



<p class="wp-block-paragraph"><u><strong>Duplicate words</strong></u></p>



<p class="wp-block-paragraph">Languages often have words that&nbsp;mean several different things. As an example, an English UI might include the string “set” in multiple places, some using the word as a verb, others as a noun. These words don’t necessarily line up between languages, so the string “set” would likely need two translations in other languages. lv_i18n can only attach one translation to each original string per language, so this may initially seem like a significant issue, but the tool is perfectly capable of handling these situations.</p>



<p class="wp-block-paragraph">lv_i18n creates a lookup table for each language, mapping strings to other strings. The initial, untranslated strings scraped from your file are really just IDs to search translations by, and you can supply corresponding “translations” for them in your base locale (development language) just like any other language.</p>



<p class="wp-block-paragraph">Returning to the previous example with the word “set”, you can change the strings in your code to “set (v)” and “set (n)” and re-run <code>lv_i18n extract</code>. In the yml files belonging to other languages, fill in the translation for "set" as a noun next to “set (n)” and "set" as a verb next to “set (v)”. In “en.yml”, just place the word “set” next to both.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/duplicate_word_example.png" alt="Translation files implementing duplicate words"/></figure>



<p class="wp-block-paragraph">Re-run <code>lv_i18n compile</code>. Running the program in English, you should still see “set” everywhere, but changing the language will now show different translations for different uses of “set”.</p>



<p class="wp-block-paragraph"><strong>Learn More about DMC's <a href="/services/embedded-development-and-embedded-programming/embedded-user-interface-design">Embedded User Interface Design</a> services and <a href="/contact">contact us</a> today for your next project.</strong></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://static.dmcinfo.com/blog/17764/lvgl-for-international-gui-design/">LVGL for International GUI Design</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
