Compare commits

...

180 Commits

Author SHA1 Message Date
Huang Xin 6eb7d91122 release: version 0.9.95 (#2646) 2025-12-08 08:59:21 +01:00
Huang Xin 1fb468b3a6 fix(epub): support SVG cover for ebooks from standardebooks.org (#2645) 2025-12-08 08:52:17 +01:00
Huang Xin 3c7d95cf10 fix(footnote): responsive popup size so that on small screen it won't overflow (#2644) 2025-12-08 07:40:30 +01:00
Huang Xin ba3f060cc4 feat: add support for importing from a directory recursively, closes #179 (#2642) 2025-12-08 07:16:57 +01:00
Huang Xin 11bc7497e8 feat: add support for renaming bookshelf groups (#2639) 2025-12-07 10:04:23 +01:00
Huang Xin fb5d149413 fix: enable shared-intent event listener only on Android for now (#2638) 2025-12-07 06:45:56 +01:00
Huang Xin 42b47d73b7 feat: support cloud storage management (#2636) 2025-12-06 20:25:40 +01:00
Huang Xin 00f36af03a feat(opds): add support to search in OPDS, closes #2598 (#2634) 2025-12-06 12:13:45 +01:00
Huang Xin b78466ca93 fix(opds): relax img-src CSP to support images served from arbitrary HTTP/HTTPS hosts and ports, closes #2631 (#2633) 2025-12-06 04:22:15 +01:00
Huang Xin 4e6f146b8f feat(android): support opening shared files from other apps, closes #2484 (#2628) 2025-12-05 18:13:06 +01:00
Huang Xin cbdd4940d0 fix(android): intercept back button press for Android 15+, closes #2454 (#2626) 2025-12-05 16:27:09 +01:00
Huang Xin d022cb984a chore: bump various dependencies (#2624) 2025-12-05 07:48:03 +01:00
Huang Xin 8de6fa267e fix(cache): invalidate config and doc cache, closes #2595 and closes #2572 (#2623) 2025-12-05 07:03:57 +01:00
Huang Xin b08b7de8e9 fix(tts): fixed highlighting of current sentence for native tts on Android, closes #2620 (#2621) 2025-12-05 04:05:18 +01:00
Huang Xin a232a39f0e fix(pdf): Fixed zoomed layout and hand tool event handling, closes #2596 (#2617) 2025-12-04 19:22:01 +01:00
Huang Xin fad7966fc4 fix(layout): auto two-column layout for unfolded screen, closes #2588 (#2615) 2025-12-04 06:56:23 +01:00
Huang Xin a1487fd60c fix: get rid of the context menu for touch screen or stylus device when selecting text, closes #2579 (#2614) 2025-12-04 06:04:06 +01:00
Huang Xin 9606e315d4 fix(layout): hide overflow of children elements in duokan bleed, closes #2597 (#2613) 2025-12-04 03:40:43 +01:00
Huang Xin 978673268b chore: bump next.js to version 16.0.7 (#2612) 2025-12-04 02:34:10 +01:00
Huang Xin 70158a7f15 refactor(opds): use catalog id instead of credentials in url params, closes #2599 (#2606) 2025-12-03 08:50:55 +01:00
Huang Xin 18d65a2c5b fix(annotator): don't copy selection to notebook with keyboard shortcut by default, closes #2603 (#2605) 2025-12-03 07:08:00 +01:00
Huang Xin 1b0c2afad7 fix(layout): fixed scrollable layout in the about readest window, closes #2593 (#2604) 2025-12-03 06:23:02 +01:00
Huang Xin cef444d374 fix: disable saving last book cover with playstore variant, closes #2600 (#2602) 2025-12-03 05:41:44 +01:00
Huang Xin 75f6efe27a compat(opds): add User-Agent header to fix downloads from Calibre Web OPDS (#2592) 2025-12-02 10:27:33 +01:00
Huang Xin 852f9f40ec chore: fix cross compiling of thumbnail extension (#2587) 2025-12-02 02:27:03 +08:00
Huang Xin b9dadc0f4f chore: update flathub metainfo (#2586) 2025-12-01 18:07:29 +01:00
Huang Xin d120cf6573 release: version 0.9.94 (#2585) 2025-12-01 17:46:33 +01:00
Huang Xin d2b3c165c7 fix(opds): use custom header for content length when streaming content (#2584) 2025-12-01 17:28:36 +01:00
Huang Xin 742f06ce05 fix(toc): scroll to current toc item and anchor in TTS mode, closes #2574 (#2583) 2025-12-01 15:50:46 +01:00
Huang Xin e28dabce65 fix(layout): refactor full height for edge to edge env and in browser env (#2582) 2025-12-01 13:48:14 +01:00
Huang Xin 33ef781e21 feat: also convert quotation marks between Chinese variants (#2578) 2025-12-01 09:27:22 +01:00
Huang Xin 02e885fbb7 fix(opds): workaround parsing of encoded params in proxied url in cloudflare (#2575) 2025-12-01 05:49:04 +01:00
Huang Xin 2ca3561093 feat(opds): support downloading ebooks from OPDS catalogs (#2571) 2025-11-30 21:22:54 +01:00
Huang Xin 97f021da87 fix(extensions): add missing tauri conf for windows build (#2563) 2025-11-28 05:56:45 +01:00
Huang Xin bd2b45e1c1 refactor(extensions): move windows-thumbnail to extensions (#2562) 2025-11-28 05:44:11 +01:00
AlI 2b6f7b71b0 feat(windows): Add explorer thumbnail registration and installer hook… (#2557)
* feat(windows): Add Windows Explorer thumbnail support for ebooks (closes #2534)

- Implement IThumbnailProvider COM handler for Windows Shell integration
- Support EPUB, MOBI, AZW, AZW3, KF8, FB2, CBZ, CBR formats
- Add cover extraction with Readest icon overlay
- Register thumbnail handler via NSIS installer hooks
- Only show thumbnails when Readest is the default app for the file type

* chore: clean up build script for thumbnail extension

---------

Co-authored-by: chrox <chrox.huang@gmail.com>
2025-11-27 21:31:52 +01:00
Huang Xin 0bad380a50 fix(layout): fixed navigation parameters not synced with settings (#2559) 2025-11-27 08:06:43 +01:00
Huang Xin ecc20e8521 feat: apply progress style to footerbar progress (#2556) 2025-11-26 16:58:26 +01:00
Huang Xin d5c813fac1 fix(layout): avoid cascading refresh when cleaning up query parameters (#2555) 2025-11-26 15:52:29 +01:00
Huang Xin d9b3757b1c fix(layout): fix layout for import book item in grid fit mode (#2554) 2025-11-26 14:49:07 +01:00
Huang Xin fd7f90236d compat(ios): avoid lookbehind assertions for iOS older than 16.4 (#2553) 2025-11-26 11:31:31 +01:00
Huang Xin df9d21393d fix(layout): layout of the grid columns setting (#2552) 2025-11-26 10:19:29 +01:00
Huang Xin 1f73f15ad8 feat: add view option to set cover image size in grid mode, closes #2545 (#2551) 2025-11-26 07:49:51 +01:00
Huang Xin ba2aa4bee6 fix(layout): skip applying safe area insets when the iframe content is reloaded (#2550) 2025-11-26 04:10:52 +01:00
Huang Xin f709a657fa compat(css): sanitize insane absolute position, closes #2547 (#2549) 2025-11-25 17:19:29 +01:00
Huang Xin 514780a572 feat(shortcut): add shortcuts for annotation tools, closes #2270 (#2548) 2025-11-25 16:10:18 +01:00
Huang Xin 0c51a625f3 i18n: add translations for Bahasa Melayu(ms) (#2543) 2025-11-25 10:35:29 +01:00
Huang Xin 6f8b2d1dc4 fix(android): support back action for grouping modal (#2542) 2025-11-25 10:10:56 +01:00
Huang Xin 0087ce2f19 feat: add option to clear custom fonts in Font Panel (#2541) 2025-11-25 09:19:24 +01:00
Huang Xin b98c2796c8 feat: add option to apply page margins in scrolled mode, closes #2014 (#2540) 2025-11-25 08:55:33 +01:00
Huang Xin 37d56b3205 fix(android): support back key in menu, dialog and alert widgets, closes #2454 (#2539) 2025-11-25 08:01:17 +01:00
Huang Xin 5a54c0fb60 compat(css): override blockquote bg color in dark mode, closes #2281 (#2538) 2025-11-25 04:22:02 +01:00
StepanSad 99b259836b Update Ukrainian translations for consistency (#2535) 2025-11-25 09:28:12 +08:00
Huang Xin a54daaaa90 feat(shortcut): add ctrl + mouse wheel to zoom in/out, closes #2011 (#2533) 2025-11-24 17:19:28 +01:00
Huang Xin fa66e6fca6 feat(pdf): add support for hand tool panning for PDFs, closes #2518 (#2532) 2025-11-24 14:37:52 +01:00
Huang Xin b7864dded2 fix(eink): adjust sidebar and notebook background color for eink, closes #2497 (#2529) 2025-11-24 09:56:26 +01:00
Huang Xin 72d9698f38 fix: prevent file corruption using a robust backup strategy, closes #2512 (#2528) 2025-11-24 08:58:42 +01:00
Huang Xin a7937cd657 fix(translator): resolve occasional translation failures when navigating to new chapters (#2527)
Closes #2451.
2025-11-24 07:47:03 +01:00
Huang Xin 998b14c5b0 fix(layout): avoid clipping text because of negative indent, closes #2498 (#2526) 2025-11-24 06:11:29 +01:00
dependabot[bot] bff9c2a770 chore(deps): bump actions/checkout in the github-actions group (#2525)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 05:35:17 +01:00
Huang Xin b5acdffc87 fix(annotator): fixed layout shift when selecting text in paginated mode on Android (#2524) 2025-11-24 04:06:25 +01:00
Huang Xin 127609160c chore: add more test cases for simplecc (#2522) 2025-11-23 17:41:54 +01:00
Huang Xin 8f22a5c570 feat: select directory to save last book cover image (#2521) 2025-11-23 17:00:49 +01:00
Huang Xin c792c18e01 feat: support text conversion between simplifed and traditional Chinese, closes #2508 (#2520) 2025-11-23 09:56:12 +01:00
Huang Xin cd614e3845 feat(cjk): add an option to replace quotation marks in vertical layout for CJK languages (#2511) 2025-11-22 14:09:20 +01:00
Huang Xin 40673f9cb8 docs: update README and app metadata (#2509) 2025-11-22 07:08:07 +01:00
Huang Xin 911aa4c73d chore(flathub): verify the readest app on flathub (#2507) 2025-11-22 04:07:10 +01:00
Huang Xin 74b4cc2ceb chore(flathub): update oars content attribute (#2504) 2025-11-21 16:44:19 +01:00
Huang Xin c0c463977d fix: proper code indentation (#2503) 2025-11-21 16:24:45 +01:00
Huang Xin 273bcafe01 fix: resolve endless loading indicator (#2502) 2025-11-21 14:58:55 +01:00
Huang Xin 9028059919 chore: fix cf deploy scripts (#2501) 2025-11-21 13:03:56 +01:00
Huang Xin c86af457e7 chore: bump Next.js to 16.0.3 (#2496) 2025-11-21 12:49:36 +01:00
Huang Xin b8e979be55 fix(layout): fixed some rendering problems on Next 16 (#2495) 2025-11-21 08:23:42 +01:00
Huang Xin 8849c19e8e fix(flathub): fix releases notes (#2494) 2025-11-21 06:20:04 +01:00
Huang Xin f1b4d02323 chore: sync app metadata release notes (#2492) 2025-11-21 04:44:50 +01:00
Huang Xin 4aa8847037 chore: bump tauri to version 2.9.3 (#2488) 2025-11-20 14:18:27 +01:00
Huang Xin d2389400a9 fix(annotator): don't scroll page when annotator is shown in TTS mode, closes #2479 (#2487) 2025-11-20 13:24:58 +01:00
Huang Xin 721e70d027 release: version 0.9.93 (#2486) 2025-11-20 06:33:58 +01:00
Huang Xin 3c1a7eac86 fix(library): fixed occasional freeze when navigating back to the library (#2485)
Closes #2482.
2025-11-20 06:15:49 +01:00
Huang Xin f8e21dbd4c fix(sync): fixed sync issues affecting new accounts, closes #2481 (#2483) 2025-11-20 04:28:41 +01:00
Huang Xin d16a35d26f fix(layout): constrain image height within table cells in paginated mode (#2480)
Closes #2472.
2025-11-19 18:06:39 +01:00
Huang Xin f881caf794 chore(pwa): config workbox to skip precaching next.js internal files (#2478) 2025-11-19 14:38:04 +01:00
54wedge eb6fa276de fix(txt): register style.css to content.opf (#2476)
* fix(txt): register style.css to content.opf

* refactor: code styling

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-11-19 13:59:04 +01:00
Huang Xin e987c8b37a chore(pwa): get rid of public headers requests (#2477) 2025-11-19 13:31:21 +01:00
Huang Xin 26d76e27ac fix(txt): more tolerant encoding detection for utf-8 (#2475) 2025-11-19 07:47:10 +01:00
Huang Xin 975549fca0 fix(font): avoid overriding monospace fonts for code blocks, closes #1805 (#2474) 2025-11-19 06:57:45 +01:00
Huang Xin 9a6bed52dd fix(sync): propagate book delete status to other devices (#2473) 2025-11-19 05:47:59 +01:00
Huang Xin efd9ad12a9 release: version 0.9.92 (#2469) 2025-11-18 15:15:27 +01:00
Huang Xin 93a71a0fd0 fix(bookshelf): ensure 'select all' only targets books in the active group (#2468) 2025-11-18 14:34:32 +01:00
Huang Xin d373d7ac28 fix(txt): handle chapter title patterns (#2467) 2025-11-18 14:09:03 +01:00
Huang Xin e0e991b599 fix(kosync): proxy kosync request only for the web app, closes #2440 (#2466) 2025-11-18 13:44:30 +01:00
Huang Xin 6d5b16ea8b fix(iap): open external app for payment (#2465) 2025-11-18 12:07:43 +01:00
Huang Xin bfac67d3f0 fix: ger rid of the AbortSignal polyfill (#2463) 2025-11-18 09:10:53 +01:00
Huang Xin 0fd316d775 fix(sync): retry sync books for previous failed sync, closes #2441 (#2462) 2025-11-17 18:52:48 +01:00
Huang Xin c42fcf9ac4 fix: save reading progress when closing app directly in reader page, closes #2346 (#2461) 2025-11-17 16:35:56 +01:00
Huang Xin 04ade02a06 fix(layout): enlarge clickable area for the close button in the reader page (#2459) 2025-11-17 15:22:05 +01:00
Huang Xin 8ee53d3367 fix(mobi): properly handle empty fragments for MOBI (#2456) 2025-11-17 05:09:09 +01:00
Huang Xin 42111945b8 fix(layout): scale table to fit within column constraints, closes #2445 (#2455) 2025-11-17 04:52:23 +01:00
Huang Xin 6fde157047 feat(bookshelf): support nested groups in the bookshelf, closes #568 (#2449) 2025-11-16 13:59:37 +01:00
Huang Xin 65ec5c9842 fix(library): avoid invalid regular expression in library search (#2439) 2025-11-16 14:07:29 +08:00
Huang Xin a6444b5b33 feat: supported updating email address for Readest account (#2437) 2025-11-12 19:03:02 +01:00
Huang Xin c7238cb04c fix(css): overriding book color for more semantic tags, closes #2433 (#2436) 2025-11-11 19:44:13 +01:00
Huang Xin 366e5fafad fix: fixed multiple windows are opened when opening with readest, closes #2429 (#2435) 2025-11-11 19:36:28 +01:00
Huang Xin fb5710c134 fix(pdf): disabled pagination with swipe gesture below minimum velocity for PDFs, closes #2428 (#2434) 2025-11-11 18:20:45 +01:00
ByteFlow c4d9652335 fix(annotator): enhance PDF context menu for translation and improve touch handling (#2430)
* fix(annotator): enhance PDF context menu for translation and improve touch handling

* feat(annotator, shortcuts): add keyboard shortcuts for selection actions

* feat(annotator): reposition popups on scroll to enhance user experience

* feat(annotator): disable currently unsupported annotator functions for PDFs

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-11-11 15:10:44 +01:00
Huang Xin df6027cf9f fix(eink): avoid gray color in E-Ink mode (#2427) 2025-11-09 19:17:35 +01:00
Huang Xin e3f7abf7f1 fix(tts): fixed incorrectly selected voices, closes #2386 (#2426) 2025-11-09 19:11:12 +01:00
Huang Xin c539917d7e compat(footnote): support more footnote formats (#2425) 2025-11-09 18:07:39 +01:00
Huang Xin 5eb870c978 fix(layout): don't hide horizontal scrollbar, closes #2418 (#2424) 2025-11-09 17:03:24 +01:00
Huang Xin b075c46ba3 compat(sync): compatibility for kosync server implementations that do not return timestamp (#2422) 2025-11-09 14:42:07 +01:00
Huang Xin 4b6c7776f5 fix(eink): no shade in eink mode (#2421) 2025-11-09 14:34:26 +01:00
Huang Xin 745a14195d fix(layout): fixed header bar z index (#2413) 2025-11-06 10:56:28 +01:00
Huang Xin fb0f660eaf fix(android): fixed back gesture to close config panel, closes #2406 (#2411) 2025-11-05 22:45:13 +01:00
Huang Xin 04a3030b79 fix(metadata): fixed changing cover image in Readest apps, closes #2402 (#2410) 2025-11-05 20:43:42 +01:00
Huang Xin 5a3114de48 fix(android): get brightness of the current window on Android, closes #2389 (#2409) 2025-11-05 19:36:45 +01:00
Huang Xin b808fc5954 fix: default to not override book color for PDF, closes #2397 (#2404) 2025-11-04 18:46:37 +01:00
Huang Xin 0ec4e37c13 fix(css): support style transformer for inline css (#2403)
* fix(css): support style transformer for inline css

* fix(eink): bold text for current chapter in the TOC, closes #2401
2025-11-04 18:36:46 +01:00
Huang Xin 9d301631ca fix(layout): restrict image container within column boundaries, closes #2390 (#2398) 2025-11-03 16:35:08 +01:00
Huang Xin ec7145bb01 fix(upload): sanitize filenames before uploading to cloud bucket (#2396) 2025-11-03 11:17:24 +01:00
Huang Xin f79d84b5fa fix(eink): more readablility for E-Ink mode on the library page, closes #2364 (#2395)
This also closes #2391 and closes #2394.
2025-11-03 09:23:35 +01:00
Huang Xin d00f1e0def feat(import): import files directly into the current book group (#2393) 2025-11-03 05:47:26 +01:00
Huang Xin 637a813732 fix(settings): don't show system fonts on Android, closes #2381 (#2388) 2025-11-02 09:49:25 +01:00
Huang Xin 4f33c9280b fix(layout): fine tuning of column width in scrolled mode and vertical mode, closes #2383 (#2387) 2025-11-02 09:34:57 +01:00
Huang Xin d54c752637 fix(koplugin): properly refresh access token (#2384) 2025-11-02 04:50:54 +01:00
Huang Xin 984d5d198d fix(css): avoid overriding table background color by default, closes #2377 (#2379) 2025-11-01 15:09:46 +01:00
Huang Xin 2568778a87 feat(layout): support for duokan bleed layout, closes #2374 (#2378) 2025-11-01 14:26:48 +01:00
Huang Xin 0d805a64f6 compat: polyfill AbortController/AbortSignal on older browsers (#2371) 2025-10-31 06:15:59 +01:00
Huang Xin 066d1c5b1e fix(import): resolve import failures for certain EPUB files, closes #230 (#2370)
If the publisher has already been parsed, don’t remap it from the author or contributor fields again.
This prevents failures when parsing metadata like <dc:creator opf:role="pbl" />.
2025-10-31 05:12:24 +01:00
Huang Xin cdc1950c3a fix(layout): various fixes on the book and reader styles and layouts, closes #2368 (#2369) 2025-10-30 19:34:55 +01:00
Huang Xin 7142874513 fix(ui): display exact storage capacity in the user profile (#2366) 2025-10-30 11:50:39 +01:00
Huang Xin 409c2f62be release: version 0.9.91 (#2362) 2025-10-29 16:37:43 +01:00
Huang Xin 82922c5053 feat(eink): add an option to save last book cover on Android, closes #1414 (#2361) 2025-10-29 16:15:08 +01:00
Huang Xin 9c5bcb13e2 feat(plan): show life-time plan name in user profile (#2360) 2025-10-29 15:36:12 +01:00
Huang Xin 1d87dcdb7f feat: add options to customize the style of highlighted text of current sentence of TTS (#2358) 2025-10-29 10:17:36 +01:00
Huang Xin c7ba44a91e eink: disable all shadows in E-Ink mode (#2354) 2025-10-29 06:46:17 +01:00
Huang Xin 19b79c5b35 fix(android): fixed Android launcher icon size on some Android systems (#2353) 2025-10-29 06:19:02 +01:00
Huang Xin 45216763e3 fix(compat): use xml serializer for sanitized doc for better compatibility (#2352) 2025-10-29 05:12:19 +01:00
Huang Xin 123293dc0e fix: fixed crash when switching allow script (#2351) 2025-10-29 03:17:17 +01:00
Huang Xin 78e61060d2 fix(tts): fixed listener of tts events (#2349) 2025-10-28 18:36:57 +01:00
Huang Xin dd5371d2fd release: version 0.9.90 (#2344) 2025-10-28 05:44:07 +01:00
Huang Xin da041d1f38 fix(ui): hover to display footer bar in non-maximized windows on desktop (#2343) 2025-10-28 05:30:41 +01:00
Huang Xin d01599a2b4 feat(security): sanitize XHTML safely to prevent XSS when scripts are disabled, closes #2316 (#2342) 2025-10-28 04:28:48 +01:00
Huang Xin e80a2268ac feat(ui): add non-linear scale to brightness slider for better low-value control (#2338)
Implement logarithmic mapping for screen brightness slider to improve
usability at low brightness levels where small percentage changes have
significant visual impact.
2025-10-27 13:45:43 +01:00
Huang Xin 3b86229c8f perf(sync): batched updating notes and books when syncing (#2337) 2025-10-27 13:37:04 +01:00
Huang Xin c4d498d183 perf: much faster sync of the whole library (#2336) 2025-10-27 05:33:12 +01:00
Huang Xin 9bc46914e2 feat(iap): support expanding cloud storage with IAP on iOS and Android (#2331) 2025-10-26 10:18:56 +01:00
AlI 75f982e677 i18n(fa): refactor Farsi translation (#2330)
Restructure the Farsi translation to align with the format and key organization used in other translation files. This improves consistency and maintainability.
2025-10-26 08:58:57 +01:00
AlI f58bfeb1d0 fix(layout): Fix popup menu for RTL languages (#2327) 2025-10-26 08:51:25 +08:00
AlI ebb077f6e0 i18n: add translations for Persian/Farsi(fa), closes #2321 (#2326) 2025-10-25 15:32:24 +02:00
Huang Xin fdf6908fc6 feat: expand cloud storage with one-time payment (#2325) 2025-10-25 11:46:51 +02:00
Huang Xin 7936660868 docs: add sponsor links in README (#2318) 2025-10-24 15:22:08 +02:00
Huang Xin 554dd3798f fix: more explanatory error message when importing books, closes #2309 (#2314) 2025-10-24 07:15:41 +02:00
Huang Xin 7f40d8e12c fix(eink): more readability for progress info and section info in E-Ink mode, closes #2311 (#2313) 2025-10-24 06:14:25 +02:00
Huang Xin 31d6f44ff5 fix(iOS): restore last page if app is terminated in the background (#2312) 2025-10-24 05:42:52 +02:00
Huang Xin 86efcde927 feat(tts): add an option to select target language for TTS on translated books (#2310) 2025-10-23 19:34:43 +02:00
Huang Xin 4acbc33762 fonts: add PT font families (#2308) 2025-10-23 16:05:28 +02:00
Huang Xin 7cd3ebfcba compat: support .fb.zip files as FBZ format (#2307)
Although .fb.zip is not an officially recognized extension, many FBZ files use this format, so it's now treated as FBZ for compatibility.
2025-10-23 15:38:44 +02:00
Huang Xin 55c97cab7e perf: improve smoothness of Arrow Up/Down scrolling, closes #2080 (#2306) 2025-10-23 15:32:51 +02:00
Huang Xin 5bde091704 feat(settings): add an option to click both sides of the screen to paginate forward, closes #1951 (#2305) 2025-10-23 13:57:03 +02:00
Huang Xin 70489b46a5 fix(search): correctly display results when searching from selected text (#2304) 2025-10-23 11:52:49 +02:00
Huang Xin d7e89cc01f fix(layout): responsive layout for the custom highlight colors options (#2303) 2025-10-23 11:51:31 +02:00
Huang Xin a1f6030e75 fix(iOS): detect and recover from WebContent process termination (#2302) 2025-10-23 10:37:07 +02:00
Huang Xin e7b2d8435e feat(eink): set underline text decoration for links in E-Ink mode, closes #2293 (#2299) 2025-10-22 18:52:30 +02:00
Huang Xin f890a9633b feat(settings): add an option to set auto screen brightness (#2297) 2025-10-22 18:45:51 +02:00
Huang Xin 5bdcd3124b chore: bump tauri to version 2.9.0 (#2296) 2025-10-22 17:51:35 +02:00
Huang Xin 752b3f8e4c chore: bump nextjs, opennextjs and supabasejs (#2295) 2025-10-22 16:19:06 +02:00
Huang Xin 001e836ae3 chore: bump eslint-config-next to version 16 (#2294) 2025-10-22 15:57:04 +02:00
Huang Xin 50d7f9a9dd feat(android): support custom data location on external sdcard (#2292) 2025-10-22 10:14:41 +02:00
Huang Xin 34a5e58872 feat: add keyboard shortcuts to go prev/next sections, closes #2287 (#2291) 2025-10-22 07:41:33 +02:00
Huang Xin 34fbea6a44 css: properly handle theme color in dark mode for code blocks, closes #2281 (#2290) 2025-10-22 06:56:53 +02:00
Huang Xin 8737535b90 feat: support Android IAP (#2286) 2025-10-21 18:24:50 +02:00
Huang Xin 28ef414c3d feat: supported footnotes for definition list, closes #2278 (#2279) 2025-10-20 10:38:46 +02:00
dependabot[bot] 16279949c1 chore(deps): bump actions/setup-node in the github-actions group (#2277) 2025-10-20 12:25:42 +08:00
Huang Xin 444e22dd0e chore: i18n for highlight color settings (#2275) 2025-10-19 18:59:26 +02:00
仿生猫梦见苦力怕 a231506713 fix(css): support at rules of CSS and fix CSS formatter (#2269)
* refactor: replace manual CSS validator with PostCSS

* Revert "refactor: replace manual CSS validator with PostCSS"

This reverts commit 609ed7dd5a50debcea0303cc1043440af9849ddc.

* fix: support at rules of CSS and fix CSS formatter

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-10-19 18:09:04 +02:00
airwish 5dcabba73c chore: add contribution settings (#2274) 2025-10-19 17:14:20 +02:00
AlI cd71c494da feat(settings): add custom highlight color picker (#2273)
Add ability to customize highlight colors with hex color picker. Users can now set custom colors for all five highlight styles (red, violet, blue, green, yellow) in the settings panel.

Fixes #2271
2025-10-19 17:03:59 +02:00
Huang Xin f66642b8ec tts: avoid using AnaNeural as default English voice (#2272) 2025-10-19 14:58:51 +02:00
Huang Xin 2283e1b0f2 fix(layout): fix inline image, closes #2263 (#2266) 2025-10-18 17:25:37 +02:00
Daniil Leontev e0eb725d8b fix: add more elements to color overrides (#2265) 2025-10-18 22:25:02 +08:00
337 changed files with 26072 additions and 5627 deletions
+4 -4
View File
@@ -11,7 +11,7 @@ jobs:
env:
RUSTFLAGS: '-C target-cpu=skylake'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: 'true'
- name: Install minimal stable with clippy and rustfmt
@@ -39,7 +39,7 @@ jobs:
- platform: 'web'
- platform: 'tauri'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: 'true'
@@ -49,7 +49,7 @@ jobs:
version: 10.14.0
- name: setup node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
@@ -66,7 +66,7 @@ jobs:
- name: install Dependencies
working-directory: apps/readest-app
run: |
pnpm install && pnpm setup-pdfjs
pnpm install && pnpm setup-vendors
- name: run tests
working-directory: apps/readest-app
+8 -8
View File
@@ -17,9 +17,9 @@ jobs:
release_version: ${{ steps.get-release-notes.outputs.release_version }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: setup node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
- name: get version
run: echo "PACKAGE_VERSION=$(node -p "require('./apps/readest-app/package.json').version")" >> $GITHUB_ENV
- name: get release
@@ -87,7 +87,7 @@ jobs:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: create KOReader plugin zip
env:
@@ -146,7 +146,7 @@ jobs:
runs-on: ${{ matrix.config.os }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: initialize git submodules
run: git submodule update --init --recursive
@@ -157,7 +157,7 @@ jobs:
version: 10.14.0
- name: setup node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
@@ -180,8 +180,8 @@ jobs:
- name: install dependencies
run: pnpm install
- name: copy pdfjs-dist to public directory
run: pnpm --filter @readest/readest-app setup-pdfjs
- name: copy pdfjs-dist and simplecc-dist to public directory
run: pnpm --filter @readest/readest-app setup-vendors
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
@@ -321,7 +321,7 @@ jobs:
echo "Building Portable Binaries"
pushd apps/readest-app/
echo "NEXT_PUBLIC_PORTABLE_APP=true" >> .env.local
pnpm tauri build
pnpm tauri build ${{ matrix.config.args }}
popd
echo "Uploading Portable Binaries"
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: 'true'
- uses: amondnet/vercel-action@v25
+3
View File
@@ -7,3 +7,6 @@
[submodule "packages/tauri-plugins"]
path = packages/tauri-plugins
url = https://github.com/readest/tauri-plugins-workspace.git
[submodule "packages/simplecc-wasm"]
path = packages/simplecc-wasm
url = https://github.com/readest/simplecc-wasm.git
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"ms-vscode.vscode-typescript-next",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"rust-lang.rust-analyzer"
]
}
Generated
+682 -701
View File
File diff suppressed because it is too large Load Diff
+16 -15
View File
@@ -5,7 +5,7 @@
<h1>Readest</h1>
<br>
[Readest][link-website] is an open-source ebook reader designed for immersive and deep reading experiences. Built as a modern rewrite of [Foliate](https://github.com/johnfactotum/foliate), it leverages [Next.js 15](https://github.com/vercel/next.js) and [Tauri v2](https://github.com/tauri-apps/tauri) to deliver a smooth, cross-platform experience across macOS, Windows, Linux, Android, iOS, and the Web.
[Readest][link-website] is an open-source ebook reader designed for immersive and deep reading experiences. Built as a modern rewrite of [Foliate](https://github.com/johnfactotum/foliate), it leverages [Next.js 16](https://github.com/vercel/next.js) and [Tauri v2](https://github.com/tauri-apps/tauri) to deliver a smooth, cross-platform experience across macOS, Windows, Linux, Android, iOS, and the Web.
[![Website][badge-website]][link-website]
[![Web App][badge-web-app]][link-web-readest]
@@ -14,7 +14,7 @@
[![Discord][badge-discord]][link-discord]
[![Reddit][badge-reddit]][link-reddit]
[![AGPL Licence][badge-license]](LICENSE)
[![Language Coverage][badge-language-coverage]]()
[![Language Coverage][badge-language-coverage]][link-locales]
[![Donate][badge-donate]][link-donate]
[![Latest release][badge-release]][link-gh-releases]
[![Last commit][badge-last-commit]][link-gh-commits]
@@ -61,6 +61,7 @@
| **Translate with DeepL and Yandex** | From a single sentence to the entire book—translate instantly. | ✅ |
| **Text-to-Speech (TTS) Support** | Enjoy smooth, multilingual narration—even within a single book. | ✅ |
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
| **OPDS/Calibre Integration** | Integrate OPDS/Calibre to access online libraries and catalogs. | ✅ |
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
## Planned Features
@@ -72,7 +73,6 @@
| ------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------ |
| [**Sync with Koreader**][link-kosync-wiki] | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | 🛠 |
| **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 |
| **Support OPDS/Calibre** | Integrate OPDS/Calibre to access online libraries and catalogs. | 🔄 |
| **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 |
| **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 |
| **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🔄 |
@@ -111,6 +111,7 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion
- macOS / iOS / iPadOS : Search and install **Readest** on the [App Store][link-appstore], _also_ available on TestFlight for beta test (send your Apple ID to <readestapp@gmail.com> to request access).
- Windows / Linux / Android: Visit and download **Readest** at [https://readest.com][link-website] or the [Releases on GitHub][link-gh-releases].
- Linux users can also install [Readest on Flathub][link-flathub].
- Web: Visit and use **Readest for Web** at [https://web.readest.com][link-web-readest].
## Requirements
@@ -144,8 +145,8 @@ cd readest
# might need to rerun this when code is updated
git submodule update --init --recursive
pnpm install
# copy pdfjs-dist to Next.js public directory
pnpm --filter @readest/readest-app setup-pdfjs
# copy vendors dist libs to public directory
pnpm --filter @readest/readest-app setup-vendors
```
### 3. Verify Dependencies Installation
@@ -261,17 +262,15 @@ Readest is open-source, and contributions are welcome! Feel free to open issues,
## Support
If Readest has been useful to you, consider supporting its development. Your contribution helps us squash bugs faster, improve performance, and keep building great features.
If Readest has been useful to you, consider supporting its development. You can [become a sponsor on GitHub](https://github.com/sponsors/readest) or just [donate with crypto](https://donate.readest.com). Your contribution helps us squash bugs faster, improve performance, and keep building great features.
### How to Donate
### Sponsors
1. **GitHub Sponsors**
Back the project directly on GitHub:
👉 [https://github.com/sponsors/readest](https://github.com/sponsors/readest)
2. **Crypto Donations**
Prefer crypto? You can donate here:
👉 [https://donate.readest.com/](https://donate.readest.com/)
<p align="center">
<a title="Browser testing via LambdaTest" href="https://www.lambdatest.com/?utm_source=readest&utm_medium=sponsor" target="_blank">
<img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" />
</a>
</p>
## License
@@ -310,10 +309,11 @@ The following fonts are utilized in this software, either bundled within the app
[badge-donate]: https://donate.readest.com/badge.svg
[badge-deepwiki]: https://deepwiki.com/badge.svg
[badge-reddit]: https://img.shields.io/reddit/subreddit-subscribers/readest?style=flat&logo=reddit&color=F37E41
[badge-language-coverage]: https://img.shields.io/badge/coverage-52%25%20population%20🌍-green
[badge-language-coverage]: https://img.shields.io/badge/coverage-53%25%20population%20🌍-green
[link-donate]: https://donate.readest.com/?tickers=btc%2Ceth%2Csol%2Cusdc
[link-appstore]: https://apps.apple.com/app/apple-store/id6738622779?pt=127463130&ct=github&mt=8
[link-website]: https://readest.com?utm_source=github&utm_medium=referral&utm_campaign=readme
[link-flathub]: https://flathub.org/en/apps/com.bilingify.readest
[link-web-readest]: https://web.readest.com
[link-gh-releases]: https://github.com/readest/readest/releases
[link-gh-commits]: https://github.com/readest/readest/commits/main
@@ -324,5 +324,6 @@ The following fonts are utilized in this software, either bundled within the app
[link-koreader]: https://github.com/koreader/koreader
[link-hellogithub]: https://hellogithub.com/repository/8a5b6ade2aee461a8bd94e59200682a7
[link-deepwiki]: https://deepwiki.com/readest/readest
[link-locales]: https://github.com/readest/readest/tree/main/apps/readest-app/public/locales
[link-kosync-wiki]: https://github.com/readest/readest/wiki/Sync-with-Koreader-devices
[link-reddit]: https://reddit.com/r/readest/
+28 -18
View File
@@ -1,25 +1,35 @@
import path from 'node:path';
import js from '@eslint/js';
import { defineConfig, globalIgnores } from 'eslint/config';
import next from 'eslint-config-next';
import nextVitals from 'eslint-config-next/core-web-vitals';
import tseslint from 'eslint-config-next/typescript';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import { fileURLToPath } from 'node:url';
import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default [
...compat.extends('next/core-web-vitals', 'next/typescript'),
const eslintConfig = defineConfig([
...tseslint,
...next,
...nextVitals,
{
plugins: {
'jsx-a11y': jsxA11y,
},
rules: {
...jsxA11y.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
},
},
];
globalIgnores([
'node_modules/**',
'.next/**',
'.open-next/**',
'out/**',
'build/**',
'public/**',
'next-env.d.ts',
]),
]);
export default eslintConfig;
+588
View File
@@ -0,0 +1,588 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "directories-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [
"crc32fast",
"libz-rs-sys",
"miniz_oxide",
]
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gif"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f954a9e9159ec994f73a30a12b96a702dde78f5547bcb561174597924f7d4162"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "image"
version = "0.25.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
dependencies = [
"bytemuck",
"byteorder-lite",
"color_quant",
"gif",
"image-webp",
"moxcms",
"num-traits",
"png",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "image-webp"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
dependencies = [
"byteorder-lite",
"quick-error",
]
[[package]]
name = "indexmap"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libredox"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "libz-rs-sys"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd"
dependencies = [
"zlib-rs",
]
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "md5"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "moxcms"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
dependencies = [
"num-traits",
"pxfm",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "png"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pxfm"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3502d6155304a4173a5f2c34b52b7ed0dd085890326cb50fd625fdf39e86b3b"
dependencies = [
"num-traits",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link",
]
[[package]]
name = "windows_thumbnail"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"directories-next",
"image",
"md5",
"once_cell",
"windows",
"windows-core",
"zip",
]
[[package]]
name = "zip"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b"
dependencies = [
"arbitrary",
"crc32fast",
"flate2",
"indexmap",
"memchr",
"zopfli",
]
[[package]]
name = "zlib-rs"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
[[package]]
name = "zopfli"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]
[[package]]
name = "zune-core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773"
[[package]]
name = "zune-jpeg"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e"
dependencies = [
"zune-core",
]
@@ -0,0 +1,31 @@
[package]
name = "windows_thumbnail"
version = "0.1.0"
edition = "2021"
publish = false
[workspace]
[lib]
name = "windows_thumbnail"
path = "src/mod.rs"
crate-type = ["cdylib"]
[dependencies]
anyhow = "1"
base64 = "0.22"
directories-next = "2.0"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "gif", "webp"] }
md5 = "0.8"
once_cell = "1.19"
zip = { version = "6.0", default-features = false, features = ["deflate"] }
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_Security",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Registry",
"Win32_UI_Shell",
] }
windows-core = "0.62"
@@ -0,0 +1,131 @@
# Windows Thumbnail Provider for Readest
This crate provides Windows Explorer thumbnail support for eBook files when Readest is set as the default application.
## Features
- **Automatic Cover Extraction**: Extracts cover images from EPUB, MOBI, AZW, AZW3, FB2, CBZ, CBR files
- **Readest Branding**: Adds a small Readest icon overlay at the bottom-right corner
- **Smart Caching**: Caches generated thumbnails for faster subsequent loads
- **File Association Aware**: Only shows thumbnails when Readest is the default app for the file type
- **COM Integration**: Full Windows Shell extension implementation via `IThumbnailProvider`
## Supported Formats
| Format | Extension | Cover Source |
|--------|-----------|--------------|
| EPUB | `.epub` | OPF manifest cover reference |
| MOBI/AZW | `.mobi`, `.azw`, `.prc` | EXTH cover offset |
| AZW3/KF8 | `.azw3`, `.kf8` | KF8 format cover |
| FB2 | `.fb2` | `<binary>` coverpage element |
| Comic Book | `.cbz`, `.cbr` | First image in archive |
| Plain Text | `.txt` | Generated placeholder |
## Building
### Library Only
```bash
cargo build --release
```
### COM DLL (for Windows Explorer integration)
```bash
cargo build --release --features com
```
### CLI Tool
```bash
cargo build --release --features cli
```
## Installation
The thumbnail provider DLL is automatically registered when Readest is installed via the NSIS installer.
### Manual Registration (for development)
```powershell
# Register the DLL
regsvr32 /s target\release\windows_thumbnail.dll
# Unregister the DLL
regsvr32 /s /u target\release\windows_thumbnail.dll
# Refresh Explorer to see changes
ie4uinit.exe -show
```
After registration, you may need to restart Windows Explorer or log out/in for changes to take effect.
## Usage (Development / Manual testing)
For local development and testing, build the Windows DLL (or the library) from the Readest Tauri app folder and register it manually. The legacy CLI test harness used to live in the separate `packages/tauri` workspace, but the thumbnail handler implementation now lives inside Readest's Tauri app.
Build the DLL (for Windows explorer integration):
```powershell
cd apps/readest-app/src-tauri
cargo build --release --manifest-path Cargo.toml --features com
```
The standalone CLI test harness is no longer distributed with the app. To test the thumbnail provider locally, build and register the DLL as shown above and use a small test harness that imports `readestlib`'s thumbnail code or use Explorer after registering the handler.
Manual registration for development (register the generated DLL):
```powershell
# Register the DLL
regsvr32 /s target\release\windows_thumbnail.dll
# Unregister the DLL
regsvr32 /s /u target\release\windows_thumbnail.dll
# Refresh Explorer to see changes
ie4uinit.exe -show
```
This generates a thumbnail with the Readest overlay at the specified size.
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Windows Explorer │
├─────────────────────────────────────────────────────────────────┤
│ │ │
│ IThumbnailProvider ───┼──► ThumbnailProvider │
│ │ │ │
│ │ ▼ │
│ │ Check File Association │
│ │ (is Readest the default?) │
│ │ │ │
│ │ ▼ (if yes) │
│ │ Extract Cover Image │
│ │ │ │
│ │ ▼ │
│ │ Add Readest Overlay │
│ │ │ │
│ │ ▼ │
│ │ Return HBITMAP │
│ │ │
└─────────────────────────────────────────────────────────────────┘
```
## COM Details
- **CLSID**: `{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}`
- **Shell Thumbnail Handler GUID**: `{e357fccd-a995-4576-b01f-234630154e96}`
- **Threading Model**: Apartment
## How It Works
1. When Windows Explorer needs a thumbnail, it queries the registered shell extension
2. The COM DLL implements `IInitializeWithItem` to receive the file path
3. It checks if Readest.exe is the default application for that file type using `AssocQueryStringW`
4. If Readest is the default, it extracts the cover and generates the thumbnail
5. If Readest is NOT the default, it returns `S_FALSE` to let Windows use other handlers
This ensures thumbnails only appear for files the user has associated with Readest.
## License
MIT License - See LICENSE file for details.
@@ -0,0 +1,506 @@
/// Windows COM Thumbnail Provider for Readest
///
/// Implements IThumbnailProvider and IInitializeWithItem for Windows Shell integration.
/// This allows Windows Explorer to show book covers as thumbnails for eBook files.
///
/// **Important**: Thumbnails are only shown when Readest.exe is the default application
/// for the file type.
///
/// ## CLSID: {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::path::PathBuf;
use std::sync::atomic::{AtomicIsize, AtomicU32, Ordering};
use windows::core::{IUnknown, Interface, GUID, HRESULT, PCWSTR, PWSTR};
use windows::Win32::Foundation::{
CLASS_E_NOAGGREGATION, E_FAIL, E_INVALIDARG, E_NOINTERFACE, HMODULE, S_FALSE, S_OK,
};
use windows::Win32::Graphics::Gdi::{
CreateDIBSection, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, HBITMAP,
};
use windows::Win32::System::Com::{CoTaskMemFree, IClassFactory, IClassFactory_Impl};
use windows::Win32::System::LibraryLoader::GetModuleFileNameW;
use windows::Win32::System::Registry::{
RegCloseKey, RegCreateKeyExW, RegDeleteTreeW, RegSetValueExW, HKEY, HKEY_CLASSES_ROOT,
KEY_WRITE, REG_OPTION_NON_VOLATILE, REG_SZ,
};
use windows::Win32::UI::Shell::{
AssocQueryStringW, IInitializeWithItem, IInitializeWithItem_Impl, IShellItem,
IThumbnailProvider, IThumbnailProvider_Impl, ASSOCF_NONE, ASSOCSTR_EXECUTABLE,
SIGDN_FILESYSPATH, WTSAT_ARGB, WTS_ALPHATYPE,
};
use windows_core::BOOL;
use windows_core::{implement, Ref};
use super::cached_thumbnail_for_path;
// ─────────────────────────────────────────────────────────────────────────────
// CLSID for Readest Thumbnail Provider
// ─────────────────────────────────────────────────────────────────────────────
/// CLSID: {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
pub const CLSID_READEST_THUMBNAIL: GUID = GUID::from_u128(0xA1B2C3D4_E5F6_7890_ABCD_EF1234567890);
/// Supported file extensions
pub const SUPPORTED_EXTENSIONS: &[&str] = &[
".epub", ".mobi", ".azw", ".azw3", ".kf8", ".prc", ".fb2", ".cbz", ".cbr", ".txt",
];
// DLL reference counting
static DLL_REF_COUNT: AtomicU32 = AtomicU32::new(0);
static DLL_MODULE_PTR: AtomicIsize = AtomicIsize::new(0);
fn dll_add_ref() {
DLL_REF_COUNT.fetch_add(1, Ordering::SeqCst);
}
fn dll_release() {
DLL_REF_COUNT.fetch_sub(1, Ordering::SeqCst);
}
fn set_dll_module(h: HMODULE) {
DLL_MODULE_PTR.store(h.0 as isize, Ordering::SeqCst);
}
fn get_dll_module() -> Option<HMODULE> {
let ptr = DLL_MODULE_PTR.load(Ordering::SeqCst);
if ptr == 0 {
None
} else {
Some(HMODULE(ptr as *mut c_void))
}
}
// ─────────────────────────────────────────────────────────────────────────────
// File Association Check
// ─────────────────────────────────────────────────────────────────────────────
/// Check if Readest.exe is the default application for a given file extension.
fn is_readest_default_for_extension(ext: &str) -> bool {
let ext_wide: Vec<u16> = ext.encode_utf16().chain(std::iter::once(0)).collect();
let mut buffer = [0u16; 260];
let mut buffer_size = buffer.len() as u32;
unsafe {
let result = AssocQueryStringW(
ASSOCF_NONE,
ASSOCSTR_EXECUTABLE,
PCWSTR(ext_wide.as_ptr()),
None,
Some(PWSTR(buffer.as_mut_ptr())),
&mut buffer_size,
);
if result.is_ok() {
let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
let path = String::from_utf16_lossy(&buffer[..len]).to_lowercase();
return path.contains("readest");
}
}
false
}
/// Check if Readest is the default app for a specific file path.
fn is_readest_default_for_file(path: &PathBuf) -> bool {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
let ext_with_dot = format!(".{}", ext.to_lowercase());
return is_readest_default_for_extension(&ext_with_dot);
}
false
}
// ─────────────────────────────────────────────────────────────────────────────
// ThumbnailProvider
// ─────────────────────────────────────────────────────────────────────────────
/// Interior mutability wrapper for COM single-threaded apartment
struct ComCell<T>(UnsafeCell<T>);
impl<T> ComCell<T> {
fn new(value: T) -> Self {
Self(UnsafeCell::new(value))
}
fn get(&self) -> &T {
unsafe { &*self.0.get() }
}
fn set(&self, value: T) {
unsafe {
*self.0.get() = value;
}
}
}
// SAFETY: COM thumbnail providers run in single-threaded apartment (STA)
unsafe impl<T> Sync for ComCell<T> {}
unsafe impl<T> Send for ComCell<T> {}
#[implement(IThumbnailProvider, IInitializeWithItem)]
pub struct ThumbnailProvider {
file_path: ComCell<Option<PathBuf>>,
file_ext: ComCell<Option<String>>,
should_provide: ComCell<bool>,
}
impl ThumbnailProvider {
pub fn new() -> Self {
dll_add_ref();
Self {
file_path: ComCell::new(None),
file_ext: ComCell::new(None),
should_provide: ComCell::new(false),
}
}
}
impl Default for ThumbnailProvider {
fn default() -> Self {
Self::new()
}
}
impl Drop for ThumbnailProvider {
fn drop(&mut self) {
dll_release();
}
}
impl IInitializeWithItem_Impl for ThumbnailProvider_Impl {
fn Initialize(&self, psi: Ref<'_, IShellItem>, _grfmode: u32) -> windows::core::Result<()> {
let item = psi.ok()?;
unsafe {
let path_pwstr = item.GetDisplayName(SIGDN_FILESYSPATH)?;
let mut len = 0usize;
let mut ptr = path_pwstr.0;
while *ptr != 0 {
len += 1;
ptr = ptr.add(1);
}
let slice = std::slice::from_raw_parts(path_pwstr.0, len);
let path_str = String::from_utf16_lossy(slice);
let path = PathBuf::from(&path_str);
CoTaskMemFree(Some(path_pwstr.0 as *const c_void));
let is_default = is_readest_default_for_file(&path);
self.should_provide.set(is_default);
if !is_default {
return Ok(());
}
let ext = path
.extension()
.and_then(|e| e.to_str())
.map(|s| s.to_lowercase())
.unwrap_or_default();
self.file_path.set(Some(path));
self.file_ext.set(Some(ext));
}
Ok(())
}
}
impl IThumbnailProvider_Impl for ThumbnailProvider_Impl {
fn GetThumbnail(
&self,
cx: u32,
phbmp: *mut HBITMAP,
pdwalpha: *mut WTS_ALPHATYPE,
) -> windows::core::Result<()> {
if !*self.should_provide.get() {
return Err(E_FAIL.into());
}
let path = self.file_path.get().as_ref().ok_or(E_FAIL)?;
let ext = self.file_ext.get().as_ref().ok_or(E_FAIL)?;
let png_bytes = cached_thumbnail_for_path(path, ext, cx).map_err(|_| E_FAIL)?;
let img = image::load_from_memory(&png_bytes).map_err(|_| E_FAIL)?;
let rgba = img.to_rgba8();
let (width, height) = (rgba.width(), rgba.height());
let bmi = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
biWidth: width as i32,
biHeight: -(height as i32),
biPlanes: 1,
biBitCount: 32,
biCompression: BI_RGB.0,
..Default::default()
},
..Default::default()
};
let mut bits: *mut c_void = std::ptr::null_mut();
unsafe {
let hbmp = CreateDIBSection(None, &bmi, DIB_RGB_COLORS, &mut bits, None, 0)
.map_err(|_| E_FAIL)?;
if bits.is_null() {
return Err(E_FAIL.into());
}
// RGBA -> BGRA
let dst =
std::slice::from_raw_parts_mut(bits as *mut u8, (width * height * 4) as usize);
let src = rgba.as_raw();
for i in 0..(width * height) as usize {
let si = i * 4;
dst[si] = src[si + 2]; // B
dst[si + 1] = src[si + 1]; // G
dst[si + 2] = src[si]; // R
dst[si + 3] = src[si + 3]; // A
}
*phbmp = hbmp;
*pdwalpha = WTSAT_ARGB;
}
Ok(())
}
}
// ─────────────────────────────────────────────────────────────────────────────
// ClassFactory
// ─────────────────────────────────────────────────────────────────────────────
#[implement(IClassFactory)]
pub struct ThumbnailProviderFactory;
impl ThumbnailProviderFactory {
pub fn new() -> Self {
dll_add_ref();
Self
}
}
impl Default for ThumbnailProviderFactory {
fn default() -> Self {
Self::new()
}
}
impl Drop for ThumbnailProviderFactory {
fn drop(&mut self) {
dll_release();
}
}
impl IClassFactory_Impl for ThumbnailProviderFactory_Impl {
fn CreateInstance(
&self,
punkouter: Ref<'_, IUnknown>,
riid: *const GUID,
ppvobject: *mut *mut c_void,
) -> windows::core::Result<()> {
unsafe {
if ppvobject.is_null() {
return Err(E_INVALIDARG.into());
}
*ppvobject = std::ptr::null_mut();
if !punkouter.is_null() {
return Err(CLASS_E_NOAGGREGATION.into());
}
let provider: IThumbnailProvider = ThumbnailProvider::new().into();
provider.query(&*riid, ppvobject).ok()
}
}
fn LockServer(&self, flock: BOOL) -> windows::core::Result<()> {
if flock.as_bool() {
dll_add_ref();
} else {
dll_release();
}
Ok(())
}
}
// ─────────────────────────────────────────────────────────────────────────────
// DLL Exports
// ─────────────────────────────────────────────────────────────────────────────
#[no_mangle]
pub extern "system" fn DllMain(hinstance: HMODULE, reason: u32, _reserved: *mut c_void) -> BOOL {
const DLL_PROCESS_ATTACH: u32 = 1;
if reason == DLL_PROCESS_ATTACH {
set_dll_module(hinstance);
}
BOOL::from(true)
}
#[no_mangle]
pub extern "system" fn DllCanUnloadNow() -> HRESULT {
if DLL_REF_COUNT.load(Ordering::SeqCst) == 0 {
S_OK
} else {
S_FALSE
}
}
#[no_mangle]
pub unsafe extern "system" fn DllGetClassObject(
rclsid: *const GUID,
riid: *const GUID,
ppv: *mut *mut c_void,
) -> HRESULT {
if ppv.is_null() || rclsid.is_null() || riid.is_null() {
return E_INVALIDARG;
}
*ppv = std::ptr::null_mut();
if *rclsid != CLSID_READEST_THUMBNAIL {
return E_NOINTERFACE;
}
if *riid != IClassFactory::IID && *riid != IUnknown::IID {
return E_NOINTERFACE;
}
let factory: IClassFactory = ThumbnailProviderFactory::new().into();
factory.query(&*riid, ppv)
}
#[no_mangle]
pub unsafe extern "system" fn DllRegisterServer() -> HRESULT {
match register_server_impl() {
Ok(()) => S_OK,
Err(e) => e,
}
}
#[no_mangle]
pub unsafe extern "system" fn DllUnregisterServer() -> HRESULT {
let _ = unregister_server_impl();
S_OK
}
// ─────────────────────────────────────────────────────────────────────────────
// Registry helpers
// ─────────────────────────────────────────────────────────────────────────────
fn get_dll_path() -> Option<String> {
let module = get_dll_module()?;
let mut buffer = [0u16; 260];
unsafe {
let len = GetModuleFileNameW(Some(module), &mut buffer);
if len == 0 {
None
} else {
Some(String::from_utf16_lossy(&buffer[..len as usize]))
}
}
}
fn clsid_string() -> String {
format!(
"{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
CLSID_READEST_THUMBNAIL.data1,
CLSID_READEST_THUMBNAIL.data2,
CLSID_READEST_THUMBNAIL.data3,
CLSID_READEST_THUMBNAIL.data4[0],
CLSID_READEST_THUMBNAIL.data4[1],
CLSID_READEST_THUMBNAIL.data4[2],
CLSID_READEST_THUMBNAIL.data4[3],
CLSID_READEST_THUMBNAIL.data4[4],
CLSID_READEST_THUMBNAIL.data4[5],
CLSID_READEST_THUMBNAIL.data4[6],
CLSID_READEST_THUMBNAIL.data4[7]
)
}
fn to_wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
unsafe fn set_reg_value(key: HKEY, name: &str, value: &str) -> Result<(), HRESULT> {
let name_w = to_wide(name);
let value_w = to_wide(value);
let bytes: &[u8] = std::slice::from_raw_parts(value_w.as_ptr() as *const u8, value_w.len() * 2);
if RegSetValueExW(key, PCWSTR(name_w.as_ptr()), Some(0), REG_SZ, Some(bytes)).is_err() {
Err(E_FAIL)
} else {
Ok(())
}
}
unsafe fn create_reg_key(parent: HKEY, subkey: &str) -> Result<HKEY, HRESULT> {
let subkey_w = to_wide(subkey);
let mut hkey = HKEY::default();
let result = RegCreateKeyExW(
parent,
PCWSTR(subkey_w.as_ptr()),
Some(0),
None,
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
None,
&mut hkey,
None,
);
if result.is_err() {
Err(E_FAIL)
} else {
Ok(hkey)
}
}
unsafe fn register_server_impl() -> Result<(), HRESULT> {
let dll_path = get_dll_path().ok_or(E_FAIL)?;
let clsid = clsid_string();
// CLSID key
let clsid_key = create_reg_key(HKEY_CLASSES_ROOT, &format!("CLSID\\{}", clsid))?;
set_reg_value(clsid_key, "", "Readest Thumbnail Provider")?;
// CRITICAL: DisableProcessIsolation = 1
let disable_isolation_name = to_wide("DisableProcessIsolation");
let value: u32 = 1;
let _ = windows::Win32::System::Registry::RegSetValueExW(
clsid_key,
PCWSTR(disable_isolation_name.as_ptr()),
Some(0),
windows::Win32::System::Registry::REG_DWORD,
Some(std::slice::from_raw_parts(
&value as *const u32 as *const u8,
4,
)),
);
let inproc_key = create_reg_key(clsid_key, "InprocServer32")?;
set_reg_value(inproc_key, "", &dll_path)?;
set_reg_value(inproc_key, "ThreadingModel", "Apartment")?;
let _ = RegCloseKey(inproc_key);
let _ = RegCloseKey(clsid_key);
// Register ShellEx thumbnail handler for each extension
for ext in SUPPORTED_EXTENSIONS {
let ext_shellex_path =
format!("{}\\ShellEx\\{{e357fccd-a995-4576-b01f-234630154e96}}", ext);
if let Ok(ext_shellex_key) = create_reg_key(HKEY_CLASSES_ROOT, &ext_shellex_path) {
let _ = set_reg_value(ext_shellex_key, "", &clsid);
let _ = RegCloseKey(ext_shellex_key);
}
}
Ok(())
}
unsafe fn unregister_server_impl() -> Result<(), HRESULT> {
let clsid = clsid_string();
let clsid_path = to_wide(&format!("CLSID\\{}", clsid));
let _ = RegDeleteTreeW(HKEY_CLASSES_ROOT, PCWSTR(clsid_path.as_ptr()));
for ext in SUPPORTED_EXTENSIONS {
let ext_path = to_wide(&format!(
"{}\\ShellEx\\{{e357fccd-a995-4576-b01f-234630154e96}}",
ext
));
let _ = RegDeleteTreeW(HKEY_CLASSES_ROOT, PCWSTR(ext_path.as_ptr()));
}
Ok(())
}
@@ -0,0 +1,648 @@
/// Cover image extraction for various eBook formats
///
/// Supports: EPUB, MOBI/AZW3/KF8, FB2, CBZ/CBR, TXT
use anyhow::{anyhow, Result};
use base64::engine::general_purpose;
use base64::Engine as _;
use directories_next::ProjectDirs;
use image::{imageops, DynamicImage, Rgba};
use md5::Context;
use once_cell::sync::Lazy;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::path::Path;
use zip::ZipArchive;
/// Thumbnail cache directory (per-user)
static CACHE_DIR: Lazy<Option<std::path::PathBuf>> = Lazy::new(|| {
ProjectDirs::from("app", "Readest", "").map(|pd| {
let dir = pd.cache_dir().join("thumbnails");
let _ = std::fs::create_dir_all(&dir);
dir
})
});
// ─────────────────────────────────────────────────────────────────────────────
// EPUB extraction
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image bytes from an EPUB file.
pub fn extract_epub_cover_bytes<R: Read + Seek>(reader: R) -> Result<Vec<u8>> {
let mut archive = ZipArchive::new(reader)?;
// Pass 1: Look for files with "cover" in the name
let mut candidates: Vec<(usize, String, u64)> = Vec::new();
for i in 0..archive.len() {
let file = archive.by_index(i)?;
let name = file.name().to_lowercase();
let size = file.size();
drop(file);
if is_image_extension(&name) && (name.contains("cover") || name.contains("front")) {
candidates.push((i, name, size));
}
}
// Sort by priority: exact "cover" match first, then by size
if !candidates.is_empty() {
candidates.sort_by(|a, b| {
let a_exact = a.1.contains("cover.") || a.1.ends_with("cover");
let b_exact = b.1.contains("cover.") || b.1.ends_with("cover");
match (a_exact, b_exact) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => b.2.cmp(&a.2),
}
});
let idx = candidates[0].0;
let mut file = archive.by_index(idx)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
return Ok(buf);
}
// Pass 2: Parse container.xml to find OPF, then parse OPF for cover
let container_xml = read_zip_file_to_string(&mut archive, "META-INF/container.xml");
if let Ok(xml) = container_xml {
if let Some(rootfile) = extract_attribute(&xml, "rootfile", "full-path") {
let opf_content = read_zip_file_to_string(&mut archive, &rootfile);
if let Ok(opf) = opf_content {
if let Some(cover_id) = find_cover_id_in_opf(&opf) {
if let Some(href) = find_href_by_id_in_opf(&opf, &cover_id) {
let base = Path::new(&rootfile).parent().unwrap_or(Path::new(""));
let cover_path = base.join(&href).to_string_lossy().replace('\\', "/");
if let Ok(bytes) = read_zip_file_to_bytes(&mut archive, &cover_path) {
return Ok(bytes);
}
}
}
if let Some(href) = find_first_image_in_manifest(&opf) {
let base = Path::new(&rootfile).parent().unwrap_or(Path::new(""));
let cover_path = base.join(&href).to_string_lossy().replace('\\', "/");
if let Ok(bytes) = read_zip_file_to_bytes(&mut archive, &cover_path) {
return Ok(bytes);
}
}
}
}
}
// Pass 3: Just grab the largest image file
let mut largest: Option<(usize, u64)> = None;
for i in 0..archive.len() {
let file = archive.by_index(i)?;
let name = file.name().to_lowercase();
let size = file.size();
drop(file);
if is_image_extension(&name) && (largest.is_none() || size > largest.unwrap().1) {
largest = Some((i, size));
}
}
if let Some((idx, _)) = largest {
let mut file = archive.by_index(idx)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
return Ok(buf);
}
Err(anyhow!("No cover image found in EPUB"))
}
// ─────────────────────────────────────────────────────────────────────────────
// MOBI/AZW3/KF8 extraction
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image from MOBI/AZW3/KF8 files.
pub fn extract_mobi_cover_bytes<R: Read + Seek>(mut reader: R) -> Result<Vec<u8>> {
let mut header = [0u8; 78];
reader.read_exact(&mut header)?;
if &header[60..68] != b"BOOKMOBI" {
return Err(anyhow!("Not a valid MOBI file"));
}
let num_records = u16::from_be_bytes([header[76], header[77]]) as usize;
let mut record_offsets: Vec<u32> = Vec::with_capacity(num_records);
for _ in 0..num_records {
let mut rec = [0u8; 8];
reader.read_exact(&mut rec)?;
record_offsets.push(u32::from_be_bytes([rec[0], rec[1], rec[2], rec[3]]));
}
if record_offsets.is_empty() {
return Err(anyhow!("No records in MOBI file"));
}
reader.seek(SeekFrom::Start(record_offsets[0] as u64))?;
let mut mobi_header = [0u8; 256];
reader.read_exact(&mut mobi_header)?;
if &mobi_header[16..20] != b"MOBI" {
return Err(anyhow!("Invalid MOBI header"));
}
let header_length = u32::from_be_bytes([
mobi_header[20],
mobi_header[21],
mobi_header[22],
mobi_header[23],
]) as usize;
let exth_flags = u32::from_be_bytes([
mobi_header[128],
mobi_header[129],
mobi_header[130],
mobi_header[131],
]);
if exth_flags & 0x40 == 0 {
return Err(anyhow!("No EXTH header in MOBI file"));
}
let exth_offset = record_offsets[0] as u64 + 16 + header_length as u64;
reader.seek(SeekFrom::Start(exth_offset))?;
let mut exth_magic = [0u8; 4];
reader.read_exact(&mut exth_magic)?;
if &exth_magic != b"EXTH" {
return Err(anyhow!("EXTH header not found"));
}
let mut exth_len_bytes = [0u8; 4];
reader.read_exact(&mut exth_len_bytes)?;
let mut exth_count_bytes = [0u8; 4];
reader.read_exact(&mut exth_count_bytes)?;
let exth_count = u32::from_be_bytes(exth_count_bytes) as usize;
let mut cover_offset: Option<u32> = None;
let first_img_idx = u32::from_be_bytes([
mobi_header[108],
mobi_header[109],
mobi_header[110],
mobi_header[111],
]);
for _ in 0..exth_count {
let mut rec_header = [0u8; 8];
if reader.read_exact(&mut rec_header).is_err() {
break;
}
let rec_type =
u32::from_be_bytes([rec_header[0], rec_header[1], rec_header[2], rec_header[3]]);
let rec_len =
u32::from_be_bytes([rec_header[4], rec_header[5], rec_header[6], rec_header[7]])
as usize;
let data_len = rec_len.saturating_sub(8);
let mut data = vec![0u8; data_len];
if reader.read_exact(&mut data).is_err() {
break;
}
if rec_type == 201 && data_len >= 4 {
cover_offset = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]));
}
}
let cover_record_idx = if let Some(offset) = cover_offset {
first_img_idx + offset
} else {
first_img_idx
};
if cover_record_idx as usize >= record_offsets.len() {
return Err(anyhow!("Cover record index out of bounds"));
}
let start = record_offsets[cover_record_idx as usize] as u64;
let end = if (cover_record_idx as usize + 1) < record_offsets.len() {
record_offsets[cover_record_idx as usize + 1] as u64
} else {
reader.seek(SeekFrom::End(0))?;
reader.stream_position()?
};
let len = (end - start) as usize;
reader.seek(SeekFrom::Start(start))?;
let mut cover_data = vec![0u8; len];
reader.read_exact(&mut cover_data)?;
if cover_data.starts_with(&[0xFF, 0xD8, 0xFF])
|| cover_data.starts_with(&[0x89, 0x50, 0x4E, 0x47])
|| cover_data.starts_with(b"GIF")
{
return Ok(cover_data);
}
Err(anyhow!("No valid cover image found in MOBI"))
}
// ─────────────────────────────────────────────────────────────────────────────
// CBZ extraction
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image from CBZ (comic book ZIP) file.
pub fn extract_cbz_cover_bytes<R: Read + Seek>(reader: R) -> Result<Vec<u8>> {
let mut archive = ZipArchive::new(reader)?;
let mut images: Vec<(usize, String)> = Vec::new();
for i in 0..archive.len() {
let file = archive.by_index(i)?;
let name = file.name().to_string();
drop(file);
if is_image_extension(&name.to_lowercase()) {
images.push((i, name));
}
}
images.sort_by(|a, b| a.1.cmp(&b.1));
if let Some((idx, _)) = images.first() {
let mut file = archive.by_index(*idx)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
return Ok(buf);
}
Err(anyhow!("No images found in CBZ"))
}
// ─────────────────────────────────────────────────────────────────────────────
// FB2 extraction
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image from FB2 (FictionBook) file.
pub fn extract_fb2_cover_bytes<R: Read>(mut reader: R) -> Result<Vec<u8>> {
let mut content = String::new();
reader.read_to_string(&mut content)?;
let cover_id = if let Some(start) = content.find("<coverpage>") {
let end = content[start..].find("</coverpage>").unwrap_or(500);
let coverpage = &content[start..start + end];
if let Some(href_pos) = coverpage.find("href=\"#") {
let id_start = href_pos + 7;
let id_end = coverpage[id_start..].find('"').unwrap_or(50);
Some(coverpage[id_start..id_start + id_end].to_string())
} else if let Some(href_pos) = coverpage.find("l:href=\"#") {
let id_start = href_pos + 9;
let id_end = coverpage[id_start..].find('"').unwrap_or(50);
Some(coverpage[id_start..id_start + id_end].to_string())
} else {
None
}
} else {
None
};
let search_pattern = if let Some(ref id) = cover_id {
format!("<binary id=\"{}\"", id)
} else {
"<binary".to_string()
};
if let Some(pos) = content.find(&search_pattern) {
if let Some(tag_end) = content[pos..].find('>') {
let data_start = pos + tag_end + 1;
if let Some(data_end) = content[data_start..].find("</binary>") {
let b64_data = content[data_start..data_start + data_end].trim();
let b64_clean: String = b64_data.chars().filter(|c| !c.is_whitespace()).collect();
let bytes = general_purpose::STANDARD.decode(&b64_clean)?;
return Ok(bytes);
}
}
}
if cover_id.is_some() {
if let Some(pos) = content.find("<binary") {
if let Some(tag_end) = content[pos..].find('>') {
let data_start = pos + tag_end + 1;
if let Some(data_end) = content[data_start..].find("</binary>") {
let b64_data = content[data_start..data_start + data_end].trim();
let b64_clean: String =
b64_data.chars().filter(|c| !c.is_whitespace()).collect();
let bytes = general_purpose::STANDARD.decode(&b64_clean)?;
return Ok(bytes);
}
}
}
}
Err(anyhow!("No cover image found in FB2"))
}
// ─────────────────────────────────────────────────────────────────────────────
// TXT "cover" (placeholder)
// ─────────────────────────────────────────────────────────────────────────────
/// Generate a placeholder thumbnail for TXT files.
pub fn extract_txt_cover_bytes<R: Read>(mut reader: R, size: u32) -> Result<Vec<u8>> {
let mut buf = vec![0u8; 4096];
let _n = reader.read(&mut buf)?;
let mut img = image::RgbaImage::from_pixel(size, size, Rgba([245, 245, 245, 255]));
for x in 0..size {
img.put_pixel(x, 0, Rgba([200, 200, 200, 255]));
img.put_pixel(x, size - 1, Rgba([200, 200, 200, 255]));
}
for y in 0..size {
img.put_pixel(0, y, Rgba([200, 200, 200, 255]));
img.put_pixel(size - 1, y, Rgba([200, 200, 200, 255]));
}
let mut out = Vec::new();
DynamicImage::ImageRgba8(img).write_to(&mut Cursor::new(&mut out), image::ImageFormat::Png)?;
Ok(out)
}
// ─────────────────────────────────────────────────────────────────────────────
// Unified extraction by extension
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image bytes based on file extension.
pub fn extract_cover_bytes_by_ext(path: &Path, ext: &str) -> Result<Vec<u8>> {
let file = std::fs::File::open(path)?;
match ext.to_lowercase().as_str() {
"epub" => extract_epub_cover_bytes(file),
"mobi" | "azw" | "azw3" | "kf8" | "prc" => extract_mobi_cover_bytes(file),
"cbz" | "cbr" => extract_cbz_cover_bytes(file),
"fb2" => extract_fb2_cover_bytes(file),
"txt" => extract_txt_cover_bytes(file, 256),
_ => Err(anyhow!("Unsupported format: {}", ext)),
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Thumbnail creation with overlay
// ─────────────────────────────────────────────────────────────────────────────
/// Create a thumbnail from cover image bytes with Readest icon overlay.
pub fn create_thumbnail_with_overlay(cover_bytes: &[u8], requested_size: u32) -> Result<Vec<u8>> {
let img = image::load_from_memory(cover_bytes)?;
let thumbnail = img.thumbnail(requested_size, requested_size);
let overlay_img = load_overlay_icon();
let mut base = thumbnail.to_rgba8();
let (base_w, base_h) = (base.width(), base.height());
if let Some(ov) = overlay_img {
let overlay_size = (requested_size / 5).clamp(24, 48);
let ov_resized = ov.resize(overlay_size, overlay_size, imageops::FilterType::Lanczos3);
let ovb = ov_resized.to_rgba8();
let (ov_w, ov_h) = (ovb.width(), ovb.height());
let x = base_w.saturating_sub(ov_w + 4);
let y = base_h.saturating_sub(ov_h + 4);
for oy in 0..ov_h {
for ox in 0..ov_w {
let dst_x = x + ox;
let dst_y = y + oy;
if dst_x < base_w && dst_y < base_h {
let src_pixel = ovb.get_pixel(ox, oy);
let alpha = src_pixel.0[3] as f32 / 255.0;
if alpha > 0.0 {
let dst_pixel = base.get_pixel(dst_x, dst_y);
let mut result = dst_pixel.0;
for c in 0..3 {
let fg = src_pixel.0[c] as f32;
let bg = result[c] as f32;
result[c] = (fg * alpha + bg * (1.0 - alpha)) as u8;
}
result[3] = 255;
base.put_pixel(dst_x, dst_y, Rgba(result));
}
}
}
}
}
let mut out = Vec::new();
DynamicImage::ImageRgba8(base).write_to(&mut Cursor::new(&mut out), image::ImageFormat::Png)?;
Ok(out)
}
/// Load the Readest overlay icon.
fn load_overlay_icon() -> Option<DynamicImage> {
// Try embedded icon
let icon_bytes = include_bytes!("../../../public/icon.png");
if let Ok(img) = image::load_from_memory(icon_bytes) {
return Some(img);
}
// Fallback: try loading from filesystem
if let Ok(exe) = std::env::current_exe() {
let candidates = [
exe.parent().map(|p| p.join("icon.png")),
exe.parent().map(|p| p.join("resources").join("icon.png")),
exe.parent()
.and_then(|p| p.parent())
.map(|p| p.join("resources").join("icon.png")),
];
for candidate in candidates.into_iter().flatten() {
if candidate.exists() {
if let Ok(bytes) = std::fs::read(&candidate) {
if let Ok(img) = image::load_from_memory(&bytes) {
return Some(img);
}
}
}
}
}
None
}
// ─────────────────────────────────────────────────────────────────────────────
// Caching
// ─────────────────────────────────────────────────────────────────────────────
/// Generate a thumbnail with disk caching.
pub fn cached_thumbnail_for_path(path: &Path, ext: &str, size: u32) -> Result<Vec<u8>> {
// Compute cache key by hashing file parts for stability without loading entire file
let mut hasher = Context::new();
hasher.consume(ext.as_bytes());
hasher.consume(&size.to_le_bytes());
let file = std::fs::File::open(path)?;
let metadata = file.metadata()?;
let file_len = metadata.len();
// Read partial chunks like the TypeScript partialMD5 implementation
const STEP: u64 = 1024;
const SIZE: u64 = 1024;
let mut file = file;
for i in -1i32..=10 {
let pos = if i == -1 {
256u64
} else {
STEP << (2 * i as u32)
};
let start = pos.min(file_len);
let end = (start + SIZE).min(file_len);
if start >= file_len {
break;
}
file.seek(SeekFrom::Start(start))?;
let mut buf = vec![0u8; (end - start) as usize];
file.read_exact(&mut buf)?;
hasher.consume(&buf);
}
let digest = hasher.finalize();
let key = format!("{:x}.png", digest);
if let Some(ref dir) = *CACHE_DIR {
let cache_path = dir.join(&key);
if cache_path.exists() {
if let Ok(cached) = std::fs::read(&cache_path) {
return Ok(cached);
}
}
}
let cover = extract_cover_bytes_by_ext(path, ext)?;
let thumbnail = create_thumbnail_with_overlay(&cover, size)?;
if let Some(ref dir) = *CACHE_DIR {
let cache_path = dir.join(&key);
let _ = std::fs::write(&cache_path, &thumbnail);
}
Ok(thumbnail)
}
// ─────────────────────────────────────────────────────────────────────────────
// Helper functions
// ─────────────────────────────────────────────────────────────────────────────
fn is_image_extension(name: &str) -> bool {
name.ends_with(".jpg")
|| name.ends_with(".jpeg")
|| name.ends_with(".png")
|| name.ends_with(".gif")
|| name.ends_with(".webp")
|| name.ends_with(".bmp")
}
fn read_zip_file_to_string<R: Read + Seek>(
archive: &mut ZipArchive<R>,
name: &str,
) -> Result<String> {
let mut file = archive.by_name(name)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn read_zip_file_to_bytes<R: Read + Seek>(
archive: &mut ZipArchive<R>,
name: &str,
) -> Result<Vec<u8>> {
let mut file = archive.by_name(name)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
}
fn extract_attribute(xml: &str, tag: &str, attr: &str) -> Option<String> {
let pattern = format!("<{}", tag);
if let Some(tag_pos) = xml.find(&pattern) {
let tag_end = xml[tag_pos..].find('>').unwrap_or(500) + tag_pos;
let tag_content = &xml[tag_pos..tag_end];
let attr_pattern = format!("{}=\"", attr);
if let Some(attr_pos) = tag_content.find(&attr_pattern) {
let value_start = attr_pos + attr_pattern.len();
if let Some(value_end) = tag_content[value_start..].find('"') {
return Some(tag_content[value_start..value_start + value_end].to_string());
}
}
}
None
}
fn find_cover_id_in_opf(opf: &str) -> Option<String> {
if let Some(pos) = opf.find("name=\"cover\"") {
let window_start = pos.saturating_sub(50);
let window_end = (pos + 100).min(opf.len());
let window = &opf[window_start..window_end];
if let Some(content_pos) = window.find("content=\"") {
let start = content_pos + 9;
if let Some(end) = window[start..].find('"') {
return Some(window[start..start + end].to_string());
}
}
}
if let Some(pos) = opf.find("properties=\"cover-image\"") {
let window_start = pos.saturating_sub(200);
let window_end = pos;
let window = &opf[window_start..window_end];
if let Some(id_pos) = window.rfind("id=\"") {
let start = id_pos + 4;
if let Some(end) = window[start..].find('"') {
return Some(window[start..start + end].to_string());
}
}
}
None
}
fn find_href_by_id_in_opf(opf: &str, id: &str) -> Option<String> {
let pattern = format!("id=\"{}\"", id);
if let Some(pos) = opf.find(&pattern) {
let window_start = pos.saturating_sub(10);
let window_end = (pos + 200).min(opf.len());
let window = &opf[window_start..window_end];
if let Some(href_pos) = window.find("href=\"") {
let start = href_pos + 6;
if let Some(end) = window[start..].find('"') {
return Some(window[start..start + end].to_string());
}
}
}
None
}
fn find_first_image_in_manifest(opf: &str) -> Option<String> {
let manifest_start = opf.find("<manifest")?;
let manifest_end = opf[manifest_start..]
.find("</manifest>")
.map(|e| manifest_start + e)?;
let manifest = &opf[manifest_start..manifest_end];
for media_type in ["image/jpeg", "image/png", "image/gif", "image/webp"] {
let pattern = format!("media-type=\"{}\"", media_type);
if let Some(pos) = manifest.find(&pattern) {
let window_start = pos.saturating_sub(200);
let window = &manifest[window_start..pos];
if let Some(href_pos) = window.rfind("href=\"") {
let start = href_pos + 6;
if let Some(end) = window[start..].find('"') {
return Some(window[start..start + end].to_string());
}
}
}
}
None
}
@@ -0,0 +1,13 @@
//! Windows Thumbnail Provider for Readest
//!
//! This module provides Windows Explorer thumbnail support for eBook files.
//! Thumbnails are only shown when Readest is set as the default application.
//!
//! Supported formats: EPUB, MOBI, AZW, AZW3, KF8, FB2, CBZ, CBR
#![allow(non_snake_case)]
mod com_provider;
mod extraction;
pub use extraction::*;
@@ -12,6 +12,7 @@ module.exports = {
'de',
'ja',
'es',
'fa',
'fr',
'it',
'el',
@@ -26,6 +27,7 @@ module.exports = {
'hi',
'id',
'vi',
'ms',
'ar',
'th',
'bo',
+29
View File
@@ -27,6 +27,7 @@ const nextConfig = {
assetPrefix: '',
reactStrictMode: true,
serverExternalPackages: ['isows'],
turbopack: {},
transpilePackages: !isDev
? [
'i18next-browser-languagedetector',
@@ -50,6 +51,15 @@ const nextConfig = {
},
],
},
{
source: '/_next/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
];
},
};
@@ -57,6 +67,8 @@ const nextConfig = {
const withPWA = withPWAInit({
dest: 'public',
disable: isDev || appPlatform !== 'web',
cacheStartUrl: false,
dynamicStartUrl: false,
cacheOnFrontEndNav: true,
aggressiveFrontEndNavCaching: true,
reloadOnOnline: true,
@@ -66,6 +78,23 @@ const withPWA = withPWAInit({
},
workboxOptions: {
disableDevLogs: true,
manifestTransforms: [
(manifestEntries) => {
const manifest = manifestEntries.filter((entry) => {
const url = entry.url;
return (
!url.includes('dynamic-css-manifest.json') &&
!url.includes('middleware-manifest.json') &&
!url.includes('react-loadable-manifest.json') &&
!url.includes('build-manifest.json') &&
!url.includes('_buildManifest.js') &&
!url.includes('_ssgManifest.js') &&
!url.includes('_headers')
);
});
return { manifest };
},
],
},
});
+47 -33
View File
@@ -1,26 +1,30 @@
{
"name": "@readest/readest-app",
"version": "0.9.88",
"version": "0.9.95",
"private": true,
"scripts": {
"dev": "dotenv -e .env.tauri -- next dev --turbopack",
"dev": "dotenv -e .env.tauri -- next dev",
"build": "dotenv -e .env.tauri -- next build",
"start": "dotenv -e .env.tauri -- next start",
"dev-web": "dotenv -e .env.web -- next dev --turbopack",
"dev-web": "dotenv -e .env.web -- next dev",
"build-web": "dotenv -e .env.web -- next build",
"start-web": "dotenv -e .env.web -- next start",
"build-tauri": "dotenv -e .env.tauri -- next build",
"i18n:extract": "i18next-scanner",
"lint": "next lint",
"lint": "eslint .",
"test": "dotenv -e .env -e .env.test.local vitest",
"tauri": "tauri",
"prepare-public-vendor": "mkdirp ./public/vendor/pdfjs",
"prepare-public-vendor": "mkdirp ./public/vendor/pdfjs ./public/vendor/simplecc",
"copy-pdfjs-js": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/legacy/build/{pdf.worker.min.mjs,pdf.mjs,pdf.d.mts}\" ./public/vendor/pdfjs",
"copy-pdfjs-fonts": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/{cmaps,standard_fonts}/*\" ./public/vendor/pdfjs",
"copy-flatten-pdfjs-annotation-layer-css": "npx postcss \"../../packages/foliate-js/vendor/pdfjs/annotation_layer_builder.css\" --no-map -u postcss-nested > ./public/vendor/pdfjs/annotation_layer_builder.css",
"copy-flatten-pdfjs-text-layer-css": "npx postcss \"../../packages/foliate-js/vendor/pdfjs/text_layer_builder.css\" --no-map -u postcss-nested > ./public/vendor/pdfjs/text_layer_builder.css",
"copy-flatten-pdfjs-css": "pnpm copy-flatten-pdfjs-annotation-layer-css && pnpm copy-flatten-pdfjs-text-layer-css",
"copy-pdfjs": "pnpm copy-pdfjs-js && pnpm copy-pdfjs-fonts && pnpm copy-flatten-pdfjs-css",
"copy-simplecc": "cpx \"../../packages/simplecc-wasm/dist/web/*\" ./public/vendor/simplecc",
"setup-pdfjs": "pnpm prepare-public-vendor && pnpm copy-pdfjs",
"setup-simplecc": "pnpm prepare-public-vendor && pnpm copy-simplecc",
"setup-vendors": "pnpm setup-pdfjs && pnpm setup-simplecc",
"build-win-x64": "dotenv -e .env.tauri.local -- tauri build --target i686-pc-windows-msvc --bundles nsis",
"build-win-arm64": "dotenv -e .env.tauri.local -- tauri build --target aarch64-pc-windows-msvc --bundles nsis",
"build-linux-x64": "dotenv -e .env.tauri.local -- tauri build --target x86_64-unknown-linux-gnu --bundles appimage",
@@ -33,13 +37,17 @@
"release-ios-appstore": "dotenv -e .env.ios-appstore.local -- bash scripts/release-ios-appstore.sh",
"release-google-play": "dotenv -e .env.google-play.local -- bash scripts/release-google-play.sh",
"config-wrangler": "sed -i \"s/\\${TRANSLATIONS_KV_ID}/$TRANSLATIONS_KV_ID/g\" wrangler.toml",
"preview": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare preview --ip 0.0.0.0",
"deploy": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"upload": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare upload",
"preview": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare preview --ip 0.0.0.0",
"deploy": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare deploy",
"upload": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare upload",
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
"patch-build-webpack": "if [ \"$(uname)\" = \"Darwin\" ]; then sed -i '' 's/next build\"/next build --webpack\"/' package.json; else sed -i 's/next build\"/next build --webpack\"/' package.json; fi",
"restore-build-original": "if [ \"$(uname)\" = \"Darwin\" ]; then sed -i '' 's/next build --webpack\"/next build\"/' package.json; else sed -i 's/next build --webpack\"/next build\"/' package.json; fi",
"update-metadata": "bash ./scripts/sync-release-notes.sh release-notes.json ../../data/metainfo/appdata.xml",
"check:optional-chaining": "count=$(grep -rno '\\?\\.[a-zA-Z_$]' .next/static/chunks/* out/_next/static/chunks/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Optional chaining found in output!'; exit 1; else echo '✅ No optional chaining found.'; fi",
"check:translations": "count=$(grep -rno '__STRING_NOT_TRANSLATED__' public/locales/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Untranslated strings found!'; exit 1; else echo '✅ All strings translated.'; fi",
"check:all": "pnpm check:optional-chaining && pnpm check:translations",
"check:lookbehind-regex": "count=$(grep -rnoE '\\(\\?<[!=]' .next/static/chunks/* out/_next/static/chunks/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Lookbehind regex found in output!'; exit 1; else echo '✅ No lookbehind regex found.'; fi",
"check:all": "pnpm check:translations && pnpm check:lookbehind-regex",
"build-check": "pnpm build && pnpm build-web && pnpm check:all"
},
"dependencies": {
@@ -47,34 +55,37 @@
"@aws-sdk/s3-request-presigner": "^3.735.0",
"@ducanh2912/next-pwa": "^10.2.9",
"@fabianlars/tauri-plugin-oauth": "2",
"@opennextjs/cloudflare": "^1.6.5",
"@opennextjs/cloudflare": "^1.13.1",
"@stripe/react-stripe-js": "^3.7.0",
"@stripe/stripe-js": "^7.4.0",
"@supabase/auth-ui-react": "^0.4.7",
"@supabase/auth-ui-shared": "^0.1.8",
"@supabase/supabase-js": "^2.55.0",
"@tauri-apps/api": "2.8.0",
"@tauri-apps/plugin-cli": "^2.4.0",
"@tauri-apps/plugin-deep-link": "^2.4.3",
"@tauri-apps/plugin-dialog": "^2.4.0",
"@tauri-apps/plugin-fs": "^2.4.2",
"@tauri-apps/plugin-haptics": "^2.3.0",
"@tauri-apps/plugin-http": "^2.5.2",
"@tauri-apps/plugin-log": "^2.7.0",
"@tauri-apps/plugin-opener": "^2.5.0",
"@tauri-apps/plugin-os": "^2.3.1",
"@tauri-apps/plugin-process": "^2.3.0",
"@tauri-apps/plugin-shell": "~2.3.1",
"@supabase/supabase-js": "^2.76.1",
"@tauri-apps/api": "2.9.0",
"@tauri-apps/plugin-cli": "^2.4.1",
"@tauri-apps/plugin-deep-link": "^2.4.5",
"@tauri-apps/plugin-dialog": "^2.4.2",
"@tauri-apps/plugin-fs": "^2.4.4",
"@tauri-apps/plugin-haptics": "^2.3.2",
"@tauri-apps/plugin-http": "^2.5.4",
"@tauri-apps/plugin-log": "^2.7.1",
"@tauri-apps/plugin-opener": "^2.5.2",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "~2.3.3",
"@tauri-apps/plugin-updater": "^2.9.0",
"@zip.js/zip.js": "^2.7.53",
"abortcontroller-polyfill": "^1.7.8",
"app-store-server-api": "^0.17.1",
"aws4fetch": "^1.0.20",
"clsx": "^2.1.1",
"cors": "^2.8.5",
"cssbeautify": "^0.3.1",
"dayjs": "^1.11.13",
"dompurify": "^3.3.0",
"foliate-js": "workspace:*",
"franc-min": "^6.2.0",
"google-auth-library": "^10.5.0",
"googleapis": "^164.1.0",
"highlight.js": "^11.11.1",
"i18next": "^24.2.0",
"i18next-browser-languagedetector": "^8.0.2",
@@ -84,19 +95,20 @@
"js-md5": "^0.8.3",
"jwt-decode": "^4.0.0",
"marked": "^15.0.12",
"next": "15.5.0",
"next": "16.0.7",
"overlayscrollbars": "^2.11.4",
"overlayscrollbars-react": "^0.5.6",
"posthog-js": "^1.246.0",
"react": "19.0.0",
"react": "19.2.0",
"react-color": "^2.19.3",
"react-dom": "19.0.0",
"react-dom": "19.2.0",
"react-i18next": "^15.2.0",
"react-icons": "^5.4.0",
"react-responsive": "^10.0.0",
"react-window": "^1.8.11",
"semver": "^7.7.1",
"stripe": "^18.2.1",
"styled-jsx": "^5.1.7",
"tinycolor2": "^1.6.0",
"uuid": "^11.1.0",
"zod": "^4.0.8",
@@ -105,7 +117,7 @@
"devDependencies": {
"@next/bundle-analyzer": "^15.4.2",
"@tailwindcss/typography": "^0.5.16",
"@tauri-apps/cli": "2.8.4",
"@tauri-apps/cli": "2.9.4",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.3.0",
"@types/cors": "^2.8.17",
@@ -118,14 +130,16 @@
"@types/semver": "^7.7.0",
"@types/tinycolor2": "^1.4.6",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.7.0",
"@typescript-eslint/eslint-plugin": "^8.48.0",
"@typescript-eslint/parser": "^8.48.0",
"@vitejs/plugin-react": "^5.1.1",
"autoprefixer": "^10.4.20",
"caniuse-lite": "^1.0.30001746",
"cpx2": "^8.0.0",
"daisyui": "^4.12.24",
"dotenv-cli": "^7.4.4",
"eslint": "^9.16.0",
"eslint-config-next": "15.0.3",
"eslint-config-next": "16.0.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"i18next-scanner": "^4.6.0",
"jsdom": "^26.1.0",
@@ -135,10 +149,10 @@
"postcss-cli": "^11.0.0",
"postcss-nested": "^7.0.2",
"raw-loader": "^4.0.2",
"tailwindcss": "^3.4.17",
"tailwindcss": "^3.4.18",
"typescript": "^5.7.2",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4",
"wrangler": "^4.31.0"
"vitest": "^4.0.15",
"wrangler": "^4.50.0"
}
}
@@ -0,0 +1 @@
ed533042-5626-4704-b5f2-fa3bbd1136ed
-2
View File
@@ -1,2 +0,0 @@
/_next/static/*
Cache-Control: public,max-age=31536000,immutable
@@ -8,7 +8,6 @@
"Auto Mode": "الوضع التلقائي",
"Behavior": "السلوك",
"Book": "الكتاب",
"Book Cover": "غلاف الكتاب",
"Bookmark": "علامة مرجعية",
"Cancel": "إلغاء",
"Chapter": "الفصل",
@@ -193,7 +192,7 @@
"No supported files found. Supported formats: {{formats}}": "لم يتم العثور على ملفات مدعومة. الصيغ المدعومة: {{formats}}",
"Drop to Import Books": "قم بالسحب والإسقاط لاستيراد الكتب",
"Custom": "مخصص",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "العالم مسرح،\nوالناس فيه ممثلون؛\nلهم مخارج ومداخل،\nوالإنسان في حياته يلعب دورًا كثيرًا،\nوأفعاله تمر بسبع مراحل.\n\n— ويليام شكسبير",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "العالم مسرح،\nوالناس فيه ممثلون؛\nلهم مخارج ومداخل،\nوالإنسان في حياته يلعب دورًا كثيرًا،\nوأفعاله تمر بسبع مراحل.\n\n— ويليام شكسبير",
"Custom Theme": "سمة مخصصة",
"Theme Name": "اسم السمة",
"Text Color": "لون النص",
@@ -220,9 +219,9 @@
"Auto Import on File Open": "استيراد تلقائي عند فتح الملف",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "لم يتم التعرف على أي فصول.",
"Failed to parse the EPUB file.": "فشل في تحليل ملف EPUB.",
"This book format is not supported.": "تنسيق الكتاب هذا غير مدعوم.",
"No chapters detected": "لم يتم التعرف على أي فصول",
"Failed to parse the EPUB file": "فشل في تحليل ملف EPUB",
"This book format is not supported": "تنسيق الكتاب هذا غير مدعوم",
"Unable to fetch the translation. Please log in first and try again.": "تعذر جلب الترجمة. يرجى تسجيل الدخول أولاً ثم المحاولة مرة أخرى.",
"Group": "تجميع",
"Always on Top": "دائمًا في المقدمة",
@@ -259,8 +258,6 @@
"(from 'As You Like It', Act II)": "(من 'كما تحب'، الفصل الثاني)",
"Link Color": "لون الرابط",
"Volume Keys for Page Flip": "مفاتيح الصوت لتقليب الصفحات",
"Clicks for Page Flip": "نقرات لتقليب الصفحات",
"Swap Clicks Area": "تبديل منطقة النقرات",
"Screen": "الشاشة",
"Orientation": "الاتجاه",
"Portrait": "عمودي",
@@ -300,7 +297,6 @@
"Disable Translation": "تعطيل الترجمة",
"Scroll": "تمرير",
"Overlap Pixels": "تداخل البكسلات",
"Click": "نقر",
"System Language": "لغة النظام",
"Security": "الأمان",
"Allow JavaScript": "السماح بـ JavaScript",
@@ -381,7 +377,6 @@
"Email:": "البريد الإلكتروني:",
"Plan:": "الخطة:",
"Amount:": "المبلغ:",
"Subscription ID:": "رقم الاشتراك:",
"Go to Library": "الانتقال إلى المكتبة",
"Need help? Contact our support team at support@readest.com": "تحتاج مساعدة؟ تواصل مع فريق الدعم على support@readest.com",
"Free Plan": "الخطة المجانية",
@@ -625,5 +620,188 @@
"Contain": "احتواء",
"{{number}} pages left in chapter": "تبقّت {{number}} صفحات في هذا الفصل",
"Device": "الجهاز",
"E-Ink Mode": "وضع الحبر الإلكتروني"
"E-Ink Mode": "وضع الحبر الإلكتروني",
"Highlight Colors": "ألوان التمييز",
"Auto Screen Brightness": "سطوع الشاشة التلقائي",
"Pagination": "التقسيم إلى صفحات",
"Disable Double Tap": "تعطيل النقر المزدوج",
"Tap to Paginate": "اضغط للتقسيم إلى صفحات",
"Click to Paginate": "انقر للتقسيم إلى صفحات",
"Tap Both Sides": "اضغط على كلا الجانبين",
"Click Both Sides": "انقر على كلا الجانبين",
"Swap Tap Sides": "تبديل جانبي اللمس",
"Swap Click Sides": "تبديل جانبي النقر",
"Source and Translated": "المصدر والمترجم",
"Translated Only": "المترجم فقط",
"Source Only": "المصدر فقط",
"TTS Text": "نص تحويل النص إلى كلام",
"The book file is corrupted": "ملف الكتاب تالف",
"The book file is empty": "ملف الكتاب فارغ",
"Failed to open the book file": "فشل في فتح ملف الكتاب",
"On-Demand Purchase": "الشراء عند الطلب",
"Full Customization": "تخصيص كامل",
"Lifetime Plan": "خطة مدى الحياة",
"One-Time Payment": "دفعة لمرة واحدة",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "قم بإجراء دفعة واحدة للاستمتاع بالوصول مدى الحياة إلى ميزات معينة على جميع الأجهزة. اشترِ ميزات أو خدمات معينة فقط عند الحاجة إليها.",
"Expand Cloud Sync Storage": "توسيع مساحة تخزين السحابة",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "قم بتوسيع مساحة التخزين السحابية الخاصة بك إلى الأبد من خلال عملية شراء لمرة واحدة. كل عملية شراء إضافية تضيف المزيد من المساحة.",
"Unlock All Customization Options": "فتح جميع خيارات التخصيص",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "فتح سمات إضافية، خطوط، خيارات تخطيط، وخدمات قراءة بصوت عالٍ، ومترجمين، وخدمات تخزين سحابية.",
"Purchase Successful!": "تمت عملية الشراء بنجاح!",
"lifetime": "مدى الحياة",
"Thank you for your purchase! Your payment has been processed successfully.": "شكرًا لشرائك! تم معالجة الدفع بنجاح.",
"Order ID:": "معرف الطلب:",
"TTS Highlighting": "تمييز تحويل النص إلى كلام",
"Style": "النمط",
"Underline": "تسطير",
"Strikethrough": "يتوسطه خط",
"Squiggly": "متعرج",
"Outline": "مخطط",
"Save Current Color": "حفظ اللون الحالي",
"Quick Colors": "ألوان سريعة",
"Highlighter": "محدد النص",
"Save Book Cover": "حفظ غلاف الكتاب",
"Auto-save last book cover": "حفظ غلاف الكتاب الأخير تلقائيًا",
"Back": "عودة",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "تم إرسال بريد التأكيد! يرجى التحقق من عناوين بريدك الإلكتروني القديمة والجديدة لتأكيد التغيير.",
"Failed to update email": "فشل في تحديث البريد الإلكتروني",
"New Email": "البريد الإلكتروني الجديد",
"Your new email": "بريدك الإلكتروني الجديد",
"Updating email ...": "جارٍ تحديث البريد الإلكتروني ...",
"Update email": "تحديث البريد الإلكتروني",
"Current email": "البريد الإلكتروني الحالي",
"Update Email": "تحديث البريد الإلكتروني",
"All": "الكل",
"Unable to open book": "غير قادر على فتح الكتاب",
"Punctuation": "علامات الترقيم",
"Replace Quotation Marks": "استبدال علامات الاقتباس",
"Enabled only in vertical layout.": "مفعل فقط في التخطيط الرأسي.",
"No Conversion": "لا تحويل",
"Simplified to Traditional": "من المبسّط إلى التقليدي",
"Traditional to Simplified": "من التقليدي إلى المبسّط",
"Simplified to Traditional (Taiwan)": "مبسّط → تقليدي (تايوان)",
"Simplified to Traditional (Hong Kong)": "مبسّط → تقليدي (هونغ كونغ)",
"Simplified to Traditional (Taiwan), with phrases": "مبسّط → تقليدي (تايوان • عبارات)",
"Traditional (Taiwan) to Simplified": "تقليدي (تايوان) → مبسّط",
"Traditional (Hong Kong) to Simplified": "تقليدي (هونغ كونغ) → مبسّط",
"Traditional (Taiwan) to Simplified, with phrases": "تقليدي (تايوان • عبارات) → مبسّط",
"Convert Simplified and Traditional Chinese": "تحويل المبسّط/التقليدي",
"Convert Mode": "وضع التحويل",
"Failed to auto-save book cover for lock screen: {{error}}": "فشل في الحفظ التلقائي لغلاف الكتاب لشاشة القفل: {{error}}",
"Download from Cloud": "تحميل من السحابة",
"Upload to Cloud": "رفع إلى السحابة",
"Clear Custom Fonts": "مسح الخطوط المخصصة",
"Columns": "الأعمدة",
"OPDS Catalogs": "كتالوجات OPDS",
"Adding LAN addresses is not supported in the web app version.": "لا يتم دعم إضافة عناوين LAN في إصدار تطبيق الويب.",
"Invalid OPDS catalog. Please check the URL.": "كتالوج OPDS غير صالح. يرجى التحقق من عنوان URL.",
"Browse and download books from online catalogs": "تصفح وتنزيل الكتب من الكتالوجات عبر الإنترنت",
"My Catalogs": "كتالوجاتي",
"Add Catalog": "إضافة كتالوج",
"No catalogs yet": "لا توجد كتالوجات بعد",
"Add your first OPDS catalog to start browsing books": "أضف كتالوج OPDS الأول لتبدأ في تصفح الكتب",
"Add Your First Catalog": "أضف كتالوجك الأول",
"Browse": "تصفح",
"Popular Catalogs": "الكتالوجات الشائعة",
"Add": "إضافة",
"Add OPDS Catalog": "إضافة كتالوج OPDS",
"Catalog Name": "اسم الكتالوج",
"My Calibre Library": "مكتبة Calibre الخاصة بي",
"OPDS URL": "رابط OPDS",
"Username (optional)": "اسم المستخدم (اختياري)",
"Password (optional)": "كلمة المرور (اختياري)",
"Description (optional)": "الوصف (اختياري)",
"A brief description of this catalog": "وصف موجز لهذا الكتالوج",
"Validating...": "جارٍ التحقق...",
"View All": "عرض الكل",
"Forward": "إلى الأمام",
"OPDS Catalog": "كتالوج OPDS",
"Home": "الصفحة الرئيسية",
"Library": "المكتبة",
"{{count}} items_zero": "{{count}} عناصر",
"{{count}} items_one": "{{count}} عنصر",
"{{count}} items_two": "{{count}} عنصران",
"{{count}} items_few": "{{count}} عناصر",
"{{count}} items_many": "{{count}} عنصرًا",
"{{count}} items_other": "{{count}} من العناصر",
"Download completed": "اكتمل التنزيل",
"Download failed": "فشل التنزيل",
"Open Access": "الوصول المفتوح",
"Borrow": "استعارة",
"Buy": "شراء",
"Subscribe": "الاشتراك",
"Sample": "عينة",
"Download": "تنزيل",
"Open & Read": "فتح وقراءة",
"Tags": "العلامات",
"Tag": "علامة",
"First": "الأول",
"Previous": "السابق",
"Next": "التالي",
"Last": "الأخير",
"Cannot Load Page": "تعذر تحميل الصفحة",
"An error occurred": "حدث خطأ ما",
"Online Library": "المكتبة عبر الإنترنت",
"URL must start with http:// or https://": "يجب أن يبدأ عنوان URL بـ http:// أو https://",
"Title, Author, Tag, etc...": "العنوان، المؤلف، العلامة، إلخ...",
"Query": "استعلام",
"Subject": "موضوع",
"Enter {{terms}}": "أدخل {{terms}}",
"No search results found": "لم يتم العثور على نتائج بحث",
"Failed to load OPDS feed: {{status}} {{statusText}}": "فشل في تحميل تغذية OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "البحث في {{title}}",
"Manage Storage": "إدارة التخزين",
"Failed to load files": "فشل في تحميل الملفات",
"Deleted {{count}} file(s)_zero": "لم يتم حذف أي ملفات",
"Deleted {{count}} file(s)_one": "تم حذف ملف واحد",
"Deleted {{count}} file(s)_two": "تم حذف ملفين",
"Deleted {{count}} file(s)_few": "تم حذف {{count}} ملفات",
"Deleted {{count}} file(s)_many": "تم حذف {{count}} ملفًا",
"Deleted {{count}} file(s)_other": "تم حذف {{count}} ملف",
"Failed to delete {{count}} file(s)_zero": "فشل حذف أي ملفات",
"Failed to delete {{count}} file(s)_one": "فشل حذف ملف واحد",
"Failed to delete {{count}} file(s)_two": "فشل حذف ملفين",
"Failed to delete {{count}} file(s)_few": "فشل حذف {{count}} ملفات",
"Failed to delete {{count}} file(s)_many": "فشل حذف {{count}} ملفًا",
"Failed to delete {{count}} file(s)_other": "فشل حذف {{count}} ملف",
"Failed to delete files": "فشل حذف الملفات",
"Total Files": "إجمالي الملفات",
"Total Size": "إجمالي الحجم",
"Quota": "الحصة",
"Used": "المستخدم",
"Files": "الملفات",
"Search files...": "ابحث في الملفات...",
"Newest First": "الأحدث أولاً",
"Oldest First": "الأقدم أولاً",
"Largest First": "الأكبر أولاً",
"Smallest First": "الأصغر أولاً",
"Name A-Z": "الاسم من أ إلى ي",
"Name Z-A": "الاسم من ي إلى أ",
"{{count}} selected_zero": "لا يوجد عناصر محددة",
"{{count}} selected_one": "عنصر واحد محدد",
"{{count}} selected_two": "عنصران محددان",
"{{count}} selected_few": "{{count}} عناصر محددة",
"{{count}} selected_many": "{{count}} عنصرًا محددًا",
"{{count}} selected_other": "{{count}} عنصر محدد",
"Delete Selected": "حذف المحدد",
"Created": "تاريخ الإنشاء",
"No files found": "لا توجد ملفات",
"No files uploaded yet": "لم يتم رفع أي ملفات بعد",
"files": "الملفات",
"Page {{current}} of {{total}}": "الصفحة {{current}} من {{total}}",
"Are you sure to delete {{count}} selected file(s)?_zero": "هل أنت متأكد من حذف العناصر المحددة؟ (لا توجد عناصر)",
"Are you sure to delete {{count}} selected file(s)?_one": "هل أنت متأكد من حذف ملف واحد محدد؟",
"Are you sure to delete {{count}} selected file(s)?_two": "هل أنت متأكد من حذف ملفين محددين؟",
"Are you sure to delete {{count}} selected file(s)?_few": "هل أنت متأكد من حذف {{count}} ملفات محددة؟",
"Are you sure to delete {{count}} selected file(s)?_many": "هل أنت متأكد من حذف {{count}} ملفًا محددًا؟",
"Are you sure to delete {{count}} selected file(s)?_other": "هل أنت متأكد من حذف {{count}} ملف محدد؟",
"Cloud Storage Usage": "استخدام التخزين السحابي",
"Rename Group": "إعادة تسمية المجموعة",
"From Directory": "من الدليل",
"Successfully imported {{count}} book(s)_zero": "لم يتم استيراد أي كتب",
"Successfully imported {{count}} book(s)_one": "تم استيراد كتاب واحد",
"Successfully imported {{count}} book(s)_two": "تم استيراد كتابين",
"Successfully imported {{count}} book(s)_few": "تم استيراد {{count}} كتب",
"Successfully imported {{count}} book(s)_many": "تم استيراد {{count}} كتابًا",
"Successfully imported {{count}} book(s)_other": "تم استيراد {{count}} كتاب"
}
@@ -105,9 +105,9 @@
"Book Covers": "বইয়ের মলাট",
"Sort by...": "সাজানোর ভিত্তি...",
"No supported files found. Supported formats: {{formats}}": "কোন সমর্থিত ফাইল পাওয়া যায়নি। সমর্থিত ফরম্যাট: {{formats}}",
"No chapters detected.": "কোন অধ্যায় শনাক্ত হয়নি।",
"Failed to parse the EPUB file.": "EPUB ফাইল পার্স করতে ব্যর্থ।",
"This book format is not supported.": "এই বই ফরম্যাট সমর্থিত নয়।",
"No chapters detected": "কোন অধ্যায় শনাক্ত হয়নি।",
"Failed to parse the EPUB file": "EPUB ফাইল পার্স করতে ব্যর্থ।",
"This book format is not supported": "এই বই ফরম্যাট সমর্থিত নয়।",
"Failed to import book(s): {{filenames}}": "বই আমদানি ব্যর্থ: {{filenames}}",
"Book uploaded: {{title}}": "বই আপলোড হয়েছে: {{title}}",
"Insufficient storage quota": "অপর্যাপ্ত স্টোরেজ কোটা",
@@ -204,9 +204,6 @@
"Scrolled Mode": "স্ক্রল মোড",
"Continuous Scroll": "ক্রমাগত স্ক্রল",
"Overlap Pixels": "ওভারল্যাপ পিক্সেল",
"Click": "ক্লিক",
"Clicks for Page Flip": "পৃষ্ঠা উল্টানোর জন্য ক্লিক",
"Swap Clicks Area": "ক্লিক এরিয়া অদলবদল",
"Disable Double Click": "ডাবল ক্লিক নিষ্ক্রিয়",
"Volume Keys for Page Flip": "পৃষ্ঠা উল্টানোর জন্য ভলিউম কী",
"Animation": "অ্যানিমেশন",
@@ -296,7 +293,7 @@
"Color": "রঙ",
"Behavior": "আচরণ",
"Reset {{settings}}": "{{settings}} রিসেট",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "সারা বিশ্বটাই একটা মঞ্চ,\nআর সব নর-নারী কেবল অভিনেতা;\nতাদের আছে প্রস্থান ও প্রবেশ,\nআর একজন মানুষ তার জীবনে অনেক চরিত্র পালন করে,\nতার অভিনয় সাত যুগের।\n\n— উইলিয়াম শেক্সপিয়র",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "সারা বিশ্বটাই একটা মঞ্চ,\nআর সব নর-নারী কেবল অভিনেতা;\nতাদের আছে প্রস্থান ও প্রবেশ,\nআর একজন মানুষ তার জীবনে অনেক চরিত্র পালন করে,\nতার অভিনয় সাত যুগের।\n\n— উইলিয়াম শেক্সপিয়র",
"(from 'As You Like It', Act II)": "('অ্যাজ ইউ লাইক ইট', দ্বিতীয় অঙ্ক থেকে)",
"Custom Theme": "কাস্টম থিম",
"Theme Name": "থিমের নাম",
@@ -305,7 +302,6 @@
"Link Color": "লিঙ্ক রঙ",
"Preview": "প্রিভিউ",
"Font & Layout": "ফন্ট ও লেআউট",
"Book Cover": "বইয়ের মলাট",
"More Info": "আরো তথ্য",
"Parallel Read": "সমান্তরাল পড়া",
"Disable": "নিষ্ক্রিয়",
@@ -379,7 +375,6 @@
"Email:": "ইমেইল:",
"Plan:": "প্ল্যান:",
"Amount:": "পরিমাণ:",
"Subscription ID:": "সাবস্ক্রিপশন ID:",
"Go to Library": "লাইব্রেরিতে যান",
"Need help? Contact our support team at support@readest.com": "সাহায্য দরকার? support@readest.com এ আমাদের সাপোর্ট টিমের সাথে যোগাযোগ করুন",
"Free Plan": "ফ্রি প্ল্যান",
@@ -609,5 +604,164 @@
"Contain": "ধারণ",
"{{number}} pages left in chapter": "অধ্যায়ে {{number}} পৃষ্ঠা বাকি",
"Device": "যন্ত্র",
"E-Ink Mode": "ই-ইঙ্ক মোড"
"E-Ink Mode": "ই-ইঙ্ক মোড",
"Highlight Colors": "হাইলাইট রঙ",
"Auto Screen Brightness": "স্বয়ংক্রিয় স্ক্রিন উজ্জ্বলতা",
"Pagination": "পৃষ্ঠা বিন্যাস",
"Disable Double Tap": "ডাবল ট্যাপ অক্ষম করুন",
"Tap to Paginate": "পৃষ্ঠায় যেতে ট্যাপ করুন",
"Click to Paginate": "পৃষ্ঠায় যেতে ক্লিক করুন",
"Tap Both Sides": "দুই পাশে ট্যাপ করুন",
"Click Both Sides": "দুই পাশে ক্লিক করুন",
"Swap Tap Sides": "ট্যাপের পাশে পরিবর্তন করুন",
"Swap Click Sides": "ক্লিকের পাশে পরিবর্তন করুন",
"Source and Translated": "উৎস এবং অনূদিত",
"Translated Only": "শুধুমাত্র অনূদিত",
"Source Only": "শুধুমাত্র উৎস",
"TTS Text": "TTS টেক্সট",
"The book file is corrupted": "বইয়ের ফাইলটি ক্ষতিগ্রস্ত।",
"The book file is empty": "বইয়ের ফাইলটি খালি।",
"Failed to open the book file": "বইয়ের ফাইল খুলতে ব্যর্থ।",
"On-Demand Purchase": "অনুরোধে ক্রয়",
"Full Customization": "সম্পূর্ণ কাস্টমাইজেশন",
"Lifetime Plan": "জীবনকাল পরিকল্পনা",
"One-Time Payment": "এককালীন পেমেন্ট",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "নির্দিষ্ট বৈশিষ্ট্যগুলিতে সমস্ত ডিভাইসে জীবনকাল অ্যাক্সেস উপভোগ করতে এককালীন পেমেন্ট করুন। যখন আপনার প্রয়োজন তখনই নির্দিষ্ট বৈশিষ্ট্য বা পরিষেবাগুলি কিনুন।",
"Expand Cloud Sync Storage": "ক্লাউড সিঙ্ক স্টোরেজ বাড়ান",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "এককালীন ক্রয়ের মাধ্যমে আপনার ক্লাউড স্টোরেজ চিরকাল বাড়ান। প্রতিটি অতিরিক্ত ক্রয় আরও স্থান যোগ করে।",
"Unlock All Customization Options": "সমস্ত কাস্টমাইজেশন বিকল্প আনলক করুন",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "অতিরিক্ত থিম, ফন্ট, লেআউট বিকল্প এবং উচ্চস্বরে পড়া, অনুবাদক, ক্লাউড স্টোরেজ পরিষেবাগুলি আনলক করুন।",
"Purchase Successful!": "ক্রয় সফল!",
"lifetime": "জীবনকাল",
"Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়া করা হয়েছে।",
"Order ID:": "অর্ডার আইডি:",
"TTS Highlighting": "টিটিএস হাইলাইটিং",
"Style": "শৈলী",
"Underline": "আন্ডারলাইন",
"Strikethrough": "কাটা লেখা",
"Squiggly": "ঢেউখেলানো দাগ",
"Outline": "বাহিরের রেখা",
"Save Current Color": "বর্তমান রঙ সংরক্ষণ করুন",
"Quick Colors": "দ্রুত রংসমূহ",
"Highlighter": "হাইলাইটার",
"Save Book Cover": "বইয়ের মলাট সংরক্ষণ করুন",
"Auto-save last book cover": "শেষ বইয়ের মলাট স্বয়ংক্রিয়ভাবে সংরক্ষণ করুন",
"Back": "পেছনে",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "নিশ্চিতকরণ ইমেল পাঠানো হয়েছে! পরিবর্তন নিশ্চিত করতে আপনার পুরানো এবং নতুন ইমেল ঠিকানা পরীক্ষা করুন।",
"Failed to update email": "ইমেল আপডেট করতে ব্যর্থ",
"New Email": "নতুন ইমেল",
"Your new email": "আপনার নতুন ইমেল",
"Updating email ...": "ইমেল আপডেট হচ্ছে ...",
"Update email": "ইমেল আপডেট করুন",
"Current email": "বর্তমান ইমেল",
"Update Email": "ইমেল আপডেট করুন",
"All": "সব",
"Unable to open book": "বই খুলতে অক্ষম",
"Punctuation": "বিরামচিহ্ন",
"Replace Quotation Marks": "উদ্ধৃতি চিহ্ন প্রতিস্থাপন করুন",
"Enabled only in vertical layout.": "শুধুমাত্র উল্লম্ব বিন্যাসে সক্রিয়।",
"No Conversion": "রূপান্তর নয়",
"Simplified to Traditional": "সরল → প্রথাগত",
"Traditional to Simplified": "প্রথাগত → সরল",
"Simplified to Traditional (Taiwan)": "সরল → প্রথাগত (তাইওয়ান)",
"Simplified to Traditional (Hong Kong)": "সরল → প্রথাগত (হংকং)",
"Simplified to Traditional (Taiwan), with phrases": "সরল → প্রথাগত (তাইওয়ান • বাক্য)",
"Traditional (Taiwan) to Simplified": "প্রথাগত (তাইওয়ান) → সরল",
"Traditional (Hong Kong) to Simplified": "প্রথাগত (হংকং) → সরল",
"Traditional (Taiwan) to Simplified, with phrases": "প্রথাগত (তাইওয়ান • বাক্য) → সরল",
"Convert Simplified and Traditional Chinese": "সরল/প্রথাগত রূপান্তর",
"Convert Mode": "রূপান্তর মোড",
"Failed to auto-save book cover for lock screen: {{error}}": "লক স্ক্রিনের জন্য বইয়ের মলাট স্বয়ংক্রিয়ভাবে সংরক্ষণ করতে ব্যর্থ: {{error}}",
"Download from Cloud": "ক্লাউড থেকে ডাউনলোড করুন",
"Upload to Cloud": "ক্লাউডে আপলোড করুন",
"Clear Custom Fonts": "কাস্টম ফন্টস মুছুন",
"Columns": "কলামস",
"OPDS Catalogs": "OPDS ক্যাটালগস",
"Adding LAN addresses is not supported in the web app version.": "ওয়েব অ্যাপ সংস্করণে LAN ঠিকানা যোগ করা সমর্থিত নয়।",
"Invalid OPDS catalog. Please check the URL.": "অবৈধ OPDS ক্যাটালগ। দয়া করে URL চেক করুন।",
"Browse and download books from online catalogs": "অনলাইন ক্যাটালগ থেকে বই ব্রাউজ এবং ডাউনলোড করুন",
"My Catalogs": "আমার ক্যাটালগস",
"Add Catalog": "ক্যাটালগ যোগ করুন",
"No catalogs yet": "এখনও কোন ক্যাটালগ নেই",
"Add your first OPDS catalog to start browsing books": "আপনার প্রথম OPDS ক্যাটালগ যোগ করুন বই ব্রাউজ শুরু করতে",
"Add Your First Catalog": "আপনার প্রথম ক্যাটালগ যোগ করুন",
"Browse": "ব্রাউজ করুন",
"Popular Catalogs": "জনপ্রিয় ক্যাটালগস",
"Add": "যোগ করুন",
"Add OPDS Catalog": "OPDS ক্যাটালগ যোগ করুন",
"Catalog Name": "ক্যাটালগের নাম",
"My Calibre Library": "আমার ক্যালিব্র লাইব্রেরি",
"OPDS URL": "OPDS URL",
"Username (optional)": "ব্যবহারকারীর নাম (ঐচ্ছিক)",
"Password (optional)": "পাসওয়ার্ড (ঐচ্ছিক)",
"Description (optional)": "বর্ণনা (ঐচ্ছিক)",
"A brief description of this catalog": "এই ক্যাটালগের সংক্ষিপ্ত বিবরণ",
"Validating...": "যাচাই করা হচ্ছে...",
"View All": "সব দেখুন",
"Forward": "ফরোয়ার্ড",
"OPDS Catalog": "OPDS ক্যাটালগ",
"Home": "হোম",
"Library": "লাইব্রেরি",
"{{count}} items_one": "{{count}} আইটেম",
"{{count}} items_other": "{{count}} আইটেম",
"Download completed": "ডাউনলোড সম্পন্ন হয়েছে",
"Download failed": "ডাউনলোড ব্যর্থ হয়েছে",
"Open Access": "ওপেন অ্যাক্সেস",
"Borrow": "ঋণ নিন",
"Buy": "কিনুন",
"Subscribe": "সাবস্ক্রাইব করুন",
"Sample": "নমুনা",
"Download": "ডাউনলোড",
"Open & Read": "খুলুন ও পড়ুন",
"Tags": "ট্যাগসমূহ",
"Tag": "ট্যাগ",
"First": "প্রথম",
"Previous": "পূর্ববর্তী",
"Next": "পরবর্তী",
"Last": "শেষ",
"Cannot Load Page": "পৃষ্ঠা লোড করা যায়নি",
"An error occurred": "একটি ত্রুটি ঘটেছে",
"Online Library": "অনলাইন লাইব্রেরি",
"URL must start with http:// or https://": "URL অবশ্যই http:// বা https:// দিয়ে শুরু হতে হবে",
"Title, Author, Tag, etc...": "শিরোনাম, লেখক, ট্যাগ, ইত্যাদি...",
"Query": "কোয়েরি",
"Subject": "বিষয়",
"Enter {{terms}}": "{{terms}} লিখুন",
"No search results found": "কোনও অনুসন্ধান ফলাফল পাওয়া যায়নি",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ফিড লোড করতে ব্যর্থ: {{status}} {{statusText}}",
"Search in {{title}}": "{{title}} এ অনুসন্ধান করুন",
"Manage Storage": "স্টোরেজ ম্যানেজমেন্ট",
"Failed to load files": "ফাইল লোড করতে ব্যর্থ",
"Deleted {{count}} file(s)_one": "{{count}}টি ফাইল মুছে ফেলা হয়েছে",
"Deleted {{count}} file(s)_other": "{{count}}টি ফাইল মুছে ফেলা হয়েছে",
"Failed to delete {{count}} file(s)_one": "{{count}}টি ফাইল মুছতে ব্যর্থ",
"Failed to delete {{count}} file(s)_other": "{{count}}টি ফাইল মুছতে ব্যর্থ",
"Failed to delete files": "ফাইল মুছতে ব্যর্থ",
"Total Files": "মোট ফাইল",
"Total Size": "মোট আকার",
"Quota": "কোটা",
"Used": "ব্যবহৃত",
"Files": "ফাইল",
"Search files...": "ফাইল খুঁজুন...",
"Newest First": "নতুন আগে",
"Oldest First": "পুরনো আগে",
"Largest First": "বড় আগে",
"Smallest First": "ছোট আগে",
"Name A-Z": "নাম A-Z",
"Name Z-A": "নাম Z-A",
"{{count}} selected_one": "{{count}}টি নির্বাচিত",
"{{count}} selected_other": "{{count}}টি নির্বাচিত",
"Delete Selected": "নির্বাচিত মুছুন",
"Created": "তৈরি হয়েছে",
"No files found": "কোনো ফাইল পাওয়া যায়নি",
"No files uploaded yet": "এখনও কোনো ফাইল আপলোড করা হয়নি",
"files": "ফাইল",
"Page {{current}} of {{total}}": "{{total}}টির মধ্যে {{current}} পৃষ্ঠা",
"Are you sure to delete {{count}} selected file(s)?_one": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত ফাইল মুছতে চান?",
"Are you sure to delete {{count}} selected file(s)?_other": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত ফাইল মুছতে চান?",
"Cloud Storage Usage": "ক্লাউড স্টোরেজ ব্যবহৃত",
"Rename Group": "গ্রুপের নাম পরিবর্তন করুন",
"From Directory": "ডিরেক্টরি থেকে",
"Successfully imported {{count}} book(s)_one": "সফলভাবে ১টি বই আমদানি করা হয়েছে",
"Successfully imported {{count}} book(s)_other": "সফলভাবে {{count}}টি বই আমদানি করা হয়েছে"
}
@@ -8,7 +8,6 @@
"Auto Mode": "རང་འགུལ་བརྗོད་བྱ།",
"Behavior": "བྱ་སྤྱོད།",
"Book": "དཔེ་དེབ།",
"Book Cover": "དཔེ་དེབ་ཀྱི་སྟོང་ངོས།",
"Bookmark": "དཔེ་རྟགས།",
"Cancel": "འདོར་བ།",
"Chapter": "ལེའུ།",
@@ -220,9 +219,9 @@
"Auto Import on File Open": "ཡིག་ཆ་ཁ་ཕྱེ་སྐབས་རང་འགུལ་གྱིས་ནང་འདྲེན།",
"LXGW WenKai GB Screen": "ཤ་ཝུ་ཝུན་ཁའེ།",
"LXGW WenKai TC": "ཤ་ཝུ་ཝུན་ཁའེ།",
"No chapters detected.": "ལེའུ་བརྟག་དཔྱད་བྱུང་མེད།",
"Failed to parse the EPUB file.": "EPUB ཡིག་ཆ་འགྲེལ་བཤད་བྱེད་ཐུབ་མེད།",
"This book format is not supported.": "དཔེ་དེབ་ཀྱི་རྣམ་པ་འདི་རྒྱབ་སྐྱོར་མི་བྱེད།",
"No chapters detected": "ལེའུ་བརྟག་དཔྱད་བྱུང་མེད།",
"Failed to parse the EPUB file": "EPUB ཡིག་ཆ་འགྲེལ་བཤད་བྱེད་ཐུབ་མེད།",
"This book format is not supported": "དཔེ་དེབ་ཀྱི་རྣམ་པ་འདི་རྒྱབ་སྐྱོར་མི་བྱེད།",
"Unable to fetch the translation. Please log in first and try again.": "སྒྱུར་བཅོས་ལེན་ཐུབ་མེད། ནང་བསྐྱོད་བྱས་རྗེས་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད་རོགས།",
"Group": "སྡེ་ཚན།",
"Always on Top": "སྒེའུ་ཁུང་གནས་སུ་བཀོད་པ།",
@@ -259,8 +258,6 @@
"(from 'As You Like It', Act II)": "《ཐམས་ཅད་དགའ་བ》ནས་དྲངས་པ། ལེའུ་གཉིས་པ།",
"Link Color": "སྦྲེལ་ཐག་གི་ཚོན་མདོག",
"Volume Keys for Page Flip": "སྒྲ་ཚད་ལྡེ་མིག་གིས་ཤོག་ལྷེ་བསྒྱུར་བ།",
"Clicks for Page Flip": "མནན་ནས་ཤོག་ལྷེ་བསྒྱུར་བ།",
"Swap Clicks Area": "མནན་སའི་ཁོངས་སུ་བརྗེ་བ།",
"Screen": "བརྡ་ཁྱབ་ངོས།",
"Orientation": "བརྡ་ཁྱབ་ངོས་ཀྱི་ཁ་ཕྱོགས།",
"Portrait": "ཀྲིང་ངོས།",
@@ -295,7 +292,6 @@
"Disable Translation": "སྒྱུར་བཅོས་སྒོ་བརྒྱབ།",
"Scroll": "འཁོར་རྒྱུག",
"Overlap Pixels": "ལྷན་པའི་པིག་སེལ།",
"Click": "མནན་པ།",
"System Language": "མ་ལག་གི་སྐད་ཡིག",
"Security": "བདེ་འཇགས།",
"Allow JavaScript": "JavaScript ཆོག་མཆན།",
@@ -361,7 +357,6 @@
"Email:": "གློག་རྡུལ་ཡིག་ཟམ།",
"Plan:": "ཐུམ་སྒྲིལ།",
"Amount:": "དངུལ་གྲངས།",
"Subscription ID:": "མཚམས་སྦྱོར་ ID །",
"Go to Library": "དཔེ་མཛོད་ལ་འགྲོ།",
"Need help? Contact our support team at support@readest.com": "རོགས་རམ་དགོས་སམ། རྒྱབ་སྐྱོར་ཚོགས་པར་འབྲེལ་བ་གནང་རོགས། support@readest.com",
"Free Plan": "རིན་མེད་ཐུམ་སྒྲིལ།",
@@ -491,7 +486,7 @@
"another device": "གཞན་ཡིག་སྣེ།",
"Local Progress": "ཀུན་འབྱུང་ཁུངས།",
"Remote Progress": "བརྒྱབ་འབྱུང་ཁུངས།",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
"Page {{page}} of {{total}} ({{percentage}}%)": "ཤོག་ {{page}} དང་ {{total}} ({{percentage}}%)",
"Current position": "ད་ལྟའི་གནས་ས།",
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "ཕྲན་བུ་ཤོག་ངོས་ {{page}} / {{total}} ({{percentage}}%)",
@@ -605,5 +600,158 @@
"Contain": "འབྱོར་བ།",
"{{number}} pages left in chapter": "དོན་ཚན་ནང་ཤོག་ཨང་ {{number}} ལོག་གི་འདུག།",
"Device": "རྐྱབ་སྐོར",
"E-Ink Mode": "ཡིག་སྒྱུར་སྤོ་བ"
"E-Ink Mode": "ཡིག་སྒྱུར་སྤོ་བ",
"Highlight Colors": "མདོག་ཚད་ཀྱི་བསྡུར་བ།",
"Auto Screen Brightness": "རྒྱབ་སྐོར་གྱི་འོད་ཟེར་རང་འགུལ།",
"Pagination": "ཤོག་གདོང་།",
"Disable Double Tap": "བརྒྱབ་གདོང་གཉིས་མ་འགྱོད།",
"Tap to Paginate": "ཤོག་གདོང་ལ་ཐོག་འགྲོ།",
"Click to Paginate": "ཤོག་གདོང་ལ་ཁྱེད་ལུས་འགྲོ།",
"Tap Both Sides": "གཡས་གཉིས་ལ་ཐོག་འགྲོ།",
"Click Both Sides": "གཡས་གཉིས་ལ་ཁྱེད་ལུས་འགྲོ།",
"Swap Tap Sides": "ཐོག་གཡས་གཉིས་བརྗེ་བ།",
"Swap Click Sides": "ཁྱེད་ལུས་གཡས་གཉིས་བརྗེ་བ།",
"Source and Translated": "ཡིག་སྒྱུར་སྤོ་བ།",
"Translated Only": "མདོག་ཚད་ཀྱི་བསྡུར་བ།",
"Source Only": "ཡིག་སྒྱུར་སྤོ་བ།",
"TTS Text": "TTS ཡིག",
"The book file is corrupted": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
"The book file is empty": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
"Failed to open the book file": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
"On-Demand Purchase": "অনুরোধে ক্রয়",
"Full Customization": "সম্পূর্ণ কাস্টমাইজেশন",
"Lifetime Plan": "জীবনকাল পরিকল্পনা",
"One-Time Payment": "এককালীন পেমেন্ট",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "নির্দিষ্ট বৈশিষ্ট্যগুলিতে সমস্ত ডিভাইসে জীবনকাল অ্যাক্সেস উপভোগ করতে এককালীন পেমেন্ট করুন। যখন আপনার প্রয়োজন তখনই নির্দিষ্ট বৈশিষ্ট্য বা পরিষেবাগুলি কিনুন।",
"Expand Cloud Sync Storage": "ক্লাউড সিঙ্ক স্টোরেজ বাড়ান",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "এককালীন ক্রয়ের মাধ্যমে আপনার ক্লাউড স্টোরেজ চিরকাল বাড়ান। প্রতিটি অতিরিক্ত ক্রয় আরও স্থান যোগ করে।",
"Unlock All Customization Options": "সমস্ত কাস্টমাইজেশন বিকল্প আনলক করুন",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "অতিরিক্ত থিম, ফন্ট, লেআউট বিকল্প এবং উচ্চস্বরে পড়া, অনুবাদক, ক্লাউড স্টোরেজ পরিষেবাগুলি আনলক করুন।",
"Purchase Successful!": "ক্রয় সফল!",
"lifetime": "জীবনকাল",
"Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়াকৃত হয়েছে।",
"Order ID:": "অর্ডার আইডি:",
"TTS Highlighting": "ཡིག་འབྲུ་སྦྱོར་བའི་གསལ་བཤད།",
"Style": "བཟོ་ལྟ།",
"Underline": "འོག་ཐིག",
"Strikethrough": "འཐེན་ཐིག",
"Squiggly": "འཁྱོག་ཐིག",
"Outline": "མཐའ་ཐིག",
"Save Current Color": "མིག་སྔའི་ཚོན་གཏན་འཁེལ་བྱེད།",
"Quick Colors": "མྱུར་ཚོན།",
"Highlighter": "མདོག་ཚད་ཀྱི་བསྡུར་བ།",
"Save Book Cover": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
"Auto-save last book cover": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
"Back": "ལོག་བྱེད།",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "གཏན་འཁེལ་ཡིག་འཕྲིན་བཏང་ཡོད། ཡིག་འཕྲིན་རྣམས་གཉིས་ལ་བལྟས་ནས་བརྗེ་བ་དེ་གཏན་འཁེལ་བྱོས།",
"Failed to update email": "ཡིག་འཕྲིན་གསར་བསྒྱུར་ཕམ་པ།",
"New Email": "ཡིག་འཕྲིན་གསར་པ།",
"Your new email": "ཁྱེད་ཀྱི་ཡིག་འཕྲིན་གསར་པ།",
"Updating email ...": "ཡིག་འཕྲིན་གསར་བསྒྱུར་བཞིན་པ། ...",
"Update email": "ཡིག་འཕྲིན་གསར་བསྒྱུར།",
"Current email": "མིག་སྔའི་ཡིག་འཕྲིན།",
"Update Email": "ཡིག་འཕྲིན་གསར་བསྒྱུར།",
"All": "ཆེན་མོ།",
"Unable to open book": "དེབ་ཀྱི་སྤྱོད་བྱས་མ་ཐུབ།",
"Punctuation": "ཚིག་གྲུབ།",
"Replace Quotation Marks": "འདྲེན་འབྲེལ་མཐུད་ཐིག་བརྗེ་བ།",
"Enabled only in vertical layout.": "གཡས་སྒྲིག་ནང་མིང་བརྗེ་བ།",
"No Conversion": "བསྒྱུར་བཤེར་མེད་",
"Simplified to Traditional": "སྟོང་བཟོས → སྲོལ་རྒྱུན",
"Traditional to Simplified": "སྲོལ་རྒྱུན → སྟོང་བཟོས",
"Simplified to Traditional (Taiwan)": "སྟོང་བཟོས → སྲོལ་རྒྱུན (ཏའི་ཝན)",
"Simplified to Traditional (Hong Kong)": "སྟོང་བཟོས → སྲོལ་རྒྱུན (ཧོང་ཀོང)",
"Simplified to Traditional (Taiwan), with phrases": "སྟོང་བཟོས → སྲོལ་རྒྱུན (ཏའི་ཝན • ཚིག་ཕྲེང)",
"Traditional (Taiwan) to Simplified": "སྲོལ་རྒྱུན (ཏའི་ཝན) → སྟོང་བཟོས",
"Traditional (Hong Kong) to Simplified": "སྲོལ་རྒྱུན (ཧོང་ཀོང) → སྟོང་བཟོས",
"Traditional (Taiwan) to Simplified, with phrases": "སྲོལ་རྒྱུན (ཏའི་ཝན • ཚིག་ཕྲེང) → སྟོང་བཟོས",
"Convert Simplified and Traditional Chinese": "རྒྱ་ཡིག སྟོང་བཟོས/སྲོལ་རྒྱུན བསྒྱུར་བ",
"Convert Mode": "བསྒྱུར་བའི་རྣམ་པ",
"Failed to auto-save book cover for lock screen: {{error}}": "སྒྲོམ་སྒོར་དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།: {{error}}",
"Download from Cloud": "སྤྲིན་ནས་ཕབ་སྟེ་འབེབས་",
"Upload to Cloud": "སྤྲིན་ལ་ཡར་སྤེལ་",
"Clear Custom Fonts": "རང་བཞིན་ཡིག་གཟུགས་སུབ་པ།",
"Columns": "མཚན་ཉིད།",
"OPDS Catalogs": "OPDS གནས་ཚུལ།",
"Adding LAN addresses is not supported in the web app version.": "གནས་ཚུལ་དང་ལུས་སྐོར་གྱི་གྲོང་ཁྱེར་གྱི་གློག་འཕྲིན་ལས་མཐུན་པ་མེད།",
"Invalid OPDS catalog. Please check the URL.": "མི་འབྱུང་བའི OPDS གནས་ཚུལ། ཨེ་ཨོ་ཨེར་ལུས་སྐོར་བཟོ་བྱས།",
"Browse and download books from online catalogs": "དེབ་གནས་བཅས་པའི་གྲོང་ཁྱེར་ལས་དེབ་འདེམས་དང་ཕབ་སྟེ་འབེབས།",
"My Catalogs": "ངའི་དཀར་ཆག",
"Add Catalog": "དཀར་ཆག་ཁ་སྣོན་",
"No catalogs yet": "དཀར་ཆག་མེད་པས།",
"Add your first OPDS catalog to start browsing books": "དེབ་ལྟ་བཤེར་འགོ་བཙུགས་ནས་ དང་པོའི་ OPDS དཀར་ཆག་ཁ་སྣོན་གནང་།",
"Add Your First Catalog": "དཀར་ཆག་དང་པོ་ཁ་སྣོན་",
"Browse": "ལྟ་བཤེར་",
"Popular Catalogs": "མི་མང་མཐོང་བའི་དཀར་ཆག",
"Add": "ཁ་སྣོན་",
"Add OPDS Catalog": "OPDS དཀར་ཆག་ཁ་སྣོན་",
"Catalog Name": "དཀར་ཆག་མིང་",
"My Calibre Library": "ངའི Calibre དེབ་མཛོད་",
"OPDS URL": "OPDS URL",
"Username (optional)": "མིང་སྒྲོམ་ (འདེམས་རུང་)",
"Password (optional)": "གསང་ཚིག (འདེམས་རུང་)",
"Description (optional)": "འགྲེལ་བཤད་ (འདེམས་རུང་)",
"A brief description of this catalog": "དཀར་ཆག་འདིའི་གསལ་བཤད་ཐུང་ཐུང་",
"Validating...": "བདེན་སྦྱོར་བཞིན་...",
"View All": "ཡོངས་ལྟ་བ་",
"Forward": "མདུན་དུ་",
"OPDS Catalog": "OPDS དཀར་ཆག",
"Home": "གཙོ་ངོས་",
"Library": "དེབ་མཛོད་",
"{{count}} items_other": "{{count}} རྣམ་གྲངས་",
"Download completed": "ཕབ་ལེན་རྫོགས་སོང་།",
"Download failed": "ཕབ་ལེན་ཕམ་པ་",
"Open Access": "ཁ་ཕྱབ་ལྟ་བཤེར་",
"Borrow": "ལེན་པ་",
"Buy": "ཉོ་",
"Subscribe": "མཁོ་མངགས་",
"Sample": "དཔེ་དབང་",
"Download": "ཕབ་ལེན་",
"Open & Read": "ཁ་ཕྱེས་ནས་ཀློག་",
"Tags": "མཚོན་འགྲེལ་",
"Tag": "མཚོན་འགྲེལ་",
"First": "དང་པོ།",
"Previous": "སྔོན་མ།",
"Next": "རྗེས་མ།",
"Last": "མཐའ་མ།",
"Cannot Load Page": "ཤོག་ངོས་འགུལ་སྐྱོང་བྱེད་ཐུབ་མེད།",
"An error occurred": "ནོར་འཁྲུལ་ཞིག་བྱུང་སོང་།",
"Online Library": "དྲ་རྒྱུན་དེབ་མཛོད།",
"URL must start with http:// or https://": "URL ནི་ http:// ཡང་ https:// ནས་འགོ་བཙུགས་དགོ།",
"Title, Author, Tag, etc...": "མིང་།, རྩོམ་པ།, མཚོན་འགྲེལ།, དེ་ལས་སྐུགས་...",
"Query": "འཚོལ་ཞིབ་",
"Subject": "དོན་ཚན་",
"Enter {{terms}}": "{{terms}} ལ་འགྲོ།",
"No search results found": "འཚོལ་ཞིབ་རྫོགས་མ་ཐུབ།",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS འཕྲིན་འདེམས་བྱས་མ་ཐུབ།: {{status}} {{statusText}}",
"Search in {{title}}": "{{title}} ནང་འཚོལ།",
"Manage Storage": "སྣོད་གསོག་དོ་དམ་བྱེད་པ",
"Failed to load files": "ཡིག་ཆ་སྣོན་པ་ཕམ་པ",
"Deleted {{count}} file(s)_other": "ཡིག་ཆ་ {{count}} བསུབས་ཟིན་པ",
"Failed to delete {{count}} file(s)_other": "ཡིག་ཆ་ {{count}} བསུབས་པ་ཕམ་པ",
"Failed to delete files": "ཡིག་ཆ་བསུབས་པ་ཕམ་པ",
"Total Files": "ཡིག་ཆ་ཡོངས་བསྡོམས",
"Total Size": "ཆེས་ཆེར་ཆེ་ཆུང",
"Quota": "ཁུལ་ཚད",
"Used": "ལག་ལེན་བྱས་ཟིན་པ",
"Files": "ཡིག་ཆ",
"Search files...": "ཡིག་ཆ་འཚོལ...",
"Newest First": "གསར་ཤོས་སྔོན་དུ",
"Oldest First": "རྙིང་ཤོས་སྔོན་དུ",
"Largest First": "ཆེ་ཤོས་སྔོན་དུ",
"Smallest First": "ཆུང་ཤོས་སྔོན་དུ",
"Name A-Z": "མིང་ A-Z",
"Name Z-A": "མིང་ Z-A",
"{{count}} selected_other": "{{count}} ཡིག་ཆ་འདེམས་ཟིན་པ",
"Delete Selected": "འདེམས་པ་བསུབས་པ",
"Created": "སྤེལ་བྱས་ཟིན་པ",
"No files found": "ཡིག་ཆ་མ་རྙེད་པ",
"No files uploaded yet": "ད་ཚུན་ཡིག་ཆ་སྣོན་མི་འདུག",
"files": "ཡིག་ཆ",
"Page {{current}} of {{total}}": "ཤོག་ངོས་ {{total}} ནས་ {{current}}",
"Are you sure to delete {{count}} selected file(s)?_other": "ཁྱེད་ཀྱིས་འདེམས་པའི་ཡིག་ཆ་ {{count}} བསུབས་དགོས་པ་ངེས་ཡིན་ན?",
"Cloud Storage Usage": "སྤྲིན་གནས་སྣོད་གསོག་ལུས་སྐོར།",
"Rename Group": "ཚོགས་མིང་བསྒྱུར་བ།",
"From Directory": "སྐོར་འདེམས་པ་ནས།",
"Successfully imported {{count}} book(s)_other": "སྤྲིན་ནས་ཕབ་སྟེ་འབེབས་ {{count}} དེབ་འདེམས་སོང་"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Automatischer Modus",
"Behavior": "Verhalten",
"Book": "Buch",
"Book Cover": "Buchcover",
"Bookmark": "Lesezeichen",
"Cancel": "Abbrechen",
"Chapter": "Kapitel",
@@ -193,7 +192,7 @@
"No supported files found. Supported formats: {{formats}}": "Keine unterstützten Dateien gefunden. Unterstützte Formate: {{formats}}",
"Drop to Import Books": "Zum Importieren von Büchern ablegen",
"Custom": "Eigen",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Die ganze Welt ist eine Bühne,\nUnd alle Männer und Frauen sind bloß Spieler;\nSie haben ihre Abgänge und ihre Auftritte,\nUnd ein Mann spielt in seinem Leben viele Rollen,\nSeine Taten sind sieben Zeitalter.\n\n—William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Die ganze Welt ist eine Bühne,\nUnd alle Männer und Frauen sind bloß Spieler;\nSie haben ihre Abgänge und ihre Auftritte,\nUnd ein Mann spielt in seinem Leben viele Rollen,\nSeine Taten sind sieben Zeitalter.\n\n— William Shakespeare",
"Custom Theme": "Benutzerdefiniertes Thema",
"Theme Name": "Themenname",
"Text Color": "Textfarbe",
@@ -220,9 +219,9 @@
"Auto Import on File Open": "Automatischer Import beim Öffnen einer Datei",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Keine Kapitel erkannt.",
"Failed to parse the EPUB file.": "Fehler beim Parsen der EPUB-Datei.",
"This book format is not supported.": "Dieses Buchformat wird nicht unterstützt.",
"No chapters detected": "Keine Kapitel erkannt",
"Failed to parse the EPUB file": "Fehler beim Parsen der EPUB-Datei",
"This book format is not supported": "Dieses Buchformat wird nicht unterstützt",
"Unable to fetch the translation. Please log in first and try again.": "Übersetzung konnte nicht abgerufen werden. Bitte zuerst anmelden und erneut versuchen.",
"Group": "Gruppieren",
"Always on Top": "Immer im Vordergrund",
@@ -259,8 +258,6 @@
"(from 'As You Like It', Act II)": "(aus 'Wie es euch gefällt', Akt II)",
"Link Color": "Linkfarbe",
"Volume Keys for Page Flip": "Volumen-Tasten für Seitenwechsel",
"Clicks for Page Flip": "Tippen zum Blättern",
"Swap Clicks Area": "Tippbereiche tauschen",
"Screen": "Bildschirm",
"Orientation": "Orientierung",
"Portrait": "Hochformat",
@@ -296,7 +293,6 @@
"Disable Translation": "Übersetzung deaktivieren",
"Scroll": "Scrollen",
"Overlap Pixels": "Überlappungspixel",
"Click": "Tippen",
"System Language": "Systemsprache",
"Security": "Sicherheit",
"Allow JavaScript": "JavaScript zulassen",
@@ -365,7 +361,6 @@
"Email:": "E-Mail:",
"Plan:": "Tarif:",
"Amount:": "Betrag:",
"Subscription ID:": "Abo-ID:",
"Go to Library": "Zur Bibliothek",
"Need help? Contact our support team at support@readest.com": "Brauchen Sie Hilfe? Kontaktieren Sie unser Support-Team unter support@readest.com",
"Free Plan": "Gratis-Tarif",
@@ -609,5 +604,164 @@
"Contain": "Inhalt",
"{{number}} pages left in chapter": "{{number}} Seiten verbleibend im Kapitel",
"Device": "Gerät",
"E-Ink Mode": "E-Ink-Modus"
"E-Ink Mode": "E-Ink-Modus",
"Highlight Colors": "Hervorhebungsfarben",
"Auto Screen Brightness": "Automatische Bildschirmhelligkeit",
"Pagination": "Seitenumbruch",
"Disable Double Tap": "Doppeltippen deaktivieren",
"Tap to Paginate": "Tippen zum Blättern",
"Click to Paginate": "Klicken zum Blättern",
"Tap Both Sides": "Tippen Sie auf beide Seiten",
"Click Both Sides": "Klicken Sie auf beide Seiten",
"Swap Tap Sides": "Seiten für das Tippen tauschen",
"Swap Click Sides": "Seiten für das Klicken tauschen",
"Source and Translated": "Quell- und Übersetzungstext",
"Translated Only": "Nur Übersetzungstext",
"Source Only": "Nur Quelltext",
"TTS Text": "TTS-Text",
"The book file is corrupted": "Die Buchdatei ist beschädigt",
"The book file is empty": "Die Buchdatei ist leer",
"Failed to open the book file": "Fehler beim Öffnen der Buchdatei",
"On-Demand Purchase": "Kauf auf Anfrage",
"Full Customization": "Vollständige Anpassung",
"Lifetime Plan": "Lebenslange Planung",
"One-Time Payment": "Einmalige Zahlung",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Zahlen Sie einmalig, um lebenslangen Zugriff auf bestimmte Funktionen auf allen Geräten zu genießen. Kaufen Sie bestimmte Funktionen oder Dienstleistungen nur, wenn Sie sie benötigen.",
"Expand Cloud Sync Storage": "Cloud-Synchronisierungs-Speicher erweitern",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Erweitern Sie Ihren Cloud-Speicher für immer mit einem einmaligen Kauf. Jeder zusätzliche Kauf fügt mehr Speicherplatz hinzu.",
"Unlock All Customization Options": "Alle Anpassungsoptionen freischalten",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Zusätzliche Themen, Schriftarten, Layoutoptionen und Vorlesefunktionen, Übersetzer, Cloud-Speicherdienste freischalten.",
"Purchase Successful!": "Kauf erfolgreich!",
"lifetime": "lebenslang",
"Thank you for your purchase! Your payment has been processed successfully.": "Danke für Ihren Kauf! Ihre Zahlung wurde erfolgreich verarbeitet.",
"Order ID:": "Bestell-ID:",
"TTS Highlighting": "TTS-Hervorhebung",
"Style": "Stil",
"Underline": "Unterstreichen",
"Strikethrough": "Durchgestrichen",
"Squiggly": "Wellig",
"Outline": "Umriss",
"Save Current Color": "Aktuelle Farbe speichern",
"Quick Colors": "Schnellfarben",
"Highlighter": "Textmarker",
"Save Book Cover": "Buchcover speichern",
"Auto-save last book cover": "Letztes Buchcover automatisch speichern",
"Back": "Zurück",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Bestätigungs-E-Mail gesendet! Bitte überprüfe deine alte und neue E-Mail-Adresse, um die Änderung zu bestätigen.",
"Failed to update email": "E-Mail konnte nicht aktualisiert werden",
"New Email": "Neue E-Mail",
"Your new email": "Deine neue E-Mail-Adresse",
"Updating email ...": "E-Mail wird aktualisiert ...",
"Update email": "E-Mail aktualisieren",
"Current email": "Aktuelle E-Mail",
"Update Email": "E-Mail aktualisieren",
"All": "Alle",
"Unable to open book": "Buch kann nicht geöffnet werden",
"Punctuation": "Interpunktion",
"Replace Quotation Marks": "Anführungszeichen ersetzen",
"Enabled only in vertical layout.": "Nur im vertikalen Layout aktiviert.",
"No Conversion": "Keine Konvertierung",
"Simplified to Traditional": "Vereinfacht → Traditionell",
"Traditional to Simplified": "Traditionell → Vereinfacht",
"Simplified to Traditional (Taiwan)": "Vereinfacht → Traditionell (Taiwan)",
"Simplified to Traditional (Hong Kong)": "Vereinfacht → Traditionell (Hongkong)",
"Simplified to Traditional (Taiwan), with phrases": "Vereinfacht → Traditionell (Taiwan • Phrasen)",
"Traditional (Taiwan) to Simplified": "Traditionell (Taiwan) → Vereinfacht",
"Traditional (Hong Kong) to Simplified": "Traditionell (Hongkong) → Vereinfacht",
"Traditional (Taiwan) to Simplified, with phrases": "Traditionell (Taiwan • Phrasen) → Vereinfacht",
"Convert Simplified and Traditional Chinese": "Vereinfach./Traditionell konvertieren",
"Convert Mode": "Konvertierungsmodus",
"Failed to auto-save book cover for lock screen: {{error}}": "Automatisches Speichern des Buchcovers für den Sperrbildschirm fehlgeschlagen: {{error}}",
"Download from Cloud": "Aus der Cloud herunterladen",
"Upload to Cloud": "In die Cloud hochladen",
"Clear Custom Fonts": "Benutzerdefinierte Schriftarten löschen",
"Columns": "Spalten",
"OPDS Catalogs": "OPDS-Kataloge",
"Adding LAN addresses is not supported in the web app version.": "Das Hinzufügen von LAN-Adressen wird in der Web-App nicht unterstützt.",
"Invalid OPDS catalog. Please check the URL.": "Ungültiger OPDS-Katalog. Bitte überprüfe die URL.",
"Browse and download books from online catalogs": "Bücher aus Online-Katalogen durchsuchen und herunterladen",
"My Catalogs": "Meine Kataloge",
"Add Catalog": "Katalog hinzufügen",
"No catalogs yet": "Noch keine Kataloge",
"Add your first OPDS catalog to start browsing books": "Füge deinen ersten OPDS-Katalog hinzu, um Bücher zu durchsuchen.",
"Add Your First Catalog": "Ersten Katalog hinzufügen",
"Browse": "Durchsuchen",
"Popular Catalogs": "Beliebte Kataloge",
"Add": "Hinzufügen",
"Add OPDS Catalog": "OPDS-Katalog hinzufügen",
"Catalog Name": "Katalogname",
"My Calibre Library": "Meine Calibre-Bibliothek",
"OPDS URL": "OPDS-URL",
"Username (optional)": "Benutzername (optional)",
"Password (optional)": "Passwort (optional)",
"Description (optional)": "Beschreibung (optional)",
"A brief description of this catalog": "Kurzbeschreibung dieses Katalogs",
"Validating...": "Wird überprüft...",
"View All": "Alle anzeigen",
"Forward": "Weiter",
"OPDS Catalog": "OPDS-Katalog",
"Home": "Start",
"Library": "Bibliothek",
"{{count}} items_one": "{{count}} Element",
"{{count}} items_other": "{{count}} Elemente",
"Download completed": "Download abgeschlossen",
"Download failed": "Download fehlgeschlagen",
"Open Access": "Offener Zugriff",
"Borrow": "Ausleihen",
"Buy": "Kaufen",
"Subscribe": "Abonnieren",
"Sample": "Leseprobe",
"Download": "Herunterladen",
"Open & Read": "Öffnen & Lesen",
"Tags": "Tags",
"Tag": "Tag",
"First": "Erste",
"Previous": "Vorherige",
"Next": "Nächste",
"Last": "Letzte",
"Cannot Load Page": "Seite kann nicht geladen werden",
"An error occurred": "Ein Fehler ist aufgetreten",
"Online Library": "Online-Bibliothek",
"URL must start with http:// or https://": "Die URL muss mit http:// oder https:// beginnen",
"Title, Author, Tag, etc...": "Titel, Autor, Tag, etc...",
"Query": "Suchbegriff",
"Subject": "Thema",
"Enter {{terms}}": "Gib {{terms}} ein",
"No search results found": "Keine Suchergebnisse gefunden",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS-Feed konnte nicht geladen werden: {{status}} {{statusText}}",
"Search in {{title}}": "Suche in {{title}}",
"Manage Storage": "Speicher verwalten",
"Failed to load files": "Dateien konnten nicht geladen werden",
"Deleted {{count}} file(s)_one": "{{count}} Datei gelöscht",
"Deleted {{count}} file(s)_other": "{{count}} Dateien gelöscht",
"Failed to delete {{count}} file(s)_one": "Löschen von {{count}} Datei fehlgeschlagen",
"Failed to delete {{count}} file(s)_other": "Löschen von {{count}} Dateien fehlgeschlagen",
"Failed to delete files": "Dateien konnten nicht gelöscht werden",
"Total Files": "Gesamtdateien",
"Total Size": "Gesamtgröße",
"Quota": "Kontingent",
"Used": "Verwendet",
"Files": "Dateien",
"Search files...": "Dateien suchen...",
"Newest First": "Neueste zuerst",
"Oldest First": "Älteste zuerst",
"Largest First": "Größte zuerst",
"Smallest First": "Kleinste zuerst",
"Name A-Z": "Name AZ",
"Name Z-A": "Name ZA",
"{{count}} selected_one": "{{count}} ausgewählt",
"{{count}} selected_other": "{{count}} ausgewählt",
"Delete Selected": "Ausgewählte löschen",
"Created": "Erstellt",
"No files found": "Keine Dateien gefunden",
"No files uploaded yet": "Noch keine Dateien hochgeladen",
"files": "Dateien",
"Page {{current}} of {{total}}": "Seite {{current}} von {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Möchten Sie {{count}} ausgewählte Datei wirklich löschen?",
"Are you sure to delete {{count}} selected file(s)?_other": "Möchten Sie {{count}} ausgewählte Dateien wirklich löschen?",
"Cloud Storage Usage": "Cloud-Speichernutzung",
"Rename Group": "Gruppe umbenennen",
"From Directory": "Aus Verzeichnis",
"Successfully imported {{count}} book(s)_one": "Erfolgreich 1 Buch importiert",
"Successfully imported {{count}} book(s)_other": "Erfolgreich {{count}} Bücher importiert"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Αυτόματη λειτουργία",
"Behavior": "Συμπεριφορά",
"Book": "Βιβλίο",
"Book Cover": "Εξώφυλλο βιβλίου",
"Bookmark": "Σελιδοδείκτης",
"Cancel": "Ακύρωση",
"Chapter": "Κεφάλαιο",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Δεν βρέθηκαν υποστηριζόμενα αρχεία. Υποστηριζόμενες μορφές: {{formats}}",
"Drop to Import Books": "Ρίξτε για εισαγωγή βιβλίων",
"Custom": "Προσαρμοσμένο",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Όλος ο κόσμος είναι σκηνή,\nΚαι όλοι οι άνδρες και οι γυναίκες απλώς ηθοποιοί·\nΈχουν τις εξόδους και τις εισόδους τους,\nΚαι ένας άνθρωπος στον χρόνο του παίζει πολλούς ρόλους,\nΟι πράξεις του υπάρχουν σε επτά ηλικίες.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Όλος ο κόσμος είναι σκηνή,\nΚαι όλοι οι άνδρες και οι γυναίκες απλώς ηθοποιοί·\nΈχουν τις εξόδους και τις εισόδους τους,\nΚαι ένας άνθρωπος στον χρόνο του παίζει πολλούς ρόλους,\nΟι πράξεις του υπάρχουν σε επτά ηλικίες.\n\n— William Shakespeare",
"Custom Theme": "Προσαρμοσμένο θέμα",
"Theme Name": "Όνομα θέματος",
"Text Color": "Χρώμα κειμένου",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Αυτόματη εισαγωγή κατά το άνοιγμα αρχείου",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Δεν εντοπίστηκαν κεφάλαια.",
"Failed to parse the EPUB file.": "Αποτυχία ανάλυσης του αρχείου EPUB.",
"This book format is not supported.": "Αυτή η μορφή βιβλίου δεν υποστηρίζεται.",
"No chapters detected": "Δεν εντοπίστηκαν κεφάλαια",
"Failed to parse the EPUB file": "Αποτυχία ανάλυσης του αρχείου EPUB",
"This book format is not supported": "Αυτή η μορφή βιβλίου δεν υποστηρίζεται",
"Unable to fetch the translation. Please log in first and try again.": "Αδυναμία λήψης της μετάφρασης. Παρακαλώ συνδεθείτε πρώτα και δοκιμάστε ξανά.",
"Group": "Ομάδα",
"Always on Top": "Πάντα στην κορυφή",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "('Όπως σας αρέσει', Πράξη II)",
"Link Color": "Χρώμα συνδέσμου",
"Volume Keys for Page Flip": "Ανατροπή με πλήκτρα έντασης",
"Clicks for Page Flip": "Κλικ για ανατροπή σελίδας",
"Swap Clicks Area": "Ανταλλαγή περιοχής κλικ",
"Screen": "Οθόνη",
"Orientation": "Προσανατολισμός",
"Portrait": "Πορτραίτο",
@@ -297,7 +294,6 @@
"Disable Translation": "Απενεργοποίηση μετάφρασης",
"Scroll": "Κύλιση",
"Overlap Pixels": "Επικάλυψη εικονοστοιχείων",
"Click": "Κλικ",
"System Language": "Γλώσσα συστήματος",
"Security": "Ασφάλεια",
"Allow JavaScript": "Επιτρέψτε JavaScript",
@@ -366,7 +362,6 @@
"Email:": "Email:",
"Plan:": "Πρόγραμμα:",
"Amount:": "Ποσό:",
"Subscription ID:": "ID συνδρομής:",
"Go to Library": "Μετάβαση στη βιβλιοθήκη",
"Need help? Contact our support team at support@readest.com": "Χρειάζεστε βοήθεια; Επικοινωνήστε με την υποστήριξη στο support@readest.com",
"Free Plan": "Δωρεάν πρόγραμμα",
@@ -609,5 +604,164 @@
"Contain": "Περιέχει",
"{{number}} pages left in chapter": "Μένουν {{number}} σελίδες στο κεφάλαιο",
"Device": "Συσκευή",
"E-Ink Mode": "Λειτουργία E-Ink"
"E-Ink Mode": "Λειτουργία E-Ink",
"Highlight Colors": "Χρώματα επισήμανσης",
"Auto Screen Brightness": "Αυτόματη φωτεινότητα οθόνης",
"Pagination": "Σελιδοποίηση",
"Disable Double Tap": "Απενεργοποίηση διπλού ταπ",
"Tap to Paginate": "Ταπ για σελιδοποίηση",
"Click to Paginate": "Κλικ για σελιδοποίηση",
"Tap Both Sides": "Ταπ και στις δύο πλευρές",
"Click Both Sides": "Κλικ και στις δύο πλευρές",
"Swap Tap Sides": "Ανταλλαγή πλευρών ταπ",
"Swap Click Sides": "Ανταλλαγή πλευρών κλικ",
"Source and Translated": "Πηγή και Μεταφρασμένο",
"Translated Only": "Μόνο Μεταφρασμένο",
"Source Only": "Μόνο πηγή",
"TTS Text": "Κείμενο TTS",
"The book file is corrupted": "Το αρχείο του βιβλίου είναι κατεστραμμένο",
"The book file is empty": "Το αρχείο του βιβλίου είναι κενό",
"Failed to open the book file": "Αποτυχία ανοίγματος του αρχείου βιβλίου",
"On-Demand Purchase": "Αγορά κατόπιν αιτήματος",
"Full Customization": "Πλήρης προσαρμογή",
"Lifetime Plan": "Σχέδιο ζωής",
"One-Time Payment": "Εφάπαξ πληρωμή",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Κάντε μια εφάπαξ πληρωμή για να απολαύσετε διαρκή πρόσβαση σε συγκεκριμένες δυνατότητες σε όλες τις συσκευές. Αγοράστε συγκεκριμένες δυνατότητες ή υπηρεσίες μόνο όταν τις χρειάζεστε.",
"Expand Cloud Sync Storage": "Επέκταση αποθήκευσης συγχρονισμού Cloud",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Επεκτείνετε την αποθήκευση cloud σας για πάντα με μια εφάπαξ αγορά. Κάθε επιπλέον αγορά προσθέτει περισσότερο χώρο.",
"Unlock All Customization Options": "Ξεκλειδώστε όλες τις επιλογές προσαρμογής",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Ξεκλειδώστε επιπλέον θέματα, γραμματοσειρές, επιλογές διάταξης και δυνατότητες ανάγνωσης, μεταφραστές, υπηρεσίες αποθήκευσης cloud.",
"Purchase Successful!": "Η αγορά ήταν επιτυχής!",
"lifetime": "διαρκής",
"Thank you for your purchase! Your payment has been processed successfully.": "Σας ευχαριστούμε για την αγορά σας! Η πληρωμή σας έχει επεξεργαστεί με επιτυχία.",
"Order ID:": "Αριθμός παραγγελίας:",
"TTS Highlighting": "Επισήμανση TTS",
"Style": "Στυλ",
"Underline": "Υπογράμμιση",
"Strikethrough": "Διαγραμμένο",
"Squiggly": "Κυματιστό",
"Outline": "Περίγραμμα",
"Save Current Color": "Αποθήκευση τρέχουσας χρώματος",
"Quick Colors": "Γρήγορα χρώματα",
"Highlighter": "Υπογραμμιστής",
"Save Book Cover": "Αποθήκευση εξωφύλλου βιβλίου",
"Auto-save last book cover": "Αυτόματη αποθήκευση τελευταίου εξωφύλλου βιβλίου",
"Back": "Πίσω",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Το email επιβεβαίωσης στάλθηκε! Παρακαλώ έλεγξε την παλιά και τη νέα σου διεύθυνση email για να επιβεβαιώσεις την αλλαγή.",
"Failed to update email": "Αποτυχία ενημέρωσης email",
"New Email": "Νέο email",
"Your new email": "Το νέο σου email",
"Updating email ...": "Ενημέρωση email ...",
"Update email": "Ενημέρωση email",
"Current email": "Τρέχον email",
"Update Email": "Ενημέρωση email",
"All": "Όλα",
"Unable to open book": "Αδύνατη η άνοιγμα του βιβλίου",
"Punctuation": "Στίξη",
"Replace Quotation Marks": "Αντικατάσταση εισαγωγικών",
"Enabled only in vertical layout.": "Ενεργοποιημένο μόνο σε κάθετη διάταξη.",
"No Conversion": "Χωρίς μετατροπή",
"Simplified to Traditional": "Απλοπ. → Παραδ.",
"Traditional to Simplified": "Παραδ. → Απλοπ.",
"Simplified to Traditional (Taiwan)": "Απλοπ. → Παραδ. (Ταϊβάν)",
"Simplified to Traditional (Hong Kong)": "Απλοπ. → Παραδ. (Χονγκ Κονγκ)",
"Simplified to Traditional (Taiwan), with phrases": "Απλοπ. → Παραδ. (Ταϊβάν • φράσεις)",
"Traditional (Taiwan) to Simplified": "Παραδ. (Ταϊβάν) → Απλοπ.",
"Traditional (Hong Kong) to Simplified": "Παραδ. (Χονγκ Κονγκ) → Απλοπ.",
"Traditional (Taiwan) to Simplified, with phrases": "Παραδ. (Ταϊβάν • φράσεις) → Απλοπ.",
"Convert Simplified and Traditional Chinese": "Μετατροπή Απλοπ./Παραδ. Κινέζικων",
"Convert Mode": "Λειτουργία μετατροπής",
"Failed to auto-save book cover for lock screen: {{error}}": "Αποτυχία αυτόματης αποθήκευσης εξωφύλλου βιβλίου για την οθόνη κλειδώματος: {{error}}",
"Download from Cloud": "Λήψη από το Cloud",
"Upload to Cloud": "Ανέβασμα στο Cloud",
"Clear Custom Fonts": "Καθαρισμός προσαρμοσμένων γραμματοσειρών",
"Columns": "Στήλες",
"OPDS Catalogs": "Κατάλογοι OPDS",
"Adding LAN addresses is not supported in the web app version.": "Η προσθήκη διευθύνσεων LAN δεν υποστηρίζεται στην έκδοση web της εφαρμογής.",
"Invalid OPDS catalog. Please check the URL.": "Μη έγκυρος κατάλογος OPDS. Ελέγξτε το URL.",
"Browse and download books from online catalogs": "Περιηγηθείτε και κατεβάστε βιβλία από online καταλόγους",
"My Catalogs": "Οι Κατάλογοί μου",
"Add Catalog": "Προσθήκη Καταλόγου",
"No catalogs yet": "Δεν υπάρχουν ακόμα κατάλογοι",
"Add your first OPDS catalog to start browsing books": "Προσθέστε τον πρώτο σας κατάλογο OPDS για να αρχίσετε την περιήγηση.",
"Add Your First Catalog": "Προσθήκη Πρώτου Καταλόγου",
"Browse": "Περιήγηση",
"Popular Catalogs": "Δημοφιλείς Κατάλογοι",
"Add": "Προσθήκη",
"Add OPDS Catalog": "Προσθήκη Καταλόγου OPDS",
"Catalog Name": "Όνομα Καταλόγου",
"My Calibre Library": "Η Βιβλιοθήκη μου στο Calibre",
"OPDS URL": "OPDS URL",
"Username (optional)": "Όνομα χρήστη (προαιρετικό)",
"Password (optional)": "Κωδικός πρόσβασης (προαιρετικό)",
"Description (optional)": "Περιγραφή (προαιρετική)",
"A brief description of this catalog": "Μια σύντομη περιγραφή του καταλόγου",
"Validating...": "Γίνεται έλεγχος...",
"View All": "Προβολή όλων",
"Forward": "Μπροστά",
"OPDS Catalog": "Κατάλογος OPDS",
"Home": "Αρχική",
"Library": "Βιβλιοθήκη",
"{{count}} items_one": "{{count}} στοιχείο",
"{{count}} items_other": "{{count}} στοιχεία",
"Download completed": "Η λήψη ολοκληρώθηκε",
"Download failed": "Η λήψη απέτυχε",
"Open Access": "Ανοιχτή πρόσβαση",
"Borrow": "Δανεισμός",
"Buy": "Αγορά",
"Subscribe": "Συνδρομή",
"Sample": "Δείγμα",
"Download": "Λήψη",
"Open & Read": "Άνοιγμα & Ανάγνωση",
"Tags": "Ετικέτες",
"Tag": "Ετικέτα",
"First": "Πρώτο",
"Previous": "Προηγούμενο",
"Next": "Επόμενο",
"Last": "Τελευταίο",
"Cannot Load Page": "Δεν είναι δυνατή η φόρτωση της σελίδας",
"An error occurred": "Προέκυψε σφάλμα",
"Online Library": "Online Βιβλιοθήκη",
"URL must start with http:// or https://": "Το URL πρέπει να ξεκινά με http:// ή https://",
"Title, Author, Tag, etc...": "Τίτλος, Συγγραφέας, Ετικέτα, κ.λπ...",
"Query": "Ερώτημα",
"Subject": "Θέμα",
"Enter {{terms}}": "Εισαγάγετε {{terms}}",
"No search results found": "Δεν βρέθηκαν αποτελέσματα αναζήτησης",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Αποτυχία φόρτωσης ροής OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Αναζήτηση στο {{title}}",
"Manage Storage": "Διαχείριση αποθήκευσης",
"Failed to load files": "Αποτυχία φόρτωσης αρχείων",
"Deleted {{count}} file(s)_one": "Διαγράφηκε {{count}} αρχείο",
"Deleted {{count}} file(s)_other": "Διαγράφηκαν {{count}} αρχεία",
"Failed to delete {{count}} file(s)_one": "Αποτυχία διαγραφής {{count}} αρχείου",
"Failed to delete {{count}} file(s)_other": "Αποτυχία διαγραφής {{count}} αρχείων",
"Failed to delete files": "Αποτυχία διαγραφής αρχείων",
"Total Files": "Σύνολο αρχείων",
"Total Size": "Συνολικό μέγεθος",
"Quota": "Όριο",
"Used": "Χρησιμοποιήθηκε",
"Files": "Αρχεία",
"Search files...": "Αναζήτηση αρχείων...",
"Newest First": "Νεότερα πρώτα",
"Oldest First": "Παλαιότερα πρώτα",
"Largest First": "Μεγαλύτερα πρώτα",
"Smallest First": "Μικρότερα πρώτα",
"Name A-Z": "Όνομα AZ",
"Name Z-A": "Όνομα ZA",
"{{count}} selected_one": "{{count}} επιλεγμένο",
"{{count}} selected_other": "{{count}} επιλεγμένα",
"Delete Selected": "Διαγραφή επιλεγμένων",
"Created": "Δημιουργήθηκε",
"No files found": "Δεν βρέθηκαν αρχεία",
"No files uploaded yet": "Δεν έχουν ανέβει αρχεία ακόμη",
"files": "αρχεία",
"Page {{current}} of {{total}}": "Σελίδα {{current}} από {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Σίγουρα θέλετε να διαγράψετε {{count}} επιλεγμένο αρχείο;",
"Are you sure to delete {{count}} selected file(s)?_other": "Σίγουρα θέλετε να διαγράψετε {{count}} επιλεγμένα αρχεία;",
"Cloud Storage Usage": "Χρήση αποθήκευσης cloud",
"Rename Group": "Μετονομασία ομάδας",
"From Directory": "Από κατάλογο",
"Successfully imported {{count}} book(s)_one": "Επιτυχής εισαγωγή 1 βιβλίου",
"Successfully imported {{count}} book(s)_other": "Επιτυχής εισαγωγή {{count}} βιβλίων"
}
@@ -9,5 +9,15 @@
"Search in {{count}} Book(s)..._one": "Search in {{count}} book...",
"Search in {{count}} Book(s)..._other": "Search in {{count}} books...",
"{{count}} pages left in chapter_one": "{{count}} page left in chapter",
"{{count}} pages left in chapter_other": "{{count}} pages left in chapter"
"{{count}} pages left in chapter_other": "{{count}} pages left in chapter",
"Deleted {{count}} file(s)_one": "Deleted {{count}} file",
"Deleted {{count}} file(s)_other": "Deleted {{count}} files",
"Failed to delete {{count}} file(s)_one": "Failed to delete {{count}} file",
"Failed to delete {{count}} file(s)_other": "Failed to delete {{count}} files",
"{{count}} selected_one": "{{count}} selected",
"{{count}} selected_other": "{{count}} selected",
"Are you sure to delete {{count}} selected file(s)?_one": "Are you sure to delete {{count}} selected file?",
"Are you sure to delete {{count}} selected file(s)?_other": "Are you sure to delete {{count}} selected files?",
"Successfully imported {{count}} book(s)_one": "Successfully imported {{count}} book",
"Successfully imported {{count}} book(s)_other": "Successfully imported {{count}} books"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Modo automático",
"Behavior": "Comportamiento",
"Book": "Libro",
"Book Cover": "Portada del libro",
"Bookmark": "Marcador",
"Cancel": "Cancelar",
"Chapter": "Capítulo",
@@ -221,7 +220,7 @@
"No supported files found. Supported formats: {{formats}}": "No se encontraron archivos compatibles. Formatos compatibles: {{formats}}",
"Drop to Import Books": "Soltar para importar libros",
"Custom": "Personalizado",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Todo el mundo es un escenario,\nY todos los hombres y mujeres son meros actores;\nTienen sus salidas y sus entradas,\nY un hombre en su tiempo interpreta muchos papeles,\nSus actos son siete edades.\n\n—William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Todo el mundo es un escenario,\nY todos los hombres y mujeres son meros actores;\nTienen sus salidas y sus entradas,\nY un hombre en su tiempo interpreta muchos papeles,\nSus actos son siete edades.\n\n— William Shakespeare",
"Custom Theme": "Tema personalizado",
"Theme Name": "Nombre del tema",
"Text Color": "Color del texto",
@@ -248,9 +247,9 @@
"Auto Import on File Open": "Importación automática al abrir archivo",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "No se detectaron capítulos.",
"Failed to parse the EPUB file.": "Error al analizar el archivo EPUB.",
"This book format is not supported.": "Este formato de libro no es compatible.",
"No chapters detected": "No se detectaron capítulos",
"Failed to parse the EPUB file": "Error al analizar el archivo EPUB",
"This book format is not supported": "Este formato de libro no es compatible",
"Unable to fetch the translation. Please log in first and try again.": "No se puede obtener la traducción. Por favor, inicia sesión primero e inténtalo de nuevo.",
"Group": "Grupo",
"Always on Top": "Siempre en la parte superior",
@@ -287,8 +286,6 @@
"(from 'As You Like It', Act II)": "(de 'Como gustéis', Acto II)",
"Link Color": "Color del enlace",
"Volume Keys for Page Flip": "Botones de volumen para pasar página",
"Clicks for Page Flip": "Toques para pasar página",
"Swap Clicks Area": "Invertir zonas de toque",
"Screen": "Pantalla",
"Orientation": "Orientación",
"Portrait": "Vertical",
@@ -325,7 +322,6 @@
"Disable Translation": "Deshabilitar traducción",
"Scroll": "Desplazar",
"Overlap Pixels": "Superponer píxeles",
"Click": "Click",
"System Language": "Idioma del sistema",
"Security": "Seguridad",
"Allow JavaScript": "Permitir JavaScript",
@@ -396,7 +392,6 @@
"Email:": "Correo electrónico:",
"Plan:": "Plan:",
"Amount:": "Importe:",
"Subscription ID:": "ID de suscripción:",
"Go to Library": "Ir a la biblioteca",
"Need help? Contact our support team at support@readest.com": "¿Necesitas ayuda? Contacta con nuestro equipo de soporte en support@readest.com",
"Free Plan": "Plan gratuito",
@@ -613,5 +608,170 @@
"Contain": "Contener",
"{{number}} pages left in chapter": "{{number}} páginas restantes en el capítulo",
"Device": "Dispositivo",
"E-Ink Mode": "Modo E-Ink"
"E-Ink Mode": "Modo E-Ink",
"Highlight Colors": "Colores de resaltado",
"Auto Screen Brightness": "Brillo automático de pantalla",
"Pagination": "Paginación",
"Disable Double Tap": "Deshabilitar Doble Toque",
"Tap to Paginate": "Tocar para Paginación",
"Click to Paginate": "Hacer Clic para Paginación",
"Tap Both Sides": "Tocar Ambos Lados",
"Click Both Sides": "Hacer Clic en Ambos Lados",
"Swap Tap Sides": "Intercambiar Lados de Toque",
"Swap Click Sides": "Intercambiar Lados de Clic",
"Source and Translated": "Fuente y Traducido",
"Translated Only": "Solo Traducido",
"Source Only": "Solo Fuente",
"TTS Text": "TTS Texto",
"The book file is corrupted": "El archivo del libro está dañado",
"The book file is empty": "El archivo del libro está vacío",
"Failed to open the book file": "Error al abrir el archivo del libro",
"On-Demand Purchase": "Compra bajo demanda",
"Full Customization": "Personalización Completa",
"Lifetime Plan": "Plan de por vida",
"One-Time Payment": "Pago Único",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Realiza un solo pago para disfrutar de acceso de por vida a funciones específicas en todos los dispositivos. Compra funciones o servicios específicos solo cuando los necesites.",
"Expand Cloud Sync Storage": "Expandir Almacenamiento de Sincronización en la Nube",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Expande tu almacenamiento en la nube para siempre con una compra única. Cada compra adicional agrega más espacio.",
"Unlock All Customization Options": "Desbloquear Todas las Opciones de Personalización",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Desbloquear temas adicionales, fuentes, opciones de diseño y funciones de lectura en voz alta, traductores, servicios de almacenamiento en la nube.",
"Purchase Successful!": "¡Compra Exitosa!",
"lifetime": "de por vida",
"Thank you for your purchase! Your payment has been processed successfully.": "¡Gracias por tu compra! Tu pago se ha procesado correctamente.",
"Order ID:": "ID de pedido:",
"TTS Highlighting": "TTS Resaltado",
"Style": "Estilo",
"Underline": "Subrayado",
"Strikethrough": "Tachado",
"Squiggly": "Ondulado",
"Outline": "Contorno",
"Save Current Color": "Guardar Color Actual",
"Quick Colors": "Colores Rápidos",
"Highlighter": "Resaltador",
"Save Book Cover": "Guardar Portada del Libro",
"Auto-save last book cover": "Guardar automáticamente la última portada del libro",
"Back": "Atrás",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "¡Correo de confirmación enviado! Por favor, revisa tus direcciones de correo electrónico antigua y nueva para confirmar el cambio.",
"Failed to update email": "Error al actualizar el correo electrónico",
"New Email": "Nuevo correo electrónico",
"Your new email": "Tu nuevo correo electrónico",
"Updating email ...": "Actualizando correo electrónico ...",
"Update email": "Actualizar correo electrónico",
"Current email": "Correo electrónico actual",
"Update Email": "Actualizar correo electrónico",
"All": "Todos",
"Unable to open book": "No se puede abrir el libro",
"Punctuation": "Signos de puntuación",
"Replace Quotation Marks": "Reemplazar comillas",
"Enabled only in vertical layout.": "Habilitado solo en diseño vertical.",
"No Conversion": "Sin conversión",
"Simplified to Traditional": "Simpl. → Trad.",
"Traditional to Simplified": "Trad. → Simpl.",
"Simplified to Traditional (Taiwan)": "Simpl. → Trad. (Taiwan)",
"Simplified to Traditional (Hong Kong)": "Simpl. → Trad. (Hong Kong)",
"Simplified to Traditional (Taiwan), with phrases": "Simpl. → Trad. (Taiwan • frases)",
"Traditional (Taiwan) to Simplified": "Trad. (Taiwan) → Simpl.",
"Traditional (Hong Kong) to Simplified": "Trad. (Hong Kong) → Simpl.",
"Traditional (Taiwan) to Simplified, with phrases": "Trad. (Taiwan • frases) → Simpl.",
"Convert Simplified and Traditional Chinese": "Convertir chino Simpl./Trad.",
"Convert Mode": "Modo de conversión",
"Failed to auto-save book cover for lock screen: {{error}}": "Error al guardar automáticamente la portada del libro para la pantalla de bloqueo: {{error}}",
"Download from Cloud": "Descargar desde la nube",
"Upload to Cloud": "Subir a la nube",
"Clear Custom Fonts": "Limpiar fuentes personalizadas",
"Columns": "Columnas",
"OPDS Catalogs": "Catálogos OPDS",
"Adding LAN addresses is not supported in the web app version.": "La versión web no permite añadir direcciones LAN.",
"Invalid OPDS catalog. Please check the URL.": "Catálogo OPDS no válido. Por favor, comprueba la URL.",
"Browse and download books from online catalogs": "Explora y descarga libros de catálogos en línea",
"My Catalogs": "Mis catálogos",
"Add Catalog": "Añadir catálogo",
"No catalogs yet": "Aún no hay catálogos",
"Add your first OPDS catalog to start browsing books": "Añade tu primer catálogo OPDS para empezar a explorar libros.",
"Add Your First Catalog": "Añadir primer catálogo",
"Browse": "Explorar",
"Popular Catalogs": "Catálogos populares",
"Add": "Añadir",
"Add OPDS Catalog": "Añadir catálogo OPDS",
"Catalog Name": "Nombre del catálogo",
"My Calibre Library": "Mi biblioteca Calibre",
"OPDS URL": "URL OPDS",
"Username (optional)": "Usuario (opcional)",
"Password (optional)": "Contraseña (opcional)",
"Description (optional)": "Descripción (opcional)",
"A brief description of this catalog": "Una breve descripción de este catálogo",
"Validating...": "Validando...",
"View All": "Ver todo",
"Forward": "Adelante",
"OPDS Catalog": "Catálogo OPDS",
"Home": "Inicio",
"Library": "Biblioteca",
"{{count}} items_one": "{{count}} elemento",
"{{count}} items_many": "{{count}} elementos",
"{{count}} items_other": "{{count}} elementos",
"Download completed": "Descarga completa",
"Download failed": "Error en la descarga",
"Open Access": "Acceso abierto",
"Borrow": "Prestar",
"Buy": "Comprar",
"Subscribe": "Suscribirse",
"Sample": "Muestra",
"Download": "Descargar",
"Open & Read": "Abrir y leer",
"Tags": "Etiquetas",
"Tag": "Etiqueta",
"First": "Primero",
"Previous": "Anterior",
"Next": "Siguiente",
"Last": "Último",
"Cannot Load Page": "No se puede cargar la página",
"An error occurred": "Ocurrió un error",
"Online Library": "Biblioteca en línea",
"URL must start with http:// or https://": "URL debe comenzar con http:// o https://",
"Title, Author, Tag, etc...": "Titulo, Autor, Etiqueta, etc...",
"Query": "Consulta",
"Subject": "Asunto",
"Enter {{terms}}": "Ingrese {{terms}}",
"No search results found": "No se encontraron resultados de búsqueda",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Error al cargar el feed OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Buscar en {{title}}",
"Manage Storage": "Administrar almacenamiento",
"Failed to load files": "Error al cargar los archivos",
"Deleted {{count}} file(s)_one": "Se eliminó {{count}} archivo",
"Deleted {{count}} file(s)_many": "Se eliminaron {{count}} archivos",
"Deleted {{count}} file(s)_other": "Se eliminaron {{count}} archivos",
"Failed to delete {{count}} file(s)_one": "Error al eliminar {{count}} archivo",
"Failed to delete {{count}} file(s)_many": "Error al eliminar {{count}} archivos",
"Failed to delete {{count}} file(s)_other": "Error al eliminar {{count}} archivos",
"Failed to delete files": "Error al eliminar los archivos",
"Total Files": "Total de archivos",
"Total Size": "Tamaño total",
"Quota": "Cuota",
"Used": "Usado",
"Files": "Archivos",
"Search files...": "Buscar archivos...",
"Newest First": "Más nuevos primero",
"Oldest First": "Más antiguos primero",
"Largest First": "Más grandes primero",
"Smallest First": "Más pequeños primero",
"Name A-Z": "Nombre AZ",
"Name Z-A": "Nombre ZA",
"{{count}} selected_one": "{{count}} seleccionado",
"{{count}} selected_many": "{{count}} seleccionados",
"{{count}} selected_other": "{{count}} seleccionados",
"Delete Selected": "Eliminar seleccionados",
"Created": "Creado",
"No files found": "No se encontraron archivos",
"No files uploaded yet": "Aún no se han subido archivos",
"files": "archivos",
"Page {{current}} of {{total}}": "Página {{current}} de {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "¿Seguro que deseas eliminar {{count}} archivo seleccionado?",
"Are you sure to delete {{count}} selected file(s)?_many": "¿Seguro que deseas eliminar {{count}} archivos seleccionados?",
"Are you sure to delete {{count}} selected file(s)?_other": "¿Seguro que deseas eliminar {{count}} archivos seleccionados?",
"Cloud Storage Usage": "Uso de almacenamiento en la nube",
"Rename Group": "Renombrar grupo",
"From Directory": "Desde el directorio",
"Successfully imported {{count}} book(s)_one": "Se importó correctamente 1 libro",
"Successfully imported {{count}} book(s)_many": "Se importaron correctamente {{count}} libros",
"Successfully imported {{count}} book(s)_other": "Se importaron correctamente {{count}} libros"
}
@@ -0,0 +1,767 @@
{
"(detected)": "(تشخیص‌داده‌شده)",
"About Readest": "درباره‌ی Readest",
"Add your notes here...": "یادداشت‌های خود را اینجا اضافه کنید...",
"Animation": "انیمیشن",
"Annotate": "حاشیه‌نویسی",
"Apply": "اِعمال",
"Auto Mode": "حالت خودکار",
"Behavior": "رفتار",
"Book": "کتاب",
"Bookmark": "نشانک",
"Cancel": "لغو",
"Chapter": "فصل",
"Cherry": "گیلاسی",
"Color": "رنگ",
"Confirm": "تأیید",
"Confirm Deletion": "تأیید حذف",
"Copied to notebook": "به دفترچه یادداشت کپی شد",
"Copy": "کپی",
"Dark Mode": "حالت تاریک",
"Default": "پیش‌فرض",
"Default Font": "فونت پیش‌فرض",
"Default Font Size": "اندازه فونت پیش‌فرض",
"Delete": "حذف",
"Delete Highlight": "حذف هایلایت",
"Dictionary": "فرهنگ لغت",
"Download Readest": "دانلود Readest",
"Edit": "ویرایش",
"Excerpts": "گزیده‌ها",
"Failed to import book(s): {{filenames}}": "وارد کردن کتاب(ها) «{{filenames}}» ناموفق بود.",
"Fast": "سریع",
"Font": "فونت",
"Font & Layout": "فونت و چیدمان",
"Font Face": "نمای فونت",
"Font Family": "خانواده‌ی فونت",
"Font Size": "اندازه فونت",
"Full Justification": "تراز کامل",
"Global Settings": "تنظیمات سراسری",
"Go Back": "بازگشت",
"Go Forward": "رفتن به جلو",
"Grass": "چمنی",
"Gray": "خاکستری",
"Gruvbox": "Gruvbox",
"Highlight": "هایلایت",
"Horizontal Direction": "جهت افقی",
"Hyphenation": "تقسیم‌بندی کلمات",
"Import Books": "وارد کردن کتاب‌ها",
"Layout": "چیدمان",
"Light Mode": "حالت روشن",
"Loading...": "در حال بارگیری...",
"Logged in": "وارد شده‌اید",
"Logged in as {{userDisplayName}}": "شما با نام «{{userDisplayName}}» وارد شده‌اید",
"Match Case": "تطابق حروف بزرگ/کوچک",
"Match Diacritics": "تطابق اعراب",
"Match Whole Words": "تطابق کل کلمات",
"Maximum Number of Columns": "حداکثر تعداد ستون‌ها",
"Minimum Font Size": "حداقل اندازه فونت",
"Monospace Font": "فونت تک‌فاصله (Monospace)",
"More Info": "اطلاعات بیشتر",
"Nord": "Nord",
"Notebook": "دفترچه یادداشت",
"Notes": "یادداشت‌ها",
"Open": "باز کردن",
"Original Text": "متن اصلی",
"Page": "صفحه",
"Paging Animation": "انیمیشن ورق زدن",
"Paragraph": "پاراگراف",
"Published": "منتشر شده",
"Publisher": "ناشر",
"Reading Progress Synced": "وضعیت مطالعه همگام‌سازی شد",
"Reload Page": "بارگذاری مجدد صفحه",
"Reveal in File Explorer": "نمایش در کاوشگر فایل",
"Reveal in Finder": "نمایش در Finder",
"Reveal in Folder": "نمایش در پوشه",
"Sans-Serif Font": "فونت Sans-Serif",
"Save": "ذخیره",
"Scrolled Mode": "حالت پیمایشی",
"Search": "جستجو",
"Search Books...": "جستجوی کتاب‌ها...",
"Search...": "جستجو...",
"Select Book": "انتخاب کتاب",
"Select Books": "انتخاب کتاب‌ها",
"Sepia": "سپیا",
"Serif Font": "فونت Serif",
"Show Book Details": "نمایش جزئیات کتاب",
"Sidebar": "نوار کناری",
"Sign In": "ورود",
"Sign Out": "خروج",
"Sky": "آسمانی",
"Slow": "آهسته",
"Solarized": "آفتابی",
"Speak": "خواندن",
"Subjects": "موضوعات",
"System Fonts": "فونت‌های سیستم",
"Theme Color": "رنگ تم",
"Theme Mode": "حالت تم",
"Translate": "ترجمه",
"Translated Text": "متن ترجمه‌شده",
"Unknown": "ناشناخته",
"Untitled": "بدون عنوان",
"Updated": "به‌روزرسانی شده",
"Version {{version}}": "نسخه‌ی {{version}}",
"Vertical Direction": "جهت عمودی",
"Welcome to your library. You can import your books here and read them anytime.": "به کتابخانه‌تان خوش آمدید. می‌توانید کتاب‌هایتان را اینجا وارد کنید و هر زمان خواستید آن‌ها را بخوانید.",
"Wikipedia": "ویکی‌پدیا",
"Writing Mode": "حالت نوشتن",
"Your Library": "کتابخانه‌ی شما",
"TTS not supported for PDF": "قابلیت تبدیل متن به گفتار برای PDF پشتیبانی نمی‌شود",
"Override Book Font": "جایگزینی فونت کتاب",
"Apply to All Books": "اِعمال برای همه‌ی کتاب‌ها",
"Apply to This Book": "اِعمال برای این کتاب",
"Unable to fetch the translation. Try again later.": "نمی‌توان ترجمه را دریافت کرد. بعداً دوباره تلاش کنید.",
"Check Update": "بررسی به‌روزرسانی",
"Already the latest version": "آخرین نسخه نصب است.",
"Book Details": "جزئیات کتاب",
"From Local File": "از فایل محلی",
"TOC": "فهرست مطالب",
"Table of Contents": "فهرست مطالب",
"Book uploaded: {{title}}": "کتاب «{{title}}» آپلود شد.",
"Failed to upload book: {{title}}": "آپلود کتاب «{{title}}» ناموفق بود.",
"Book downloaded: {{title}}": "کتاب «{{title}}» دانلود شد.",
"Failed to download book: {{title}}": "دانلود کتاب «{{title}}» ناموفق بود.",
"Upload Book": "بارگذاری کتاب",
"Auto Upload Books to Cloud": "بارگذاری خودکار کتاب‌ها در فضای ابری",
"Book deleted: {{title}}": "کتاب «{{title}}» حذف شد.",
"Failed to delete book: {{title}}": "حذف کتاب «{{title}}» ناموفق بود.",
"Check Updates on Start": "بررسی به‌روزرسانی‌ها هنگام شروع",
"Insufficient storage quota": "سهمیه‌ی ذخیره‌سازی کافی نیست",
"Font Weight": "وزن فونت",
"Line Spacing": "فاصله‌ی خطوط",
"Word Spacing": "فاصله‌ی کلمات",
"Letter Spacing": "فاصله‌ی حروف",
"Text Indent": "تورفتگی متن",
"Paragraph Margin": "حاشیه‌ی پاراگراف",
"Override Book Layout": "جایگزینی چیدمان کتاب",
"Untitled Group": "گروه بدون عنوان",
"Group Books": "گروه‌بندی کتاب‌ها",
"Remove From Group": "حذف از گروه",
"Create New Group": "ایجاد گروه جدید",
"Deselect Book": "لغو انتخاب کتاب",
"Download Book": "دانلود کتاب",
"Deselect Group": "لغو انتخاب گروه",
"Select Group": "انتخاب گروه",
"Keep Screen Awake": "روشن نگه داشتن صفحه",
"Email address": "آدرس ایمیل",
"Your Password": "رمز عبور شما",
"Your email address": "آدرس ایمیل شما",
"Your password": "رمز عبور شما",
"Sign in": "ورود",
"Signing in...": "در حال ورود...",
"Sign in with {{provider}}": "ورود با {{provider}}",
"Already have an account? Sign in": "اگر از قبل حساب دارید، وارد شوید",
"Create a Password": "ایجاد رمز عبور",
"Sign up": "ثبت‌نام",
"Signing up...": "در حال ثبت‌نام...",
"Don't have an account? Sign up": "حساب کاربری ندارید؟ ثبت‌نام کنید",
"Check your email for the confirmation link": "ایمیل خود را برای لینک تأیید بررسی کنید",
"Signing in ...": "در حال ورود ...",
"Send a magic link email": "ارسال لینک جادویی به ایمیل",
"Check your email for the magic link": "ایمیل خود را برای دریافت لینک جادویی بررسی کنید",
"Send reset password instructions": "ارسال دستورالعمل بازنشانی رمز عبور",
"Sending reset instructions ...": "در حال ارسال دستورالعمل بازنشانی...",
"Forgot your password?": "رمز عبور خود را فراموش کرده‌اید؟",
"Check your email for the password reset link": "ایمیل خود را برای لینک بازنشانی رمز عبور بررسی کنید",
"New Password": "رمز عبور جدید",
"Your new password": "رمز عبور جدید شما",
"Update password": "به‌روزرسانی رمز عبور",
"Updating password ...": "در حال به‌روزرسانی رمز عبور...",
"Your password has been updated": "رمز عبور شما به‌روزرسانی شد",
"Phone number": "شماره تلفن",
"Your phone number": "شماره تلفن شما",
"Token": "توکن",
"Your OTP token": "کد یک‌بارمصرف شما",
"Verify token": "تأیید کد",
"Sign in with Google": "ورود با گوگل",
"Sign in with Apple": "ورود با اپل",
"Sign in with GitHub": "ورود با گیت‌هاب",
"Account": "حساب",
"Failed to delete user. Please try again later.": "حذف کاربر ناموفق بود. لطفاً بعداً دوباره تلاش کنید.",
"Community Support": "پشتیبانی جامعه",
"Priority Support": "پشتیبانی اولویت‌دار",
"Loading profile...": "در حال بارگذاری پروفایل...",
"Delete Account": "حذف حساب",
"Delete Your Account?": "حذف حساب کاربری؟",
"This action cannot be undone. All your data in the cloud will be permanently deleted.": "این عمل غیرقابل بازگشت است. تمام داده‌های شما در فضای ابری به‌طور دائمی حذف خواهند شد.",
"Delete Permanently": "حذف دائمی",
"RTL Direction": "جهت راست‌به‌چپ",
"Maximum Column Height": "حداکثر ارتفاع ستون",
"Maximum Column Width": "حداکثر عرض ستون",
"Continuous Scroll": "پیمایش مداوم",
"Fullscreen": "تمام‌صفحه",
"No supported files found. Supported formats: {{formats}}": "فایل پشتیبانی‌شده‌ای یافت نشد. فرمت‌های قابل پشتیبانی: {{formats}}",
"Drop to Import Books": "برای وارد کردن کتاب‌ها، فایل را رها کنید",
"Custom": "سفارشی",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "تمام دنیا صحنه‌ای است،\nو همه‌ی مردان و زنان تنها بازیگرانی بیش نیستند؛\nآن‌ها ورود و خروج خود را دارند،\nو هر آدمی در طول زمان نقش‌های بسیاری ایفا می‌کند،\nاعمال او هفت دوره‌اند.\n\n— ویلیام شکسپیر",
"Custom Theme": "تم سفارشی",
"Theme Name": "نام تم",
"Text Color": "رنگ متن",
"Background Color": "رنگ پس‌زمینه",
"Preview": "پیش‌نمایش",
"Contrast": "کنتراست",
"Sunset": "غروب",
"Double Border": "حاشیه‌ی دوبل",
"Border Color": "رنگ حاشیه",
"Border Frame": "قاب حاشیه",
"Show Header": "نمایش سربرگ",
"Show Footer": "نمایش پاورقی",
"Small": "کوچک",
"Large": "بزرگ",
"Auto": "خودکار",
"Language": "زبان",
"No annotations to export": "هیچ حاشیه‌نویسی‌ای برای خروجی‌گرفتن وجود ندارد",
"Author": "نویسنده",
"Exported from Readest": "خروجی‌گرفته‌شده از Readest",
"Highlights & Annotations": "هایلایت‌ها و حاشیه‌نویسی‌ها",
"Note": "یادداشت",
"Copied to clipboard": "به کلیپ‌بورد کپی شد",
"Export Annotations": "خروجی‌گرفتن از حاشیه‌نویسی‌ها",
"Auto Import on File Open": "وارد کردن خودکار هنگام باز کردن فایل",
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected": "هیچ فصلی یافت نشد",
"Failed to parse the EPUB file": "خطا در پردازش فایل EPUB",
"This book format is not supported": "این فرمت کتاب پشتیبانی نمی‌شود",
"Unable to fetch the translation. Please log in first and try again.": "نمی‌توان ترجمه را دریافت کرد. لطفاً ابتدا وارد شوید و دوباره تلاش کنید.",
"Group": "گروه",
"Always on Top": "همیشه در بالاترین لایه پنجره‌ها",
"No Timeout": "بدون محدودیت زمانی",
"{{value}} minute": "{{value}} دقیقه",
"{{value}} minutes": "{{value}} دقیقه",
"{{value}} hour": "{{value}} ساعت",
"{{value}} hours": "{{value}} ساعت",
"CJK Font": "فونت CJK",
"Clear Search": "پاک کردن جستجو",
"Header & Footer": "سربرگ و پاورقی",
"Apply also in Scrolled Mode": "همچنین در حالت پیمایشی اِعمال شود",
"A new version of Readest is available!": "نسخه‌ی جدیدی از Readest در دسترس است!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "نسخه‌ی {{newVersion}} Readest در دسترس است (نسخه‌ی نصب‌شده: {{currentVersion}}).",
"Download and install now?": "الان دانلود و نصب شود؟",
"Downloading {{downloaded}} of {{contentLength}}": "در حال دانلود {{downloaded}} از {{contentLength}}",
"Download finished": "دانلود تکمیل شد",
"DOWNLOAD & INSTALL": "دانلود و نصب",
"Changelog": "لیست تغییرات",
"Software Update": "به‌روزرسانی نرم‌افزار",
"Title": "عنوان",
"Date Read": "تاریخ مطالعه",
"Date Added": "تاریخ اضافه‌شدن",
"Format": "نوع فرمت",
"Ascending": "صعودی",
"Descending": "نزولی",
"Sort by...": "مرتب‌سازی بر اساس...",
"Added": "افزوده شده",
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
"Description": "توضیحات",
"No description available": "هیچ توضیحی موجود نیست",
"List": "لیست",
"Grid": "جدول",
"(from 'As You Like It', Act II)": "(از «همان‌طور که دوست دارید»، پرده‌ی دوم)",
"Link Color": "رنگ لینک",
"Volume Keys for Page Flip": "استفاده از کلیدهای صدا برای ورق زدن صفحه",
"Screen": "صفحه",
"Orientation": "جهت",
"Portrait": "عمودی",
"Landscape": "افقی",
"Open Last Book on Start": "باز کردن آخرین کتاب هنگام شروع برنامه",
"Checking for updates...": "در حال بررسی به‌روزرسانی‌ها...",
"Error checking for updates": "خطا در بررسی به‌روزرسانی‌ها",
"Details": "جزئیات",
"File Size": "حجم فایل",
"Auto Detect": "تشخیص خودکار",
"Next Section": "بخش بعدی",
"Previous Section": "بخش قبلی",
"Next Page": "صفحه‌ی بعد",
"Previous Page": "صفحه‌ی قبل",
"Are you sure to delete {{count}} selected book(s)?_one": "آیا از حذف {{count}} کتاب انتخاب‌شده مطمئن هستید؟",
"Are you sure to delete {{count}} selected book(s)?_other": "آیا از حذف {{count}} کتاب انتخاب‌شده مطمئن هستید؟",
"Are you sure to delete the selected book?": "آیا از حذف کتاب انتخاب‌شده مطمئن هستید؟",
"Deselect": "لغو انتخاب",
"Select All": "انتخاب همه",
"No translation available.": "هیچ ترجمه‌ای در دسترس نیست.",
"Translated by {{provider}}.": "ترجمه‌شده توسط {{provider}}.",
"DeepL": "DeepL",
"Google Translate": "ترجمه‌ی Google",
"Azure Translator": "مترجم Azure",
"Invert Image In Dark Mode": "معکوس کردن تصویر در حالت تاریک",
"Help improve Readest": "کمک به بهبود Readest",
"Sharing anonymized statistics": "اشتراک‌گذاری آمار ناشناس",
"Interface Language": "زبان برنامه",
"Translation": "ترجمه",
"Enable Translation": "فعال کردن ترجمه",
"Translation Service": "سرویس ترجمه",
"Translate To": "ترجمه به",
"Disable Translation": "غیرفعال کردن ترجمه",
"Scroll": "پیمایش",
"Overlap Pixels": "پیکسل‌های هم‌پوشانی",
"System Language": "زبان سیستم",
"Security": "امنیت",
"Allow JavaScript": "اجازه‌ی اجرای JavaScript",
"Enable only if you trust the file.": "فقط در صورتی فعال کنید که به فایل اعتماد دارید.",
"Sort TOC by Page": "مرتب‌سازی فهرست مطالب بر اساس صفحه",
"Search in {{count}} Book(s)..._one": "جستجو در {{count}} کتاب...",
"Search in {{count}} Book(s)..._other": "جستجو در {{count}} کتاب...",
"No notes match your search": "هیچ یادداشتی با جستجوی شما مطابقت ندارد",
"Search notes and excerpts...": "جستجوی یادداشت‌ها و گزیده‌ها...",
"Sign in to Sync": "برای همگام‌سازی وارد شوید",
"Synced at {{time}}": "همگام‌سازی شده در {{time}}",
"Never synced": "هرگز همگام‌سازی نشده",
"Show Remaining Time": "نمایش زمان باقی‌مانده",
"{{time}} min left in chapter": "{{time}} دقیقه تا انتهای فصل",
"Override Book Color": "جایگزینی رنگ کتاب",
"Login Required": "نیازمند ورود",
"Quota Exceeded": "سهمیه به پایان رسیده است",
"{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% از کاراکترهای ترجمه‌ی روزانه استفاده شده است.",
"Translation Characters": "کاراکترهای ترجمه",
"{{engine}}: {{count}} voices_one": "{{engine}}: {{count}} صدا",
"{{engine}}: {{count}} voices_other": "{{engine}}: {{count}} صدا",
"Something went wrong. Don't worry, our team has been notified and we're working on a fix.": "مشکلی پیش آمد. نگران نباشید—تیم ما مطلع شد و در حال رفع مشکل است.",
"Error Details:": "جزئیات خطا:",
"Try Again": "دوباره تلاش کنید",
"Need help?": "به کمک نیاز دارید؟",
"Contact Support": "تماس با پشتیبانی",
"Code Highlighting": "هایلایت کد",
"Enable Highlighting": "فعال کردن هایلایت",
"Code Language": "زبان کد",
"Top Margin (px)": "حاشیه‌ی بالا (px)",
"Bottom Margin (px)": "حاشیه‌ی پایین (px)",
"Right Margin (px)": "حاشیه‌ی راست (px)",
"Left Margin (px)": "حاشیه‌ی چپ (px)",
"Column Gap (%)": "فاصله‌ی ستون (%)",
"Always Show Status Bar": "نمایش همیشگی نوار وضعیت",
"Custom Content CSS": "CSS محتوای سفارشی",
"Enter CSS for book content styling...": "CSS برای استایل محتوای کتاب وارد کنید...",
"Custom Reader UI CSS": "CSS رابط خواننده‌ی سفارشی",
"Enter CSS for reader interface styling...": "CSS برای استایل رابط خواننده وارد کنید...",
"Crop": "برش",
"Book Covers": "جلد کتاب‌ها",
"Fit": "متناسب",
"Reset {{settings}}": "بازنشانی {{settings}}",
"Reset Settings": "بازنشانی تنظیمات",
"{{count}} pages left in chapter_one": "{{count}} صفحه تا انتهای فصل",
"{{count}} pages left in chapter_other": "{{count}} صفحه تا انتهای فصل",
"Show Remaining Pages": "نمایش صفحات باقی‌مانده",
"Source Han Serif CN VF": "Source Han Serif CN VF",
"Huiwen-mincho": "Huiwen-mincho",
"KingHwa_OldSong": "KingHwa_OldSong",
"Manage Subscription": "مدیریت اشتراک",
"Coming Soon": "به‌زودی",
"Upgrade to {{plan}}": "ارتقاء به {{plan}}",
"Upgrade to Plus or Pro": "ارتقاء به Plus یا Pro",
"Current Plan": "طرح فعلی",
"Plan Limits": "محدودیت‌های طرح",
"Processing your payment...": "در حال پردازش پرداخت شما...",
"Please wait while we confirm your subscription.": "لطفاً تا تأیید اشتراک صبور باشید.",
"Payment Processing": "پردازش پرداخت",
"Your payment is being processed. This usually takes a few moments.": "پرداخت شما در حال پردازش است. معمولاً چند لحظه طول می‌کشد.",
"Payment Failed": "پرداخت ناموفق",
"We couldn't process your subscription. Please try again or contact support if the issue persists.": "نتوانستیم اشتراک شما را پردازش کنیم. لطفاً دوباره تلاش کنید یا در صورت ادامه‌ی مشکل با پشتیبانی تماس بگیرید.",
"Back to Profile": "بازگشت به پروفایل",
"Subscription Successful!": "اشتراک با موفقیت فعال شد!",
"Thank you for your subscription! Your payment has been processed successfully.": "از اشتراک شما سپاس‌گزاریم! پرداخت شما با موفقیت انجام شد.",
"Email:": "ایمیل:",
"Plan:": "طرح:",
"Amount:": "مبلغ:",
"Go to Library": "رفتن به کتابخانه",
"Need help? Contact our support team at support@readest.com": "نیاز به کمک دارید؟ با تیم پشتیبانی ما به آدرس support@readest.com تماس بگیرید",
"Free Plan": "طرح رایگان",
"month": "ماه",
"AI Translations (per day)": "ترجمه‌های هوش مصنوعی (در روز)",
"Plus Plan": "طرح Plus",
"Includes All Free Plan Benefits": "شامل تمام امکانات طرح رایگان",
"Pro Plan": "طرح Pro",
"More AI Translations": "ترجمه‌های هوش مصنوعی بیشتر",
"Complete Your Subscription": "تکمیل اشتراک",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% از فضای همگام‌سازی ابری استفاده شده است.",
"Cloud Sync Storage": "ذخیره‌سازی همگام‌سازی ابری",
"Parallel Read": "خواندن موازی",
"Disable": "غیرفعال",
"Enable": "فعال",
"Upgrade to Readest Premium": "ارتقاء به Readest Premium",
"Show Source Text": "نمایش متن منبع",
"Cross-Platform Sync": "همگام‌سازی چندپلتفرمی",
"Seamlessly sync your library, progress, highlights, and notes across all your devices—never lose your place again.": "کتابخانه، وضعیت مطالعه، هایلایت‌ها و یادداشت‌های خود را بین همه‌ی دستگاه‌ها به‌صورت یکپارچه همگام‌سازی کنید — دیگر هرگز محل مطالعه‌تان را از دست ندهید.",
"Customizable Reading": "تجربه‌ی مطالعه شخصی‌سازی‌شده",
"Personalize every detail with adjustable fonts, layouts, themes, and advanced display settings for the perfect reading experience.": "همه‌چیز را با فونت‌ها، چیدمان‌ها، تِم‌ها و تنظیمات نمایش پیشرفته قابل تغییر کنید تا تجربه‌ی خواندنِ ایده‌آل خود را بسازید.",
"AI Read Aloud": "خواندن صوتی با هوش مصنوعی",
"Enjoy hands-free reading with natural-sounding AI voices that bring your books to life.": "از خواندن بدون دخالت دست با صداهای طبیعیِ هوش مصنوعی لذت ببرید که کتاب‌هایتان را جان می‌بخشند.",
"AI Translations": "ترجمه‌های هوش مصنوعی",
"Connect with fellow readers and get help fast in our friendly community channels.": "با دیگر خوانندگان ارتباط برقرار کنید و از کانال‌های دوستانه‌ی جامعه‌ی ما کمک بگیرید.",
"Unlimited AI Read Aloud Hours": "ساعات نامحدود خواندن صوتی با هوش مصنوعی",
"Listen without limits—convert as much text as you like into immersive audio.": "بدون محدودیت گوش دهید — هر میزان متن را که بخواهید می‌توانید به صدایی جذاب تبدیل کنید.",
"Unlock enhanced translation capabilities with more daily usage and advanced options.": "با استفاده‌ی روزانه‌ی بیشتر و گزینه‌های پیشرفته، قابلیت‌های ترجمه را افزایش دهید.",
"DeepL Pro Access": "دسترسی به DeepL Pro",
"Enjoy faster responses and dedicated assistance whenever you need help.": "از پاسخ‌های سریع‌تر و کمک اختصاصی هر زمان که نیاز دارید برخوردار شوید.",
"Daily translation quota reached. Upgrade your plan to continue using AI translations.": "سهمیه‌ی ترجمه‌ی روزانه به پایان رسیده است. برای ادامه‌ی استفاده از ترجمه‌های هوش مصنوعی، طرح خود را ارتقاء دهید.",
"Translate any text instantly with the power of Google, Azure, or DeepL—understand content in any language.": "هر متنی را فوراً با قدرت Google، Azure یا DeepL ترجمه کنید — محتوای هر زبانی را بفهمید.",
"Includes All Plus Plan Benefits": "شامل تمام مزایای طرح Plus",
"Early Feature Access": "دسترسی زودهنگام به ویژگی‌ها",
"Be the first to explore new features, updates, and innovations before anyone else.": "اولین نفری باشید که ویژگی‌ها، به‌روزرسانی‌ها و نوآوری‌های جدید را تجربه می‌کنید.",
"Advanced AI Tools": "ابزارهای هوش مصنوعی پیشرفته",
"Harness powerful AI tools for smarter reading, translation, and content discovery.": "از ابزارهای قدرتمند هوش مصنوعی برای خواندن هوشمندتر، ترجمه و کشف محتوا بهره ببرید.",
"Translate up to 100,000 characters daily with the most accurate translation engine available.": "تا روزانه ۱۰۰٬۰۰۰ کاراکتر را با دقیق‌ترین ابزار ترجمه، ترجمه کنید.",
"Translate up to 500,000 characters daily with the most accurate translation engine available.": "تا روزانه ۵۰۰٬۰۰۰ کاراکتر را با دقیق‌ترین ابزار ترجمه، ترجمه کنید.",
"Securely store and access your entire reading collection with up to 5 GB of cloud storage.": "مجموعه‌ی خواندن خود را تا ۵ گیگابایت به‌صورت امن در فضای ابری ذخیره و دسترسی داشته باشید.",
"Securely store and access your entire reading collection with up to 20 GB of cloud storage.": "کل مجموعه‌ی خواندن خود را تا ۲۰ گیگابایت به‌صورت ایمن در فضای ابری ذخیره و دسترسی داشته باشید.",
"Deleted cloud backup of the book: {{title}}": "نسخه‌ی پشتیبان ابری کتاب «{{title}}» حذف شد.",
"Are you sure to delete the cloud backup of the selected book?": "آیا از حذف نسخه‌ی پشتیبان ابری کتاب انتخاب‌شده مطمئن هستید؟",
"What's New in Readest": "چه چیز تازه‌ای در Readest است",
"Enter book title": "عنوان کتاب را وارد کنید",
"Subtitle": "زیرعنوان",
"Enter book subtitle": "زیرعنوان کتاب را وارد کنید",
"Enter author name": "نام نویسنده را وارد کنید",
"Series": "سری",
"Enter series name": "نام سری را وارد کنید",
"Series Index": "شماره‌ی سری",
"Enter series index": "شماره‌ی سری را وارد کنید",
"Total in Series": "تعداد کل در سری",
"Enter total books in series": "تعداد کل کتاب‌ها در سری را وارد کنید",
"Enter publisher": "ناشر را وارد کنید",
"Publication Date": "تاریخ انتشار",
"Identifier": "شناسه",
"Enter book description": "توضیحات کتاب را وارد کنید",
"Change cover image": "تغییر تصویر جلد",
"Replace": "جایگزین",
"Unlock cover": "باز کردن قفل جلد",
"Lock cover": "قفل کردن جلد",
"Auto-Retrieve Metadata": "دریافت خودکار فراداده",
"Auto-Retrieve": "دریافت خودکار",
"Unlock all fields": "باز کردن قفل همه‌ی فیلدها",
"Unlock All": "باز کردن قفل همه",
"Lock all fields": "قفل کردن همه‌ی فیلدها",
"Lock All": "قفل کردن همه",
"Reset": "بازنشانی",
"Edit Metadata": "ویرایش فراداده",
"Locked": "قفل شده",
"Select Metadata Source": "انتخاب منبع فراداده",
"Keep manual input": "حفظ ورودی دستی",
"Google Books": "کتاب‌های گوگل",
"Open Library": "Open Library",
"Fiction, Science, History": "داستان، علم، تاریخ",
"Open Book in New Window": "باز کردن کتاب در پنجره‌ی جدید",
"Voices for {{lang}}": "صداها برای {{lang}}",
"Yandex Translate": "ترجمه‌ی Yandex",
"YYYY or YYYY-MM-DD": "YYYY یا YYYY-MM-DD",
"Restore Purchase": "بازیابی خرید",
"No purchases found to restore.": "هیچ خریدی برای بازیابی یافت نشد.",
"Failed to restore purchases.": "بازیابی خریدها ناموفق بود.",
"Failed to manage subscription.": "مدیریت اشتراک ناموفق بود.",
"Failed to load subscription plans.": "بارگذاری طرح‌های اشتراک ناموفق بود.",
"year": "سال",
"Failed to create checkout session": "ایجاد صفحه‌ی پرداخت ناموفق بود",
"Storage": "ذخیره‌سازی",
"Terms of Service": "شرایط و قوانین",
"Privacy Policy": "سیاست حفظ حریم خصوصی",
"Disable Double Click": "غیرفعال کردن کلیک دوبل",
"TTS not supported for this document": "قابلیت تبدیل متن به گفتار برای این سند پشتیبانی نمی‌شود",
"Reset Password": "بازنشانی رمز عبور",
"Show Reading Progress": "نمایش وضعیت مطالعه",
"Reading Progress Style": "سبک نمایش وضعیت مطالعه",
"Page Number": "شماره صفحه",
"Percentage": "درصد",
"Deleted local copy of the book: {{title}}": "نسخه‌ی محلی کتاب «{{title}}» حذف شد.",
"Failed to delete cloud backup of the book: {{title}}": "حذف نسخه‌ی پشتیبان ابری کتاب «{{title}}» ناموفق بود.",
"Failed to delete local copy of the book: {{title}}": "حذف نسخه‌ی محلی کتاب «{{title}}» ناموفق بود.",
"Are you sure to delete the local copy of the selected book?": "آیا از حذف نسخه‌ی محلی کتاب انتخاب‌شده مطمئن هستید؟",
"Remove from Cloud & Device": "حذف از فضای ابری و دستگاه",
"Remove from Cloud Only": "حذف فقط از فضای ابری",
"Remove from Device Only": "حذف فقط از دستگاه",
"Disconnected": "قطع اتصال",
"KOReader Sync Settings": "تنظیمات همگام‌سازی KOReader",
"Sync Strategy": "استراتژی همگام‌سازی",
"Ask on conflict": "پرسش هنگام تداخل",
"Always use latest": "همیشه از آخرین استفاده شود",
"Send changes only": "فقط ارسال تغییرات",
"Receive changes only": "فقط دریافت تغییرات",
"Checksum Method": "روش Checksum",
"File Content (recommended)": "محتوای فایل (توصیه‌شده)",
"File Name": "نام فایل",
"Device Name": "نام دستگاه",
"Connect to your KOReader Sync server.": "به سرور همگام‌سازی KOReader خود متصل شوید.",
"Server URL": "آدرس سرور",
"Username": "نام کاربری",
"Your Username": "نام کاربری شما",
"Password": "رمز عبور",
"Connect": "اتصال",
"KOReader Sync": "همگام‌سازی KOReader",
"Sync Conflict": "تداخل همگام‌سازی",
"Sync reading progress from \"{{deviceName}}\"?": "همگام‌سازی وضعیت مطالعه از «{{deviceName}}»؟",
"another device": "دستگاه دیگر",
"Local Progress": "وضعیت مطالعه محلی",
"Remote Progress": "وضعیت مطالعه از راه دور",
"Page {{page}} of {{total}} ({{percentage}}%)": "صفحه‌ی {{page}} از {{total}} ({{percentage}}%)",
"Current position": "موقعیت فعلی",
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "تقریباً صفحه‌ی {{page}} از {{total}} ({{percentage}}%)",
"Approximately {{percentage}}%": "تقریباً {{percentage}}%",
"Failed to connect": "اتصال برقرار نشد",
"Sync Server Connected": "سرور همگام‌سازی متصل شد",
"Sync as {{userDisplayName}}": "همگام‌سازی به‌عنوان {{userDisplayName}}",
"Custom Fonts": "فونت‌های سفارشی",
"Cancel Delete": "لغو حذف",
"Import Font": "وارد کردن فونت",
"Delete Font": "حذف فونت",
"Tips": "نکات",
"Custom fonts can be selected from the Font Face menu": "فونت‌های سفارشی را می‌توان از منوی انتخاب فونت برگزید",
"Manage Custom Fonts": "مدیریت فونت‌های سفارشی",
"Select Files": "انتخاب فایل‌ها",
"Select Image": "انتخاب تصویر",
"Select Video": "انتخاب ویدیو",
"Select Audio": "انتخاب صوت",
"Select Fonts": "انتخاب فونت‌ها",
"Supported font formats: .ttf, .otf, .woff, .woff2": "فرمت‌های فونت پشتیبانی‌شده: .ttf, .otf, .woff, .woff2",
"Push Progress": "ارسال وضعیت مطالعه",
"Pull Progress": "دریافت وضعیت مطالعه",
"Previous Paragraph": "پاراگراف قبلی",
"Previous Sentence": "جمله‌ی قبلی",
"Pause": "توقف",
"Play": "پخش",
"Next Sentence": "جمله‌ی بعدی",
"Next Paragraph": "پاراگراف بعدی",
"Separate Cover Page": "جدا کردن صفحه‌ی جلد",
"Resize Notebook": "تغییر اندازه دفترچه یادداشت",
"Resize Sidebar": "تغییر اندازه نوار کناری",
"Get Help from the Readest Community": "دریافت کمک از جامعه‌ی Readest",
"Remove cover image": "حذف تصویر جلد",
"Bookshelf": "قفسه‌ی کتاب",
"View Menu": "نمایش منو",
"Settings Menu": "منوی تنظیمات",
"View account details and quota": "مشاهده‌ی جزئیات حساب و وضعیت سهمیه",
"Library Header": "سربرگ کتابخانه",
"Book Content": "محتوای کتاب",
"Footer Bar": "نوار پاورقی",
"Header Bar": "نوار سربرگ",
"View Options": "گزینه‌های نمایش",
"Book Menu": "منوی کتاب",
"Search Options": "گزینه‌های جستجو",
"Close": "بستن",
"Delete Book Options": "گزینه‌های حذف کتاب",
"ON": "روشن",
"OFF": "خاموش",
"Reading Progress": "وضعیت مطالعه",
"Page Margin": "حاشیه صفحه",
"Remove Bookmark": "حذف نشانک",
"Add Bookmark": "افزودن نشانک",
"Books Content": "محتوای کتاب‌ها",
"Jump to Location": "پرش به مکان",
"Unpin Notebook": "برداشتن سنجاق دفترچه یادداشت",
"Pin Notebook": "سنجاق کردن دفترچه یادداشت",
"Hide Search Bar": "پنهان کردن نوار جستجو",
"Show Search Bar": "نمایش نوار جستجو",
"On {{current}} of {{total}} page": "در صفحه‌ی {{current}} از {{total}}",
"Section Title": "عنوان بخش",
"Decrease": "کاهش",
"Increase": "افزایش",
"Settings Panels": "پنل‌های تنظیمات",
"Settings": "تنظیمات",
"Unpin Sidebar": "برداشتن سنجاق نوار کناری",
"Pin Sidebar": "سنجاق کردن نوار کناری",
"Toggle Sidebar": "تغییر وضعیت نوار کناری",
"Toggle Translation": "تغییر وضعیت ترجمه",
"Translation Disabled": "ترجمه غیرفعال است",
"Minimize": "کوچک کردن",
"Maximize or Restore": "بزرگ کردن یا بازگردانی",
"Exit Parallel Read": "خروج از خواندن موازی",
"Enter Parallel Read": "ورود به خواندن موازی",
"Zoom Level": "اندازه بزرگ‌نمایی",
"Zoom Out": "کوچک‌نمایی",
"Reset Zoom": "بازنشانی بزرگ‌نمایی",
"Zoom In": "بزرگ‌نمایی",
"Zoom Mode": "حالت بزرگ‌نمایی",
"Single Page": "تک‌صفحه‌ای",
"Auto Spread": "گسترش خودکار",
"Fit Page": "تناسب صفحه",
"Fit Width": "تناسب با عرض",
"Failed to select directory": "انتخاب مسیر ناموفق بود",
"The new data directory must be different from the current one.": "مسیر داده‌ی جدید باید با مسیر فعلی متفاوت باشد.",
"Migration failed: {{error}}": "انتقال ناموفق بود: {{error}}",
"Change Data Location": "تغییر محل داده‌ها",
"Current Data Location": "محل فعلی داده‌ها",
"Total size: {{size}}": "حجم کل: {{size}}",
"Calculating file info...": "در حال محاسبه‌ی اطلاعات فایل...",
"New Data Location": "محل جدید داده‌ها",
"Choose Different Folder": "انتخاب پوشه‌ی دیگر",
"Choose New Folder": "انتخاب پوشه‌ی جدید",
"Migrating data...": "در حال انتقال داده‌ها...",
"Copying: {{file}}": "در حال کپی‌کردن: {{file}}",
"{{current}} of {{total}} files": "{{current}} از {{total}} فایل‌ها",
"Migration completed successfully!": "انتقال با موفقیت انجام شد!",
"Your data has been moved to the new location. Please restart the application to complete the process.": "داده‌های شما به محل جدید منتقل شدند. لطفاً برای تکمیل فرایند، برنامه را مجدداً راه‌اندازی کنید.",
"Migration failed": "انتقال ناموفق بود",
"Important Notice": "اطلاعیه‌ی مهم",
"This will move all your app data to the new location. Make sure the destination has enough free space.": "این کار، همه‌ی داده‌های برنامه را به محل جدید منتقل خواهد کرد. مطمئن شوید که در مقصد، فضای کافی وجود دارد.",
"Restart App": "راه‌اندازی مجدد برنامه",
"Start Migration": "شروع انتقال",
"Advanced Settings": "تنظیمات پیشرفته",
"File count: {{size}}": "تعداد فایل‌ها: {{size}}",
"Background Read Aloud": "خواندن با صدای بلند در پس‌زمینه",
"Ready to read aloud": "آماده برای خواندن با صدای بلند",
"Read Aloud": "خواندن با صدای بلند",
"Screen Brightness": "روشنایی صفحه",
"Background Image": "تصویر پس‌زمینه",
"Import Image": "وارد کردن تصویر",
"Opacity": "شفافیت",
"Size": "اندازه",
"Cover": "جلد",
"Contain": "شامل",
"{{number}} pages left in chapter": "{{number}} صفحه تا انتهای فصل",
"Device": "دستگاه",
"E-Ink Mode": "حالت E-Ink",
"Highlight Colors": "رنگ‌های هایلایت",
"Auto Screen Brightness": "روشنایی خودکار صفحه",
"Pagination": "صفحه‌بندی",
"Disable Double Tap": "غیرفعال کردن کلیک دوبل",
"Tap to Paginate": "ضربه برای صفحه‌بندی",
"Click to Paginate": "کلیک برای صفحه‌بندی",
"Tap Both Sides": "ضربه هر دو طرف",
"Click Both Sides": "کلیک هر دو طرف",
"Swap Tap Sides": "جابجایی طرف‌های ضربه",
"Swap Click Sides": "جابجایی طرف‌های کلیک",
"Source and Translated": "منبع و ترجمه شده",
"Translated Only": "فقط ترجمه شده",
"Source Only": "فقط منبع",
"TTS Text": "متن TTS",
"The book file is corrupted": "فایل کتاب خراب است",
"The book file is empty": "فایل کتاب خالی است",
"Failed to open the book file": "خطا در باز کردن فایل کتاب",
"On-Demand Purchase": "خرید سفارشی",
"Full Customization": "سفارشی‌سازی کامل",
"Lifetime Plan": "طرح مادام‌العمر",
"One-Time Payment": "پرداخت یک‌باره",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "یک پرداخت یک‌باره انجام دهید تا به‌صورت مادام‌العمر به ویژگی‌های خاص در تمام دستگاه‌ها دسترسی پیدا کنید. فقط زمانی که به ویژگی‌ها یا خدمات خاصی نیاز دارید، آن‌ها را خریداری کنید.",
"Expand Cloud Sync Storage": "گسترش فضای همگام‌سازی ابری",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "فضای ابری خود را به‌صورت دائمی با یک خرید یک‌باره گسترش دهید. هر خرید اضافی فضای بیشتری اضافه می‌کند.",
"Unlock All Customization Options": "باز کردن تمام گزینه‌های سفارشی‌سازی",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "باز کردن تم‌ها، فونت‌ها، گزینه‌های چیدمان و خواندن با صدای بلند، مترجم‌ها، خدمات ذخیره‌سازی ابری اضافی.",
"Purchase Successful!": "خرید با موفقیت انجام شد!",
"lifetime": "مادام‌العمر",
"Thank you for your purchase! Your payment has been processed successfully.": "از خرید شما متشکریم! پرداخت شما با موفقیت انجام شد.",
"Order ID:": "شناسه سفارش:",
"TTS Highlighting": "هایلایت TTS",
"Style": "سبک",
"Underline": "خط زیرین",
"Strikethrough": "خط‌خورده",
"Squiggly": "خط‌موج‌دار",
"Outline": "خط‌کشی",
"Save Current Color": "ذخیره رنگ فعلی",
"Quick Colors": "رنگ‌های سریع",
"Highlighter": "ماژیک هایلایت",
"Save Book Cover": "ذخیره جلد کتاب",
"Auto-save last book cover": "ذخیره خودکار آخرین جلد کتاب",
"Back": "بازگشت",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "ایمیل تأیید ارسال شد! لطفاً ایمیل قدیمی و جدید خود را بررسی کنید تا تغییر را تأیید نمایید.",
"Failed to update email": "به‌روزرسانی ایمیل ناموفق بود",
"New Email": "ایمیل جدید",
"Your new email": "ایمیل جدید شما",
"Updating email ...": "در حال به‌روزرسانی ایمیل ...",
"Update email": "به‌روزرسانی ایمیل",
"Current email": "ایمیل فعلی",
"Update Email": "به‌روزرسانی ایمیل",
"All": "همه",
"Unable to open book": "قادر به باز کردن کتاب نیست",
"Punctuation": "علامت نگارشی",
"Replace Quotation Marks": "جایگزینی علامت نقل قول",
"Enabled only in vertical layout.": "فقط در طرح عمودی فعال است.",
"No Conversion": "بدون تبدیل",
"Simplified to Traditional": "ساده → سنتی",
"Traditional to Simplified": "سنتی → ساده",
"Simplified to Traditional (Taiwan)": "ساده → سنتی (تایوان)",
"Simplified to Traditional (Hong Kong)": "ساده → سنتی (هنگ‌کنگ)",
"Simplified to Traditional (Taiwan), with phrases": "ساده → سنتی (تایوان • عبارات)",
"Traditional (Taiwan) to Simplified": "سنتی (تایوان) → ساده",
"Traditional (Hong Kong) to Simplified": "سنتی (هنگ‌کنگ) → ساده",
"Traditional (Taiwan) to Simplified, with phrases": "سنتی (تایوان • عبارات) → ساده",
"Convert Simplified and Traditional Chinese": "تبدیل چینی ساده/سنتی",
"Convert Mode": "حالت تبدیل",
"Failed to auto-save book cover for lock screen: {{error}}": "ذخیره‌ی خودکار جلد کتاب برای صفحه‌ی قفل ناموفق بود: {{error}}",
"Download from Cloud": "دانلود از فضای ابری",
"Upload to Cloud": "آپلود به فضای ابری",
"Clear Custom Fonts": "پاک‌کردن فونت‌های سفارشی",
"Columns": "ستون‌ها",
"OPDS Catalogs": "فهرست‌های OPDS",
"Adding LAN addresses is not supported in the web app version.": "افزودن آدرس‌های LAN در نسخه وب پشتیبانی نمی‌شود.",
"Invalid OPDS catalog. Please check the URL.": "فهرست OPDS نامعتبر است. لطفاً آدرس را بررسی کنید.",
"Browse and download books from online catalogs": "مرور و دانلود کتاب‌ها از فهرست‌های آنلاین",
"My Catalogs": "فهرست‌های من",
"Add Catalog": "افزودن فهرست",
"No catalogs yet": "هنوز فهرستی وجود ندارد",
"Add your first OPDS catalog to start browsing books": "برای شروع مرور کتاب‌ها، اولین فهرست OPDS خود را اضافه کنید.",
"Add Your First Catalog": "افزودن اولین فهرست",
"Browse": "مرور",
"Popular Catalogs": "فهرست‌های محبوب",
"Add": "افزودن",
"Add OPDS Catalog": "افزودن فهرست OPDS",
"Catalog Name": "نام فهرست",
"My Calibre Library": "کتابخانه Calibre من",
"OPDS URL": "آدرس OPDS",
"Username (optional)": "نام کاربری (اختیاری)",
"Password (optional)": "رمز عبور (اختیاری)",
"Description (optional)": "توضیحات (اختیاری)",
"A brief description of this catalog": "توضیح کوتاهی درباره این فهرست",
"Validating...": "در حال بررسی...",
"View All": "مشاهده همه",
"Forward": "بعدی",
"OPDS Catalog": "فهرست OPDS",
"Home": "خانه",
"Library": "کتابخانه",
"{{count}} items_one": "{{count}} مورد",
"{{count}} items_other": "{{count}} مورد",
"Download completed": "دانلود کامل شد",
"Download failed": "دانلود ناموفق بود",
"Open Access": "دسترسی آزاد",
"Borrow": "امانت گرفتن",
"Buy": "خرید",
"Subscribe": "اشتراک",
"Sample": "نمونه",
"Download": "دانلود",
"Open & Read": "باز کردن و خواندن",
"Tags": "برچسب‌ها",
"Tag": "برچسب",
"First": "اولین",
"Previous": "قبلی",
"Next": "بعدی",
"Last": "آخرین",
"Cannot Load Page": "بارگذاری صفحه امکان‌پذیر نیست",
"An error occurred": "خطایی رخ داد",
"Online Library": "کتابخانه آنلاین",
"URL must start with http:// or https://": "آدرس باید با http:// یا https:// شروع شود",
"Title, Author, Tag, etc...": "عنوان، نویسنده، برچسب و غیره...",
"Query": "پرس‌وجو",
"Subject": "موضوع",
"Enter {{terms}}": "وارد کردن {{terms}}",
"No search results found": "هیچ نتیجه‌ای یافت نشد",
"Failed to load OPDS feed: {{status}} {{statusText}}": "بارگذاری فید OPDS ناموفق بود: {{status}} {{statusText}}",
"Search in {{title}}": "جستجو در {{title}}",
"Manage Storage": "مدیریت فضای ذخیره‌سازی",
"Failed to load files": "بارگیری فایل‌ها ناموفق بود",
"Deleted {{count}} file(s)_one": "{{count}} فایل حذف شد",
"Deleted {{count}} file(s)_other": "{{count}} فایل حذف شدند",
"Failed to delete {{count}} file(s)_one": "حذف {{count}} فایل ناموفق بود",
"Failed to delete {{count}} file(s)_other": "حذف {{count}} فایل ناموفق بود",
"Failed to delete files": "حذف فایل‌ها ناموفق بود",
"Total Files": "کل فایل‌ها",
"Total Size": "اندازه کل",
"Quota": "سهمیه",
"Used": "استفاده‌شده",
"Files": "فایل‌ها",
"Search files...": "جستجوی فایل‌ها...",
"Newest First": "جدیدترین‌ها",
"Oldest First": "قدیمی‌ترین‌ها",
"Largest First": "بزرگ‌ترین‌ها",
"Smallest First": "کوچک‌ترین‌ها",
"Name A-Z": "نام AZ",
"Name Z-A": "نام ZA",
"{{count}} selected_one": "{{count}} مورد انتخاب شد",
"{{count}} selected_other": "{{count}} مورد انتخاب شدند",
"Delete Selected": "حذف موارد انتخاب‌شده",
"Created": "ایجاد شده",
"No files found": "هیچ فایلی پیدا نشد",
"No files uploaded yet": "هنوز فایلی بارگذاری نشده است",
"files": "فایل‌ها",
"Page {{current}} of {{total}}": "صفحه {{current}} از {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "آیا از حذف {{count}} فایل انتخاب‌شده مطمئن هستید؟",
"Are you sure to delete {{count}} selected file(s)?_other": "آیا از حذف {{count}} فایل انتخاب‌شده مطمئن هستید؟",
"Cloud Storage Usage": "استفاده از فضای ذخیره‌سازی ابری",
"Rename Group": "تغییر نام گروه",
"From Directory": "از مسیر",
"Successfully imported {{count}} book(s)_one": "با موفقیت 1 کتاب وارد شد",
"Successfully imported {{count}} book(s)_other": "با موفقیت {{count}} کتاب وارد شد"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Mode automatique",
"Behavior": "Comportement",
"Book": "Livre",
"Book Cover": "Couverture du livre",
"Bookmark": "Signet",
"Cancel": "Annuler",
"Chapter": "Chapitre",
@@ -193,7 +192,7 @@
"No supported files found. Supported formats: {{formats}}": "Aucun fichier pris en charge trouvé. Formats pris en charge : {{formats}}",
"Drop to Import Books": "Déposez pour importer des livres",
"Custom": "Personnalisé",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Le monde entier est un théâtre,\nEt tous les hommes et les femmes ne sont que des acteurs ;\nIls ont leurs sorties et leurs entrées,\nEt un homme, dans sa vie, joue de nombreux rôles,\nSes actes étant sept âges.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Le monde entier est un théâtre,\nEt tous les hommes et les femmes ne sont que des acteurs ;\nIls ont leurs sorties et leurs entrées,\nEt un homme, dans sa vie, joue de nombreux rôles,\nSes actes étant sept âges.\n\n— William Shakespeare",
"Custom Theme": "Thème personnalisé",
"Theme Name": "Nom du thème",
"Text Color": "Couleur du texte",
@@ -220,9 +219,9 @@
"Auto Import on File Open": "Importation automatique à l'ouverture du fichier",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Aucun chapitre détecté.",
"Failed to parse the EPUB file.": "Échec de l'analyse du fichier EPUB.",
"This book format is not supported.": "Ce format de livre n'est pas pris en charge.",
"No chapters detected": "Aucun chapitre détecté",
"Failed to parse the EPUB file": "Échec de l'analyse du fichier EPUB",
"This book format is not supported": "Ce format de livre n'est pas pris en charge",
"Unable to fetch the translation. Please log in first and try again.": "Impossible de récupérer la traduction. Veuillez d'abord vous connecter et réessayer.",
"Group": "Groupe",
"Always on Top": "Toujours au-dessus",
@@ -259,8 +258,6 @@
"(from 'As You Like It', Act II)": "(de 'Comme il vous plaira', Acte II)",
"Link Color": "Couleur du lien",
"Volume Keys for Page Flip": "Boutons volume pour tourner la page",
"Clicks for Page Flip": "Taps pour tourner la page",
"Swap Clicks Area": "Inverser zones de tap",
"Screen": "Écran",
"Orientation": "Orientation",
"Portrait": "Portrait",
@@ -297,7 +294,6 @@
"Disable Translation": " désactiver la traduction",
"Scroll": "Défilement",
"Overlap Pixels": "Pixels de chevauchement",
"Click": "Cliquer",
"System Language": "Langue du système",
"Security": "Sécurité",
"Allow JavaScript": "Autoriser JavaScript",
@@ -369,7 +365,6 @@
"Email:": "E-mail:",
"Plan:": "Abonnement:",
"Amount:": "Montant:",
"Subscription ID:": "ID dabonnement:",
"Go to Library": "Aller à la bibliothèque",
"Need help? Contact our support team at support@readest.com": "Besoin daide? Contactez notre équipe à support@readest.com",
"Free Plan": "Abonnement gratuit",
@@ -613,5 +608,170 @@
"Contain": "Contenir",
"{{number}} pages left in chapter": "{{number}} pages restantes dans le chapitre",
"Device": "Appareil",
"E-Ink Mode": "Mode E-Ink"
"E-Ink Mode": "Mode E-Ink",
"Highlight Colors": "Couleurs de Surlignage",
"Auto Screen Brightness": "Luminosité Automatique de l'Écran",
"Pagination": "Pagination",
"Disable Double Tap": "Désactiver le Double Tap",
"Tap to Paginate": "Tapoter pour Paginater",
"Click to Paginate": "Cliquer pour Paginater",
"Tap Both Sides": "Tapoter des Deux Côtés",
"Click Both Sides": "Cliquer des Deux Côtés",
"Swap Tap Sides": "Échanger les Côtés de Tap",
"Swap Click Sides": "Échanger les Côtés de Clic",
"Source and Translated": "Source et Traduit",
"Translated Only": "Traduit Seulement",
"Source Only": "Source Seulement",
"TTS Text": "TTS Texte",
"The book file is corrupted": "Le fichier du livre est corrompu",
"The book file is empty": "Le fichier du livre est vide",
"Failed to open the book file": "Échec de l'ouverture du fichier du livre",
"On-Demand Purchase": "Achat à la Demande",
"Full Customization": "Personnalisation Complète",
"Lifetime Plan": "Plan de Vie",
"One-Time Payment": "Paiement Unique",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Effectuez un paiement unique pour profiter d'un accès à vie à des fonctionnalités spécifiques sur tous les appareils. Achetez des fonctionnalités ou des services spécifiques uniquement lorsque vous en avez besoin.",
"Expand Cloud Sync Storage": "Étendre le Stockage de Synchronisation Cloud",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Étendez votre stockage cloud pour toujours avec un achat unique. Chaque achat supplémentaire ajoute plus d'espace.",
"Unlock All Customization Options": "Débloquer Toutes les Options de Personnalisation",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Débloquer des thèmes supplémentaires, des polices, des options de mise en page et des fonctions de lecture à haute voix, des traducteurs, des services de stockage cloud.",
"Purchase Successful!": "Achat Réussi!",
"lifetime": "à vie",
"Thank you for your purchase! Your payment has been processed successfully.": "Merci pour votre achat ! Votre paiement a été traité avec succès.",
"Order ID:": "ID de commande :",
"TTS Highlighting": "Surbrillance TTS",
"Style": "Style",
"Underline": "Souligner",
"Strikethrough": "Barré",
"Squiggly": "Ondulé",
"Outline": "Contour",
"Save Current Color": "Enregistrer la Couleur Actuelle",
"Quick Colors": "Couleurs Rapides",
"Highlighter": "Surligneur",
"Save Book Cover": "Enregistrer la Couverture du Livre",
"Auto-save last book cover": "Enregistrer automatiquement la dernière couverture du livre",
"Back": "Retour",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "E-mail de confirmation envoyé ! Veuillez vérifier vos anciennes et nouvelles adresses e-mail pour confirmer le changement.",
"Failed to update email": "Échec de la mise à jour de le-mail",
"New Email": "Nouvel e-mail",
"Your new email": "Votre nouvel e-mail",
"Updating email ...": "Mise à jour de le-mail ...",
"Update email": "Mettre à jour le-mail",
"Current email": "E-mail actuel",
"Update Email": "Mettre à jour le-mail",
"All": "Tous",
"Unable to open book": "Impossible d'ouvrir le livre",
"Punctuation": "Ponctuation",
"Replace Quotation Marks": "Remplacer les guillemets",
"Enabled only in vertical layout.": "Activé uniquement en disposition verticale.",
"No Conversion": "Pas de conversion",
"Simplified to Traditional": "Simp. → Trad.",
"Traditional to Simplified": "Trad. → Simp.",
"Simplified to Traditional (Taiwan)": "Simp. → Trad. (Taïwan)",
"Simplified to Traditional (Hong Kong)": "Simp. → Trad. (Hong Kong)",
"Simplified to Traditional (Taiwan), with phrases": "Simp. → Trad. (Taïwan • phrases)",
"Traditional (Taiwan) to Simplified": "Trad. (Taïwan) → Simp.",
"Traditional (Hong Kong) to Simplified": "Trad. (Hong Kong) → Simp.",
"Traditional (Taiwan) to Simplified, with phrases": "Trad. (Taïwan • phrases) → Simp.",
"Convert Simplified and Traditional Chinese": "Convertir chinois simp./trad.",
"Convert Mode": "Mode de conversion",
"Failed to auto-save book cover for lock screen: {{error}}": "Échec de l'enregistrement automatique de la couverture du livre pour l'écran de verrouillage : {{error}}",
"Download from Cloud": "Télécharger depuis le Cloud",
"Upload to Cloud": "Téléverser vers le Cloud",
"Clear Custom Fonts": "Effacer les Polices Personnalisées",
"Columns": "Colonnes",
"OPDS Catalogs": "Catalogues OPDS",
"Adding LAN addresses is not supported in the web app version.": "Lajout dadresses LAN nest pas pris en charge dans la version web.",
"Invalid OPDS catalog. Please check the URL.": "Catalogue OPDS invalide. Veuillez vérifier lURL.",
"Browse and download books from online catalogs": "Parcourez et téléchargez des livres depuis des catalogues en ligne",
"My Catalogs": "Mes catalogues",
"Add Catalog": "Ajouter un catalogue",
"No catalogs yet": "Aucun catalogue pour le moment",
"Add your first OPDS catalog to start browsing books": "Ajoutez votre premier catalogue OPDS pour commencer à parcourir des livres.",
"Add Your First Catalog": "Ajouter votre premier catalogue",
"Browse": "Parcourir",
"Popular Catalogs": "Catalogues populaires",
"Add": "Ajouter",
"Add OPDS Catalog": "Ajouter un catalogue OPDS",
"Catalog Name": "Nom du catalogue",
"My Calibre Library": "Ma bibliothèque Calibre",
"OPDS URL": "URL OPDS",
"Username (optional)": "Nom dutilisateur (optionnel)",
"Password (optional)": "Mot de passe (optionnel)",
"Description (optional)": "Description (optionnelle)",
"A brief description of this catalog": "Brève description de ce catalogue",
"Validating...": "Validation...",
"View All": "Tout afficher",
"Forward": "Suivant",
"OPDS Catalog": "Catalogue OPDS",
"Home": "Accueil",
"Library": "Bibliothèque",
"{{count}} items_one": "{{count}} élément",
"{{count}} items_many": "{{count}} éléments",
"{{count}} items_other": "{{count}} éléments",
"Download completed": "Téléchargement terminé",
"Download failed": "Échec du téléchargement",
"Open Access": "Accès libre",
"Borrow": "Emprunter",
"Buy": "Acheter",
"Subscribe": "Sabonner",
"Sample": "Extrait",
"Download": "Télécharger",
"Open & Read": "Ouvrir et lire",
"Tags": "Tags",
"Tag": "Tag",
"First": "Premier",
"Previous": "Précédent",
"Next": "Suivant",
"Last": "Dernier",
"Cannot Load Page": "Impossible de charger la page",
"An error occurred": "Une erreur est survenue",
"Online Library": "Bibliothèque en ligne",
"URL must start with http:// or https://": "URL doit commencer par http:// ou https://",
"Title, Author, Tag, etc...": "Titre, Auteur, Tag, etc...",
"Query": "Requête",
"Subject": "Sujet",
"Enter {{terms}}": "Entrez {{terms}}",
"No search results found": "Aucun résultat trouvé",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Échec du chargement du flux OPDS : {{status}} {{statusText}}",
"Search in {{title}}": "Rechercher dans {{title}}",
"Manage Storage": "Gérer le stockage",
"Failed to load files": "Échec du chargement des fichiers",
"Deleted {{count}} file(s)_one": "{{count}} fichier supprimé",
"Deleted {{count}} file(s)_many": "{{count}} fichiers supprimés",
"Deleted {{count}} file(s)_other": "{{count}} fichiers supprimés",
"Failed to delete {{count}} file(s)_one": "Échec de la suppression de {{count}} fichier",
"Failed to delete {{count}} file(s)_many": "Échec de la suppression de {{count}} fichiers",
"Failed to delete {{count}} file(s)_other": "Échec de la suppression de {{count}} fichiers",
"Failed to delete files": "Échec de la suppression des fichiers",
"Total Files": "Nombre total de fichiers",
"Total Size": "Taille totale",
"Quota": "Quota",
"Used": "Utilisé",
"Files": "Fichiers",
"Search files...": "Rechercher des fichiers...",
"Newest First": "Les plus récents",
"Oldest First": "Les plus anciens",
"Largest First": "Les plus volumineux",
"Smallest First": "Les moins volumineux",
"Name A-Z": "Nom A-Z",
"Name Z-A": "Nom Z-A",
"{{count}} selected_one": "{{count}} sélectionné",
"{{count}} selected_many": "{{count}} sélectionnés",
"{{count}} selected_other": "{{count}} sélectionnés",
"Delete Selected": "Supprimer la sélection",
"Created": "Créé",
"No files found": "Aucun fichier trouvé",
"No files uploaded yet": "Aucun fichier téléchargé pour le moment",
"files": "fichiers",
"Page {{current}} of {{total}}": "Page {{current}} sur {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Voulez-vous vraiment supprimer {{count}} fichier sélectionné ?",
"Are you sure to delete {{count}} selected file(s)?_many": "Voulez-vous vraiment supprimer {{count}} fichiers sélectionnés ?",
"Are you sure to delete {{count}} selected file(s)?_other": "Voulez-vous vraiment supprimer {{count}} fichiers sélectionnés ?",
"Cloud Storage Usage": "Utilisation du stockage cloud",
"Rename Group": "Renommer le groupe",
"From Directory": "Depuis le répertoire",
"Successfully imported {{count}} book(s)_one": "Importation réussie de 1 livre",
"Successfully imported {{count}} book(s)_many": "Importation réussie de {{count}} livres",
"Successfully imported {{count}} book(s)_other": "Importation réussie de {{count}} livres"
}
@@ -8,7 +8,6 @@
"Auto Mode": "स्वचालित मोड",
"Behavior": "व्यवहार",
"Book": "पुस्तक",
"Book Cover": "पुस्तक का कवर",
"Bookmark": "बुकमार्क",
"Cancel": "रद्द करें",
"Chapter": "अध्याय",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "कोई समर्थित फ़ाइलें नहीं मिलीं। समर्थित प्रारूप: {{formats}}",
"Drop to Import Books": "पुस्तकें आयात करने के लिए छोड़ें",
"Custom": "कस्टम",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "पूरी दुनिया एक स्टेज है,\nऔर सभी आदमी और महिलाएँ केवल अभिनेता हैं;\nउनके निकास और प्रवेश होते हैं,\nऔर एक आदमी अपने समय में कई भूमिकाएँ निभाता है,\nउसके कार्य सात युग होते हैं।\n\n— विलियम शेक्सपियर",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "पूरी दुनिया एक स्टेज है,\nऔर सभी आदमी और महिलाएँ केवल अभिनेता हैं;\nउनके निकास और प्रवेश होते हैं,\nऔर एक आदमी अपने समय में कई भूमिकाएँ निभाता है,\nउसके कार्य सात युग होते हैं।\n\n— विलियम शेक्सपियर",
"Custom Theme": "कस्टम थीम",
"Theme Name": "थीम का नाम",
"Text Color": "टेक्स्ट रंग",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "फ़ाइल खोलने पर स्वचालित आयात",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "कोई अध्याय नहीं मिला।",
"Failed to parse the EPUB file.": "EPUB फ़ाइल को पार्स करने में विफल।",
"This book format is not supported.": "यह पुस्तक प्रारूप समर्थित नहीं है।",
"No chapters detected": "कोई अध्याय नहीं मिला।",
"Failed to parse the EPUB file": "EPUB फ़ाइल को पार्स करने में विफल।",
"This book format is not supported": "यह पुस्तक प्रारूप समर्थित नहीं है।",
"Unable to fetch the translation. Please log in first and try again.": "अनुवाद प्राप्त करने में असमर्थ। कृपया पहले लॉग इन करें और पुनः प्रयास करें।",
"Group": "समूह",
"Always on Top": "सर्वोच्च पर हमेशा",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "('जैसा आप इसे पसंद करते हैं', अधिनियम II)",
"Link Color": "लिंक रंग",
"Volume Keys for Page Flip": "पृष्ठ फ़्लिप के लिए वॉल्यूम कुंजियाँ",
"Clicks for Page Flip": "पृष्ठ फ़्लिप के लिए क्लिक करें",
"Swap Clicks Area": "क्लिक क्षेत्र स्वैप करें",
"Screen": "स्क्रीन",
"Orientation": "अवयव",
"Portrait": "पोर्ट्रेट",
@@ -297,7 +294,6 @@
"Disable Translation": "अनुवाद अक्षम करें",
"Scroll": "स्क्रॉल",
"Overlap Pixels": "ओवरलैप पिक्सेल",
"Click": "क्लिक करें",
"System Language": "सिस्टम भाषा",
"Security": "सुरक्षा",
"Allow JavaScript": "जावास्क्रिप्ट की अनुमति दें",
@@ -366,7 +362,6 @@
"Email:": "ईमेल:",
"Plan:": "योजना:",
"Amount:": "राशि:",
"Subscription ID:": "सदस्यता आईडी:",
"Go to Library": "लाइब्रेरी पर जाएँ",
"Need help? Contact our support team at support@readest.com": "मदद चाहिए? हमारी सहायता टीम से संपर्क करें: support@readest.com",
"Free Plan": "मुफ्त योजना",
@@ -609,5 +604,164 @@
"Contain": "समाहित करें",
"{{number}} pages left in chapter": "इस अध्याय में {{number}} पृष्ठ शेष हैं",
"Device": "डिवाइस",
"E-Ink Mode": "ई-इंक मोड"
"E-Ink Mode": "ई-इंक मोड",
"Highlight Colors": "हाइलाइट रंग",
"Auto Screen Brightness": "स्वचालित स्क्रीन ब्राइटनेस",
"Pagination": "पृष्ठांकन",
"Disable Double Tap": "डबल टैप अक्षम करें",
"Tap to Paginate": "पृष्ठांकन के लिए टैप करें",
"Click to Paginate": "पृष्ठांकन के लिए क्लिक करें",
"Tap Both Sides": "दोनों पक्षों पर टैप करें",
"Click Both Sides": "दोनों पक्षों पर क्लिक करें",
"Swap Tap Sides": "स्वैप टैप साइड्स",
"Swap Click Sides": "स्वैप क्लिक साइड्स",
"Source and Translated": "स्रोत और अनुवादित",
"Translated Only": "केवल अनुवादित",
"Source Only": "केवल स्रोत",
"TTS Text": "TTS टेक्स्ट",
"The book file is corrupted": "पुस्तक फ़ाइल भ्रष्ट है।",
"The book file is empty": "पुस्तक फ़ाइल खाली है।",
"Failed to open the book file": "पुस्तक फ़ाइल खोलने में विफल।",
"On-Demand Purchase": "आवश्यकता के अनुसार खरीदारी",
"Full Customization": "पूर्ण अनुकूलन",
"Lifetime Plan": "जीवनकाल योजना",
"One-Time Payment": "एक बार का भुगतान",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "सभी उपकरणों पर विशिष्ट सुविधाओं तक जीवनकाल पहुंच का आनंद लेने के लिए एक बार का भुगतान करें। जब आपको उनकी आवश्यकता हो, तो केवल विशिष्ट सुविधाओं या सेवाओं को खरीदें।",
"Expand Cloud Sync Storage": "क्लाउड सिंक स्टोरेज का विस्तार करें",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "एक बार के खरीदारी के साथ अपने क्लाउड स्टोरेज का विस्तार करें। प्रत्येक अतिरिक्त खरीदारी अधिक स्थान जोड़ती है।",
"Unlock All Customization Options": "सभी अनुकूलन विकल्प अनलॉक करें",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "अतिरिक्त थीम, फ़ॉन्ट, लेआउट विकल्प और पढ़ने के लिए, अनुवादक, क्लाउड स्टोरेज सेवाएँ अनलॉक करें।",
"Purchase Successful!": "खरीदारी सफल!",
"lifetime": "जीवनकाल",
"Thank you for your purchase! Your payment has been processed successfully.": "आपकी खरीदारी के लिए धन्यवाद! आपका भुगतान सफलतापूर्वक संसाधित हो गया है।",
"Order ID:": "ऑर्डर आईडी:",
"TTS Highlighting": "TTS हाइलाइटिंग",
"Style": "शैली",
"Underline": "रेखांकित",
"Strikethrough": "रेखांकित करें",
"Squiggly": "स्क्विगली",
"Outline": "आउटलाइन",
"Save Current Color": "वर्तमान रंग सहेजें",
"Quick Colors": "त्वरित रंग",
"Highlighter": "हाइलाइटर",
"Save Book Cover": "बुक कवर सहेजें",
"Auto-save last book cover": "अंतिम पुस्तक कवर को स्वचालित रूप से सहेजें",
"Back": "वापस",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "पुष्टिकरण ईमेल भेज दिया गया है! कृपया परिवर्तन की पुष्टि करने के लिए अपना पुराना और नया ईमेल पता जांचें।",
"Failed to update email": "ईमेल अपडेट करने में विफल",
"New Email": "नया ईमेल",
"Your new email": "आपका नया ईमेल",
"Updating email ...": "ईमेल अपडेट किया जा रहा है ...",
"Update email": "ईमेल अपडेट करें",
"Current email": "वर्तमान ईमेल",
"Update Email": "ईमेल अपडेट करें",
"All": "सभी",
"Unable to open book": "पुस्तक खोलने में असमर्थ",
"Punctuation": "विराम चिह्न",
"Replace Quotation Marks": "उद्धरण चिह्न बदलें",
"Enabled only in vertical layout.": "केवल ऊर्ध्वाधर लेआउट में सक्षम।",
"No Conversion": "कोई रूपांतरण नहीं",
"Simplified to Traditional": "सरलीकृत → पारंपरिक",
"Traditional to Simplified": "पारंपरिक → सरलीकृत",
"Simplified to Traditional (Taiwan)": "सरलीकृत → पारंपरिक (ताइवान)",
"Simplified to Traditional (Hong Kong)": "सरलीकृत → पारंपरिक (हांगकांग)",
"Simplified to Traditional (Taiwan), with phrases": "सरलीकृत → पारंपरिक (ताइवान • वाक्यांश)",
"Traditional (Taiwan) to Simplified": "पारंपरिक (ताइवान) → सरलीकृत",
"Traditional (Hong Kong) to Simplified": "पारंपरिक (हांगकांग) → सरलीकृत",
"Traditional (Taiwan) to Simplified, with phrases": "पारंपरिक (ताइवान • वाक्यांश) → सरलीकृत",
"Convert Simplified and Traditional Chinese": "सरलीकृत/पारंपरिक चीनी रूपांतरण",
"Convert Mode": "रूपांतरण मोड",
"Failed to auto-save book cover for lock screen: {{error}}": "लॉक स्क्रीन के लिए पुस्तक कवर स्वचालित रूप से सहेजने में विफल: {{error}}",
"Download from Cloud": "क्लाउड से डाउनलोड करें",
"Upload to Cloud": "क्लाउड पर अपलोड करें",
"Clear Custom Fonts": "कस्टम फ़ॉन्ट साफ़ करें",
"Columns": "कॉलम",
"OPDS Catalogs": "OPDS कैटलॉग",
"Adding LAN addresses is not supported in the web app version.": "वेब ऐप संस्करण में LAN पता जोड़ना समर्थित नहीं है।",
"Invalid OPDS catalog. Please check the URL.": "अमान्य OPDS कैटलॉग। कृपया URL जांचें।",
"Browse and download books from online catalogs": "ऑनलाइन कैटलॉग से किताबें ब्राउज़ और डाउनलोड करें",
"My Catalogs": "मेरे कैटलॉग",
"Add Catalog": "कैटलॉग जोड़ें",
"No catalogs yet": "अभी कोई कैटलॉग नहीं",
"Add your first OPDS catalog to start browsing books": "किताबें ब्राउज़ करने के लिए अपना पहला OPDS कैटलॉग जोड़ें",
"Add Your First Catalog": "अपना पहला कैटलॉग जोड़ें",
"Browse": "ब्राउज़",
"Popular Catalogs": "लोकप्रिय कैटलॉग",
"Add": "जोड़ें",
"Add OPDS Catalog": "OPDS कैटलॉग जोड़ें",
"Catalog Name": "कैटलॉग नाम",
"My Calibre Library": "मेरी Calibre लाइब्रेरी",
"OPDS URL": "OPDS URL",
"Username (optional)": "यूज़रनेम (वैकल्पिक)",
"Password (optional)": "पासवर्ड (वैकल्पिक)",
"Description (optional)": "विवरण (वैकल्पिक)",
"A brief description of this catalog": "इस कैटलॉग का संक्षिप्त विवरण",
"Validating...": "मान्य किया जा रहा है...",
"View All": "सभी देखें",
"Forward": "आगे",
"OPDS Catalog": "OPDS कैटलॉग",
"Home": "होम",
"Library": "लाइब्रेरी",
"{{count}} items_one": "{{count}} आइटम",
"{{count}} items_other": "{{count}} आइटम",
"Download completed": "डाउनलोड पूरा हुआ",
"Download failed": "डाउनलोड विफल हुआ",
"Open Access": "ओपन एक्सेस",
"Borrow": "उधार लें",
"Buy": "खरीदें",
"Subscribe": "सदस्यता लें",
"Sample": "नमूना",
"Download": "डाउनलोड",
"Open & Read": "खोलें और पढ़ें",
"Tags": "टैग",
"Tag": "टैग",
"First": "पहला",
"Previous": "पिछला",
"Next": "अगला",
"Last": "अंतिम",
"Cannot Load Page": "पृष्ठ लोड नहीं किया जा सका",
"An error occurred": "एक त्रुटि हुई",
"Online Library": "ऑनलाइन लाइब्रेरी",
"URL must start with http:// or https://": "URL http:// या https:// से शुरू होना चाहिए",
"Title, Author, Tag, etc...": "शीर्षक, लेखक, टैग, आदि...",
"Query": "प्रश्न",
"Subject": "विषय",
"Enter {{terms}}": "{{terms}} दर्ज करें",
"No search results found": "कोई खोज परिणाम नहीं मिला",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS फ़ीड लोड करने में विफल: {{status}} {{statusText}}",
"Search in {{title}}": "{{title}} में खोजें",
"Manage Storage": "स्टोरेज प्रबंधित करें",
"Failed to load files": "फ़ाइलें लोड करने में विफल",
"Deleted {{count}} file(s)_one": "{{count}} फ़ाइल हटाई गई",
"Deleted {{count}} file(s)_other": "{{count}} फ़ाइलें हटाई गईं",
"Failed to delete {{count}} file(s)_one": "{{count}} फ़ाइल हटाने में विफल",
"Failed to delete {{count}} file(s)_other": "{{count}} फ़ाइलें हटाने में विफल",
"Failed to delete files": "फ़ाइलें हटाने में विफल",
"Total Files": "कुल फ़ाइलें",
"Total Size": "कुल आकार",
"Quota": "कोटा",
"Used": "उपयोग किया गया",
"Files": "फ़ाइलें",
"Search files...": "फ़ाइलें खोजें...",
"Newest First": "नवीनतम पहले",
"Oldest First": "सबसे पुराने पहले",
"Largest First": "सबसे बड़ी पहले",
"Smallest First": "सबसे छोटी पहले",
"Name A-Z": "नाम A-Z",
"Name Z-A": "नाम Z-A",
"{{count}} selected_one": "{{count}} चयनित",
"{{count}} selected_other": "{{count}} चयनित",
"Delete Selected": "चयनित हटाएँ",
"Created": "बनाई गई",
"No files found": "कोई फ़ाइल नहीं मिली",
"No files uploaded yet": "अभी तक कोई फ़ाइल अपलोड नहीं की गई",
"files": "फ़ाइलें",
"Page {{current}} of {{total}}": "पृष्ठ {{current}} / {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "क्या आप वाकई {{count}} चयनित फ़ाइल हटाना चाहते हैं?",
"Are you sure to delete {{count}} selected file(s)?_other": "क्या आप वाकई {{count}} चयनित फ़ाइलें हटाना चाहते हैं?",
"Cloud Storage Usage": "क्लाउड स्टोरेज उपयोग",
"Rename Group": "समूह का नाम बदलें",
"From Directory": "निर्देशिका से",
"Successfully imported {{count}} book(s)_one": "सफलतापूर्वक 1 पुस्तक आयात की गई",
"Successfully imported {{count}} book(s)_other": "सफलतापूर्वक {{count}} पुस्तकों का आयात किया गया"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Mode Otomatis",
"Behavior": "Perilaku",
"Book": "Buku",
"Book Cover": "Sampul Buku",
"Bookmark": "Penanda",
"Cancel": "Batal",
"Chapter": "Bab",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Tidak ada file yang didukung ditemukan. Format yang didukung: {{formats}}",
"Drop to Import Books": "Jatuhkan untuk Mengimpor Buku",
"Custom": "Kustom",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Seluruh dunia adalah panggung,\nDan semua pria dan wanita hanyalah pemain;\nMereka memiliki keluar dan masuk mereka,\nDan seorang pria dalam hidupnya memainkan banyak peran,\nTindakannya ada tujuh zaman.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Seluruh dunia adalah panggung,\nDan semua pria dan wanita hanyalah pemain;\nMereka memiliki keluar dan masuk mereka,\nDan seorang pria dalam hidupnya memainkan banyak peran,\nTindakannya ada tujuh zaman.\n\n— William Shakespeare",
"Custom Theme": "Tema Kustom",
"Theme Name": "Nama Tema",
"Text Color": "Warna Teks",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Impor Otomatis saat File Dibuka",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Tidak ada bab yang terdeteksi.",
"Failed to parse the EPUB file.": "Gagal mengurai file EPUB.",
"This book format is not supported.": "Format buku ini tidak didukung.",
"No chapters detected": "Tidak ada bab yang terdeteksi",
"Failed to parse the EPUB file": "Gagal mengurai file EPUB",
"This book format is not supported": "Format buku ini tidak didukung",
"Unable to fetch the translation. Please log in first and try again.": "Tidak dapat mengambil terjemahan. Silakan masuk terlebih dahulu dan coba lagi.",
"Group": "Grup",
"Always on Top": "Selalu di Atas",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(dari 'Seperti yang Anda Inginkan', Bab II)",
"Link Color": "Warna Tautan",
"Volume Keys for Page Flip": "Volume Keys untuk Pembalikan Halaman",
"Clicks for Page Flip": "Klik untuk Pembalikan Halaman",
"Swap Clicks Area": "Area Klik Tukar",
"Screen": "Screen",
"Orientation": "Orientasi",
"Portrait": "Potret",
@@ -296,7 +293,6 @@
"Disable Translation": "Nonaktifkan Terjemahan",
"Scroll": "Scroll",
"Overlap Pixels": "Overlap Piksel",
"Click": "Klik",
"System Language": "Bahasa Sistem",
"Security": "Keamanan",
"Allow JavaScript": "Izinkan JavaScript",
@@ -362,7 +358,6 @@
"Email:": "Email:",
"Plan:": "Paket:",
"Amount:": "Jumlah:",
"Subscription ID:": "ID Langganan:",
"Go to Library": "Pergi ke Perpustakaan",
"Need help? Contact our support team at support@readest.com": "Perlu bantuan? Hubungi tim dukungan kami di support@readest.com",
"Free Plan": "Paket Gratis",
@@ -605,5 +600,158 @@
"Contain": "Mengandung",
"{{number}} pages left in chapter": "{{number}} halaman tersisa di bab",
"Device": "Perangkat",
"E-Ink Mode": "Mode E-Ink"
"E-Ink Mode": "Mode E-Ink",
"Highlight Colors": "Warna Sorotan",
"Auto Screen Brightness": "Auto Kecerahan Layar",
"Pagination": "Paginasi",
"Disable Double Tap": "Nonaktifkan Ketuk Ganda",
"Tap to Paginate": "Ketuk untuk Paginasi",
"Click to Paginate": "Klik untuk Paginasi",
"Tap Both Sides": "Ketuk Kedua Sisi",
"Click Both Sides": "Klik Kedua Sisi",
"Swap Tap Sides": "Tukar Sisi Ketuk",
"Swap Click Sides": "Tukar Sisi Klik",
"Source and Translated": "Sumber dan Terjemahan",
"Translated Only": "Hanya Terjemahan",
"Source Only": "Hanya Sumber",
"TTS Text": "Teks TTS",
"The book file is corrupted": "File buku rusak",
"The book file is empty": "File buku kosong",
"Failed to open the book file": "Gagal membuka file buku",
"On-Demand Purchase": "Pembelian Sesuai Permintaan",
"Full Customization": "Kustomisasi Penuh",
"Lifetime Plan": "Rencana Seumur Hidup",
"One-Time Payment": "Pembayaran Sekali",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Lakukan pembayaran sekali untuk menikmati akses seumur hidup ke fitur tertentu di semua perangkat. Beli fitur atau layanan tertentu hanya saat Anda membutuhkannya.",
"Expand Cloud Sync Storage": "Perluas Penyimpanan Sinkronisasi Cloud",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Perluas penyimpanan cloud Anda selamanya dengan pembelian sekali. Setiap pembelian tambahan menambah lebih banyak ruang.",
"Unlock All Customization Options": "Buka Semua Opsi Kustomisasi",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Buka tema tambahan, font, opsi tata letak, dan baca nyaring, penerjemah, layanan penyimpanan cloud.",
"Purchase Successful!": "Pembelian Berhasil!",
"lifetime": "seumur hidup",
"Thank you for your purchase! Your payment has been processed successfully.": "Terima kasih atas pembelian Anda! Pembayaran Anda telah berhasil diproses.",
"Order ID:": "ID Pesanan:",
"TTS Highlighting": "TTS Menyorot",
"Style": "Gaya",
"Underline": "Garisan Bawah",
"Strikethrough": "Garis Tengah",
"Squiggly": "Bergelombang",
"Outline": "Garis Besar",
"Save Current Color": "Simpan Warna Saat Ini",
"Quick Colors": "Warna Cepat",
"Highlighter": "Penyorot",
"Save Book Cover": "Simpan Sampul Buku",
"Auto-save last book cover": "Simpan otomatis sampul buku terakhir",
"Back": "Kembali",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Email konfirmasi telah dikirim! Silakan periksa alamat email lama dan baru Anda untuk mengonfirmasi perubahan.",
"Failed to update email": "Gagal memperbarui email",
"New Email": "Email baru",
"Your new email": "Email baru Anda",
"Updating email ...": "Memperbarui email ...",
"Update email": "Perbarui email",
"Current email": "Email saat ini",
"Update Email": "Perbarui email",
"All": "Semua",
"Unable to open book": "Tidak dapat membuka buku",
"Punctuation": "Tanda Baca",
"Replace Quotation Marks": "Ganti Tanda Kutip",
"Enabled only in vertical layout.": "Hanya diaktifkan dalam tata letak vertikal.",
"No Conversion": "Tanpa konversi",
"Simplified to Traditional": "Sederhana → Tradisional",
"Traditional to Simplified": "Tradisional → Sederhana",
"Simplified to Traditional (Taiwan)": "Sederhana → Tradisional (Taiwan)",
"Simplified to Traditional (Hong Kong)": "Sederhana → Tradisional (Hong Kong)",
"Simplified to Traditional (Taiwan), with phrases": "Sederhana → Tradisional (Taiwan • frasa)",
"Traditional (Taiwan) to Simplified": "Tradisional (Taiwan) → Sederhana",
"Traditional (Hong Kong) to Simplified": "Tradisional (Hong Kong) → Sederhana",
"Traditional (Taiwan) to Simplified, with phrases": "Tradisional (Taiwan • frasa) → Sederhana",
"Convert Simplified and Traditional Chinese": "Konversi Tionghoa Sederhana/Tradisional",
"Convert Mode": "Mode Konversi",
"Failed to auto-save book cover for lock screen: {{error}}": "Gagal menyimpan otomatis sampul buku untuk layar kunci: {{error}}",
"Download from Cloud": "Unduh dari Cloud",
"Upload to Cloud": "Unggah ke Cloud",
"Clear Custom Fonts": "Bersihkan Font Kustom",
"Columns": "Kolom",
"OPDS Catalogs": "Katalog OPDS",
"Adding LAN addresses is not supported in the web app version.": "Menambahkan alamat LAN tidak didukung di versi web.",
"Invalid OPDS catalog. Please check the URL.": "Katalog OPDS tidak valid. Silakan periksa URL-nya.",
"Browse and download books from online catalogs": "Jelajahi dan unduh buku dari katalog online",
"My Catalogs": "Katalog Saya",
"Add Catalog": "Tambah Katalog",
"No catalogs yet": "Belum ada katalog",
"Add your first OPDS catalog to start browsing books": "Tambahkan katalog OPDS pertama Anda untuk mulai menjelajahi buku",
"Add Your First Catalog": "Tambah Katalog Pertama Anda",
"Browse": "Jelajahi",
"Popular Catalogs": "Katalog Populer",
"Add": "Tambah",
"Add OPDS Catalog": "Tambah Katalog OPDS",
"Catalog Name": "Nama Katalog",
"My Calibre Library": "Perpustakaan Calibre Saya",
"OPDS URL": "URL OPDS",
"Username (optional)": "Username (opsional)",
"Password (optional)": "Password (opsional)",
"Description (optional)": "Deskripsi (opsional)",
"A brief description of this catalog": "Deskripsi singkat untuk katalog ini",
"Validating...": "Memvalidasi...",
"View All": "Lihat Semua",
"Forward": "Maju",
"OPDS Catalog": "Katalog OPDS",
"Home": "Beranda",
"Library": "Perpustakaan",
"{{count}} items_other": "{{count}} item",
"Download completed": "Unduhan selesai",
"Download failed": "Unduhan gagal",
"Open Access": "Akses Terbuka",
"Borrow": "Pinjam",
"Buy": "Beli",
"Subscribe": "Berlangganan",
"Sample": "Contoh",
"Download": "Unduh",
"Open & Read": "Buka & Baca",
"Tags": "Tag",
"Tag": "Tag",
"First": "Pertama",
"Previous": "Sebelumnya",
"Next": "Berikutnya",
"Last": "Terakhir",
"Cannot Load Page": "Tidak dapat memuat halaman",
"An error occurred": "Terjadi kesalahan",
"Online Library": "Perpustakaan Online",
"URL must start with http:// or https://": "URL harus diawali dengan http:// atau https://",
"Title, Author, Tag, etc...": "Judul, Penulis, Tag, dll...",
"Query": "Kueri",
"Subject": "Subjek",
"Enter {{terms}}": "Masukkan {{terms}}",
"No search results found": "Tidak ada hasil pencarian ditemukan",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Gagal memuat feed OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Cari di {{title}}",
"Manage Storage": "Kelola Penyimpanan",
"Failed to load files": "Gagal memuat file",
"Deleted {{count}} file(s)_other": "{{count}} file dihapus",
"Failed to delete {{count}} file(s)_other": "Gagal menghapus {{count}} file",
"Failed to delete files": "Gagal menghapus file",
"Total Files": "Total File",
"Total Size": "Total Ukuran",
"Quota": "Kuota",
"Used": "Digunakan",
"Files": "File",
"Search files...": "Cari file...",
"Newest First": "Terbaru",
"Oldest First": "Terlama",
"Largest First": "Terbesar",
"Smallest First": "Terkecil",
"Name A-Z": "Nama A-Z",
"Name Z-A": "Nama Z-A",
"{{count}} selected_other": "{{count}} dipilih",
"Delete Selected": "Hapus yang Dipilih",
"Created": "Dibuat",
"No files found": "Tidak ada file ditemukan",
"No files uploaded yet": "Belum ada file diunggah",
"files": "file",
"Page {{current}} of {{total}}": "Halaman {{current}} dari {{total}}",
"Are you sure to delete {{count}} selected file(s)?_other": "Yakin ingin menghapus {{count}} file yang dipilih?",
"Cloud Storage Usage": "Penggunaan Penyimpanan Cloud",
"Rename Group": "Ganti Nama Grup",
"From Directory": "Dari Direktori",
"Successfully imported {{count}} book(s)_other": "Berhasil mengimpor {{count}} buku"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Modalità automatica",
"Behavior": "Comportamento",
"Book": "Libro",
"Book Cover": "Copertina",
"Bookmark": "Segnalibro",
"Cancel": "Annulla",
"Chapter": "Capitolo",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Nessun file supportato trovato. Formati supportati: {{formats}}",
"Drop to Import Books": "Rilascia per importare libri",
"Custom": "Personalizzato",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Tutto il mondo è un palcoscenico,\nE tutti gli uomini e le donne sono solo attori;\nHanno le loro uscite e le loro entrate,\nE un uomo nella sua vita interpreta molte parti,\nI suoi atti sono sette età.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Tutto il mondo è un palcoscenico,\nE tutti gli uomini e le donne sono solo attori;\nHanno le loro uscite e le loro entrate,\nE un uomo nella sua vita interpreta molte parti,\nI suoi atti sono sette età.\n\n— William Shakespeare",
"Custom Theme": "Tema personalizzato",
"Theme Name": "Nome tema",
"Text Color": "Colore testo",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Importazione automatica all'apertura del file",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Nessun capitolo rilevato.",
"Failed to parse the EPUB file.": "Impossibile analizzare il file EPUB.",
"This book format is not supported.": "Questo formato di libro non è supportato.",
"No chapters detected": "Nessun capitolo rilevato",
"Failed to parse the EPUB file": "Impossibile analizzare il file EPUB",
"This book format is not supported": "Questo formato di libro non è supportato",
"Unable to fetch the translation. Please log in first and try again.": "Impossibile recuperare la traduzione. Accedi prima e riprova.",
"Group": "Gruppo",
"Always on Top": "Sempre in primo piano",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(da 'Come vi piace', Atto II)",
"Link Color": "Colore link",
"Volume Keys for Page Flip": "Tasti volume per girare pagina",
"Clicks for Page Flip": "Clic per girare pagina",
"Swap Clicks Area": "Inverti area dei clic",
"Screen": "Schermo",
"Orientation": "Orientamento",
"Portrait": "Ritratto",
@@ -298,7 +295,6 @@
"Disable Translation": "Disabilita traduzione",
"Scroll": "Scorri",
"Overlap Pixels": "Pixel di sovrapposizione",
"Click": "Fai clic",
"System Language": "Lingua di sistema",
"Security": "Security",
"Allow JavaScript": "Consenti JavaScript",
@@ -370,7 +366,6 @@
"Email:": "Email:",
"Plan:": "Piano:",
"Amount:": "Importo:",
"Subscription ID:": "ID Abbonamento:",
"Go to Library": "Vai alla Libreria",
"Need help? Contact our support team at support@readest.com": "Hai bisogno di aiuto? Contatta il nostro supporto a support@readest.com",
"Free Plan": "Piano Gratuito",
@@ -613,5 +608,170 @@
"Contain": "Contenere",
"{{number}} pages left in chapter": "{{number}} pagine rimaste nel capitolo",
"Device": "Dispositivo",
"E-Ink Mode": "Modalità E-Ink"
"E-Ink Mode": "Modalità E-Ink",
"Highlight Colors": "Colori Evidenziazione",
"Auto Screen Brightness": "Luminosità schermo automatica",
"Pagination": "Impaginazione",
"Disable Double Tap": "Disabilita Doppio Tap",
"Tap to Paginate": "Tocca per Impaginare",
"Click to Paginate": "Clicca per Impaginare",
"Tap Both Sides": "Tocca Entrambi i Lati",
"Click Both Sides": "Clicca Entrambi i Lati",
"Swap Tap Sides": "Scambia Sides Tap",
"Swap Click Sides": "Scambia Sides Click",
"Source and Translated": "Fonte e Tradotto",
"Translated Only": "Solo Tradotto",
"Source Only": "Solo Fonte",
"TTS Text": "Testo TTS",
"The book file is corrupted": "Il file del libro è danneggiato",
"The book file is empty": "Il file del libro è vuoto",
"Failed to open the book file": "Impossibile aprire il file del libro",
"On-Demand Purchase": "Acquisto su Richiesta",
"Full Customization": "Personalizzazione Completa",
"Lifetime Plan": "Piano a Vita",
"One-Time Payment": "Pagamento Una Tantum",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Effettua un pagamento una tantum per godere dell'accesso a vita a funzionalità specifiche su tutti i dispositivi. Acquista funzionalità o servizi specifici solo quando ne hai bisogno.",
"Expand Cloud Sync Storage": "Espandi Spazio di Archiviazione Cloud",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Espandi il tuo spazio di archiviazione cloud per sempre con un acquisto una tantum. Ogni acquisto aggiuntivo aumenta lo spazio.",
"Unlock All Customization Options": "Sblocca Tutte le Opzioni di Personalizzazione",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Sblocca temi aggiuntivi, font, opzioni di layout e lettura ad alta voce, traduttori, servizi di archiviazione cloud.",
"Purchase Successful!": "Acquisto Riuscito!",
"lifetime": "a vita",
"Thank you for your purchase! Your payment has been processed successfully.": "Grazie per il tuo acquisto! Il pagamento è stato elaborato con successo.",
"Order ID:": "ID Ordine:",
"TTS Highlighting": "Evidenziazione TTS",
"Style": "Stile",
"Underline": "Sottolineato",
"Strikethrough": "Barrato",
"Squiggly": "Ondulato",
"Outline": "Contorno",
"Save Current Color": "Salva Colore Corrente",
"Quick Colors": "Colori Veloci",
"Highlighter": "Evidenziatore",
"Save Book Cover": "Salva Copertina del Libro",
"Auto-save last book cover": "Salva automaticamente l'ultima copertina del libro",
"Back": "Indietro",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Email di conferma inviata! Controlla il tuo vecchio e nuovo indirizzo email per confermare la modifica.",
"Failed to update email": "Aggiornamento dell'email non riuscito",
"New Email": "Nuova email",
"Your new email": "La tua nuova email",
"Updating email ...": "Aggiornamento dell'email ...",
"Update email": "Aggiorna email",
"Current email": "Email attuale",
"Update Email": "Aggiorna email",
"All": "Tutti",
"Unable to open book": "Impossibile aprire il libro",
"Punctuation": "Segni di punteggiatura",
"Replace Quotation Marks": "Sostituisci le virgolette",
"Enabled only in vertical layout.": "Abilitato solo in layout verticale.",
"No Conversion": "Nessuna conversione",
"Simplified to Traditional": "Semplificato → Tradizionale",
"Traditional to Simplified": "Tradizionale → Semplificato",
"Simplified to Traditional (Taiwan)": "Semplificato → Tradizionale (Taiwan)",
"Simplified to Traditional (Hong Kong)": "Semplificato → Tradizionale (Hong Kong)",
"Simplified to Traditional (Taiwan), with phrases": "Semplificato → Tradizionale (Taiwan • frasi)",
"Traditional (Taiwan) to Simplified": "Tradizionale (Taiwan) → Semplificato",
"Traditional (Hong Kong) to Simplified": "Tradizionale (Hong Kong) → Semplificato",
"Traditional (Taiwan) to Simplified, with phrases": "Tradizionale (Taiwan • frasi) → Semplificato",
"Convert Simplified and Traditional Chinese": "Converti Cinese Semplificato/Tradizionale",
"Convert Mode": "Modalità conversione",
"Failed to auto-save book cover for lock screen: {{error}}": "Impossibile salvare automaticamente la copertina del libro per la schermata di blocco: {{error}}",
"Download from Cloud": "Scarica dal Cloud",
"Upload to Cloud": "Carica sul Cloud",
"Clear Custom Fonts": "Pulisci Font Personalizzati",
"Columns": "Colonne",
"OPDS Catalogs": "Cataloghi OPDS",
"Adding LAN addresses is not supported in the web app version.": "L'aggiunta di indirizzi LAN non è supportata nella versione web.",
"Invalid OPDS catalog. Please check the URL.": "Catalogo OPDS non valido. Controlla l'URL.",
"Browse and download books from online catalogs": "Sfoglia e scarica libri dai cataloghi online",
"My Catalogs": "I miei cataloghi",
"Add Catalog": "Aggiungi catalogo",
"No catalogs yet": "Nessun catalogo",
"Add your first OPDS catalog to start browsing books": "Aggiungi il tuo primo catalogo OPDS per iniziare a sfogliare i libri",
"Add Your First Catalog": "Aggiungi il tuo primo catalogo",
"Browse": "Sfoglia",
"Popular Catalogs": "Cataloghi popolari",
"Add": "Aggiungi",
"Add OPDS Catalog": "Aggiungi catalogo OPDS",
"Catalog Name": "Nome del catalogo",
"My Calibre Library": "La mia libreria Calibre",
"OPDS URL": "URL OPDS",
"Username (optional)": "Username (opzionale)",
"Password (optional)": "Password (opzionale)",
"Description (optional)": "Descrizione (opzionale)",
"A brief description of this catalog": "Breve descrizione del catalogo",
"Validating...": "Convalida in corso...",
"View All": "Vedi tutto",
"Forward": "Avanti",
"OPDS Catalog": "Catalogo OPDS",
"Home": "Home",
"Library": "Libreria",
"{{count}} items_one": "{{count}} elemento",
"{{count}} items_many": "{{count}} elementi",
"{{count}} items_other": "{{count}} elementi",
"Download completed": "Download completato",
"Download failed": "Download non riuscito",
"Open Access": "Accesso libero",
"Borrow": "Prendi in prestito",
"Buy": "Acquista",
"Subscribe": "Abbonati",
"Sample": "Anteprima",
"Download": "Scarica",
"Open & Read": "Apri e leggi",
"Tags": "Tag",
"Tag": "Tag",
"First": "Primo",
"Previous": "Precedente",
"Next": "Successivo",
"Last": "Ultimo",
"Cannot Load Page": "Impossibile caricare la pagina",
"An error occurred": "Si è verificato un errore",
"Online Library": "Libreria online",
"URL must start with http:// or https://": "URL deve iniziare con http:// o https://",
"Title, Author, Tag, etc...": "Titolo, Autore, Tag, ecc...",
"Query": "Query",
"Subject": "Soggetto",
"Enter {{terms}}": "Inserisci {{terms}}",
"No search results found": "Nessun risultato di ricerca trovato",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Impossibile caricare il feed OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Cerca in {{title}}",
"Manage Storage": "Gestisci archiviazione",
"Failed to load files": "Impossibile caricare i file",
"Deleted {{count}} file(s)_one": "È stato eliminato {{count}} file",
"Deleted {{count}} file(s)_many": "Sono stati eliminati {{count}} file",
"Deleted {{count}} file(s)_other": "Sono stati eliminati {{count}} file",
"Failed to delete {{count}} file(s)_one": "Impossibile eliminare {{count}} file",
"Failed to delete {{count}} file(s)_many": "Impossibile eliminare {{count}} file",
"Failed to delete {{count}} file(s)_other": "Impossibile eliminare {{count}} file",
"Failed to delete files": "Impossibile eliminare i file",
"Total Files": "File totali",
"Total Size": "Dimensione totale",
"Quota": "Quota",
"Used": "Utilizzato",
"Files": "File",
"Search files...": "Cerca file...",
"Newest First": "Più recenti",
"Oldest First": "Più vecchi",
"Largest First": "Più grandi",
"Smallest First": "Più piccoli",
"Name A-Z": "Nome A-Z",
"Name Z-A": "Nome Z-A",
"{{count}} selected_one": "{{count}} selezionato",
"{{count}} selected_many": "{{count}} selezionati",
"{{count}} selected_other": "{{count}} selezionati",
"Delete Selected": "Elimina selezionati",
"Created": "Creato",
"No files found": "Nessun file trovato",
"No files uploaded yet": "Nessun file caricato",
"files": "file",
"Page {{current}} of {{total}}": "Pagina {{current}} di {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Sei sicuro di voler eliminare {{count}} file selezionato?",
"Are you sure to delete {{count}} selected file(s)?_many": "Sei sicuro di voler eliminare {{count}} file selezionati?",
"Are you sure to delete {{count}} selected file(s)?_other": "Sei sicuro di voler eliminare {{count}} file selezionati?",
"Cloud Storage Usage": "Utilizzo archiviazione cloud",
"Rename Group": "Rinomina gruppo",
"From Directory": "Da directory",
"Successfully imported {{count}} book(s)_one": "Importato con successo 1 libro",
"Successfully imported {{count}} book(s)_many": "Importati con successo {{count}} libri",
"Successfully imported {{count}} book(s)_other": "Importati con successo {{count}} libri"
}
@@ -8,7 +8,6 @@
"Auto Mode": "自動モード",
"Behavior": "動作",
"Book": "書籍",
"Book Cover": "表紙",
"Bookmark": "ブックマーク",
"Cancel": "キャンセル",
"Chapter": "章",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "サポートされているファイルが見つかりません。サポートされている形式:{{formats}}",
"Drop to Import Books": "書籍をインポートするにはドロップ",
"Custom": "カスタム",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "全世界は舞台であり、\nそしてすべての男性と女性は単なる役者です。\n彼らは出入り口を持っており、\nそして一人の男性は時に多くの役を演じます。\n彼の行為は七つの時代です。\n\n— ウィリアム・シェイクスピア",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "全世界は舞台であり、\nそしてすべての男性と女性は単なる役者です。\n彼らは出入り口を持っており、\nそして一人の男性は時に多くの役を演じます。\n彼の行為は七つの時代です。\n\n— ウィリアム・シェイクスピア",
"Custom Theme": "カスタムテーマ",
"Theme Name": "テーマ名",
"Text Color": "テキストカラー",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "ファイルを開くと自動インポート",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "章が検出されません",
"Failed to parse the EPUB file.": "EPUBファイルの解析に失敗しました",
"This book format is not supported.": "この書籍形式はサポートされていません",
"No chapters detected": "章が検出されません",
"Failed to parse the EPUB file": "EPUBファイルの解析に失敗しました",
"This book format is not supported": "この書籍形式はサポートされていません",
"Unable to fetch the translation. Please log in first and try again.": "翻訳を取得できません。まずログインして再試行してください。",
"Group": "グループ",
"Always on Top": "常に最前面",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(「お気に召すまま」第2幕)",
"Link Color": "リンクカラー",
"Volume Keys for Page Flip": "音量キーでページめくり",
"Clicks for Page Flip": "タップでページめくり",
"Swap Clicks Area": "タップ領域を入れ替え",
"Screen": "画面",
"Orientation": "向き",
"Portrait": "縦",
@@ -296,7 +293,6 @@
"Disable Translation": "翻訳を無効にする",
"Scroll": "スクロール",
"Overlap Pixels": "重なりピクセル",
"Click": "クリック",
"System Language": "システム言語",
"Security": "セキュリティ",
"Allow JavaScript": "JavaScriptを許可",
@@ -362,7 +358,6 @@
"Email:": "メール:",
"Plan:": "プラン:",
"Amount:": "金額:",
"Subscription ID:": "サブスクリプションID:",
"Go to Library": "ライブラリへ移動",
"Need help? Contact our support team at support@readest.com": "サポートが必要ですか? support@readest.com までお問い合わせください。",
"Free Plan": "無料プラン",
@@ -605,5 +600,158 @@
"Contain": "含む",
"{{number}} pages left in chapter": "章に{{number}}ページ残り",
"Device": "デバイス",
"E-Ink Mode": "E-Inkモード"
"E-Ink Mode": "E-Inkモード",
"Highlight Colors": "ハイライトカラー",
"Auto Screen Brightness": "自動画面の明るさ",
"Pagination": "ページネーション",
"Disable Double Tap": "ダブルタップを無効にする",
"Tap to Paginate": "タップしてページを切り替える",
"Click to Paginate": "クリックしてページを切り替える",
"Tap Both Sides": "両側をタップ",
"Click Both Sides": "両側をクリック",
"Swap Tap Sides": "タップサイドを入れ替える",
"Swap Click Sides": "クリックサイドを入れ替える",
"Source and Translated": "原文と翻訳",
"Translated Only": "翻訳のみ",
"Source Only": "原文のみ",
"TTS Text": "TTSテキスト",
"The book file is corrupted": "本のファイルが破損しています",
"The book file is empty": "本のファイルが空です",
"Failed to open the book file": "本のファイルを開くことができませんでした",
"On-Demand Purchase": "オンデマンド購入",
"Full Customization": "フルカスタマイズ",
"Lifetime Plan": "ライフタイムプラン",
"One-Time Payment": "ワンタイムペイメント",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "すべてのデバイスで特定の機能にライフタイムアクセスを楽しむために、1回の支払いを行います。必要なときにのみ特定の機能やサービスを購入します。",
"Expand Cloud Sync Storage": "クラウド同期ストレージを拡張",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "1回の購入でクラウドストレージを永遠に拡張します。追加の購入ごとに、さらに多くのスペースが追加されます。",
"Unlock All Customization Options": "すべてのカスタマイズオプションを解除",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "追加のテーマ、フォント、レイアウトオプション、読み上げ、翻訳者、クラウドストレージサービスを解除します。",
"Purchase Successful!": "購入成功!",
"lifetime": "ライフタイム",
"Thank you for your purchase! Your payment has been processed successfully.": "ご購入ありがとうございます!お支払いが正常に処理されました。",
"Order ID:": "購入ID",
"TTS Highlighting": "TTSハイライト",
"Style": "スタイル",
"Underline": "下線",
"Strikethrough": "取り消し線",
"Squiggly": "波線",
"Outline": "アウトライン",
"Save Current Color": "現在の色を保存",
"Quick Colors": "クイックカラー",
"Highlighter": "蛍光ペン",
"Save Book Cover": "本のカバーを保存",
"Auto-save last book cover": "最後の本のカバーを自動保存",
"Back": "戻る",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "確認メールを送信しました。変更を確認するために、古いメールアドレスと新しいメールアドレスを確認してください。",
"Failed to update email": "メールの更新に失敗しました",
"New Email": "新しいメールアドレス",
"Your new email": "あなたの新しいメールアドレス",
"Updating email ...": "メールを更新しています ...",
"Update email": "メールを更新",
"Current email": "現在のメールアドレス",
"Update Email": "メールを更新",
"All": "すべて",
"Unable to open book": "本を開くことができません",
"Punctuation": "句読点",
"Replace Quotation Marks": "引用符を置き換える",
"Enabled only in vertical layout.": "縦書きレイアウトでのみ有効です。",
"No Conversion": "変換なし",
"Simplified to Traditional": "簡体字 → 繁体字",
"Traditional to Simplified": "繁体字 → 簡体字",
"Simplified to Traditional (Taiwan)": "簡体字 → 繁体字(台湾)",
"Simplified to Traditional (Hong Kong)": "簡体字 → 繁体字(香港)",
"Simplified to Traditional (Taiwan), with phrases": "簡体字 → 繁体字(台湾)• フレーズ",
"Traditional (Taiwan) to Simplified": "繁体字(台湾) → 簡体字",
"Traditional (Hong Kong) to Simplified": "繁体字(香港) → 簡体字",
"Traditional (Taiwan) to Simplified, with phrases": "繁体字(台湾) → 簡体字 • フレーズ",
"Convert Simplified and Traditional Chinese": "簡体字/繁体字変換",
"Convert Mode": "変換モード",
"Failed to auto-save book cover for lock screen: {{error}}": "ロック画面の本のカバーの自動保存に失敗しました:{{error}}",
"Download from Cloud": "クラウドからダウンロード",
"Upload to Cloud": "クラウドにアップロード",
"Clear Custom Fonts": "カスタムフォントをクリア",
"Columns": "列",
"OPDS Catalogs": "OPDSカタログ",
"Adding LAN addresses is not supported in the web app version.": "Webアプリ版ではLANアドレスの追加はサポートされていません。",
"Invalid OPDS catalog. Please check the URL.": "無効なOPDSカタログです。URLを確認してください。",
"Browse and download books from online catalogs": "オンラインカタログから本を閲覧・ダウンロード",
"My Catalogs": "マイカタログ",
"Add Catalog": "カタログを追加",
"No catalogs yet": "まだカタログはありません",
"Add your first OPDS catalog to start browsing books": "本を閲覧するには、最初のOPDSカタログを追加してください。",
"Add Your First Catalog": "最初のカタログを追加",
"Browse": "閲覧",
"Popular Catalogs": "人気カタログ",
"Add": "追加",
"Add OPDS Catalog": "OPDSカタログを追加",
"Catalog Name": "カタログ名",
"My Calibre Library": "私のCalibreライブラリ",
"OPDS URL": "OPDS URL",
"Username (optional)": "ユーザー名(任意)",
"Password (optional)": "パスワード(任意)",
"Description (optional)": "説明(任意)",
"A brief description of this catalog": "このカタログの簡単な説明",
"Validating...": "検証中...",
"View All": "すべて表示",
"Forward": "進む",
"OPDS Catalog": "OPDSカタログ",
"Home": "ホーム",
"Library": "ライブラリ",
"{{count}} items_other": "{{count}} 件",
"Download completed": "ダウンロード完了",
"Download failed": "ダウンロード失敗",
"Open Access": "オープンアクセス",
"Borrow": "借りる",
"Buy": "購入",
"Subscribe": "購読",
"Sample": "サンプル",
"Download": "ダウンロード",
"Open & Read": "開いて読む",
"Tags": "タグ",
"Tag": "タグ",
"First": "最初",
"Previous": "前へ",
"Next": "次へ",
"Last": "最後",
"Cannot Load Page": "ページを読み込めません",
"An error occurred": "エラーが発生しました",
"Online Library": "オンラインライブラリ",
"URL must start with http:// or https://": "URLはhttp://またはhttps://で始まる必要があります",
"Title, Author, Tag, etc...": "タイトル、著者、タグなど...",
"Query": "クエリ",
"Subject": "件名",
"Enter {{terms}}": "{{terms}}を入力してください",
"No search results found": "検索結果が見つかりません",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDSフィードの読み込みに失敗しました: {{status}} {{statusText}}",
"Search in {{title}}": "{{title}}内を検索",
"Manage Storage": "ストレージ管理",
"Failed to load files": "ファイルの読み込みに失敗しました",
"Deleted {{count}} file(s)_other": "{{count}} 件のファイルを削除しました",
"Failed to delete {{count}} file(s)_other": "{{count}} 件のファイルの削除に失敗しました",
"Failed to delete files": "ファイルの削除に失敗しました",
"Total Files": "ファイル合計",
"Total Size": "合計サイズ",
"Quota": "容量制限",
"Used": "使用済み",
"Files": "ファイル",
"Search files...": "ファイルを検索...",
"Newest First": "新しい順",
"Oldest First": "古い順",
"Largest First": "大きい順",
"Smallest First": "小さい順",
"Name A-Z": "名前 A-Z",
"Name Z-A": "名前 Z-A",
"{{count}} selected_other": "{{count}} 件選択済み",
"Delete Selected": "選択した項目を削除",
"Created": "作成日",
"No files found": "ファイルが見つかりません",
"No files uploaded yet": "まだファイルがアップロードされていません",
"files": "ファイル",
"Page {{current}} of {{total}}": "ページ {{current}} / {{total}}",
"Are you sure to delete {{count}} selected file(s)?_other": "選択した {{count}} 件のファイルを削除してもよろしいですか?",
"Cloud Storage Usage": "クラウドストレージ使用量",
"Rename Group": "グループ名を変更",
"From Directory": "ディレクトリから",
"Successfully imported {{count}} book(s)_other": "成功裏に{{count}}冊の本をインポートしました"
}
@@ -8,7 +8,6 @@
"Auto Mode": "자동 모드",
"Behavior": "동작",
"Book": "책",
"Book Cover": "책 표지",
"Bookmark": "북마크",
"Cancel": "취소",
"Chapter": "챕터",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "지원되는 파일이 없습니다. 지원되는 형식: {{formats}}",
"Drop to Import Books": "책 가져오기 위해 드롭",
"Custom": "사용자 정의",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "세상은 무대이며,\n그리고 모든 남자와 여자는 단지 배우일 뿐입니다;\n그들은 나가는 곳과 들어오는 곳이 있으며,\n그리고 한 남자는 그의 시간에 많은 역할을 합니다,\n그의 행동은 일곱 시대입니다.\n\n— 윌리엄 셰익스피어",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "세상은 무대이며,\n그리고 모든 남자와 여자는 단지 배우일 뿐입니다;\n그들은 나가는 곳과 들어오는 곳이 있으며,\n그리고 한 남자는 그의 시간에 많은 역할을 합니다,\n그의 행동은 일곱 시대입니다.\n\n— 윌리엄 셰익스피어",
"Custom Theme": "사용자 정의 테마",
"Theme Name": "테마 이름",
"Text Color": "텍스트 색상",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "파일 열 때 자동 가져오기",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "장 발견되지 않음.",
"Failed to parse the EPUB file.": "EPUB 파일 구문 분석 실패.",
"This book format is not supported.": "이 책 형식은 지원되지 않습니다.",
"No chapters detected": "장 발견되지 않음",
"Failed to parse the EPUB file": "EPUB 파일 구문 분석 실패",
"This book format is not supported": "이 책 형식은 지원되지 않습니다",
"Unable to fetch the translation. Please log in first and try again.": "번역을 가져올 수 없습니다. 먼저 로그인하고 다시 시도하세요.",
"Group": "그룹",
"Always on Top": "항상 위에",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(당신이 좋아하는 대로, 2막)",
"Link Color": "링크 색상",
"Volume Keys for Page Flip": "페이지 넘김을 위한 볼륨 키",
"Clicks for Page Flip": "페이지 넘김을 위한 클릭",
"Swap Clicks Area": "클릭 영역 교환",
"Screen": "화면",
"Orientation": "방향",
"Portrait": "세로",
@@ -296,7 +293,6 @@
"Disable Translation": "번역 비활성화",
"Scroll": "스크롤",
"Overlap Pixels": "겹치는 픽셀",
"Click": "클릭",
"System Language": "시스템 언어",
"Security": "보안",
"Allow JavaScript": "JavaScript 허용",
@@ -362,7 +358,6 @@
"Email:": "이메일:",
"Plan:": "요금제:",
"Amount:": "금액:",
"Subscription ID:": "구독 ID:",
"Go to Library": "라이브러리로 이동",
"Need help? Contact our support team at support@readest.com": "도움이 필요하시면 support@readest.com으로 문의해주세요.",
"Free Plan": "무료 요금제",
@@ -605,5 +600,158 @@
"Contain": "포함",
"{{number}} pages left in chapter": "남은 페이지 {{number}}장",
"Device": "장치",
"E-Ink Mode": "E-Ink 모드"
"E-Ink Mode": "E-Ink 모드",
"Highlight Colors": "하이라이트 색상",
"Auto Screen Brightness": "자동 화면 밝기",
"Pagination": "페이지 매김",
"Disable Double Tap": "더블 탭 비활성화",
"Tap to Paginate": "탭하여 페이지 매김",
"Click to Paginate": "클릭하여 페이지 매김",
"Tap Both Sides": "양쪽을 탭",
"Click Both Sides": "양쪽을 클릭",
"Swap Tap Sides": "탭 사이드 전환",
"Swap Click Sides": "클릭 사이드 전환",
"Source and Translated": "원본 및 번역",
"Translated Only": "번역만",
"Source Only": "원본만",
"TTS Text": "TTS 텍스트",
"The book file is corrupted": "책 파일이 손상되었습니다",
"The book file is empty": "책 파일이 비어 있습니다",
"Failed to open the book file": "책 파일을 열지 못했습니다",
"On-Demand Purchase": "온디맨드 구매",
"Full Customization": "풀 커스터마이징",
"Lifetime Plan": "평생 플랜",
"One-Time Payment": "일회성 결제",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "모든 장치에서 특정 기능에 대한 평생 액세스를 즐기기 위해 단일 결제를 수행합니다. 필요할 때만 특정 기능이나 서비스를 구매합니다.",
"Expand Cloud Sync Storage": "클라우드 동기화 저장소 확장",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "1회 구매로 클라우드 저장소를 영구적으로 확장합니다. 추가 구매 시 더 많은 공간이 추가됩니다.",
"Unlock All Customization Options": "모든 커스터마이징 옵션 잠금 해제",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "추가 테마, 글꼴, 레이아웃 옵션 및 읽기, 번역기, 클라우드 스토리지 서비스를 잠금 해제합니다.",
"Purchase Successful!": "구매 성공!",
"lifetime": "평생",
"Thank you for your purchase! Your payment has been processed successfully.": "구매해주셔서 감사합니다! 결제가 성공적으로 처리되었습니다.",
"Order ID:": "주문 ID:",
"TTS Highlighting": "TTS 하이라이팅",
"Style": "스타일",
"Underline": "밑줄",
"Strikethrough": "취소선",
"Squiggly": "물결선",
"Outline": "윤곽선",
"Save Current Color": "현재 색상 저장",
"Quick Colors": "빠른 색상",
"Highlighter": "형광펜",
"Save Book Cover": "책 표지 저장",
"Auto-save last book cover": "마지막 책 표지 자동 저장",
"Back": "뒤로",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "확인 이메일이 전송되었습니다! 변경을 확인하려면 이전 이메일 주소와 새 이메일 주소를 확인하세요.",
"Failed to update email": "이메일 업데이트 실패",
"New Email": "새 이메일",
"Your new email": "새 이메일 주소",
"Updating email ...": "이메일 업데이트 중...",
"Update email": "이메일 업데이트",
"Current email": "현재 이메일",
"Update Email": "이메일 업데이트",
"All": "전체",
"Unable to open book": "책을 열 수 없습니다",
"Punctuation": "구두점",
"Replace Quotation Marks": "인용 부호 바꾸기",
"Enabled only in vertical layout.": "세로 레이아웃에서만 활성화됩니다.",
"No Conversion": "변환 없음",
"Simplified to Traditional": "간체 → 번체",
"Traditional to Simplified": "번체 → 간체",
"Simplified to Traditional (Taiwan)": "간체 → 번체 (대만)",
"Simplified to Traditional (Hong Kong)": "간체 → 번체 (홍콩)",
"Simplified to Traditional (Taiwan), with phrases": "간체 → 번체 (대만) • 문구",
"Traditional (Taiwan) to Simplified": "번체 (대만) → 간체",
"Traditional (Hong Kong) to Simplified": "번체 (홍콩) → 간체",
"Traditional (Taiwan) to Simplified, with phrases": "번체 (대만) → 간체 • 문구",
"Convert Simplified and Traditional Chinese": "간체/번체 변환",
"Convert Mode": "변환 모드",
"Failed to auto-save book cover for lock screen: {{error}}": "잠금 화면용 책 표지 자동 저장 실패: {{error}}",
"Download from Cloud": "클라우드에서 다운로드",
"Upload to Cloud": "클라우드에 업로드",
"Clear Custom Fonts": "사용자 정의 글꼴 지우기",
"Columns": "열",
"OPDS Catalogs": "OPDS 카탈로그",
"Adding LAN addresses is not supported in the web app version.": "웹 앱 버전에서는 LAN 주소 추가를 지원하지 않습니다.",
"Invalid OPDS catalog. Please check the URL.": "잘못된 OPDS 카탈로그입니다. URL을 확인해주세요.",
"Browse and download books from online catalogs": "온라인 카탈로그에서 책을 탐색하고 다운로드",
"My Catalogs": "내 카탈로그",
"Add Catalog": "카탈로그 추가",
"No catalogs yet": "아직 카탈로그가 없습니다",
"Add your first OPDS catalog to start browsing books": "책을 탐색하려면 첫 번째 OPDS 카탈로그를 추가하세요.",
"Add Your First Catalog": "첫 번째 카탈로그 추가",
"Browse": "탐색",
"Popular Catalogs": "인기 카탈로그",
"Add": "추가",
"Add OPDS Catalog": "OPDS 카탈로그 추가",
"Catalog Name": "카탈로그 이름",
"My Calibre Library": "내 Calibre 라이브러리",
"OPDS URL": "OPDS URL",
"Username (optional)": "사용자 이름 (선택 사항)",
"Password (optional)": "비밀번호 (선택 사항)",
"Description (optional)": "설명 (선택 사항)",
"A brief description of this catalog": "이 카탈로그의 간단한 설명",
"Validating...": "검증 중...",
"View All": "모두 보기",
"Forward": "다음",
"OPDS Catalog": "OPDS 카탈로그",
"Home": "홈",
"Library": "라이브러리",
"{{count}} items_other": "{{count}}개 항목",
"Download completed": "다운로드 완료",
"Download failed": "다운로드 실패",
"Open Access": "오픈 액세스",
"Borrow": "대출",
"Buy": "구매",
"Subscribe": "구독",
"Sample": "샘플",
"Download": "다운로드",
"Open & Read": "열기 및 읽기",
"Tags": "태그",
"Tag": "태그",
"First": "처음",
"Previous": "이전",
"Next": "다음",
"Last": "마지막",
"Cannot Load Page": "페이지를 불러올 수 없습니다",
"An error occurred": "오류가 발생했습니다",
"Online Library": "온라인 라이브러리",
"URL must start with http:// or https://": "URL은 http:// 또는 https://로 시작해야 합니다",
"Title, Author, Tag, etc...": "제목, 저자, 태그 등...",
"Query": "쿼리",
"Subject": "주제",
"Enter {{terms}}": "{{terms}} 입력",
"No search results found": "검색 결과가 없습니다",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS 피드를 불러오지 못했습니다: {{status}} {{statusText}}",
"Search in {{title}}": "{{title}}에서 검색",
"Manage Storage": "저장소 관리",
"Failed to load files": "파일 로드 실패",
"Deleted {{count}} file(s)_other": "{{count}}개의 파일이 삭제되었습니다",
"Failed to delete {{count}} file(s)_other": "{{count}}개의 파일 삭제 실패",
"Failed to delete files": "파일 삭제 실패",
"Total Files": "총 파일",
"Total Size": "총 용량",
"Quota": "쿼터",
"Used": "사용됨",
"Files": "파일",
"Search files...": "파일 검색...",
"Newest First": "최신순",
"Oldest First": "오래된순",
"Largest First": "큰 파일순",
"Smallest First": "작은 파일순",
"Name A-Z": "이름 A-Z",
"Name Z-A": "이름 Z-A",
"{{count}} selected_other": "{{count}}개 선택됨",
"Delete Selected": "선택 삭제",
"Created": "생성됨",
"No files found": "파일이 없습니다",
"No files uploaded yet": "아직 업로드된 파일이 없습니다",
"files": "파일",
"Page {{current}} of {{total}}": "페이지 {{current}} / {{total}}",
"Are you sure to delete {{count}} selected file(s)?_other": "선택한 {{count}}개의 파일을 삭제하시겠습니까?",
"Cloud Storage Usage": "클라우드 저장소 사용량",
"Rename Group": "그룹 이름 바꾸기",
"From Directory": "디렉토리에서",
"Successfully imported {{count}} book(s)_other": "성공적으로 {{count}} 권의 책이 가져와졌습니다"
}
@@ -0,0 +1,757 @@
{
"Email address": "Alamat e-mel",
"Your Password": "Kata Laluan Anda",
"Your email address": "Alamat e-mel anda",
"Your password": "Kata laluan anda",
"Sign in": "Log masuk",
"Signing in...": "Sedang log masuk...",
"Sign in with {{provider}}": "Log masuk dengan {{provider}}",
"Already have an account? Sign in": "Sudah ada akaun? Log masuk",
"Create a Password": "Cipta Kata Laluan",
"Sign up": "Daftar",
"Signing up...": "Sedang mendaftar...",
"Don't have an account? Sign up": "Tiada akaun? Daftar",
"Check your email for the confirmation link": "Semak e-mel anda untuk pautan pengesahan",
"Signing in ...": "Sedang log masuk...",
"Send a magic link email": "Hantar e-mel pautan ajaib",
"Check your email for the magic link": "Semak e-mel anda untuk pautan ajaib",
"Send reset password instructions": "Hantar arahan tetapkan semula kata laluan",
"Sending reset instructions ...": "Menghantar arahan tetapkan semula...",
"Forgot your password?": "Lupa kata laluan?",
"Check your email for the password reset link": "Semak e-mel anda untuk pautan tetapkan semula kata laluan",
"Phone number": "Nombor telefon",
"Your phone number": "Nombor telefon anda",
"Token": "Token",
"Your OTP token": "Token OTP anda",
"Verify token": "Sahkan token",
"Go Back": "Kembali",
"Sign in with Google": "Log masuk dengan Google",
"Sign in with Apple": "Log masuk dengan Apple",
"Sign in with GitHub": "Log masuk dengan GitHub",
"New Password": "Kata Laluan Baharu",
"Your new password": "Kata laluan baharu anda",
"Update password": "Kemas kini kata laluan",
"Updating password ...": "Mengemas kini kata laluan...",
"Your password has been updated": "Kata laluan anda telah dikemas kini",
"Back": "Kembali",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "E-mel pengesahan telah dihantar! Sila semak alamat e-mel lama dan baharu anda untuk mengesahkan perubahan.",
"Failed to update email": "Gagal mengemas kini e-mel",
"New Email": "E-mel Baharu",
"Your new email": "E-mel baharu anda",
"Updating email ...": "Mengemas kini e-mel...",
"Update email": "Kemas kini e-mel",
"Current email": "E-mel semasa",
"Something went wrong. Don't worry, our team has been notified and we're working on a fix.": "Sesuatu tidak kena. Jangan risau, pasukan kami telah dimaklumkan dan kami sedang menanganinya.",
"Error Details:": "Butiran Ralat:",
"Try Again": "Cuba Lagi",
"Your Library": "Perpustakaan Anda",
"Need help?": "Perlukan bantuan?",
"Contact Support": "Hubungi Sokongan",
"Show Book Details": "Tunjuk Butiran Buku",
"Bookshelf": "Rak Buku",
"Import Books": "Import Buku",
"Confirm Deletion": "Sahkan Pemadaman",
"Are you sure to delete {{count}} selected book(s)?_other": "Adakah anda pasti untuk memadam {{count}} buku yang dipilih?",
"Deselect Book": "Nyahpilih Buku",
"Select Book": "Pilih Buku",
"Download Book": "Muat Turun Buku",
"Upload Book": "Muat Naik Buku",
"Delete": "Padam",
"Deselect Group": "Nyahpilih Kumpulan",
"Select Group": "Pilih Kumpulan",
"Untitled Group": "Kumpulan Tanpa Tajuk",
"Group Books": "Kumpulan Buku",
"Remove From Group": "Alih Keluar Daripada Kumpulan",
"Create New Group": "Cipta Kumpulan Baharu",
"Save": "Simpan",
"All": "Semua",
"Cancel": "Batal",
"Confirm": "Sahkan",
"From Local File": "Dari Fail Tempatan",
"Search in {{count}} Book(s)..._other": "Cari dalam {{count}} Buku...",
"Search Books...": "Cari Buku...",
"Clear Search": "Kosongkan Carian",
"Select Books": "Pilih Buku",
"Deselect": "Nyahpilih",
"Select All": "Pilih Semua",
"View Menu": "Menu Paparan",
"Settings Menu": "Menu Tetapan",
"Failed to select directory": "Gagal memilih direktori",
"The new data directory must be different from the current one.": "Direktori data baharu mesti berbeza daripada yang semasa.",
"Migration failed: {{error}}": "Migrasi gagal: {{error}}",
"Change Data Location": "Tukar Lokasi Data",
"Current Data Location": "Lokasi Data Semasa",
"Loading...": "Memuatkan...",
"File count: {{size}}": "Bilangan fail: {{size}}",
"Total size: {{size}}": "Jumlah saiz: {{size}}",
"Calculating file info...": "Mengira maklumat fail...",
"New Data Location": "Lokasi Data Baharu",
"Choose New Folder": "Pilih Folder Baharu",
"Choose Different Folder": "Pilih Folder Lain",
"Migrating data...": "Memindahkan data...",
"Copying: {{file}}": "Menyalin: {{file}}",
"{{current}} of {{total}} files": "{{current}} daripada {{total}} fail",
"Migration completed successfully!": "Migrasi berjaya diselesaikan!",
"Your data has been moved to the new location. Please restart the application to complete the process.": "Data anda telah dipindahkan ke lokasi baharu. Sila mulakan semula aplikasi untuk melengkapkan proses.",
"Migration failed": "Migrasi gagal",
"Important Notice": "Notis Penting",
"This will move all your app data to the new location. Make sure the destination has enough free space.": "Ini akan memindahkan semua data aplikasi anda ke lokasi baharu. Pastikan destinasi mempunyai ruang kosong yang mencukupi.",
"Close": "Tutup",
"Restart App": "Mula Semula Aplikasi",
"Start Migration": "Mula Migrasi",
"Open": "Buka",
"Group": "Kumpulan",
"Details": "Butiran",
"Dark Mode": "Mod Gelap",
"Light Mode": "Mod Cerah",
"Auto Mode": "Mod Auto",
"Logged in as {{userDisplayName}}": "Log masuk sebagai {{userDisplayName}}",
"Logged in": "Log masuk",
"View account details and quota": "Lihat butiran akaun dan kuota",
"Account": "Akaun",
"Sign In": "Log Masuk",
"Auto Upload Books to Cloud": "Auto Muat Naik Buku ke Awan",
"Auto Import on File Open": "Auto Import Apabila Buka Fail",
"Open Last Book on Start": "Buka Buku Terakhir Semasa Mula",
"Check Updates on Start": "Semak Kemas Kini Semasa Mula",
"Open Book in New Window": "Buka Buku dalam Tetingkap Baharu",
"Fullscreen": "Skrin Penuh",
"Always on Top": "Sentiasa di Atas",
"Always Show Status Bar": "Sentiasa Tunjuk Bar Status",
"Keep Screen Awake": "Kekalkan Skrin Berjaga",
"Background Read Aloud": "Baca Dengan Kuat di Latar Belakang",
"Reload Page": "Muat Semula Halaman",
"Settings": "Tetapan",
"Advanced Settings": "Tetapan Lanjutan",
"Save Book Cover": "Simpan Kulit Buku",
"Auto-save last book cover": "Auto-simpan kulit buku terakhir",
"Upgrade to Readest Premium": "Naik Taraf ke Readest Premium",
"Download Readest": "Muat Turun Readest",
"About Readest": "Tentang Readest",
"Help improve Readest": "Bantu tingkatkan Readest",
"Sharing anonymized statistics": "Berkongsi statistik tanpa nama",
"List": "Senarai",
"Grid": "Grid",
"Crop": "Pangkas",
"Fit": "Muat",
"Title": "Tajuk",
"Author": "Pengarang",
"Format": "Format",
"Date Read": "Tarikh Dibaca",
"Date Added": "Tarikh Ditambah",
"Ascending": "Menaik",
"Descending": "Menurun",
"Book Covers": "Kulit Buku",
"Sort by...": "Susun mengikut...",
"No supported files found. Supported formats: {{formats}}": "Tiada fail yang disokong dijumpai. Format yang disokong: {{formats}}",
"No chapters detected": "Tiada bab dikesan",
"Failed to parse the EPUB file": "Gagal menghurai fail EPUB",
"This book format is not supported": "Format buku ini tidak disokong",
"Failed to open the book file": "Gagal membuka fail buku",
"The book file is empty": "Fail buku kosong",
"The book file is corrupted": "Fail buku rosak",
"Failed to import book(s): {{filenames}}": "Gagal mengimport buku: {{filenames}}",
"Book uploaded: {{title}}": "Buku dimuat naik: {{title}}",
"Insufficient storage quota": "Kuota storan tidak mencukupi",
"Failed to upload book: {{title}}": "Gagal memuat naik buku: {{title}}",
"Book downloaded: {{title}}": "Buku dimuat turun: {{title}}",
"Failed to download book: {{title}}": "Gagal memuat turun buku: {{title}}",
"Book deleted: {{title}}": "Buku dipadam: {{title}}",
"Deleted cloud backup of the book: {{title}}": "Sandaran awan buku dipadam: {{title}}",
"Deleted local copy of the book: {{title}}": "Salinan tempatan buku dipadam: {{title}}",
"Failed to delete book: {{title}}": "Gagal memadam buku: {{title}}",
"Failed to delete cloud backup of the book: {{title}}": "Gagal memadam sandaran awan buku: {{title}}",
"Failed to delete local copy of the book: {{title}}": "Gagal memadam salinan tempatan buku: {{title}}",
"Library Header": "Pengepala Perpustakaan",
"Welcome to your library. You can import your books here and read them anytime.": "Selamat datang ke perpustakaan anda. Anda boleh import buku anda di sini dan membacanya bila-bila masa.",
"Copied to notebook": "Disalin ke buku nota",
"No annotations to export": "Tiada anotasi untuk dieksport",
"Exported from Readest": "Dieksport dari Readest",
"Highlights & Annotations": "Penonjolan & Anotasi",
"Untitled": "Tanpa Tajuk",
"Note": "Nota",
"Copied to clipboard": "Disalin ke papan klip",
"Copy": "Salin",
"Delete Highlight": "Padam Penonjolan",
"Highlight": "Tonjolkan",
"Annotate": "Anotasi",
"Search": "Cari",
"Dictionary": "Kamus",
"Wikipedia": "Wikipedia",
"Translate": "Terjemah",
"Speak": "Tutur",
"Login Required": "Log Masuk Diperlukan",
"Quota Exceeded": "Kuota Terlebih",
"Unable to fetch the translation. Please log in first and try again.": "Tidak dapat mendapatkan terjemahan. Sila log masuk dahulu dan cuba lagi.",
"Unable to fetch the translation. Try again later.": "Tidak dapat mendapatkan terjemahan. Cuba lagi kemudian.",
"Original Text": "Teks Asal",
"Auto Detect": "Kesan Auto",
"(detected)": "(dikesan)",
"Translated Text": "Teks Terjemahan",
"System Language": "Bahasa Sistem",
"No translation available.": "Tiada terjemahan tersedia.",
"Translated by {{provider}}.": "Diterjemah oleh {{provider}}.",
"Remove Bookmark": "Alih Keluar Tandabuku",
"Add Bookmark": "Tambah Tandabuku",
"Books Content": "Kandungan Buku",
"Book Content": "Kandungan Buku",
"Screen Brightness": "Kecerahan Skrin",
"Color": "Warna",
"Previous Section": "Seksyen Sebelumnya",
"Next Section": "Seksyen Seterusnya",
"Previous Page": "Halaman Sebelumnya",
"Next Page": "Halaman Seterusnya",
"Go Forward": "Ke Hadapan",
"Reading Progress": "Kemajuan Membaca",
"Jump to Location": "Lompat ke Lokasi",
"Font Size": "Saiz Fon",
"Page Margin": "Margin Halaman",
"Small": "Kecil",
"Large": "Besar",
"Line Spacing": "Jarak Baris",
"Footer Bar": "Bar Pengaki",
"Table of Contents": "Kandungan",
"Font & Layout": "Fon & Susun Atur",
"Header Bar": "Bar Pengepala",
"View Options": "Pilihan Paparan",
"Sync Conflict": "Konflik Segerak",
"Sync reading progress from \"{{deviceName}}\"?": "Segerakkan kemajuan membaca dari \"{{deviceName}}\"?",
"another device": "peranti lain",
"Local Progress": "Kemajuan Tempatan",
"Remote Progress": "Kemajuan Jauh",
"Failed to connect": "Gagal menyambung",
"Disconnected": "Terputus",
"KOReader Sync Settings": "Tetapan Segerak KOReader",
"Sync as {{userDisplayName}}": "Segerak sebagai {{userDisplayName}}",
"Sync Server Connected": "Pelayan Segerak Disambung",
"Sync Strategy": "Strategi Segerak",
"Ask on conflict": "Tanya jika konflik",
"Always use latest": "Sentiasa guna terkini",
"Send changes only": "Hantar perubahan sahaja",
"Receive changes only": "Terima perubahan sahaja",
"Checksum Method": "Kaedah Semak Jumlah",
"File Content (recommended)": "Kandungan Fail (disyorkan)",
"File Name": "Nama Fail",
"Device Name": "Nama Peranti",
"Connect to your KOReader Sync server.": "Sambung ke pelayan Segerak KOReader anda.",
"Server URL": "URL Pelayan",
"Username": "Nama Pengguna",
"Your Username": "Nama pengguna anda",
"Password": "Kata Laluan",
"Connect": "Sambung",
"Notebook": "Buku Nota",
"Unpin Notebook": "Nyahpin Buku Nota",
"Pin Notebook": "Pin Buku Nota",
"Hide Search Bar": "Sembunyikan Bar Carian",
"Show Search Bar": "Tunjuk Bar Carian",
"Resize Notebook": "Ubah Saiz Buku Nota",
"No notes match your search": "Tiada nota sepadan dengan carian anda",
"Excerpts": "Petikan",
"Notes": "Nota",
"Add your notes here...": "Tambah nota anda di sini...",
"Search notes and excerpts...": "Cari nota dan petikan...",
"{{time}} min left in chapter": "{{time}} min lagi dalam bab",
"{{number}} pages left in chapter": "{{number}} halaman lagi dalam bab",
"{{count}} pages left in chapter_other": "{{count}} halaman lagi dalam bab",
"On {{current}} of {{total}} page": "Di halaman {{current}} daripada {{total}}",
"Unable to open book": "Tidak dapat membuka buku",
"Section Title": "Tajuk Seksyen",
"More Info": "Maklumat Lanjut",
"Parallel Read": "Baca Selari",
"Disable": "Lumpuhkan",
"Enable": "Aktifkan",
"Exit Parallel Read": "Keluar Baca Selari",
"Enter Parallel Read": "Masuk Baca Selari",
"KOReader Sync": "Segerak KOReader",
"Push Progress": "Hantar Kemajuan",
"Pull Progress": "Tarik Kemajuan",
"Export Annotations": "Eksport Anotasi",
"Sort TOC by Page": "Susun Kandungan mengikut Halaman",
"Edit": "Edit",
"Go to Library": "Pergi ke Perpustakaan",
"Book Menu": "Menu Buku",
"Unpin Sidebar": "Nyahpin Bar Sisi",
"Pin Sidebar": "Pin Bar Sisi",
"Search...": "Cari...",
"Search Options": "Pilihan Carian",
"Book": "Buku",
"Chapter": "Bab",
"Match Case": "Padankan Kes",
"Match Whole Words": "Padankan Seluruh Perkataan",
"Match Diacritics": "Padankan Diakritik",
"Sidebar": "Bar Sisi",
"Resize Sidebar": "Ubah Saiz Bar Sisi",
"TOC": "Kandungan",
"Bookmark": "Tandabuku",
"Toggle Sidebar": "Togol Bar Sisi",
"Toggle Translation": "Togol Terjemahan",
"Disable Translation": "Lumpuhkan Terjemahan",
"Enable Translation": "Aktifkan Terjemahan",
"Translation Disabled": "Terjemahan Dilumpuhkan",
"Previous Paragraph": "Perenggan Sebelumnya",
"Previous Sentence": "Ayat Sebelumnya",
"Pause": "Jeda",
"Play": "Main",
"Next Sentence": "Ayat Seterusnya",
"Next Paragraph": "Perenggan Seterusnya",
"Read Aloud": "Baca Dengan Kuat",
"Ready to read aloud": "Bersedia untuk baca dengan kuat",
"TTS not supported for PDF": "TTS tidak disokong untuk PDF",
"TTS not supported for this document": "TTS tidak disokong untuk dokumen ini",
"No Timeout": "Tiada Had Masa",
"{{value}} minute": "{{value}} minit",
"{{value}} minutes": "{{value}} minit",
"{{value}} hour": "{{value}} jam",
"{{value}} hours": "{{value}} jam",
"Voices for {{lang}}": "Suara untuk {{lang}}",
"Slow": "Perlahan",
"Fast": "Pantas",
"{{engine}}: {{count}} voices_other": "{{engine}}: {{count}} suara",
"Zoom Level": "Tahap Zum",
"Zoom Out": "Zum Keluar",
"Reset Zoom": "Tetapkan Semula Zum",
"Zoom In": "Zum Masuk",
"Zoom Mode": "Mod Zum",
"Single Page": "Halaman Tunggal",
"Auto Spread": "Hamparan Auto",
"Fit Page": "Muat Halaman",
"Fit Width": "Muat Lebar",
"Separate Cover Page": "Halaman Kulit Berasingan",
"Scrolled Mode": "Mod Tatal",
"Sign in to Sync": "Log masuk untuk Segerak",
"Synced at {{time}}": "Disegerak pada {{time}}",
"Never synced": "Tidak pernah disegerak",
"Invert Image In Dark Mode": "Songsangkan Imej Dalam Mod Gelap",
"Failed to auto-save book cover for lock screen: {{error}}": "Gagal auto-simpan kulit buku untuk skrin kunci: {{error}}",
"Reading Progress Synced": "Kemajuan Membaca Disegerak",
"Page {{page}} of {{total}} ({{percentage}}%)": "Halaman {{page}} daripada {{total}} ({{percentage}}%)",
"Current position": "Kedudukan semasa",
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Lebih kurang halaman {{page}} daripada {{total}} ({{percentage}}%)",
"Approximately {{percentage}}%": "Lebih kurang {{percentage}}%",
"Delete Your Account?": "Padam Akaun Anda?",
"This action cannot be undone. All your data in the cloud will be permanently deleted.": "Tindakan ini tidak boleh dibatalkan. Semua data anda di awan akan dipadam secara kekal.",
"Delete Permanently": "Padam Kekal",
"Restore Purchase": "Pulihkan Pembelian",
"Manage Subscription": "Urus Langganan",
"Reset Password": "Tetapkan Semula Kata Laluan",
"Update Email": "Kemas Kini E-mel",
"Sign Out": "Log Keluar",
"Delete Account": "Padam Akaun",
"Upgrade to {{plan}}": "Naik Taraf ke {{plan}}",
"Complete Your Subscription": "Lengkapkan Langganan Anda",
"Coming Soon": "Akan Datang",
"Upgrade to Plus or Pro": "Naik Taraf ke Plus atau Pro",
"Current Plan": "Pelan Semasa",
"On-Demand Purchase": "Pembelian Atas Permintaan",
"Plan Limits": "Had Pelan",
"Full Customization": "Penyesuaian Penuh",
"Failed to create checkout session": "Gagal mencipta sesi pembayaran",
"No purchases found to restore.": "Tiada pembelian dijumpai untuk dipulihkan.",
"Failed to restore purchases.": "Gagal memulihkan pembelian.",
"Failed to manage subscription.": "Gagal mengurus langganan.",
"Failed to delete user. Please try again later.": "Gagal memadam pengguna. Sila cuba lagi kemudian.",
"Loading profile...": "Memuatkan profil...",
"Processing your payment...": "Memproses pembayaran anda...",
"Please wait while we confirm your subscription.": "Sila tunggu sementara kami mengesahkan langganan anda.",
"Payment Processing": "Pemprosesan Pembayaran",
"Your payment is being processed. This usually takes a few moments.": "Pembayaran anda sedang diproses. Ini biasanya mengambil masa sebentar.",
"Payment Failed": "Pembayaran Gagal",
"We couldn't process your subscription. Please try again or contact support if the issue persists.": "Kami tidak dapat memproses langganan anda. Sila cuba lagi atau hubungi sokongan jika masalah berterusan.",
"Back to Profile": "Kembali ke Profil",
"Purchase Successful!": "Pembelian Berjaya!",
"Subscription Successful!": "Langganan Berjaya!",
"Thank you for your purchase! Your payment has been processed successfully.": "Terima kasih atas pembelian anda! Pembayaran anda telah diproses dengan jayanya.",
"Thank you for your subscription! Your payment has been processed successfully.": "Terima kasih atas langganan anda! Pembayaran anda telah diproses dengan jayanya.",
"Email:": "E-mel:",
"Plan:": "Pelan:",
"Amount:": "Jumlah:",
"Order ID:": "ID Pesanan:",
"Need help? Contact our support team at support@readest.com": "Perlukan bantuan? Hubungi pasukan sokongan kami di support@readest.com",
"Lifetime Plan": "Pelan Seumur Hidup",
"lifetime": "seumur hidup",
"One-Time Payment": "Bayaran Sekali",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Buat bayaran sekali untuk menikmati akses seumur hidup kepada ciri tertentu pada semua peranti. Beli ciri atau perkhidmatan tertentu hanya apabila anda memerlukannya.",
"Expand Cloud Sync Storage": "Kembangkan Storan Segerak Awan",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Kembangkan storan awan anda selamanya dengan pembelian sekali. Setiap pembelian tambahan menambah lebih banyak ruang.",
"Unlock All Customization Options": "Buka Kunci Semua Pilihan Penyesuaian",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Buka kunci tema tambahan, fon, pilihan susun atur dan baca dengan kuat, penterjemah, perkhidmatan storan awan.",
"Free Plan": "Pelan Percuma",
"month": "bulan",
"year": "tahun",
"Cross-Platform Sync": "Segerak Merentas Platform",
"Seamlessly sync your library, progress, highlights, and notes across all your devices—never lose your place again.": "Segerakkan perpustakaan, kemajuan, penonjolan dan nota anda dengan lancar merentas semua peranti anda—tidak akan kehilangan tempat anda lagi.",
"Customizable Reading": "Pembacaan Boleh Disesuaikan",
"Personalize every detail with adjustable fonts, layouts, themes, and advanced display settings for the perfect reading experience.": "Personalisasikan setiap butiran dengan fon boleh laras, susun atur, tema dan tetapan paparan lanjutan untuk pengalaman membaca yang sempurna.",
"AI Read Aloud": "Baca Dengan Kuat AI",
"Enjoy hands-free reading with natural-sounding AI voices that bring your books to life.": "Nikmati pembacaan tanpa tangan dengan suara AI yang berbunyi semula jadi yang menghidupkan buku anda.",
"AI Translations": "Terjemahan AI",
"Translate any text instantly with the power of Google, Azure, or DeepL—understand content in any language.": "Terjemahkan sebarang teks dengan segera dengan kuasa Google, Azure atau DeepL—fahami kandungan dalam mana-mana bahasa.",
"Community Support": "Sokongan Komuniti",
"Connect with fellow readers and get help fast in our friendly community channels.": "Berhubung dengan pembaca lain dan dapatkan bantuan pantas dalam saluran komuniti mesra kami.",
"Cloud Sync Storage": "Storan Segerak Awan",
"AI Translations (per day)": "Terjemahan AI (setiap hari)",
"Plus Plan": "Pelan Plus",
"Includes All Free Plan Benefits": "Termasuk Semua Faedah Pelan Percuma",
"Unlimited AI Read Aloud Hours": "Waktu Baca Dengan Kuat AI Tanpa Had",
"Listen without limits—convert as much text as you like into immersive audio.": "Dengar tanpa had—tukar sebanyak mana teks yang anda suka menjadi audio yang mengasyikkan.",
"More AI Translations": "Lebih Banyak Terjemahan AI",
"Unlock enhanced translation capabilities with more daily usage and advanced options.": "Buka kunci keupayaan terjemahan yang dipertingkatkan dengan lebih banyak penggunaan harian dan pilihan lanjutan.",
"DeepL Pro Access": "Akses DeepL Pro",
"Translate up to 100,000 characters daily with the most accurate translation engine available.": "Terjemahkan sehingga 100,000 aksara setiap hari dengan enjin terjemahan paling tepat yang tersedia.",
"Securely store and access your entire reading collection with up to 5 GB of cloud storage.": "Simpan dan akses koleksi bacaan anda dengan selamat dengan sehingga 5 GB storan awan.",
"Priority Support": "Sokongan Keutamaan",
"Enjoy faster responses and dedicated assistance whenever you need help.": "Nikmati respons yang lebih pantas dan bantuan khusus apabila anda memerlukan bantuan.",
"Pro Plan": "Pelan Pro",
"Includes All Plus Plan Benefits": "Termasuk Semua Faedah Pelan Plus",
"Early Feature Access": "Akses Ciri Awal",
"Be the first to explore new features, updates, and innovations before anyone else.": "Jadilah yang pertama meneroka ciri baharu, kemas kini dan inovasi sebelum orang lain.",
"Advanced AI Tools": "Alat AI Lanjutan",
"Harness powerful AI tools for smarter reading, translation, and content discovery.": "Manfaatkan alat AI yang berkuasa untuk pembacaan, terjemahan dan penemuan kandungan yang lebih bijak.",
"Translate up to 500,000 characters daily with the most accurate translation engine available.": "Terjemahkan sehingga 500,000 aksara setiap hari dengan enjin terjemahan paling tepat yang tersedia.",
"Securely store and access your entire reading collection with up to 20 GB of cloud storage.": "Simpan dan akses koleksi bacaan anda dengan selamat dengan sehingga 20 GB storan awan.",
"Version {{version}}": "Versi {{version}}",
"Check Update": "Semak Kemas Kini",
"Already the latest version": "Sudah versi terkini",
"Checking for updates...": "Memeriksa kemas kini...",
"Error checking for updates": "Ralat memeriksa kemas kini",
"Drop to Import Books": "Lepas untuk Import Buku",
"Terms of Service": "Terma Perkhidmatan",
"Privacy Policy": "Dasar Privasi",
"ON": "HIDUP",
"OFF": "MATI",
"Enter book title": "Masukkan tajuk buku",
"Subtitle": "Subtajuk",
"Enter book subtitle": "Masukkan subtajuk buku",
"Enter author name": "Masukkan nama pengarang",
"Series": "Siri",
"Enter series name": "Masukkan nama siri",
"Series Index": "Indeks Siri",
"Enter series index": "Masukkan indeks siri",
"Total in Series": "Jumlah dalam Siri",
"Enter total books in series": "Masukkan jumlah buku dalam siri",
"Publisher": "Penerbit",
"Enter publisher": "Masukkan penerbit",
"Publication Date": "Tarikh Penerbitan",
"YYYY or YYYY-MM-DD": "TTTT atau TTTT-BB-HH",
"Language": "Bahasa",
"Identifier": "Pengenal Pasti",
"Subjects": "Subjek",
"Fiction, Science, History": "Fiksyen, Sains, Sejarah",
"Description": "Penerangan",
"Enter book description": "Masukkan penerangan buku",
"Change cover image": "Tukar imej kulit",
"Replace": "Ganti",
"Remove cover image": "Alih keluar imej kulit",
"Unlock cover": "Buka kunci kulit",
"Lock cover": "Kunci kulit",
"Auto-Retrieve Metadata": "Auto-Dapatkan Metadata",
"Auto": "Auto",
"Auto-Retrieve": "Auto-Dapatkan",
"Unlock all fields": "Buka kunci semua medan",
"Unlock All": "Buka Kunci Semua",
"Lock all fields": "Kunci semua medan",
"Lock All": "Kunci Semua",
"Reset": "Tetapkan Semula",
"Are you sure to delete the selected book?": "Adakah anda pasti untuk memadam buku yang dipilih?",
"Are you sure to delete the cloud backup of the selected book?": "Adakah anda pasti untuk memadam sandaran awan buku yang dipilih?",
"Are you sure to delete the local copy of the selected book?": "Adakah anda pasti untuk memadam salinan tempatan buku yang dipilih?",
"Edit Metadata": "Edit Metadata",
"Book Details": "Butiran Buku",
"Unknown": "Tidak Diketahui",
"Delete Book Options": "Pilihan Padam Buku",
"Remove from Cloud & Device": "Alih Keluar dari Awan & Peranti",
"Remove from Cloud Only": "Alih Keluar dari Awan Sahaja",
"Remove from Device Only": "Alih Keluar dari Peranti Sahaja",
"Download from Cloud": "Muat Turun dari Awan",
"Upload to Cloud": "Muat Naik ke Awan",
"Published": "Diterbitkan",
"Updated": "Dikemas kini",
"Added": "Ditambah",
"File Size": "Saiz Fail",
"No description available": "Tiada penerangan tersedia",
"Locked": "Dikunci",
"Select Metadata Source": "Pilih Sumber Metadata",
"Keep manual input": "Kekalkan input manual",
"Background Image": "Imej Latar Belakang",
"Import Image": "Import Imej",
"Opacity": "Kelegapan",
"Size": "Saiz",
"Cover": "Kulit",
"Contain": "Kandungan",
"Code Highlighting": "Penonjolan Kod",
"Enable Highlighting": "Aktifkan Penonjolan",
"Code Language": "Bahasa Kod",
"Highlight Colors": "Warna Penonjolan",
"Theme Color": "Warna Tema",
"Custom": "Tersuai",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Seluruh dunia adalah pentas,\nDan semua lelaki dan wanita hanyalah pelakon;\nMereka ada pintu keluar dan pintu masuk mereka,\nDan seseorang dalam masanya memainkan banyak peranan,\nTindakannya adalah tujuh zaman.\n\n— William Shakespeare",
"(from 'As You Like It', Act II)": "(daripada 'As You Like It', Babak II)",
"Custom Theme": "Tema Tersuai",
"Theme Name": "Nama Tema",
"Text Color": "Warna Teks",
"Background Color": "Warna Latar Belakang",
"Link Color": "Warna Pautan",
"Preview": "Pratonton",
"Theme Mode": "Mod Tema",
"TTS Highlighting": "Penonjolan TTS",
"Style": "Gaya",
"Highlighter": "Penanda",
"Underline": "Garis Bawah",
"Strikethrough": "Garis Lorek",
"Squiggly": "Berkelok-kelok",
"Outline": "Garis Besar",
"Save Current Color": "Simpan Warna Semasa",
"Quick Colors": "Warna Pantas",
"Override Book Color": "Ganti Warna Buku",
"Scroll": "Tatal",
"Continuous Scroll": "Tatal Berterusan",
"Overlap Pixels": "Piksel Bertindih",
"Pagination": "Penomboran Halaman",
"Tap to Paginate": "Ketik untuk Nomborkan Halaman",
"Click to Paginate": "Klik untuk Nomborkan Halaman",
"Tap Both Sides": "Ketik Kedua-dua Sisi",
"Click Both Sides": "Klik Kedua-dua Sisi",
"Swap Tap Sides": "Tukar Sisi Ketukan",
"Swap Click Sides": "Tukar Sisi Klik",
"Disable Double Tap": "Lumpuhkan Ketuk Dua Kali",
"Disable Double Click": "Lumpuhkan Klik Dua Kali",
"Volume Keys for Page Flip": "Kekunci Volum untuk Balik Halaman",
"Animation": "Animasi",
"Paging Animation": "Animasi Halaman",
"Device": "Peranti",
"E-Ink Mode": "Mod E-Ink",
"Auto Screen Brightness": "Kecerahan Skrin Auto",
"Security": "Keselamatan",
"Allow JavaScript": "Benarkan JavaScript",
"Enable only if you trust the file.": "Aktifkan hanya jika anda mempercayai fail.",
"Font": "Fon",
"Custom Fonts": "Fon Tersuai",
"Cancel Delete": "Batal Pemadaman",
"Delete Font": "Padam Fon",
"Import Font": "Import Fon",
"Tips": "Petua",
"Supported font formats: .ttf, .otf, .woff, .woff2": "Format fon yang disokong: .ttf, .otf, .woff, .woff2",
"Custom fonts can be selected from the Font Face menu": "Fon tersuai boleh dipilih dari menu Muka Fon",
"Global Settings": "Tetapan Global",
"Apply to All Books": "Gunakan pada Semua Buku",
"Apply to This Book": "Gunakan pada Buku Ini",
"Reset Settings": "Tetapkan Semula Tetapan",
"Clear Custom Fonts": "Kosongkan Fon Tersuai",
"Manage Custom Fonts": "Urus Fon Tersuai",
"System Fonts": "Fon Sistem",
"Serif Font": "Fon Serif",
"Sans-Serif Font": "Fon Sans-Serif",
"Override Book Font": "Ganti Fon Buku",
"Default Font Size": "Saiz Fon Lalai",
"Minimum Font Size": "Saiz Fon Minimum",
"Font Weight": "Berat Fon",
"Font Family": "Keluarga Fon",
"Default Font": "Fon Lalai",
"CJK Font": "Fon CJK",
"Font Face": "Muka Fon",
"Monospace Font": "Fon Monospace",
"Source and Translated": "Sumber dan Terjemahan",
"Translated Only": "Terjemahan Sahaja",
"Source Only": "Sumber Sahaja",
"No Conversion": "Tiada Penukaran",
"Simplified to Traditional": "Ringkas ke Tradisional",
"Traditional to Simplified": "Tradisional ke Ringkas",
"Simplified to Traditional (Taiwan)": "Ringkas ke Tradisional (Taiwan)",
"Simplified to Traditional (Hong Kong)": "Ringkas ke Tradisional (Hong Kong)",
"Simplified to Traditional (Taiwan), with phrases": "Ringkas ke Tradisional (Taiwan), dengan frasa",
"Traditional (Taiwan) to Simplified": "Tradisional (Taiwan) ke Ringkas",
"Traditional (Hong Kong) to Simplified": "Tradisional (Hong Kong) ke Ringkas",
"Traditional (Taiwan) to Simplified, with phrases": "Tradisional (Taiwan) ke Ringkas, dengan frasa",
"Interface Language": "Bahasa Antara Muka",
"Translation": "Terjemahan",
"Show Source Text": "Tunjuk Teks Sumber",
"TTS Text": "Teks TTS",
"Translation Service": "Perkhidmatan Terjemahan",
"Translate To": "Terjemah Ke",
"Punctuation": "Tanda Baca",
"Replace Quotation Marks": "Ganti Tanda Petikan",
"Enabled only in vertical layout.": "Diaktifkan hanya dalam susun atur menegak.",
"Convert Simplified and Traditional Chinese": "Tukar Cina Ringkas dan Tradisional",
"Convert Mode": "Mod Tukar",
"Override Book Layout": "Ganti Susun Atur Buku",
"Writing Mode": "Mod Penulisan",
"Default": "Lalai",
"Horizontal Direction": "Arah Mendatar",
"Vertical Direction": "Arah Menegak",
"RTL Direction": "Arah RTL",
"Border Frame": "Bingkai Sempadan",
"Double Border": "Sempadan Berganda",
"Border Color": "Warna Sempadan",
"Paragraph": "Perenggan",
"Paragraph Margin": "Margin Perenggan",
"Word Spacing": "Jarak Perkataan",
"Letter Spacing": "Jarak Huruf",
"Text Indent": "Inden Teks",
"Full Justification": "Justifikasi Penuh",
"Hyphenation": "Pemenggalan",
"Page": "Halaman",
"Top Margin (px)": "Margin Atas (px)",
"Bottom Margin (px)": "Margin Bawah (px)",
"Left Margin (px)": "Margin Kiri (px)",
"Right Margin (px)": "Margin Kanan (px)",
"Column Gap (%)": "Jurang Lajur (%)",
"Maximum Number of Columns": "Bilangan Maksimum Lajur",
"Maximum Column Height": "Tinggi Maksimum Lajur",
"Maximum Column Width": "Lebar Maksimum Lajur",
"Apply also in Scrolled Mode": "Gunakan juga dalam Mod Tatal",
"Header & Footer": "Pengepala & Pengaki",
"Show Header": "Tunjuk Pengepala",
"Show Footer": "Tunjuk Pengaki",
"Show Remaining Time": "Tunjuk Masa Berbaki",
"Show Remaining Pages": "Tunjuk Halaman Berbaki",
"Show Reading Progress": "Tunjuk Kemajuan Membaca",
"Reading Progress Style": "Gaya Kemajuan Membaca",
"Page Number": "Nombor Halaman",
"Percentage": "Peratusan",
"Screen": "Skrin",
"Orientation": "Orientasi",
"Portrait": "Potret",
"Landscape": "Landskap",
"Apply": "Gunakan",
"Custom Content CSS": "CSS Kandungan Tersuai",
"Enter CSS for book content styling...": "Masukkan CSS untuk penggayaan kandungan buku...",
"Custom Reader UI CSS": "CSS UI Pembaca Tersuai",
"Enter CSS for reader interface styling...": "Masukkan CSS untuk penggayaan antara muka pembaca...",
"Decrease": "Kurangkan",
"Increase": "Tambahkan",
"Layout": "Susun Atur",
"Behavior": "Tingkah Laku",
"Settings Panels": "Panel Tetapan",
"Reset {{settings}}": "Tetapkan Semula {{settings}}",
"Get Help from the Readest Community": "Dapatkan Bantuan dari Komuniti Readest",
"A new version of Readest is available!": "Versi baharu Readest tersedia!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} tersedia (versi terpasang {{currentVersion}}).",
"Download and install now?": "Muat turun dan pasang sekarang?",
"Downloading {{downloaded}} of {{contentLength}}": "Memuat turun {{downloaded}} daripada {{contentLength}}",
"Download finished": "Muat turun selesai",
"DOWNLOAD & INSTALL": "MUAT TURUN & PASANG",
"Changelog": "Log Perubahan",
"Software Update": "Kemas Kini Perisian",
"What's New in Readest": "Apa Yang Baharu dalam Readest",
"Minimize": "Minima",
"Maximize or Restore": "Maksima atau Pulihkan",
"Failed to load subscription plans.": "Gagal memuatkan pelan langganan.",
"Select Files": "Pilih Fail",
"Select Image": "Pilih Imej",
"Select Video": "Pilih Video",
"Select Audio": "Pilih Audio",
"Select Fonts": "Pilih Fon",
"Storage": "Storan",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% Ruang Segerak Awan Digunakan.",
"Translation Characters": "Aksara Terjemahan",
"{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% Aksara Terjemahan Harian Digunakan.",
"Daily translation quota reached. Upgrade your plan to continue using AI translations.": "Kuota terjemahan harian dicapai. Naik taraf pelan anda untuk terus menggunakan terjemahan AI.",
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
"LXGW WenKai TC": "LXGW WenKai TC",
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
"Source Han Serif CN VF": "Source Han Serif CN VF",
"Huiwen-mincho": "Huiwen-mincho",
"KingHwa_OldSong": "KingHwa_OldSong",
"Google Books": "Google Books",
"Open Library": "Open Library",
"Azure Translator": "Azure Translator",
"DeepL": "DeepL",
"Google Translate": "Google Translate",
"Yandex Translate": "Yandex Translate",
"Gray": "Kelabu",
"Sepia": "Sepia",
"Grass": "Rumput",
"Cherry": "Ceri",
"Sky": "Langit",
"Solarized": "Solarized",
"Gruvbox": "Gruvbox",
"Nord": "Nord",
"Contrast": "Kontras",
"Sunset": "Matahari Terbenam",
"Reveal in Finder": "Tunjukkan dalam Finder",
"Reveal in File Explorer": "Tunjukkan dalam File Explorer",
"Reveal in Folder": "Tunjukkan dalam Folder",
"Columns": "Lajur",
"OPDS Catalogs": "Katalog OPDS",
"Adding LAN addresses is not supported in the web app version.": "Menambah alamat LAN tidak disokong dalam versi web.",
"Invalid OPDS catalog. Please check the URL.": "Katalog OPDS tidak sah. Sila semak URL.",
"Browse and download books from online catalogs": "Terokai dan muat turun buku dari katalog dalam talian",
"My Catalogs": "Katalog Saya",
"Add Catalog": "Tambah Katalog",
"No catalogs yet": "Tiada katalog lagi",
"Add your first OPDS catalog to start browsing books": "Tambah katalog OPDS pertama anda untuk mula meneroka buku",
"Add Your First Catalog": "Tambah Katalog Pertama Anda",
"Browse": "Terokai",
"Popular Catalogs": "Katalog Popular",
"Add": "Tambah",
"Add OPDS Catalog": "Tambah Katalog OPDS",
"Catalog Name": "Nama Katalog",
"My Calibre Library": "Perpustakaan Calibre Saya",
"OPDS URL": "URL OPDS",
"Username (optional)": "Nama Pengguna (pilihan)",
"Password (optional)": "Kata Laluan (pilihan)",
"Description (optional)": "Penerangan (pilihan)",
"A brief description of this catalog": "Penerangan ringkas katalog ini",
"Validating...": "Mengesahkan...",
"View All": "Lihat Semua",
"Forward": "Maju",
"OPDS Catalog": "Katalog OPDS",
"Home": "Laman Utama",
"Library": "Perpustakaan",
"{{count}} items_other": "{{count}} item",
"Download completed": "Muat turun selesai",
"Download failed": "Muat turun gagal",
"Open Access": "Akses Terbuka",
"Borrow": "Pinjam",
"Buy": "Beli",
"Subscribe": "Langgan",
"Sample": "Contoh",
"Download": "Muat turun",
"Open & Read": "Buka & Baca",
"Tags": "Tag",
"Tag": "Tag",
"First": "Pertama",
"Previous": "Sebelumnya",
"Next": "Seterusnya",
"Last": "Terakhir",
"Cannot Load Page": "Tidak dapat memuatkan halaman",
"An error occurred": "Ralat telah berlaku",
"Online Library": "Perpustakaan Dalam Talian",
"URL must start with http:// or https://": "URL mesti bermula dengan http:// atau https://",
"Title, Author, Tag, etc...": "Tajuk, Pengarang, Tag, dll...",
"Query": "Pertanyaan",
"Subject": "Subjek",
"Enter {{terms}}": "Masukkan {{terms}}",
"No search results found": "Tiada hasil carian ditemui",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Gagal memuatkan suapan OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Cari dalam {{title}}",
"Manage Storage": "Urus Penyimpanan",
"Failed to load files": "Gagal memuatkan fail",
"Deleted {{count}} file(s)_other": "{{count}} fail telah dipadam",
"Failed to delete {{count}} file(s)_other": "Gagal memadam {{count}} fail",
"Failed to delete files": "Gagal memadam fail",
"Total Files": "Jumlah Fail",
"Total Size": "Jumlah Saiz",
"Quota": "Kuota",
"Used": "Digunakan",
"Files": "Fail",
"Search files...": "Cari fail...",
"Newest First": "Terbaru dahulu",
"Oldest First": "Tertua dahulu",
"Largest First": "Terbesar dahulu",
"Smallest First": "Terkecil dahulu",
"Name A-Z": "Nama A-Z",
"Name Z-A": "Nama Z-A",
"{{count}} selected_other": "{{count}} dipilih",
"Delete Selected": "Padam Dipilih",
"Created": "Dicipta",
"No files found": "Tiada fail dijumpai",
"No files uploaded yet": "Belum ada fail dimuat naik",
"files": "fail",
"Page {{current}} of {{total}}": "Halaman {{current}} daripada {{total}}",
"Are you sure to delete {{count}} selected file(s)?_other": "Adakah anda pasti mahu memadam {{count}} fail yang dipilih?",
"Cloud Storage Usage": "Penggunaan Storan Awan",
"Rename Group": "Namakan Semula Kumpulan",
"From Directory": "Dari Direktori",
"Successfully imported {{count}} book(s)_other": "Berjaya mengimport {{count}} buku"
}
@@ -81,9 +81,9 @@
"Descending": "Aflopend",
"Sort by...": "Sorteren op...",
"No supported files found. Supported formats: {{formats}}": "Geen ondersteunde bestanden gevonden. Ondersteunde formaten: {{formats}}",
"No chapters detected.": "Geen hoofdstukken gedetecteerd.",
"Failed to parse the EPUB file.": "EPUB-bestand kon niet worden verwerkt.",
"This book format is not supported.": "Dit boekformaat wordt niet ondersteund.",
"No chapters detected": "Geen hoofdstukken gedetecteerd",
"Failed to parse the EPUB file": "EPUB-bestand kon niet worden verwerkt",
"This book format is not supported": "Dit boekformaat wordt niet ondersteund",
"Failed to import book(s): {{filenames}}": "Importeren van boek(en) mislukt: {{filenames}}",
"Book uploaded: {{title}}": "Boek geüpload: {{title}}",
"Insufficient storage quota": "Onvoldoende opslagruimte",
@@ -184,8 +184,6 @@
"Behavior": "Gedrag",
"Continuous Scroll": "Doorlopend scrollen",
"Volume Keys for Page Flip": "Volumetoetsen voor pagina omslaan",
"Clicks for Page Flip": "Klikken voor pagina omslaan",
"Swap Clicks Area": "Klikgebieden omwisselen",
"Apply": "Toepassen",
"Font": "Lettertype",
"Layout": "Opmaak",
@@ -198,7 +196,6 @@
"Link Color": "Linkkleur",
"Preview": "Voorbeeld",
"Font & Layout": "Lettertype & opmaak",
"Book Cover": "Boekomslag",
"More Info": "Meer informatie",
"Parallel Read": "Parallel lezen",
"Export Annotations": "Annotaties exporteren",
@@ -271,7 +268,7 @@
"Reveal in File Explorer": "Tonen in Verkenner",
"Reveal in Folder": "Tonen in map",
"Don't have an account? Sign up": "Nog geen account? Registreer u",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "De hele wereld is een toneel,\nEn alle mannen en vrouwen slechts spelers;\nZij hebben hun uitgangen en hun entrees,\nEn één man speelt in zijn tijd vele rollen,\nZijn daden zijnde zeven leeftijden.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "De hele wereld is een toneel,\nEn alle mannen en vrouwen slechts spelers;\nZij hebben hun uitgangen en hun entrees,\nEn één man speelt in zijn tijd vele rollen,\nZijn daden zijnde zeven leeftijden.\n\n— William Shakespeare",
"Next Section": "Volgende sectie",
"Previous Section": "Vorige sectie",
"Next Page": "Volgende pagina",
@@ -297,7 +294,6 @@
"Disable Translation": "Vertaling uitschakelen",
"Scroll": "Scrollen",
"Overlap Pixels": "Overlap pixels",
"Click": "Klik",
"System Language": "Systemtaal",
"Security": "Beveiliging",
"Allow JavaScript": "JavaScript toestaan",
@@ -364,7 +360,6 @@
"Email:": "E-mail:",
"Plan:": "Abonnement:",
"Amount:": "Bedrag:",
"Subscription ID:": "Abonnements-ID:",
"Go to Library": "Ga naar bibliotheek",
"Need help? Contact our support team at support@readest.com": "Hulp nodig? Neem contact op met support via support@readest.com",
"Free Plan": "Gratis abonnement",
@@ -609,5 +604,164 @@
"Contain": "Bevatten",
"{{number}} pages left in chapter": "Nog {{number}} paginas in hoofdstuk",
"Device": "Apparaat",
"E-Ink Mode": "E-Ink Modus"
"E-Ink Mode": "E-Ink Modus",
"Highlight Colors": "Markeerkleuren",
"Auto Screen Brightness": "Automatische Schermhelderheid",
"Pagination": "Paginering",
"Disable Double Tap": "Schakel Dubbel Tikken Uit",
"Tap to Paginate": "Tik om te Pagineren",
"Click to Paginate": "Klik om te Pagineren",
"Tap Both Sides": "Tik op Beide Zijden",
"Click Both Sides": "Klik op Beide Zijden",
"Swap Tap Sides": "Wissel Tap Zijden",
"Swap Click Sides": "Wissel Klik Zijden",
"Source and Translated": "Bron en Vertaling",
"Translated Only": "Alleen Vertaling",
"Source Only": "Alleen Bron",
"TTS Text": "TTS Tekst",
"The book file is corrupted": "Het boekbestand is beschadigd",
"The book file is empty": "Het boekbestand is leeg",
"Failed to open the book file": "Het openen van het boekbestand is mislukt",
"On-Demand Purchase": "On-Demand Aankoop",
"Full Customization": "Volledige Aanpassing",
"Lifetime Plan": "Levenslange Abonnement",
"One-Time Payment": "Eenmalige Betaling",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Doe een eenmalige betaling om levenslange toegang te krijgen tot specifieke functies op alle apparaten. Koop specifieke functies of diensten alleen wanneer je ze nodig hebt.",
"Expand Cloud Sync Storage": "Cloud Sync Opslag Uitbreiden",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Breid je cloudopslag voor altijd uit met een eenmalige aankoop. Elke extra aankoop voegt meer ruimte toe.",
"Unlock All Customization Options": "Ontgrendel Alle Aanpassingsopties",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Ontgrendel extra thema's, lettertypen, lay-outopties en voorlezen, vertalers, cloudopslagdiensten.",
"Purchase Successful!": "Aankoop Succesvol!",
"lifetime": "levenslang",
"Thank you for your purchase! Your payment has been processed successfully.": "Bedankt voor je aankoop! Je betaling is succesvol verwerkt.",
"Order ID:": "Bestel-ID:",
"TTS Highlighting": "TTS-markering",
"Style": "Stijl",
"Underline": "Onderstrepen",
"Strikethrough": "Doorhalen",
"Squiggly": "Kronkelig",
"Outline": "Omtrek",
"Save Current Color": "Huidige kleur opslaan",
"Quick Colors": "Snelle kleuren",
"Highlighter": "Marker",
"Save Book Cover": "Opslaan boekomslag",
"Auto-save last book cover": "Automatisch opslaan laatste boekomslag",
"Back": "Terug",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Bevestigingsmail verzonden! Controleer je oude en nieuwe e-mailadres om de wijziging te bevestigen.",
"Failed to update email": "E-mail bijwerken mislukt",
"New Email": "Nieuw e-mailadres",
"Your new email": "Je nieuwe e-mailadres",
"Updating email ...": "E-mail wordt bijgewerkt...",
"Update email": "E-mail bijwerken",
"Current email": "Huidig e-mailadres",
"Update Email": "E-mail bijwerken",
"All": "Alles",
"Unable to open book": "Kan boek niet openen",
"Punctuation": "Interpunctie",
"Replace Quotation Marks": "Aanhalingstekens vervangen",
"Enabled only in vertical layout.": "Alleen ingeschakeld in verticale lay-out.",
"No Conversion": "Geen conversie",
"Simplified to Traditional": "Vereenvoudigd → Traditioneel",
"Traditional to Simplified": "Traditioneel → Vereenvoudigd",
"Simplified to Traditional (Taiwan)": "Vereenvoudigd → Traditioneel (Taiwan)",
"Simplified to Traditional (Hong Kong)": "Vereenvoudigd → Traditioneel (Hong Kong)",
"Simplified to Traditional (Taiwan), with phrases": "Vereenvoudigd → Traditioneel (Taiwan • zinnen)",
"Traditional (Taiwan) to Simplified": "Traditioneel (Taiwan) → Vereenvoudigd",
"Traditional (Hong Kong) to Simplified": "Traditioneel (Hong Kong) → Vereenvoudigd",
"Traditional (Taiwan) to Simplified, with phrases": "Traditioneel (Taiwan • zinnen) → Vereenvoudigd",
"Convert Simplified and Traditional Chinese": "Converteer Vereenvoudigd/Traditioneel Chinees",
"Convert Mode": "Conversiemodus",
"Failed to auto-save book cover for lock screen: {{error}}": "Automatisch opslaan van boekomslag voor vergrendelscherm mislukt: {{error}}",
"Download from Cloud": "Downloaden van Cloud",
"Upload to Cloud": "Uploaden naar Cloud",
"Clear Custom Fonts": "Aangepaste Lettertypen Wissen",
"Columns": "Kolommen",
"OPDS Catalogs": "OPDS-catalogi",
"Adding LAN addresses is not supported in the web app version.": "Het toevoegen van LAN-adressen wordt niet ondersteund in de webversie.",
"Invalid OPDS catalog. Please check the URL.": "Ongeldige OPDS-catalogus. Controleer de URL.",
"Browse and download books from online catalogs": "Blader door en download boeken uit online catalogi",
"My Catalogs": "Mijn catalogi",
"Add Catalog": "Catalogus toevoegen",
"No catalogs yet": "Nog geen catalogi",
"Add your first OPDS catalog to start browsing books": "Voeg je eerste OPDS-catalogus toe om boeken te bekijken",
"Add Your First Catalog": "Voeg je eerste catalogus toe",
"Browse": "Bladeren",
"Popular Catalogs": "Populaire catalogi",
"Add": "Toevoegen",
"Add OPDS Catalog": "OPDS-catalogus toevoegen",
"Catalog Name": "Catalogusnaam",
"My Calibre Library": "Mijn Calibre-bibliotheek",
"OPDS URL": "OPDS-URL",
"Username (optional)": "Gebruikersnaam (optioneel)",
"Password (optional)": "Wachtwoord (optioneel)",
"Description (optional)": "Beschrijving (optioneel)",
"A brief description of this catalog": "Een korte beschrijving van deze catalogus",
"Validating...": "Valideren...",
"View All": "Alles bekijken",
"Forward": "Verder",
"OPDS Catalog": "OPDS-catalogus",
"Home": "Startpagina",
"Library": "Bibliotheek",
"{{count}} items_one": "{{count}} item",
"{{count}} items_other": "{{count}} items",
"Download completed": "Download voltooid",
"Download failed": "Download mislukt",
"Open Access": "Open toegang",
"Borrow": "Lenen",
"Buy": "Kopen",
"Subscribe": "Abonneren",
"Sample": "Voorbeeld",
"Download": "Downloaden",
"Open & Read": "Openen & Lezen",
"Tags": "Tags",
"Tag": "Tag",
"First": "Eerste",
"Previous": "Vorige",
"Next": "Volgende",
"Last": "Laatste",
"Cannot Load Page": "Pagina kan niet worden geladen",
"An error occurred": "Er is een fout opgetreden",
"Online Library": "Online Bibliotheek",
"URL must start with http:// or https://": "URL moet beginnen met http:// of https://",
"Title, Author, Tag, etc...": "Titel, Auteur, Tag, enzovoort...",
"Query": "Zoekopdracht",
"Subject": "Onderwerp",
"Enter {{terms}}": "Voer {{terms}} in",
"No search results found": "Geen zoekresultaten gevonden",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Het laden van de OPDS-feed is mislukt: {{status}} {{statusText}}",
"Search in {{title}}": "Zoeken in {{title}}",
"Manage Storage": "Opslag beheren",
"Failed to load files": "Bestanden laden mislukt",
"Deleted {{count}} file(s)_one": "{{count}} bestand verwijderd",
"Deleted {{count}} file(s)_other": "{{count}} bestanden verwijderd",
"Failed to delete {{count}} file(s)_one": "Kon {{count}} bestand niet verwijderen",
"Failed to delete {{count}} file(s)_other": "Kon {{count}} bestanden niet verwijderen",
"Failed to delete files": "Bestanden verwijderen mislukt",
"Total Files": "Totaal aantal bestanden",
"Total Size": "Totale grootte",
"Quota": "Quota",
"Used": "Gebruikt",
"Files": "Bestanden",
"Search files...": "Bestanden zoeken...",
"Newest First": "Nieuwste eerst",
"Oldest First": "Oudste eerst",
"Largest First": "Grootste eerst",
"Smallest First": "Kleinste eerst",
"Name A-Z": "Naam A-Z",
"Name Z-A": "Naam Z-A",
"{{count}} selected_one": "{{count}} geselecteerd",
"{{count}} selected_other": "{{count}} geselecteerd",
"Delete Selected": "Verwijder geselecteerde",
"Created": "Gemaakt",
"No files found": "Geen bestanden gevonden",
"No files uploaded yet": "Nog geen bestanden geüpload",
"files": "bestanden",
"Page {{current}} of {{total}}": "Pagina {{current}} van {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Weet je zeker dat je {{count}} geselecteerd bestand wilt verwijderen?",
"Are you sure to delete {{count}} selected file(s)?_other": "Weet je zeker dat je {{count}} geselecteerde bestanden wilt verwijderen?",
"Cloud Storage Usage": "Cloudopslaggebruik",
"Rename Group": "Groep hernoemen",
"From Directory": "Vanuit map",
"Successfully imported {{count}} book(s)_one": "Succesvol 1 boek geïmporteerd",
"Successfully imported {{count}} book(s)_other": "Succesvol {{count}} boeken geïmporteerd"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Tryb automatyczny",
"Behavior": "Zachowanie",
"Book": "Książka",
"Book Cover": "Okładka książki",
"Bookmark": "Zakładka",
"Cancel": "Anuluj",
"Chapter": "Rozdział",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Nie znaleziono obsługiwanych plików. Obsługiwane formaty: {{formats}}",
"Drop to Import Books": "Upuść, aby zaimportować książki",
"Custom": "Niestandardowy",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Cały świat jest sceną,\nA wszyscy mężczyźni i kobiety są tylko aktorami;\nMają swoje wyjścia i wejścia,\nI jeden człowiek w swoim czasie gra wiele ról,\nJego czyny to siedem wieków.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Cały świat jest sceną,\nA wszyscy mężczyźni i kobiety są tylko aktorami;\nMają swoje wyjścia i wejścia,\nI jeden człowiek w swoim czasie gra wiele ról,\nJego czyny to siedem wieków.\n\n— William Shakespeare",
"Custom Theme": "Niestandardowy motyw",
"Theme Name": "Nazwa motywu",
"Text Color": "Kolor tekstu",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Automatyczne importowanie przy otwieraniu pliku",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Nie wykryto rozdziałów.",
"Failed to parse the EPUB file.": "Nie udało się przeanalizować pliku EPUB.",
"This book format is not supported.": "Ten format książki nie jest obsługiwany.",
"No chapters detected": "Nie wykryto rozdziałów",
"Failed to parse the EPUB file": "Nie udało się przeanalizować pliku EPUB",
"This book format is not supported": "Ten format książki nie jest obsługiwany",
"Unable to fetch the translation. Please log in first and try again.": "Nie można pobrać tłumaczenia. Proszę najpierw się zalogować i spróbować ponownie.",
"Group": "Grupa",
"Always on Top": "Zawsze na wierzchu",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(z „Jak wam się podoba”, Akt II)",
"Link Color": "Kolor linku",
"Volume Keys for Page Flip": "Klucze głośności do przewracania stron",
"Clicks for Page Flip": "Kliknięcia do przewracania stron",
"Swap Clicks Area": "Zamień obszar kliknięcia",
"Screen": "Ekran",
"Orientation": "Orientacja",
"Portrait": "Portret",
@@ -299,7 +296,6 @@
"Disable Translation": "Wyłącz tłumaczenie",
"Scroll": "Przewijaj",
"Overlap Pixels": "Przesunięcie pikseli",
"Click": "Kliknij",
"System Language": "Język systemu",
"Security": "Bezpieczeństwo",
"Allow JavaScript": "Zezwól na JavaScript",
@@ -374,7 +370,6 @@
"Email:": "E-mail:",
"Plan:": "Plan:",
"Amount:": "Kwota:",
"Subscription ID:": "ID subskrypcji:",
"Go to Library": "Przejdź do biblioteki",
"Need help? Contact our support team at support@readest.com": "Potrzebujesz pomocy? Skontaktuj się z naszym zespołem wsparcia: support@readest.com",
"Free Plan": "Bezpłatny plan",
@@ -617,5 +612,176 @@
"Contain": "Zawierać",
"{{number}} pages left in chapter": "Pozostało {{number}} stron w rozdziale",
"Device": "Urządzenie",
"E-Ink Mode": "Tryb E-Ink"
"E-Ink Mode": "Tryb E-Ink",
"Highlight Colors": "Kolory wyróżnień",
"Auto Screen Brightness": "Automatyczna jasność ekranu",
"Pagination": "Stronicowanie",
"Disable Double Tap": "Wyłącz podwójne dotknięcie",
"Tap to Paginate": "Dotknij, aby stronicować",
"Click to Paginate": "Kliknij, aby stronicować",
"Tap Both Sides": "Dotknij obu stron",
"Click Both Sides": "Kliknij obu stron",
"Swap Tap Sides": "Wymień Tap Zewnętrzne",
"Swap Click Sides": "Wymień Click Zewnętrzne",
"Source and Translated": "Źródło i Tłumaczenie",
"Translated Only": "Tylko Tłumaczenie",
"Source Only": "Tylko Źródło",
"TTS Text": "Tekst TTS",
"The book file is corrupted": "Plik książki jest uszkodzony",
"The book file is empty": "Plik książki jest pusty",
"Failed to open the book file": "Nie udało się otworzyć pliku książki",
"On-Demand Purchase": "Zakup na żądanie",
"Full Customization": "Pełna Personalizacja",
"Lifetime Plan": "Plan na Całe Życie",
"One-Time Payment": "Jednorazowa Płatność",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Dokonaj jednorazowej płatności, aby cieszyć się dożywotnim dostępem do określonych funkcji na wszystkich urządzeniach. Kupuj konkretne funkcje lub usługi tylko wtedy, gdy ich potrzebujesz.",
"Expand Cloud Sync Storage": "Rozszerz Przechowywanie w Chmurze",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Rozszerz swoją przestrzeń w chmurze na zawsze dzięki jednorazowemu zakupowi. Każdy dodatkowy zakup dodaje więcej miejsca.",
"Unlock All Customization Options": "Odblokuj Wszystkie Opcje Personalizacji",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Odblokuj dodatkowe motywy, czcionki, opcje układu oraz funkcje czytania na głos, tłumacze, usługi przechowywania w chmurze.",
"Purchase Successful!": "Zakup Udany!",
"lifetime": "dożywotnio",
"Thank you for your purchase! Your payment has been processed successfully.": "Dziękujemy za zakup! Twoja płatność została pomyślnie przetworzona.",
"Order ID:": "ID zamówienia:",
"TTS Highlighting": "Wyróżnianie TTS",
"Style": "Styl",
"Underline": "Podkreślenie",
"Strikethrough": "Przekreślenie",
"Squiggly": "Falista linia",
"Outline": "Obrys",
"Save Current Color": "Zapisz bieżący kolor",
"Quick Colors": "Szybkie kolory",
"Highlighter": "Zakreślacz",
"Save Book Cover": "Zapisz okładkę książki",
"Auto-save last book cover": "Automatyczne zapisywanie ostatniej okładki książki",
"Back": "Wstecz",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "E-mail potwierdzający został wysłany! Sprawdź swój stary i nowy adres e-mail, aby potwierdzić zmianę.",
"Failed to update email": "Nie udało się zaktualizować e-maila",
"New Email": "Nowy e-mail",
"Your new email": "Twój nowy adres e-mail",
"Updating email ...": "Aktualizowanie e-maila...",
"Update email": "Zaktualizuj e-mail",
"Current email": "Obecny e-mail",
"Update Email": "Zaktualizuj e-mail",
"All": "Wszystkie",
"Unable to open book": "Nie można otworzyć książki",
"Punctuation": "Interpunkcja",
"Replace Quotation Marks": "Zamień cudzysłowy",
"Enabled only in vertical layout.": "Włączone tylko w układzie pionowym.",
"No Conversion": "Brak konwersji",
"Simplified to Traditional": "Uproszczony → Tradycyjny",
"Traditional to Simplified": "Tradycyjny → Uproszczony",
"Simplified to Traditional (Taiwan)": "Uproszczony → Tradycyjny (Tajwan)",
"Simplified to Traditional (Hong Kong)": "Uproszczony → Tradycyjny (Hongkong)",
"Simplified to Traditional (Taiwan), with phrases": "Uproszczony → Tradycyjny (Tajwan • frazy)",
"Traditional (Taiwan) to Simplified": "Tradycyjny (Tajwan) → Uproszczony",
"Traditional (Hong Kong) to Simplified": "Tradycyjny (Hongkong) → Uproszczony",
"Traditional (Taiwan) to Simplified, with phrases": "Tradycyjny (Tajwan • frazy) → Uproszczony",
"Convert Simplified and Traditional Chinese": "Konwertuj chiński uproszczony/tradycyjny",
"Convert Mode": "Tryb konwersji",
"Failed to auto-save book cover for lock screen: {{error}}": "Nie udało się automatycznie zapisać okładki książki dla ekranu blokady: {{error}}",
"Download from Cloud": "Pobierz z chmury",
"Upload to Cloud": "Prześlij do chmury",
"Clear Custom Fonts": "Wyczyść niestandardowe czcionki",
"Columns": "Kolumny",
"OPDS Catalogs": "Katalogi OPDS",
"Adding LAN addresses is not supported in the web app version.": "Dodawanie adresów LAN nie jest obsługiwane w wersji webowej.",
"Invalid OPDS catalog. Please check the URL.": "Nieprawidłowy katalog OPDS. Sprawdź URL.",
"Browse and download books from online catalogs": "Przeglądaj i pobieraj książki z katalogów online",
"My Catalogs": "Moje katalogi",
"Add Catalog": "Dodaj katalog",
"No catalogs yet": "Brak katalogów",
"Add your first OPDS catalog to start browsing books": "Dodaj pierwszy katalog OPDS, aby rozpocząć przeglądanie książek",
"Add Your First Catalog": "Dodaj pierwszy katalog",
"Browse": "Przeglądaj",
"Popular Catalogs": "Popularne katalogi",
"Add": "Dodaj",
"Add OPDS Catalog": "Dodaj katalog OPDS",
"Catalog Name": "Nazwa katalogu",
"My Calibre Library": "Moja biblioteka Calibre",
"OPDS URL": "URL OPDS",
"Username (optional)": "Nazwa użytkownika (opcjonalnie)",
"Password (optional)": "Hasło (opcjonalnie)",
"Description (optional)": "Opis (opcjonalnie)",
"A brief description of this catalog": "Krótki opis katalogu",
"Validating...": "Weryfikacja...",
"View All": "Pokaż wszystkie",
"Forward": "Dalej",
"OPDS Catalog": "Katalog OPDS",
"Home": "Strona główna",
"Library": "Biblioteka",
"{{count}} items_one": "{{count}} element",
"{{count}} items_few": "{{count}} elementy",
"{{count}} items_many": "{{count}} elementów",
"{{count}} items_other": "{{count}} elementu",
"Download completed": "Pobieranie zakończone",
"Download failed": "Pobieranie nie powiodło się",
"Open Access": "Dostęp otwarty",
"Borrow": "Wypożycz",
"Buy": "Kup",
"Subscribe": "Subskrybuj",
"Sample": "Próbka",
"Download": "Pobierz",
"Open & Read": "Otwórz i czytaj",
"Tags": "Tagi",
"Tag": "Tag",
"First": "Pierwsza",
"Previous": "Poprzednia",
"Next": "Następna",
"Last": "Ostatnia",
"Cannot Load Page": "Nie można załadować strony",
"An error occurred": "Wystąpił błąd",
"Online Library": "Biblioteka online",
"URL must start with http:// or https://": "URL musi zaczynać się od http:// lub https://",
"Title, Author, Tag, etc...": "Tytuł, Autor, Tag, itp...",
"Query": "Zapytanie",
"Subject": "Temat",
"Enter {{terms}}": "Wprowadź {{terms}}",
"No search results found": "Nie znaleziono wyników wyszukiwania",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Nie udało się załadować kanału OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Szukaj w {{title}}",
"Manage Storage": "Zarządzaj pamięcią",
"Failed to load files": "Nie udało się wczytać plików",
"Deleted {{count}} file(s)_one": "Usunięto {{count}} plik",
"Deleted {{count}} file(s)_few": "Usunięto {{count}} pliki",
"Deleted {{count}} file(s)_many": "Usunięto {{count}} plików",
"Deleted {{count}} file(s)_other": "Usunięto {{count}} pliku",
"Failed to delete {{count}} file(s)_one": "Nie udało się usunąć {{count}} pliku",
"Failed to delete {{count}} file(s)_few": "Nie udało się usunąć {{count}} plików",
"Failed to delete {{count}} file(s)_many": "Nie udało się usunąć {{count}} plików",
"Failed to delete {{count}} file(s)_other": "Nie udało się usunąć {{count}} pliku",
"Failed to delete files": "Nie udało się usunąć plików",
"Total Files": "Łączna liczba plików",
"Total Size": "Łączny rozmiar",
"Quota": "Limit",
"Used": "Użyto",
"Files": "Pliki",
"Search files...": "Szukaj plików...",
"Newest First": "Najnowsze najpierw",
"Oldest First": "Najstarsze najpierw",
"Largest First": "Największe najpierw",
"Smallest First": "Najmniejsze najpierw",
"Name A-Z": "Nazwa A-Z",
"Name Z-A": "Nazwa Z-A",
"{{count}} selected_one": "Wybrano {{count}} plik",
"{{count}} selected_few": "Wybrano {{count}} pliki",
"{{count}} selected_many": "Wybrano {{count}} plików",
"{{count}} selected_other": "Wybrano {{count}} pliku",
"Delete Selected": "Usuń wybrane",
"Created": "Utworzono",
"No files found": "Nie znaleziono plików",
"No files uploaded yet": "Nie przesłano jeszcze żadnych plików",
"files": "pliki",
"Page {{current}} of {{total}}": "Strona {{current}} z {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Czy na pewno chcesz usunąć {{count}} wybrany plik?",
"Are you sure to delete {{count}} selected file(s)?_few": "Czy na pewno chcesz usunąć {{count}} wybrane pliki?",
"Are you sure to delete {{count}} selected file(s)?_many": "Czy na pewno chcesz usunąć {{count}} wybranych plików?",
"Are you sure to delete {{count}} selected file(s)?_other": "Czy na pewno chcesz usunąć {{count}} wybrany plik?",
"Cloud Storage Usage": "Użycie pamięci w chmurze",
"Rename Group": "Zmień nazwę grupy",
"From Directory": "Z katalogu",
"Successfully imported {{count}} book(s)_one": "Pomyślnie zaimportowano 1 książkę",
"Successfully imported {{count}} book(s)_few": "Pomyślnie zaimportowano {{count}} książki",
"Successfully imported {{count}} book(s)_many": "Pomyślnie zaimportowano {{count}} książek",
"Successfully imported {{count}} book(s)_other": "Pomyślnie zaimportowano {{count}} książek"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Modo Automático",
"Behavior": "Comportamento",
"Book": "Livro",
"Book Cover": "Capa do Livro",
"Bookmark": "Marcador",
"Cancel": "Cancelar",
"Chapter": "Capítulo",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Nenhum arquivo suportado encontrado. Formatos suportados: {{formats}}",
"Drop to Import Books": "Arraste para Importar Livros",
"Custom": "Personalizado",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Todo o mundo é um palco,\nE todos os homens e mulheres são meros atores;\nEles têm suas saídas e suas entradas,\nE um homem em seu tempo desempenha muitos papéis,\nSeus atos sendo sete idades.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Todo o mundo é um palco,\nE todos os homens e mulheres são meros atores;\nEles têm suas saídas e suas entradas,\nE um homem em seu tempo desempenha muitos papéis,\nSeus atos sendo sete idades.\n\n— William Shakespeare",
"Custom Theme": "Tema Personalizado",
"Theme Name": "Nome do Tema",
"Text Color": "Cor do Texto",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Importação Automática ao Abrir Arquivo",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Nenhum capítulo detectado.",
"Failed to parse the EPUB file.": "Falha ao analisar o arquivo EPUB.",
"This book format is not supported.": "Este formato de livro não é suportado.",
"No chapters detected": "Nenhum capítulo detectado",
"Failed to parse the EPUB file": "Falha ao analisar o arquivo EPUB",
"This book format is not supported": "Este formato de livro não é suportado",
"Unable to fetch the translation. Please log in first and try again.": "Não foi possível buscar a tradução. Faça login primeiro e tente novamente.",
"Group": "Grupo",
"Always on Top": "Sempre no Topo",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(de 'Como Gostais', Ato II)",
"Link Color": "Cor do Link",
"Volume Keys for Page Flip": "Teclas de Volume para Virar Página",
"Clicks for Page Flip": "Cliques para Virar Página",
"Swap Clicks Area": "Trocar Área de Cliques",
"Screen": "Tela",
"Orientation": "Orientação",
"Portrait": "Retrato",
@@ -298,7 +295,6 @@
"Disable Translation": "Desativar tradução",
"Scroll": "Rolar",
"Overlap Pixels": "Sobreposição de Pixels",
"Click": "Clique",
"System Language": "Idioma do Sistema",
"Security": "Segurança",
"Allow JavaScript": "Permitir JavaScript",
@@ -370,7 +366,6 @@
"Email:": "E-mail:",
"Plan:": "Plano:",
"Amount:": "Valor:",
"Subscription ID:": "ID da assinatura:",
"Go to Library": "Ir para a biblioteca",
"Need help? Contact our support team at support@readest.com": "Precisa de ajuda? Entre em contato com nossa equipe de suporte: support@readest.com",
"Free Plan": "Plano gratuito",
@@ -613,5 +608,170 @@
"Contain": "Contém",
"{{number}} pages left in chapter": "Faltam {{number}} páginas neste capítulo",
"Device": "Dispositivo",
"E-Ink Mode": "Modo E-Ink"
"E-Ink Mode": "Modo E-Ink",
"Highlight Colors": "Cores de Destaque",
"Auto Screen Brightness": "Brilho Automático da Tela",
"Pagination": "Paginação",
"Disable Double Tap": "Desativar Toque Duplo",
"Tap to Paginate": "Toque para Paginar",
"Click to Paginate": "Clique para Paginar",
"Tap Both Sides": "Toque em Ambos os Lados",
"Click Both Sides": "Clique em Ambos os Lados",
"Swap Tap Sides": "Trocar Lados do Toque",
"Swap Click Sides": "Trocar Lados do Clique",
"Source and Translated": "Fonte e Traduzido",
"Translated Only": "Apenas Traduzido",
"Source Only": "Apenas Fonte",
"TTS Text": "TTS Texto",
"The book file is corrupted": "O arquivo do livro está corrompido",
"The book file is empty": "O arquivo do livro está vazio",
"Failed to open the book file": "Falha ao abrir o arquivo do livro",
"On-Demand Purchase": "Compra sob demanda",
"Full Customization": "Personalização Completa",
"Lifetime Plan": "Plano Vitalício",
"One-Time Payment": "Pagamento Único",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Faça um único pagamento para desfrutar de acesso vitalício a recursos específicos em todos os dispositivos. Compre recursos ou serviços específicos apenas quando precisar deles.",
"Expand Cloud Sync Storage": "Expandir Armazenamento de Sincronização em Nuvem",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Expanda seu armazenamento em nuvem para sempre com uma compra única. Cada compra adicional adiciona mais espaço.",
"Unlock All Customization Options": "Desbloquear Todas as Opções de Personalização",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Desbloquear temas adicionais, fontes, opções de layout e leitura em voz alta, tradutores, serviços de armazenamento em nuvem.",
"Purchase Successful!": "Compra Bem-Sucedida!",
"lifetime": "vitalício",
"Thank you for your purchase! Your payment has been processed successfully.": "Obrigado pela sua compra! Seu pagamento foi processado com sucesso.",
"Order ID:": "ID do Pedido:",
"TTS Highlighting": "Destaque TTS",
"Style": "Estilo",
"Underline": "Sublinhado",
"Strikethrough": "Tachado",
"Squiggly": "Ondulado",
"Outline": "Contorno",
"Save Current Color": "Salvar cor atual",
"Quick Colors": "Cores rápidas",
"Highlighter": "Marcador",
"Save Book Cover": "Salvar Capa do Livro",
"Auto-save last book cover": "Salvar automaticamente a última capa do livro",
"Back": "Voltar",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "E-mail de confirmação enviado! Verifique os seus endereços de e-mail antigo e novo para confirmar a alteração.",
"Failed to update email": "Falha ao atualizar o e-mail",
"New Email": "Novo e-mail",
"Your new email": "O seu novo e-mail",
"Updating email ...": "A atualizar o e-mail...",
"Update email": "Atualizar e-mail",
"Current email": "E-mail atual",
"Update Email": "Atualizar e-mail",
"All": "Todos",
"Unable to open book": "Não foi possível abrir o livro",
"Punctuation": "Pontuação",
"Replace Quotation Marks": "Substituir Aspas",
"Enabled only in vertical layout.": "Habilitado apenas no layout vertical.",
"No Conversion": "Sem conversão",
"Simplified to Traditional": "Simplificado → Tradicional",
"Traditional to Simplified": "Tradicional → Simplificado",
"Simplified to Traditional (Taiwan)": "Simplificado → Tradicional (Taiwan)",
"Simplified to Traditional (Hong Kong)": "Simplificado → Tradicional (Hong Kong)",
"Simplified to Traditional (Taiwan), with phrases": "Simplificado → Tradicional (Taiwan • frases)",
"Traditional (Taiwan) to Simplified": "Tradicional (Taiwan) → Simplificado",
"Traditional (Hong Kong) to Simplified": "Tradicional (Hong Kong) → Simplificado",
"Traditional (Taiwan) to Simplified, with phrases": "Tradicional (Taiwan • frases) → Simplificado",
"Convert Simplified and Traditional Chinese": "Converter Chinês Simpl./Trad.",
"Convert Mode": "Modo de conversão",
"Failed to auto-save book cover for lock screen: {{error}}": "Falha ao salvar automaticamente a capa do livro para a tela de bloqueio: {{error}}",
"Download from Cloud": "Baixar da Nuvem",
"Upload to Cloud": "Enviar para a Nuvem",
"Clear Custom Fonts": "Limpar Fontes Personalizadas",
"Columns": "Colunas",
"OPDS Catalogs": "Catálogos OPDS",
"Adding LAN addresses is not supported in the web app version.": "Adicionar endereços LAN não é suportado na versão web.",
"Invalid OPDS catalog. Please check the URL.": "Catálogo OPDS inválido. Verifique o URL.",
"Browse and download books from online catalogs": "Navegar e baixar livros de catálogos online",
"My Catalogs": "Meus catálogos",
"Add Catalog": "Adicionar catálogo",
"No catalogs yet": "Nenhum catálogo ainda",
"Add your first OPDS catalog to start browsing books": "Adicione seu primeiro catálogo OPDS para começar a navegar pelos livros",
"Add Your First Catalog": "Adicione seu primeiro catálogo",
"Browse": "Navegar",
"Popular Catalogs": "Catálogos populares",
"Add": "Adicionar",
"Add OPDS Catalog": "Adicionar catálogo OPDS",
"Catalog Name": "Nome do catálogo",
"My Calibre Library": "Minha biblioteca Calibre",
"OPDS URL": "URL do OPDS",
"Username (optional)": "Nome de usuário (opcional)",
"Password (optional)": "Senha (opcional)",
"Description (optional)": "Descrição (opcional)",
"A brief description of this catalog": "Uma breve descrição deste catálogo",
"Validating...": "Validando...",
"View All": "Ver todos",
"Forward": "Avançar",
"OPDS Catalog": "Catálogo OPDS",
"Home": "Início",
"Library": "Biblioteca",
"{{count}} items_one": "{{count}} item",
"{{count}} items_many": "{{count}} itens",
"{{count}} items_other": "{{count}} item",
"Download completed": "Download concluído",
"Download failed": "Falha no download",
"Open Access": "Acesso aberto",
"Borrow": "Emprestar",
"Buy": "Comprar",
"Subscribe": "Assinar",
"Sample": "Amostra",
"Download": "Baixar",
"Open & Read": "Abrir e ler",
"Tags": "Etiquetas",
"Tag": "Etiqueta",
"First": "Pierwsza",
"Previous": "Poprzednia",
"Next": "Następna",
"Last": "Ostatnia",
"Cannot Load Page": "Nie można załadować strony",
"An error occurred": "Wystąpił błąd",
"Online Library": "Biblioteca online",
"URL must start with http:// or https://": "URL deve começar com http:// ou https://",
"Title, Author, Tag, etc...": "Título, Autor, Etiqueta, etc...",
"Query": "Consulta",
"Subject": "Assunto",
"Enter {{terms}}": "Insira {{terms}}",
"No search results found": "Nenhum resultado encontrado",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Falha ao carregar o feed OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Pesquisar em {{title}}",
"Manage Storage": "Gerir Armazenamento",
"Failed to load files": "Falha ao carregar arquivos",
"Deleted {{count}} file(s)_one": "Apagado {{count}} ficheiro",
"Deleted {{count}} file(s)_many": "Apagados {{count}} ficheiros",
"Deleted {{count}} file(s)_other": "Apagado {{count}} ficheiro",
"Failed to delete {{count}} file(s)_one": "Falha ao apagar {{count}} ficheiro",
"Failed to delete {{count}} file(s)_many": "Falha ao apagar {{count}} ficheiros",
"Failed to delete {{count}} file(s)_other": "Falha ao apagar {{count}} ficheiro",
"Failed to delete files": "Falha ao apagar arquivos",
"Total Files": "Total de Arquivos",
"Total Size": "Tamanho Total",
"Quota": "Quota",
"Used": "Usado",
"Files": "Arquivos",
"Search files...": "Procurar arquivos...",
"Newest First": "Mais Recentes Primeiro",
"Oldest First": "Mais Antigos Primeiro",
"Largest First": "Maiores Primeiro",
"Smallest First": "Menores Primeiro",
"Name A-Z": "Nome A-Z",
"Name Z-A": "Nome Z-A",
"{{count}} selected_one": "{{count}} selecionado",
"{{count}} selected_many": "{{count}} selecionados",
"{{count}} selected_other": "{{count}} selecionado",
"Delete Selected": "Apagar Selecionados",
"Created": "Criado",
"No files found": "Nenhum arquivo encontrado",
"No files uploaded yet": "Nenhum arquivo enviado ainda",
"files": "arquivos",
"Page {{current}} of {{total}}": "Página {{current}} de {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Tem certeza de que deseja apagar {{count}} ficheiro selecionado?",
"Are you sure to delete {{count}} selected file(s)?_many": "Tem certeza de que deseja apagar {{count}} ficheiros selecionados?",
"Are you sure to delete {{count}} selected file(s)?_other": "Tem certeza de que deseja apagar {{count}} ficheiro selecionado?",
"Cloud Storage Usage": "Uso de Armazenamento na Nuvem",
"Rename Group": "Renomear Grupo",
"From Directory": "Do Diretório",
"Successfully imported {{count}} book(s)_one": "Importado com sucesso 1 livro",
"Successfully imported {{count}} book(s)_many": "Importados com sucesso {{count}} livros",
"Successfully imported {{count}} book(s)_other": "Importados com sucesso {{count}} livros"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Автоматический режим",
"Behavior": "Поведение",
"Book": "Книга",
"Book Cover": "Обложка книги",
"Bookmark": "Закладка",
"Cancel": "Отмена",
"Chapter": "Глава",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Поддерживаемые файлы не найдены. Поддерживаемые форматы: {{formats}}",
"Drop to Import Books": "Перетащите сюда книги для импорта",
"Custom": "Пользовательский",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Весь мир — театр,\nИ все мужчины и женщины — просто актёры;\nУ них есть свои выходы и входы,\nИ один человек в своё время играет много ролей,\nЕго поступки — это семь веков.\n\n— Уильям Шекспир",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Весь мир — театр,\nИ все мужчины и женщины — просто актёры;\nУ них есть свои выходы и входы,\nИ один человек в своё время играет много ролей,\nЕго поступки — это семь веков.\n\n— Уильям Шекспир",
"Custom Theme": "Пользовательская тема",
"Theme Name": "Название темы",
"Text Color": "Цвет текста",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Автоматический импорт при открытии файла",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Не обнаружено глав.",
"Failed to parse the EPUB file.": "Не удалось разобрать файл EPUB.",
"This book format is not supported.": "Этот формат книги не поддерживается.",
"No chapters detected": "Не обнаружено глав",
"Failed to parse the EPUB file": "Не удалось разобрать файл EPUB",
"This book format is not supported": "Этот формат книги не поддерживается",
"Unable to fetch the translation. Please log in first and try again.": "Не удалось получить перевод. Пожалуйста, войдите в систему и попробуйте снова.",
"Group": "Группа",
"Always on Top": "Всегда сверху",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(из 'Как вам это понравится', Акт II)",
"Link Color": "Цвет ссылки",
"Volume Keys for Page Flip": "Клавиши громкости для перелистывания страниц",
"Clicks for Page Flip": "Клики для перелистывания страниц",
"Swap Clicks Area": "Переключить область кликов",
"Screen": "Экран",
"Orientation": "Ориентация",
"Portrait": "Портрет",
@@ -299,7 +296,6 @@
"Disable Translation": "Отключить перевод",
"Scroll": "Прокрутка",
"Overlap Pixels": "Перекрывающиеся пиксели",
"Click": "Клик",
"System Language": "Системный язык",
"Security": "Безопасность",
"Allow JavaScript": "Разрешить JavaScript",
@@ -374,7 +370,6 @@
"Email:": "Электронная почта:",
"Plan:": "План:",
"Amount:": "Сумма:",
"Subscription ID:": "ID подписки:",
"Go to Library": "Перейти в библиотеку",
"Need help? Contact our support team at support@readest.com": "Нужна помощь? Свяжитесь с нашей поддержкой: support@readest.com",
"Free Plan": "Бесплатный план",
@@ -617,5 +612,176 @@
"Contain": "Содержать",
"{{number}} pages left in chapter": "Осталось {{number}} страниц в главе",
"Device": "Устройство",
"E-Ink Mode": "E-Ink режим"
"E-Ink Mode": "E-Ink режим",
"Highlight Colors": "Цвета выделения",
"Auto Screen Brightness": "Автояркость экрана",
"Pagination": "Пагинация",
"Disable Double Tap": "Отключить двойной тап",
"Tap to Paginate": "Тапните, чтобы разбить на страницы",
"Click to Paginate": "Нажмите, чтобы разбить на страницы",
"Tap Both Sides": "Тапните обе стороны",
"Click Both Sides": "Нажмите обе стороны",
"Swap Tap Sides": "Переключить стороны касания",
"Swap Click Sides": "Переключить стороны клика",
"Source and Translated": "Источник и перевод",
"Translated Only": "Только перевод",
"Source Only": "Только источник",
"TTS Text": "TTS Текст",
"The book file is corrupted": "Файл книги поврежден",
"The book file is empty": "Файл книги пуст",
"Failed to open the book file": "Не удалось открыть файл книги",
"On-Demand Purchase": "Покупка по запросу",
"Full Customization": "Полная настройка",
"Lifetime Plan": "Пожизненный план",
"One-Time Payment": "Единовременный платеж",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Сделайте единовременный платеж, чтобы получить пожизненный доступ к определенным функциям на всех устройствах. Покупайте конкретные функции или услуги только тогда, когда они вам нужны.",
"Expand Cloud Sync Storage": "Расширить облачное хранилище",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Расширьте свое облачное хранилище навсегда с помощью единовременной покупки. Каждая дополнительная покупка добавляет больше места.",
"Unlock All Customization Options": "Разблокировать Все Опции Настройки",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Разблокировать дополнительные темы, шрифты, параметры макета и чтения вслух, переводчики, услуги облачного хранилища.",
"Purchase Successful!": "Покупка Успешна!",
"lifetime": "пожизненный",
"Thank you for your purchase! Your payment has been processed successfully.": "Спасибо за вашу покупку! Ваш платеж был успешно обработан.",
"Order ID:": "ID заказа:",
"TTS Highlighting": "Выделение TTS",
"Style": "Стиль",
"Underline": "Подчёркивание",
"Strikethrough": "Зачёркивание",
"Squiggly": "Волнистая линия",
"Outline": "Контур",
"Save Current Color": "Сохранить текущий цвет",
"Quick Colors": "Быстрые цвета",
"Highlighter": "Маркер",
"Save Book Cover": "Сохранить обложку книги",
"Auto-save last book cover": "Автоматически сохранять последнюю обложку книги",
"Back": "Назад",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Письмо с подтверждением отправлено! Пожалуйста, проверьте старый и новый адреса электронной почты, чтобы подтвердить изменение.",
"Failed to update email": "Не удалось обновить электронную почту",
"New Email": "Новый адрес электронной почты",
"Your new email": "Ваш новый адрес электронной почты",
"Updating email ...": "Обновление электронной почты ...",
"Update email": "Обновить электронную почту",
"Current email": "Текущий адрес электронной почты",
"Update Email": "Обновить электронную почту",
"All": "Все",
"Unable to open book": "Не удалось открыть книгу",
"Punctuation": "Пунктуация",
"Replace Quotation Marks": "Заменить кавычки",
"Enabled only in vertical layout.": "Включено только в вертикальной раскладке.",
"No Conversion": "Без преобразования",
"Simplified to Traditional": "Упрощённый → Традиционный",
"Traditional to Simplified": "Традиционный → Упрощённый",
"Simplified to Traditional (Taiwan)": "Упрощённый → Традиционный (Тайвань)",
"Simplified to Traditional (Hong Kong)": "Упрощённый → Традиционный (Гонконг)",
"Simplified to Traditional (Taiwan), with phrases": "Упрощённый → Традиционный (Тайвань • фразы)",
"Traditional (Taiwan) to Simplified": "Традиционный (Тайвань) → Упрощённый",
"Traditional (Hong Kong) to Simplified": "Традиционный (Гонконг) → Упрощённый",
"Traditional (Taiwan) to Simplified, with phrases": "Традиционный (Тайвань • фразы) → Упрощённый",
"Convert Simplified and Traditional Chinese": "Преобразовать упрощённый/традиционный китайский",
"Convert Mode": "Режим преобразования",
"Failed to auto-save book cover for lock screen: {{error}}": "Не удалось автоматически сохранить обложку книги для экрана блокировки: {{error}}",
"Download from Cloud": "Скачать из облака",
"Upload to Cloud": "Загрузить в облако",
"Clear Custom Fonts": "Очистить пользовательские шрифты",
"Columns": "Колонки",
"OPDS Catalogs": "Каталоги OPDS",
"Adding LAN addresses is not supported in the web app version.": "Добавление LAN-адресов не поддерживается в веб-версии.",
"Invalid OPDS catalog. Please check the URL.": "Недействительный каталог OPDS. Пожалуйста, проверьте URL.",
"Browse and download books from online catalogs": "Просматривать и скачивать книги из онлайн-каталогов",
"My Catalogs": "Мои каталоги",
"Add Catalog": "Добавить каталог",
"No catalogs yet": "Каталоги отсутствуют",
"Add your first OPDS catalog to start browsing books": "Добавьте первый каталог OPDS, чтобы начать просмотр книг",
"Add Your First Catalog": "Добавьте первый каталог",
"Browse": "Просмотр",
"Popular Catalogs": "Популярные каталоги",
"Add": "Добавить",
"Add OPDS Catalog": "Добавить каталог OPDS",
"Catalog Name": "Название каталога",
"My Calibre Library": "Моя библиотека Calibre",
"OPDS URL": "URL OPDS",
"Username (optional)": "Имя пользователя (необязательно)",
"Password (optional)": "Пароль (необязательно)",
"Description (optional)": "Описание (необязательно)",
"A brief description of this catalog": "Краткое описание каталога",
"Validating...": "Проверка...",
"View All": "Посмотреть все",
"Forward": "Вперёд",
"OPDS Catalog": "Каталог OPDS",
"Home": "Главная",
"Library": "Библиотека",
"{{count}} items_one": "{{count}} элемент",
"{{count}} items_few": "{{count}} элемента",
"{{count}} items_many": "{{count}} элементов",
"{{count}} items_other": "{{count}} элемента",
"Download completed": "Загрузка завершена",
"Download failed": "Загрузка не удалась",
"Open Access": "Открытый доступ",
"Borrow": "Взять в аренду",
"Buy": "Купить",
"Subscribe": "Подписаться",
"Sample": "Образец",
"Download": "Скачать",
"Open & Read": "Открыть и читать",
"Tags": "Теги",
"Tag": "Тег",
"First": "Первая",
"Previous": "Предыдущая",
"Next": "Следующая",
"Last": "Последняя",
"Cannot Load Page": "Не удалось загрузить страницу",
"An error occurred": "Произошла ошибка",
"Online Library": "Онлайн библиотека",
"URL must start with http:// or https://": "URL должен начинаться с http:// или https://",
"Title, Author, Tag, etc...": "Название, Автор, Тег и т.д...",
"Query": "Запрос",
"Subject": "Тема",
"Enter {{terms}}": "Введите {{terms}}",
"No search results found": "Результаты поиска не найдены",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Не удалось загрузить ленту OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Поиск в {{title}}",
"Manage Storage": "Управление хранилищем",
"Failed to load files": "Не удалось загрузить файлы",
"Deleted {{count}} file(s)_one": "Удалён {{count}} файл",
"Deleted {{count}} file(s)_few": "Удалено {{count}} файла",
"Deleted {{count}} file(s)_many": "Удалено {{count}} файлов",
"Deleted {{count}} file(s)_other": "Удалён {{count}} файл",
"Failed to delete {{count}} file(s)_one": "Не удалось удалить {{count}} файл",
"Failed to delete {{count}} file(s)_few": "Не удалось удалить {{count}} файла",
"Failed to delete {{count}} file(s)_many": "Не удалось удалить {{count}} файлов",
"Failed to delete {{count}} file(s)_other": "Не удалось удалить {{count}} файл",
"Failed to delete files": "Не удалось удалить файлы",
"Total Files": "Всего файлов",
"Total Size": "Общий размер",
"Quota": "Квота",
"Used": "Использовано",
"Files": "Файлы",
"Search files...": "Поиск файлов...",
"Newest First": "Сначала новые",
"Oldest First": "Сначала старые",
"Largest First": "Сначала крупные",
"Smallest First": "Сначала мелкие",
"Name A-Z": "Имя A-Z",
"Name Z-A": "Имя Z-A",
"{{count}} selected_one": "{{count}} выбранный",
"{{count}} selected_few": "{{count}} выбранных",
"{{count}} selected_many": "{{count}} выбранных",
"{{count}} selected_other": "{{count}} выбранный",
"Delete Selected": "Удалить выбранные",
"Created": "Создано",
"No files found": "Файлы не найдены",
"No files uploaded yet": "Файлы ещё не загружены",
"files": "файлы",
"Page {{current}} of {{total}}": "Страница {{current}} из {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Вы уверены, что хотите удалить {{count}} выбранный файл?",
"Are you sure to delete {{count}} selected file(s)?_few": "Вы уверены, что хотите удалить {{count}} выбранных файла?",
"Are you sure to delete {{count}} selected file(s)?_many": "Вы уверены, что хотите удалить {{count}} выбранных файлов?",
"Are you sure to delete {{count}} selected file(s)?_other": "Вы уверены, что хотите удалить {{count}} выбранный файл?",
"Cloud Storage Usage": "Использование облачного хранилища",
"Rename Group": "Переименовать группу",
"From Directory": "Из каталога",
"Successfully imported {{count}} book(s)_one": "Успешно импортирован 1 книга",
"Successfully imported {{count}} book(s)_few": "Успешно импортировано {{count}} книги",
"Successfully imported {{count}} book(s)_many": "Успешно импортировано {{count}} книг",
"Successfully imported {{count}} book(s)_other": "Успешно импортировано {{count}} книг"
}
@@ -105,9 +105,9 @@
"Book Covers": "පොත් ආවරණ",
"Sort by...": "අනුව සකස් කරන්න...",
"No supported files found. Supported formats: {{formats}}": "සහාය දක්වන ගොනු හමුනොවිණි. සහාය දක්වන ආකෘති: {{formats}}",
"No chapters detected.": "කොටස් හඳුනානොගැනිණි.",
"Failed to parse the EPUB file.": "EPUB ගොනුව විග්‍රහ කිරීමට අසමත්.",
"This book format is not supported.": "මෙම පොත ආකෘතියට සහාය නොදක්වයි.",
"No chapters detected": "කොටස් හඳුනානොගැනිණි",
"Failed to parse the EPUB file": "EPUB ගොනුව විග්‍රහ කිරීමට අසමත්",
"This book format is not supported": "මෙම පොත ආකෘතියට සහාය නොදක්වයි",
"Failed to import book(s): {{filenames}}": "පොත් ආයාත කිරීමට අසමත්: {{filenames}}",
"Book uploaded: {{title}}": "පොත උඩුගත විය: {{title}}",
"Insufficient storage quota": "ප්‍රමාණවත් නොවන ගබඩා ප්‍රමාණය",
@@ -204,9 +204,6 @@
"Scrolled Mode": "අනුචලන ආකාරය",
"Continuous Scroll": "අඛණ්ඩ අනුචලනය",
"Overlap Pixels": "අතිච්ඡාදන පික්සල්",
"Click": "ක්ලික්",
"Clicks for Page Flip": "පිටු පෙරැළීම සඳහා ක්ලික්",
"Swap Clicks Area": "ක්ලික් ප්‍රදේශය මාරු කරන්න",
"Disable Double Click": "ද්විත්ව ක්ලික් අබල කරන්න",
"Volume Keys for Page Flip": "පිටු පෙරැළීම සඳහා ශබ්ද යතුර",
"Animation": "සජීවිකරණය",
@@ -296,7 +293,7 @@
"Color": "වර්ණය",
"Behavior": "හැසිරීම",
"Reset {{settings}}": "{{settings}} නැවත සකස් කරන්න",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "සියලු ලෝකය වේදිකාවක්,\nසහ සියලු පුරුෂයන් සහ ගැහැණියන් හුදු රංගන ශිල්පීන්;\nඔවුන්ට පිටවීම් සහ ඇතුල්වීම් ඇත,\nසහ එක් මනුෂ්‍යයෙක් ඔහුගේ කාලයේ බොහෝ භාගයන් ඉටු කරයි,\nඔහුගේ ක්‍රියා වයස් හතකි.\n\n— විලියම් ශේක්ස්පියර්",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "සියලු ලෝකය වේදිකාවක්,\nසහ සියලු පුරුෂයන් සහ ගැහැණියන් හුදු රංගන ශිල්පීන්;\nඔවුන්ට පිටවීම් සහ ඇතුල්වීම් ඇත,\nසහ එක් මනුෂ්‍යයෙක් ඔහුගේ කාලයේ බොහෝ භාගයන් ඉටු කරයි,\nඔහුගේ ක්‍රියා වයස් හතකි.\n\n— විලියම් ශේක්ස්පියර්",
"(from 'As You Like It', Act II)": "('ඔබට අවශ්‍ය ලෙස', දෙවන අංකයෙන්)",
"Custom Theme": "අභිරුචි තේමාව",
"Theme Name": "තේමා නාමය",
@@ -305,7 +302,6 @@
"Link Color": "සබැඳි වර්ණය",
"Preview": "පෙර දසුන",
"Font & Layout": "අකුරු සහ පිරිසැලසුම",
"Book Cover": "පොත් ආවරණය",
"More Info": "තවත් තොරතුරු",
"Parallel Read": "සමාන්තර කියවීම",
"Disable": "අබල කරන්න",
@@ -379,7 +375,6 @@
"Email:": "ඊමේල්:",
"Plan:": "සැලැස්ම:",
"Amount:": "මුදල:",
"Subscription ID:": "දායකත්ව ID:",
"Go to Library": "පුස්තකාලයට යන්න",
"Need help? Contact our support team at support@readest.com": "උදව් අවශ්‍යද? support@readest.com හි අපගේ සහාය කණ්ඩායමට සම්බන්ධ වන්න",
"Free Plan": "නොමිලේ සැලැස්ම",
@@ -609,5 +604,164 @@
"Contain": "අඩංගු වන්න",
"{{number}} pages left in chapter": "පරිච්ඡේදයේ පිටු {{number}} ඉතිරිව ඇත",
"Device": "උපකරණය",
"E-Ink Mode": "E-Ink ආකාරය"
"E-Ink Mode": "E-Ink ආකාරය",
"Highlight Colors": "ඉස්මතු වර්ණ",
"Auto Screen Brightness": "ස්වයංක්‍රීය තිර දිදුලනුම",
"Pagination": "පිටුගත කිරීම",
"Disable Double Tap": "දෙවරක් ටැප් කිරීම අබල කරන්න",
"Tap to Paginate": "පිටුගත කිරීමට ටැප් කරන්න",
"Click to Paginate": "පිටුගත කිරීමට ක්ලික් කරන්න",
"Tap Both Sides": "පැත්ත දෙකම ටැප් කරන්න",
"Click Both Sides": "පැත්ත දෙකම ක්ලික් කරන්න",
"Swap Tap Sides": "පැත්ත දෙකම ටැප් කරන්න",
"Swap Click Sides": "පැත්ත දෙකම ක්ලික් කරන්න",
"Source and Translated": "මූලාශ්‍ර සහ පරිවර්තනය",
"Translated Only": "පරිවර්තනය පමණක්",
"Source Only": "මූලාශ්‍රය පමණක්",
"TTS Text": "TTS පෙළ",
"The book file is corrupted": "පොතේ ගොනුව අසමත් වී ඇත",
"The book file is empty": "පොතේ ගොනුව හිස් වේ",
"Failed to open the book file": "පොතේ ගොනුව විවෘත කිරීමට අසමත් විය",
"On-Demand Purchase": "අවශ්‍යතාවය මත මිලදී ගැනීම",
"Full Customization": "සම්පූර්ණ අභිරුචි",
"Lifetime Plan": "පොදුකාලීන සැලැස්ම",
"One-Time Payment": "එක්වර ගෙවීම",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "සියලු උපකරණ සඳහා විශේෂිත විශේෂාංග සඳහා පොදුකාලීන ප්‍රවේශය ලබා ගැනීමට එක්වර ගෙවීමක් කරන්න. ඔබට අවශ්‍ය වන විශේෂිත විශේෂාංග හෝ සේවා මිලදී ගන්න.",
"Expand Cloud Sync Storage": "කලාප සමාන්‍ය ගබඩා විශාල කරන්න",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "එක්වර මිලදී ගැනීමක් මඟින් ඔබගේ කලාප ගබඩාව සදාකාලිකව විශාල කරන්න. එක් එක් අමතර මිලදී ගැනීමක් තවත් ස්ථානයක් එක් කරයි.",
"Unlock All Customization Options": "සියලු අභිරුචි විකල්ප විවෘත කරන්න",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "අමතර තේමාවන්, ෆොන්ට්, ආකෘති විකල්ප සහ පවසා කියවීම, පරිවර්තක, කලාප ගබඩා සේවා විවෘත කරන්න.",
"Purchase Successful!": "මිලදී ගැනීම සාර්ථකයි!",
"lifetime": "පොදුකාලීන",
"Thank you for your purchase! Your payment has been processed successfully.": "ඔබේ මිලදී ගැනීමට ස්තූතියි! ඔබේ ගෙවීම සාර්ථකව සැකසිණි.",
"Order ID:": "ඇණවුම් හැඳුනුම්පත:",
"TTS Highlighting": "TTS උද්දීපනය",
"Style": "රූපය",
"Underline": "ඇටය",
"Strikethrough": "ඇඳුම",
"Squiggly": "වැලි රේඛාව",
"Outline": "පරිසීමාව",
"Save Current Color": "දැන් ඇති වර්ණය සුරකින්න",
"Quick Colors": "ක්ෂණික වර්ණ",
"Highlighter": "ඉස්මතු කරන්නා",
"Save Book Cover": "පොත් ආවරණය සුරකින්න",
"Auto-save last book cover": "අවසන් පොත් ආවරණය ස්වයංක්‍රීයව සුරකින්න",
"Back": "ආපසු",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "තහවුරු කිරීමේ විද්‍යුත් තැපැල් පණිවිඩය යවන්නා! වෙනස්කම තහවුරු කිරීම සඳහා ඔබගේ පැරණි සහ නව විද්‍යුත් තැපැල් ලිපින පරීක්ෂා කරන්න.",
"Failed to update email": "විද්‍යුත් තැපැල් යාවත්කාලීන කිරීමට අසමත් විය",
"New Email": "නව විද්‍යුත් තැපැල්",
"Your new email": "ඔබේ නව විද්‍යුත් තැපැල්",
"Updating email ...": "විද්‍යුත් තැපැල් යාවත්කාලීන කරමින් ...",
"Update email": "විද්‍යුත් තැපැල් යාවත්කාලීන කරන්න",
"Current email": "වත්මන් විද්‍යුත් තැපැල්",
"Update Email": "විද්‍යුත් තැපැල් යාවත්කාලීන කරන්න",
"All": "සියල්ල",
"Unable to open book": "පොත විවෘත කිරීමට නොහැක",
"Punctuation": "ලකුණු",
"Replace Quotation Marks": "උද්ධෘත ලකුණු ප්‍රතිස්ථාපනය කරන්න",
"Enabled only in vertical layout.": "සිරස් ආකෘතියේ පමණක් සක්‍රීයයි.",
"No Conversion": "ප්‍රතිවර්තනයක් නැත",
"Simplified to Traditional": "සරල → සාම්ප්‍රදායික",
"Traditional to Simplified": "සාම්ප්‍රදායික → සරල",
"Simplified to Traditional (Taiwan)": "සරල → සාම්ප්‍රදායික (තායිවான்)",
"Simplified to Traditional (Hong Kong)": "සරල → සාම්ප්‍රදායික (හොං කොං)",
"Simplified to Traditional (Taiwan), with phrases": "සරල → සාම්ප්‍රදායික (තායිවාන • වාක්‍ය)",
"Traditional (Taiwan) to Simplified": "සාම්ප්‍රදායික (තායිවාන) → සරල",
"Traditional (Hong Kong) to Simplified": "සාම්ප්‍රදායික (හොං කොං) → සරල",
"Traditional (Taiwan) to Simplified, with phrases": "සාම්ප්‍රදායික (තායිවාන • වාක්‍ය) → සරල",
"Convert Simplified and Traditional Chinese": "සරල/සාම්ප්‍රදායික චීන පරිවර්තනය",
"Convert Mode": "පරිවර්තන මෝඩියුලය",
"Failed to auto-save book cover for lock screen: {{error}}": "අගුළු තිරය සඳහා පොත් ආවරණය ස්වයංක්‍රීයව සුරකිීමට අසමත් විය: {{error}}",
"Download from Cloud": "කලාඬයෙන් බාගත කරන්න",
"Upload to Cloud": "කලාඬයට උඩුගත කරන්න",
"Clear Custom Fonts": "අභිරුචි අකුරු පැහැදිලි කරන්න",
"Columns": "තීරු",
"OPDS Catalogs": "OPDS දත්තසමුදා",
"Adding LAN addresses is not supported in the web app version.": "වෙබ් යෙදුම් අනුවාදයේ LAN ලිපින එක් කිරීම සහය නොදක්වයි.",
"Invalid OPDS catalog. Please check the URL.": "අවලංගු OPDS දත්තසමුදායකි. කරුණාකර URL පරීක්ෂා කරන්න.",
"Browse and download books from online catalogs": "ඔන්ලයින් දත්තසමුදා වලින් පොත් බ්‍රවුස් සහ බාගන්න",
"My Catalogs": "මගේ දත්තසමුදා",
"Add Catalog": "දත්තසමුදා එක් කරන්න",
"No catalogs yet": "තවම දත්තසමුදා නැත",
"Add your first OPDS catalog to start browsing books": "පොත් බ්‍රවුස් කිරීමට ඔබේ පළමු OPDS දත්තසමුදා එක් කරන්න",
"Add Your First Catalog": "ඔබේ පළමු දත්තසමුදා එක් කරන්න",
"Browse": "බ්‍රවුස් කරන්න",
"Popular Catalogs": "ප්‍රසිද්ධ දත්තසමුදා",
"Add": "එකතු කරන්න",
"Add OPDS Catalog": "OPDS දත්තසමුදා එක් කරන්න",
"Catalog Name": "දත්තසමුදා නාමය",
"My Calibre Library": "මගේ Calibre පුස්තකාලය",
"OPDS URL": "OPDS URL",
"Username (optional)": "පරිශීලක නාමය (අත්‍යාවශ්‍ය නොවේ)",
"Password (optional)": "මුරපදය (අත්‍යාවශ්‍ය නොවේ)",
"Description (optional)": "විස්තරය (අත්‍යාවශ්‍ය නොවේ)",
"A brief description of this catalog": "මෙම දත්තසමුදායේ කෙටි විස්තරයක්",
"Validating...": "තහවුරු කරමින්...",
"View All": "සියල්ල බලන්න",
"Forward": "ඉදිරියට",
"OPDS Catalog": "OPDS දත්තසමුදා",
"Home": "මුල් පිටුව",
"Library": "පුස්තකාලය",
"{{count}} items_one": "{{count}} අයිතමය",
"{{count}} items_other": "{{count}} අයිතම",
"Download completed": "බාගත කිරීම සම්පූර්ණයි",
"Download failed": "බාගත කිරීම අසාර්ථකයි",
"Open Access": "විවෘත ප්‍රවේශය",
"Borrow": "උधාර ගන්න",
"Buy": "ගන්න",
"Subscribe": "දායක වන්න",
"Sample": "නම්නය",
"Download": "බාගන්න",
"Open & Read": "විවෘත කර කියවන්න",
"Tags": "ටැග්",
"Tag": "ටැග්",
"First": "පළමුවා",
"Previous": "පෙර",
"Next": "ඊළඟ",
"Last": "අවසාන",
"Cannot Load Page": "පිටුවට ප්‍රවේශ විය නොහැක",
"An error occurred": "දෝෂයක් සිදු විය",
"Online Library": "ඔන්ලයින් පුස්තකාලය",
"URL must start with http:// or https://": "URL එක http:// හෝ https:// සමඟ ආරම්භ විය යුතුය",
"Title, Author, Tag, etc...": "ශීර්ෂය, කතුවරයා, ටැග්, ආදිය...",
"Query": "විමසුම",
"Subject": "විෂය",
"Enter {{terms}}": "{{terms}} ඇතුළත් කරන්න",
"No search results found": "සෙවුම් ප්‍රතිඵල නොමැත",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ආහාරය පූරණය කිරීමට අසමත් විය: {{status}} {{statusText}}",
"Search in {{title}}": "{{title}} තුළ සෙවීම",
"Manage Storage": "ගබඩා කළමනාකරණය කරන්න",
"Failed to load files": "ගොනු උඩුගත කිරීමට නොහැකි විය",
"Deleted {{count}} file(s)_one": "ගොනුව මකා දමා ඇත",
"Deleted {{count}} file(s)_other": "ගොනු මකා දමා ඇත",
"Failed to delete {{count}} file(s)_one": "ගොනුව මකා දැමිය නොහැකි විය",
"Failed to delete {{count}} file(s)_other": "ගොනු මකා දැමිය නොහැකි විය",
"Failed to delete files": "ගොනු මකා දැමිය නොහැකි විය",
"Total Files": "මුළු ගොනු",
"Total Size": "මුළු ප්‍රමාණය",
"Quota": "කොටස",
"Used": "භාවිතා කරන ලදී",
"Files": "ගොනු",
"Search files...": "ගොනු සොයන්න...",
"Newest First": "නවතම පළමුව",
"Oldest First": "පැරණිම පළමුව",
"Largest First": "විශාලතම පළමුව",
"Smallest First": "කුඩාතම පළමුව",
"Name A-Z": "නම A-Z",
"Name Z-A": "නම Z-A",
"{{count}} selected_one": "තෝරාගත් ගොනුව",
"{{count}} selected_other": "තෝරාගත් ගොනු",
"Delete Selected": "තෝරාගත් මකා දමන්න",
"Created": "තනන ලදී",
"No files found": "ගොනු හමු නොවීය",
"No files uploaded yet": "ගොනු තවම උඩුගත කර නැත",
"files": "ගොනු",
"Page {{current}} of {{total}}": "පිටුව {{current}} / {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "තෝරාගත් ගොනුව මකන්නට විශ්වාසද?",
"Are you sure to delete {{count}} selected file(s)?_other": "තෝරාගත් ගොනු මකන්නට විශ්වාසද?",
"Cloud Storage Usage": "කලාප ගබඩා භාවිතය",
"Rename Group": "කණ්ඩායම නැවත නම් කරන්න",
"From Directory": "ෆෝල්ඩරයෙන්",
"Successfully imported {{count}} book(s)_one": "සාර්ථකව ආයාත කළ 1 පොත",
"Successfully imported {{count}} book(s)_other": "සාර්ථකව ආයාත කළ {{count}} පොත්"
}
@@ -135,9 +135,9 @@
"Book Covers": "Bokomslag",
"Sort by...": "Sortera efter...",
"No supported files found. Supported formats: {{formats}}": "Inga filer hittades. Format som stöds: {{formats}}",
"No chapters detected.": "Inga kapitel hittades.",
"Failed to parse the EPUB file.": "Kunde inte läsa EPUB-filen.",
"This book format is not supported.": "Detta bokformat stöds inte.",
"No chapters detected": "Inga kapitel hittades",
"Failed to parse the EPUB file": "Kunde inte läsa EPUB-filen",
"This book format is not supported": "Detta bokformat stöds inte",
"Failed to import book(s): {{filenames}}": "Kunde inte importera bok(er): {{filenames}}",
"Book uploaded: {{title}}": "Bok uppladdad: {{title}}",
"Insufficient storage quota": "Otillräcklig lagringskvot",
@@ -241,7 +241,6 @@
"{{count}} pages left in chapter_other": "{{count}} sidor kvar i kapitlet",
"On {{current}} of {{total}} page": "På {{current}} av {{total}} sidor",
"Section Title": "Sektionstitel",
"Book Cover": "Bokomslag",
"More Info": "Mer info",
"Parallel Read": "Parallell läsning",
"Disable": "Inaktivera",
@@ -347,7 +346,6 @@
"Email:": "E-post:",
"Plan:": "Plan:",
"Amount:": "Belopp:",
"Subscription ID:": "Prenumerations-ID:",
"Need help? Contact our support team at support@readest.com": "Behöver du hjälp? Kontakta vår support på support@readest.com",
"Free Plan": "Gratisplan",
"month": "månad",
@@ -454,9 +452,6 @@
"Scroll": "Rullning",
"Continuous Scroll": "Kontinuerlig rullning",
"Overlap Pixels": "Överlappande pixlar",
"Click": "Klick",
"Clicks for Page Flip": "Klick för sidbyte",
"Swap Clicks Area": "Byt klickområde",
"Disable Double Click": "Inaktivera dubbelklick",
"Volume Keys for Page Flip": "Volymknappar för sidbyte",
"Animation": "Animation",
@@ -545,7 +540,7 @@
"Behavior": "Beteende",
"Settings Panels": "Inställningspaneler",
"Reset {{settings}}": "Återställ {{settings}}",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Hela världen är en scen,\nOch alla män och kvinnor bara spelare;\nDe har sina utgångar och entréer,\nOch en man spelar i sin tid många roller,\nHans akter är sju åldrar.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Hela världen är en scen,\nOch alla män och kvinnor bara spelare;\nDe har sina utgångar och entréer,\nOch en man spelar i sin tid många roller,\nHans akter är sju åldrar.\n\n— William Shakespeare",
"(from 'As You Like It', Act II)": "(från 'Som ni behagar', Akt II)",
"Custom Theme": "Anpassat tema",
"Theme Name": "Temanamn",
@@ -609,5 +604,164 @@
"Contain": "Innehålla",
"{{number}} pages left in chapter": "{{number}} sidor kvar i kapitlet",
"Device": "Enhet",
"E-Ink Mode": "E-Ink-läge"
"E-Ink Mode": "E-Ink-läge",
"Highlight Colors": "Markeringsfärger",
"Auto Screen Brightness": "Auto-skärmens ljusstyrka",
"Pagination": "Pagination",
"Disable Double Tap": "Inaktivera dubbeltryck",
"Tap to Paginate": "Tryck för att bläddra",
"Click to Paginate": "Klicka för att bläddra",
"Tap Both Sides": "Tryck på båda sidor",
"Click Both Sides": "Klicka på båda sidor",
"Swap Tap Sides": "Byt plats på tap-sidor",
"Swap Click Sides": "Byt plats på klick-sidor",
"Source and Translated": "Källa och översatt",
"Translated Only": "Översatt endast",
"Source Only": "Endast källa",
"TTS Text": "TTS-text",
"The book file is corrupted": "Bokfilen är skadad",
"The book file is empty": "Bokfilen är tom",
"Failed to open the book file": "Kunde inte öppna bokfilen",
"On-Demand Purchase": "Köp på begäran",
"Full Customization": "Full anpassning",
"Lifetime Plan": "Livstidsplan",
"One-Time Payment": "Engångsbetalning",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Gör en engångsbetalning för att få livstidsåtkomst till specifika funktioner på alla enheter. Köp specifika funktioner eller tjänster endast när du behöver dem.",
"Expand Cloud Sync Storage": "Utöka molnsynkroniseringslagring",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Utöka din molnlagring för alltid med ett engångsköp. Varje ytterligare köp lägger till mer utrymme.",
"Unlock All Customization Options": "Lås upp alla anpassningsalternativ",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Lås upp ytterligare teman, typsnitt, layoutalternativ och uppläsning, översättare, molnlagringstjänster.",
"Purchase Successful!": "Köp lyckades!",
"lifetime": "livstid",
"Thank you for your purchase! Your payment has been processed successfully.": "Tack för ditt köp! Din betalning har behandlats.",
"Order ID:": "Order-ID:",
"TTS Highlighting": "TTS-markering",
"Style": "Stil",
"Underline": "Understrykning",
"Strikethrough": "Genomstrykning",
"Squiggly": "Vågig linje",
"Outline": "Kontur",
"Save Current Color": "Spara aktuell färg",
"Quick Colors": "Snabba färger",
"Highlighter": "Markeringspenna",
"Save Book Cover": "Spara bokomslag",
"Auto-save last book cover": "Automatiskt spara senaste bokomslag",
"Back": "Tillbaka",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Bekräftelsemail skickat! Kontrollera din gamla och nya e-postadress för att bekräfta ändringen.",
"Failed to update email": "Misslyckades med att uppdatera e-post",
"New Email": "Ny e-post",
"Your new email": "Din nya e-post",
"Updating email ...": "Uppdaterar e-post ...",
"Update email": "Uppdatera e-post",
"Current email": "Nuvarande e-post",
"Update Email": "Uppdatera e-post",
"All": "Alla",
"Unable to open book": "Kunde inte öppna bok",
"Punctuation": "Interpunktion",
"Replace Quotation Marks": "Ersätt citattecken",
"Enabled only in vertical layout.": "Aktiverad endast i vertikal layout.",
"No Conversion": "Ingen konvertering",
"Simplified to Traditional": "Förenklad → Traditionell",
"Traditional to Simplified": "Traditionell → Förenklad",
"Simplified to Traditional (Taiwan)": "Förenklad → Traditionell (Taiwan)",
"Simplified to Traditional (Hong Kong)": "Förenklad → Traditionell (Hongkong)",
"Simplified to Traditional (Taiwan), with phrases": "Förenklad → Traditionell (Taiwan • fraser)",
"Traditional (Taiwan) to Simplified": "Traditionell (Taiwan) → Förenklad",
"Traditional (Hong Kong) to Simplified": "Traditionell (Hongkong) → Förenklad",
"Traditional (Taiwan) to Simplified, with phrases": "Traditionell (Taiwan • fraser) → Förenklad",
"Convert Simplified and Traditional Chinese": "Konvertera förenklad/traditionell kinesiska",
"Convert Mode": "Konverteringsläge",
"Failed to auto-save book cover for lock screen: {{error}}": "Kunde inte autospara bokomslag för låsskärm: {{error}}",
"Download from Cloud": "Ladda ner från molnet",
"Upload to Cloud": "Ladda upp till molnet",
"Clear Custom Fonts": "Rensa anpassade typsnitt",
"Columns": "Kolumner",
"OPDS Catalogs": "OPDS-kataloger",
"Adding LAN addresses is not supported in the web app version.": "Att lägga till LAN-adresser stöds inte i webbappen.",
"Invalid OPDS catalog. Please check the URL.": "Ogiltig OPDS-katalog. Kontrollera URL:en.",
"Browse and download books from online catalogs": "Bläddra och ladda ner böcker från onlinekataloger",
"My Catalogs": "Mina kataloger",
"Add Catalog": "Lägg till katalog",
"No catalogs yet": "Inga kataloger än",
"Add your first OPDS catalog to start browsing books": "Lägg till din första OPDS-katalog för att börja bläddra i böcker",
"Add Your First Catalog": "Lägg till din första katalog",
"Browse": "Bläddra",
"Popular Catalogs": "Populära kataloger",
"Add": "Lägg till",
"Add OPDS Catalog": "Lägg till OPDS-katalog",
"Catalog Name": "Katalognamn",
"My Calibre Library": "Mitt Calibre-bibliotek",
"OPDS URL": "OPDS-URL",
"Username (optional)": "Användarnamn (valfritt)",
"Password (optional)": "Lösenord (valfritt)",
"Description (optional)": "Beskrivning (valfritt)",
"A brief description of this catalog": "En kort beskrivning av denna katalog",
"Validating...": "Verifierar...",
"View All": "Visa alla",
"Forward": "Framåt",
"OPDS Catalog": "OPDS-katalog",
"Home": "Start",
"Library": "Bibliotek",
"{{count}} items_one": "{{count}} objekt",
"{{count}} items_other": "{{count}} objekt",
"Download completed": "Nedladdning klar",
"Download failed": "Nedladdning misslyckades",
"Open Access": "Öppen åtkomst",
"Borrow": "Låna",
"Buy": "Köp",
"Subscribe": "Prenumerera",
"Sample": "Prov",
"Download": "Ladda ner",
"Open & Read": "Öppna & Läs",
"Tags": "Taggar",
"Tag": "Tagg",
"First": "Första",
"Previous": "Föregående",
"Next": "Nästa",
"Last": "Sista",
"Cannot Load Page": "Kan inte ladda sidan",
"An error occurred": "Ett fel uppstod",
"Online Library": "Onlinebibliotek",
"URL must start with http:// or https://": "URL måste börja med http:// eller https://",
"Title, Author, Tag, etc...": "Titel, författare, tagg, etc...",
"Query": "Fråga",
"Subject": "Ämne",
"Enter {{terms}}": "Ange {{terms}}",
"No search results found": "Inga sökresultat hittades",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Misslyckades med att ladda OPDS-flöde: {{status}} {{statusText}}",
"Search in {{title}}": "Sök i {{title}}",
"Manage Storage": "Hantera lagring",
"Failed to load files": "Misslyckades att ladda filer",
"Deleted {{count}} file(s)_one": "Raderade filen",
"Deleted {{count}} file(s)_other": "Raderade filerna",
"Failed to delete {{count}} file(s)_one": "Kunde inte radera filen",
"Failed to delete {{count}} file(s)_other": "Kunde inte radera filerna",
"Failed to delete files": "Kunde inte radera filer",
"Total Files": "Totalt antal filer",
"Total Size": "Total storlek",
"Quota": "Kvot",
"Used": "Använd",
"Files": "Filer",
"Search files...": "Sök filer...",
"Newest First": "Nyast först",
"Oldest First": "Äldst först",
"Largest First": "Störst först",
"Smallest First": "Minskst först",
"Name A-Z": "Namn A-Ö",
"Name Z-A": "Namn Ö-A",
"{{count}} selected_one": "Vald fil",
"{{count}} selected_other": "Valda filer",
"Delete Selected": "Radera valda",
"Created": "Skapad",
"No files found": "Inga filer hittades",
"No files uploaded yet": "Inga filer har laddats upp än",
"files": "filer",
"Page {{current}} of {{total}}": "Sida {{current}} av {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Är du säker på att du vill radera filen?",
"Are you sure to delete {{count}} selected file(s)?_other": "Är du säker på att du vill radera filerna?",
"Cloud Storage Usage": "Användning av molnlagring",
"Rename Group": "Byt namn på grupp",
"From Directory": "Från katalog",
"Successfully imported {{count}} book(s)_one": "Importerat 1 bok",
"Successfully imported {{count}} book(s)_other": "Importerat {{count}} böcker"
}
@@ -105,9 +105,9 @@
"Book Covers": "புத்தக அட்டைகள்",
"Sort by...": "வரிசைப்படுத்து...",
"No supported files found. Supported formats: {{formats}}": "ஆதரிக்கப்படும் கோப்புகள் இல்லை. ஆதரிக்கப்படும் வடிவங்கள்: {{formats}}",
"No chapters detected.": "அத்தியாயங்கள் கண்டறியப்படவில்லை.",
"Failed to parse the EPUB file.": "EPUB கோப்பை பகுப்பாய்வு செய்ய முடியவில்லை.",
"This book format is not supported.": "இந்த புத்தக வடிவம் ஆதரிக்கப்படவில்லை.",
"No chapters detected": "அத்தியாயங்கள் கண்டறியப்படவில்லை",
"Failed to parse the EPUB file": "EPUB கோப்பை பகுப்பாய்வு செய்ய முடியவில்லை",
"This book format is not supported": "இந்த புத்தக வடிவம் ஆதரிக்கப்படவில்லை",
"Failed to import book(s): {{filenames}}": "புத்தகங்களை இறக்குமதி செய்ய முடியவில்லை: {{filenames}}",
"Book uploaded: {{title}}": "புத்தகம் பதிவேற்றப்பட்டது: {{title}}",
"Insufficient storage quota": "போதுமான சேமிப்பக ஒதுக்கீடு இல்லை",
@@ -204,9 +204,6 @@
"Scrolled Mode": "உருட்டல் பயன்முறை",
"Continuous Scroll": "தொடர்ச்சியான உருட்டல்",
"Overlap Pixels": "ஒன்றுடன் ஒன்று பிக்சல்கள்",
"Click": "கிளிக்",
"Clicks for Page Flip": "பக்கம் புரட்டுவதற்கு கிளிக்கள்",
"Swap Clicks Area": "கிளிக் பகுதியை மாற்றவும்",
"Disable Double Click": "இரட்டை கிளிக்கை முடக்கவும்",
"Volume Keys for Page Flip": "பக்கம் புரட்டுவதற்கு ஒலி விசைகள்",
"Animation": "அனிமேஷன்",
@@ -296,7 +293,7 @@
"Color": "நிறம்",
"Behavior": "நடத்தை",
"Reset {{settings}}": "{{settings}} மீட்டமைக்கவும்",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "உலகம் முழுவதும் ஒரு மேடை,\nஅதில் ஆண்களும் பெண்களும் வெறும் நடிகர்கள்;\nஅவர்களுக்கு வெளியேறுதலும் நுழைவும் உண்டு,\nஒரு மனிதன் தன் காலத்தில் பல பாத்திரங்களை வகிக்கிறான்,\nஅவனுடைய செயல்கள் ஏழு வயதுகளாக இருக்கின்றன.\n\n— வில்லியம் ஷேக்ஸ்பியர்",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "உலகம் முழுவதும் ஒரு மேடை,\nஅதில் ஆண்களும் பெண்களும் வெறும் நடிகர்கள்;\nஅவர்களுக்கு வெளியேறுதலும் நுழைவும் உண்டு,\nஒரு மனிதன் தன் காலத்தில் பல பாத்திரங்களை வகிக்கிறான்,\nஅவனுடைய செயல்கள் ஏழு வயதுகளாக இருக்கின்றன.\n\n— வில்லியம் ஷேக்ஸ்பியர்",
"(from 'As You Like It', Act II)": "('ஆஸ் யூ லைக் இட்', இரண்டாவது அங்கத்திலிருந்து)",
"Custom Theme": "தனிப்பயன் தீம்",
"Theme Name": "தீம் பெயர்",
@@ -305,7 +302,6 @@
"Link Color": "இணைப்பு நிறம்",
"Preview": "முன்னோட்டம்",
"Font & Layout": "எழுத்துரு மற்றும் அமைப்பு",
"Book Cover": "புத்தக அட்டை",
"More Info": "மேலும் தகவல்",
"Parallel Read": "இணை வாசிப்பு",
"Disable": "முடக்கவும்",
@@ -379,7 +375,6 @@
"Email:": "மின்னஞ்சல்:",
"Plan:": "திட்டம்:",
"Amount:": "தொகை:",
"Subscription ID:": "சந்தா ID:",
"Go to Library": "நூலகத்திற்கு செல்லவும்",
"Need help? Contact our support team at support@readest.com": "உதவி தேவையா? support@readest.com இல் எங்கள் ஆதரவு குழுவைத் தொடர்பு கொள்ளவும்",
"Free Plan": "இலவச திட்டம்",
@@ -609,5 +604,164 @@
"Contain": "அடங்கும்",
"{{number}} pages left in chapter": "அத்தியாயத்தில் {{number}} பக்கங்கள் மீதமுள்ளன",
"Device": "சாதனம்",
"E-Ink Mode": "E-Ink முறை"
"E-Ink Mode": "E-Ink முறை",
"Highlight Colors": "முத்திரை நிறங்கள்",
"Auto Screen Brightness": "தானியங்கி திரை பிரகாசம்",
"Pagination": "பக்கமிடுதல்",
"Disable Double Tap": "இரட்டை தொடுதலை முடக்கு",
"Tap to Paginate": "பக்கம் மாற்ற தொடுங்கள்",
"Click to Paginate": "பக்கம் மாற்ற கிளிக் செய்யவும்",
"Tap Both Sides": "இரு பக்கங்களையும் தொடுங்கள்",
"Click Both Sides": "இரு பக்கங்களையும் கிளிக் செய்யவும்",
"Swap Tap Sides": "இரு பக்கங்களின் இடத்தை மாற்றவும்",
"Swap Click Sides": "இரு பக்கங்களின் இடத்தை மாற்றவும்",
"Source and Translated": "மூல மற்றும் மொழிபெயர்க்கப்பட்டது",
"Translated Only": "மொழிபெயர்க்கப்பட்டது மட்டும்",
"Source Only": "மூல மட்டும்",
"TTS Text": "TTS உரை",
"The book file is corrupted": "பொதுவாக, புத்தகக் கோப்பு சேதமடைந்துள்ளது",
"The book file is empty": "புத்தகக் கோப்பு காலியாக உள்ளது",
"Failed to open the book file": "புத்தகக் கோப்பைத் திறக்க முடியவில்லை",
"On-Demand Purchase": "தேவைப்படும் போது வாங்குதல்",
"Full Customization": "முழு தனிப்பயனாக்கம்",
"Lifetime Plan": "ஆயுள்திருத்தம்",
"One-Time Payment": "ஒரே முறை கட்டணம்",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "எல்லா சாதனங்களிலும் குறிப்பிட்ட அம்சங்களுக்கு ஆயுள்திருத்தம் பெற ஒரே முறை கட்டணம் செலுத்தவும். நீங்கள் தேவைப்படும் போது மட்டுமே குறிப்பிட்ட அம்சங்கள் அல்லது சேவைகளை வாங்கவும்.",
"Expand Cloud Sync Storage": "மோலச் சின்க் சேமிப்பிடத்தை விரிவாக்கவும்",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "ஒரே முறை வாங்குதலுடன் உங்கள் மேக சேமிப்பிடத்தை எப்போதும் விரிவாக்கவும். ஒவ்வொரு கூடுதல் வாங்குதலும் மேலும் இடத்தைச் சேர்க்கிறது.",
"Unlock All Customization Options": "அனைத்து தனிப்பயனாக்க விருப்பங்களைத் திறக்கவும்",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "கூடுதல் தீமைகள், எழுத்துருக்கள், வடிவமைப்பு விருப்பங்கள் மற்றும் வாசிக்க, மொழிபெயர்க்க, மேக சேமிப்பு சேவைகளைத் திறக்கவும்.",
"Purchase Successful!": "கொள்முதல் வெற்றிகரமாக முடிந்தது!",
"lifetime": "ஆயுள்திருத்தம்",
"Thank you for your purchase! Your payment has been processed successfully.": "உங்கள் வாங்குதலுக்கு நன்றி! உங்கள் கட்டணம் வெற்றிகரமாக செயல்படுத்தப்பட்டது.",
"Order ID:": "ஆர்டர் ஐடி:",
"TTS Highlighting": "TTS சிறப்பித்தல்",
"Style": "பாணி",
"Underline": "அடிக்கோடு",
"Strikethrough": "குறிக்கோடு",
"Squiggly": "சுருள் கோடு",
"Outline": "வட்டம்",
"Save Current Color": "தற்போதைய நிறத்தை சேமிக்கவும்",
"Quick Colors": "விரைவு நிறங்கள்",
"Highlighter": "ஹைலைட்டர்",
"Save Book Cover": "புத்தகக் கோப்பைச் சேமிக்கவும்",
"Auto-save last book cover": "அවසන් புத்தகக் கோப்பு காலியாக உள்ளது",
"Back": "பின் செல்ல",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "உறுதிப்பத்திர மின்னஞ்சல் அனுப்பப்பட்டுள்ளது! மாற்றத்தை உறுதிப்படுத்த உங்கள் பழைய மற்றும் புதிய மின்னஞ்சல் முகவரிகளை சரிபார்க்கவும்.",
"Failed to update email": "மின்னஞ்சல் புதுப்பிப்பில் தோல்வி",
"New Email": "புதிய மின்னஞ்சல்",
"Your new email": "உங்கள் புதிய மின்னஞ்சல்",
"Updating email ...": "மின்னஞ்சல் புதுப்பிக்கப்படுகிறது ...",
"Update email": "மின்னஞ்சல் புதுப்பிக்கவும்",
"Current email": "தற்போதைய மின்னஞ்சல்",
"Update Email": "மின்னஞ்சல் புதுப்பிக்கவும்",
"All": "அனைத்து",
"Unable to open book": "புத்தகத்தைத் திறக்க முடியவில்லை",
"Punctuation": "விருத்தி",
"Replace Quotation Marks": "உதயம்சுட்டிகளை மாற்றவும்",
"Enabled only in vertical layout.": "செங்குத்து அமைப்பில் மட்டுமே இயலுமைப்படுத்தப்பட்டது.",
"No Conversion": "மாற்றம் செய்யவில்லை",
"Simplified to Traditional": "எளிய → பாரம்பரிய",
"Traditional to Simplified": "பாரம்பரிய → எளிய",
"Simplified to Traditional (Taiwan)": "எளிய → பாரம்பரிய (தைவான்)",
"Simplified to Traditional (Hong Kong)": "எளிய → பாரம்பரிய (ஹாங்காங்)",
"Simplified to Traditional (Taiwan), with phrases": "எளிய → பாரம்பரிய (தைவான் • வசனங்கள்)",
"Traditional (Taiwan) to Simplified": "பாரம்பரிய (தைவான்) → எளிய",
"Traditional (Hong Kong) to Simplified": "பாரம்பரிய (ஹாங்காங்) → எளிய",
"Traditional (Taiwan) to Simplified, with phrases": "பாரம்பரிய (தைவான் • வசனங்கள்) → எளிய",
"Convert Simplified and Traditional Chinese": "எளிய/பாரம்பரிய சீன மொழி மாற்றம்",
"Convert Mode": "மாற்ற முறை",
"Failed to auto-save book cover for lock screen: {{error}}": "பூட்டு திரைக்கான புத்தகக் கோப்பை தானாகச் சேமிக்க முடியவில்லை: {{error}}",
"Download from Cloud": "மேகத்திலிருந்து பதிவிறக்கு செய்யவும்",
"Upload to Cloud": "மேகத்திற்கு பதிவேற்றவும்",
"Clear Custom Fonts": "தனிப்பயன் எழுத்துருக்களை அழிக்கவும்",
"Columns": "நெடுவரிசைகள்",
"OPDS Catalogs": "OPDS பட்டியல்கள்",
"Adding LAN addresses is not supported in the web app version.": "LAN முகவரிகளைச் சேர்ப்பது வலை பயன்பாட்டில் ஆதரிக்கப்படவில்லை.",
"Invalid OPDS catalog. Please check the URL.": "தவறான OPDS பட்டியல். URL ஐச் சரிபார்க்கவும்.",
"Browse and download books from online catalogs": "ஆன்லைன் பட்டியல்களில் இருந்து புத்தகங்களை உலாவவும் மற்றும் பதிவிறக்கவும்",
"My Catalogs": "என் பட்டியல்கள்",
"Add Catalog": "பட்டியலைச் சேர்க்கவும்",
"No catalogs yet": "இன்னும் எந்த பட்டியலும் இல்லை",
"Add your first OPDS catalog to start browsing books": "புத்தகங்களை உலாவ தொடங்க உங்கள் முதல் OPDS பட்டியலைச் சேர்க்கவும்",
"Add Your First Catalog": "உங்கள் முதல் பட்டியலைச் சேர்க்கவும்",
"Browse": "உலாவவும்",
"Popular Catalogs": "பிரபல பட்டியல்கள்",
"Add": "சேர்க்கவும்",
"Add OPDS Catalog": "OPDS பட்டியலைச் சேர்க்கவும்",
"Catalog Name": "பட்டியல் பெயர்",
"My Calibre Library": "என் Calibre நூலகம்",
"OPDS URL": "OPDS URL",
"Username (optional)": "பயனர் பெயர் (விருப்பம்)",
"Password (optional)": "கடவுச்சொல் (விருப்பம்)",
"Description (optional)": "விவரிப்பு (விருப்பம்)",
"A brief description of this catalog": "இந்த பட்டியலின் சுருக்கமான விளக்கம்",
"Validating...": "சரிபார்க்கப்படுகிறது...",
"View All": "அனைத்தையும் பார்க்கவும்",
"Forward": "முன்னேற்று",
"OPDS Catalog": "OPDS பட்டியல்",
"Home": "முகப்பு",
"Library": "நூலகம்",
"{{count}} items_one": "{{count}} பொருள்",
"{{count}} items_other": "{{count}} பொருட்கள்",
"Download completed": "பதிவிறக்கம் முடிந்தது",
"Download failed": "பதிவிறக்கம் தோல்வியடைந்தது",
"Open Access": "திறந்த அணுகல்",
"Borrow": "கடன் எடுக்கவும்",
"Buy": "வாங்கவும்",
"Subscribe": "சந்தா செய்யவும்",
"Sample": "மாதிரிப் புத்தகம்",
"Download": "பதிவிறக்கம் செய்யவும்",
"Open & Read": "திறந்து வாசிக்கவும்",
"Tags": "குறிச்சொற்கள்",
"Tag": "குறிச்சொல்",
"First": "முதல்",
"Previous": "முந்தையது",
"Next": "அடுத்தது",
"Last": "இறுதி",
"Cannot Load Page": "பக்கம் ஏற்ற முடியவில்லை",
"An error occurred": "ஒரு பிழை ஏற்பட்டது",
"Online Library": "ஆன்லைன் நூலகம்",
"URL must start with http:// or https://": "URL http:// அல்லது https:// கொண்டு தொடங்க வேண்டும்",
"Title, Author, Tag, etc...": "தலைப்பு, ஆசிரியர், குறிச்சொல், மற்றும் பல...",
"Query": "கேள்வி",
"Subject": "பொருள்",
"Enter {{terms}}": "{{terms}} உள்ளிடவும்",
"No search results found": "தேடல் முடிவுகள் இல்லை",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ஊட்டத்தை ஏற்ற முடியவில்லை: {{status}} {{statusText}}",
"Search in {{title}}": "{{title}} இல் தேடவும்",
"Manage Storage": "சேமிப்பை நிர்வகி",
"Failed to load files": "கோப்புகளை ஏற்ற முடியவில்லை",
"Deleted {{count}} file(s)_one": "கோப்பு நீக்கப்பட்டது",
"Deleted {{count}} file(s)_other": "கோப்புகள் நீக்கப்பட்டன",
"Failed to delete {{count}} file(s)_one": "கோப்பை நீக்க முடியவில்லை",
"Failed to delete {{count}} file(s)_other": "கோப்புகளை நீக்க முடியவில்லை",
"Failed to delete files": "கோப்புகளை நீக்க முடியவில்லை",
"Total Files": "மொத்த கோப்புகள்",
"Total Size": "மொத்த அளவு",
"Quota": "கோட்டா",
"Used": "பயன்படுத்தப்பட்டது",
"Files": "கோப்புகள்",
"Search files...": "கோப்புகளைத் தேடு...",
"Newest First": "புதியவை முதலில்",
"Oldest First": "பழையவை முதலில்",
"Largest First": "பெரியவை முதலில்",
"Smallest First": "சிறியவை முதலில்",
"Name A-Z": "பெயர் A-ஆல்",
"Name Z-A": "பெயர் ஆல்-A",
"{{count}} selected_one": "தேர்ந்தெடுக்கப்பட்ட கோப்பு",
"{{count}} selected_other": "தேர்ந்தெடுக்கப்பட்ட கோப்புகள்",
"Delete Selected": "தேர்ந்தெடுத்தவை நீக்கு",
"Created": "உருவாக்கப்பட்டது",
"No files found": "கோப்புகள் இல்லை",
"No files uploaded yet": "இன்னும் எந்த கோப்பும் பதிவேற்றப்படவில்லை",
"files": "கோப்புகள்",
"Page {{current}} of {{total}}": "பக்கம் {{current}} / {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "இந்த கோப்பை நீக்க விரும்புகிறீர்களா?",
"Are you sure to delete {{count}} selected file(s)?_other": "இந்த கோப்புகளை நீக்க விரும்புகிறீர்களா?",
"Cloud Storage Usage": "மேக சேமிப்பு பயன்பாடு",
"Rename Group": "குழுவை மறுபெயரிடவும்",
"From Directory": "கோப்புறையிலிருந்து",
"Successfully imported {{count}} book(s)_one": "வெற்றிகரமாக 1 புத்தகம் இறக்குமதி செய்யப்பட்டது",
"Successfully imported {{count}} book(s)_other": "வெற்றிகரமாக {{count}} புத்தகங்கள் இறக்குமதி செய்யப்பட்டது"
}
@@ -101,9 +101,9 @@
"Book Covers": "ปกหนังสือ",
"Sort by...": "เรียงตาม...",
"No supported files found. Supported formats: {{formats}}": "ไม่พบไฟล์ที่รองรับ รูปแบบที่รองรับ: {{formats}}",
"No chapters detected.": "ไม่พบบท",
"Failed to parse the EPUB file.": "ไม่สามารถอ่านไฟล์ EPUB",
"This book format is not supported.": "ไม่รองรับรูปแบบหนังสือนี้",
"No chapters detected": "ไม่พบบท",
"Failed to parse the EPUB file": "ไม่สามารถอ่านไฟล์ EPUB",
"This book format is not supported": "ไม่รองรับรูปแบบหนังสือนี้",
"Failed to import book(s): {{filenames}}": "ไม่สามารถนำเข้าหนังสือ: {{filenames}}",
"Book uploaded: {{title}}": "อัปโหลดหนังสือแล้ว: {{title}}",
"Insufficient storage quota": "พื้นที่จัดเก็บไม่เพียงพอ",
@@ -169,9 +169,6 @@
"Scrolled Mode": "โหมดเลื่อน",
"Continuous Scroll": "เลื่อนต่อเนื่อง",
"Overlap Pixels": "พิกเซลซ้อน",
"Click": "คลิก",
"Clicks for Page Flip": "คลิกเพื่อพลิกหน้า",
"Swap Clicks Area": "สลับพื้นที่คลิก",
"Volume Keys for Page Flip": "ปุ่มเสียงเพื่อพลิกหน้า",
"Animation": "ภาพเคลื่อนไหว",
"Paging Animation": "ภาพเคลื่อนไหวพลิกหน้า",
@@ -258,7 +255,6 @@
"Link Color": "สีลิงก์",
"Preview": "ดูตัวอย่าง",
"Font & Layout": "ฟอนต์และเลย์เอาต์",
"Book Cover": "ปกหนังสือ",
"More Info": "ข้อมูลเพิ่มเติม",
"Parallel Read": "อ่านคู่ขนาน",
"Disable": "ปิด",
@@ -315,7 +311,6 @@
"Email:": "อีเมล:",
"Plan:": "แผน:",
"Amount:": "จำนวนเงิน:",
"Subscription ID:": "รหัสสมาชิก:",
"Go to Library": "ไปที่คลังหนังสือ",
"Need help? Contact our support team at support@readest.com": "ต้องการความช่วยเหลือ? ติดต่อทีมสนับสนุนที่ support@readest.com",
"Free Plan": "แผนฟรี",
@@ -404,7 +399,7 @@
"Reveal in File Explorer": "แสดงใน File Explorer",
"Reveal in Folder": "แสดงในโฟลเดอร์",
"Don't have an account? Sign up": "ยังไม่มีบัญชี? สมัครสมาชิก",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "โลกทั้งใบเป็นเวที\nและชายหญิงทั้งปวงเป็นนักแสดง\nมีทางเข้าและทางออก\nแต่ละคนในชีวิตเล่นหลายบท\nการกระทำของเขาแบ่งเป็นเจ็ดวัย\n\n— วิลเลียม เชกสเปียร์",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "โลกทั้งใบเป็นเวที\nและชายหญิงทั้งปวงเป็นนักแสดง\nมีทางเข้าและทางออก\nแต่ละคนในชีวิตเล่นหลายบท\nการกระทำของเขาแบ่งเป็นเจ็ดวัย\n\n— วิลเลียม เชกสเปียร์",
"What's New in Readest": "มีอะไรใหม่ใน Readest",
"Enter book title": "ป้อนชื่อหนังสือ",
"Subtitle": "หัวข้อรอง",
@@ -605,5 +600,158 @@
"Contain": "บรรจุ",
"{{number}} pages left in chapter": "เหลือ {{number}} หน้าในบท",
"Device": "อุปกรณ์",
"E-Ink Mode": "โหมด E-Ink"
"E-Ink Mode": "โหมด E-Ink",
"Highlight Colors": "สีไฮไลต์",
"Auto Screen Brightness": "ความสว่างหน้าจออัตโนมัติ",
"Pagination": "การแบ่งหน้า",
"Disable Double Tap": "ปิดการแตะสองครั้ง",
"Tap to Paginate": "แตะเพื่อแบ่งหน้า",
"Click to Paginate": "คลิกเพื่อแบ่งหน้า",
"Tap Both Sides": "แตะทั้งสองด้าน",
"Click Both Sides": "คลิกทั้งสองด้าน",
"Swap Tap Sides": "เปลี่ยนด้านการแตะ",
"Swap Click Sides": "เปลี่ยนด้านการคลิก",
"Source and Translated": "ต้นฉบับและแปลแล้ว",
"Translated Only": "แปลแล้วเท่านั้น",
"Source Only": "ต้นฉบับเท่านั้น",
"TTS Text": "ข้อความ TTS",
"The book file is corrupted": "ไฟล์หนังสือเสียหาย",
"The book file is empty": "ไฟล์หนังสือว่างเปล่า",
"Failed to open the book file": "ไม่สามารถเปิดไฟล์หนังสือได้",
"On-Demand Purchase": "การซื้อเมื่อจำเป็น",
"Full Customization": "การปรับแต่งเต็มรูปแบบ",
"Lifetime Plan": "แผนการใช้งานตลอดชีพ",
"One-Time Payment": "การชำระเงินครั้งเดียว",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "ทำการชำระเงินครั้งเดียวเพื่อเพลิดเพลินกับการเข้าถึงฟีเจอร์เฉพาะตลอดชีพบนอุปกรณ์ทั้งหมด ซื้อฟีเจอร์หรือบริการเฉพาะเมื่อคุณต้องการเท่านั้น",
"Expand Cloud Sync Storage": "ขยายพื้นที่เก็บข้อมูลซิงค์คลาวด์",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "ขยายพื้นที่เก็บข้อมูลซิงค์คลาวด์ของคุณตลอดไปด้วยการซื้อครั้งเดียว การซื้อเพิ่มเติมแต่ละครั้งจะเพิ่มพื้นที่มากขึ้น",
"Unlock All Customization Options": "ปลดล็อกตัวเลือกการปรับแต่งทั้งหมด",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "ปลดล็อกธีมเพิ่มเติม ฟอนต์ ตัวเลือกการจัดรูปแบบ และฟีเจอร์การอ่านออกเสียง ตัวแปล และบริการพื้นที่เก็บข้อมูลคลาวด์",
"Purchase Successful!": "การซื้อสำเร็จ!",
"lifetime": "ตลอดชีพ",
"Thank you for your purchase! Your payment has been processed successfully.": "ขอบคุณสำหรับการซื้อ! การชำระเงินของคุณได้รับการประมวลผลเรียบร้อยแล้ว",
"Order ID:": "รหัสคำสั่งซื้อ:",
"TTS Highlighting": "การไฮไลต์ TTS",
"Style": "สไตล์",
"Underline": "ขีดเส้นใต้",
"Strikethrough": "ขีดฆ่า",
"Squiggly": "เส้นหยัก",
"Outline": "เส้นขอบ",
"Save Current Color": "บันทึกสีปัจจุบัน",
"Quick Colors": "สีด่วน",
"Highlighter": "ปากกาเน้นข้อความ",
"Save Book Cover": "บันทึกปกหนังสือ",
"Auto-save last book cover": "บันทึกปกหนังสือสุดท้ายโดยอัตโนมัติ",
"Back": "กลับ",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "ส่งอีเมลยืนยันแล้ว! กรุณาตรวจสอบอีเมลเก่าและใหม่ของคุณเพื่อยืนยันการเปลี่ยนแปลง",
"Failed to update email": "ไม่สามารถอัปเดตอีเมลได้",
"New Email": "อีเมลใหม่",
"Your new email": "อีเมลใหม่ของคุณ",
"Updating email ...": "กำลังอัปเดตอีเมล ...",
"Update email": "อัปเดตอีเมล",
"Current email": "อีเมลปัจจุบัน",
"Update Email": "อัปเดตอีเมล",
"All": "ทั้งหมด",
"Unable to open book": "ไม่สามารถเปิดหนังสือได้",
"Punctuation": "เครื่องหมายวรรคตอน",
"Replace Quotation Marks": "แทนที่เครื่องหมายคำพูด",
"Enabled only in vertical layout.": "เปิดใช้งานเฉพาะในเค้าโครงแนวตั้งเท่านั้น",
"No Conversion": "ไม่แปลงค่า",
"Simplified to Traditional": "ตัวย่อ → ตัวเต็ม",
"Traditional to Simplified": "ตัวเต็ม → ตัวย่อ",
"Simplified to Traditional (Taiwan)": "ตัวย่อ → ตัวเต็ม (ไต้หวัน)",
"Simplified to Traditional (Hong Kong)": "ตัวย่อ → ตัวเต็ม (ฮ่องกง)",
"Simplified to Traditional (Taiwan), with phrases": "ตัวย่อ → ตัวเต็ม (ไต้หวัน • วลี)",
"Traditional (Taiwan) to Simplified": "ตัวเต็ม (ไต้หวัน) → ตัวย่อ",
"Traditional (Hong Kong) to Simplified": "ตัวเต็ม (ฮ่องกง) → ตัวย่อ",
"Traditional (Taiwan) to Simplified, with phrases": "ตัวเต็ม (ไต้หวัน • วลี) → ตัวย่อ",
"Convert Simplified and Traditional Chinese": "แปลงจีนตัวย่อ/ตัวเต็ม",
"Convert Mode": "โหมดแปลงค่า",
"Failed to auto-save book cover for lock screen: {{error}}": "ไม่สามารถบันทึกปกหนังสือสำหรับหน้าจอล็อกโดยอัตโนมัติ: {{error}}",
"Download from Cloud": "ดาวน์โหลดจากคลาวด์",
"Upload to Cloud": "อัปโหลดไปยังคลาวด์",
"Clear Custom Fonts": "ล้างฟอนต์กำหนดเอง",
"Columns": "คอลัมน์",
"OPDS Catalogs": "แคตตาล็อก OPDS",
"Adding LAN addresses is not supported in the web app version.": "ไม่รองรับการเพิ่มที่อยู่ LAN ในเวอร์ชันเว็บแอป",
"Invalid OPDS catalog. Please check the URL.": "แคตตาล็อก OPDS ไม่ถูกต้อง กรุณาตรวจสอบ URL",
"Browse and download books from online catalogs": "เรียกดูและดาวน์โหลดหนังสือจากแคตตาล็อกออนไลน์",
"My Catalogs": "แคตตาล็อกของฉัน",
"Add Catalog": "เพิ่มแคตตาล็อก",
"No catalogs yet": "ยังไม่มีแคตตาล็อก",
"Add your first OPDS catalog to start browsing books": "เพิ่มแคตตาล็อก OPDS แรกของคุณเพื่อเริ่มเรียกดูหนังสือ",
"Add Your First Catalog": "เพิ่มแคตตาล็อกแรกของคุณ",
"Browse": "เรียกดู",
"Popular Catalogs": "แคตตาล็อกยอดนิยม",
"Add": "เพิ่ม",
"Add OPDS Catalog": "เพิ่มแคตตาล็อก OPDS",
"Catalog Name": "ชื่อแคตตาล็อก",
"My Calibre Library": "ห้องสมุด Calibre ของฉัน",
"OPDS URL": "URL OPDS",
"Username (optional)": "ชื่อผู้ใช้ (ไม่บังคับ)",
"Password (optional)": "รหัสผ่าน (ไม่บังคับ)",
"Description (optional)": "คำอธิบาย (ไม่บังคับ)",
"A brief description of this catalog": "คำอธิบายสั้น ๆ ของแคตตาล็อกนี้",
"Validating...": "กำลังตรวจสอบ...",
"View All": "ดูทั้งหมด",
"Forward": "ไปข้างหน้า",
"OPDS Catalog": "แคตตาล็อก OPDS",
"Home": "หน้าแรก",
"Library": "ห้องสมุด",
"{{count}} items_other": "{{count}} รายการ",
"Download completed": "ดาวน์โหลดเสร็จสิ้น",
"Download failed": "ดาวน์โหลดล้มเหลว",
"Open Access": "เข้าถึงได้ทันที",
"Borrow": "ยืม",
"Buy": "ซื้อ",
"Subscribe": "สมัครสมาชิก",
"Sample": "ตัวอย่าง",
"Download": "ดาวน์โหลด",
"Open & Read": "เปิดและอ่าน",
"Tags": "แท็ก",
"Tag": "แท็ก",
"First": "แรก",
"Previous": "ก่อนหน้า",
"Next": "ถัดไป",
"Last": "สุดท้าย",
"Cannot Load Page": "ไม่สามารถโหลดหน้าหน้าได้",
"An error occurred": "เกิดข้อผิดพลาด",
"Online Library": "ห้องสมุดออนไลน์",
"URL must start with http:// or https://": "URL ต้องเริ่มต้นด้วย http:// หรือ https://",
"Title, Author, Tag, etc...": "ชื่อเรื่อง ผู้แต่ง แท็ก ฯลฯ...",
"Query": "แบบสอบถาม",
"Subject": "หัวข้อ",
"Enter {{terms}}": "ป้อน {{terms}}",
"No search results found": "ไม่พบผลการค้นหา",
"Failed to load OPDS feed: {{status}} {{statusText}}": "ไม่สามารถโหลดฟีด OPDS ได้: {{status}} {{statusText}}",
"Search in {{title}}": "ค้นหาใน {{title}}",
"Manage Storage": "จัดการพื้นที่เก็บข้อมูล",
"Failed to load files": "ไม่สามารถโหลดไฟล์ได้",
"Deleted {{count}} file(s)_other": "ลบไฟล์เรียบร้อยแล้ว",
"Failed to delete {{count}} file(s)_other": "ลบไฟล์ไม่สำเร็จ",
"Failed to delete files": "ลบไฟล์ไม่สำเร็จ",
"Total Files": "จำนวนไฟล์ทั้งหมด",
"Total Size": "ขนาดรวมทั้งหมด",
"Quota": "โควต้า",
"Used": "ใช้ไปแล้ว",
"Files": "ไฟล์",
"Search files...": "ค้นหาไฟล์...",
"Newest First": "ใหม่ที่สุดก่อน",
"Oldest First": "เก่าที่สุดก่อน",
"Largest First": "ใหญ่ที่สุดก่อน",
"Smallest First": "เล็กที่สุดก่อน",
"Name A-Z": "ชื่อ A-ฮ",
"Name Z-A": "ชื่อ ฮ-A",
"{{count}} selected_other": "เลือกไฟล์แล้ว",
"Delete Selected": "ลบไฟล์ที่เลือก",
"Created": "สร้างเมื่อ",
"No files found": "ไม่พบไฟล์",
"No files uploaded yet": "ยังไม่มีการอัปโหลดไฟล์",
"files": "ไฟล์",
"Page {{current}} of {{total}}": "หน้า {{current}} จาก {{total}}",
"Are you sure to delete {{count}} selected file(s)?_other": "คุณแน่ใจหรือว่าต้องการลบไฟล์ที่เลือก?",
"Cloud Storage Usage": "การใช้งานพื้นที่เก็บข้อมูลคลาวด์",
"Rename Group": "เปลี่ยนชื่อกลุ่ม",
"From Directory": "จากไดเรกทอรี",
"Successfully imported {{count}} book(s)_other": "นำเข้า {{count}} หนังสือเรียบร้อยแล้ว"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Otomatik Mod",
"Behavior": "Davranış",
"Book": "Kitap",
"Book Cover": "Kitap Kapağı",
"Bookmark": "Yer İmi",
"Cancel": "İptal",
"Chapter": "Bölüm",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Desteklenen dosya bulunamadı. Desteklenen formatlar: {{formats}}",
"Drop to Import Books": "Kitapları İçe Aktarmak İçin Bırak",
"Custom": "Özel",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Tüm dünya bir sahnedir,\nVe tüm erkekler ve kadınlar sadece oyunculardır;\nOnların çıkışları ve girişleri vardır,\nVe bir adam zamanında birçok rol oynar,\nOnun eylemleri yedi yaşındadır.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Tüm dünya bir sahnedir,\nVe tüm erkekler ve kadınlar sadece oyunculardır;\nOnların çıkışları ve girişleri vardır,\nVe bir adam zamanında birçok rol oynar,\nOnun eylemleri yedi yaşındadır.\n\n— William Shakespeare",
"Custom Theme": "Özel Tema",
"Theme Name": "Tema Adı",
"Text Color": "Metin Rengi",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Dosya Açıldığında Otomatik İçe Aktar",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Bölüm bulunamadı.",
"Failed to parse the EPUB file.": "EPUB dosyası ayrıştırılamadı.",
"This book format is not supported.": "Bu kitap formatı desteklenmiyor.",
"No chapters detected": "Bölüm bulunamadı",
"Failed to parse the EPUB file": "EPUB dosyası ayrıştırılamadı",
"This book format is not supported": "Bu kitap formatı desteklenmiyor",
"Unable to fetch the translation. Please log in first and try again.": "Çeviri alınamıyor. Lütfen önce giriş yapın ve tekrar deneyin.",
"Group": "Grup",
"Always on Top": "Her Zaman Üstte",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(As You Like It, 2. Perde)",
"Link Color": "Bağlantı Rengi",
"Volume Keys for Page Flip": "Sayfa Çevirme için Ses Seviyesi Tuşları",
"Clicks for Page Flip": "Sayfa Çevirme için Tıklamalar",
"Swap Clicks Area": "Tıklama Alanını Değiştir",
"Screen": "Ekran",
"Orientation": "Yönlendirme",
"Portrait": "Portre",
@@ -297,7 +294,6 @@
"Disable Translation": "Çeviriyi Devre Dışı Bırak",
"Scroll": "Kaydır",
"Overlap Pixels": "Örtüşen Pikseller",
"Click": "Klik",
"System Language": "System Dili",
"Security": "Security",
"Allow JavaScript": "JavaScript'e İzin Ver",
@@ -366,7 +362,6 @@
"Email:": "E-posta:",
"Plan:": "Plan:",
"Amount:": "Tutar:",
"Subscription ID:": "Abonelik ID'si:",
"Go to Library": "Kütüphaneye Git",
"Need help? Contact our support team at support@readest.com": "Yardım mı gerekiyor? Destek ekibimizle iletişime geçin: support@readest.com",
"Free Plan": "Ücretsiz Plan",
@@ -609,5 +604,164 @@
"Contain": "İçerir",
"{{number}} pages left in chapter": "Bu bölümde {{number}} sayfa kaldı",
"Device": "Cihaz",
"E-Ink Mode": "E-Ink Modu"
"E-Ink Mode": "E-Ink Modu",
"Highlight Colors": "Vurgu Renkleri",
"Auto Screen Brightness": "Otomatik Ekran Parlaklığı",
"Pagination": "Sayfalama",
"Disable Double Tap": "Çift Dokunmayı Devre Dışı Bırak",
"Tap to Paginate": "Sayfalamak için Dokunun",
"Click to Paginate": "Sayfalamak için Tıklayın",
"Tap Both Sides": "Her İki Tarafa Dokunun",
"Click Both Sides": "Her İki Tarafa Tıklayın",
"Swap Tap Sides": "Dokunma Taraflarını Değiştir",
"Swap Click Sides": "Tıklama Taraflarını Değiştir",
"Source and Translated": "Kaynak ve Çeviri",
"Translated Only": "Yalnızca Çeviri",
"Source Only": "Yalnızca Kaynak",
"TTS Text": "TTS Metni",
"The book file is corrupted": "Kitap dosyası bozulmuş",
"The book file is empty": "Kitap dosyası boş",
"Failed to open the book file": "Kitap dosyası açılamadı",
"On-Demand Purchase": "İhtiyaç Duyulduğunda Satın Alma",
"Full Customization": "Tam Özelleştirme",
"Lifetime Plan": "Ömür Boyu Plan",
"One-Time Payment": "Tek Seferlik Ödeme",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Tüm cihazlarda belirli özelliklere ömür boyu erişim sağlamak için tek seferlik bir ödeme yapın. Belirli özellikleri veya hizmetleri yalnızca ihtiyaç duyduğunuzda satın alın.",
"Expand Cloud Sync Storage": "Bulut Senkronizasyonu Depolama Alanını Genişlet",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Tek seferlik bir satın alma ile bulut depolama alanınızı sonsuza kadar genişletin. Her ek satın alma daha fazla alan ekler.",
"Unlock All Customization Options": "Tüm Özelleştirme Seçeneklerini Aç",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Ek tema, yazı tipi, düzen seçeneklerini ve sesli okuma, çevirmenler, bulut depolama hizmetlerini açın.",
"Purchase Successful!": "Satın Alma Başarılı!",
"lifetime": "ömür boyu",
"Thank you for your purchase! Your payment has been processed successfully.": "Satın alma işleminiz için teşekkürler! Ödemeniz başarıyla işlendi.",
"Order ID:": "Sipariş Kimliği:",
"TTS Highlighting": "TTS Vurgulama",
"Style": "Stil",
"Underline": "Altı Çizili",
"Strikethrough": "Üstü Çizili",
"Squiggly": "Dalgalı Çizgi",
"Outline": "Kontur",
"Save Current Color": "Mevcut Rengi Kaydet",
"Quick Colors": "Hızlı Renkler",
"Highlighter": "Vurgulayıcı",
"Save Book Cover": "Kitap Kapağını Kaydet",
"Auto-save last book cover": "Son kitap kapağını otomatik olarak kaydet",
"Back": "Geri",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Onay e-postası gönderildi! Değişikliği onaylamak için eski ve yeni e-posta adreslerinizi kontrol edin.",
"Failed to update email": "E-posta güncellenemedi",
"New Email": "Yeni e-posta",
"Your new email": "Yeni e-posta adresiniz",
"Updating email ...": "E-posta güncelleniyor ...",
"Update email": "E-postayı güncelle",
"Current email": "Mevcut e-posta",
"Update Email": "E-postayı güncelle",
"All": "Tümü",
"Unable to open book": "Kitap açılamıyor",
"Punctuation": "Noktalama İşaretleri",
"Replace Quotation Marks": "Tırnak İşaretlerini Değiştir",
"Enabled only in vertical layout.": "Yalnızca dikey düzenle etkinleştirildi.",
"No Conversion": "Dönüştürme yok",
"Simplified to Traditional": "Basitleştirilmiş → Geleneksel",
"Traditional to Simplified": "Geleneksel → Basitleştirilmiş",
"Simplified to Traditional (Taiwan)": "Basitleştirilmiş → Geleneksel (Tayvan)",
"Simplified to Traditional (Hong Kong)": "Basitleştirilmiş → Geleneksel (Hong Kong)",
"Simplified to Traditional (Taiwan), with phrases": "Basitleştirilmiş → Geleneksel (Tayvan • ifadeler)",
"Traditional (Taiwan) to Simplified": "Geleneksel (Tayvan) → Basitleştirilmiş",
"Traditional (Hong Kong) to Simplified": "Geleneksel (Hong Kong) → Basitleştirilmiş",
"Traditional (Taiwan) to Simplified, with phrases": "Geleneksel (Tayvan • ifadeler) → Basitleştirilmiş",
"Convert Simplified and Traditional Chinese": "Basitleştirilmiş/Geleneksel Çince Dönüştür",
"Convert Mode": "Dönüştürme Modu",
"Failed to auto-save book cover for lock screen: {{error}}": "Kitap kapağı kilit ekranı için otomatik kaydedilemedi: {{error}}",
"Download from Cloud": "Buluttan İndir",
"Upload to Cloud": "Buluta Yükle",
"Clear Custom Fonts": "Özel Yazı Tiplerini Temizle",
"Columns": "Sütunlar",
"OPDS Catalogs": "OPDS Katalogları",
"Adding LAN addresses is not supported in the web app version.": "Web uygulaması sürümünde LAN adresi ekleme desteklenmiyor.",
"Invalid OPDS catalog. Please check the URL.": "Geçersiz OPDS kataloğu. Lütfen URL'yi kontrol edin.",
"Browse and download books from online catalogs": "Çevrimiçi kataloglardan kitapları görüntüleyin ve indirin",
"My Catalogs": "Kataloglarım",
"Add Catalog": "Katalog Ekle",
"No catalogs yet": "Henüz katalog yok",
"Add your first OPDS catalog to start browsing books": "Kitapları görüntülemeye başlamak için ilk OPDS kataloğunuzu ekleyin",
"Add Your First Catalog": "İlk Kataloğunuzu Ekleyin",
"Browse": "Gözat",
"Popular Catalogs": "Popüler Kataloglar",
"Add": "Ekle",
"Add OPDS Catalog": "OPDS Kataloğu Ekle",
"Catalog Name": "Katalog Adı",
"My Calibre Library": "Calibre Kütüphanem",
"OPDS URL": "OPDS URL",
"Username (optional)": "Kullanıcı Adı (opsiyonel)",
"Password (optional)": "Şifre (opsiyonel)",
"Description (optional)": "Açıklama (opsiyonel)",
"A brief description of this catalog": "Bu katalog hakkında kısa açıklama",
"Validating...": "Doğrulanıyor...",
"View All": "Tümünü Görüntüle",
"Forward": "İleri",
"OPDS Catalog": "OPDS Kataloğu",
"Home": "Ana Sayfa",
"Library": "Kütüphane",
"{{count}} items_one": "{{count}} öğe",
"{{count}} items_other": "{{count}} öğe",
"Download completed": "İndirme tamamlandı",
"Download failed": "İndirme başarısız",
"Open Access": "Açık Erişim",
"Borrow": "Ödünç Al",
"Buy": "Satın Al",
"Subscribe": "Abone Ol",
"Sample": "Örnek",
"Download": "İndir",
"Open & Read": "Aç ve Oku",
"Tags": "Etiketler",
"Tag": "Etiket",
"First": "İlk",
"Previous": "Önceki",
"Next": "Sonraki",
"Last": "Son",
"Cannot Load Page": "Sayfa yüklenemiyor",
"An error occurred": "Bir hata oluştu",
"Online Library": "Çevrimiçi Kütüphane",
"URL must start with http:// or https://": "URL http:// veya https:// ile başlamalıdır",
"Title, Author, Tag, etc...": "Başlık, Yazar, Etiket, vb...",
"Query": "Sorgu",
"Subject": "Konu",
"Enter {{terms}}": "{{terms}} girin",
"No search results found": "Arama sonucu bulunamadı",
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS beslemesi yüklenemedi: {{status}} {{statusText}}",
"Search in {{title}}": "{{title}} içinde ara",
"Manage Storage": "Depolamayı Yönet",
"Failed to load files": "Dosyalar yüklenemedi",
"Deleted {{count}} file(s)_one": "{{count}} dosya silindi",
"Deleted {{count}} file(s)_other": "{{count}} dosya silindi",
"Failed to delete {{count}} file(s)_one": "{{count}} dosya silinemedi",
"Failed to delete {{count}} file(s)_other": "{{count}} dosya silinemedi",
"Failed to delete files": "Dosyalar silinemedi",
"Total Files": "Toplam Dosya",
"Total Size": "Toplam Boyut",
"Quota": "Kota",
"Used": "Kullanıldı",
"Files": "Dosyalar",
"Search files...": "Dosyaları ara...",
"Newest First": "En Yeni Önce",
"Oldest First": "En Eski Önce",
"Largest First": "En Büyük Önce",
"Smallest First": "En Küçük Önce",
"Name A-Z": "İsim A-Z",
"Name Z-A": "İsim Z-A",
"{{count}} selected_one": "{{count}} seçili dosya",
"{{count}} selected_other": "{{count}} seçili dosya",
"Delete Selected": "Seçilenleri Sil",
"Created": "Oluşturulma Tarihi",
"No files found": "Dosya bulunamadı",
"No files uploaded yet": "Henüz dosya yüklenmedi",
"files": "dosyalar",
"Page {{current}} of {{total}}": "{{total}} sayfa içinde {{current}}. sayfa",
"Are you sure to delete {{count}} selected file(s)?_one": "Seçilen {{count}} dosyayı silmek istediğinizden emin misiniz?",
"Are you sure to delete {{count}} selected file(s)?_other": "Seçilen {{count}} dosyayı silmek istediğinizden emin misiniz?",
"Cloud Storage Usage": "Bulut Depolama Kullanımı",
"Rename Group": "Grubu Yeniden Adlandır",
"From Directory": "Dizinden",
"Successfully imported {{count}} book(s)_one": "Başarıyla 1 kitap içe aktarıldı",
"Successfully imported {{count}} book(s)_other": "Başarıyla {{count}} kitap içe aktarıldı"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Автоматичний режим",
"Behavior": "Поведінка",
"Book": "Книга",
"Book Cover": "Обкладинка книги",
"Bookmark": "Закладка",
"Cancel": "Скасувати",
"Chapter": "Розділ",
@@ -172,7 +171,7 @@
"Phone number": "Номер телефону",
"Your phone number": "Ваш номер телефону",
"Token": "Токен",
"Your OTP token": "Ваш OTP токен",
"Your OTP token": "Ваш OTP-токен",
"Verify token": "Перевірити токен",
"Sign in with Google": "Увійти через Google",
"Sign in with Apple": "Увійти через Apple",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Не знайдено підтримуваних файлів. Підтримувані формати: {{formats}}",
"Drop to Import Books": "Перетягніть для імпорту книг",
"Custom": "Користувацький",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Так, світ — театр,\nДе всі чоловіки й жінки — актори.\nТут кожному приписаний свій вихід,\nне одну з них кожне грає роль.\nСім дій в тій п’єсі...\nВільям Шекспір\n(переклад Олександра Мокровольського)",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Так, світ — театр,\nДе всі чоловіки й жінки — актори.\nТут кожному приписаний свій вихід,\nне одну з них кожне грає роль.\nСім дій в тій п’єсі...\nВільям Шекспір\n(переклад Олександра Мокровольського)",
"Custom Theme": "Користувацька тема",
"Theme Name": "Назва теми",
"Text Color": "Колір тексту",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Автоматичний імпорт при відкритті файлу",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Не виявлено жодного розділу.",
"Failed to parse the EPUB file.": "Не вдалося розібрати файл EPUB.",
"This book format is not supported.": "Цей формат книги не підтримується.",
"No chapters detected": "Не виявлено жодного розділу",
"Failed to parse the EPUB file": "Не вдалося розібрати файл EPUB",
"This book format is not supported": "Цей формат книги не підтримується",
"Unable to fetch the translation. Please log in first and try again.": "Не вдалося отримати переклад. Будь ласка, спочатку увійдіть у систему та спробуйте ще раз.",
"Group": "Група",
"Always on Top": "Завжди зверху",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(із 'Як вам це подобається', Дія II)",
"Link Color": "Колір посилання",
"Volume Keys for Page Flip": "Перегортати сторінки клавішами гучності",
"Clicks for Page Flip": "Перегортати сторінки натисканням",
"Swap Clicks Area": "Поміняти області клацання місцями",
"Screen": "Екран",
"Orientation": "Орієнтація",
"Portrait": "Портрет",
@@ -299,7 +296,6 @@
"Disable Translation": "Вимкнути переклад",
"Scroll": "Прокрутка",
"Overlap Pixels": "Перекривати пікселі",
"Click": "Натиснути",
"System Language": "Системна мова",
"Security": "Безпека",
"Allow JavaScript": "Дозволити JavaScript",
@@ -374,7 +370,6 @@
"Email:": "Електронна пошта:",
"Plan:": "План:",
"Amount:": "Сума:",
"Subscription ID:": "ID підписки:",
"Go to Library": "Перейти до бібліотеки",
"Need help? Contact our support team at support@readest.com": "Потрібна допомога? Зверніться до служби підтримки: support@readest.com",
"Free Plan": "Безкоштовний план",
@@ -428,7 +423,7 @@
"Enter series index": "Введіть номер циклу",
"Total in Series": "Всього у циклі",
"Enter total books in series": "Введіть загальну кількість книг у циклі",
"Enter publisher": "Введіть видавництвця",
"Enter publisher": "Введіть видавця",
"Publication Date": "Дата видання",
"Identifier": "Ідентифікатор",
"Enter book description": "Введіть опис книги",
@@ -555,8 +550,8 @@
"Add Bookmark": "Додати закладку",
"Books Content": "Зміст книг",
"Jump to Location": "Перейти до місця",
"Unpin Notebook": "Відкріпити блокнот",
"Pin Notebook": "Закріпити блокнот",
"Unpin Notebook": "Відкріпити нотатник",
"Pin Notebook": "Закріпити нотатник",
"Hide Search Bar": "Сховати панель пошуку",
"Show Search Bar": "Показати панель пошуку",
"On {{current}} of {{total}} page": "На {{current}} з {{total}} сторінці",
@@ -613,9 +608,180 @@
"Import Image": "Імпортувати зображення",
"Opacity": "Непрозорість",
"Size": "Розмір",
"Cover": "Обкладинка",
"Contain": "Вміщати",
"Cover": "Покрити",
"Contain": "Втиснути",
"{{number}} pages left in chapter": "Залишилось {{number}} сторінок у розділі",
"Device": "Пристрій",
"E-Ink Mode": "E-Ink режим"
"E-Ink Mode": "E-Ink режим",
"Highlight Colors": "Кольори виділення",
"Auto Screen Brightness": "Автоматична яскравість екрану",
"Pagination": "Розбиття на сторінки",
"Disable Double Tap": "Вимкнути подвійне натискання",
"Tap to Paginate": "Натисніть, щоб розбити на сторінки",
"Click to Paginate": "Клікніть, щоб розбити на сторінки",
"Tap Both Sides": "Натисніть обидві сторони",
"Click Both Sides": "Клікніть обидві сторони",
"Swap Tap Sides": "Змінити сторони дотику",
"Swap Click Sides": "Змінити сторони кліку",
"Source and Translated": "Джерело та переклад",
"Translated Only": "Тільки переклад",
"Source Only": "Тільки джерело",
"TTS Text": "Текст TTS",
"The book file is corrupted": "Файл книги пошкоджено",
"The book file is empty": "Файл книги порожній",
"Failed to open the book file": "Не вдалося відкрити файл книги",
"On-Demand Purchase": "Покупка за запитом",
"Full Customization": "Повна кастомізація",
"Lifetime Plan": "На все життя",
"One-Time Payment": "Одноразовий платіж",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Зробіть одноразовий платіж, щоб отримати довічний доступ до певних функцій на всіх пристроях. Купуйте конкретні функції або послуги лише тоді, коли вони вам потрібні.",
"Expand Cloud Sync Storage": "Розширити сховище синхронізації в хмарі",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Розширте своє хмарне сховище назавжди за допомогою одноразової покупки. Кожна додаткова покупка додає більше місця.",
"Unlock All Customization Options": "Відкрити всі параметри налаштування",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Відкрийте додаткові теми, шрифти, параметри макета та функції читання вголос, перекладачі, послуги хмарного зберігання.",
"Purchase Successful!": "Покупка успішна!",
"lifetime": "довічний",
"Thank you for your purchase! Your payment has been processed successfully.": "Дякуємо за вашу покупку! Ваш платіж успішно оброблено.",
"Order ID:": "ID замовлення:",
"TTS Highlighting": "Підсвічування TTS",
"Style": "Стиль",
"Underline": "Підкреслення",
"Strikethrough": "Закреслення",
"Squiggly": "Хвиляста лінія",
"Outline": "Контур",
"Save Current Color": "Зберегти поточний колір",
"Quick Colors": "Швидкі кольори",
"Highlighter": "Маркер",
"Save Book Cover": "Зберегти обкладинку книги",
"Auto-save last book cover": "Автоматично зберегти останню обкладинку книги",
"Back": "Назад",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Лист підтвердження надіслано! Будь ласка, перевірте стару та нову електронну адресу, щоб підтвердити зміну.",
"Failed to update email": "Не вдалося оновити електронну пошту",
"New Email": "Нова електронна адреса",
"Your new email": "Ваша нова електронна адреса",
"Updating email ...": "Оновлення електронної адреси ...",
"Update email": "Оновити електронну адресу",
"Current email": "Поточна електронна адреса",
"Update Email": "Оновити Адресу",
"All": "Всі",
"Unable to open book": "Не вдалося відкрити книгу",
"Punctuation": "Пунктуація",
"Replace Quotation Marks": "Замінити лапки",
"Enabled only in vertical layout.": "Активовано лише у вертикальному макеті.",
"No Conversion": "Без конвертації",
"Simplified to Traditional": "Спрощена → Традиційна",
"Traditional to Simplified": "Традиційна → Спрощена",
"Simplified to Traditional (Taiwan)": "Спрощена → Традиційна (Тайвань)",
"Simplified to Traditional (Hong Kong)": "Спрощена → Традиційна (Хонкон)",
"Simplified to Traditional (Taiwan), with phrases": "Спрощена → Традиційна (Тайвань • фрази)",
"Traditional (Taiwan) to Simplified": "Традиційна (Тайвань) → Спрощена",
"Traditional (Hong Kong) to Simplified": "Традиційна (Хонкон) → Спрощена",
"Traditional (Taiwan) to Simplified, with phrases": "Традиційна (Тайвань • фрази) → Спрощена",
"Convert Simplified and Traditional Chinese": "Конвертувати спрощену/традиційну китайську",
"Convert Mode": "Режим конвертації",
"Failed to auto-save book cover for lock screen: {{error}}": "Не вдалося автоматично зберегти обкладинку книги для екрана блокування: {{error}}",
"Download from Cloud": "Завантажити з хмари",
"Upload to Cloud": "Завантажити в хмару",
"Clear Custom Fonts": "Очистити користувацькі шрифти",
"Columns": "Стовпці",
"OPDS Catalogs": "Каталоги OPDS",
"Adding LAN addresses is not supported in the web app version.": "Додавання LAN-адрес не підтримується у веб-версії.",
"Invalid OPDS catalog. Please check the URL.": "Недійсний каталог OPDS. Будь ласка, перевірте URL.",
"Browse and download books from online catalogs": "Переглядайте та завантажуйте книги з онлайн-каталогів",
"My Catalogs": "Мої каталоги",
"Add Catalog": "Додати каталог",
"No catalogs yet": "Каталогів поки немає",
"Add your first OPDS catalog to start browsing books": "Додайте перший каталог OPDS, щоб почати перегляд книг",
"Add Your First Catalog": "Додайте свій перший каталог",
"Browse": "Переглянути",
"Popular Catalogs": "Популярні каталоги",
"Add": "Додати",
"Add OPDS Catalog": "Додати каталог OPDS",
"Catalog Name": "Назва каталогу",
"My Calibre Library": "Моя бібліотека Calibre",
"OPDS URL": "OPDS URL",
"Username (optional)": "Ім’я користувача (необов’язково)",
"Password (optional)": "Пароль (необов’язково)",
"Description (optional)": "Опис (необов’язково)",
"A brief description of this catalog": "Короткий опис цього каталогу",
"Validating...": "Перевірка...",
"View All": "Переглянути все",
"Forward": "Вперед",
"OPDS Catalog": "Каталог OPDS",
"Home": "Головна",
"Library": "Бібліотека",
"{{count}} items_one": "{{count}} елемент",
"{{count}} items_few": "{{count}} елементи",
"{{count}} items_many": "{{count}} елементів",
"{{count}} items_other": "{{count}} елементів",
"Download completed": "Завантаження завершено",
"Download failed": "Завантаження не вдалося",
"Open Access": "Вільний доступ",
"Borrow": "Позичити",
"Buy": "Купити",
"Subscribe": "Підписатися",
"Sample": "Зразок",
"Download": "Завантажити",
"Open & Read": "Відкрити та читати",
"Tags": "Теги",
"Tag": "Тег",
"First": "Перша",
"Previous": "Попередня",
"Next": "Наступна",
"Last": "Остання",
"Cannot Load Page": "Не вдалося завантажити сторінку",
"An error occurred": "Сталася помилка",
"Online Library": "Онлайн бібліотека",
"URL must start with http:// or https://": "URL повинен починатися з http:// або https://",
"Title, Author, Tag, etc...": "Назва, автор, тег тощо...",
"Query": "Запит",
"Subject": "Тема",
"Enter {{terms}}": "Введіть {{terms}}",
"No search results found": "Результатів пошуку не знайдено",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Не вдалося завантажити стрічку OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Пошук у {{title}}",
"Manage Storage": "Керування сховищем",
"Failed to load files": "Не вдалося завантажити файли",
"Deleted {{count}} file(s)_one": "Видалено {{count}} файл",
"Deleted {{count}} file(s)_few": "Видалено {{count}} файли",
"Deleted {{count}} file(s)_many": "Видалено {{count}} файлів",
"Deleted {{count}} file(s)_other": "Видалено {{count}} файлів",
"Failed to delete {{count}} file(s)_one": "Не вдалося видалити {{count}} файл",
"Failed to delete {{count}} file(s)_few": "Не вдалося видалити {{count}} файли",
"Failed to delete {{count}} file(s)_many": "Не вдалося видалити {{count}} файлів",
"Failed to delete {{count}} file(s)_other": "Не вдалося видалити {{count}} файлів",
"Failed to delete files": "Не вдалося видалити файли",
"Total Files": "Всього файлів",
"Total Size": "Загальний розмір",
"Quota": "Квота",
"Used": "Використано",
"Files": "Файли",
"Search files...": "Пошук файлів...",
"Newest First": "Спершу новіші",
"Oldest First": "Спершу старіші",
"Largest First": "Спершу найбільші",
"Smallest First": "Спершу найменші",
"Name A-Z": "Ім'я A-Z",
"Name Z-A": "Ім'я Z-A",
"{{count}} selected_one": "Вибрано {{count}} файл",
"{{count}} selected_few": "Вибрано {{count}} файли",
"{{count}} selected_many": "Вибрано {{count}} файлів",
"{{count}} selected_other": "Вибрано {{count}} файлів",
"Delete Selected": "Видалити вибране",
"Created": "Створено",
"No files found": "Файли не знайдено",
"No files uploaded yet": "Файли ще не завантажені",
"files": "файли",
"Page {{current}} of {{total}}": "Сторінка {{current}} з {{total}}",
"Are you sure to delete {{count}} selected file(s)?_one": "Ви впевнені, що хочете видалити {{count}} файл?",
"Are you sure to delete {{count}} selected file(s)?_few": "Ви впевнені, що хочете видалити {{count}} файли?",
"Are you sure to delete {{count}} selected file(s)?_many": "Ви впевнені, що хочете видалити {{count}} файлів?",
"Are you sure to delete {{count}} selected file(s)?_other": "Ви впевнені, що хочете видалити {{count}} файлів?",
"Cloud Storage Usage": "Використання хмарного сховища",
"Rename Group": "Перейменувати групу",
"From Directory": "З каталогу",
"Successfully imported {{count}} book(s)_one": "Успішно імпортовано 1 книгу",
"Successfully imported {{count}} book(s)_few": "Успішно імпортовано {{count}} книги",
"Successfully imported {{count}} book(s)_many": "Успішно імпортовано {{count}} книг",
"Successfully imported {{count}} book(s)_other": "Успішно імпортовано {{count}} книг"
}
@@ -8,7 +8,6 @@
"Auto Mode": "Chế độ tự động",
"Behavior": "Hành vi",
"Book": "Sách",
"Book Cover": "Bìa sách",
"Bookmark": "Đánh dấu",
"Cancel": "Hủy",
"Chapter": "Chương",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "Không tìm thấy tệp được hỗ trợ. Định dạng được hỗ trợ: {{formats}}",
"Drop to Import Books": "Kéo và thả để nhập sách",
"Custom": "Tùy chỉnh",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "Cả thế giới này là một sân khấu,\nVà tất cả đàn ông và phụ nữ chỉ là những diễn viên;\nHọ có cửa ra và cửa vào của mình,\nVà một người trong cuộc đời mình đóng nhiều vai,\nHành động của mình là bảy tuổi.\n\n— William Shakespeare",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Cả thế giới này là một sân khấu,\nVà tất cả đàn ông và phụ nữ chỉ là những diễn viên;\nHọ có cửa ra và cửa vào của mình,\nVà một người trong cuộc đời mình đóng nhiều vai,\nHành động của mình là bảy tuổi.\n\n— William Shakespeare",
"Custom Theme": "Chủ đề tùy chỉnh",
"Theme Name": "Tên chủ đề",
"Text Color": "Màu văn bản",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "Tự động nhập khi mở tệp",
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"No chapters detected.": "Không phát hiện thấy chương nào.",
"Failed to parse the EPUB file.": "Không thể phân tích tệp EPUB.",
"This book format is not supported.": "Định dạng sách này không được hỗ trợ.",
"No chapters detected": "Không phát hiện thấy chương nào",
"Failed to parse the EPUB file": "Không thể phân tích tệp EPUB",
"This book format is not supported": "Định dạng sách này không được hỗ trợ",
"Unable to fetch the translation. Please log in first and try again.": "Không thể tải dịch vụ. Vui lòng đăng nhập trước và thử lại.",
"Group": "Nhóm",
"Always on Top": "Luôn ở trên cùng",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(từ 'Như bạn thích', Hành động II)",
"Link Color": "Màu liên kết",
"Volume Keys for Page Flip": "Phím âm lượng để lật trang",
"Clicks for Page Flip": "Nhấp để lật trang",
"Swap Clicks Area": "Đổi khu vực nhấp",
"Screen": "Màn hình",
"Orientation": "Định hướng",
"Portrait": "Chân dung",
@@ -296,7 +293,6 @@
"Disable Translation": "Tắt dịch",
"Scroll": "Cuộn",
"Overlap Pixels": "Chồng lấn pixel",
"Click": "Nhấp",
"System Language": "Ngôn ngữ hệ thống",
"Security": "An ninh",
"Allow JavaScript": "Cho phép JavaScript",
@@ -362,7 +358,6 @@
"Email:": "Email:",
"Plan:": "Gói:",
"Amount:": "Số tiền:",
"Subscription ID:": "ID đăng ký:",
"Go to Library": "Đi đến Thư viện",
"Need help? Contact our support team at support@readest.com": "Cần hỗ trợ? Liên hệ nhóm hỗ trợ tại support@readest.com",
"Free Plan": "Gói miễn phí",
@@ -605,5 +600,158 @@
"Contain": "Chứa",
"{{number}} pages left in chapter": "Còn {{number}} trang trong chương",
"Device": "Thiết bị",
"E-Ink Mode": "Chế độ E-Ink"
"E-Ink Mode": "Chế độ E-Ink",
"Highlight Colors": "Màu nổi bật",
"Auto Screen Brightness": "Độ sáng màn hình tự động",
"Pagination": "Phân trang",
"Disable Double Tap": "Vô hiệu hóa chạm hai lần",
"Tap to Paginate": "Chạm để phân trang",
"Click to Paginate": "Nhấp để phân trang",
"Tap Both Sides": "Chạm vào cả hai bên",
"Click Both Sides": "Nhấp vào cả hai bên",
"Swap Tap Sides": "Đổi bên chạm",
"Swap Click Sides": "Đổi bên nhấp",
"Source and Translated": "Nguồn và Đã dịch",
"Translated Only": "Chỉ Đã dịch",
"Source Only": "Chỉ Nguồn",
"TTS Text": "Văn bản TTS",
"The book file is corrupted": "Tệp sách bị hỏng",
"The book file is empty": "Tệp sách trống",
"Failed to open the book file": "Không thể mở tệp sách",
"On-Demand Purchase": "Mua theo yêu cầu",
"Full Customization": "Tùy chỉnh đầy đủ",
"Lifetime Plan": "Kế hoạch trọn đời",
"One-Time Payment": "Thanh toán một lần",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Thực hiện một khoản thanh toán duy nhất để tận hưởng quyền truy cập trọn đời vào các tính năng cụ thể trên tất cả các thiết bị. Mua các tính năng hoặc dịch vụ cụ thể chỉ khi bạn cần chúng.",
"Expand Cloud Sync Storage": "Mở rộng dung lượng lưu trữ đồng bộ hóa đám mây",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Mở rộng dung lượng lưu trữ đám mây của bạn mãi mãi với một lần mua. Mỗi lần mua thêm sẽ thêm không gian.",
"Unlock All Customization Options": "Mở khóa tất cả tùy chọn tùy chỉnh",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Mở khóa các chủ đề, phông chữ, tùy chọn bố cục và đọc to, dịch giả, dịch vụ lưu trữ đám mây bổ sung.",
"Purchase Successful!": "Mua hàng thành công!",
"lifetime": "trọn đời",
"Thank you for your purchase! Your payment has been processed successfully.": "Cảm ơn bạn đã mua hàng! Thanh toán của bạn đã được xử lý thành công.",
"Order ID:": "Mã đơn hàng:",
"TTS Highlighting": "Đánh dấu TTS",
"Style": "Kiểu",
"Underline": "Gạch chân",
"Strikethrough": "Gạch ngang",
"Squiggly": "Gạch ngoằn ngoèo",
"Outline": "Đường viền",
"Save Current Color": "Lưu màu hiện tại",
"Quick Colors": "Màu nhanh",
"Highlighter": "Bút đánh dấu",
"Save Book Cover": "Lưu bìa sách",
"Auto-save last book cover": "Tự động lưu bìa sách cuối cùng",
"Back": "Quay lại",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Email xác nhận đã được gửi! Vui lòng kiểm tra địa chỉ email cũ và mới của bạn để xác nhận thay đổi.",
"Failed to update email": "Cập nhật email thất bại",
"New Email": "Email mới",
"Your new email": "Email mới của bạn",
"Updating email ...": "Đang cập nhật email ...",
"Update email": "Cập nhật email",
"Current email": "Email hiện tại",
"Update Email": "Cập nhật email",
"All": "Tất cả",
"Unable to open book": "Không thể mở sách",
"Punctuation": "Dấu câu",
"Replace Quotation Marks": "Thay thế dấu ngoặc kép",
"Enabled only in vertical layout.": "Chỉ bật trong bố cục dọc.",
"No Conversion": "Không chuyển đổi",
"Simplified to Traditional": "Giản thể → Phồn thể",
"Traditional to Simplified": "Phồn thể → Giản thể",
"Simplified to Traditional (Taiwan)": "Giản thể → Phồn thể (Đài Loan)",
"Simplified to Traditional (Hong Kong)": "Giản thể → Phồn thể (Hồng Kông)",
"Simplified to Traditional (Taiwan), with phrases": "Giản thể → Phồn thể (Đài Loan • cụm từ)",
"Traditional (Taiwan) to Simplified": "Phồn thể (Đài Loan) → Giản thể",
"Traditional (Hong Kong) to Simplified": "Phồn thể (Hồng Kông) → Giản thể",
"Traditional (Taiwan) to Simplified, with phrases": "Phồn thể (Đài Loan • cụm từ) → Giản thể",
"Convert Simplified and Traditional Chinese": "Chuyển đổi Trung giản/Phồn thể",
"Convert Mode": "Chế độ chuyển đổi",
"Failed to auto-save book cover for lock screen: {{error}}": "Không thể tự động lưu bìa sách cho màn hình khóa: {{error}}",
"Download from Cloud": "Tải xuống từ đám mây",
"Upload to Cloud": "Tải lên đám mây",
"Clear Custom Fonts": "Xóa phông chữ tùy chỉnh",
"Columns": "Cột",
"OPDS Catalogs": "Danh mục OPDS",
"Adding LAN addresses is not supported in the web app version.": "Việc thêm địa chỉ LAN không được hỗ trợ trong phiên bản web.",
"Invalid OPDS catalog. Please check the URL.": "Danh mục OPDS không hợp lệ. Vui lòng kiểm tra URL.",
"Browse and download books from online catalogs": "Duyệt và tải sách từ các danh mục trực tuyến",
"My Catalogs": "Danh mục của tôi",
"Add Catalog": "Thêm danh mục",
"No catalogs yet": "Chưa có danh mục nào",
"Add your first OPDS catalog to start browsing books": "Thêm danh mục OPDS đầu tiên để bắt đầu duyệt sách",
"Add Your First Catalog": "Thêm danh mục đầu tiên",
"Browse": "Duyệt",
"Popular Catalogs": "Danh mục phổ biến",
"Add": "Thêm",
"Add OPDS Catalog": "Thêm danh mục OPDS",
"Catalog Name": "Tên danh mục",
"My Calibre Library": "Thư viện Calibre của tôi",
"OPDS URL": "URL OPDS",
"Username (optional)": "Tên đăng nhập (tùy chọn)",
"Password (optional)": "Mật khẩu (tùy chọn)",
"Description (optional)": "Mô tả (tùy chọn)",
"A brief description of this catalog": "Mô tả ngắn gọn về danh mục này",
"Validating...": "Đang xác thực...",
"View All": "Xem tất cả",
"Forward": "Tiếp",
"OPDS Catalog": "Danh mục OPDS",
"Home": "Trang chủ",
"Library": "Thư viện",
"{{count}} items_other": "{{count}} mục",
"Download completed": "Tải xuống hoàn tất",
"Download failed": "Tải xuống thất bại",
"Open Access": "Truy cập mở",
"Borrow": "Mượn",
"Buy": "Mua",
"Subscribe": "Đăng ký",
"Sample": "Mẫu",
"Download": "Tải xuống",
"Open & Read": "Mở & Đọc",
"Tags": "Thẻ",
"Tag": "Thẻ",
"First": "Đầu tiên",
"Previous": "Trước",
"Next": "Tiếp theo",
"Last": "Cuối cùng",
"Cannot Load Page": "Không thể tải trang",
"An error occurred": "Đã xảy ra lỗi",
"Online Library": "Thư viện trực tuyến",
"URL must start with http:// or https://": "URL phải bắt đầu bằng http:// hoặc https://",
"Title, Author, Tag, etc...": "Tiêu đề, Tác giả, Thẻ, v.v...",
"Query": "Truy vấn",
"Subject": "Chủ đề",
"Enter {{terms}}": "Nhập {{terms}}",
"No search results found": "Không tìm thấy kết quả tìm kiếm",
"Failed to load OPDS feed: {{status}} {{statusText}}": "Không tải được nguồn cấp OPDS: {{status}} {{statusText}}",
"Search in {{title}}": "Tìm kiếm trong {{title}}",
"Manage Storage": "Quản lý bộ nhớ",
"Failed to load files": "Không tải được tệp",
"Deleted {{count}} file(s)_other": "Đã xóa {{count}} tệp",
"Failed to delete {{count}} file(s)_other": "Không xóa được {{count}} tệp",
"Failed to delete files": "Không xóa được tệp",
"Total Files": "Tổng số tệp",
"Total Size": "Tổng dung lượng",
"Quota": "Dung lượng cho phép",
"Used": "Đã sử dụng",
"Files": "Tệp",
"Search files...": "Tìm tệp...",
"Newest First": "Mới nhất trước",
"Oldest First": "Cũ nhất trước",
"Largest First": "Lớn nhất trước",
"Smallest First": "Nhỏ nhất trước",
"Name A-Z": "Tên A-Z",
"Name Z-A": "Tên Z-A",
"{{count}} selected_other": "Đã chọn {{count}} tệp",
"Delete Selected": "Xóa các mục đã chọn",
"Created": "Đã tạo",
"No files found": "Không tìm thấy tệp",
"No files uploaded yet": "Chưa tải lên tệp nào",
"files": "tệp",
"Page {{current}} of {{total}}": "Trang {{current}} trên {{total}}",
"Are you sure to delete {{count}} selected file(s)?_other": "Bạn có chắc muốn xóa {{count}} tệp đã chọn không?",
"Cloud Storage Usage": "Sử dụng lưu trữ đám mây",
"Rename Group": "Đổi tên nhóm",
"From Directory": "Từ thư mục",
"Successfully imported {{count}} book(s)_other": "Đã nhập thành công {{count}} sách"
}
@@ -8,7 +8,6 @@
"Auto Mode": "自动主题",
"Behavior": "行为",
"Book": "书籍",
"Book Cover": "书籍封面",
"Bookmark": "书签",
"Cancel": "取消",
"Chapter": "章节",
@@ -87,7 +86,7 @@
"Show Book Details": "显示书籍详情",
"Sidebar": "侧边栏",
"Sign In": "登录",
"Sign Out": "登出",
"Sign Out": "登出账户",
"Sky": "天青",
"Slow": "慢",
"Solarized": "日晖",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "未找到支持的文件。支持的格式:{{formats}}",
"Drop to Import Books": "拖放导入书籍",
"Custom": "自定义",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "世界是座舞台, \n所有男女都只是演员; \n各有其出场和入场; \n每个人皆扮演许多角色, \n而演出共分为七个阶段。\n\n——威廉·莎士比亚",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "世界是座舞台, \n所有男女都只是演员; \n各有其出场和入场; \n每个人皆扮演许多角色, \n而演出共分为七个阶段。\n\n——威廉·莎士比亚",
"Custom Theme": "自定义主题",
"Theme Name": "主题名称",
"Text Color": "文本颜色",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "打开文件时自动导入",
"LXGW WenKai GB Screen": "霞鹜文楷",
"LXGW WenKai TC": "霞鶩文楷",
"No chapters detected.": "未检测到章节",
"Failed to parse the EPUB file.": "无法解析 EPUB 文件",
"This book format is not supported.": "不支持此书籍格式",
"No chapters detected": "未检测到章节",
"Failed to parse the EPUB file": "无法解析 EPUB 文件",
"This book format is not supported": "不支持此书籍格式",
"Unable to fetch the translation. Please log in first and try again.": "无法获取翻译。请登录后重试。",
"Group": "分组",
"Always on Top": "窗口置顶",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "出自《皆大欢喜》,第二幕",
"Link Color": "链接颜色",
"Volume Keys for Page Flip": "音量键翻页",
"Clicks for Page Flip": "点击翻页",
"Swap Clicks Area": "交换点击区域",
"Screen": "屏幕",
"Orientation": "屏幕方向",
"Portrait": "竖屏",
@@ -296,7 +293,6 @@
"Disable Translation": "禁用翻译",
"Scroll": "滚动",
"Overlap Pixels": "重叠像素",
"Click": "点击",
"System Language": "系统语言",
"Security": "安全",
"Allow JavaScript": "允许 JavaScript",
@@ -362,7 +358,6 @@
"Email:": "邮箱:",
"Plan:": "套餐:",
"Amount:": "金额:",
"Subscription ID:": "订阅 ID",
"Go to Library": "前往书库",
"Need help? Contact our support team at support@readest.com": "需要帮助?请联系支持团队:support@readest.com",
"Free Plan": "免费套餐",
@@ -396,7 +391,7 @@
"Daily translation quota reached. Upgrade your plan to continue using AI translations.": "已达每日翻译配额。升级套餐以继续使用 AI 翻译。",
"Includes All Plus Plan Benefits": "包含所有 Plus 套餐功能",
"Early Feature Access": "抢先体验新功能",
"Be the first to explore new features, updates, and innovations before anyone else.": "抢先体验新功能、更新创新。",
"Be the first to explore new features, updates, and innovations before anyone else.": "抢先体验新功能、更新创新成果。",
"Advanced AI Tools": "高级 AI 工具",
"Harness powerful AI tools for smarter reading, translation, and content discovery.": "利用强大的 AI 工具实现智能阅读、翻译和内容发现。",
"Translate up to 100,000 characters daily with the most accurate translation engine available.": "使用最精准的翻译引擎每天可翻译10万字。",
@@ -605,5 +600,158 @@
"Contain": "适应",
"{{number}} pages left in chapter": "本章剩余{{number}}页",
"Device": "设备",
"E-Ink Mode": "E-Ink 模式"
"E-Ink Mode": "E-Ink 模式",
"Highlight Colors": "高亮颜色",
"Auto Screen Brightness": "自动屏幕亮度",
"Pagination": "翻页",
"Disable Double Tap": "禁用双击",
"Tap to Paginate": "点击翻页",
"Click to Paginate": "点击翻页",
"Tap Both Sides": "点击两侧翻页",
"Click Both Sides": "点击两侧翻页",
"Swap Tap Sides": "交换点击区域",
"Swap Click Sides": "交换点击区域",
"Source and Translated": "原文和译文",
"Translated Only": "仅译文",
"Source Only": "仅原文",
"TTS Text": "TTS 文本",
"The book file is corrupted": "书籍文件已损坏",
"The book file is empty": "书籍文件为空",
"Failed to open the book file": "无法打开书籍文件",
"On-Demand Purchase": "按需购买",
"Full Customization": "完全自定义",
"Lifetime Plan": "终身套餐",
"One-Time Payment": "一次性付款",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "进行一次性付款以享受在所有设备上对特定功能的终身访问。仅在需要时购买特定功能或服务。",
"Expand Cloud Sync Storage": "扩展云同步存储",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "通过一次性购买,永久扩展您的云存储。每次额外购买都会增加更多空间。",
"Unlock All Customization Options": "解锁所有自定义选项",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "解锁其他主题、字体、布局选项以及朗读、翻译、云存储服务。",
"Purchase Successful!": "购买成功!",
"lifetime": "永久",
"Thank you for your purchase! Your payment has been processed successfully.": "感谢您的购买!您的付款已成功处理。",
"Order ID:": "订单号:",
"TTS Highlighting": "TTS 高亮",
"Style": "样式",
"Underline": "下划线",
"Strikethrough": "删除线",
"Squiggly": "波浪线",
"Outline": "轮廓",
"Save Current Color": "保存当前颜色",
"Quick Colors": "快速颜色",
"Highlighter": "荧光笔",
"Save Book Cover": "保存书籍封面",
"Auto-save last book cover": "自动保存最后一本书的封面",
"Back": "返回",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "确认邮件已发送!请检查您的旧邮箱和新邮箱以确认更改。",
"Failed to update email": "更新邮箱失败",
"New Email": "新邮箱",
"Your new email": "您的新邮箱",
"Updating email ...": "正在更新邮箱 ...",
"Update email": "更新邮箱",
"Current email": "当前邮箱",
"Update Email": "更新邮箱",
"All": "全部",
"Unable to open book": "无法打开书籍",
"Punctuation": "标点符号",
"Replace Quotation Marks": "替换引号",
"Enabled only in vertical layout.": "仅在竖排布局中启用",
"No Conversion": "不转换",
"Simplified to Traditional": "简体 → 繁体",
"Traditional to Simplified": "繁体 → 简体",
"Simplified to Traditional (Taiwan)": "简体 → 繁体(台湾)",
"Simplified to Traditional (Hong Kong)": "简体 → 繁体(香港)",
"Simplified to Traditional (Taiwan), with phrases": "简体 → 繁体(台湾)习惯用语",
"Traditional (Taiwan) to Simplified": "繁体(台湾) → 简体",
"Traditional (Hong Kong) to Simplified": "繁体(香港) → 简体",
"Traditional (Taiwan) to Simplified, with phrases": "繁体(台湾) → 简体习惯用语",
"Convert Simplified and Traditional Chinese": "简繁中文转换",
"Convert Mode": "转换模式",
"Failed to auto-save book cover for lock screen: {{error}}": "自动保存锁屏书籍封面失败:{{error}}",
"Download from Cloud": "从云端下载",
"Upload to Cloud": "上传到云端",
"Clear Custom Fonts": "清除自定义字体",
"Columns": "列数",
"OPDS Catalogs": "OPDS 目录",
"Adding LAN addresses is not supported in the web app version.": "Web 版不支持添加局域网地址。",
"Invalid OPDS catalog. Please check the URL.": "无效的 OPDS 目录。请检查 URL。",
"Browse and download books from online catalogs": "浏览并下载在线目录中的书籍",
"My Catalogs": "我的目录",
"Add Catalog": "添加目录",
"No catalogs yet": "尚无目录",
"Add your first OPDS catalog to start browsing books": "添加您的第一个 OPDS 目录以开始浏览书籍",
"Add Your First Catalog": "添加第一个目录",
"Browse": "浏览",
"Popular Catalogs": "热门目录",
"Add": "添加",
"Add OPDS Catalog": "添加 OPDS 目录",
"Catalog Name": "目录名称",
"My Calibre Library": "我的 Calibre 图书馆",
"OPDS URL": "OPDS URL",
"Username (optional)": "用户名(可选)",
"Password (optional)": "密码(可选)",
"Description (optional)": "描述(可选)",
"A brief description of this catalog": "该目录的简要描述",
"Validating...": "验证中...",
"View All": "查看全部",
"Forward": "前进",
"OPDS Catalog": "OPDS 目录",
"Home": "首页",
"Library": "图书馆",
"{{count}} items_other": "{{count}} 项",
"Download completed": "下载完成",
"Download failed": "下载失败",
"Open Access": "开放访问",
"Borrow": "借阅",
"Buy": "购买",
"Subscribe": "订阅",
"Sample": "样章",
"Download": "下载",
"Open & Read": "打开并阅读",
"Tags": "标签",
"Tag": "标签",
"First": "首页",
"Previous": "上一页",
"Next": "下一页",
"Last": "末页",
"Cannot Load Page": "无法加载页面",
"An error occurred": "发生错误",
"Online Library": "在线书库",
"URL must start with http:// or https://": "URL 必须以 http:// 或 https:// 开头",
"Title, Author, Tag, etc...": "标题,作者,标签等...",
"Query": "查询",
"Subject": "主题",
"Enter {{terms}}": "输入 {{terms}}",
"No search results found": "未找到搜索结果",
"Failed to load OPDS feed: {{status}} {{statusText}}": "加载 OPDS 源失败:{{status}} {{statusText}}",
"Search in {{title}}": "在 {{title}} 中搜索",
"Manage Storage": "管理存储",
"Failed to load files": "加载文件失败",
"Deleted {{count}} file(s)_other": "已删除 {{count}} 个文件",
"Failed to delete {{count}} file(s)_other": "删除 {{count}} 个文件失败",
"Failed to delete files": "删除文件失败",
"Total Files": "文件总数",
"Total Size": "总大小",
"Quota": "配额",
"Used": "已使用",
"Files": "文件",
"Search files...": "搜索文件...",
"Newest First": "最新优先",
"Oldest First": "最旧优先",
"Largest First": "最大优先",
"Smallest First": "最小优先",
"Name A-Z": "名称 A-Z",
"Name Z-A": "名称 Z-A",
"{{count}} selected_other": "已选择 {{count}} 个文件",
"Delete Selected": "删除选中项",
"Created": "创建时间",
"No files found": "未找到文件",
"No files uploaded yet": "尚未上传文件",
"files": "文件",
"Page {{current}} of {{total}}": "第 {{current}} 页,共 {{total}} 页",
"Are you sure to delete {{count}} selected file(s)?_other": "确定要删除已选择的 {{count}} 个文件吗?",
"Cloud Storage Usage": "云存储使用情况",
"Rename Group": "重命名分组",
"From Directory": "从文件夹导入",
"Successfully imported {{count}} book(s)_other": "成功导入 {{count}} 本书"
}
@@ -8,7 +8,6 @@
"Auto Mode": "自動主題",
"Behavior": "行為",
"Book": "書籍",
"Book Cover": "書籍封面",
"Bookmark": "書籤",
"Cancel": "取消",
"Chapter": "章節",
@@ -194,7 +193,7 @@
"No supported files found. Supported formats: {{formats}}": "未找到支援的檔案。支援的格式:{{formats}}",
"Drop to Import Books": "拖放導入書籍",
"Custom": "自定義",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n—William Shakespeare": "世界是一個舞台,\n所有的男人和女人都只是演員:\n他們有出口,也有入口;\n一個人在他的時代扮演許多角​​色。 \n\n——威廉·莎士比亞",
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "世界是一個舞台,\n所有的男人和女人都只是演員:\n他們有出口,也有入口;\n一個人在他的時代扮演許多角​​色。 \n\n——威廉·莎士比亞",
"Custom Theme": "自定義主題",
"Theme Name": "主題名稱",
"Text Color": "文字顏色",
@@ -221,9 +220,9 @@
"Auto Import on File Open": "打開文件時自動導入",
"LXGW WenKai GB Screen": "霞鶩文楷 SC",
"LXGW WenKai TC": "霞鶩文楷 TC",
"No chapters detected.": "未檢測到章節",
"Failed to parse the EPUB file.": "無法解析 EPUB 文件",
"This book format is not supported.": "不支援此書籍格式",
"No chapters detected": "未檢測到章節",
"Failed to parse the EPUB file": "無法解析 EPUB 文件",
"This book format is not supported": "不支援此書籍格式",
"Unable to fetch the translation. Please log in first and try again.": "無法獲取翻譯。請先登入然後重試。",
"Group": "分組",
"Always on Top": "視窗置頂",
@@ -260,8 +259,6 @@
"(from 'As You Like It', Act II)": "(出自《皆大歡喜》,第二幕)",
"Link Color": "連結顏色",
"Volume Keys for Page Flip": "音量鍵翻頁",
"Clicks for Page Flip": "點擊翻頁",
"Swap Clicks Area": "交換點擊區域",
"Screen": "螢幕",
"Orientation": "螢幕方向",
"Portrait": "豎屏",
@@ -296,7 +293,6 @@
"Disable Translation": "停用翻譯",
"Scroll": "滾動",
"Overlap Pixels": "重疊像素",
"Click": "點擊",
"System Language": "系統語言",
"Security": "安全",
"Allow JavaScript": "允許 JavaScript",
@@ -362,7 +358,6 @@
"Email:": "電子郵件:",
"Plan:": "方案:",
"Amount:": "金額:",
"Subscription ID:": "訂閱 ID",
"Go to Library": "前往書庫",
"Need help? Contact our support team at support@readest.com": "需要協助?請聯絡支援團隊:support@readest.com",
"Free Plan": "免費方案",
@@ -396,7 +391,7 @@
"Daily translation quota reached. Upgrade your plan to continue using AI translations.": "已達每日翻譯配額。升級方案以繼續使用 AI 翻譯。",
"Includes All Plus Plan Benefits": "包含所有 Plus 方案優惠",
"Early Feature Access": "搶先體驗新功能",
"Be the first to explore new features, updates, and innovations before anyone else.": "搶先體驗新功能、更新創新。",
"Be the first to explore new features, updates, and innovations before anyone else.": "搶先體驗新功能、更新創新成果。",
"Advanced AI Tools": "進階 AI 工具",
"Harness powerful AI tools for smarter reading, translation, and content discovery.": "運用強大的 AI 工具實現智慧閱讀、翻譯和內容探索。",
"Translate up to 100,000 characters daily with the most accurate translation engine available.": "使用最精準的翻譯引擎每天翻譯多達10萬字元。",
@@ -605,5 +600,158 @@
"Contain": "適應",
"{{number}} pages left in chapter": "本章剩餘{{number}}頁",
"Device": "設備",
"E-Ink Mode": "E-Ink 模式"
"E-Ink Mode": "E-Ink 模式",
"Highlight Colors": "高亮顏色",
"Auto Screen Brightness": "自動螢幕亮度",
"Pagination": "翻頁",
"Disable Double Tap": "禁用雙擊",
"Tap to Paginate": "點擊翻頁",
"Click to Paginate": "點擊翻頁",
"Tap Both Sides": "點擊兩側翻頁",
"Click Both Sides": "點擊兩側翻頁",
"Swap Tap Sides": "交換點擊區域",
"Swap Click Sides": "交換點擊區域",
"Source and Translated": "原文和翻譯",
"Translated Only": "僅翻譯",
"Source Only": "僅原文",
"TTS Text": "TTS 文字",
"The book file is corrupted": "書籍文件已損壞",
"The book file is empty": "書籍文件為空",
"Failed to open the book file": "無法打開書籍文件",
"On-Demand Purchase": "按需購買",
"Full Customization": "完全自定義",
"Lifetime Plan": "終身方案",
"One-Time Payment": "一次性付款",
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "進行一次性付款以享受在所有設備上對特定功能的終身訪問。僅在需要時購買特定功能或服務。",
"Expand Cloud Sync Storage": "擴展雲同步存儲",
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "通過一次性購買,永久擴展您的雲存儲。每次額外購買都會增加更多空間。",
"Unlock All Customization Options": "解鎖所有自定義選項",
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "解鎖其他主題、字體、佈局選項以及朗讀、翻譯、雲存儲服務。",
"Purchase Successful!": "購買成功!",
"lifetime": "永久",
"Thank you for your purchase! Your payment has been processed successfully.": "感謝您的購買!您的付款已成功處理。",
"Order ID:": "訂單編號:",
"TTS Highlighting": "TTS 高亮",
"Style": "樣式",
"Underline": "底線",
"Strikethrough": "刪除線",
"Squiggly": "波浪線",
"Outline": "輪廓",
"Save Current Color": "儲存目前顏色",
"Quick Colors": "快速顏色",
"Highlighter": "螢光筆",
"Save Book Cover": "保存書籍封面",
"Auto-save last book cover": "自動保存最後一本書的封面",
"Back": "返回",
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "確認郵件已發送!請檢查您的舊電子郵件和新電子郵件以確認更改。",
"Failed to update email": "更新電子郵件失敗",
"New Email": "新電子郵件",
"Your new email": "您的新電子郵件",
"Updating email ...": "正在更新電子郵件 ...",
"Update email": "更新電子郵件",
"Current email": "當前電子郵件",
"Update Email": "更新電子郵件",
"All": "全部",
"Unable to open book": "無法打開書籍",
"Punctuation": "標點符號",
"Replace Quotation Marks": "替換引號",
"Enabled only in vertical layout.": "僅在直排佈局中啟用",
"No Conversion": "不轉換",
"Simplified to Traditional": "簡體 → 繁體",
"Traditional to Simplified": "繁體 → 簡體",
"Simplified to Traditional (Taiwan)": "簡體 → 繁體(台灣)",
"Simplified to Traditional (Hong Kong)": "簡體 → 繁體(香港)",
"Simplified to Traditional (Taiwan), with phrases": "簡體 → 繁體(台灣)慣用語",
"Traditional (Taiwan) to Simplified": "繁體(台灣) → 簡體",
"Traditional (Hong Kong) to Simplified": "繁體(香港) → 簡體",
"Traditional (Taiwan) to Simplified, with phrases": "繁體(台灣) → 簡體慣用語",
"Convert Simplified and Traditional Chinese": "簡繁中文轉換",
"Convert Mode": "轉換模式",
"Failed to auto-save book cover for lock screen: {{error}}": "自動保存鎖定螢幕書籍封面失敗:{{error}}",
"Download from Cloud": "從雲端下載",
"Upload to Cloud": "上傳到雲端",
"Clear Custom Fonts": "清除自訂字型",
"Columns": "欄數",
"OPDS Catalogs": "OPDS 目錄",
"Adding LAN addresses is not supported in the web app version.": "網頁版不支援新增區域網路位址。",
"Invalid OPDS catalog. Please check the URL.": "無效的 OPDS 目錄。請檢查 URL。",
"Browse and download books from online catalogs": "瀏覽並下載線上目錄的書籍",
"My Catalogs": "我的目錄",
"Add Catalog": "新增目錄",
"No catalogs yet": "尚無目錄",
"Add your first OPDS catalog to start browsing books": "新增第一個 OPDS 目錄以開始瀏覽書籍",
"Add Your First Catalog": "新增第一個目錄",
"Browse": "瀏覽",
"Popular Catalogs": "熱門目錄",
"Add": "新增",
"Add OPDS Catalog": "新增 OPDS 目錄",
"Catalog Name": "目錄名稱",
"My Calibre Library": "我的 Calibre 圖書館",
"OPDS URL": "OPDS URL",
"Username (optional)": "使用者名稱(可選)",
"Password (optional)": "密碼(可選)",
"Description (optional)": "描述(可選)",
"A brief description of this catalog": "此目錄的簡短描述",
"Validating...": "驗證中...",
"View All": "檢視全部",
"Forward": "前往",
"OPDS Catalog": "OPDS 目錄",
"Home": "首頁",
"Library": "圖書館",
"{{count}} items_other": "{{count}} 項",
"Download completed": "下載完成",
"Download failed": "下載失敗",
"Open Access": "開放存取",
"Borrow": "借閱",
"Buy": "購買",
"Subscribe": "訂閱",
"Sample": "試閱",
"Download": "下載",
"Open & Read": "開啟並閱讀",
"Tags": "標籤",
"Tag": "標籤",
"First": "第一頁",
"Previous": "上一頁",
"Next": "下一頁",
"Last": "最後一頁",
"Cannot Load Page": "無法載入頁面",
"An error occurred": "發生錯誤",
"Online Library": "線上書庫",
"URL must start with http:// or https://": "URL 必須以 http:// 或 https:// 開頭",
"Title, Author, Tag, etc...": "標題、作者、標籤等...",
"Query": "查詢",
"Subject": "主題",
"Enter {{terms}}": "輸入 {{terms}}",
"No search results found": "未找到搜尋結果",
"Failed to load OPDS feed: {{status}} {{statusText}}": "載入 OPDS 資料源失敗:{{status}} {{statusText}}",
"Search in {{title}}": "在 {{title}} 中搜尋",
"Manage Storage": "管理儲存",
"Failed to load files": "載入檔案失敗",
"Deleted {{count}} file(s)_other": "已刪除 {{count}} 個檔案",
"Failed to delete {{count}} file(s)_other": "刪除 {{count}} 個檔案失敗",
"Failed to delete files": "刪除檔案失敗",
"Total Files": "檔案總數",
"Total Size": "總大小",
"Quota": "配額",
"Used": "已使用",
"Files": "檔案",
"Search files...": "搜尋檔案...",
"Newest First": "最新優先",
"Oldest First": "最舊優先",
"Largest First": "最大優先",
"Smallest First": "最小優先",
"Name A-Z": "名稱 A-Z",
"Name Z-A": "名稱 Z-A",
"{{count}} selected_other": "已選擇 {{count}} 個檔案",
"Delete Selected": "刪除所選",
"Created": "建立時間",
"No files found": "找不到檔案",
"No files uploaded yet": "尚未上傳檔案",
"files": "檔案",
"Page {{current}} of {{total}}": "第 {{current}} 頁,共 {{total}} 頁",
"Are you sure to delete {{count}} selected file(s)?_other": "確定要刪除已選擇的 {{count}} 個檔案嗎?",
"Cloud Storage Usage": "雲端儲存使用情況",
"Rename Group": "重新命名",
"From Directory": "從目錄導入",
"Successfully imported {{count}} book(s)_other": "成功導入 {{count}} 本書"
}
+92
View File
@@ -1,5 +1,97 @@
{
"releases": {
"0.9.95": {
"date": "2025-12-08",
"notes": [
"OPDS: You can now search, browse, and download ebooks directly from OPDS catalogs",
"OPDS: Improved compatibility with Calibre Web for reliable downloads",
"OPDS: Fixed an issue where book covers from self-hosted Calibre OPDS servers did not display correctly",
"Cloud Storage: You can view and delete files stored in your cloud storage directly from the app",
"Bookshelf: Added support for importing books from a folder recursively",
"Bookshelf: You can now rename your bookshelf groups",
"EPUB: Added support for SVG covers from Standard Ebooks",
"Annotations: Keyboard shortcuts no longer copy selected text into the notebook",
"Footnotes: Footnote popups now scale properly on small screens",
"Touch/Styli: Removed the context menu for smoother interaction on touch and stylus devices",
"Layout: Devices with unfoldable screens now automatically switch to a two-column layout",
"PDF: Improved zoomed-in navigation and hand-tool behavior for smoother page navigation",
"Android: Fixed highlighting of the current sentence when using native Android text-to-speech",
"Android: Back button handling now works correctly on Android 15 and above",
"Android: You can now open files shared from other apps"
]
},
"0.9.94": {
"date": "2025-12-02",
"notes": [
"OPDS: You can now browse and download ebooks directly from OPDS catalogs",
"PDF: Added a hand-tool mode that makes navigating PDF pages easier",
"Covers: You can now choose a folder to save the latest book cover image",
"CJK: Added one-tap conversion between Simplified and Traditional Chinese",
"Shortcuts: Use Ctrl + mouse wheel to quickly zoom in or out",
"Library: Added a new option to adjust cover sizes in grid view",
"Windows: Added Ebook thumbnails with book covers in Windows Explorer",
"TTS: Fixed an issue where the page would scroll when showing the annotator",
"Annotations: Resolved layout shifts when selecting text in paginated mode on Android",
"Files: Improved backup handling to prevent file corruption",
"Layout: Fixed text clipping caused by negative indents",
"E-ink: Improved sidebar and notebook background colors for e-ink devices",
"Android: The Back button now works properly in menus, dialogs, and alerts",
"iOS: Fixed occasional failures when opening books on older iOS versions (16.4 and below)"
]
},
"0.9.93": {
"date": "2025-11-20",
"notes": [
"Sync: Improved reliability for new accounts so your books sync correctly from the start",
"Library: Fixed an occasional issue where returning to the library could get stuck",
"Layout: Improved image display in paginated mode so images won't exceed the available height",
"TXT: Improved performance and stability when opening and parsing TXT files",
"Fonts: Fixed an issue where monospace fonts were not applied correctly in code blocks"
]
},
"0.9.92": {
"date": "2025-11-18",
"notes": [
"Bookshelf: Added support for nested groups to organize your books more flexibly",
"Sync: Improved reliability by automatically retrying failed book syncs",
"Sync: Improved token refresh so login sessions stay active longer on KOReader",
"E-Ink: Enhanced readability on the library page for E-Ink devices",
"UI: Storage capacity is now displayed with precise values in the user profile",
"Reader: You can now import files directly into the currently selected book group",
"Account: You can now update the email address linked to your Readest account",
"Footnotes: Added compatibility for more footnote formats"
]
},
"0.9.91": {
"date": "2025-10-29",
"notes": [
"TTS: Fixed an issue where system voices were not working on Android",
"TTS: Added options to customize the highlight style during read-aloud",
"E-Ink: Disabled all shadows to improve display clarity",
"E-Ink: Added an option to save the last book cover on Android",
"Android: Fixed a crash that occurred when toggling 'Allow Script'",
"Android: Adjusted launcher icon size for better compatibility across devices"
]
},
"0.9.90": {
"date": "2025-10-28",
"notes": [
"Sync: Faster library syncing and support for in-app one-time purchases of extra cloud storage",
"TTS: You can now choose the target language for read-aloud on translated books",
"Reader: New keyboard shortcuts make it easier to move between sections",
"Reader: Smoother scrolling for a more comfortable reading experience",
"Reader: Footnotes in definition lists are now fully supported",
"Reader: Improved search accuracy and display of search results",
"Reader: Clearer error messages when importing books",
"Reader: Improved safety when loading book content",
"Settings: You can now choose your own highlight colors",
"Settings: Added option to adjust screen brightness automatically",
"E-Ink: Better readability and clearer progress display with underlined links",
"Android: You can now store app data on an external SD card",
"iOS: Restores your last reading page if the app was closed in the background",
"Localization: Added Persian (Farsi) language support"
]
},
"0.9.88": {
"date": "2025-10-18",
"notes": [
@@ -3,42 +3,10 @@
set -e
VERSION=$(jq -r '.version' package.json)
if [[ -z "$VERSION" ]]; then
echo "❌ Failed to extract version from package.json"
exit 1
fi
echo "📦 Found version: $VERSION"
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" ]]; then
echo "❌ Invalid version format: $VERSION"
exit 1
fi
# Convert x.y.z => x * 10000 + y * 1000 + z
VERSION_CODE=$((10#$MAJOR * 10000 + 10#$MINOR * 1000 + 10#$PATCH))
echo "🔢 Computed versionCode: $VERSION_CODE"
PROPERTIES_FILE="./src-tauri/gen/android/app/tauri.properties"
MANIFEST="./src-tauri/gen/android/app/src/main/AndroidManifest.xml"
INSTALL_PERMISSION_LINE='<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>'
STORAGE_PERMISSION_LINE='<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>'
if [[ ! -f "$PROPERTIES_FILE" ]]; then
echo "❌ File not found: $PROPERTIES_FILE"
exit 1
fi
tmpfile=$(mktemp)
sed "s/^tauri\.android\.versionName=.*/tauri.android.versionName=$VERSION/" "$PROPERTIES_FILE" | \
sed "s/^tauri\.android\.versionCode=.*/tauri.android.versionCode=$VERSION_CODE/" > "$tmpfile"
mv "$tmpfile" "$PROPERTIES_FILE"
echo "✅ Updated $PROPERTIES_FILE"
ised() {
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "$@"
@@ -71,7 +39,7 @@ if grep -q 'MANAGE_EXTERNAL_STORAGE' "$MANIFEST"; then
fi
echo "🚀 Running: pnpm tauri android build"
pnpm tauri android build
pnpm tauri android build --config src-tauri/tauri.playstore.conf.json
# --- ADD PERMISSION BACK AFTER BUILD ---
if ! grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST"; then
@@ -94,4 +62,5 @@ if [[ -z "$GOOGLE_PLAY_JSON_KEY_FILE" ]]; then
exit 1
fi
cd ../../
fastlane android upload_production
+183
View File
@@ -0,0 +1,183 @@
#!/bin/bash
# Sync release notes from JSON to XML AppData file
# Usage: ./sync_release_notes.sh [json_file] [xml_file]
#
# Description:
# Extracts release information from a JSON file and updates the <releases>
# section in an AppData XML file, then calculates the SHA256 checksum.
#
# Arguments:
# json_file: Path to JSON file (default: releases.json)
# xml_file: Path to XML file (default: appdata.xml)
set -e
set -o pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Helper functions
log_info() {
echo -e "${GREEN}${NC} $1"
}
log_warn() {
echo -e "${YELLOW}${NC} $1"
}
log_error() {
echo -e "${RED}${NC} $1" >&2
}
# Check if required commands are available
if ! command -v jq >/dev/null 2>&1; then
log_error "jq is required but not installed."
echo "Install it with: apt-get install jq (Debian/Ubuntu) or brew install jq (macOS)"
exit 1
fi
# Parse arguments
JSON_FILE="${1:-releases.json}"
XML_FILE="${2:-appdata.xml}"
# Validate input files
if [ ! -f "$JSON_FILE" ]; then
log_error "JSON file '$JSON_FILE' not found"
exit 1
fi
if [ ! -f "$XML_FILE" ]; then
log_error "XML file '$XML_FILE' not found"
exit 1
fi
# Validate JSON format
if ! jq empty "$JSON_FILE" 2>/dev/null; then
log_error "Invalid JSON format in '$JSON_FILE'"
exit 1
fi
# Check if releases key exists
if ! jq -e '.releases' "$JSON_FILE" >/dev/null 2>&1; then
log_error "No 'releases' key found in JSON file"
exit 1
fi
echo "================================================"
echo "Release Notes Sync Tool"
echo "================================================"
echo "Source: $JSON_FILE"
echo "Target: $XML_FILE"
echo ""
# Create backup
BACKUP_FILE="${XML_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
cp "$XML_FILE" "$BACKUP_FILE"
log_info "Created backup: $BACKUP_FILE"
# Create temporary files
TEMP_XML=$(mktemp)
TEMP_RELEASES=$(mktemp)
# Cleanup function
cleanup() {
rm -f "$TEMP_XML" "$TEMP_RELEASES"
}
trap cleanup EXIT
# Extract releases from JSON and convert to XML format
log_info "Extracting releases from JSON..."
RELEASE_COUNT=$(jq '.releases | length' "$JSON_FILE")
echo " Found $RELEASE_COUNT releases"
jq -r '
.releases | to_entries | .[0:10] | .[] |
" <release version=\"\(.key)\" date=\"\(.value.date)\">
<description>
<ul>
" +
((.value.notes | map(" <li>\(.)</li>")) | join("\n")) +
"
</ul>
</description>
</release>"
' "$JSON_FILE" > "$TEMP_RELEASES"
# Find the releases section in XML and replace it
log_info "Updating XML file..."
awk -v releases="$TEMP_RELEASES" '
BEGIN { in_releases = 0; printed_releases = 0 }
/<releases>/ {
print $0
if (printed_releases == 0) {
while ((getline line < releases) > 0) {
print line
}
close(releases)
printed_releases = 1
}
in_releases = 1
next
}
/<\/releases>/ {
in_releases = 0
print $0
next
}
in_releases { next }
{ print }
' "$XML_FILE" > "$TEMP_XML"
# Validate the generated XML
if ! grep -q "<releases>" "$TEMP_XML" || ! grep -q "</releases>" "$TEMP_XML"; then
log_error "Failed to generate valid XML structure"
log_warn "Restoring from backup..."
mv "$BACKUP_FILE" "$XML_FILE"
exit 1
fi
# Remove backup if everything is fine
rm "$BACKUP_FILE"
# Replace original file
mv "$TEMP_XML" "$XML_FILE"
log_info "XML file updated successfully"
# Calculate SHA256 checksum
echo ""
log_info "Calculating SHA256 checksum..."
if command -v sha256sum >/dev/null 2>&1; then
CHECKSUM=$(sha256sum "$XML_FILE" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
CHECKSUM=$(shasum -a 256 "$XML_FILE" | awk '{print $1}')
else
log_error "Neither sha256sum nor shasum found"
exit 1
fi
# Save checksum to file
CHECKSUM_FILE="${XML_FILE}.sha256"
echo "$CHECKSUM $XML_FILE" > "$CHECKSUM_FILE"
echo ""
echo "================================================"
echo "Summary"
echo "================================================"
echo " Updated file: $XML_FILE"
echo " Backup file: $BACKUP_FILE"
echo " Checksum file: $CHECKSUM_FILE"
echo " SHA256: $CHECKSUM"
echo " Releases: $RELEASE_COUNT"
echo ""
log_info "Sync completed successfully!"
echo ""
echo "To verify the checksum, run:"
if command -v sha256sum >/dev/null 2>&1; then
echo " sha256sum -c $CHECKSUM_FILE"
else
echo " shasum -a 256 -c $CHECKSUM_FILE"
fi
+62
View File
@@ -1,3 +1,65 @@
use std::{env, fs, path::PathBuf, process::Command};
fn main() {
println!("cargo:rerun-if-changed=../extensions/windows-thumbnail/src");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os == "windows" {
build_windows_thumbnail();
}
tauri_build::build()
}
fn build_windows_thumbnail() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let dll_crate_dir = manifest_dir
.join("..")
.join("extensions")
.join("windows-thumbnail");
let dll_crate_manifest = dll_crate_dir.join("Cargo.toml");
let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".into());
let mut cmd = Command::new(env::var("CARGO").unwrap_or("cargo".into()));
cmd.arg("build")
.arg("--package")
.arg("windows_thumbnail")
.arg("--manifest-path")
.arg(&dll_crate_manifest);
if profile == "release" {
cmd.arg("--release");
}
let target_triple = env::var("TARGET").unwrap_or_default();
let host_triple = env::var("HOST").unwrap_or_default();
if !target_triple.is_empty() && target_triple != host_triple {
cmd.arg("--target").arg(&target_triple);
}
let status = cmd
.status()
.expect("Failed to run cargo build for windows_thumbnail");
if !status.success() {
panic!("Failed to build windows_thumbnail DLL");
}
let dll_name = "windows_thumbnail.dll";
let candidate_paths = [
dll_crate_dir.join("target").join(&profile).join(dll_name),
dll_crate_dir
.join("target")
.join(&target_triple)
.join(&profile)
.join(dll_name),
];
let dll_src = candidate_paths
.iter()
.find(|p| p.exists())
.expect("Failed to find built windows_thumbnail DLL");
let dll_dest = &dll_crate_dir.join("target").join(dll_name);
fs::copy(dll_src, dll_dest).expect("Failed to copy windows_thumbnail DLL");
println!("cargo:rerun-if-changed={}", dll_dest.display());
}
@@ -69,6 +69,9 @@
},
{
"path": "**/Readest/**/*"
},
{
"path": "**/last-book-cover.png"
}
]
},
@@ -104,6 +107,12 @@
},
{
"url": "https://*:*"
},
{
"url": "http://*"
},
{
"url": "https://*"
}
]
},
@@ -134,6 +143,14 @@
"oauth:allow-cancel",
"sign-in-with-apple:default",
"opener:default",
{
"identifier": "opener:allow-open-url",
"allow": [
{
"url": "alipays:*"
}
]
},
"haptics:allow-vibrate",
"haptics:allow-impact-feedback",
"haptics:allow-notification-feedback",
@@ -57,9 +57,15 @@ android {
}
}
getByName("release") {
isMinifyEnabled = true
if (keystorePropertiesFile.exists()) {
signingConfig = signingConfigs.getByName("signing")
}
proguardFiles(
*fileTree(".") { include("**/*.pro") }
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
.toList().toTypedArray()
)
}
}
kotlinOptions {
@@ -75,13 +81,13 @@ rust {
}
dependencies {
implementation("androidx.webkit:webkit:1.6.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.activity:activity-ktx:1.9.3")
implementation("com.google.android.material:material:1.8.0")
implementation("androidx.webkit:webkit:1.14.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.activity:activity-ktx:1.10.1")
implementation("com.google.android.material:material:1.12.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
}
apply(from = "tauri.build.gradle.kts")
apply(from = "tauri.build.gradle.kts")
@@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
@@ -16,6 +17,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:largeHeap="true"
android:enableOnBackInvokedCallback="false"
android:label="@string/app_name"
android:theme="@style/Theme.readest"
android:hardwareAccelerated="true"
@@ -2,17 +2,25 @@ package com.bilingify.readest
import android.os.Build
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.view.KeyEvent
import android.webkit.WebView
import android.net.Uri
import android.util.Log
import android.content.Intent
import android.graphics.Color
import android.app.ActivityManager
import android.content.res.Configuration
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.activity.enableEdgeToEdge
import androidx.activity.OnBackPressedCallback
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import app.tauri.plugin.JSArray
import app.tauri.plugin.JSObject
import com.readest.native_bridge.KeyDownInterceptor
import com.readest.native_bridge.NativeBridgePlugin
class MainActivity : TauriActivity(), KeyDownInterceptor {
private lateinit var wv: WebView
@@ -39,6 +47,32 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
interceptBackKeyEnabled = enabled
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
val keyCode = event.keyCode
val keyName = keyEventMap[keyCode]
if (keyName != null) {
val shouldIntercept = when (keyCode) {
KeyEvent.KEYCODE_BACK -> interceptBackKeyEnabled
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> interceptVolumeKeysEnabled
else -> false
}
if (shouldIntercept) {
wv.evaluateJavascript(
"""
try { window.onNativeKeyDown("$keyName", $keyCode); } catch (_) {}
""".trimIndent(),
null
)
return true
}
}
}
return super.dispatchKeyEvent(event)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
val keyName = keyEventMap[keyCode]
if (keyName != null) {
@@ -82,6 +116,8 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
handleIncomingIntent(intent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setTaskDescription(
ActivityManager.TaskDescription(
@@ -91,5 +127,90 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
)
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
OnBackInvokedCallback {
Log.d("MainActivity", "Back invoked callback triggered ${interceptBackKeyEnabled}")
if (interceptBackKeyEnabled) {
Log.d("MainActivity", "Back intercepted (OnBackInvokedCallback)")
wv.evaluateJavascript(
"""window.onNativeKeyDown("Back", ${KeyEvent.KEYCODE_BACK});""",
null
)
} else {
finish()
}
}
)
}
onBackPressedDispatcher.addCallback(this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (interceptBackKeyEnabled) {
Log.d("MainActivity", "Back intercepted (OnBackPressedDispatcher)")
wv.evaluateJavascript(
"""window.onNativeKeyDown("Back", ${KeyEvent.KEYCODE_BACK});""",
null
)
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
}
}
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
NativeBridgePlugin.getInstance()?.handleActivityResult(requestCode, resultCode, data)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent?.let { handleIncomingIntent(it) }
}
private fun handleIncomingIntent(intent: Intent) {
when (intent.action) {
Intent.ACTION_SEND -> {
if (intent.type != null) {
handleSingleFile(intent)
}
}
Intent.ACTION_SEND_MULTIPLE -> {
if (intent.type != null) {
handleMultipleFiles(intent)
}
}
}
}
private fun handleSingleFile(intent: Intent) {
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
uri?.let { fileUri ->
val payload = JSObject().apply {
var urls = JSArray()
urls.put(fileUri.toString())
put("urls", urls)
}
NativeBridgePlugin.getInstance()?.triggerEvent("shared-intent", payload)
}
}
private fun handleMultipleFiles(intent: Intent) {
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
uris?.let { fileUris ->
val payload = JSObject().apply {
var urls = JSArray()
fileUris.forEach { urls.put(it.toString()) }
put("urls", urls)
}
NativeBridgePlugin.getInstance()?.triggerEvent("shared-intent", payload)
}
}
}
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@mipmap/ic_launcher_foreground"
android:inset="22%"/>
</foreground>
</adaptive-icon>
@@ -0,0 +1,85 @@
; Readest NSIS Installer Hooks
; Registers/unregisters the thumbnail provider DLL for Windows Explorer thumbnails
!include "LogicLib.nsh"
; CLSID for Readest Thumbnail Provider
!define CLSID_READEST_THUMBNAIL "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
; IThumbnailProvider Shell Extension Handler GUID
!define SHELL_THUMBNAIL_HANDLER "{e357fccd-a995-4576-b01f-234630154e96}"
;------------------------------------------------------------------------------
; NSIS_HOOK_POSTINSTALL - Called after files are installed
;------------------------------------------------------------------------------
!macro NSIS_HOOK_POSTINSTALL
DetailPrint "Registering Readest Thumbnail Provider..."
; Always do manual registration for reliability
; regsvr32 may fail silently if DLL can't find dependencies
; Register CLSID with full DLL path
WriteRegStr HKCR "CLSID\${CLSID_READEST_THUMBNAIL}" "" "Readest Thumbnail Provider"
WriteRegStr HKCR "CLSID\${CLSID_READEST_THUMBNAIL}\InprocServer32" "" "$INSTDIR\readest_thumbnail.dll"
WriteRegStr HKCR "CLSID\${CLSID_READEST_THUMBNAIL}\InprocServer32" "ThreadingModel" "Apartment"
; CRITICAL: Disable process isolation - required because we use IInitializeWithItem, not IInitializeWithStream
; Without this, Windows runs the handler in an isolated process that can't load the DLL properly
WriteRegDWORD HKCR "CLSID\${CLSID_READEST_THUMBNAIL}" "DisableProcessIsolation" 1
; ========== EPUB ==========
; Register thumbnail handler directly on extension (this is what Windows Shell uses)
WriteRegStr HKCR ".epub\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
; ========== MOBI ==========
WriteRegStr HKCR ".mobi\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
; ========== AZW ==========
WriteRegStr HKCR ".azw\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
; ========== AZW3 ==========
WriteRegStr HKCR ".azw3\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
; ========== KF8 ==========
WriteRegStr HKCR ".kf8\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
; ========== FB2 ==========
WriteRegStr HKCR ".fb2\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
; ========== CBZ ==========
WriteRegStr HKCR ".cbz\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
; ========== CBR ==========
WriteRegStr HKCR ".cbr\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
DetailPrint "Thumbnail provider registered successfully."
; Refresh shell to apply changes - SHCNE_ASSOCCHANGED
System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0, p 0, p 0)'
!macroend
;------------------------------------------------------------------------------
; NSIS_HOOK_PREUNINSTALL - Called before files are removed
;------------------------------------------------------------------------------
!macro NSIS_HOOK_PREUNINSTALL
DetailPrint "Unregistering Readest Thumbnail Provider..."
; Remove CLSID
DeleteRegKey HKCR "CLSID\${CLSID_READEST_THUMBNAIL}"
; Remove ShellEx from extensions
DeleteRegKey HKCR ".epub\ShellEx\${SHELL_THUMBNAIL_HANDLER}"
DeleteRegKey HKCR ".mobi\ShellEx\${SHELL_THUMBNAIL_HANDLER}"
DeleteRegKey HKCR ".azw\ShellEx\${SHELL_THUMBNAIL_HANDLER}"
DeleteRegKey HKCR ".azw3\ShellEx\${SHELL_THUMBNAIL_HANDLER}"
DeleteRegKey HKCR ".kf8\ShellEx\${SHELL_THUMBNAIL_HANDLER}"
DeleteRegKey HKCR ".fb2\ShellEx\${SHELL_THUMBNAIL_HANDLER}"
DeleteRegKey HKCR ".cbz\ShellEx\${SHELL_THUMBNAIL_HANDLER}"
DeleteRegKey HKCR ".cbr\ShellEx\${SHELL_THUMBNAIL_HANDLER}"
; Delete the DLL file
Delete "$INSTDIR\readest_thumbnail.dll"
; Refresh shell
System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0, p 0, p 0)'
!macroend
@@ -33,6 +33,7 @@ android {
}
dependencies {
implementation("com.android.billingclient:billing-ktx:7.1.1")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.browser:browser:1.8.0")
@@ -0,0 +1,283 @@
package com.readest.native_bridge
import android.app.Activity
import android.content.Context
import android.util.Log
import com.android.billingclient.api.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.*
class BillingManager(private val activity: Activity) : PurchasesUpdatedListener {
private lateinit var billingClient: BillingClient
private val productsCache = mutableMapOf<String, ProductDetails>()
private var purchaseCallback: ((PurchaseData?) -> Unit)? = null
private val scope = CoroutineScope(Dispatchers.Main)
companion object {
private const val TAG = "BillingManager"
}
fun initialize(callback: (Boolean) -> Unit) {
billingClient = BillingClient.newBuilder(activity)
.setListener(this)
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Billing client setup finished successfully")
callback(true)
} else {
Log.e(TAG, "Billing setup failed: ${billingResult.debugMessage}")
callback(false)
}
}
override fun onBillingServiceDisconnected() {
Log.w(TAG, "Billing service disconnected")
// Try to reconnect
initialize { }
}
})
}
fun fetchProducts(productIds: List<String>, callback: (List<ProductData>) -> Unit) {
if (!::billingClient.isInitialized || !billingClient.isReady) {
Log.e(TAG, "Billing client not ready")
callback(emptyList())
return
}
scope.launch {
val products = mutableListOf<ProductData>()
// Check for subscription products
val subsIds = productIds.filter {
it.contains("monthly") || it.contains("yearly") || it.contains("subscription")
}
if (subsIds.isNotEmpty()) {
fetchProductsOfType(subsIds, BillingClient.ProductType.SUBS) { subProducts ->
products.addAll(subProducts)
// Then fetch in-app products
val inAppIds = productIds - subsIds.toSet()
if (inAppIds.isNotEmpty()) {
fetchProductsOfType(inAppIds, BillingClient.ProductType.INAPP) { inAppProducts ->
products.addAll(inAppProducts)
callback(products)
}
} else {
callback(products)
}
}
} else {
// Only in-app products
fetchProductsOfType(productIds, BillingClient.ProductType.INAPP) { inAppProducts ->
products.addAll(inAppProducts)
callback(products)
}
}
}
}
private fun fetchProductsOfType(
productIds: List<String>,
productType: String,
callback: (List<ProductData>) -> Unit
) {
val productList = productIds.map { productId ->
QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(productType)
.build()
}
val params = QueryProductDetailsParams.newBuilder()
.setProductList(productList)
.build()
billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val products = productDetailsList.map { productDetails ->
// Cache for purchase later
productsCache[productDetails.productId] = productDetails
when (productType) {
BillingClient.ProductType.SUBS -> {
val offer = productDetails.subscriptionOfferDetails?.firstOrNull()
val pricingPhase = offer?.pricingPhases?.pricingPhaseList?.firstOrNull()
pricingPhase?.let {
ProductData(
id = productDetails.productId,
title = productDetails.title,
description = productDetails.description,
price = it.formattedPrice,
priceCurrencyCode = it.priceCurrencyCode,
priceAmountMicros = it.priceAmountMicros,
productType = "subscription"
)
}
}
BillingClient.ProductType.INAPP -> {
val oneTimeOffer = productDetails.oneTimePurchaseOfferDetails
oneTimeOffer?.let {
ProductData(
id = productDetails.productId,
title = productDetails.title,
description = productDetails.description,
price = it.formattedPrice,
priceCurrencyCode = it.priceCurrencyCode,
priceAmountMicros = it.priceAmountMicros,
productType = "consumable"
)
}
}
else -> null
}
}.filterNotNull()
callback(products)
} else {
Log.e(TAG, "Failed to fetch products: ${billingResult.debugMessage}")
callback(emptyList())
}
}
}
fun purchaseProduct(productId: String, callback: (PurchaseData?) -> Unit) {
val productDetails = productsCache[productId]
if (productDetails == null) {
Log.e(TAG, "Product not found in cache: $productId")
callback(null)
return
}
purchaseCallback = callback
val productDetailsParamsList = listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.apply {
// For subscriptions, use the first offer
productDetails.subscriptionOfferDetails?.firstOrNull()?.let { offer ->
setOfferToken(offer.offerToken)
}
}
.build()
)
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build()
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
Log.e(TAG, "Failed to launch billing flow: ${billingResult.debugMessage}")
callback(null)
purchaseCallback = null
}
}
fun restorePurchases(callback: (List<PurchaseData>) -> Unit) {
if (!::billingClient.isInitialized || !billingClient.isReady) {
Log.e(TAG, "Billing client not ready")
callback(emptyList())
return
}
scope.launch {
val allPurchases = mutableListOf<PurchaseData>()
// Query in-app purchases
val inappParams = QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build()
billingClient.queryPurchasesAsync(inappParams) { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
allPurchases.addAll(purchases.map { purchase ->
convertToPurchaseData(purchase, "restored")
})
}
// Query subscription purchases
val subsParams = QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.SUBS)
.build()
billingClient.queryPurchasesAsync(subsParams) { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
allPurchases.addAll(purchases.map { purchase ->
convertToPurchaseData(purchase, "restored")
})
}
callback(allPurchases)
}
}
}
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
purchases?.forEach { purchase ->
handlePurchase(purchase)
}
}
BillingClient.BillingResponseCode.USER_CANCELED -> {
Log.d(TAG, "Purchase cancelled by user")
purchaseCallback?.invoke(null)
purchaseCallback = null
}
else -> {
Log.e(TAG, "Purchase failed: ${billingResult.debugMessage}")
purchaseCallback?.invoke(null)
purchaseCallback = null
}
}
}
private fun handlePurchase(purchase: Purchase) {
// Acknowledge the purchase
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Purchase acknowledged")
}
}
}
val purchaseData = convertToPurchaseData(purchase, "purchased")
purchaseCallback?.invoke(purchaseData)
purchaseCallback = null
}
}
private fun convertToPurchaseData(purchase: Purchase, state: String): PurchaseData {
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
return PurchaseData(
platform = "android",
productId = purchase.products.firstOrNull() ?: "",
orderId = purchase.orderId ?: purchase.purchaseToken,
purchaseToken = purchase.purchaseToken,
purchaseDate = dateFormat.format(Date(purchase.purchaseTime)),
purchaseState = state,
)
}
}
@@ -8,6 +8,7 @@ import android.util.Log
import android.os.Build
import android.os.Environment
import android.provider.Settings
import android.provider.DocumentsContract
import android.view.View
import android.view.KeyEvent
import android.view.WindowInsets
@@ -25,12 +26,15 @@ import androidx.core.content.FileProvider
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.browser.customtabs.CustomTabsIntent
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.Permission
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.JSObject
import app.tauri.plugin.JSArray
import app.tauri.plugin.Plugin
import app.tauri.plugin.Invoke
import org.json.JSONArray
@@ -38,42 +42,76 @@ import java.io.*
@InvokeArg
class AuthRequestArgs {
var authUrl: String? = null
var authUrl: String? = null
}
@InvokeArg
class CopyURIRequestArgs {
var uri: String? = null
var dst: String? = null
var uri: String? = null
var dst: String? = null
}
@InvokeArg
class InstallPackageRequestArgs {
var path: String? = null
var path: String? = null
}
@InvokeArg
class SetSystemUIVisibilityRequestArgs {
var visible: Boolean? = false
var darkMode: Boolean? = false
var visible: Boolean? = false
var darkMode: Boolean? = false
}
@InvokeArg
class InterceptKeysRequestArgs {
var volumeKeys: Boolean? = null
var backKey: Boolean? = null
var volumeKeys: Boolean? = null
var backKey: Boolean? = null
}
@InvokeArg
class LockScreenOrientationRequestArgs {
var orientation: String? = null
var orientation: String? = null
}
@InvokeArg
class SetScreenBrightnessRequestArgs {
var brightness: Double? = null // 0.0 to 1.0
var brightness: Double? = null // 0.0 to 1.0
}
@InvokeArg
class OpenExternalUrlArgs {
var url: String? = null
}
@InvokeArg
class FetchProductsRequestArgs {
val productIds: List<String>? = null
}
@InvokeArg
class PurchaseProductRequestArgs {
val productId: String? = null
}
data class ProductData(
val id: String,
val title: String,
val description: String,
val price: String,
val priceCurrencyCode: String?,
val priceAmountMicros: Long,
val productType: String
)
data class PurchaseData(
val productId: String,
val orderId: String,
val purchaseToken: String,
val purchaseDate: String,
val purchaseState: String,
val platform: String = "android"
)
interface KeyDownInterceptor {
fun interceptVolumeKeys(enabled: Boolean)
fun interceptBackKey(enabled: Boolean)
@@ -88,13 +126,21 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
private val implementation = NativeBridge()
private var redirectScheme = "readest"
private var redirectHost = "auth-callback"
private val billingManager by lazy {
BillingManager(activity)
}
companion object {
var pendingInvoke: Invoke? = null
private const val REQUEST_MANAGE_STORAGE = 1001
private const val FOLDER_PICKER_REQUEST_CODE = 1002
var pendingInvoke: Invoke? = null
var pendingFolderPickerInvoke: Invoke? = null
private var instance: NativeBridgePlugin? = null
fun getInstance(): NativeBridgePlugin? = instance
}
override fun load(webView: WebView) {
instance = this
super.load(webView)
handleIntent(activity.intent)
}
@@ -424,12 +470,19 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
fun get_screen_brightness(invoke: Invoke) {
val ret = JSObject()
try {
val brightness = Settings.System.getInt(
activity.contentResolver,
Settings.System.SCREEN_BRIGHTNESS
)
val normalizedBrightness = brightness / 255.0
ret.put("brightness", normalizedBrightness)
val window = activity.window
val layoutParams = window.attributes
val brightness = layoutParams.screenBrightness
if (brightness >= 0.0f) {
ret.put("brightness", brightness.toDouble())
} else {
val systemBrightness = Settings.System.getInt(
activity.contentResolver,
Settings.System.SCREEN_BRIGHTNESS
)
ret.put("brightness", systemBrightness / 255.0)
}
} catch (e: Exception) {
ret.put("error", e.message)
ret.put("brightness", -1.0)
@@ -458,6 +511,120 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
invoke.resolve(ret)
}
@Command
fun iap_initialize(invoke: Invoke) {
billingManager.initialize { success ->
val result = JSObject()
result.put("success", success)
invoke.resolve(result)
}
}
@Command
fun iap_fetch_products(invoke: Invoke) {
try {
val args = invoke.parseArgs(FetchProductsRequestArgs::class.java)
val productIds = args.productIds ?: emptyList()
if (productIds.isEmpty()) {
invoke.reject("Product IDs list is empty")
return
}
billingManager.fetchProducts(productIds) { products ->
val result = JSObject()
val productsArray = JSArray()
for (product in products) {
val productObject = JSObject().apply {
put("id", product.id)
put("title", product.title)
put("description", product.description)
put("price", product.price)
put("priceCurrencyCode", product.priceCurrencyCode)
put("priceAmountMicros", product.priceAmountMicros)
put("productType", product.productType)
}
productsArray.put(productObject)
}
result.put("products", productsArray)
invoke.resolve(result)
}
} catch (e: Exception) {
invoke.reject("Failed to parse fetch products arguments: ${e.message}")
}
}
@Command
fun iap_purchase_product(invoke: Invoke) {
try {
val args = invoke.parseArgs(PurchaseProductRequestArgs::class.java)
val productId = args.productId ?: ""
if (productId.isEmpty()) {
invoke.reject("Product ID is empty")
return
}
billingManager.purchaseProduct(productId) { purchase ->
if (purchase != null) {
val result = JSObject()
val purchaseData = JSObject().apply {
put("platform", purchase.platform)
put("packageName", activity.packageName)
put("productId", purchase.productId)
put("orderId", purchase.orderId)
put("purchaseToken", purchase.purchaseToken)
put("purchaseDate", purchase.purchaseDate)
put("purchaseState", purchase.purchaseState)
}
result.put("purchase", purchaseData)
invoke.resolve(result)
} else {
invoke.reject("Purchase failed or was cancelled")
}
}
} catch (e: Exception) {
invoke.reject("Failed to parse purchase arguments: ${e.message}")
}
}
@Command
fun iap_restore_purchases(invoke: Invoke) {
billingManager.restorePurchases { purchases ->
val result = JSObject()
val purchasesArray = JSArray()
for (purchase in purchases) {
val purchaseObject = JSObject().apply {
put("platform", purchase.platform)
put("packageName", activity.packageName)
put("productId", purchase.productId)
put("orderId", purchase.orderId)
put("purchaseToken", purchase.purchaseToken)
put("purchaseDate", purchase.purchaseDate)
put("purchaseState", purchase.purchaseState)
}
purchasesArray.put(purchaseObject)
}
result.put("purchases", purchasesArray)
invoke.resolve(result)
}
}
@Command
fun get_external_sdcard_path(invoke: Invoke) {
val result = JSObject()
val externalDirs = activity.getExternalFilesDirs(null)
for (file in externalDirs) {
if (file != null && Environment.isExternalStorageRemovable(file)) {
val pathParts = file.absolutePath.split("/Android/")
if (pathParts.isNotEmpty()) {
result.put("path", pathParts[0])
invoke.resolve(result)
}
}
}
result.put("path", null)
invoke.resolve(result)
}
@Command
fun request_manage_storage_permission(invoke: Invoke) {
val ret = JSObject()
@@ -506,4 +673,110 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
}
}
}
@Command
fun open_external_url(invoke: Invoke) {
val args = invoke.parseArgs(OpenExternalUrlArgs::class.java)
val url = args.url ?: ""
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
activity.startActivity(intent)
val ret = JSObject()
ret.put("success", true)
invoke.resolve(ret)
} catch (e: Exception) {
invoke.reject("Failed to open URL: ${e.message}")
}
}
@Command
fun select_directory(invoke: Invoke) {
pendingFolderPickerInvoke = invoke
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
activity.startActivityForResult(intent, FOLDER_PICKER_REQUEST_CODE)
} catch (e: Exception) {
val result = JSObject()
result.put("cancelled", true)
result.put("uri", null)
result.put("path", null)
result.put("error", e.message)
invoke.resolve(result)
pendingFolderPickerInvoke = null
}
}
fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == FOLDER_PICKER_REQUEST_CODE) {
val invoke = pendingFolderPickerInvoke
if (invoke != null) {
handleDirectorySelected(data?.data, invoke)
pendingFolderPickerInvoke = null
}
}
}
private fun handleDirectorySelected(uri: Uri?, invoke: Invoke) {
val result = JSObject()
if (uri == null) {
result.put("cancelled", true)
result.put("uri", null)
result.put("path", null)
} else {
try {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
activity.contentResolver.takePersistableUriPermission(uri, flags)
result.put("cancelled", false)
result.put("uri", uri.toString())
result.put("path", extractPathFromUri(uri))
} catch (e: SecurityException) {
result.put("cancelled", true)
result.put("uri", uri.toString())
result.put("path", extractPathFromUri(uri))
result.put("error", "Permission error: ${e.message}")
} catch (e: Exception) {
result.put("cancelled", true)
result.put("uri", null)
result.put("path", null)
result.put("error", "Error: ${e.message}")
}
}
invoke.resolve(result)
pendingInvoke = null
}
private fun extractPathFromUri(uri: Uri): String? {
val path = uri.path ?: return null
return try {
when {
DocumentsContract.isTreeUri(uri) -> {
val treeDocId = DocumentsContract.getTreeDocumentId(uri)
val split = treeDocId.split(":")
if (split[0].equals("primary", ignoreCase = true)) {
if (split.size > 1) {
Environment.getExternalStorageDirectory().path + "/" + split[1]
} else {
Environment.getExternalStorageDirectory().path
}
} else {
"/storage/${split[0]}/" + (if (split.size > 1) split[1] else "")
}
}
else -> null
}
} catch (e: Exception) {
path
}
}
fun triggerEvent(eventName: String, payload: JSObject) {
activity.runOnUiThread {
trigger(eventName, payload)
}
}
}
@@ -17,7 +17,14 @@ const COMMANDS: &[&str] = &[
"get_safe_area_insets",
"get_screen_brightness",
"set_screen_brightness",
"get_external_sdcard_path",
"open_external_url",
"select_directory",
"register_listener",
"remove_listener",
"request_manage_storage_permission",
"check_permissions",
"request_permissions",
"checkPermissions",
"requestPermissions",
];
@@ -187,16 +187,246 @@ class VolumeKeyHandler: NSObject {
}
}
class WebViewLifecycleManager: NSObject {
private weak var webView: WKWebView?
private var originalNavigationDelegate: WKNavigationDelegate?
private var isMonitoring = false
private var lastBackgroundTime: Date?
private var backgroundTimeThreshold: TimeInterval = 180.0
func startMonitoring(webView: WKWebView) {
self.webView = webView
originalNavigationDelegate = webView.navigationDelegate
webView.navigationDelegate = self
isMonitoring = true
logger.log("WebViewLifecycleManager: Started monitoring WebView")
}
func stopMonitoring() {
isMonitoring = false
if let original = originalNavigationDelegate {
webView?.navigationDelegate = original
}
logger.log("WebViewLifecycleManager: Stopped monitoring WebView")
}
func handleAppWillEnterForeground() {
guard isMonitoring, let webView = webView else {
logger.warning(
"WebViewLifecycleManager: Cannot handle foreground - not monitoring or webView is nil")
return
}
logger.log("WebViewLifecycleManager: App entering foreground")
var timeInBackground: TimeInterval = 0
if let backgroundTime = lastBackgroundTime {
timeInBackground = Date().timeIntervalSince(backgroundTime)
logger.log("WebViewLifecycleManager: Time in background: \(timeInBackground)s")
}
// If app was backgrounded for more than threshold, check WebView health
if timeInBackground > backgroundTimeThreshold {
logger.log(
"WebViewLifecycleManager: App was backgrounded for \(timeInBackground)s, checking WebView health..."
)
checkAndRecoverWebView(webView, reason: "long_background")
} else {
// Still do a quick check after a small delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
self?.quickHealthCheck(webView)
}
}
lastBackgroundTime = nil
}
func handleAppWillResignActive() {
logger.log("WebViewLifecycleManager: App will resign active")
guard let webView = webView else { return }
webView.evaluateJavaScript("window.location.href") { result, error in
if let error = error {
logger.error("WebViewLifecycleManager: Failed to capture URL on background: \(error)")
return
}
if let urlString = result as? String {
if urlString.hasPrefix("http") || urlString.hasPrefix("tauri") {
UserDefaults.standard.set(urlString, forKey: "tauri_last_valid_url")
logger.log("WebViewLifecycleManager: Saved valid URL")
}
}
}
}
func handleAppDidEnterBackground() {
lastBackgroundTime = Date()
}
private func quickHealthCheck(_ webView: WKWebView) {
logger.log("WebViewLifecycleManager: Performing quick health check")
webView.evaluateJavaScript("window.location.href") { [weak self] result, error in
if let error = error {
logger.error("WebViewLifecycleManager: Quick health check failed: \(error)")
self?.checkAndRecoverWebView(webView, reason: "health_check_failed")
} else if let urlString = result as? String {
if urlString.contains("about:blank") || urlString.isEmpty {
logger.warning("WebViewLifecycleManager: WebView showing about:blank!")
self?.recoverWebView(webView, reason: "about_blank")
}
}
}
}
private func checkAndRecoverWebView(_ webView: WKWebView, reason: String) {
logger.log("WebViewLifecycleManager: Checking WebView health (reason: \(reason))")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
webView.evaluateJavaScript("window.location.href") { result, error in
if let error = error {
logger.error("WebViewLifecycleManager: Error checking WebView URL: \(error)")
self?.recoverWebView(webView, reason: "js_error_\(reason)")
} else if let urlString = result as? String {
logger.log("WebViewLifecycleManager: Current URL after \(reason): \(urlString)")
if urlString.contains("about:blank") || urlString.isEmpty {
logger.warning("WebViewLifecycleManager: Detected blank WebView after \(reason)")
self?.recoverWebView(webView, reason: reason)
} else {
logger.log("WebViewLifecycleManager: WebView appears healthy")
}
}
}
}
}
private func recoverWebView(_ webView: WKWebView, reason: String) {
logger.log("WebViewLifecycleManager: Recovering WebView (reason: \(reason))")
if let lastURL = UserDefaults.standard.string(forKey: "tauri_last_valid_url"),
let url = URL(string: lastURL)
{
logger.log("WebViewLifecycleManager: Reloading from saved URL: \(lastURL)")
webView.load(URLRequest(url: url))
} else {
logger.log("WebViewLifecycleManager: No saved URL, performing standard reload")
webView.reload()
}
}
}
extension WebViewLifecycleManager: WKNavigationDelegate {
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
logger.error("WebViewLifecycleManager: WebContent process TERMINATED!")
recoverWebView(webView, reason: "process_terminated")
if let original = originalNavigationDelegate,
original.responds(to: #selector(webViewWebContentProcessDidTerminate(_:)))
{
original.webViewWebContentProcessDidTerminate?(webView)
}
}
// Save successful navigation URLs
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if let url = webView.url {
let urlString = url.absoluteString
if urlString.hasPrefix("http") || urlString.hasPrefix("tauri") {
UserDefaults.standard.set(urlString, forKey: "tauri_last_valid_url")
logger.log("WebViewLifecycleManager: Saved valid URL")
}
}
if let original = originalNavigationDelegate,
original.responds(to: #selector(webView(_:didFinish:)))
{
original.webView?(webView, didFinish: navigation)
}
}
// Proxy other important navigation delegate methods
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
logger.error("WebViewLifecycleManager: Navigation failed: \(error)")
if let original = originalNavigationDelegate,
original.responds(to: #selector(webView(_:didFail:withError:)))
{
original.webView?(webView, didFail: navigation, withError: error)
}
}
func webView(
_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!,
withError error: Error
) {
logger.error("WebViewLifecycleManager: Provisional navigation failed: \(error)")
if let original = originalNavigationDelegate,
original.responds(to: #selector(webView(_:didFailProvisionalNavigation:withError:)))
{
original.webView?(webView, didFailProvisionalNavigation: navigation, withError: error)
}
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
if let original = originalNavigationDelegate,
original.responds(to: #selector(webView(_:didStartProvisionalNavigation:)))
{
original.webView?(webView, didStartProvisionalNavigation: navigation)
}
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
if let original = originalNavigationDelegate,
original.responds(to: #selector(webView(_:didCommit:)))
{
original.webView?(webView, didCommit: navigation)
}
}
func webView(
_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
if let original = originalNavigationDelegate {
original.webView?(
webView, decidePolicyFor: navigationAction, decisionHandler: decisionHandler)
} else {
decisionHandler(.allow)
}
}
func webView(
_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void
) {
if let original = originalNavigationDelegate {
original.webView?(
webView, decidePolicyFor: navigationResponse, decisionHandler: decisionHandler)
} else {
decisionHandler(.allow)
}
}
}
class NativeBridgePlugin: Plugin {
private var webView: WKWebView?
private var authSession: ASWebAuthenticationSession?
private var currentOrientationMask: UIInterfaceOrientationMask = .all
private var originalDelegate: UIApplicationDelegate?
private var webViewLifecycleManager: WebViewLifecycleManager?
@objc public override func load(webview: WKWebView) {
self.webView = webview
logger.log("NativeBridgePlugin loaded")
webViewLifecycleManager = WebViewLifecycleManager()
webViewLifecycleManager?.startMonitoring(webView: webview)
logger.log("NativeBridgePlugin: WebView lifecycle monitoring activated")
NotificationCenter.default.addObserver(
self,
selector: #selector(appDidBecomeActive),
@@ -211,6 +441,13 @@ class NativeBridgePlugin: Plugin {
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(appWillEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
if let app = UIApplication.value(forKey: "sharedApplication") as? UIApplication {
self.originalDelegate = app.delegate
app.delegate = self
@@ -219,6 +456,11 @@ class NativeBridgePlugin: Plugin {
}
}
@objc func appWillEnterForeground() {
logger.log("NativeBridgePlugin: App will enter foreground")
webViewLifecycleManager?.handleAppWillEnterForeground()
}
@objc func appDidBecomeActive() {
if volumeKeyHandler != nil {
activateVolumeKeyInterception()
@@ -226,9 +468,11 @@ class NativeBridgePlugin: Plugin {
}
@objc func appDidEnterBackground() {
logger.log("NativeBridgePlugin: App did enter background")
if let handler = volumeKeyHandler, handler.isIntercepting {
handler.stopInterception()
}
webViewLifecycleManager?.handleAppDidEnterBackground()
}
func activateVolumeKeyInterception() {
@@ -245,6 +489,9 @@ class NativeBridgePlugin: Plugin {
}
deinit {
webViewLifecycleManager?.stopMonitoring()
webViewLifecycleManager = nil
NotificationCenter.default.removeObserver(self)
}
@@ -626,14 +873,17 @@ extension NativeBridgePlugin: UIApplicationDelegate {
}
public func applicationWillResignActive(_ application: UIApplication) {
webViewLifecycleManager?.handleAppWillResignActive()
self.originalDelegate?.applicationWillResignActive?(application)
}
public func applicationWillEnterForeground(_ application: UIApplication) {
webViewLifecycleManager?.handleAppWillEnterForeground()
self.originalDelegate?.applicationWillEnterForeground?(application)
}
public func applicationDidEnterBackground(_ application: UIApplication) {
webViewLifecycleManager?.handleAppDidEnterBackground()
self.originalDelegate?.applicationDidEnterBackground?(application)
}
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-check-permissions"
description = "Enables the check-permissions command without any pre-configured scope."
commands.allow = ["check-permissions"]
[[permission]]
identifier = "deny-check-permissions"
description = "Denies the check-permissions command without any pre-configured scope."
commands.deny = ["check-permissions"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-check-permissions"
description = "Enables the check_permissions command without any pre-configured scope."
commands.allow = ["check_permissions"]
[[permission]]
identifier = "deny-check-permissions"
description = "Denies the check_permissions command without any pre-configured scope."
commands.deny = ["check_permissions"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-get-external-sdcard-path"
description = "Enables the get_external_sdcard_path command without any pre-configured scope."
commands.allow = ["get_external_sdcard_path"]
[[permission]]
identifier = "deny-get-external-sdcard-path"
description = "Denies the get_external_sdcard_path command without any pre-configured scope."
commands.deny = ["get_external_sdcard_path"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-open-external-url"
description = "Enables the open_external_url command without any pre-configured scope."
commands.allow = ["open_external_url"]
[[permission]]
identifier = "deny-open-external-url"
description = "Denies the open_external_url command without any pre-configured scope."
commands.deny = ["open_external_url"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-register-listener"
description = "Enables the register_listener command without any pre-configured scope."
commands.allow = ["register_listener"]
[[permission]]
identifier = "deny-register-listener"
description = "Denies the register_listener command without any pre-configured scope."
commands.deny = ["register_listener"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-remove-listener"
description = "Enables the remove_listener command without any pre-configured scope."
commands.allow = ["remove_listener"]
[[permission]]
identifier = "deny-remove-listener"
description = "Denies the remove_listener command without any pre-configured scope."
commands.deny = ["remove_listener"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-request-permissions"
description = "Enables the request-permissions command without any pre-configured scope."
commands.allow = ["request-permissions"]
[[permission]]
identifier = "deny-request-permissions"
description = "Denies the request-permissions command without any pre-configured scope."
commands.deny = ["request-permissions"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-request-permissions"
description = "Enables the request_permissions command without any pre-configured scope."
commands.allow = ["request_permissions"]
[[permission]]
identifier = "deny-request-permissions"
description = "Denies the request_permissions command without any pre-configured scope."
commands.deny = ["request_permissions"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-select-directory"
description = "Enables the select_directory command without any pre-configured scope."
commands.allow = ["select_directory"]
[[permission]]
identifier = "deny-select-directory"
description = "Denies the select_directory command without any pre-configured scope."
commands.deny = ["select_directory"]
@@ -22,7 +22,14 @@ Default permissions for the plugin
- `allow-get-safe-area-insets`
- `allow-get-screen-brightness`
- `allow-set-screen-brightness`
- `allow-get-external-sdcard-path`
- `allow-open-external-url`
- `allow-select-directory`
- `allow-request-manage-storage-permission`
- `allow-register-listener`
- `allow-remove-listener`
- `allow-check-permissions`
- `allow-request-permissions`
- `allow-checkPermissions`
- `allow-requestPermissions`
@@ -90,6 +97,32 @@ Denies the auth_with_safari command without any pre-configured scope.
<tr>
<td>
`native-bridge:allow-check-permissions`
</td>
<td>
Enables the check-permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-check-permissions`
</td>
<td>
Denies the check-permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-checkPermissions`
</td>
@@ -116,6 +149,32 @@ Denies the checkPermissions command without any pre-configured scope.
<tr>
<td>
`native-bridge:allow-check-permissions`
</td>
<td>
Enables the check_permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-check-permissions`
</td>
<td>
Denies the check_permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-copy-uri-to-path`
</td>
@@ -142,6 +201,32 @@ Denies the copy_uri_to_path command without any pre-configured scope.
<tr>
<td>
`native-bridge:allow-get-external-sdcard-path`
</td>
<td>
Enables the get_external_sdcard_path command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-get-external-sdcard-path`
</td>
<td>
Denies the get_external_sdcard_path command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-get-safe-area-insets`
</td>
@@ -454,6 +539,110 @@ Denies the lock_screen_orientation command without any pre-configured scope.
<tr>
<td>
`native-bridge:allow-open-external-url`
</td>
<td>
Enables the open_external_url command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-open-external-url`
</td>
<td>
Denies the open_external_url command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-register-listener`
</td>
<td>
Enables the register_listener command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-register-listener`
</td>
<td>
Denies the register_listener command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-remove-listener`
</td>
<td>
Enables the remove_listener command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-remove-listener`
</td>
<td>
Denies the remove_listener command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-request-permissions`
</td>
<td>
Enables the request-permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-request-permissions`
</td>
<td>
Denies the request-permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-requestPermissions`
</td>
@@ -506,6 +695,58 @@ Denies the request_manage_storage_permission command without any pre-configured
<tr>
<td>
`native-bridge:allow-request-permissions`
</td>
<td>
Enables the request_permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-request-permissions`
</td>
<td>
Denies the request_permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-select-directory`
</td>
<td>
Enables the select_directory command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-select-directory`
</td>
<td>
Denies the select_directory command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-set-screen-brightness`
</td>
@@ -19,7 +19,14 @@ permissions = [
"allow-get-safe-area-insets",
"allow-get-screen-brightness",
"allow-set-screen-brightness",
"allow-get-external-sdcard-path",
"allow-open-external-url",
"allow-select-directory",
"allow-request-manage-storage-permission",
"allow-register-listener",
"allow-remove-listener",
"allow-check-permissions",
"allow-request-permissions",
"allow-checkPermissions",
"allow-requestPermissions",
]
@@ -318,6 +318,18 @@
"const": "deny-auth-with-safari",
"markdownDescription": "Denies the auth_with_safari command without any pre-configured scope."
},
{
"description": "Enables the check-permissions command without any pre-configured scope.",
"type": "string",
"const": "allow-check-permissions",
"markdownDescription": "Enables the check-permissions command without any pre-configured scope."
},
{
"description": "Denies the check-permissions command without any pre-configured scope.",
"type": "string",
"const": "deny-check-permissions",
"markdownDescription": "Denies the check-permissions command without any pre-configured scope."
},
{
"description": "Enables the checkPermissions command without any pre-configured scope.",
"type": "string",
@@ -330,6 +342,18 @@
"const": "deny-checkPermissions",
"markdownDescription": "Denies the checkPermissions command without any pre-configured scope."
},
{
"description": "Enables the check_permissions command without any pre-configured scope.",
"type": "string",
"const": "allow-check-permissions",
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
},
{
"description": "Denies the check_permissions command without any pre-configured scope.",
"type": "string",
"const": "deny-check-permissions",
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
},
{
"description": "Enables the copy_uri_to_path command without any pre-configured scope.",
"type": "string",
@@ -342,6 +366,18 @@
"const": "deny-copy-uri-to-path",
"markdownDescription": "Denies the copy_uri_to_path command without any pre-configured scope."
},
{
"description": "Enables the get_external_sdcard_path command without any pre-configured scope.",
"type": "string",
"const": "allow-get-external-sdcard-path",
"markdownDescription": "Enables the get_external_sdcard_path command without any pre-configured scope."
},
{
"description": "Denies the get_external_sdcard_path command without any pre-configured scope.",
"type": "string",
"const": "deny-get-external-sdcard-path",
"markdownDescription": "Denies the get_external_sdcard_path command without any pre-configured scope."
},
{
"description": "Enables the get_safe_area_insets command without any pre-configured scope.",
"type": "string",
@@ -486,6 +522,54 @@
"const": "deny-lock-screen-orientation",
"markdownDescription": "Denies the lock_screen_orientation command without any pre-configured scope."
},
{
"description": "Enables the open_external_url command without any pre-configured scope.",
"type": "string",
"const": "allow-open-external-url",
"markdownDescription": "Enables the open_external_url command without any pre-configured scope."
},
{
"description": "Denies the open_external_url command without any pre-configured scope.",
"type": "string",
"const": "deny-open-external-url",
"markdownDescription": "Denies the open_external_url command without any pre-configured scope."
},
{
"description": "Enables the register_listener command without any pre-configured scope.",
"type": "string",
"const": "allow-register-listener",
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
},
{
"description": "Denies the register_listener command without any pre-configured scope.",
"type": "string",
"const": "deny-register-listener",
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
},
{
"description": "Enables the remove_listener command without any pre-configured scope.",
"type": "string",
"const": "allow-remove-listener",
"markdownDescription": "Enables the remove_listener command without any pre-configured scope."
},
{
"description": "Denies the remove_listener command without any pre-configured scope.",
"type": "string",
"const": "deny-remove-listener",
"markdownDescription": "Denies the remove_listener command without any pre-configured scope."
},
{
"description": "Enables the request-permissions command without any pre-configured scope.",
"type": "string",
"const": "allow-request-permissions",
"markdownDescription": "Enables the request-permissions command without any pre-configured scope."
},
{
"description": "Denies the request-permissions command without any pre-configured scope.",
"type": "string",
"const": "deny-request-permissions",
"markdownDescription": "Denies the request-permissions command without any pre-configured scope."
},
{
"description": "Enables the requestPermissions command without any pre-configured scope.",
"type": "string",
@@ -510,6 +594,30 @@
"const": "deny-request-manage-storage-permission",
"markdownDescription": "Denies the request_manage_storage_permission command without any pre-configured scope."
},
{
"description": "Enables the request_permissions command without any pre-configured scope.",
"type": "string",
"const": "allow-request-permissions",
"markdownDescription": "Enables the request_permissions command without any pre-configured scope."
},
{
"description": "Denies the request_permissions command without any pre-configured scope.",
"type": "string",
"const": "deny-request-permissions",
"markdownDescription": "Denies the request_permissions command without any pre-configured scope."
},
{
"description": "Enables the select_directory command without any pre-configured scope.",
"type": "string",
"const": "allow-select-directory",
"markdownDescription": "Enables the select_directory command without any pre-configured scope."
},
{
"description": "Denies the select_directory command without any pre-configured scope.",
"type": "string",
"const": "deny-select-directory",
"markdownDescription": "Denies the select_directory command without any pre-configured scope."
},
{
"description": "Enables the set_screen_brightness command without any pre-configured scope.",
"type": "string",
@@ -547,10 +655,10 @@
"markdownDescription": "Denies the use_background_audio command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-request-manage-storage-permission`\n- `allow-checkPermissions`\n- `allow-requestPermissions`",
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-select-directory`\n- `allow-request-manage-storage-permission`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`",
"type": "string",
"const": "default",
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-request-manage-storage-permission`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-select-directory`\n- `allow-request-manage-storage-permission`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
}
]
}
@@ -1,6 +1,8 @@
use tauri::{command, AppHandle, Runtime};
use std::path::PathBuf;
use tauri::{command, AppHandle, Runtime, State};
use crate::models::*;
use crate::DirectoryCallbackState;
use crate::NativeBridgeExt;
use crate::Result;
@@ -142,6 +144,41 @@ pub(crate) async fn set_screen_brightness<R: Runtime>(
app.native_bridge().set_screen_brightness(payload)
}
#[command]
pub(crate) async fn get_external_sdcard_path<R: Runtime>(
app: AppHandle<R>,
) -> Result<GetExternalSDCardPathResponse> {
app.native_bridge().get_external_sdcard_path()
}
#[command]
pub(crate) async fn open_external_url<R: Runtime>(
app: AppHandle<R>,
payload: OpenExternalUrlRequest,
) -> Result<OpenExternalUrlResponse> {
app.native_bridge().open_external_url(payload)
}
#[command]
pub(crate) async fn select_directory<R: Runtime>(
app: AppHandle<R>,
callback_state: State<'_, DirectoryCallbackState<R>>,
) -> Result<SelectDirectoryResponse> {
let result = app.native_bridge().select_directory()?;
if let Some(dir_path) = &result.path {
let path = PathBuf::from(dir_path);
if let Ok(callback_guard) = callback_state.callback.lock() {
if let Some(callback) = callback_guard.as_ref() {
callback(&app, &path);
}
}
}
Ok(result)
}
#[command]
pub(crate) async fn request_manage_storage_permission<R: Runtime>(
app: AppHandle<R>,
@@ -118,6 +118,21 @@ impl<R: Runtime> NativeBridge<R> {
Err(crate::Error::UnsupportedPlatformError)
}
pub fn get_external_sdcard_path(&self) -> crate::Result<GetExternalSDCardPathResponse> {
Err(crate::Error::UnsupportedPlatformError)
}
pub fn open_external_url(
&self,
_payload: OpenExternalUrlRequest,
) -> crate::Result<OpenExternalUrlResponse> {
Err(crate::Error::UnsupportedPlatformError)
}
pub fn select_directory(&self) -> crate::Result<SelectDirectoryResponse> {
Err(crate::Error::UnsupportedPlatformError)
}
pub fn request_manage_storage_permission(
&self,
) -> crate::Result<RequestManageStoragePermissionResponse> {
@@ -1,3 +1,4 @@
use std::sync::{Arc, Mutex};
use tauri::{
plugin::{Builder, TauriPlugin},
Manager, Runtime,
@@ -17,6 +18,9 @@ mod platform;
pub use error::{Error, Result};
use std::path::PathBuf;
use tauri::AppHandle;
#[cfg(desktop)]
use desktop::NativeBridge;
#[cfg(mobile)]
@@ -33,6 +37,20 @@ impl<R: Runtime, T: Manager<R>> crate::NativeBridgeExt<R> for T {
}
}
type DirectoryCallback<R> = Box<dyn Fn(&AppHandle<R>, &PathBuf) + Send + Sync>;
pub struct DirectoryCallbackState<R: Runtime> {
pub callback: Arc<Mutex<Option<DirectoryCallback<R>>>>,
}
impl<R: Runtime> Default for DirectoryCallbackState<R> {
fn default() -> Self {
Self {
callback: Arc::new(Mutex::new(None)),
}
}
}
/// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("native-bridge")
@@ -55,6 +73,9 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
commands::get_safe_area_insets,
commands::get_screen_brightness,
commands::set_screen_brightness,
commands::get_external_sdcard_path,
commands::open_external_url,
commands::select_directory,
commands::request_manage_storage_permission,
])
.setup(|app, api| {
@@ -63,7 +84,18 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
#[cfg(desktop)]
let native_bridge = desktop::init(app, api)?;
app.manage(native_bridge);
app.manage(DirectoryCallbackState::<R>::default());
Ok(())
})
.build()
}
pub fn register_select_directory_callback<R: Runtime>(
app: &AppHandle<R>,
callback: impl Fn(&AppHandle<R>, &PathBuf) + Send + Sync + 'static,
) {
if let Some(state) = app.try_state::<DirectoryCallbackState<R>>() {
let mut cb = state.callback.lock().unwrap();
*cb = Some(Box::new(callback));
}
}
@@ -189,6 +189,33 @@ impl<R: Runtime> NativeBridge<R> {
}
}
impl<R: Runtime> NativeBridge<R> {
pub fn get_external_sdcard_path(&self) -> crate::Result<GetExternalSDCardPathResponse> {
self.0
.run_mobile_plugin("get_external_sdcard_path", ())
.map_err(Into::into)
}
}
impl<R: Runtime> NativeBridge<R> {
pub fn open_external_url(
&self,
payload: OpenExternalUrlRequest,
) -> crate::Result<OpenExternalUrlResponse> {
self.0
.run_mobile_plugin("open_external_url", payload)
.map_err(Into::into)
}
}
impl<R: Runtime> NativeBridge<R> {
pub fn select_directory(&self) -> crate::Result<SelectDirectoryResponse> {
self.0
.run_mobile_plugin("select_directory", ())
.map_err(Into::into)
}
}
impl<R: Runtime> NativeBridge<R> {
pub fn request_manage_storage_permission(
&self,
@@ -102,12 +102,15 @@ pub struct Product {
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Purchase {
pub platform: String, // "ios" or "android"
pub package_name: Option<String>,
pub product_id: String,
pub transaction_id: String,
pub transaction_id: Option<String>,
pub original_transaction_id: Option<String>,
pub order_id: Option<String>,
pub purchase_token: Option<String>,
pub purchase_date: String,
pub original_transaction_id: String,
pub purchase_state: String, // "purchased", "pending", "cancelled"
pub platform: String, // "ios" or "android"
pub purchase_state: String, // "purchased", "pending", "cancelled", "restored"
}
#[derive(Debug, Serialize, Deserialize)]
@@ -187,8 +190,37 @@ pub struct SetScreenBrightnessResponse {
pub error: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GetExternalSDCardPathResponse {
pub path: Option<String>,
pub error: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RequestManageStoragePermissionResponse {
pub manage_storage: String, // "granted", "denied", or "prompt"
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenExternalUrlRequest {
pub url: String,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenExternalUrlResponse {
pub success: bool,
pub error: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SelectDirectoryResponse {
pub cancelled: Option<bool>,
pub uri: Option<String>,
pub path: Option<String>,
pub error: Option<String>,
}
@@ -383,7 +383,10 @@ class NativeTTSPlugin(private val activity: Activity) : Plugin(activity) {
val args = invoke.parseArgs(SetVoiceArgs::class.java)
try {
val voices = textToSpeech?.voices
val targetVoice = voices?.find { it.name == args.voice }
val targetVoice = voices?.find { voice ->
val languageTag = voice.locale.toLanguageTag()
voice.name == args.voice || (languageTag.contains(voice.name) && languageTag == args.voice)
}
if (targetVoice != null) {
val result = textToSpeech?.setVoice(targetVoice)
@@ -404,10 +407,17 @@ class NativeTTSPlugin(private val activity: Activity) : Plugin(activity) {
fun get_all_voices(invoke: Invoke) {
try {
val voices = textToSpeech?.voices?.map { voice ->
val voiceName = voice.name
val language = voice.locale.toLanguageTag()
val (id, name) = if (language.contains(voiceName)) {
language to language
} else {
voiceName to voiceName
}
JSObject().apply {
put("id", voice.name)
put("name", voice.name)
put("lang", voice.locale.toLanguageTag())
put("id", id)
put("name", name)
put("lang", language)
put("disabled", false)
}
} ?: emptyList()
@@ -11,10 +11,12 @@ const COMMANDS: &[&str] = &[
"set_media_session_active",
"update_media_session_state",
"update_media_session_metadata",
"register_listener",
"remove_listener",
"check_permissions",
"request_permissions",
"checkPermissions",
"requestPermissions",
"registerListener",
"remove_listener",
];
fn main() {
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-check-permissions"
description = "Enables the check_permissions command without any pre-configured scope."
commands.allow = ["check_permissions"]
[[permission]]
identifier = "deny-check-permissions"
description = "Denies the check_permissions command without any pre-configured scope."
commands.deny = ["check_permissions"]
@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-registerListener"
description = "Enables the registerListener command without any pre-configured scope."
commands.allow = ["registerListener"]
[[permission]]
identifier = "deny-registerListener"
description = "Denies the registerListener command without any pre-configured scope."
commands.deny = ["registerListener"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-register-listener"
description = "Enables the register_listener command without any pre-configured scope."
commands.allow = ["register_listener"]
[[permission]]
identifier = "deny-register-listener"
description = "Denies the register_listener command without any pre-configured scope."
commands.deny = ["register_listener"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-request-permissions"
description = "Enables the request_permissions command without any pre-configured scope."
commands.allow = ["request_permissions"]
[[permission]]
identifier = "deny-request-permissions"
description = "Denies the request_permissions command without any pre-configured scope."
commands.deny = ["request_permissions"]
@@ -16,10 +16,12 @@ Default permissions for the plugin
- `allow-set-media-session-active`
- `allow-update-media-session-state`
- `allow-update-media-session-metadata`
- `allow-register-listener`
- `allow-remove-listener`
- `allow-check-permissions`
- `allow-request-permissions`
- `allow-checkPermissions`
- `allow-requestPermissions`
- `allow-registerListener`
- `allow-remove-listener`
## Permission Table
@@ -59,6 +61,32 @@ Denies the checkPermissions command without any pre-configured scope.
<tr>
<td>
`native-tts:allow-check-permissions`
</td>
<td>
Enables the check_permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-tts:deny-check-permissions`
</td>
<td>
Denies the check_permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-tts:allow-get-all-voices`
</td>
@@ -137,12 +165,12 @@ Denies the pause command without any pre-configured scope.
<tr>
<td>
`native-tts:allow-registerListener`
`native-tts:allow-register-listener`
</td>
<td>
Enables the registerListener command without any pre-configured scope.
Enables the register_listener command without any pre-configured scope.
</td>
</tr>
@@ -150,12 +178,12 @@ Enables the registerListener command without any pre-configured scope.
<tr>
<td>
`native-tts:deny-registerListener`
`native-tts:deny-register-listener`
</td>
<td>
Denies the registerListener command without any pre-configured scope.
Denies the register_listener command without any pre-configured scope.
</td>
</tr>
@@ -215,6 +243,32 @@ Denies the requestPermissions command without any pre-configured scope.
<tr>
<td>
`native-tts:allow-request-permissions`
</td>
<td>
Enables the request_permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-tts:deny-request-permissions`
</td>
<td>
Denies the request_permissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-tts:allow-resume`
</td>
@@ -13,8 +13,10 @@ permissions = [
"allow-set-media-session-active",
"allow-update-media-session-state",
"allow-update-media-session-metadata",
"allow-register-listener",
"allow-remove-listener",
"allow-check-permissions",
"allow-request-permissions",
"allow-checkPermissions",
"allow-requestPermissions",
"allow-registerListener",
"allow-remove-listener",
]
@@ -306,6 +306,18 @@
"const": "deny-checkPermissions",
"markdownDescription": "Denies the checkPermissions command without any pre-configured scope."
},
{
"description": "Enables the check_permissions command without any pre-configured scope.",
"type": "string",
"const": "allow-check-permissions",
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
},
{
"description": "Denies the check_permissions command without any pre-configured scope.",
"type": "string",
"const": "deny-check-permissions",
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
},
{
"description": "Enables the get_all_voices command without any pre-configured scope.",
"type": "string",
@@ -343,16 +355,16 @@
"markdownDescription": "Denies the pause command without any pre-configured scope."
},
{
"description": "Enables the registerListener command without any pre-configured scope.",
"description": "Enables the register_listener command without any pre-configured scope.",
"type": "string",
"const": "allow-registerListener",
"markdownDescription": "Enables the registerListener command without any pre-configured scope."
"const": "allow-register-listener",
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
},
{
"description": "Denies the registerListener command without any pre-configured scope.",
"description": "Denies the register_listener command without any pre-configured scope.",
"type": "string",
"const": "deny-registerListener",
"markdownDescription": "Denies the registerListener command without any pre-configured scope."
"const": "deny-register-listener",
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
},
{
"description": "Enables the remove_listener command without any pre-configured scope.",
@@ -378,6 +390,18 @@
"const": "deny-requestPermissions",
"markdownDescription": "Denies the requestPermissions command without any pre-configured scope."
},
{
"description": "Enables the request_permissions command without any pre-configured scope.",
"type": "string",
"const": "allow-request-permissions",
"markdownDescription": "Enables the request_permissions command without any pre-configured scope."
},
{
"description": "Denies the request_permissions command without any pre-configured scope.",
"type": "string",
"const": "deny-request-permissions",
"markdownDescription": "Denies the request_permissions command without any pre-configured scope."
},
{
"description": "Enables the resume command without any pre-configured scope.",
"type": "string",
@@ -487,10 +511,10 @@
"markdownDescription": "Denies the update_media_session_state command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-init`\n- `allow-speak`\n- `allow-stop`\n- `allow-pause`\n- `allow-resume`\n- `allow-set-rate`\n- `allow-set-pitch`\n- `allow-set-voice`\n- `allow-get-all-voices`\n- `allow-set-media-session-active`\n- `allow-update-media-session-state`\n- `allow-update-media-session-metadata`\n- `allow-checkPermissions`\n- `allow-requestPermissions`\n- `allow-registerListener`\n- `allow-remove-listener`",
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-init`\n- `allow-speak`\n- `allow-stop`\n- `allow-pause`\n- `allow-resume`\n- `allow-set-rate`\n- `allow-set-pitch`\n- `allow-set-voice`\n- `allow-get-all-voices`\n- `allow-set-media-session-active`\n- `allow-update-media-session-state`\n- `allow-update-media-session-metadata`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`",
"type": "string",
"const": "default",
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-init`\n- `allow-speak`\n- `allow-stop`\n- `allow-pause`\n- `allow-resume`\n- `allow-set-rate`\n- `allow-set-pitch`\n- `allow-set-voice`\n- `allow-get-all-voices`\n- `allow-set-media-session-active`\n- `allow-update-media-session-state`\n- `allow-update-media-session-metadata`\n- `allow-checkPermissions`\n- `allow-requestPermissions`\n- `allow-registerListener`\n- `allow-remove-listener`"
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-init`\n- `allow-speak`\n- `allow-stop`\n- `allow-pause`\n- `allow-resume`\n- `allow-set-rate`\n- `allow-set-pitch`\n- `allow-set-voice`\n- `allow-get-all-voices`\n- `allow-set-media-session-active`\n- `allow-update-media-session-state`\n- `allow-update-media-session-metadata`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
}
]
}
+67 -6
View File
@@ -6,22 +6,30 @@ extern crate cocoa;
#[macro_use]
extern crate objc;
#[cfg(target_os = "windows")]
mod windows;
use tauri::utils::config::BackgroundThrottlingPolicy;
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
#[cfg(desktop)]
use std::path::PathBuf;
#[cfg(desktop)]
use tauri::{AppHandle, Listener, Manager, Url};
#[cfg(desktop)]
use tauri::{AppHandle, Manager};
use tauri_plugin_fs::FsExt;
#[cfg(desktop)]
use tauri::{Listener, Url};
#[cfg(target_os = "macos")]
mod macos;
mod transfer_file;
use tauri::{command, Emitter, WebviewUrl, WebviewWindowBuilder, Window};
#[cfg(target_os = "android")]
use tauri_plugin_native_bridge::register_select_directory_callback;
#[cfg(target_os = "android")]
use tauri_plugin_native_bridge::{NativeBridgeExt, OpenExternalUrlRequest};
use tauri_plugin_oauth::start;
#[cfg(not(target_os = "android"))]
use tauri_plugin_opener::OpenerExt;
use transfer_file::{download_file, upload_file};
#[cfg(desktop)]
@@ -42,7 +50,6 @@ fn allow_file_in_scopes(app: &AppHandle, files: Vec<PathBuf>) {
}
}
#[cfg(desktop)]
fn allow_dir_in_scopes(app: &AppHandle, dir: &PathBuf) {
let fs_scope = app.fs_scope();
let asset_protocol_scope = app.asset_protocol_scope();
@@ -216,6 +223,11 @@ pub fn run() {
allow_dir_in_scopes(app.handle(), &PathBuf::from(get_executable_dir()));
}
#[cfg(target_os = "android")]
register_select_directory_callback(app.handle(), move |app, path| {
allow_dir_in_scopes(app, path);
});
#[cfg(desktop)]
{
app.handle().plugin(tauri_plugin_cli::init())?;
@@ -257,9 +269,58 @@ pub fn run() {
eprintln!("Failed to initialize tauri_plugin_log: {e}");
};
let app_handle = app.handle().clone();
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
.background_throttling(BackgroundThrottlingPolicy::Disabled)
.background_color(tauri::window::Color(50, 49, 48, 255));
.background_color(tauri::window::Color(50, 49, 48, 255))
.initialization_script(
r#"
window.addEventListener('DOMContentLoaded', function() {
document.documentElement.classList.add('edge-to-edge');
const isTauriLocal = window.location.protocol === 'tauri:' ||
window.location.protocol === 'about:' ||
window.location.hostname === 'tauri.localhost';
const needsSafeArea = !isTauriLocal;
if (needsSafeArea && !document.getElementById('safe-area-style')) {
const style = document.createElement('style');
style.id = 'safe-area-style';
style.textContent = `
body {
padding-top: env(safe-area-inset-top) !important;
padding-bottom: env(safe-area-inset-bottom) !important;
padding-left: env(safe-area-inset-left) !important;
padding-right: env(safe-area-inset-right) !important;
}
`;
document.head.appendChild(style);
}
});
"#,
)
.on_navigation(move |url| {
if url.scheme() == "alipays" || url.scheme() == "alipay" {
let url_str = url.as_str().to_string();
#[cfg(target_os = "android")]
{
let handle = app_handle.clone();
tauri::async_runtime::spawn(async move {
match handle
.native_bridge()
.open_external_url(OpenExternalUrlRequest { url: url_str })
{
Ok(result) => println!("Result: {:?}", result),
Err(e) => eprintln!("Error: {:?}", e),
}
});
}
#[cfg(not(target_os = "android"))]
{
let _ = app_handle.opener().open_url(url_str, None::<&str>);
}
return false;
}
true
});
#[cfg(desktop)]
let win_builder = win_builder.inner_size(800.0, 600.0).resizable(true);
+44 -21
View File
@@ -107,6 +107,7 @@ pub async fn download_file(
file_path: &str,
headers: HashMap<String, String>,
body: Option<String>,
single_threaded: Option<bool>,
on_progress: Channel<ProgressPayload>,
) -> Result<()> {
use futures::stream::{self, StreamExt};
@@ -116,32 +117,23 @@ pub async fn download_file(
const PART_SIZE: u64 = 1024 * 1024;
let client = reqwest::Client::new();
let force_single = single_threaded.unwrap_or(false);
// Check if server supports range requests
let range_resp = client.get(url).header("Range", "bytes=0-0").send().await?;
let accept_ranges = range_resp
.headers()
.get("accept-ranges")
.map(|v| v.to_str().unwrap_or(""))
.unwrap_or("")
.eq_ignore_ascii_case("bytes");
let total = range_resp
.headers()
.get("content-range")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.split('/').nth(1))
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
if !accept_ranges || total == 0 {
// Fallback to original single-threaded logic
async fn single_threaded_download(
client: &reqwest::Client,
url: &str,
file_path: &str,
headers: &HashMap<String, String>,
body: &Option<String>,
on_progress: Channel<ProgressPayload>,
) -> Result<()> {
let mut request = if let Some(body) = body {
client.post(url).body(body)
client.post(url).body(body.clone())
} else {
client.get(url)
};
for (key, value) in headers.iter() {
for (key, value) in headers {
request = request.header(key, value);
}
@@ -168,7 +160,38 @@ pub async fn download_file(
});
}
file.flush().await?;
return Ok(());
Ok(())
}
if force_single {
return single_threaded_download(&client, url, file_path, &headers, &body, on_progress)
.await;
}
// Check if server supports range requests
let mut range_req = client.get(url).header("Range", "bytes=0-0");
for (key, value) in headers.iter() {
range_req = range_req.header(key, value);
}
let range_resp = range_req.send().await?;
let accept_ranges = range_resp
.headers()
.get("accept-ranges")
.map(|v| v.to_str().unwrap_or(""))
.unwrap_or("")
.eq_ignore_ascii_case("bytes");
let total = range_resp
.headers()
.get("content-range")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.split('/').nth(1))
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
if !accept_ranges || total == 0 {
return single_threaded_download(&client, url, file_path, &headers, &body, on_progress)
.await;
}
// Multi-part download with range access
@@ -0,0 +1 @@
+6 -2
View File
@@ -15,8 +15,8 @@
"security": {
"csp": {
"default-src": "'self' 'unsafe-inline' blob: data: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost",
"connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com https://translate.googleapis.com https://translate.toil.cc https://*.microsofttranslator.com https://edge.microsoft.com https://*.googleusercontent.com",
"img-src": "'self' blob: data: asset: http://asset.localhost https://*",
"connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost http://*:* https://*:* https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com https://translate.googleapis.com https://translate.toil.cc https://*.microsofttranslator.com https://edge.microsoft.com https://*.googleusercontent.com",
"img-src": "'self' blob: data: asset: http://asset.localhost https://* https://*:* http://* http://*:*",
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com https://chinese-fonts-cdn.netlify.app https://cdnjs.cloudflare.com",
"font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://db.onlinewebfonts.com https://cdn.jsdelivr.net https://fonts.gstatic.com https://chinese-fonts-cdn.netlify.app https://cdnjs.cloudflare.com",
"frame-src": "'self' blob: asset: http://asset.localhost https://*.stripe.com",
@@ -53,6 +53,10 @@
"windows": {
"webviewInstallMode": {
"type": "embedBootstrapper"
},
"nsis": {
"installerHooks": "nsis/installer-hooks.nsh",
"installMode": "both"
}
},
"macOS": {
@@ -0,0 +1,7 @@
{
"bundle": {
"resources": {
"../extensions/windows-thumbnail/target/windows_thumbnail.dll": "readest_thumbnail.dll"
}
}
}
@@ -1,5 +1,6 @@
import { describe, it, expect, vi } from 'vitest';
import { POST } from '@/app/api/apple/iap-verify/route';
import { POST as applePost } from '@/app/api/apple/iap-verify/route';
import { POST as googlePost } from '@/app/api/google/iap-verify/route';
import { NextRequest } from 'next/server';
import { setupSupabaseMocks } from '../helpers/supabase-mock';
@@ -16,7 +17,7 @@ vi.mock('@/utils/supabase', () => ({
}));
describe.skipIf(SKIP_IAP_API_TESTS)('/api/apple/iap-verify', () => {
it('should verify a valid transaction', async () => {
it('should verify a valid Apple IAP transaction', async () => {
setupSupabaseMocks();
const request = new NextRequest('http://localhost:3000/api/apple/iap-verify', {
method: 'POST',
@@ -30,7 +31,34 @@ describe.skipIf(SKIP_IAP_API_TESTS)('/api/apple/iap-verify', () => {
}),
});
const response = await POST(request);
const response = await applePost(request);
const data = await response.json();
console.log('Response:', data);
expect(response.status).toBe(200);
expect(data.purchase).toBeDefined();
});
});
describe.skipIf(SKIP_IAP_API_TESTS)('/api/google/iap-verify', () => {
it('should verify a valid Google IAP purchase', async () => {
setupSupabaseMocks();
const request = new NextRequest('http://localhost:3000/api/google/iap-verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer test-token',
},
body: JSON.stringify({
packageName: 'com.bilingify.readest',
productId: 'com.bilingify.readest.monthly.pro',
orderId: 'GPA.3388-4630-1043-97604',
purchaseToken:
'bhedilellajejnodjfcanjai.AO-J1Ow94pzeLM6e8pCJLT_tV-RnffT3HKMTcstovMNVlUTOwhx38SU5Seq3EO5qiQ0Le_VQU1ShN7nxaFQILY3UPX2nhdAKLBbekC_MxBnRf-Bpgegh_NA',
}),
});
const response = await googlePost(request);
const data = await response.json();
console.log('Response:', data);
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { vi } from 'vitest';
export const setupSupabaseMocks = async (
@@ -9,10 +8,7 @@ export const setupSupabaseMocks = async (
update: null,
insert: null,
adminUpsert: null,
adminSelect: {
data: [],
error: null,
},
adminSelect: null,
adminSelectMany: null,
adminUpdate: null,
adminInsert: null,
@@ -49,6 +45,7 @@ export const setupSupabaseMocks = async (
eq: vi.fn().mockResolvedValue(customResponses.update || { data: {}, error: null }),
})),
insert: vi.fn().mockResolvedValue(customResponses.insert || { data: {}, error: null }),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
vi.mocked(createSupabaseAdminClient).mockReturnValue({
@@ -105,5 +102,6 @@ export const setupSupabaseMocks = async (
match: vi.fn().mockResolvedValue(customResponses.adminDelete || { data: {}, error: null }),
})),
})),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
};
@@ -1,10 +1,10 @@
import { describe, it, expect, beforeAll } from 'vitest';
import { AppleIAPVerifier, createAppleIAPVerifier } from '@/libs/iap/apple/verifier';
import { AppleIAPVerifier, createAppleIAPVerifier } from '@/libs/payment/iap/apple/verifier';
const SKIP_IAP_API_TESTS = !process.env['ENABLE_IAP_API_TESTS'];
const REAL_TEST_DATA = {
validSubscription: {
transactionId: '2000000969168810',
transactionId: '2000000976418990',
originalTransactionId: '2000000968585424',
productId: 'com.bilingify.readest.monthly.plus',
},
@@ -43,7 +43,7 @@ describe.skipIf(SKIP_IAP_API_TESTS)('Apple IAP Integration Tests', () => {
expect(result.success).toBe(true);
expect(result.verified).toBe(true);
expect(result.status).toBe('active');
expect(result.status).toBe('expired');
expect(result.transactionId).toBe(transactionId);
expect(result.originalTransactionId).toBe(originalTransactionId);
expect(result.bundleId).toBe(process.env['APPLE_IAP_BUNDLE_ID']);
@@ -0,0 +1,47 @@
import { describe, test, expect, beforeAll } from 'vitest';
import init, { simplecc } from '@simplecc/simplecc_wasm';
import { readFile } from 'fs/promises';
import { join } from 'path';
describe.concurrent('suite', () => {
beforeAll(async () => {
const wasmPath = join(process.cwd(), 'public/vendor/simplecc/simplecc_wasm_bg.wasm');
const wasmBuffer = await readFile(wasmPath);
await init(wasmBuffer);
});
test('basic s2t and t2s', () => {
expect(simplecc('发财了去植发', 's2t')).toBe('發財了去植髮');
expect(simplecc('發財了去植髮', 't2s')).toBe('发财了去植发');
});
test('s2tw - Simplified to Traditional (Taiwan)', () => {
expect(simplecc('鼠标', 's2tw')).toBe('鼠標');
expect(simplecc('软件', 's2tw')).toBe('軟件');
expect(simplecc('信息', 's2tw')).toBe('信息');
});
test('s2twp - Simplified to Traditional (Taiwan) with phrases', () => {
expect(simplecc('计算机', 's2twp')).toBe('計算機');
expect(simplecc('打印机', 's2twp')).toBe('印表機');
expect(simplecc('激光', 's2twp')).toBe('雷射');
expect(simplecc('鼠标里面的硅二极管坏了,导致光标分辨率降低。', 's2twp')).toBe(
'滑鼠裡面的矽二極體壞了,導致游標解析度降低。',
);
});
test('tw2s - Traditional (Taiwan) to Simplified', () => {
expect(simplecc('滑鼠', 'tw2s')).toBe('滑鼠');
expect(simplecc('軟體', 'tw2s')).toBe('软体');
expect(simplecc('資訊', 'tw2s')).toBe('资讯');
});
test('tw2sp - Traditional (Taiwan) to Simplified with phrases', () => {
expect(simplecc('電腦', 'tw2sp')).toBe('电脑');
expect(simplecc('印表機', 'tw2sp')).toBe('打印机');
expect(simplecc('雷射', 'tw2sp')).toBe('激光');
expect(
simplecc('我們在寮國的伺服器的硬碟需要使用網際網路演算法軟體解決非同步的問題。', 'tw2sp'),
).toBe('我们在老挝的服务器的硬盘需要使用互联网算法软件解决异步的问题。');
});
});
@@ -1,104 +1,15 @@
import { z } from 'zod';
import { NextResponse } from 'next/server';
import { getAppleIAPVerifier } from '@/libs/iap/apple/verifier';
import { createSupabaseAdminClient } from '@/utils/supabase';
import { IAPError } from '@/libs/payment/iap/types';
import { validateUserAndToken } from '@/utils/access';
import { IAPError } from '@/types/error';
import { getAppleIAPVerifier } from '@/libs/payment/iap/apple/verifier';
import { processPurchaseData, VerifiedPurchase } from '@/libs/payment/iap/apple/server';
const iapVerificationSchema = z.object({
transactionId: z.string().min(1, 'Transaction ID is required'),
originalTransactionId: z.string().min(1, 'Original Transaction ID is required'),
});
const PRODUCT_MAP: Record<string, string> = {
'com.bilingify.readest.monthly.plus': 'Plus',
'com.bilingify.readest.yearly.plus': 'Plus',
'com.bilingify.readest.monthly.pro': 'Pro',
'com.bilingify.readest.yearly.pro': 'Pro',
};
const getProductName = (productId: string) => {
return PRODUCT_MAP[productId] || productId;
};
const getProductPlan = (productId: string) => {
if (productId.includes('plus')) {
return 'plus';
} else if (productId.includes('pro')) {
return 'pro';
}
return 'free';
};
interface Purchase {
status: string;
customerEmail: string;
subscriptionId: string;
planName: string;
productId: string;
platform: string;
transactionId: string;
originalTransactionId: string;
purchaseDate?: string;
expiresDate?: string | null;
quantity: number;
environment: string;
bundleId: string;
webOrderLineItemId?: string;
subscriptionGroupIdentifier?: string;
type?: string;
revocationDate?: string | null;
revocationReason?: number | null;
}
async function updateUserSubscription(userId: string, purchase: Purchase) {
try {
const supabase = createSupabaseAdminClient();
const { data, error } = await supabase.from('apple_iap_subscriptions').upsert(
{
user_id: userId,
platform: purchase.platform,
product_id: purchase.productId,
transaction_id: purchase.transactionId,
original_transaction_id: purchase.originalTransactionId,
status: purchase.status === 'active' ? 'active' : 'expired',
purchase_date: purchase.purchaseDate,
expires_date: purchase.expiresDate,
environment: purchase.environment,
bundle_id: purchase.bundleId,
quantity: purchase.quantity || 1,
auto_renew_status: true,
web_order_line_item_id: purchase.webOrderLineItemId,
subscription_group_identifier: purchase.subscriptionGroupIdentifier,
created_at: new Date(),
updated_at: new Date(),
},
{
onConflict: 'user_id,original_transaction_id',
},
);
if (error) {
console.error('Database update error:', error);
throw new Error(`Database update failed: ${error.message}`);
}
const plan = await getProductPlan(purchase.productId);
await supabase
.from('plans')
.update({
plan: ['active', 'trialing'].includes(purchase.status) ? plan : 'free',
status: purchase.status,
})
.eq('id', userId);
return data;
} catch (error) {
console.error('Failed to update user subscription:', error);
throw error;
}
}
export async function POST(request: Request) {
const body = await request.json();
let validatedInput;
@@ -115,7 +26,7 @@ export async function POST(request: Request) {
);
}
}
const { transactionId, originalTransactionId } = validatedInput!;
const { originalTransactionId } = validatedInput!;
const { user, token } = await validateUserAndToken(request.headers.get('authorization'));
if (!user || !token) {
@@ -123,59 +34,13 @@ export async function POST(request: Request) {
}
try {
const supabase = createSupabaseAdminClient();
const { data: existingSubscription } = await supabase
.from('apple_iap_subscriptions')
.select('*')
.eq('original_transaction_id', originalTransactionId)
.single();
console.log('Existing subscription:', existingSubscription);
// Should not restore purchase for another account
if (existingSubscription && existingSubscription.user_id !== user.id) {
return NextResponse.json(
{ error: IAPError.TRANSACTION_BELONGS_TO_ANOTHER_USER },
{ status: 403 },
);
}
if (
existingSubscription &&
existingSubscription.transactionId === transactionId &&
existingSubscription.status === 'active'
) {
console.log('Transaction already verified and active');
const purchase = {
status: existingSubscription.status,
customerEmail: user.email,
subscriptionId:
existingSubscription.web_order_line_item_id ||
existingSubscription.original_transaction_id,
planName: getProductName(existingSubscription.product_id),
productId: existingSubscription.product_id,
platform: existingSubscription.platform,
transactionId: existingSubscription.transaction_id,
originalTransactionId: existingSubscription.original_transaction_id,
purchaseDate: existingSubscription.purchase_date,
expiresDate: existingSubscription.expires_date,
quantity: existingSubscription.quantity,
environment: existingSubscription.environment,
bundleId: existingSubscription.bundle_id,
};
return NextResponse.json({
purchase,
error: null,
});
}
const defaultIAPVerifier = getAppleIAPVerifier();
const verificationResult = await defaultIAPVerifier.verifyTransaction(originalTransactionId);
if (!verificationResult.success) {
console.error('Apple verification failed:', verificationResult.error);
return NextResponse.json(
{
error: verificationResult.error || IAPError.UNKNOWN_ERROR,
error: verificationResult.error || IAPError.TRANSACTION_CANNOT_BE_VERIFIED,
purchase: null,
},
{ status: 400 },
@@ -188,36 +53,15 @@ export async function POST(request: Request) {
productId: transaction.productId,
environment: transaction.environment,
});
if (transaction.environment === 'Sandbox' && process.env.NODE_ENV === 'production') {
console.warn('Sandbox transaction in production environment');
}
const purchase = {
status: verificationResult.status!,
customerEmail: user.email!,
subscriptionId: transaction.webOrderLineItemId || transaction.originalTransactionId,
planName: getProductName(transaction.productId),
productId: transaction.productId,
platform: 'ios',
transactionId: transaction.transactionId,
originalTransactionId: transaction.originalTransactionId,
purchaseDate: verificationResult.purchaseDate?.toISOString(),
expiresDate: verificationResult.expiresDate?.toISOString() || null,
quantity: transaction.quantity,
environment: transaction.environment.toLowerCase(),
bundleId: transaction.bundleId,
webOrderLineItemId: transaction.webOrderLineItemId,
subscriptionGroupIdentifier: transaction.subscriptionGroupIdentifier,
type: transaction.type,
revocationDate: verificationResult.revocationDate?.toISOString() || null,
revocationReason: verificationResult.revocationReason,
};
try {
await updateUserSubscription(user.id, purchase);
const purchase: VerifiedPurchase = await processPurchaseData(user, verificationResult);
return NextResponse.json({
purchase,
error: null,
});
} catch (dbError) {
console.error('Database update failed:', dbError);
return NextResponse.json(
{
error: IAPError.TRANSACTION_SERVICE_UNAVAILABLE,
@@ -226,11 +70,6 @@ export async function POST(request: Request) {
{ status: 500 },
);
}
return NextResponse.json({
purchase,
error: null,
});
} catch (error) {
console.error('IAP verification error:', error);
return NextResponse.json(
@@ -0,0 +1,92 @@
import { z } from 'zod';
import { NextResponse } from 'next/server';
import { validateUserAndToken } from '@/utils/access';
import { getGoogleIAPVerifier, VerifyPurchaseParams } from '@/libs/payment/iap/google/verifier';
import { processPurchaseData, VerifiedPurchase } from '@/libs/payment/iap/google/server';
import { IAPError } from '@/libs/payment/iap/types';
const iapVerificationSchema = z.object({
packageName: z.string().min(1, 'Package name is required'),
productId: z.string().min(1, 'Product ID is required'),
orderId: z.string().min(1, 'Order ID is required'),
purchaseToken: z.string().min(1, 'Purchase token is required'),
});
export async function POST(request: Request) {
const body = await request.json();
let validatedInput;
try {
validatedInput = iapVerificationSchema.parse(body);
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{
error: 'Invalid input data',
purchase: null,
},
{ status: 400 },
);
}
}
const { orderId, purchaseToken, productId, packageName } = validatedInput!;
const { user, token } = await validateUserAndToken(request.headers.get('authorization'));
if (!user || !token) {
return NextResponse.json({ error: IAPError.NOT_AUTHENTICATED }, { status: 403 });
}
try {
const googleIAPVerifier = getGoogleIAPVerifier();
const verifyParams: VerifyPurchaseParams = { orderId, purchaseToken, productId, packageName };
const verificationResult = await googleIAPVerifier.verifyPurchase(verifyParams);
if (!verificationResult.success) {
console.error('Google verification failed:', verificationResult.error);
return NextResponse.json(
{
error: verificationResult.error || IAPError.TRANSACTION_CANNOT_BE_VERIFIED,
purchase: null,
},
{ status: 400 },
);
}
const purchaseData = verificationResult.purchaseData!;
console.log('Google verification successful:', {
orderId: purchaseData.orderId,
productId: productId,
purchaseState: purchaseData.purchaseState,
});
let purchase: VerifiedPurchase;
try {
purchase = await processPurchaseData(user, verifyParams, verificationResult);
if (verificationResult.purchaseData?.acknowledgementState === 0) {
try {
await googleIAPVerifier.acknowledgePurchase(verifyParams);
purchase.acknowledgementState = 1;
} catch (ackError) {
console.error('Failed to acknowledge purchase:', ackError);
}
}
return NextResponse.json({
purchase,
error: null,
});
} catch (dbError) {
console.error('Database update failed:', dbError);
return NextResponse.json(
{
error: IAPError.TRANSACTION_SERVICE_UNAVAILABLE,
purchase: null,
},
{ status: 500 },
);
}
} catch (error) {
console.error('IAP verification error:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : IAPError.UNKNOWN_ERROR },
{ status: 500 },
);
}
}
@@ -0,0 +1,186 @@
import { NextRequest, NextResponse } from 'next/server';
async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
// Cloudflare Workers incorrectly decodes %26 to & in the url parameter value,
// causing query parameters within the proxied URL (like &start_index=26) to be
// treated as separate top-level parameters instead of part of the url value.
// We work around this by manually extracting the url parameter - capturing everything
// from 'url=' until we hit our known parameters (&stream= or &auth=), then decoding it.
const fullUrl = request.url;
const urlParamStart = fullUrl.indexOf('url=') + 4;
const streamParam = fullUrl.lastIndexOf('&stream=');
const authParam = fullUrl.lastIndexOf('&auth=');
const urlParamEnd = Math.min(...[streamParam, authParam].filter((i) => i > 0), fullUrl.length);
const encodedUrl = fullUrl.substring(urlParamStart, urlParamEnd);
const url = decodeURIComponent(encodedUrl);
const auth = request.nextUrl.searchParams.get('auth');
const stream = request.nextUrl.searchParams.get('stream');
if (!url) {
return NextResponse.json(
{ error: 'Missing URL parameter. Usage: /api/opds/proxy?url=YOUR_OPDS_URL' },
{ status: 400 },
);
}
try {
new URL(url);
} catch {
return NextResponse.json({ error: 'Invalid URL format' }, { status: 400 });
}
try {
console.log(`[OPDS Proxy] ${method}: ${url}`);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 20000);
const headers: HeadersInit = {
'User-Agent': 'Readest/1.0 (OPDS Browser)',
Accept: 'application/atom+xml, application/xml, text/xml, application/json, */*',
};
if (auth) {
headers['Authorization'] = auth;
}
const response = await fetch(url, {
method,
headers,
signal: controller.signal,
});
clearTimeout(timeout);
if (!response.ok) {
console.error(`[OPDS Proxy] HTTP ${response.status} for ${url}`);
if (method === 'HEAD') {
if (response.status === 401) {
return new NextResponse(null, {
status: 403,
headers: {
...Object.fromEntries(response.headers.entries()),
'Cache-Control': 'public, max-age=300',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.text();
if (response.status === 401) {
return new NextResponse(data, {
status: 403,
headers: {
...Object.fromEntries(response.headers.entries()),
'Cache-Control': 'public, max-age=300',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
return new NextResponse(data, {
status: response.status,
headers: {
...Object.fromEntries(response.headers.entries()),
'Cache-Control': 'public, max-age=300',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
const contentType = response.headers.get('Content-Type') || 'text/xml';
const contentLength = response.headers.get('Content-Length');
if (method === 'HEAD') {
console.log(`[OPDS Proxy] HEAD Success: ${url}`);
return new NextResponse(null, {
status: 200,
headers: {
'Content-Type': contentType,
'Content-Length': contentLength || '',
'Cache-Control': 'public, max-age=300',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
if (stream === 'true' || (contentLength && parseInt(contentLength) > 1024 * 1024)) {
console.log(`[OPDS Proxy] Streaming: ${url}`);
return new NextResponse(response.body, {
status: 200,
headers: {
'Content-Type': contentType,
'X-Content-Length': contentLength || '',
'Cache-Control': 'public, max-age=300',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
} else {
const data = await response.text();
console.log(`[OPDS Proxy] Success: ${url} (${data.length} bytes)`);
return new NextResponse(data, {
status: 200,
headers: {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=300',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
} catch (error) {
console.error('[OPDS Proxy] Error:', error);
if (error instanceof Error) {
if (error.name === 'AbortError') {
return NextResponse.json(
{ error: 'Request timeout - the OPDS server took too long to respond' },
{ status: 504 },
);
}
return NextResponse.json(
{
error: error.message,
url: url,
hint: 'Check if the OPDS URL is accessible and returns valid OPDS/Atom/JSON content',
},
{ status: 500 },
);
}
return NextResponse.json({ error: 'Failed to fetch OPDS feed', url: url }, { status: 500 });
}
}
export async function GET(request: NextRequest) {
return handleRequest(request, 'GET');
}
export async function HEAD(request: NextRequest) {
return handleRequest(request, 'HEAD');
}
export async function OPTIONS(_: NextRequest) {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}

Some files were not shown because too many files have changed in this diff Show More