LVGLでC言語を使用した多言語対応の実装

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"));
}

この方式により、ビルド時の依存を一切増やさず、実行速度も確保しつつ、必要な分だけ多言語対応を実装できる。将来的にはマクロやコード生成で冗長性をさらに削減することも可能である。

タグ: LVGL C言語 組み込みシステム 多言語対応 i18n

8月1日 00:46 投稿