A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: User-Interface I18n System (#728)

## Summary

**What is the goal of this PR?**
This PR introduces Internationalization (i18n) support, enabling users
to switch the UI language dynamically.

**What changes are included?**
- Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage
language state and string retrieval.

- Data Structures:

- `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported
language.
- `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access.
- `lib/I18n/translations.csv`: single source of truth.

- Documentation: Added `docs/i18n.md` detailing the workflow for
developers and translators.

- New Settings activity:
`src/activities/settings/LanguageSelectActivity.h/cpp`

## Additional Context

This implementation (building on concepts from #505) prioritizes
performance and memory efficiency.

The core approach is to store all localized strings for each language in
dedicated arrays and access them via enums. This provides O(1) access
with zero runtime overhead, and avoids the heap allocations, hashing,
and collision handling required by `std::map` or `std::unordered_map`.

The main trade-off is that enums and string arrays must remain perfectly
synchronized—any mismatch would result in incorrect strings being
displayed in the UI.

To eliminate this risk, I added a Python script that automatically
generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which
will serve as the single source of truth for all translations. The full
design and workflow are documented in `docs/i18n.md`.

### Next Steps

- [x] Python script `generate_i18n.py` to auto-generate C++ files from
CSV
- [x] Populate translations.csv with initial translations.

Currently available translations: English, Español, Français, Deutsch,
Čeština, Português (Brasil), Русский, Svenska.
Thanks, community!

**Status:** EDIT: ready to be merged.

As a proof of concept, the SPANISH strings currently mirror the English
ones, but are fully uppercased.

---

### AI Usage

Did you use AI tools to help write this code? _**< PARTIALLY >**_
I used AI for the black work of replacing strings with I18n references
across the project, and for generating the documentation. EDIT: also
some help with merging changes from master.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: yeyeto2788 <juanernestobiondi@gmail.com>

authored by

Uri Tauber
google-labs-jules[bot]
yeyeto2788
and committed by
GitHub
7ba59788 3d47c081

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