i18n

lv_i18n 是 LVGL(Light and Versatile Graphics Library)官方提供的原生轻量级多语言国际化解决方案。国际化(Internationalization,简称 i18n)是指让软件能够适配不同语言。lv_i18n 通过扫描源代码中的文本、管理 YAML 格式的翻译文件,并自动生成 C 代码,帮助开发者在图形界面中方便地实现多语言切换。

环境搭建

编译器:
vscode
安装 C/C++ 插件
安装 CMake Tools 插件
最好将 vscode 添加到环境变量中

如果 VSCode 中使用了 ESP-IDF 插件可以先禁用掉,防止干扰;实际不影响,但是会存在一些不必要的警告;

工具环境:
Node.js
lv_i18n 是一个基于 Node.js 的命令行工具,因此需要在开发机上安装 Node.js 环境
建议安装长久支持版

LV_I18N:
lv_i18n 是 LVGL 官方提供的国际化(i18n)命令行工具
i18n for LVGL GitHub
通过如下命令安装 lv_i18n 库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS D:\Tools\IDE\PowerShell\7> node -v
v20.19.3
PS D:\Tools\IDE\PowerShell\7> npm install lv_i18n -g

added 1 package in 59s

2 packages are looking for funding
run `npm fund` for details
PS D:\Tools\IDE\PowerShell\7> lv_i18n -h
usage: lv_i18n.js [-h] [-v] {extract,compile,rename} ...

optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit

Known commands:
{extract,compile,rename}

See 'lv_i18n.js <command> -h' for more information on specific command.
PS D:\Tools\IDE\PowerShell\7>

UI 工具
Gui Guider

字体文件

字体文件是存储字符视觉特征数据的二进制文件,核心定义了每个字符的轮廓形状(矢量)/ 像素点阵(位图)、字符大小、字间距、行高、基线位置等关键视觉属性。

常见格式

  • TrueType (.ttf):主流矢量字体格式,用数学曲线描述字符轮廓,可无损缩放;
  • OpenType (.otf):TTF 的扩展格式,支持更多字符集(如多语言、特殊符号)和排版特性;
  • 点阵字体 (如 .fnt):预先存储固定尺寸的像素点阵,体积小、渲染快,但缩放会失真,适合低性能硬件;
  • Web 字体 (.woff/.woff2):针对网页优化的压缩字体格式,暂不适合嵌入式场景。

核心作用

字体文件本身只是 “字符数据手册”,无法直接显示 —— 就像一本只有文字轮廓说明的 “字帖”,必须由专门的工具(字体库)解读,才能把字符 “画” 到屏幕上。

  • 无对应字体文件:程序无法识别字符的视觉形态,会显示乱码(□/�)或直接不显示;
  • 有对应字体文件:需字体库解析后,才能将字符渲染为屏幕可识别的像素阵列。

LVGL 中的字体文件使用方式

LVGL 针对嵌入式场景做了优化,支持两种字体使用模式:

  1. 内嵌字体:将字体文件转换为 C 代码数组(如 lv_font_simsun_16.c),直接编译进固件,无需外部存储和解析库,适配单片机等资源受限场景;
  2. 外部字体文件:将 .ttf/.otf 放在 Flash/SD 卡等外部存储,通过字体库(如 FreeType)动态读取解析,支持动态切换字体、调整大小,适合资源充足的场景。

生成字体文件

以 Gui Guider 工具为例,登录并新建项目后,在首页依次点击【编辑】→【生成字体】,在弹出的对话框中设置所需的字号,并选择要引用的 .ttf 字体文件。

注意:在生成之前,务必确认所选 .ttf 字体文件是否包含目标语言(如中文、日文等)的字符集。如果字体文件中缺失这些字符,那么最终生成的 .c 字体文件将不包含对应的字形数据,导致界面上该语言的文字无法正常显示(出现乱码或空白方块)。因此,建议在生成前先验证字体文件的字符覆盖范围,或者直接选用支持目标语言的完整字体(如思源黑体、Noto 系列等)。

完成字体生成后,点击菜单栏的【工程】→【代码导出】→【RT-Thread】,在导出的 generated 目录下的 guider_customer_fonts 文件夹中,即可找到生成的 .c 字体文件。

字体库

字体库(字体引擎)是负责解析字体文件生成可渲染像素数据的软件库,核心作用是架起 “字体文件” 和 “屏幕显示” 之间的桥梁。

FreeType

FreeType 是一个开源的字体引擎库,它提供统一的接口来访问多种字体格式文件,从而实现在屏幕上显示矢量字体。我们只需要移植这个字体引擎,调用对应的 API 接口,并提供字体文件,FreeType 就会帮我们解析字形轮廓、闭合曲线、填充颜色,最终生成可以直接显示的点阵位图。

