From 7ba5978848258759e88f84e8a4ec27bbac43db75 Mon Sep 17 00:00:00 2001 From: Uri Tauber <142022451+Uri-Tauber@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:28:42 +0200 Subject: [PATCH] feat: User-Interface I18n System (#728) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary **What is the goal of this PR?** This PR introduces Internationalization (i18n) support, enabling users to switch the UI language dynamically. **What changes are included?** - Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage language state and string retrieval. - Data Structures: - `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported language. - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access. - `lib/I18n/translations.csv`: single source of truth. - Documentation: Added `docs/i18n.md` detailing the workflow for developers and translators. - New Settings activity: `src/activities/settings/LanguageSelectActivity.h/cpp` ## Additional Context This implementation (building on concepts from #505) prioritizes performance and memory efficiency. The core approach is to store all localized strings for each language in dedicated arrays and access them via enums. This provides O(1) access with zero runtime overhead, and avoids the heap allocations, hashing, and collision handling required by `std::map` or `std::unordered_map`. The main trade-off is that enums and string arrays must remain perfectly synchronized—any mismatch would result in incorrect strings being displayed in the UI. To eliminate this risk, I added a Python script that automatically generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which will serve as the single source of truth for all translations. The full design and workflow are documented in `docs/i18n.md`. ### Next Steps - [x] Python script `generate_i18n.py` to auto-generate C++ files from CSV - [x] Populate translations.csv with initial translations. Currently available translations: English, Español, Français, Deutsch, Čeština, Português (Brasil), Русский, Svenska. Thanks, community! **Status:** EDIT: ready to be merged. As a proof of concept, the SPANISH strings currently mirror the English ones, but are fully uppercased. --- ### AI Usage Did you use AI tools to help write this code? _**< PARTIALLY >**_ I used AI for the black work of replacing strings with I18n references across the project, and for generating the documentation. EDIT: also some help with merging changes from master. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: yeyeto2788 --- .gitignore | 1 + docs/i18n.md | 229 +++++++ lib/I18n/I18n.cpp | 96 +++ lib/I18n/I18n.h | 42 ++ lib/I18n/I18nKeys.h | 381 +++++++++++ lib/I18n/I18nStrings.h | 19 + lib/I18n/translations/czech.yaml | 317 +++++++++ lib/I18n/translations/english.yaml | 317 +++++++++ lib/I18n/translations/french.yaml | 317 +++++++++ lib/I18n/translations/german.yaml | 317 +++++++++ lib/I18n/translations/portuguese.yaml | 317 +++++++++ lib/I18n/translations/russia.yaml | 317 +++++++++ lib/I18n/translations/spanish.yaml | 317 +++++++++ lib/I18n/translations/swedish.yaml | 317 +++++++++ platformio.ini | 1 + scripts/gen_i18n.py | 620 ++++++++++++++++++ src/SettingsList.h | 130 ++-- src/activities/boot_sleep/BootActivity.cpp | 5 +- src/activities/boot_sleep/SleepActivity.cpp | 7 +- .../browser/OpdsBookBrowserActivity.cpp | 45 +- src/activities/home/HomeActivity.cpp | 12 +- src/activities/home/MyLibraryActivity.cpp | 8 +- src/activities/home/RecentBooksActivity.cpp | 7 +- src/activities/home/RecentBooksActivity.h | 1 + .../network/CalibreConnectActivity.cpp | 32 +- .../network/CrossPointWebServerActivity.cpp | 32 +- .../network/NetworkModeSelectionActivity.cpp | 23 +- .../network/WifiSelectionActivity.cpp | 86 +-- src/activities/reader/EpubReaderActivity.cpp | 13 +- .../EpubReaderChapterSelectionActivity.cpp | 7 +- .../reader/EpubReaderMenuActivity.cpp | 12 +- .../reader/EpubReaderMenuActivity.h | 16 +- .../EpubReaderPercentSelectionActivity.cpp | 7 +- .../reader/KOReaderSyncActivity.cpp | 66 +- src/activities/reader/TxtReaderActivity.cpp | 5 +- src/activities/reader/XtcReaderActivity.cpp | 7 +- .../XtcReaderChapterSelectionActivity.cpp | 13 +- .../settings/ButtonRemapActivity.cpp | 29 +- .../settings/CalibreSettingsActivity.cpp | 32 +- .../settings/ClearCacheActivity.cpp | 30 +- .../settings/KOReaderAuthActivity.cpp | 21 +- .../settings/KOReaderSettingsActivity.cpp | 35 +- .../settings/LanguageSelectActivity.cpp | 94 +++ .../settings/LanguageSelectActivity.h | 33 + src/activities/settings/OtaUpdateActivity.cpp | 23 +- src/activities/settings/SettingsActivity.cpp | 57 +- src/activities/settings/SettingsActivity.h | 52 +- src/activities/util/KeyboardEntryActivity.cpp | 11 +- src/components/themes/BaseTheme.cpp | 8 +- src/components/themes/lyra/LyraTheme.cpp | 3 +- src/main.cpp | 2 + src/network/CrossPointWebServer.cpp | 6 +- 52 files changed, 4516 insertions(+), 379 deletions(-) create mode 100644 docs/i18n.md create mode 100644 lib/I18n/I18n.cpp create mode 100644 lib/I18n/I18n.h create mode 100644 lib/I18n/I18nKeys.h create mode 100644 lib/I18n/I18nStrings.h create mode 100644 lib/I18n/translations/czech.yaml create mode 100644 lib/I18n/translations/english.yaml create mode 100644 lib/I18n/translations/french.yaml create mode 100644 lib/I18n/translations/german.yaml create mode 100644 lib/I18n/translations/portuguese.yaml create mode 100644 lib/I18n/translations/russia.yaml create mode 100644 lib/I18n/translations/spanish.yaml create mode 100644 lib/I18n/translations/swedish.yaml create mode 100755 scripts/gen_i18n.py create mode 100644 src/activities/settings/LanguageSelectActivity.cpp create mode 100644 src/activities/settings/LanguageSelectActivity.h diff --git a/.gitignore b/.gitignore index ec281eb9..64f74b14 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .DS_Store .vscode lib/EpdFont/fontsrc +lib/I18n/I18nStrings.cpp *.generated.h .vs build diff --git a/docs/i18n.md b/docs/i18n.md new file mode 100644 index 00000000..645a0568 --- /dev/null +++ b/docs/i18n.md @@ -0,0 +1,229 @@ +# Internationalization (I18N) + +This guide explains the multi-language support system in CrossPoint Reader. + +## Supported Languages (Updating) + + +--- + + +## For Developers + +### Translation System Architecture + +The I18N system uses **per-language YAML files** to maintain translations and a Python script to generate C++ code: + +``` +lib/I18n/ +├── translations/ # One YAML file per language +│ ├── english.yaml +│ ├── spanish.yaml +│ ├── french.yaml +│ └── ... +├── I18n.h +├── I18n.cpp +├── I18nKeys.h # Enums (auto-generated) +├── I18nStrings.h # String array declarations (auto-generated) +└── I18nStrings.cpp # String array definitions (auto-generated) + +scripts/ +└── gen_i18n.py # Code generator script +``` + +**Key principle:** All translations are managed in the YAML files under `lib/I18n/translations/`. The Python script generates the necessary C++ code automatically. + +--- + +### YAML File Format + +Each language has its own file in `lib/I18n/translations/` (e.g. `spanish.yaml`). + +A file looks like this: + +```yaml +_language_name: "Español" +_language_code: "SPANISH" +_order: "1" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "BOOTING" +STR_BROWSE_FILES: "Buscar archivos" +``` + +**Metadata keys** (prefixed with `_`): +- `_language_name` — Native display name shown to the user (e.g. "Français") +- `_language_code` — C++ enum name (e.g. "FRENCH"). Must be a valid C++ identifier. +- `_order` — Controls the position in the Language enum (English is always 0) + +**Rules:** +- Use UTF-8 encoding +- Every line must follow the format: `KEY: "value"` +- Keys must be valid C++ identifiers (uppercase, strats with STR_) +- Keys must be unique within a file +- String values must be quoted +- Use `\n` for newlines, `\\` for literal backslashes, `\"` for literal quotes inside values + +--- + +### Adding New Strings + +To add a new translatable string: + +#### 1. Edit the English YAML file + +Add the key to `lib/I18n/translations/english.yaml`: + +```yaml +STR_MY_NEW_STRING: "My New String" +``` + +Then add translations in each language file. If a key is missing from a +language file, the generator will automatically use the English text as a +fallback (and print a warning). + +#### 2. Run the generator script + +```bash +python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/ +``` + +This automatically: +- Fills missing translations from English +- Updates the `StrId` enum in `I18nKeys.h` +- Regenerates all language arrays in `I18nStrings.cpp` + +#### 3. Use in code + +```cpp +#include + +// Using the tr() macro (recommended) +renderer.drawText(font, x, y, tr(STR_MY_NEW_STRING)); + +// Using I18N.get() directly +const char* text = I18N.get(StrId::STR_MY_NEW_STRING); +``` + +**That's it!** No manual array synchronization needed. + +--- + +### Adding a New Language + +To add support for a new language (e.g., Italian): + +#### 1. Create a new YAML file + +Create `lib/I18n/translations/italian.yaml`: + +```yaml +_language_name: "Italiano" +_language_code: "ITALIAN" +_order: "7" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "AVVIO" +``` + +You only need to include the strings you have translations for. Missing +keys will fall back to English automatically. + +#### 2. Run the generator + +```bash +python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/ +``` + +This automatically updates all necessary code. + +--- + +### Modifying Existing Translations + +Simply edit the relevant YAML file and regenerate: + +```bash +python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/ +``` + +--- + +### UTF-8 Encoding + +The YAML files use UTF-8 encoding. Special characters are automatically converted to C++ UTF-8 hex sequences by the generator. + +--- + +### I18N API Reference + +```cpp +// === Convenience Macros (Recommended) === + +// tr(id) - Get translated string without StrId:: prefix +const char* text = tr(STR_SETTINGS_TITLE); +renderer.drawText(font, x, y, tr(STR_BROWSE_FILES)); +Serial.printf("Status: %s\n", tr(STR_CONNECTED)); + +// I18N - Shorthand for I18n::getInstance() +I18N.setLanguage(Language::SPANISH); +Language lang = I18N.getLanguage(); + +// === Full API === + +// Get the singleton instance +I18n& instance = I18n::getInstance(); + +// Get translated string (three equivalent ways) +const char* text = tr(STR_SETTINGS_TITLE); // Macro (recommended) +const char* text = I18N.get(StrId::STR_SETTINGS_TITLE); // Direct call +const char* text = I18N[StrId::STR_SETTINGS_TITLE]; // Operator overload + +// Set language +I18N.setLanguage(Language::SPANISH); + +// Get current language +Language lang = I18N.getLanguage(); + +// Save language setting to file +I18N.saveSettings(); + +// Load language setting from file +I18N.loadSettings(); + +// Get character set for font subsetting (static method) +const char* chars = I18n::getCharacterSet(Language::FRENCH); +``` + +--- + +## File Storage + +Language settings are stored in: +``` +/.crosspoint/language.bin +``` + +This file contains: +- Version byte +- Current language selection (1 byte) + +--- + +## Translation Workflow + +### For Developers (Adding Features) + +1. Add new strings to `lib/I18n/translations/english.yaml` +2. Run `python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/` +3. Use the new `StrId` in your code +4. Request translations from translators + +### For Translators + +1. Open the YAML file for your language in `lib/I18n/translations/` +2. Add or update translations using the format `STR_KEY: "translated text"` +3. Keep translations concise (E-ink space constraints) +4. Make sure the file is in UTF-8 encoding +5. Run `python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/` to verify +6. Test on device or submit for review diff --git a/lib/I18n/I18n.cpp b/lib/I18n/I18n.cpp new file mode 100644 index 00000000..ae9fc53c --- /dev/null +++ b/lib/I18n/I18n.cpp @@ -0,0 +1,96 @@ +#include "I18n.h" + +#include +#include +#include + +#include "I18nStrings.h" + +using namespace i18n_strings; + +// Settings file path +static constexpr const char* SETTINGS_FILE = "/.crosspoint/language.bin"; +static constexpr uint8_t SETTINGS_VERSION = 1; + +I18n& I18n::getInstance() { + static I18n instance; + return instance; +} + +const char* I18n::get(StrId id) const { + const auto index = static_cast(id); + if (index >= static_cast(StrId::_COUNT)) { + return "???"; + } + + // Use generated helper function - no hardcoded switch needed! + const char* const* strings = getStringArray(_language); + return strings[index]; +} + +void I18n::setLanguage(Language lang) { + if (lang >= Language::_COUNT) { + return; + } + _language = lang; + saveSettings(); +} + +const char* I18n::getLanguageName(Language lang) const { + const auto index = static_cast(lang); + if (index >= static_cast(Language::_COUNT)) { + return "???"; + } + return LANGUAGE_NAMES[index]; +} + +void I18n::saveSettings() { + Storage.mkdir("/.crosspoint"); + + FsFile file; + if (!Storage.openFileForWrite("I18N", SETTINGS_FILE, file)) { + Serial.printf("[I18N] Failed to save settings\n"); + return; + } + + serialization::writePod(file, SETTINGS_VERSION); + serialization::writePod(file, static_cast(_language)); + + file.close(); + Serial.printf("[I18N] Settings saved: language=%d\n", static_cast(_language)); +} + +void I18n::loadSettings() { + FsFile file; + if (!Storage.openFileForRead("I18N", SETTINGS_FILE, file)) { + Serial.printf("[I18N] No settings file, using default (English)\n"); + return; + } + + uint8_t version; + serialization::readPod(file, version); + if (version != SETTINGS_VERSION) { + Serial.printf("[I18N] Settings version mismatch\n"); + file.close(); + return; + } + + uint8_t lang; + serialization::readPod(file, lang); + if (lang < static_cast(Language::_COUNT)) { + _language = static_cast(lang); + Serial.printf("[I18N] Loaded language: %d\n", static_cast(_language)); + } + + file.close(); +} + +// Generate character set for a specific language +const char* I18n::getCharacterSet(Language lang) { + const auto langIndex = static_cast(lang); + if (langIndex >= static_cast(Language::_COUNT)) { + lang = Language::ENGLISH; // Fallback to first language + } + + return CHARACTER_SETS[static_cast(lang)]; +} \ No newline at end of file diff --git a/lib/I18n/I18n.h b/lib/I18n/I18n.h new file mode 100644 index 00000000..e546635e --- /dev/null +++ b/lib/I18n/I18n.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "I18nKeys.h" +/** + * Internationalization (i18n) system for CrossPoint Reader + */ + +class I18n { + public: + static I18n& getInstance(); + + // Disable copy + I18n(const I18n&) = delete; + I18n& operator=(const I18n&) = delete; + + // Get localized string by ID + const char* get(StrId id) const; + + const char* operator[](StrId id) const { return get(id); } + + Language getLanguage() const { return _language; } + void setLanguage(Language lang); + const char* getLanguageName(Language lang) const; + + void saveSettings(); + void loadSettings(); + + // Get all unique characters used in a specific language + // Returns a sorted string of unique characters + static const char* getCharacterSet(Language lang); + + private: + I18n() : _language(Language::ENGLISH) {} + + Language _language; +}; + +// Convenience macros +#define tr(id) I18n::getInstance().get(StrId::id) +#define I18N I18n::getInstance() diff --git a/lib/I18n/I18nKeys.h b/lib/I18n/I18nKeys.h new file mode 100644 index 00000000..536e219a --- /dev/null +++ b/lib/I18n/I18nKeys.h @@ -0,0 +1,381 @@ +#pragma once +#include + +// THIS FILE IS AUTO-GENERATED BY gen_i18n.py. DO NOT EDIT. + +// Forward declaration for string arrays +namespace i18n_strings { +extern const char* const STRINGS_EN[]; +extern const char* const STRINGS_ES[]; +extern const char* const STRINGS_FR[]; +extern const char* const STRINGS_DE[]; +extern const char* const STRINGS_CZ[]; +extern const char* const STRINGS_PO[]; +extern const char* const STRINGS_RU[]; +extern const char* const STRINGS_SV[]; +} // namespace i18n_strings + +// Language enum +enum class Language : uint8_t { + ENGLISH = 0, + SPANISH = 1, + FRENCH = 2, + GERMAN = 3, + CZECH = 4, + PORTUGUESE = 5, + RUSSIAN = 6, + SWEDISH = 7, + _COUNT +}; + +// Language display names (defined in I18nStrings.cpp) +extern const char* const LANGUAGE_NAMES[]; + +// Character sets for each language (defined in I18nStrings.cpp) +extern const char* const CHARACTER_SETS[]; + +// String IDs +enum class StrId : uint16_t { + STR_CROSSPOINT, + STR_BOOTING, + STR_SLEEPING, + STR_ENTERING_SLEEP, + STR_BROWSE_FILES, + STR_FILE_TRANSFER, + STR_SETTINGS_TITLE, + STR_CALIBRE_LIBRARY, + STR_CONTINUE_READING, + STR_NO_OPEN_BOOK, + STR_START_READING, + STR_BOOKS, + STR_NO_BOOKS_FOUND, + STR_SELECT_CHAPTER, + STR_NO_CHAPTERS, + STR_END_OF_BOOK, + STR_EMPTY_CHAPTER, + STR_INDEXING, + STR_MEMORY_ERROR, + STR_PAGE_LOAD_ERROR, + STR_EMPTY_FILE, + STR_OUT_OF_BOUNDS, + STR_LOADING, + STR_LOAD_XTC_FAILED, + STR_LOAD_TXT_FAILED, + STR_LOAD_EPUB_FAILED, + STR_SD_CARD_ERROR, + STR_WIFI_NETWORKS, + STR_NO_NETWORKS, + STR_NETWORKS_FOUND, + STR_SCANNING, + STR_CONNECTING, + STR_CONNECTED, + STR_CONNECTION_FAILED, + STR_CONNECTION_TIMEOUT, + STR_FORGET_NETWORK, + STR_SAVE_PASSWORD, + STR_REMOVE_PASSWORD, + STR_PRESS_OK_SCAN, + STR_PRESS_ANY_CONTINUE, + STR_SELECT_HINT, + STR_HOW_CONNECT, + STR_JOIN_NETWORK, + STR_CREATE_HOTSPOT, + STR_JOIN_DESC, + STR_HOTSPOT_DESC, + STR_STARTING_HOTSPOT, + STR_HOTSPOT_MODE, + STR_CONNECT_WIFI_HINT, + STR_OPEN_URL_HINT, + STR_OR_HTTP_PREFIX, + STR_SCAN_QR_HINT, + STR_CALIBRE_WIRELESS, + STR_CALIBRE_WEB_URL, + STR_CONNECT_WIRELESS, + STR_NETWORK_LEGEND, + STR_MAC_ADDRESS, + STR_CHECKING_WIFI, + STR_ENTER_WIFI_PASSWORD, + STR_ENTER_TEXT, + STR_TO_PREFIX, + STR_CALIBRE_DISCOVERING, + STR_CALIBRE_CONNECTING_TO, + STR_CALIBRE_CONNECTED_TO, + STR_CALIBRE_WAITING_COMMANDS, + STR_CONNECTION_FAILED_RETRYING, + STR_CALIBRE_DISCONNECTED, + STR_CALIBRE_WAITING_TRANSFER, + STR_CALIBRE_TRANSFER_HINT, + STR_CALIBRE_RECEIVING, + STR_CALIBRE_RECEIVED, + STR_CALIBRE_WAITING_MORE, + STR_CALIBRE_FAILED_CREATE_FILE, + STR_CALIBRE_PASSWORD_REQUIRED, + STR_CALIBRE_TRANSFER_INTERRUPTED, + STR_CALIBRE_INSTRUCTION_1, + STR_CALIBRE_INSTRUCTION_2, + STR_CALIBRE_INSTRUCTION_3, + STR_CALIBRE_INSTRUCTION_4, + STR_CAT_DISPLAY, + STR_CAT_READER, + STR_CAT_CONTROLS, + STR_CAT_SYSTEM, + STR_SLEEP_SCREEN, + STR_SLEEP_COVER_MODE, + STR_STATUS_BAR, + STR_HIDE_BATTERY, + STR_EXTRA_SPACING, + STR_TEXT_AA, + STR_SHORT_PWR_BTN, + STR_ORIENTATION, + STR_FRONT_BTN_LAYOUT, + STR_SIDE_BTN_LAYOUT, + STR_LONG_PRESS_SKIP, + STR_FONT_FAMILY, + STR_EXT_READER_FONT, + STR_EXT_CHINESE_FONT, + STR_EXT_UI_FONT, + STR_FONT_SIZE, + STR_LINE_SPACING, + STR_ASCII_LETTER_SPACING, + STR_ASCII_DIGIT_SPACING, + STR_CJK_SPACING, + STR_COLOR_MODE, + STR_SCREEN_MARGIN, + STR_PARA_ALIGNMENT, + STR_HYPHENATION, + STR_TIME_TO_SLEEP, + STR_REFRESH_FREQ, + STR_CALIBRE_SETTINGS, + STR_KOREADER_SYNC, + STR_CHECK_UPDATES, + STR_LANGUAGE, + STR_SELECT_WALLPAPER, + STR_CLEAR_READING_CACHE, + STR_CALIBRE, + STR_USERNAME, + STR_PASSWORD, + STR_SYNC_SERVER_URL, + STR_DOCUMENT_MATCHING, + STR_AUTHENTICATE, + STR_KOREADER_USERNAME, + STR_KOREADER_PASSWORD, + STR_FILENAME, + STR_BINARY, + STR_SET_CREDENTIALS_FIRST, + STR_WIFI_CONN_FAILED, + STR_AUTHENTICATING, + STR_AUTH_SUCCESS, + STR_KOREADER_AUTH, + STR_SYNC_READY, + STR_AUTH_FAILED, + STR_DONE, + STR_CLEAR_CACHE_WARNING_1, + STR_CLEAR_CACHE_WARNING_2, + STR_CLEAR_CACHE_WARNING_3, + STR_CLEAR_CACHE_WARNING_4, + STR_CLEARING_CACHE, + STR_CACHE_CLEARED, + STR_ITEMS_REMOVED, + STR_FAILED_LOWER, + STR_CLEAR_CACHE_FAILED, + STR_CHECK_SERIAL_OUTPUT, + STR_DARK, + STR_LIGHT, + STR_CUSTOM, + STR_COVER, + STR_NONE_OPT, + STR_FIT, + STR_CROP, + STR_NO_PROGRESS, + STR_FULL_OPT, + STR_NEVER, + STR_IN_READER, + STR_ALWAYS, + STR_IGNORE, + STR_SLEEP, + STR_PAGE_TURN, + STR_PORTRAIT, + STR_LANDSCAPE_CW, + STR_INVERTED, + STR_LANDSCAPE_CCW, + STR_FRONT_LAYOUT_BCLR, + STR_FRONT_LAYOUT_LRBC, + STR_FRONT_LAYOUT_LBCR, + STR_PREV_NEXT, + STR_NEXT_PREV, + STR_BOOKERLY, + STR_NOTO_SANS, + STR_OPEN_DYSLEXIC, + STR_SMALL, + STR_MEDIUM, + STR_LARGE, + STR_X_LARGE, + STR_TIGHT, + STR_NORMAL, + STR_WIDE, + STR_JUSTIFY, + STR_ALIGN_LEFT, + STR_CENTER, + STR_ALIGN_RIGHT, + STR_MIN_1, + STR_MIN_5, + STR_MIN_10, + STR_MIN_15, + STR_MIN_30, + STR_PAGES_1, + STR_PAGES_5, + STR_PAGES_10, + STR_PAGES_15, + STR_PAGES_30, + STR_UPDATE, + STR_CHECKING_UPDATE, + STR_NEW_UPDATE, + STR_CURRENT_VERSION, + STR_NEW_VERSION, + STR_UPDATING, + STR_NO_UPDATE, + STR_UPDATE_FAILED, + STR_UPDATE_COMPLETE, + STR_POWER_ON_HINT, + STR_EXTERNAL_FONT, + STR_BUILTIN_DISABLED, + STR_NO_ENTRIES, + STR_DOWNLOADING, + STR_DOWNLOAD_FAILED, + STR_ERROR_MSG, + STR_UNNAMED, + STR_NO_SERVER_URL, + STR_FETCH_FEED_FAILED, + STR_PARSE_FEED_FAILED, + STR_NETWORK_PREFIX, + STR_IP_ADDRESS_PREFIX, + STR_SCAN_QR_WIFI_HINT, + STR_ERROR_GENERAL_FAILURE, + STR_ERROR_NETWORK_NOT_FOUND, + STR_ERROR_CONNECTION_TIMEOUT, + STR_SD_CARD, + STR_BACK, + STR_EXIT, + STR_HOME, + STR_SAVE, + STR_SELECT, + STR_TOGGLE, + STR_CONFIRM, + STR_CANCEL, + STR_CONNECT, + STR_OPEN, + STR_DOWNLOAD, + STR_RETRY, + STR_YES, + STR_NO, + STR_STATE_ON, + STR_STATE_OFF, + STR_SET, + STR_NOT_SET, + STR_DIR_LEFT, + STR_DIR_RIGHT, + STR_DIR_UP, + STR_DIR_DOWN, + STR_CAPS_ON, + STR_CAPS_OFF, + STR_OK_BUTTON, + STR_ON_MARKER, + STR_SLEEP_COVER_FILTER, + STR_FILTER_CONTRAST, + STR_STATUS_BAR_FULL_PERCENT, + STR_STATUS_BAR_FULL_BOOK, + STR_STATUS_BAR_BOOK_ONLY, + STR_STATUS_BAR_FULL_CHAPTER, + STR_UI_THEME, + STR_THEME_CLASSIC, + STR_THEME_LYRA, + STR_SUNLIGHT_FADING_FIX, + STR_REMAP_FRONT_BUTTONS, + STR_OPDS_BROWSER, + STR_COVER_CUSTOM, + STR_RECENTS, + STR_MENU_RECENT_BOOKS, + STR_NO_RECENT_BOOKS, + STR_CALIBRE_DESC, + STR_FORGET_AND_REMOVE, + STR_FORGET_BUTTON, + STR_CALIBRE_STARTING, + STR_CALIBRE_SETUP, + STR_CALIBRE_STATUS, + STR_CLEAR_BUTTON, + STR_DEFAULT_VALUE, + STR_REMAP_PROMPT, + STR_UNASSIGNED, + STR_ALREADY_ASSIGNED, + STR_REMAP_RESET_HINT, + STR_REMAP_CANCEL_HINT, + STR_HW_BACK_LABEL, + STR_HW_CONFIRM_LABEL, + STR_HW_LEFT_LABEL, + STR_HW_RIGHT_LABEL, + STR_GO_TO_PERCENT, + STR_GO_HOME_BUTTON, + STR_SYNC_PROGRESS, + STR_DELETE_CACHE, + STR_CHAPTER_PREFIX, + STR_PAGES_SEPARATOR, + STR_BOOK_PREFIX, + STR_KBD_SHIFT, + STR_KBD_SHIFT_CAPS, + STR_KBD_LOCK, + STR_CALIBRE_URL_HINT, + STR_PERCENT_STEP_HINT, + STR_SYNCING_TIME, + STR_CALC_HASH, + STR_HASH_FAILED, + STR_FETCH_PROGRESS, + STR_UPLOAD_PROGRESS, + STR_NO_CREDENTIALS_MSG, + STR_KOREADER_SETUP_HINT, + STR_PROGRESS_FOUND, + STR_REMOTE_LABEL, + STR_LOCAL_LABEL, + STR_PAGE_OVERALL_FORMAT, + STR_PAGE_TOTAL_OVERALL_FORMAT, + STR_DEVICE_FROM_FORMAT, + STR_APPLY_REMOTE, + STR_UPLOAD_LOCAL, + STR_NO_REMOTE_MSG, + STR_UPLOAD_PROMPT, + STR_UPLOAD_SUCCESS, + STR_SYNC_FAILED_MSG, + STR_SECTION_PREFIX, + STR_UPLOAD, + STR_BOOK_S_STYLE, + STR_EMBEDDED_STYLE, + STR_OPDS_SERVER_URL, + // Sentinel - must be last + _COUNT +}; + +// Helper function to get string array for a language +inline const char* const* getStringArray(Language lang) { + switch (lang) { + case Language::ENGLISH: + return i18n_strings::STRINGS_EN; + case Language::SPANISH: + return i18n_strings::STRINGS_ES; + case Language::FRENCH: + return i18n_strings::STRINGS_FR; + case Language::GERMAN: + return i18n_strings::STRINGS_DE; + case Language::CZECH: + return i18n_strings::STRINGS_CZ; + case Language::PORTUGUESE: + return i18n_strings::STRINGS_PO; + case Language::RUSSIAN: + return i18n_strings::STRINGS_RU; + case Language::SWEDISH: + return i18n_strings::STRINGS_SV; + default: + return i18n_strings::STRINGS_EN; + } +} + +// Helper function to get language count +constexpr uint8_t getLanguageCount() { return static_cast(Language::_COUNT); } diff --git a/lib/I18n/I18nStrings.h b/lib/I18n/I18nStrings.h new file mode 100644 index 00000000..b2c14ea3 --- /dev/null +++ b/lib/I18n/I18nStrings.h @@ -0,0 +1,19 @@ +#pragma once +#include + +#include "I18nKeys.h" + +// THIS FILE IS AUTO-GENERATED BY gen_i18n.py. DO NOT EDIT. + +namespace i18n_strings { + +extern const char* const STRINGS_EN[]; +extern const char* const STRINGS_ES[]; +extern const char* const STRINGS_FR[]; +extern const char* const STRINGS_DE[]; +extern const char* const STRINGS_CZ[]; +extern const char* const STRINGS_PO[]; +extern const char* const STRINGS_RU[]; +extern const char* const STRINGS_SV[]; + +} // namespace i18n_strings diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml new file mode 100644 index 00000000..4ce1b645 --- /dev/null +++ b/lib/I18n/translations/czech.yaml @@ -0,0 +1,317 @@ +_language_name: "Čeština" +_language_code: "CZECH" +_order: "4" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "SPUŠTĚNÍ" +STR_SLEEPING: "SPÁNEK" +STR_ENTERING_SLEEP: "Vstup do režimu spánku..." +STR_BROWSE_FILES: "Procházet soubory" +STR_FILE_TRANSFER: "Přenos souborů" +STR_SETTINGS_TITLE: "Nastavení" +STR_CALIBRE_LIBRARY: "Knihovna Calibre" +STR_CONTINUE_READING: "Pokračovat ve čtení" +STR_NO_OPEN_BOOK: "Žádná otevřená kniha" +STR_START_READING: "Začněte číst níže" +STR_BOOKS: "Knihy" +STR_NO_BOOKS_FOUND: "Žádné knihy nenalezeny" +STR_SELECT_CHAPTER: "Vybrat kapitolu" +STR_NO_CHAPTERS: "Žádné kapitoly" +STR_END_OF_BOOK: "Konec knihy" +STR_EMPTY_CHAPTER: "Prázdná kapitola" +STR_INDEXING: "Indexování..." +STR_MEMORY_ERROR: "Chyba paměti" +STR_PAGE_LOAD_ERROR: "Chyba načítání stránky" +STR_EMPTY_FILE: "Prázdný soubor" +STR_OUT_OF_BOUNDS: "Mimo hranice" +STR_LOADING: "Načítání..." +STR_LOAD_XTC_FAILED: "Nepodařilo se načíst XTC" +STR_LOAD_TXT_FAILED: "Nepodařilo se načíst TXT" +STR_LOAD_EPUB_FAILED: "Nepodařilo se načíst EPUB" +STR_SD_CARD_ERROR: "Chyba SD karty" +STR_WIFI_NETWORKS: "Wi-Fi sítě" +STR_NO_NETWORKS: "Žádné sítě nenalezeny" +STR_NETWORKS_FOUND: "Nalezeno %zu sítí" +STR_SCANNING: "Skenování..." +STR_CONNECTING: "Připojování..." +STR_CONNECTED: "Připojeno!" +STR_CONNECTION_FAILED: "Připojení se nezdařilo" +STR_CONNECTION_TIMEOUT: "Časový limit připojení" +STR_FORGET_NETWORK: "Zapomenout síť?" +STR_SAVE_PASSWORD: "Uložit heslo pro příště?" +STR_REMOVE_PASSWORD: "Odstranit uložené heslo?" +STR_PRESS_OK_SCAN: "Stiskněte OK pro přeskenování" +STR_PRESS_ANY_CONTINUE: "Pokračujte stiskem libovolné klávesy" +STR_SELECT_HINT: "VLEVO/VPRAVO: Vybrat | OK: Potvrdit" +STR_HOW_CONNECT: "Jak se chcete připojit?" +STR_JOIN_NETWORK: "Připojit se k síti" +STR_CREATE_HOTSPOT: "Vytvořit hotspot" +STR_JOIN_DESC: "Připojit se k existující síti WiFi" +STR_HOTSPOT_DESC: "Vytvořit síť WiFi, ke které se mohou připojit ostatní" +STR_STARTING_HOTSPOT: "Spouštění hotspotu..." +STR_HOTSPOT_MODE: "Režim hotspotu" +STR_CONNECT_WIFI_HINT: "Připojte své zařízení k této síti WiFi" +STR_OPEN_URL_HINT: "Otevřete tuto URL ve svém prohlížeči" +STR_OR_HTTP_PREFIX: "nebo http://" +STR_SCAN_QR_HINT: "nebo naskenujte QR kód telefonem:" +STR_CALIBRE_WIRELESS: "Calibre Wireless" +STR_CALIBRE_WEB_URL: "URL webu Calibre" +STR_CONNECT_WIRELESS: "Připojit jako bezdrátové zařízení" +STR_NETWORK_LEGEND: "* = Šifrováno | + = Uloženo" +STR_MAC_ADDRESS: "MAC adresa:" +STR_CHECKING_WIFI: "Kontrola WiFi..." +STR_ENTER_WIFI_PASSWORD: "Zadejte heslo WiFi" +STR_ENTER_TEXT: "Zadejte text" +STR_TO_PREFIX: "pro" +STR_CALIBRE_DISCOVERING: "Prozkoumávání Calibre..." +STR_CALIBRE_CONNECTING_TO: "Připojování k" +STR_CALIBRE_CONNECTED_TO: "Připojeno k" +STR_CALIBRE_WAITING_COMMANDS: "Čekám na příkazy…" +STR_CONNECTION_FAILED_RETRYING: "(Připojení se nezdařilo, opakování pokusu)" +STR_CALIBRE_DISCONNECTED: "Calibre odpojeno" +STR_CALIBRE_WAITING_TRANSFER: "Čekání na přenos..." +STR_CALIBRE_TRANSFER_HINT: "Nezdaří-li se přenos, povolte\\n„Ignorovat volné místo“ v Calibre\\nnastavení pluginu SmartDevice." +STR_CALIBRE_RECEIVING: "Příjem:" +STR_CALIBRE_RECEIVED: "Přijato:" +STR_CALIBRE_WAITING_MORE: "Čekání na další..." +STR_CALIBRE_FAILED_CREATE_FILE: "Nepodařilo se vytvořit soubor" +STR_CALIBRE_PASSWORD_REQUIRED: "Vyžadováno heslo" +STR_CALIBRE_TRANSFER_INTERRUPTED: "Přenos přerušen" +STR_CALIBRE_INSTRUCTION_1: "1) Nainstalujte plugin CrossPoint Reader" +STR_CALIBRE_INSTRUCTION_2: "2) Buďte ve stejné síti WiFi" +STR_CALIBRE_INSTRUCTION_3: "3) V Calibre: „Odeslat do zařízení“" +STR_CALIBRE_INSTRUCTION_4: "„Při odesílání ponechat tuto obrazovku otevřenou“" +STR_CAT_DISPLAY: "Displej" +STR_CAT_READER: "Čtečka" +STR_CAT_CONTROLS: "Ovládací prvky" +STR_CAT_SYSTEM: "Systém" +STR_SLEEP_SCREEN: "Obrazovka spánku" +STR_SLEEP_COVER_MODE: "Obrazovka spánku Režim krytu" +STR_STATUS_BAR: "Stavový řádek" +STR_HIDE_BATTERY: "Skrýt baterii %" +STR_EXTRA_SPACING: "Extra mezery mezi odstavci" +STR_TEXT_AA: "Vyhlazování textu" +STR_SHORT_PWR_BTN: "Krátké stisknutí tlačítka napájení" +STR_ORIENTATION: "Orientace čtení" +STR_FRONT_BTN_LAYOUT: "Rozvržení předních tlačítek" +STR_SIDE_BTN_LAYOUT: "Rozvržení bočních tlačítek (čtečka)" +STR_LONG_PRESS_SKIP: "Dlouhé stisknutí Přeskočit kapitolu" +STR_FONT_FAMILY: "Rodina písem čtečky" +STR_EXT_READER_FONT: "Písmo externí čtečky" +STR_EXT_CHINESE_FONT: "Písmo čtečky" +STR_EXT_UI_FONT: "Písmo rozhraní" +STR_FONT_SIZE: "Velikost písma rozhraní" +STR_LINE_SPACING: "Řádkování čtečky" +STR_ASCII_LETTER_SPACING: "Mezery písmen ASCII" +STR_ASCII_DIGIT_SPACING: "Mezery číslic ASCII" +STR_CJK_SPACING: "Mezery CJK" +STR_COLOR_MODE: "Režim barev" +STR_SCREEN_MARGIN: "Okraj obrazovky čtečky" +STR_PARA_ALIGNMENT: "Zarovnání odstavců čtečky" +STR_HYPHENATION: "Dělení slov" +STR_TIME_TO_SLEEP: "Čas do uspání" +STR_REFRESH_FREQ: "Frekvence obnovení" +STR_CALIBRE_SETTINGS: "Nastavení Calibre" +STR_KOREADER_SYNC: "KOReaderu Sync" +STR_CHECK_UPDATES: "Zkontrolovat aktualizace" +STR_LANGUAGE: "Jazyk" +STR_SELECT_WALLPAPER: "Vybrat tapetu" +STR_CLEAR_READING_CACHE: "Vymazat mezipaměť čtení" +STR_CALIBRE: "Calibre" +STR_USERNAME: "Uživatelské jméno" +STR_PASSWORD: "Heslo" +STR_SYNC_SERVER_URL: "URL synch. serveru" +STR_DOCUMENT_MATCHING: "Párování dokumentů" +STR_AUTHENTICATE: "Ověření" +STR_KOREADER_USERNAME: "Uživ. jméno KOReaderu" +STR_KOREADER_PASSWORD: "Heslo KOReaderu" +STR_FILENAME: "Název souboru" +STR_BINARY: "Binární" +STR_SET_CREDENTIALS_FIRST: "Nastavte přihlašovací údaje" +STR_WIFI_CONN_FAILED: "Připojení k Wi-Fi selhalo" +STR_AUTHENTICATING: "Ověřování..." +STR_AUTH_SUCCESS: "Úspěšné ověření!" +STR_KOREADER_AUTH: "Ověření KOReaderu" +STR_SYNC_READY: "Synchronizace KOReaderu je připravena k použití" +STR_AUTH_FAILED: "Ověření selhalo" +STR_DONE: "Hotovo" +STR_CLEAR_CACHE_WARNING_1: "Tímto vymažete všechna data knih v mezipaměti." +STR_CLEAR_CACHE_WARNING_2: "Veškerý průběh čtení bude ztracen!" +STR_CLEAR_CACHE_WARNING_3: "Knihy bude nutné znovu indexovat" +STR_CLEAR_CACHE_WARNING_4: "při opětovném otevření." +STR_CLEARING_CACHE: "Mazání mezipaměti..." +STR_CACHE_CLEARED: "Mezipaměť vymazána" +STR_ITEMS_REMOVED: "položky odstraněny" +STR_FAILED_LOWER: "selhalo" +STR_CLEAR_CACHE_FAILED: "Vymazání mezipaměti se nezdařilo" +STR_CHECK_SERIAL_OUTPUT: "Podrobnosti naleznete v sériovém výstupu" +STR_DARK: "Tmavý" +STR_LIGHT: "Světlý" +STR_CUSTOM: "Vlastní" +STR_COVER: "Obálka" +STR_NONE_OPT: "Žádný" +STR_FIT: "Přizpůsobit" +STR_CROP: "Oříznout" +STR_NO_PROGRESS: "Žádný postup" +STR_FULL_OPT: "Plná" +STR_NEVER: "Nikdy" +STR_IN_READER: "Ve čtečce" +STR_ALWAYS: "Vždy" +STR_IGNORE: "Ignorovat" +STR_SLEEP: "Spánek" +STR_PAGE_TURN: "Otáčení stránek" +STR_PORTRAIT: "Na výšku" +STR_LANDSCAPE_CW: "Na šířku po směru hod. ručiček" +STR_INVERTED: "Invertovaný" +STR_LANDSCAPE_CCW: "Na šířku proti směru hod. ručiček" +STR_FRONT_LAYOUT_BCLR: "Zpět, Potvrdit, Vlevo, Vpravo" +STR_FRONT_LAYOUT_LRBC: "Vlevo, Vpravo, Zpět, Potvrdit" +STR_FRONT_LAYOUT_LBCR: "Vlevo, Zpět, Potvrdit, Vpravo" +STR_PREV_NEXT: "Předchozí/Další" +STR_NEXT_PREV: "Další/Předchozí" +STR_BOOKERLY: "Bookerly" +STR_NOTO_SANS: "Noto Sans" +STR_OPEN_DYSLEXIC: "Open Dyslexic" +STR_SMALL: "Malý" +STR_MEDIUM: "Střední" +STR_LARGE: "Velký" +STR_X_LARGE: "Obří" +STR_TIGHT: "Těsný" +STR_NORMAL: "Normální" +STR_WIDE: "Široký" +STR_JUSTIFY: "Zarovnat do bloku" +STR_ALIGN_LEFT: "Vlevo" +STR_CENTER: "Na střed" +STR_ALIGN_RIGHT: "Vpravo" +STR_MIN_1: "1 min" +STR_MIN_5: "5 min" +STR_MIN_10: "10 min" +STR_MIN_15: "15 min" +STR_MIN_30: "30 min" +STR_PAGES_1: "1 stránka" +STR_PAGES_5: "5 stránek" +STR_PAGES_10: "10 stránek" +STR_PAGES_15: "15 stránek" +STR_PAGES_30: "30 stránek" +STR_UPDATE: "Aktualizace" +STR_CHECKING_UPDATE: "Kontrola aktualizací…" +STR_NEW_UPDATE: "Nová aktualizace k dispozici!" +STR_CURRENT_VERSION: "Aktuální verze:" +STR_NEW_VERSION: "Nová verze:" +STR_UPDATING: "Aktualizace..." +STR_NO_UPDATE: "Žádná aktualizace k dispozici" +STR_UPDATE_FAILED: "Aktualizace selhala" +STR_UPDATE_COMPLETE: "Aktualizace dokončena" +STR_POWER_ON_HINT: "Stiskněte a podržte tlačítko napájení pro opětovné zapnutí" +STR_EXTERNAL_FONT: "Externí písmo" +STR_BUILTIN_DISABLED: "Vestavěné (Zakázáno)" +STR_NO_ENTRIES: "Žádné položky nenalezeny" +STR_DOWNLOADING: "Stahování..." +STR_DOWNLOAD_FAILED: "Stahování selhalo" +STR_ERROR_MSG: "Chyba:" +STR_UNNAMED: "Nepojmenované" +STR_NO_SERVER_URL: "Není nakonfigurována adresa URL serveru" +STR_FETCH_FEED_FAILED: "Načtení kanálu se nezdařilo" +STR_PARSE_FEED_FAILED: "Analyzování kanálu se nezdařilo" +STR_NETWORK_PREFIX: "Síť:" +STR_IP_ADDRESS_PREFIX: "IP adresa:" +STR_SCAN_QR_WIFI_HINT: "nebo naskenujte QR kód telefonem pro připojení k Wi-Fi." +STR_ERROR_GENERAL_FAILURE: "Chyba: Obecná chyba" +STR_ERROR_NETWORK_NOT_FOUND: "Chyba: Síť nenalezena" +STR_ERROR_CONNECTION_TIMEOUT: "Chyba: Časový limit připojení" +STR_SD_CARD: "SD karta" +STR_BACK: "« Zpět" +STR_EXIT: "« Konec" +STR_HOME: "« Domů" +STR_SAVE: "« Uložit" +STR_SELECT: "Vybrat" +STR_TOGGLE: "Přepnout" +STR_CONFIRM: "Potvrdit" +STR_CANCEL: "Zrušit" +STR_CONNECT: "Připojit" +STR_OPEN: "Otevřít" +STR_DOWNLOAD: "Stáhnout" +STR_RETRY: "Zkusit znovu" +STR_YES: "Ano" +STR_NO: "Ne" +STR_STATE_ON: "ZAP" +STR_STATE_OFF: "VYP" +STR_SET: "Nastavit" +STR_NOT_SET: "Nenastaveno" +STR_DIR_LEFT: "Vlevo" +STR_DIR_RIGHT: "Vpravo" +STR_DIR_UP: "Nahoru" +STR_DIR_DOWN: "Dolů" +STR_CAPS_ON: "PÍSMO" +STR_CAPS_OFF: "písmo" +STR_OK_BUTTON: "OK" +STR_ON_MARKER: "[ZAP]" +STR_SLEEP_COVER_FILTER: "Filtr obrazovky spánku" +STR_FILTER_CONTRAST: "Kontrast" +STR_STATUS_BAR_FULL_PERCENT: "Plný s procenty" +STR_STATUS_BAR_FULL_BOOK: "Plný s pruhem knih" +STR_STATUS_BAR_BOOK_ONLY: "Pouze pruh knih" +STR_STATUS_BAR_FULL_CHAPTER: "Plná s pruhem kapitol" +STR_UI_THEME: "Šablona rozhraní" +STR_THEME_CLASSIC: "Klasická" +STR_THEME_LYRA: "Lyra" +STR_SUNLIGHT_FADING_FIX: "Oprava blednutí na slunci" +STR_REMAP_FRONT_BUTTONS: "Přemapovat přední tlačítka" +STR_OPDS_BROWSER: "Prohlížeč OPDS" +STR_COVER_CUSTOM: "Obálka + Vlastní" +STR_RECENTS: "Nedávné" +STR_MENU_RECENT_BOOKS: "Nedávné knihy" +STR_NO_RECENT_BOOKS: "Žádné nedávné knihy" +STR_CALIBRE_DESC: "Používat přenosy bezdrátových zařízení Calibre" +STR_FORGET_AND_REMOVE: "Zapomenout síť a odstranit uložené heslo?" +STR_FORGET_BUTTON: "Zapomenout na síť" +STR_CALIBRE_STARTING: "Spuštění Calibre..." +STR_CALIBRE_SETUP: "Nastavení" +STR_CALIBRE_STATUS: "Stav" +STR_CLEAR_BUTTON: "Vymazat" +STR_DEFAULT_VALUE: "Výchozí" +STR_REMAP_PROMPT: "Stiskněte přední tlačítko pro každou roli" +STR_UNASSIGNED: "Nepřiřazeno" +STR_ALREADY_ASSIGNED: "Již přiřazeno" +STR_REMAP_RESET_HINT: "Boční tlačítko Nahoru: Obnovit výchozí rozvržení" +STR_REMAP_CANCEL_HINT: "Boční tlačítko Dolů: Zrušit přemapování" +STR_HW_BACK_LABEL: "Zpět (1. tlačítko)" +STR_HW_CONFIRM_LABEL: "Potvrdit (2. tlačítko)" +STR_HW_LEFT_LABEL: "Vlevo (3. tlačítko)" +STR_HW_RIGHT_LABEL: "Vpravo (4. tlačítko)" +STR_GO_TO_PERCENT: "Přejít na %" +STR_GO_HOME_BUTTON: "Přejít Domů" +STR_SYNC_PROGRESS: "Průběh synchronizace" +STR_DELETE_CACHE: "Smazat mezipaměť knihy" +STR_CHAPTER_PREFIX: "Kapitola:" +STR_PAGES_SEPARATOR: "stránek |" +STR_BOOK_PREFIX: "Kniha:" +STR_KBD_SHIFT: "shift" +STR_KBD_SHIFT_CAPS: "SHIFT" +STR_KBD_LOCK: "ZÁMEK" +STR_CALIBRE_URL_HINT: "Pro Calibre přidejte /opds do URL adresy" +STR_PERCENT_STEP_HINT: "Vlevo/Vpravo: 1 % Nahoru/Dolů: 10 %" +STR_SYNCING_TIME: "Čas synchronizace..." +STR_CALC_HASH: "Výpočet hashe dokumentu..." +STR_HASH_FAILED: "Nepodařilo se vypočítat hash dokumentu" +STR_FETCH_PROGRESS: "Načítání vzdáleného průběhu..." +STR_UPLOAD_PROGRESS: "Průběh nahrávání..." +STR_NO_CREDENTIALS_MSG: "Přihlašovací údaje nejsou nakonfigurovány" +STR_KOREADER_SETUP_HINT: "Nastavit účet KOReader v Nastavení" +STR_PROGRESS_FOUND: "Nalezen průběh!" +STR_REMOTE_LABEL: "Vzdálené:" +STR_LOCAL_LABEL: "Lokální:" +STR_PAGE_OVERALL_FORMAT: "Stránka %d, celkově %.2f%%" +STR_PAGE_TOTAL_OVERALL_FORMAT: "Stránka %d/%d, celkově %.2f%%" +STR_DEVICE_FROM_FORMAT: " Od: %s" +STR_APPLY_REMOTE: "Použít vzdálený postup" +STR_UPLOAD_LOCAL: "Nahrát lokální postup" +STR_NO_REMOTE_MSG: "Nenalezen žádný vzdálený postup" +STR_UPLOAD_PROMPT: "Nahrát aktuální pozici?" +STR_UPLOAD_SUCCESS: "Postup nahrán!" +STR_SYNC_FAILED_MSG: "Synchronizace se nezdařila" +STR_SECTION_PREFIX: "Sekce" +STR_UPLOAD: "Nahrát" +STR_BOOK_S_STYLE: "Styl knihy" +STR_EMBEDDED_STYLE: "Vložený styl" +STR_OPDS_SERVER_URL: "URL serveru OPDS" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml new file mode 100644 index 00000000..eb0e4b26 --- /dev/null +++ b/lib/I18n/translations/english.yaml @@ -0,0 +1,317 @@ +_language_name: "English" +_language_code: "ENGLISH" +_order: "0" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "BOOTING" +STR_SLEEPING: "SLEEPING" +STR_ENTERING_SLEEP: "Entering Sleep..." +STR_BROWSE_FILES: "Browse Files" +STR_FILE_TRANSFER: "File Transfer" +STR_SETTINGS_TITLE: "Settings" +STR_CALIBRE_LIBRARY: "Calibre Library" +STR_CONTINUE_READING: "Continue Reading" +STR_NO_OPEN_BOOK: "No open book" +STR_START_READING: "Start reading below" +STR_BOOKS: "Books" +STR_NO_BOOKS_FOUND: "No books found" +STR_SELECT_CHAPTER: "Select Chapter" +STR_NO_CHAPTERS: "No chapters" +STR_END_OF_BOOK: "End of book" +STR_EMPTY_CHAPTER: "Empty chapter" +STR_INDEXING: "Indexing..." +STR_MEMORY_ERROR: "Memory error" +STR_PAGE_LOAD_ERROR: "Page load error" +STR_EMPTY_FILE: "Empty file" +STR_OUT_OF_BOUNDS: "Out of bounds" +STR_LOADING: "Loading..." +STR_LOAD_XTC_FAILED: "Failed to load XTC" +STR_LOAD_TXT_FAILED: "Failed to load TXT" +STR_LOAD_EPUB_FAILED: "Failed to load EPUB" +STR_SD_CARD_ERROR: "SD card error" +STR_WIFI_NETWORKS: "WiFi Networks" +STR_NO_NETWORKS: "No networks found" +STR_NETWORKS_FOUND: "%zu networks found" +STR_SCANNING: "Scanning..." +STR_CONNECTING: "Connecting..." +STR_CONNECTED: "Connected!" +STR_CONNECTION_FAILED: "Connection Failed" +STR_CONNECTION_TIMEOUT: "Connection timeout" +STR_FORGET_NETWORK: "Forget Network?" +STR_SAVE_PASSWORD: "Save password for next time?" +STR_REMOVE_PASSWORD: "Remove saved password?" +STR_PRESS_OK_SCAN: "Press OK to scan again" +STR_PRESS_ANY_CONTINUE: "Press any button to continue" +STR_SELECT_HINT: "LEFT/RIGHT: Select | OK: Confirm" +STR_HOW_CONNECT: "How would you like to connect?" +STR_JOIN_NETWORK: "Join a Network" +STR_CREATE_HOTSPOT: "Create Hotspot" +STR_JOIN_DESC: "Connect to an existing WiFi network" +STR_HOTSPOT_DESC: "Create a WiFi network others can join" +STR_STARTING_HOTSPOT: "Starting Hotspot..." +STR_HOTSPOT_MODE: "Hotspot Mode" +STR_CONNECT_WIFI_HINT: "Connect your device to this WiFi network" +STR_OPEN_URL_HINT: "Open this URL in your browser" +STR_OR_HTTP_PREFIX: "or http://" +STR_SCAN_QR_HINT: "or scan QR code with your phone:" +STR_CALIBRE_WIRELESS: "Calibre Wireless" +STR_CALIBRE_WEB_URL: "Calibre Web URL" +STR_CONNECT_WIRELESS: "Connect as Wireless Device" +STR_NETWORK_LEGEND: "* = Encrypted | + = Saved" +STR_MAC_ADDRESS: "MAC address:" +STR_CHECKING_WIFI: "Checking WiFi..." +STR_ENTER_WIFI_PASSWORD: "Enter WiFi Password" +STR_ENTER_TEXT: "Enter Text" +STR_TO_PREFIX: "to " +STR_CALIBRE_DISCOVERING: "Discovering Calibre..." +STR_CALIBRE_CONNECTING_TO: "Connecting to " +STR_CALIBRE_CONNECTED_TO: "Connected to " +STR_CALIBRE_WAITING_COMMANDS: "Waiting for commands..." +STR_CONNECTION_FAILED_RETRYING: "(Connection failed, retrying)" +STR_CALIBRE_DISCONNECTED: "Calibre disconnected" +STR_CALIBRE_WAITING_TRANSFER: "Waiting for transfer..." +STR_CALIBRE_TRANSFER_HINT: "If transfer fails, enable\\n'Ignore free space' in Calibre's\\nSmartDevice plugin settings." +STR_CALIBRE_RECEIVING: "Receiving: " +STR_CALIBRE_RECEIVED: "Received: " +STR_CALIBRE_WAITING_MORE: "Waiting for more..." +STR_CALIBRE_FAILED_CREATE_FILE: "Failed to create file" +STR_CALIBRE_PASSWORD_REQUIRED: "Password required" +STR_CALIBRE_TRANSFER_INTERRUPTED: "Transfer interrupted" +STR_CALIBRE_INSTRUCTION_1: "1) Install CrossPoint Reader plugin" +STR_CALIBRE_INSTRUCTION_2: "2) Be on the same WiFi network" +STR_CALIBRE_INSTRUCTION_3: "3) In Calibre: \"Send to device\"" +STR_CALIBRE_INSTRUCTION_4: "\"Keep this screen open while sending\"" +STR_CAT_DISPLAY: "Display" +STR_CAT_READER: "Reader" +STR_CAT_CONTROLS: "Controls" +STR_CAT_SYSTEM: "System" +STR_SLEEP_SCREEN: "Sleep Screen" +STR_SLEEP_COVER_MODE: "Sleep Screen Cover Mode" +STR_STATUS_BAR: "Status Bar" +STR_HIDE_BATTERY: "Hide Battery %" +STR_EXTRA_SPACING: "Extra Paragraph Spacing" +STR_TEXT_AA: "Text Anti-Aliasing" +STR_SHORT_PWR_BTN: "Short Power Button Click" +STR_ORIENTATION: "Reading Orientation" +STR_FRONT_BTN_LAYOUT: "Front Button Layout" +STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)" +STR_LONG_PRESS_SKIP: "Long-press Chapter Skip" +STR_FONT_FAMILY: "Reader Font Family" +STR_EXT_READER_FONT: "External Reader Font" +STR_EXT_CHINESE_FONT: "Reader Font" +STR_EXT_UI_FONT: "UI Font" +STR_FONT_SIZE: "UI Font Size" +STR_LINE_SPACING: "Reader Line Spacing" +STR_ASCII_LETTER_SPACING: "ASCII Letter Spacing" +STR_ASCII_DIGIT_SPACING: "ASCII Digit Spacing" +STR_CJK_SPACING: "CJK Spacing" +STR_COLOR_MODE: "Color Mode" +STR_SCREEN_MARGIN: "Reader Screen Margin" +STR_PARA_ALIGNMENT: "Reader Paragraph Alignment" +STR_HYPHENATION: "Hyphenation" +STR_TIME_TO_SLEEP: "Time to Sleep" +STR_REFRESH_FREQ: "Refresh Frequency" +STR_CALIBRE_SETTINGS: "Calibre Settings" +STR_KOREADER_SYNC: "KOReader Sync" +STR_CHECK_UPDATES: "Check for updates" +STR_LANGUAGE: "Language" +STR_SELECT_WALLPAPER: "Select Wallpaper" +STR_CLEAR_READING_CACHE: "Clear Reading Cache" +STR_CALIBRE: "Calibre" +STR_USERNAME: "Username" +STR_PASSWORD: "Password" +STR_SYNC_SERVER_URL: "Sync Server URL" +STR_DOCUMENT_MATCHING: "Document Matching" +STR_AUTHENTICATE: "Authenticate" +STR_KOREADER_USERNAME: "KOReader Username" +STR_KOREADER_PASSWORD: "KOReader Password" +STR_FILENAME: "Filename" +STR_BINARY: "Binary" +STR_SET_CREDENTIALS_FIRST: "Set credentials first" +STR_WIFI_CONN_FAILED: "WiFi connection failed" +STR_AUTHENTICATING: "Authenticating..." +STR_AUTH_SUCCESS: "Successfully authenticated!" +STR_KOREADER_AUTH: "KOReader Auth" +STR_SYNC_READY: "KOReader sync is ready to use" +STR_AUTH_FAILED: "Authentication Failed" +STR_DONE: "Done" +STR_CLEAR_CACHE_WARNING_1: "This will clear all cached book data." +STR_CLEAR_CACHE_WARNING_2: "All reading progress will be lost!" +STR_CLEAR_CACHE_WARNING_3: "Books will need to be re-indexed" +STR_CLEAR_CACHE_WARNING_4: "when opened again." +STR_CLEARING_CACHE: "Clearing cache..." +STR_CACHE_CLEARED: "Cache Cleared" +STR_ITEMS_REMOVED: "items removed" +STR_FAILED_LOWER: "failed" +STR_CLEAR_CACHE_FAILED: "Failed to clear cache" +STR_CHECK_SERIAL_OUTPUT: "Check serial output for details" +STR_DARK: "Dark" +STR_LIGHT: "Light" +STR_CUSTOM: "Custom" +STR_COVER: "Cover" +STR_NONE_OPT: "None" +STR_FIT: "Fit" +STR_CROP: "Crop" +STR_NO_PROGRESS: "No Progress" +STR_FULL_OPT: "Full" +STR_NEVER: "Never" +STR_IN_READER: "In Reader" +STR_ALWAYS: "Always" +STR_IGNORE: "Ignore" +STR_SLEEP: "Sleep" +STR_PAGE_TURN: "Page Turn" +STR_PORTRAIT: "Portrait" +STR_LANDSCAPE_CW: "Landscape CW" +STR_INVERTED: "Inverted" +STR_LANDSCAPE_CCW: "Landscape CCW" +STR_FRONT_LAYOUT_BCLR: "Bck, Cnfrm, Lft, Rght" +STR_FRONT_LAYOUT_LRBC: "Lft, Rght, Bck, Cnfrm" +STR_FRONT_LAYOUT_LBCR: "Lft, Bck, Cnfrm, Rght" +STR_PREV_NEXT: "Prev/Next" +STR_NEXT_PREV: "Next/Prev" +STR_BOOKERLY: "Bookerly" +STR_NOTO_SANS: "Noto Sans" +STR_OPEN_DYSLEXIC: "Open Dyslexic" +STR_SMALL: "Small" +STR_MEDIUM: "Medium" +STR_LARGE: "Large" +STR_X_LARGE: "X Large" +STR_TIGHT: "Tight" +STR_NORMAL: "Normal" +STR_WIDE: "Wide" +STR_JUSTIFY: "Justify" +STR_ALIGN_LEFT: "Left" +STR_CENTER: "Center" +STR_ALIGN_RIGHT: "Right" +STR_MIN_1: "1 min" +STR_MIN_5: "5 min" +STR_MIN_10: "10 min" +STR_MIN_15: "15 min" +STR_MIN_30: "30 min" +STR_PAGES_1: "1 page" +STR_PAGES_5: "5 pages" +STR_PAGES_10: "10 pages" +STR_PAGES_15: "15 pages" +STR_PAGES_30: "30 pages" +STR_UPDATE: "Update" +STR_CHECKING_UPDATE: "Checking for update..." +STR_NEW_UPDATE: "New update available!" +STR_CURRENT_VERSION: "Current Version: " +STR_NEW_VERSION: "New Version: " +STR_UPDATING: "Updating..." +STR_NO_UPDATE: "No update available" +STR_UPDATE_FAILED: "Update failed" +STR_UPDATE_COMPLETE: "Update complete" +STR_POWER_ON_HINT: "Press and hold power button to turn back on" +STR_EXTERNAL_FONT: "External Font" +STR_BUILTIN_DISABLED: "Built-in (Disabled)" +STR_NO_ENTRIES: "No entries found" +STR_DOWNLOADING: "Downloading..." +STR_DOWNLOAD_FAILED: "Download failed" +STR_ERROR_MSG: "Error:" +STR_UNNAMED: "Unnamed" +STR_NO_SERVER_URL: "No server URL configured" +STR_FETCH_FEED_FAILED: "Failed to fetch feed" +STR_PARSE_FEED_FAILED: "Failed to parse feed" +STR_NETWORK_PREFIX: "Network: " +STR_IP_ADDRESS_PREFIX: "IP Address: " +STR_SCAN_QR_WIFI_HINT: "or scan QR code with your phone to connect to Wifi." +STR_ERROR_GENERAL_FAILURE: "Error: General failure" +STR_ERROR_NETWORK_NOT_FOUND: "Error: Network not found" +STR_ERROR_CONNECTION_TIMEOUT: "Error: Connection timeout" +STR_SD_CARD: "SD card" +STR_BACK: "« Back" +STR_EXIT: "« Exit" +STR_HOME: "« Home" +STR_SAVE: "« Save" +STR_SELECT: "Select" +STR_TOGGLE: "Toggle" +STR_CONFIRM: "Confirm" +STR_CANCEL: "Cancel" +STR_CONNECT: "Connect" +STR_OPEN: "Open" +STR_DOWNLOAD: "Download" +STR_RETRY: "Retry" +STR_YES: "Yes" +STR_NO: "No" +STR_STATE_ON: "ON" +STR_STATE_OFF: "OFF" +STR_SET: "Set" +STR_NOT_SET: "Not Set" +STR_DIR_LEFT: "Left" +STR_DIR_RIGHT: "Right" +STR_DIR_UP: "Up" +STR_DIR_DOWN: "Down" +STR_CAPS_ON: "CAPS" +STR_CAPS_OFF: "caps" +STR_OK_BUTTON: "OK" +STR_ON_MARKER: "[ON]" +STR_SLEEP_COVER_FILTER: "Sleep Screen Cover Filter" +STR_FILTER_CONTRAST: "Contrast" +STR_STATUS_BAR_FULL_PERCENT: "Full w/ Percentage" +STR_STATUS_BAR_FULL_BOOK: "Full w/ Book Bar" +STR_STATUS_BAR_BOOK_ONLY: "Book Bar Only" +STR_STATUS_BAR_FULL_CHAPTER: "Full w/ Chapter Bar" +STR_UI_THEME: "UI Theme" +STR_THEME_CLASSIC: "Classic" +STR_THEME_LYRA: "Lyra" +STR_SUNLIGHT_FADING_FIX: "Sunlight Fading Fix" +STR_REMAP_FRONT_BUTTONS: "Remap Front Buttons" +STR_OPDS_BROWSER: "OPDS Browser" +STR_COVER_CUSTOM: "Cover + Custom" +STR_RECENTS: "Recents" +STR_MENU_RECENT_BOOKS: "Recent Books" +STR_NO_RECENT_BOOKS: "No recent books" +STR_CALIBRE_DESC: "Use Calibre wireless device transfers" +STR_FORGET_AND_REMOVE: "Forget network and remove saved password?" +STR_FORGET_BUTTON: "Forget network" +STR_CALIBRE_STARTING: "Starting Calibre..." +STR_CALIBRE_SETUP: "Setup" +STR_CALIBRE_STATUS: "Status" +STR_CLEAR_BUTTON: "Clear" +STR_DEFAULT_VALUE: "Default" +STR_REMAP_PROMPT: "Press a front button for each role" +STR_UNASSIGNED: "Unassigned" +STR_ALREADY_ASSIGNED: "Already assigned" +STR_REMAP_RESET_HINT: "Side button Up: Reset to default layout" +STR_REMAP_CANCEL_HINT: "Side button Down: Cancel remapping" +STR_HW_BACK_LABEL: "Back (1st button)" +STR_HW_CONFIRM_LABEL: "Confirm (2nd button)" +STR_HW_LEFT_LABEL: "Left (3rd button)" +STR_HW_RIGHT_LABEL: "Right (4th button)" +STR_GO_TO_PERCENT: "Go to %" +STR_GO_HOME_BUTTON: "Go Home" +STR_SYNC_PROGRESS: "Sync Progress" +STR_DELETE_CACHE: "Delete Book Cache" +STR_CHAPTER_PREFIX: "Chapter: " +STR_PAGES_SEPARATOR: " pages | " +STR_BOOK_PREFIX: "Book: " +STR_KBD_SHIFT: "shift" +STR_KBD_SHIFT_CAPS: "SHIFT" +STR_KBD_LOCK: "LOCK" +STR_CALIBRE_URL_HINT: "For Calibre, add /opds to your URL" +STR_PERCENT_STEP_HINT: "Left/Right: 1% Up/Down: 10%" +STR_SYNCING_TIME: "Syncing time..." +STR_CALC_HASH: "Calculating document hash..." +STR_HASH_FAILED: "Failed to calculate document hash" +STR_FETCH_PROGRESS: "Fetching remote progress..." +STR_UPLOAD_PROGRESS: "Uploading progress..." +STR_NO_CREDENTIALS_MSG: "No credentials configured" +STR_KOREADER_SETUP_HINT: "Set up KOReader account in Settings" +STR_PROGRESS_FOUND: "Progress found!" +STR_REMOTE_LABEL: "Remote:" +STR_LOCAL_LABEL: "Local:" +STR_PAGE_OVERALL_FORMAT: "Page %d, %.2f%% overall" +STR_PAGE_TOTAL_OVERALL_FORMAT: "Page %d/%d, %.2f%% overall" +STR_DEVICE_FROM_FORMAT: " From: %s" +STR_APPLY_REMOTE: "Apply remote progress" +STR_UPLOAD_LOCAL: "Upload local progress" +STR_NO_REMOTE_MSG: "No remote progress found" +STR_UPLOAD_PROMPT: "Upload current position?" +STR_UPLOAD_SUCCESS: "Progress uploaded!" +STR_SYNC_FAILED_MSG: "Sync failed" +STR_SECTION_PREFIX: "Section " +STR_UPLOAD: "Upload" +STR_BOOK_S_STYLE: "Book's Style" +STR_EMBEDDED_STYLE: "Embedded Style" +STR_OPDS_SERVER_URL: "OPDS Server URL" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml new file mode 100644 index 00000000..00af367c --- /dev/null +++ b/lib/I18n/translations/french.yaml @@ -0,0 +1,317 @@ +_language_name: "Français" +_language_code: "FRENCH" +_order: "2" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "DÉMARRAGE EN COURS" +STR_SLEEPING: "VEILLE" +STR_ENTERING_SLEEP: "Mise en veille…" +STR_BROWSE_FILES: "Fichiers" +STR_FILE_TRANSFER: "Transfert" +STR_SETTINGS_TITLE: "Réglages" +STR_CALIBRE_LIBRARY: "Bibliothèque Calibre" +STR_CONTINUE_READING: "Continuer la lecture" +STR_NO_OPEN_BOOK: "Aucun livre ouvert" +STR_START_READING: "Lisez votre premier livre ci-dessous" +STR_BOOKS: "Livres" +STR_NO_BOOKS_FOUND: "Dossier vide" +STR_SELECT_CHAPTER: "Choix du chapitre" +STR_NO_CHAPTERS: "Aucun chapitre" +STR_END_OF_BOOK: "Fin du livre" +STR_EMPTY_CHAPTER: "Chapitre vide" +STR_INDEXING: "Indexation en cours…" +STR_MEMORY_ERROR: "Erreur de mémoire" +STR_PAGE_LOAD_ERROR: "Erreur de chargement" +STR_EMPTY_FILE: "Fichier vide" +STR_OUT_OF_BOUNDS: "Dépassement de mémoire" +STR_LOADING: "Chargement…" +STR_LOAD_XTC_FAILED: "Erreur de chargement du fichier XTC" +STR_LOAD_TXT_FAILED: "Erreur de chargement du fichier TXT" +STR_LOAD_EPUB_FAILED: "Erreur de chargement du fichier EPUB" +STR_SD_CARD_ERROR: "Carte mémoire absente" +STR_WIFI_NETWORKS: "Réseaux WiFi" +STR_NO_NETWORKS: "Aucun réseau" +STR_NETWORKS_FOUND: "%zu réseaux" +STR_SCANNING: "Recherche de réseaux en cours…" +STR_CONNECTING: "Connexion en cours…" +STR_CONNECTED: "Connecté !" +STR_CONNECTION_FAILED: "Échec de la connexion" +STR_CONNECTION_TIMEOUT: "Délai de connexion dépassé" +STR_FORGET_NETWORK: "Oublier ce réseau ?" +STR_SAVE_PASSWORD: "Enregistrer le mot de passe ?" +STR_REMOVE_PASSWORD: "Supprimer le mot de passe enregistré ?" +STR_PRESS_OK_SCAN: "Appuyez sur OK pour détecter à nouveau" +STR_PRESS_ANY_CONTINUE: "Appuyez sur une touche pour continuer" +STR_SELECT_HINT: "GAUCHE/DROITE: Sélectionner | OK: Valider" +STR_HOW_CONNECT: "Comment voulez-vous vous connecter ?" +STR_JOIN_NETWORK: "Connexion à un réseau" +STR_CREATE_HOTSPOT: "Créer un point d’accès" +STR_JOIN_DESC: "Se connecter à un réseau WiFi existant" +STR_HOTSPOT_DESC: "Créer un réseau WiFi accessible depuis d’autres appareils" +STR_STARTING_HOTSPOT: "Création du point d’accès en cours…" +STR_HOTSPOT_MODE: "Mode point d’accès" +STR_CONNECT_WIFI_HINT: "Connectez un appareil à ce réseau WiFi" +STR_OPEN_URL_HINT: "Ouvrez cette URL dans votre navigateur" +STR_OR_HTTP_PREFIX: "ou http://" +STR_SCAN_QR_HINT: "ou scannez le QR code avec votre téléphone" +STR_CALIBRE_WIRELESS: "Connexion à Calibre sans fil" +STR_CALIBRE_WEB_URL: "URL Web Calibre" +STR_CONNECT_WIRELESS: "Se connecter comme appareil sans fil" +STR_NETWORK_LEGEND: "* = Sécurisé | + = Sauvegardé" +STR_MAC_ADDRESS: "Adresse MAC :" +STR_CHECKING_WIFI: "Vérification du réseau WiFi..." +STR_ENTER_WIFI_PASSWORD: "Entrez le mot de passe WiFi" +STR_ENTER_TEXT: "Entrez le texte" +STR_TO_PREFIX: "à " +STR_CALIBRE_DISCOVERING: "Recherche de Calibre en cours…" +STR_CALIBRE_CONNECTING_TO: "Connexion à " +STR_CALIBRE_CONNECTED_TO: "Connecté à " +STR_CALIBRE_WAITING_COMMANDS: "En attente de commandes…" +STR_CONNECTION_FAILED_RETRYING: "(Échec de la connexion, nouvelle tentative)" +STR_CALIBRE_DISCONNECTED: "Calibre déconnecté" +STR_CALIBRE_WAITING_TRANSFER: "En attente de transfert…" +STR_CALIBRE_TRANSFER_HINT: "Si le transfert échoue, activez\\n’Ignorer l’espace libre’ dans les\\nparamètres du plugin SmartDevice de Calibre." +STR_CALIBRE_RECEIVING: "Réception : " +STR_CALIBRE_RECEIVED: "Reçus : " +STR_CALIBRE_WAITING_MORE: "En attente de données supplémentaires…" +STR_CALIBRE_FAILED_CREATE_FILE: "Échec de la création du fichier" +STR_CALIBRE_PASSWORD_REQUIRED: "Mot de passe requis" +STR_CALIBRE_TRANSFER_INTERRUPTED: "Transfert interrompu" +STR_CALIBRE_INSTRUCTION_1: "1) Installer le plugin CrossPoint Reader" +STR_CALIBRE_INSTRUCTION_2: "2) Se connecter au même réseau WiFi" +STR_CALIBRE_INSTRUCTION_3: "3) Dans Calibre : ‘Envoyer vers l’appareil’" +STR_CALIBRE_INSTRUCTION_4: "“Gardez cet écran ouvert pendant le transfert”" +STR_CAT_DISPLAY: "Affichage" +STR_CAT_READER: "Lecteur" +STR_CAT_CONTROLS: "Commandes" +STR_CAT_SYSTEM: "Système" +STR_SLEEP_SCREEN: "Écran de veille" +STR_SLEEP_COVER_MODE: "Mode d’image de l’écran de veille" +STR_STATUS_BAR: "Barre d’état" +STR_HIDE_BATTERY: "Masquer % batterie" +STR_EXTRA_SPACING: "Espacement des paragraphes" +STR_TEXT_AA: "Lissage du texte" +STR_SHORT_PWR_BTN: "Appui court bout. alim." +STR_ORIENTATION: "Orientation de lecture" +STR_FRONT_BTN_LAYOUT: "Disposition des boutons avant" +STR_SIDE_BTN_LAYOUT: "Disposition des boutons latéraux" +STR_LONG_PRESS_SKIP: "Appui long pour saut de chapitre" +STR_FONT_FAMILY: "Police de caractères du lecteur" +STR_EXT_READER_FONT: "Police externe" +STR_EXT_CHINESE_FONT: "Police du lecteur" +STR_EXT_UI_FONT: "Police de l’interface" +STR_FONT_SIZE: "Taille du texte de l’interface" +STR_LINE_SPACING: "Espacement des lignes" +STR_ASCII_LETTER_SPACING: "Espacement des lettres ASCII" +STR_ASCII_DIGIT_SPACING: "Espacement des chiffres ASCII" +STR_CJK_SPACING: "Espacement CJK" +STR_COLOR_MODE: "Mode couleur" +STR_SCREEN_MARGIN: "Marges du lecteur" +STR_PARA_ALIGNMENT: "Alignement des paragraphes" +STR_HYPHENATION: "Césure" +STR_TIME_TO_SLEEP: "Mise en veille automatique" +STR_REFRESH_FREQ: "Fréquence de rafraîchissement" +STR_CALIBRE_SETTINGS: "Réglages Calibre" +STR_KOREADER_SYNC: "Synchronisation KOReader" +STR_CHECK_UPDATES: "Mise à jour" +STR_LANGUAGE: "Langue" +STR_SELECT_WALLPAPER: "Fond d’écran" +STR_CLEAR_READING_CACHE: "Vider le cache de lecture" +STR_CALIBRE: "Calibre" +STR_USERNAME: "Nom d’utilisateur" +STR_PASSWORD: "Mot de passe" +STR_SYNC_SERVER_URL: "URL du serveur" +STR_DOCUMENT_MATCHING: "Correspondance" +STR_AUTHENTICATE: "Se connecter" +STR_KOREADER_USERNAME: "Nom d’utilisateur" +STR_KOREADER_PASSWORD: "Mot de passe" +STR_FILENAME: "Nom de fichier" +STR_BINARY: "Binaire" +STR_SET_CREDENTIALS_FIRST: "Identifiants manquants" +STR_WIFI_CONN_FAILED: "Échec de connexion WiFi" +STR_AUTHENTICATING: "Connexion en cours…" +STR_AUTH_SUCCESS: "Connexion réussie !" +STR_KOREADER_AUTH: "Auth KOReader" +STR_SYNC_READY: "Synchronisation KOReader prête" +STR_AUTH_FAILED: "Échec de la connexion" +STR_DONE: "OK" +STR_CLEAR_CACHE_WARNING_1: "Le cache de votre bibliothèque sera entièrement vidé" +STR_CLEAR_CACHE_WARNING_2: "Votre progression de lecture sera perdue !" +STR_CLEAR_CACHE_WARNING_3: "Les livres devront être réindexés" +STR_CLEAR_CACHE_WARNING_4: "à leur prochaine ouverture." +STR_CLEARING_CACHE: "Suppression du cache…" +STR_CACHE_CLEARED: "Cache supprimé" +STR_ITEMS_REMOVED: "éléments supprimés" +STR_FAILED_LOWER: "ont échoué" +STR_CLEAR_CACHE_FAILED: "Échec de la suppression du cache" +STR_CHECK_SERIAL_OUTPUT: "Vérifiez la console série pour plus de détails" +STR_DARK: "Sombre" +STR_LIGHT: "Clair" +STR_CUSTOM: "Custom" +STR_COVER: "Couverture" +STR_NONE_OPT: "Aucun" +STR_FIT: "Ajusté" +STR_CROP: "Rogné" +STR_NO_PROGRESS: "Sans progression" +STR_FULL_OPT: "Complète" +STR_NEVER: "Jamais" +STR_IN_READER: "Dans le lecteur" +STR_ALWAYS: "Toujours" +STR_IGNORE: "Ignorer" +STR_SLEEP: "Mise en veille" +STR_PAGE_TURN: "Page suivante" +STR_PORTRAIT: "Portrait" +STR_LANDSCAPE_CW: "Paysage" +STR_INVERTED: "Inversé" +STR_LANDSCAPE_CCW: "Paysage inversé" +STR_FRONT_LAYOUT_BCLR: "Ret, OK, Gauche, Droite" +STR_FRONT_LAYOUT_LRBC: "Gauche, Droite, Ret, OK" +STR_FRONT_LAYOUT_LBCR: "Gauche, Ret, OK, Droite" +STR_PREV_NEXT: "Prec/Suiv" +STR_NEXT_PREV: "Suiv/Prec" +STR_BOOKERLY: "Bookerly" +STR_NOTO_SANS: "Noto Sans" +STR_OPEN_DYSLEXIC: "Open Dyslexic" +STR_SMALL: "Petite" +STR_MEDIUM: "Moyenne" +STR_LARGE: "Grande" +STR_X_LARGE: "T Grande" +STR_TIGHT: "Serré" +STR_NORMAL: "Normal" +STR_WIDE: "Large" +STR_JUSTIFY: "Justifier" +STR_ALIGN_LEFT: "Gauche" +STR_CENTER: "Centre" +STR_ALIGN_RIGHT: "Droite" +STR_MIN_1: "1 min" +STR_MIN_5: "5 min" +STR_MIN_10: "10 min" +STR_MIN_15: "15 min" +STR_MIN_30: "30 min" +STR_PAGES_1: "1 page" +STR_PAGES_5: "5 pages" +STR_PAGES_10: "10 pages" +STR_PAGES_15: "15 pages" +STR_PAGES_30: "30 pages" +STR_UPDATE: "Mise à jour" +STR_CHECKING_UPDATE: "Recherche de mises à jour en cours…" +STR_NEW_UPDATE: "Nouvelle mise à jour disponible !" +STR_CURRENT_VERSION: "Version actuelle :" +STR_NEW_VERSION: "Nouvelle version : " +STR_UPDATING: "Mise à jour en cours…" +STR_NO_UPDATE: "Aucune mise à jour disponible" +STR_UPDATE_FAILED: "Échec de la mise à jour" +STR_UPDATE_COMPLETE: "Mise à jour effectuée" +STR_POWER_ON_HINT: "Maintenir le bouton d’alimentation pour redémarrer" +STR_EXTERNAL_FONT: "Police externe" +STR_BUILTIN_DISABLED: "Intégrée (désactivée)" +STR_NO_ENTRIES: "Aucune entrée trouvée" +STR_DOWNLOADING: "Téléchargement en cours…" +STR_DOWNLOAD_FAILED: "Échec du téléchargement" +STR_ERROR_MSG: "Erreur : " +STR_UNNAMED: "Sans titre" +STR_NO_SERVER_URL: "Aucune URL serveur configurée" +STR_FETCH_FEED_FAILED: "Échec du téléchargement du flux" +STR_PARSE_FEED_FAILED: "Échec de l’analyse du flux" +STR_NETWORK_PREFIX: "Réseau : " +STR_IP_ADDRESS_PREFIX: "Adresse IP : " +STR_SCAN_QR_WIFI_HINT: "or scan QR code with your phone to connect to Wifi." +STR_ERROR_GENERAL_FAILURE: "Erreur : Échec général" +STR_ERROR_NETWORK_NOT_FOUND: "Erreur : Réseau introuvable" +STR_ERROR_CONNECTION_TIMEOUT: "Erreur : Délai de connexion dépassé" +STR_SD_CARD: "Carte SD" +STR_BACK: "« Retour" +STR_EXIT: "« Sortie" +STR_HOME: "« Accueil" +STR_SAVE: "« Sauver" +STR_SELECT: "OK" +STR_TOGGLE: "Modifier" +STR_CONFIRM: "Confirmer" +STR_CANCEL: "Annuler" +STR_CONNECT: "OK" +STR_OPEN: "Ouvrir" +STR_DOWNLOAD: "Télécharger" +STR_RETRY: "Réessayer" +STR_YES: "Oui" +STR_NO: "Non" +STR_STATE_ON: "ON" +STR_STATE_OFF: "OFF" +STR_SET: "Défini" +STR_NOT_SET: "Non défini" +STR_DIR_LEFT: "Gauche" +STR_DIR_RIGHT: "Droite" +STR_DIR_UP: "Haut" +STR_DIR_DOWN: "Bas" +STR_CAPS_ON: "MAJ" +STR_CAPS_OFF: "maj" +STR_OK_BUTTON: "OK" +STR_ON_MARKER: "[ON]" +STR_SLEEP_COVER_FILTER: "Filtre affichage veille" +STR_FILTER_CONTRAST: "Contraste" +STR_STATUS_BAR_FULL_PERCENT: "Complète + %" +STR_STATUS_BAR_FULL_BOOK: "Complète + barre livre" +STR_STATUS_BAR_BOOK_ONLY: "Barre livre" +STR_STATUS_BAR_FULL_CHAPTER: "Complète + barre chapitre" +STR_UI_THEME: "Thème de l’interface" +STR_THEME_CLASSIC: "Classique" +STR_THEME_LYRA: "Lyra" +STR_SUNLIGHT_FADING_FIX: "Amélioration de la lisibilité au soleil" +STR_REMAP_FRONT_BUTTONS: "Réassigner les boutons avant" +STR_OPDS_BROWSER: "Navigateur OPDS" +STR_COVER_CUSTOM: "Couverture + Custom" +STR_RECENTS: "Récents" +STR_MENU_RECENT_BOOKS: "Livres récents" +STR_NO_RECENT_BOOKS: "Aucun livre récent" +STR_CALIBRE_DESC: "Utiliser les transferts sans fil Calibre" +STR_FORGET_AND_REMOVE: "Oublier le réseau et supprimer le mot de passe enregistré ?" +STR_FORGET_BUTTON: "Oublier le réseau" +STR_CALIBRE_STARTING: "Démarrage de Calibre..." +STR_CALIBRE_SETUP: "Configuration" +STR_CALIBRE_STATUS: "Statut" +STR_CLEAR_BUTTON: "Effacer" +STR_DEFAULT_VALUE: "Défaut" +STR_REMAP_PROMPT: "Appuyez sur un bouton avant pour chaque rôle" +STR_UNASSIGNED: "Non assigné" +STR_ALREADY_ASSIGNED: "Déjà assigné" +STR_REMAP_RESET_HINT: "Bouton latéral haut : Réinitialiser" +STR_REMAP_CANCEL_HINT: "Bouton latéral bas : Annuler le réglage" +STR_HW_BACK_LABEL: "Retour (1er bouton)" +STR_HW_CONFIRM_LABEL: "OK (2ème bouton)" +STR_HW_LEFT_LABEL: "Gauche (3ème bouton)" +STR_HW_RIGHT_LABEL: "Droite (4ème bouton)" +STR_GO_TO_PERCENT: "Aller à %" +STR_GO_HOME_BUTTON: "Aller à l’accueil" +STR_SYNC_PROGRESS: "Synchroniser la progression" +STR_DELETE_CACHE: "Supprimer le cache du livre" +STR_CHAPTER_PREFIX: "Chapitre : " +STR_PAGES_SEPARATOR: " pages | " +STR_BOOK_PREFIX: "Livre : " +STR_KBD_SHIFT: "maj" +STR_KBD_SHIFT_CAPS: "MAJ" +STR_KBD_LOCK: "VERR MAJ" +STR_CALIBRE_URL_HINT: "Pour Calibre, ajoutez /opds à l’URL" +STR_PERCENT_STEP_HINT: "Gauche/Droite : 1% Haut/Bas : 10%" +STR_SYNCING_TIME: "Synchronisation de l’heure…" +STR_CALC_HASH: "Calcul du hash du document…" +STR_HASH_FAILED: "Échec du calcul du hash du document" +STR_FETCH_PROGRESS: "Téléchargement de la progression…" +STR_UPLOAD_PROGRESS: "Envoi de la progression…" +STR_NO_CREDENTIALS_MSG: "Aucun identifiant configuré" +STR_KOREADER_SETUP_HINT: "Configurez le compte KOReader dans les réglages" +STR_PROGRESS_FOUND: "Progression trouvée !" +STR_REMOTE_LABEL: "En ligne :" +STR_LOCAL_LABEL: "Locale :" +STR_PAGE_OVERALL_FORMAT: "Page %d, %.2f%% au total" +STR_PAGE_TOTAL_OVERALL_FORMAT: "Page %d/%d, %.2f%% au total" +STR_DEVICE_FROM_FORMAT: " De : %s" +STR_APPLY_REMOTE: "Appliquer la progression en ligne" +STR_UPLOAD_LOCAL: "Envoyer la progression locale" +STR_NO_REMOTE_MSG: "Aucune progression en ligne trouvée" +STR_UPLOAD_PROMPT: "Envoyer la position actuelle ?" +STR_UPLOAD_SUCCESS: "Progression envoyée !" +STR_SYNC_FAILED_MSG: "Échec de la synchronisation" +STR_SECTION_PREFIX: "Section " +STR_UPLOAD: "Envoi" +STR_BOOK_S_STYLE: "Style du livre" +STR_EMBEDDED_STYLE: "Style intégré" +STR_OPDS_SERVER_URL: "URL du serveur OPDS" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml new file mode 100644 index 00000000..0879c925 --- /dev/null +++ b/lib/I18n/translations/german.yaml @@ -0,0 +1,317 @@ +_language_name: "Deutsch" +_language_code: "GERMAN" +_order: "3" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "STARTEN" +STR_SLEEPING: "STANDBY" +STR_ENTERING_SLEEP: "Standby..." +STR_BROWSE_FILES: "Durchsuchen" +STR_FILE_TRANSFER: "Datentransfer" +STR_SETTINGS_TITLE: "Einstellungen" +STR_CALIBRE_LIBRARY: "Calibre-Bibliothek" +STR_CONTINUE_READING: "Weiterlesen" +STR_NO_OPEN_BOOK: "Aktuell kein Buch" +STR_START_READING: "Lesen beginnen" +STR_BOOKS: "Bücher" +STR_NO_BOOKS_FOUND: "Keine Bücher" +STR_SELECT_CHAPTER: "Kapitel auswählen" +STR_NO_CHAPTERS: "Keine Kapitel" +STR_END_OF_BOOK: "Buchende" +STR_EMPTY_CHAPTER: "Kapitelende" +STR_INDEXING: "Indexieren…" +STR_MEMORY_ERROR: "Speicherfehler" +STR_PAGE_LOAD_ERROR: "Seitenladefehler" +STR_EMPTY_FILE: "Leere Datei" +STR_OUT_OF_BOUNDS: "Zu groß" +STR_LOADING: "Laden…" +STR_LOAD_XTC_FAILED: "Ladefehler bei XTC" +STR_LOAD_TXT_FAILED: "Ladefehler bei TXT" +STR_LOAD_EPUB_FAILED: "Ladefehler bei EPUB" +STR_SD_CARD_ERROR: "SD-Karten-Fehler" +STR_WIFI_NETWORKS: "WLAN-Netzwerke" +STR_NO_NETWORKS: "Kein WLAN gefunden" +STR_NETWORKS_FOUND: "%zu WLAN-Netzwerke gefunden" +STR_SCANNING: "Suchen..." +STR_CONNECTING: "Verbinden..." +STR_CONNECTED: "Verbunden!" +STR_CONNECTION_FAILED: "Verbindungsfehler" +STR_CONNECTION_TIMEOUT: "Verbindungs-Timeout" +STR_FORGET_NETWORK: "WLAN vergessen?" +STR_SAVE_PASSWORD: "Passwort speichern?" +STR_REMOVE_PASSWORD: "Passwort entfernen?" +STR_PRESS_OK_SCAN: "OK für neue Suche" +STR_PRESS_ANY_CONTINUE: "Beliebige Taste drücken" +STR_SELECT_HINT: "links/rechts: Auswahl | OK: Best" +STR_HOW_CONNECT: "Wie möchtest du dich verbinden?" +STR_JOIN_NETWORK: "Netzwerk beitreten" +STR_CREATE_HOTSPOT: "Hotspot erstellen" +STR_JOIN_DESC: "Mit einem bestehenden WLAN verbinden" +STR_HOTSPOT_DESC: "WLAN für andere erstellen" +STR_STARTING_HOTSPOT: "Hotspot starten…" +STR_HOTSPOT_MODE: "Hotspot-Modus" +STR_CONNECT_WIFI_HINT: "Gerät mit diesem WLAN verbinden" +STR_OPEN_URL_HINT: "Diese URL im Browser öffnen" +STR_OR_HTTP_PREFIX: "oder http://" +STR_SCAN_QR_HINT: "oder QR-Code mit dem Handy scannen:" +STR_CALIBRE_WIRELESS: "Calibre Wireless" +STR_CALIBRE_WEB_URL: "Calibre-Web-URL" +STR_CONNECT_WIRELESS: "Als Drahtlos-Gerät hinzufügen" +STR_NETWORK_LEGEND: "* = Verschlüsselt | + = Gespeichert" +STR_MAC_ADDRESS: "MAC-Adresse:" +STR_CHECKING_WIFI: "WLAN prüfen…" +STR_ENTER_WIFI_PASSWORD: "WLAN-Passwort eingeben" +STR_ENTER_TEXT: "Text eingeben" +STR_TO_PREFIX: "bis" +STR_CALIBRE_DISCOVERING: "Calibre finden..." +STR_CALIBRE_CONNECTING_TO: "Verbinden mit" +STR_CALIBRE_CONNECTED_TO: "Verbunden mit" +STR_CALIBRE_WAITING_COMMANDS: "Auf Befehle warten…" +STR_CONNECTION_FAILED_RETRYING: "(Keine Verbindung, wiederholen)" +STR_CALIBRE_DISCONNECTED: "Calibre getrennt" +STR_CALIBRE_WAITING_TRANSFER: "Auf Übertragung warten..." +STR_CALIBRE_TRANSFER_HINT: "Bei Übertragungsfehler \\n'Freien Speicher ign.' in den\\nCalibre-Einstellungen einschalten." +STR_CALIBRE_RECEIVING: "Empfange:" +STR_CALIBRE_RECEIVED: "Empfangen:" +STR_CALIBRE_WAITING_MORE: "Auf mehr warten…" +STR_CALIBRE_FAILED_CREATE_FILE: "Speicherfehler" +STR_CALIBRE_PASSWORD_REQUIRED: "Passwort nötig" +STR_CALIBRE_TRANSFER_INTERRUPTED: "Übertragung unterbrochen" +STR_CALIBRE_INSTRUCTION_1: "1) CrossPoint Reader-Plugin installieren" +STR_CALIBRE_INSTRUCTION_2: "2) Mit selbem WLAN verbinden" +STR_CALIBRE_INSTRUCTION_3: "3) In Calibre: \"An Gerät senden\"" +STR_CALIBRE_INSTRUCTION_4: "Bildschirm beim Senden offenlassen" +STR_CAT_DISPLAY: "Anzeige" +STR_CAT_READER: "Lesen" +STR_CAT_CONTROLS: "Bedienung" +STR_CAT_SYSTEM: "System" +STR_SLEEP_SCREEN: "Standby-Bild" +STR_SLEEP_COVER_MODE: "Standby-Bildmodus" +STR_STATUS_BAR: "Statusleiste" +STR_HIDE_BATTERY: "Batterie % ausblenden" +STR_EXTRA_SPACING: "Absatzabstand" +STR_TEXT_AA: "Schriftglättung" +STR_SHORT_PWR_BTN: "An-Taste kurz drücken" +STR_ORIENTATION: "Leseausrichtung" +STR_FRONT_BTN_LAYOUT: "Vorderes Tastenlayout" +STR_SIDE_BTN_LAYOUT: "Seitliche Tasten (Lesen)" +STR_LONG_PRESS_SKIP: "Langes Drücken springt Kap." +STR_FONT_FAMILY: "Lese-Schriftfamilie" +STR_EXT_READER_FONT: "Externe Schriftart" +STR_EXT_CHINESE_FONT: "Lese-Schriftart" +STR_EXT_UI_FONT: "Menü-Schriftart" +STR_FONT_SIZE: "Schriftgröße" +STR_LINE_SPACING: "Lese-Zeilenabstand" +STR_ASCII_LETTER_SPACING: "ASCII-Zeichenabstand" +STR_ASCII_DIGIT_SPACING: "ASCII-Ziffernabstand" +STR_CJK_SPACING: "CJK-Zeichenabstand" +STR_COLOR_MODE: "Farbmodus" +STR_SCREEN_MARGIN: "Lese-Seitenränder" +STR_PARA_ALIGNMENT: "Lese-Absatzausrichtung" +STR_HYPHENATION: "Silbentrennung" +STR_TIME_TO_SLEEP: "Standby nach" +STR_REFRESH_FREQ: "Anti-Ghosting nach" +STR_CALIBRE_SETTINGS: "Calibre-Einstellungen" +STR_KOREADER_SYNC: "KOReader-Synchr." +STR_CHECK_UPDATES: "Nach Updates suchen" +STR_LANGUAGE: "Sprache" +STR_SELECT_WALLPAPER: "Bildauswahl Standby" +STR_CLEAR_READING_CACHE: "Lese-Cache leeren" +STR_CALIBRE: "Calibre" +STR_USERNAME: "Benutzername" +STR_PASSWORD: "Passwort nötig" +STR_SYNC_SERVER_URL: "Sync-Server-URL" +STR_DOCUMENT_MATCHING: "Dateizuordnung" +STR_AUTHENTICATE: "Authentifizieren" +STR_KOREADER_USERNAME: "KOReader-Benutzername" +STR_KOREADER_PASSWORD: "KOReader-Passwort" +STR_FILENAME: "Dateiname" +STR_BINARY: "Binärdatei" +STR_SET_CREDENTIALS_FIRST: "Zuerst anmelden" +STR_WIFI_CONN_FAILED: "WLAN-Verbindung fehlgeschlagen" +STR_AUTHENTICATING: "Authentifizieren…" +STR_AUTH_SUCCESS: "Erfolgreich authentifiziert!" +STR_KOREADER_AUTH: "KOReader-Auth" +STR_SYNC_READY: "KOReader-Synchronisierung bereit" +STR_AUTH_FAILED: "Authentifizierung fehlg." +STR_DONE: "Erledigt" +STR_CLEAR_CACHE_WARNING_1: "Alle Buch-Caches werden geleert." +STR_CLEAR_CACHE_WARNING_2: "Lesefortschritt wird gelöscht!" +STR_CLEAR_CACHE_WARNING_3: "Bücher müssen beim Öffnen" +STR_CLEAR_CACHE_WARNING_4: "neu eingelesen werden." +STR_CLEARING_CACHE: "Cache leeren…" +STR_CACHE_CLEARED: "Cache geleert" +STR_ITEMS_REMOVED: "Einträge entfernt" +STR_FAILED_LOWER: "fehlgeschlagen" +STR_CLEAR_CACHE_FAILED: "Fehler beim Cache-Leeren" +STR_CHECK_SERIAL_OUTPUT: "Serielle Ausgabe prüfen" +STR_DARK: "Dunkel" +STR_LIGHT: "Hell" +STR_CUSTOM: "Eigenes" +STR_COVER: "Umschlag" +STR_NONE_OPT: "Leer" +STR_FIT: "Anpassen" +STR_CROP: "Zuschnitt" +STR_NO_PROGRESS: "Ohne Fortschr." +STR_FULL_OPT: "Vollst." +STR_NEVER: "Nie" +STR_IN_READER: "Beim Lesen" +STR_ALWAYS: "Immer" +STR_IGNORE: "Ignorieren" +STR_SLEEP: "Standby" +STR_PAGE_TURN: "Umblättern" +STR_PORTRAIT: "Hochformat" +STR_LANDSCAPE_CW: "Querformat rechts" +STR_INVERTED: "Invertiert" +STR_LANDSCAPE_CCW: "Querformat links" +STR_FRONT_LAYOUT_BCLR: "Zurück, Bst, L, R" +STR_FRONT_LAYOUT_LRBC: "L, R, Zurück, Bst" +STR_FRONT_LAYOUT_LBCR: "L, Zurück, Bst, R" +STR_PREV_NEXT: "Zurück/Weiter" +STR_NEXT_PREV: "Weiter/Zuürck" +STR_BOOKERLY: "Bookerly" +STR_NOTO_SANS: "Noto Sans" +STR_OPEN_DYSLEXIC: "Open Dyslexic" +STR_SMALL: "Klein" +STR_MEDIUM: "Mittel" +STR_LARGE: "Groß" +STR_X_LARGE: "Extragroß" +STR_TIGHT: "Eng" +STR_NORMAL: "Normal" +STR_WIDE: "Breit" +STR_JUSTIFY: "Blocksatz" +STR_ALIGN_LEFT: "Links" +STR_CENTER: "Zentriert" +STR_ALIGN_RIGHT: "Rechts" +STR_MIN_1: "1 Min" +STR_MIN_5: "5 Min" +STR_MIN_10: "10 Min" +STR_MIN_15: "15 Min" +STR_MIN_30: "30 Min" +STR_PAGES_1: "1 Seite" +STR_PAGES_5: "5 Seiten" +STR_PAGES_10: "10 Seiten" +STR_PAGES_15: "15 Seiten" +STR_PAGES_30: "30 Seiten" +STR_UPDATE: "Update" +STR_CHECKING_UPDATE: "Update suchen…" +STR_NEW_UPDATE: "Neues Update verfügbar!" +STR_CURRENT_VERSION: "Aktuelle Version:" +STR_NEW_VERSION: "Neue Version:" +STR_UPDATING: "Aktualisiere…" +STR_NO_UPDATE: "Kein Update verfügbar" +STR_UPDATE_FAILED: "Updatefehler" +STR_UPDATE_COMPLETE: "Update fertig" +STR_POWER_ON_HINT: "An-Knopf lang drücken, um neuzustarten" +STR_EXTERNAL_FONT: "Externe Schrift" +STR_BUILTIN_DISABLED: "Vorinstalliert (aus)" +STR_NO_ENTRIES: "Keine Einträge" +STR_DOWNLOADING: "Herunterladen…" +STR_DOWNLOAD_FAILED: "Ladefehler" +STR_ERROR_MSG: "Fehler:" +STR_UNNAMED: "Unbenannt" +STR_NO_SERVER_URL: "Keine Server-URL konfiguriert" +STR_FETCH_FEED_FAILED: "Feedfehler" +STR_PARSE_FEED_FAILED: "Feed-Format ungültig" +STR_NETWORK_PREFIX: "Netzwerk:" +STR_IP_ADDRESS_PREFIX: "IP-Adresse:" +STR_SCAN_QR_WIFI_HINT: "oder QR-Code mit dem Handy scannen für WLAN." +STR_ERROR_GENERAL_FAILURE: "Fehler: Allgemeiner Fehler" +STR_ERROR_NETWORK_NOT_FOUND: "Fehler: Kein Netzwerk" +STR_ERROR_CONNECTION_TIMEOUT: "Fehler: Zeitüberschreitung" +STR_SD_CARD: "SD-Karte" +STR_BACK: "« Zurück" +STR_EXIT: "« Verlassen" +STR_HOME: "« Start" +STR_SAVE: "« Speichern" +STR_SELECT: "Auswahl" +STR_TOGGLE: "Ändern" +STR_CONFIRM: "Bestätigen" +STR_CANCEL: "Abbrechen" +STR_CONNECT: "Verbinden" +STR_OPEN: "Öffnen" +STR_DOWNLOAD: "Herunterladen" +STR_RETRY: "Wiederh." +STR_YES: "Ja" +STR_NO: "Nein" +STR_STATE_ON: "An" +STR_STATE_OFF: "Aus" +STR_SET: "Gesetzt" +STR_NOT_SET: "Leer" +STR_DIR_LEFT: "Links" +STR_DIR_RIGHT: "Rechts" +STR_DIR_UP: "Hoch" +STR_DIR_DOWN: "Runter" +STR_CAPS_ON: "UMSCH" +STR_CAPS_OFF: "umsch" +STR_OK_BUTTON: "OK" +STR_ON_MARKER: "[AN]" +STR_SLEEP_COVER_FILTER: "Standby-Coverfilter" +STR_FILTER_CONTRAST: "Kontrast" +STR_STATUS_BAR_FULL_PERCENT: "Komplett + Prozent" +STR_STATUS_BAR_FULL_BOOK: "Komplett + Buch" +STR_STATUS_BAR_BOOK_ONLY: "Nur Buch" +STR_STATUS_BAR_FULL_CHAPTER: "Komplett + Kapitel" +STR_UI_THEME: "System-Design" +STR_THEME_CLASSIC: "Klassisch" +STR_THEME_LYRA: "Lyra" +STR_SUNLIGHT_FADING_FIX: "Anti-Verblassen" +STR_REMAP_FRONT_BUTTONS: "Vordere Tasten belegen" +STR_OPDS_BROWSER: "OPDS-Browser" +STR_COVER_CUSTOM: "Umschlag + Eigenes" +STR_RECENTS: "Zuletzt" +STR_MENU_RECENT_BOOKS: "Zuletzt gelesen" +STR_NO_RECENT_BOOKS: "Keine Bücher" +STR_CALIBRE_DESC: "Calibre-Übertragung (WLAN)" +STR_FORGET_AND_REMOVE: "WLAN entfernen & Passwort löschen?" +STR_FORGET_BUTTON: "WLAN entfernen" +STR_CALIBRE_STARTING: "Calibre starten…" +STR_CALIBRE_SETUP: "Installation" +STR_CALIBRE_STATUS: "Status" +STR_CLEAR_BUTTON: "Leeren" +STR_DEFAULT_VALUE: "Standard" +STR_REMAP_PROMPT: "Entsprechende Vordertaste drücken" +STR_UNASSIGNED: "Leer" +STR_ALREADY_ASSIGNED: "Bereits zugeordnet" +STR_REMAP_RESET_HINT: "Seitentaste hoch: Standard" +STR_REMAP_CANCEL_HINT: "Seitentaste runter: Abbrechen" +STR_HW_BACK_LABEL: "Zurück (1. Taste)" +STR_HW_CONFIRM_LABEL: "Bestätigen (2. Taste)" +STR_HW_LEFT_LABEL: "Links (3. Taste)" +STR_HW_RIGHT_LABEL: "Rechts (4. Taste)" +STR_GO_TO_PERCENT: "Gehe zu %" +STR_GO_HOME_BUTTON: "Zum Anfang" +STR_SYNC_PROGRESS: "Fortschritt synchronisieren" +STR_DELETE_CACHE: "Buch-Cache leeren" +STR_CHAPTER_PREFIX: "Kapitel:" +STR_PAGES_SEPARATOR: " Seiten | " +STR_BOOK_PREFIX: "Buch: " +STR_KBD_SHIFT: "umsch" +STR_KBD_SHIFT_CAPS: "UMSCH" +STR_KBD_LOCK: "FESTST" +STR_CALIBRE_URL_HINT: "Calibre: URL um /opds ergänzen" +STR_PERCENT_STEP_HINT: "links/rechts: 1% hoch/runter: 10%" +STR_SYNCING_TIME: "Zeit synchonisieren…" +STR_CALC_HASH: "Dokument-Hash berechnen…" +STR_HASH_FAILED: "Dokument-Hash fehlgeschlagen" +STR_FETCH_PROGRESS: "Externen Fortschritt abrufen..." +STR_UPLOAD_PROGRESS: "Fortschritt hochladen…" +STR_NO_CREDENTIALS_MSG: "Zugangsdaten fehlen" +STR_KOREADER_SETUP_HINT: "KOReader-Konto unter Einst. anlegen" +STR_PROGRESS_FOUND: "Gefunden!" +STR_REMOTE_LABEL: "Extern:" +STR_LOCAL_LABEL: "Lokal:" +STR_PAGE_OVERALL_FORMAT: " Seite %d, %.2f%% insgesamt" +STR_PAGE_TOTAL_OVERALL_FORMAT: " Seite %d/%d, %.2f%% insgesamt" +STR_DEVICE_FROM_FORMAT: " Von: %s" +STR_APPLY_REMOTE: "Ext. Fortschritt übern." +STR_UPLOAD_LOCAL: "Lokalen Fortschritt hochl." +STR_NO_REMOTE_MSG: "Kein externer Fortschritt" +STR_UPLOAD_PROMPT: "Aktuelle Position hochladen?" +STR_UPLOAD_SUCCESS: "Hochgeladen!" +STR_SYNC_FAILED_MSG: "Fehlgeschlagen" +STR_SECTION_PREFIX: "Abschnitt" +STR_UPLOAD: "Hochladen" +STR_BOOK_S_STYLE: "Buch-Stil" +STR_EMBEDDED_STYLE: "Eingebetteter Stil" +STR_OPDS_SERVER_URL: "OPDS-Server-URL" diff --git a/lib/I18n/translations/portuguese.yaml b/lib/I18n/translations/portuguese.yaml new file mode 100644 index 00000000..484a33f1 --- /dev/null +++ b/lib/I18n/translations/portuguese.yaml @@ -0,0 +1,317 @@ +_language_name: "Português (Brasil)" +_language_code: "PORTUGUESE" +_order: "5" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "INICIANDO" +STR_SLEEPING: "EM REPOUSO" +STR_ENTERING_SLEEP: "Entrando em repouso..." +STR_BROWSE_FILES: "Arquivos" +STR_FILE_TRANSFER: "Transferência" +STR_SETTINGS_TITLE: "Configurações" +STR_CALIBRE_LIBRARY: "Biblioteca do Calibre" +STR_CONTINUE_READING: "Continuar lendo" +STR_NO_OPEN_BOOK: "Nenhum livro aberto" +STR_START_READING: "Comece a ler abaixo" +STR_BOOKS: "Livros" +STR_NO_BOOKS_FOUND: "Nenhum livro encontrado" +STR_SELECT_CHAPTER: "Escolher capítulo" +STR_NO_CHAPTERS: "Sem capítulos" +STR_END_OF_BOOK: "Fim do livro" +STR_EMPTY_CHAPTER: "Capítulo vazio" +STR_INDEXING: "Indexando..." +STR_MEMORY_ERROR: "Erro de memória" +STR_PAGE_LOAD_ERROR: "Erro página" +STR_EMPTY_FILE: "Arquivo vazio" +STR_OUT_OF_BOUNDS: "Fora dos limites" +STR_LOADING: "Carregando..." +STR_LOAD_XTC_FAILED: "Falha ao carregar XTC" +STR_LOAD_TXT_FAILED: "Falha ao carregar TXT" +STR_LOAD_EPUB_FAILED: "Falha ao carregar EPUB" +STR_SD_CARD_ERROR: "Erro no cartão SD" +STR_WIFI_NETWORKS: "Redes Wi‑Fi" +STR_NO_NETWORKS: "Sem redes" +STR_NETWORKS_FOUND: "%zu redes encontradas" +STR_SCANNING: "Procurando..." +STR_CONNECTING: "Conectando..." +STR_CONNECTED: "Conectado!" +STR_CONNECTION_FAILED: "Falha na conexão" +STR_CONNECTION_TIMEOUT: "Tempo limite conexão" +STR_FORGET_NETWORK: "Esquecer rede?" +STR_SAVE_PASSWORD: "Salvar senha a próxima vez?" +STR_REMOVE_PASSWORD: "Remover senha salva?" +STR_PRESS_OK_SCAN: "Pressione OK procurar novamente" +STR_PRESS_ANY_CONTINUE: "Pressione qualquer botão continuar" +STR_SELECT_HINT: "ESQ/DIR: Escolher | OK: Confirmar" +STR_HOW_CONNECT: "Como você gostaria se conectar?" +STR_JOIN_NETWORK: "Entrar em uma rede" +STR_CREATE_HOTSPOT: "Criar hotspot" +STR_JOIN_DESC: "Conecte-se a uma rede Wi‑Fi existente" +STR_HOTSPOT_DESC: "Crie uma rede Wi‑Fi outras pessoas entrarem" +STR_STARTING_HOTSPOT: "Iniciando hotspot..." +STR_HOTSPOT_MODE: "Modo hotspot" +STR_CONNECT_WIFI_HINT: "Conecte seu dispositivo a esta rede Wi‑Fi" +STR_OPEN_URL_HINT: "Abra este URL seu navegador" +STR_OR_HTTP_PREFIX: "ou http://" +STR_SCAN_QR_HINT: "ou escaneie o QR code com seu celular:" +STR_CALIBRE_WIRELESS: "Calibre sem fio" +STR_CALIBRE_WEB_URL: "URL do Calibre Web" +STR_CONNECT_WIRELESS: "Conectar como dispositivo sem fio" +STR_NETWORK_LEGEND: "* = Criptografada | + = Salva" +STR_MAC_ADDRESS: "Endereço MAC:" +STR_CHECKING_WIFI: "Verificando Wi‑Fi..." +STR_ENTER_WIFI_PASSWORD: "Digite a senha Wi‑Fi" +STR_ENTER_TEXT: "Inserir texto" +STR_TO_PREFIX: "para" +STR_CALIBRE_DISCOVERING: "Procurando o Calibre..." +STR_CALIBRE_CONNECTING_TO: "Conectando a" +STR_CALIBRE_CONNECTED_TO: "Conectado a" +STR_CALIBRE_WAITING_COMMANDS: "Aguardando comandos..." +STR_CONNECTION_FAILED_RETRYING: "(Falha conexão, tentando novamente)" +STR_CALIBRE_DISCONNECTED: "Calibre desconectado" +STR_CALIBRE_WAITING_TRANSFER: "Aguardando transferência..." +STR_CALIBRE_TRANSFER_HINT: "Se a transferência falhar, ative\n\\n'Ignorar espaço livre'\\n nas \\nconfigurações do\nplugin SmartDevice\\n Calibre." +STR_CALIBRE_RECEIVING: "Recebendo:" +STR_CALIBRE_RECEIVED: "Recebido:" +STR_CALIBRE_WAITING_MORE: "Aguardando mais..." +STR_CALIBRE_FAILED_CREATE_FILE: "Falha ao criar o arquivo" +STR_CALIBRE_PASSWORD_REQUIRED: "Senha obrigatória" +STR_CALIBRE_TRANSFER_INTERRUPTED: "Transf. interrompida" +STR_CALIBRE_INSTRUCTION_1: "1) Instale o plugin CrossPoint Reader" +STR_CALIBRE_INSTRUCTION_2: "2) Esteja mesma rede Wi‑Fi" +STR_CALIBRE_INSTRUCTION_3: "3) No Calibre: \"Enviar o dispositivo\"" +STR_CALIBRE_INSTRUCTION_4: "\"Mantenha esta tela aberta durante o envio\"" +STR_CAT_DISPLAY: "Tela" +STR_CAT_READER: "Leitor" +STR_CAT_CONTROLS: "Controles" +STR_CAT_SYSTEM: "Sistema" +STR_SLEEP_SCREEN: "Tela de repouso" +STR_SLEEP_COVER_MODE: "Modo capa tela repouso" +STR_STATUS_BAR: "Barra de status" +STR_HIDE_BATTERY: "Ocultar % da bateria" +STR_EXTRA_SPACING: "Espaço de parágrafos extra" +STR_TEXT_AA: "Suavização de texto" +STR_SHORT_PWR_BTN: "Clique curto botão ligar" +STR_ORIENTATION: "Orientação de leitura" +STR_FRONT_BTN_LAYOUT: "Disposição botões frontais" +STR_SIDE_BTN_LAYOUT: "Disposição botões laterais" +STR_LONG_PRESS_SKIP: "Pular capítulo com pressão longa" +STR_FONT_FAMILY: "Fonte do leitor" +STR_EXT_READER_FONT: "Fonte leitor externo" +STR_EXT_CHINESE_FONT: "Fonte do leitor" +STR_EXT_UI_FONT: "Fonte da interface" +STR_FONT_SIZE: "Tam. fonte UI" +STR_LINE_SPACING: "Espaçamento entre linhas" +STR_ASCII_LETTER_SPACING: "Espaçamento letras ASCII" +STR_ASCII_DIGIT_SPACING: "Espaçamento dígitos ASCII" +STR_CJK_SPACING: "Espaçamento CJK" +STR_COLOR_MODE: "Modo de cor" +STR_SCREEN_MARGIN: "Margens da tela" +STR_PARA_ALIGNMENT: "Alinhamento parágrafo" +STR_HYPHENATION: "Hifenização" +STR_TIME_TO_SLEEP: "Tempo para repousar" +STR_REFRESH_FREQ: "Frequência atualização" +STR_CALIBRE_SETTINGS: "Configuração do Calibre" +STR_KOREADER_SYNC: "Sincronização KOReader" +STR_CHECK_UPDATES: "Verificar atualizações" +STR_LANGUAGE: "Idioma" +STR_SELECT_WALLPAPER: "Escolher papel parede" +STR_CLEAR_READING_CACHE: "Limpar cache de leitura" +STR_CALIBRE: "Calibre" +STR_USERNAME: "Nome de usuário" +STR_PASSWORD: "Senha" +STR_SYNC_SERVER_URL: "URL servidor sincronização" +STR_DOCUMENT_MATCHING: "Documento correspondente" +STR_AUTHENTICATE: "Autenticar" +STR_KOREADER_USERNAME: "Usuário do KOReader" +STR_KOREADER_PASSWORD: "Senha do KOReader" +STR_FILENAME: "Nome do arquivo" +STR_BINARY: "Binário" +STR_SET_CREDENTIALS_FIRST: "Defina as credenciais primeiro" +STR_WIFI_CONN_FAILED: "Falha na conexão Wi‑Fi" +STR_AUTHENTICATING: "Autenticando..." +STR_AUTH_SUCCESS: "Autenticado com sucesso!" +STR_KOREADER_AUTH: "Autenticação KOReader" +STR_SYNC_READY: "A sincronização KOReader está pronta uso" +STR_AUTH_FAILED: "Falha na autenticação" +STR_DONE: "Feito" +STR_CLEAR_CACHE_WARNING_1: "Isso vai limpar todos os dados livros em cache." +STR_CLEAR_CACHE_WARNING_2: "Todo o progresso de leitura será perdido!" +STR_CLEAR_CACHE_WARNING_3: "Os livros precisarão ser reindexados" +STR_CLEAR_CACHE_WARNING_4: "quando forem abertos novamente." +STR_CLEARING_CACHE: "Limpando cache..." +STR_CACHE_CLEARED: "Cache limpo" +STR_ITEMS_REMOVED: "itens removidos" +STR_FAILED_LOWER: "falhou" +STR_CLEAR_CACHE_FAILED: "Falha ao limpar o cache" +STR_CHECK_SERIAL_OUTPUT: "Ver saída serial" +STR_DARK: "Escuro" +STR_LIGHT: "Claro" +STR_CUSTOM: "Personalizado" +STR_COVER: "Capa" +STR_NONE_OPT: "Nenhum" +STR_FIT: "Ajustar" +STR_CROP: "Recortar" +STR_NO_PROGRESS: "Sem progresso" +STR_FULL_OPT: "Completo" +STR_NEVER: "Nunca" +STR_IN_READER: "No leitor" +STR_ALWAYS: "Sempre" +STR_IGNORE: "Ignorar" +STR_SLEEP: "Repouso" +STR_PAGE_TURN: "Virar página" +STR_PORTRAIT: "Retrato" +STR_LANDSCAPE_CW: "Paisagem H" +STR_INVERTED: "Invertido" +STR_LANDSCAPE_CCW: "Paisagem AH" +STR_FRONT_LAYOUT_BCLR: "Vol, Conf, Esq, Dir" +STR_FRONT_LAYOUT_LRBC: "Esq, Dir, Vol, Conf" +STR_FRONT_LAYOUT_LBCR: "Esq, Vol, Conf, Dir" +STR_PREV_NEXT: "Ant/Próx" +STR_NEXT_PREV: "Próx/Ant" +STR_BOOKERLY: "Bookerly" +STR_NOTO_SANS: "Noto Sans" +STR_OPEN_DYSLEXIC: "Open Dyslexic" +STR_SMALL: "Pequeno" +STR_MEDIUM: "Médio" +STR_LARGE: "Grande" +STR_X_LARGE: "Extra grande" +STR_TIGHT: "Apertado" +STR_NORMAL: "Normal" +STR_WIDE: "Largo" +STR_JUSTIFY: "Justificar" +STR_ALIGN_LEFT: "Esquerda" +STR_CENTER: "Centralizar" +STR_ALIGN_RIGHT: "Direita" +STR_MIN_1: "1 min" +STR_MIN_5: "5 min" +STR_MIN_10: "10 min" +STR_MIN_15: "15 min" +STR_MIN_30: "30 min" +STR_PAGES_1: "1 página" +STR_PAGES_5: "5 páginas" +STR_PAGES_10: "10 páginas" +STR_PAGES_15: "15 páginas" +STR_PAGES_30: "30 páginas" +STR_UPDATE: "Atualizar" +STR_CHECKING_UPDATE: "Verificando atualização..." +STR_NEW_UPDATE: "Nova atualização disponível!" +STR_CURRENT_VERSION: "Versão atual:" +STR_NEW_VERSION: "Nova versão:" +STR_UPDATING: "Atualizando..." +STR_NO_UPDATE: "Nenhuma atualização disponível" +STR_UPDATE_FAILED: "Falha na atualização" +STR_UPDATE_COMPLETE: "Atualização concluída" +STR_POWER_ON_HINT: "Pressione e segure o botão energia ligar novamente" +STR_EXTERNAL_FONT: "Fonte externa" +STR_BUILTIN_DISABLED: "Integrada (desativada)" +STR_NO_ENTRIES: "Nenhum entries encontrado" +STR_DOWNLOADING: "Baixando..." +STR_DOWNLOAD_FAILED: "Falha no download" +STR_ERROR_MSG: "Erro:" +STR_UNNAMED: "Sem nome" +STR_NO_SERVER_URL: "Nenhum URL servidor configurado" +STR_FETCH_FEED_FAILED: "Falha ao buscar o feed" +STR_PARSE_FEED_FAILED: "Falha ao interpretar o feed" +STR_NETWORK_PREFIX: "Rede:" +STR_IP_ADDRESS_PREFIX: "Endereço IP:" +STR_SCAN_QR_WIFI_HINT: "ou escaneie o QR code com seu celular conectar ao Wi‑Fi." +STR_ERROR_GENERAL_FAILURE: "Erro: falha geral" +STR_ERROR_NETWORK_NOT_FOUND: "Erro: rede não encontrada" +STR_ERROR_CONNECTION_TIMEOUT: "Erro: tempo limite conexão" +STR_SD_CARD: "Cartão SD" +STR_BACK: "« Voltar" +STR_EXIT: "« Sair" +STR_HOME: "« Início" +STR_SAVE: "« Salvar" +STR_SELECT: "Escolher" +STR_TOGGLE: "Alternar" +STR_CONFIRM: "Confirmar" +STR_CANCEL: "Cancelar" +STR_CONNECT: "Conectar" +STR_OPEN: "Abrir" +STR_DOWNLOAD: "Baixar" +STR_RETRY: "Tentar novamente" +STR_YES: "Sim" +STR_NO: "Não" +STR_STATE_ON: "LIG." +STR_STATE_OFF: "DESL." +STR_SET: "Definir" +STR_NOT_SET: "Não definido" +STR_DIR_LEFT: "Esquerda" +STR_DIR_RIGHT: "Direita" +STR_DIR_UP: "Cima" +STR_DIR_DOWN: "Baixo" +STR_CAPS_ON: "CAPS" +STR_CAPS_OFF: "caps" +STR_OK_BUTTON: "OK" +STR_ON_MARKER: "[LIGADO]" +STR_SLEEP_COVER_FILTER: "Filtro capa tela repouso" +STR_FILTER_CONTRAST: "Contraste" +STR_STATUS_BAR_FULL_PERCENT: "Completa c/ porcentagem" +STR_STATUS_BAR_FULL_BOOK: "Completa c/ barra livro" +STR_STATUS_BAR_BOOK_ONLY: "Só barra do livro" +STR_STATUS_BAR_FULL_CHAPTER: "Completa c/ barra capítulo" +STR_UI_THEME: "Tema da interface" +STR_THEME_CLASSIC: "Clássico" +STR_THEME_LYRA: "Lyra" +STR_SUNLIGHT_FADING_FIX: "Ajuste desbotamento ao sol" +STR_REMAP_FRONT_BUTTONS: "Remapear botões frontais" +STR_OPDS_BROWSER: "Navegador OPDS" +STR_COVER_CUSTOM: "Capa + personalizado" +STR_RECENTS: "Recentes" +STR_MENU_RECENT_BOOKS: "Livros recentes" +STR_NO_RECENT_BOOKS: "Sem livros recentes" +STR_CALIBRE_DESC: "Usar transferências sem fio Calibre" +STR_FORGET_AND_REMOVE: "Esquecer a rede e remover a senha salva?" +STR_FORGET_BUTTON: "Esquecer rede" +STR_CALIBRE_STARTING: "Iniciando Calibre..." +STR_CALIBRE_SETUP: "Configuração" +STR_CALIBRE_STATUS: "Status" +STR_CLEAR_BUTTON: "Limpar" +STR_DEFAULT_VALUE: "Padrão" +STR_REMAP_PROMPT: "Pressione um botão frontal cada função" +STR_UNASSIGNED: "Não atribuído" +STR_ALREADY_ASSIGNED: "Já atribuído" +STR_REMAP_RESET_HINT: "Botão lateral cima: redefinir o disposição padrão" +STR_REMAP_CANCEL_HINT: "Botão lateral baixo: cancelar remapeamento" +STR_HW_BACK_LABEL: "Voltar (1º botão)" +STR_HW_CONFIRM_LABEL: "Confirmar (2º botão)" +STR_HW_LEFT_LABEL: "Esquerda (3º botão)" +STR_HW_RIGHT_LABEL: "Direita (4º botão)" +STR_GO_TO_PERCENT: "Ir para %" +STR_GO_HOME_BUTTON: "Ir para o início" +STR_SYNC_PROGRESS: "Sincronizar progresso" +STR_DELETE_CACHE: "Excluir cache do livro" +STR_CHAPTER_PREFIX: "Capítulo:" +STR_PAGES_SEPARATOR: "páginas |" +STR_BOOK_PREFIX: "Livro:" +STR_KBD_SHIFT: "shift" +STR_KBD_SHIFT_CAPS: "SHIFT" +STR_KBD_LOCK: "TRAVAR" +STR_CALIBRE_URL_HINT: "Para o Calibre, adicione /opds ao seu URL" +STR_PERCENT_STEP_HINT: "Esq/Dir: 1% Cima/Baixo: 10%" +STR_SYNCING_TIME: "Sincronizando horário..." +STR_CALC_HASH: "Calculando hash documento..." +STR_HASH_FAILED: "Falha ao calcular o hash documento" +STR_FETCH_PROGRESS: "Buscando progresso remoto..." +STR_UPLOAD_PROGRESS: "Enviando progresso..." +STR_NO_CREDENTIALS_MSG: "Nenhuma credencial configurada" +STR_KOREADER_SETUP_HINT: "Configure a conta do KOReader em Config." +STR_PROGRESS_FOUND: "Progresso encontrado!" +STR_REMOTE_LABEL: "Remoto:" +STR_LOCAL_LABEL: "Local:" +STR_PAGE_OVERALL_FORMAT: "Página %d, %.2f%% total" +STR_PAGE_TOTAL_OVERALL_FORMAT: "Página %d/%d, %.2f%% total" +STR_DEVICE_FROM_FORMAT: "De: %s" +STR_APPLY_REMOTE: "Aplicar progresso remoto" +STR_UPLOAD_LOCAL: "Enviar progresso local" +STR_NO_REMOTE_MSG: "Nenhum progresso remoto encontrado" +STR_UPLOAD_PROMPT: "Enviar posição atual?" +STR_UPLOAD_SUCCESS: "Progresso enviado!" +STR_SYNC_FAILED_MSG: "Falha na sincronização" +STR_SECTION_PREFIX: "Seção" +STR_UPLOAD: "Enviar" +STR_BOOK_S_STYLE: "Estilo do livro" +STR_EMBEDDED_STYLE: "Estilo embutido" +STR_OPDS_SERVER_URL: "URL do servidor OPDS" diff --git a/lib/I18n/translations/russia.yaml b/lib/I18n/translations/russia.yaml new file mode 100644 index 00000000..7307f700 --- /dev/null +++ b/lib/I18n/translations/russia.yaml @@ -0,0 +1,317 @@ +_language_name: "Русский" +_language_code: "RUSSIAN" +_order: "6" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "Загрузка" +STR_SLEEPING: "Спящий режим" +STR_ENTERING_SLEEP: "Переход в сон..." +STR_BROWSE_FILES: "Обзор файлов" +STR_FILE_TRANSFER: "Передача файлов" +STR_SETTINGS_TITLE: "Настройки" +STR_CALIBRE_LIBRARY: "Библиотека Calibre" +STR_CONTINUE_READING: "Продолжить чтение" +STR_NO_OPEN_BOOK: "Нет открытой книги" +STR_START_READING: "Начать чтение ниже" +STR_BOOKS: "Книги" +STR_NO_BOOKS_FOUND: "Книги не найдены" +STR_SELECT_CHAPTER: "Выберите главу" +STR_NO_CHAPTERS: "Глав нет" +STR_END_OF_BOOK: "Конец книги" +STR_EMPTY_CHAPTER: "Пустая глава" +STR_INDEXING: "Индексация..." +STR_MEMORY_ERROR: "Ошибка памяти" +STR_PAGE_LOAD_ERROR: "Ошибка загрузки страницы" +STR_EMPTY_FILE: "Пустой файл" +STR_OUT_OF_BOUNDS: "Выход за пределы" +STR_LOADING: "Загрузка..." +STR_LOAD_XTC_FAILED: "Не удалось загрузить XTC" +STR_LOAD_TXT_FAILED: "Не удалось загрузить TXT" +STR_LOAD_EPUB_FAILED: "Не удалось загрузить EPUB" +STR_SD_CARD_ERROR: "Ошибка SD-карты" +STR_WIFI_NETWORKS: "Wi-Fi сети" +STR_NO_NETWORKS: "Сети не найдены" +STR_NETWORKS_FOUND: "Найдено сетей: %zu" +STR_SCANNING: "Сканирование..." +STR_CONNECTING: "Подключение..." +STR_CONNECTED: "Подключено!" +STR_CONNECTION_FAILED: "Ошибка подключения" +STR_CONNECTION_TIMEOUT: "Тайм-аут подключения" +STR_FORGET_NETWORK: "Забыть сеть?" +STR_SAVE_PASSWORD: "Сохранить пароль?" +STR_REMOVE_PASSWORD: "Удалить сохранённый пароль?" +STR_PRESS_OK_SCAN: "Нажмите OK для повторного поиска" +STR_PRESS_ANY_CONTINUE: "Нажмите любую кнопку" +STR_SELECT_HINT: "ВЛЕВО/ВПРАВО: выбор | OK: подтвердить" +STR_HOW_CONNECT: "Как вы хотите подключиться?" +STR_JOIN_NETWORK: "Подключиться к сети" +STR_CREATE_HOTSPOT: "Создать точку доступа" +STR_JOIN_DESC: "Подключение к существующей сети Wi-Fi" +STR_HOTSPOT_DESC: "Создать сеть Wi-Fi для подключения других" +STR_STARTING_HOTSPOT: "Запуск точки доступа..." +STR_HOTSPOT_MODE: "Режим точки доступа" +STR_CONNECT_WIFI_HINT: "Подключите устройство к этой сети Wi-Fi" +STR_OPEN_URL_HINT: "Откройте этот адрес в браузере" +STR_OR_HTTP_PREFIX: "или http://" +STR_SCAN_QR_HINT: "или отсканируйте QR-код:" +STR_CALIBRE_WIRELESS: "Calibre по Wi-Fi" +STR_CALIBRE_WEB_URL: "Web-адрес Calibre" +STR_CONNECT_WIRELESS: "Подключить как беспроводное устройство" +STR_NETWORK_LEGEND: "* = Защищена | + = Сохранена" +STR_MAC_ADDRESS: "MAC-адрес:" +STR_CHECKING_WIFI: "Проверка Wi-Fi..." +STR_ENTER_WIFI_PASSWORD: "Введите пароль Wi-Fi" +STR_ENTER_TEXT: "Введите текст" +STR_TO_PREFIX: "к " +STR_CALIBRE_DISCOVERING: "Поиск Calibre..." +STR_CALIBRE_CONNECTING_TO: "Подключение к " +STR_CALIBRE_CONNECTED_TO: "Подключено к " +STR_CALIBRE_WAITING_COMMANDS: "Ожидание команд..." +STR_CONNECTION_FAILED_RETRYING: "(Ошибка подключения" +STR_CALIBRE_DISCONNECTED: "Соединение с Calibre разорвано" +STR_CALIBRE_WAITING_TRANSFER: "Ожидание передачи..." +STR_CALIBRE_TRANSFER_HINT: "Если передача не удаётся" +STR_CALIBRE_RECEIVING: "Получение:" +STR_CALIBRE_RECEIVED: "Получено:" +STR_CALIBRE_WAITING_MORE: "Ожидание следующих файлов..." +STR_CALIBRE_FAILED_CREATE_FILE: "Не удалось создать файл" +STR_CALIBRE_PASSWORD_REQUIRED: "Требуется пароль" +STR_CALIBRE_TRANSFER_INTERRUPTED: "Передача прервана" +STR_CALIBRE_INSTRUCTION_1: "1) Установите плагин CrossPoint Reader" +STR_CALIBRE_INSTRUCTION_2: "2) Подключитесь к той же сети Wi-Fi" +STR_CALIBRE_INSTRUCTION_3: "3) В Calibre выберите: «Отправить на устройство»" +STR_CALIBRE_INSTRUCTION_4: "Не закрывайте этот экран во время отправки" +STR_CAT_DISPLAY: "Экран" +STR_CAT_READER: "Чтение" +STR_CAT_CONTROLS: "Управление" +STR_CAT_SYSTEM: "Система" +STR_SLEEP_SCREEN: "Экран сна" +STR_SLEEP_COVER_MODE: "Режим обложки сна" +STR_STATUS_BAR: "Строка состояния" +STR_HIDE_BATTERY: "Скрыть % батареи" +STR_EXTRA_SPACING: "Доп. интервал абзаца" +STR_TEXT_AA: "Сглаживание текста" +STR_SHORT_PWR_BTN: "Короткое нажатие PWR" +STR_ORIENTATION: "Ориентация чтения" +STR_FRONT_BTN_LAYOUT: "Боковые кнопки" +STR_SIDE_BTN_LAYOUT: "Боковые кнопки" +STR_LONG_PRESS_SKIP: "Долгое нажатие - смена главы" +STR_FONT_FAMILY: "Шрифт чтения" +STR_EXT_READER_FONT: "Внешний шрифт чтения" +STR_EXT_CHINESE_FONT: "Шрифт CJK" +STR_EXT_UI_FONT: "Шрифт интерфейса" +STR_FONT_SIZE: "Размер шрифта интерфейса" +STR_LINE_SPACING: "Межстрочный интервал" +STR_ASCII_LETTER_SPACING: "Интервал букв ASCII" +STR_ASCII_DIGIT_SPACING: "Интервал цифр ASCII" +STR_CJK_SPACING: "Интервал CJK" +STR_COLOR_MODE: "Цветовой режим" +STR_SCREEN_MARGIN: "Поля экрана" +STR_PARA_ALIGNMENT: "Выравнивание абзаца" +STR_HYPHENATION: "Перенос слов" +STR_TIME_TO_SLEEP: "Сон Через" +STR_REFRESH_FREQ: "Частота обновления" +STR_CALIBRE_SETTINGS: "Настройки Calibre" +STR_KOREADER_SYNC: "Синхронизация KOReader" +STR_CHECK_UPDATES: "Проверить обновления" +STR_LANGUAGE: "Язык" +STR_SELECT_WALLPAPER: "Выбрать обои" +STR_CLEAR_READING_CACHE: "Очистить кэш чтения" +STR_CALIBRE: "Calibre" +STR_USERNAME: "Имя пользователя" +STR_PASSWORD: "Пароль" +STR_SYNC_SERVER_URL: "URL сервера синхронизации" +STR_DOCUMENT_MATCHING: "Сопоставление документов" +STR_AUTHENTICATE: "Авторизация" +STR_KOREADER_USERNAME: "Имя пользователя KOReader" +STR_KOREADER_PASSWORD: "Пароль KOReader" +STR_FILENAME: "Имя файла" +STR_BINARY: "Бинарный" +STR_SET_CREDENTIALS_FIRST: "Сначала укажите данные" +STR_WIFI_CONN_FAILED: "Не удалось подключиться к Wi-Fi" +STR_AUTHENTICATING: "Авторизация..." +STR_AUTH_SUCCESS: "Авторизация успешна!" +STR_KOREADER_AUTH: "Авторизация KOReader" +STR_SYNC_READY: "Синхронизация KOReader готова" +STR_AUTH_FAILED: "Ошибка авторизации" +STR_DONE: "Готово" +STR_CLEAR_CACHE_WARNING_1: "Будут удалены все данные кэша книг." +STR_CLEAR_CACHE_WARNING_2: "Весь прогресс чтения будет потерян!" +STR_CLEAR_CACHE_WARNING_3: "Книги потребуется переиндексировать" +STR_CLEAR_CACHE_WARNING_4: "при повторном открытии." +STR_CLEARING_CACHE: "Очистка кэша..." +STR_CACHE_CLEARED: "Кэш очищен" +STR_ITEMS_REMOVED: "элементов удалено" +STR_FAILED_LOWER: "ошибка" +STR_CLEAR_CACHE_FAILED: "Не удалось очистить кэш" +STR_CHECK_SERIAL_OUTPUT: "Проверьте вывод по UART для деталей" +STR_DARK: "Тёмный" +STR_LIGHT: "Светлый" +STR_CUSTOM: "Свой" +STR_COVER: "Обложка" +STR_NONE_OPT: "Нет" +STR_FIT: "Вписать" +STR_CROP: "Обрезать" +STR_NO_PROGRESS: "Без прогресса" +STR_FULL_OPT: "Полная" +STR_NEVER: "Никогда" +STR_IN_READER: "В режиме чтения" +STR_ALWAYS: "Всегда" +STR_IGNORE: "Игнорировать" +STR_SLEEP: "Сон" +STR_PAGE_TURN: "Перелистывание" +STR_PORTRAIT: "Портрет" +STR_LANDSCAPE_CW: "Ландшафт (CW)" +STR_INVERTED: "Инверсия" +STR_LANDSCAPE_CCW: "Ландшафт (CCW)" +STR_FRONT_LAYOUT_BCLR: "Наз" +STR_FRONT_LAYOUT_LRBC: "Лев" +STR_FRONT_LAYOUT_LBCR: "Лев" +STR_PREV_NEXT: "Назад/Вперёд" +STR_NEXT_PREV: "Вперёд/Назад" +STR_BOOKERLY: "Bookerly" +STR_NOTO_SANS: "Noto Sans" +STR_OPEN_DYSLEXIC: "Open Dyslexic" +STR_SMALL: "Маленький" +STR_MEDIUM: "Средний" +STR_LARGE: "Большой" +STR_X_LARGE: "Очень большой" +STR_TIGHT: "Узкий" +STR_NORMAL: "Обычный" +STR_WIDE: "Широкий" +STR_JUSTIFY: "По ширине" +STR_ALIGN_LEFT: "По левому краю" +STR_CENTER: "По центру" +STR_ALIGN_RIGHT: "По правому краю" +STR_MIN_1: "1 мин" +STR_MIN_5: "5 мин" +STR_MIN_10: "10 мин" +STR_MIN_15: "15 мин" +STR_MIN_30: "30 мин" +STR_PAGES_1: "1 стр." +STR_PAGES_5: "5 стр." +STR_PAGES_10: "10 стр." +STR_PAGES_15: "15 стр." +STR_PAGES_30: "30 стр." +STR_UPDATE: "Обновление" +STR_CHECKING_UPDATE: "Проверка обновлений..." +STR_NEW_UPDATE: "Доступно новое обновление!" +STR_CURRENT_VERSION: "Текущая версия:" +STR_NEW_VERSION: "Новая версия:" +STR_UPDATING: "Обновление..." +STR_NO_UPDATE: "Обновлений нет" +STR_UPDATE_FAILED: "Ошибка обновления" +STR_UPDATE_COMPLETE: "Обновление завершено" +STR_POWER_ON_HINT: "Удерживайте кнопку питания для включения" +STR_EXTERNAL_FONT: "Внешний шрифт" +STR_BUILTIN_DISABLED: "Встроенный (отключён)" +STR_NO_ENTRIES: "Записи не найдены" +STR_DOWNLOADING: "Загрузка..." +STR_DOWNLOAD_FAILED: "Ошибка загрузки" +STR_ERROR_MSG: "Ошибка:" +STR_UNNAMED: "Без имени" +STR_NO_SERVER_URL: "URL сервера не настроен" +STR_FETCH_FEED_FAILED: "Не удалось получить ленту" +STR_PARSE_FEED_FAILED: "Не удалось обработать ленту" +STR_NETWORK_PREFIX: "Сеть:" +STR_IP_ADDRESS_PREFIX: "IP-адрес:" +STR_SCAN_QR_WIFI_HINT: "или отсканируйте QR-код для подключения к Wi-Fi." +STR_ERROR_GENERAL_FAILURE: "Ошибка: Общая ошибка" +STR_ERROR_NETWORK_NOT_FOUND: "Ошибка: Сеть не найдена" +STR_ERROR_CONNECTION_TIMEOUT: "Ошибка: Тайм-аут соединения" +STR_SD_CARD: "SD-карта" +STR_BACK: "« Назад" +STR_EXIT: "« Выход" +STR_HOME: "« Главная" +STR_SAVE: "« Сохранить" +STR_SELECT: "Выбрать" +STR_TOGGLE: "Выбор" +STR_CONFIRM: "Подтв." +STR_CANCEL: "Отмена" +STR_CONNECT: "Подкл." +STR_OPEN: "Открыть" +STR_DOWNLOAD: "Скачать" +STR_RETRY: "Повторить" +STR_YES: "Да" +STR_NO: "Нет" +STR_STATE_ON: "ВКЛ" +STR_STATE_OFF: "ВЫКЛ" +STR_SET: "Установлено" +STR_NOT_SET: "Не установлено" +STR_DIR_LEFT: "Влево" +STR_DIR_RIGHT: "Вправо" +STR_DIR_UP: "Вверх" +STR_DIR_DOWN: "Вниз" +STR_CAPS_ON: "CAPS" +STR_CAPS_OFF: "caps" +STR_OK_BUTTON: "OK" +STR_ON_MARKER: "[ВКЛ]" +STR_SLEEP_COVER_FILTER: "Фильтр обложки сна" +STR_FILTER_CONTRAST: "Контраст" +STR_STATUS_BAR_FULL_PERCENT: "Полная + %" +STR_STATUS_BAR_FULL_BOOK: "Полная + шкала книги" +STR_STATUS_BAR_BOOK_ONLY: "Только шкала книги" +STR_STATUS_BAR_FULL_CHAPTER: "Полная + шкала главы" +STR_UI_THEME: "Тема интерфейса" +STR_THEME_CLASSIC: "Классическая" +STR_THEME_LYRA: "Lyra" +STR_SUNLIGHT_FADING_FIX: "Компенсация выцветания" +STR_REMAP_FRONT_BUTTONS: "Переназначить передние кнопки" +STR_OPDS_BROWSER: "OPDS браузер" +STR_COVER_CUSTOM: "Обложка + Свой" +STR_RECENTS: "Недавние" +STR_MENU_RECENT_BOOKS: "Недавние книги" +STR_NO_RECENT_BOOKS: "Нет недавних книг" +STR_CALIBRE_DESC: "Использовать беспроводную передачу Calibre" +STR_FORGET_AND_REMOVE: "Забыть сеть и удалить сохранённый пароль?" +STR_FORGET_BUTTON: "Забыть сеть" +STR_CALIBRE_STARTING: "Запуск Calibre..." +STR_CALIBRE_SETUP: "Настройка" +STR_CALIBRE_STATUS: "Статус" +STR_CLEAR_BUTTON: "Очистить" +STR_DEFAULT_VALUE: "По умолчанию" +STR_REMAP_PROMPT: "Назначьте роль для каждой кнопки" +STR_UNASSIGNED: "Не назначено" +STR_ALREADY_ASSIGNED: "Уже назначено" +STR_REMAP_RESET_HINT: "Боковая кнопка вверх: сбросить по умолчанию" +STR_REMAP_CANCEL_HINT: "Боковая кнопка вниз: отменить переназначение" +STR_HW_BACK_LABEL: "Назад (1-я кнопка)" +STR_HW_CONFIRM_LABEL: "Подтвердить (2-я кнопка)" +STR_HW_LEFT_LABEL: "Влево (3-я кнопка)" +STR_HW_RIGHT_LABEL: "Вправо (4-я кнопка)" +STR_GO_TO_PERCENT: "Перейти к %" +STR_GO_HOME_BUTTON: "На главную" +STR_SYNC_PROGRESS: "Синхронизировать прогресс" +STR_DELETE_CACHE: "Удалить кэш книги" +STR_CHAPTER_PREFIX: "Глава:" +STR_PAGES_SEPARATOR: "стр. |" +STR_BOOK_PREFIX: "Книга:" +STR_KBD_SHIFT: "shift" +STR_KBD_SHIFT_CAPS: "SHIFT" +STR_KBD_LOCK: "LOCK" +STR_CALIBRE_URL_HINT: "Для Calibre добавьте /opds к URL" +STR_PERCENT_STEP_HINT: "Влево/Вправо: 1% Вверх/Вниз: 10%" +STR_SYNCING_TIME: "Синхронизация времени..." +STR_CALC_HASH: "Расчёт хэша документа..." +STR_HASH_FAILED: "Не удалось вычислить хэш документа" +STR_FETCH_PROGRESS: "Получение удалённого прогресса..." +STR_UPLOAD_PROGRESS: "Отправка прогресса..." +STR_NO_CREDENTIALS_MSG: "Данные для входа не настроены" +STR_KOREADER_SETUP_HINT: "Настройте аккаунт KOReader в настройках" +STR_PROGRESS_FOUND: "Прогресс найден!" +STR_REMOTE_LABEL: "Удалённый:" +STR_LOCAL_LABEL: "Локальный:" +STR_PAGE_OVERALL_FORMAT: "Страница %d, %.2f%% всего" +STR_PAGE_TOTAL_OVERALL_FORMAT: "Страница %d/%d" +STR_DEVICE_FROM_FORMAT: "От: %s" +STR_APPLY_REMOTE: "Применить удалённый прогресс" +STR_UPLOAD_LOCAL: "Отправить локальный прогресс" +STR_NO_REMOTE_MSG: "Удалённый прогресс не найден" +STR_UPLOAD_PROMPT: "Отправить текущую позицию?" +STR_UPLOAD_SUCCESS: "Прогресс отправлен!" +STR_SYNC_FAILED_MSG: "Ошибка синхронизации" +STR_SECTION_PREFIX: "Раздел" +STR_UPLOAD: "Отправить" +STR_BOOK_S_STYLE: "Стиль книги" +STR_EMBEDDED_STYLE: "Встроенный стиль" +STR_OPDS_SERVER_URL: "URL OPDS сервера" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml new file mode 100644 index 00000000..4556aad6 --- /dev/null +++ b/lib/I18n/translations/spanish.yaml @@ -0,0 +1,317 @@ +_language_name: "Español" +_language_code: "SPANISH" +_order: "1" + +STR_CROSSPOINT: "CrossPoint" +STR_BOOTING: "BOOTING" +STR_SLEEPING: "SLEEPING" +STR_ENTERING_SLEEP: "ENTERING SLEEP..." +STR_BROWSE_FILES: "Buscar archivos" +STR_FILE_TRANSFER: "Transferencia de archivos" +STR_SETTINGS_TITLE: "Configuración" +STR_CALIBRE_LIBRARY: "Libreria Calibre" +STR_CONTINUE_READING: "Continuar leyendo" +STR_NO_OPEN_BOOK: "No hay libros abiertos" +STR_START_READING: "Start reading below" +STR_BOOKS: "Libros" +STR_NO_BOOKS_FOUND: "No se encontraron libros" +STR_SELECT_CHAPTER: "Seleccionar capítulo" +STR_NO_CHAPTERS: "Sin capítulos" +STR_END_OF_BOOK: "Fin del libro" +STR_EMPTY_CHAPTER: "Capítulo vacío" +STR_INDEXING: "Indexando..." +STR_MEMORY_ERROR: "Error de memoria" +STR_PAGE_LOAD_ERROR: "Error al cargar la página" +STR_EMPTY_FILE: "Archivo vacío" +STR_OUT_OF_BOUNDS: "Out of bounds" +STR_LOADING: "Cargando..." +STR_LOAD_XTC_FAILED: "Error al cargar XTC" +STR_LOAD_TXT_FAILED: "Error al cargar TXT" +STR_LOAD_EPUB_FAILED: "Error al cargar EPUB" +STR_SD_CARD_ERROR: "Error en la tarjeta SD" +STR_WIFI_NETWORKS: "Redes Wi-Fi" +STR_NO_NETWORKS: "No hay redes disponibles" +STR_NETWORKS_FOUND: "%zu redes encontradas" +STR_SCANNING: "Buscando..." +STR_CONNECTING: "Conectando..." +STR_CONNECTED: "Conectado!" +STR_CONNECTION_FAILED: "Error de conexion" +STR_CONNECTION_TIMEOUT: "Connection timeout" +STR_FORGET_NETWORK: "Olvidar la red?" +STR_SAVE_PASSWORD: "Guardar contraseña para la próxima vez?" +STR_REMOVE_PASSWORD: "Borrar contraseñas guardadas?" +STR_PRESS_OK_SCAN: "Presione OK para buscar de nuevo" +STR_PRESS_ANY_CONTINUE: "Presione cualquier botón para continuar" +STR_SELECT_HINT: "Izquierda/Derecha: Seleccionar | OK: Confirmar" +STR_HOW_CONNECT: "Cómo te gustaría conectarte?" +STR_JOIN_NETWORK: "Unirse a una red" +STR_CREATE_HOTSPOT: "Crear punto de acceso" +STR_JOIN_DESC: "Conectarse a una red Wi-Fi existente" +STR_HOTSPOT_DESC: "Crear una red Wi-Fi para que otros se unan" +STR_STARTING_HOTSPOT: "Iniciando punto de acceso..." +STR_HOTSPOT_MODE: "Modo punto de acceso" +STR_CONNECT_WIFI_HINT: "Conectar su dispositivo a esta red Wi-Fi" +STR_OPEN_URL_HINT: "Abre esta dirección en tu navegador" +STR_OR_HTTP_PREFIX: "o http://" +STR_SCAN_QR_HINT: "o escanee este código QR con su móvil:" +STR_CALIBRE_WIRELESS: "Calibre inalámbrico" +STR_CALIBRE_WEB_URL: "URL del sitio web de Calibre" +STR_CONNECT_WIRELESS: "Conectar como dispositivo inalámbrico" +STR_NETWORK_LEGEND: "* = Cifrado | + = Guardado" +STR_MAC_ADDRESS: "Dirección MAC:" +STR_CHECKING_WIFI: "Verificando Wi-Fi..." +STR_ENTER_WIFI_PASSWORD: "Introduzca la contraseña de Wi-Fi" +STR_ENTER_TEXT: "Introduzca el texto" +STR_TO_PREFIX: "a " +STR_CALIBRE_DISCOVERING: "Discovering Calibre..." +STR_CALIBRE_CONNECTING_TO: "Conectándose a" +STR_CALIBRE_CONNECTED_TO: "Conectado a " +STR_CALIBRE_WAITING_COMMANDS: "Esperando comandos..." +STR_CONNECTION_FAILED_RETRYING: "(Error de conexión, intentándolo nuevamente)" +STR_CALIBRE_DISCONNECTED: "Calibre desconectado" +STR_CALIBRE_WAITING_TRANSFER: "Esperando transferencia..." +STR_CALIBRE_TRANSFER_HINT: "Si la transferencia falla, habilite \\n'Ignorar espacio libre' en las configuraciones del \\nplugin smartdevice de calibre." +STR_CALIBRE_RECEIVING: "Recibiendo: " +STR_CALIBRE_RECEIVED: "Recibido: " +STR_CALIBRE_WAITING_MORE: "Esperando más..." +STR_CALIBRE_FAILED_CREATE_FILE: "Error al crear el archivo" +STR_CALIBRE_PASSWORD_REQUIRED: "Contraseña requerida" +STR_CALIBRE_TRANSFER_INTERRUPTED: "Transferencia interrumpida" +STR_CALIBRE_INSTRUCTION_1: "1) Instala CrossPoint Reader plugin" +STR_CALIBRE_INSTRUCTION_2: "2) Conéctese a la misma red Wi-Fi" +STR_CALIBRE_INSTRUCTION_3: "3) En Calibre: \"Enviar a dispotivo\"" +STR_CALIBRE_INSTRUCTION_4: "\"Permanezca en esta pantalla mientras se envía\"" +STR_CAT_DISPLAY: "Pantalla" +STR_CAT_READER: "Lector" +STR_CAT_CONTROLS: "Control" +STR_CAT_SYSTEM: "Sistema" +STR_SLEEP_SCREEN: "Salva Pantallas" +STR_SLEEP_COVER_MODE: "Modo de salva pantallas" +STR_STATUS_BAR: "Barra de estado" +STR_HIDE_BATTERY: "Ocultar porcentaje de batería" +STR_EXTRA_SPACING: "Espaciado extra de párrafos" +STR_TEXT_AA: "Suavizado de bordes de texto" +STR_SHORT_PWR_BTN: "Clic breve del botón de encendido" +STR_ORIENTATION: "Orientación de la lectura" +STR_FRONT_BTN_LAYOUT: "Diseño de los botones frontales" +STR_SIDE_BTN_LAYOUT: "Diseño de los botones laterales (Lector)" +STR_LONG_PRESS_SKIP: "Pasar a la capítulo al presiónar largamente" +STR_FONT_FAMILY: "Familia de tipografía del lector" +STR_EXT_READER_FONT: "Tipografía externa" +STR_EXT_CHINESE_FONT: "Tipografía (Lectura)" +STR_EXT_UI_FONT: "Tipografía (Pantalla)" +STR_FONT_SIZE: "Tamaño de la fuente (Pantalla)" +STR_LINE_SPACING: "Interlineado (Lectura)" +STR_ASCII_LETTER_SPACING: "Espaciado de letras ASCII" +STR_ASCII_DIGIT_SPACING: "Espaciado de dígitos ASCII" +STR_CJK_SPACING: "Espaciado CJK" +STR_COLOR_MODE: "Modo de color" +STR_SCREEN_MARGIN: "Margen de lectura" +STR_PARA_ALIGNMENT: "Ajuste de parágrafo del lector" +STR_HYPHENATION: "Hyphenation" +STR_TIME_TO_SLEEP: "Tiempo para dormir" +STR_REFRESH_FREQ: "Frecuencia de actualización" +STR_CALIBRE_SETTINGS: "Configuraciones de Calibre" +STR_KOREADER_SYNC: "Síncronización de KOReader" +STR_CHECK_UPDATES: "Verificar actualizaciones" +STR_LANGUAGE: "Idioma" +STR_SELECT_WALLPAPER: "Seleccionar fondo" +STR_CLEAR_READING_CACHE: "Borrar caché de lectura" +STR_CALIBRE: "Calibre" +STR_USERNAME: "Nombre de usuario" +STR_PASSWORD: "Contraseña" +STR_SYNC_SERVER_URL: "URL del servidor de síncronización" +STR_DOCUMENT_MATCHING: "Coincidencia de documentos" +STR_AUTHENTICATE: "Autentificar" +STR_KOREADER_USERNAME: "Nombre de usuario de KOReader" +STR_KOREADER_PASSWORD: "Contraseña de KOReader" +STR_FILENAME: "Nombre del archivo" +STR_BINARY: "Binario" +STR_SET_CREDENTIALS_FIRST: "Configurar credenciales primero" +STR_WIFI_CONN_FAILED: "Falló la conexión Wi-Fi" +STR_AUTHENTICATING: "Autentificando..." +STR_AUTH_SUCCESS: "Autenticación exitsosa!" +STR_KOREADER_AUTH: "Autenticación KOReader" +STR_SYNC_READY: "La síncronización de KOReader está lista para usarse" +STR_AUTH_FAILED: "Falló la autenticación" +STR_DONE: "Hecho" +STR_CLEAR_CACHE_WARNING_1: "Esto borrará todos los datos en cache del libro." +STR_CLEAR_CACHE_WARNING_2: " ¡Se perderá todo el avance de leer!" +STR_CLEAR_CACHE_WARNING_3: "Los libros deberán ser reíndexados" +STR_CLEAR_CACHE_WARNING_4: "cuando se abran de nuevo." +STR_CLEARING_CACHE: "Borrando caché..." +STR_CACHE_CLEARED: "Cache limpia" +STR_ITEMS_REMOVED: "Elementos eliminados" +STR_FAILED_LOWER: "Falló" +STR_CLEAR_CACHE_FAILED: "No se pudo borrar la cache" +STR_CHECK_SERIAL_OUTPUT: "Verifique la salida serial para detalles" +STR_DARK: "Oscuro" +STR_LIGHT: "Claro" +STR_CUSTOM: "Personalizado" +STR_COVER: "Portada" +STR_NONE_OPT: "Ninguno" +STR_FIT: "Ajustar" +STR_CROP: "Recortar" +STR_NO_PROGRESS: "Sin avance" +STR_FULL_OPT: "Completa" +STR_NEVER: "Nunca" +STR_IN_READER: "En el lector" +STR_ALWAYS: "Siempre" +STR_IGNORE: "Ignorar" +STR_SLEEP: "Dormir" +STR_PAGE_TURN: "Paso de página" +STR_PORTRAIT: "Portrato" +STR_LANDSCAPE_CW: "Paisaje sentido horario" +STR_INVERTED: "Invertido" +STR_LANDSCAPE_CCW: "Paisaje sentido antihorario" +STR_FRONT_LAYOUT_BCLR: "Atrás, Confirmar, Izquierda, Derecha" +STR_FRONT_LAYOUT_LRBC: "Izquierda, Derecha, Atrás, Confirmar" +STR_FRONT_LAYOUT_LBCR: "Izquierda, Atrás, Confirmar, Derecha" +STR_PREV_NEXT: "Anterior/Siguiente" +STR_NEXT_PREV: "Siguiente/Anterior" +STR_BOOKERLY: "Relacionado con libros" +STR_NOTO_SANS: "Noto Sans" +STR_OPEN_DYSLEXIC: "Open Dyslexic" +STR_SMALL: "Pequeño" +STR_MEDIUM: "Medio" +STR_LARGE: "Grande" +STR_X_LARGE: "Extra grande" +STR_TIGHT: "Ajustado" +STR_NORMAL: "Normal" +STR_WIDE: "Ancho" +STR_JUSTIFY: "Justificar" +STR_ALIGN_LEFT: "Izquierda" +STR_CENTER: "Centro" +STR_ALIGN_RIGHT: "Derecha" +STR_MIN_1: "1 Minuto" +STR_MIN_5: "10 Minutos" +STR_MIN_10: "5 Minutos" +STR_MIN_15: "15 Minutos" +STR_MIN_30: "30 Minutos" +STR_PAGES_1: "1 Página" +STR_PAGES_5: "5 Páginas" +STR_PAGES_10: "10 Páginas" +STR_PAGES_15: "15 Páginas" +STR_PAGES_30: "30 Páginas" +STR_UPDATE: "ActualizaR" +STR_CHECKING_UPDATE: "Verificando actualización..." +STR_NEW_UPDATE: "¡Nueva actualización disponible!" +STR_CURRENT_VERSION: "Versión actual:" +STR_NEW_VERSION: "Nueva versión:" +STR_UPDATING: "Actualizando..." +STR_NO_UPDATE: "No hay actualizaciones disponibles" +STR_UPDATE_FAILED: "Falló la actualización" +STR_UPDATE_COMPLETE: "Actualización completada" +STR_POWER_ON_HINT: "Presione y mantenga presionado el botón de encendido para volver a encender" +STR_EXTERNAL_FONT: "Fuente externa" +STR_BUILTIN_DISABLED: "Incorporado (Desactivado)" +STR_NO_ENTRIES: "No se encontraron elementos" +STR_DOWNLOADING: "Descargando..." +STR_DOWNLOAD_FAILED: "Falló la descarga" +STR_ERROR_MSG: "Error" +STR_UNNAMED: "Sin nombre" +STR_NO_SERVER_URL: "No se ha configurado la url del servidor" +STR_FETCH_FEED_FAILED: "Failed to fetch feed" +STR_PARSE_FEED_FAILED: "Failed to parse feed" +STR_NETWORK_PREFIX: "Red: " +STR_IP_ADDRESS_PREFIX: "Dirección IP: " +STR_SCAN_QR_WIFI_HINT: "O escanee el código QR con su teléfono para conectarse a WI-FI." +STR_ERROR_GENERAL_FAILURE: "Error: Fallo general" +STR_ERROR_NETWORK_NOT_FOUND: "Error: Red no encontrada" +STR_ERROR_CONNECTION_TIMEOUT: "Error: Connection timeout" +STR_SD_CARD: "Tarjeta SD" +STR_BACK: "« Atrás" +STR_EXIT: "« SaliR" +STR_HOME: "« Inicio" +STR_SAVE: "« Guardar" +STR_SELECT: "Seleccionar" +STR_TOGGLE: "Cambiar" +STR_CONFIRM: "Confirmar" +STR_CANCEL: "Cancelar" +STR_CONNECT: "Conectar" +STR_OPEN: "Abrir" +STR_DOWNLOAD: "Descargar" +STR_RETRY: "Reintentar" +STR_YES: "Sí" +STR_NO: "No" +STR_STATE_ON: "ENCENDIDO" +STR_STATE_OFF: "APAGADO" +STR_SET: "Configurar" +STR_NOT_SET: "No configurado" +STR_DIR_LEFT: "Izquierda" +STR_DIR_RIGHT: "Derecha" +STR_DIR_UP: "Arriba" +STR_DIR_DOWN: "Abajo" +STR_CAPS_ON: "MAYÚSCULAS" +STR_CAPS_OFF: "caps" +STR_OK_BUTTON: "OK" +STR_ON_MARKER: "[ENCENDIDO]" +STR_SLEEP_COVER_FILTER: "Filtro de salva pantalla y protección de la pantalla" +STR_FILTER_CONTRAST: "Contraste" +STR_STATUS_BAR_FULL_PERCENT: "Completa con porcentaje" +STR_STATUS_BAR_FULL_BOOK: "Completa con progreso del libro" +STR_STATUS_BAR_BOOK_ONLY: "Solo progreso del libro" +STR_STATUS_BAR_FULL_CHAPTER: "Completa con progreso de capítulos" +STR_UI_THEME: "Estilo de pantalla" +STR_THEME_CLASSIC: "Clásico" +STR_THEME_LYRA: "LYRA" +STR_SUNLIGHT_FADING_FIX: "Corrección de desvastado por sol" +STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales" +STR_OPDS_BROWSER: "Navegador opds" +STR_COVER_CUSTOM: "Portada + Personalizado" +STR_RECENTS: "Recientes" +STR_MENU_RECENT_BOOKS: "Libros recientes" +STR_NO_RECENT_BOOKS: "No hay libros recientes" +STR_CALIBRE_DESC: "Utilice las transferencias dispositivos inalámbricos de calibre" +STR_FORGET_AND_REMOVE: "Olvidar la red y eliminar la contraseña guardada?" +STR_FORGET_BUTTON: "Olvidar la red" +STR_CALIBRE_STARTING: "Iniciando calibre..." +STR_CALIBRE_SETUP: "Configuración" +STR_CALIBRE_STATUS: "Estado" +STR_CLEAR_BUTTON: "Borrar" +STR_DEFAULT_VALUE: "Previo" +STR_REMAP_PROMPT: "Presione un botón frontal para cada función" +STR_UNASSIGNED: "No asignado" +STR_ALREADY_ASSIGNED: "Ya asignado" +STR_REMAP_RESET_HINT: "Botón lateral arriba: Restablecer a la configuración previo" +STR_REMAP_CANCEL_HINT: "Botón lateral abajo: Anular reconfiguración" +STR_HW_BACK_LABEL: "Atrás (Primer botón)" +STR_HW_CONFIRM_LABEL: "Confirmar (Segundo botón)" +STR_HW_LEFT_LABEL: "Izquierda (Tercer botón)" +STR_HW_RIGHT_LABEL: "Derecha (Cuarto botón)" +STR_GO_TO_PERCENT: "Ir a %" +STR_GO_HOME_BUTTON: "Volver a inicio" +STR_SYNC_PROGRESS: "Progreso de síncronización" +STR_DELETE_CACHE: "Borrar cache del libro" +STR_CHAPTER_PREFIX: "Capítulo:" +STR_PAGES_SEPARATOR: " Páginas |" +STR_BOOK_PREFIX: "Libro:" +STR_KBD_SHIFT: "shift" +STR_KBD_SHIFT_CAPS: "SHIFT" +STR_KBD_LOCK: "BLOQUEAR" +STR_CALIBRE_URL_HINT: "Para calibre, agregue /opds a su urL" +STR_PERCENT_STEP_HINT: "Izquierda/Derecha: 1% Arriba/Abajo: 10%" +STR_SYNCING_TIME: "Tiempo de síncronización..." +STR_CALC_HASH: "Calculando hash del documento..." +STR_HASH_FAILED: "No se pudo calcular el hash del documento" +STR_FETCH_PROGRESS: "Recuperando progreso remoto..." +STR_UPLOAD_PROGRESS: "Subiendo progreso..." +STR_NO_CREDENTIALS_MSG: "No se han configurado credenciales" +STR_KOREADER_SETUP_HINT: "Configure una cuenta de KOReader en la configuración" +STR_PROGRESS_FOUND: "¡Progreso encontrado!" +STR_REMOTE_LABEL: "Remoto" +STR_LOCAL_LABEL: "Local" +STR_PAGE_OVERALL_FORMAT: "Página %d, %.2f%% Completada" +STR_PAGE_TOTAL_OVERALL_FORMAT: "Página %d / %d, %.2f% Completada" +STR_DEVICE_FROM_FORMAT: " De: %s" +STR_APPLY_REMOTE: "Aplicar progreso remoto" +STR_UPLOAD_LOCAL: "Subir progreso local" +STR_NO_REMOTE_MSG: "No se encontró progreso remoto" +STR_UPLOAD_PROMPT: "Subir posicion actual?" +STR_UPLOAD_SUCCESS: "¡Progreso subido!" +STR_SYNC_FAILED_MSG: "Fallo de síncronización" +STR_SECTION_PREFIX: "Seccion" +STR_UPLOAD: "Subir" +STR_BOOK_S_STYLE: "Estilo del libro" +STR_EMBEDDED_STYLE: "Estilo integrado" +STR_OPDS_SERVER_URL: "URL del servidor OPDS" diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml new file mode 100644 index 00000000..7cf2795b --- /dev/null +++ b/lib/I18n/translations/swedish.yaml @@ -0,0 +1,317 @@ +_language_name: "Svenska" +_language_code: "SWEDISH" +_order: "7" + +STR_CROSSPOINT: "Crosspoint" +STR_BOOTING: "STARTAR" +STR_SLEEPING: "VILA" +STR_ENTERING_SLEEP: "Går i vila…" +STR_BROWSE_FILES: "Bläddra filer…" +STR_FILE_TRANSFER: "Filöverföring" +STR_SETTINGS_TITLE: "Inställningar" +STR_CALIBRE_LIBRARY: "Calibrebibliotek" +STR_CONTINUE_READING: "Fortsätt läsa" +STR_NO_OPEN_BOOK: "Ingen öppen bok" +STR_START_READING: "Börja läsa nedan" +STR_BOOKS: "Böcker" +STR_NO_BOOKS_FOUND: "Inga böcker hittade" +STR_SELECT_CHAPTER: "Välj kapitel" +STR_NO_CHAPTERS: "Inga kapitel" +STR_END_OF_BOOK: "Slutet på boken" +STR_EMPTY_CHAPTER: "Tomt kapitel" +STR_INDEXING: "Indexerar…" +STR_MEMORY_ERROR: "Minnesfel" +STR_PAGE_LOAD_ERROR: "Sidladdningsfel" +STR_EMPTY_FILE: "Tom fil" +STR_OUT_OF_BOUNDS: "Utanför gränserna" +STR_LOADING: "Laddar…" +STR_LOAD_XTC_FAILED: "Misslyckades ladda XTC" +STR_LOAD_TXT_FAILED: "Misslyckades ladda TCT" +STR_LOAD_EPUB_FAILED: "Misslyckades ladda EPUB" +STR_SD_CARD_ERROR: "SD-kortfel" +STR_WIFI_NETWORKS: "Trådlösa nätverk" +STR_NO_NETWORKS: "Inga nätverk funna" +STR_NETWORKS_FOUND: "%zu nätverk funna" +STR_SCANNING: "Scannar…" +STR_CONNECTING: "Ansluter…" +STR_CONNECTED: "Ansluten!" +STR_CONNECTION_FAILED: "Anslutning misslyckades" +STR_CONNECTION_TIMEOUT: "Anslutnings timeout" +STR_FORGET_NETWORK: "Glöm nätverk?" +STR_SAVE_PASSWORD: "Spara lösenord till nästa gång?" +STR_REMOVE_PASSWORD: "Radera sparat lösenord?" +STR_PRESS_OK_SCAN: "Tryck OK för att skanna igen" +STR_PRESS_ANY_CONTINUE: "Tryck valfri knapp för att fortsätta" +STR_SELECT_HINT: "VÄNSTER/HÖGER: Välj OK: Bekräfta" +STR_HOW_CONNECT: "Hur vill du ansluta?" +STR_JOIN_NETWORK: "Anslut till ett nätverk" +STR_CREATE_HOTSPOT: "Skapa surfzon" +STR_JOIN_DESC: "Anslut till ett befintligt trådlöst nätverk" +STR_HOTSPOT_DESC: "Skapa ett trådlöst nätverk andra kan ansluta till" +STR_STARTING_HOTSPOT: "Startar surfzon…" +STR_HOTSPOT_MODE: "Surfzonsläge" +STR_CONNECT_WIFI_HINT: "Anslut din enhet till detta trådlösa nätverk" +STR_OPEN_URL_HINT: "Öppna denna adress i din browser" +STR_OR_HTTP_PREFIX: "eller http://" +STR_SCAN_QR_HINT: "eller skanna QR-kod med din telefon:" +STR_CALIBRE_WIRELESS: "Calibre Trådlöst" +STR_CALIBRE_WEB_URL: "Calibre webbadress" +STR_CONNECT_WIRELESS: "Anslut som trådlös enhet" +STR_NETWORK_LEGEND: "* = Krypterad | + = Sparad" +STR_MAC_ADDRESS: "MAC-adress:" +STR_CHECKING_WIFI: "Kontrollerar trådlöst nätverk…" +STR_ENTER_WIFI_PASSWORD: "Skriv in WiFi-lösenord" +STR_ENTER_TEXT: "Skriv text" +STR_TO_PREFIX: "till" +STR_CALIBRE_DISCOVERING: "Söker Calibre…" +STR_CALIBRE_CONNECTING_TO: "Ansluter till" +STR_CALIBRE_CONNECTED_TO: "Ansluten till" +STR_CALIBRE_WAITING_COMMANDS: "Väntar på kommandon…" +STR_CONNECTION_FAILED_RETRYING: "(Anslutning misslyckades. Försöker igen)" +STR_CALIBRE_DISCONNECTED: "Calibre nedkopplat" +STR_CALIBRE_WAITING_TRANSFER: "Väntar på överföring…" +STR_CALIBRE_TRANSFER_HINT: "Om överföring misslyckas: Aktivera\\n'Ignorera fritt utrymme' i Calibre's\\nSmartDevice plugin settings." +STR_CALIBRE_RECEIVING: "Tar emot:" +STR_CALIBRE_RECEIVED: "Mottaget:" +STR_CALIBRE_WAITING_MORE: "Väntar på mer.." +STR_CALIBRE_FAILED_CREATE_FILE: "Misslyckades att skapa fil" +STR_CALIBRE_PASSWORD_REQUIRED: "Lösenord krävs" +STR_CALIBRE_TRANSFER_INTERRUPTED: "Överföring avbröts" +STR_CALIBRE_INSTRUCTION_1: "1) Installera CrossPoint Reader plugin" +STR_CALIBRE_INSTRUCTION_2: "2) Anslut till samma trådlösa nätverk" +STR_CALIBRE_INSTRUCTION_3: "3) I Calibre: ”Skicka till enhet”" +STR_CALIBRE_INSTRUCTION_4: "”Håll denna skärm öppen under sändning”" +STR_CAT_DISPLAY: "Skärm" +STR_CAT_READER: "Läsare" +STR_CAT_CONTROLS: "Kontroller" +STR_CAT_SYSTEM: "System" +STR_SLEEP_SCREEN: "Viloskärm" +STR_SLEEP_COVER_MODE: "Viloskärmens omslagsläge" +STR_STATUS_BAR: "Statusrad" +STR_HIDE_BATTERY: "Dölj batteriprocent" +STR_EXTRA_SPACING: "Extra paragrafmellanrum" +STR_TEXT_AA: "Textkantutjämning" +STR_SHORT_PWR_BTN: "Kort strömknappsklick" +STR_ORIENTATION: "Läsrikting" +STR_FRONT_BTN_LAYOUT: "Frontknappslayout" +STR_SIDE_BTN_LAYOUT: "Sidoknappslayout (Läsare)" +STR_LONG_PRESS_SKIP: "Lång-tryck Kapitelskippning" +STR_FONT_FAMILY: "Eboksläsarens typsnittsfamilj" +STR_EXT_READER_FONT: "Extern Eboksläsartypsnitt" +STR_EXT_CHINESE_FONT: "Eboksläsartypsnitt" +STR_EXT_UI_FONT: "Användargränssnittets typsnitt" +STR_FONT_SIZE: "Användargränssnittets typsnittsstorlek" +STR_LINE_SPACING: "Eboksläsarens linjemellanrum" +STR_ASCII_LETTER_SPACING: "ASCII-bokstavsmellanrum" +STR_ASCII_DIGIT_SPACING: "ASCII-siffermellanrum" +STR_CJK_SPACING: "CJK-mellanrum" +STR_COLOR_MODE: "Färgläge" +STR_SCREEN_MARGIN: "Eboksläsarens skärmmarginal" +STR_PARA_ALIGNMENT: "Eboksläsarens paragraflinjeplacering" +STR_HYPHENATION: "Avstavning" +STR_TIME_TO_SLEEP: "Tid för att gå i vila" +STR_REFRESH_FREQ: "Uppdateringsfrekvens" +STR_CALIBRE_SETTINGS: "Calibreinställningar" +STR_KOREADER_SYNC: "KorReader-synkronisering" +STR_CHECK_UPDATES: "Kolla efter uppdateringar" +STR_LANGUAGE: "Språk" +STR_SELECT_WALLPAPER: "Välj bakgrundsbild" +STR_CLEAR_READING_CACHE: "Rensa Eboksläsarens cache" +STR_CALIBRE: "Calibre" +STR_USERNAME: "Användarnamn" +STR_PASSWORD: "Lösenord" +STR_SYNC_SERVER_URL: "Synkronisera serveradress" +STR_DOCUMENT_MATCHING: "Dokumentmatchning" +STR_AUTHENTICATE: "Autentisera " +STR_KOREADER_USERNAME: "KOReader användarnamn" +STR_KOREADER_PASSWORD: "KOReader lösenord" +STR_FILENAME: "Filnamn" +STR_BINARY: "Binär" +STR_SET_CREDENTIALS_FIRST: "Referenser" +STR_WIFI_CONN_FAILED: "Trådlös anslutning misslyckades" +STR_AUTHENTICATING: "Autentiserar…" +STR_AUTH_SUCCESS: "Lyckad autentisering!" +STR_KOREADER_AUTH: "KORreader autentisering" +STR_SYNC_READY: "KOReader synk är redo att användas" +STR_AUTH_FAILED: "Autentisering misslyckades" +STR_DONE: "Klar" +STR_CLEAR_CACHE_WARNING_1: "Detta rensar all cachad bokdata" +STR_CLEAR_CACHE_WARNING_2: "Alla läsframsteg kommer att försvinna!" +STR_CLEAR_CACHE_WARNING_3: "Böcker kommer att behöva omindexeras" +STR_CLEAR_CACHE_WARNING_4: "när de öppnas på nytt." +STR_CLEARING_CACHE: "Rensar cache…" +STR_CACHE_CLEARED: "Cache rensad!" +STR_ITEMS_REMOVED: "objekt raderade" +STR_FAILED_LOWER: "misslyckades " +STR_CLEAR_CACHE_FAILED: "Misslyckades att rensa cache" +STR_CHECK_SERIAL_OUTPUT: "Kolla seriell utgång för detaljer" +STR_DARK: "Mörk" +STR_LIGHT: "Ljus" +STR_CUSTOM: "Valfri" +STR_COVER: "Omslag" +STR_NONE_OPT: "Ingen öppen bok" +STR_FIT: "Passa" +STR_CROP: "Beskär" +STR_NO_PROGRESS: "Ingen framgång" +STR_FULL_OPT: "Full" +STR_NEVER: "Aldrig" +STR_IN_READER: "I Eboksläsare" +STR_ALWAYS: "Alltid" +STR_IGNORE: "Ignorera" +STR_SLEEP: "Vila" +STR_PAGE_TURN: "Sidvändning" +STR_PORTRAIT: "Porträtt" +STR_LANDSCAPE_CW: "Landskap medurs" +STR_INVERTED: "Inverterad" +STR_LANDSCAPE_CCW: "Landskap moturs" +STR_FRONT_LAYOUT_BCLR: "Bak, Bekr,Vän, Hög" +STR_FRONT_LAYOUT_LRBC: "Vän, Hög, Bak, Bekr" +STR_FRONT_LAYOUT_LBCR: "Vän, Bak, Bekr, Hög" +STR_PREV_NEXT: "Förra/Nästa" +STR_NEXT_PREV: "Nästa/Förra" +STR_BOOKERLY: "Bookerly" +STR_NOTO_SANS: "Noto Sans" +STR_OPEN_DYSLEXIC: "Öppen dyslektisk" +STR_SMALL: "Liten" +STR_MEDIUM: "Medium" +STR_LARGE: "Stor" +STR_X_LARGE: "Extra stor" +STR_TIGHT: "Smal" +STR_NORMAL: "Normal" +STR_WIDE: "Bred" +STR_JUSTIFY: "Rättfärdiga" +STR_ALIGN_LEFT: "Vänster" +STR_CENTER: "Mitten" +STR_ALIGN_RIGHT: "Höger" +STR_MIN_1: "1 min" +STR_MIN_5: "5 min" +STR_MIN_10: "10 min" +STR_MIN_15: "15 min" +STR_MIN_30: "30 min" +STR_PAGES_1: "1 sida" +STR_PAGES_5: "5 sidor" +STR_PAGES_10: "10 sidor" +STR_PAGES_15: "15 sidor" +STR_PAGES_30: "30 sidor" +STR_UPDATE: "Uppdatera" +STR_CHECKING_UPDATE: "Söker uppdatering…" +STR_NEW_UPDATE: "Ny uppdatering tillgänglig!" +STR_CURRENT_VERSION: "Nuvarande version:" +STR_NEW_VERSION: "Ny version:" +STR_UPDATING: "Uppdaterar…" +STR_NO_UPDATE: "Ingen uppdatering tillgänglig" +STR_UPDATE_FAILED: "Uppdatering misslyckades" +STR_UPDATE_COMPLETE: "Uppdatering färdig" +STR_POWER_ON_HINT: "Tryck och håll strömknappen för att sätta på igen" +STR_EXTERNAL_FONT: "Externt typsnitt" +STR_BUILTIN_DISABLED: "Inbyggd (Avstängd)" +STR_NO_ENTRIES: "Inga poster funna" +STR_DOWNLOADING: "Laddar ner…" +STR_DOWNLOAD_FAILED: "Nedladdning misslyckades" +STR_ERROR_MSG: "Fel:" +STR_UNNAMED: "Ej namngiven" +STR_NO_SERVER_URL: "Ingen serveradress konfigurerad" +STR_FETCH_FEED_FAILED: "Misslyckades att hämta flöde" +STR_PARSE_FEED_FAILED: "Misslyckades att analysera flöde" +STR_NETWORK_PREFIX: "Nätverk:" +STR_IP_ADDRESS_PREFIX: "IP-adress;" +STR_SCAN_QR_WIFI_HINT: "eller skanna QR-kod med din telefon för att ansluta till WiFi." +STR_ERROR_GENERAL_FAILURE: "Fel: Generellt fel" +STR_ERROR_NETWORK_NOT_FOUND: "Fel: Nätverk hittades inte" +STR_ERROR_CONNECTION_TIMEOUT: "Fel: Anslutningstimeout" +STR_SD_CARD: "SD-kort" +STR_BACK: "« Bak" +STR_EXIT: "« Avsluta" +STR_HOME: "« Hem" +STR_SAVE: "« Spara" +STR_SELECT: "Välj " +STR_TOGGLE: "Växla" +STR_CONFIRM: "Bekräfta" +STR_CANCEL: "Avbryt" +STR_CONNECT: "Anslut" +STR_OPEN: "Öppna" +STR_DOWNLOAD: "Ladda ner" +STR_RETRY: "Försök igen" +STR_YES: "Ja" +STR_NO: "Nej" +STR_STATE_ON: "PÅ" +STR_STATE_OFF: "AV" +STR_SET: "Inställd" +STR_NOT_SET: "Inte inställd" +STR_DIR_LEFT: "Vänster" +STR_DIR_RIGHT: "Höger" +STR_DIR_UP: "Upp" +STR_DIR_DOWN: "Ner" +STR_CAPS_ON: "VERSALER" +STR_CAPS_OFF: "versaler" +STR_OK_BUTTON: "Okej" +STR_ON_MARKER: "[PÅ]" +STR_SLEEP_COVER_FILTER: "Viloskärmens omslagsfilter" +STR_FILTER_CONTRAST: "Kontrast" +STR_STATUS_BAR_FULL_PERCENT: "Full w/ Procent" +STR_STATUS_BAR_FULL_BOOK: "Full w/ Boklist" +STR_STATUS_BAR_BOOK_ONLY: "Boklist enbart" +STR_STATUS_BAR_FULL_CHAPTER: "Full w/ Kapitellist" +STR_UI_THEME: "Användargränssnittstema" +STR_THEME_CLASSIC: "Klassisk" +STR_THEME_LYRA: "Lyra" +STR_SUNLIGHT_FADING_FIX: "Fix för solskensmattning" +STR_REMAP_FRONT_BUTTONS: "Ändra frontknappar" +STR_OPDS_BROWSER: "OPDS-webbläsare" +STR_COVER_CUSTOM: "Omslag + Valfri" +STR_RECENTS: "Senaste" +STR_MENU_RECENT_BOOKS: "Senaste böckerna" +STR_NO_RECENT_BOOKS: "Inga senaste böcker" +STR_CALIBRE_DESC: "Använd Calibres trådlösa enhetsöverföring" +STR_FORGET_AND_REMOVE: "Glöm nätverk och ta bort sparat lösenord?" +STR_FORGET_BUTTON: "Glöm nätverk" +STR_CALIBRE_STARTING: "Starar Calibre…" +STR_CALIBRE_SETUP: "Inställning" +STR_CALIBRE_STATUS: "Status" +STR_CLEAR_BUTTON: "Rensa" +STR_DEFAULT_VALUE: "Standard" +STR_REMAP_PROMPT: "Tryck en frontknapp för var funktion" +STR_UNASSIGNED: "Otilldelad" +STR_ALREADY_ASSIGNED: "Redan tilldelad" +STR_REMAP_RESET_HINT: "Översta sidoknapp: Återställ standardlayout" +STR_REMAP_CANCEL_HINT: "Nedre sidoknapp: Avbryt tilldelning" +STR_HW_BACK_LABEL: "Bak (Första knapp)" +STR_HW_CONFIRM_LABEL: "Bekräfta (Andra knapp)" +STR_HW_LEFT_LABEL: "Vänster (Tredje knapp)" +STR_HW_RIGHT_LABEL: "Höger (Fjärde knapp)" +STR_GO_TO_PERCENT: "Gå till %" +STR_GO_HOME_BUTTON: "Gå Hem" +STR_SYNC_PROGRESS: "Synkroniseringsframsteg" +STR_DELETE_CACHE: "Radera bokcache" +STR_CHAPTER_PREFIX: "Kapitel:" +STR_PAGES_SEPARATOR: " sidor | " +STR_BOOK_PREFIX: "Bok:" +STR_KBD_SHIFT: "shift" +STR_KBD_SHIFT_CAPS: "SHIFT" +STR_KBD_LOCK: "LOCK" +STR_CALIBRE_URL_HINT: "För Calibre: lägg till /opds i din adress" +STR_PERCENT_STEP_HINT: "Vänster/Höger: 1% Upp/Ner 10%" +STR_SYNCING_TIME: "Synkroniserar tid…" +STR_CALC_HASH: "Beräknar dokumenthash" +STR_HASH_FAILED: "Misslyckades att beräkna dokumenthash" +STR_FETCH_PROGRESS: "Hämtar fjärrframsteg" +STR_UPLOAD_PROGRESS: "Laddar upp framsteg" +STR_NO_CREDENTIALS_MSG: "Inga uppgifter inställda" +STR_KOREADER_SETUP_HINT: "Ställ in KOReaderkonto i Inställningar" +STR_PROGRESS_FOUND: "Framsteg funna!" +STR_REMOTE_LABEL: "Fjärr:" +STR_LOCAL_LABEL: "Lokalt:" +STR_PAGE_OVERALL_FORMAT: "Sida %d, %.2f%% totalt" +STR_PAGE_TOTAL_OVERALL_FORMAT: "Sida %d/%d, %.2f%% totalt" +STR_DEVICE_FROM_FORMAT: " Från: %s" +STR_APPLY_REMOTE: "Använd fjärrframsteg" +STR_UPLOAD_LOCAL: "Ladda upp lokala framsteg" +STR_NO_REMOTE_MSG: "Inga fjärrframsteg funna" +STR_UPLOAD_PROMPT: "Ladda upp nuvarande position?" +STR_UPLOAD_SUCCESS: "Framsteg uppladdade!" +STR_SYNC_FAILED_MSG: "Synkronisering misslyckades" +STR_SECTION_PREFIX: "Sektion" +STR_UPLOAD: "Uppladdning" +STR_BOOK_S_STYLE: "Bokstil" +STR_EMBEDDED_STYLE: "Inbäddad stil" +STR_OPDS_SERVER_URL: "OPDS-serveradress" diff --git a/platformio.ini b/platformio.ini index c5a23af5..f6ad828b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -45,6 +45,7 @@ board_build.partitions = partitions.csv extra_scripts = pre:scripts/build_html.py + pre:scripts/gen_i18n.py ; Libraries lib_deps = diff --git a/scripts/gen_i18n.py b/scripts/gen_i18n.py new file mode 100755 index 00000000..5d67acfb --- /dev/null +++ b/scripts/gen_i18n.py @@ -0,0 +1,620 @@ +#!/usr/bin/env python3 +""" +Generate I18n C++ files from per-language YAML translations. + +Reads YAML files from a translations directory (one file per language) and generates: +- I18nKeys.h: Language enum, StrId enum, helper functions +- I18nStrings.h: String array declarations +- I18nStrings.cpp: String array definitions with all translations + +Each YAML file must contain: + _language_name: "Native Name" (e.g. "Español") + _language_code: "ENUM_NAME" (e.g. "SPANISH") + STR_KEY: "translation text" + +The English file is the reference. Missing keys in other languages are +automatically filled from English, with a warning. + +Usage: + python gen_i18n.py + +Example: + python gen_i18n.py lib/I18n/translations lib/I18n/ +""" + +import sys +import os +import re +from pathlib import Path +from typing import List, Dict, Tuple + + +# --------------------------------------------------------------------------- +# YAML file reading (simple key: "value" format, no PyYAML dependency) +# --------------------------------------------------------------------------- + +def _unescape_yaml_value(raw: str, filepath: str = "", line_num: int = 0) -> str: + """ + Process escape sequences in a YAML value string. + + Recognized escapes: \\\\ → \\ \\" → " \\n → newline + """ + result: List[str] = [] + i = 0 + while i < len(raw): + if raw[i] == "\\" and i + 1 < len(raw): + nxt = raw[i + 1] + if nxt == "\\": + result.append("\\") + elif nxt == '"': + result.append('"') + elif nxt == "n": + result.append("\n") + else: + raise ValueError( + f"{filepath}:{line_num}: unknown escape '\\{nxt}'" + ) + i += 2 + else: + result.append(raw[i]) + i += 1 + return "".join(result) + + +def parse_yaml_file(filepath: str) -> Dict[str, str]: + """ + Parse a simple YAML file of the form: + key: "value" + + Only supports flat key-value pairs with quoted string values. + Aborts on formatting errors. + """ + result = {} + with open(filepath, "r", encoding="utf-8") as f: + for line_num, raw_line in enumerate(f, start=1): + line = raw_line.rstrip("\n\r") + + if not line.strip(): + continue + + match = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*"(.*)"$', line) + if not match: + raise ValueError( + f"{filepath}:{line_num}: bad format: {line!r}\n" + f' Expected: KEY: "value"' + ) + + key = match.group(1) + raw_value = match.group(2) + + # Un-escape: process character by character to handle + # \\, \", and \n sequences correctly + value = _unescape_yaml_value(raw_value, filepath, line_num) + + if key in result: + raise ValueError(f"{filepath}:{line_num}: duplicate key '{key}'") + + result[key] = value + + return result + + +# --------------------------------------------------------------------------- +# Load all languages from a directory of YAML files +# --------------------------------------------------------------------------- + +def load_translations( + translations_dir: str, +) -> Tuple[List[str], List[str], List[str], Dict[str, List[str]]]: + """ + Read every YAML file in *translations_dir* and return: + language_codes e.g. ["ENGLISH", "SPANISH", ...] + language_names e.g. ["English", "Español", ...] + string_keys ordered list of STR_* keys (from English) + translations {key: [translation_per_language]} + + English is always first; + """ + yaml_dir = Path(translations_dir) + if not yaml_dir.is_dir(): + raise FileNotFoundError(f"Translations directory not found: {translations_dir}") + + yaml_files = sorted(yaml_dir.glob("*.yaml")) + if not yaml_files: + raise FileNotFoundError(f"No .yaml files found in {translations_dir}") + + # Parse every file + parsed: Dict[str, Dict[str, str]] = {} + for yf in yaml_files: + parsed[yf.name] = parse_yaml_file(str(yf)) + + # Identify the English file (must exist) + english_file = None + for name, data in parsed.items(): + if data.get("_language_code", "").upper() == "ENGLISH": + english_file = name + break + + if english_file is None: + raise ValueError("No YAML file with _language_code: ENGLISH found") + + # Order: English first, then by _order metadata (falls back to filename) + def sort_key(fname: str) -> Tuple[int, int, str]: + """English always first (0), then by _order, then by filename.""" + if fname == english_file: + return (0, 0, fname) + order = parsed[fname].get("_order", "999") + try: + order_int = int(order) + except ValueError: + order_int = 999 + return (1, order_int, fname) + + ordered_files = sorted(parsed, key=sort_key) + + # Extract metadata + language_codes: List[str] = [] + language_names: List[str] = [] + for fname in ordered_files: + data = parsed[fname] + code = data.get("_language_code") + name = data.get("_language_name") + if not code or not name: + raise ValueError(f"{fname}: missing _language_code or _language_name") + language_codes.append(code) + language_names.append(name) + + # String keys come from English (order matters) + english_data = parsed[english_file] + string_keys = [k for k in english_data if not k.startswith("_")] + + # Validate all keys are valid C++ identifiers + for key in string_keys: + if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", key): + raise ValueError(f"Invalid C++ identifier in English file: '{key}'") + + # Build translations dict, filling missing keys from English + translations: Dict[str, List[str]] = {} + for key in string_keys: + row: List[str] = [] + for fname in ordered_files: + data = parsed[fname] + value = data.get(key, "") + if not value.strip() and fname != english_file: + value = english_data[key] + lang_code = parsed[fname].get("_language_code", fname) + print(f" INFO: '{key}' missing in {lang_code}, using English fallback") + row.append(value) + translations[key] = row + + # Warn about extra keys in non-English files + for fname in ordered_files: + if fname == english_file: + continue + data = parsed[fname] + extra = [k for k in data if not k.startswith("_") and k not in english_data] + if extra: + lang_code = data.get("_language_code", fname) + print(f" WARNING: {lang_code} has keys not in English: {', '.join(extra)}") + + print(f"Loaded {len(language_codes)} languages, {len(string_keys)} string keys") + return language_codes, language_names, string_keys, translations + + +# --------------------------------------------------------------------------- +# C++ string escaping +# --------------------------------------------------------------------------- + +LANG_ABBREVIATIONS = { + "english": "EN", + "español": "ES", "espanol": "ES", + "italiano": "IT", + "svenska": "SV", + "français": "FR", "francais": "FR", + "deutsch": "DE", "german": "DE", + "português": "PT", "portugues": "PT", "português (brasil)": "PO", + "中文": "ZH", "chinese": "ZH", + "日本語": "JA", "japanese": "JA", + "한국어": "KO", "korean": "KO", + "русский": "RU", "russian": "RU", + "العربية": "AR", "arabic": "AR", + "עברית": "HE", "hebrew": "HE", + "فارسی": "FA", "persian": "FA", + "čeština": "CZ", +} + + +def get_lang_abbreviation(lang_code: str, lang_name: str) -> str: + """Return a 2-letter abbreviation for a language.""" + lower = lang_name.lower() + if lower in LANG_ABBREVIATIONS: + return LANG_ABBREVIATIONS[lower] + return lang_code[:2].upper() + + +def escape_cpp_string(s: str) -> List[str]: + r""" + Convert *s* into one or more C++ string literal segments. + + Non-ASCII characters are emitted as \xNN hex sequences. After each + hex escape a new segment is started so the compiler doesn't merge + subsequent hex digits into the escape. + + Returns a list of string segments (without quotes). For simple ASCII + strings this is a single-element list. + """ + if not s: + return [""] + + s = s.replace("\n", "\\n") + + # Build a flat list of "tokens", where each token is either a regular + # character sequence or a hex escape. A segment break happens after + # every hex escape. + segments: List[str] = [] + current: List[str] = [] + i = 0 + + def _flush() -> None: + segments.append("".join(current)) + current.clear() + + while i < len(s): + ch = s[i] + + if ch == "\\" and i + 1 < len(s): + nxt = s[i + 1] + if nxt in "ntr\"\\": + current.append(ch + nxt) + i += 2 + elif nxt == "x" and i + 3 < len(s): + current.append(s[i : i + 4]) + _flush() # segment break after hex + i += 4 + else: + current.append("\\\\") + i += 1 + elif ch == '"': + current.append('\\"') + i += 1 + elif ord(ch) < 128: + current.append(ch) + i += 1 + else: + for byte in ch.encode("utf-8"): + current.append(f"\\x{byte:02X}") + _flush() # segment break after hex + i += 1 + + # Flush remaining content + _flush() + + return segments + + +def format_cpp_string_literal(segments: List[str], indent: str = " ") -> List[str]: + """ + Format string segments (from escape_cpp_string) as indented C++ string + literal lines, each wrapped in quotes. + Also wraps long segments to respect ~120 column limit. + """ + # Effective limit for content: 120 - 4 (indent) - 2 (quotes) - 1 (comma/safety) = 113 + # Using 113 to match clang-format exactly (120 - 4 - 2 - 1) + MAX_CONTENT_LEN = 113 + + lines: List[str] = [] + + for seg in segments: + # Short segment (e.g. hex escape or short text) + if len(seg) <= MAX_CONTENT_LEN: + lines.append(f'{indent}"{seg}"') + continue + + # Long segment - wrap it + current = seg + while len(current) > MAX_CONTENT_LEN: + # Find best split point + # Scan forward to find last space <= MAX_CONTENT_LEN + last_space = -1 + idx = 0 + while idx <= MAX_CONTENT_LEN and idx < len(current): + if current[idx] == ' ': + last_space = idx + + # Handle escapes to step correctly + if current[idx] == '\\': + idx += 2 + else: + idx += 1 + + # If we found a space, split after it + if last_space != -1: + # Include the space in the first line + split_point = last_space + 1 + lines.append(f'{indent}"{current[:split_point]}"') + current = current[split_point:] + else: + # No space, forced break at MAX_CONTENT_LEN (or slightly less) + cut_at = MAX_CONTENT_LEN + # Don't cut in the middle of an escape sequence + if current[cut_at - 1] == '\\': + cut_at -= 1 + + lines.append(f'{indent}"{current[:cut_at]}"') + current = current[cut_at:] + + if current: + lines.append(f'{indent}"{current}"') + + return lines + + +# --------------------------------------------------------------------------- +# Character-set computation +# --------------------------------------------------------------------------- + +def compute_character_set(translations: Dict[str, List[str]], lang_index: int) -> str: + """Return a sorted string of every unique character used in a language.""" + chars = set() + for values in translations.values(): + for ch in values[lang_index]: + chars.add(ord(ch)) + return "".join(chr(cp) for cp in sorted(chars)) + + +# --------------------------------------------------------------------------- +# Code generators +# --------------------------------------------------------------------------- + +def generate_keys_header( + languages: List[str], + language_names: List[str], + string_keys: List[str], + output_path: str, +) -> None: + """Generate I18nKeys.h.""" + lines: List[str] = [ + "#pragma once", + "#include ", + "", + "// THIS FILE IS AUTO-GENERATED BY gen_i18n.py. DO NOT EDIT.", + "", + "// Forward declaration for string arrays", + "namespace i18n_strings {", + ] + + for code, name in zip(languages, language_names): + abbrev = get_lang_abbreviation(code, name) + lines.append(f"extern const char* const STRINGS_{abbrev}[];") + + lines.append("} // namespace i18n_strings") + lines.append("") + + # Language enum + lines.append("// Language enum") + lines.append("enum class Language : uint8_t {") + for i, lang in enumerate(languages): + lines.append(f" {lang} = {i},") + lines.append(" _COUNT") + lines.append("};") + lines.append("") + + # Extern declarations + lines.append("// Language display names (defined in I18nStrings.cpp)") + lines.append("extern const char* const LANGUAGE_NAMES[];") + lines.append("") + lines.append("// Character sets for each language (defined in I18nStrings.cpp)") + lines.append("extern const char* const CHARACTER_SETS[];") + lines.append("") + + # StrId enum + lines.append("// String IDs") + lines.append("enum class StrId : uint16_t {") + for key in string_keys: + lines.append(f" {key},") + lines.append(" // Sentinel - must be last") + lines.append(" _COUNT") + lines.append("};") + lines.append("") + + # getStringArray helper + lines.append("// Helper function to get string array for a language") + lines.append("inline const char* const* getStringArray(Language lang) {") + lines.append(" switch (lang) {") + for code, name in zip(languages, language_names): + abbrev = get_lang_abbreviation(code, name) + lines.append(f" case Language::{code}:") + lines.append(f" return i18n_strings::STRINGS_{abbrev};") + first_abbrev = get_lang_abbreviation(languages[0], language_names[0]) + lines.append(" default:") + lines.append(f" return i18n_strings::STRINGS_{first_abbrev};") + lines.append(" }") + lines.append("}") + lines.append("") + + # getLanguageCount helper (single line to match checked-in format) + lines.append("// Helper function to get language count") + lines.append( + "constexpr uint8_t getLanguageCount() " + "{ return static_cast(Language::_COUNT); }" + ) + + _write_file(output_path, lines) + + +def generate_strings_header( + languages: List[str], + language_names: List[str], + output_path: str, +) -> None: + """Generate I18nStrings.h.""" + lines: List[str] = [ + "#pragma once", + '#include ', + "", + '#include "I18nKeys.h"', + "", + "// THIS FILE IS AUTO-GENERATED BY gen_i18n.py. DO NOT EDIT.", + "", + "namespace i18n_strings {", + "", + ] + + for code, name in zip(languages, language_names): + abbrev = get_lang_abbreviation(code, name) + lines.append(f"extern const char* const STRINGS_{abbrev}[];") + + lines.append("") + lines.append("} // namespace i18n_strings") + + _write_file(output_path, lines) + + +def generate_strings_cpp( + languages: List[str], + language_names: List[str], + string_keys: List[str], + translations: Dict[str, List[str]], + output_path: str, +) -> None: + """Generate I18nStrings.cpp.""" + lines: List[str] = [ + '#include "I18nStrings.h"', + "", + "// THIS FILE IS AUTO-GENERATED BY gen_i18n.py. DO NOT EDIT.", + "", + ] + + # LANGUAGE_NAMES array + lines.append("// Language display names") + lines.append("const char* const LANGUAGE_NAMES[] = {") + for name in language_names: + _append_string_entry(lines, name) + lines.append("};") + lines.append("") + + # CHARACTER_SETS array + lines.append("// Character sets for each language") + lines.append("const char* const CHARACTER_SETS[] = {") + for lang_idx, name in enumerate(language_names): + charset = compute_character_set(translations, lang_idx) + _append_string_entry(lines, charset, comment=name) + lines.append("};") + lines.append("") + + # Per-language string arrays + lines.append("namespace i18n_strings {") + lines.append("") + + for lang_idx, (code, name) in enumerate(zip(languages, language_names)): + abbrev = get_lang_abbreviation(code, name) + lines.append(f"const char* const STRINGS_{abbrev}[] = {{") + + for key in string_keys: + text = translations[key][lang_idx] + _append_string_entry(lines, text) + + lines.append("};") + lines.append("") + + lines.append("} // namespace i18n_strings") + lines.append("") + + # Compile-time size checks + lines.append("// Compile-time validation of array sizes") + for code, name in zip(languages, language_names): + abbrev = get_lang_abbreviation(code, name) + lines.append( + f"static_assert(sizeof(i18n_strings::STRINGS_{abbrev}) " + f"/ sizeof(i18n_strings::STRINGS_{abbrev}[0]) ==" + ) + lines.append(" static_cast(StrId::_COUNT),") + lines.append(f' "STRINGS_{abbrev} size mismatch");') + + _write_file(output_path, lines) + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _append_string_entry( + lines: List[str], text: str, comment: str = "" +) -> None: + """Escape *text*, format as indented C++ lines, append comma (and optional comment).""" + segments = escape_cpp_string(text) + formatted = format_cpp_string_literal(segments) + suffix = f", // {comment}" if comment else "," + formatted[-1] += suffix + lines.extend(formatted) + + +def _write_file(path: str, lines: List[str]) -> None: + with open(path, "w", encoding="utf-8", newline="\n") as f: + f.write("\n".join(lines)) + f.write("\n") + print(f"Generated: {path}") + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(translations_dir=None, output_dir=None) -> None: + # Default paths (relative to project root) + default_translations_dir = "lib/I18n/translations" + default_output_dir = "lib/I18n/" + + if translations_dir is None or output_dir is None: + if len(sys.argv) == 3: + translations_dir = sys.argv[1] + output_dir = sys.argv[2] + else: + # Default for no arguments or weird arguments (e.g. SCons) + translations_dir = default_translations_dir + output_dir = default_output_dir + + + if not os.path.isdir(translations_dir): + print(f"Error: Translations directory not found: {translations_dir}") + sys.exit(1) + + if not os.path.isdir(output_dir): + print(f"Error: Output directory not found: {output_dir}") + sys.exit(1) + + print(f"Reading translations from: {translations_dir}") + print(f"Output directory: {output_dir}") + print() + + try: + languages, language_names, string_keys, translations = load_translations( + translations_dir + ) + + out = Path(output_dir) + generate_keys_header(languages, language_names, string_keys, str(out / "I18nKeys.h")) + generate_strings_header(languages, language_names, str(out / "I18nStrings.h")) + generate_strings_cpp( + languages, language_names, string_keys, translations, str(out / "I18nStrings.cpp") + ) + + print() + print("✓ Code generation complete!") + print(f" Languages: {len(languages)}") + print(f" String keys: {len(string_keys)}") + + except Exception as e: + print(f"\nError: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() +else: + try: + Import("env") + print("Running i18n generation script from PlatformIO...") + main() + except NameError: + pass diff --git a/src/SettingsList.h b/src/SettingsList.h index e493f40f..193c5b17 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include "CrossPointSettings.h" @@ -12,90 +14,110 @@ inline std::vector getSettingsList() { return { // --- Display --- - SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, - {"Dark", "Light", "Custom", "Cover", "None", "Cover + Custom"}, "sleepScreen", "Display"), - SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}, - "sleepScreenCoverMode", "Display"), - SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter, - {"None", "Contrast", "Inverted"}, "sleepScreenCoverFilter", "Display"), + SettingInfo::Enum(StrId::STR_SLEEP_SCREEN, &CrossPointSettings::sleepScreen, + {StrId::STR_DARK, StrId::STR_LIGHT, StrId::STR_CUSTOM, StrId::STR_COVER, StrId::STR_NONE_OPT, + StrId::STR_COVER_CUSTOM}, + "sleepScreen", StrId::STR_CAT_DISPLAY), + SettingInfo::Enum(StrId::STR_SLEEP_COVER_MODE, &CrossPointSettings::sleepScreenCoverMode, + {StrId::STR_FIT, StrId::STR_CROP}, "sleepScreenCoverMode", StrId::STR_CAT_DISPLAY), + SettingInfo::Enum(StrId::STR_SLEEP_COVER_FILTER, &CrossPointSettings::sleepScreenCoverFilter, + {StrId::STR_NONE_OPT, StrId::STR_FILTER_CONTRAST, StrId::STR_INVERTED}, + "sleepScreenCoverFilter", StrId::STR_CAT_DISPLAY), SettingInfo::Enum( - "Status Bar", &CrossPointSettings::statusBar, - {"None", "No Progress", "Full w/ Percentage", "Full w/ Book Bar", "Book Bar Only", "Full w/ Chapter Bar"}, - "statusBar", "Display"), - SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}, - "hideBatteryPercentage", "Display"), - SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, - {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}, "refreshFrequency", "Display"), - SettingInfo::Enum("UI Theme", &CrossPointSettings::uiTheme, {"Classic", "Lyra"}, "uiTheme", "Display"), - SettingInfo::Toggle("Sunlight Fading Fix", &CrossPointSettings::fadingFix, "fadingFix", "Display"), + StrId::STR_STATUS_BAR, &CrossPointSettings::statusBar, + {StrId::STR_NONE_OPT, StrId::STR_NO_PROGRESS, StrId::STR_STATUS_BAR_FULL_PERCENT, + StrId::STR_STATUS_BAR_FULL_BOOK, StrId::STR_STATUS_BAR_BOOK_ONLY, StrId::STR_STATUS_BAR_FULL_CHAPTER}, + "statusBar", StrId::STR_CAT_DISPLAY), + SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage, + {StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage", + StrId::STR_CAT_DISPLAY), + SettingInfo::Enum( + StrId::STR_REFRESH_FREQ, &CrossPointSettings::refreshFrequency, + {StrId::STR_PAGES_1, StrId::STR_PAGES_5, StrId::STR_PAGES_10, StrId::STR_PAGES_15, StrId::STR_PAGES_30}, + "refreshFrequency", StrId::STR_CAT_DISPLAY), + SettingInfo::Enum(StrId::STR_UI_THEME, &CrossPointSettings::uiTheme, + {StrId::STR_THEME_CLASSIC, StrId::STR_THEME_LYRA}, "uiTheme", StrId::STR_CAT_DISPLAY), + SettingInfo::Toggle(StrId::STR_SUNLIGHT_FADING_FIX, &CrossPointSettings::fadingFix, "fadingFix", + StrId::STR_CAT_DISPLAY), // --- Reader --- - SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}, - "fontFamily", "Reader"), - SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}, "fontSize", - "Reader"), - SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}, "lineSpacing", - "Reader"), - SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}, "screenMargin", "Reader"), - SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment, - {"Justify", "Left", "Center", "Right", "Book's Style"}, "paragraphAlignment", "Reader"), - SettingInfo::Toggle("Book's Embedded Style", &CrossPointSettings::embeddedStyle, "embeddedStyle", "Reader"), - SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled, "hyphenationEnabled", "Reader"), - SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation, - {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}, "orientation", "Reader"), - SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing, - "extraParagraphSpacing", "Reader"), - SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing, "textAntiAliasing", "Reader"), + SettingInfo::Enum(StrId::STR_FONT_FAMILY, &CrossPointSettings::fontFamily, + {StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC}, "fontFamily", + StrId::STR_CAT_READER), + SettingInfo::Enum(StrId::STR_FONT_SIZE, &CrossPointSettings::fontSize, + {StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE}, "fontSize", + StrId::STR_CAT_READER), + SettingInfo::Enum(StrId::STR_LINE_SPACING, &CrossPointSettings::lineSpacing, + {StrId::STR_TIGHT, StrId::STR_NORMAL, StrId::STR_WIDE}, "lineSpacing", StrId::STR_CAT_READER), + SettingInfo::Value(StrId::STR_SCREEN_MARGIN, &CrossPointSettings::screenMargin, {5, 40, 5}, "screenMargin", + StrId::STR_CAT_READER), + SettingInfo::Enum(StrId::STR_PARA_ALIGNMENT, &CrossPointSettings::paragraphAlignment, + {StrId::STR_JUSTIFY, StrId::STR_ALIGN_LEFT, StrId::STR_CENTER, StrId::STR_ALIGN_RIGHT, + StrId::STR_BOOK_S_STYLE}, + "paragraphAlignment", StrId::STR_CAT_READER), + SettingInfo::Toggle(StrId::STR_EMBEDDED_STYLE, &CrossPointSettings::embeddedStyle, "embeddedStyle", + StrId::STR_CAT_READER), + SettingInfo::Toggle(StrId::STR_HYPHENATION, &CrossPointSettings::hyphenationEnabled, "hyphenationEnabled", + StrId::STR_CAT_READER), + SettingInfo::Enum(StrId::STR_ORIENTATION, &CrossPointSettings::orientation, + {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, StrId::STR_LANDSCAPE_CCW}, + "orientation", StrId::STR_CAT_READER), + SettingInfo::Toggle(StrId::STR_EXTRA_SPACING, &CrossPointSettings::extraParagraphSpacing, "extraParagraphSpacing", + StrId::STR_CAT_READER), + SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing", + StrId::STR_CAT_READER), // --- Controls --- - SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout, - {"Prev, Next", "Next, Prev"}, "sideButtonLayout", "Controls"), - SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip, "longPressChapterSkip", - "Controls"), - SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"}, - "shortPwrBtn", "Controls"), + SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, + {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS), + SettingInfo::Toggle(StrId::STR_LONG_PRESS_SKIP, &CrossPointSettings::longPressChapterSkip, "longPressChapterSkip", + StrId::STR_CAT_CONTROLS), + SettingInfo::Enum(StrId::STR_SHORT_PWR_BTN, &CrossPointSettings::shortPwrBtn, + {StrId::STR_IGNORE, StrId::STR_SLEEP, StrId::STR_PAGE_TURN}, "shortPwrBtn", + StrId::STR_CAT_CONTROLS), // --- System --- - SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, - {"1 min", "5 min", "10 min", "15 min", "30 min"}, "sleepTimeout", "System"), + SettingInfo::Enum(StrId::STR_TIME_TO_SLEEP, &CrossPointSettings::sleepTimeout, + {StrId::STR_MIN_1, StrId::STR_MIN_5, StrId::STR_MIN_10, StrId::STR_MIN_15, StrId::STR_MIN_30}, + "sleepTimeout", StrId::STR_CAT_SYSTEM), // --- KOReader Sync (web-only, uses KOReaderCredentialStore) --- SettingInfo::DynamicString( - "KOReader Username", [] { return KOREADER_STORE.getUsername(); }, + StrId::STR_KOREADER_USERNAME, [] { return KOREADER_STORE.getUsername(); }, [](const std::string& v) { KOREADER_STORE.setCredentials(v, KOREADER_STORE.getPassword()); KOREADER_STORE.saveToFile(); }, - "koUsername", "KOReader Sync"), + "koUsername", StrId::STR_KOREADER_SYNC), SettingInfo::DynamicString( - "KOReader Password", [] { return KOREADER_STORE.getPassword(); }, + StrId::STR_KOREADER_PASSWORD, [] { return KOREADER_STORE.getPassword(); }, [](const std::string& v) { KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), v); KOREADER_STORE.saveToFile(); }, - "koPassword", "KOReader Sync"), + "koPassword", StrId::STR_KOREADER_SYNC), SettingInfo::DynamicString( - "Sync Server URL", [] { return KOREADER_STORE.getServerUrl(); }, + StrId::STR_SYNC_SERVER_URL, [] { return KOREADER_STORE.getServerUrl(); }, [](const std::string& v) { KOREADER_STORE.setServerUrl(v); KOREADER_STORE.saveToFile(); }, - "koServerUrl", "KOReader Sync"), + "koServerUrl", StrId::STR_KOREADER_SYNC), SettingInfo::DynamicEnum( - "Document Matching", {"Filename", "Binary"}, + StrId::STR_DOCUMENT_MATCHING, {StrId::STR_FILENAME, StrId::STR_BINARY}, [] { return static_cast(KOREADER_STORE.getMatchMethod()); }, [](uint8_t v) { KOREADER_STORE.setMatchMethod(static_cast(v)); KOREADER_STORE.saveToFile(); }, - "koMatchMethod", "KOReader Sync"), + "koMatchMethod", StrId::STR_KOREADER_SYNC), // --- OPDS Browser (web-only, uses CrossPointSettings char arrays) --- - SettingInfo::String("OPDS Server URL", SETTINGS.opdsServerUrl, sizeof(SETTINGS.opdsServerUrl), "opdsServerUrl", - "OPDS Browser"), - SettingInfo::String("OPDS Username", SETTINGS.opdsUsername, sizeof(SETTINGS.opdsUsername), "opdsUsername", - "OPDS Browser"), - SettingInfo::String("OPDS Password", SETTINGS.opdsPassword, sizeof(SETTINGS.opdsPassword), "opdsPassword", - "OPDS Browser"), + SettingInfo::String(StrId::STR_OPDS_SERVER_URL, SETTINGS.opdsServerUrl, sizeof(SETTINGS.opdsServerUrl), + "opdsServerUrl", StrId::STR_OPDS_BROWSER), + SettingInfo::String(StrId::STR_USERNAME, SETTINGS.opdsUsername, sizeof(SETTINGS.opdsUsername), "opdsUsername", + StrId::STR_OPDS_BROWSER), + SettingInfo::String(StrId::STR_PASSWORD, SETTINGS.opdsPassword, sizeof(SETTINGS.opdsPassword), "opdsPassword", + StrId::STR_OPDS_BROWSER), }; -} +} \ No newline at end of file diff --git a/src/activities/boot_sleep/BootActivity.cpp b/src/activities/boot_sleep/BootActivity.cpp index 66d08fec..9e59ed59 100644 --- a/src/activities/boot_sleep/BootActivity.cpp +++ b/src/activities/boot_sleep/BootActivity.cpp @@ -1,6 +1,7 @@ #include "BootActivity.h" #include +#include #include "fontIds.h" #include "images/Logo120.h" @@ -13,8 +14,8 @@ void BootActivity::onEnter() { renderer.clearScreen(); renderer.drawImage(Logo120, (pageWidth - 120) / 2, (pageHeight - 120) / 2, 120, 120); - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD); - renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING"); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSPOINT), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_BOOTING)); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION); renderer.displayBuffer(); } diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index b843c857..bcf204ac 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -15,7 +16,7 @@ void SleepActivity::onEnter() { Activity::onEnter(); - GUI.drawPopup(renderer, "Entering Sleep..."); + GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP)); switch (SETTINGS.sleepScreen) { case (CrossPointSettings::SLEEP_SCREEN_MODE::BLANK): @@ -110,8 +111,8 @@ void SleepActivity::renderDefaultSleepScreen() const { renderer.clearScreen(); renderer.drawImage(Logo120, (pageWidth - 120) / 2, (pageHeight - 120) / 2, 120, 120); - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD); - renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING"); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSPOINT), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_SLEEPING)); // Make sleep screen dark unless light is selected in settings if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) { diff --git a/src/activities/browser/OpdsBookBrowserActivity.cpp b/src/activities/browser/OpdsBookBrowserActivity.cpp index f20b6bf0..4b012d9d 100644 --- a/src/activities/browser/OpdsBookBrowserActivity.cpp +++ b/src/activities/browser/OpdsBookBrowserActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -28,7 +29,7 @@ void OpdsBookBrowserActivity::onEnter() { currentPath = ""; // Root path - user provides full URL in settings selectorIndex = 0; errorMessage.clear(); - statusMessage = "Checking WiFi..."; + statusMessage = tr(STR_CHECKING_WIFI); requestUpdate(); // Check WiFi and connect if needed, then fetch feed @@ -60,7 +61,7 @@ void OpdsBookBrowserActivity::loop() { // WiFi connected - just retry fetching the feed LOG_DBG("OPDS", "Retry: WiFi connected, retrying fetch"); state = BrowserState::LOADING; - statusMessage = "Loading..."; + statusMessage = tr(STR_LOADING); requestUpdate(); fetchFeed(currentPath); } else { @@ -141,11 +142,11 @@ void OpdsBookBrowserActivity::render(Activity::RenderLock&&) { const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, "OPDS Browser", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_OPDS_BROWSER), true, EpdFontFamily::BOLD); if (state == BrowserState::CHECK_WIFI) { renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str()); - const auto labels = mappedInput.mapLabels("« Back", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; @@ -153,23 +154,23 @@ void OpdsBookBrowserActivity::render(Activity::RenderLock&&) { if (state == BrowserState::LOADING) { renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str()); - const auto labels = mappedInput.mapLabels("« Back", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == BrowserState::ERROR) { - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Error:"); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, tr(STR_ERROR_MSG)); renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, errorMessage.c_str()); - const auto labels = mappedInput.mapLabels("« Back", "Retry", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_RETRY), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == BrowserState::DOWNLOADING) { - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 40, "Downloading..."); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 40, tr(STR_DOWNLOADING)); renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 10, statusMessage.c_str()); if (downloadTotal > 0) { const int barWidth = pageWidth - 100; @@ -184,15 +185,15 @@ void OpdsBookBrowserActivity::render(Activity::RenderLock&&) { // Browsing state // Show appropriate button hint based on selected entry type - const char* confirmLabel = "Open"; + const char* confirmLabel = tr(STR_OPEN); if (!entries.empty() && entries[selectorIndex].type == OpdsEntryType::BOOK) { - confirmLabel = "Download"; + confirmLabel = tr(STR_DOWNLOAD); } - const auto labels = mappedInput.mapLabels("« Back", confirmLabel, "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), confirmLabel, "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); if (entries.empty()) { - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "No entries found"); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_NO_ENTRIES)); renderer.displayBuffer(); return; } @@ -227,7 +228,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) { const char* serverUrl = SETTINGS.opdsServerUrl; if (strlen(serverUrl) == 0) { state = BrowserState::ERROR; - errorMessage = "No server URL configured"; + errorMessage = tr(STR_NO_SERVER_URL); requestUpdate(); return; } @@ -241,7 +242,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) { OpdsParserStream stream{parser}; if (!HttpDownloader::fetchUrl(url, stream)) { state = BrowserState::ERROR; - errorMessage = "Failed to fetch feed"; + errorMessage = tr(STR_FETCH_FEED_FAILED); requestUpdate(); return; } @@ -249,7 +250,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) { if (!parser) { state = BrowserState::ERROR; - errorMessage = "Failed to parse feed"; + errorMessage = tr(STR_PARSE_FEED_FAILED); requestUpdate(); return; } @@ -260,7 +261,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) { if (entries.empty()) { state = BrowserState::ERROR; - errorMessage = "No entries found"; + errorMessage = tr(STR_NO_ENTRIES); requestUpdate(); return; } @@ -275,7 +276,7 @@ void OpdsBookBrowserActivity::navigateToEntry(const OpdsEntry& entry) { currentPath = entry.href; state = BrowserState::LOADING; - statusMessage = "Loading..."; + statusMessage = tr(STR_LOADING); entries.clear(); selectorIndex = 0; requestUpdate(); @@ -293,7 +294,7 @@ void OpdsBookBrowserActivity::navigateBack() { navigationHistory.pop_back(); state = BrowserState::LOADING; - statusMessage = "Loading..."; + statusMessage = tr(STR_LOADING); entries.clear(); selectorIndex = 0; requestUpdate(); @@ -340,7 +341,7 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) { requestUpdate(); } else { state = BrowserState::ERROR; - errorMessage = "Download failed"; + errorMessage = tr(STR_DOWNLOAD_FAILED); requestUpdate(); } } @@ -349,7 +350,7 @@ void OpdsBookBrowserActivity::checkAndConnectWifi() { // Already connected? Verify connection is valid by checking IP if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) { state = BrowserState::LOADING; - statusMessage = "Loading..."; + statusMessage = tr(STR_LOADING); requestUpdate(); fetchFeed(currentPath); return; @@ -373,7 +374,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) { if (connected) { LOG_DBG("OPDS", "WiFi connected via selection, fetching feed"); state = BrowserState::LOADING; - statusMessage = "Loading..."; + statusMessage = tr(STR_LOADING); requestUpdate(); fetchFeed(currentPath); } else { @@ -383,7 +384,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) { WiFi.disconnect(); WiFi.mode(WIFI_OFF); state = BrowserState::ERROR; - errorMessage = "WiFi connection failed"; + errorMessage = tr(STR_WIFI_CONN_FAILED); requestUpdate(); } } diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 61102069..3fff5b11 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -69,7 +70,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) { // Try to generate thumbnail image for Continue Reading card if (!showingLoading) { showingLoading = true; - popupRect = GUI.drawPopup(renderer, "Loading..."); + popupRect = GUI.drawPopup(renderer, tr(STR_LOADING)); } GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size())); bool success = epub.generateThumbBmp(coverHeight); @@ -87,7 +88,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) { // Try to generate thumbnail image for Continue Reading card if (!showingLoading) { showingLoading = true; - popupRect = GUI.drawPopup(renderer, "Loading..."); + popupRect = GUI.drawPopup(renderer, tr(STR_LOADING)); } GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size())); bool success = xtc.generateThumbBmp(coverHeight); @@ -226,10 +227,11 @@ void HomeActivity::render(Activity::RenderLock&&) { std::bind(&HomeActivity::storeCoverBuffer, this)); // Build menu items dynamically - std::vector menuItems = {"Browse Files", "Recents", "File Transfer", "Settings"}; + std::vector menuItems = {tr(STR_BROWSE_FILES), tr(STR_MENU_RECENT_BOOKS), tr(STR_FILE_TRANSFER), + tr(STR_SETTINGS_TITLE)}; if (hasOpdsUrl) { // Insert OPDS Browser after My Library - menuItems.insert(menuItems.begin() + 2, "OPDS Browser"); + menuItems.insert(menuItems.begin() + 2, tr(STR_OPDS_BROWSER)); } GUI.drawButtonMenu( @@ -240,7 +242,7 @@ void HomeActivity::render(Activity::RenderLock&&) { static_cast(menuItems.size()), selectorIndex - recentBooks.size(), [&menuItems](int index) { return std::string(menuItems[index]); }, nullptr); - const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down"); + const auto labels = mappedInput.mapLabels("", tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/home/MyLibraryActivity.cpp b/src/activities/home/MyLibraryActivity.cpp index 54e69adf..c011198e 100644 --- a/src/activities/home/MyLibraryActivity.cpp +++ b/src/activities/home/MyLibraryActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -195,13 +196,13 @@ void MyLibraryActivity::render(Activity::RenderLock&&) { const auto pageHeight = renderer.getScreenHeight(); auto metrics = UITheme::getInstance().getMetrics(); - auto folderName = basepath == "/" ? "SD card" : basepath.substr(basepath.rfind('/') + 1).c_str(); + auto folderName = basepath == "/" ? tr(STR_SD_CARD) : basepath.substr(basepath.rfind('/') + 1).c_str(); GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, folderName); const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing; if (files.empty()) { - renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, "No books found"); + renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, tr(STR_NO_BOOKS_FOUND)); } else { GUI.drawList( renderer, Rect{0, contentTop, pageWidth, contentHeight}, files.size(), selectorIndex, @@ -209,7 +210,8 @@ void MyLibraryActivity::render(Activity::RenderLock&&) { } // Help text - const auto labels = mappedInput.mapLabels(basepath == "/" ? "« Home" : "« Back", "Open", "Up", "Down"); + const auto labels = mappedInput.mapLabels(basepath == "/" ? tr(STR_HOME) : tr(STR_BACK), tr(STR_OPEN), tr(STR_DIR_UP), + tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/home/RecentBooksActivity.cpp b/src/activities/home/RecentBooksActivity.cpp index 74bbf4fd..05317214 100644 --- a/src/activities/home/RecentBooksActivity.cpp +++ b/src/activities/home/RecentBooksActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -89,14 +90,14 @@ void RecentBooksActivity::render(Activity::RenderLock&&) { const auto pageHeight = renderer.getScreenHeight(); auto metrics = UITheme::getInstance().getMetrics(); - GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, "Recent Books"); + GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_MENU_RECENT_BOOKS)); const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing; // Recent tab if (recentBooks.empty()) { - renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, "No recent books"); + renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, tr(STR_NO_RECENT_BOOKS)); } else { GUI.drawList( renderer, Rect{0, contentTop, pageWidth, contentHeight}, recentBooks.size(), selectorIndex, @@ -105,7 +106,7 @@ void RecentBooksActivity::render(Activity::RenderLock&&) { } // Help text - const auto labels = mappedInput.mapLabels("« Home", "Open", "Up", "Down"); + const auto labels = mappedInput.mapLabels(tr(STR_HOME), tr(STR_OPEN), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/home/RecentBooksActivity.h b/src/activities/home/RecentBooksActivity.h index 3c9819c1..9ad44213 100644 --- a/src/activities/home/RecentBooksActivity.h +++ b/src/activities/home/RecentBooksActivity.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include diff --git a/src/activities/network/CalibreConnectActivity.cpp b/src/activities/network/CalibreConnectActivity.cpp index fdf4db6c..74f281c7 100644 --- a/src/activities/network/CalibreConnectActivity.cpp +++ b/src/activities/network/CalibreConnectActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -178,9 +179,9 @@ void CalibreConnectActivity::render(Activity::RenderLock&&) { renderer.clearScreen(); const auto pageHeight = renderer.getScreenHeight(); if (state == CalibreConnectState::SERVER_STARTING) { - renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Starting Calibre...", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_CALIBRE_STARTING), true, EpdFontFamily::BOLD); } else if (state == CalibreConnectState::ERROR) { - renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Calibre setup failed", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_CONNECTION_FAILED), true, EpdFontFamily::BOLD); } renderer.displayBuffer(); } @@ -190,31 +191,32 @@ void CalibreConnectActivity::renderServerRunning() const { constexpr int SMALL_SPACING = 20; constexpr int SECTION_SPACING = 40; constexpr int TOP_PADDING = 14; - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Connect to Calibre", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_CALIBRE_WIRELESS), true, EpdFontFamily::BOLD); int y = 55 + TOP_PADDING; - renderer.drawCenteredText(UI_10_FONT_ID, y, "Network", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_WIFI_NETWORKS), true, EpdFontFamily::BOLD); y += LINE_SPACING; - std::string ssidInfo = "Network: " + connectedSSID; + std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID; if (ssidInfo.length() > 28) { ssidInfo.replace(25, ssidInfo.length() - 25, "..."); } renderer.drawCenteredText(UI_10_FONT_ID, y, ssidInfo.c_str()); - renderer.drawCenteredText(UI_10_FONT_ID, y + LINE_SPACING, ("IP: " + connectedIP).c_str()); + renderer.drawCenteredText(UI_10_FONT_ID, y + LINE_SPACING, + (std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP).c_str()); y += LINE_SPACING * 2 + SECTION_SPACING; - renderer.drawCenteredText(UI_10_FONT_ID, y, "Setup", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_CALIBRE_SETUP), true, EpdFontFamily::BOLD); y += LINE_SPACING; - renderer.drawCenteredText(SMALL_FONT_ID, y, "1) Install CrossPoint Reader plugin"); - renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING, "2) Be on the same WiFi network"); - renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 2, "3) In Calibre: \"Send to device\""); - renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 3, "Keep this screen open while sending"); + renderer.drawCenteredText(SMALL_FONT_ID, y, tr(STR_CALIBRE_INSTRUCTION_1)); + renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING, tr(STR_CALIBRE_INSTRUCTION_2)); + renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 2, tr(STR_CALIBRE_INSTRUCTION_3)); + renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 3, tr(STR_CALIBRE_INSTRUCTION_4)); y += SMALL_SPACING * 3 + SECTION_SPACING; - renderer.drawCenteredText(UI_10_FONT_ID, y, "Status", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_CALIBRE_STATUS), true, EpdFontFamily::BOLD); y += LINE_SPACING; if (lastProgressTotal > 0 && lastProgressReceived <= lastProgressTotal) { - std::string label = "Receiving"; + std::string label = tr(STR_CALIBRE_RECEIVING); if (!currentUploadName.empty()) { label += ": " + currentUploadName; if (label.length() > 34) { @@ -230,13 +232,13 @@ void CalibreConnectActivity::renderServerRunning() const { } if (lastCompleteAt > 0 && (millis() - lastCompleteAt) < 6000) { - std::string msg = "Received: " + lastCompleteName; + std::string msg = std::string(tr(STR_CALIBRE_RECEIVED)) + lastCompleteName; if (msg.length() > 36) { msg.replace(33, msg.length() - 33, "..."); } renderer.drawCenteredText(SMALL_FONT_ID, y, msg.c_str()); } - const auto labels = mappedInput.mapLabels("« Exit", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_EXIT), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index 076e4e29..18a33f97 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -345,7 +346,7 @@ void CrossPointWebServerActivity::render(Activity::RenderLock&&) { } else if (state == WebServerActivityState::AP_STARTING) { renderer.clearScreen(); const auto pageHeight = renderer.getScreenHeight(); - renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Starting Hotspot...", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_STARTING_HOTSPOT), true, EpdFontFamily::BOLD); renderer.displayBuffer(); } } @@ -376,21 +377,20 @@ void CrossPointWebServerActivity::renderServerRunning() const { // Use consistent line spacing constexpr int LINE_SPACING = 28; // Space between lines - renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_FILE_TRANSFER), true, EpdFontFamily::BOLD); if (isApMode) { // AP mode display - center the content block int startY = 55; - renderer.drawCenteredText(UI_10_FONT_ID, startY, "Hotspot Mode", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, startY, tr(STR_HOTSPOT_MODE), true, EpdFontFamily::BOLD); - std::string ssidInfo = "Network: " + connectedSSID; + std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID; renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ssidInfo.c_str()); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 2, "Connect your device to this WiFi network"); + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 2, tr(STR_CONNECT_WIFI_HINT)); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, - "or scan QR code with your phone to connect to Wifi."); + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, tr(STR_SCAN_QR_WIFI_HINT)); // Show QR code for URL const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;"; drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig); @@ -401,24 +401,24 @@ void CrossPointWebServerActivity::renderServerRunning() const { renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str(), true, EpdFontFamily::BOLD); // Show IP address as fallback - std::string ipUrl = "or http://" + connectedIP + "/"; + std::string ipUrl = std::string(tr(STR_OR_HTTP_PREFIX)) + connectedIP + "/"; renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, ipUrl.c_str()); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Open this URL in your browser"); + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, tr(STR_OPEN_URL_HINT)); // Show QR code for URL - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "or scan QR code with your phone:"); + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, tr(STR_SCAN_QR_HINT)); drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl); } else { // STA mode display (original behavior) const int startY = 65; - std::string ssidInfo = "Network: " + connectedSSID; + std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID; if (ssidInfo.length() > 28) { ssidInfo.replace(25, ssidInfo.length() - 25, "..."); } renderer.drawCenteredText(UI_10_FONT_ID, startY, ssidInfo.c_str()); - std::string ipInfo = "IP Address: " + connectedIP; + std::string ipInfo = std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP; renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ipInfo.c_str()); // Show web server URL prominently @@ -426,16 +426,16 @@ void CrossPointWebServerActivity::renderServerRunning() const { renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, webInfo.c_str(), true, EpdFontFamily::BOLD); // Also show hostname URL - std::string hostnameUrl = std::string("or http://") + AP_HOSTNAME + ".local/"; + std::string hostnameUrl = std::string(tr(STR_OR_HTTP_PREFIX)) + AP_HOSTNAME + ".local/"; renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str()); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Open this URL in your browser"); + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, tr(STR_OPEN_URL_HINT)); // Show QR code for URL drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:"); + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, tr(STR_SCAN_QR_HINT)); } - const auto labels = mappedInput.mapLabels("« Exit", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_EXIT), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } diff --git a/src/activities/network/NetworkModeSelectionActivity.cpp b/src/activities/network/NetworkModeSelectionActivity.cpp index abe984ff..466ea220 100644 --- a/src/activities/network/NetworkModeSelectionActivity.cpp +++ b/src/activities/network/NetworkModeSelectionActivity.cpp @@ -1,6 +1,7 @@ #include "NetworkModeSelectionActivity.h" #include +#include #include "MappedInputManager.h" #include "components/UITheme.h" @@ -8,12 +9,6 @@ namespace { constexpr int MENU_ITEM_COUNT = 3; -const char* MENU_ITEMS[MENU_ITEM_COUNT] = {"Join a Network", "Connect to Calibre", "Create Hotspot"}; -const char* MENU_DESCRIPTIONS[MENU_ITEM_COUNT] = { - "Connect to an existing WiFi network", - "Use Calibre wireless device transfers", - "Create a WiFi network others can join", -}; } // namespace void NetworkModeSelectionActivity::onEnter() { @@ -66,10 +61,16 @@ void NetworkModeSelectionActivity::render(Activity::RenderLock&&) { const auto pageHeight = renderer.getScreenHeight(); // Draw header - renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_FILE_TRANSFER), true, EpdFontFamily::BOLD); // Draw subtitle - renderer.drawCenteredText(UI_10_FONT_ID, 50, "How would you like to connect?"); + renderer.drawCenteredText(UI_10_FONT_ID, 50, tr(STR_HOW_CONNECT)); + + // Menu items and descriptions + static constexpr StrId menuItems[MENU_ITEM_COUNT] = {StrId::STR_JOIN_NETWORK, StrId::STR_CALIBRE_WIRELESS, + StrId::STR_CREATE_HOTSPOT}; + static constexpr StrId menuDescs[MENU_ITEM_COUNT] = {StrId::STR_JOIN_DESC, StrId::STR_CALIBRE_DESC, + StrId::STR_HOTSPOT_DESC}; // Draw menu items centered on screen constexpr int itemHeight = 50; // Height for each menu item (including description) @@ -86,12 +87,12 @@ void NetworkModeSelectionActivity::render(Activity::RenderLock&&) { // Draw text: black=false (white text) when selected (on black background) // black=true (black text) when not selected (on white background) - renderer.drawText(UI_10_FONT_ID, 30, itemY, MENU_ITEMS[i], /*black=*/!isSelected); - renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected); + renderer.drawText(UI_10_FONT_ID, 30, itemY, I18N.get(menuItems[i]), /*black=*/!isSelected); + renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, I18N.get(menuDescs[i]), /*black=*/!isSelected); } // Draw help text at bottom - const auto labels = mappedInput.mapLabels("« Back", "Select", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index bbfefdb7..deefd781 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -1,6 +1,7 @@ #include "WifiSelectionActivity.h" #include +#include #include #include @@ -38,9 +39,9 @@ void WifiSelectionActivity::onEnter() { // Cache MAC address for display uint8_t mac[6]; WiFi.macAddress(mac); - char macStr[32]; - snprintf(macStr, sizeof(macStr), "MAC address: %02x-%02x-%02x-%02x-%02x-%02x", mac[0], mac[1], mac[2], mac[3], mac[4], - mac[5]); + char macStr[64]; + snprintf(macStr, sizeof(macStr), "%s %02x-%02x-%02x-%02x-%02x-%02x", tr(STR_MAC_ADDRESS), mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]); cachedMacAddress = std::string(macStr); // Trigger first update to show scanning message @@ -191,7 +192,7 @@ void WifiSelectionActivity::selectNetwork(const int index) { state = WifiSelectionState::PASSWORD_ENTRY; // Don't allow screen updates while changing activity enterNewActivity(new KeyboardEntryActivity( - renderer, mappedInput, "Enter WiFi Password", + renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD), "", // No initial text 50, // Y position 64, // Max password length @@ -266,9 +267,9 @@ void WifiSelectionActivity::checkConnectionStatus() { } if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) { - connectionError = "Error: General failure"; + connectionError = tr(STR_ERROR_GENERAL_FAILURE); if (status == WL_NO_SSID_AVAIL) { - connectionError = "Error: Network not found"; + connectionError = tr(STR_ERROR_NETWORK_NOT_FOUND); } state = WifiSelectionState::CONNECTION_FAILED; requestUpdate(); @@ -278,7 +279,7 @@ void WifiSelectionActivity::checkConnectionStatus() { // Check for timeout if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) { WiFi.disconnect(); - connectionError = "Error: Connection timeout"; + connectionError = tr(STR_ERROR_CONNECTION_TIMEOUT); state = WifiSelectionState::CONNECTION_FAILED; requestUpdate(); return; @@ -510,14 +511,14 @@ void WifiSelectionActivity::renderNetworkList() const { const auto pageHeight = renderer.getScreenHeight(); // Draw header - renderer.drawCenteredText(UI_12_FONT_ID, 15, "WiFi Networks", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_WIFI_NETWORKS), true, EpdFontFamily::BOLD); if (networks.empty()) { // No networks found or scan failed const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto top = (pageHeight - height) / 2; - renderer.drawCenteredText(UI_10_FONT_ID, top, "No networks found"); - renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press Connect to scan again"); + renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_NO_NETWORKS)); + renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, tr(STR_PRESS_OK_SCAN)); } else { // Calculate how many networks we can display constexpr int startY = 60; @@ -572,8 +573,8 @@ void WifiSelectionActivity::renderNetworkList() const { } // Show network count - char countStr[32]; - snprintf(countStr, sizeof(countStr), "%zu networks found", networks.size()); + char countStr[64]; + snprintf(countStr, sizeof(countStr), tr(STR_NETWORKS_FOUND), networks.size()); renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr); } @@ -581,12 +582,12 @@ void WifiSelectionActivity::renderNetworkList() const { renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str()); // Draw help text - renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved"); + renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, tr(STR_NETWORK_LEGEND)); const bool hasSavedPassword = !networks.empty() && networks[selectedNetworkIndex].hasSavedPassword; - const char* forgetLabel = hasSavedPassword ? "Forget" : ""; + const char* forgetLabel = hasSavedPassword ? tr(STR_FORGET_BUTTON) : ""; - const auto labels = mappedInput.mapLabels("« Back", "Connect", forgetLabel, "Refresh"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_CONNECT), forgetLabel, tr(STR_RETRY)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } @@ -596,11 +597,11 @@ void WifiSelectionActivity::renderConnecting() const { const auto top = (pageHeight - height) / 2; if (state == WifiSelectionState::SCANNING) { - renderer.drawCenteredText(UI_10_FONT_ID, top, "Scanning..."); + renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_SCANNING)); } else { - renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Connecting...", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_CONNECTING), true, EpdFontFamily::BOLD); - std::string ssidInfo = "to " + selectedSSID; + std::string ssidInfo = std::string(tr(STR_TO_PREFIX)) + selectedSSID; if (ssidInfo.length() > 25) { ssidInfo.replace(22, ssidInfo.length() - 22, "..."); } @@ -613,19 +614,19 @@ void WifiSelectionActivity::renderConnected() const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto top = (pageHeight - height * 4) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 30, "Connected!", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, top - 30, tr(STR_CONNECTED), true, EpdFontFamily::BOLD); - std::string ssidInfo = "Network: " + selectedSSID; + std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID; if (ssidInfo.length() > 28) { ssidInfo.replace(25, ssidInfo.length() - 25, "..."); } renderer.drawCenteredText(UI_10_FONT_ID, top + 10, ssidInfo.c_str()); - const std::string ipInfo = "IP Address: " + connectedIP; + const std::string ipInfo = std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP; renderer.drawCenteredText(UI_10_FONT_ID, top + 40, ipInfo.c_str()); // Use centralized button hints - const auto labels = mappedInput.mapLabels("", "Continue", "", ""); + const auto labels = mappedInput.mapLabels("", tr(STR_DONE), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } @@ -635,15 +636,15 @@ void WifiSelectionActivity::renderSavePrompt() const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto top = (pageHeight - height * 3) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Connected!", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_CONNECTED), true, EpdFontFamily::BOLD); - std::string ssidInfo = "Network: " + selectedSSID; + std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID; if (ssidInfo.length() > 28) { ssidInfo.replace(25, ssidInfo.length() - 25, "..."); } renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str()); - renderer.drawCenteredText(UI_10_FONT_ID, top + 40, "Save password for next time?"); + renderer.drawCenteredText(UI_10_FONT_ID, top + 40, tr(STR_SAVE_PASSWORD)); // Draw Yes/No buttons const int buttonY = top + 80; @@ -654,20 +655,22 @@ void WifiSelectionActivity::renderSavePrompt() const { // Draw "Yes" button if (savePromptSelection == 0) { - renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Yes]"); + std::string text = "[" + std::string(tr(STR_YES)) + "]"; + renderer.drawText(UI_10_FONT_ID, startX, buttonY, text.c_str()); } else { - renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, "Yes"); + renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, tr(STR_YES)); } // Draw "No" button if (savePromptSelection == 1) { - renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[No]"); + std::string text = "[" + std::string(tr(STR_NO)) + "]"; + renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, text.c_str()); } else { - renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "No"); + renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, tr(STR_NO)); } // Use centralized button hints - const auto labels = mappedInput.mapLabels("« Skip", "Select", "Left", "Right"); + const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } @@ -676,11 +679,11 @@ void WifiSelectionActivity::renderConnectionFailed() const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto top = (pageHeight - height * 2) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 20, "Connection Failed", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, top - 20, tr(STR_CONNECTION_FAILED), true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, top + 20, connectionError.c_str()); // Use centralized button hints - const auto labels = mappedInput.mapLabels("« Back", "Continue", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_DONE), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } @@ -690,14 +693,15 @@ void WifiSelectionActivity::renderForgetPrompt() const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto top = (pageHeight - height * 3) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Forget Network", true, EpdFontFamily::BOLD); - std::string ssidInfo = "Network: " + selectedSSID; + renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_FORGET_NETWORK), true, EpdFontFamily::BOLD); + + std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID; if (ssidInfo.length() > 28) { ssidInfo.replace(25, ssidInfo.length() - 25, "..."); } renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str()); - renderer.drawCenteredText(UI_10_FONT_ID, top + 40, "Forget network and remove saved password?"); + renderer.drawCenteredText(UI_10_FONT_ID, top + 40, tr(STR_FORGET_AND_REMOVE)); // Draw Cancel/Forget network buttons const int buttonY = top + 80; @@ -708,19 +712,21 @@ void WifiSelectionActivity::renderForgetPrompt() const { // Draw "Cancel" button if (forgetPromptSelection == 0) { - renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Cancel]"); + std::string text = "[" + std::string(tr(STR_CANCEL)) + "]"; + renderer.drawText(UI_10_FONT_ID, startX, buttonY, text.c_str()); } else { - renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, "Cancel"); + renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, tr(STR_CANCEL)); } // Draw "Forget network" button if (forgetPromptSelection == 1) { - renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[Forget network]"); + std::string text = "[" + std::string(tr(STR_FORGET_BUTTON)) + "]"; + renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, text.c_str()); } else { - renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "Forget network"); + renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, tr(STR_FORGET_BUTTON)); } // Use centralized button hints - const auto labels = mappedInput.mapLabels("« Back", "Select", "Left", "Right"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 0c523619..db6cbc16 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "CrossPointSettings.h" @@ -501,7 +502,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) { // Show end of book screen if (currentSpineIndex == epub->getSpineItemsCount()) { renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "End of book", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } @@ -540,7 +541,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) { viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) { LOG_DBG("ERS", "Cache not found, building..."); - const auto popupFn = [this]() { GUI.drawPopup(renderer, "Indexing..."); }; + const auto popupFn = [this]() { GUI.drawPopup(renderer, tr(STR_INDEXING)); }; if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, @@ -585,7 +586,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) { if (section->pageCount == 0) { LOG_DBG("ERS", "No pages to render"); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty chapter", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_EMPTY_CHAPTER), true, EpdFontFamily::BOLD); renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); renderer.displayBuffer(); return; @@ -593,7 +594,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) { if (section->currentPage < 0 || section->currentPage >= section->pageCount) { LOG_DBG("ERS", "Page out of bounds: %d (max %d)", section->currentPage, section->pageCount); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Out of bounds", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_OUT_OF_BOUNDS), true, EpdFontFamily::BOLD); renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); renderer.displayBuffer(); return; @@ -762,8 +763,8 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in std::string title; int titleWidth; if (tocIndex == -1) { - title = "Unnamed"; - titleWidth = renderer.getTextWidth(SMALL_FONT_ID, "Unnamed"); + title = tr(STR_UNNAMED); + titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); } else { const auto tocItem = epub->getTocItem(tocIndex); title = tocItem.title; diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index ba62015b..6089a7d2 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -1,6 +1,7 @@ #include "EpubReaderChapterSelectionActivity.h" #include +#include #include "MappedInputManager.h" #include "components/UITheme.h" @@ -104,8 +105,8 @@ void EpubReaderChapterSelectionActivity::render(Activity::RenderLock&&) { // Manual centering to honor content gutters. const int titleX = - contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, "Go to Chapter", EpdFontFamily::BOLD)) / 2; - renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, "Go to Chapter", true, EpdFontFamily::BOLD); + contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, tr(STR_SELECT_CHAPTER), EpdFontFamily::BOLD)) / 2; + renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, tr(STR_SELECT_CHAPTER), true, EpdFontFamily::BOLD); const auto pageStartIndex = selectorIndex / pageItems * pageItems; // Highlight only the content area, not the hint gutters. @@ -127,7 +128,7 @@ void EpubReaderChapterSelectionActivity::render(Activity::RenderLock&&) { renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected); } - const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index 99ab17f3..41774d94 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -1,6 +1,7 @@ #include "EpubReaderMenuActivity.h" #include +#include #include "MappedInputManager.h" #include "components/UITheme.h" @@ -84,9 +85,10 @@ void EpubReaderMenuActivity::render(Activity::RenderLock&&) { // Progress summary std::string progressLine; if (totalPages > 0) { - progressLine = "Chapter: " + std::to_string(currentPage) + "/" + std::to_string(totalPages) + " pages | "; + progressLine = std::string(tr(STR_CHAPTER_PREFIX)) + std::to_string(currentPage) + "/" + + std::to_string(totalPages) + std::string(tr(STR_PAGES_SEPARATOR)); } - progressLine += "Book: " + std::to_string(bookProgressPercent) + "%"; + progressLine += std::string(tr(STR_BOOK_PREFIX)) + std::to_string(bookProgressPercent) + "%"; renderer.drawCenteredText(UI_10_FONT_ID, 45, progressLine.c_str()); // Menu Items @@ -102,18 +104,18 @@ void EpubReaderMenuActivity::render(Activity::RenderLock&&) { renderer.fillRect(contentX, displayY, contentWidth - 1, lineHeight, true); } - renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, menuItems[i].label.c_str(), !isSelected); + renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, I18N.get(menuItems[i].labelId), !isSelected); if (menuItems[i].action == MenuAction::ROTATE_SCREEN) { // Render current orientation value on the right edge of the content area. - const auto value = orientationLabels[pendingOrientation]; + const char* value = I18N.get(orientationLabels[pendingOrientation]); const auto width = renderer.getTextWidth(UI_10_FONT_ID, value); renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected); } } // Footer / Hints - const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index 6bdb2436..d2da2b3c 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include @@ -34,21 +35,24 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { private: struct MenuItem { MenuAction action; - std::string label; + StrId labelId; }; // Fixed menu layout (order matters for up/down navigation). - const std::vector menuItems = { - {MenuAction::SELECT_CHAPTER, "Go to Chapter"}, {MenuAction::ROTATE_SCREEN, "Reading Orientation"}, - {MenuAction::GO_TO_PERCENT, "Go to %"}, {MenuAction::GO_HOME, "Go Home"}, - {MenuAction::SYNC, "Sync Progress"}, {MenuAction::DELETE_CACHE, "Delete Book Cache"}}; + const std::vector menuItems = {{MenuAction::SELECT_CHAPTER, StrId::STR_SELECT_CHAPTER}, + {MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION}, + {MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT}, + {MenuAction::GO_HOME, StrId::STR_GO_HOME_BUTTON}, + {MenuAction::SYNC, StrId::STR_SYNC_PROGRESS}, + {MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE}}; int selectedIndex = 0; ButtonNavigator buttonNavigator; std::string title = "Reader Menu"; uint8_t pendingOrientation = 0; - const std::vector orientationLabels = {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}; + const std::vector orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, + StrId::STR_LANDSCAPE_CCW}; int currentPage = 0; int totalPages = 0; int bookProgressPercent = 0; diff --git a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp index 060f0ceb..188d19e2 100644 --- a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp @@ -1,6 +1,7 @@ #include "EpubReaderPercentSelectionActivity.h" #include +#include #include "MappedInputManager.h" #include "components/UITheme.h" @@ -59,7 +60,7 @@ void EpubReaderPercentSelectionActivity::render(Activity::RenderLock&&) { renderer.clearScreen(); // Title and numeric percent value. - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Position", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_GO_TO_PERCENT), true, EpdFontFamily::BOLD); const std::string percentText = std::to_string(percent) + "%"; renderer.drawCenteredText(UI_12_FONT_ID, 90, percentText.c_str(), true, EpdFontFamily::BOLD); @@ -84,10 +85,10 @@ void EpubReaderPercentSelectionActivity::render(Activity::RenderLock&&) { renderer.fillRect(knobX, barY - 4, 4, barHeight + 8, true); // Hint text for step sizes. - renderer.drawCenteredText(SMALL_FONT_ID, barY + 30, "Left/Right: 1% Up/Down: 10%", true); + renderer.drawCenteredText(SMALL_FONT_ID, barY + 30, tr(STR_PERCENT_STEP_HINT), true); // Button hints follow the current front button layout. - const auto labels = mappedInput.mapLabels("« Back", "Select", "-", "+"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "-", "+"); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/reader/KOReaderSyncActivity.cpp b/src/activities/reader/KOReaderSyncActivity.cpp index c60fdc97..6d6d88f6 100644 --- a/src/activities/reader/KOReaderSyncActivity.cpp +++ b/src/activities/reader/KOReaderSyncActivity.cpp @@ -1,6 +1,7 @@ #include "KOReaderSyncActivity.h" #include +#include #include #include #include @@ -54,7 +55,7 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) { { RenderLock lock(*this); state = SYNCING; - statusMessage = "Syncing time..."; + statusMessage = tr(STR_SYNCING_TIME); } requestUpdate(); @@ -63,7 +64,7 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) { { RenderLock lock(*this); - statusMessage = "Calculating document hash..."; + statusMessage = tr(STR_CALC_HASH); } requestUpdate(); @@ -81,7 +82,7 @@ void KOReaderSyncActivity::performSync() { { RenderLock lock(*this); state = SYNC_FAILED; - statusMessage = "Failed to calculate document hash"; + statusMessage = tr(STR_HASH_FAILED); } requestUpdate(); return; @@ -91,7 +92,7 @@ void KOReaderSyncActivity::performSync() { { RenderLock lock(*this); - statusMessage = "Fetching remote progress..."; + statusMessage = tr(STR_FETCH_PROGRESS); } requestUpdateAndWait(); @@ -140,7 +141,7 @@ void KOReaderSyncActivity::performUpload() { { RenderLock lock(*this); state = UPLOADING; - statusMessage = "Uploading progress..."; + statusMessage = tr(STR_UPLOAD_PROGRESS); } requestUpdate(); requestUpdateAndWait(); @@ -191,7 +192,7 @@ void KOReaderSyncActivity::onEnter() { if (WiFi.status() == WL_CONNECTED) { LOG_DBG("KOSync", "Already connected to WiFi"); state = SYNCING; - statusMessage = "Syncing time..."; + statusMessage = tr(STR_SYNCING_TIME); requestUpdate(); // Perform sync directly (will be handled in loop) @@ -202,7 +203,7 @@ void KOReaderSyncActivity::onEnter() { syncTimeWithNTP(); { RenderLock lock(*self); - self->statusMessage = "Calculating document hash..."; + self->statusMessage = tr(STR_CALC_HASH); } self->requestUpdate(); self->performSync(); @@ -236,13 +237,13 @@ void KOReaderSyncActivity::render(Activity::RenderLock&&) { const auto pageWidth = renderer.getScreenWidth(); renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_KOREADER_SYNC), true, EpdFontFamily::BOLD); if (state == NO_CREDENTIALS) { - renderer.drawCenteredText(UI_10_FONT_ID, 280, "No credentials configured", true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, 320, "Set up KOReader account in Settings"); + renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_NO_CREDENTIALS_MSG), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 320, tr(STR_KOREADER_SETUP_HINT)); - const auto labels = mappedInput.mapLabels("Back", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; @@ -256,40 +257,41 @@ void KOReaderSyncActivity::render(Activity::RenderLock&&) { if (state == SHOWING_RESULT) { // Show comparison - renderer.drawCenteredText(UI_10_FONT_ID, 120, "Progress found!", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 120, tr(STR_PROGRESS_FOUND), true, EpdFontFamily::BOLD); // Get chapter names from TOC const int remoteTocIndex = epub->getTocIndexForSpineIndex(remotePosition.spineIndex); const int localTocIndex = epub->getTocIndexForSpineIndex(currentSpineIndex); - const std::string remoteChapter = (remoteTocIndex >= 0) - ? epub->getTocItem(remoteTocIndex).title - : ("Section " + std::to_string(remotePosition.spineIndex + 1)); - const std::string localChapter = (localTocIndex >= 0) ? epub->getTocItem(localTocIndex).title - : ("Section " + std::to_string(currentSpineIndex + 1)); + const std::string remoteChapter = + (remoteTocIndex >= 0) ? epub->getTocItem(remoteTocIndex).title + : (std::string(tr(STR_SECTION_PREFIX)) + std::to_string(remotePosition.spineIndex + 1)); + const std::string localChapter = + (localTocIndex >= 0) ? epub->getTocItem(localTocIndex).title + : (std::string(tr(STR_SECTION_PREFIX)) + std::to_string(currentSpineIndex + 1)); // Remote progress - chapter and page - renderer.drawText(UI_10_FONT_ID, 20, 160, "Remote:", true); + renderer.drawText(UI_10_FONT_ID, 20, 160, tr(STR_REMOTE_LABEL), true); char remoteChapterStr[128]; snprintf(remoteChapterStr, sizeof(remoteChapterStr), " %s", remoteChapter.c_str()); renderer.drawText(UI_10_FONT_ID, 20, 185, remoteChapterStr); char remotePageStr[64]; - snprintf(remotePageStr, sizeof(remotePageStr), " Page %d, %.2f%% overall", remotePosition.pageNumber + 1, + snprintf(remotePageStr, sizeof(remotePageStr), tr(STR_PAGE_OVERALL_FORMAT), remotePosition.pageNumber + 1, remoteProgress.percentage * 100); renderer.drawText(UI_10_FONT_ID, 20, 210, remotePageStr); if (!remoteProgress.device.empty()) { char deviceStr[64]; - snprintf(deviceStr, sizeof(deviceStr), " From: %s", remoteProgress.device.c_str()); + snprintf(deviceStr, sizeof(deviceStr), tr(STR_DEVICE_FROM_FORMAT), remoteProgress.device.c_str()); renderer.drawText(UI_10_FONT_ID, 20, 235, deviceStr); } // Local progress - chapter and page - renderer.drawText(UI_10_FONT_ID, 20, 270, "Local:", true); + renderer.drawText(UI_10_FONT_ID, 20, 270, tr(STR_LOCAL_LABEL), true); char localChapterStr[128]; snprintf(localChapterStr, sizeof(localChapterStr), " %s", localChapter.c_str()); renderer.drawText(UI_10_FONT_ID, 20, 295, localChapterStr); char localPageStr[64]; - snprintf(localPageStr, sizeof(localPageStr), " Page %d/%d, %.2f%% overall", currentPage + 1, totalPagesInSpine, + snprintf(localPageStr, sizeof(localPageStr), tr(STR_PAGE_TOTAL_OVERALL_FORMAT), currentPage + 1, totalPagesInSpine, localProgress.percentage * 100); renderer.drawText(UI_10_FONT_ID, 20, 320, localPageStr); @@ -300,45 +302,45 @@ void KOReaderSyncActivity::render(Activity::RenderLock&&) { if (selectedOption == 0) { renderer.fillRect(0, optionY - 2, pageWidth - 1, optionHeight); } - renderer.drawText(UI_10_FONT_ID, 20, optionY, "Apply remote progress", selectedOption != 0); + renderer.drawText(UI_10_FONT_ID, 20, optionY, tr(STR_APPLY_REMOTE), selectedOption != 0); // Upload option if (selectedOption == 1) { renderer.fillRect(0, optionY + optionHeight - 2, pageWidth - 1, optionHeight); } - renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight, "Upload local progress", selectedOption != 1); + renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight, tr(STR_UPLOAD_LOCAL), selectedOption != 1); // Bottom button hints: show Back and Select - const auto labels = mappedInput.mapLabels("Back", "Select", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == NO_REMOTE_PROGRESS) { - renderer.drawCenteredText(UI_10_FONT_ID, 280, "No remote progress found", true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, 320, "Upload current position?"); + renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_NO_REMOTE_MSG), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 320, tr(STR_UPLOAD_PROMPT)); - const auto labels = mappedInput.mapLabels("Back", "Upload", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_UPLOAD), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == UPLOAD_COMPLETE) { - renderer.drawCenteredText(UI_10_FONT_ID, 300, "Progress uploaded!", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_UPLOAD_SUCCESS), true, EpdFontFamily::BOLD); - const auto labels = mappedInput.mapLabels("Back", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == SYNC_FAILED) { - renderer.drawCenteredText(UI_10_FONT_ID, 280, "Sync failed", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_SYNC_FAILED_MSG), true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, 320, statusMessage.c_str()); - const auto labels = mappedInput.mapLabels("Back", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 0e6dbc0d..dac2fef1 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -182,7 +183,7 @@ void TxtReaderActivity::buildPageIndex() { LOG_DBG("TRS", "Building page index for %zu bytes...", fileSize); - GUI.drawPopup(renderer, "Indexing..."); + GUI.drawPopup(renderer, tr(STR_INDEXING)); while (offset < fileSize) { std::vector tempLines; @@ -350,7 +351,7 @@ void TxtReaderActivity::render(Activity::RenderLock&&) { if (pageOffsets.empty()) { renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty file", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_EMPTY_FILE), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index ba1cb53c..8e10fb67 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "CrossPointSettings.h" #include "CrossPointState.h" @@ -143,7 +144,7 @@ void XtcReaderActivity::render(Activity::RenderLock&&) { if (currentPage >= xtc->getPageCount()) { // Show end of book screen renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "End of book", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } @@ -172,7 +173,7 @@ void XtcReaderActivity::renderPage() { if (!pageBuffer) { LOG_ERR("XTR", "Failed to allocate page buffer (%lu bytes)", pageBufferSize); renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_MEMORY_ERROR), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } @@ -183,7 +184,7 @@ void XtcReaderActivity::renderPage() { LOG_ERR("XTR", "Failed to load page %lu", currentPage); free(pageBuffer); renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_PAGE_LOAD_ERROR), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index 5951cabb..8518a495 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -1,6 +1,7 @@ #include "XtcReaderChapterSelectionActivity.h" #include +#include #include @@ -104,14 +105,14 @@ void XtcReaderChapterSelectionActivity::render(Activity::RenderLock&&) { const int pageItems = getPageItems(); // Manual centering to honor content gutters. const int titleX = - contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, "Select Chapter", EpdFontFamily::BOLD)) / 2; - renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, "Select Chapter", true, EpdFontFamily::BOLD); + contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, tr(STR_SELECT_CHAPTER), EpdFontFamily::BOLD)) / 2; + renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, tr(STR_SELECT_CHAPTER), true, EpdFontFamily::BOLD); const auto& chapters = xtc->getChapters(); if (chapters.empty()) { // Center the empty state within the gutter-safe content region. - const int emptyX = contentX + (contentWidth - renderer.getTextWidth(UI_10_FONT_ID, "No chapters")) / 2; - renderer.drawText(UI_10_FONT_ID, emptyX, 120 + contentY, "No chapters"); + const int emptyX = contentX + (contentWidth - renderer.getTextWidth(UI_10_FONT_ID, tr(STR_NO_CHAPTERS))) / 2; + renderer.drawText(UI_10_FONT_ID, emptyX, 120 + contentY, tr(STR_NO_CHAPTERS)); renderer.displayBuffer(); return; } @@ -121,13 +122,13 @@ void XtcReaderChapterSelectionActivity::render(Activity::RenderLock&&) { renderer.fillRect(contentX, 60 + contentY + (selectorIndex % pageItems) * 30 - 2, contentWidth - 1, 30); for (int i = pageStartIndex; i < static_cast(chapters.size()) && i < pageStartIndex + pageItems; i++) { const auto& chapter = chapters[i]; - const char* title = chapter.name.empty() ? "Unnamed" : chapter.name.c_str(); + const char* title = chapter.name.empty() ? tr(STR_UNNAMED) : chapter.name.c_str(); renderer.drawText(UI_10_FONT_ID, contentX + 20, 60 + contentY + (i % pageItems) * 30, title, i != selectorIndex); } // Skip button hints in landscape CW mode (they overlap content) if (renderer.getOrientation() != GfxRenderer::LandscapeClockwise) { - const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } diff --git a/src/activities/settings/ButtonRemapActivity.cpp b/src/activities/settings/ButtonRemapActivity.cpp index a3c5d592..1b9a3132 100644 --- a/src/activities/settings/ButtonRemapActivity.cpp +++ b/src/activities/settings/ButtonRemapActivity.cpp @@ -1,6 +1,7 @@ #include "ButtonRemapActivity.h" #include +#include #include "CrossPointSettings.h" #include "MappedInputManager.h" @@ -106,8 +107,8 @@ void ButtonRemapActivity::render(Activity::RenderLock&&) { return "-"; }; - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Remap Front Buttons", true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, 40, "Press a front button for each role"); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_REMAP_FRONT_BUTTONS), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 40, tr(STR_REMAP_PROMPT)); for (uint8_t i = 0; i < kRoleCount; i++) { const int y = 70 + i * 30; @@ -122,7 +123,7 @@ void ButtonRemapActivity::render(Activity::RenderLock&&) { renderer.drawText(UI_10_FONT_ID, 20, y, roleName, !isSelected); // Show currently assigned hardware button (or unassigned). - const char* assigned = (tempMapping[i] == kUnassigned) ? "Unassigned" : getHardwareName(tempMapping[i]); + const char* assigned = (tempMapping[i] == kUnassigned) ? tr(STR_UNASSIGNED) : getHardwareName(tempMapping[i]); const auto width = renderer.getTextWidth(UI_10_FONT_ID, assigned); renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, y, assigned, !isSelected); } @@ -133,8 +134,8 @@ void ButtonRemapActivity::render(Activity::RenderLock&&) { } // Provide side button actions at the bottom of the screen (split across two lines). - renderer.drawCenteredText(SMALL_FONT_ID, 250, "Side button Up: Reset to default layout", true); - renderer.drawCenteredText(SMALL_FONT_ID, 280, "Side button Down: Cancel remapping", true); + renderer.drawCenteredText(SMALL_FONT_ID, 250, tr(STR_REMAP_RESET_HINT), true); + renderer.drawCenteredText(SMALL_FONT_ID, 280, tr(STR_REMAP_CANCEL_HINT), true); // Live preview of logical labels under front buttons. // This mirrors the on-device front button order: Back, Confirm, Left, Right. @@ -157,7 +158,7 @@ bool ButtonRemapActivity::validateUnassigned(const uint8_t pressedButton) { // Block reusing a hardware button already assigned to another role. for (uint8_t i = 0; i < kRoleCount; i++) { if (tempMapping[i] == pressedButton && i != currentStep) { - errorMessage = "Already assigned"; + errorMessage = tr(STR_ALREADY_ASSIGNED); errorUntil = millis() + kErrorDisplayMs; return false; } @@ -168,27 +169,27 @@ bool ButtonRemapActivity::validateUnassigned(const uint8_t pressedButton) { const char* ButtonRemapActivity::getRoleName(const uint8_t roleIndex) const { switch (roleIndex) { case 0: - return "Back"; + return tr(STR_BACK); case 1: - return "Confirm"; + return tr(STR_CONFIRM); case 2: - return "Left"; + return tr(STR_DIR_LEFT); case 3: default: - return "Right"; + return tr(STR_DIR_RIGHT); } } const char* ButtonRemapActivity::getHardwareName(const uint8_t buttonIndex) const { switch (buttonIndex) { case CrossPointSettings::FRONT_HW_BACK: - return "Back (1st button)"; + return tr(STR_HW_BACK_LABEL); case CrossPointSettings::FRONT_HW_CONFIRM: - return "Confirm (2nd button)"; + return tr(STR_HW_CONFIRM_LABEL); case CrossPointSettings::FRONT_HW_LEFT: - return "Left (3rd button)"; + return tr(STR_HW_LEFT_LABEL); case CrossPointSettings::FRONT_HW_RIGHT: - return "Right (4th button)"; + return tr(STR_HW_RIGHT_LABEL); default: return "Unknown"; } diff --git a/src/activities/settings/CalibreSettingsActivity.cpp b/src/activities/settings/CalibreSettingsActivity.cpp index b89fa326..58b0db78 100644 --- a/src/activities/settings/CalibreSettingsActivity.cpp +++ b/src/activities/settings/CalibreSettingsActivity.cpp @@ -1,6 +1,7 @@ #include "CalibreSettingsActivity.h" #include +#include #include @@ -12,7 +13,7 @@ namespace { constexpr int MENU_ITEMS = 3; -const char* menuNames[MENU_ITEMS] = {"OPDS Server URL", "Username", "Password"}; +const StrId menuNames[MENU_ITEMS] = {StrId::STR_CALIBRE_WEB_URL, StrId::STR_USERNAME, StrId::STR_PASSWORD}; } // namespace void CalibreSettingsActivity::onEnter() { @@ -57,7 +58,7 @@ void CalibreSettingsActivity::handleSelection() { // OPDS Server URL exitActivity(); enterNewActivity(new KeyboardEntryActivity( - renderer, mappedInput, "OPDS Server URL", SETTINGS.opdsServerUrl, 10, + renderer, mappedInput, tr(STR_CALIBRE_WEB_URL), SETTINGS.opdsServerUrl, 10, 127, // maxLength false, // not password [this](const std::string& url) { @@ -75,7 +76,7 @@ void CalibreSettingsActivity::handleSelection() { // Username exitActivity(); enterNewActivity(new KeyboardEntryActivity( - renderer, mappedInput, "Username", SETTINGS.opdsUsername, 10, + renderer, mappedInput, tr(STR_USERNAME), SETTINGS.opdsUsername, 10, 63, // maxLength false, // not password [this](const std::string& username) { @@ -93,7 +94,7 @@ void CalibreSettingsActivity::handleSelection() { // Password exitActivity(); enterNewActivity(new KeyboardEntryActivity( - renderer, mappedInput, "Password", SETTINGS.opdsPassword, 10, + renderer, mappedInput, tr(STR_PASSWORD), SETTINGS.opdsPassword, 10, 63, // maxLength false, // not password mode [this](const std::string& password) { @@ -116,10 +117,10 @@ void CalibreSettingsActivity::render(Activity::RenderLock&&) { const auto pageWidth = renderer.getScreenWidth(); // Draw header - renderer.drawCenteredText(UI_12_FONT_ID, 15, "OPDS Browser", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_OPDS_BROWSER), true, EpdFontFamily::BOLD); // Draw info text about Calibre - renderer.drawCenteredText(UI_10_FONT_ID, 40, "For Calibre, add /opds to your URL"); + renderer.drawCenteredText(UI_10_FONT_ID, 40, tr(STR_CALIBRE_URL_HINT)); // Draw selection highlight renderer.fillRect(0, 70 + selectedIndex * 30 - 2, pageWidth - 1, 30); @@ -129,23 +130,26 @@ void CalibreSettingsActivity::render(Activity::RenderLock&&) { const int settingY = 70 + i * 30; const bool isSelected = (i == selectedIndex); - renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected); + renderer.drawText(UI_10_FONT_ID, 20, settingY, I18N.get(menuNames[i]), !isSelected); // Draw status for each setting - const char* status = "[Not Set]"; + std::string status = std::string("[") + tr(STR_NOT_SET) + "]"; if (i == 0) { - status = (strlen(SETTINGS.opdsServerUrl) > 0) ? "[Set]" : "[Not Set]"; + status = (strlen(SETTINGS.opdsServerUrl) > 0) ? std::string("[") + tr(STR_SET) + "]" + : std::string("[") + tr(STR_NOT_SET) + "]"; } else if (i == 1) { - status = (strlen(SETTINGS.opdsUsername) > 0) ? "[Set]" : "[Not Set]"; + status = (strlen(SETTINGS.opdsUsername) > 0) ? std::string("[") + tr(STR_SET) + "]" + : std::string("[") + tr(STR_NOT_SET) + "]"; } else if (i == 2) { - status = (strlen(SETTINGS.opdsPassword) > 0) ? "[Set]" : "[Not Set]"; + status = (strlen(SETTINGS.opdsPassword) > 0) ? std::string("[") + tr(STR_SET) + "]" + : std::string("[") + tr(STR_NOT_SET) + "]"; } - const auto width = renderer.getTextWidth(UI_10_FONT_ID, status); - renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected); + const auto width = renderer.getTextWidth(UI_10_FONT_ID, status.c_str()); + renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status.c_str(), !isSelected); } // Draw button hints - const auto labels = mappedInput.mapLabels("« Back", "Select", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/settings/ClearCacheActivity.cpp b/src/activities/settings/ClearCacheActivity.cpp index b0f59bbc..69931185 100644 --- a/src/activities/settings/ClearCacheActivity.cpp +++ b/src/activities/settings/ClearCacheActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "MappedInputManager.h" @@ -21,46 +22,47 @@ void ClearCacheActivity::render(Activity::RenderLock&&) { const auto pageHeight = renderer.getScreenHeight(); renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Clear Cache", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_CLEAR_READING_CACHE), true, EpdFontFamily::BOLD); if (state == WARNING) { - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 60, "This will clear all cached book data.", true); - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 30, "All reading progress will be lost!", true, + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 60, tr(STR_CLEAR_CACHE_WARNING_1), true); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 30, tr(STR_CLEAR_CACHE_WARNING_2), true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, "Books will need to be re-indexed", true); - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 30, "when opened again.", true); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, tr(STR_CLEAR_CACHE_WARNING_3), true); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 30, tr(STR_CLEAR_CACHE_WARNING_4), true); - const auto labels = mappedInput.mapLabels("« Cancel", "Clear", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_CLEAR_BUTTON), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == CLEARING) { - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Clearing cache...", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_CLEARING_CACHE), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } if (state == SUCCESS) { - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Cache Cleared", true, EpdFontFamily::BOLD); - String resultText = String(clearedCount) + " items removed"; + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, tr(STR_CACHE_CLEARED), true, EpdFontFamily::BOLD); + std::string resultText = std::to_string(clearedCount) + " " + std::string(tr(STR_ITEMS_REMOVED)); if (failedCount > 0) { - resultText += ", " + String(failedCount) + " failed"; + resultText += ", " + std::to_string(failedCount) + " " + std::string(tr(STR_FAILED_LOWER)); } renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, resultText.c_str()); - const auto labels = mappedInput.mapLabels("« Back", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == FAILED) { - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Failed to clear cache", true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, "Check serial output for details"); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, tr(STR_CLEAR_CACHE_FAILED), true, + EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, tr(STR_CHECK_SERIAL_OUTPUT)); - const auto labels = mappedInput.mapLabels("« Back", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; diff --git a/src/activities/settings/KOReaderAuthActivity.cpp b/src/activities/settings/KOReaderAuthActivity.cpp index 30838bc4..f205c0e6 100644 --- a/src/activities/settings/KOReaderAuthActivity.cpp +++ b/src/activities/settings/KOReaderAuthActivity.cpp @@ -1,6 +1,7 @@ #include "KOReaderAuthActivity.h" #include +#include #include #include "KOReaderCredentialStore.h" @@ -17,7 +18,7 @@ void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) { { RenderLock lock(*this); state = FAILED; - errorMessage = "WiFi connection failed"; + errorMessage = tr(STR_WIFI_CONN_FAILED); } requestUpdate(); return; @@ -26,7 +27,7 @@ void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) { { RenderLock lock(*this); state = AUTHENTICATING; - statusMessage = "Authenticating..."; + statusMessage = tr(STR_AUTHENTICATING); } requestUpdate(); @@ -40,7 +41,7 @@ void KOReaderAuthActivity::performAuthentication() { RenderLock lock(*this); if (result == KOReaderSyncClient::OK) { state = SUCCESS; - statusMessage = "Successfully authenticated!"; + statusMessage = tr(STR_AUTH_SUCCESS); } else { state = FAILED; errorMessage = KOReaderSyncClient::errorString(result); @@ -58,7 +59,7 @@ void KOReaderAuthActivity::onEnter() { // Check if already connected if (WiFi.status() == WL_CONNECTED) { state = AUTHENTICATING; - statusMessage = "Authenticating..."; + statusMessage = tr(STR_AUTHENTICATING); requestUpdate(); // Perform authentication in a separate task @@ -89,7 +90,7 @@ void KOReaderAuthActivity::onExit() { void KOReaderAuthActivity::render(Activity::RenderLock&&) { renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Auth", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_KOREADER_AUTH), true, EpdFontFamily::BOLD); if (state == AUTHENTICATING) { renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD); @@ -98,20 +99,20 @@ void KOReaderAuthActivity::render(Activity::RenderLock&&) { } if (state == SUCCESS) { - renderer.drawCenteredText(UI_10_FONT_ID, 280, "Success!", true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, 320, "KOReader sync is ready to use"); + renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_AUTH_SUCCESS), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 320, tr(STR_SYNC_READY)); - const auto labels = mappedInput.mapLabels("Done", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_DONE), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == FAILED) { - renderer.drawCenteredText(UI_10_FONT_ID, 280, "Authentication Failed", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_AUTH_FAILED), true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, 320, errorMessage.c_str()); - const auto labels = mappedInput.mapLabels("Back", "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; diff --git a/src/activities/settings/KOReaderSettingsActivity.cpp b/src/activities/settings/KOReaderSettingsActivity.cpp index ff0fe055..b46d1191 100644 --- a/src/activities/settings/KOReaderSettingsActivity.cpp +++ b/src/activities/settings/KOReaderSettingsActivity.cpp @@ -1,6 +1,7 @@ #include "KOReaderSettingsActivity.h" #include +#include #include @@ -13,7 +14,8 @@ namespace { constexpr int MENU_ITEMS = 5; -const char* menuNames[MENU_ITEMS] = {"Username", "Password", "Sync Server URL", "Document Matching", "Authenticate"}; +const StrId menuNames[MENU_ITEMS] = {StrId::STR_USERNAME, StrId::STR_PASSWORD, StrId::STR_SYNC_SERVER_URL, + StrId::STR_DOCUMENT_MATCHING, StrId::STR_AUTHENTICATE}; } // namespace void KOReaderSettingsActivity::onEnter() { @@ -58,7 +60,7 @@ void KOReaderSettingsActivity::handleSelection() { // Username exitActivity(); enterNewActivity(new KeyboardEntryActivity( - renderer, mappedInput, "KOReader Username", KOREADER_STORE.getUsername(), 10, + renderer, mappedInput, tr(STR_KOREADER_USERNAME), KOREADER_STORE.getUsername(), 10, 64, // maxLength false, // not password [this](const std::string& username) { @@ -75,7 +77,7 @@ void KOReaderSettingsActivity::handleSelection() { // Password exitActivity(); enterNewActivity(new KeyboardEntryActivity( - renderer, mappedInput, "KOReader Password", KOREADER_STORE.getPassword(), 10, + renderer, mappedInput, tr(STR_KOREADER_PASSWORD), KOREADER_STORE.getPassword(), 10, 64, // maxLength false, // show characters [this](const std::string& password) { @@ -94,7 +96,7 @@ void KOReaderSettingsActivity::handleSelection() { const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl; exitActivity(); enterNewActivity(new KeyboardEntryActivity( - renderer, mappedInput, "Sync Server URL", prefillUrl, 10, + renderer, mappedInput, tr(STR_SYNC_SERVER_URL), prefillUrl, 10, 128, // maxLength - URLs can be long false, // not password [this](const std::string& url) { @@ -137,7 +139,7 @@ void KOReaderSettingsActivity::render(Activity::RenderLock&&) { const auto pageWidth = renderer.getScreenWidth(); // Draw header - renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_KOREADER_SYNC), true, EpdFontFamily::BOLD); // Draw selection highlight renderer.fillRect(0, 60 + selectedIndex * 30 - 2, pageWidth - 1, 30); @@ -147,28 +149,31 @@ void KOReaderSettingsActivity::render(Activity::RenderLock&&) { const int settingY = 60 + i * 30; const bool isSelected = (i == selectedIndex); - renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected); + renderer.drawText(UI_10_FONT_ID, 20, settingY, I18N.get(menuNames[i]), !isSelected); // Draw status for each item - const char* status = ""; + std::string status = ""; if (i == 0) { - status = KOREADER_STORE.getUsername().empty() ? "[Not Set]" : "[Set]"; + status = std::string("[") + (KOREADER_STORE.getUsername().empty() ? tr(STR_NOT_SET) : tr(STR_SET)) + "]"; } else if (i == 1) { - status = KOREADER_STORE.getPassword().empty() ? "[Not Set]" : "[Set]"; + status = std::string("[") + (KOREADER_STORE.getPassword().empty() ? tr(STR_NOT_SET) : tr(STR_SET)) + "]"; } else if (i == 2) { - status = KOREADER_STORE.getServerUrl().empty() ? "[Default]" : "[Custom]"; + status = + std::string("[") + (KOREADER_STORE.getServerUrl().empty() ? tr(STR_DEFAULT_VALUE) : tr(STR_CUSTOM)) + "]"; } else if (i == 3) { - status = KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? "[Filename]" : "[Binary]"; + status = std::string("[") + + (KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? tr(STR_FILENAME) : tr(STR_BINARY)) + + "]"; } else if (i == 4) { - status = KOREADER_STORE.hasCredentials() ? "" : "[Set credentials first]"; + status = KOREADER_STORE.hasCredentials() ? "" : std::string("[") + tr(STR_SET_CREDENTIALS_FIRST) + "]"; } - const auto width = renderer.getTextWidth(UI_10_FONT_ID, status); - renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected); + const auto width = renderer.getTextWidth(UI_10_FONT_ID, status.c_str()); + renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status.c_str(), !isSelected); } // Draw button hints - const auto labels = mappedInput.mapLabels("« Back", "Select", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/settings/LanguageSelectActivity.cpp b/src/activities/settings/LanguageSelectActivity.cpp new file mode 100644 index 00000000..686e44b8 --- /dev/null +++ b/src/activities/settings/LanguageSelectActivity.cpp @@ -0,0 +1,94 @@ +#include "LanguageSelectActivity.h" + +#include +#include + +#include "MappedInputManager.h" +#include "fontIds.h" + +void LanguageSelectActivity::onEnter() { + Activity::onEnter(); + + totalItems = getLanguageCount(); + + // Set current selection based on current language + selectedIndex = static_cast(I18N.getLanguage()); + + requestUpdate(); +} + +void LanguageSelectActivity::onExit() { Activity::onExit(); } + +void LanguageSelectActivity::loop() { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { + onBack(); + return; + } + + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { + handleSelection(); + return; + } + + if (mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left)) { + selectedIndex = (selectedIndex + totalItems - 1) % totalItems; + requestUpdate(); + } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right)) { + selectedIndex = (selectedIndex + 1) % totalItems; + requestUpdate(); + } +} + +void LanguageSelectActivity::handleSelection() { + { + RenderLock lock(*this); + I18N.setLanguage(static_cast(selectedIndex)); + } + + // Return to previous page + onBack(); +} + +void LanguageSelectActivity::render(Activity::RenderLock&&) { + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + constexpr int rowHeight = 30; + + // Title + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_LANGUAGE), true, EpdFontFamily::BOLD); + + // Current language marker + const int currentLang = static_cast(I18N.getLanguage()); + + // Draw options + for (int i = 0; i < totalItems; i++) { + const int itemY = 60 + i * rowHeight; + const bool isSelected = (i == selectedIndex); + const bool isCurrent = (i == currentLang); + + // Draw selection highlight + if (isSelected) { + renderer.fillRect(0, itemY - 2, pageWidth - 1, rowHeight); + } + + // Draw language name - get it from i18n system + const char* langName = I18N.getLanguageName(static_cast(i)); + renderer.drawText(UI_10_FONT_ID, 20, itemY, langName, !isSelected); + + // Draw current selection marker + if (isCurrent) { + const char* marker = tr(STR_ON_MARKER); + const auto width = renderer.getTextWidth(UI_10_FONT_ID, marker); + renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, itemY, marker, !isSelected); + } + } + + // Button hints + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(); +} diff --git a/src/activities/settings/LanguageSelectActivity.h b/src/activities/settings/LanguageSelectActivity.h new file mode 100644 index 00000000..7b20be3a --- /dev/null +++ b/src/activities/settings/LanguageSelectActivity.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include + +#include "../ActivityWithSubactivity.h" +#include "components/UITheme.h" + +class MappedInputManager; + +/** + * Activity for selecting UI language + */ +class LanguageSelectActivity final : public Activity { + public: + explicit LanguageSelectActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& onBack) + : Activity("LanguageSelect", renderer, mappedInput), onBack(onBack) {} + + void onEnter() override; + void onExit() override; + void loop() override; + void render(Activity::RenderLock&&) override; + + private: + void handleSelection(); + + std::function onBack; + int selectedIndex = 0; + int totalItems = 0; +}; diff --git a/src/activities/settings/OtaUpdateActivity.cpp b/src/activities/settings/OtaUpdateActivity.cpp index d254f236..8d2e7cd8 100644 --- a/src/activities/settings/OtaUpdateActivity.cpp +++ b/src/activities/settings/OtaUpdateActivity.cpp @@ -1,6 +1,7 @@ #include "OtaUpdateActivity.h" #include +#include #include #include "MappedInputManager.h" @@ -97,27 +98,27 @@ void OtaUpdateActivity::render(Activity::RenderLock&&) { const auto pageWidth = renderer.getScreenWidth(); renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Update", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_UPDATE), true, EpdFontFamily::BOLD); if (state == CHECKING_FOR_UPDATE) { - renderer.drawCenteredText(UI_10_FONT_ID, 300, "Checking for update...", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_CHECKING_UPDATE), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } if (state == WAITING_CONFIRMATION) { - renderer.drawCenteredText(UI_10_FONT_ID, 200, "New update available!", true, EpdFontFamily::BOLD); - renderer.drawText(UI_10_FONT_ID, 20, 250, "Current Version: " CROSSPOINT_VERSION); - renderer.drawText(UI_10_FONT_ID, 20, 270, ("New Version: " + updater.getLatestVersion()).c_str()); + renderer.drawCenteredText(UI_10_FONT_ID, 200, tr(STR_NEW_UPDATE), true, EpdFontFamily::BOLD); + renderer.drawText(UI_10_FONT_ID, 20, 250, (std::string(tr(STR_CURRENT_VERSION)) + CROSSPOINT_VERSION).c_str()); + renderer.drawText(UI_10_FONT_ID, 20, 270, (std::string(tr(STR_NEW_VERSION)) + updater.getLatestVersion()).c_str()); - const auto labels = mappedInput.mapLabels("Cancel", "Update", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_UPDATE), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == UPDATE_IN_PROGRESS) { - renderer.drawCenteredText(UI_10_FONT_ID, 310, "Updating...", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 310, tr(STR_UPDATING), true, EpdFontFamily::BOLD); renderer.drawRect(20, 350, pageWidth - 40, 50); renderer.fillRect(24, 354, static_cast(updaterProgress * static_cast(pageWidth - 44)), 42); renderer.drawCenteredText(UI_10_FONT_ID, 420, @@ -130,20 +131,20 @@ void OtaUpdateActivity::render(Activity::RenderLock&&) { } if (state == NO_UPDATE) { - renderer.drawCenteredText(UI_10_FONT_ID, 300, "No update available", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_NO_UPDATE), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } if (state == FAILED) { - renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update failed", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_UPDATE_FAILED), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } if (state == FINISHED) { - renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update complete", true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, 350, "Press and hold power button to turn back on"); + renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_UPDATE_COMPLETE), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 350, tr(STR_POWER_ON_HINT)); renderer.displayBuffer(); state = SHUTTING_DOWN; return; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index b0ebc59e..9889c56b 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -8,6 +8,7 @@ #include "ClearCacheActivity.h" #include "CrossPointSettings.h" #include "KOReaderSettingsActivity.h" +#include "LanguageSelectActivity.h" #include "MappedInputManager.h" #include "OtaUpdateActivity.h" #include "SettingsList.h" @@ -15,7 +16,8 @@ #include "components/UITheme.h" #include "fontIds.h" -const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"}; +const StrId SettingsActivity::categoryNames[categoryCount] = {StrId::STR_CAT_DISPLAY, StrId::STR_CAT_READER, + StrId::STR_CAT_CONTROLS, StrId::STR_CAT_SYSTEM}; void SettingsActivity::onEnter() { Activity::onEnter(); @@ -27,14 +29,14 @@ void SettingsActivity::onEnter() { systemSettings.clear(); for (auto& setting : getSettingsList()) { - if (!setting.category) continue; - if (strcmp(setting.category, "Display") == 0) { + if (setting.category == StrId::STR_NONE_OPT) continue; + if (setting.category == StrId::STR_CAT_DISPLAY) { displaySettings.push_back(std::move(setting)); - } else if (strcmp(setting.category, "Reader") == 0) { + } else if (setting.category == StrId::STR_CAT_READER) { readerSettings.push_back(std::move(setting)); - } else if (strcmp(setting.category, "Controls") == 0) { + } else if (setting.category == StrId::STR_CAT_CONTROLS) { controlsSettings.push_back(std::move(setting)); - } else if (strcmp(setting.category, "System") == 0) { + } else if (setting.category == StrId::STR_CAT_SYSTEM) { systemSettings.push_back(std::move(setting)); } // Web-only categories (KOReader Sync, OPDS Browser) are skipped for device UI @@ -42,12 +44,13 @@ void SettingsActivity::onEnter() { // Append device-only ACTION items controlsSettings.insert(controlsSettings.begin(), - SettingInfo::Action("Remap Front Buttons", SettingAction::RemapFrontButtons)); - systemSettings.push_back(SettingInfo::Action("Network", SettingAction::Network)); - systemSettings.push_back(SettingInfo::Action("KOReader Sync", SettingAction::KOReaderSync)); - systemSettings.push_back(SettingInfo::Action("OPDS Browser", SettingAction::OPDSBrowser)); - systemSettings.push_back(SettingInfo::Action("Clear Cache", SettingAction::ClearCache)); - systemSettings.push_back(SettingInfo::Action("Check for updates", SettingAction::CheckForUpdates)); + SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons)); + systemSettings.push_back(SettingInfo::Action(StrId::STR_WIFI_NETWORKS, SettingAction::Network)); + systemSettings.push_back(SettingInfo::Action(StrId::STR_KOREADER_SYNC, SettingAction::KOReaderSync)); + systemSettings.push_back(SettingInfo::Action(StrId::STR_OPDS_BROWSER, SettingAction::OPDSBrowser)); + systemSettings.push_back(SettingInfo::Action(StrId::STR_CLEAR_READING_CACHE, SettingAction::ClearCache)); + systemSettings.push_back(SettingInfo::Action(StrId::STR_CHECK_UPDATES, SettingAction::CheckForUpdates)); + systemSettings.push_back(SettingInfo::Action(StrId::STR_LANGUAGE, SettingAction::Language)); // Reset selection to first category selectedCategoryIndex = 0; @@ -193,6 +196,9 @@ void SettingsActivity::toggleCurrentSetting() { case SettingAction::CheckForUpdates: enterSubActivity(new OtaUpdateActivity(renderer, mappedInput, onComplete)); break; + case SettingAction::Language: + enterSubActivity(new LanguageSelectActivity(renderer, mappedInput, onComplete)); + break; case SettingAction::None: // Do nothing break; @@ -212,12 +218,12 @@ void SettingsActivity::render(Activity::RenderLock&&) { auto metrics = UITheme::getInstance().getMetrics(); - GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, "Settings"); + GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_SETTINGS_TITLE)); std::vector tabs; tabs.reserve(categoryCount); for (int i = 0; i < categoryCount; i++) { - tabs.push_back({categoryNames[i], selectedCategoryIndex == i}); + tabs.push_back({I18N.get(categoryNames[i]), selectedCategoryIndex == i}); } GUI.drawTabBar(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight}, tabs, selectedSettingIndex == 0); @@ -228,18 +234,19 @@ void SettingsActivity::render(Activity::RenderLock&&) { Rect{0, metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing, pageWidth, pageHeight - (metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.buttonHintsHeight + metrics.verticalSpacing * 2)}, - settingsCount, selectedSettingIndex - 1, [&settings](int index) { return std::string(settings[index].name); }, - nullptr, nullptr, + settingsCount, selectedSettingIndex - 1, + [&settings](int index) { return std::string(I18N.get(settings[index].nameId)); }, nullptr, nullptr, [&settings](int i) { + const auto& setting = settings[i]; std::string valueText = ""; - if (settings[i].type == SettingType::TOGGLE && settings[i].valuePtr != nullptr) { - const bool value = SETTINGS.*(settings[i].valuePtr); - valueText = value ? "ON" : "OFF"; - } else if (settings[i].type == SettingType::ENUM && settings[i].valuePtr != nullptr) { - const uint8_t value = SETTINGS.*(settings[i].valuePtr); - valueText = settings[i].enumValues[value]; - } else if (settings[i].type == SettingType::VALUE && settings[i].valuePtr != nullptr) { - valueText = std::to_string(SETTINGS.*(settings[i].valuePtr)); + if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) { + const bool value = SETTINGS.*(setting.valuePtr); + valueText = value ? tr(STR_STATE_ON) : tr(STR_STATE_OFF); + } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { + const uint8_t value = SETTINGS.*(setting.valuePtr); + valueText = I18N.get(setting.enumValues[value]); + } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { + valueText = std::to_string(SETTINGS.*(setting.valuePtr)); } return valueText; }); @@ -250,7 +257,7 @@ void SettingsActivity::render(Activity::RenderLock&&) { metrics.versionTextY, CROSSPOINT_VERSION); // Draw help text - const auto labels = mappedInput.mapLabels("« Back", "Toggle", "Up", "Down"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_TOGGLE), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); // Always use standard refresh for settings screen diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index 1fdbcc61..802060e9 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -19,13 +20,14 @@ enum class SettingAction { Network, ClearCache, CheckForUpdates, + Language, }; struct SettingInfo { - const char* name; + StrId nameId; SettingType type; uint8_t CrossPointSettings::* valuePtr = nullptr; - std::vector enumValues; + std::vector enumValues; SettingAction action = SettingAction::None; struct ValueRange { @@ -35,8 +37,8 @@ struct SettingInfo { }; ValueRange valueRange = {}; - const char* key = nullptr; // JSON API key (nullptr for ACTION types) - const char* category = nullptr; // Category for web UI grouping + const char* key = nullptr; // JSON API key (nullptr for ACTION types) + StrId category = StrId::STR_NONE_OPT; // Category for web UI grouping // Direct char[] string fields (for settings stored in CrossPointSettings) char* stringPtr = nullptr; @@ -48,10 +50,10 @@ struct SettingInfo { std::function stringGetter; std::function stringSetter; - static SettingInfo Toggle(const char* name, uint8_t CrossPointSettings::* ptr, const char* key = nullptr, - const char* category = nullptr) { + static SettingInfo Toggle(StrId nameId, uint8_t CrossPointSettings::* ptr, const char* key = nullptr, + StrId category = StrId::STR_NONE_OPT) { SettingInfo s; - s.name = name; + s.nameId = nameId; s.type = SettingType::TOGGLE; s.valuePtr = ptr; s.key = key; @@ -59,10 +61,10 @@ struct SettingInfo { return s; } - static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector values, - const char* key = nullptr, const char* category = nullptr) { + static SettingInfo Enum(StrId nameId, uint8_t CrossPointSettings::* ptr, std::vector values, + const char* key = nullptr, StrId category = StrId::STR_NONE_OPT) { SettingInfo s; - s.name = name; + s.nameId = nameId; s.type = SettingType::ENUM; s.valuePtr = ptr; s.enumValues = std::move(values); @@ -71,18 +73,18 @@ struct SettingInfo { return s; } - static SettingInfo Action(const char* name, SettingAction action) { + static SettingInfo Action(StrId nameId, SettingAction action) { SettingInfo s; - s.name = name; + s.nameId = nameId; s.type = SettingType::ACTION; s.action = action; return s; } - static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange, - const char* key = nullptr, const char* category = nullptr) { + static SettingInfo Value(StrId nameId, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange, + const char* key = nullptr, StrId category = StrId::STR_NONE_OPT) { SettingInfo s; - s.name = name; + s.nameId = nameId; s.type = SettingType::VALUE; s.valuePtr = ptr; s.valueRange = valueRange; @@ -91,10 +93,10 @@ struct SettingInfo { return s; } - static SettingInfo String(const char* name, char* ptr, size_t maxLen, const char* key = nullptr, - const char* category = nullptr) { + static SettingInfo String(StrId nameId, char* ptr, size_t maxLen, const char* key = nullptr, + StrId category = StrId::STR_NONE_OPT) { SettingInfo s; - s.name = name; + s.nameId = nameId; s.type = SettingType::STRING; s.stringPtr = ptr; s.stringMaxLen = maxLen; @@ -103,11 +105,11 @@ struct SettingInfo { return s; } - static SettingInfo DynamicEnum(const char* name, std::vector values, std::function getter, + static SettingInfo DynamicEnum(StrId nameId, std::vector values, std::function getter, std::function setter, const char* key = nullptr, - const char* category = nullptr) { + StrId category = StrId::STR_NONE_OPT) { SettingInfo s; - s.name = name; + s.nameId = nameId; s.type = SettingType::ENUM; s.enumValues = std::move(values); s.valueGetter = std::move(getter); @@ -117,11 +119,11 @@ struct SettingInfo { return s; } - static SettingInfo DynamicString(const char* name, std::function getter, + static SettingInfo DynamicString(StrId nameId, std::function getter, std::function setter, const char* key = nullptr, - const char* category = nullptr) { + StrId category = StrId::STR_NONE_OPT) { SettingInfo s; - s.name = name; + s.nameId = nameId; s.type = SettingType::STRING; s.stringGetter = std::move(getter); s.stringSetter = std::move(setter); @@ -148,7 +150,7 @@ class SettingsActivity final : public ActivityWithSubactivity { const std::function onGoHome; static constexpr int categoryCount = 4; - static const char* categoryNames[categoryCount]; + static const StrId categoryNames[categoryCount]; void enterCategory(int categoryIndex); void toggleCurrentSetting(); diff --git a/src/activities/util/KeyboardEntryActivity.cpp b/src/activities/util/KeyboardEntryActivity.cpp index 54b025c5..5d1a935c 100644 --- a/src/activities/util/KeyboardEntryActivity.cpp +++ b/src/activities/util/KeyboardEntryActivity.cpp @@ -1,5 +1,7 @@ #include "KeyboardEntryActivity.h" +#include + #include "MappedInputManager.h" #include "components/UITheme.h" #include "fontIds.h" @@ -259,7 +261,8 @@ void KeyboardEntryActivity::render(Activity::RenderLock&&) { // SHIFT key (logical col 0, spans 2 key widths) const bool shiftSelected = (selectedRow == 4 && selectedCol >= SHIFT_COL && selectedCol < SPACE_COL); - renderItemWithSelector(currentX + 2, rowY, shiftString[shiftState], shiftSelected); + static constexpr StrId shiftIds[3] = {StrId::STR_KBD_SHIFT, StrId::STR_KBD_SHIFT_CAPS, StrId::STR_KBD_LOCK}; + renderItemWithSelector(currentX + 2, rowY, I18N.get(shiftIds[shiftState]), shiftSelected); currentX += 2 * (keyWidth + keySpacing); // Space bar (logical cols 2-6, spans 5 key widths) @@ -277,7 +280,7 @@ void KeyboardEntryActivity::render(Activity::RenderLock&&) { // OK button (logical col 9, spans 2 key widths) const bool okSelected = (selectedRow == 4 && selectedCol >= DONE_COL); - renderItemWithSelector(currentX + 2, rowY, "OK", okSelected); + renderItemWithSelector(currentX + 2, rowY, tr(STR_OK_BUTTON), okSelected); } else { // Regular rows: render each key individually for (int col = 0; col < getRowLength(row); col++) { @@ -294,11 +297,11 @@ void KeyboardEntryActivity::render(Activity::RenderLock&&) { } // Draw help text - const auto labels = mappedInput.mapLabels("« Back", "Select", "Left", "Right"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); // Draw side button hints for Up/Down navigation - GUI.drawSideButtonHints(renderer, "Up", "Down"); + GUI.drawSideButtonHints(renderer, tr(STR_DIR_UP), tr(STR_DIR_DOWN)); renderer.displayBuffer(); } diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 5af1b725..bc7c7669 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -9,6 +9,7 @@ #include #include "Battery.h" +#include "I18n.h" #include "RecentBooksStore.h" #include "components/UITheme.h" #include "fontIds.h" @@ -554,7 +555,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2; if (coverRendered) { // Draw box behind "Continue Reading" text (inverted when selected: black box instead of white) - const char* continueText = "Continue Reading"; + const char* continueText = tr(STR_CONTINUE_READING); const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText); constexpr int continuePadding = 6; const int continueBoxWidth = continueTextWidth + continuePadding * 2; @@ -565,7 +566,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected); renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected); } else { - renderer.drawCenteredText(UI_10_FONT_ID, continueY, "Continue Reading", !bookSelected); + renderer.drawCenteredText(UI_10_FONT_ID, continueY, tr(STR_CONTINUE_READING), !bookSelected); } } else { // No book to continue reading @@ -593,7 +594,8 @@ void BaseTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount rect.width - BaseMetrics::values.contentSidePadding * 2, BaseMetrics::values.menuRowHeight); } - const char* label = buttonLabel(i).c_str(); + std::string labelStr = buttonLabel(i); + const char* label = labelStr.c_str(); const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, label); const int textX = rect.x + (rect.width - textWidth) / 2; const int lineHeight = renderer.getLineHeight(UI_10_FONT_ID); diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 2e3ad4cd..7fc5a6f1 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -352,7 +352,8 @@ void LyraTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount renderer.fillRoundedRect(tileRect.x, tileRect.y, tileRect.width, tileRect.height, cornerRadius, Color::LightGray); } - const char* label = buttonLabel(i).c_str(); + std::string labelStr = buttonLabel(i); + const char* label = labelStr.c_str(); const int textX = tileRect.x + 16; const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID); const int textY = tileRect.y + (LyraMetrics::values.menuRowHeight - lineHeight) / 2; diff --git a/src/main.cpp b/src/main.cpp index 4dfe3e68..1d9d01ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -304,6 +305,7 @@ void setup() { } SETTINGS.loadFromFile(); + I18N.loadSettings(); KOREADER_STORE.loadFromFile(); UITheme::getInstance().reload(); ButtonNavigator::setMappedInputManager(mappedInputManager); diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 91f1fd51..e2f60ebe 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -1017,8 +1017,8 @@ void CrossPointWebServer::handleGetSettings() const { doc.clear(); doc["key"] = s.key; - doc["name"] = s.name; - doc["category"] = s.category; + doc["name"] = I18N.get(s.nameId); + doc["category"] = I18N.get(s.category); switch (s.type) { case SettingType::TOGGLE: { @@ -1037,7 +1037,7 @@ void CrossPointWebServer::handleGetSettings() const { } JsonArray options = doc["options"].to(); for (const auto& opt : s.enumValues) { - options.add(opt); + options.add(I18N.get(opt)); } break; }