LVGLにおける多言語切り替え機能は、標準ではやや複雑で扱いにくい。公式ではNode.jsとnpmを用いてlv_i18n_gツールを使い、JSONからコードを生成する流れを推奨しているが、開発フローに余計な依存を持ち込むことになり、組み込み環境では現実的ではない場合が多い。そこで、簡潔かつ効率的な手動管理方式を採用する方法を紹介する。ただし、使用するフォントは対象言語の文字をサポートしている必要がある。
1. 言語データの構造体設計
まず、各UI要素に対応する文字列を一元管理するための構造体を定義する。キー名は画面内のコンポーネントと関連付けることで、後続の参照を容易にする。
#ifndef LANGUAGE_H
#define LANGUAGE_H
typedef enum {
LANG_JAPANESE,
LANG_ENGLISH,
LANG_SIMPLIFIED_CHINESE,
// 必要に応じて追加
} lang_id_t;
// 表示文字列を保持する構造体
typedef struct {
const char* home_config_btn;
const char* config_menu_title;
const char* power_off_option;
const char* language_setting;
const char* touchscreen_calibration;
const char* back_button_label;
// その他のラベルをここに追加
} lang_bundle_t;
#endif
2. 言語ごとの文字列リソース定義
次に、各言語の具体的な文字列を静的に定義する。これらのデータはROM上に配置されるため、RAM使用量への影響は最小限に抑えられる。
#include "language.h"
// 日本語リソース
static const lang_bundle_t bundle_ja = {
.home_config_btn = "設定",
.config_menu_title = "メニュー",
.power_off_option = "電源を切る",
.language_setting = "言語設定",
.touchscreen_calibration = "タッチパネル校正",
.back_button_label = "戻る"
};
// 英語リソース
static const lang_bundle_t bundle_en = {
.home_config_btn = "Settings",
.config_menu_title = "Menu",
.power_off_option = "Power Off",
.language_setting = "Language",
.touchscreen_calibration = "Touch Screen",
.back_button_label = "Back"
};
// 言語IDに対応するバンドルの配列(順序はlang_id_tと一致させる)
static const lang_bundle_t* const lang_bundles[] = {
[LANG_JAPANESE] = &bundle_ja,
[LANG_ENGLISH] = &bundle_en,
[LANG_SIMPLIFIED_CHINESE] = NULL // 未実装時はNULL
};
// 現在選択中の言語を保持するグローバル変数
lang_id_t g_current_language = LANG_JAPANESE;
3. 文字列取得関数の実装
指定された言語IDとラベルキーに基づき、適切な文字列を返す関数を実装する。内部では直接比較ではなく、構造体メンバにアクセスすることで高速に処理できる。
#include "language.h"
#include <string.h>
const char* get_lang_text(lang_id_t lang, const char* key) {
if (lang >= sizeof(lang_bundles)/sizeof(lang_bundles[0]) || lang_bundles[lang] == NULL) {
return "??";
}
const lang_bundle_t* bundle = lang_bundles[lang];
if (strcmp(key, "home_config_btn") == 0) {
return bundle->home_config_btn;
}
if (strcmp(key, "config_menu_title") == 0) {
return bundle->config_menu_title;
}
if (strcmp(key, "power_off_option") == 0) {
return bundle->power_off_option;
}
if (strcmp(key, "language_setting") == 0) {
return bundle->language_setting;
}
if (strcmp(key, "touchscreen_calibration") == 0) {
return bundle->touchscreen_calibration;
}
if (strcmp(key, "back_button_label") == 0) {
return bundle->back_button_label;
}
return "???"; // キー未登録
}
4. 実際のUIでの利用例
LvGLのラベルなどに文字列を設定する際には、現在の言語とキーを引数として関数を呼び出す。
// 設定ボタンのテキストを更新
lv_obj_t* label = lv_label_create(home_screen);
lv_label_set_text(label, get_lang_text(g_current_language, "home_config_btn"));
// 言語切り替え後に再描画が必要な場合は、この関数を再呼び出し
void refresh_ui_language(lv_obj_t* obj) {
lv_label_set_text(obj, get_lang_text(g_current_language, "back_button_label"));
}
この方式により、ビルド時の依存を一切増やさず、実行速度も確保しつつ、必要な分だけ多言語対応を実装できる。将来的にはマクロやコード生成で冗長性をさらに削減することも可能である。