diff --git a/docs/specification/LOCALE_PACKS.md b/docs/specification/LOCALE_PACKS.md index 9b056e93..d5b497f3 100644 --- a/docs/specification/LOCALE_PACKS.md +++ b/docs/specification/LOCALE_PACKS.md @@ -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`。 ## 新增一种语言 diff --git a/docs/specification/LOCALE_PACK_RELEASE_SPEC.md b/docs/specification/LOCALE_PACK_RELEASE_SPEC.md index 8bb28285..bb01e1c3 100644 --- a/docs/specification/LOCALE_PACK_RELEASE_SPEC.md +++ b/docs/specification/LOCALE_PACK_RELEASE_SPEC.md @@ -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。 diff --git a/docs/specification/LOCALIZATION_SPEC.md b/docs/specification/LOCALIZATION_SPEC.md index 4e902418..bc5dae57 100644 --- a/docs/specification/LOCALIZATION_SPEC.md +++ b/docs/specification/LOCALIZATION_SPEC.md @@ -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 到俄文的硬件重映射。 当前不得启用的输入法包括: diff --git a/modules/ui_shared/include/ui/localization.h b/modules/ui_shared/include/ui/localization.h index 55d0335d..9c8ca00e 100644 --- a/modules/ui_shared/include/ui/localization.h +++ b/modules/ui_shared/include/ui/localization.h @@ -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; }; diff --git a/modules/ui_shared/include/ui/widgets/ime/ime_widget.h b/modules/ui_shared/include/ui/widgets/ime/ime_widget.h index 01edea6a..73ca0bb7 100644 --- a/modules/ui_shared/include/ui/widgets/ime/ime_widget.h +++ b/modules/ui_shared/include/ui/widgets/ime/ime_widget.h @@ -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); diff --git a/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp b/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp index af729bb7..f91176f6 100644 --- a/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp +++ b/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp @@ -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& 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() ? "" : pack.layout.c_str()); s_ime_packs.push_back(std::move(pack)); return true; } diff --git a/modules/ui_shared/src/ui/widgets/ime/ime_widget.cpp b/modules/ui_shared/src/ui/widgets/ime/ime_widget.cpp index a321fa69..ee3a7e4b 100644 --- a/modules/ui_shared/src/ui/widgets/ime/ime_widget.cpp +++ b/modules/ui_shared/src/ui/widgets/ime/ime_widget.cpp @@ -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& 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)) { diff --git a/packs/ar/locales/ar/strings.tsv b/packs/ar/locales/ar/strings.tsv index 145dbe3b..dcb67569 100644 --- a/packs/ar/locales/ar/strings.tsv +++ b/packs/ar/locales/ar/strings.tsv @@ -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%% diff --git a/packs/europe-cyrillic-ext/DESCRIPTION.txt b/packs/europe-cyrillic-ext/DESCRIPTION.txt index 4113d609..597fb7d1 100644 --- a/packs/europe-cyrillic-ext/DESCRIPTION.txt +++ b/packs/europe-cyrillic-ext/DESCRIPTION.txt @@ -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. diff --git a/packs/europe-cyrillic-ext/README.md b/packs/europe-cyrillic-ext/README.md index 514b5eb7..6b71e228 100644 --- a/packs/europe-cyrillic-ext/README.md +++ b/packs/europe-cyrillic-ext/README.md @@ -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. diff --git a/packs/europe-cyrillic-ext/ime/ru-cyrillic-keyboard/manifest.ini b/packs/europe-cyrillic-ext/ime/ru-cyrillic-keyboard/manifest.ini new file mode 100644 index 00000000..a291a3db --- /dev/null +++ b/packs/europe-cyrillic-ext/ime/ru-cyrillic-keyboard/manifest.ini @@ -0,0 +1,5 @@ +kind=ime +id=ru-cyrillic-keyboard +display_name=Russian Cyrillic Keyboard +backend=builtin-keyboard-layout +layout=ru-cyrillic diff --git a/packs/europe-cyrillic-ext/locales/ru/manifest.ini b/packs/europe-cyrillic-ext/locales/ru/manifest.ini index 65eb9a8c..0dd615ba 100644 --- a/packs/europe-cyrillic-ext/locales/ru/manifest.ini +++ b/packs/europe-cyrillic-ext/locales/ru/manifest.ini @@ -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 diff --git a/packs/europe-cyrillic-ext/locales/ru/strings.tsv b/packs/europe-cyrillic-ext/locales/ru/strings.tsv index abc36c3a..b03d34ba 100644 --- a/packs/europe-cyrillic-ext/locales/ru/strings.tsv +++ b/packs/europe-cyrillic-ext/locales/ru/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%% diff --git a/packs/europe-cyrillic-ext/package.ini b/packs/europe-cyrillic-ext/package.ini index 647744a6..e9c2b480 100644 --- a/packs/europe-cyrillic-ext/package.ini +++ b/packs/europe-cyrillic-ext/package.ini @@ -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. diff --git a/packs/europe-latin-ext/locales/de/strings.tsv b/packs/europe-latin-ext/locales/de/strings.tsv index ae7f0abb..4ddd4097 100644 --- a/packs/europe-latin-ext/locales/de/strings.tsv +++ b/packs/europe-latin-ext/locales/de/strings.tsv @@ -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%% diff --git a/packs/europe-latin-ext/locales/es/strings.tsv b/packs/europe-latin-ext/locales/es/strings.tsv index 04094aa1..b55dfe85 100644 --- a/packs/europe-latin-ext/locales/es/strings.tsv +++ b/packs/europe-latin-ext/locales/es/strings.tsv @@ -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%% diff --git a/packs/europe-latin-ext/locales/fr/strings.tsv b/packs/europe-latin-ext/locales/fr/strings.tsv index 048d2153..020e9b2e 100644 --- a/packs/europe-latin-ext/locales/fr/strings.tsv +++ b/packs/europe-latin-ext/locales/fr/strings.tsv @@ -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%% diff --git a/packs/europe-latin-ext/locales/it/strings.tsv b/packs/europe-latin-ext/locales/it/strings.tsv index 66fc2ac0..9afd604d 100644 --- a/packs/europe-latin-ext/locales/it/strings.tsv +++ b/packs/europe-latin-ext/locales/it/strings.tsv @@ -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%% diff --git a/packs/europe-latin-ext/locales/nl/strings.tsv b/packs/europe-latin-ext/locales/nl/strings.tsv index cef2cb1f..847e8b48 100644 --- a/packs/europe-latin-ext/locales/nl/strings.tsv +++ b/packs/europe-latin-ext/locales/nl/strings.tsv @@ -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%% diff --git a/packs/europe-latin-ext/locales/pl/strings.tsv b/packs/europe-latin-ext/locales/pl/strings.tsv index e4aab3c8..84bb2b3e 100644 --- a/packs/europe-latin-ext/locales/pl/strings.tsv +++ b/packs/europe-latin-ext/locales/pl/strings.tsv @@ -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%% diff --git a/packs/europe-latin-ext/locales/pt-PT/strings.tsv b/packs/europe-latin-ext/locales/pt-PT/strings.tsv index 3932db58..9510b5e1 100644 --- a/packs/europe-latin-ext/locales/pt-PT/strings.tsv +++ b/packs/europe-latin-ext/locales/pt-PT/strings.tsv @@ -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%% diff --git a/packs/ja/locales/ja/strings.tsv b/packs/ja/locales/ja/strings.tsv index 197097bb..07e0ac99 100644 --- a/packs/ja/locales/ja/strings.tsv +++ b/packs/ja/locales/ja/strings.tsv @@ -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%% diff --git a/packs/ko/locales/ko/strings.tsv b/packs/ko/locales/ko/strings.tsv index d2bc5a8e..06f995a3 100644 --- a/packs/ko/locales/ko/strings.tsv +++ b/packs/ko/locales/ko/strings.tsv @@ -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%% diff --git a/packs/zh-Hans/locales/zh-Hans/strings.tsv b/packs/zh-Hans/locales/zh-Hans/strings.tsv index bcd7105e..d9f7c600 100644 --- a/packs/zh-Hans/locales/zh-Hans/strings.tsv +++ b/packs/zh-Hans/locales/zh-Hans/strings.tsv @@ -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%% diff --git a/packs/zh-Hant/locales/zh-Hant/strings.tsv b/packs/zh-Hant/locales/zh-Hant/strings.tsv index d94504f3..375f1935 100644 --- a/packs/zh-Hant/locales/zh-Hant/strings.tsv +++ b/packs/zh-Hant/locales/zh-Hant/strings.tsv @@ -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%% diff --git a/scripts/build_pack_repository.py b/scripts/build_pack_repository.py index 38fa01a3..11db34b1 100644 --- a/scripts/build_pack_repository.py +++ b/scripts/build_pack_repository.py @@ -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 diff --git a/tools/validate_locale_packs.py b/tools/validate_locale_packs.py index 0536be64..ec7d34a9 100644 --- a/tools/validate_locale_packs.py +++ b/tools/validate_locale_packs.py @@ -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")):