FreeType 是一个字体解析引擎,它负责把 .ttf 文件里的“字帖内容”翻译成屏幕能显示的点阵。具体来说:

  • 解析 TTF 文件
    .ttf 文件里存储的是矢量轮廓(用数学曲线描述的字符形状),而不是现成的点阵。FreeType 能读懂这些曲线,并根据你指定的尺寸实时计算出对应的点阵位图。
  • 实时渲染
    LVGL 本身不能直接解析 TTF 文件。当你调用 LVGL 的 API 想显示一个字符时,如果这个字符来自 TTF 字体,LVGL 会调用 FreeType 去“画出”这个字符,然后把画好的位图拿过来显示。
  • 动态缩放与变形
    因为 FreeType 是在运行时解析矢量轮廓,所以你可以随意改变字体大小(比如从 16px 变成 32px),甚至应用倾斜、加粗等样式,而不需要准备多个不同大小的内嵌字体

FreeType官方
FreeType GitHub 地址,我这里安装的是最新的 2.14.2 版本

有了字体文件,为什么还需要字体库?

LVGL 本身不支持直接读取 .ttf、.otf 这类矢量字体文件。因为这些文件里存储的是字符的数学轮廓(比如用贝塞尔曲线描述的笔画),而不是屏幕上可以直接显示的像素点阵。LVGL 只认识已经画好的位图,不认识这些曲线公式。

所以,想让 LVGL 使用一个 .ttf 字体,你有两种选择:

  1. 动态解析(需要字体库)
    在程序运行时,借助一个“翻译官”——比如 FreeType 字体库——去读取 .ttf 文件,把里面的曲线公式实时转换成指定大小的点阵位图,再把位图交给 LVGL 去显示。这种方式灵活,可以随时改变字号、加载不同字体,但需要额外移植字体库。
  2. 静态转换(不需要字体库)
    在编译前,先用工具(如 LVGL 官方提供的字体转换器)把 .ttf 文件里需要的字符“画”成固定大小的位图,并生成一个 .c 文件(就像你之前看到的那个)。这个文件里已经存好了每个字的像素数据,直接编译进程序,LVGL 就能直接使用。这种方式简单、运行快,但字体大小固定,也无法动态换字体。

简单来说:如果不使用 FreeType 这类字体库,你就只能提前把字体转成 .c 数组硬编码进代码;如果希望程序能灵活地缩放文字、加载不同风格的字体,就需要字体库在运行时帮忙解析。

移植 FreeType

使用流程

在使用 LV_I18N 实现多语言时,一个常见的误区是认为“只要翻译了文本就能正常显示”。但实际上,LV_I18N 仅负责文本内容的多语言切换,而文字最终能否正确渲染,取决于字体是否支持对应语言的字符集。如果使用的字体文件中没有包含中文、日文等字符的轮廓数据,屏幕上就会显示为乱码或方块。

因此,在完成多语言词条的提取和编译后,还需要为每种语言配置合适的字体。你可以选择:

  • 使用 FreeType 等字体库动态加载 TTF 矢量字体(灵活但需额外移植);
  • 或提前将所需字符生成内嵌字体 C 文件(简单但大小固定)。

只有“内容(翻译)”与“载体(字体)”两者配合,才能真正实现完善的多语言界面。

创建 yml 翻译文件

YML(YAML)文件是 lv_i18n 中核心的语言配置文件,作用可以通俗理解为:存放不同语言的文本翻译内容,让 LVGL 界面能便捷切换多语言
YML 文件以 “键 - 值” 的形式组织文本,一个 YML 文件对应一种语言(比如 en.yml 对应英语、zh-CN.yml 对应简体中文)。
ISO 639-1标准语言代码表

格式说明

1
2
3
4
5
6
7
8
9
10
11
# 以简体中文为例
zh-CN:
Sound: 声音
Bluetooth: 蓝牙
History: 历史记录
Your blood pressure: 你的心跳
TODAY: 今天
Yesterday: 昨天
user_logged_in:
one: 1 个用户已登录
other: '%d 个用户已登录'

zh-CN 是语言标识,格式为[语言代码-地区代码],根节点下的所有内容都是该语言的翻译项。

Sound: 声音 是翻译所需的键值对,左键:避免空格和中文,右键:支持目标语言的任意字符(中文、特殊符号等)。

user_logged_in 是 lv_i18n 特有的复数处理格式,用于根据数值动态切换翻译文本:

  • 当值为 1 的时候就显示 one 后面的内容
  • 当值非 1 的时候就显示 other 后面的内容
  • 注意:0 也是非 1,显示在 other 后面

注意:YAML 依赖缩进(建议 2 个空格),不能用 Tab(除非你确定你的 Tab 是两个字符),否则会解析报错。

手动创建

指 YML 语言配置文件的所有内容(包括词条别名、翻译词条、复数规则等) 均由自己逐行编写。

  1. 创建一个 lv_i18n 的文件夹,用于存放编译后的 lv_i18n.c和.h文件

  2. 在 lv_i18n 的文件夹中创建 yml 翻译文件

