feat: add Cyrillic keyboard layout pack

This commit is contained in:
liu weikai
2026-06-13 20:34:04 +08:00
parent 0cba07bbc9
commit afd92cd93f
27 changed files with 250 additions and 39 deletions
+20 -4
View File
@@ -27,7 +27,8 @@ Trail Mate 把本地化明确建模为三类 pack:
- `Font Pack`
拥有字形覆盖元数据以及外部 `font.bin` 资源。
- `IME Pack`
声明脚本特定的输入行为,例如简体中文拼音。IME pack 只有在固件里已经有真实后端时才允许进入 runtime payload
声明脚本特定的输入行为,例如简体中文拼音,或直接提交目标脚本字符的键盘布局
IME pack 只有在固件里已经有真实后端时才允许进入 runtime payload。
Settings 页选择的是 `Locale Pack`,而不是直接选择字体或 IME。
@@ -235,16 +236,29 @@ display_name=Pinyin
backend=builtin-pinyin
```
直接键盘布局使用同一类 IME manifest,但必须把后端与布局分开声明:
```ini
kind=ime
id=ru-cyrillic-keyboard
display_name=Russian Cyrillic Keyboard
backend=builtin-keyboard-layout
layout=ru-cyrillic
```
目前可启用的输入法实现是:
- `zh-hans-pinyin`,后端为 `builtin-pinyin`
- `ru-cyrillic-keyboard`,后端为 `builtin-keyboard-layout`,布局为 `ru-cyrillic`
backend 的真实实现位于固件代码中。IME pack manifest 是把它注册进运行时并暴露出来的那一层。
`layout` 是直接键盘布局后端的目标布局名;转换型后端例如拼音可以不声明 `layout`
硬规则:
- 只有已经有真实输入引擎和正确候选词表的 IME 才能出现在 runtime payload 中。
- 未实现的假名、韩文、阿拉伯、西里尔、繁中注音/仓颉和泛 Latin 软键盘,不得用 `backend=builtin-*` 提前占位。
- 未实现的假名、韩文、阿拉伯、繁中注音/仓颉和泛 Latin 软键盘,不得用 `backend=builtin-*` 提前占位。
- 西里尔输入只有 `ru-cyrillic-keyboard` 当前可发布;它是直接键盘布局,不是候选词转换引擎。
- 台湾繁中不得默认拼音。未来 `zh-Hant-TW` 的首选输入法应是注音,仓颉/速成可以作为可选扩展。
- 欧洲 Latin 语言通常不需要 IME pack;沿用普通 `EN` / `123` 输入路径即可。
- locale manifest 只有在真实 IME 可用时才写 `ime_pack`。display-only locale 不写假依赖。
@@ -523,11 +537,12 @@ runtime registry 仍然直接从 `/trailmate/packs` 对已解包资源进行编
## 当前 IME 覆盖
目前仓库 bundle 中,只有简体中文声明并发布了 IME pack
目前仓库 bundle 中,简体中文和俄语声明了 IME pack
- `zh-Hans` -> `zh-hans-pinyin`
- `ru` -> `ru-cyrillic-keyboard`
繁体中文、日文、韩文、阿拉伯语、俄语和欧洲 Latin bundle 目前都是 display-only review 包。它们有意继续沿用现有 `EN` / `123` 输入路径,直到对应脚本的专用 IME pack 出现。
繁体中文、日文、韩文、阿拉伯语和欧洲 Latin bundle 目前都是 display-only review 包。它们有意继续沿用现有 `EN` / `123` 输入路径,直到对应脚本的专用 IME pack 出现。
具体约束:
@@ -535,6 +550,7 @@ runtime registry 仍然直接从 `/trailmate/packs` 对已解包资源进行编
- `ja` 必须等假名/罗马字日文输入模型完成后才能挂 IME。
- `ko` 必须等韩文输入模型完成后才能挂 IME。
- `ar` 必须等阿拉伯键盘、RTL 编辑与 shaping 行为闭环后才能挂 IME。
- `ru` 可以挂 `ru-cyrillic-keyboard`;该能力只承诺直接 Cyrillic 键盘布局,不承诺物理 QWERTY 到俄文的硬件重映射。
- 欧洲 Latin locale 不应为了“有输入法列表项”而挂虚假的 `builtin-latin`
## 新增一种语言
@@ -232,12 +232,16 @@ python scripts/build_pack_repository.py --pack-root packs --site-root site
1. `zh-Hans`
- 可以作为 `release`
- 当前唯一可发布 IME 是 `zh-hans-pinyin`
- 可发布 IME 是 `zh-hans-pinyin`
2. `zh-Hant`
- 按台湾繁中 `review` 包处理。
- 不挂拼音 IME。
- 进入 `release` 前必须经过台湾用语与输入法习惯审查。
3. `ar``ru``de``es``fr``it``nl``pl``pt-PT``ja``ko`
3. `ru`
- 可以作为 `review` 包处理。
- 可发布 IME 是 `ru-cyrillic-keyboard`,后端为 `builtin-keyboard-layout`,布局为 `ru-cyrillic`
- 该 IME 是直接虚拟键盘布局,不承诺物理键盘硬件重映射。
4. `ar``de``es``fr``it``nl``pl``pt-PT``ja``ko`
- 可打包为 `review`
- 不进入普通用户语言选择器。
- 不发布假 IME。
+10 -2
View File
@@ -182,9 +182,12 @@ IME pack manifest 只是输入法后端的声明,不是后端本身。
一个输入法要成为可启用输入法,必须同时满足:
1. 固件中存在真实后端。
2. 候选词、组合逻辑、提交行为与目标语言/地区习惯匹配。
2. 候选词、组合逻辑或直接键盘布局、提交行为与目标语言/地区习惯匹配。
3. UI widget 知道如何展示该输入模式。
4. locale manifest 显式依赖它,且该 locale 已经是可发布质量
4. 需要作为 locale 默认输入能力时,locale manifest 显式依赖它。
`translation_status` 仍然独立决定 locale 是否进入普通用户语言选择器。
`review` locale 可以依赖真实 IME 以便运行时完整校验和安装该能力,但不能因此被当成 release UI locale。
因此,不允许用 `backend=builtin-kana``backend=builtin-hangul``backend=builtin-arabic` 这类尚未实现的名字提前占位。未实现输入法可以写进规划文档,但不能作为 runtime IME pack 进入 payload。
@@ -283,6 +286,11 @@ locale id 必须按 BCP-47 思路表达语言、脚本与地区语境,而不
- `zh-hans-pinyin`
- 后端:`builtin-pinyin`
- 适用范围:简体中文拼音输入。
2. `ru`
- `ru-cyrillic-keyboard`
- 后端:`builtin-keyboard-layout`
- 布局:`ru-cyrillic`
- 适用范围:俄文 Cyrillic 直接键盘布局;不包含候选词转换,也不承诺物理 QWERTY 到俄文的硬件重映射。
当前不得启用的输入法包括:
@@ -27,6 +27,7 @@ struct ImeInfo
const char* id = nullptr;
const char* display_name = nullptr;
const char* backend = nullptr;
const char* layout = nullptr;
bool builtin = true;
};
@@ -33,7 +33,8 @@ class ImeWidget
enum class Mode
{
EN,
CN,
SCRIPT,
CN = SCRIPT,
NUM
};
@@ -57,7 +58,10 @@ class ImeWidget
void refresh_touch_candidates();
void sync_textarea();
bool handle_key_code(uint32_t key);
bool handle_text_token(const char* token);
bool commit_candidate(int candidate_index);
bool pinyin_mode() const;
bool direct_keyboard_mode() const;
static void on_toggle_clicked(lv_event_t* e);
static void on_touch_key_event(lv_event_t* e);
@@ -96,6 +96,7 @@ struct ImePackRecord
std::string id;
std::string display_name;
std::string backend;
std::string layout;
bool builtin = false;
};
@@ -557,10 +558,13 @@ void append_unique_string(std::vector<std::string>& values, const std::string& v
bool ime_backend_supports_runtime_input(const ImePackRecord& pack)
{
// The current widget has one real conversion engine and dictionary:
// Simplified Chinese Pinyin. Other manifests may still be cataloged as
// pack metadata, but exposing them would route users into the wrong script.
return pack.id == "zh-hans-pinyin" && pack.backend == "builtin-pinyin";
if (pack.id == "zh-hans-pinyin" && pack.backend == "builtin-pinyin")
{
return true;
}
return pack.id == "ru-cyrillic-keyboard" &&
pack.backend == "builtin-keyboard-layout" &&
pack.layout == "ru-cyrillic";
}
bool ime_backend_can_be_enabled(const ImePackRecord& pack)
@@ -1446,6 +1450,7 @@ void rebuild_ime_views()
view.id = pack.id.c_str();
view.display_name = pack.display_name.c_str();
view.backend = pack.backend.c_str();
view.layout = pack.layout.empty() ? nullptr : pack.layout.c_str();
view.builtin = pack.builtin;
s_ime_views.push_back(view);
}
@@ -1619,11 +1624,16 @@ bool catalog_external_ime_pack(const std::string& pack_dir)
{
pack.backend = "none";
}
if (const char* layout = manifest_value(manifest, "layout"))
{
pack.layout = layout;
}
std::printf("%s ime pack catalog id=%s backend=%s\n",
std::printf("%s ime pack catalog id=%s backend=%s layout=%s\n",
kLogTag,
pack.id.c_str(),
pack.backend.c_str());
pack.backend.c_str(),
pack.layout.empty() ? "<none>" : pack.layout.c_str());
s_ime_packs.push_back(std::move(pack));
return true;
}
@@ -31,9 +31,58 @@ static constexpr int kCompactCandidatesPerPage = 7;
static constexpr lv_coord_t kCompactImeRowHeight = 22;
static constexpr lv_coord_t kCompactImeControlHeight = 18;
bool script_input_available()
enum class ScriptInputKind
{
return ::ui::i18n::any_enabled_script_input();
None,
Pinyin,
RuCyrillicKeyboard
};
const ::ui::i18n::ImeInfo* active_ime_info()
{
const char* active_id = ::ui::i18n::active_ime_pack_id();
if (!active_id || active_id[0] == '\0')
{
return nullptr;
}
const std::size_t count = ::ui::i18n::ime_count();
for (std::size_t index = 0; index < count; ++index)
{
const ::ui::i18n::ImeInfo* ime = ::ui::i18n::ime_at(index);
if (ime != nullptr && ime->id != nullptr && std::strcmp(ime->id, active_id) == 0)
{
return ime;
}
}
return nullptr;
}
ScriptInputKind active_script_input_kind()
{
const ::ui::i18n::ImeInfo* ime = active_ime_info();
if (!ime || !ime->id || !ime->backend)
{
return ScriptInputKind::None;
}
if (std::strcmp(ime->id, "zh-hans-pinyin") == 0 &&
std::strcmp(ime->backend, "builtin-pinyin") == 0)
{
return ScriptInputKind::Pinyin;
}
if (std::strcmp(ime->id, "ru-cyrillic-keyboard") == 0 &&
std::strcmp(ime->backend, "builtin-keyboard-layout") == 0 &&
ime->layout != nullptr &&
std::strcmp(ime->layout, "ru-cyrillic") == 0)
{
return ScriptInputKind::RuCyrillicKeyboard;
}
return ScriptInputKind::None;
}
bool script_mode(ImeWidget::Mode mode)
{
return mode == ImeWidget::Mode::SCRIPT;
}
bool hardware_keyboard_available()
@@ -57,6 +106,15 @@ static const char* kTouchNumMap[] = {
"-", "/", ":", ";", "(", ")", "$", "&", "@", "Enter", "\n",
".", ",", "?", "!", "'", "\"", "%", "+", "\n",
"Space", ""};
static const char* kTouchRuCyrillicMap[] = {
"й", "ц", "у", "к", "е", "н", "г", "ш", "щ", "з", "х", "ъ", "Bksp", "\n",
"ф", "ы", "в", "а", "п", "р", "о", "л", "д", "ж", "э", "Enter", "\n",
"я", "ч", "с", "м", "и", "т", "ь", "б", "ю", ",", ".", "?", "\n",
"Space", ""};
static constexpr const char* kRuCyrillicFontProbe =
"йцукенгшщзхъфывапролджэячсмитьбю";
#endif
std::string make_candidates_text(const std::vector<std::string>& candidates,
int active_idx,
@@ -173,6 +231,14 @@ bool translate_touch_token(const char* token, uint32_t& key)
return false;
}
bool touch_token_is_action(const char* token)
{
return token != nullptr &&
(std::strcmp(token, "Bksp") == 0 ||
std::strcmp(token, "Enter") == 0 ||
std::strcmp(token, "Space") == 0);
}
bool resolve_touch_button_id(lv_event_t* e, lv_obj_t* matrix, uint32_t& button_id)
{
if (!matrix)
@@ -465,18 +531,20 @@ void ImeWidget::detach()
void ImeWidget::setMode(Mode mode)
{
if (mode == Mode::CN && !script_input_available())
const ScriptInputKind requested_kind = active_script_input_kind();
if (script_mode(mode) && requested_kind == ScriptInputKind::None)
{
mode = Mode::EN;
}
mode_ = mode;
ime_.setEnabled(mode_ == Mode::CN);
const bool use_pinyin = pinyin_mode();
ime_.setEnabled(use_pinyin);
if (textarea_)
{
const char* cur = lv_textarea_get_text(textarea_);
committed_text_ = cur ? std::string(cur) : std::string();
if (mode_ == Mode::CN)
if (use_pinyin)
{
ime_.reset();
lv_textarea_set_accepted_chars(textarea_, "");
@@ -496,9 +564,20 @@ ImeWidget::Mode ImeWidget::mode() const
return mode_;
}
bool ImeWidget::pinyin_mode() const
{
return script_mode(mode_) && active_script_input_kind() == ScriptInputKind::Pinyin;
}
bool ImeWidget::direct_keyboard_mode() const
{
return script_mode(mode_) &&
active_script_input_kind() == ScriptInputKind::RuCyrillicKeyboard;
}
void ImeWidget::cycleMode()
{
if (!script_input_available())
if (active_script_input_kind() == ScriptInputKind::None)
{
setMode(mode_ == Mode::EN ? Mode::NUM : Mode::EN);
return;
@@ -506,9 +585,9 @@ void ImeWidget::cycleMode()
if (mode_ == Mode::EN)
{
setMode(Mode::CN);
setMode(Mode::SCRIPT);
}
else if (mode_ == Mode::CN)
else if (script_mode(mode_))
{
setMode(Mode::NUM);
}
@@ -542,7 +621,7 @@ bool ImeWidget::handle_key_code(uint32_t key)
bool consumed = false;
bool update_text = false;
if (mode_ == Mode::CN)
if (pinyin_mode())
{
if (key == LV_KEY_BACKSPACE)
{
@@ -679,6 +758,19 @@ bool ImeWidget::handle_key_code(uint32_t key)
return consumed;
}
bool ImeWidget::handle_text_token(const char* token)
{
if (!textarea_ || !token || token[0] == '\0' || !direct_keyboard_mode())
{
return false;
}
committed_text_ += token;
sync_textarea();
refresh_labels();
return true;
}
bool ImeWidget::handle_key(lv_event_t* e)
{
if (!textarea_ || !e)
@@ -718,7 +810,7 @@ void ImeWidget::setText(const char* text)
if (!textarea_) return;
committed_text_ = text ? std::string(text) : std::string();
sync_textarea();
if (mode_ == Mode::CN)
if (pinyin_mode())
{
ime_.reset();
}
@@ -732,7 +824,18 @@ void ImeWidget::refresh_touch_keyboard()
{
return;
}
const char* const* map = (mode_ == Mode::NUM) ? kTouchNumMap : kTouchEnMap;
const char* const* map = kTouchEnMap;
const lv_font_t* font = ::ui::fonts::localized_font(::ui::fonts::ui_chrome_font());
if (mode_ == Mode::NUM)
{
map = kTouchNumMap;
}
else if (direct_keyboard_mode())
{
map = kTouchRuCyrillicMap;
font = ::ui::fonts::content_font(kRuCyrillicFontProbe, ::ui::fonts::ui_chrome_font());
}
lv_obj_set_style_text_font(keyboard_matrix_, font, LV_PART_ITEMS);
lv_btnmatrix_set_map(keyboard_matrix_, map);
}
@@ -743,7 +846,7 @@ void ImeWidget::refresh_touch_candidates()
return;
}
const bool show_candidates = mode_ == Mode::CN;
const bool show_candidates = pinyin_mode();
if (!show_candidates)
{
lv_obj_add_flag(candidate_row_, LV_OBJ_FLAG_HIDDEN);
@@ -829,7 +932,7 @@ void ImeWidget::refresh_labels()
return;
}
if (mode_ == Mode::CN && !script_input_available())
if (script_mode(mode_) && active_script_input_kind() == ScriptInputKind::None)
{
mode_ = Mode::EN;
ime_.setEnabled(false);
@@ -837,7 +940,7 @@ void ImeWidget::refresh_labels()
if (textarea_)
{
if (mode_ == Mode::CN && ime_.hasBuffer())
if (pinyin_mode() && ime_.hasBuffer())
{
if (lv_group_t* g = lv_group_get_default())
{
@@ -889,6 +992,23 @@ void ImeWidget::refresh_labels()
return;
}
const ScriptInputKind script_kind = active_script_input_kind();
if (script_kind == ScriptInputKind::RuCyrillicKeyboard)
{
lv_label_set_text(toggle_label_, "RU");
#if UI_SHARED_TOUCH_IME_ENABLED
if (touch_keyboard_enabled_)
{
set_candidates_label_text(candidates_label_, ::ui::i18n::tr("Cyrillic keyboard"));
refresh_touch_keyboard();
refresh_touch_candidates();
return;
}
#endif
set_candidates_label_text(candidates_label_, "");
return;
}
lv_label_set_text(toggle_label_, "CN");
#if UI_SHARED_TOUCH_IME_ENABLED
if (touch_keyboard_enabled_)
@@ -912,7 +1032,7 @@ void ImeWidget::refresh_candidates()
{
return;
}
if (!ime_.hasBuffer())
if (!pinyin_mode() || !ime_.hasBuffer())
{
set_candidates_label_text(candidates_label_, "");
return;
@@ -960,6 +1080,12 @@ void ImeWidget::on_touch_key_event(lv_event_t* e)
}
const char* token = lv_btnmatrix_get_btn_text(self->keyboard_matrix_, button_id);
if (self->direct_keyboard_mode() && !touch_token_is_action(token))
{
(void)self->handle_text_token(token);
return;
}
uint32_t key = 0;
if (!translate_touch_token(token, key))
{
+1
View File
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) جهات الاتصال (Meshtastic - %.3fMHz)
Contacts (RNode) جهات الاتصال (RNode)
Contacts (RNode - %.3fMHz) جهات الاتصال (RNode - %.3fMHz)
Conversation protocol mismatch عدم تطابق بروتوكول المحادثة
Cyrillic keyboard لوحة مفاتيح سيريلية
Data Exchange تبادل البيانات
Debug Logs سجلات التصحيح
Decoding: %d%% فك التشفير: %d%%
1 Action إجراء
42 Contacts (RNode) جهات الاتصال (RNode)
43 Contacts (RNode - %.3fMHz) جهات الاتصال (RNode - %.3fMHz)
44 Conversation protocol mismatch عدم تطابق بروتوكول المحادثة
45 Cyrillic keyboard لوحة مفاتيح سيريلية
46 Data Exchange تبادل البيانات
47 Debug Logs سجلات التصحيح
48 Decoding: %d%% فك التشفير: %d%%
+5 -3
View File
@@ -1,9 +1,11 @@
European Cyrillic Extended installs the Russian locale together with a shared
Cyrillic external font pack so interface labels and Russian content can render
without inflating the firmware image.
without inflating the firmware image. It also registers a Russian Cyrillic
keyboard layout backed by the firmware's direct keyboard-layout runtime.
This bundle is display-only today. It does not add a Russian IME, and the locale
remains in review status until native-language review is complete.
The locale remains in review status until native-language review is complete.
The keyboard layout is a direct virtual-keyboard layout; it does not provide
candidate conversion or physical keyboard remapping.
Credits: Russian translations were contributed by polarikus, based on the
polarikus/trail-mate Russian localization PR.
+11 -2
View File
@@ -6,6 +6,7 @@ It contains:
- locale pack: `ru`
- font pack: `cyrillic-eu`
- IME pack: `ru-cyrillic-keyboard`
The Russian translations were contributed by polarikus.
@@ -20,6 +21,7 @@ The locale manifest points to:
- `ui_font_pack=cyrillic-eu`
- `content_font_pack=cyrillic-eu`
- `ime_pack=ru-cyrillic-keyboard`
Bundle-level package metadata lives in:
@@ -41,6 +43,8 @@ Bundle-level package metadata lives in:
- `locales/ru/`
- `manifest.ini`
- `strings.tsv`
- `ime/ru-cyrillic-keyboard/`
- `manifest.ini`
## Generate The Font Pack
@@ -65,7 +69,11 @@ python tools/generate_binfont_with_lv_font_conv.py `
Notes:
- This bundle is display-only for now. It does not declare an IME pack yet.
- The Russian locale is still in `translation_status=review`, but the bundle now
declares the real `ru-cyrillic-keyboard` IME pack.
- `ru-cyrillic-keyboard` is a direct virtual-keyboard layout backed by
`backend=builtin-keyboard-layout` and `layout=ru-cyrillic`; it is not a
candidate-conversion engine and does not remap physical keyboard hardware.
- `build.ini` is the source-of-truth for generating `font.bin` during Pages/package builds.
- `font.bin` is ignored by Git. Regenerate it whenever the subset changes.
- `ranges.txt` and `estimated_ram_bytes` let the runtime decide whether the pack fits the active memory profile.
@@ -75,7 +83,7 @@ Notes:
`python scripts/build_pack_repository.py --pack-root packs --site-root site` produces:
- `site/assets/packs/europe-cyrillic-ext-1.1.1.zip`
- `site/assets/packs/europe-cyrillic-ext-1.2.0.zip`
- `site/data/packs.json`
The zip is the bundle artifact for a future Extensions downloader. Its `payload/` directory unpacks into `/trailmate/packs/...`.
@@ -90,6 +98,7 @@ Copy the bundle contents so the SD card ends up with:
/trailmate/packs/fonts/cyrillic-eu/font.bin
/trailmate/packs/locales/ru/manifest.ini
/trailmate/packs/locales/ru/strings.tsv
/trailmate/packs/ime/ru-cyrillic-keyboard/manifest.ini
```
After reboot, `Русский` appears in Settings only after the locale is promoted to `translation_status=release`. While it is in `review`, the runtime can install and index the pack but will not offer it as a selectable UI language.
@@ -0,0 +1,5 @@
kind=ime
id=ru-cyrillic-keyboard
display_name=Russian Cyrillic Keyboard
backend=builtin-keyboard-layout
layout=ru-cyrillic
@@ -5,4 +5,5 @@ native_name=Русский
translation_status=review
ui_font_pack=cyrillic-eu
content_font_pack=cyrillic-eu
ime_pack=ru-cyrillic-keyboard
strings=strings.tsv
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) Контакты (Meshtastic - %.3fМГц)
Contacts (RNode) Контакты (RNode)
Contacts (RNode - %.3fMHz) Контакты (RNode - %.3fМГц)
Conversation protocol mismatch Несовпадение протокола
Cyrillic keyboard Кириллическая клавиатура
Data Exchange Обмен данными
Debug Logs Журналы отладки
Decoding: %d%% Декодирование: %d%%
1 Action Действие
42 Contacts (RNode) Контакты (RNode)
43 Contacts (RNode - %.3fMHz) Контакты (RNode - %.3fМГц)
44 Conversation protocol mismatch Несовпадение протокола
45 Cyrillic keyboard Кириллическая клавиатура
46 Data Exchange Обмен данными
47 Debug Logs Журналы отладки
48 Decoding: %d%% Декодирование: %d%%
+4 -4
View File
@@ -1,14 +1,14 @@
kind=package
id=europe-cyrillic-ext
package_type=locale-bundle
version=1.1.1
version=1.2.0
display_name=European Cyrillic Extended
summary=Shared Cyrillic font coverage plus a Russian review locale pack; display-only until native review is complete.
summary=Shared Cyrillic font coverage, a Russian review locale pack, and a Russian Cyrillic keyboard layout.
description=DESCRIPTION.txt
readme=README.md
author=Trail Mate
homepage=https://vicliu624.github.io/trail-mate/
min_firmware_version=0.1.24-alpha
min_firmware_version=0.1.25-alpha
supported_memory_profiles=constrained,standard,extended
tags=language,cyrillic,europe,russian,review
tags=language,cyrillic,europe,russian,review,ime
; Russian translations contributed by polarikus.
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) Kontakte (Meshtastic - %.3fMHz)
Contacts (RNode) Kontakte (RNode)
Contacts (RNode - %.3fMHz) Kontakte (RNode - %.3fMHz)
Conversation protocol mismatch Konversationsprotokoll stimmt nicht überein
Cyrillic keyboard Kyrillische Tastatur
Data Exchange Datenaustausch
Debug Logs Debug-Protokolle
Decoding: %d%% Dekodierung: %d%%
1 Action Aktion
42 Contacts (RNode) Kontakte (RNode)
43 Contacts (RNode - %.3fMHz) Kontakte (RNode - %.3fMHz)
44 Conversation protocol mismatch Konversationsprotokoll stimmt nicht überein
45 Cyrillic keyboard Kyrillische Tastatur
46 Data Exchange Datenaustausch
47 Debug Logs Debug-Protokolle
48 Decoding: %d%% Dekodierung: %d%%
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) Contactos (Meshtastic - %.3fMHz)
Contacts (RNode) Contactos (RNode)
Contacts (RNode - %.3fMHz) Contactos (RNode - %.3fMHz)
Conversation protocol mismatch Protocolo no coincide
Cyrillic keyboard Teclado cirílico
Data Exchange Intercambio datos
Debug Logs Registros depuración
Decoding: %d%% Decodificando: %d%%
1 Action Acción
42 Contacts (RNode) Contactos (RNode)
43 Contacts (RNode - %.3fMHz) Contactos (RNode - %.3fMHz)
44 Conversation protocol mismatch Protocolo no coincide
45 Cyrillic keyboard Teclado cirílico
46 Data Exchange Intercambio datos
47 Debug Logs Registros depuración
48 Decoding: %d%% Decodificando: %d%%
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) Contacts (Meshtastic - %.3fMHz)
Contacts (RNode) Contacts (RNode)
Contacts (RNode - %.3fMHz) Contacts (RNode - %.3fMHz)
Conversation protocol mismatch Protocole incompatible
Cyrillic keyboard Clavier cyrillique
Data Exchange Échange données
Debug Logs Journaux débogage
Decoding: %d%% Décodage : %d%%
1 Action Action
42 Contacts (RNode) Contacts (RNode)
43 Contacts (RNode - %.3fMHz) Contacts (RNode - %.3fMHz)
44 Conversation protocol mismatch Protocole incompatible
45 Cyrillic keyboard Clavier cyrillique
46 Data Exchange Échange données
47 Debug Logs Journaux débogage
48 Decoding: %d%% Décodage : %d%%
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) Contatti (Meshtastic - %.3fMHz)
Contacts (RNode) Contatti (RNode)
Contacts (RNode - %.3fMHz) Contatti (RNode - %.3fMHz)
Conversation protocol mismatch Protocollo non corrisponde
Cyrillic keyboard Tastiera cirillica
Data Exchange Scambio dati
Debug Logs Log di debug
Decoding: %d%% Decodifica: %d%%
1 Action Azione
42 Contacts (RNode) Contatti (RNode)
43 Contacts (RNode - %.3fMHz) Contatti (RNode - %.3fMHz)
44 Conversation protocol mismatch Protocollo non corrisponde
45 Cyrillic keyboard Tastiera cirillica
46 Data Exchange Scambio dati
47 Debug Logs Log di debug
48 Decoding: %d%% Decodifica: %d%%
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) Contacten (Meshtastic - %.3fMHz)
Contacts (RNode) Contacten (RNode)
Contacts (RNode - %.3fMHz) Contacten (RNode - %.3fMHz)
Conversation protocol mismatch Gespreksprotocol komt niet overeen
Cyrillic keyboard Cyrillisch toetsenbord
Data Exchange Gegevensuitwisseling
Debug Logs Foutopsporingslogboeken
Decoding: %d%% Decodering: %d%%
1 Action Actie
42 Contacts (RNode) Contacten (RNode)
43 Contacts (RNode - %.3fMHz) Contacten (RNode - %.3fMHz)
44 Conversation protocol mismatch Gespreksprotocol komt niet overeen
45 Cyrillic keyboard Cyrillisch toetsenbord
46 Data Exchange Gegevensuitwisseling
47 Debug Logs Foutopsporingslogboeken
48 Decoding: %d%% Decodering: %d%%
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) Kontakty (Meshtastic - %.3fMHz)
Contacts (RNode) Kontakty (RNode)
Contacts (RNode - %.3fMHz) Kontakty (RNode - %.3fMHz)
Conversation protocol mismatch Niezgodność protokołu konwersacji
Cyrillic keyboard Klawiatura cyryliczna
Data Exchange Wymiana danych
Debug Logs Dzienniki debugowania
Decoding: %d%% Dekodowanie: %d%%
1 Action Akcja
42 Contacts (RNode) Kontakty (RNode)
43 Contacts (RNode - %.3fMHz) Kontakty (RNode - %.3fMHz)
44 Conversation protocol mismatch Niezgodność protokołu konwersacji
45 Cyrillic keyboard Klawiatura cyryliczna
46 Data Exchange Wymiana danych
47 Debug Logs Dzienniki debugowania
48 Decoding: %d%% Dekodowanie: %d%%
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) Contatos (Meshtastic - %.3fMHz)
Contacts (RNode) Contatos (RNode)
Contacts (RNode - %.3fMHz) Contatos (RNode - %.3fMHz)
Conversation protocol mismatch Incompatibilidade de protocolo de conversação
Cyrillic keyboard Teclado cirílico
Data Exchange Troca de dados
Debug Logs Logs de depuração
Decoding: %d%% Decodificação: %d%%
1 Action Ação
42 Contacts (RNode) Contatos (RNode)
43 Contacts (RNode - %.3fMHz) Contatos (RNode - %.3fMHz)
44 Conversation protocol mismatch Incompatibilidade de protocolo de conversação
45 Cyrillic keyboard Teclado cirílico
46 Data Exchange Troca de dados
47 Debug Logs Logs de depuração
48 Decoding: %d%% Decodificação: %d%%
+1
View File
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) 連絡先 (Meshtastic - %.3fMHz)
Contacts (RNode) 連絡先 (RNode)
Contacts (RNode - %.3fMHz) 連絡先 (RNode - %.3fMHz)
Conversation protocol mismatch 会話プロトコルが一致しません
Cyrillic keyboard キリル キーボード
Data Exchange データ交換
Debug Logs デバッグ ログ
Decoding: %d%% デコード: %d%%
1 Action アクション
42 Contacts (RNode) 連絡先 (RNode)
43 Contacts (RNode - %.3fMHz) 連絡先 (RNode - %.3fMHz)
44 Conversation protocol mismatch 会話プロトコルが一致しません
45 Cyrillic keyboard キリル キーボード
46 Data Exchange データ交換
47 Debug Logs デバッグ ログ
48 Decoding: %d%% デコード: %d%%
+1
View File
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) 연락처(Meshtastic - %.3fMHz)
Contacts (RNode) 연락처(RNode)
Contacts (RNode - %.3fMHz) 연락처(RNode - %.3fMHz)
Conversation protocol mismatch 대화 프로토콜 불일치
Cyrillic keyboard 키릴 키보드
Data Exchange 데이터 교환
Debug Logs 디버그 로그
Decoding: %d%% 디코딩: %d%%
1 Action 액션
42 Contacts (RNode) 연락처(RNode)
43 Contacts (RNode - %.3fMHz) 연락처(RNode - %.3fMHz)
44 Conversation protocol mismatch 대화 프로토콜 불일치
45 Cyrillic keyboard 키릴 키보드
46 Data Exchange 데이터 교환
47 Debug Logs 디버그 로그
48 Decoding: %d%% 디코딩: %d%%
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) 联系人(Meshtastic - %.3fMHz)
Contacts (RNode) 联系人(RNode)
Contacts (RNode - %.3fMHz) 联系人(RNode - %.3fMHz)
Conversation protocol mismatch 会话协议不匹配
Cyrillic keyboard 西里尔键盘
Data Exchange 数据交换
Debug Logs 调试日志
Decoding: %d%% 解码中:%d%%
1 Action 操作
42 Contacts (RNode) 联系人(RNode)
43 Contacts (RNode - %.3fMHz) 联系人(RNode - %.3fMHz)
44 Conversation protocol mismatch 会话协议不匹配
45 Cyrillic keyboard 西里尔键盘
46 Data Exchange 数据交换
47 Debug Logs 调试日志
48 Decoding: %d%% 解码中:%d%%
@@ -42,6 +42,7 @@ Contacts (Meshtastic - %.3fMHz) 聯絡人(Meshtastic - %.3fMHz)
Contacts (RNode) 聯絡人(RNode)
Contacts (RNode - %.3fMHz) 聯絡人(RNode - %.3fMHz)
Conversation protocol mismatch 會話協議不匹配
Cyrillic keyboard 西里爾鍵盤
Data Exchange 資料交換
Debug Logs 除錯日誌
Decoding: %d%% 解碼中:%d%%
1 Action 操作
42 Contacts (RNode) 聯絡人(RNode)
43 Contacts (RNode - %.3fMHz) 聯絡人(RNode - %.3fMHz)
44 Conversation protocol mismatch 會話協議不匹配
45 Cyrillic keyboard 西里爾鍵盤
46 Data Exchange 資料交換
47 Debug Logs 除錯日誌
48 Decoding: %d%% 解碼中:%d%%
+1
View File
@@ -362,6 +362,7 @@ def collect_ime_records(stage_bundle_dir: Path) -> list[dict[str, object]]:
"id": manifest.get("id", manifest_path.parent.name),
"display_name": manifest.get("display_name", manifest_path.parent.name),
"backend": manifest.get("backend", "none"),
"layout": manifest.get("layout", ""),
}
)
return records
+11
View File
@@ -43,6 +43,11 @@ ALLOWED_ENGLISH_EQUIVALENTS = {
REAL_IME_BACKENDS = {
"builtin-pinyin": {"zh-hans-pinyin"},
"builtin-keyboard-layout": {"ru-cyrillic-keyboard"},
}
REAL_IME_LAYOUTS = {
"ru-cyrillic-keyboard": "ru-cyrillic",
}
@@ -159,6 +164,12 @@ def validate_manifests(pack_root: Path) -> list[str]:
backend = manifest.get("backend", "none")
if ime_id not in REAL_IME_BACKENDS.get(backend, set()):
errors.append(f"{manifest_path}: unsupported runtime IME backend/id: {backend}/{ime_id}")
expected_layout = REAL_IME_LAYOUTS.get(ime_id, "")
layout = manifest.get("layout", "")
if expected_layout and layout != expected_layout:
errors.append(f"{manifest_path}: expected layout={expected_layout} for IME {ime_id}")
elif not expected_layout and layout:
errors.append(f"{manifest_path}: unexpected layout for non-layout IME: {ime_id}")
ime_backends[ime_id] = backend
for manifest_path in sorted(pack_root.glob("*/locales/*/manifest.ini")):