## Summary * Add support for configuring and using multiple OPDS servers, replacing the previous single-server limitation. Closes https://github.com/crosspoint-reader/crosspoint-reader/issues/1178 * New OpdsServerStore singleton (modeled after WifiCredentialStore) that persists up to 8 OPDS servers to /.crosspoint/opds.json with MAC-based password obfuscation. * One-time migration from legacy single-server fields in CrossPointSettings to the new store on first boot. * New OpdsServerListActivity for the device UI — works in two modes: a settings list (add/edit/delete servers) and a picker (select which server to browse). When only one server is configured, the picker is skipped automatically. * Renamed CalibreSettingsActivity → OpdsSettingsActivity for clarity. It now edits individual OpdsServer entries (name, URL, username, password, delete). * OpdsBookBrowserActivity now receives an OpdsServer at construction and uses its credentials for all fetches/downloads, and shows the server name in the header. * HttpDownloader::fetchUrl and downloadToFile accept optional per-call username/password parameters instead of reading from global settings. * REST API endpoints on CrossPointWebServer: GET /api/opds, POST /api/opds, POST /api/opds/delete — passwords are never exposed over the API (only a hasPassword flag), and omitting the password field on update preserves the existing one. * Web UI (SettingsPage.html) with dynamic OPDS server management cards — add, edit, save, and delete servers from the browser. <img width="932" height="906" alt="SCR-20260416-stvu" src="https://github.com/user-attachments/assets/a8f18d84-4204-46a0-bb31-b73d24b3255f" /> ## Additional Context * The OpdsServerStore JSON format and obfuscation scheme are identical to WifiCredentialStore, so the same JsonSettingsIO infrastructure handles both. * The web API uses POST /api/opds/delete instead of DELETE /api/opds because the ESP32 WebServer doesn't support the DELETE method with a request body. * Existing single-server configurations are migrated automatically — no user action required. After migration the legacy CrossPointSettings fields are cleared so it only runs once. * The HttpDownloader changes are backward-compatible: the credential parameters default to empty strings, so existing callers are unaffected. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< YES >**_ --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
322 lines
11 KiB
YAML
322 lines
11 KiB
YAML
_language_name: "English"
|
|
_language_code: "EN"
|
|
_order: "0"
|
|
|
|
STR_CROSSPOINT: "CrossPoint"
|
|
STR_BOOTING: "BOOTING"
|
|
STR_SLEEPING: "SLEEPING"
|
|
STR_ENTERING_SLEEP: "Going to sleep"
|
|
STR_BROWSE_FILES: "Browse Files"
|
|
STR_FILE_TRANSFER: "File Transfer"
|
|
STR_SETTINGS_TITLE: "Settings"
|
|
STR_CONTINUE_READING: "Continue Reading"
|
|
STR_NO_OPEN_BOOK: "No open book"
|
|
STR_START_READING: "Start reading below"
|
|
STR_NO_FILES_FOUND: "No files found"
|
|
STR_SELECT_CHAPTER: "Select Chapter"
|
|
STR_NO_CHAPTERS: "No chapters"
|
|
STR_END_OF_BOOK: "End of book"
|
|
STR_EMPTY_CHAPTER: "Empty chapter"
|
|
STR_INDEXING: "Indexing"
|
|
STR_MEMORY_ERROR: "Memory error"
|
|
STR_PAGE_LOAD_ERROR: "Page load error"
|
|
STR_EMPTY_FILE: "Empty file"
|
|
STR_OUT_OF_BOUNDS: "Out of bounds"
|
|
STR_LOADING: "Loading..."
|
|
STR_LOADING_POPUP: "Loading"
|
|
STR_WIFI_NETWORKS: "WiFi Networks"
|
|
STR_NO_NETWORKS: "No networks found"
|
|
STR_NETWORKS_FOUND: "%zu networks found"
|
|
STR_SCANNING: "Scanning..."
|
|
STR_CONNECTING: "Connecting..."
|
|
STR_CONNECTED: "Connected!"
|
|
STR_CONNECTION_FAILED: "Connection Failed"
|
|
STR_FORGET_NETWORK: "Forget Network?"
|
|
STR_SAVE_PASSWORD: "Save password for next time?"
|
|
STR_PRESS_OK_SCAN: "Press OK to scan again"
|
|
STR_JOIN_NETWORK: "Join a Network"
|
|
STR_CREATE_HOTSPOT: "Create Hotspot"
|
|
STR_JOIN_DESC: "Connect to an existing WiFi network"
|
|
STR_HOTSPOT_DESC: "Create a WiFi network others can join"
|
|
STR_STARTING_HOTSPOT: "Starting Hotspot..."
|
|
STR_HOTSPOT_MODE: "Hotspot Mode"
|
|
STR_CONNECT_WIFI_HINT: "Connect your device to this WiFi network"
|
|
STR_OPEN_URL_HINT: "Open this URL in your browser"
|
|
STR_OR_HTTP_PREFIX: "or http://"
|
|
STR_SCAN_QR_HINT: "or scan QR code with your phone:"
|
|
STR_CALIBRE_WIRELESS: "Calibre Wireless"
|
|
STR_CALIBRE_WEB_URL: "OPDS URL"
|
|
STR_NETWORK_LEGEND: "* = Encrypted | + = Saved"
|
|
STR_MAC_ADDRESS: "MAC address:"
|
|
STR_CHECKING_WIFI: "Checking WiFi..."
|
|
STR_ENTER_WIFI_PASSWORD: "Enter WiFi Password"
|
|
STR_TO_PREFIX: "to "
|
|
STR_CALIBRE_RECEIVING: "Receiving: "
|
|
STR_CALIBRE_RECEIVED: "Received: "
|
|
STR_CALIBRE_INSTRUCTION_1: "1) Install CrossPoint Reader plugin"
|
|
STR_CALIBRE_INSTRUCTION_2: "2) Be on the same WiFi network"
|
|
STR_CALIBRE_INSTRUCTION_3: "3) In Calibre: \"Send to device\""
|
|
STR_CALIBRE_INSTRUCTION_4: "\"Keep this screen open while sending\""
|
|
STR_CAT_DISPLAY: "Display"
|
|
STR_CAT_READER: "Reader"
|
|
STR_CAT_CONTROLS: "Controls"
|
|
STR_CAT_SYSTEM: "System"
|
|
STR_SLEEP_SCREEN: "Sleep Screen"
|
|
STR_SLEEP_COVER_MODE: "Sleep Screen Cover Mode"
|
|
STR_HIDE_BATTERY: "Hide Battery %"
|
|
STR_EXTRA_SPACING: "Extra Paragraph Spacing"
|
|
STR_TEXT_AA: "Text Anti-Aliasing"
|
|
STR_IMAGES: "Images"
|
|
STR_IMAGES_DISPLAY: "Display"
|
|
STR_IMAGES_PLACEHOLDER: "Placeholder"
|
|
STR_IMAGES_SUPPRESS: "Suppress"
|
|
STR_SHORT_PWR_BTN: "Short Power Button Click"
|
|
STR_ORIENTATION: "Reading Orientation"
|
|
STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)"
|
|
STR_LONG_PRESS_SKIP: "Long-press Chapter Skip"
|
|
STR_FONT_FAMILY: "Reader Font Family"
|
|
STR_FONT_SIZE: "Reader Font Size"
|
|
STR_LINE_SPACING: "Reader Line Spacing"
|
|
STR_SCREEN_MARGIN: "Reader Screen Margin"
|
|
STR_PARA_ALIGNMENT: "Reader Paragraph Alignment"
|
|
STR_HYPHENATION: "Hyphenation"
|
|
STR_TIME_TO_SLEEP: "Time to Sleep"
|
|
STR_SHOW_HIDDEN_FILES: "Show Hidden Files"
|
|
STR_REFRESH_FREQ: "Refresh Frequency"
|
|
STR_KOREADER_SYNC: "KOReader Sync"
|
|
STR_CHECK_UPDATES: "Check for updates"
|
|
STR_LANGUAGE: "Language"
|
|
STR_CLEAR_READING_CACHE: "Clear Reading Cache"
|
|
STR_USERNAME: "Username"
|
|
STR_PASSWORD: "Password"
|
|
STR_SYNC_SERVER_URL: "Sync Server URL"
|
|
STR_DOCUMENT_MATCHING: "Document Matching"
|
|
STR_AUTHENTICATE: "Authenticate"
|
|
STR_KOREADER_USERNAME: "KOReader Username"
|
|
STR_KOREADER_PASSWORD: "KOReader Password"
|
|
STR_FILENAME: "Filename"
|
|
STR_BINARY: "Binary"
|
|
STR_SET_CREDENTIALS_FIRST: "Set credentials first"
|
|
STR_WIFI_CONN_FAILED: "WiFi connection failed"
|
|
STR_AUTHENTICATING: "Authenticating..."
|
|
STR_AUTH_SUCCESS: "Successfully authenticated!"
|
|
STR_KOREADER_AUTH: "KOReader Auth"
|
|
STR_SYNC_READY: "KOReader sync is ready to use"
|
|
STR_AUTH_FAILED: "Authentication Failed"
|
|
STR_DONE: "Done"
|
|
STR_CLEAR_CACHE_WARNING_1: "This will clear all cached book data."
|
|
STR_CLEAR_CACHE_WARNING_2: "All reading progress will be lost!"
|
|
STR_CLEAR_CACHE_WARNING_3: "Books will need to be re-indexed"
|
|
STR_CLEAR_CACHE_WARNING_4: "when opened again."
|
|
STR_CLEARING_CACHE: "Clearing cache..."
|
|
STR_CACHE_CLEARED: "Cache Cleared"
|
|
STR_ITEMS_REMOVED: "items removed"
|
|
STR_FAILED_LOWER: "failed"
|
|
STR_CLEAR_CACHE_FAILED: "Failed to clear cache"
|
|
STR_CHECK_SERIAL_OUTPUT: "Check serial output for details"
|
|
STR_DARK: "Dark"
|
|
STR_LIGHT: "Light"
|
|
STR_CUSTOM: "Custom"
|
|
STR_COVER: "Cover"
|
|
STR_NONE_OPT: "None"
|
|
STR_FIT: "Fit"
|
|
STR_CROP: "Crop"
|
|
STR_NEVER: "Never"
|
|
STR_IN_READER: "In Reader"
|
|
STR_ALWAYS: "Always"
|
|
STR_IGNORE: "Ignore"
|
|
STR_SLEEP: "Sleep"
|
|
STR_PAGE_TURN: "Page Turn"
|
|
STR_FORCE_REFRESH: "Refresh Screen"
|
|
STR_PORTRAIT: "Portrait"
|
|
STR_LANDSCAPE_CW: "Landscape CW"
|
|
STR_INVERTED: "Inverted"
|
|
STR_LANDSCAPE_CCW: "Landscape CCW"
|
|
STR_PREV_NEXT: "Prev/Next"
|
|
STR_NEXT_PREV: "Next/Prev"
|
|
STR_NOTO_SERIF: "Noto Serif"
|
|
STR_NOTO_SANS: "Noto Sans"
|
|
STR_OPEN_DYSLEXIC: "Open Dyslexic"
|
|
STR_SMALL: "Small"
|
|
STR_MEDIUM: "Medium"
|
|
STR_LARGE: "Large"
|
|
STR_X_LARGE: "X Large"
|
|
STR_TIGHT: "Tight"
|
|
STR_NORMAL: "Normal"
|
|
STR_WIDE: "Wide"
|
|
STR_JUSTIFY: "Justify"
|
|
STR_ALIGN_LEFT: "Left"
|
|
STR_CENTER: "Center"
|
|
STR_ALIGN_RIGHT: "Right"
|
|
STR_MIN_1: "1 min"
|
|
STR_MIN_5: "5 min"
|
|
STR_MIN_10: "10 min"
|
|
STR_MIN_15: "15 min"
|
|
STR_MIN_30: "30 min"
|
|
STR_PAGES_1: "1 page"
|
|
STR_PAGES_5: "5 pages"
|
|
STR_PAGES_10: "10 pages"
|
|
STR_PAGES_15: "15 pages"
|
|
STR_PAGES_30: "30 pages"
|
|
STR_UPDATE: "Update"
|
|
STR_CHECKING_UPDATE: "Checking for update..."
|
|
STR_NEW_UPDATE: "New update available!"
|
|
STR_CURRENT_VERSION: "Current Version: "
|
|
STR_NEW_VERSION: "New Version: "
|
|
STR_UPDATING: "Updating..."
|
|
STR_NO_UPDATE: "No update available"
|
|
STR_UPDATE_FAILED: "Update failed"
|
|
STR_UPDATE_COMPLETE: "Update complete"
|
|
STR_POWER_ON_HINT: "Press and hold power button to turn back on"
|
|
STR_NO_ENTRIES: "No entries found"
|
|
STR_DOWNLOADING: "Downloading..."
|
|
STR_DOWNLOAD_FAILED: "Download failed"
|
|
STR_ERROR_MSG: "Error:"
|
|
STR_UNNAMED: "Unnamed"
|
|
STR_NO_SERVER_URL: "No server URL configured"
|
|
STR_FETCH_FEED_FAILED: "Failed to fetch feed"
|
|
STR_PARSE_FEED_FAILED: "Failed to parse feed"
|
|
STR_NEXT_PAGE: "Next Page »"
|
|
STR_PREV_PAGE: "« Previous Page"
|
|
STR_NETWORK_PREFIX: "Network: "
|
|
STR_IP_ADDRESS_PREFIX: "IP Address: "
|
|
STR_ERROR_GENERAL_FAILURE: "Error: General failure"
|
|
STR_ERROR_NETWORK_NOT_FOUND: "Error: Network not found"
|
|
STR_ERROR_CONNECTION_TIMEOUT: "Error: Connection timeout"
|
|
STR_SD_CARD: "SD card"
|
|
STR_BACK: "« Back"
|
|
STR_EXIT: "« Exit"
|
|
STR_HOME: "« Home"
|
|
STR_SELECT: "Select"
|
|
STR_SELECTED: "Selected"
|
|
STR_TOGGLE: "Toggle"
|
|
STR_CONFIRM: "Confirm"
|
|
STR_CANCEL: "Cancel"
|
|
STR_CONNECT: "Connect"
|
|
STR_OPEN: "Open"
|
|
STR_DOWNLOAD: "Download"
|
|
STR_RETRY: "Retry"
|
|
STR_YES: "Yes"
|
|
STR_NO: "No"
|
|
STR_SHOW: "Show"
|
|
STR_HIDE: "Hide"
|
|
STR_STATE_ON: "ON"
|
|
STR_STATE_OFF: "OFF"
|
|
STR_NOT_SET: "Not Set"
|
|
STR_DIR_LEFT: "Left"
|
|
STR_DIR_RIGHT: "Right"
|
|
STR_DIR_UP: "Up"
|
|
STR_DIR_DOWN: "Down"
|
|
STR_OK_BUTTON: "OK"
|
|
STR_SLEEP_COVER_FILTER: "Sleep Screen Cover Filter"
|
|
STR_FILTER_CONTRAST: "Contrast"
|
|
STR_CUSTOMISE_STATUS_BAR: "Customise Status Bar"
|
|
STR_CHAPTER_PAGE_COUNT: "Chapter Page Count"
|
|
STR_BOOK_PROGRESS_PERCENTAGE: "Book Progress Percentage"
|
|
STR_PROGRESS_BAR: "Progress Bar"
|
|
STR_PROGRESS_BAR_THICKNESS: "Progress Bar Thickness"
|
|
STR_PROGRESS_BAR_THIN: "Thin"
|
|
STR_PROGRESS_BAR_MEDIUM: "Medium"
|
|
STR_PROGRESS_BAR_THICK: "Thick"
|
|
STR_BOOK: "Book"
|
|
STR_CHAPTER: "Chapter"
|
|
STR_EXAMPLE_CHAPTER: "Chapter 21"
|
|
STR_EXAMPLE_BOOK: "Book Title"
|
|
STR_PREVIEW: "Preview"
|
|
STR_TITLE: "Title"
|
|
STR_BATTERY: "Battery"
|
|
STR_UI_THEME: "UI Theme"
|
|
STR_THEME_CLASSIC: "Classic"
|
|
STR_THEME_LYRA: "Lyra"
|
|
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
|
STR_SUNLIGHT_FADING_FIX: "Sunlight Fading Fix"
|
|
STR_REMAP_FRONT_BUTTONS: "Remap Front Buttons"
|
|
STR_OPDS_BROWSER: "OPDS Browser"
|
|
STR_SEARCH: "Search"
|
|
STR_COVER_CUSTOM: "Cover + Custom"
|
|
STR_MENU_RECENT_BOOKS: "Recent Books"
|
|
STR_NO_RECENT_BOOKS: "No recent books"
|
|
STR_CALIBRE_DESC: "Use Calibre wireless device transfers"
|
|
STR_FORGET_AND_REMOVE: "Forget network and remove saved password?"
|
|
STR_FORGET_BUTTON: "Forget"
|
|
STR_CALIBRE_STARTING: "Starting Calibre..."
|
|
STR_CALIBRE_SETUP: "Setup"
|
|
STR_CALIBRE_STATUS: "Status"
|
|
STR_CLEAR_BUTTON: "Clear"
|
|
STR_DEFAULT_VALUE: "Default"
|
|
STR_REMAP_PROMPT: "Press a front button for each role"
|
|
STR_UNASSIGNED: "Unassigned"
|
|
STR_ALREADY_ASSIGNED: "Already assigned"
|
|
STR_REMAP_RESET_HINT: "Side button Up: Reset to default layout"
|
|
STR_REMAP_CANCEL_HINT: "Side button Down: Cancel remapping"
|
|
STR_HW_BACK_LABEL: "Back (1st button)"
|
|
STR_HW_CONFIRM_LABEL: "Confirm (2nd button)"
|
|
STR_HW_LEFT_LABEL: "Left (3rd button)"
|
|
STR_HW_RIGHT_LABEL: "Right (4th button)"
|
|
STR_GO_TO_PERCENT: "Go to %"
|
|
STR_GO_HOME_BUTTON: "Go Home"
|
|
STR_SYNC_PROGRESS: "Sync Progress"
|
|
STR_DELETE_CACHE: "Delete Book Cache"
|
|
STR_DELETE: "Delete"
|
|
STR_DISPLAY_QR: "Show page as QR"
|
|
STR_CHAPTER_PREFIX: "Chapter: "
|
|
STR_PAGES_SEPARATOR: " pages | "
|
|
STR_BOOK_PREFIX: "Book: "
|
|
STR_CALIBRE_URL_HINT: "For Calibre, add /opds to your URL"
|
|
STR_PERCENT_STEP_HINT: "Left/Right: 1% Up/Down: 10%"
|
|
STR_SYNCING_TIME: "Syncing time..."
|
|
STR_CALC_HASH: "Calculating document hash..."
|
|
STR_HASH_FAILED: "Failed to calculate document hash"
|
|
STR_FETCH_PROGRESS: "Fetching remote progress..."
|
|
STR_UPLOAD_PROGRESS: "Uploading progress..."
|
|
STR_NO_CREDENTIALS_MSG: "No credentials configured"
|
|
STR_KOREADER_SETUP_HINT: "Set up KOReader account in Settings"
|
|
STR_PROGRESS_FOUND: "Progress found!"
|
|
STR_REMOTE_LABEL: "Remote:"
|
|
STR_LOCAL_LABEL: "Local:"
|
|
STR_PAGE_OVERALL_FORMAT: "Page %d, %.2f%% overall"
|
|
STR_PAGE_TOTAL_OVERALL_FORMAT: "Page %d/%d, %.2f%% overall"
|
|
STR_DEVICE_FROM_FORMAT: " From: %s"
|
|
STR_APPLY_REMOTE: "Apply remote progress"
|
|
STR_UPLOAD_LOCAL: "Upload local progress"
|
|
STR_NO_REMOTE_MSG: "No remote progress found"
|
|
STR_UPLOAD_PROMPT: "Upload current position?"
|
|
STR_UPLOAD_SUCCESS: "Progress uploaded!"
|
|
STR_SYNC_FAILED_MSG: "Sync failed"
|
|
STR_SECTION_PREFIX: "Section "
|
|
STR_UPLOAD: "Upload"
|
|
STR_BOOK_S_STYLE: "Book's Style"
|
|
STR_EMBEDDED_STYLE: "Embedded Style"
|
|
STR_OPDS_SERVER_URL: "OPDS Server URL"
|
|
STR_FOOTNOTES: "Footnotes"
|
|
STR_NO_FOOTNOTES: "No footnotes on this page"
|
|
STR_LINK: "[link]"
|
|
STR_SCREENSHOT_BUTTON: "Take screenshot"
|
|
STR_ADD_SERVER: "Add Server"
|
|
STR_SERVER_NAME: "Server Name"
|
|
STR_NO_SERVERS: "No OPDS servers configured"
|
|
STR_DELETE_SERVER: "Delete Server"
|
|
STR_DELETE_CONFIRM: "Delete this server?"
|
|
STR_OPDS_SERVERS: "OPDS Servers"
|
|
STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: "
|
|
STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)"
|
|
STR_CRASH_TITLE: "System Crash"
|
|
STR_CRASH_DESCRIPTION: "A detailed report was saved to crash_report.txt. Please include this file in your bug report."
|
|
STR_CRASH_REASON: "Crash reason:"
|
|
STR_CRASH_NO_REASON: "(No reason was recorded)"
|
|
STR_KB_HINT_MOVE_CURSOR: "Press LEFT or RIGHT to move cursor"
|
|
STR_KB_HINT_RETURN_CURSOR: "Press LEFT to return to cursor position"
|
|
STR_KB_HINT_HIDE_PASSWORD: "Hold RIGHT then press [***] to hide password"
|
|
STR_KB_HINT_SHOW_PASSWORD: "Hold RIGHT then press [abc] to show password"
|
|
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Press [***] to hide password"
|
|
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Press [abc] to show password"
|
|
STR_KB_HINT_EDIT_ENTRY: "Hold UP to edit entry"
|
|
STR_KB_TIPS: "Tips:"
|
|
STR_KB_HINT_RETURN_KEYBOARD: "Press DOWN to return to keyboard"
|
|
STR_KB_HINT_EXIT_URL_MODE: "Press ABC to exit URL mode"
|
|
STR_KB_HINT_CLEAR_TEXT: "Hold DEL to clear all text"
|
|
STR_KB_HINT_SECONDARY_CHAR: "Hold SELECT for secondary char"
|
|
STR_KB_HINT_UPPER_SECONDARY: "Hold SELECT for UPPERCASE or secondary char"
|
|
STR_KB_HINT_LOWER_SECONDARY: "Hold SELECT for lowercase or secondary char"
|
|
STR_KB_HINT_URL_SNIPPETS: "Press URL for snippets"
|