1
2
3
4
5
6
7
# 新建 `en-GB.yml` 并手写全部内容:
# 完全手写的词条别名 + 翻译词条
Sound: Sound
Bluetooth: Bluetooth
user_logged_in:
one: One user is logged in
other: '%d users are logged in'

半自动创建

借助 lv_i18n 提供的命令行工具生成 YML 文件的基础结构,仅需手动补充翻译词条,词条别名由工具自动提取。

创建翻译目录与基础 YML 文件

1
2
3
4
5
6
# 1. 创建 translations 文件夹(建议放在项目根目录,方便管理)
mkdir -p translations

# 2. 进入 translations 文件夹,创建多语言 YML 文件(示例:英文(默认)、中文、日文)
cd translations
touch en.yml zh-CN.yml ja.yml

初始化 YML 文件的基础结构

每个 YML 文件开头必须声明语言标识(这是 lv_i18n 识别语言的核心)

1
2
3
4
5
6
7
8
9
10
# en.yml(英文,默认语言)
language: en # 语言标识必须正确(符合 ISO 639-1 标准)

# zh-CN.yml(简体中文)
language: zh-CN


# ja.yml(日文)
language: ja

代码中包裹需要翻译的文本

1
2
3
4
5
6
// 正确写法
lv_label_set_text(label, _("hello_world"));
lv_btn_set_text(btn, _("Confirm"));

// 错误写法(宏名不能加空格)
lv_label_set_text(label, _ ("hello_world"));

提取词条

1
2
# 提取 screens  目录下所有 .c 文件的词条,写入 translations 目录下所有 .yml 文件
lv_i18n extract -s './lvgl_v8/*.c' -t './translations/*.yml'

路径说明:

  • -s(source):指定要扫描的源代码文件路径,*.c 匹配所有 .c 文件;
  • -t(target):指定要写入的 YML 文件路径,*.yml 匹配所有 .yml 文件;
  • 路径分隔符:Windows 用 \,Linux/macOS 用 /,建议跨平台用 /。

填充翻译内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# zh-CN.yml(手动补充)
zh-cn:
translations:
Firmware Upgrade: 固件升级
Simple drawing: 简笔画
Line drawing: 线稿画
Sketch drawing: 素描画
Anime drawing: 动漫画
Cartoon drawing: 卡通画
Ink wash painting: 水墨画
Chat: 聊天
Verifying...: 验证中...
Upgrade Success: 升级成功
Upgrade Failed: 升级失败
Downloading: 下载中
Download the APP and Connect WIFI: 下载 APP 并连接 WIFI
Please power off when not in use: 不使用时请关机
Hold to Talk: 按住说话
Currently the battery is low please turn off the device and charge it in time: 当前电池电量低,请关闭设备及时充电!
Battery is low, turn it off, then charge it!: 电量低,先关机,再充电!
Please turn off your device when not in use: 不使用请关闭设备


# en.yml(工具自动生成,无需改)
...

# ja.yml(手动补充)
...

生成核心文件

当我们已经正确生成 YML 翻译配置文件后,需要将声明式的 YML 文件转为代码可用的 .c 文件

1
2
# 将当前目录下所有 YML 翻译文件编译 / 转换为可直接嵌入项目的 C 源码文件(.c + .h)
lv_i18n compile -t "./*.yml" -o .

路径说明:

  • -t(–target):指定要处理的 YML 源文件路径
  • -o(–output):指定生成的 .c/.h 文件的输出目录

使用

若已按流程正确生成 lv_i18n.c 和 lv_i18n.h 源码文件,lv_i18n 的实际使用流程会非常清晰,核心仅需完成三个关键步骤:初始化翻译模块、设置默认显示语言、按需动态切换语言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/********************* 1. 初始化(必做,前置条件)*********************/
#include "lv_i18n.h"

lv_init(); // 先初始化 LVGL 核心
// 传入生成好的语言包(compile/build 生成的 lv_i18n_language_pack)
lv_i18n_init(lv_i18n_language_pack);

/********************* 2. 设置默认语言(必做)*********************/
// 格式:语言标识需与 YML 文件中定义的 language 字段完全一致(大小写/格式敏感)
lv_i18n_set_locale("en-GB"); // 英式英语(需确保 en-GB.yml 中 language: en-GB)
// 常用示例:
// lv_i18n_set_locale("en"); // 通用英语(匹配 YML 中 language: en)
// lv_i18n_set_locale("zh-CN"); // 简体中文(匹配 YML 中 language: zh-CN)

/********************* 3. 切换语言(按需使用)*********************/
// 运行时动态切换,切换后需刷新控件文本(核心遗漏:文本不会自动刷新)
lv_i18n_set_locale("zh-CN"); // 切换到简体中文
// lv_i18n_set_locale("ja"); // 切换到日语(需确保 ja.yml 已配置)

// 补充:切换语言后刷新控件(关键!否则文本仍显示旧语言)
lv_label_set_text(label, _("Firmware Upgrade"));

错误