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; }