From 8c8ccd8783a5afaa4b0bca95097c5847328fe662 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Thu, 28 Aug 2025 21:50:29 +0200 Subject: [PATCH] Merge develop into main (#305) ## New features - Implement translations for apps - Created `tt::settings::setLanguage` and `::getLanguage()` - External app errors are now reported to the user via an AlertDialog - Store system settings in `/data/settings.properties` - Created a "Region & Language" app and moved the timezone setting there. ## Other changes - Change `/data` and `/system` filesystem sector size from 4096 to 512 bytes to allow for more small files (60+ files of 4kB were over the limit of 256kB for the filesystem) - Increased size of `/data` and `/system` - Moved `tt::time::*` to `tt::settings` - Removed the timezone setting from the "Time & Date" setting app - Reverse encoder direction of Lilygo T-Lora Pager - Improved partability of `Time.cpp` (removed separate set of functions for PC/sim) --- .../Source/hal/TpagerKeyboard.cpp | 8 +- Data/data/settings.properties | 2 + Data/system/app/Launcher/i18n/en-GB.i18n | 3 + Data/system/app/Launcher/i18n/en-US.i18n | 3 + Data/system/app/Launcher/i18n/fr-FR.i18n | 3 + Data/system/app/Launcher/i18n/nl-BE.i18n | 3 + Data/system/app/Launcher/i18n/nl-NL.i18n | 3 + .../system/app/LocaleSettings/i18n/en-GB.i18n | 7 + .../system/app/LocaleSettings/i18n/en-US.i18n | 7 + .../system/app/LocaleSettings/i18n/fr-FR.i18n | 7 + .../system/app/LocaleSettings/i18n/nl-BE.i18n | 7 + .../system/app/LocaleSettings/i18n/nl-NL.i18n | 7 + Data/system/i18n/core/en-GB.i18n | 7 + Data/system/i18n/core/en-US.i18n | 7 + Data/system/i18n/core/fr-FR.i18n | 7 + Data/system/i18n/core/nl-BE.i18n | 7 + Data/system/i18n/core/nl-NL.i18n | 7 + Documentation/ideas.md | 6 +- .../Tactility/i18n/CoreTextResources.h | 19 ++ .../Include/Tactility/i18n/TextResources.h | 49 +++++ .../Include/Tactility/settings/Language.h | 24 +++ .../Tactility/settings/SettingsProperties.h | 16 ++ .../Tactility/{time => settings}/Time.h | 2 +- .../Tactility/app/launcher/TextResources.h | 15 ++ .../app/localesettings/LocaleSettings.h | 7 + .../app/localesettings/TextResources.h | 19 ++ .../Private/Tactility/settings/TimePrivate.h | 7 + .../Private/Tactility/time/TimePrivate.h | 7 - Tactility/Source/PartitionsEsp.cpp | 18 +- Tactility/Source/Tactility.cpp | 2 + Tactility/Source/TactilityHeadless.cpp | 4 +- Tactility/Source/app/ElfApp.cpp | 62 ++++--- Tactility/Source/app/launcher/Launcher.cpp | 19 +- .../app/localesettings/LocaleSettings.cpp | 168 ++++++++++++++++++ .../app/timedatesettings/TimeDateSettings.cpp | 68 +------ Tactility/Source/file/PropertiesFile.cpp | 1 + Tactility/Source/i18n/TextResources.cpp | 82 +++++++++ Tactility/Source/lvgl/Statusbar.cpp | 4 +- .../development/DevelopmentService.cpp | 6 + .../Source/service/wifi/WifiSettings.cpp | 2 +- Tactility/Source/settings/Language.cpp | 65 +++++++ .../Source/settings/SettingsProperties.cpp | 77 ++++++++ Tactility/Source/settings/Time.cpp | 81 +++++++++ Tactility/Source/time/Time.cpp | 104 ----------- TactilityC/Source/tt_time.cpp | 12 +- TactilityCore/Include/Tactility/file/File.h | 2 + TactilityCore/Source/file/File.cpp | 24 +++ Translations/.gitignore | 2 + Translations/README.md | 25 +++ Translations/Translations.ods | Bin 0 -> 13804 bytes Translations/generate-all.py | 37 ++++ Translations/generate.py | 96 ++++++++++ partitions-test.csv | 7 + partitions.csv | 4 +- sdkconfig.board.cyd-2432s024c | 5 + sdkconfig.board.cyd-2432s032c | 5 + sdkconfig.board.cyd-4848s040c | 5 + sdkconfig.board.cyd-8048s043c | 5 + sdkconfig.board.cyd-jc2432w328c | 5 + sdkconfig.board.cyd-jc8048w550c | 5 + sdkconfig.board.elecrow-crowpanel-advance-28 | 5 + sdkconfig.board.elecrow-crowpanel-advance-35 | 5 + sdkconfig.board.elecrow-crowpanel-advance-50 | 5 + sdkconfig.board.elecrow-crowpanel-basic-28 | 5 + sdkconfig.board.elecrow-crowpanel-basic-35 | 5 + sdkconfig.board.elecrow-crowpanel-basic-50 | 5 + sdkconfig.board.lilygo-tdeck | 5 + sdkconfig.board.lilygo-tlora-pager | 5 + sdkconfig.board.m5stack-core2 | 5 + sdkconfig.board.m5stack-cores3 | 5 + sdkconfig.board.unphone | 5 + sdkconfig.board.waveshare-s3-touch-43 | 5 + sdkconfig.defaults | 5 + 73 files changed, 1114 insertions(+), 219 deletions(-) create mode 100644 Data/data/settings.properties create mode 100644 Data/system/app/Launcher/i18n/en-GB.i18n create mode 100644 Data/system/app/Launcher/i18n/en-US.i18n create mode 100644 Data/system/app/Launcher/i18n/fr-FR.i18n create mode 100644 Data/system/app/Launcher/i18n/nl-BE.i18n create mode 100644 Data/system/app/Launcher/i18n/nl-NL.i18n create mode 100644 Data/system/app/LocaleSettings/i18n/en-GB.i18n create mode 100644 Data/system/app/LocaleSettings/i18n/en-US.i18n create mode 100644 Data/system/app/LocaleSettings/i18n/fr-FR.i18n create mode 100644 Data/system/app/LocaleSettings/i18n/nl-BE.i18n create mode 100644 Data/system/app/LocaleSettings/i18n/nl-NL.i18n create mode 100644 Data/system/i18n/core/en-GB.i18n create mode 100644 Data/system/i18n/core/en-US.i18n create mode 100644 Data/system/i18n/core/fr-FR.i18n create mode 100644 Data/system/i18n/core/nl-BE.i18n create mode 100644 Data/system/i18n/core/nl-NL.i18n create mode 100644 Tactility/Include/Tactility/i18n/CoreTextResources.h create mode 100644 Tactility/Include/Tactility/i18n/TextResources.h create mode 100644 Tactility/Include/Tactility/settings/Language.h create mode 100644 Tactility/Include/Tactility/settings/SettingsProperties.h rename Tactility/Include/Tactility/{time => settings}/Time.h (96%) create mode 100644 Tactility/Private/Tactility/app/launcher/TextResources.h create mode 100644 Tactility/Private/Tactility/app/localesettings/LocaleSettings.h create mode 100644 Tactility/Private/Tactility/app/localesettings/TextResources.h create mode 100644 Tactility/Private/Tactility/settings/TimePrivate.h delete mode 100644 Tactility/Private/Tactility/time/TimePrivate.h create mode 100644 Tactility/Source/app/localesettings/LocaleSettings.cpp create mode 100644 Tactility/Source/i18n/TextResources.cpp create mode 100644 Tactility/Source/settings/Language.cpp create mode 100644 Tactility/Source/settings/SettingsProperties.cpp create mode 100644 Tactility/Source/settings/Time.cpp delete mode 100644 Tactility/Source/time/Time.cpp create mode 100644 Translations/.gitignore create mode 100644 Translations/README.md create mode 100644 Translations/Translations.ods create mode 100644 Translations/generate-all.py create mode 100644 Translations/generate.py create mode 100644 partitions-test.csv diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp index 092073de..21f40397 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp @@ -239,13 +239,13 @@ void TpagerKeyboard::initEncoder(void) { } pcnt_chan_config_t chan_1_config = { - .edge_gpio_num = ENCODER_A, - .level_gpio_num = ENCODER_B, - }; - pcnt_chan_config_t chan_2_config = { .edge_gpio_num = ENCODER_B, .level_gpio_num = ENCODER_A, }; + pcnt_chan_config_t chan_2_config = { + .edge_gpio_num = ENCODER_A, + .level_gpio_num = ENCODER_B, + }; pcnt_channel_handle_t pcnt_chan_1 = NULL; pcnt_channel_handle_t pcnt_chan_2 = NULL; diff --git a/Data/data/settings.properties b/Data/data/settings.properties new file mode 100644 index 00000000..0b905db8 --- /dev/null +++ b/Data/data/settings.properties @@ -0,0 +1,2 @@ +language=en-US +timeFormat24h=true \ No newline at end of file diff --git a/Data/system/app/Launcher/i18n/en-GB.i18n b/Data/system/app/Launcher/i18n/en-GB.i18n new file mode 100644 index 00000000..5226a6e5 --- /dev/null +++ b/Data/system/app/Launcher/i18n/en-GB.i18n @@ -0,0 +1,3 @@ +Apps +Files +Settings diff --git a/Data/system/app/Launcher/i18n/en-US.i18n b/Data/system/app/Launcher/i18n/en-US.i18n new file mode 100644 index 00000000..5226a6e5 --- /dev/null +++ b/Data/system/app/Launcher/i18n/en-US.i18n @@ -0,0 +1,3 @@ +Apps +Files +Settings diff --git a/Data/system/app/Launcher/i18n/fr-FR.i18n b/Data/system/app/Launcher/i18n/fr-FR.i18n new file mode 100644 index 00000000..663a29ea --- /dev/null +++ b/Data/system/app/Launcher/i18n/fr-FR.i18n @@ -0,0 +1,3 @@ +Appli +Fichiers +Réglages diff --git a/Data/system/app/Launcher/i18n/nl-BE.i18n b/Data/system/app/Launcher/i18n/nl-BE.i18n new file mode 100644 index 00000000..89a1e21f --- /dev/null +++ b/Data/system/app/Launcher/i18n/nl-BE.i18n @@ -0,0 +1,3 @@ +Apps +Bestanden +Instellingen diff --git a/Data/system/app/Launcher/i18n/nl-NL.i18n b/Data/system/app/Launcher/i18n/nl-NL.i18n new file mode 100644 index 00000000..89a1e21f --- /dev/null +++ b/Data/system/app/Launcher/i18n/nl-NL.i18n @@ -0,0 +1,3 @@ +Apps +Bestanden +Instellingen diff --git a/Data/system/app/LocaleSettings/i18n/en-GB.i18n b/Data/system/app/LocaleSettings/i18n/en-GB.i18n new file mode 100644 index 00000000..ab750d45 --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/en-GB.i18n @@ -0,0 +1,7 @@ +Dutch (Netherlands) +Dutch (Belgium) +English (United States) +English (United Kingdom) +French (France) +Region +Language diff --git a/Data/system/app/LocaleSettings/i18n/en-US.i18n b/Data/system/app/LocaleSettings/i18n/en-US.i18n new file mode 100644 index 00000000..ab750d45 --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/en-US.i18n @@ -0,0 +1,7 @@ +Dutch (Netherlands) +Dutch (Belgium) +English (United States) +English (United Kingdom) +French (France) +Region +Language diff --git a/Data/system/app/LocaleSettings/i18n/fr-FR.i18n b/Data/system/app/LocaleSettings/i18n/fr-FR.i18n new file mode 100644 index 00000000..209b263a --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/fr-FR.i18n @@ -0,0 +1,7 @@ +Néerlandais (Pays-Bas) +Néerlandais (Belgique) +Anglais (États-Unis) +Anglais (Royaume-Uni) +Français (France) +Région +Langue diff --git a/Data/system/app/LocaleSettings/i18n/nl-BE.i18n b/Data/system/app/LocaleSettings/i18n/nl-BE.i18n new file mode 100644 index 00000000..39900b4b --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/nl-BE.i18n @@ -0,0 +1,7 @@ +Nederlands (Nederland) +Nederlands (België) +Engels (Verenigde Staten) +Engels (Verenigd Koninkrijk) +Frans (Frankrijk) +Regio +Taal diff --git a/Data/system/app/LocaleSettings/i18n/nl-NL.i18n b/Data/system/app/LocaleSettings/i18n/nl-NL.i18n new file mode 100644 index 00000000..39900b4b --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/nl-NL.i18n @@ -0,0 +1,7 @@ +Nederlands (Nederland) +Nederlands (België) +Engels (Verenigde Staten) +Engels (Verenigd Koninkrijk) +Frans (Frankrijk) +Regio +Taal diff --git a/Data/system/i18n/core/en-GB.i18n b/Data/system/i18n/core/en-GB.i18n new file mode 100644 index 00000000..a04e3142 --- /dev/null +++ b/Data/system/i18n/core/en-GB.i18n @@ -0,0 +1,7 @@ +OK +Yes +No +Cancel +Retry +Close +Open diff --git a/Data/system/i18n/core/en-US.i18n b/Data/system/i18n/core/en-US.i18n new file mode 100644 index 00000000..a04e3142 --- /dev/null +++ b/Data/system/i18n/core/en-US.i18n @@ -0,0 +1,7 @@ +OK +Yes +No +Cancel +Retry +Close +Open diff --git a/Data/system/i18n/core/fr-FR.i18n b/Data/system/i18n/core/fr-FR.i18n new file mode 100644 index 00000000..2f035013 --- /dev/null +++ b/Data/system/i18n/core/fr-FR.i18n @@ -0,0 +1,7 @@ +OK +Oui +Non +Annuler +Réessayer +Fermer +Ouvrir diff --git a/Data/system/i18n/core/nl-BE.i18n b/Data/system/i18n/core/nl-BE.i18n new file mode 100644 index 00000000..1a5002f4 --- /dev/null +++ b/Data/system/i18n/core/nl-BE.i18n @@ -0,0 +1,7 @@ +OK +Ja +Nee +Annuleren +Opnieuw +Sluiten +Openen diff --git a/Data/system/i18n/core/nl-NL.i18n b/Data/system/i18n/core/nl-NL.i18n new file mode 100644 index 00000000..1a5002f4 --- /dev/null +++ b/Data/system/i18n/core/nl-NL.i18n @@ -0,0 +1,7 @@ +OK +Ja +Nee +Annuleren +Opnieuw +Sluiten +Openen diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 75719885..e055f691 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -4,15 +4,15 @@ - Move Development settings from flash to `/data/apps/development/development.properties` (just the "start on boot") - Move Display settings from flash to `/data/apps/display/display.properties` -- App data directory should be automatically created (and then we can remove the custom code from Notes.cpp) -- When an external app fails to load (e.g. due to mapping error) then show an error dialog. - Expose app::Paths to TactilityC - Call tt::lvgl::isSyncSet after HAL init and show an error (and crash?) when it is not set. - External app loading: Check the version of Tactility and check ESP target hardware to check for compatibility. -- Localization of texts (load in boot app from sd?) - App packaging - Create more unit tests for `tactility-core` - Make a URL handler. Use it for handling local files. Match file types with apps. +- Fix Development service: when no SD card is present, the app fails to install. Consider installing to `/data` +- Refactor `PropertiesFile.cpp` to use `tt::file::readLines()` (see TODO in code) +- Localize all apps ## Lower Priority diff --git a/Tactility/Include/Tactility/i18n/CoreTextResources.h b/Tactility/Include/Tactility/i18n/CoreTextResources.h new file mode 100644 index 00000000..ad389b83 --- /dev/null +++ b/Tactility/Include/Tactility/i18n/CoreTextResources.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Tactility/i18n/TextResources.h" + +// WARNING: This file is auto-generated. Do not edit manually. + +namespace tt::i18n::core { + +enum class Text { + OK = 0, + YES = 1, + NO = 2, + CANCEL = 3, + RETRY = 4, + CLOSE = 5, + OPEN = 6, +}; + +} diff --git a/Tactility/Include/Tactility/i18n/TextResources.h b/Tactility/Include/Tactility/i18n/TextResources.h new file mode 100644 index 00000000..4e016443 --- /dev/null +++ b/Tactility/Include/Tactility/i18n/TextResources.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +namespace tt::i18n { + +/** + * Holds localized text data. + * + * It is used with data generated from Translations/ with the python generation scripts. + * It's used with a header file that specifies the indexes, and generated text files (.i18n) + */ +class TextResources { + + std::vector data; + std::string path; + static std::string ERROR_RESULT; + +public: + /** + * @param[in] path + */ + TextResources(const std::string& path) : path(path) {} + + const std::string& get(const int index) const { + if (index < data.size()) { + return data[index]; + } else { + return ERROR_RESULT; + } + } + + template + const std::string& get(EnumType value) const { return get(static_cast(value)); } + + const std::string& operator[](const int index) const { return get(index); } + + template + const std::string& operator[](const EnumType index) const { return get(index); } + + /** + * Load or reload an i18n file with the system's current locale settings. + * @return true on success + */ + bool load(); +}; + +} \ No newline at end of file diff --git a/Tactility/Include/Tactility/settings/Language.h b/Tactility/Include/Tactility/settings/Language.h new file mode 100644 index 00000000..953cb281 --- /dev/null +++ b/Tactility/Include/Tactility/settings/Language.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace tt::settings { + +enum class Language { + en_GB, + en_US, + fr_FR, + nl_BE, + nl_NL, + count +}; + +void setLanguage(Language language); + +Language getLanguage(); + +std::string toString(Language language); + +bool fromString(const std::string& text, Language& language); + +} diff --git a/Tactility/Include/Tactility/settings/SettingsProperties.h b/Tactility/Include/Tactility/settings/SettingsProperties.h new file mode 100644 index 00000000..3161c81d --- /dev/null +++ b/Tactility/Include/Tactility/settings/SettingsProperties.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Language.h" + +namespace tt::settings { + +struct SettingsProperties { + Language language; + bool timeFormat24h; +}; + +bool loadSettingsProperties(SettingsProperties& properties); + +bool saveSettingsProperties(const SettingsProperties& properties); + +} diff --git a/Tactility/Include/Tactility/time/Time.h b/Tactility/Include/Tactility/settings/Time.h similarity index 96% rename from Tactility/Include/Tactility/time/Time.h rename to Tactility/Include/Tactility/settings/Time.h index 88448e4f..40ff450f 100644 --- a/Tactility/Include/Tactility/time/Time.h +++ b/Tactility/Include/Tactility/settings/Time.h @@ -2,7 +2,7 @@ #include -namespace tt::time { +namespace tt::settings { /** * Set the timezone diff --git a/Tactility/Private/Tactility/app/launcher/TextResources.h b/Tactility/Private/Tactility/app/launcher/TextResources.h new file mode 100644 index 00000000..efb5acd9 --- /dev/null +++ b/Tactility/Private/Tactility/app/launcher/TextResources.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Tactility/i18n/TextResources.h" + +// WARNING: This file is auto-generated. Do not edit manually. + +namespace tt::app::launcher::i18n { + +enum class Text { + APPS = 0, + FILES = 1, + SETTINGS = 2, +}; + +} diff --git a/Tactility/Private/Tactility/app/localesettings/LocaleSettings.h b/Tactility/Private/Tactility/app/localesettings/LocaleSettings.h new file mode 100644 index 00000000..5fd746c0 --- /dev/null +++ b/Tactility/Private/Tactility/app/localesettings/LocaleSettings.h @@ -0,0 +1,7 @@ +#pragma once + +namespace tt::app::localesettings { + +void start(); + +} \ No newline at end of file diff --git a/Tactility/Private/Tactility/app/localesettings/TextResources.h b/Tactility/Private/Tactility/app/localesettings/TextResources.h new file mode 100644 index 00000000..08728900 --- /dev/null +++ b/Tactility/Private/Tactility/app/localesettings/TextResources.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Tactility/i18n/TextResources.h" + +// WARNING: This file is auto-generated. Do not edit manually. + +namespace tt::app::localesettings::i18n { + +enum class Text { + NL_NL = 0, + NL_BE = 1, + EN_US = 2, + EN_GB = 3, + FR_FR = 4, + REGION = 5, + LANGUAGE = 6, +}; + +} diff --git a/Tactility/Private/Tactility/settings/TimePrivate.h b/Tactility/Private/Tactility/settings/TimePrivate.h new file mode 100644 index 00000000..1968e5e2 --- /dev/null +++ b/Tactility/Private/Tactility/settings/TimePrivate.h @@ -0,0 +1,7 @@ +#pragma once + +namespace tt::settings { + +void initTimeZone(); + +} diff --git a/Tactility/Private/Tactility/time/TimePrivate.h b/Tactility/Private/Tactility/time/TimePrivate.h deleted file mode 100644 index 92b13222..00000000 --- a/Tactility/Private/Tactility/time/TimePrivate.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -namespace tt::time { - -void init(); - -} diff --git a/Tactility/Source/PartitionsEsp.cpp b/Tactility/Source/PartitionsEsp.cpp index cf9f7bbf..51225ba8 100644 --- a/Tactility/Source/PartitionsEsp.cpp +++ b/Tactility/Source/PartitionsEsp.cpp @@ -9,7 +9,7 @@ namespace tt { -static const char* TAG = "partitions"; +static const char* TAG = "Partitions"; static esp_err_t initNvsFlashSafely() { esp_err_t result = nvs_flash_init(); @@ -22,13 +22,27 @@ static esp_err_t initNvsFlashSafely() { static wl_handle_t data_wl_handle = WL_INVALID_HANDLE; +size_t getSectorSize() { +#if defined(CONFIG_FATFS_SECTOR_512) + return 512; +#elif defined(CONFIG_FATFS_SECTOR_1024) + return 1024; +#elif defined(CONFIG_FATFS_SECTOR_2048) + return 2048; +#elif defined(CONFIG_FATFS_SECTOR_4096) + return 4096; +#else +#error Not implemented +#endif +} + esp_err_t initPartitionsEsp() { ESP_ERROR_CHECK(initNvsFlashSafely()); const esp_vfs_fat_mount_config_t mount_config = { .format_if_mount_failed = false, .max_files = 4, - .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, + .allocation_unit_size = getSectorSize(), .disk_status_check_enable = false, .use_one_fat = true, }; diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 8cb64c69..5250348c 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -47,6 +47,7 @@ namespace app { namespace imageviewer { extern const AppManifest manifest; } namespace inputdialog { extern const AppManifest manifest; } namespace launcher { extern const AppManifest manifest; } + namespace localesettings { extern const AppManifest manifest; } namespace log { extern const AppManifest manifest; } namespace notes { extern const AppManifest manifest; } namespace power { extern const AppManifest manifest; } @@ -89,6 +90,7 @@ static void registerSystemApps() { addApp(app::imageviewer::manifest); addApp(app::inputdialog::manifest); addApp(app::launcher::manifest); + addApp(app::localesettings::manifest); addApp(app::log::manifest); addApp(app::notes::manifest); addApp(app::serialconsole::manifest); diff --git a/Tactility/Source/TactilityHeadless.cpp b/Tactility/Source/TactilityHeadless.cpp index 97570836..a827e5ab 100644 --- a/Tactility/Source/TactilityHeadless.cpp +++ b/Tactility/Source/TactilityHeadless.cpp @@ -6,7 +6,7 @@ #include "Tactility/service/ServiceRegistration.h" #include -#include +#include #ifdef ESP_PLATFORM #include "Tactility/InitEsp.h" @@ -45,7 +45,7 @@ void initHeadless(const hal::Configuration& config) { initEsp(); #endif hardwareConfig = &config; - time::init(); + settings::initTimeZone(); hal::init(config); network::ntp::init(); registerAndStartSystemServices(); diff --git a/Tactility/Source/app/ElfApp.cpp b/Tactility/Source/app/ElfApp.cpp index 3653d649..298ad7a4 100644 --- a/Tactility/Source/app/ElfApp.cpp +++ b/Tactility/Source/app/ElfApp.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace tt::app { @@ -39,10 +40,18 @@ class ElfApp : public App { const std::string filePath; std::unique_ptr elfFileData; - esp_elf_t elf; + esp_elf_t elf { + .psegment = nullptr, + .svaddr = 0, + .ptext = nullptr, + .pdata = nullptr, + .sec = { }, + .entry = nullptr + }; bool shouldCleanupElf = false; // Whether we have to clean up the above "elf" object std::unique_ptr manifest; void* data = nullptr; + std::string lastError = ""; bool startElf() { TT_LOG_I(TAG, "Starting ELF %s", filePath.c_str()); @@ -58,14 +67,17 @@ class ElfApp : public App { } if (esp_elf_init(&elf) != ESP_OK) { - TT_LOG_E(TAG, "Failed to initialize"); + lastError = "Failed to initialize"; + TT_LOG_E(TAG, "%s", lastError.c_str()); elfFileData = nullptr; return false; } - if (esp_elf_relocate(&elf, elfFileData.get()) != ESP_OK) { - TT_LOG_E(TAG, "Failed to load executable"); - esp_elf_deinit(&elf); + auto relocate_result = esp_elf_relocate(&elf, elfFileData.get()); + if (relocate_result != 0) { + // Note: the result code mapes to values from cstdlib's errno.h + lastError = std::format("Failed to load executable (error code {})", -relocate_result); + TT_LOG_E(TAG, "%s", lastError.c_str()); elfFileData = nullptr; return false; } @@ -74,7 +86,8 @@ class ElfApp : public App { char* argv[] = {}; if (esp_elf_request(&elf, 0, argc, argv) != ESP_OK) { - TT_LOG_W(TAG, "Executable returned error code"); + lastError = "Executable returned error code"; + TT_LOG_E(TAG, "%s", lastError.c_str()); esp_elf_deinit(&elf); elfFileData = nullptr; return false; @@ -106,22 +119,29 @@ public: auto lock = elfManifestLock->asScopedLock(); lock.lock(); - auto initial_count = elfManifestSetCount; - if (startElf()) { - if (elfManifestSetCount > initial_count) { - manifest = std::make_unique(elfManifest); - lock.unlock(); - - if (manifest->createData != nullptr) { - data = manifest->createData(); - } - - if (manifest->onCreate != nullptr) { - manifest->onCreate(&appContext, data); - } - } - } else { + elfManifestSetCount = 0; + if (!startElf()) { service::loader::stopApp(); + auto message = lastError.empty() ? "Application failed to start." : std::format("Application failed to start: {}", lastError); + alertdialog::start("Error", message); + return; + } + + if (elfManifestSetCount == 0) { + service::loader::stopApp(); + alertdialog::start("Error", "Application failed to start: application failed to register itself"); + return; + } + + manifest = std::make_unique(elfManifest); + lock.unlock(); + + if (manifest->createData != nullptr) { + data = manifest->createData(); + } + + if (manifest->onCreate != nullptr) { + manifest->onCreate(&appContext, data); } } diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index af2e8ff5..1c994cc7 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -1,4 +1,5 @@ #include "Tactility/app/AppContext.h" +#include "Tactility/app/launcher/TextResources.h" #include "Tactility/app/AppRegistration.h" #include "Tactility/service/loader/Loader.h" @@ -7,10 +8,10 @@ #include #include -#define TAG "launcher" - namespace tt::app::launcher { +constexpr auto* TAG = "Launcher"; + static void onAppPressed(TT_UNUSED lv_event_t* e) { auto* appId = (const char*)lv_event_get_user_data(e); service::loader::startApp(appId); @@ -52,6 +53,7 @@ static lv_obj_t* createAppButton(lv_obj_t* parent, const char* title, const char } class LauncherApp : public App { + tt::i18n::TextResources textResources = tt::i18n::TextResources("/system/app/Launcher/i18n"); void onCreate(TT_UNUSED AppContext& app) override { BootProperties boot_properties; @@ -62,6 +64,8 @@ class LauncherApp : public App { } void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) override { + textResources.load(); + auto* wrapper = lv_obj_create(parent); lv_obj_align(wrapper, LV_ALIGN_CENTER, 0, 0); @@ -87,9 +91,14 @@ class LauncherApp : public App { auto apps_icon_path = paths->getSystemPathLvgl("icon_apps.png"); auto files_icon_path = paths->getSystemPathLvgl("icon_files.png"); auto settings_icon_path = paths->getSystemPathLvgl("icon_settings.png"); - createAppButton(wrapper, "Apps", apps_icon_path.c_str(), "AppList", 0); - createAppButton(wrapper, "Files", files_icon_path.c_str(), "Files", padding); - createAppButton(wrapper, "Settings", settings_icon_path.c_str(), "Settings", padding); + + const auto& apps_title = textResources[i18n::Text::APPS]; + const auto& files_title = textResources[i18n::Text::FILES]; + const auto& settings_title = textResources[i18n::Text::SETTINGS]; + + createAppButton(wrapper, apps_title.c_str(), apps_icon_path.c_str(), "AppList", 0); + createAppButton(wrapper, files_title.c_str(), files_icon_path.c_str(), "Files", padding); + createAppButton(wrapper, settings_title.c_str(), settings_icon_path.c_str(), "Settings", padding); } }; diff --git a/Tactility/Source/app/localesettings/LocaleSettings.cpp b/Tactility/Source/app/localesettings/LocaleSettings.cpp new file mode 100644 index 00000000..cb4bb560 --- /dev/null +++ b/Tactility/Source/app/localesettings/LocaleSettings.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace tt::app::localesettings { + +constexpr auto* TAG = "LocaleSettings"; + +extern const AppManifest manifest; + +class LocaleSettingsApp : public App { + tt::i18n::TextResources textResources = tt::i18n::TextResources("/system/app/LocaleSettings/i18n"); + Mutex mutex = Mutex(Mutex::Type::Recursive); + lv_obj_t* timeZoneLabel = nullptr; + lv_obj_t* regionLabel = nullptr; + lv_obj_t* languageDropdown = nullptr; + lv_obj_t* languageLabel = nullptr; + + static void onConfigureTimeZonePressed(TT_UNUSED lv_event_t* event) { + timezone::start(); + } + + std::map languageMap; + + std::string getLanguageOptions() const { + std::vector items; + for (int i = 0; i < static_cast(settings::Language::count); i++) { + switch (static_cast(i)) { + case settings::Language::en_GB: + items.push_back(textResources[i18n::Text::EN_GB]); + break; + case settings::Language::en_US: + items.push_back(textResources[i18n::Text::EN_US]); + break; + case settings::Language::fr_FR: + items.push_back(textResources[i18n::Text::FR_FR]); + break; + case settings::Language::nl_BE: + items.push_back(textResources[i18n::Text::NL_BE]); + break; + case settings::Language::nl_NL: + items.push_back(textResources[i18n::Text::NL_NL]); + break; + case settings::Language::count: + break; + } + } + return string::join(items, "\n"); + } + + void updateViews() { + textResources.load(); + + lv_label_set_text(regionLabel , textResources[i18n::Text::REGION].c_str()); + lv_label_set_text(languageLabel, textResources[i18n::Text::LANGUAGE].c_str()); + + std::string language_options = getLanguageOptions(); + lv_dropdown_set_options(languageDropdown, language_options.c_str()); + lv_dropdown_set_selected(languageDropdown, static_cast(settings::getLanguage())); + } + + static void onLanguageSet(lv_event_t* event) { + auto* dropdown = static_cast(lv_event_get_target(event)); + auto index = lv_dropdown_get_selected(dropdown); + auto language = static_cast(index); + settings::setLanguage(language); + + auto* self = static_cast(lv_event_get_user_data(event)); + self->updateViews(); + } + +public: + + void onShow(AppContext& app, lv_obj_t* parent) override { + textResources.load(); + + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + + lvgl::toolbar_create(parent, app); + + auto* main_wrapper = lv_obj_create(parent); + lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_width(main_wrapper, LV_PCT(100)); + lv_obj_set_flex_grow(main_wrapper, 1); + + auto* region_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_width(region_wrapper, LV_PCT(100)); + lv_obj_set_height(region_wrapper, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(region_wrapper, 0, 0); + lv_obj_set_style_border_width(region_wrapper, 0, 0); + + regionLabel = lv_label_create(region_wrapper); + lv_label_set_text(regionLabel , textResources[i18n::Text::REGION].c_str()); + lv_obj_align(regionLabel , LV_ALIGN_LEFT_MID, 0, 0); + + timeZoneLabel = lv_label_create(region_wrapper); + std::string timeZoneName = settings::getTimeZoneName(); + if (timeZoneName.empty()) { + timeZoneName = "not set"; + } + lv_label_set_text(timeZoneLabel, timeZoneName.c_str()); + // TODO: Find out why Y offset is needed + lv_obj_align_to(timeZoneLabel, regionLabel, LV_ALIGN_OUT_RIGHT_MID, 10, 8); + + auto* region_button = lv_button_create(region_wrapper); + lv_obj_align(region_button, LV_ALIGN_TOP_RIGHT, 0, 0); + auto* region_button_image = lv_image_create(region_button); + lv_obj_add_event_cb(region_button, onConfigureTimeZonePressed, LV_EVENT_SHORT_CLICKED, nullptr); + lv_image_set_src(region_button_image, LV_SYMBOL_SETTINGS); + + auto* language_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_width(language_wrapper, LV_PCT(100)); + lv_obj_set_height(language_wrapper, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(language_wrapper, 0, 0); + lv_obj_set_style_border_width(language_wrapper, 0, 0); + + languageLabel = lv_label_create(language_wrapper); + lv_label_set_text(languageLabel, textResources[i18n::Text::LANGUAGE].c_str()); + lv_obj_align(languageLabel, LV_ALIGN_LEFT_MID, 0, 0); + + languageDropdown = lv_dropdown_create(language_wrapper); + lv_obj_align(languageDropdown, LV_ALIGN_RIGHT_MID, 0, 0); + std::string language_options = getLanguageOptions(); + lv_dropdown_set_options(languageDropdown, language_options.c_str()); + lv_dropdown_set_selected(languageDropdown, static_cast(settings::getLanguage())); + lv_obj_add_event_cb(languageDropdown, onLanguageSet, LV_EVENT_VALUE_CHANGED, this); + } + + void onResult(AppContext& app, TT_UNUSED LaunchId launchId, Result result, std::unique_ptr bundle) override { + if (result == Result::Ok && bundle != nullptr) { + const auto name = timezone::getResultName(*bundle); + const auto code = timezone::getResultCode(*bundle); + TT_LOG_I(TAG, "Result name=%s code=%s", name.c_str(), code.c_str()); + settings::setTimeZone(name, code); + + if (!name.empty()) { + if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + lv_label_set_text(timeZoneLabel, name.c_str()); + lvgl::unlock(); + } + } + } + } +}; + +extern const AppManifest manifest = { + .id = "LocaleSettings", + .name = "Region & Language", + .icon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS, + .type = Type::Settings, + .createApp = create +}; + +void start() { + service::loader::startApp(manifest.id); +} + +} // namespace diff --git a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp index f9290374..95e4eb57 100644 --- a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp +++ b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp @@ -1,33 +1,25 @@ -#include "Tactility/app/timezone/TimeZone.h" -#include "Tactility/lvgl/Toolbar.h" -#include "Tactility/service/loader/Loader.h" -#include "Tactility/lvgl/LvglSync.h" - #include -#include +#include +#include +#include +#include + #include -#define TAG "text_viewer" - namespace tt::app::timedatesettings { +constexpr auto* TAG = "TimeDate"; + extern const AppManifest manifest; class TimeDateSettingsApp : public App { -private: - Mutex mutex = Mutex(Mutex::Type::Recursive); - lv_obj_t* regionLabelWidget = nullptr; - - static void onConfigureTimeZonePressed(TT_UNUSED lv_event_t* event) { - timezone::start(); - } static void onTimeFormatChanged(lv_event_t* event) { auto* widget = lv_event_get_target_obj(event); bool show_24 = lv_obj_has_state(widget, LV_STATE_CHECKED); - time::setTimeFormat24Hour(show_24); + settings::setTimeFormat24Hour(show_24); } public: @@ -42,32 +34,6 @@ public: lv_obj_set_width(main_wrapper, LV_PCT(100)); lv_obj_set_flex_grow(main_wrapper, 1); - auto* region_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_width(region_wrapper, LV_PCT(100)); - lv_obj_set_height(region_wrapper, LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(region_wrapper, 0, 0); - lv_obj_set_style_border_width(region_wrapper, 0, 0); - - auto* region_prefix_label = lv_label_create(region_wrapper); - lv_label_set_text(region_prefix_label, "Region: "); - lv_obj_align(region_prefix_label, LV_ALIGN_LEFT_MID, 0, 0); - - auto* region_label = lv_label_create(region_wrapper); - std::string timeZoneName = time::getTimeZoneName(); - if (timeZoneName.empty()) { - timeZoneName = "not set"; - } - regionLabelWidget = region_label; - lv_label_set_text(region_label, timeZoneName.c_str()); - // TODO: Find out why Y offset is needed - lv_obj_align_to(region_label, region_prefix_label, LV_ALIGN_OUT_RIGHT_MID, 0, 8); - - auto* region_button = lv_button_create(region_wrapper); - lv_obj_align(region_button, LV_ALIGN_TOP_RIGHT, 0, 0); - auto* region_button_image = lv_image_create(region_button); - lv_obj_add_event_cb(region_button, onConfigureTimeZonePressed, LV_EVENT_SHORT_CLICKED, nullptr); - lv_image_set_src(region_button_image, LV_SYMBOL_SETTINGS); - auto* time_format_wrapper = lv_obj_create(main_wrapper); lv_obj_set_width(time_format_wrapper, LV_PCT(100)); lv_obj_set_height(time_format_wrapper, LV_SIZE_CONTENT); @@ -81,28 +47,12 @@ public: auto* time_24h_switch = lv_switch_create(time_format_wrapper); lv_obj_align(time_24h_switch, LV_ALIGN_RIGHT_MID, 0, 0); lv_obj_add_event_cb(time_24h_switch, onTimeFormatChanged, LV_EVENT_VALUE_CHANGED, nullptr); - if (time::isTimeFormat24Hour()) { + if (settings::isTimeFormat24Hour()) { lv_obj_add_state(time_24h_switch, LV_STATE_CHECKED); } else { lv_obj_remove_state(time_24h_switch, LV_STATE_CHECKED); } } - - void onResult(AppContext& app, TT_UNUSED LaunchId launchId, Result result, std::unique_ptr bundle) override { - if (result == Result::Ok && bundle != nullptr) { - auto name = timezone::getResultName(*bundle); - auto code = timezone::getResultCode(*bundle); - TT_LOG_I(TAG, "Result name=%s code=%s", name.c_str(), code.c_str()); - time::setTimeZone(name, code); - - if (!name.empty()) { - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { - lv_label_set_text(regionLabelWidget, name.c_str()); - lvgl::unlock(); - } - } - } - } }; extern const AppManifest manifest = { diff --git a/Tactility/Source/file/PropertiesFile.cpp b/Tactility/Source/file/PropertiesFile.cpp index 283fa903..088f44d8 100644 --- a/Tactility/Source/file/PropertiesFile.cpp +++ b/Tactility/Source/file/PropertiesFile.cpp @@ -31,6 +31,7 @@ bool loadPropertiesFile(const std::string& filePath, std::function + +#include +#include +#include +#include + +namespace tt::i18n { + +constexpr auto* TAG = "I18n"; + +static std::string getFallbackLocale() { + return "en-US"; +} + +static std::string getDesiredLocale() { + switch (settings::getLanguage()) { + case settings::Language::en_GB: + return "en-GB"; + case settings::Language::en_US: + return "en-US"; + case settings::Language::fr_FR: + return "fr-FR"; + case settings::Language::nl_BE: + return "nl-BE"; + case settings::Language::nl_NL: + return "nl-NL"; + default: + return getFallbackLocale(); + } +} + +static std::string getI18nDataFilePath(const std::string& path) { + auto locale = getDesiredLocale(); + auto desired_file_path = std::format("{}/{}.i18n", path, locale); + if (file::isFile(desired_file_path)) { + return desired_file_path; + } else { + TT_LOG_W(TAG, "Translations not found for %s at %s", locale.c_str(), desired_file_path.c_str()); + } + + auto fallback_locale = getFallbackLocale(); + auto fallback_file_path = std::format("{}/{}.i18n", path, getFallbackLocale()); + if (file::isFile(fallback_file_path)) { + return fallback_file_path; + } else { + TT_LOG_W(TAG, "Fallback translations not found for %s at %s", fallback_locale.c_str(), fallback_file_path.c_str()); + return ""; + } +} + +std::string TextResources::ERROR_RESULT = "TXT_RES_ERROR"; + +bool TextResources::load() { + std::vector new_data; + + // Resolve the language file that we need (depends on system language selection) + auto file_path = getI18nDataFilePath(path); + if (file_path.empty()) { + TT_LOG_E(TAG, "Couldn't find i18n data for %s", path.c_str()); + return false; + } + + file::withLock(file_path, [&file_path, &new_data] { + file::readLines(file_path, true, [&new_data](const char* line) { + new_data.push_back(line); + }); + }); + + if (new_data.empty()) { + TT_LOG_E(TAG, "Couldn't find i18n data for %s", path.c_str()); + return false; + } + + data = std::move(new_data); + return true; +} + +} diff --git a/Tactility/Source/lvgl/Statusbar.cpp b/Tactility/Source/lvgl/Statusbar.cpp index 7e9e39b1..68ca0deb 100644 --- a/Tactility/Source/lvgl/Statusbar.cpp +++ b/Tactility/Source/lvgl/Statusbar.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include @@ -199,7 +199,7 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) { static void update_time(Statusbar* statusbar) { if (statusbar_data.time_set) { - bool format24 = time::isTimeFormat24Hour(); + bool format24 = settings::isTimeFormat24Hour(); int hours = format24 ? statusbar_data.time_hours : statusbar_data.time_hours % 12; lv_label_set_text_fmt(statusbar->time, "%d:%02d", hours, statusbar_data.time_minutes); } else { diff --git a/Tactility/Source/service/development/DevelopmentService.cpp b/Tactility/Source/service/development/DevelopmentService.cpp index 959f8efb..6091816f 100644 --- a/Tactility/Source/service/development/DevelopmentService.cpp +++ b/Tactility/Source/service/development/DevelopmentService.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace tt::service::development { @@ -198,6 +199,11 @@ esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) { auto app_id = id_key_pos->second; if (app_id.ends_with(".app.elf")) { + if (!file::isFile(app_id)) { + TT_LOG_W(TAG, "[400] /app/run cannot find app %s", app_id.c_str()); + httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "app not found"); + return ESP_FAIL; + } app::registerElfApp(app_id); app_id = app::getElfAppId(app_id); } else if (!app::findAppById(app_id.c_str())) { diff --git a/Tactility/Source/service/wifi/WifiSettings.cpp b/Tactility/Source/service/wifi/WifiSettings.cpp index 4ce1a75a..d2bd369c 100644 --- a/Tactility/Source/service/wifi/WifiSettings.cpp +++ b/Tactility/Source/service/wifi/WifiSettings.cpp @@ -2,7 +2,7 @@ #include "Tactility/Preferences.h" #include "Tactility/file/PropertiesFile.h" -#include +#include #include namespace tt::service::wifi::settings { diff --git a/Tactility/Source/settings/Language.cpp b/Tactility/Source/settings/Language.cpp new file mode 100644 index 00000000..9a0b6dc8 --- /dev/null +++ b/Tactility/Source/settings/Language.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +namespace tt::settings { + +constexpr auto* TAG = "Language"; + +void setLanguage(Language newLanguage) { + SettingsProperties properties; + if (!loadSettingsProperties(properties)) { + return; + } + + properties.language = newLanguage; + saveSettingsProperties(properties); +} + +Language getLanguage() { + SettingsProperties properties; + if (!loadSettingsProperties(properties)) { + return Language::en_US; + } else { + return properties.language; + } +} + +std::string toString(Language language) { + switch (language) { + case Language::en_GB: + return "en-GB"; + case Language::en_US: + return "en-US"; + case Language::fr_FR: + return "fr-FR"; + case Language::nl_BE: + return "nl-BE"; + case Language::nl_NL: + return "nl-NL"; + default: + TT_LOG_E(TAG, "Missing serialization for language %d", static_cast(language)); + std::unreachable(); + } +} + +bool fromString(const std::string& text, Language& language) { + if (text == "en-GB") { + language = Language::en_GB; + } else if (text == "en-US") { + language = Language::en_US; + } else if (text == "fr-FR") { + language = Language::fr_FR; + } else if (text == "nl-BE") { + language = Language::nl_BE; + } else if (text == "nl-NL") { + language = Language::nl_NL; + } else { + return false; + } + + return true; +} + +} diff --git a/Tactility/Source/settings/SettingsProperties.cpp b/Tactility/Source/settings/SettingsProperties.cpp new file mode 100644 index 00000000..05e56624 --- /dev/null +++ b/Tactility/Source/settings/SettingsProperties.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include + +namespace tt::settings { + +constexpr auto* TAG = "SettingsProperties"; +constexpr auto* FILE_PATH = "/data/settings.properties"; + +static Mutex mutex = Mutex(); +static bool cached = false; +static SettingsProperties cachedProperties; + +static bool loadSettingsPropertiesFromFile(SettingsProperties& properties) { + std::map map; + if (!file::withLock(FILE_PATH, [&map] { + return file::loadPropertiesFile(FILE_PATH, map); + })) { + TT_LOG_E(TAG, "Failed to load %s", FILE_PATH); + return false; + } + + auto language_entry = map.find("language"); + if (language_entry != map.end()) { + if (!fromString(language_entry->second, properties.language)) { + TT_LOG_W(TAG, "Unknown language \"%s\" in %s", language_entry->second.c_str(), FILE_PATH); + properties.language = Language::en_US; + } + } else { + properties.language = Language::en_US; + } + + auto time_format_entry = map.find("timeFormat24h"); + bool time_format_24h = time_format_entry == map.end() ? true : (time_format_entry->second == "true"); + properties.timeFormat24h = time_format_24h; + + return true; +} + +bool loadSettingsProperties(SettingsProperties& properties) { + auto scoped_lock = mutex.asScopedLock(); + scoped_lock.lock(); + + if (!cached) { + if (!loadSettingsPropertiesFromFile(cachedProperties)) { + return false; + } + cached = true; + } + + properties = cachedProperties; + return true; +} + +bool saveSettingsProperties(const SettingsProperties& properties) { + auto scoped_lock = mutex.asScopedLock(); + scoped_lock.lock(); + + return file::withLock(FILE_PATH, [&properties] { + std::map map; + map["language"] = toString(properties.language); + map["timeFormat24h"] = properties.timeFormat24h ? "true" : "false"; + + if (!file::savePropertiesFile(FILE_PATH, map)) { + TT_LOG_E(TAG, "Failed to save %s", FILE_PATH); + return false; + } + + cachedProperties = properties; + cached = true; + return true; + }); +} + +} \ No newline at end of file diff --git a/Tactility/Source/settings/Time.cpp b/Tactility/Source/settings/Time.cpp new file mode 100644 index 00000000..48ee837e --- /dev/null +++ b/Tactility/Source/settings/Time.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include +#include + +#ifdef ESP_PLATFORM +#include +#endif + +namespace tt::settings { + +constexpr auto* TIME_SETTINGS_NAMESPACE = "time"; + +constexpr auto* TIMEZONE_PREFERENCES_KEY_NAME = "tz_name"; +constexpr auto* TIMEZONE_PREFERENCES_KEY_CODE = "tz_code"; +constexpr auto* TIMEZONE_PREFERENCES_KEY_TIME24 = "tz_time24"; + +void initTimeZone() { +#ifdef ESP_PLATFORM + auto code= getTimeZoneCode(); + if (!code.empty()) { + setenv("TZ", code.c_str(), 1); + tzset(); + } +#endif +} + +void setTimeZone(const std::string& name, const std::string& code) { + Preferences preferences(TIME_SETTINGS_NAMESPACE); + preferences.putString(TIMEZONE_PREFERENCES_KEY_NAME, name); + preferences.putString(TIMEZONE_PREFERENCES_KEY_CODE, code); + +#ifdef ESP_PLATFORM + setenv("TZ", code.c_str(), 1); + tzset(); +#endif + + kernel::publishSystemEvent(kernel::SystemEvent::Time); +} + +std::string getTimeZoneName() { + Preferences preferences(TIME_SETTINGS_NAMESPACE); + std::string result; + if (preferences.optString(TIMEZONE_PREFERENCES_KEY_NAME, result)) { + return result; + } else { + return {}; + } +} + +std::string getTimeZoneCode() { + Preferences preferences(TIME_SETTINGS_NAMESPACE); + std::string result; + if (preferences.optString(TIMEZONE_PREFERENCES_KEY_CODE, result)) { + return result; + } else { + return {}; + } +} + +bool isTimeFormat24Hour() { + SettingsProperties properties; + if (!loadSettingsProperties(properties)) { + return true; + } else { + return properties.timeFormat24h; + } +} + +void setTimeFormat24Hour(bool show24Hour) { + SettingsProperties properties; + if (!loadSettingsProperties(properties)) { + return; + } + + properties.timeFormat24h = show24Hour; + saveSettingsProperties(properties); +} + +} diff --git a/Tactility/Source/time/Time.cpp b/Tactility/Source/time/Time.cpp deleted file mode 100644 index 99d97f12..00000000 --- a/Tactility/Source/time/Time.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "Tactility/time/Time.h" -#include "Tactility/kernel/SystemEvents.h" - -#ifdef ESP_PLATFORM -#include -#include "Tactility/Preferences.h" -#endif - -namespace tt::time { - -#ifdef ESP_PLATFORM - -#define TIME_SETTINGS_NAMESPACE "time" - -#define TIMEZONE_PREFERENCES_KEY_NAME "tz_name" -#define TIMEZONE_PREFERENCES_KEY_CODE "tz_code" -#define TIMEZONE_PREFERENCES_KEY_TIME24 "tz_time24" - -void init() { - auto code= getTimeZoneCode(); - if (!code.empty()) { - setenv("TZ", code.c_str(), 1); - tzset(); - } -} - -void setTimeZone(const std::string& name, const std::string& code) { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - preferences.putString(TIMEZONE_PREFERENCES_KEY_NAME, name); - preferences.putString(TIMEZONE_PREFERENCES_KEY_CODE, code); - - setenv("TZ", code.c_str(), 1); - tzset(); - - kernel::publishSystemEvent(kernel::SystemEvent::Time); -} - -std::string getTimeZoneName() { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - std::string result; - if (preferences.optString(TIMEZONE_PREFERENCES_KEY_NAME, result)) { - return result; - } else { - return {}; - } -} - -std::string getTimeZoneCode() { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - std::string result; - if (preferences.optString(TIMEZONE_PREFERENCES_KEY_CODE, result)) { - return result; - } else { - return {}; - } -} - -bool isTimeFormat24Hour() { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - bool show24Hour = true; - preferences.optBool(TIMEZONE_PREFERENCES_KEY_TIME24, show24Hour); - return show24Hour; -} - -void setTimeFormat24Hour(bool show24Hour) { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - preferences.putBool(TIMEZONE_PREFERENCES_KEY_TIME24, show24Hour); - kernel::publishSystemEvent(kernel::SystemEvent::Time); -} - -#else - -static std::string timeZoneName; -static std::string timeZoneCode; -static bool show24Hour = true; - -void init() {} - -void setTimeZone(const std::string& name, const std::string& code) { - timeZoneName = name; - timeZoneCode = code; - kernel::publishSystemEvent(kernel::SystemEvent::Time); -} - -std::string getTimeZoneName() { - return timeZoneName; -} - -std::string getTimeZoneCode() { - return timeZoneCode; -} - -bool isTimeFormat24Hour() { - return show24Hour; -} - -void setTimeFormat24Hour(bool enabled) { - show24Hour = enabled; - kernel::publishSystemEvent(kernel::SystemEvent::Time); -} - -#endif - -} diff --git a/TactilityC/Source/tt_time.cpp b/TactilityC/Source/tt_time.cpp index 9a255e8f..ee97242b 100644 --- a/TactilityC/Source/tt_time.cpp +++ b/TactilityC/Source/tt_time.cpp @@ -1,6 +1,6 @@ #include "tt_time.h" -#include +#include #include using namespace tt; @@ -8,11 +8,11 @@ using namespace tt; extern "C" { void tt_timezone_set(const char* name, const char* code) { - time::setTimeZone(name, code); + settings::setTimeZone(name, code); } bool tt_timezone_get_name(char* buffer, size_t bufferSize) { - auto name = time::getTimeZoneName(); + auto name = settings::getTimeZoneName(); if (bufferSize < (name.length() + 1)) { return false; } else { @@ -22,7 +22,7 @@ bool tt_timezone_get_name(char* buffer, size_t bufferSize) { } bool tt_timezone_get_code(char* buffer, size_t bufferSize) { - auto code = time::getTimeZoneCode(); + auto code = settings::getTimeZoneCode(); if (bufferSize < (code.length() + 1)) { return false; } else { @@ -32,11 +32,11 @@ bool tt_timezone_get_code(char* buffer, size_t bufferSize) { } bool tt_timezone_is_format_24_hour() { - return time::isTimeFormat24Hour(); + return settings::isTimeFormat24Hour(); } void tt_timezone_set_format_24_hour(bool show24Hour) { - return time::setTimeFormat24Hour(show24Hour); + return settings::setTimeFormat24Hour(show24Hour); } } diff --git a/TactilityCore/Include/Tactility/file/File.h b/TactilityCore/Include/Tactility/file/File.h index 76808394..a96a9f0c 100644 --- a/TactilityCore/Include/Tactility/file/File.h +++ b/TactilityCore/Include/Tactility/file/File.h @@ -110,4 +110,6 @@ int scandir( ScandirSort _Nullable sort ); +bool readLines(const std::string& filePath, bool stripNewLine, std::function callback); + } diff --git a/TactilityCore/Source/file/File.cpp b/TactilityCore/Source/file/File.cpp index 3d778e61..b79fde56 100644 --- a/TactilityCore/Source/file/File.cpp +++ b/TactilityCore/Source/file/File.cpp @@ -222,4 +222,28 @@ bool isDirectory(const std::string& path) { return stat(path.c_str(), &stat_result) == 0 && S_ISDIR(stat_result.st_mode); } +bool readLines(const std::string& filepath, bool stripNewLine, std::function callback) { + auto* file = fopen(filepath.c_str(), "r"); + if (file == nullptr) { + return false; + } + + char line[1024]; + + while (fgets(line, sizeof(line), file) != nullptr) { + // Strip newline + if (stripNewLine) { + size_t line_length = strlen(line); + if (line_length > 0 && line[line_length - 1] == '\n') { + line[line_length - 1] = '\0'; + } + } + // Publish + callback(line); + } + + fclose(file); + return true; +} + } diff --git a/Translations/.gitignore b/Translations/.gitignore new file mode 100644 index 00000000..f54462c7 --- /dev/null +++ b/Translations/.gitignore @@ -0,0 +1,2 @@ +*.csv +*.ods# \ No newline at end of file diff --git a/Translations/README.md b/Translations/README.md new file mode 100644 index 00000000..535a2d52 --- /dev/null +++ b/Translations/README.md @@ -0,0 +1,25 @@ +# Translations + +## Requirements + +- Install [LibreOffice Calc](https://libreoffice.org/) +- Ensure you have Python 3 installed (Python 2 is not supported) + +## Steps + +To add new translations or edit existing ones, please follow these steps: + +1. Edit `Translations.ods` (see chapter below) +2. In LibreOffice Calc, select the relevant tab that you want to export +3. Click on `File` -> `Save a copy...` and save the file as `[tabname].csv` (without the "[]") +4. Repeat step 2 and 3 for all tabs that you updated +5. Run `python generate-all.py` + +## Notes + +- Do not commit the CSV files +- When editing the ODS file, make sure you don't paste in formatted data (use CTRL+Shift+V instead of CTRL+V) +- ODS export settings: + - Field delimiter: `,` + - String delimiter: `"` + - Encoding: `UTF-8` \ No newline at end of file diff --git a/Translations/Translations.ods b/Translations/Translations.ods new file mode 100644 index 0000000000000000000000000000000000000000..3ce333cdb1c527796c70bc09d411fc7401decf78 GIT binary patch literal 13804 zcmc(G1yo$i(k`yS-7R=vfDkl5fZ*=#3~pftx8N?pgS)%CySoMmAxLm{`N_HK+?;d& zbI$wMd+WVdGkZ<#y}G`xuHMsK-PI*81r37@0f7Jk!Cr!|CVF+5T7p+%iGS5wJW2=!lNdrXMQ^RMFBQ9VAwr2a z<~`T;*L^YwAJ>c9Sd8tD`2Fm9&7C7oB(Y-beJ)LoPZyh}6?S^Io@XI6h!GPEd6WFY zjS$*jFnUszMD5!c@W0XF$ro^6zS2_|VAb1X5q77}!eNrP5mD3m9H&^^Pc^n}cyQ=P zr<&&UmejQWCah}a?sPDQoqR&VIi45zLHf2qcrD$n5KkDg6Eb@PneoF?aEThFI&(r= zmgZcd3A7UYDm@hhi9B<|<`zb`f8uW2RvB-ae7sCte2c^4i;$I%a>z%}*t{3>fmiW* zrl2B@n7br?N%KNkBr{Z^R7N?kvbV`aKY9?w>4MBu-+9;Pl%8`HT?|ybE0JdJyg9JT zqOO%@_w83mfLe=r`$j5DL5fm5%lWKyyb+(Olw4w-MM^{SMO(y` z?aD(O5&u_F%V3X33B^&0kRW7ptP9}V?gKP{g=ORFFB zd9HBzyU{INa(e-NR-|%x>HQ8GszDgM<$M81L+?L_3G;B{1cEcLvPg_$VzLs62;#hx zQxq4bQYj$wk)&3eUS;B8*9)dEy||!1^QaTCvOmoDn2IFFLdkIld~{{X#5N}Ws!c8$ zsv@H)XZq4I^haECC8Mbj2oe*Y0zEtEc$CpvjC+4)T=8JZBeFiIFo-MNU`!{FOmR5L zz`owm3m$psY3AFsZ`pLB=BY3CX3)CX!70^NOQ(W9#=+bnBwGp35E17A6^=4JX!Y^n zXl%RQx2~2TAK$4Tc8IjE$!P}ylqeKHtFF!}kQlcrzdG8m`cA{vFt&q_b(w*;npL%y zJP4hl3r?9PH*o?U{J>i458gAJT=k!*<1cRnv-N{C&+H&e8|6JX9og|&VV8}%eApKmUDSFRItj0b0)`CQTBXxc%+PGM#Vrt(Q6RV%HoJa` zRA_O@gSEY>F+i=1O08bS2a%p0&<(_-W)KjALw<+cDmP9al^%|;)|p5ODT5gS!&R!| z#SvMh`%On;25!g$Z%ip_62Ei>DzBDoLGL&*rn`S8zOV9z z!EK(opKT0iVi(a7PvP9`*r?BiN#!^j6Mlw2NG(KaI`eW0)nQ+Pe7zv7{eVt)vu3>b z6SiouS4c#YFA-P%;HOVmpaUm^m=LKK|3$SZuWKVJvYs zs>=1u_Eh&*C6a{mq+{9YMri#_6e-UfD3oAspg`H*z|kX;Zp#|S47l@d~e4cqj^CpV)gMb2@kU1!1(;?LeuM38*H#x=c!k{NJd zdG(Q7^@5{@xJHCM6Z;lj2=>w;x4DxS3;$ zqfL-FwapK@hvv)JXlA16cOSAVi?E|NB=4igbw7ZU6dX^v>g!%I@qod6^2xC3$5{W- zp^iAKWn))uD!s&-vIZq6=;JSv()nmzC+!U6KyZ~7&>5m1CNB(#gxju*?eP^UHZ8)6 zOAoNfFPXe3iO=*ptg?eM0sQDbOL!$IoPRlHJX^wM;?UbdevHb_mGU)fWVz_Z<}(`Z zLC_pVL4tbiNmh9{tsfgP8t~dTfQ=X#2&im4N+uox?63zYt|6M`Ew0u(q!>5b-?NYqT;ip*KbGdnNE=cCNX-xBxQU<-Hheu^wGGplYST15sP# zQ$TTlaa=OdT06XccB_7^%q*6FXV#^Wzd>V*9{y?vZ8;3V$Nm?B*n>7{q&`pYF2s)& zx{qHGKfE@IKb_omxlOfDu^ zN^z%aqK-<$#cEl&OuP}3I zzKv0TJ*%@7E9PZ*|IYF7N7@c=`3jk!vBIWw~d`)G6d#w#Z#K z#4?l4v$)#iUi=~(4q7al%-JUy7M;mu`uH$r(spWmV>>jM;UP4!=&tWs@l!T#c|L}g zTJ+j@bHW#M<|{X=0;DBahB7)ZD*URQ-mOoN$cIs;2tpx2QT5E*?9AB)_7z^W@kRsE zAfS$RblFGlCqyGG=--81xRMsE5H4)Z?wZhjm7n@DgnTHz5eDH zkG#d6v+bx*pv|@~Q$5_!T9MkDGvMfVu`cK zg?-_X!`g<$cVnW&$*_R(j+4cXwt2m z#~7M1>p={T#ES4-`Bauwa|9E`bdyZtxrKGZ`HM|p6gobm9ma_Ah1KAwjdS@Eqs`5? z(DwH_S%<)OWwli1p{KA3bfk>?VRtmzBdyh=s>dVrC*xWwL%}>-$W5iRX-l=(n{OyT z?N@VUHU$R>0pSb%PxeE8wx5x$4fyA>>Dd=P&|b0GXu@baQbJ&mw*r{;I@-(5g@f#> zn(1AmM{x&jCUu$2KD?~X6eo$U+85i2!4BmY)h)?*e8n42)gRGK-pFcbA8__zi^yJl zkoyKm$Z86usL&Z`QxE#e)8QdVhZcBPU%tJw@UVG~MoWoMfCf!@{}QU~6QBXEEY-ZH zfv^V?+b-OJL!9#FoxaHI#|?y;cX!Yj&;SScD+jpGD=jN(Mdg8q>U+*#UCfglr#!_5 zChlAhKJ(`o6tyby#QIgJOtt#@w&A(LSccJHPYJ{mPFF^#T7cKuy^esbaRpf@!cMJT zo^35B{pbeo8>bTQSmr6wELFO^qdr+g9l5wT(M!Ld;W}kEM4;(#pe}WnFk0atZUpn8 z_kp#nskG+9D@Zx7uNUsZF1YTl-8F``&n+xV$&E(S(6iV~vF(GWcms7GJ@~8Z;`2f> z-;wLRXPpE0#E0iumgYrUM5z=IY}SwVylz#=pq8DNEp+=z@R< zP^jnXqxa{A)239LI9*Y+JnCR$btVXJajroS=M|Z>QPd3BAifXagD~hLz>;fFkLfH|8Sk!R%d58KY4v-1aA0 z3S7ncYIn`VB0sfWWrXS;1WfXwH{uwaIc-UPrAEAnM2f>?MvFW{pO2wLq+e=+U;v8| z?>6d&MC;0id#I{%k6o<&Zlm4?i;`=_Kw7#3fKW(bHKCR7K53#>OpXOIvRiI;Wx8q3 z!QG%{wG>gHYiC2;eMGpHPEGsXQDj2mRl3%Xb`$emrN5p=i z2cQJAEjZObGyE-*w{OnlgbKZig&<|#5*gYGVm$k>hAm$WW2T77$j$MxKjjv4gjUwp)Z74JzBP0Vbni$|>KFFmpN0yOfAg9|n!h8lXqFZ2;BFM&oSS-wpP ztH?0G6gJI}UCCUDG)o*Eze$>suL0LCZ9K$VXsQ&UEsSy3|GHc7qP-k3uSmf z2~H<9^wWbVs7B_bn=~?#94YK=zv*t^s)EB{K94eHszcFOyvUG~v!6>clJBZ&f~B7b zD51{WHiC=A>N!S8By55-LHZ)p_dUmt#Xy}hF{v0!>0@cMBs~>>{QFTXj-wHN?6&1kl4lBNX3h3dO6Orp<#TC zH&dP?y4)sd9sUJEn1&sM0~36%xWy~!w*q8!w*s5?9LDRFv-36sv!|fcXp>R$xYL( zATY(jo~=w#7Vs9y<;W&E;!Y`@t)(lJHe?$yfeHOO8~64n(MI)~-%T?d33Cs+PXK%9 z4*{IeHB4k!w;_mw{=muhDpd3i-5Y1>YsRf?+q43S$WlTNi)LYom;PU;JwmV44^~GYEf^okkyJ6Q`oEaf$&R3bPyARUi&JSHO zm*Um6qO1B{52x8?zGh0(wMJ{XV1Akvbm%U#N@6444c7(T^UlQv8O=@6$S@D2R`bg00_N z(vq?j^^G;#rdW`Vh4&GbXzlTzpeNd|K6JmW&!_Mvrdhf{hGw;cC{8}ZT-avZStkx`3GAl{vU3lbv{dXtxLk56@d~j z&R9A#t;PJLcnv&yox5M(Gv8ONA|Ehoq6CURsQJ{UZvpPvDy>Gmn$=Wb!tULX>}I4d5X3e z-u28(0SFrow?~X#K+YB7%RVBLL{7AV!|~MMw2rH#MZ#P}K@9aDv31trp8Ep*uuZnB zDu*WuQeTG#GogvTq51@krB?FUwkqa{whM862B`emgdr7i*)x)mHU@g^#qw#Z$irBTeOo*ZBZSN;;AtZwL~j^h}{}%mih^ z#_Ep* z$OA0}rDZO3&vw1e>(l@U;L?j6EGX>5spw#6r}_Hu*2*^nnvpMKa@yOBcnu_jd@7li z*gSm(rs^48E)L3S_#fFLugCZ#d&N_@4xHS^P>~|tX?(D@P24N*I&Wqk_7d}ywha1d z0m1S+I{uokF$z*$di-~tU*^5y`hZE#0~1mh@ex5q%#9>Op(Mb;^KfO*Z<7f$*0U0f zWkKSkS-NO-Y2$btVLV~sb$?wM$EV{!ofQ~C?^vcW=i+61R1x>&xs&?9P_$ql|JiW( z$xAayIKNplaQ3{pf4K3xm*eL?I-TTHAc$eVhazi?uY0k(h&#EDj}$bFoou%+;m6uL zwwtNj!Gxc!w!yf%W=tp?2S;wd+U>ho@U_Dv{DjH+A9wl;Dco7+K?Nw;kktq5R z+xiCagM$Ls@_lp_D_xB?wgSGX+GA3@q!bNQe)5v=ok&t%hNWwSLDvbl2pJl^zU#w# zhG=0$>DQP~vt|^LpPtGw8n?|IBW*@LlX@fAdCUq&Fa5yf2=D*O=x@0SU|je#n8^5T zZkUL1MrkT3i_p6rLu5n5${C3B&eofDuYeozAWjDwfn{Y@Ycf*OrdJCQ!bYS&9kB<(otc<5$n7-D$ouds-GR4%51%ISXZ zxaiB#G)5}Td zNgnkk`sJOh5cIhC1MY=jMdNDf9oI#ccN-L+(rufOcN0E9spXt`#4T7k z=W2$em-Js;30gnhtQ0G7@7a*}<_pQvySt-E3l5&0kikY)WJ^HKiARdlI;N;1~@AI5yTFZ@I<}ke5Pq44zEBLQm}D z;O=P}=V@(dkK{(;y`&wvQh1xFeIYae8AR@#ZyG&pMJ%F`C#!e^H#443jm-Ft4PDM1 z7_sE)c~1`~ox@+m-qm5ds3z@%b7>cplLq_`);RpMOl`rIke>At8~Gk+HC_h=_=& zsHhkj8DGDC&CAOxBqSsyC8emSsHLT4WMpJ%X=!h7@8;&_>+2gD8X6N5lai8>ot<4= zTwGmU-Q3*V-Q7JrJUl%;y}Z1b{H z-cP7E`===~?D88Py*H4N^b!QbhNjK=Db{e{nkOUI@{y$@0LEhn_GygcC*ug)^yNeb z*J=&aHBu+$d3f)D>Qm$zC9=u%Vg0<0QVYC{9(gK1?v9zzoYfUxB^0da?TUy?jo@zj z@qLX9E)XyPAS-BG1!&OQ6NSu1fWu`=RjwV#YjJh45gVJ_)*R}bIM9Dwn-?X&o2yJL z0c4AZoZx%vsBo+UhV3)cy?U7a-2_JGSuntH7RkXkUlx#s*f0>NaeY`PNR-!1+${6o zAz+|ht&_-nk$K4ubW56zBRXW6H&3%pLWnsXw``twm3F}pl9bRJ9B}O%oKFmuk4^yU zk}|N$_Vi)b*RCuz%Jg_#PK8DX1fH`_IHJeV47}E3O6pUT7Ah%_~WhS;m2;uY7Y0SFH3U^xeR#^o@^s6l|oA?&28kLcqmNE1&l3}Hrj}R)l^!yt}G;cJ$DCRl_I=4chWSt zUmx(}^c^vZ9CC-b=<{NU^tv^x+k-8`57W~R{P>Wr_4VncAo9efcl`y+oX zA(PuC$NMm{En;s?-LcCbn4dDo7wTbl(88O~R-&?ZX6xhus$WMJqm6Oo!dhM4_#@s? z7Uz-+rw7~;IFm8>JbVsv$DXY%-)cJe-c|tPk1{McT5emY@&4&H{F4;aE~idJZHg+s zz)QCie+%0uPFXALpq=nk>6A&8a8NcBnp{3-?j%Yx9<%O=q2*M;11bTKL(wv^rO#8s z#o0gv*Pw1~MsmAp1bh07@OHcR11_mpces^{^_nqI@4#46w^!Nrd(j8gt+pdo8`OaB zh11XfJ&ftI6NQ~Rfg1e@3CO0}%W{+EOMndiWUF%~XTR(ghgV#BF2l?nT(nQ__@tG$ zn($T#ccAX)H>W1G8#i3@3aLMd0LLD7A2@|=YOC$yo#dMI>z*?F(fSB3 z4iU9CDv`0MZ2@drqMdJJnCgPRh2*&rh`?qQS#2h*U>$AH(UMzZ=gM`ZLHjcj_HKPf zCS0V508Cde9;)4^TWy9bMJ-OH=d-{&oT`TS`@V18jh7Pd7Z2nPPJbb1ls)jIsa%Vj zwy4aD*XH8WN&(L{SSoh7@zPorZ$ZB<#$=mvZ+=+Nd%r=hDf0{W_3)<`@i3@IiKz{46BTrEF+O*y5Pv zE-RWR$PAHw_jr+M_7b%jHH+qQ-v=xsqNhutu^RI=qk`c(U;sU9O4(Q zS=Nz3Ni^l=xaB}y0lj5Y|5rZkYcYdmgHdp(on(kciS-o#qM$Bs7)OK!Z<1T3g-VZ` zER{wc)X8%$_>1L3YCD5ZHJw&ZPib*}Zb>JrB~TfBYUeMDR{Qu>g^4e;^BE#OoS!ow z@ic&*pm8)qH+E;i^MZpX?THojZtCB0E@i(fN#p_TKJy}uUxlnpdv2Jx$)2Pqm@{jZ z;t>j0w|;sc4z$&$r5Y(PHxVd7fV_A8ngAu4d?CshSloPlu6@}w7+lFqd!;f6=vu8( zDa?ylS)GGE-m~KTo{Hs|z_sfmGH!Y|51shK7S?;GvWo1qn z#4s3x+d8|M4i&P5<#~i6S;%b1y^}!JG=E@ZDGzM+xV>8>&MzrZc2lmg zaBl0qgXqI4wa~RfEbl*CO+GN~ISf&a129;A>6BMFiIWUW0NC>fM2Zx5`FvB2?NcBT zM7!0|!qvf5n+UPTmksh*m9k(@orMWNwYzX26X}-IuK{DVmUD;GQ>K8pb%k8RE9}Q3 zsDq<#rmgghWpo4vM$>6jrZ9&2b})3evB%r^2?_292f<_Cb)@jgeWqCi!Qh$`IkgqAP3@bg1dPq=m|dE|%;G%U6m zt@YGx$4uJkiU!Du6!#7HrNar_;-9meXa+EI3Z|nqpD;7KDwt_9T@&@$%-mxoF63_Z zvWhY9Bzx2;8FAXY`1IoN^;G#~x2)*hhui{p19=_$O0% zF(vkWE2P=D?S-;cH{|rebkoYP$ucbrgTU(X%Yeg*P5;AO19jST+d}P{=Uqh=ex<_P z1Jd)1Iqdt_^O;>KRc0!KZ(8cA)U9|AC&?dPCsLvG&D@AH@qnFTn+bJ}2Wq(MecbM2 zkseCZ!d%Y9EniJ>K6VV-UTuBUzlY+5*?&4P=GTAX9Jxg4H7S>q;%7{=1ueYg0~rgE zdRlAmS&Z#ojSrr4L7SI*-c^i%><{cDtx3}_3D~Q2w;NmLTRA#!DU-9yj4VTf6uEKr zHk*jQMO(2po0yhuy*|npYpijVnQbYF09MUGh(OTw^mjaE*PMF1^2F|FqRgcJg~C4+)tSe4Lk6Wkl(7kX7wL)j-C# zO;IJ93g1x;kI+#(c zR?5o5+vRAS4CV3;;b9p9ld*K7ymI6oM zlj=h`RdTjIRbR`S1lPo%!u+^p9xsZZrk4IP%wzWfYD|NGcp3b(b#^R~9pi>Jq3R zTdLvRaXmM6H)-_K8D%#;!3@~9Q(il^*qceOw2La4>YGTwINA%vs~Y_9kXz2iJlfw3 z6_L>rZ<^)qKgHXvU3{=WQpoG1IC`n|O;y47dA_vatz)`mU))G071ttJSn&_qR^S_6ehtYn}2+_MZN2L}Yd3N0Iq6Kr$9w z?)Iu!7c{x8wfIqk_KR(VH*;{{7oE_)$( zofqPTH!rp{XZMCNLEulvQhTr~;$kZF+yPn%0Lsj7DrnJW+G;MRZVd>;7hB}r;bnfL zH>r(GHxdrq(c3e4EO`lJLnrP*PDf9D!N7#DEK)b5uKgu2aX)lh0C!k8NX@J8B88DG z#NqoDH|wQkDa))U3|3I>6=|*3YPM8>8_w~#s5#W8WOQZnO(qX)ah_^`x%-_Z4_}*}R zO;X>VRPtKy1f7(eHR%soOJ*e=#;13H(JGe*U?E>;iN-B((b(58J1u0xSL@s*(1g)N z(i9XxKZlRIE+NFMMjn& ztHHnccmvhHm3kQX?XqI{C}P?f?bXNn%4Drsi!)>pM9c$x(%zBDhQ-%Gb^Lbp3g~~W z5q>f-`yHr||GND*^M~huD)9UB{ok4YQ^B7&zpYmOZvy_0n(+Tblb`Fd|7PU>H0J+5 zO`d!6zuD@4);o9Yf=|#sHJ>2NJcW;*_q8A-L}cHW3F-U%{ShCW=Tb5v%7RQ1vZ8>0 zpY?f;h?T|&T6D9Zi(GO0Pu8hRO5ut0jAcz=<-p6AexiyJ6(WS)zU-xu)s`d=*f(MS z_8>rJQcu=WEE`pYvQVMc2E@_FtLbesSPLw_^*vY~9b>G4V;gKNfIDK*TMmfxV=J(# ze!oc}wQC&okq6uh9esbNCo4H`WAHUqXM(wd1*8NpjY5R2r6fA0gc0^L?*C-ZhHu0h zh1``hV^g$@)W=f*NuO#9C;g!;A|wI@%gJL!2*O%LW*Y%TiIrzrCZGrLO*-4x;aQgm z#Mm)02t&j0xbcsDm(()n_cWw6QOKvAyt=QB;{6=HeI7$SMQEauVu`}ie30NFvThlH@&L_;jCx*8j~*ULyf^lsa-@4Wm-`g1oQMjFb1|=-aa1 z&j3|ANk|dvbAaj<%+C!{NGNQGzfZaN*+0MN_XLdpl=@Wy0^(;<#V=BRX8)Hd7JpXZ zmoNmx&vb%c^y!)XPq06xT>L%G&)wc%H2BQ^70$m>2L2x9&)Piw1l#ahg`m==y zp#Hmsex-Q)3+4Bdfqy(R_;UmQ7s)-N{7U)w{T2RQ?vH2Xe#T~h5#jUgzsH>C^M(H@ z`Nuf^&$D#DNE78xC+q%G@Q;DwpJD!Abp8B?d`@-xE$IKBQhy9@{8`Zn+&_nuf0z8b z@W-$E List[List[str]]: + """ + Load a CSV file into a list of rows, where each row is a list of strings. + + Args: + path: Path to the CSV file. + delimiter: Field delimiter character. + quotechar: Quote character. + encoding: File encoding. + skip_header: If True, skip the first row (header). + + Returns: + List of rows (list of lists of strings). + """ + rows: List[List[str]] = [] + with open(path, "r", encoding=encoding, newline="") as f: + reader = csv.reader(f, delimiter=delimiter, quotechar=quotechar) + if skip_header: + next(reader, None) + for row in reader: + rows.append(row) + return rows + +def print_help(): + print("Usage: python generate.py [csv_file] [header_file_path] [header_namespace] [i18n_directory]\n\n") + print("\t[csv_file] the CSV file containing the translations, exported from the .ods file") + print("\t[header_file_path] the path to the header file to be generated") + print("\t[header_namespace] the C++ namespace to use for the generated header file") + print("\t[i18n_directory] the directory where the .i18n files will be generated") + +def open_i18n_files(row, i18n_path): + result = [] + for i in range(1, len(row)): + filepath = f"{i18n_path}/{row[i]}.i18n" + print(f"Opening {filepath}") + file = open(filepath, "w") + result.append(file) + return result + +def close_i18n_files(files): + for file in files: + file.close() + +def generate_header(filepath, namespace, rows): + file = open(filepath, "w") + file.write("#pragma once\n\n") + file.write("#include \"Tactility/i18n/TextResources.h\"\n\n") + file.write("// WARNING: This file is auto-generated. Do not edit manually.\n\n") + file.write(f"namespace {namespace}") + file.write(" {\n\n") + file.write("enum class Text {\n") + for i in range(1, len(rows)): + key = rows[i][0].upper() + file.write(f" {key} = {i - 1},\n") + file.write("};\n") + file.write("\n}\n") + file.close() + +def translate(rows, language_index, file): + for i in range(1, len(rows)): + value = rows[i][language_index] + if value == "": + value = f"{rows[i][0]}_untranslated" + file.write(value) + file.write("\n") + +if __name__ == "__main__": + if "--help" in sys.argv: + print_help() + sys.exit() + if len(sys.argv) != 5: + print_help() + sys.exit() + project_root_path = get_project_root() + csv_file = f"{project_root_path}/Translations/{sys.argv[1]}" + header_path = f"{project_root_path}/{sys.argv[2]}" + header_namespace = sys.argv[3] + i18n_path = f"{project_root_path}/{sys.argv[4]}" + rows = load_csv(csv_file) + if len(rows) == 0: + print("Error: CSV file is empty.") + sys.exit(1) + generate_header(header_path, header_namespace, rows) + i18n_files = open_i18n_files(rows[0], i18n_path) + for i in range(0, len(i18n_files)): + i18n_file = i18n_files[i] + translate(rows, i + 1, i18n_file) + close_i18n_files(i18n_files) diff --git a/partitions-test.csv b/partitions-test.csv new file mode 100644 index 00000000..b2934bcb --- /dev/null +++ b/partitions-test.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 3M, +system, data, fat, , 450k, +data, data, fat, , 450k, diff --git a/partitions.csv b/partitions.csv index 3bc4b6f9..00c39a9b 100644 --- a/partitions.csv +++ b/partitions.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 3M, -system, data, fat, , 256k, -data, data, fat, , 256k, +system, data, fat, , 300k, +data, data, fat, , 600k, diff --git a/sdkconfig.board.cyd-2432s024c b/sdkconfig.board.cyd-2432s024c index be18fd74..9323e8c0 100644 --- a/sdkconfig.board.cyd-2432s024c +++ b/sdkconfig.board.cyd-2432s024c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_CYD_2432S024C=y diff --git a/sdkconfig.board.cyd-2432s032c b/sdkconfig.board.cyd-2432s032c index fbb5e695..2a32068c 100644 --- a/sdkconfig.board.cyd-2432s032c +++ b/sdkconfig.board.cyd-2432s032c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_CYD_2432S032C=y diff --git a/sdkconfig.board.cyd-4848s040c b/sdkconfig.board.cyd-4848s040c index 4cab54dd..85d0a54a 100644 --- a/sdkconfig.board.cyd-4848s040c +++ b/sdkconfig.board.cyd-4848s040c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_COMPILER_OPTIMIZATION_PERF=y diff --git a/sdkconfig.board.cyd-8048s043c b/sdkconfig.board.cyd-8048s043c index 70ad4211..be9659c4 100644 --- a/sdkconfig.board.cyd-8048s043c +++ b/sdkconfig.board.cyd-8048s043c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_COMPILER_OPTIMIZATION_PERF=y diff --git a/sdkconfig.board.cyd-jc2432w328c b/sdkconfig.board.cyd-jc2432w328c index 72668cbc..45ba3c90 100644 --- a/sdkconfig.board.cyd-jc2432w328c +++ b/sdkconfig.board.cyd-jc2432w328c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_CYD_JC2432W328C=y diff --git a/sdkconfig.board.cyd-jc8048w550c b/sdkconfig.board.cyd-jc8048w550c index d9dc67c9..fcb52cd5 100644 --- a/sdkconfig.board.cyd-jc8048w550c +++ b/sdkconfig.board.cyd-jc8048w550c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_COMPILER_OPTIMIZATION_PERF=y diff --git a/sdkconfig.board.elecrow-crowpanel-advance-28 b/sdkconfig.board.elecrow-crowpanel-advance-28 index 3f8de84c..a8a2eea1 100644 --- a/sdkconfig.board.elecrow-crowpanel-advance-28 +++ b/sdkconfig.board.elecrow-crowpanel-advance-28 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_28=y diff --git a/sdkconfig.board.elecrow-crowpanel-advance-35 b/sdkconfig.board.elecrow-crowpanel-advance-35 index beba2c37..ee6d6403 100644 --- a/sdkconfig.board.elecrow-crowpanel-advance-35 +++ b/sdkconfig.board.elecrow-crowpanel-advance-35 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_35=y diff --git a/sdkconfig.board.elecrow-crowpanel-advance-50 b/sdkconfig.board.elecrow-crowpanel-advance-50 index 34328ab4..73c1d1a8 100644 --- a/sdkconfig.board.elecrow-crowpanel-advance-50 +++ b/sdkconfig.board.elecrow-crowpanel-advance-50 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_50=y diff --git a/sdkconfig.board.elecrow-crowpanel-basic-28 b/sdkconfig.board.elecrow-crowpanel-basic-28 index 69983a3d..b9e5395e 100644 --- a/sdkconfig.board.elecrow-crowpanel-basic-28 +++ b/sdkconfig.board.elecrow-crowpanel-basic-28 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_28=y diff --git a/sdkconfig.board.elecrow-crowpanel-basic-35 b/sdkconfig.board.elecrow-crowpanel-basic-35 index e28b5048..3907565d 100644 --- a/sdkconfig.board.elecrow-crowpanel-basic-35 +++ b/sdkconfig.board.elecrow-crowpanel-basic-35 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_35=y diff --git a/sdkconfig.board.elecrow-crowpanel-basic-50 b/sdkconfig.board.elecrow-crowpanel-basic-50 index 7c928797..8ea89fcd 100644 --- a/sdkconfig.board.elecrow-crowpanel-basic-50 +++ b/sdkconfig.board.elecrow-crowpanel-basic-50 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_50=y diff --git a/sdkconfig.board.lilygo-tdeck b/sdkconfig.board.lilygo-tdeck index 6de01c9c..aa73175e 100644 --- a/sdkconfig.board.lilygo-tdeck +++ b/sdkconfig.board.lilygo-tdeck @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_LILYGO_TDECK=y diff --git a/sdkconfig.board.lilygo-tlora-pager b/sdkconfig.board.lilygo-tlora-pager index 16fb1e30..f68156ed 100644 --- a/sdkconfig.board.lilygo-tlora-pager +++ b/sdkconfig.board.lilygo-tlora-pager @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_LILYGO_TLORA_PAGER=y diff --git a/sdkconfig.board.m5stack-core2 b/sdkconfig.board.m5stack-core2 index c4e62cd3..3ad7fb7f 100644 --- a/sdkconfig.board.m5stack-core2 +++ b/sdkconfig.board.m5stack-core2 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_M5STACK_CORE2=y diff --git a/sdkconfig.board.m5stack-cores3 b/sdkconfig.board.m5stack-cores3 index 9f981363..27b2e87e 100644 --- a/sdkconfig.board.m5stack-cores3 +++ b/sdkconfig.board.m5stack-cores3 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_M5STACK_CORES3=y diff --git a/sdkconfig.board.unphone b/sdkconfig.board.unphone index c2202fb9..82359473 100644 --- a/sdkconfig.board.unphone +++ b/sdkconfig.board.unphone @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_UNPHONE=y diff --git a/sdkconfig.board.waveshare-s3-touch-43 b/sdkconfig.board.waveshare-s3-touch-43 index 6e5babe4..c89e2b2c 100644 --- a/sdkconfig.board.waveshare-s3-touch-43 +++ b/sdkconfig.board.waveshare-s3-touch-43 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_WAVESHARE_S3_TOUCH_43=y diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 199fed35..97d27e96 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware defaults CONFIG_TT_BOARD_CUSTOM=y