From 7473151c8a4c2248200c6e864142a5227f221aa8 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:12:09 +0200 Subject: [PATCH 1/6] feat: I2C keyboard scanner + cardkb (#183) * I2C keyboard scanner + cardkb * scan all keyboards * request input from all keyboards * CardKB Fn+ENTER: simulate LONG PRESS --- include/input/I2CKeyboardInputDriver.h | 85 ++++++++- include/input/I2CKeyboardScanner.h | 17 ++ source/graphics/DeviceGUI.cpp | 20 +-- source/input/I2CKeyboardInputDriver.cpp | 225 +++++++++++++++++++++++- source/input/I2CKeyboardScanner.cpp | 58 ++++++ 5 files changed, 384 insertions(+), 21 deletions(-) create mode 100644 include/input/I2CKeyboardScanner.h create mode 100644 source/input/I2CKeyboardScanner.cpp diff --git a/include/input/I2CKeyboardInputDriver.h b/include/input/I2CKeyboardInputDriver.h index c4ed820..af5b7a8 100644 --- a/include/input/I2CKeyboardInputDriver.h +++ b/include/input/I2CKeyboardInputDriver.h @@ -1,15 +1,94 @@ #pragma once #include "input/InputDriver.h" +#include +#include +#include class I2CKeyboardInputDriver : public InputDriver { public: I2CKeyboardInputDriver(void); virtual void init(void) override; - virtual void task_handler(void) override; - virtual ~I2CKeyboardInputDriver(void); + virtual void task_handler(void) override{}; + virtual void readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) = 0; + virtual ~I2CKeyboardInputDriver(void) {} + + struct KeyboardDefinition { + I2CKeyboardInputDriver *driver; // Pointer to the driver instance + std::string name; // Name of the keyboard + uint8_t address; // I2C address of the keyboard + }; + + using KeyboardList = std::list>; + static KeyboardList &getI2CKeyboardList(void) { return i2cKeyboardList; } + + protected: + bool registerI2CKeyboard(I2CKeyboardInputDriver *driver, std::string name, uint8_t address); private: static void keyboard_read(lv_indev_t *indev, lv_indev_data_t *data); -}; \ No newline at end of file + + static KeyboardList i2cKeyboardList; // list of registered I2C keyboards +}; + +class TDeckKeyboardInputDriver : public I2CKeyboardInputDriver +{ + public: + TDeckKeyboardInputDriver(uint8_t address); + void readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) override; + virtual ~TDeckKeyboardInputDriver(void) {} +}; + +class TCA8418KeyboardInputDriver : public I2CKeyboardInputDriver +{ + public: + TCA8418KeyboardInputDriver(uint8_t address); + void init(void) override; + void readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) override; + virtual ~TCA8418KeyboardInputDriver(void) {} +}; + +class TLoraPagerKeyboardInputDriver : public TCA8418KeyboardInputDriver +{ + public: + TLoraPagerKeyboardInputDriver(uint8_t address); + void init(void) override; + void readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) override; + virtual ~TLoraPagerKeyboardInputDriver(void) {} +}; + +class TDeckProKeyboardInputDriver : public TCA8418KeyboardInputDriver +{ + public: + TDeckProKeyboardInputDriver(uint8_t address); + void init(void) override; + void readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) override; + virtual ~TDeckProKeyboardInputDriver(void) {} +}; + +class BBQ10KeyboardInputDriver : public I2CKeyboardInputDriver +{ + public: + BBQ10KeyboardInputDriver(uint8_t address); + void init(void) override; + void readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) override; + virtual ~BBQ10KeyboardInputDriver(void) {} +}; + +class CardKBInputDriver : public I2CKeyboardInputDriver +{ + public: + CardKBInputDriver(uint8_t address); + void readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) override; + virtual ~CardKBInputDriver(void) {} +}; + +class MPR121KeyboardInputDriver : public I2CKeyboardInputDriver +{ + public: + MPR121KeyboardInputDriver(uint8_t address); + void init(void) override; + void readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) override; + virtual ~MPR121KeyboardInputDriver(void) {} +}; diff --git a/include/input/I2CKeyboardScanner.h b/include/input/I2CKeyboardScanner.h new file mode 100644 index 0000000..47451ea --- /dev/null +++ b/include/input/I2CKeyboardScanner.h @@ -0,0 +1,17 @@ +#pragma once + +class I2CKeyboardInputDriver; + +/** + * @brief This class is used to scan I2C connected keyboards and creates + * the corresponding input device (factory). + * It is used by the T-Deck, T-Deck Pro, T-Lora Pager, and other + * I2C connected keyboards. + */ +class I2CKeyboardScanner +{ + public: + I2CKeyboardScanner(void); + virtual I2CKeyboardInputDriver *scan(void); + virtual ~I2CKeyboardScanner(void) {} +}; diff --git a/source/graphics/DeviceGUI.cpp b/source/graphics/DeviceGUI.cpp index 04b8d2a..03a89fb 100644 --- a/source/graphics/DeviceGUI.cpp +++ b/source/graphics/DeviceGUI.cpp @@ -1,17 +1,17 @@ #include "graphics/DeviceGUI.h" #include "graphics/driver/DisplayDriver.h" #include "graphics/driver/DisplayDriverConfig.h" +#include "input/I2CKeyboardScanner.h" #include "input/InputDriver.h" #include +#include "input/I2CKeyboardInputDriver.h" +static I2CKeyboardInputDriver *keyboardDriver = nullptr; + #if LV_USE_LIBINPUT #include "input/LinuxInputDriver.h" static LinuxInputDriver *linuxInputDriver = nullptr; #else -#if defined(INPUTDRIVER_I2C_KBD_TYPE) -#include "input/I2CKeyboardInputDriver.h" -static I2CKeyboardInputDriver *keyboardDriver = nullptr; -#endif #if defined(INPUTDRIVER_ENCODER_TYPE) #include "input/EncoderInputDriver.h" static EncoderInputDriver *encoderDriver = nullptr; @@ -31,15 +31,13 @@ static ButtonInputDriver *buttonDriver = nullptr; DeviceGUI::DeviceGUI(const DisplayDriverConfig *cfg, DisplayDriver *driver) : displaydriver(driver), inputdriver(nullptr) { + #if LV_USE_LIBINPUT if (cfg) linuxInputDriver = new LinuxInputDriver(cfg->keyboard(), cfg->pointer()); // else // linuxInputDriver = InputDriver::instance(); #else -#if defined(INPUTDRIVER_I2C_KBD_TYPE) - keyboardDriver = new I2CKeyboardInputDriver; -#endif #if defined(INPUTDRIVER_ENCODER_TYPE) encoderDriver = new EncoderInputDriver; #endif @@ -60,14 +58,14 @@ void DeviceGUI::init(IClientBase *client) displaydriver->init(this); ILOG_DEBUG("Input driver init..."); + I2CKeyboardScanner scanner; + keyboardDriver = scanner.scan(); + if (keyboardDriver) + keyboardDriver->init(); #if LV_USE_LIBINPUT if (linuxInputDriver) linuxInputDriver->init(); #endif -#if defined(INPUTDRIVER_I2C_KBD_TYPE) - if (keyboardDriver) - keyboardDriver->init(); -#endif #if defined(INPUTDRIVER_ENCODER_TYPE) if (encoderDriver) encoderDriver->init(); diff --git a/source/input/I2CKeyboardInputDriver.cpp b/source/input/I2CKeyboardInputDriver.cpp index c667726..84687bb 100644 --- a/source/input/I2CKeyboardInputDriver.cpp +++ b/source/input/I2CKeyboardInputDriver.cpp @@ -1,10 +1,13 @@ -#ifdef INPUTDRIVER_I2C_KBD_TYPE #include "input/I2CKeyboardInputDriver.h" #include "util/ILog.h" #include #include +#include "indev/lv_indev_private.h" + +I2CKeyboardInputDriver::KeyboardList I2CKeyboardInputDriver::i2cKeyboardList; + I2CKeyboardInputDriver::I2CKeyboardInputDriver(void) {} void I2CKeyboardInputDriver::init(void) @@ -20,6 +23,33 @@ void I2CKeyboardInputDriver::init(void) lv_indev_set_group(keyboard, inputGroup); } +bool I2CKeyboardInputDriver::registerI2CKeyboard(I2CKeyboardInputDriver *driver, std::string name, uint8_t address) +{ + auto keyboardDef = std::unique_ptr(new KeyboardDefinition{driver, name, address}); + i2cKeyboardList.push_back(std::move(keyboardDef)); + ILOG_INFO("Registered I2C keyboard: %s at address 0x%02X", name.c_str(), address); + return true; +} + +void I2CKeyboardInputDriver::keyboard_read(lv_indev_t *indev, lv_indev_data_t *data) +{ + // Read from all registered keyboards + for (auto &keyboardDef : i2cKeyboardList) { + keyboardDef->driver->readKeyboard(keyboardDef->address, indev, data); + if (data->state == LV_INDEV_STATE_PRESSED) { + // If any keyboard reports a key press, we stop reading further + return; + } + } +} + +// ---------- TDeckKeyboardInputDriver Implementation ---------- + +TDeckKeyboardInputDriver::TDeckKeyboardInputDriver(uint8_t address) +{ + registerI2CKeyboard(this, "T-Deck Keyboard", address); +} + /****************************************************************** LV_KEY_NEXT: Focus on the next object LV_KEY_PREV: Focus on the previous object @@ -48,11 +78,11 @@ void I2CKeyboardInputDriver::init(void) LV_KEY_END = 3, // 0x03, ETX *******************************************************************/ -void I2CKeyboardInputDriver::keyboard_read(lv_indev_t *indev, lv_indev_data_t *data) +void TDeckKeyboardInputDriver::readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) { char keyValue = 0; - Wire.requestFrom(INPUTDRIVER_I2C_KBD_TYPE, 1); - if (Wire.available() > 0) { + uint8_t bytes = Wire.requestFrom(address, 1); + if (Wire.available() > 0 && bytes > 0) { keyValue = Wire.read(); // ignore empty reads and keycode 224(E0, shift-0 on T-Deck) which causes internal issues if (keyValue != (char)0x00 && keyValue != (char)0xE0) { @@ -73,8 +103,189 @@ void I2CKeyboardInputDriver::keyboard_read(lv_indev_t *indev, lv_indev_data_t *d data->key = (uint32_t)keyValue; } -void I2CKeyboardInputDriver::task_handler(void) {} +// ---------- TCA8418KeyboardInputDriver Implementation ---------- -I2CKeyboardInputDriver::~I2CKeyboardInputDriver(void) {} +TCA8418KeyboardInputDriver::TCA8418KeyboardInputDriver(uint8_t address) +{ + registerI2CKeyboard(this, "TCA8418 Keyboard", address); +} -#endif \ No newline at end of file +void TCA8418KeyboardInputDriver::init(void) +{ + // Additional initialization for TCA8418 if needed + I2CKeyboardInputDriver::init(); +} + +void TCA8418KeyboardInputDriver::readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) +{ + // TODO + char keyValue = 0; + data->state = LV_INDEV_STATE_RELEASED; + data->key = (uint32_t)keyValue; +} + +// ---------- TLoraPagerKeyboardInputDriver Implementation ---------- + +TLoraPagerKeyboardInputDriver::TLoraPagerKeyboardInputDriver(uint8_t address) : TCA8418KeyboardInputDriver(address) +{ + registerI2CKeyboard(this, "TLora Pager Keyboard", address); +} + +void TLoraPagerKeyboardInputDriver::init(void) +{ + // Additional initialization for TLora-Pager if needed + TCA8418KeyboardInputDriver::init(); +} + +void TLoraPagerKeyboardInputDriver::readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) +{ + // TODO + char keyValue = 0; + data->state = LV_INDEV_STATE_RELEASED; + data->key = (uint32_t)keyValue; +} + +// ---------- TDeckProKeyboardInputDriver Implementation ---------- + +TDeckProKeyboardInputDriver::TDeckProKeyboardInputDriver(uint8_t address) : TCA8418KeyboardInputDriver(address) +{ + registerI2CKeyboard(this, "T-Deck Pro Keyboard", address); +} + +void TDeckProKeyboardInputDriver::init(void) +{ + // Additional initialization for TLora-Pager if needed + TCA8418KeyboardInputDriver::init(); +} + +void TDeckProKeyboardInputDriver::readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) +{ + // TODO + char keyValue = 0; + data->state = LV_INDEV_STATE_RELEASED; + data->key = (uint32_t)keyValue; +} + +// ---------- BBQ10KeyboardInputDriver Implementation ---------- + +BBQ10KeyboardInputDriver::BBQ10KeyboardInputDriver(uint8_t address) +{ + registerI2CKeyboard(this, "BBQ10 Keyboard", address); +} + +void BBQ10KeyboardInputDriver::init(void) +{ + I2CKeyboardInputDriver::init(); + // Additional initialization for BBQ10 if needed +} + +void BBQ10KeyboardInputDriver::readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) +{ + char keyValue = 0; + uint8_t bytes = Wire.requestFrom(address, 1); + if (Wire.available() > 0 && bytes > 0) { + keyValue = Wire.read(); + // ignore empty reads and keycode 224(E0, shift-0 on T-Deck) which causes internal issues + if (keyValue != (char)0x00 && keyValue != (char)0xE0) { + data->state = LV_INDEV_STATE_PRESSED; + ILOG_DEBUG("key press value: %d", (int)keyValue); + + switch (keyValue) { + case 0x0D: + keyValue = LV_KEY_ENTER; + break; + default: + break; + } + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + } + data->key = (uint32_t)keyValue; +} + +// ---------- CardKBInputDriver Implementation ---------- + +CardKBInputDriver::CardKBInputDriver(uint8_t address) +{ + registerI2CKeyboard(this, "Card Keyboard", address); +} + +void CardKBInputDriver::readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) +{ + char keyValue = 0; + Wire.requestFrom(address, 1); + if (Wire.available() > 0) { + keyValue = Wire.read(); + // ignore empty reads and keycode 224 which causes internal issues + if (keyValue != (char)0x00 && keyValue != (char)0xE0) { + data->state = LV_INDEV_STATE_PRESSED; + ILOG_DEBUG("key press value: %d", (int)keyValue); + + switch (keyValue) { + case 0x0D: + keyValue = LV_KEY_ENTER; + break; + case 0xB4: + keyValue = LV_KEY_LEFT; + break; + case 0xB5: + keyValue = LV_KEY_UP; + break; + case 0xB6: + keyValue = LV_KEY_DOWN; + break; + case 0xB7: + keyValue = LV_KEY_RIGHT; + break; + case 0x99: // Fn+UP + keyValue = LV_KEY_HOME; + break; + case 0xA4: // Fn+DOWN + keyValue = LV_KEY_END; + break; + case 0x8B: // Fn+BS + keyValue = LV_KEY_DEL; + break; + case 0x8C: // Fn+TAB + keyValue = LV_KEY_PREV; + break; + case 0xA3: // Fn+ENTER + // simulate a long press on Fn+ENTER (see indev_keypad_proc() in indev.c) + indev->wait_until_release = 0; + indev->pr_timestamp = lv_tick_get() - indev->long_press_time - 1; + indev->long_pr_sent = 0; + indev->keypad.last_state = LV_INDEV_STATE_PRESSED; + indev->keypad.last_key = LV_KEY_ENTER; + keyValue = LV_KEY_ENTER; + break; + default: + break; + } + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + } + data->key = (uint32_t)keyValue; +} + +// ---------- MPR121KeyboardInputDriver Implementation ---------- + +MPR121KeyboardInputDriver::MPR121KeyboardInputDriver(uint8_t address) +{ + registerI2CKeyboard(this, "MPR121 Keyboard", address); +} + +void MPR121KeyboardInputDriver::init(void) +{ + I2CKeyboardInputDriver::init(); + // Additional initialization for MPR121 if needed +} + +void MPR121KeyboardInputDriver::readKeyboard(uint8_t address, lv_indev_t *indev, lv_indev_data_t *data) +{ + // TODO + char keyValue = 0; + data->state = LV_INDEV_STATE_RELEASED; + data->key = (uint32_t)keyValue; +} diff --git a/source/input/I2CKeyboardScanner.cpp b/source/input/I2CKeyboardScanner.cpp new file mode 100644 index 0000000..cba78a4 --- /dev/null +++ b/source/input/I2CKeyboardScanner.cpp @@ -0,0 +1,58 @@ +#include "input/I2CKeyboardScanner.h" +#include "input/I2CKeyboardInputDriver.h" + +#include "util/ILog.h" +#include + +enum KeyboardAddresses { + SCAN_TDECK_KB_ADDR = 0x55, + SCAN_TCA8418_KB_ADDR = 0x34, + SCAN_CARDKB_ADDR = 0x5F, + SCAN_BBQ10_KB_ADDR = 0x1F, + SCAN_MPR121_KB_ADDR = 0x5A +}; + +I2CKeyboardScanner::I2CKeyboardScanner(void) {} + +I2CKeyboardInputDriver *I2CKeyboardScanner::scan(void) +{ + ILOG_DEBUG("I2CKeyboardScanner::init ..."); + uint8_t i2cKeyboards[] = {SCAN_TDECK_KB_ADDR, SCAN_TCA8418_KB_ADDR, SCAN_CARDKB_ADDR, SCAN_BBQ10_KB_ADDR, + SCAN_MPR121_KB_ADDR}; + I2CKeyboardInputDriver *driver = nullptr; + for (uint8_t i = 0; i < sizeof(i2cKeyboards); i++) { + uint8_t address = i2cKeyboards[i]; + Wire.beginTransmission(address); + if (Wire.endTransmission() == 0) { + switch (address) { + case SCAN_TDECK_KB_ADDR: + driver = new TDeckKeyboardInputDriver(address); + break; + case SCAN_TCA8418_KB_ADDR: +#if defined(T_LORA_PAGER) + driver = new TLoraPagerKeyboardInputDriver(address); +#elif defined(T_DECK_PRO) + driver = new TDeckProKeyboardInputDriver(address); +#else + driver = new TCA8418KeyboardInputDriver(address); +#endif + break; + case SCAN_CARDKB_ADDR: + driver = new CardKBInputDriver(address); + break; + case SCAN_BBQ10_KB_ADDR: + driver = new BBQ10KeyboardInputDriver(address); + break; + case SCAN_MPR121_KB_ADDR: + driver = new MPR121KeyboardInputDriver(address); + break; + default: + break; + } + } + } + if (I2CKeyboardInputDriver::getI2CKeyboardList().empty()) { + ILOG_DEBUG("No I2C keyboards found"); + } + return driver; +} From 0f32b64dca418c6465763ec576509a6a2bfbc50a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:23:11 +0200 Subject: [PATCH 2/6] Upgrade trunk (#182) Co-authored-by: mverch67 <71137295+mverch67@users.noreply.github.com> --- .trunk/trunk.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 331bbd4..e1de4fa 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -6,7 +6,7 @@ cli: plugins: sources: - id: trunk - ref: v1.7.1 + ref: v1.7.2 uri: https://github.com/trunk-io/plugins # Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) runtimes: @@ -20,12 +20,12 @@ lint: - trufflehog@3.90.5 - yamllint@1.37.1 - bandit@1.8.6 - - checkov@3.2.461 + - checkov@3.2.464 - terrascan@1.19.9 - - trivy@0.64.1 + - trivy@0.65.0 #- trufflehog@3.63.2-rc0 - - taplo@0.9.3 - - ruff@0.12.7 + - taplo@0.10.0 + - ruff@0.12.9 - isort@6.0.1 - markdownlint@0.45.0 - oxipng@9.1.5 @@ -34,7 +34,7 @@ lint: - flake8@7.3.0 - hadolint@2.12.1-beta - shfmt@3.6.0 - - shellcheck@0.10.0 + - shellcheck@0.11.0 - black@25.1.0 - git-diff-check - gitleaks@8.28.0 From ade58d6b5864967e163bc64ef8f8b42c0fe733e9 Mon Sep 17 00:00:00 2001 From: ave Date: Wed, 27 Aug 2025 13:41:11 +0200 Subject: [PATCH 3/6] fix display of LoRa frequency on TFTView_320x240 (#186) --- source/graphics/TFT/TFTView_320x240.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/source/graphics/TFT/TFTView_320x240.cpp b/source/graphics/TFT/TFTView_320x240.cpp index 1fee328..b8a069e 100644 --- a/source/graphics/TFT/TFTView_320x240.cpp +++ b/source/graphics/TFT/TFTView_320x240.cpp @@ -5819,7 +5819,14 @@ void TFTView_320x240::updateLoRaConfig(const meshtastic_Config_LoRaConfig &cfg) { db.config.lora = cfg; db.config.has_lora = true; - showLoRaFrequency(cfg); + + // This must be run before displaying LoRa frequency as channel of 0 ("calculate from hash") leads to an integer underflow + if (!db.config.lora.channel_num) { + db.config.lora.channel_num = LoRaPresets::getDefaultSlot(db.config.lora.region, THIS->db.config.lora.modem_preset, + THIS->db.channel[0].settings.name); + } + + showLoRaFrequency(db.config.lora); char region[30]; lv_snprintf(region, sizeof(region), _("Region: %s"), LoRaPresets::loRaRegionToString(cfg.region)); @@ -5833,11 +5840,6 @@ void TFTView_320x240::updateLoRaConfig(const meshtastic_Config_LoRaConfig &cfg) uint32_t numChannels = LoRaPresets::getNumChannels(cfg.region, cfg.modem_preset); lv_slider_set_range(objects.frequency_slot_slider, 1, numChannels); - - if (!db.config.lora.channel_num) { - db.config.lora.channel_num = LoRaPresets::getDefaultSlot(db.config.lora.region, THIS->db.config.lora.modem_preset, - THIS->db.channel[0].settings.name); - } lv_slider_set_value(objects.frequency_slot_slider, db.config.lora.channel_num, LV_ANIM_OFF); if (db.config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_UNSET) { @@ -5855,8 +5857,8 @@ void TFTView_320x240::updateLoRaConfig(const meshtastic_Config_LoRaConfig &cfg) void TFTView_320x240::showLoRaFrequency(const meshtastic_Config_LoRaConfig &cfg) { char loraFreq[48]; - float frequency = LoRaPresets::getRadioFreq(cfg.region, cfg.modem_preset, cfg.channel_num) + db.config.lora.frequency_offset; - if (frequency > 1.0 && frequency < 10000.0) { + float frequency = LoRaPresets::getRadioFreq(cfg.region, cfg.modem_preset, cfg.channel_num) + cfg.frequency_offset; + if (cfg.region) { sprintf(loraFreq, "LoRa %g MHz\n[%s kHz]", frequency, LoRaPresets::getBandwidthString(cfg.modem_preset)); } else { strcpy(loraFreq, _("region unset")); From aa92a3d81af11f97f1a72dd0247217e27e03e41b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:44:10 +0200 Subject: [PATCH 4/6] Upgrade trunk (#185) Co-authored-by: mverch67 <71137295+mverch67@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index e1de4fa..22f0edf 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -20,12 +20,12 @@ lint: - trufflehog@3.90.5 - yamllint@1.37.1 - bandit@1.8.6 - - checkov@3.2.464 + - checkov@3.2.466 - terrascan@1.19.9 - trivy@0.65.0 #- trufflehog@3.63.2-rc0 - taplo@0.10.0 - - ruff@0.12.9 + - ruff@0.12.10 - isort@6.0.1 - markdownlint@0.45.0 - oxipng@9.1.5 From a2e4526ce5250b8c4def292a875bfcd7c55bcf82 Mon Sep 17 00:00:00 2001 From: Aleksey Vasilenko Date: Wed, 27 Aug 2025 18:28:29 +0300 Subject: [PATCH 5/6] chore: update Ukrainian translation (#174) Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com> --- locale/uk.yml | 118 +++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/locale/uk.yml b/locale/uk.yml index d509318..34f71b4 100644 --- a/locale/uk.yml +++ b/locale/uk.yml @@ -1,6 +1,6 @@ uk: no new messages: немає повідомлень - 1 of 1 nodes online: 1 нода онлайн + 1 of 1 nodes online: 1 вузол онлайн uptime 00:00:00: час роботи 00:00:00 LoRa 0.0 MHz: ~ no signal: немає сигналу @@ -11,18 +11,18 @@ uk: map tiles not found!: мапи не знайдено! Settings: Налаштування "User name: ": "Ім'я користувача: " - "Modem Preset: LONG FAST": "Режим Модему: LONG FAST" - "Channel: LongFast": "Канал: LongFast" - "Role: Client": "Роль: Client" - "WiFi: ": "WiFi: <не задано>" - "Screen Timeout: 60s": "Таймаут Екрана: 60s" - "Lock: off/off": "Блокування: вимкн./вимкн." - "Screen Brightness: 60%": "Яскравість Екрана: 60%" - "Theme: Dark": "Тема: Темна" - "Screen Calibration: default": "Калібрування Екрана: за умовчанням" - "Input Control: none/none": "Контроль Вводу: немає/немає" - "Message Alert Buzzer: on": "Сповіщення: увімк." - "Language: English": "Мова: Українська" + 'Modem Preset: LONG FAST': 'Режим Модему: LONG FAST' + 'Channel: LongFast': 'Канал: LongFast' + 'Role: Client': 'Роль: Client' + 'WiFi: ': 'WiFi: <не задано>' + 'Screen Timeout: 60s': 'Таймаут Екрана: 60с' + 'Lock: off/off': 'Блокування: вимкн./вимкн.' + 'Screen Brightness: 60%': 'Яскравість Екрана: 60%' + 'Theme: Dark': 'Тема: Темна' + 'Screen Calibration: default': 'Калібрування Екрана: за умовчанням' + 'Input Control: none/none': 'Контроль Вводу: немає/немає' + 'Message Alert Buzzer: on': 'Сповіщення: увімк.' + 'Language: English': 'Мова: Українська' Configuration Reset: Скидання Налаштувань Backup & Restore: Збереження та Відновлення Reboot / Shutdown: Перезапуск / Вимкнення @@ -41,8 +41,8 @@ uk: Locations Map: Мапа no chats: немає чатів Node Search: Пошук Нод - Packet Statistics: Статистика пакетів - Node Options: Параметри Ноди + Packet Statistics: Статистика Пакетів + Node Options: Параметри Вузла LoRa TX off!: LoRa TX вимкн.! Short Name: Коротке Ім'я Long Name: Довге Ім'я @@ -53,9 +53,9 @@ uk: "Client\nClient Mute\nRouter\nRepeater\nTracker\nSensor\nTAK\nClient Hidden\nLost & Found\nTAK Tracker": ~ WiFi SSID: ~ WiFi pre-shared Key: ~ - "Brightness: 60%": "Яскравість: 60%" + 'Brightness: 60%': 'Яскравість: 60%' "Dark\nLight": "Темна\nСвітла" - "Timeout: 60s": "Таймаут: 60s" + 'Timeout: 60s': 'Таймаут: 60с' Screen Lock: Блокування Екрану Settings Lock: Блокування Налаштувань Lock PIN: PIN-код Блокування @@ -70,10 +70,10 @@ uk: Backup: Збереження Restore: Відновлення Public/Private Key: Публічний/Приватний Ключ - "NodeDB Reset\nFactory Reset\nClear Chat History": "Скидання NodeDB\nСкидання налаштувань\nОчистити історію чатів" - Channel Name: Ім'я каналу - Pre-shared Key: PSK ключ - Filter: Фільтр + "NodeDB Reset\nFactory Reset\nClear Chat History": "Скидання NodeDB\nСкидання Налаштувань\nОчистити Історію Чатів" + Channel Name: Ім'я Каналу + Pre-shared Key: PSK Ключ + Filter: Фільтрувати Unknown: Невідомо Offline: Не в мережі Public Key: Публічний Ключ @@ -82,13 +82,13 @@ uk: MQTT: ~ Position: Позиція Name: Ім'я - Highlight: Позначка + Highlight: Підсвічувати Active Chat: Активний Чат Telemetry: Телеметрія IAQ: ~ Start: Старт - "choose\nnode": "оберіть\nноду" - choose target node: оберіть цільову ноду + "choose\nnode": "оберіть\nвузол" + choose target node: оберіть цільовий вузол "New Message from\n": "Нове Повідомлення від\n" Restoring messages ...: Відновлення повідомлень ... Please set region and name: Будь ласка, вкажіть регіон та ім'я @@ -97,63 +97,63 @@ uk: OK: ~ Cancel: Скасувати now: зараз - "Lock: %s/%s": "Блокування: %s/%s" - "on": увімк. - "off": вимкн. - "Screen Calibration: %s": "Калібрування Екрана: %s" + 'Lock: %s/%s': 'Блокування: %s/%s' + 'on': увімк. + 'off': вимкн. + 'Screen Calibration: %s': 'Калібрування Екрана: %s' done: готово default: за умовчанням "Client\nClient Mute\nTracker\nSensor\nTAK\nClient Hidden\nLost & Found\nTAK Tracker": ~ Rebooting ...: Перезапуск ... - ">> Programming mode <<": ">> Режим програмування <<" + '>> Programming mode <<': '>> Режим програмування <<' Enter Text ...: Введіть Текст ... - "!Enter Filter ...": "!Введіть Фільтр ..." + '!Enter Filter ...': '!Введіть Фільтр ...' Enter Filter ...: Введіть Фільтр ... - "FrequencySlot: %d (%g MHz)": "Частотний слот: %d (%g MHz)" - "Brightness: %d%%": "Яскравість: %d%%" - "Timeout: off": "Таймаут: вимкн." - "Timeout: %ds": "Таймаут: %ds" + 'FrequencySlot: %d (%g MHz)': 'Частотний слот: %d (%g MHz)' + 'Brightness: %d%%': 'Яскравість: %d%%' + 'Timeout: off': 'Таймаут: вимкн.' + 'Timeout: %ds': 'Таймаут: %dс' No map tiles found on SDCard!: Мапи не знайдено! Locations Map (%d/%d): Мапа (%d/%d) Stop: Стоп - "heard: !%08x": "почуто: !%08x" - "Packet Log: %d": "Журнал Пакетів: %d" - "Language: %s": "Мова: %s" - "Screen Timeout: off": "Таймаут Екрана: вимкн." - "Screen Timeout: %ds": "Таймаут Екрана: %ds" - "Screen Brightness: %d%%": "Яскравість Екрана: %d%%" - "Theme: %s": "Тема: %s" - "Region: %s": "Регіон: %s" + 'heard: !%08x': 'почуто: !%08x' + 'Packet Log: %d': 'Журнал Пакетів: %d' + 'Language: %s': 'Мова: %s' + 'Screen Timeout: off': 'Таймаут Екрана: вимкн.' + 'Screen Timeout: %ds': 'Таймаут Екрана: %dс' + 'Screen Brightness: %d%%': 'Яскравість Екрана: %d%%' + 'Theme: %s': 'Тема: %s' + 'Region: %s': 'Регіон: %s' "User name: %s": "Ім'я користувача: %s" - "Device Role: %s": "Роль: %s" - "Modem Preset: %s": "Режим Модему: %s" - "WiFi: %s": ~ + 'Device Role: %s': 'Роль: %s' + 'Modem Preset: %s': 'Режим Модему: %s' + 'WiFi: %s': ~ : <не задано> Util %0.1f%% Air %0.1f%%: ~ - "hops: %d": "стрибки: %d" + 'hops: %d': 'стрибки: %d' unknown: невідомо Shutting down ...: Вимикання ... region unset: регіон не задано Banner & Sound: Банер та Звук Banner only: Тільки банер Sound only: Тільки звук - "Message Alert: %s": "Сповіщення: %s" - "Channel: %s": "Канал: %s" + 'Message Alert: %s': 'Сповіщення: %s' + 'Channel: %s': 'Канал: %s' Failed to write keys!: Не вдалося записати ключі! Failed to restore keys!: Не вдалося відновити ключі! Failed to parse keys!: Не вдалося розібрати ключі! Failed to retrieve keys!: Не вдалося отримати ключі! - "%d active chat(s)": - one: "%d активний чат" - other: "%d активні чати" + '%d active chat(s)': + one: '%d активний чат' + other: '%d активні чати' "New message from \n%s": "Нове повідомлення від \n%s" - "Input Control: %s/%s": "Контроль Вводу: %s/%s" - "%d of %d nodes online": - one: "1 нода онлайн" - other: "%d з %d нод онлайн" - "Filter: %d of %d nodes": "Фільтр: %d з %d нод" - "%d new message": "%d нове повідомлення" - "%d new messages": "%d нові повідомлення" - "uptime: %02d:%02d:%02d": "час роботи: %02d:%02d:%02d" + 'Input Control: %s/%s': 'Контроль Вводу: %s/%s' + '%d of %d nodes online': + one: 1 вузол онлайн + other: '%d з %d вузлів онлайн' + 'Filter: %d of %d nodes': 'Фільтр: %d з %d вузлів' + '%d new message': '%d нове повідомлення' + '%d new messages': '%d нові повідомлення' + 'uptime: %02d:%02d:%02d': 'час роботи: %02d:%02d:%02d' "%s (%0.1f GB)\nUsed: %d MB (%d%%)": "%s (%0.1f GB)\nВикорист.: %d MB (%d%%)" "Heap: %d (%d%%)\nLVGL: %d (%d%%)": ~ From a3e0e1be372d069f47b4c19d718f5267251744d7 Mon Sep 17 00:00:00 2001 From: mverch67 Date: Wed, 27 Aug 2025 17:38:34 +0200 Subject: [PATCH 6/6] update ukrainian translation --- locale/lv_i18n.c | 176 +++++++++++++++++++++++++++++------------------ locale/lv_i18n.h | 9 ++- 2 files changed, 116 insertions(+), 69 deletions(-) diff --git a/locale/lv_i18n.c b/locale/lv_i18n.c index 1c79d07..4f4d343 100644 --- a/locale/lv_i18n.c +++ b/locale/lv_i18n.c @@ -37,8 +37,13 @@ static inline uint32_t op_t(uint32_t val) UNUSED(val); return 0; } +static inline uint32_t op_e(uint32_t val) +{ + UNUSED(val); + return 0; +} -static lv_i18n_phrase_t en_singulars[] = { +static const lv_i18n_phrase_t en_singulars[] = { {"No map tiles found on SDCard!", "Map tiles not found!"}, {NULL, NULL} // End mark }; @@ -61,7 +66,7 @@ static const lv_i18n_lang_t en_lang = {.locale_name = "en", .locale_plural_fn = en_plural_fn}; -static lv_i18n_phrase_t de_singulars[] = { +static const lv_i18n_phrase_t de_singulars[] = { {"User name: %s", "Benutzer: %s"}, {"Device Role: %s", "Gerätemodus: %s"}, {"no new messages", "keine Nachrichten"}, @@ -184,11 +189,11 @@ static lv_i18n_phrase_t de_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t de_plurals_one[] = { +static const lv_i18n_phrase_t de_plurals_one[] = { {"%d active chat(s)", "%d aktives Gespräch"}, {"%d of %d nodes online", "1 Gerät online"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t de_plurals_other[] = { +static const lv_i18n_phrase_t de_plurals_other[] = { {"%d active chat(s)", "%d aktive Gespräche"}, {"%d of %d nodes online", "%d / %d Geräte online"}, {NULL, NULL} // End mark }; @@ -212,7 +217,7 @@ static const lv_i18n_lang_t de_lang = {.locale_name = "de", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = de_plurals_other, .locale_plural_fn = de_plural_fn}; -static lv_i18n_phrase_t el_singulars[] = { +static const lv_i18n_phrase_t el_singulars[] = { {"no new messages", "κανένα νέο μήνυμα"}, {"1 of 1 nodes online", "1 Κόμβος online"}, {"uptime 00:00:00", "Χρόν.λειτ. 00:00:00"}, @@ -350,11 +355,11 @@ static lv_i18n_phrase_t el_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t el_plurals_one[] = { +static const lv_i18n_phrase_t el_plurals_one[] = { {"%d active chat(s)", "%d ενεργ. συνομιλία"}, {"%d of %d nodes online", "1 Κόμβος online"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t el_plurals_other[] = { +static const lv_i18n_phrase_t el_plurals_other[] = { {"%d active chat(s)", "%d ενεργ. συνομιλίες"}, {"%d of %d nodes online", "%d / %d Κόμβοι online"}, {NULL, NULL} // End mark }; @@ -374,7 +379,7 @@ static const lv_i18n_lang_t el_lang = {.locale_name = "el", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = el_plurals_other, .locale_plural_fn = el_plural_fn}; -static lv_i18n_phrase_t es_singulars[] = { +static const lv_i18n_phrase_t es_singulars[] = { {"no new messages", "Sin mensajes nuevos"}, {"1 of 1 nodes online", "1 de 1 nodos activos"}, {"DEL", "BOR"}, @@ -514,11 +519,11 @@ static lv_i18n_phrase_t es_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t es_plurals_one[] = { +static const lv_i18n_phrase_t es_plurals_one[] = { {"%d active chat(s)", "1 chat activo"}, {"%d of %d nodes online", "1 nodo activo"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t es_plurals_other[] = { +static const lv_i18n_phrase_t es_plurals_other[] = { {"%d active chat(s)", "%d chat activos"}, {"%d of %d nodes online", "%d de %d nodos activos"}, {NULL, NULL} // End mark }; @@ -526,9 +531,17 @@ static uint8_t es_plural_fn(int32_t num) { uint32_t n = op_n(num); UNUSED(n); - + uint32_t e = op_e(n); + UNUSED(e); + uint32_t i = op_i(n); + UNUSED(i); + uint32_t v = op_v(n); + UNUSED(v); + uint32_t i1000000 = i % 1000000; if ((n == 1)) return LV_I18N_PLURAL_TYPE_ONE; + if ((e == 0 && i != 0 && i1000000 == 0 && v == 0) || ((!(0 <= e && e <= 5)))) + return LV_I18N_PLURAL_TYPE_MANY; return LV_I18N_PLURAL_TYPE_OTHER; } @@ -538,7 +551,7 @@ static const lv_i18n_lang_t es_lang = {.locale_name = "es", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = es_plurals_other, .locale_plural_fn = es_plural_fn}; -static lv_i18n_phrase_t fi_singulars[] = { +static const lv_i18n_phrase_t fi_singulars[] = { {"no new messages", "ei uusia viestejä"}, {"1 of 1 nodes online", "1/1 nodea verkossa"}, {"uptime 00:00:00", "käyttöaika 00:00:00"}, @@ -669,7 +682,7 @@ static const lv_i18n_lang_t fi_lang = {.locale_name = "fi", .locale_plural_fn = fi_plural_fn}; -static lv_i18n_phrase_t fr_singulars[] = { +static const lv_i18n_phrase_t fr_singulars[] = { {"no new messages", "0 nouveau message"}, {"1 of 1 nodes online", "1 sur 1 noeud actif"}, {"uptime 00:00:00", "Activité: 00:00:00"}, @@ -808,11 +821,11 @@ static lv_i18n_phrase_t fr_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t fr_plurals_one[] = { +static const lv_i18n_phrase_t fr_plurals_one[] = { {"%d active chat(s)", "%d chat actif"}, {"%d of %d nodes online", "%d sur %d noeud actif"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t fr_plurals_other[] = { +static const lv_i18n_phrase_t fr_plurals_other[] = { {"%d active chat(s)", "%d chats actifs"}, {"%d of %d nodes online", "%d sur %d noeuds actifs"}, {NULL, NULL} // End mark }; @@ -822,9 +835,15 @@ static uint8_t fr_plural_fn(int32_t num) UNUSED(n); uint32_t i = op_i(n); UNUSED(i); - + uint32_t e = op_e(n); + UNUSED(e); + uint32_t v = op_v(n); + UNUSED(v); + uint32_t i1000000 = i % 1000000; if ((((i == 0) || (i == 1)))) return LV_I18N_PLURAL_TYPE_ONE; + if ((e == 0 && i != 0 && i1000000 == 0 && v == 0) || ((!(0 <= e && e <= 5)))) + return LV_I18N_PLURAL_TYPE_MANY; return LV_I18N_PLURAL_TYPE_OTHER; } @@ -834,7 +853,7 @@ static const lv_i18n_lang_t fr_lang = {.locale_name = "fr", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = fr_plurals_other, .locale_plural_fn = fr_plural_fn}; -static lv_i18n_phrase_t it_singulars[] = { +static const lv_i18n_phrase_t it_singulars[] = { {"no new messages", "nessun messaggio"}, {"1 of 1 nodes online", "1 di 1 nodi online"}, {"uptime 00:00:00", "tempo di attività 00:00:00"}, @@ -969,11 +988,11 @@ static lv_i18n_phrase_t it_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t it_plurals_one[] = { +static const lv_i18n_phrase_t it_plurals_one[] = { {"%d active chat(s)", "%d chat attiva"}, {"%d of %d nodes online", "1 nodo online"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t it_plurals_other[] = { +static const lv_i18n_phrase_t it_plurals_other[] = { {"%d active chat(s)", "%d chat attive"}, {"%d of %d nodes online", "%d di %d nodi online"}, {NULL, NULL} // End mark }; @@ -985,9 +1004,13 @@ static uint8_t it_plural_fn(int32_t num) UNUSED(i); uint32_t v = op_v(n); UNUSED(v); - + uint32_t e = op_e(n); + UNUSED(e); + uint32_t i1000000 = i % 1000000; if ((i == 1 && v == 0)) return LV_I18N_PLURAL_TYPE_ONE; + if ((e == 0 && i != 0 && i1000000 == 0 && v == 0) || ((!(0 <= e && e <= 5)))) + return LV_I18N_PLURAL_TYPE_MANY; return LV_I18N_PLURAL_TYPE_OTHER; } @@ -997,7 +1020,7 @@ static const lv_i18n_lang_t it_lang = {.locale_name = "it", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = it_plurals_other, .locale_plural_fn = it_plural_fn}; -static lv_i18n_phrase_t nl_singulars[] = { +static const lv_i18n_phrase_t nl_singulars[] = { {"no new messages", "0 nieuwe berichten"}, {"1 of 1 nodes online", "1 van 1 nodes online"}, {"uptime 00:00:00", "uptime 00:00:00"}, @@ -1155,11 +1178,11 @@ static lv_i18n_phrase_t nl_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t nl_plurals_one[] = { +static const lv_i18n_phrase_t nl_plurals_one[] = { {"%d active chat(s)", "%d actieve chats"}, {"%d of %d nodes online", "1 node online"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t nl_plurals_other[] = { +static const lv_i18n_phrase_t nl_plurals_other[] = { {"%d active chat(s)", "%d actieve chats"}, {"%d of %d nodes online", "%d van %d nodes online"}, {NULL, NULL} // End mark }; @@ -1183,7 +1206,7 @@ static const lv_i18n_lang_t nl_lang = {.locale_name = "nl", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = nl_plurals_other, .locale_plural_fn = nl_plural_fn}; -static lv_i18n_phrase_t no_singulars[] = { +static const lv_i18n_phrase_t no_singulars[] = { {"no new messages", "Ingen nye meldinger"}, {"1 of 1 nodes online", "1 av 1 noder online"}, {"uptime 00:00:00", "oppetid 00:00:00"}, @@ -1344,11 +1367,11 @@ static lv_i18n_phrase_t no_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t no_plurals_one[] = { +static const lv_i18n_phrase_t no_plurals_one[] = { {"%d active chat(s)", "1 aktiv chat"}, {"%d of %d nodes online", "1 node online"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t no_plurals_other[] = { +static const lv_i18n_phrase_t no_plurals_other[] = { {"%d active chat(s)", "%d aktive chat"}, {"%d of %d nodes online", "%d av %d noder online"}, {NULL, NULL} // End mark }; @@ -1368,7 +1391,7 @@ static const lv_i18n_lang_t no_lang = {.locale_name = "no", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = no_plurals_other, .locale_plural_fn = no_plural_fn}; -static lv_i18n_phrase_t pl_singulars[] = { +static const lv_i18n_phrase_t pl_singulars[] = { {"no new messages", "Brak wiadomości"}, {"1 of 1 nodes online", "1 z 1 węzłów online"}, {"User name: ", "Nazwa"}, @@ -1498,7 +1521,7 @@ static const lv_i18n_lang_t pl_lang = {.locale_name = "pl", .locale_plural_fn = pl_plural_fn}; -static lv_i18n_phrase_t pt_singulars[] = { +static const lv_i18n_phrase_t pt_singulars[] = { {"no new messages", "Nenhuma mensagem"}, {"1 of 1 nodes online", "1 dispositivo online"}, {"uptime 00:00:00", "Tempo ligado\n00:00:00"}, @@ -1624,11 +1647,11 @@ static lv_i18n_phrase_t pt_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t pt_plurals_one[] = { +static const lv_i18n_phrase_t pt_plurals_one[] = { {"%d of %d nodes online", "1 dispositivo online"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t pt_plurals_other[] = { +static const lv_i18n_phrase_t pt_plurals_other[] = { {"%d of %d nodes online", "%d/%d dispositivos online"}, {NULL, NULL} // End mark }; @@ -1638,9 +1661,15 @@ static uint8_t pt_plural_fn(int32_t num) UNUSED(n); uint32_t i = op_i(n); UNUSED(i); - + uint32_t e = op_e(n); + UNUSED(e); + uint32_t v = op_v(n); + UNUSED(v); + uint32_t i1000000 = i % 1000000; if (((0 <= i && i <= 1))) return LV_I18N_PLURAL_TYPE_ONE; + if ((e == 0 && i != 0 && i1000000 == 0 && v == 0) || ((!(0 <= e && e <= 5)))) + return LV_I18N_PLURAL_TYPE_MANY; return LV_I18N_PLURAL_TYPE_OTHER; } @@ -1670,7 +1699,7 @@ static const lv_i18n_lang_t ro_lang = {.locale_name = "ro", .locale_plural_fn = ro_plural_fn}; -static lv_i18n_phrase_t ru_singulars[] = { +static const lv_i18n_phrase_t ru_singulars[] = { {"no new messages", "нет новых сообщений"}, {"1 of 1 nodes online", "1 из 1 узлов онлайн"}, {"DEL", "DEL"}, @@ -1817,7 +1846,7 @@ static const lv_i18n_lang_t ru_lang = {.locale_name = "ru", .locale_plural_fn = ru_plural_fn}; -static lv_i18n_phrase_t se_singulars[] = { +static const lv_i18n_phrase_t se_singulars[] = { {"no new messages", "inga nya meddelanden"}, {"1 of 1 nodes online", "1 av 1 noder online"}, {"uptime 00:00:00", "upptid 00:00:00"}, @@ -1966,11 +1995,11 @@ static lv_i18n_phrase_t se_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t se_plurals_one[] = { +static const lv_i18n_phrase_t se_plurals_one[] = { {"%d active chat(s)", "1 aktiv chatt"}, {"%d of %d nodes online", "1 nod online"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t se_plurals_other[] = { +static const lv_i18n_phrase_t se_plurals_other[] = { {"%d active chat(s)", "%d aktiva chattar"}, {"%d of %d nodes online", "%d av %d noder online"}, {NULL, NULL} // End mark }; @@ -1992,7 +2021,7 @@ static const lv_i18n_lang_t se_lang = {.locale_name = "se", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = se_plurals_other, .locale_plural_fn = se_plural_fn}; -static lv_i18n_phrase_t sl_singulars[] = { +static const lv_i18n_phrase_t sl_singulars[] = { {"User name: %s", "Uporabniško ime: %s"}, {"Device Role: %s", "Vloga: %s"}, {"no new messages", "ni novih sporočil"}, @@ -2133,11 +2162,11 @@ static lv_i18n_phrase_t sl_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t sl_plurals_one[] = { +static const lv_i18n_phrase_t sl_plurals_one[] = { {"%d of %d nodes online", "1 vozlišč povezano"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t sl_plurals_other[] = { +static const lv_i18n_phrase_t sl_plurals_other[] = { {"%d of %d nodes online", "%d / %d vozl. povezanih"}, {NULL, NULL} // End mark }; @@ -2165,7 +2194,7 @@ static const lv_i18n_lang_t sl_lang = {.locale_name = "sl", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = sl_plurals_other, .locale_plural_fn = sl_plural_fn}; -static lv_i18n_phrase_t sr_singulars[] = { +static const lv_i18n_phrase_t sr_singulars[] = { {"no new messages", "nema novih poruka"}, {"1 of 1 nodes online", "1 od 1 nodova online"}, {"uptime 00:00:00", "uključen pre 00:00:00"}, @@ -2320,7 +2349,7 @@ static const lv_i18n_lang_t sr_lang = {.locale_name = "sr", .locale_plural_fn = sr_plural_fn}; -static lv_i18n_phrase_t tr_singulars[] = { +static const lv_i18n_phrase_t tr_singulars[] = { {"no new messages", "Yeni mesaj yok"}, {"1 of 1 nodes online", "1/1 düğüm aktif"}, {"uptime 00:00:00", "Çalışma süresi %s"}, @@ -2451,11 +2480,11 @@ static lv_i18n_phrase_t tr_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t tr_plurals_one[] = { +static const lv_i18n_phrase_t tr_plurals_one[] = { {"%d of %d nodes online", "1 düğüm çevrimiçi"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t tr_plurals_other[] = { +static const lv_i18n_phrase_t tr_plurals_other[] = { {"%d of %d nodes online", "%d / %d düğümler çevrimiçi"}, {NULL, NULL} // End mark }; @@ -2475,9 +2504,9 @@ static const lv_i18n_lang_t tr_lang = {.locale_name = "tr", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = tr_plurals_other, .locale_plural_fn = tr_plural_fn}; -static lv_i18n_phrase_t uk_singulars[] = { +static const lv_i18n_phrase_t uk_singulars[] = { {"no new messages", "немає повідомлень"}, - {"1 of 1 nodes online", "1 нода онлайн"}, + {"1 of 1 nodes online", "1 вузол онлайн"}, {"uptime 00:00:00", "час роботи 00:00:00"}, {"no signal", "немає сигналу"}, {"silent", "тихий"}, @@ -2489,7 +2518,7 @@ static lv_i18n_phrase_t uk_singulars[] = { {"Channel: LongFast", "Канал: LongFast"}, {"Role: Client", "Роль: Client"}, {"WiFi: ", "WiFi: <не задано>"}, - {"Screen Timeout: 60s", "Таймаут Екрана: 60s"}, + {"Screen Timeout: 60s", "Таймаут Екрана: 60с"}, {"Lock: off/off", "Блокування: вимкн./вимкн."}, {"Screen Brightness: 60%", "Яскравість Екрана: 60%"}, {"Theme: Dark", "Тема: Темна"}, @@ -2514,8 +2543,8 @@ static lv_i18n_phrase_t uk_singulars[] = { {"Locations Map", "Мапа"}, {"no chats", "немає чатів"}, {"Node Search", "Пошук Нод"}, - {"Packet Statistics", "Статистика пакетів"}, - {"Node Options", "Параметри Ноди"}, + {"Packet Statistics", "Статистика Пакетів"}, + {"Node Options", "Параметри Вузла"}, {"LoRa TX off!", "LoRa TX вимкн.!"}, {"Short Name", "Коротке Ім'я"}, {"Long Name", "Довге Ім'я"}, @@ -2524,7 +2553,7 @@ static lv_i18n_phrase_t uk_singulars[] = { {"Secondary Channels", "Вторинні Канали"}, {"Brightness: 60%", "Яскравість: 60%"}, {"Dark\nLight", "Темна\nСвітла"}, - {"Timeout: 60s", "Таймаут: 60s"}, + {"Timeout: 60s", "Таймаут: 60с"}, {"Screen Lock", "Блокування Екрану"}, {"Settings Lock", "Блокування Налаштувань"}, {"Lock PIN", "PIN-код Блокування"}, @@ -2539,10 +2568,10 @@ static lv_i18n_phrase_t uk_singulars[] = { {"Backup", "Збереження"}, {"Restore", "Відновлення"}, {"Public/Private Key", "Публічний/Приватний Ключ"}, - {"NodeDB Reset\nFactory Reset\nClear Chat History", "Скидання NodeDB\nСкидання налаштувань\nОчистити історію чатів"}, - {"Channel Name", "Ім'я каналу"}, - {"Pre-shared Key", "PSK ключ"}, - {"Filter", "Фільтр"}, + {"NodeDB Reset\nFactory Reset\nClear Chat History", "Скидання NodeDB\nСкидання Налаштувань\nОчистити Історію Чатів"}, + {"Channel Name", "Ім'я Каналу"}, + {"Pre-shared Key", "PSK Ключ"}, + {"Filter", "Фільтрувати"}, {"Unknown", "Невідомо"}, {"Offline", "Не в мережі"}, {"Public Key", "Публічний Ключ"}, @@ -2550,12 +2579,12 @@ static lv_i18n_phrase_t uk_singulars[] = { {"Hops away", "Стрибки"}, {"Position", "Позиція"}, {"Name", "Ім'я"}, - {"Highlight", "Позначка"}, + {"Highlight", "Підсвічувати"}, {"Active Chat", "Активний Чат"}, {"Telemetry", "Телеметрія"}, {"Start", "Старт"}, - {"choose\nnode", "оберіть\nноду"}, - {"choose target node", "оберіть цільову ноду"}, + {"choose\nnode", "оберіть\nвузол"}, + {"choose target node", "оберіть цільовий вузол"}, {"New Message from\n", "Нове Повідомлення від\n"}, {"Restoring messages ...", "Відновлення повідомлень ..."}, {"Please set region and name", "Будь ласка, вкажіть регіон та ім'я"}, @@ -2577,7 +2606,7 @@ static lv_i18n_phrase_t uk_singulars[] = { {"FrequencySlot: %d (%g MHz)", "Частотний слот: %d (%g MHz)"}, {"Brightness: %d%%", "Яскравість: %d%%"}, {"Timeout: off", "Таймаут: вимкн."}, - {"Timeout: %ds", "Таймаут: %ds"}, + {"Timeout: %ds", "Таймаут: %dс"}, {"No map tiles found on SDCard!", "Мапи не знайдено!"}, {"Locations Map (%d/%d)", "Мапа (%d/%d)"}, {"Stop", "Стоп"}, @@ -2585,7 +2614,7 @@ static lv_i18n_phrase_t uk_singulars[] = { {"Packet Log: %d", "Журнал Пакетів: %d"}, {"Language: %s", "Мова: %s"}, {"Screen Timeout: off", "Таймаут Екрана: вимкн."}, - {"Screen Timeout: %ds", "Таймаут Екрана: %ds"}, + {"Screen Timeout: %ds", "Таймаут Екрана: %dс"}, {"Screen Brightness: %d%%", "Яскравість Екрана: %d%%"}, {"Theme: %s", "Тема: %s"}, {"Region: %s", "Регіон: %s"}, @@ -2608,7 +2637,7 @@ static lv_i18n_phrase_t uk_singulars[] = { {"Failed to retrieve keys!", "Не вдалося отримати ключі!"}, {"New message from \n%s", "Нове повідомлення від \n%s"}, {"Input Control: %s/%s", "Контроль Вводу: %s/%s"}, - {"Filter: %d of %d nodes", "Фільтр: %d з %d нод"}, + {"Filter: %d of %d nodes", "Фільтр: %d з %d вузлів"}, {"%d new message", "%d нове повідомлення"}, {"%d new messages", "%d нові повідомлення"}, {"uptime: %02d:%02d:%02d", "час роботи: %02d:%02d:%02d"}, @@ -2616,12 +2645,12 @@ static lv_i18n_phrase_t uk_singulars[] = { {NULL, NULL} // End mark }; -static lv_i18n_phrase_t uk_plurals_one[] = { - {"%d active chat(s)", "%d активний чат"}, {"%d of %d nodes online", "1 нода онлайн"}, {NULL, NULL} // End mark +static const lv_i18n_phrase_t uk_plurals_one[] = { + {"%d active chat(s)", "%d активний чат"}, {"%d of %d nodes online", "1 вузол онлайн"}, {NULL, NULL} // End mark }; -static lv_i18n_phrase_t uk_plurals_other[] = { - {"%d active chat(s)", "%d активні чати"}, {"%d of %d nodes online", "%d з %d нод онлайн"}, {NULL, NULL} // End mark +static const lv_i18n_phrase_t uk_plurals_other[] = { + {"%d active chat(s)", "%d активні чати"}, {"%d of %d nodes online", "%d з %d вузлів онлайн"}, {NULL, NULL} // End mark }; static uint8_t uk_plural_fn(int32_t num) @@ -2632,9 +2661,14 @@ static uint8_t uk_plural_fn(int32_t num) UNUSED(v); uint32_t i = op_i(n); UNUSED(i); - - if ((i == 1 && v == 0)) + uint32_t i10 = i % 10; + uint32_t i100 = i % 100; + if ((v == 0 && i10 == 1 && i100 != 11)) return LV_I18N_PLURAL_TYPE_ONE; + if ((v == 0 && (2 <= i10 && i10 <= 4) && (!(12 <= i100 && i100 <= 14)))) + return LV_I18N_PLURAL_TYPE_FEW; + if ((v == 0 && i10 == 0) || (v == 0 && (5 <= i10 && i10 <= 9)) || (v == 0 && (11 <= i100 && i100 <= 14))) + return LV_I18N_PLURAL_TYPE_MANY; return LV_I18N_PLURAL_TYPE_OTHER; } @@ -2644,7 +2678,7 @@ static const lv_i18n_lang_t uk_lang = {.locale_name = "uk", .plurals[LV_I18N_PLURAL_TYPE_OTHER] = uk_plurals_other, .locale_plural_fn = uk_plural_fn}; -static lv_i18n_phrase_t zh_cn_singulars[] = { +static const lv_i18n_phrase_t zh_cn_singulars[] = { {"no new messages", "暂无新消息"}, {"1 of 1 nodes online", "1/1 个节点在线"}, {"DEL", "删除"}, @@ -2829,6 +2863,14 @@ int lv_i18n_init(const lv_i18n_language_pack_t *langs) return 0; } +/** + * Sugar for simplified `lv_i18n_init` call + */ +int lv_i18n_init_default(void) +{ + return lv_i18n_init(lv_i18n_language_pack); +} + /** * Change the localization (language) * @param l_name name of the translation locale to use. E.g. "en-GB" @@ -2851,12 +2893,12 @@ int lv_i18n_set_locale(const char *l_name) return -1; } -static const char *__lv_i18n_get_text_core(lv_i18n_phrase_t *trans, const char *msg_id) +static const char *__lv_i18n_get_text_core(const lv_i18n_phrase_t *trans, const char *msg_id) { uint16_t i; for (i = 0; trans[i].msg_id != NULL; i++) { if (strcmp(trans[i].msg_id, msg_id) == 0) { - /*The msg_id has found. Check the translation*/ + /*The msg_id has been found. Check the translation*/ if (trans[i].translation) return trans[i].translation; } diff --git a/locale/lv_i18n.h b/locale/lv_i18n.h index 8a4af09..cd72c95 100644 --- a/locale/lv_i18n.h +++ b/locale/lv_i18n.h @@ -25,8 +25,8 @@ typedef struct { typedef struct { const char *locale_name; - lv_i18n_phrase_t *singulars; - lv_i18n_phrase_t *plurals[_LV_I18N_PLURAL_TYPE_NUM]; + const lv_i18n_phrase_t *singulars; + const lv_i18n_phrase_t *plurals[_LV_I18N_PLURAL_TYPE_NUM]; uint8_t (*locale_plural_fn)(int32_t num); } lv_i18n_lang_t; @@ -41,6 +41,11 @@ extern const lv_i18n_language_pack_t lv_i18n_language_pack[]; */ int lv_i18n_init(const lv_i18n_language_pack_t *langs); +/** + * Sugar for simplified `lv_i18n_init` call + */ +int lv_i18n_init_default(void); + /** * Change the localization (language) * @param l_name name of the translation locale to use. E.g. "en_GB"