forked from akai/readest
Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 519e222883 | |||
| 488a242787 | |||
| 523a03d212 | |||
| d49f3442e8 | |||
| 48dc570809 | |||
| 509320813f | |||
| 54d7e73acb | |||
| 1bc0eb3a4b | |||
| c0bf2d40dd | |||
| e30a39f9cb | |||
| 9727e4e9b8 | |||
| 3380912cdb | |||
| 91a5454b46 | |||
| e0c5acf4c8 | |||
| 7e062be0d0 | |||
| b23876c2b9 | |||
| e9a152683e | |||
| 34f1af727f | |||
| dfc24f3803 | |||
| ec233f7b37 | |||
| bb34b1ba59 | |||
| b1fb477359 | |||
| 3c877a19fc | |||
| b47baa6b74 | |||
| 14c85cbadf | |||
| a88b9139f8 | |||
| 251a4b52c0 | |||
| 1aaeaf5ebe | |||
| 9fd152d727 | |||
| 2571ceede4 | |||
| 932cfa3b9f | |||
| 01fde8573c | |||
| d95656b37b | |||
| 210eda0340 | |||
| 0d8b877d73 | |||
| 79d39b2295 | |||
| e03b0af58a | |||
| 71c2143cba | |||
| 70a7f5be19 | |||
| d2242423f5 | |||
| 782e9c8b20 | |||
| 55b2c678a4 | |||
| 48d754c184 | |||
| ff0443ace7 | |||
| cc3cc58de0 | |||
| 80d60266c2 | |||
| f06cdb5899 | |||
| dfe68988aa | |||
| d0264de3eb | |||
| 77c4303938 | |||
| 5724d2eeec | |||
| 64007b1188 | |||
| 50ee7bd8be | |||
| 2bcbde4bf1 | |||
| 99f75dd3b6 | |||
| 2b9132b4a8 | |||
| 82782be16e | |||
| 7067129887 | |||
| 6e706ac8f7 | |||
| 39b902c927 | |||
| 94a9cd7c9d | |||
| 0dae7db140 | |||
| 32954e025c | |||
| 61dda9a517 | |||
| 99a4cb50ac | |||
| f4b00d6dec | |||
| f69422a61c | |||
| 7f4c157a90 | |||
| 7d9724ff8b | |||
| 06db7f36f8 | |||
| f0c249bce2 | |||
| e8a96fba95 | |||
| 92b01c40a0 | |||
| 6003eeadba | |||
| 856ed4d3b3 | |||
| 267fe58a8c | |||
| 1b00d8c41c | |||
| cb126613b1 | |||
| 515bfee2a1 | |||
| 584f3511f8 | |||
| 3ea54e6725 | |||
| 48212d892f | |||
| 977c61b914 | |||
| 66fceb2313 | |||
| 76feefff00 | |||
| 1be43ff704 | |||
| b6d4a547e5 | |||
| 5ee860f1e8 | |||
| 0b01132d88 | |||
| b98c487919 | |||
| b43b08d423 | |||
| 3ddb6e6422 | |||
| ea9146be5b | |||
| 78cb1f45e4 | |||
| e472d1cf1e | |||
| 75dc04d598 | |||
| fc4c033cf3 | |||
| dbee054838 | |||
| 45b805dcd4 | |||
| a8ac54b51c | |||
| 86f705eae9 | |||
| d390209dab | |||
| 149f94be4c | |||
| ef1a1eab98 | |||
| dc7c6e14bc | |||
| 8c31dc85f9 | |||
| 7b8d1ddc35 | |||
| 8c12399732 | |||
| 93a4b1a448 | |||
| 04b0752b22 | |||
| 5137c08204 | |||
| ec0b44fb9f | |||
| 0bf83dfe67 | |||
| b151b70cb9 | |||
| f2640359a0 | |||
| 079aeaedb0 | |||
| bfacadb964 | |||
| 2e98ed44ee | |||
| 952d2a3553 | |||
| 225aa0ca59 | |||
| 37f718b84b | |||
| db8f53a6e1 | |||
| ce345ad67f | |||
| 8e53e625a4 |
@@ -11,7 +11,7 @@ jobs:
|
||||
env:
|
||||
RUSTFLAGS: '-C target-cpu=skylake'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Install minimal stable with clippy and rustfmt
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
- platform: 'web'
|
||||
- platform: 'tauri'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
version: 10.14.0
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
@@ -57,8 +57,10 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/readest-app/.next/cache
|
||||
key: nextjs-${{ runner.os }}-${{ hashFiles('apps/readest-app/package.json', 'pnpm-lock.yaml') }}
|
||||
restore-keys: nextjs-${{ runner.os }}-
|
||||
key: nextjs-${{ matrix.config.platform }}-${{ github.sha }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
nextjs-${{ matrix.config.platform }}-${{ github.sha }}-
|
||||
nextjs-${{ matrix.config.platform }}-
|
||||
|
||||
- name: install Dependencies
|
||||
working-directory: apps/readest-app
|
||||
@@ -70,6 +72,11 @@ jobs:
|
||||
run: |
|
||||
pnpm test -- --watch=false
|
||||
|
||||
- name: run lint
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm lint
|
||||
|
||||
- name: build the web App
|
||||
if: matrix.config.platform == 'web'
|
||||
working-directory: apps/readest-app
|
||||
|
||||
@@ -17,14 +17,14 @@ jobs:
|
||||
release_version: ${{ steps.get-release-notes.outputs.release_version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
- name: get version
|
||||
run: echo "PACKAGE_VERSION=$(node -p "require('./apps/readest-app/package.json').version")" >> $GITHUB_ENV
|
||||
- name: get release
|
||||
id: get-release
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.repos.getLatestRelease({
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
core.setOutput('release_tag', data.tag_name);
|
||||
- name: get release notes
|
||||
id: get-release-notes
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -48,6 +48,39 @@ jobs:
|
||||
core.setOutput('release_version', version);
|
||||
core.setOutput('release_note', releaseNote);
|
||||
|
||||
update-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: get-release
|
||||
|
||||
steps:
|
||||
- name: update release
|
||||
id: update-release
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
release_id: ${{ needs.get-release.outputs.release_id }}
|
||||
release_tag: ${{ needs.get-release.outputs.release_tag }}
|
||||
release_note: ${{ needs.get-release.outputs.release_note }}
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.repos.generateReleaseNotes({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
tag_name: process.env.release_tag,
|
||||
})
|
||||
const notes = process.env.release_note.split(/(?:\d\.\s)/).filter(Boolean);
|
||||
const formattedNotes = notes.map(note => `* ${note.trim()}`).join("\n");
|
||||
const body = `## Release Highlight\n${formattedNotes}\n\n${data.body}`;
|
||||
github.rest.repos.updateRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: process.env.release_id,
|
||||
body: body,
|
||||
draft: false,
|
||||
prerelease: false
|
||||
})
|
||||
|
||||
build-tauri:
|
||||
needs: get-release
|
||||
permissions:
|
||||
@@ -91,7 +124,7 @@ jobs:
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: initialize git submodules
|
||||
run: git submodule update --init --recursive
|
||||
@@ -102,14 +135,14 @@ jobs:
|
||||
version: 10.14.0
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: setup Java (for Android build only)
|
||||
if: matrix.config.release == 'android'
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
@@ -177,11 +210,6 @@ jobs:
|
||||
pnpm tauri icon ../../data/icons/readest-book.png
|
||||
git checkout .
|
||||
|
||||
MANIFEST="src-tauri/gen/android/app/src/main/AndroidManifest.xml"
|
||||
PERMISSION_LINE='<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>'
|
||||
grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST" || \
|
||||
sed -i "/android.permission.INTERNET/a \ $PERMISSION_LINE" "$MANIFEST"
|
||||
|
||||
pushd src-tauri/gen/android
|
||||
echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" > keystore.properties
|
||||
echo "password=${{ secrets.ANDROID_KEY_PASSWORD }}" >> keystore.properties
|
||||
@@ -288,41 +316,8 @@ jobs:
|
||||
echo "Uploading $bin_file to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file --clobber
|
||||
|
||||
update-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-release, build-tauri]
|
||||
|
||||
steps:
|
||||
- name: update release
|
||||
id: update-release
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
release_id: ${{ needs.get-release.outputs.release_id }}
|
||||
release_tag: ${{ needs.get-release.outputs.release_tag }}
|
||||
release_note: ${{ needs.get-release.outputs.release_note }}
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.repos.generateReleaseNotes({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
tag_name: process.env.release_tag,
|
||||
})
|
||||
const notes = process.env.release_note.split(/(?:\d\.\s)/).filter(Boolean);
|
||||
const formattedNotes = notes.map(note => `* ${note.trim()}`).join("\n");
|
||||
const body = `## Release Highlight\n${formattedNotes}\n\n${data.body}`;
|
||||
github.rest.repos.updateRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: process.env.release_id,
|
||||
body: body,
|
||||
draft: false,
|
||||
prerelease: false
|
||||
})
|
||||
|
||||
upload-to-r2:
|
||||
needs: [get-release, update-release]
|
||||
needs: [get-release, build-tauri]
|
||||
uses: ./.github/workflows/upload-to-r2.yml
|
||||
with:
|
||||
tag: ${{ needs.get-release.outputs.release_tag }}
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
build_and_deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: 'true'
|
||||
- uses: amondnet/vercel-action@v25
|
||||
|
||||
@@ -40,3 +40,6 @@ next-env.d.ts
|
||||
target
|
||||
|
||||
fastlane/report.xml
|
||||
|
||||
*.koplugin.zip
|
||||
|
||||
|
||||
Generated
+374
-376
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,7 @@ rust-version = "1.77.2"
|
||||
tauri = { path = "packages/tauri/crates/tauri" }
|
||||
tauri-utils = { path = "packages/tauri/crates/tauri-utils" }
|
||||
tauri-build = { path = "packages/tauri/crates/tauri-build" }
|
||||
tauri-plugin-os = { path = "packages/tauri-plugins/plugins/os" }
|
||||
tauri-plugin-fs = { path = "packages/tauri-plugins/plugins/fs" }
|
||||
tauri-plugin-dialog = { path = "packages/tauri-plugins/plugins/dialog" }
|
||||
tauri-plugin-deep-link = { path = "packages/tauri-plugins/plugins/deep-link" }
|
||||
|
||||
@@ -11,14 +11,15 @@
|
||||
[![Web App][badge-web-app]][link-web-readest]
|
||||
[![OS][badge-platforms]][link-website]
|
||||
<br>
|
||||
[![][badge-hellogithub]][link-hellogithub]
|
||||
[![][badge-discord]][link-discord]
|
||||
[![Discord][badge-discord]][link-discord]
|
||||
[![Reddit][badge-reddit]][link-reddit]
|
||||
[![AGPL Licence][badge-license]](LICENSE)
|
||||
[![Latest release][badge-release]][link-gh-releases]
|
||||
[![Language Coverage][badge-language-coverage]]()
|
||||
[![Donate][badge-donate]][link-donate]
|
||||
<br>
|
||||
[![Latest release][badge-release]][link-gh-releases]
|
||||
[![Last commit][badge-last-commit]][link-gh-commits]
|
||||
[![Commits][badge-commit-activity]][link-gh-pulse]
|
||||
[![][badge-hellogithub]][link-hellogithub]
|
||||
[![Ask DeepWiki][badge-deepwiki]][link-deepwiki]
|
||||
|
||||
</div>
|
||||
@@ -44,39 +45,38 @@
|
||||
|
||||
<div align="left">✅ Implemented</div>
|
||||
|
||||
| **Feature** | **Description** | **Status** |
|
||||
| --------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------- |
|
||||
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, TXT, PDF (experimental) | ✅ |
|
||||
| **Scroll/Page View Modes** | Switch between scrolling or paginated reading modes. | ✅ |
|
||||
| **Full-Text Search** | Search across the entire book to find relevant sections. | ✅ |
|
||||
| **Annotations and Highlighting** | Add highlights, bookmarks, and notes to enhance your reading experience. | ✅ |
|
||||
| **Excerpt Text for Note-Taking** | Easily excerpt text from books for detailed notes and analysis. | ✅ |
|
||||
| **Dictionary/Wikipedia Lookup** | Instantly look up words and terms when reading. | ✅ |
|
||||
| **[Parallel Read][link-parallel-read]** | Read two books or documents simultaneously in a split-screen view. | ✅ |
|
||||
| **Customize Font and Layout** | Adjust font, layout, theme mode, and theme colors for a personalized experience. | ✅ |
|
||||
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
|
||||
| **Sync across Platforms** | Synchronize book files, reading progress, notes, and bookmarks across all supported platforms. | ✅ |
|
||||
| **Translate with DeepL** | From a single sentence to the entire book—translate instantly with DeepL. | ✅ |
|
||||
| **Translate with Yandex** | Instantly translate text or books using Yandex Translate. | ✅ |
|
||||
| **Text-to-Speech (TTS) Support** | Enjoy smooth, multilingual narration—even within a single book. | ✅ |
|
||||
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
|
||||
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
|
||||
| **Feature** | **Description** | **Status** |
|
||||
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, TXT, PDF (experimental) | ✅ |
|
||||
| **Scroll/Page View Modes** | Switch between scrolling or paginated reading modes. | ✅ |
|
||||
| **Full-Text Search** | Search across the entire book to find relevant sections. | ✅ |
|
||||
| **Annotations and Highlighting** | Add highlights, bookmarks, and notes to enhance your reading experience. | ✅ |
|
||||
| **Excerpt Text for Note-Taking** | Easily excerpt text from books for detailed notes and analysis. | ✅ |
|
||||
| **Dictionary/Wikipedia Lookup** | Instantly look up words and terms when reading. | ✅ |
|
||||
| **[Parallel Read][link-parallel-read]** | Read two books or documents simultaneously in a split-screen view. | ✅ |
|
||||
| **Customize Font and Layout** | Adjust font, layout, theme mode, and theme colors for a personalized experience. | ✅ |
|
||||
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
|
||||
| **Sync across Platforms** | Synchronize book files, reading progress, notes, and bookmarks across all supported platforms. | ✅ |
|
||||
| **Accessibility** | Provides full keyboard navigation and supports for screen readers such as VoiceOver, TalkBack, NVDA, and Orca. | ✅ |
|
||||
| **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. | ✅ |
|
||||
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
|
||||
|
||||
## Planned Features
|
||||
|
||||
<div align="left">🛠 Building</div>
|
||||
<div align="left">🔄 Planned</div>
|
||||
|
||||
| **Feature** | **Description** | **Priority** |
|
||||
| ------------------------------- | ------------------------------------------------------------------------------------------ | ------------ |
|
||||
| **Sync with Koreader** | 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. | 🛠 |
|
||||
| **Keyboard Navigation** | Implement vimium-style keybindings for book navigation. | 🔄 |
|
||||
| **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. | 🔄 |
|
||||
| **In-Library Full-Text Search** | Search across your entire ebook library to find topics and quotes. | 🔄 |
|
||||
| **Feature** | **Description** | **Priority** |
|
||||
| ------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------ |
|
||||
| [**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. | 🔄 |
|
||||
| **In-Library Full-Text Search** | Search across your entire ebook library to find topics and quotes. | 🔄 |
|
||||
|
||||
Stay tuned for continuous improvements and updates! Contributions and suggestions are always welcome—let's build the ultimate reading experience together. 😊
|
||||
|
||||
@@ -109,9 +109,9 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion
|
||||
|
||||
### Platform-Specific Downloads
|
||||
|
||||
- macOS / iOS / iPadOS : Search for "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 [https://readest.com][link-website] or the [Releases on GitHub][link-gh-releases].
|
||||
- Web: Visit [https://web.readest.com][link-web-readest].
|
||||
- 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].
|
||||
- Web: Visit and use **Readest for Web** at [https://web.readest.com][link-web-readest].
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -303,12 +303,14 @@ The following fonts are utilized in this software, either bundled within the app
|
||||
[badge-license]: https://img.shields.io/github/license/readest/readest?color=teal
|
||||
[badge-release]: https://img.shields.io/github/release/readest/readest?color=green
|
||||
[badge-platforms]: https://img.shields.io/badge/platforms-macOS%2C%20Windows%2C%20Linux%2C%20Android%2C%20iOS%2C%20Web%2C%20PWA-green
|
||||
[badge-last-commit]: https://img.shields.io/github/last-commit/readest/readest?color=green
|
||||
[badge-commit-activity]: https://img.shields.io/github/commit-activity/m/readest/readest
|
||||
[badge-last-commit]: https://img.shields.io/github/last-commit/readest/readest?color=blue
|
||||
[badge-commit-activity]: https://img.shields.io/github/commit-activity/m/readest/readest?color=blue
|
||||
[badge-discord]: https://img.shields.io/discord/1314226120886976544?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square
|
||||
[badge-hellogithub]: https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=8a5b6ade2aee461a8bd94e59200682a7&claim_uid=eRLUbPOy2qZtDgw&theme=small
|
||||
[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
|
||||
[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
|
||||
@@ -322,3 +324,5 @@ 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-kosync-wiki]: https://github.com/readest/readest/wiki/Sync-with-Koreader-devices
|
||||
[link-reddit]: https://reddit.com/r/readest/
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
import path from 'node:path';
|
||||
import js from '@eslint/js';
|
||||
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
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
export default [...compat.extends("next/core-web-vitals", "next/typescript")];
|
||||
|
||||
export default [
|
||||
...compat.extends('next/core-web-vitals', 'next/typescript'),
|
||||
{
|
||||
plugins: {
|
||||
'jsx-a11y': jsxA11y,
|
||||
},
|
||||
rules: {
|
||||
...jsxA11y.configs.recommended.rules,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -28,6 +28,9 @@ module.exports = {
|
||||
'ar',
|
||||
'th',
|
||||
'bo',
|
||||
'bn',
|
||||
'ta',
|
||||
'si',
|
||||
'zh-CN',
|
||||
'zh-TW',
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.9.71",
|
||||
"version": "0.9.81",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev",
|
||||
@@ -47,19 +47,19 @@
|
||||
"@aws-sdk/s3-request-presigner": "^3.735.0",
|
||||
"@ducanh2912/next-pwa": "^10.2.9",
|
||||
"@fabianlars/tauri-plugin-oauth": "2",
|
||||
"@opennextjs/cloudflare": "^1.6.1",
|
||||
"@opennextjs/cloudflare": "^1.6.5",
|
||||
"@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.50.2",
|
||||
"@tauri-apps/api": "2.6.0",
|
||||
"@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.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.3.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.1",
|
||||
"@tauri-apps/plugin-dialog": "^2.3.2",
|
||||
"@tauri-apps/plugin-fs": "^2.4.1",
|
||||
"@tauri-apps/plugin-haptics": "^2.3.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.1",
|
||||
"@tauri-apps/plugin-log": "^2.6.0",
|
||||
"@tauri-apps/plugin-opener": "^2.4.0",
|
||||
"@tauri-apps/plugin-os": "^2.3.0",
|
||||
@@ -84,7 +84,7 @@
|
||||
"js-md5": "^0.8.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"marked": "^15.0.12",
|
||||
"next": "15.3.3",
|
||||
"next": "15.5.0",
|
||||
"overlayscrollbars": "^2.11.4",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"posthog-js": "^1.246.0",
|
||||
@@ -105,7 +105,7 @@
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^15.4.2",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tauri-apps/cli": "2.7.0",
|
||||
"@tauri-apps/cli": "2.8.1",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
@@ -125,6 +125,7 @@
|
||||
"dotenv-cli": "^7.4.4",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-next": "15.0.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"i18next-scanner": "^4.6.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"mkdirp": "^3.0.1",
|
||||
@@ -137,6 +138,6 @@
|
||||
"typescript": "^5.7.2",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4",
|
||||
"wrangler": "^4.26.0"
|
||||
"wrangler": "^4.31.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
"Search...": "بحث...",
|
||||
"Select Book": "تحديد الكتاب",
|
||||
"Select Books": "تحديد الكتب",
|
||||
"Select Multiple Books": "تحديد عدة كتب",
|
||||
"Sepia": "بني داكن",
|
||||
"Serif Font": "خط مذيل (Serif)",
|
||||
"Show Book Details": "عرض تفاصيل الكتاب",
|
||||
@@ -117,6 +116,7 @@
|
||||
"Book Details": "تفاصيل الكتاب",
|
||||
"From Local File": "من ملف محلي",
|
||||
"TOC": "جدول المحتويات",
|
||||
"Table of Contents": "جدول المحتويات",
|
||||
"Book uploaded: {{title}}": "تم تحميل الكتاب: {{title}}",
|
||||
"Failed to upload book: {{title}}": "فشل تحميل الكتاب: {{title}}",
|
||||
"Book downloaded: {{title}}": "تم تنزيل الكتاب: {{title}}",
|
||||
@@ -344,7 +344,6 @@
|
||||
"Left Margin (px)": "الهامش الأيسر (بكسل)",
|
||||
"Column Gap (%)": "تباعد الأعمدة (%)",
|
||||
"Always Show Status Bar": "دائمًا إظهار شريط الحالة",
|
||||
"Translation Not Available": "الترجمة غير متاحة",
|
||||
"Custom Content CSS": "CSS مخصص للمحتوى",
|
||||
"Enter CSS for book content styling...": "أدخل CSS لتنسيق محتوى الكتاب...",
|
||||
"Custom Reader UI CSS": "CSS مخصص لواجهة القارئ",
|
||||
@@ -459,7 +458,6 @@
|
||||
"Google Books": "كتب جوجل",
|
||||
"Open Library": "المكتبة المفتوحة",
|
||||
"Fiction, Science, History": "خيال، علوم، تاريخ",
|
||||
"Select Cover Image": "اختر صورة الغلاف",
|
||||
"Open Book in New Window": "فتح الكتاب في نافذة جديدة",
|
||||
"Voices for {{lang}}": "الأصوات لـ {{lang}}",
|
||||
"Yandex Translate": "ترجمة Yandex",
|
||||
@@ -495,12 +493,10 @@
|
||||
"Always use latest": "استخدام الأحدث دائمًا",
|
||||
"Send changes only": "إرسال التغييرات فقط",
|
||||
"Receive changes only": "استلام التغييرات فقط",
|
||||
"Disabled": "معطل",
|
||||
"Checksum Method": "طريقة التحقق من الصحة",
|
||||
"File Content (recommended)": "محتوى الملف (موصى به)",
|
||||
"File Name": "اسم الملف",
|
||||
"Device Name": "اسم الجهاز",
|
||||
"Sync Tolerance": "تحمل المزامنة",
|
||||
"Connect to your KOReader Sync server.": "الاتصال بخادم مزامنة KOReader الخاص بك.",
|
||||
"Server URL": "عنوان URL للخادم",
|
||||
"Username": "اسم المستخدم",
|
||||
@@ -519,6 +515,69 @@
|
||||
"Approximately {{percentage}}%": "تقريبًا {{percentage}}%",
|
||||
"Failed to connect": "فشل في الاتصال",
|
||||
"Sync Server Connected": "تم الاتصال بخادم المزامنة",
|
||||
"Precision: {{precision}} digits after the decimal": "الدقة: {{precision}} أرقام بعد الفاصلة",
|
||||
"Sync as {{userDisplayName}}": "المزامنة كـ {{userDisplayName}}"
|
||||
"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": "الحصول على المساعدة من مجتمع ريديست",
|
||||
"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": "تكبير أو استعادة"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
{
|
||||
"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": "পাসওয়ার্ড রিসেট লিঙ্কের জন্য ইমেইল চেক করুন",
|
||||
"Phone number": "ফোন নম্বর",
|
||||
"Your phone number": "আপনার ফোন নম্বর",
|
||||
"Token": "টোকেন",
|
||||
"Your OTP token": "আপনার ওটিপি টোকেন",
|
||||
"Verify token": "টোকেন যাচাই করুন",
|
||||
"Sign in with Google": "গুগল দিয়ে সাইন ইন",
|
||||
"Sign in with Apple": "অ্যাপল দিয়ে সাইন ইন",
|
||||
"Sign in with GitHub": "গিটহাব দিয়ে সাইন ইন",
|
||||
"New Password": "নতুন পাসওয়ার্ড",
|
||||
"Your new password": "আপনার নতুন পাসওয়ার্ড",
|
||||
"Update password": "পাসওয়ার্ড আপডেট করুন",
|
||||
"Updating password ...": "পাসওয়ার্ড আপডেট করা হচ্ছে...",
|
||||
"Your password has been updated": "আপনার পাসওয়ার্ড আপডেট হয়েছে",
|
||||
"Something went wrong. Don't worry, our team has been notified and we're working on a fix.": "কিছু ভুল হয়েছে। চিন্তা করবেন না, আমাদের দল জানানো হয়েছে এবং আমরা সমাধানে কাজ করছি।",
|
||||
"Error Details:": "ত্রুটির বিস্তারিত:",
|
||||
"Try Again": "আবার চেষ্টা করুন",
|
||||
"Go Back": "ফিরে যান",
|
||||
"Your Library": "আপনার লাইব্রেরি",
|
||||
"Need help?": "সাহায্য দরকার?",
|
||||
"Contact Support": "সাপোর্টের সাথে যোগাযোগ",
|
||||
"Open": "খুলুন",
|
||||
"Group": "গ্রুপ",
|
||||
"Details": "বিস্তারিত",
|
||||
"Delete": "মুছুন",
|
||||
"Cancel": "বাতিল",
|
||||
"Confirm Deletion": "মুছে ফেলা নিশ্চিত করুন",
|
||||
"Are you sure to delete {{count}} selected book(s)?_one": "আপনি কি নিশ্চিত যে নির্বাচিত বইটি মুছবেন?",
|
||||
"Are you sure to delete {{count}} selected book(s)?_other": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত বই মুছবেন?",
|
||||
"Deselect Book": "বই অনির্বাচিত করুন",
|
||||
"Select Book": "বই নির্বাচন করুন",
|
||||
"Show Book Details": "বইয়ের বিস্তারিত দেখান",
|
||||
"Download Book": "বই ডাউনলোড করুন",
|
||||
"Upload Book": "বই আপলোড করুন",
|
||||
"Deselect Group": "গ্রুপ অনির্বাচিত করুন",
|
||||
"Select Group": "গ্রুপ নির্বাচন করুন",
|
||||
"Untitled Group": "নামবিহীন গ্রুপ",
|
||||
"Group Books": "বই গ্রুপ করুন",
|
||||
"Remove From Group": "গ্রুপ থেকে সরান",
|
||||
"Create New Group": "নতুন গ্রুপ তৈরি করুন",
|
||||
"Save": "সংরক্ষণ",
|
||||
"Confirm": "নিশ্চিত করুন",
|
||||
"From Local File": "স্থানীয় ফাইল থেকে",
|
||||
"Search in {{count}} Book(s)..._one": "বইটিতে খুঁজুন...",
|
||||
"Search in {{count}} Book(s)..._other": "{{count}}টি বইয়ে খুঁজুন...",
|
||||
"Search Books...": "বই খুঁজুন...",
|
||||
"Clear Search": "খোঁজ পরিষ্কার করুন",
|
||||
"Import Books": "বই আমদানি করুন",
|
||||
"Select Books": "বই নির্বাচন করুন",
|
||||
"Deselect": "অনির্বাচিত করুন",
|
||||
"Select All": "সব নির্বাচন করুন",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}} হিসেবে লগ ইন",
|
||||
"Logged in": "লগ ইন করেছেন",
|
||||
"Account": "অ্যাকাউন্ট",
|
||||
"Sign In": "সাইন ইন",
|
||||
"Auto Upload Books to Cloud": "স্বয়ংক্রিয় ক্লাউডে বই আপলোড",
|
||||
"Auto Import on File Open": "ফাইল খোলার সময় স্বয়ংক্রিয় আমদানি",
|
||||
"Open Last Book on Start": "শুরুতে শেষ বইটি খুলুন",
|
||||
"Check Updates on Start": "শুরুতে আপডেট চেক করুন",
|
||||
"Open Book in New Window": "নতুন উইন্ডোতে বই খুলুন",
|
||||
"Fullscreen": "পূর্ণ স্ক্রিন",
|
||||
"Always on Top": "সবসময় উপরে",
|
||||
"Always Show Status Bar": "সবসময় স্ট্যাটাস বার দেখান",
|
||||
"Keep Screen Awake": "স্ক্রিন জাগিয়ে রাখুন",
|
||||
"Reload Page": "পৃষ্ঠা রিলোড করুন",
|
||||
"Dark Mode": "ডার্ক মোড",
|
||||
"Light Mode": "লাইট মোড",
|
||||
"Auto Mode": "অটো মোড",
|
||||
"Upgrade to Readest Premium": "রিডেস্ট প্রিমিয়ামে আপগ্রেড",
|
||||
"Download Readest": "রিডেস্ট ডাউনলোড",
|
||||
"About Readest": "রিডেস্ট সম্পর্কে",
|
||||
"Help improve Readest": "রিডেস্ট উন্নত করতে সাহায্য করুন",
|
||||
"Sharing anonymized statistics": "বেনামী পরিসংখ্যান শেয়ার করা",
|
||||
"List": "তালিকা",
|
||||
"Grid": "গ্রিড",
|
||||
"Crop": "কাটুন",
|
||||
"Fit": "ফিট",
|
||||
"Title": "শিরোনাম",
|
||||
"Author": "লেখক",
|
||||
"Format": "ফরম্যাট",
|
||||
"Date Read": "পড়ার তারিখ",
|
||||
"Date Added": "যোগ করার তারিখ",
|
||||
"Ascending": "ঊর্ধ্বক্রম",
|
||||
"Descending": "অবতরণ ক্রম",
|
||||
"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.": "এই বই ফরম্যাট সমর্থিত নয়।",
|
||||
"Failed to import book(s): {{filenames}}": "বই আমদানি ব্যর্থ: {{filenames}}",
|
||||
"Book uploaded: {{title}}": "বই আপলোড হয়েছে: {{title}}",
|
||||
"Insufficient storage quota": "অপর্যাপ্ত স্টোরেজ কোটা",
|
||||
"Failed to upload book: {{title}}": "বই আপলোড ব্যর্থ: {{title}}",
|
||||
"Book downloaded: {{title}}": "বই ডাউনলোড হয়েছে: {{title}}",
|
||||
"Failed to download book: {{title}}": "বই ডাউনলোড ব্যর্থ: {{title}}",
|
||||
"Book deleted: {{title}}": "বই মুছে ফেলা হয়েছে: {{title}}",
|
||||
"Deleted cloud backup of the book: {{title}}": "বইয়ের ক্লাউড ব্যাকআপ মুছে ফেলা হয়েছে: {{title}}",
|
||||
"Deleted local copy of the book: {{title}}": "বইয়ের স্থানীয় কপি মুছে ফেলা হয়েছে: {{title}}",
|
||||
"Failed to delete book: {{title}}": "বই মুছতে ব্যর্থ: {{title}}",
|
||||
"Failed to delete cloud backup of the book: {{title}}": "বইয়ের ক্লাউড ব্যাকআপ মুছতে ব্যর্থ: {{title}}",
|
||||
"Failed to delete local copy of the book: {{title}}": "বইয়ের স্থানীয় কপি মুছতে ব্যর্থ: {{title}}",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "আপনার লাইব্রেরিতে স্বাগতম। আপনি এখানে বই আমদানি করে যেকোনো সময় পড়তে পারেন।",
|
||||
"Copied to notebook": "নোটবুকে কপি করা হয়েছে",
|
||||
"No annotations to export": "রপ্তানি করার জন্য কোন টীকা নেই",
|
||||
"Exported from Readest": "রিডেস্ট থেকে রপ্তানি করা হয়েছে",
|
||||
"Highlights & Annotations": "হাইলাইট ও টীকা",
|
||||
"Untitled": "নামবিহীন",
|
||||
"Note": "নোট",
|
||||
"Copied to clipboard": "ক্লিপবোর্ডে কপি করা হয়েছে",
|
||||
"Copy": "কপি",
|
||||
"Delete Highlight": "হাইলাইট মুছুন",
|
||||
"Highlight": "হাইলাইট",
|
||||
"Annotate": "টীকা",
|
||||
"Search": "খুঁজুন",
|
||||
"Dictionary": "অভিধান",
|
||||
"Wikipedia": "উইকিপিডিয়া",
|
||||
"Translate": "অনুবাদ",
|
||||
"Speak": "বলুন",
|
||||
"Login Required": "লগইন প্রয়োজন",
|
||||
"Quota Exceeded": "কোটা অতিক্রম",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "অনুবাদ আনতে পারছি না। প্রথমে লগইন করুন এবং আবার চেষ্টা করুন।",
|
||||
"Unable to fetch the translation. Try again later.": "অনুবাদ আনতে পারছি না। পরে আবার চেষ্টা করুন।",
|
||||
"Original Text": "মূল টেক্সট",
|
||||
"Auto Detect": "স্বয়ংক্রিয় শনাক্তকরণ",
|
||||
"(detected)": "(শনাক্ত)",
|
||||
"Translated Text": "অনূদিত টেক্সট",
|
||||
"System Language": "সিস্টেম ভাষা",
|
||||
"Loading...": "লোড হচ্ছে...",
|
||||
"No translation available.": "কোন অনুবাদ উপলব্ধ নয়।",
|
||||
"Translated by {{provider}}.": "{{provider}} দ্বারা অনূদিত।",
|
||||
"Bookmark": "বুকমার্ক",
|
||||
"Next Section": "পরবর্তী বিভাগ",
|
||||
"Previous Section": "পূর্ববর্তী বিভাগ",
|
||||
"Next Page": "পরবর্তী পৃষ্ঠা",
|
||||
"Previous Page": "পূর্ববর্তী পৃষ্ঠা",
|
||||
"Go Forward": "এগিয়ে যান",
|
||||
"Small": "ছোট",
|
||||
"Large": "বড়",
|
||||
"Sync Conflict": "সিঙ্ক দ্বন্দ্ব",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "\"{{deviceName}}\" থেকে পড়ার অগ্রগতি সিঙ্ক করবেন?",
|
||||
"another device": "অন্য ডিভাইস",
|
||||
"Local Progress": "স্থানীয় অগ্রগতি",
|
||||
"Remote Progress": "দূরবর্তী অগ্রগতি",
|
||||
"Failed to connect": "সংযোগ ব্যর্থ",
|
||||
"Disconnected": "সংযোগ বিচ্ছিন্ন",
|
||||
"KOReader Sync Settings": "KOReader সিঙ্ক সেটিংস",
|
||||
"Sync as {{userDisplayName}}": "{{userDisplayName}} হিসেবে সিঙ্ক",
|
||||
"Sync Server Connected": "সিঙ্ক সার্ভার সংযুক্ত",
|
||||
"Sync Strategy": "সিঙ্ক কৌশল",
|
||||
"Ask on conflict": "দ্বন্দ্বে জিজ্ঞাসা করুন",
|
||||
"Always use latest": "সবসময় সর্বশেষ ব্যবহার করুন",
|
||||
"Send changes only": "শুধু পরিবর্তন পাঠান",
|
||||
"Receive changes only": "শুধু পরিবর্তন গ্রহণ করুন",
|
||||
"Checksum Method": "চেকসাম পদ্ধতি",
|
||||
"File Content (recommended)": "ফাইল কন্টেন্ট (সুপারিশকৃত)",
|
||||
"File Name": "ফাইলের নাম",
|
||||
"Device Name": "ডিভাইসের নাম",
|
||||
"Connect to your KOReader Sync server.": "আপনার KOReader সিঙ্ক সার্ভারে সংযোগ করুন।",
|
||||
"Server URL": "সার্ভার URL",
|
||||
"Username": "ইউজারনেম",
|
||||
"Your Username": "আপনার ইউজারনেম",
|
||||
"Password": "পাসওয়ার্ড",
|
||||
"Connect": "সংযোগ",
|
||||
"Notebook": "নোটবুক",
|
||||
"No notes match your search": "আপনার খোঁজের সাথে কোন নোট মিলছে না",
|
||||
"Excerpts": "উদ্ধৃতি",
|
||||
"Notes": "নোট",
|
||||
"Add your notes here...": "এখানে আপনার নোট যোগ করুন...",
|
||||
"Search notes and excerpts...": "নোট ও উদ্ধৃতি খুঁজুন...",
|
||||
"{{time}} min left in chapter": "অধ্যায়ে {{time}} মিনিট বাকি",
|
||||
"{{count}} pages left in chapter_one": "অধ্যায়ে ১ পৃষ্ঠা বাকি",
|
||||
"{{count}} pages left in chapter_other": "অধ্যায়ে {{count}} পৃষ্ঠা বাকি",
|
||||
"Theme Mode": "থিম মোড",
|
||||
"Invert Image In Dark Mode": "ডার্ক মোডে ছবি উল্টান",
|
||||
"Override Book Color": "বইয়ের রঙ পরিবর্তন",
|
||||
"Theme Color": "থিম রঙ",
|
||||
"Custom": "কাস্টম",
|
||||
"Code Highlighting": "কোড হাইলাইটিং",
|
||||
"Enable Highlighting": "হাইলাইটিং সক্রিয়",
|
||||
"Code Language": "কোড ভাষা",
|
||||
"Auto": "অটো",
|
||||
"Scroll": "স্ক্রল",
|
||||
"Scrolled Mode": "স্ক্রল মোড",
|
||||
"Continuous Scroll": "ক্রমাগত স্ক্রল",
|
||||
"Overlap Pixels": "ওভারল্যাপ পিক্সেল",
|
||||
"Click": "ক্লিক",
|
||||
"Clicks for Page Flip": "পৃষ্ঠা উল্টানোর জন্য ক্লিক",
|
||||
"Swap Clicks Area": "ক্লিক এরিয়া অদলবদল",
|
||||
"Disable Double Click": "ডাবল ক্লিক নিষ্ক্রিয়",
|
||||
"Volume Keys for Page Flip": "পৃষ্ঠা উল্টানোর জন্য ভলিউম কী",
|
||||
"Animation": "অ্যানিমেশন",
|
||||
"Paging Animation": "পৃষ্ঠা অ্যানিমেশন",
|
||||
"Security": "নিরাপত্তা",
|
||||
"Allow JavaScript": "জাভাস্ক্রিপ্ট অনুমতি",
|
||||
"Enable only if you trust the file.": "শুধুমাত্র ফাইলে বিশ্বাস থাকলে সক্রিয় করুন।",
|
||||
"Font": "ফন্ট",
|
||||
"Custom Fonts": "কাস্টম ফন্ট",
|
||||
"Cancel Delete": "মুছা বাতিল",
|
||||
"Delete Font": "ফন্ট মুছুন",
|
||||
"Import Font": "ফন্ট আমদানি",
|
||||
"Tips": "টিপস",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "সমর্থিত ফন্ট ফরম্যাট: .ttf, .otf, .woff, .woff2",
|
||||
"Custom fonts can be selected from the Font Face menu": "কাস্টম ফন্ট Font Face মেনু থেকে নির্বাচন করা যায়",
|
||||
"Global Settings": "গ্লোবাল সেটিংস",
|
||||
"Apply to All Books": "সব বইয়ে প্রয়োগ",
|
||||
"Apply to This Book": "এই বইয়ে প্রয়োগ",
|
||||
"Reset Settings": "সেটিংস রিসেট",
|
||||
"Manage Custom Fonts": "কাস্টম ফন্ট ব্যবস্থাপনা",
|
||||
"System Fonts": "সিস্টেম ফন্ট",
|
||||
"Serif Font": "সেরিফ ফন্ট",
|
||||
"Sans-Serif Font": "স্যান্স-সেরিফ ফন্ট",
|
||||
"Override Book Font": "বইয়ের ফন্ট পরিবর্তন",
|
||||
"Font Size": "ফন্ট সাইজ",
|
||||
"Default Font Size": "ডিফল্ট ফন্ট সাইজ",
|
||||
"Minimum Font Size": "সর্বনিম্ন ফন্ট সাইজ",
|
||||
"Font Weight": "ফন্ট ওয়েট",
|
||||
"Font Family": "ফন্ট ফ্যামিলি",
|
||||
"Default Font": "ডিফল্ট ফন্ট",
|
||||
"CJK Font": "CJK ফন্ট",
|
||||
"Font Face": "ফন্ট ফেস",
|
||||
"Monospace Font": "মনোস্পেস ফন্ট",
|
||||
"Language": "ভাষা",
|
||||
"Interface Language": "ইন্টারফেস ভাষা",
|
||||
"Translation": "অনুবাদ",
|
||||
"Enable Translation": "অনুবাদ সক্রিয়",
|
||||
"Show Source Text": "উৎস টেক্সট দেখান",
|
||||
"Translation Service": "অনুবাদ সেবা",
|
||||
"Translate To": "অনুবাদ করুন",
|
||||
"Override Book Layout": "বইয়ের লেআউট পরিবর্তন",
|
||||
"Writing Mode": "লেখার মোড",
|
||||
"Default": "ডিফল্ট",
|
||||
"Horizontal Direction": "অনুভূমিক দিক",
|
||||
"Vertical Direction": "উল্লম্ব দিক",
|
||||
"RTL Direction": "RTL দিক",
|
||||
"Border Frame": "বর্ডার ফ্রেম",
|
||||
"Double Border": "ডাবল বর্ডার",
|
||||
"Border Color": "বর্ডার রঙ",
|
||||
"Paragraph": "অনুচ্ছেদ",
|
||||
"Paragraph Margin": "অনুচ্ছেদ মার্জিন",
|
||||
"Line Spacing": "লাইন স্পেসিং",
|
||||
"Word Spacing": "শব্দ স্পেসিং",
|
||||
"Letter Spacing": "অক্ষর স্পেসিং",
|
||||
"Text Indent": "টেক্সট ইনডেন্ট",
|
||||
"Full Justification": "পূর্ণ সারিবদ্ধতা",
|
||||
"Hyphenation": "হাইফেনেশন",
|
||||
"Page": "পৃষ্ঠা",
|
||||
"Top Margin (px)": "উপরের মার্জিন (px)",
|
||||
"Bottom Margin (px)": "নিচের মার্জিন (px)",
|
||||
"Left Margin (px)": "বাম মার্জিন (px)",
|
||||
"Right Margin (px)": "ডান মার্জিন (px)",
|
||||
"Column Gap (%)": "কলাম গ্যাপ (%)",
|
||||
"Maximum Number of Columns": "সর্বোচ্চ কলাম সংখ্যা",
|
||||
"Maximum Column Height": "সর্বোচ্চ কলাম উচ্চতা",
|
||||
"Maximum Column Width": "সর্বোচ্চ কলাম প্রস্থ",
|
||||
"Header & Footer": "হেডার ও ফুটার",
|
||||
"Show Header": "হেডার দেখান",
|
||||
"Show Footer": "ফুটার দেখান",
|
||||
"Show Remaining Time": "বাকি সময় দেখান",
|
||||
"Show Remaining Pages": "বাকি পৃষ্ঠা দেখান",
|
||||
"Show Reading Progress": "পড়ার অগ্রগতি দেখান",
|
||||
"Reading Progress Style": "পড়ার অগ্রগতির স্টাইল",
|
||||
"Page Number": "পৃষ্ঠা নম্বর",
|
||||
"Percentage": "শতাংশ",
|
||||
"Apply also in Scrolled Mode": "স্ক্রল মোডেও প্রয়োগ করুন",
|
||||
"Screen": "স্ক্রিন",
|
||||
"Orientation": "দিকবিন্যাস",
|
||||
"Portrait": "পোর্ট্রেট",
|
||||
"Landscape": "ল্যান্ডস্কেপ",
|
||||
"Apply": "প্রয়োগ",
|
||||
"Custom Content CSS": "কাস্টম কন্টেন্ট CSS",
|
||||
"Enter CSS for book content styling...": "বই কন্টেন্ট স্টাইলিংয়ের জন্য CSS লিখুন...",
|
||||
"Custom Reader UI CSS": "কাস্টম রিডার UI CSS",
|
||||
"Enter CSS for reader interface styling...": "রিডার ইন্টারফেস স্টাইলিংয়ের জন্য CSS লিখুন...",
|
||||
"Layout": "লেআউট",
|
||||
"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— উইলিয়াম শেক্সপিয়র",
|
||||
"(from 'As You Like It', Act II)": "('অ্যাজ ইউ লাইক ইট', দ্বিতীয় অঙ্ক থেকে)",
|
||||
"Custom Theme": "কাস্টম থিম",
|
||||
"Theme Name": "থিমের নাম",
|
||||
"Text Color": "টেক্সট রঙ",
|
||||
"Background Color": "ব্যাকগ্রাউন্ড রঙ",
|
||||
"Link Color": "লিঙ্ক রঙ",
|
||||
"Preview": "প্রিভিউ",
|
||||
"Font & Layout": "ফন্ট ও লেআউট",
|
||||
"Book Cover": "বইয়ের মলাট",
|
||||
"More Info": "আরো তথ্য",
|
||||
"Parallel Read": "সমান্তরাল পড়া",
|
||||
"Disable": "নিষ্ক্রিয়",
|
||||
"Enable": "সক্রিয়",
|
||||
"KOReader Sync": "KOReader সিঙ্ক",
|
||||
"Push Progress": "অগ্রগতি পুশ",
|
||||
"Pull Progress": "অগ্রগতি পুল",
|
||||
"Export Annotations": "টীকা রপ্তানি",
|
||||
"Sort TOC by Page": "পৃষ্ঠা অনুযায়ী TOC সাজান",
|
||||
"Edit": "সম্পাদনা",
|
||||
"Search...": "খুঁজুন...",
|
||||
"Book": "বই",
|
||||
"Chapter": "অধ্যায়",
|
||||
"Match Case": "কেস মিলান",
|
||||
"Match Whole Words": "পূর্ণ শব্দ মিলান",
|
||||
"Match Diacritics": "ডায়াক্রিটিক্স মিলান",
|
||||
"TOC": "সূচিপত্র",
|
||||
"Table of Contents": "সূচিপত্র",
|
||||
"Sidebar": "সাইডবার",
|
||||
"Disable Translation": "অনুবাদ নিষ্ক্রিয়",
|
||||
"TTS not supported for PDF": "PDF এর জন্য TTS সমর্থিত নয়",
|
||||
"TTS not supported for this document": "এই ডকুমেন্টের জন্য TTS সমর্থিত নয়",
|
||||
"No Timeout": "কোন টাইমআউট নেই",
|
||||
"{{value}} minute": "{{value}} মিনিট",
|
||||
"{{value}} minutes": "{{value}} মিনিট",
|
||||
"{{value}} hour": "{{value}} ঘন্টা",
|
||||
"{{value}} hours": "{{value}} ঘন্টা",
|
||||
"Voices for {{lang}}": "{{lang}} এর জন্য কণ্ঠস্বর",
|
||||
"Slow": "ধীর",
|
||||
"Fast": "দ্রুত",
|
||||
"{{engine}}: {{count}} voices_one": "{{engine}}: ১টি কণ্ঠস্বর",
|
||||
"{{engine}}: {{count}} voices_other": "{{engine}}: {{count}}টি কণ্ঠস্বর",
|
||||
"Sign in to Sync": "সিঙ্কের জন্য সাইন ইন",
|
||||
"Synced at {{time}}": "{{time}} এ সিঙ্ক হয়েছে",
|
||||
"Never synced": "কখনো সিঙ্ক হয়নি",
|
||||
"Reading Progress Synced": "পড়ার অগ্রগতি সিঙ্ক হয়েছে",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "{{total}} এর {{page}} পৃষ্ঠা ({{percentage}}%)",
|
||||
"Current position": "বর্তমান অবস্থান",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "প্রায় {{total}} এর {{page}} পৃষ্ঠা ({{percentage}}%)",
|
||||
"Approximately {{percentage}}%": "প্রায় {{percentage}}%",
|
||||
"Delete Your Account?": "আপনার অ্যাকাউন্ট মুছবেন?",
|
||||
"This action cannot be undone. All your data in the cloud will be permanently deleted.": "এই কাজ পূর্বাবস্থায় ফেরানো যাবে না। ক্লাউডে আপনার সব ডেটা স্থায়ীভাবে মুছে যাবে।",
|
||||
"Delete Permanently": "স্থায়ীভাবে মুছুন",
|
||||
"Restore Purchase": "ক্রয় পুনরুদ্ধার",
|
||||
"Manage Subscription": "সাবস্ক্রিপশন পরিচালনা",
|
||||
"Reset Password": "পাসওয়ার্ড রিসেট",
|
||||
"Sign Out": "সাইন আউট",
|
||||
"Delete Account": "অ্যাকাউন্ট মুছুন",
|
||||
"Upgrade to {{plan}}": "{{plan}} এ আপগ্রেড",
|
||||
"Complete Your Subscription": "আপনার সাবস্ক্রিপশন সম্পূর্ণ করুন",
|
||||
"Coming Soon": "শীঘ্রই আসছে",
|
||||
"Upgrade to Plus or Pro": "প্লাস বা প্রো তে আপগ্রেড",
|
||||
"Current Plan": "বর্তমান প্ল্যান",
|
||||
"Plan Limits": "প্ল্যান সীমা",
|
||||
"Failed to delete user. Please try again later.": "ইউজার মুছতে ব্যর্থ। পরে আবার চেষ্টা করুন।",
|
||||
"Failed to create checkout session": "চেকআউট সেশন তৈরি করতে ব্যর্থ",
|
||||
"No purchases found to restore.": "পুনরুদ্ধার করার জন্য কোন ক্রয় পাওয়া যায়নি।",
|
||||
"Failed to restore purchases.": "ক্রয় পুনরুদ্ধার করতে ব্যর্থ।",
|
||||
"Failed to manage subscription.": "সাবস্ক্রিপশন পরিচালনা করতে ব্যর্থ।",
|
||||
"Failed to load subscription plans.": "সাবস্ক্রিপশন প্ল্যান লোড করতে ব্যর্থ।",
|
||||
"Loading profile...": "প্রোফাইল লোড হচ্ছে...",
|
||||
"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:": "পরিমাণ:",
|
||||
"Subscription ID:": "সাবস্ক্রিপশন ID:",
|
||||
"Go to Library": "লাইব্রেরিতে যান",
|
||||
"Need help? Contact our support team at support@readest.com": "সাহায্য দরকার? support@readest.com এ আমাদের সাপোর্ট টিমের সাথে যোগাযোগ করুন",
|
||||
"Free Plan": "ফ্রি প্ল্যান",
|
||||
"month": "মাস",
|
||||
"year": "বছর",
|
||||
"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": "AI পড়ে শোনানো",
|
||||
"Enjoy hands-free reading with natural-sounding AI voices that bring your books to life.": "প্রাকৃতিক শোনা AI কণ্ঠস্বর দিয়ে হ্যান্ডস-ফ্রি পড়া উপভোগ করুন যা আপনার বইগুলোকে জীবন্ত করে তোলে।",
|
||||
"AI Translations": "AI অনুবাদ",
|
||||
"Translate any text instantly with the power of Google, Azure, or DeepL—understand content in any language.": "Google, Azure, বা DeepL এর শক্তি দিয়ে যেকোনো টেক্সট তাৎক্ষণিক অনুবাদ করুন—যেকোনো ভাষায় কন্টেন্ট বুঝুন।",
|
||||
"Community Support": "কমিউনিটি সাপোর্ট",
|
||||
"Connect with fellow readers and get help fast in our friendly community channels.": "সহ পাঠকদের সাথে যুক্ত হোন এবং আমাদের বন্ধুত্বপূর্ণ কমিউনিটি চ্যানেলে দ্রুত সাহায্য পান।",
|
||||
"Cloud Sync Storage": "ক্লাউড সিঙ্ক স্টোরেজ",
|
||||
"AI Translations (per day)": "AI অনুবাদ (প্রতিদিন)",
|
||||
"Plus Plan": "প্লাস প্ল্যান",
|
||||
"Includes All Free Plan Benefits": "সব ফ্রি প্ল্যান সুবিধা অন্তর্ভুক্ত",
|
||||
"Unlimited AI Read Aloud Hours": "সীমাহীন AI পড়ে শোনানোর সময়",
|
||||
"Listen without limits—convert as much text as you like into immersive audio.": "সীমা ছাড়াই শুনুন—যত খুশি টেক্সটকে নিমজ্জনকারী অডিওতে রূপান্তর করুন।",
|
||||
"More AI Translations": "আরো AI অনুবাদ",
|
||||
"Unlock enhanced translation capabilities with more daily usage and advanced options.": "আরো দৈনিক ব্যবহার এবং উন্নত অপশন দিয়ে উন্নত অনুবাদ ক্ষমতা আনলক করুন।",
|
||||
"DeepL Pro Access": "DeepL Pro অ্যাক্সেস",
|
||||
"Translate up to 100,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.": "৫ GB ক্লাউড স্টোরেজ দিয়ে আপনার সম্পূর্ণ পড়ার সংগ্রহ নিরাপদে সংরক্ষণ এবং অ্যাক্সেস করুন।",
|
||||
"Priority Support": "অগ্রাধিকার সাপোর্ট",
|
||||
"Enjoy faster responses and dedicated assistance whenever you need help.": "যখনই সাহায্যের প্রয়োজন হয় দ্রুত প্রতিক্রিয়া এবং নিবেদিত সহায়তা উপভোগ করুন।",
|
||||
"Pro Plan": "প্রো প্ল্যান",
|
||||
"Includes All Plus Plan Benefits": "সব প্লাস প্ল্যান সুবিধা অন্তর্ভুক্ত",
|
||||
"Early Feature Access": "আর্লি ফিচার অ্যাক্সেস",
|
||||
"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 500,000 characters daily with the most accurate translation engine available.": "উপলব্ধ সবচেয়ে নির্ভুল অনুবাদ ইঞ্জিন দিয়ে দৈনিক ৫,০০,০০০ অক্ষর পর্যন্ত অনুবাদ করুন।",
|
||||
"Securely store and access your entire reading collection with up to 20 GB of cloud storage.": "২০ GB ক্লাউড স্টোরেজ দিয়ে আপনার সম্পূর্ণ পড়ার সংগ্রহ নিরাপদে সংরক্ষণ এবং অ্যাক্সেস করুন।",
|
||||
"Version {{version}}": "সংস্করণ {{version}}",
|
||||
"Check Update": "আপডেট চেক করুন",
|
||||
"Already the latest version": "ইতিমধ্যে সর্বশেষ সংস্করণ",
|
||||
"Checking for updates...": "আপডেট চেক করা হচ্ছে...",
|
||||
"Error checking for updates": "আপডেট চেক করতে ত্রুটি",
|
||||
"Drop to Import Books": "বই আমদানি করতে ড্রপ করুন",
|
||||
"Terms of Service": "সেবার শর্তাবলী",
|
||||
"Privacy Policy": "গোপনীয়তা নীতি",
|
||||
"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": "সিরিজের মোট বই সংখ্যা লিখুন",
|
||||
"Publisher": "প্রকাশক",
|
||||
"Enter publisher": "প্রকাশক লিখুন",
|
||||
"Publication Date": "প্রকাশনার তারিখ",
|
||||
"YYYY or YYYY-MM-DD": "YYYY বা YYYY-MM-DD",
|
||||
"Identifier": "শনাক্তকারী",
|
||||
"Subjects": "বিষয়",
|
||||
"Fiction, Science, History": "কল্পকাহিনী, বিজ্ঞান, ইতিহাস",
|
||||
"Description": "বিবরণ",
|
||||
"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": "রিসেট",
|
||||
"Are you sure to delete the selected book?": "আপনি কি নিশ্চিত যে নির্বাচিত বইটি মুছবেন?",
|
||||
"Are you sure to delete the cloud backup of the selected book?": "আপনি কি নিশ্চিত যে নির্বাচিত বইয়ের ক্লাউড ব্যাকআপ মুছবেন?",
|
||||
"Are you sure to delete the local copy of the selected book?": "আপনি কি নিশ্চিত যে নির্বাচিত বইয়ের স্থানীয় কপি মুছবেন?",
|
||||
"Edit Metadata": "মেটাডেটা সম্পাদনা",
|
||||
"Book Details": "বইয়ের বিস্তারিত",
|
||||
"Unknown": "অজানা",
|
||||
"Remove from Cloud & Device": "ক্লাউড ও ডিভাইস থেকে সরান",
|
||||
"Remove from Cloud Only": "শুধু ক্লাউড থেকে সরান",
|
||||
"Remove from Device Only": "শুধু ডিভাইস থেকে সরান",
|
||||
"Published": "প্রকাশিত",
|
||||
"Updated": "আপডেট হয়েছে",
|
||||
"Added": "যোগ করা হয়েছে",
|
||||
"File Size": "ফাইল সাইজ",
|
||||
"No description available": "কোন বিবরণ উপলব্ধ নয়",
|
||||
"Locked": "লক করা",
|
||||
"Select Metadata Source": "মেটাডেটা সোর্স নির্বাচন",
|
||||
"Keep manual input": "ম্যানুয়াল ইনপুট রাখুন",
|
||||
"A new version of Readest is available!": "রিডেস্টের একটি নতুন সংস্করণ উপলব্ধ!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "রিডেস্ট {{newVersion}} উপলব্ধ (ইনস্টল করা সংস্করণ {{currentVersion}})।",
|
||||
"Download and install now?": "এখনই ডাউনলোড এবং ইনস্টল করবেন?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "{{contentLength}} এর {{downloaded}} ডাউনলোড হচ্ছে",
|
||||
"Download finished": "ডাউনলোড সম্পূর্ণ",
|
||||
"DOWNLOAD & INSTALL": "ডাউনলোড ও ইনস্টল",
|
||||
"Changelog": "পরিবর্তন লগ",
|
||||
"Software Update": "সফটওয়্যার আপডেট",
|
||||
"What's New in Readest": "রিডেস্টে নতুন কী",
|
||||
"Select Files": "ফাইল নির্বাচন",
|
||||
"Select Image": "ছবি নির্বাচন",
|
||||
"Select Video": "ভিডিও নির্বাচন",
|
||||
"Select Audio": "অডিও নির্বাচন",
|
||||
"Select Fonts": "ফন্ট নির্বাচন",
|
||||
"Storage": "স্টোরেজ",
|
||||
"{{percentage}}% of Cloud Sync Space Used.": "ক্লাউড সিঙ্ক স্পেসের {{percentage}}% ব্যবহৃত।",
|
||||
"Translation Characters": "অনুবাদ অক্ষর",
|
||||
"{{percentage}}% of Daily Translation Characters Used.": "দৈনিক অনুবাদ অক্ষরের {{percentage}}% ব্যবহৃত।",
|
||||
"Daily translation quota reached. Upgrade your plan to continue using AI translations.": "দৈনিক অনুবাদ কোটা শেষ। 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": "গুগল বুকস",
|
||||
"Open Library": "ওপেন লাইব্রেরি",
|
||||
"Azure Translator": "Azure অনুবাদক",
|
||||
"DeepL": "DeepL",
|
||||
"Google Translate": "গুগল ট্রান্সলেট",
|
||||
"Yandex Translate": "ইয়ান্ডেক্স ট্রান্সলেট",
|
||||
"Gray": "ধূসর",
|
||||
"Sepia": "সেপিয়া",
|
||||
"Grass": "ঘাস",
|
||||
"Cherry": "চেরি",
|
||||
"Sky": "আকাশ",
|
||||
"Solarized": "সোলারাইজড",
|
||||
"Gruvbox": "গ্রুভবক্স",
|
||||
"Nord": "নর্ড",
|
||||
"Contrast": "কন্ট্রাস্ট",
|
||||
"Sunset": "সূর্যাস্ত",
|
||||
"Reveal in Finder": "ফাইন্ডারে দেখান",
|
||||
"Reveal in File Explorer": "ফাইল এক্সপ্লোরারে দেখান",
|
||||
"Reveal in Folder": "ফোল্ডারে দেখান",
|
||||
"Previous Paragraph": "পূর্ববর্তী অনুচ্ছেদ",
|
||||
"Previous Sentence": "পূর্ববর্তী বাক্য",
|
||||
"Pause": "বিরতি",
|
||||
"Play": "চালান",
|
||||
"Next Sentence": "পরবর্তী বাক্য",
|
||||
"Next Paragraph": "পরবর্তী অনুচ্ছেদ",
|
||||
"Separate Cover Page": "আলাদা কভার পৃষ্ঠা",
|
||||
"Resize Notebook": "নোটবুকের আকার পরিবর্তন",
|
||||
"Resize Sidebar": "সাইডবারের আকার পরিবর্তন",
|
||||
"Get Help from the Readest Community": "রিডেস্ট কমিউনিটি থেকে সাহায্য নিন",
|
||||
"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": "বৃহৎ করুন বা পুনরুদ্ধার করুন"
|
||||
}
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "འཚོལ་བཤེར།...",
|
||||
"Select Book": "དཔེ་དེབ་འདེམས་པ།",
|
||||
"Select Books": "དཔེ་དེབ་འདེམས་པ།",
|
||||
"Select Multiple Books": "དཔེ་དེབ་མང་པོ་འདེམས་པ།",
|
||||
"Sepia": "རྙིང་པའི་ཉམས་འགྱུར།",
|
||||
"Serif Font": "མཐའ་ཐིག་ཡོད་པའི་ཡིག་གཟུགས།",
|
||||
"Show Book Details": "དཔེ་དེབ་ཀྱི་གནས་ཚུལ་རྒྱས་པ་མངོན་པ།",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "དཔེ་དེབ་ཀྱི་གནས་ཚུལ་རྒྱས་པ།",
|
||||
"From Local File": "ས་གནས་ཀྱི་ཡིག་ཆ་ནས་ནང་འདྲེན།",
|
||||
"TOC": "དཀར་ཆག",
|
||||
"Table of Contents": "དཀར་ཆག",
|
||||
"Book uploaded: {{title}}": "དཔེ་དེབ་སྤྲད་ཟིན། {{title}}",
|
||||
"Failed to upload book: {{title}}": "དཔེ་དེབ་སྤྲད་མ་ཐུབ། {{title}}",
|
||||
"Book downloaded: {{title}}": "དཔེ་དེབ་ཕབ་ལེན་བྱས་ཟིན། {{title}}",
|
||||
@@ -329,7 +329,6 @@
|
||||
"Left Margin (px)": "གཡོན་ངོས་ཀྱི་བར་ཐག",
|
||||
"Column Gap (%)": "གྲལ་ཐིག་བར་གྱི་བར་ཐག",
|
||||
"Always Show Status Bar": "རྟག་ཏུ་གནས་ཚུལ་གྱི་གྲལ་ཐིག་མངོན་པ།",
|
||||
"Translation Not Available": "སྒྱུར་བཅོས་སྤྱོད་ཐུབ་མེད།",
|
||||
"Custom Content CSS": "རང་འགུལ་གྱི་ནང་དོན་ CSS",
|
||||
"Enter CSS for book content styling...": "དཔེ་དེབ་ཀྱི་ནང་དོན་གྱི་རྣམ་པའི་ CSS ནང་འཇུག་བྱོས།...",
|
||||
"Custom Reader UI CSS": "རང་འགུལ་གྱི་མཐུད་ངོས་ CSS",
|
||||
@@ -438,7 +437,6 @@
|
||||
"Google Books": "Google དཔེ་མཛོད།",
|
||||
"Open Library": "སྒོ་འབྱེད་དཔེ་ཁང་།",
|
||||
"Fiction, Science, History": "སྒྲུང་གཏམ། ཚན་རིག ལོ་རྒྱུས།",
|
||||
"Select Cover Image": "སྟོང་ངོས་ཀྱི་པར་རིས་འདེམས་པ།",
|
||||
"Open Book in New Window": "སྒེའུ་ཁུང་གསར་པའི་ནང་དཔེ་དེབ་ཁ་ཕྱེ་བ།",
|
||||
"Voices for {{lang}}": "{{lang}} གྱི་སྒྲ་སྐད།",
|
||||
"Yandex Translate": "Yandex སྒྱུར་བཅོས།",
|
||||
@@ -477,13 +475,10 @@
|
||||
"Always use latest": "དེ་ལས་འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Send changes only": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Receive changes only": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Disabled": "བཀག་པ།",
|
||||
"Checksum Method": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"File Content (recommended)": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"File Name": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"Device Name": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"Sync Tolerance": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"Precision: {{precision}} digits after the decimal": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"Connect to your KOReader Sync server.": "ཁྱེད་ཀྱིས KOReader སྤྲི་དོན་སང་བཞིན་འདེམས་པ།",
|
||||
"Server URL": "སང་བཞིན་འདེམས་པའི URL",
|
||||
"Username": "ཁྱེད་ཀྱིས་འབྱུང་ཁུངས་འདེམས་པ།",
|
||||
@@ -498,7 +493,71 @@
|
||||
"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": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "ཤོག་ {{page}} དང་ {{total}} ({{percentage}}%)",
|
||||
"Current position": "ད་ལྟ་བཞིན་འདེམས་པ།",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "ད་ལྟ་བཞིན་འདེམས་པ།",
|
||||
"Approximately {{percentage}}%": "ད་ལྟ་བཞིན་འདེམས་པ།"
|
||||
"Current position": "ད་ལྟའི་གནས་ས།",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "ཕྲན་བུ་ཤོག་ངོས་ {{page}} / {{total}} ({{percentage}}%)",
|
||||
"Approximately {{percentage}}%": "ཕྲན་བུ་ {{percentage}}%",
|
||||
"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": "བཀྲ་ཤིས་བཏང་བ།"
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
"Search...": "Suchen...",
|
||||
"Select Book": "Buch auswählen",
|
||||
"Select Books": "Bücher auswählen",
|
||||
"Select Multiple Books": "Mehrere Bücher auswählen",
|
||||
"Sepia": "Sepia",
|
||||
"Serif Font": "Serifenschrift",
|
||||
"Show Book Details": "Buchdetails anzeigen",
|
||||
@@ -117,6 +116,7 @@
|
||||
"Book Details": "Buchdetails",
|
||||
"From Local File": "Aus lokaler Datei",
|
||||
"TOC": "Inhaltsverzeichnis",
|
||||
"Table of Contents": "Inhaltsverzeichnis",
|
||||
"Book uploaded: {{title}}": "Buch hochgeladen: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Fehler beim Hochladen des Buches: {{title}}",
|
||||
"Book downloaded: {{title}}": "Buch heruntergeladen: {{title}}",
|
||||
@@ -332,7 +332,6 @@
|
||||
"Left Margin (px)": "Linker Rand (px)",
|
||||
"Column Gap (%)": "Spaltenabstand (%)",
|
||||
"Always Show Status Bar": "Statusleiste immer anzeigen",
|
||||
"Translation Not Available": "Übersetzung nicht verfügbar",
|
||||
"Custom Content CSS": "Benutzerdefiniertes Inhalts-CSS",
|
||||
"Enter CSS for book content styling...": "CSS für die Buchinhaltsgestaltung eingeben...",
|
||||
"Custom Reader UI CSS": "Benutzerdefiniertes CSS für die Leseroberfläche",
|
||||
@@ -443,7 +442,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Belletristik, Wissenschaft, Geschichte",
|
||||
"Select Cover Image": "Coverbild auswählen",
|
||||
"Open Book in New Window": "Buch in neuem Fenster öffnen",
|
||||
"Voices for {{lang}}": "Stimmen für {{lang}}",
|
||||
"Yandex Translate": "Yandex Übersetzer",
|
||||
@@ -479,12 +477,10 @@
|
||||
"Always use latest": "Immer die neueste Version verwenden",
|
||||
"Send changes only": "Nur Änderungen senden",
|
||||
"Receive changes only": "Nur Änderungen empfangen",
|
||||
"Disabled": "Deaktiviert",
|
||||
"Checksum Method": "Prüfziffernverfahren",
|
||||
"File Content (recommended)": "Dateiinhalte (empfohlen)",
|
||||
"File Name": "Dateiname",
|
||||
"Device Name": "Gerätename",
|
||||
"Sync Tolerance": "Sync-Toleranz",
|
||||
"Connect to your KOReader Sync server.": "Mit Ihrem KOReader Sync-Server verbinden.",
|
||||
"Server URL": "Server-URL",
|
||||
"Username": "Benutzername",
|
||||
@@ -503,6 +499,69 @@
|
||||
"Approximately {{percentage}}%": "Ungefähr {{percentage}}%",
|
||||
"Failed to connect": "Verbindung fehlgeschlagen",
|
||||
"Sync Server Connected": "Sync-Server verbunden",
|
||||
"Precision: {{precision}} digits after the decimal": "Präzision: {{precision}} Nachkommastellen",
|
||||
"Sync as {{userDisplayName}}": "Sync als {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Sync als {{userDisplayName}}",
|
||||
"Custom Fonts": "Benutzerdefinierte Schriftarten",
|
||||
"Cancel Delete": "Löschen abbrechen",
|
||||
"Import Font": "Schriftart importieren",
|
||||
"Delete Font": "Schriftart löschen",
|
||||
"Tips": "Tipps",
|
||||
"Custom fonts can be selected from the Font Face menu": "Benutzerdefinierte Schriftarten können aus dem Schriftart-Menü ausgewählt werden",
|
||||
"Manage Custom Fonts": "Benutzerdefinierte Schriftarten verwalten",
|
||||
"Select Files": "Dateien auswählen",
|
||||
"Select Image": "Bild auswählen",
|
||||
"Select Video": "Video auswählen",
|
||||
"Select Audio": "Audio auswählen",
|
||||
"Select Fonts": "Schriftarten auswählen",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Unterstützte Schriftartenformate: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Fortschritt senden",
|
||||
"Pull Progress": "Fortschritt empfangen",
|
||||
"Previous Paragraph": "Vorheriger Absatz",
|
||||
"Previous Sentence": "Vorheriger Satz",
|
||||
"Pause": "Pause",
|
||||
"Play": "Wiedergabe",
|
||||
"Next Sentence": "Nächster Satz",
|
||||
"Next Paragraph": "Nächster Absatz",
|
||||
"Separate Cover Page": "Titelseite separat anzeigen",
|
||||
"Resize Notebook": "Notizbuchgröße ändern",
|
||||
"Resize Sidebar": "Seitenleisten-Größe ändern",
|
||||
"Get Help from the Readest Community": "Hilfe von der Readest-Community erhalten",
|
||||
"Remove cover image": "Coverbild entfernen",
|
||||
"Bookshelf": "Bücherregal",
|
||||
"View Menu": "Menü anzeigen",
|
||||
"Settings Menu": "Einstellungsmenü",
|
||||
"View account details and quota": "Kontodetails und Kontingent anzeigen",
|
||||
"Library Header": "Bibliothekskopf",
|
||||
"Book Content": "Buchinhalt",
|
||||
"Footer Bar": "Fußzeile",
|
||||
"Header Bar": "Kopfzeile",
|
||||
"View Options": "Ansichtsoptionen",
|
||||
"Book Menu": "Buchmenü",
|
||||
"Search Options": "Suchoptionen",
|
||||
"Close": "Schließen",
|
||||
"Delete Book Options": "Buch-Löschoptionen",
|
||||
"ON": "EIN",
|
||||
"OFF": "AUS",
|
||||
"Reading Progress": "Lesefortschritt",
|
||||
"Page Margin": "Seitenrand",
|
||||
"Remove Bookmark": "Lesezeichen entfernen",
|
||||
"Add Bookmark": "Lesezeichen hinzufügen",
|
||||
"Books Content": "Buchinhalt",
|
||||
"Jump to Location": "Zu Standort springen",
|
||||
"Unpin Notebook": "Notizbuch lösen",
|
||||
"Pin Notebook": "Notizbuch anheften",
|
||||
"Hide Search Bar": "Suchleiste ausblenden",
|
||||
"Show Search Bar": "Suchleiste einblenden",
|
||||
"On {{current}} of {{total}} page": "Auf Seite {{current}} von {{total}}",
|
||||
"Section Title": "Abschnittsüberschrift",
|
||||
"Decrease": "Verringern",
|
||||
"Increase": "Erhöhen",
|
||||
"Settings Panels": "Einstellungsfenster",
|
||||
"Settings": "Einstellungen",
|
||||
"Unpin Sidebar": "Seitenleiste lösen",
|
||||
"Pin Sidebar": "Seitenleiste anheften",
|
||||
"Toggle Sidebar": "Seitenleiste umschalten",
|
||||
"Toggle Translation": "Übersetzung umschalten",
|
||||
"Translation Disabled": "Übersetzung deaktiviert",
|
||||
"Minimize": "Minimieren",
|
||||
"Maximize or Restore": "Maximieren oder Wiederherstellen"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Αναζήτηση...",
|
||||
"Select Book": "Επιλογή βιβλίου",
|
||||
"Select Books": "Επιλογή βιβλίων",
|
||||
"Select Multiple Books": "Επιλογή πολλαπλών βιβλίων",
|
||||
"Sepia": "Σέπια",
|
||||
"Serif Font": "Γραμματοσειρά Serif",
|
||||
"Show Book Details": "Εμφάνιση λεπτομερειών βιβλίου",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Λεπτομέρειες βιβλίου",
|
||||
"From Local File": "Από τοπικό αρχείο",
|
||||
"TOC": "Πίνακας περιεχομένων",
|
||||
"Table of Contents": "Πίνακας περιεχομένων",
|
||||
"Book uploaded: {{title}}": "Το βιβλίο με τίτλο {{title}} ανέβηκε",
|
||||
"Failed to upload book: {{title}}": "Αποτυχία ανέβασμα βιβλίου: {{title}}",
|
||||
"Book downloaded: {{title}}": "Το βιβλίο με τίτλο {{title}} κατέβηκε",
|
||||
@@ -333,7 +333,6 @@
|
||||
"Left Margin (px)": "Αριστερό περιθώριο (px)",
|
||||
"Column Gap (%)": "Απόσταση στηλών (%)",
|
||||
"Always Show Status Bar": "Πάντα εμφάνιση γραμμής κατάστασης",
|
||||
"Translation Not Available": "Η μετάφραση δεν είναι διαθέσιμη",
|
||||
"Custom Content CSS": "Προσαρμοσμένο CSS περιεχομένου",
|
||||
"Enter CSS for book content styling...": "Εισαγάγετε CSS για στυλ περιεχομένου βιβλίου...",
|
||||
"Custom Reader UI CSS": "Προσαρμοσμένο CSS διεπαφής αναγνώστη",
|
||||
@@ -443,7 +442,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Μυθοπλασία, Επιστήμη, Ιστορία",
|
||||
"Select Cover Image": "Επιλέξτε εικόνα εξωφύλλου",
|
||||
"Open Book in New Window": "Άνοιγμα βιβλίου σε νέο παράθυρο",
|
||||
"Voices for {{lang}}": "Φωνές για {{lang}}",
|
||||
"Yandex Translate": "Μετάφραση Yandex",
|
||||
@@ -479,12 +477,10 @@
|
||||
"Always use latest": "Χρησιμοποίησε πάντα την τελευταία έκδοση",
|
||||
"Send changes only": "Αποστολή μόνο αλλαγών",
|
||||
"Receive changes only": "Λήψη μόνο αλλαγών",
|
||||
"Disabled": "Απενεργοποιημένο",
|
||||
"Checksum Method": "Μέθοδος ελέγχου",
|
||||
"File Content (recommended)": "Περιεχόμενο αρχείου (συνιστάται)",
|
||||
"File Name": "Όνομα αρχείου",
|
||||
"Device Name": "Όνομα συσκευής",
|
||||
"Sync Tolerance": "Ανοχή συγχρονισμού",
|
||||
"Connect to your KOReader Sync server.": "Συνδεθείτε στον διακομιστή συγχρονισμού KOReader.",
|
||||
"Server URL": "Διεύθυνση URL διακομιστή",
|
||||
"Username": "Όνομα χρήστη",
|
||||
@@ -503,6 +499,69 @@
|
||||
"Approximately {{percentage}}%": "Περίπου {{percentage}}%",
|
||||
"Failed to connect": "Αποτυχία σύνδεσης",
|
||||
"Sync Server Connected": "Ο διακομιστής συγχρονισμού είναι συνδεδεμένος",
|
||||
"Precision: {{precision}} digits after the decimal": "Ακρίβεια: {{precision}} ψηφία μετά την υποδιαστολή",
|
||||
"Sync as {{userDisplayName}}": "Συγχρονισμός ως {{userDisplayName}}"
|
||||
"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": "Μεγιστοποίηση ή Επαναφορά"
|
||||
}
|
||||
|
||||
@@ -83,12 +83,10 @@
|
||||
"Always use latest": "Usar siempre el más reciente",
|
||||
"Send changes only": "Solo enviar cambios",
|
||||
"Receive changes only": "Solo recibir cambios",
|
||||
"Disabled": "Desactivado",
|
||||
"Checksum Method": "Método de identificación",
|
||||
"File Content (recommended)": "Contenido del archivo (recomendado)",
|
||||
"File Name": "Nombre del archivo",
|
||||
"Device Name": "Nombre del dispositivo",
|
||||
"Sync Tolerance": "Tolerancia de sincronización",
|
||||
"Reading Progress Synced": "Progreso de lectura sincronizado",
|
||||
"Sync Conflict": "Conflicto de sincronización",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "¿Quieres sincronizar el progreso de lectura desde el dispositivo \"{{deviceName}}\"?",
|
||||
@@ -111,7 +109,6 @@
|
||||
"Search...": "Buscar...",
|
||||
"Select Book": "Seleccionar libro",
|
||||
"Select Books": "Seleccionar libros",
|
||||
"Select Multiple Books": "Seleccionar varios libros",
|
||||
"Sepia": "Sepia",
|
||||
"Serif Font": "Fuente serif",
|
||||
"Show Book Details": "Mostrar detalles del libro",
|
||||
@@ -147,6 +144,7 @@
|
||||
"Book Details": "Detalles del libro",
|
||||
"From Local File": "Desde archivo local",
|
||||
"TOC": "Índice",
|
||||
"Table of Contents": "Índice",
|
||||
"Book uploaded: {{title}}": "Libro subido: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Error al subir libro: {{title}}",
|
||||
"Book downloaded: {{title}}": "Libro descargado: {{title}}",
|
||||
@@ -365,7 +363,6 @@
|
||||
"Left Margin (px)": "Margen izquierdo (px)",
|
||||
"Column Gap (%)": "Espacio entre columnas (%)",
|
||||
"Always Show Status Bar": "Mostrar siempre la barra de estado",
|
||||
"Translation Not Available": "Traducción no disponible",
|
||||
"Custom Content CSS": "CSS del contenido",
|
||||
"Enter CSS for book content styling...": "Introduce CSS para el contenido del libro...",
|
||||
"Custom Reader UI CSS": "CSS de la interfaz",
|
||||
@@ -476,7 +473,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Ficción, Ciencia, Historia",
|
||||
"Select Cover Image": "Seleccionar imagen de portada",
|
||||
"Open Book in New Window": "Abre el libro en una nueva ventana",
|
||||
"Voices for {{lang}}": "Voces para {{lang}}",
|
||||
"Yandex Translate": "Yandex Translate",
|
||||
@@ -507,6 +503,69 @@
|
||||
"Remove from Device Only": "Eliminar solo del dispositivo",
|
||||
"Failed to connect": "Error al conectar",
|
||||
"Sync Server Connected": "Servidor de sincronización conectado",
|
||||
"Precision: {{precision}} digits after the decimal": "Precisión: {{precision}} dígitos después del decimal",
|
||||
"Sync as {{userDisplayName}}": "Sincronizar como {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Sincronizar como {{userDisplayName}}",
|
||||
"Custom Fonts": "Fuentes Personalizadas",
|
||||
"Cancel Delete": "Cancelar Eliminación",
|
||||
"Import Font": "Importar Fuente",
|
||||
"Delete Font": "Eliminar Fuente",
|
||||
"Tips": "Consejos",
|
||||
"Custom fonts can be selected from the Font Face menu": "Las fuentes personalizadas se pueden seleccionar desde el menú de Tipografía",
|
||||
"Manage Custom Fonts": "Administrar Fuentes Personalizadas",
|
||||
"Select Files": "Seleccionar Archivos",
|
||||
"Select Image": "Seleccionar Imagen",
|
||||
"Select Video": "Seleccionar Video",
|
||||
"Select Audio": "Seleccionar Audio",
|
||||
"Select Fonts": "Seleccionar Fuentes",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Formatos de fuente compatibles: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Empujar progreso",
|
||||
"Pull Progress": "Tirar progreso",
|
||||
"Previous Paragraph": "Párrafo anterior",
|
||||
"Previous Sentence": "Oración anterior",
|
||||
"Pause": "Pausar",
|
||||
"Play": "Reproducir",
|
||||
"Next Sentence": "Siguiente oración",
|
||||
"Next Paragraph": "Siguiente párrafo",
|
||||
"Separate Cover Page": "Portada separada",
|
||||
"Resize Notebook": "Redimensionar cuaderno",
|
||||
"Resize Sidebar": "Redimensionar barra lateral",
|
||||
"Get Help from the Readest Community": "Obtén ayuda de la comunidad de Readest",
|
||||
"Remove cover image": "Eliminar imagen de portada",
|
||||
"Bookshelf": "Estantería",
|
||||
"View Menu": "Menú de Vista",
|
||||
"Settings Menu": "Menú de Configuración",
|
||||
"View account details and quota": "Ver detalles de la cuenta y cuota",
|
||||
"Library Header": "Encabezado de Biblioteca",
|
||||
"Book Content": "Contenido del Libro",
|
||||
"Footer Bar": "Barra de Pie de Página",
|
||||
"Header Bar": "Barra de Encabezado",
|
||||
"View Options": "Opciones de Vista",
|
||||
"Book Menu": "Menú de Libro",
|
||||
"Search Options": "Opciones de Búsqueda",
|
||||
"Close": "Cerrar",
|
||||
"Delete Book Options": "Opciones de Eliminar Libro",
|
||||
"ON": "ENCENDIDO",
|
||||
"OFF": "APAGADO",
|
||||
"Reading Progress": "Progreso de Lectura",
|
||||
"Page Margin": "Márgenes de Página",
|
||||
"Remove Bookmark": "Eliminar Marcador",
|
||||
"Add Bookmark": "Agregar Marcador",
|
||||
"Books Content": "Contenido de Libros",
|
||||
"Jump to Location": "Saltar a Ubicación",
|
||||
"Unpin Notebook": "Desanclar Cuaderno",
|
||||
"Pin Notebook": "Anclar Cuaderno",
|
||||
"Hide Search Bar": "Ocultar Barra de Búsqueda",
|
||||
"Show Search Bar": "Mostrar Barra de Búsqueda",
|
||||
"On {{current}} of {{total}} page": "En {{current}} de {{total}} página",
|
||||
"Section Title": "Título de Sección",
|
||||
"Decrease": "Disminuir",
|
||||
"Increase": "Aumentar",
|
||||
"Settings Panels": "Paneles de Configuración",
|
||||
"Settings": "Configuraciones",
|
||||
"Unpin Sidebar": "Desanclar Barra Lateral",
|
||||
"Pin Sidebar": "Anclar Barra Lateral",
|
||||
"Toggle Sidebar": "Alternar Barra Lateral",
|
||||
"Toggle Translation": "Alternar Traducción",
|
||||
"Translation Disabled": "Traducción Deshabilitada",
|
||||
"Minimize": "Minimizar",
|
||||
"Maximize or Restore": "Maximizar o Restaurar"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Rechercher...",
|
||||
"Select Book": "Sélectionner un livre",
|
||||
"Select Books": "Sélectionner des livres",
|
||||
"Select Multiple Books": "Sélectionner plusieurs livres",
|
||||
"Sepia": "Sépia",
|
||||
"Serif Font": "Police avec empattement",
|
||||
"Show Book Details": "Afficher les détails du livre",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Détails du livre",
|
||||
"From Local File": "Depuis un fichier local",
|
||||
"TOC": "Table des matières",
|
||||
"Table of Contents": "Table des matières",
|
||||
"Book uploaded: {{title}}": "Livre téléchargé : {{title}}",
|
||||
"Failed to upload book: {{title}}": "Échec du téléchargement du livre : {{title}}",
|
||||
"Book downloaded: {{title}}": "Livre téléchargé : {{title}}",
|
||||
@@ -335,7 +335,6 @@
|
||||
"Left Margin (px)": "Marge gauche (px)",
|
||||
"Column Gap (%)": "Espacement des colonnes (%)",
|
||||
"Always Show Status Bar": "Afficher toujours la barre d'état",
|
||||
"Translation Not Available": "Traduction non disponible",
|
||||
"Custom Content CSS": "CSS du contenu",
|
||||
"Enter CSS for book content styling...": "Saisissez le CSS pour le contenu du livre...",
|
||||
"Custom Reader UI CSS": "CSS de l’interface",
|
||||
@@ -447,7 +446,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Fiction, Science, Histoire",
|
||||
"Select Cover Image": "Sélectionner l'image de couverture",
|
||||
"Open Book in New Window": "Ouvrir dans une nouvelle fenêtre",
|
||||
"Voices for {{lang}}": "Voix pour {{lang}}",
|
||||
"Yandex Translate": "Yandex Traduction",
|
||||
@@ -483,12 +481,10 @@
|
||||
"Always use latest": "Toujours utiliser la dernière version",
|
||||
"Send changes only": "Envoyer uniquement les modifications",
|
||||
"Receive changes only": "Recevoir uniquement les modifications",
|
||||
"Disabled": "Désactivé",
|
||||
"Checksum Method": "Méthode de somme de contrôle",
|
||||
"File Content (recommended)": "Contenu du fichier (recommandé)",
|
||||
"File Name": "Nom du fichier",
|
||||
"Device Name": "Nom de l'appareil",
|
||||
"Sync Tolerance": "Tolérance de synchronisation",
|
||||
"Connect to your KOReader Sync server.": "Connectez-vous à votre serveur de synchronisation KOReader.",
|
||||
"Server URL": "URL du serveur",
|
||||
"Username": "Nom d'utilisateur",
|
||||
@@ -507,6 +503,69 @@
|
||||
"Approximately {{percentage}}%": "Environ {{percentage}}%",
|
||||
"Failed to connect": "Échec de la connexion",
|
||||
"Sync Server Connected": "Serveur de synchronisation connecté",
|
||||
"Precision: {{precision}} digits after the decimal": "Précision : {{precision}} chiffres après la virgule",
|
||||
"Sync as {{userDisplayName}}": "Synchroniser en tant que {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Synchroniser en tant que {{userDisplayName}}",
|
||||
"Custom Fonts": "Polices Personnalisées",
|
||||
"Cancel Delete": "Annuler la Suppression",
|
||||
"Import Font": "Importer une Police",
|
||||
"Delete Font": "Supprimer la Police",
|
||||
"Tips": "Conseils",
|
||||
"Custom fonts can be selected from the Font Face menu": "Les polices personnalisées peuvent être sélectionnées depuis le menu Police",
|
||||
"Manage Custom Fonts": "Gérer les Polices Personnalisées",
|
||||
"Select Files": "Sélectionner des Fichiers",
|
||||
"Select Image": "Sélectionner une Image",
|
||||
"Select Video": "Sélectionner une Vidéo",
|
||||
"Select Audio": "Sélectionner un Audio",
|
||||
"Select Fonts": "Sélectionner des Polices",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Formats de police pris en charge : .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Pousser la Progression",
|
||||
"Pull Progress": "Tirer la Progression",
|
||||
"Previous Paragraph": "Paragraphe précédent",
|
||||
"Previous Sentence": "Phrase précédente",
|
||||
"Pause": "Pause",
|
||||
"Play": "Lecture",
|
||||
"Next Sentence": "Phrase suivante",
|
||||
"Next Paragraph": "Paragraphe suivant",
|
||||
"Separate Cover Page": "Page de couverture séparée",
|
||||
"Resize Notebook": "Redimensionner le Carnet",
|
||||
"Resize Sidebar": "Redimensionner la Barre Latérale",
|
||||
"Get Help from the Readest Community": "Obtenir de l'aide de la communauté Readest",
|
||||
"Remove cover image": "Supprimer l'image de couverture",
|
||||
"Bookshelf": "Étagère à livres",
|
||||
"View Menu": "Menu d'affichage",
|
||||
"Settings Menu": "Menu des paramètres",
|
||||
"View account details and quota": "Voir les détails du compte et le quota",
|
||||
"Library Header": "En-tête de la bibliothèque",
|
||||
"Book Content": "Contenu du livre",
|
||||
"Footer Bar": "Barre de pied de page",
|
||||
"Header Bar": "Barre d'en-tête",
|
||||
"View Options": "Options d'affichage",
|
||||
"Book Menu": "Menu du livre",
|
||||
"Search Options": "Options de recherche",
|
||||
"Close": "Fermer",
|
||||
"Delete Book Options": "Options de suppression du livre",
|
||||
"ON": "ACTIVER",
|
||||
"OFF": "DÉSACTIVER",
|
||||
"Reading Progress": "Progression de lecture",
|
||||
"Page Margin": "Marge de page",
|
||||
"Remove Bookmark": "Supprimer le Marque-page",
|
||||
"Add Bookmark": "Ajouter un Marque-page",
|
||||
"Books Content": "Contenu des Livres",
|
||||
"Jump to Location": "Aller à l'Emplacement",
|
||||
"Unpin Notebook": "Détacher le Carnet",
|
||||
"Pin Notebook": "Épingler le Carnet",
|
||||
"Hide Search Bar": "Masquer la Barre de Recherche",
|
||||
"Show Search Bar": "Afficher la Barre de Recherche",
|
||||
"On {{current}} of {{total}} page": "Sur {{current}} de {{total}} page",
|
||||
"Section Title": "Titre de Section",
|
||||
"Decrease": "Diminuer",
|
||||
"Increase": "Augmenter",
|
||||
"Settings Panels": "Panneaux de Configuration",
|
||||
"Settings": "Paramètres",
|
||||
"Unpin Sidebar": "Détacher la Barre Latérale",
|
||||
"Pin Sidebar": "Épingler la Barre Latérale",
|
||||
"Toggle Sidebar": "Alternar la Barre Latérale",
|
||||
"Toggle Translation": "Alternar Traduction",
|
||||
"Translation Disabled": "Traduction Désactivée",
|
||||
"Minimize": "Minimiser",
|
||||
"Maximize or Restore": "Maximiser ou Restaurer"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "खोजें...",
|
||||
"Select Book": "पुस्तक चुनें",
|
||||
"Select Books": "पुस्तकें चुनें",
|
||||
"Select Multiple Books": "कई पुस्तकें चुनें",
|
||||
"Sepia": "सेपिया",
|
||||
"Serif Font": "सेरिफ फ़ॉन्ट",
|
||||
"Show Book Details": "पुस्तक विवरण दिखाएं",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "पुस्तक विवरण",
|
||||
"From Local File": "स्थानीय फ़ाइल से",
|
||||
"TOC": "सामग्री",
|
||||
"Table of Contents": "सामग्री",
|
||||
"Book uploaded: {{title}}": "पुस्तक अपलोड की गई: {{title}}",
|
||||
"Failed to upload book: {{title}}": "पुस्तक अपलोड करने में विफल: {{title}}",
|
||||
"Book downloaded: {{title}}": "पुस्तक डाउनलोड की गई: {{title}}",
|
||||
@@ -333,7 +333,6 @@
|
||||
"Left Margin (px)": "बायां हाशिया (px)",
|
||||
"Column Gap (%)": "स्तंभों के बीच की दूरी (%)",
|
||||
"Always Show Status Bar": "स्थिति पट्टी हमेशा दिखाएं",
|
||||
"Translation Not Available": "अनुवाद उपलब्ध नहीं है",
|
||||
"Custom Content CSS": "सामग्री का CSS",
|
||||
"Enter CSS for book content styling...": "पुस्तक सामग्री के लिए CSS दर्ज करें...",
|
||||
"Custom Reader UI CSS": "इंटरफ़ेस का CSS",
|
||||
@@ -443,7 +442,6 @@
|
||||
"Google Books": "गूगल बुक्स",
|
||||
"Open Library": "ओपन लाइब्रेरी",
|
||||
"Fiction, Science, History": "काल्पनिक, विज्ञान, इतिहास",
|
||||
"Select Cover Image": "कवर इमेज चुनें",
|
||||
"Open Book in New Window": "नई विंडो में पुस्तक खोलें",
|
||||
"Voices for {{lang}}": "{{lang}} के लिए आवाज़ें",
|
||||
"Yandex Translate": "यांडेक्स अनुवाद",
|
||||
@@ -479,12 +477,10 @@
|
||||
"Always use latest": "हमेशा नवीनतम का उपयोग करें",
|
||||
"Send changes only": "केवल परिवर्तन भेजें",
|
||||
"Receive changes only": "केवल परिवर्तन प्राप्त करें",
|
||||
"Disabled": "अक्षम",
|
||||
"Checksum Method": "चेकसम विधि",
|
||||
"File Content (recommended)": "फाइल सामग्री (अनुशंसित)",
|
||||
"File Name": "फाइल नाम",
|
||||
"Device Name": "डिवाइस नाम",
|
||||
"Sync Tolerance": "सिंक सहिष्णुता",
|
||||
"Connect to your KOReader Sync server.": "अपने KOReader सिंक सर्वर से कनेक्ट करें।",
|
||||
"Server URL": "सर्वर यूआरएल",
|
||||
"Username": "उपयोगकर्ता नाम",
|
||||
@@ -503,6 +499,69 @@
|
||||
"Approximately {{percentage}}%": "लगभग {{percentage}}%",
|
||||
"Failed to connect": "कनेक्ट करने में विफल",
|
||||
"Sync Server Connected": "सिंक सर्वर कनेक्टेड",
|
||||
"Precision: {{precision}} digits after the decimal": "सटीकता: {{precision}} दशमलव के बाद अंक",
|
||||
"Sync as {{userDisplayName}}": "सिंक के रूप में {{userDisplayName}}"
|
||||
"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": "अधिकतम या पुनर्स्थापित करें"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Cari...",
|
||||
"Select Book": "Pilih Buku",
|
||||
"Select Books": "Pilih buku",
|
||||
"Select Multiple Books": "Pilih beberapa buku",
|
||||
"Sepia": "Sepia",
|
||||
"Serif Font": "Font Serif",
|
||||
"Show Book Details": "Tampilkan Detail Buku",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Detail Buku",
|
||||
"From Local File": "Dari file lokal",
|
||||
"TOC": "Daftar Isi",
|
||||
"Table of Contents": "Daftar Isi",
|
||||
"Book uploaded: {{title}}": "Buku diunggah: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Gagal mengunggah buku: {{title}}",
|
||||
"Book downloaded: {{title}}": "Buku diunduh: {{title}}",
|
||||
@@ -330,7 +330,6 @@
|
||||
"Left Margin (px)": "Margin Kiri (px)",
|
||||
"Column Gap (%)": "Jarak Antar Kolom (%)",
|
||||
"Always Show Status Bar": "Selalu Tampilkan Bilah Status",
|
||||
"Translation Not Available": "Terjemahan Tidak Tersedia",
|
||||
"Custom Content CSS": "CSS konten",
|
||||
"Enter CSS for book content styling...": "Masukkan CSS untuk gaya konten buku...",
|
||||
"Custom Reader UI CSS": "CSS antarmuka",
|
||||
@@ -439,7 +438,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Fiksi, Sains, Sejarah",
|
||||
"Select Cover Image": "Pilihan Gambar Sampul",
|
||||
"Open Book in New Window": "Buka Buku di Jendela Baru",
|
||||
"Voices for {{lang}}": "Suara untuk {{lang}}",
|
||||
"Yandex Translate": "Yandex Translate",
|
||||
@@ -475,12 +473,10 @@
|
||||
"Always use latest": "Selalu gunakan yang terbaru",
|
||||
"Send changes only": "Kirim perubahan saja",
|
||||
"Receive changes only": "Terima perubahan saja",
|
||||
"Disabled": "Dinonaktifkan",
|
||||
"Checksum Method": "Metode Checksum",
|
||||
"File Content (recommended)": "Konten File (disarankan)",
|
||||
"File Name": "Nama File",
|
||||
"Device Name": "Nama Perangkat",
|
||||
"Sync Tolerance": "Toleransi Sinkronisasi",
|
||||
"Connect to your KOReader Sync server.": "Hubungkan ke server Sinkronisasi KOReader Anda.",
|
||||
"Server URL": "URL Server",
|
||||
"Username": "Nama Pengguna",
|
||||
@@ -499,6 +495,69 @@
|
||||
"Approximately {{percentage}}%": "Sekitar {{percentage}}%",
|
||||
"Failed to connect": "Gagal terhubung",
|
||||
"Sync Server Connected": "Server sinkronisasi terhubung",
|
||||
"Precision: {{precision}} digits after the decimal": "Presisi: {{precision}} digit setelah desimal",
|
||||
"Sync as {{userDisplayName}}": "Sinkronkan sebagai {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Sinkronkan sebagai {{userDisplayName}}",
|
||||
"Custom Fonts": "Font Kustom",
|
||||
"Cancel Delete": "Batal Hapus",
|
||||
"Import Font": "Impor Font",
|
||||
"Delete Font": "Hapus Font",
|
||||
"Tips": "Tips",
|
||||
"Custom fonts can be selected from the Font Face menu": "Font kustom dapat dipilih dari menu Font Face",
|
||||
"Manage Custom Fonts": "Kelola Font Kustom",
|
||||
"Select Files": "Pilih File",
|
||||
"Select Image": "Pilih Gambar",
|
||||
"Select Video": "Pilih Video",
|
||||
"Select Audio": "Pilih Audio",
|
||||
"Select Fonts": "Pilih Font",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Format font yang didukung: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Dorong Progres",
|
||||
"Pull Progress": "Tarik Progres",
|
||||
"Previous Paragraph": "Paragraf Sebelumnya",
|
||||
"Previous Sentence": "Kalimat Sebelumnya",
|
||||
"Pause": "Jeda",
|
||||
"Play": "Putar",
|
||||
"Next Sentence": "Kalimat Berikutnya",
|
||||
"Next Paragraph": "Paragraf Berikutnya",
|
||||
"Separate Cover Page": "Halaman Sampul Terpisah",
|
||||
"Resize Notebook": "Ubah Ukuran Buku Catatan",
|
||||
"Resize Sidebar": "Ubah Ukuran Bilah Samping",
|
||||
"Get Help from the Readest Community": "Dapatkan Bantuan dari Komunitas Readest",
|
||||
"Remove cover image": "Hapus gambar sampul",
|
||||
"Bookshelf": "Rak Buku",
|
||||
"View Menu": "Lihat Menu",
|
||||
"Settings Menu": "Menu Pengaturan",
|
||||
"View account details and quota": "Lihat detail akun dan kuota",
|
||||
"Library Header": "Header Perpustakaan",
|
||||
"Book Content": "Isi Buku",
|
||||
"Footer Bar": "Bilah Footer",
|
||||
"Header Bar": "Bilah Header",
|
||||
"View Options": "Opsi Tampilan",
|
||||
"Book Menu": "Menu Buku",
|
||||
"Search Options": "Opsi Pencarian",
|
||||
"Close": "Tutup",
|
||||
"Delete Book Options": "Opsi Hapus Buku",
|
||||
"ON": "AKTIF",
|
||||
"OFF": "TIDAK AKTIF",
|
||||
"Reading Progress": "Progres Membaca",
|
||||
"Page Margin": "Margin Halaman",
|
||||
"Remove Bookmark": "Hapus Penanda",
|
||||
"Add Bookmark": "Tambahkan Penanda",
|
||||
"Books Content": "Isi Buku",
|
||||
"Jump to Location": "Lompat ke Lokasi",
|
||||
"Unpin Notebook": "Batal Pin Notebook",
|
||||
"Pin Notebook": "Pin Notebook",
|
||||
"Hide Search Bar": "Sembunyikan Bilah Pencarian",
|
||||
"Show Search Bar": "Tampilkan Bilah Pencarian",
|
||||
"On {{current}} of {{total}} page": "Pada {{current}} dari {{total}} halaman",
|
||||
"Section Title": "Judul Bagian",
|
||||
"Decrease": "Kecilkan",
|
||||
"Increase": "Besarakan",
|
||||
"Settings Panels": "Panel Pengaturan",
|
||||
"Settings": "Pengaturan",
|
||||
"Unpin Sidebar": "Batal Pin Bilah Samping",
|
||||
"Pin Sidebar": "Pin Bilah Samping",
|
||||
"Toggle Sidebar": "Toggel Bilah Samping",
|
||||
"Toggle Translation": "Toggel Terjemahan",
|
||||
"Translation Disabled": "Terjemahan Dinonaktifkan",
|
||||
"Minimize": "Minimalkan",
|
||||
"Maximize or Restore": "Maksimalkan atau Pulihkan"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Cerca...",
|
||||
"Select Book": "Seleziona libro",
|
||||
"Select Books": "Seleziona libri",
|
||||
"Select Multiple Books": "Seleziona più libri",
|
||||
"Sepia": "Seppia",
|
||||
"Serif Font": "Font serif",
|
||||
"Show Book Details": "Mostra dettagli libro",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Dettagli libro",
|
||||
"From Local File": "Da file locale",
|
||||
"TOC": "Sommario",
|
||||
"Table of Contents": "Sommario",
|
||||
"Book uploaded: {{title}}": "Libro caricato: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Caricamento libro non riuscito: {{title}}",
|
||||
"Book downloaded: {{title}}": "Libro scaricato: {{title}}",
|
||||
@@ -336,7 +336,6 @@
|
||||
"Left Margin (px)": "Margine Sinistro (px)",
|
||||
"Column Gap (%)": "Spazio tra Colonne (%)",
|
||||
"Always Show Status Bar": "Mostra sempre la barra di stato",
|
||||
"Translation Not Available": "Traduzione non disponibile",
|
||||
"Custom Content CSS": "CSS contenuto",
|
||||
"Enter CSS for book content styling...": "Inserisci il CSS per il contenuto del libro...",
|
||||
"Custom Reader UI CSS": "CSS interfaccia",
|
||||
@@ -447,7 +446,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Narrativa, Scienza, Storia",
|
||||
"Select Cover Image": "Seleziona immagine di copertina",
|
||||
"Open Book in New Window": "Apri libro in una nuova finestra",
|
||||
"Voices for {{lang}}": "Voci per {{lang}}",
|
||||
"Yandex Translate": "Yandex Translate",
|
||||
@@ -483,12 +481,10 @@
|
||||
"Always use latest": "Usa sempre l'ultima versione",
|
||||
"Send changes only": "Invia solo le modifiche",
|
||||
"Receive changes only": "Ricevi solo le modifiche",
|
||||
"Disabled": "Disabilitato",
|
||||
"Checksum Method": "Metodo di Controllo",
|
||||
"File Content (recommended)": "Contenuto del File (consigliato)",
|
||||
"File Name": "Nome del File",
|
||||
"Device Name": "Nome del Dispositivo",
|
||||
"Sync Tolerance": "Tolleranza di Sincronizzazione",
|
||||
"Connect to your KOReader Sync server.": "Connettiti al tuo server KOReader Sync.",
|
||||
"Server URL": "URL del Server",
|
||||
"Username": "Nome Utente",
|
||||
@@ -507,6 +503,69 @@
|
||||
"Approximately {{percentage}}%": "Circa {{percentage}}%",
|
||||
"Failed to connect": "Impossibile connettersi",
|
||||
"Sync Server Connected": "Server di sincronizzazione connesso",
|
||||
"Precision: {{precision}} digits after the decimal": "Precisione: {{precision}} cifre dopo la virgola",
|
||||
"Sync as {{userDisplayName}}": "Sincronizza come {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Sincronizza come {{userDisplayName}}",
|
||||
"Custom Fonts": "Font Personalizzati",
|
||||
"Cancel Delete": "Annulla Eliminazione",
|
||||
"Import Font": "Importa Font",
|
||||
"Delete Font": "Elimina Font",
|
||||
"Tips": "Suggerimenti",
|
||||
"Custom fonts can be selected from the Font Face menu": "I font personalizzati possono essere selezionati dal menu Carattere",
|
||||
"Manage Custom Fonts": "Gestisci Font Personalizzati",
|
||||
"Select Files": "Seleziona File",
|
||||
"Select Image": "Seleziona Immagine",
|
||||
"Select Video": "Seleziona Video",
|
||||
"Select Audio": "Seleziona Audio",
|
||||
"Select Fonts": "Seleziona Font",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Formati di font supportati: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Progresso push",
|
||||
"Pull Progress": "Progresso pull",
|
||||
"Previous Paragraph": "Paragrafo precedente",
|
||||
"Previous Sentence": "Frase precedente",
|
||||
"Pause": "Pausa",
|
||||
"Play": "Riproduci",
|
||||
"Next Sentence": "Frase successiva",
|
||||
"Next Paragraph": "Paragrafo successivo",
|
||||
"Separate Cover Page": "Pagina di copertina separata",
|
||||
"Resize Notebook": "Ridimensiona quaderno",
|
||||
"Resize Sidebar": "Ridimensiona barra laterale",
|
||||
"Get Help from the Readest Community": "Ottieni aiuto dalla community di Readest",
|
||||
"Remove cover image": "Rimuovi immagine di copertina",
|
||||
"Bookshelf": "Scaffale dei libri",
|
||||
"View Menu": "Visualizza menu",
|
||||
"Settings Menu": "Menu impostazioni",
|
||||
"View account details and quota": "Visualizza dettagli account e quota",
|
||||
"Library Header": "Intestazione biblioteca",
|
||||
"Book Content": "Contenuto del libro",
|
||||
"Footer Bar": "Barra piè di pagina",
|
||||
"Header Bar": "Barra intestazione",
|
||||
"View Options": "Opzioni di visualizzazione",
|
||||
"Book Menu": "Menu libro",
|
||||
"Search Options": "Opzioni di ricerca",
|
||||
"Close": "Chiudi",
|
||||
"Delete Book Options": "Opzioni di eliminazione libro",
|
||||
"ON": "ATTIVO",
|
||||
"OFF": "DISATTIVO",
|
||||
"Reading Progress": "Progresso di lettura",
|
||||
"Page Margin": "Margine di pagina",
|
||||
"Remove Bookmark": "Rimuovi Segnalibro",
|
||||
"Add Bookmark": "Aggiungi Segnalibro",
|
||||
"Books Content": "Contenuto dei Libri",
|
||||
"Jump to Location": "Salta alla Posizione",
|
||||
"Unpin Notebook": "Sblocca Quaderno",
|
||||
"Pin Notebook": "Blocca Quaderno",
|
||||
"Hide Search Bar": "Nascondi Barra di Ricerca",
|
||||
"Show Search Bar": "Mostra Barra di Ricerca",
|
||||
"On {{current}} of {{total}} page": "A pagina {{current}} di {{total}}",
|
||||
"Section Title": "Titolo Sezione",
|
||||
"Decrease": "Diminuisci",
|
||||
"Increase": "Aumenta",
|
||||
"Settings Panels": "Pannelli di Impostazione",
|
||||
"Settings": "Impostazioni",
|
||||
"Unpin Sidebar": "Sblocca Bilah Samping",
|
||||
"Pin Sidebar": "Blocca Bilah Samping",
|
||||
"Toggle Sidebar": "Toggel Bilah Samping",
|
||||
"Toggle Translation": "Toggel Terjemahan",
|
||||
"Translation Disabled": "Terjemahan Dinonaktifkan",
|
||||
"Minimize": "Minimalkan",
|
||||
"Maximize or Restore": "Maksimalkan atau Pulihkan"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "検索...",
|
||||
"Select Book": "書籍を選択",
|
||||
"Select Books": "書籍を選択",
|
||||
"Select Multiple Books": "複数の書籍を選択",
|
||||
"Sepia": "セピア",
|
||||
"Serif Font": "セリフ体",
|
||||
"Show Book Details": "書籍の詳細を表示",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "書籍の詳細",
|
||||
"From Local File": "ローカルファイルから",
|
||||
"TOC": "目次",
|
||||
"Table of Contents": "目次",
|
||||
"Book uploaded: {{title}}": "書籍がアップロードされました:{{title}}",
|
||||
"Failed to upload book: {{title}}": "書籍のアップロードに失敗しました:{{title}}",
|
||||
"Book downloaded: {{title}}": "書籍がダウンロードされました:{{title}}",
|
||||
@@ -330,7 +330,6 @@
|
||||
"Left Margin (px)": "左マージン (px)",
|
||||
"Column Gap (%)": "列間の間隔 (%)",
|
||||
"Always Show Status Bar": "ステータスバーを常に表示",
|
||||
"Translation Not Available": "翻訳は利用できません",
|
||||
"Custom Content CSS": "コンテンツCSS",
|
||||
"Enter CSS for book content styling...": "本のコンテンツ用CSSを入力...",
|
||||
"Custom Reader UI CSS": "リーダーUIのCSS",
|
||||
@@ -439,7 +438,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "小説、科学、歴史",
|
||||
"Select Cover Image": "表紙画像を選択",
|
||||
"Open Book in New Window": "新しいウィンドウで本を開く",
|
||||
"Voices for {{lang}}": "{{lang}}の音声",
|
||||
"Yandex Translate": "Yandex翻訳",
|
||||
@@ -475,12 +473,10 @@
|
||||
"Always use latest": "最新を常に使用",
|
||||
"Send changes only": "変更のみを送信",
|
||||
"Receive changes only": "変更のみを受信",
|
||||
"Disabled": "無効",
|
||||
"Checksum Method": "チェックサム方式",
|
||||
"File Content (recommended)": "ファイル内容(推奨)",
|
||||
"File Name": "ファイル名",
|
||||
"Device Name": "デバイス名",
|
||||
"Sync Tolerance": "同期許容範囲",
|
||||
"Connect to your KOReader Sync server.": "KOReader Syncサーバーに接続します。",
|
||||
"Server URL": "サーバーURL",
|
||||
"Username": "ユーザー名",
|
||||
@@ -499,6 +495,69 @@
|
||||
"Approximately {{percentage}}%": "おおよそ {{percentage}}%",
|
||||
"Failed to connect": "接続に失敗しました",
|
||||
"Sync Server Connected": "同期サーバーに接続されました",
|
||||
"Precision: {{precision}} digits after the decimal": "精度: {{precision}} 桁の小数点以下",
|
||||
"Sync as {{userDisplayName}}": "{{userDisplayName}} として同期"
|
||||
"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": "最大化または復元"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "검색...",
|
||||
"Select Book": "책 선택",
|
||||
"Select Books": "책 선택",
|
||||
"Select Multiple Books": "여러 책 선택",
|
||||
"Sepia": "세피아",
|
||||
"Serif Font": "세리프체",
|
||||
"Show Book Details": "책 세부 정보 표시",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "책 세부 정보",
|
||||
"From Local File": "로컬 파일에서",
|
||||
"TOC": "목차",
|
||||
"Table of Contents": "목차",
|
||||
"Book uploaded: {{title}}": "책 업로드됨: {{title}}",
|
||||
"Failed to upload book: {{title}}": "책 업로드 실패: {{title}}",
|
||||
"Book downloaded: {{title}}": "책 다운로드됨: {{title}}",
|
||||
@@ -330,7 +330,6 @@
|
||||
"Left Margin (px)": "왼쪽 여백 (px)",
|
||||
"Column Gap (%)": "열 간격 (%)",
|
||||
"Always Show Status Bar": "상태 표시줄 항상 표시",
|
||||
"Translation Not Available": "번역을 사용할 수 없음",
|
||||
"Custom Content CSS": "콘텐츠 CSS",
|
||||
"Enter CSS for book content styling...": "도서 콘텐츠 스타일을 위한 CSS 입력...",
|
||||
"Custom Reader UI CSS": "리더 UI CSS",
|
||||
@@ -439,7 +438,6 @@
|
||||
"Google Books": "구글 북스",
|
||||
"Open Library": "오픈 라이브러리",
|
||||
"Fiction, Science, History": "소설, 과학, 역사",
|
||||
"Select Cover Image": "표지 이미지 선택",
|
||||
"Open Book in New Window": "새 창에서 책 열기",
|
||||
"Voices for {{lang}}": "{{lang}}의 음성",
|
||||
"Yandex Translate": "Yandex 번역",
|
||||
@@ -475,12 +473,10 @@
|
||||
"Always use latest": "항상 최신 버전 사용",
|
||||
"Send changes only": "변경 사항만 전송",
|
||||
"Receive changes only": "변경 사항만 수신",
|
||||
"Disabled": "비활성화",
|
||||
"Checksum Method": "체크섬 방법",
|
||||
"File Content (recommended)": "파일 내용 (권장)",
|
||||
"File Name": "파일 이름",
|
||||
"Device Name": "장치 이름",
|
||||
"Sync Tolerance": "동기화 허용 오차",
|
||||
"Connect to your KOReader Sync server.": "KOReader Sync 서버에 연결합니다.",
|
||||
"Server URL": "서버 URL",
|
||||
"Username": "사용자 이름",
|
||||
@@ -499,6 +495,69 @@
|
||||
"Approximately {{percentage}}%": "대략 {{percentage}}%",
|
||||
"Failed to connect": "연결 실패",
|
||||
"Sync Server Connected": "동기화 서버에 연결됨",
|
||||
"Precision: {{precision}} digits after the decimal": "정밀도: {{precision}} 자리의 소수점 이하",
|
||||
"Sync as {{userDisplayName}}": "{{userDisplayName}}로 동기화"
|
||||
"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": "최대화 또는 복원"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"Search Books...": "Boeken zoeken...",
|
||||
"Clear Search": "Zoekopdracht wissen",
|
||||
"Import Books": "Boeken importeren",
|
||||
"Select Multiple Books": "Meerdere boeken selecteren",
|
||||
"Select Books": "Boeken selecteren",
|
||||
"Logged in as {{userDisplayName}}": "Ingelogd als {{userDisplayName}}",
|
||||
"Logged in": "Ingelogd",
|
||||
@@ -211,6 +210,7 @@
|
||||
"Match Whole Words": "Hele woorden",
|
||||
"Match Diacritics": "Diakritische tekens",
|
||||
"TOC": "Inhoudsopgave",
|
||||
"Table of Contents": "Inhoudsopgave",
|
||||
"Sidebar": "Zijbalk",
|
||||
"TTS not supported for PDF": "TTS niet ondersteund voor PDF",
|
||||
"No Timeout": "Geen time-out",
|
||||
@@ -333,7 +333,6 @@
|
||||
"Left Margin (px)": "Linker marge (px)",
|
||||
"Column Gap (%)": "Kolomafstand (%)",
|
||||
"Always Show Status Bar": "Statusbalk altijd weergeven",
|
||||
"Translation Not Available": "Vertaling niet beschikbaar",
|
||||
"Custom Content CSS": "Aangepaste inhoud-CSS",
|
||||
"Enter CSS for book content styling...": "Voer CSS in voor de opmaak van de boekinhoud...",
|
||||
"Custom Reader UI CSS": "Aangepaste interface-CSS",
|
||||
@@ -443,7 +442,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Fictie, Wetenschap, Geschiedenis",
|
||||
"Select Cover Image": "Selecteer omslagafbeelding",
|
||||
"Open Book in New Window": "Open boek in nieuw venster",
|
||||
"Voices for {{lang}}": "Stemmen voor {{lang}}",
|
||||
"Yandex Translate": "Yandex Vertalen",
|
||||
@@ -479,12 +477,10 @@
|
||||
"Always use latest": "Altijd de nieuwste gebruiken",
|
||||
"Send changes only": "Verzend alleen wijzigingen",
|
||||
"Receive changes only": "Ontvang alleen wijzigingen",
|
||||
"Disabled": "Uitgeschakeld",
|
||||
"Checksum Method": "Checksum-methode",
|
||||
"File Content (recommended)": "Bestandsinhoud (aanbevolen)",
|
||||
"File Name": "Bestandsnaam",
|
||||
"Device Name": "Apparaatnaam",
|
||||
"Sync Tolerance": "Synchronisatietolerantie",
|
||||
"Connect to your KOReader Sync server.": "Verbind met je KOReader Sync-server.",
|
||||
"Server URL": "Server-URL",
|
||||
"Username": "Gebruikersnaam",
|
||||
@@ -503,6 +499,69 @@
|
||||
"Approximately {{percentage}}%": "Ongeveer {{percentage}}%",
|
||||
"Failed to connect": "Verbinding mislukt",
|
||||
"Sync Server Connected": "Synchronisatie server verbonden",
|
||||
"Precision: {{precision}} digits after the decimal": "Precisie: {{precision}} cijfers na de komma",
|
||||
"Sync as {{userDisplayName}}": "Synchroniseren als {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Synchroniseren als {{userDisplayName}}",
|
||||
"Custom Fonts": "Aangepaste Lettertypen",
|
||||
"Cancel Delete": "Verwijderen Annuleren",
|
||||
"Import Font": "Lettertype Importeren",
|
||||
"Delete Font": "Lettertype Verwijderen",
|
||||
"Tips": "Tips",
|
||||
"Custom fonts can be selected from the Font Face menu": "Aangepaste lettertypen kunnen worden geselecteerd vanuit het Lettertype menu",
|
||||
"Manage Custom Fonts": "Aangepaste Lettertypen Beheren",
|
||||
"Select Files": "Bestanden Selecteren",
|
||||
"Select Image": "Afbeelding Selecteren",
|
||||
"Select Video": "Video Selecteren",
|
||||
"Select Audio": "Audio Selecteren",
|
||||
"Select Fonts": "Lettertypen Selecteren",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Ondersteunde lettertypeformaten: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Voortgang pushen",
|
||||
"Pull Progress": "Voortgang pullen",
|
||||
"Previous Paragraph": "Vorige alinea",
|
||||
"Previous Sentence": "Vorige zin",
|
||||
"Pause": "Pauzeren",
|
||||
"Play": "Afspelen",
|
||||
"Next Sentence": "Volgende zin",
|
||||
"Next Paragraph": "Volgende alinea",
|
||||
"Separate Cover Page": "Afzonderlijke omslagpagina",
|
||||
"Resize Notebook": "Formaat Notitieblok Aanpassen",
|
||||
"Resize Sidebar": "Formaat Zijbalk Aanpassen",
|
||||
"Get Help from the Readest Community": "Hulp krijgen van de Readest Community",
|
||||
"Remove cover image": "Omslagafbeelding verwijderen",
|
||||
"Bookshelf": "Boekenkast",
|
||||
"View Menu": "Menu bekijken",
|
||||
"Settings Menu": "Instellingenmenu",
|
||||
"View account details and quota": "Accountgegevens en quotum bekijken",
|
||||
"Library Header": "Bibliotheekkop",
|
||||
"Book Content": "Boekinhoud",
|
||||
"Footer Bar": "Voettekstbalk",
|
||||
"Header Bar": "Koptekstbalk",
|
||||
"View Options": "Weergaveopties",
|
||||
"Book Menu": "Boekmenu",
|
||||
"Search Options": "Zoekopties",
|
||||
"Close": "Sluiten",
|
||||
"Delete Book Options": "Boekverwijderopties",
|
||||
"ON": "AAN",
|
||||
"OFF": "UIT",
|
||||
"Reading Progress": "Leesvoortgang",
|
||||
"Page Margin": "Pagina Marges",
|
||||
"Remove Bookmark": "Bladwijzer Verwijderen",
|
||||
"Add Bookmark": "Bladwijzer Toevoegen",
|
||||
"Books Content": "Boekinhoud",
|
||||
"Jump to Location": "Ga naar Locatie",
|
||||
"Unpin Notebook": "Notitieblok Losmaken",
|
||||
"Pin Notebook": "Notitieblok Vastmaken",
|
||||
"Hide Search Bar": "Verberg Zoekbalk",
|
||||
"Show Search Bar": "Toon Zoekbalk",
|
||||
"On {{current}} of {{total}} page": "Op {{current}} van {{total}} pagina",
|
||||
"Section Title": "Sectietitel",
|
||||
"Decrease": "Verkleinen",
|
||||
"Increase": "Vergroten",
|
||||
"Settings Panels": "Instellingenpanelen",
|
||||
"Settings": "Instellingen",
|
||||
"Unpin Sidebar": "Zijbalk Losmaken",
|
||||
"Pin Sidebar": "Zijbalk Vastmaken",
|
||||
"Toggle Sidebar": "Zijbalk Wisselen",
|
||||
"Toggle Translation": "Vertaling Wisselen",
|
||||
"Translation Disabled": "Vertaling Uitgeschakeld",
|
||||
"Minimize": "Minimaliseren",
|
||||
"Maximize or Restore": "Maximaliseren of Herstellen"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Szukaj...",
|
||||
"Select Book": "Wybierz książkę",
|
||||
"Select Books": "Wybierz książki",
|
||||
"Select Multiple Books": "Wybierz wiele książek",
|
||||
"Sepia": "Sepia",
|
||||
"Serif Font": "Czcionka szeryfowa",
|
||||
"Show Book Details": "Pokaż szczegóły książki",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Szczegóły książki",
|
||||
"From Local File": "Z pliku lokalnego",
|
||||
"TOC": "Spis treści",
|
||||
"Table of Contents": "Spis treści",
|
||||
"Book uploaded: {{title}}": "Książka przesłana: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Nie udało się przesłać książki: {{title}}",
|
||||
"Book downloaded: {{title}}": "Książka pobrana: {{title}}",
|
||||
@@ -339,7 +339,6 @@
|
||||
"Left Margin (px)": "Lewy margines (px)",
|
||||
"Column Gap (%)": "Odstęp między kolumnami (%)",
|
||||
"Always Show Status Bar": "Zawsze pokazuj pasek stanu",
|
||||
"Translation Not Available": "Brak dostępnego tłumaczenia",
|
||||
"Custom Content CSS": "CSS treści",
|
||||
"Enter CSS for book content styling...": "Wprowadź CSS dla stylu treści książki...",
|
||||
"Custom Reader UI CSS": "CSS interfejsu",
|
||||
@@ -451,7 +450,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Beletrystyka, Nauka, Historia",
|
||||
"Select Cover Image": "Zaznacz obraz okładki",
|
||||
"Open Book in New Window": "Otwórz książkę w nowym oknie",
|
||||
"Voices for {{lang}}": "{{lang}} Głosy",
|
||||
"Yandex Translate": "Yandex Tłumacz",
|
||||
@@ -487,12 +485,10 @@
|
||||
"Always use latest": "Zawsze używaj najnowszej",
|
||||
"Send changes only": "Wyślij tylko zmiany",
|
||||
"Receive changes only": "Odbierz tylko zmiany",
|
||||
"Disabled": "Wyłączone",
|
||||
"Checksum Method": "Metoda sumy kontrolnej",
|
||||
"File Content (recommended)": "Zawartość pliku (zalecane)",
|
||||
"File Name": "Nazwa pliku",
|
||||
"Device Name": "Nazwa urządzenia",
|
||||
"Sync Tolerance": "Tolerancja synchronizacji",
|
||||
"Connect to your KOReader Sync server.": "Połącz z serwerem synchronizacji KOReader.",
|
||||
"Server URL": "Adres URL serwera",
|
||||
"Username": "Nazwa użytkownika",
|
||||
@@ -511,6 +507,69 @@
|
||||
"Approximately {{percentage}}%": "Około {{percentage}}%",
|
||||
"Failed to connect": "Nie udało się połączyć",
|
||||
"Sync Server Connected": "Serwer synchronizacji połączony",
|
||||
"Precision: {{precision}} digits after the decimal": "Precyzja: {{precision}} cyfr po przecinku",
|
||||
"Sync as {{userDisplayName}}": "Synchronizuj jako {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Synchronizuj jako {{userDisplayName}}",
|
||||
"Custom Fonts": "Niestandardowe Czcionki",
|
||||
"Cancel Delete": "Anuluj Usuwanie",
|
||||
"Import Font": "Importuj Czcionkę",
|
||||
"Delete Font": "Usuń Czcionkę",
|
||||
"Tips": "Wskazówki",
|
||||
"Custom fonts can be selected from the Font Face menu": "Niestandardowe czcionki można wybrać z menu Czcionka",
|
||||
"Manage Custom Fonts": "Zarządzaj Niestandardowymi Czcionkami",
|
||||
"Select Files": "Wybierz Pliki",
|
||||
"Select Image": "Wybierz Obraz",
|
||||
"Select Video": "Wybierz Wideo",
|
||||
"Select Audio": "Wybierz Audio",
|
||||
"Select Fonts": "Wybierz Czcionki",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Obsługiwane formaty czcionek: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Wyślij postęp",
|
||||
"Pull Progress": "Pobierz postęp",
|
||||
"Previous Paragraph": "Poprzedni akapit",
|
||||
"Previous Sentence": "Poprzednie zdanie",
|
||||
"Pause": "Pauza",
|
||||
"Play": "Odtwórz",
|
||||
"Next Sentence": "Następne zdanie",
|
||||
"Next Paragraph": "Następny akapit",
|
||||
"Separate Cover Page": "Osobna strona okładki",
|
||||
"Resize Notebook": "Zmień rozmiar notatnika",
|
||||
"Resize Sidebar": "Zmień rozmiar paska bocznego",
|
||||
"Get Help from the Readest Community": "Uzyskaj pomoc od społeczności Readest",
|
||||
"Remove cover image": "Usuń obraz okładki",
|
||||
"Bookshelf": "Półka na książki",
|
||||
"View Menu": "Pokaż menu",
|
||||
"Settings Menu": "Menu ustawień",
|
||||
"View account details and quota": "Zobacz szczegóły konta i limit",
|
||||
"Library Header": "Nagłówek biblioteki",
|
||||
"Book Content": "Zawartość książki",
|
||||
"Footer Bar": "Pasek stopki",
|
||||
"Header Bar": "Pasek nagłówka",
|
||||
"View Options": "Opcje widoku",
|
||||
"Book Menu": "Menu książki",
|
||||
"Search Options": "Opcje wyszukiwania",
|
||||
"Close": "Zamknij",
|
||||
"Delete Book Options": "Opcje usuwania książki",
|
||||
"ON": "WŁĄCZONE",
|
||||
"OFF": "WYŁĄCZONE",
|
||||
"Reading Progress": "Postęp czytania",
|
||||
"Page Margin": "Margines strony",
|
||||
"Remove Bookmark": "Usuń zakładkę",
|
||||
"Add Bookmark": "Dodaj zakładkę",
|
||||
"Books Content": "Zawartość książek",
|
||||
"Jump to Location": "Przejdź do lokalizacji",
|
||||
"Unpin Notebook": "Odczep notatnik",
|
||||
"Pin Notebook": "Przypnij notatnik",
|
||||
"Hide Search Bar": "Ukryj pasek wyszukiwania",
|
||||
"Show Search Bar": "Pokaż pasek wyszukiwania",
|
||||
"On {{current}} of {{total}} page": "Na {{current}} z {{total}} strony",
|
||||
"Section Title": "Tytuł sekcji",
|
||||
"Decrease": "Zmniejsz",
|
||||
"Increase": "Zwiększ",
|
||||
"Settings Panels": "Panele ustawień",
|
||||
"Settings": "Ustawienia",
|
||||
"Unpin Sidebar": "Odczep pasek boczny",
|
||||
"Pin Sidebar": "Przypnij pasek boczny",
|
||||
"Toggle Sidebar": "Włącz/Wyłącz pasek boczny",
|
||||
"Toggle Translation": "Włącz/Wyłącz tłumaczenie",
|
||||
"Translation Disabled": "Tłumaczenie wyłączone",
|
||||
"Minimize": "Minimalizuj",
|
||||
"Maximize or Restore": "Maksymalizuj lub Przywróć"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Pesquisar...",
|
||||
"Select Book": "Selecionar Livro",
|
||||
"Select Books": "Selecionar livros",
|
||||
"Select Multiple Books": "Selecionar múltiplos livros",
|
||||
"Sepia": "Sépia",
|
||||
"Serif Font": "Fonte Serif",
|
||||
"Show Book Details": "Mostrar Detalhes do Livro",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Detalhes do Livro",
|
||||
"From Local File": "Do arquivo local",
|
||||
"TOC": "Índice",
|
||||
"Table of Contents": "Índice",
|
||||
"Book uploaded: {{title}}": "Livro enviado: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Falha ao enviar livro: {{title}}",
|
||||
"Book downloaded: {{title}}": "Livro baixado: {{title}}",
|
||||
@@ -336,7 +336,6 @@
|
||||
"Left Margin (px)": "Margem esquerda (px)",
|
||||
"Column Gap (%)": "Espaço entre colunas (%)",
|
||||
"Always Show Status Bar": "Exibir sempre a barra de status",
|
||||
"Translation Not Available": "Tradução não disponível",
|
||||
"Custom Content CSS": "CSS do conteúdo",
|
||||
"Enter CSS for book content styling...": "Insira o CSS para o estilo do conteúdo do livro...",
|
||||
"Custom Reader UI CSS": "CSS da interface",
|
||||
@@ -447,7 +446,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Ficção, Ciência, História",
|
||||
"Select Cover Image": "Selecionar imagem da capa",
|
||||
"Open Book in New Window": "Abrir livro em nova janela",
|
||||
"Voices for {{lang}}": "Vozes para {{lang}}",
|
||||
"Yandex Translate": "Yandex Translate",
|
||||
@@ -483,12 +481,10 @@
|
||||
"Always use latest": "Sempre usar o mais recente",
|
||||
"Send changes only": "Enviar apenas alterações",
|
||||
"Receive changes only": "Receber apenas alterações",
|
||||
"Disabled": "Desativado",
|
||||
"Checksum Method": "Método de Verificação",
|
||||
"File Content (recommended)": "Conteúdo do Arquivo (recomendado)",
|
||||
"File Name": "Nome do Arquivo",
|
||||
"Device Name": "Nome do Dispositivo",
|
||||
"Sync Tolerance": "Tolerância de Sincronização",
|
||||
"Connect to your KOReader Sync server.": "Conectar ao seu servidor KOReader Sync.",
|
||||
"Server URL": "URL do Servidor",
|
||||
"Username": "Nome de Usuário",
|
||||
@@ -507,6 +503,69 @@
|
||||
"Approximately {{percentage}}%": "Aproximadamente {{percentage}}%",
|
||||
"Failed to connect": "Falha ao conectar",
|
||||
"Sync Server Connected": "Servidor de Sincronização Conectado",
|
||||
"Precision: {{precision}} digits after the decimal": "Precisão: {{precision}} dígitos após a vírgula",
|
||||
"Sync as {{userDisplayName}}": "Sincronizar como {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Sincronizar como {{userDisplayName}}",
|
||||
"Custom Fonts": "Fontes Personalizadas",
|
||||
"Cancel Delete": "Cancelar Exclusão",
|
||||
"Import Font": "Importar Fonte",
|
||||
"Delete Font": "Excluir Fonte",
|
||||
"Tips": "Dicas",
|
||||
"Custom fonts can be selected from the Font Face menu": "Fontes personalizadas podem ser selecionadas no menu Fonte",
|
||||
"Manage Custom Fonts": "Gerenciar Fontes Personalizadas",
|
||||
"Select Files": "Selecionar Arquivos",
|
||||
"Select Image": "Selecionar Imagem",
|
||||
"Select Video": "Selecionar Vídeo",
|
||||
"Select Audio": "Selecionar Áudio",
|
||||
"Select Fonts": "Selecionar Fontes",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Formatos de fonte suportados: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Enviar Progresso",
|
||||
"Pull Progress": "Obter Progresso",
|
||||
"Previous Paragraph": "Parágrafo anterior",
|
||||
"Previous Sentence": "Frase anterior",
|
||||
"Pause": "Pausar",
|
||||
"Play": "Reproduzir",
|
||||
"Next Sentence": "Próxima frase",
|
||||
"Next Paragraph": "Próximo parágrafo",
|
||||
"Separate Cover Page": "Páginas de Capa Separadas",
|
||||
"Resize Notebook": "Redimensionar Caderno",
|
||||
"Resize Sidebar": "Redimensionar Barra Lateral",
|
||||
"Get Help from the Readest Community": "Obter ajuda da comunidade Readest",
|
||||
"Remove cover image": "Remover imagem da capa",
|
||||
"Bookshelf": "Estante de Livros",
|
||||
"View Menu": "Ver Menu",
|
||||
"Settings Menu": "Menu de Configurações",
|
||||
"View account details and quota": "Ver detalhes da conta e cota",
|
||||
"Library Header": "Cabeçalho da Biblioteca",
|
||||
"Book Content": "Conteúdo do Livro",
|
||||
"Footer Bar": "Barra de Rodapé",
|
||||
"Header Bar": "Barra de Cabeçalho",
|
||||
"View Options": "Opções de Visualização",
|
||||
"Book Menu": "Menu do Livro",
|
||||
"Search Options": "Opções de Pesquisa",
|
||||
"Close": "Fechar",
|
||||
"Delete Book Options": "Opções de Exclusão de Livro",
|
||||
"ON": "LIGADO",
|
||||
"OFF": "DESLIGADO",
|
||||
"Reading Progress": "Progresso de Leitura",
|
||||
"Page Margin": "Margem da Página",
|
||||
"Remove Bookmark": "Remover Marcador",
|
||||
"Add Bookmark": "Adicionar Marcador",
|
||||
"Books Content": "Conteúdo dos Livros",
|
||||
"Jump to Location": "Ir para Localização",
|
||||
"Unpin Notebook": "Desafixar Caderno",
|
||||
"Pin Notebook": "Fixar Caderno",
|
||||
"Hide Search Bar": "Ocultar Barra de Pesquisa",
|
||||
"Show Search Bar": "Mostrar Barra de Pesquisa",
|
||||
"On {{current}} of {{total}} page": "Na {{current}} de {{total}} página",
|
||||
"Section Title": "Título da Seção",
|
||||
"Decrease": "Diminuir",
|
||||
"Increase": "Aumentar",
|
||||
"Settings Panels": "Painéis de Configurações",
|
||||
"Settings": "Configurações",
|
||||
"Unpin Sidebar": "Desafixar Barra Lateral",
|
||||
"Pin Sidebar": "Fixar Barra Lateral",
|
||||
"Toggle Sidebar": "Alternar Barra Lateral",
|
||||
"Toggle Translation": "Alternar Tradução",
|
||||
"Translation Disabled": "Tradução Desativada",
|
||||
"Minimize": "Minimizar",
|
||||
"Maximize or Restore": "Maximizar ou Restaurar"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Поиск...",
|
||||
"Select Book": "Выбрать книгу",
|
||||
"Select Books": "Выбрать книги",
|
||||
"Select Multiple Books": "Выбрать несколько книг",
|
||||
"Sepia": "Сепия",
|
||||
"Serif Font": "Шрифт с засечками",
|
||||
"Show Book Details": "Показать детали книги",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Детали книги",
|
||||
"From Local File": "Из локального файла",
|
||||
"TOC": "Содержание",
|
||||
"Table of Contents": "Содержание",
|
||||
"Book uploaded: {{title}}": "Книга загружена: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Не удалось загрузить книгу: {{title}}",
|
||||
"Book downloaded: {{title}}": "Книга скачана: {{title}}",
|
||||
@@ -339,7 +339,6 @@
|
||||
"Left Margin (px)": "Левый отступ (пикс)",
|
||||
"Column Gap (%)": "Промежуток между колонками (%)",
|
||||
"Always Show Status Bar": "Всегда показывать панель состояния",
|
||||
"Translation Not Available": "Перевод недоступен",
|
||||
"Custom Content CSS": "CSS содержимого",
|
||||
"Enter CSS for book content styling...": "Введите CSS для оформления содержимого книги...",
|
||||
"Custom Reader UI CSS": "CSS интерфейса",
|
||||
@@ -451,7 +450,6 @@
|
||||
"Google Books": "Google Книги",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Художественная литература, Наука, История",
|
||||
"Select Cover Image": "Выбрать изображение обложки",
|
||||
"Open Book in New Window": "Открыть книгу в новом окне",
|
||||
"Voices for {{lang}}": "Голоса для {{lang}}",
|
||||
"Yandex Translate": "Yandex Translate",
|
||||
@@ -487,12 +485,10 @@
|
||||
"Always use latest": "Всегда использовать последнюю",
|
||||
"Send changes only": "Отправлять только изменения",
|
||||
"Receive changes only": "Получать только изменения",
|
||||
"Disabled": "Отключено",
|
||||
"Checksum Method": "Метод контрольной суммы",
|
||||
"File Content (recommended)": "Содержимое файла (рекомендуется)",
|
||||
"File Name": "Имя файла",
|
||||
"Device Name": "Имя устройства",
|
||||
"Sync Tolerance": "Допуск синхронизации",
|
||||
"Connect to your KOReader Sync server.": "Подключитесь к вашему серверу синхронизации KOReader.",
|
||||
"Server URL": "URL сервера",
|
||||
"Username": "Имя пользователя",
|
||||
@@ -511,6 +507,69 @@
|
||||
"Approximately {{percentage}}%": "Приблизительно {{percentage}}%",
|
||||
"Failed to connect": "Не удалось подключиться",
|
||||
"Sync Server Connected": "Сервер синхронизации подключен",
|
||||
"Precision: {{precision}} digits after the decimal": "Точность: {{precision}} знаков после запятой",
|
||||
"Sync as {{userDisplayName}}": "Синхронизировать как {{userDisplayName}}"
|
||||
"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": "Максимизировать или восстановить"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
{
|
||||
"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": "මුරපදය නැවත සැකසීමේ සබැඳිය සඳහා ඊමේල් පරීක්ෂා කරන්න",
|
||||
"Phone number": "දුරකථන අංකය",
|
||||
"Your phone number": "ඔබේ දුරකථන අංකය",
|
||||
"Token": "ටෝකනය",
|
||||
"Your OTP token": "ඔබේ OTP ටෝකනය",
|
||||
"Verify token": "ටෝකනය තහවුරු කරන්න",
|
||||
"Sign in with Google": "Google සමඟ ඇතුල් වන්න",
|
||||
"Sign in with Apple": "Apple සමඟ ඇතුල් වන්න",
|
||||
"Sign in with GitHub": "GitHub සමඟ ඇතුල් වන්න",
|
||||
"New Password": "නව මුරපදය",
|
||||
"Your new password": "ඔබේ නව මුරපදය",
|
||||
"Update password": "මුරපදය යාවත්කාලීන කරන්න",
|
||||
"Updating password ...": "මුරපදය යාවත්කාලීන කරමින්...",
|
||||
"Your password has been updated": "ඔබේ මුරපදය යාවත්කාලීන කරන ලදී",
|
||||
"Something went wrong. Don't worry, our team has been notified and we're working on a fix.": "යමක් වැරදුණි. කනගාටු නොවන්න, අපේ කණ්ඩායමට දැනුම් දී ඇති අතර අපි විසඳුමක් සඳහා කටයුතු කරමින් සිටිමු.",
|
||||
"Error Details:": "දෝෂ විස්තර:",
|
||||
"Try Again": "නැවත උත්සාහ කරන්න",
|
||||
"Go Back": "ආපසු යන්න",
|
||||
"Your Library": "ඔබේ පුස්තකාලය",
|
||||
"Need help?": "උදව් අවශ්යද?",
|
||||
"Contact Support": "සහාය සඳහා සම්බන්ධ වන්න",
|
||||
"Open": "විවෘත කරන්න",
|
||||
"Group": "කණ්ඩායම",
|
||||
"Details": "විස්තර",
|
||||
"Delete": "මකන්න",
|
||||
"Cancel": "අවලංගු කරන්න",
|
||||
"Confirm Deletion": "මකා දැමීම තහවුරු කරන්න",
|
||||
"Are you sure to delete {{count}} selected book(s)?_one": "ඔබට විශ්වාසද තෝරාගත් පොත මකා දැමීමට?",
|
||||
"Are you sure to delete {{count}} selected book(s)?_other": "ඔබට විශ්වාසද තෝරාගත් පොත් {{count}} මකා දැමීමට?",
|
||||
"Deselect Book": "පොත අනිශ්චිත කරන්න",
|
||||
"Select Book": "පොත තෝරන්න",
|
||||
"Show Book Details": "පොතේ විස්තර පෙන්වන්න",
|
||||
"Download Book": "පොත බාගන්න",
|
||||
"Upload Book": "පොත උඩුගත කරන්න",
|
||||
"Deselect Group": "කණ්ඩායම අනිශ්චිත කරන්න",
|
||||
"Select Group": "කණ්ඩායම තෝරන්න",
|
||||
"Untitled Group": "නම් නැති කණ්ඩායම",
|
||||
"Group Books": "පොත් කණ්ඩායම් කරන්න",
|
||||
"Remove From Group": "කණ්ඩායමෙන් ඉවත් කරන්න",
|
||||
"Create New Group": "නව කණ්ඩායමක් සාදන්න",
|
||||
"Save": "සුරකින්න",
|
||||
"Confirm": "තහවුරු කරන්න",
|
||||
"From Local File": "දේශීය ගොනුවෙන්",
|
||||
"Search in {{count}} Book(s)..._one": "පොතක සොයන්න...",
|
||||
"Search in {{count}} Book(s)..._other": "පොත් {{count}} කින් සොයන්න...",
|
||||
"Search Books...": "පොත් සොයන්න...",
|
||||
"Clear Search": "සෙවීම මකන්න",
|
||||
"Import Books": "පොත් ආයාත කරන්න",
|
||||
"Select Books": "පොත් තෝරන්න",
|
||||
"Deselect": "අනිශ්චිත කරන්න",
|
||||
"Select All": "සියල්ල තෝරන්න",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}} ලෙස පුරන්ගද්",
|
||||
"Logged in": "පුරනගද්",
|
||||
"Account": "ගිණුම",
|
||||
"Sign In": "ඇතුල් වන්න",
|
||||
"Auto Upload Books to Cloud": "ස්වයංක්රීයව cloud එකට පොත් උඩුගත කරන්න",
|
||||
"Auto Import on File Open": "ගොනුව විවෘත කිරීමේදී ස්වයංක්රීයව ආයාත කරන්න",
|
||||
"Open Last Book on Start": "ආරම්භයේදී අන්තිම පොත විවෘත කරන්න",
|
||||
"Check Updates on Start": "ආරම්භයේදී යාවත්කාලීන පරීක්ෂා කරන්න",
|
||||
"Open Book in New Window": "නව කවුළුවක පොත විවෘත කරන්න",
|
||||
"Fullscreen": "සම්පූර්ණ තිරය",
|
||||
"Always on Top": "සෑම විටම ඉහළින්",
|
||||
"Always Show Status Bar": "සෑම විටම තත්ත්ව තීරුව පෙන්වන්න",
|
||||
"Keep Screen Awake": "තිරය අවදි තබන්න",
|
||||
"Reload Page": "පිටුව නැවත පූරණය කරන්න",
|
||||
"Dark Mode": "අඳුරු ආකාරය",
|
||||
"Light Mode": "ආලෝකමය ආකාරය",
|
||||
"Auto Mode": "ස්වයංක්රීය ආකාරය",
|
||||
"Upgrade to Readest Premium": "Readest Premium වෙත උසස් කරන්න",
|
||||
"Download Readest": "Readest බාගන්න",
|
||||
"About Readest": "Readest ගැන",
|
||||
"Help improve Readest": "Readest වැඩිදියුණු කිරීමට උදව් කරන්න",
|
||||
"Sharing anonymized statistics": "අනන්ය සංඛ්යාලේඛන බෙදාගැනීම",
|
||||
"List": "ලැයිස්තුව",
|
||||
"Grid": "ජාලකය",
|
||||
"Crop": "කපන්න",
|
||||
"Fit": "ගැලපෙන",
|
||||
"Title": "නම",
|
||||
"Author": "කතෘ",
|
||||
"Format": "ආකෘතිය",
|
||||
"Date Read": "කියැවූ දිනය",
|
||||
"Date Added": "එක් කළ දිනය",
|
||||
"Ascending": "ආරෝහණ",
|
||||
"Descending": "අවරෝහණ",
|
||||
"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.": "මෙම පොත ආකෘතියට සහාය නොදක්වයි.",
|
||||
"Failed to import book(s): {{filenames}}": "පොත් ආයාත කිරීමට අසමත්: {{filenames}}",
|
||||
"Book uploaded: {{title}}": "පොත උඩුගත විය: {{title}}",
|
||||
"Insufficient storage quota": "ප්රමාණවත් නොවන ගබඩා ප්රමාණය",
|
||||
"Failed to upload book: {{title}}": "පොත උඩුගත කිරීමට අසමත්: {{title}}",
|
||||
"Book downloaded: {{title}}": "පොත බාගන්නා ලදී: {{title}}",
|
||||
"Failed to download book: {{title}}": "පොත බාගැනීමට අසමත්: {{title}}",
|
||||
"Book deleted: {{title}}": "පොත මකා දමන ලදී: {{title}}",
|
||||
"Deleted cloud backup of the book: {{title}}": "පොතේ cloud backup මකා දමන ලදී: {{title}}",
|
||||
"Deleted local copy of the book: {{title}}": "පොතේ දේශීය පිටපත මකා දමන ලදී: {{title}}",
|
||||
"Failed to delete book: {{title}}": "පොත මකා දැමීමට අසමත්: {{title}}",
|
||||
"Failed to delete cloud backup of the book: {{title}}": "පොතේ cloud backup මකා දැමීමට අසමත්: {{title}}",
|
||||
"Failed to delete local copy of the book: {{title}}": "පොතේ දේශීය පිටපත මකා දැමීමට අසමත්: {{title}}",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "ඔබේ පුස්තකාලයට සාදරයෙන් පිළිගනිමු. ඔබට මෙහි ඔබේ පොත් ආයාත කර ඕනෑම වේලාවක කියවිය හැක.",
|
||||
"Copied to notebook": "සටහන් පොතට පිටපත් කරන ලදී",
|
||||
"No annotations to export": "නිර්යාත කිරීමට සටහන් නැත",
|
||||
"Exported from Readest": "Readest වෙතින් නිර්යාත කරන ලදී",
|
||||
"Highlights & Annotations": "ඉස්මතු කිරීම් සහ සටහන්",
|
||||
"Untitled": "නම් නැති",
|
||||
"Note": "සටහන",
|
||||
"Copied to clipboard": "clipboard එකට පිටපත් කරන ලදී",
|
||||
"Copy": "පිටපත් කරන්න",
|
||||
"Delete Highlight": "ඉස්මතු කිරීම මකන්න",
|
||||
"Highlight": "ඉස්මතු කරන්න",
|
||||
"Annotate": "සටහන් තබන්න",
|
||||
"Search": "සොයන්න",
|
||||
"Dictionary": "ශබ්දකෝෂය",
|
||||
"Wikipedia": "විකිපීඩියා",
|
||||
"Translate": "පරිවර්තනය කරන්න",
|
||||
"Speak": "කථා කරන්න",
|
||||
"Login Required": "පිවිසීම අවශ්ය",
|
||||
"Quota Exceeded": "ප්රමාණය ඉක්මවන ලදී",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "පරිවර්තනය ලබා ගැනීමට නොහැකි. කරුණාකර පළමුව ඇතුල් වී නැවත උත්සාහ කරන්න.",
|
||||
"Unable to fetch the translation. Try again later.": "පරිවර්තනය ලබා ගැනීමට නොහැකි. පසුව නැවත උත්සාහ කරන්න.",
|
||||
"Original Text": "මුල් පෙළ",
|
||||
"Auto Detect": "ස්වයංක්රීයව අනාවරණය",
|
||||
"(detected)": "(අනාවරණය විය)",
|
||||
"Translated Text": "පරිවර්තනය කළ පෙළ",
|
||||
"System Language": "පද්ධති භාෂාව",
|
||||
"Loading...": "පූරණය වෙමින්...",
|
||||
"No translation available.": "පරිවර්තනය ලබා ගත නොහැක.",
|
||||
"Translated by {{provider}}.": "{{provider}} විසින් පරිවර්තනය කරන ලදී.",
|
||||
"Bookmark": "පොත් සලකුණ",
|
||||
"Next Section": "ඊළඟ කොටස",
|
||||
"Previous Section": "පෙර කොටස",
|
||||
"Next Page": "ඊළඟ පිටුව",
|
||||
"Previous Page": "පෙර පිටුව",
|
||||
"Go Forward": "ඉදිරියට යන්න",
|
||||
"Small": "කුඩා",
|
||||
"Large": "විශාල",
|
||||
"Sync Conflict": "සමමුහුර්ත ගැටුම",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "\"{{deviceName}}\" වෙතින් කියවීමේ ප්රගතිය සමමුහුර්ත කරන්නද?",
|
||||
"another device": "වෙනත් උපකරණයක්",
|
||||
"Local Progress": "දේශීය ප්රගතිය",
|
||||
"Remote Progress": "දුරස්ථ ප්රගතිය",
|
||||
"Failed to connect": "සම්බන්ධ වීමට අසමත්",
|
||||
"Disconnected": "විසන්ධි වුණි",
|
||||
"KOReader Sync Settings": "KOReader සමමුහුර්ත සැකසුම්",
|
||||
"Sync as {{userDisplayName}}": "{{userDisplayName}} ලෙස සමමුහුර්ත කරන්න",
|
||||
"Sync Server Connected": "සමමුහුර්ත සේවාදායකය සම්බන්ධ විය",
|
||||
"Sync Strategy": "සමමුහුර්ත උපාය",
|
||||
"Ask on conflict": "ගැටුමේදී විමසන්න",
|
||||
"Always use latest": "සෑම විටම නවතම භාවිතා කරන්න",
|
||||
"Send changes only": "වෙනස්කම් පමණක් යවන්න",
|
||||
"Receive changes only": "වෙනස්කම් පමණක් ලබන්න",
|
||||
"Checksum Method": "පරීක්ෂණ ක්රමය",
|
||||
"File Content (recommended)": "ගොනු අන්තර්ගතය (නිර්දේශිත)",
|
||||
"File Name": "ගොනුවේ නම",
|
||||
"Device Name": "උපකරණයේ නම",
|
||||
"Connect to your KOReader Sync server.": "ඔබේ KOReader සමමුහුර්ත සේවාදායකයට සම්බන්ධ වන්න.",
|
||||
"Server URL": "සේවාදායක URL",
|
||||
"Username": "පරිශීලක නම",
|
||||
"Your Username": "ඔබේ පරිශීලක නම",
|
||||
"Password": "මුරපදය",
|
||||
"Connect": "සම්බන්ධ කරන්න",
|
||||
"Notebook": "සටහන් පොත",
|
||||
"No notes match your search": "ඔබේ සෙවීමට ගැලපෙන සටහන් නැත",
|
||||
"Excerpts": "උදාහරණ",
|
||||
"Notes": "සටහන්",
|
||||
"Add your notes here...": "මෙහි ඔබේ සටහන් එක් කරන්න...",
|
||||
"Search notes and excerpts...": "සටහන් සහ උදාහරණ සොයන්න...",
|
||||
"{{time}} min left in chapter": "පරිච්ඡේදයේ මිනිත්තු {{time}} ක් ඉතිරි",
|
||||
"{{count}} pages left in chapter_one": "පරිච්ඡේදයේ පිටුව 1 ක් ඉතිරි",
|
||||
"{{count}} pages left in chapter_other": "පරිච්ඡේදයේ පිටු {{count}} ක් ඉතිරි",
|
||||
"Theme Mode": "තේමා ආකාරය",
|
||||
"Invert Image In Dark Mode": "අඳුරු ආකාරයේදී රූපය පරිවර්තනය කරන්න",
|
||||
"Override Book Color": "පොතේ වර්ණය අභිබවන්න",
|
||||
"Theme Color": "තේමා වර්ණය",
|
||||
"Custom": "අභිරුචි",
|
||||
"Code Highlighting": "කේත ඉස්මතු කිරීම",
|
||||
"Enable Highlighting": "ඉස්මතු කිරීම සක්රීය කරන්න",
|
||||
"Code Language": "කේත භාෂාව",
|
||||
"Auto": "ස්වයංක්රීය",
|
||||
"Scroll": "අනුචලනය",
|
||||
"Scrolled Mode": "අනුචලන ආකාරය",
|
||||
"Continuous Scroll": "අඛණ්ඩ අනුචලනය",
|
||||
"Overlap Pixels": "අතිච්ඡාදන පික්සල්",
|
||||
"Click": "ක්ලික්",
|
||||
"Clicks for Page Flip": "පිටු පෙරැළීම සඳහා ක්ලික්",
|
||||
"Swap Clicks Area": "ක්ලික් ප්රදේශය මාරු කරන්න",
|
||||
"Disable Double Click": "ද්විත්ව ක්ලික් අබල කරන්න",
|
||||
"Volume Keys for Page Flip": "පිටු පෙරැළීම සඳහා ශබ්ද යතුර",
|
||||
"Animation": "සජීවිකරණය",
|
||||
"Paging Animation": "පිටු සජීවිකරණය",
|
||||
"Security": "ආරක්ෂාව",
|
||||
"Allow JavaScript": "JavaScript ට ඉඩ දෙන්න",
|
||||
"Enable only if you trust the file.": "ගොනුව විශ්වාසදායක නම් පමණක් සක්රීය කරන්න.",
|
||||
"Font": "අකුරු වර්ගය",
|
||||
"Custom Fonts": "අභිරුචි අකුරු",
|
||||
"Cancel Delete": "මකා දැමීම අවලංගු කරන්න",
|
||||
"Delete Font": "අකුරු මකන්න",
|
||||
"Import Font": "අකුරු ආයාත කරන්න",
|
||||
"Tips": "ඉඟි",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "සහාය දක්වන අකුරු ආකෘති: .ttf, .otf, .woff, .woff2",
|
||||
"Custom fonts can be selected from the Font Face menu": "Font Face මෙනුවෙන් අභිරුචි අකුරු තෝරා ගත හැක",
|
||||
"Global Settings": "ගෝලීය සැකසුම්",
|
||||
"Apply to All Books": "සියලු පොත් වලට අනුගත කරන්න",
|
||||
"Apply to This Book": "මෙම පොතට අනුගත කරන්න",
|
||||
"Reset Settings": "සැකසුම් නැවත සකස් කරන්න",
|
||||
"Manage Custom Fonts": "අභිරුචි අකුරු කළමනාකරණය කරන්න",
|
||||
"System Fonts": "පද්ධති අකුරු",
|
||||
"Serif Font": "Serif අකුරු",
|
||||
"Sans-Serif Font": "Sans-Serif අකුරු",
|
||||
"Override Book Font": "පොතේ අකුරු අභිබවන්න",
|
||||
"Font Size": "අකුරු ප්රමාණය",
|
||||
"Default Font Size": "සාමාන්ය අකුරු ප්රමාණය",
|
||||
"Minimum Font Size": "අවම අකුරු ප්රමාණය",
|
||||
"Font Weight": "අකුරු බර",
|
||||
"Font Family": "අකුරු පවුල",
|
||||
"Default Font": "සාමාන්ය අකුරු",
|
||||
"CJK Font": "CJK අකුරු",
|
||||
"Font Face": "අකුරු මුහුණ",
|
||||
"Monospace Font": "Monospace අකුරු",
|
||||
"Language": "භාෂාව",
|
||||
"Interface Language": "අතුරුමුහුණතේ භාෂාව",
|
||||
"Translation": "පරිවර්තනය",
|
||||
"Enable Translation": "පරිවර්තනය සක්රීය කරන්න",
|
||||
"Show Source Text": "මූලාශ්ර පෙළ පෙන්වන්න",
|
||||
"Translation Service": "පරිවර්තන සේවාව",
|
||||
"Translate To": "වෙත පරිවර්තනය කරන්න",
|
||||
"Override Book Layout": "පොතේ පිරිසැලසුම අභිබවන්න",
|
||||
"Writing Mode": "ලේඛන ආකාරය",
|
||||
"Default": "සාමාන්ය",
|
||||
"Horizontal Direction": "තිරස් දිශාව",
|
||||
"Vertical Direction": "සිරස් දිශාව",
|
||||
"RTL Direction": "RTL දිශාව",
|
||||
"Border Frame": "මායිම් රාමුව",
|
||||
"Double Border": "ද්විත්ව මායිම",
|
||||
"Border Color": "මායිම් වර්ණය",
|
||||
"Paragraph": "ඡේදය",
|
||||
"Paragraph Margin": "ඡේද මායිම",
|
||||
"Line Spacing": "පේළි අන්තරය",
|
||||
"Word Spacing": "වචන අන්තරය",
|
||||
"Letter Spacing": "අකුරු අන්තරය",
|
||||
"Text Indent": "පෙළ අභ්යන්තරය",
|
||||
"Full Justification": "සම්පූර්ණ සාධාරණීකරණය",
|
||||
"Hyphenation": "යොමු කිරීම",
|
||||
"Page": "පිටුව",
|
||||
"Top Margin (px)": "ඉහළ මායිම (px)",
|
||||
"Bottom Margin (px)": "පහළ මායිම (px)",
|
||||
"Left Margin (px)": "වම් මායිම (px)",
|
||||
"Right Margin (px)": "දකුණු මායිම (px)",
|
||||
"Column Gap (%)": "තීරු පරතරය (%)",
|
||||
"Maximum Number of Columns": "අවරෝධ තීරු ගණන",
|
||||
"Maximum Column Height": "අවරෝධ තීරු උස",
|
||||
"Maximum Column Width": "අවරෝධ තීරු පළල",
|
||||
"Header & Footer": "ශිර්ෂය සහ පාදය",
|
||||
"Show Header": "ශිර්ෂය පෙන්වන්න",
|
||||
"Show Footer": "පාදය පෙන්වන්න",
|
||||
"Show Remaining Time": "ඉතිරි වේලාව පෙන්වන්න",
|
||||
"Show Remaining Pages": "ඉතිරි පිටු පෙන්වන්න",
|
||||
"Show Reading Progress": "කියවීමේ ප්රගතිය පෙන්වන්න",
|
||||
"Reading Progress Style": "කියවීමේ ප්රගති ශෛලිය",
|
||||
"Page Number": "පිටු අංකය",
|
||||
"Percentage": "ප්රතිශතය",
|
||||
"Apply also in Scrolled Mode": "අනුචලන ආකාරයේදීද අනුගත කරන්න",
|
||||
"Screen": "තිරය",
|
||||
"Orientation": "දිශානතිය",
|
||||
"Portrait": "සිරස්",
|
||||
"Landscape": "තිරස්",
|
||||
"Apply": "අනුගත කරන්න",
|
||||
"Custom Content CSS": "අභිරුචි අන්තර්ගත CSS",
|
||||
"Enter CSS for book content styling...": "පොතේ අන්තර්ගත ශෛලිකරණය සඳහා CSS ඇතුල් කරන්න...",
|
||||
"Custom Reader UI CSS": "අභිරුචි කියවන්නා UI CSS",
|
||||
"Enter CSS for reader interface styling...": "කියවන්නා අතුරුමුහුණත ශෛලිකරණය සඳහා CSS ඇතුල් කරන්න...",
|
||||
"Layout": "පිරිසැලසුම",
|
||||
"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— විලියම් ශේක්ස්පියර්",
|
||||
"(from 'As You Like It', Act II)": "('ඔබට අවශ්ය ලෙස', දෙවන අංකයෙන්)",
|
||||
"Custom Theme": "අභිරුචි තේමාව",
|
||||
"Theme Name": "තේමා නාමය",
|
||||
"Text Color": "පෙළ වර්ණය",
|
||||
"Background Color": "පසුබිම් වර්ණය",
|
||||
"Link Color": "සබැඳි වර්ණය",
|
||||
"Preview": "පෙර දසුන",
|
||||
"Font & Layout": "අකුරු සහ පිරිසැලසුම",
|
||||
"Book Cover": "පොත් ආවරණය",
|
||||
"More Info": "තවත් තොරතුරු",
|
||||
"Parallel Read": "සමාන්තර කියවීම",
|
||||
"Disable": "අබල කරන්න",
|
||||
"Enable": "සක්රීය කරන්න",
|
||||
"KOReader Sync": "KOReader සමමුහුර්තය",
|
||||
"Push Progress": "ප්රගතිය ඉදිරියට",
|
||||
"Pull Progress": "ප්රගතිය අදින්න",
|
||||
"Export Annotations": "සටහන් නිර්යාත කරන්න",
|
||||
"Sort TOC by Page": "පිටුව අනුව TOC සකස් කරන්න",
|
||||
"Edit": "සංස්කරණය",
|
||||
"Search...": "සොයන්න...",
|
||||
"Book": "පොත",
|
||||
"Chapter": "පරිච්ඡේදය",
|
||||
"Match Case": "අකුරු ගැළපීම",
|
||||
"Match Whole Words": "සම්පූර්ණ වචන ගැළපීම",
|
||||
"Match Diacritics": "ධ්වනි ලකුණු ගැළපීම",
|
||||
"TOC": "සූචිය",
|
||||
"Table of Contents": "සූචිය",
|
||||
"Sidebar": "පැති තීරුව",
|
||||
"Disable Translation": "පරිවර්තනය අබල කරන්න",
|
||||
"TTS not supported for PDF": "PDF සඳහා TTS සහාය නොදක්වයි",
|
||||
"TTS not supported for this document": "මෙම ලේඛනය සඳහා TTS සහාය නොදක්වයි",
|
||||
"No Timeout": "කාල සීමාවක් නැත",
|
||||
"{{value}} minute": "මිනිත්තු {{value}}",
|
||||
"{{value}} minutes": "මිනිත්තු {{value}}",
|
||||
"{{value}} hour": "පැය {{value}}",
|
||||
"{{value}} hours": "පැය {{value}}",
|
||||
"Voices for {{lang}}": "{{lang}} සඳහා හඬ",
|
||||
"Slow": "මන්දගාමී",
|
||||
"Fast": "වේගවත්",
|
||||
"{{engine}}: {{count}} voices_one": "{{engine}}: හඬ 1",
|
||||
"{{engine}}: {{count}} voices_other": "{{engine}}: හඬ {{count}}",
|
||||
"Sign in to Sync": "සමමුහුර්ත කිරීම සඳහා ඇතුල් වන්න",
|
||||
"Synced at {{time}}": "{{time}} වේලාවේ සමමුහුර්ත විය",
|
||||
"Never synced": "කිසි විටෙක සමමුහුර්ත නොවිණි",
|
||||
"Reading Progress Synced": "කියවීමේ ප්රගතිය සමමුහුර්ත විය",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "{{total}} න් {{page}} පිටුව ({{percentage}}%)",
|
||||
"Current position": "වර්තමාන ස්ථානය",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "දළ වශයෙන් {{total}} න් {{page}} පිටුව ({{percentage}}%)",
|
||||
"Approximately {{percentage}}%": "දළ වශයෙන් {{percentage}}%",
|
||||
"Delete Your Account?": "ඔබේ ගිණුම මකන්නද?",
|
||||
"This action cannot be undone. All your data in the cloud will be permanently deleted.": "මෙම ක්රියාව අවලංගු කළ නොහැක. cloud හි ඔබේ සියලු දත්ත ස්ථිරවම මකා දැමෙනු ඇත.",
|
||||
"Delete Permanently": "ස්ථිරවම මකන්න",
|
||||
"Restore Purchase": "මිලදී ගැනීම ප්රතිෂ්ඨාපනය කරන්න",
|
||||
"Manage Subscription": "දායකත්වය කළමනාකරණය කරන්න",
|
||||
"Reset Password": "මුරපදය නැවත සකස් කරන්න",
|
||||
"Sign Out": "ඉවත් වන්න",
|
||||
"Delete Account": "ගිණුම මකන්න",
|
||||
"Upgrade to {{plan}}": "{{plan}} වෙත උසස් කරන්න",
|
||||
"Complete Your Subscription": "ඔබේ දායකත්වය සම්පූර්ණ කරන්න",
|
||||
"Coming Soon": "ඉක්මනින් පැමිණේ",
|
||||
"Upgrade to Plus or Pro": "Plus හෝ Pro වෙත උසස් කරන්න",
|
||||
"Current Plan": "වර්තමාන සැලැස්ම",
|
||||
"Plan Limits": "සැලැස්මේ සීමා",
|
||||
"Failed to delete user. Please try again later.": "පරිශීලකයා මකා දැමීමට අසමත්. කරුණාකර පසුව නැවත උත්සාහ කරන්න.",
|
||||
"Failed to create checkout session": "checkout session නිර්මාණය කිරීමට අසමත්",
|
||||
"No purchases found to restore.": "ප්රතිෂ්ඨාපනය කිරීමට මිලදී ගැනීම් නොමැත.",
|
||||
"Failed to restore purchases.": "මිලදී ගැනීම් ප්රතිෂ්ඨාපනය කිරීමට අසමත්.",
|
||||
"Failed to manage subscription.": "දායකත්වය කළමනාකරණය කිරීමට අසමත්.",
|
||||
"Failed to load subscription plans.": "දායකත්ව සැලැස්ම් පූරණය කිරීමට අසමත්.",
|
||||
"Loading profile...": "පැතිකඩ පූරණය වෙමින්...",
|
||||
"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:": "මුදල:",
|
||||
"Subscription ID:": "දායකත්ව ID:",
|
||||
"Go to Library": "පුස්තකාලයට යන්න",
|
||||
"Need help? Contact our support team at support@readest.com": "උදව් අවශ්යද? support@readest.com හි අපගේ සහාය කණ්ඩායමට සම්බන්ධ වන්න",
|
||||
"Free Plan": "නොමිලේ සැලැස්ම",
|
||||
"month": "මාසය",
|
||||
"year": "වර්ෂය",
|
||||
"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": "AI පවසා කියවීම",
|
||||
"Enjoy hands-free reading with natural-sounding AI voices that bring your books to life.": "ඔබේ පොත් ජීවයට ගෙන එන ස්වභාවික ශබ්ද සහිත AI හඬ සමඟ අත් නිදහස් කියවීම රස විඳින්න.",
|
||||
"AI Translations": "AI පරිවර්තන",
|
||||
"Translate any text instantly with the power of Google, Azure, or DeepL—understand content in any language.": "Google, Azure, හෝ DeepL හි බලය සමඟ ඕනෑම පෙළක් ක්ෂණිකව පරිවර්තනය කරන්න - ඕනෑම භාෂාවකින් අන්තර්ගතය තේරුම් ගන්න.",
|
||||
"Community Support": "ප්රජා සහාය",
|
||||
"Connect with fellow readers and get help fast in our friendly community channels.": "සහෘදී පාඨකයන් සමඟ සම්බන්ධ වන්න සහ අපගේ මිත්රශීලී ප්රජා නාලිකා වල ඉක්මනින් උදව් ලබා ගන්න.",
|
||||
"Cloud Sync Storage": "Cloud සමමුහුර්ත ගබඩාව",
|
||||
"AI Translations (per day)": "AI පරිවර්තන (දිනකට)",
|
||||
"Plus Plan": "Plus සැලැස්ම",
|
||||
"Includes All Free Plan Benefits": "සියලු නොමිලේ සැලැස්මේ ප්රතිලාභ ඇතුළත්",
|
||||
"Unlimited AI Read Aloud Hours": "අසීමිත AI පවසා කියවීමේ පැය",
|
||||
"Listen without limits—convert as much text as you like into immersive audio.": "සීමාවකින් තොරව සවන් දෙන්න - ඔබට අවශ්ය තරම් පෙළ ගිලී යන ශ්රව්ය බවට පරිවර්තනය කරන්න.",
|
||||
"More AI Translations": "තවත් AI පරිවර්තන",
|
||||
"Unlock enhanced translation capabilities with more daily usage and advanced options.": "වැඩි දෛනික භාවිතය සහ උසස් විකල්ප සමඟ වැඩිදියුණු කළ පරිවර්තන හැකියාවන් අනවරණය කරන්න.",
|
||||
"DeepL Pro Access": "DeepL Pro ප්රවේශය",
|
||||
"Translate up to 100,000 characters daily with the most accurate translation engine available.": "ලබා ගත හැකි වඩාත් නිරවද්ය පරිවර්තන එන්ජිම සමඟ දිනකට අක්ෂර 100,000 ක් දක්වා පරිවර්තනය කරන්න.",
|
||||
"Securely store and access your entire reading collection with up to 5 GB of cloud storage.": "GB 5 ක් දක්වා cloud ගබඩාව සමඟ ඔබේ සම්පූර්ණ කියවීමේ එකතුව ආරක්ෂිතව ගබඩා කරන්න සහ ප්රවේශ වන්න.",
|
||||
"Priority Support": "ප්රමුඛතා සහාය",
|
||||
"Enjoy faster responses and dedicated assistance whenever you need help.": "ඔබට උදව් අවශ්ය වන විට ඉක්මන් ප්රතිචාර සහ කැප වූ සහාය රස විඳින්න.",
|
||||
"Pro Plan": "Pro සැලැස්ම",
|
||||
"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": "උසස් AI මෙවලම්",
|
||||
"Harness powerful AI tools for smarter reading, translation, and content discovery.": "දක්ෂ කියවීම, පරිවර්තනය සහ අන්තර්ගත සොයාගැනීම සඳහා ප්රබල AI මෙවලම් භාවිතා කරන්න.",
|
||||
"Translate up to 500,000 characters daily with the most accurate translation engine available.": "ලබා ගත හැකි වඩාත් නිරවද්ය පරිවර්තන එන්ජිම සමඟ දිනකට අක්ෂර 500,000 ක් දක්වා පරිවර්තනය කරන්න.",
|
||||
"Securely store and access your entire reading collection with up to 20 GB of cloud storage.": "GB 20 ක් දක්වා cloud ගබඩාව සමඟ ඔබේ සම්පූර්ණ කියවීමේ එකතුව ආරක්ෂිතව ගබඩා කරන්න සහ ප්රවේශ වන්න.",
|
||||
"Version {{version}}": "සංස්කරණය {{version}}",
|
||||
"Check Update": "යාවත්කාලීන පරීක්ෂා කරන්න",
|
||||
"Already the latest version": "දැනටමත් නවතම සංස්කරණය",
|
||||
"Checking for updates...": "යාවත්කාලීන සඳහා පරීක්ෂා කරමින්...",
|
||||
"Error checking for updates": "යාවත්කාලීන පරීක්ෂා කිරීමේ දෝෂය",
|
||||
"Drop to Import Books": "පොත් ආයාත කිරීමට අතහරින්න",
|
||||
"Terms of Service": "සේවා නියමයන්",
|
||||
"Privacy Policy": "පෞද්ගලිකත්ව ප්රතිපත්තිය",
|
||||
"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": "කතා මාලාවේ මුළු පොත් ගණන ඇතුල් කරන්න",
|
||||
"Publisher": "ප්රකාශක",
|
||||
"Enter publisher": "ප්රකාශක ඇතුල් කරන්න",
|
||||
"Publication Date": "ප්රකාශන දිනය",
|
||||
"YYYY or YYYY-MM-DD": "YYYY හෝ YYYY-MM-DD",
|
||||
"Identifier": "හඳුනාගැනීම",
|
||||
"Subjects": "විෂයන්",
|
||||
"Fiction, Science, History": "ප්රබන්ධ, විද්යාව, ඉතිහාසය",
|
||||
"Description": "විස්තරය",
|
||||
"Enter book description": "පොතේ විස්තරය ඇතුල් කරන්න",
|
||||
"Change cover image": "ආවරණ රූපය වෙනස් කරන්න",
|
||||
"Replace": "ආදේශ කරන්න",
|
||||
"Unlock cover": "ආවරණය අගුළු ඇරින්න",
|
||||
"Lock cover": "ආවරණය අගුළු දමන්න",
|
||||
"Auto-Retrieve Metadata": "Metadata ස්වයංක්රීයව ලබා ගන්න",
|
||||
"Auto-Retrieve": "ස්වයංක්රීයව ලබා ගන්න",
|
||||
"Unlock all fields": "සියලු ක්ෂේත්ර අගුළු ඇරින්න",
|
||||
"Unlock All": "සියල්ල අගුළු ඇරින්න",
|
||||
"Lock all fields": "සියලු ක්ෂේත්ර අගුළු දමන්න",
|
||||
"Lock All": "සියල්ල අගුළු දමන්න",
|
||||
"Reset": "නැවත සකස් කරන්න",
|
||||
"Are you sure to delete the selected book?": "ඔබට විශ්වාසද තෝරාගත් පොත මකා දැමීමට?",
|
||||
"Are you sure to delete the cloud backup of the selected book?": "ඔබට විශ්වාසද තෝරාගත් පොතේ cloud backup මකා දැමීමට?",
|
||||
"Are you sure to delete the local copy of the selected book?": "ඔබට විශ්වාසද තෝරාගත් පොතේ දේශීය පිටපත මකා දැමීමට?",
|
||||
"Edit Metadata": "Metadata සංස්කරණය කරන්න",
|
||||
"Book Details": "පොතේ විස්තර",
|
||||
"Unknown": "නොදන්නා",
|
||||
"Remove from Cloud & Device": "Cloud සහ උපකරණයෙන් ඉවත් කරන්න",
|
||||
"Remove from Cloud Only": "Cloud එකෙන් පමණක් ඉවත් කරන්න",
|
||||
"Remove from Device Only": "උපකරණයෙන් පමණක් ඉවත් කරන්න",
|
||||
"Published": "ප්රකාශිතය",
|
||||
"Updated": "යාවත්කාලීන කරන ලදී",
|
||||
"Added": "එක් කරන ලදී",
|
||||
"File Size": "ගොනු ප්රමාණය",
|
||||
"No description available": "විස්තරයක් ලබා ගත නොහැක",
|
||||
"Locked": "අගුළු දමන ලදී",
|
||||
"Select Metadata Source": "Metadata මූලාශ්රය තෝරන්න",
|
||||
"Keep manual input": "අතින් ඇතුල් කළ දේ තබාගන්න",
|
||||
"A new version of Readest is available!": "Readest හි නව සංස්කරණයක් ලබා ගත හැක!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} ලබා ගත හැක (ස්ථාපිත සංස්කරණය {{currentVersion}}).",
|
||||
"Download and install now?": "දැන් බාගන්න සහ ස්ථාපනය කරන්නද?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "{{contentLength}} න් {{downloaded}} බාගනිමින්",
|
||||
"Download finished": "බාගැනීම සම්පූර්ණයි",
|
||||
"DOWNLOAD & INSTALL": "බාගන්න සහ ස්ථාපනය කරන්න",
|
||||
"Changelog": "වෙනස්කම් ලිපිය",
|
||||
"Software Update": "මෘදුකාංග යාවත්කාලීන",
|
||||
"What's New in Readest": "Readest හි නවතම දේ",
|
||||
"Select Files": "ගොනු තෝරන්න",
|
||||
"Select Image": "රූපය තෝරන්න",
|
||||
"Select Video": "වීඩියෝව තෝරන්න",
|
||||
"Select Audio": "ශ්රව්ය තෝරන්න",
|
||||
"Select Fonts": "අකුරු තෝරන්න",
|
||||
"Storage": "ගබඩාව",
|
||||
"{{percentage}}% of Cloud Sync Space Used.": "Cloud සමමුහුර්ත ඉඩෙන් {{percentage}}% භාවිතා කර ඇත.",
|
||||
"Translation Characters": "පරිවර්තන අක්ෂර",
|
||||
"{{percentage}}% of Daily Translation Characters Used.": "දෛනික පරිවර්තන අක්ෂර වලින් {{percentage}}% භාවිතා කර ඇත.",
|
||||
"Daily translation quota reached. Upgrade your plan to continue using AI translations.": "දෛනික පරිවර්තන කෝටාව ළඟා විය. 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": "අළු",
|
||||
"Sepia": "සීපියා",
|
||||
"Grass": "තණකොළ",
|
||||
"Cherry": "චෙරි",
|
||||
"Sky": "අහස",
|
||||
"Solarized": "සූර්යකිරණ",
|
||||
"Gruvbox": "Gruvbox",
|
||||
"Nord": "Nord",
|
||||
"Contrast": "පරස්පර",
|
||||
"Sunset": "හිරු බැස්ම",
|
||||
"Reveal in Finder": "Finder හි පෙන්වන්න",
|
||||
"Reveal in File Explorer": "File Explorer හි පෙන්වන්න",
|
||||
"Reveal in Folder": "ෆෝල්ඩරයේ පෙන්වන්න",
|
||||
"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": "ඉහළ නැංවීම හෝ ප්රතිසංස්කරණය"
|
||||
}
|
||||
@@ -0,0 +1,567 @@
|
||||
{
|
||||
"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": "கடவுச்சொல் மீட்டமைப்பு இணைப்பிற்கு மின்னஞ்சலைச் சரிபார்க்கவும்",
|
||||
"Phone number": "தொலைபேசி எண்",
|
||||
"Your phone number": "உங்கள் தொலைபேசி எண்",
|
||||
"Token": "டோக்கன்",
|
||||
"Your OTP token": "உங்கள் OTP டோக்கன்",
|
||||
"Verify token": "டோக்கனை சரிபார்க்கவும்",
|
||||
"Sign in with Google": "Google மூலம் உள்நுழையவும்",
|
||||
"Sign in with Apple": "Apple மூலம் உள்நுழையவும்",
|
||||
"Sign in with GitHub": "GitHub மூலம் உள்நுழையவும்",
|
||||
"New Password": "புதிய கடவுச்சொல்",
|
||||
"Your new password": "உங்கள் புதிய கடவுச்சொல்",
|
||||
"Update password": "கடவுச்சொல் புதுப்பிக்கவும்",
|
||||
"Updating password ...": "கடவுச்சொல் புதுப்பிக்கிறது...",
|
||||
"Your password has been updated": "உங்கள் கடவுச்சொல் புதுப்பிக்கப்பட்டது",
|
||||
"Something went wrong. Don't worry, our team has been notified and we're working on a fix.": "ஏதோ தவறு நடந்துள்ளது. கவலைப்பட வேண்டாம், எங்கள் குழு அறிவிக்கப்பட்டுள்ளது மற்றும் நாங்கள் தீர்வு செய்து கொண்டிருக்கிறோம்.",
|
||||
"Error Details:": "பிழை விவரங்கள்:",
|
||||
"Try Again": "மீண்டும் முயற்சிக்கவும்",
|
||||
"Go Back": "பின் செல்லவும்",
|
||||
"Your Library": "உங்கள் நூலகம்",
|
||||
"Need help?": "உதவி தேவையா?",
|
||||
"Contact Support": "ஆதரவை தொடர்பு கொள்ளவும்",
|
||||
"Open": "திறக்கவும்",
|
||||
"Group": "குழு",
|
||||
"Details": "விவரங்கள்",
|
||||
"Delete": "நீக்கவும்",
|
||||
"Cancel": "ரத்து செய்யவும்",
|
||||
"Confirm Deletion": "நீக்குதலை உறுதிசெய்யவும்",
|
||||
"Are you sure to delete {{count}} selected book(s)?_one": "தேர்ந்தெடுக்கப்பட்ட புத்தகத்தை நீக்க உறுதியா?",
|
||||
"Are you sure to delete {{count}} selected book(s)?_other": "தேர்ந்தெடுக்கப்பட்ட {{count}} புத்தகங்களை நீக்க உறுதியா?",
|
||||
"Deselect Book": "புத்தகத்தை தேர்வுநீக்கவум்",
|
||||
"Select Book": "புத்தகத்தை தேர்வு செய்யவும்",
|
||||
"Show Book Details": "புத்தக விவரங்களைக் காட்டவும்",
|
||||
"Download Book": "புத்தகத்தை பதிவிறக்கவும்",
|
||||
"Upload Book": "புத்தகத்தை பதிவேற்றவும்",
|
||||
"Deselect Group": "குழுவை தேர்வுநீক்கவும்",
|
||||
"Select Group": "குழுவை தேர்வு செய்யவும்",
|
||||
"Untitled Group": "பெயரிடப்படாத குழு",
|
||||
"Group Books": "புத்தகங்களை குழுவாக்கவும்",
|
||||
"Remove From Group": "குழுவிலிருந்து நீக்கவும்",
|
||||
"Create New Group": "புதிய குழு உருவாக்கவும்",
|
||||
"Save": "சேமிக்கவும்",
|
||||
"Confirm": "உறுதிசெய்யவும்",
|
||||
"From Local File": "உள்ளூர் கோப்பிலிருந்து",
|
||||
"Search in {{count}} Book(s)..._one": "புத்தகத்தில் தேடவும்...",
|
||||
"Search in {{count}} Book(s)..._other": "{{count}} புத்தகங்களில் தேடவும்...",
|
||||
"Search Books...": "புத்தகங்களைத் தேடவும்...",
|
||||
"Clear Search": "தேடலை அழிக்கவும்",
|
||||
"Import Books": "புத்தகங்களை இறக்குமதி செய்யவும்",
|
||||
"Select Books": "புத்தகங்களை தேர்வு செய்யவும்",
|
||||
"Deselect": "தேர்வுநீக்கவும்",
|
||||
"Select All": "அனைத்தையும் தேர்வு செய்யவும்",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}} ஆக உள்நுழைந்துள்ளீர்கள்",
|
||||
"Logged in": "உள்நுழைந்துள்ளீர்கள்",
|
||||
"Account": "கணக்கு",
|
||||
"Sign In": "உள்நுழையவும்",
|
||||
"Auto Upload Books to Cloud": "புத்தகங்களை cloud இல் தானாக பதிவேற்றவும்",
|
||||
"Auto Import on File Open": "கோப்பு திறக்கும்போது தானாக இறக்குமதி",
|
||||
"Open Last Book on Start": "தொடங்கும்போது கடைசி புத்தகத்தைத் திறக்கவும்",
|
||||
"Check Updates on Start": "தொடங்கும்போது புதுப்பிப்புகளைச் சரிபார்க்கவும்",
|
||||
"Open Book in New Window": "புதிய சாளரத்தில் புத்தகத்தைத் திறக்கவும்",
|
||||
"Fullscreen": "முழுத்திரை",
|
||||
"Always on Top": "எப்போதும் மேலே",
|
||||
"Always Show Status Bar": "எப்போதும் நிலைப் பட்டியைக் காட்டவும்",
|
||||
"Keep Screen Awake": "திரையை விழித்திருக்கச் செய்யவும்",
|
||||
"Reload Page": "பக்கத்தை மீண்டும் ஏற்றவும்",
|
||||
"Dark Mode": "இருட்டு பயன்முறை",
|
||||
"Light Mode": "வெளிச்ச பயன்முறை",
|
||||
"Auto Mode": "தானியங்கி பயன்முறை",
|
||||
"Upgrade to Readest Premium": "Readest Premium க்கு மேம்படுத்தவும்",
|
||||
"Download Readest": "Readest பதிவிறக்கவும்",
|
||||
"About Readest": "Readest பற்றி",
|
||||
"Help improve Readest": "Readest மேம்படுத்த உதவவும்",
|
||||
"Sharing anonymized statistics": "அடையாள விவரங்களை நீக்கிய புள்ளிவிவரங்களைப் பகிர்தல்",
|
||||
"List": "பட்டியல்",
|
||||
"Grid": "கட்டம்",
|
||||
"Crop": "வெட்டவும்",
|
||||
"Fit": "பொருத்தவும்",
|
||||
"Title": "தலைப்பு",
|
||||
"Author": "ஆசிரியர்",
|
||||
"Format": "வடிவம்",
|
||||
"Date Read": "படித்த தேதி",
|
||||
"Date Added": "சேர்த்த தேதி",
|
||||
"Ascending": "ஏறுவரிசை",
|
||||
"Descending": "இறங்குவரிசை",
|
||||
"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.": "இந்த புத்தக வடிவம் ஆதரிக்கப்படவில்லை.",
|
||||
"Failed to import book(s): {{filenames}}": "புத்தகங்களை இறக்குமதி செய்ய முடியவில்லை: {{filenames}}",
|
||||
"Book uploaded: {{title}}": "புத்தகம் பதிவேற்றப்பட்டது: {{title}}",
|
||||
"Insufficient storage quota": "போதுமான சேமிப்பக ஒதுக்கீடு இல்லை",
|
||||
"Failed to upload book: {{title}}": "புத்தகத்தை பதிவேற்ற முடியவில்லை: {{title}}",
|
||||
"Book downloaded: {{title}}": "புத்தகம் பதிவிறக்கப்பட்டது: {{title}}",
|
||||
"Failed to download book: {{title}}": "புத்தகத்தை பதிவிறக்க முடியவில்லை: {{title}}",
|
||||
"Book deleted: {{title}}": "புத்தகம் நீக்கப்பட்டது: {{title}}",
|
||||
"Deleted cloud backup of the book: {{title}}": "புத்தகத்தின் cloud backup நீக்கப்பட்டது: {{title}}",
|
||||
"Deleted local copy of the book: {{title}}": "புத்தகத்தின் உள்ளூர் நகல் நீக்கப்பட்டது: {{title}}",
|
||||
"Failed to delete book: {{title}}": "புத்தகத்தை நீக்க முடியவில்லை: {{title}}",
|
||||
"Failed to delete cloud backup of the book: {{title}}": "புத்தகத்தின் cloud backup நீக்க முடியவில்லை: {{title}}",
|
||||
"Failed to delete local copy of the book: {{title}}": "புத்தகத்தின் உள்ளூர் நகல் நீக்க முடியவில்லை: {{title}}",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "உங்கள் நூலகத்திற்கு வரவேற்கிறோம். நீங்கள் இங்கே உங்கள் புத்தகங்களை இறக்குமதி செய்து எப்போது வேண்டுமானாலும் படிக்கலாம்.",
|
||||
"Copied to notebook": "குறிப்பேட்டில் நகலெடுக்கப்பட்டது",
|
||||
"No annotations to export": "ஏற்றுமதி செய்ய குறிப்புகள் இல்லை",
|
||||
"Exported from Readest": "Readest இல் இருந்து ஏற்றுமதி செய்யப்பட்டது",
|
||||
"Highlights & Annotations": "முத்திரைகள் மற்றும் குறிப்புகள்",
|
||||
"Untitled": "பெயரிடப்படாதது",
|
||||
"Note": "குறிப்பு",
|
||||
"Copied to clipboard": "clipboard இல் நகலெடுக்கப்பட்டது",
|
||||
"Copy": "நகல்",
|
||||
"Delete Highlight": "முத்திரையை நீக்கவும்",
|
||||
"Highlight": "முத்திரை",
|
||||
"Annotate": "குறிப்பிடவும்",
|
||||
"Search": "தேடவும்",
|
||||
"Dictionary": "அகராதி",
|
||||
"Wikipedia": "விக்கிபீடியா",
|
||||
"Translate": "மொழிபெயர்க்கவும்",
|
||||
"Speak": "பேசவும்",
|
||||
"Login Required": "உள்நுழைவு தேவை",
|
||||
"Quota Exceeded": "ஒதுக்கீடு மீறப்பட்டது",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "மொழிபெயர்ப்பைப் பெற முடியவில்லை. முதலில் உள்நுழைந்து மீண்டும் முயற்சிக்கவும்.",
|
||||
"Unable to fetch the translation. Try again later.": "மொழிபெயர்ப்பைப் பெற முடியவில்லை. பின்னர் மீண்டும் முயற்சிக்கவும்.",
|
||||
"Original Text": "மூல உரை",
|
||||
"Auto Detect": "தானாக கண்டறிதல்",
|
||||
"(detected)": "(கண்டறியப்பட்டது)",
|
||||
"Translated Text": "மொழிபெயர்க்கப்பட்ட உரை",
|
||||
"System Language": "கணினி மொழி",
|
||||
"Loading...": "ஏற்றுகிறது...",
|
||||
"No translation available.": "மொழிபெயர்ப்பு கிடைக்கவில்லை.",
|
||||
"Translated by {{provider}}.": "{{provider}} மூலம் மொழிபெயர்க்கப்பட்டது.",
|
||||
"Bookmark": "புக்மார்க்",
|
||||
"Next Section": "அடுத்த பிரிவு",
|
||||
"Previous Section": "முந்தைய பிரிவு",
|
||||
"Next Page": "அடுத்த பக்கம்",
|
||||
"Previous Page": "முந்தைய பக்கம்",
|
||||
"Go Forward": "முன்னோக்கி செல்லவும்",
|
||||
"Small": "சிறிய",
|
||||
"Large": "பெரிய",
|
||||
"Sync Conflict": "ஒத்திசைவு முரண்பாடு",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "\"{{deviceName}}\" இல் இருந்து வாசிப்பு முன்னேற்றத்தை ஒத்திசைக்கவா?",
|
||||
"another device": "மற்றொரு சாதனம்",
|
||||
"Local Progress": "உள்ளூர் முன்னேற்றம்",
|
||||
"Remote Progress": "தொலை முன்னேற்றம்",
|
||||
"Failed to connect": "இணைக்க முடியவில்லை",
|
||||
"Disconnected": "துண்டிக்கப்பட்டது",
|
||||
"KOReader Sync Settings": "KOReader ஒத்திசைவு அமைப்புகள்",
|
||||
"Sync as {{userDisplayName}}": "{{userDisplayName}} ஆக ஒத்திசைக்கவும்",
|
||||
"Sync Server Connected": "ஒத்திசைவு சர்வர் இணைக்கப்பட்டது",
|
||||
"Sync Strategy": "ஒத்திசைவு உத்தி",
|
||||
"Ask on conflict": "முரண்பாட்டில் கேட்கவும்",
|
||||
"Always use latest": "எப்போதும் சமீபத்தியதைப் பயன்படுத்தவும்",
|
||||
"Send changes only": "மாற்றங்களை மட்டும் அனுப்பவும்",
|
||||
"Receive changes only": "மாற்றங்களை மட்டும் பெறவும்",
|
||||
"Checksum Method": "சரிபார்ப்பு முறை",
|
||||
"File Content (recommended)": "கோப்பு உள்ளடக்கம் (பரிந்துரைக்கப்பட்டது)",
|
||||
"File Name": "கோப்பு பெயர்",
|
||||
"Device Name": "சாதன பெயர்",
|
||||
"Connect to your KOReader Sync server.": "உங்கள் KOReader ஒத்திசைவு சர்வருடன் இணையவும்.",
|
||||
"Server URL": "சர்வர் URL",
|
||||
"Username": "பயனர்பெயர்",
|
||||
"Your Username": "உங்கள் பயனர்பெயர்",
|
||||
"Password": "கடவுச்சொல்",
|
||||
"Connect": "இணைக்கவும்",
|
||||
"Notebook": "குறிப்பேடு",
|
||||
"No notes match your search": "உங்கள் தேடலுக்கு பொருந்தும் குறிப்புகள் இல்லை",
|
||||
"Excerpts": "உதாரணங்கள்",
|
||||
"Notes": "குறிப்புகள்",
|
||||
"Add your notes here...": "உங்கள் குறிப்புகளை இங்கே சேர்க்கவும்...",
|
||||
"Search notes and excerpts...": "குறிப்புகள் மற்றும் உதாரணங்களைத் தேடவும்...",
|
||||
"{{time}} min left in chapter": "அத்தியாயத்தில் {{time}} நிமிடங்கள் மீதமுள்ளன",
|
||||
"{{count}} pages left in chapter_one": "அத்தியாயத்தில் 1 பக்கம் மீதமுள்ளது",
|
||||
"{{count}} pages left in chapter_other": "அத்தியாயத்தில் {{count}} பக்கங்கள் மீதமுள்ளன",
|
||||
"Theme Mode": "தீம் பயன்முறை",
|
||||
"Invert Image In Dark Mode": "இருட்டு பயன்முறையில் படத்தை தலைகீழாக மாற்றவும்",
|
||||
"Override Book Color": "புத்தக நிறத்தை மேலெழுதவும்",
|
||||
"Theme Color": "தீம் நிறம்",
|
||||
"Custom": "தனிப்பயன்",
|
||||
"Code Highlighting": "குறியீடு முத்திரை",
|
||||
"Enable Highlighting": "முத்திரையிடுதலை இயக்கவும்",
|
||||
"Code Language": "குறியீடு மொழி",
|
||||
"Auto": "தானியங்கி",
|
||||
"Scroll": "உருட்டவும்",
|
||||
"Scrolled Mode": "உருட்டல் பயன்முறை",
|
||||
"Continuous Scroll": "தொடர்ச்சியான உருட்டல்",
|
||||
"Overlap Pixels": "ஒன்றுடன் ஒன்று பிக்சல்கள்",
|
||||
"Click": "கிளிக்",
|
||||
"Clicks for Page Flip": "பக்கம் புரட்டுவதற்கு கிளிக்கள்",
|
||||
"Swap Clicks Area": "கிளிக் பகுதியை மாற்றவும்",
|
||||
"Disable Double Click": "இரட்டை கிளிக்கை முடக்கவும்",
|
||||
"Volume Keys for Page Flip": "பக்கம் புரட்டுவதற்கு ஒலி விசைகள்",
|
||||
"Animation": "அனிமேஷன்",
|
||||
"Paging Animation": "பக்க அனிமேஷன்",
|
||||
"Security": "பாதுகாப்பு",
|
||||
"Allow JavaScript": "JavaScript அனுமதிக்கவும்",
|
||||
"Enable only if you trust the file.": "கோப்பை நம்பினால் மட்டும் இயக்கவும்.",
|
||||
"Font": "எழுத்துரு",
|
||||
"Custom Fonts": "தனிப்பயன் எழுத்துருக்கள்",
|
||||
"Cancel Delete": "நீக்குதலை ரத்துசெய்யவும்",
|
||||
"Delete Font": "எழுத்துருவை நீக்கவும்",
|
||||
"Import Font": "எழுத்துருவை இறக்குமதி செய்யவும்",
|
||||
"Tips": "குறிப்புகள்",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "ஆதரிக்கப்படும் எழுத்துரு வடிவங்கள்: .ttf, .otf, .woff, .woff2",
|
||||
"Custom fonts can be selected from the Font Face menu": "Font Face மெனுவிலிருந்து தனிப்பயன் எழுத்துருக்களை தேர்வு செய்யலாம்",
|
||||
"Global Settings": "உலகளாவிய அமைப்புகள்",
|
||||
"Apply to All Books": "அனைத்து புத்தகங்களுக்கும் பொருந்தும்",
|
||||
"Apply to This Book": "இந்த புத்தகத்திற்கு பொருந்தும்",
|
||||
"Reset Settings": "அமைப்புகளை மீட்டமைக்கவும்",
|
||||
"Manage Custom Fonts": "தனிப்பயன் எழுத்துருக்களை நிர்வகிக்கவும்",
|
||||
"System Fonts": "கணினி எழுத்துருக்கள்",
|
||||
"Serif Font": "Serif எழுத்துரு",
|
||||
"Sans-Serif Font": "Sans-Serif எழுத்துரு",
|
||||
"Override Book Font": "புத்தக எழுத்துருவை மேலெழுதவும்",
|
||||
"Font Size": "எழுத்துரு அளவு",
|
||||
"Default Font Size": "இயல்புநிலை எழுத்துரு அளவு",
|
||||
"Minimum Font Size": "குறைந்தபட்ச எழுத்துரு அளவு",
|
||||
"Font Weight": "எழுத்துரு எடை",
|
||||
"Font Family": "எழுத்துரு குடும்பம்",
|
||||
"Default Font": "இயல்புநிலை எழுத்துரு",
|
||||
"CJK Font": "CJK எழுத்துரு",
|
||||
"Font Face": "எழுத்துரு முகம்",
|
||||
"Monospace Font": "Monospace எழுத்துரு",
|
||||
"Language": "மொழி",
|
||||
"Interface Language": "இடைமுக மொழி",
|
||||
"Translation": "மொழிபெயர்ப்பு",
|
||||
"Enable Translation": "மொழிபெயர்ப்பை இயக்கவும்",
|
||||
"Show Source Text": "மூல உரையைக் காட்டவும்",
|
||||
"Translation Service": "மொழிபெயர்ப்பு சேவை",
|
||||
"Translate To": "இதற்கு மொழிபெயர்க்கவும்",
|
||||
"Override Book Layout": "புத்தக அமைப்பை மேலெழுதவும்",
|
||||
"Writing Mode": "எழுதும் பயன்முறை",
|
||||
"Default": "இயல்புநிலை",
|
||||
"Horizontal Direction": "கிடைமட்ட திசை",
|
||||
"Vertical Direction": "செங்குத்து திசை",
|
||||
"RTL Direction": "RTL திசை",
|
||||
"Border Frame": "எல்லை சட்டகம்",
|
||||
"Double Border": "இரட்டை எல்லை",
|
||||
"Border Color": "எல்லை நிறம்",
|
||||
"Paragraph": "பத்தி",
|
||||
"Paragraph Margin": "பத்தி விளிம்பு",
|
||||
"Line Spacing": "வரி இடைவெளி",
|
||||
"Word Spacing": "சொல் இடைவெளி",
|
||||
"Letter Spacing": "எழுத்து இடைவெளி",
|
||||
"Text Indent": "உரை உள்தள்ளல்",
|
||||
"Full Justification": "முழு நியாயம்",
|
||||
"Hyphenation": "இணைக்கோடு",
|
||||
"Page": "பக்கம்",
|
||||
"Top Margin (px)": "மேல் விளிம்பு (px)",
|
||||
"Bottom Margin (px)": "கீழ் விளிம்பு (px)",
|
||||
"Left Margin (px)": "இடது விளிம்பு (px)",
|
||||
"Right Margin (px)": "வலது விளிம்பு (px)",
|
||||
"Column Gap (%)": "நெடுவரிசை இடைவெளி (%)",
|
||||
"Maximum Number of Columns": "அதிகபட்ச நெடுவரிசை எண்ணிக்கை",
|
||||
"Maximum Column Height": "அதிகபட்ச நெடுவரிசை உயரம்",
|
||||
"Maximum Column Width": "அதிகபட்ச நெடுவரிசை அகலம்",
|
||||
"Header & Footer": "தலைப்பு மற்றும் அடிக்குறிப்பு",
|
||||
"Show Header": "தலைப்பைக் காட்டவும்",
|
||||
"Show Footer": "அடிக்குறிப்பைக் காட்டவும்",
|
||||
"Show Remaining Time": "மீதமுள்ள நேரத்தைக் காட்டவும்",
|
||||
"Show Remaining Pages": "மீதமுள்ள பக்கங்களைக் காட்டவும்",
|
||||
"Show Reading Progress": "வாசிப்பு முன்னேற்றத்தைக் காட்டவும்",
|
||||
"Reading Progress Style": "வாசிப்பு முன்னேற்ற பாணி",
|
||||
"Page Number": "பக்க எண்",
|
||||
"Percentage": "சதவீதம்",
|
||||
"Apply also in Scrolled Mode": "உருட்டல் பயன்முறையிலும் பொருந்தும்",
|
||||
"Screen": "திரை",
|
||||
"Orientation": "திசைநிலை",
|
||||
"Portrait": "நிலைமான்",
|
||||
"Landscape": "கிடைமான்",
|
||||
"Apply": "பொருந்தும்",
|
||||
"Custom Content CSS": "தனிப்பயன் உள்ளடக்க CSS",
|
||||
"Enter CSS for book content styling...": "புத்தக உள்ளடக்க வடிவமைப்பிற்கு CSS ஐ உள்ளிடவும்...",
|
||||
"Custom Reader UI CSS": "தனிப்பயன் வாசிப்பான் UI CSS",
|
||||
"Enter CSS for reader interface styling...": "வாசிப்பான் இடைமுக வடிவமைப்பிற்கு CSS ஐ உள்ளிடவும்...",
|
||||
"Layout": "அமைப்பு",
|
||||
"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— வில்லியம் ஷேக்ஸ்பியர்",
|
||||
"(from 'As You Like It', Act II)": "('ஆஸ் யூ லைக் இட்', இரண்டாவது அங்கத்திலிருந்து)",
|
||||
"Custom Theme": "தனிப்பயன் தீம்",
|
||||
"Theme Name": "தீம் பெயர்",
|
||||
"Text Color": "உரை நிறம்",
|
||||
"Background Color": "பின்னணி நிறம்",
|
||||
"Link Color": "இணைப்பு நிறம்",
|
||||
"Preview": "முன்னோட்டம்",
|
||||
"Font & Layout": "எழுத்துரு மற்றும் அமைப்பு",
|
||||
"Book Cover": "புத்தக அட்டை",
|
||||
"More Info": "மேலும் தகவல்",
|
||||
"Parallel Read": "இணை வாசிப்பு",
|
||||
"Disable": "முடக்கவும்",
|
||||
"Enable": "இயக்கவும்",
|
||||
"KOReader Sync": "KOReader ஒத்திசைவு",
|
||||
"Push Progress": "முன்னேற்றத்தை அனுப்பவும்",
|
||||
"Pull Progress": "முன்னேற்றத்தை இழுக்கவும்",
|
||||
"Export Annotations": "குறிப்புகளை ஏற்றுமதி செய்யவும்",
|
||||
"Sort TOC by Page": "பக்கத்தின்படி TOC வரிசைப்படுத்தவும்",
|
||||
"Edit": "திருத்தவும்",
|
||||
"Search...": "தேடவும்...",
|
||||
"Book": "புத்தகம்",
|
||||
"Chapter": "அத்தியாயம்",
|
||||
"Match Case": "எழுத்து பொருத்தம்",
|
||||
"Match Whole Words": "முழு வார்த்தைகள் பொருத்தம்",
|
||||
"Match Diacritics": "உச்சரிப்பு குறிகள் பொருத்தம்",
|
||||
"TOC": "உள்ளடக்கம்",
|
||||
"Table of Contents": "உள்ளடக்கம்",
|
||||
"Sidebar": "பக்கப்பட்டி",
|
||||
"Disable Translation": "மொழிபெயர்ப்பை முடக்கவும்",
|
||||
"TTS not supported for PDF": "PDF க்கு TTS ஆதரிக்கப்படவில்லை",
|
||||
"TTS not supported for this document": "இந்த ஆவணத்திற்கு TTS ஆதரிக்கப்படவில்லை",
|
||||
"No Timeout": "நேர வரம்பு இல்லை",
|
||||
"{{value}} minute": "{{value}} நிமிடம்",
|
||||
"{{value}} minutes": "{{value}} நிமிடங்கள்",
|
||||
"{{value}} hour": "{{value}} மணிநேரம்",
|
||||
"{{value}} hours": "{{value}} மணிநேரங்கள்",
|
||||
"Voices for {{lang}}": "{{lang}} க்கான குரல்கள்",
|
||||
"Slow": "மெதுவாக",
|
||||
"Fast": "வேகமாக",
|
||||
"{{engine}}: {{count}} voices_one": "{{engine}}: 1 குரல்",
|
||||
"{{engine}}: {{count}} voices_other": "{{engine}}: {{count}} குரல்கள்",
|
||||
"Sign in to Sync": "ஒத்திசைக்க உள்நுழையவும்",
|
||||
"Synced at {{time}}": "{{time}} இல் ஒத்திசைக்கப்பட்டது",
|
||||
"Never synced": "ஒத்திசைக்கப்படவில்லை",
|
||||
"Reading Progress Synced": "வாசிப்பு முன்னேற்றம் ஒத்திசைக்கப்பட்டது",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "{{total}} இல் {{page}} பக்கம் ({{percentage}}%)",
|
||||
"Current position": "தற்போதைய நிலை",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "தோராயமாக {{total}} இல் {{page}} பக்கம் ({{percentage}}%)",
|
||||
"Approximately {{percentage}}%": "தோராயமாக {{percentage}}%",
|
||||
"Delete Your Account?": "உங்கள் கணக்கை நீக்கவா?",
|
||||
"This action cannot be undone. All your data in the cloud will be permanently deleted.": "இந்த செயலை மாற்ற முடியாது. cloud இல் உள்ள உங்கள் எல்லா தரவும் நிரந்தரமாக நீக்கப்படும்.",
|
||||
"Delete Permanently": "நிரந்தரமாக நீக்கவும்",
|
||||
"Restore Purchase": "வாங்குதலை மீட்டெடுக்கவும்",
|
||||
"Manage Subscription": "சந்தாவை நிர்வகிக்கவும்",
|
||||
"Reset Password": "கடவுச்சொல் மீட்டமைக்கவும்",
|
||||
"Sign Out": "வெளியேறவும்",
|
||||
"Delete Account": "கணக்கை நீக்கவும்",
|
||||
"Upgrade to {{plan}}": "{{plan}} க்கு மேம்படுத்தவும்",
|
||||
"Complete Your Subscription": "உங்கள் சந்தாவை நிறைவு செய்யவும்",
|
||||
"Coming Soon": "விரைவில் வரும்",
|
||||
"Upgrade to Plus or Pro": "Plus அல்லது Pro க்கு மேம்படுத்தவும்",
|
||||
"Current Plan": "தற்போதைய திட்டம்",
|
||||
"Plan Limits": "திட்ட வரம்புகள்",
|
||||
"Failed to delete user. Please try again later.": "பயனரை நீக்க முடியவில்லை. பின்னர் மீண்டும் முயற்சிக்கவும்.",
|
||||
"Failed to create checkout session": "checkout session உருவாக்க முடியவில்லை",
|
||||
"No purchases found to restore.": "மீட்டெடுக்க வாங்குதல்கள் இல்லை.",
|
||||
"Failed to restore purchases.": "வாங்குதல்களை மீட்டெடுக்க முடியவில்லை.",
|
||||
"Failed to manage subscription.": "சந்தாவை நிர்வகிக்க முடியவில்லை.",
|
||||
"Failed to load subscription plans.": "சந்தா திட்டங்களை ஏற்ற முடியவில்லை.",
|
||||
"Loading profile...": "சுயவிவரம் ஏற்றுகிறது...",
|
||||
"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:": "தொகை:",
|
||||
"Subscription ID:": "சந்தா ID:",
|
||||
"Go to Library": "நூலகத்திற்கு செல்லவும்",
|
||||
"Need help? Contact our support team at support@readest.com": "உதவி தேவையா? support@readest.com இல் எங்கள் ஆதரவு குழுவைத் தொடர்பு கொள்ளவும்",
|
||||
"Free Plan": "இலவச திட்டம்",
|
||||
"month": "மாதம்",
|
||||
"year": "வருடம்",
|
||||
"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": "AI உரை வாசிப்பு",
|
||||
"Enjoy hands-free reading with natural-sounding AI voices that bring your books to life.": "உங்கள் புத்தகங்களை உயிர்ப்பிக்கும் இயற்கையான ஒலியுடைய AI குரல்களுடன் கைகள் இல்லாமல் வாசிப்பை அனுபவிக்கவும்.",
|
||||
"AI Translations": "AI மொழிபெயர்ப்புகள்",
|
||||
"Translate any text instantly with the power of Google, Azure, or DeepL—understand content in any language.": "Google, Azure அல்லது DeepL இன் சக்தியுடன் எந்த உரையையும் உடனடியாக மொழிபெயர்க்கவும் - எந்த மொழியிலும் உள்ளடக்கத்தை புரிந்துகொள்ளவும்.",
|
||||
"Community Support": "சமூக ஆதரவு",
|
||||
"Connect with fellow readers and get help fast in our friendly community channels.": "சக வாசகர்களுடன் இணையுங்கள் மற்றும் எங்கள் நட்பு சமூக சேனல்களில் விரைவாக உதவி பெறுங்கள்.",
|
||||
"Cloud Sync Storage": "Cloud ஒத்திசைவு சேமிப்பு",
|
||||
"AI Translations (per day)": "AI மொழிபெயர்ப்புகள் (ஒரு நாளுக்கு)",
|
||||
"Plus Plan": "Plus திட்டம்",
|
||||
"Includes All Free Plan Benefits": "அனைத்து இலவச திட்ட நன்மைகளும் உள்ளடங்கும்",
|
||||
"Unlimited AI Read Aloud Hours": "வரம்பற்ற AI வாசிப்பு மணிநேரங்கள்",
|
||||
"Listen without limits—convert as much text as you like into immersive audio.": "வரம்பு இல்லாமல் கேளுங்கள் - நீங்கள் விரும்பும் அளவு உரையை அழுத்தமான ஆடியோவாக மாற்றுங்கள்.",
|
||||
"More AI Translations": "மேலும் AI மொழிபெயர்ப்புகள்",
|
||||
"Unlock enhanced translation capabilities with more daily usage and advanced options.": "அதிக தினசரி பயன்பாடு மற்றும் மேம்பட்ட விருப்பங்களுடன் மேம்பட்ட மொழிபெயர்ப்பு திறன்களை திறக்கவும்.",
|
||||
"DeepL Pro Access": "DeepL Pro அணுகல்",
|
||||
"Translate up to 100,000 characters daily with the most accurate translation engine available.": "கிடைக்கக்கூடிய மிகவும் துல்லியமான மொழிபெயர்ப்பு இயந்திரத்துடன் தினசரி 100,000 எழுத்துகள் வரை மொழிபெயர்க்கவும்.",
|
||||
"Securely store and access your entire reading collection with up to 5 GB of cloud storage.": "5 GB cloud சேமிப்புடன் உங்கள் முழு வாசிப்பு சேகரிப்பையும் பாதுகாப்பாக சேமித்து அணுகவும்.",
|
||||
"Priority Support": "முன்னுரிமை ஆதரவு",
|
||||
"Enjoy faster responses and dedicated assistance whenever you need help.": "உங்களுக்கு உதவி தேவைப்படும் போதெல்லாம் விரைவான பதில்கள் மற்றும் அர்ப்பணிப்புள்ள உதவியை அனுபவிக்கவும்.",
|
||||
"Pro Plan": "Pro திட்டம்",
|
||||
"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": "மேம்பட்ட AI கருவிகள்",
|
||||
"Harness powerful AI tools for smarter reading, translation, and content discovery.": "புத்திசாலித்தனமான வாசிப்பு, மொழிபெயர்ப்பு மற்றும் உள்ளடக்க கண்டுபிடிப்புக்காக சக்திவாய்ந்த AI கருவிகளைப் பயன்படுத்துங்கள்.",
|
||||
"Translate up to 500,000 characters daily with the most accurate translation engine available.": "கிடைக்கக்கூடிய மிகவும் துல்லியமான மொழிபெயர்ப்பு இயந்திரத்துடன் தினசரி 500,000 எழுத்துகள் வரை மொழிபெயர்க்கவும்.",
|
||||
"Securely store and access your entire reading collection with up to 20 GB of cloud storage.": "20 GB cloud சேமிப்புடன் உங்கள் முழு வாசிப்பு சேகரிப்பையும் பாதுகாப்பாக சேமித்து அணுகவும்.",
|
||||
"Version {{version}}": "பதிப்பு {{version}}",
|
||||
"Check Update": "புதுப்பிப்பு சரிபார்க்கவும்",
|
||||
"Already the latest version": "ஏற்கனவே சமீபத்திய பதிப்பு",
|
||||
"Checking for updates...": "புதுப்பிப்புகளுக்கு சரிபார்க்கிறது...",
|
||||
"Error checking for updates": "புதுப்பிப்புகள் சரிபார்க்கும்போது பிழை",
|
||||
"Drop to Import Books": "புத்தகங்களை இறக்குமதி செய்ய இழுத்து விடவும்",
|
||||
"Terms of Service": "சேவை விதிமுறைகள்",
|
||||
"Privacy Policy": "தனியுரிமைக் கொள்கை",
|
||||
"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": "தொடரில் மொத்த புத்தகங்களை உள்ளிடவும்",
|
||||
"Publisher": "வெளியீட்டாளர்",
|
||||
"Enter publisher": "வெளியீட்டாளரை உள்ளிடவும்",
|
||||
"Publication Date": "வெளியீட்டு தேதி",
|
||||
"YYYY or YYYY-MM-DD": "YYYY அல்லது YYYY-MM-DD",
|
||||
"Identifier": "அடையாளங்காட்டி",
|
||||
"Subjects": "பாடங்கள்",
|
||||
"Fiction, Science, History": "கற்பனை, அறிவியல், வரலாறு",
|
||||
"Description": "விளக்கம்",
|
||||
"Enter book description": "புத்தக விளக்கத்தை உள்ளிடவும்",
|
||||
"Change cover image": "அட்டை படத்தை மாற்றவும்",
|
||||
"Replace": "மாற்றவும்",
|
||||
"Unlock cover": "அட்டையை திறக்கவும்",
|
||||
"Lock cover": "அட்டையை பூட்டவும்",
|
||||
"Auto-Retrieve Metadata": "Metadata ஐ தானாக பெறவும்",
|
||||
"Auto-Retrieve": "தானாக பெறவும்",
|
||||
"Unlock all fields": "அனைத்து புலங்களையும் திறக்கவும்",
|
||||
"Unlock All": "அனைத்தையும் திறக்கவும்",
|
||||
"Lock all fields": "அனைத்து புலங்களையும் பூட்டவும்",
|
||||
"Lock All": "அனைத்தையும் பூட்டவும்",
|
||||
"Reset": "மீட்டமைக்கவும்",
|
||||
"Are you sure to delete the selected book?": "தேர்ந்தெடுக்கப்பட்ட புத்தகத்தை நீக்க உறுதியா?",
|
||||
"Are you sure to delete the cloud backup of the selected book?": "தேர்ந்தெடுக்கப்பட்ட புத்தகத்தின் cloud backup ஐ நீக்க உறுதியா?",
|
||||
"Are you sure to delete the local copy of the selected book?": "தேர்ந்தெடுக்கப்பட்ட புத்தகத்தின் உள்ளூர் நகலை நீக்க உறுதியா?",
|
||||
"Edit Metadata": "Metadata ஐ திருத்தவும்",
|
||||
"Book Details": "புத்தக விவரங்கள்",
|
||||
"Unknown": "தெரியாதது",
|
||||
"Remove from Cloud & Device": "Cloud மற்றும் சாதனத்திலிருந்து நீக்கவும்",
|
||||
"Remove from Cloud Only": "Cloud இலிருந்து மட்டும் நீக்கவும்",
|
||||
"Remove from Device Only": "சாதனத்திலிருந்து மட்டும் நீக்கவும்",
|
||||
"Published": "வெளியிடப்பட்டது",
|
||||
"Updated": "புதுப்பிக்கப்பட்டது",
|
||||
"Added": "சேர்க்கப்பட்டது",
|
||||
"File Size": "கோப்பு அளவு",
|
||||
"No description available": "விளக்கம் கிடைக்கவில்லை",
|
||||
"Locked": "பூட்டப்பட்டது",
|
||||
"Select Metadata Source": "Metadata மூலத்தைத் தேர்வுசெய்யவும்",
|
||||
"Keep manual input": "கைமுறை உள்ளீட்டைத் தக்கவைக்கவும்",
|
||||
"A new version of Readest is available!": "Readest இன் புதிய பதிப்பு கிடைக்கிறது!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} கிடைக்கிறது (நிறுவப்பட்ட பதிப்பு {{currentVersion}}).",
|
||||
"Download and install now?": "இப்போது பதிவிறக்கி நிறுவ வேண்டுமா?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "{{contentLength}} இல் {{downloaded}} பதிவிறக்கம்",
|
||||
"Download finished": "பதிவிறக்கம் முடிந்தது",
|
||||
"DOWNLOAD & INSTALL": "பதிவிறக்கம் மற்றும் நிறுவல்",
|
||||
"Changelog": "மாற்ற பதிவு",
|
||||
"Software Update": "மென்பொருள் புதுப்பிப்பு",
|
||||
"What's New in Readest": "Readest இல் புதியது",
|
||||
"Select Files": "கோப்புகளைத் தேர்வுசெய்யவும்",
|
||||
"Select Image": "படத்தைத் தேர்வுசெய்யவும்",
|
||||
"Select Video": "வீடியோவைத் தேர்வுசெய்யவும்",
|
||||
"Select Audio": "ஆடியோவைத் தேர்வுசெய்யவும்",
|
||||
"Select Fonts": "எழுத்துருக்களைத் தேர்வுசெய்யவும்",
|
||||
"Storage": "சேமிப்பு",
|
||||
"{{percentage}}% of Cloud Sync Space Used.": "Cloud Sync இடத்தில் {{percentage}}% பயன்படுத்தப்பட்டுள்ளது.",
|
||||
"Translation Characters": "மொழிபெயர்ப்பு எழுத்துகள்",
|
||||
"{{percentage}}% of Daily Translation Characters Used.": "தினசரி மொழிபெயர்ப்பு எழுத்துகளில் {{percentage}}% பயன்படுத்தப்பட்டுள்ளது.",
|
||||
"Daily translation quota reached. Upgrade your plan to continue using AI translations.": "தினசரி மொழிபெயர்ப்பு ஒதுக்கீட்டை அடைந்துவிட்டது. 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": "சாம்பல்",
|
||||
"Sepia": "செபியா",
|
||||
"Grass": "புல்",
|
||||
"Cherry": "செர்ரி",
|
||||
"Sky": "வானம்",
|
||||
"Solarized": "சூரியமயம்",
|
||||
"Gruvbox": "Gruvbox",
|
||||
"Nord": "Nord",
|
||||
"Contrast": "மாறுபாடு",
|
||||
"Sunset": "சூரிய அஸ்தமனம்",
|
||||
"Reveal in Finder": "Finder இல் காட்டவும்",
|
||||
"Reveal in File Explorer": "File Explorer இல் காட்டவும்",
|
||||
"Reveal in Folder": "கோப்புறையில் காட்டவும்",
|
||||
"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": "அதிகரிக்கவும் அல்லது மீட்டமைக்கவும்"
|
||||
}
|
||||
@@ -63,7 +63,6 @@
|
||||
"Search Books...": "ค้นหาหนังสือ...",
|
||||
"Clear Search": "ล้างการค้นหา",
|
||||
"Import Books": "นำเข้าหนังสือ",
|
||||
"Select Multiple Books": "เลือกหลายเล่ม",
|
||||
"Select Books": "เลือกหนังสือ",
|
||||
"Deselect": "ยกเลิกเลือก",
|
||||
"Select All": "เลือกทั้งหมด",
|
||||
@@ -274,9 +273,9 @@
|
||||
"Match Whole Words": "ตรงทั้งคำ",
|
||||
"Match Diacritics": "ตรงตามเครื่องหมายวรรณยุกต์",
|
||||
"TOC": "สารบัญ",
|
||||
"Table of Contents": "สารบัญ",
|
||||
"Sidebar": "แถบข้าง",
|
||||
"Disable Translation": "ปิดการแปล",
|
||||
"Translation Not Available": "ไม่มีการแปล",
|
||||
"TTS not supported for PDF": "ไม่รองรับ TTS สำหรับ PDF",
|
||||
"No Timeout": "ไม่จำกัดเวลา",
|
||||
"{{value}} minute": "{{value}} นาที",
|
||||
@@ -439,7 +438,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "นิยาย, วิทยาศาสตร์, ประวัติศาสตร์",
|
||||
"Select Cover Image": "เลือกภาพปก",
|
||||
"Open Book in New Window": "เปิดหนังสือในหน้าต่างใหม่",
|
||||
"Voices for {{lang}}": "เสียงสำหรับ {{lang}}",
|
||||
"Yandex Translate": "Yandex Translate",
|
||||
@@ -475,12 +473,10 @@
|
||||
"Always use latest": "ใช้เวอร์ชันล่าสุดเสมอ",
|
||||
"Send changes only": "ส่งการเปลี่ยนแปลงเท่านั้น",
|
||||
"Receive changes only": "รับการเปลี่ยนแปลงเท่านั้น",
|
||||
"Disabled": "ปิดใช้งาน",
|
||||
"Checksum Method": "วิธีการตรวจสอบความถูกต้อง",
|
||||
"File Content (recommended)": "เนื้อหาไฟล์ (แนะนำ)",
|
||||
"File Name": "ชื่อไฟล์",
|
||||
"Device Name": "ชื่ออุปกรณ์",
|
||||
"Sync Tolerance": "ความทนทานต่อการซิงค์",
|
||||
"Connect to your KOReader Sync server.": "เชื่อมต่อกับเซิร์ฟเวอร์ KOReader Sync ของคุณ",
|
||||
"Server URL": "URL ของเซิร์ฟเวอร์",
|
||||
"Username": "ชื่อผู้ใช้",
|
||||
@@ -499,6 +495,69 @@
|
||||
"Approximately {{percentage}}%": "ประมาณ {{percentage}}%",
|
||||
"Failed to connect": "ไม่สามารถเชื่อมต่อได้",
|
||||
"Sync Server Connected": "เซิร์ฟเวอร์ซิงค์เชื่อมต่อแล้ว",
|
||||
"Precision: {{precision}} digits after the decimal": "ความแม่นยำ: {{precision}} หลักหลังจุดทศนิยม",
|
||||
"Sync as {{userDisplayName}}": "ซิงค์เป็น {{userDisplayName}}"
|
||||
"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": "ขยายหรือคืนค่า"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Ara...",
|
||||
"Select Book": "Kitap Seç",
|
||||
"Select Books": "Kitapları seç",
|
||||
"Select Multiple Books": "Birden fazla kitap seç",
|
||||
"Sepia": "Sepya",
|
||||
"Serif Font": "Serif Yazı Tipi",
|
||||
"Show Book Details": "Kitap Detaylarını Göster",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Kitap Detayları",
|
||||
"From Local File": "Yerel dosyadan",
|
||||
"TOC": "İçindekiler",
|
||||
"Table of Contents": "İçindekiler",
|
||||
"Book uploaded: {{title}}": "Kitap yüklendi: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Kitap yüklenemedi: {{title}}",
|
||||
"Book downloaded: {{title}}": "Kitap indirildi: {{title}}",
|
||||
@@ -333,7 +333,6 @@
|
||||
"Left Margin (px)": "Sol Kenar Boşluğu (px)",
|
||||
"Column Gap (%)": "Sütun Aralığı (%)",
|
||||
"Always Show Status Bar": "Durum Çubuğunu Her Zaman Göster",
|
||||
"Translation Not Available": "Çeviri Mevcut Değil",
|
||||
"Custom Content CSS": "Özel İçerik CSS'si",
|
||||
"Enter CSS for book content styling...": "Kitap içeriği stili için CSS girin...",
|
||||
"Custom Reader UI CSS": "Özel Okuyucu Arayüzü CSS'si",
|
||||
@@ -443,7 +442,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Kurgu, Bilim, Tarih",
|
||||
"Select Cover Image": "Kapak Resmi Seç",
|
||||
"Open Book in New Window": "Kitabı Yeni Pencerede Aç",
|
||||
"Voices for {{lang}}": "{{lang}} için Sesler",
|
||||
"Yandex Translate": "Yandex Çeviri",
|
||||
@@ -479,12 +477,10 @@
|
||||
"Always use latest": "Her Zaman En Sonunu Kullan",
|
||||
"Send changes only": "Sadece Değişiklikleri Gönder",
|
||||
"Receive changes only": "Sadece Değişiklikleri Al",
|
||||
"Disabled": "Devre Dışı",
|
||||
"Checksum Method": "Kontrol Toplamı Yöntemi",
|
||||
"File Content (recommended)": "Dosya İçeriği (önerilen)",
|
||||
"File Name": "Dosya Adı",
|
||||
"Device Name": "Cihaz Adı",
|
||||
"Sync Tolerance": "Senkronizasyon Toleransı",
|
||||
"Connect to your KOReader Sync server.": "KOReader Senkronizasyon sunucunuza bağlanın.",
|
||||
"Server URL": "Sunucu URL'si",
|
||||
"Username": "Kullanıcı Adı",
|
||||
@@ -503,6 +499,69 @@
|
||||
"Approximately {{percentage}}%": "Yaklaşık {{percentage}}%",
|
||||
"Failed to connect": "Bağlanma başarısız oldu",
|
||||
"Sync Server Connected": "Senkronizasyon Sunucusu Bağlandı",
|
||||
"Precision: {{precision}} digits after the decimal": "Hassasiyet: {{precision}} ondalık basamaktan sonra",
|
||||
"Sync as {{userDisplayName}}": "Senkronize et {{userDisplayName}} olarak"
|
||||
"Sync as {{userDisplayName}}": "Senkronize et {{userDisplayName}} olarak",
|
||||
"Custom Fonts": "Özel Yazı Tipleri",
|
||||
"Cancel Delete": "Silmeyi İptal Et",
|
||||
"Import Font": "Yazı Tipi İçe Aktar",
|
||||
"Delete Font": "Yazı Tipini Sil",
|
||||
"Tips": "İpuçları",
|
||||
"Custom fonts can be selected from the Font Face menu": "Özel yazı tipleri Yazı Tipi menüsünden seçilebilir",
|
||||
"Manage Custom Fonts": "Özel Yazı Tiplerini Yönet",
|
||||
"Select Files": "Dosyaları Seç",
|
||||
"Select Image": "Resim Seç",
|
||||
"Select Video": "Video Seç",
|
||||
"Select Audio": "Ses Seç",
|
||||
"Select Fonts": "Yazı Tiplerini Seç",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Desteklenen yazı tipi formatları: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "İlerlemeyi Gönder",
|
||||
"Pull Progress": "İlerlemeyi Al",
|
||||
"Previous Paragraph": "Önceki Paragraf",
|
||||
"Previous Sentence": "Önceki Cümle",
|
||||
"Pause": "Duraklat",
|
||||
"Play": "Oynat",
|
||||
"Next Sentence": "Sonraki Cümle",
|
||||
"Next Paragraph": "Sonraki Paragraf",
|
||||
"Separate Cover Page": "Ayrı Kapak Sayfası",
|
||||
"Resize Notebook": "Defteri Yeniden Boyutlandır",
|
||||
"Resize Sidebar": "Kenar Çubuğunu Yeniden Boyutlandır",
|
||||
"Get Help from the Readest Community": "Readest Topluluğundan Yardım Alın",
|
||||
"Remove cover image": "Kapak resmini kaldır",
|
||||
"Bookshelf": "Kitaplık",
|
||||
"View Menu": "Menüyü Görüntüle",
|
||||
"Settings Menu": "Ayarlar Menüsü",
|
||||
"View account details and quota": "Hesap detaylarını ve kotayı görüntüle",
|
||||
"Library Header": "Kütüphane Başlığı",
|
||||
"Book Content": "Kitap İçeriği",
|
||||
"Footer Bar": "Alt Çubuk",
|
||||
"Header Bar": "Üst Çubuk",
|
||||
"View Options": "Görünüm Seçenekleri",
|
||||
"Book Menu": "Kitap Menüsü",
|
||||
"Search Options": "Arama Seçenekleri",
|
||||
"Close": "Kapat",
|
||||
"Delete Book Options": "Kitap Silme Seçenekleri",
|
||||
"ON": "AÇIK",
|
||||
"OFF": "KAPALI",
|
||||
"Reading Progress": "Okuma İlerlemesi",
|
||||
"Page Margin": "Sayfa Kenar Boşluğu",
|
||||
"Remove Bookmark": "Yer İşareti Kaldır",
|
||||
"Add Bookmark": "Yer İşareti Ekle",
|
||||
"Books Content": "Kitap İçeriği",
|
||||
"Jump to Location": "Konuma Atlama",
|
||||
"Unpin Notebook": "Defteri Sabitlemeyi Kaldır",
|
||||
"Pin Notebook": "Defteri Sabitle",
|
||||
"Hide Search Bar": "Arama Çubuğunu Gizle",
|
||||
"Show Search Bar": "Arama Çubuğunu Göster",
|
||||
"On {{current}} of {{total}} page": "{{current}} / {{total}} sayfada",
|
||||
"Section Title": "Bölüm Başlığı",
|
||||
"Decrease": "Azalt",
|
||||
"Increase": "Artır",
|
||||
"Settings Panels": "Ayarlar Panelleri",
|
||||
"Settings": "Ayarlar",
|
||||
"Unpin Sidebar": "Kenar Çubuğunu Sabitlemeyi Kaldır",
|
||||
"Pin Sidebar": "Kenar Çubuğunu Sabitle",
|
||||
"Toggle Sidebar": "Kenar Çubuğunu Değiştir",
|
||||
"Toggle Translation": "Çeviriyi Değiştir",
|
||||
"Translation Disabled": "Çeviri Devre Dışı",
|
||||
"Minimize": "Küçült",
|
||||
"Maximize or Restore": "Büyüt veya Geri Yükle"
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
"Fast": "Швидко",
|
||||
"Font": "Шрифт",
|
||||
"Font & Layout": "Шрифт та макет",
|
||||
"Font Face": "Накреслення шрифту",
|
||||
"Font Face": "Налаштування шрифту",
|
||||
"Font Family": "Сімейство шрифтів",
|
||||
"Font Size": "Розмір шрифту",
|
||||
"Full Justification": "Повне вирівнювання",
|
||||
"Global Settings": "Глобальні налаштування",
|
||||
"Global Settings": "Ґлобальні налаштування",
|
||||
"Go Back": "Назад",
|
||||
"Go Forward": "Вперед",
|
||||
"Grass": "Трав'яний",
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Пошук...",
|
||||
"Select Book": "Вибрати книгу",
|
||||
"Select Books": "Вибрати книги",
|
||||
"Select Multiple Books": "Вибрати кілька книг",
|
||||
"Sepia": "Сепія",
|
||||
"Serif Font": "Шрифт із засічками",
|
||||
"Show Book Details": "Показати деталі книги",
|
||||
@@ -93,7 +92,7 @@
|
||||
"Slow": "Повільно",
|
||||
"Solarized": "Сонячний",
|
||||
"Speak": "Озвучити",
|
||||
"Subjects": "Теми",
|
||||
"Subjects": "Жанри",
|
||||
"System Fonts": "Системні шрифти",
|
||||
"Theme Color": "Колір теми",
|
||||
"Theme Mode": "Режим теми",
|
||||
@@ -114,10 +113,11 @@
|
||||
"Apply to This Book": "Застосувати лише до цієї книги",
|
||||
"Unable to fetch the translation. Try again later.": "Не вдалося отримати переклад. Спробуйте пізніше.",
|
||||
"Check Update": "Перевірити оновлення",
|
||||
"Already the latest version": "У вас вже встановлена остання версія",
|
||||
"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}}",
|
||||
@@ -237,7 +237,7 @@
|
||||
"Header & Footer": "Заголовок і нижній колонтитул",
|
||||
"Apply also in Scrolled Mode": "Застосувати також у прокрученому режимі",
|
||||
"A new version of Readest is available!": "Доступна нова версія Readest!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} доступна (встановлено {{currentVersion}}).",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Нова версія Readest {{newVersion}} доступна. (Поточна {{currentVersion}}).",
|
||||
"Download and install now?": "Завантажити та встановити зараз?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Завантаженно {{downloaded}} з {{contentLength}}",
|
||||
"Download finished": "Завантаження завершено",
|
||||
@@ -339,7 +339,6 @@
|
||||
"Left Margin (px)": "Лівий відступ (px)",
|
||||
"Column Gap (%)": "Проміжок між стовпцями (%)",
|
||||
"Always Show Status Bar": "Завжди показувати панель стану",
|
||||
"Translation Not Available": "Переклад недоступний",
|
||||
"Custom Content CSS": "Користуацький CSS вмісту",
|
||||
"Enter CSS for book content styling...": "Введіть CSS для стилізації вмісту книги...",
|
||||
"Custom Reader UI CSS": "CSS інтерфейсу",
|
||||
@@ -451,7 +450,6 @@
|
||||
"Google Books": "Google Книги",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Художня література, Наукова фантастика, Історія",
|
||||
"Select Cover Image": "Оберіть зображення обкладинки",
|
||||
"Open Book in New Window": "Відкрити книгу в новому вікні",
|
||||
"Voices for {{lang}}": "Голоси для {{lang}}",
|
||||
"Yandex Translate": "Yandex Перекладач (росія)",
|
||||
@@ -469,48 +467,109 @@
|
||||
"Disable Double Click": "Вимкнути подвійне натискання",
|
||||
"TTS not supported for this document": "TTS не підтримується в цьому документі",
|
||||
"Reset Password": "Скинути пароль",
|
||||
"Show Reading Progress": "Показати прогрес читання",
|
||||
"Reading Progress Style": "Стиль прогресу читання",
|
||||
"Show Reading Progress": "Показати проґрес читання",
|
||||
"Reading Progress Style": "Стиль проґресу читання",
|
||||
"Page Number": "Номер сторінки",
|
||||
"Percentage": "Процент",
|
||||
"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": "Відключено",
|
||||
"Remove from Cloud Only": "Видалити тільки із хмари",
|
||||
"Remove from Device Only": "Видалити тільки із пристрою",
|
||||
"Disconnected": "Від'єднано",
|
||||
"KOReader Sync Settings": "Налаштування синхронізації KOReader",
|
||||
"Sync Strategy": "Стратегія синхронізації",
|
||||
"Ask on conflict": "Запитувати при конфлікті",
|
||||
"Always use latest": "Завжди використовувати останню версію",
|
||||
"Always use latest": "Завжди використовувати найновішу",
|
||||
"Send changes only": "Відправити тільки зміни",
|
||||
"Receive changes only": "Отримати тільки зміни",
|
||||
"Disabled": "Вимкнено",
|
||||
"Checksum Method": "Метод контрольної суми",
|
||||
"Checksum Method": "Метод контролю суми",
|
||||
"File Content (recommended)": "Вміст файлу (рекомендується)",
|
||||
"File Name": "Ім'я файлу",
|
||||
"Device Name": "Ім'я пристрою",
|
||||
"Sync Tolerance": "Допустима похибка синхронізації",
|
||||
"Connect to your KOReader Sync server.": "Підключіться до свого сервера синхронізації KOReader.",
|
||||
"Connect to your KOReader Sync server.": "Під'єдайте свій сервер синхронізації KOReader.",
|
||||
"Server URL": "URL сервера",
|
||||
"Username": "Ім'я користувача",
|
||||
"Your Username": "Ваше ім'я користувача",
|
||||
"Password": "Пароль",
|
||||
"Connect": "Підключитися",
|
||||
"Connect": "Під'єднатися",
|
||||
"KOReader Sync": "Синхронізація KOReader",
|
||||
"Sync Conflict": "Конфлікт синхронізації",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "Синхронізувати прогрес читання з \"{{deviceName}}\"?",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "Синхронізувати прогрес читання із \"{{deviceName}}\"?",
|
||||
"another device": "інший пристрій",
|
||||
"Local Progress": "Локальний прогрес",
|
||||
"Remote Progress": "Віддалений прогрес",
|
||||
"Local Progress": "Локальний проґрес",
|
||||
"Remote Progress": "Проґрес у хмарі",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "Сторінка {{page}} з {{total}} ({{percentage}}%)",
|
||||
"Current position": "Поточна позиція",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Приблизно сторінка {{page}} з {{total}} ({{percentage}}%)",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Приблизно сторінка {{page}} із {{total}} ({{percentage}}%)",
|
||||
"Approximately {{percentage}}%": "Приблизно {{percentage}}%",
|
||||
"Failed to connect": "Не вдалося підключитися",
|
||||
"Sync Server Connected": "Сервер синхронізації підключено",
|
||||
"Precision: {{precision}} digits after the decimal": "Точність: {{precision}} знаків після коми",
|
||||
"Sync as {{userDisplayName}}": "Синхронізувати як {{userDisplayName}}"
|
||||
"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": "Збільшити або відновити"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "Tìm kiếm...",
|
||||
"Select Book": "Chọn sách",
|
||||
"Select Books": "Chọn sách",
|
||||
"Select Multiple Books": "Chọn nhiều sách",
|
||||
"Sepia": "Nâu cổ",
|
||||
"Serif Font": "Phông chữ có chân",
|
||||
"Show Book Details": "Hiển thị chi tiết sách",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "Chi tiết sách",
|
||||
"From Local File": "Từ tệp địa phương",
|
||||
"TOC": "Mục lục",
|
||||
"Table of Contents": "Mục lục",
|
||||
"Book uploaded: {{title}}": "Sách đã tải lên: {{title}}",
|
||||
"Failed to upload book: {{title}}": "Không thể tải lên sách: {{title}}",
|
||||
"Book downloaded: {{title}}": "Sách đã tải về: {{title}}",
|
||||
@@ -330,7 +330,6 @@
|
||||
"Left Margin (px)": "Lề trái (px)",
|
||||
"Column Gap (%)": "Khoảng cách cột (%)",
|
||||
"Always Show Status Bar": "Luôn hiển thị thanh trạng thái",
|
||||
"Translation Not Available": "Không có bản dịch",
|
||||
"Custom Content CSS": "CSS nội dung",
|
||||
"Enter CSS for book content styling...": "Nhập CSS cho phần nội dung sách...",
|
||||
"Custom Reader UI CSS": "CSS giao diện đọc",
|
||||
@@ -439,7 +438,6 @@
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
"Fiction, Science, History": "Tiểu thuyết, Khoa học, Lịch sử",
|
||||
"Select Cover Image": "Chọn ảnh bìa",
|
||||
"Open Book in New Window": "Mở Sách trong Cửa sổ Mới",
|
||||
"Voices for {{lang}}": "Giọng nói cho {{lang}}",
|
||||
"Yandex Translate": "Yandex Dịch",
|
||||
@@ -475,12 +473,10 @@
|
||||
"Always use latest": "Luôn sử dụng phiên bản mới nhất",
|
||||
"Send changes only": "Chỉ gửi thay đổi",
|
||||
"Receive changes only": "Chỉ nhận thay đổi",
|
||||
"Disabled": "Đã tắt",
|
||||
"Checksum Method": "Phương pháp kiểm tra",
|
||||
"File Content (recommended)": "Nội dung tệp (được khuyến nghị)",
|
||||
"File Name": "Tên tệp",
|
||||
"Device Name": "Tên thiết bị",
|
||||
"Sync Tolerance": "Độ dung sai đồng bộ",
|
||||
"Connect to your KOReader Sync server.": "Kết nối với máy chủ đồng bộ KOReader của bạn.",
|
||||
"Server URL": "URL máy chủ",
|
||||
"Username": "Tên người dùng",
|
||||
@@ -499,6 +495,69 @@
|
||||
"Approximately {{percentage}}%": "Khoảng {{percentage}}%",
|
||||
"Failed to connect": "Không thể kết nối",
|
||||
"Sync Server Connected": "Máy chủ đồng bộ đã kết nối",
|
||||
"Precision: {{precision}} digits after the decimal": "Độ chính xác: {{precision}} chữ số sau dấu thập phân",
|
||||
"Sync as {{userDisplayName}}": "Đồng bộ dưới tên {{userDisplayName}}"
|
||||
"Sync as {{userDisplayName}}": "Đồng bộ dưới tên {{userDisplayName}}",
|
||||
"Custom Fonts": "Phông Chữ Tùy Chỉnh",
|
||||
"Cancel Delete": "Hủy Xóa",
|
||||
"Import Font": "Nhập Phông Chữ",
|
||||
"Delete Font": "Xóa Phông Chữ",
|
||||
"Tips": "Mẹo",
|
||||
"Custom fonts can be selected from the Font Face menu": "Phông chữ tùy chỉnh có thể được chọn từ menu Phông Chữ",
|
||||
"Manage Custom Fonts": "Quản Lý Phông Chữ Tùy Chỉnh",
|
||||
"Select Files": "Chọn Tệp",
|
||||
"Select Image": "Chọn Hình Ảnh",
|
||||
"Select Video": "Chọn Video",
|
||||
"Select Audio": "Chọn Âm Thanh",
|
||||
"Select Fonts": "Chọn Phông Chữ",
|
||||
"Supported font formats: .ttf, .otf, .woff, .woff2": "Định dạng phông chữ được hỗ trợ: .ttf, .otf, .woff, .woff2",
|
||||
"Push Progress": "Đẩy tiến độ",
|
||||
"Pull Progress": "Kéo tiến độ",
|
||||
"Previous Paragraph": "Đoạn trước",
|
||||
"Previous Sentence": "Câu trước",
|
||||
"Pause": "Tạm dừng",
|
||||
"Play": "Phát",
|
||||
"Next Sentence": "Câu tiếp theo",
|
||||
"Next Paragraph": "Đoạn tiếp theo",
|
||||
"Separate Cover Page": "Trang Bìa Riêng",
|
||||
"Resize Notebook": "Thay Đổi Kích Thước Sổ Ghi Chép",
|
||||
"Resize Sidebar": "Thay Đổi Kích Thước Thanh Bên",
|
||||
"Get Help from the Readest Community": "Nhận Trợ Giúp từ Cộng Đồng Readest",
|
||||
"Remove cover image": "Xóa ảnh bìa",
|
||||
"Bookshelf": "Kệ sách",
|
||||
"View Menu": "Xem menu",
|
||||
"Settings Menu": "Menu cài đặt",
|
||||
"View account details and quota": "Xem chi tiết tài khoản và hạn mức",
|
||||
"Library Header": "Tiêu đề thư viện",
|
||||
"Book Content": "Nội dung sách",
|
||||
"Footer Bar": "Thanh chân trang",
|
||||
"Header Bar": "Thanh đầu trang",
|
||||
"View Options": "Tùy chọn xem",
|
||||
"Book Menu": "Menu sách",
|
||||
"Search Options": "Tùy chọn tìm kiếm",
|
||||
"Close": "Đóng",
|
||||
"Delete Book Options": "Tùy chọn xóa sách",
|
||||
"ON": "BẬT",
|
||||
"OFF": "TẮT",
|
||||
"Reading Progress": "Tiến độ đọc",
|
||||
"Page Margin": "Lề trang",
|
||||
"Remove Bookmark": "Xóa đánh dấu",
|
||||
"Add Bookmark": "Thêm đánh dấu",
|
||||
"Books Content": "Nội dung sách",
|
||||
"Jump to Location": "Nhảy đến vị trí",
|
||||
"Unpin Notebook": "Bỏ ghim sổ tay",
|
||||
"Pin Notebook": "Ghim sổ tay",
|
||||
"Hide Search Bar": "Ẩn thanh tìm kiếm",
|
||||
"Show Search Bar": "Hiện thanh tìm kiếm",
|
||||
"On {{current}} of {{total}} page": "Trên {{current}} của {{total}} trang",
|
||||
"Section Title": "Tiêu đề phần",
|
||||
"Decrease": "Giảm",
|
||||
"Increase": "Tăng",
|
||||
"Settings Panels": "Các bảng điều khiển cài đặt",
|
||||
"Settings": "Cài đặt",
|
||||
"Unpin Sidebar": "Bỏ ghim thanh bên",
|
||||
"Pin Sidebar": "Ghim thanh bên",
|
||||
"Toggle Sidebar": "Chuyển đổi thanh bên",
|
||||
"Toggle Translation": "Chuyển đổi dịch thuật",
|
||||
"Translation Disabled": "Dịch thuật đã tắt",
|
||||
"Minimize": "Thu nhỏ",
|
||||
"Maximize or Restore": "Tối đa hóa hoặc Khôi phục"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "搜索...",
|
||||
"Select Book": "选择书籍",
|
||||
"Select Books": "选择书籍",
|
||||
"Select Multiple Books": "选择多本书籍",
|
||||
"Sepia": "旧韵",
|
||||
"Serif Font": "衬线字体",
|
||||
"Show Book Details": "显示书籍详情",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "书籍详情",
|
||||
"From Local File": "从本地文件导入",
|
||||
"TOC": "目录",
|
||||
"Table of Contents": "目录",
|
||||
"Book uploaded: {{title}}": "书籍已上传:{{title}}",
|
||||
"Failed to upload book: {{title}}": "上传书籍失败:{{title}}",
|
||||
"Book downloaded: {{title}}": "书籍已下载:{{title}}",
|
||||
@@ -330,7 +330,6 @@
|
||||
"Left Margin (px)": "左边距",
|
||||
"Column Gap (%)": "列间距",
|
||||
"Always Show Status Bar": "始终显示状态栏",
|
||||
"Translation Not Available": "翻译不可用",
|
||||
"Custom Content CSS": "自定义内容 CSS",
|
||||
"Enter CSS for book content styling...": "输入书籍内容样式的CSS…",
|
||||
"Custom Reader UI CSS": "自定义界面 CSS",
|
||||
@@ -439,7 +438,6 @@
|
||||
"Google Books": "谷歌图书",
|
||||
"Open Library": "开放图书馆",
|
||||
"Fiction, Science, History": "小说,科学,历史",
|
||||
"Select Cover Image": "选择封面图片",
|
||||
"Open Book in New Window": "在新窗口中打开书籍",
|
||||
"Voices for {{lang}}": "{{lang}} 的语音",
|
||||
"Yandex Translate": "Yandex 翻译",
|
||||
@@ -475,12 +473,10 @@
|
||||
"Always use latest": "始终使用最新",
|
||||
"Send changes only": "仅发送更改",
|
||||
"Receive changes only": "仅接收更改",
|
||||
"Disabled": "已禁用",
|
||||
"Checksum Method": "校验和方法",
|
||||
"File Content (recommended)": "文件内容(推荐)",
|
||||
"File Name": "文件名",
|
||||
"Device Name": "设备名称",
|
||||
"Sync Tolerance": "同步容忍度",
|
||||
"Connect to your KOReader Sync server.": "连接到您的 KOReader 同步服务器。",
|
||||
"Server URL": "服务器 URL",
|
||||
"Username": "用户名",
|
||||
@@ -499,6 +495,69 @@
|
||||
"Approximately {{percentage}}%": "大约 {{percentage}}%",
|
||||
"Failed to connect": "连接失败",
|
||||
"Sync Server Connected": "同步服务器已连接",
|
||||
"Precision: {{precision}} digits after the decimal": "精度:{{precision}} 位小数",
|
||||
"Sync as {{userDisplayName}}": "以 {{userDisplayName}} 身份同步"
|
||||
"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": "最大化或还原"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"Search...": "搜索...",
|
||||
"Select Book": "選擇書籍",
|
||||
"Select Books": "選擇書籍",
|
||||
"Select Multiple Books": "選擇多本書籍",
|
||||
"Sepia": "舊韻",
|
||||
"Serif Font": "襯線字體",
|
||||
"Show Book Details": "顯示書籍詳情",
|
||||
@@ -118,6 +117,7 @@
|
||||
"Book Details": "書籍詳情",
|
||||
"From Local File": "從本地文件導入",
|
||||
"TOC": "目錄",
|
||||
"Table of Contents": "目錄",
|
||||
"Book uploaded: {{title}}": "書籍已上傳:{{title}}",
|
||||
"Failed to upload book: {{title}}": "書籍上傳失敗:{{title}}",
|
||||
"Book downloaded: {{title}}": "書籍已下載:{{title}}",
|
||||
@@ -330,7 +330,6 @@
|
||||
"Left Margin (px)": "左邊距",
|
||||
"Column Gap (%)": "欄間距",
|
||||
"Always Show Status Bar": "始終顯示狀態欄",
|
||||
"Translation Not Available": "翻譯不可用",
|
||||
"Custom Content CSS": "自訂內容 CSS",
|
||||
"Enter CSS for book content styling...": "輸入書籍內容樣式的CSS…",
|
||||
"Custom Reader UI CSS": "自訂介面 CSS",
|
||||
@@ -439,7 +438,6 @@
|
||||
"Google Books": "Google 圖書",
|
||||
"Open Library": "開放圖書館",
|
||||
"Fiction, Science, History": "小說,科學,歷史",
|
||||
"Select Cover Image": "選擇封面圖片",
|
||||
"Open Book in New Window": "在新視窗中打開書籍",
|
||||
"Voices for {{lang}}": "{{lang}} 的語音",
|
||||
"Yandex Translate": "Yandex 翻譯",
|
||||
@@ -475,12 +473,10 @@
|
||||
"Always use latest": "始終使用最新",
|
||||
"Send changes only": "僅發送更改",
|
||||
"Receive changes only": "僅接收更改",
|
||||
"Disabled": "已禁用",
|
||||
"Checksum Method": "校驗和方法",
|
||||
"File Content (recommended)": "文件內容(推薦)",
|
||||
"File Name": "文件名",
|
||||
"Device Name": "設備名稱",
|
||||
"Sync Tolerance": "同步容忍度",
|
||||
"Connect to your KOReader Sync server.": "連接到您的 KOReader 同步伺服器。",
|
||||
"Server URL": "伺服器 URL",
|
||||
"Username": "用戶名",
|
||||
@@ -499,6 +495,69 @@
|
||||
"Approximately {{percentage}}%": "大約 {{percentage}}%",
|
||||
"Failed to connect": "連接失敗",
|
||||
"Sync Server Connected": "同步伺服器已連接",
|
||||
"Precision: {{precision}} digits after the decimal": "精度: {{precision}} 位小數",
|
||||
"Sync as {{userDisplayName}}": "以 {{userDisplayName}} 身份同步"
|
||||
"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": "最大化或還原"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,85 @@
|
||||
{
|
||||
"releases": {
|
||||
"0.9.81": {
|
||||
"date": "2025-09-19",
|
||||
"notes": [
|
||||
"TTS: Added desktop common media control support when TTS is playing",
|
||||
"PDF: Disabled swipe-up gesture for toggling the action bar when zoomed in",
|
||||
"Fonts: Fixed an issue where embedded fonts in ebooks were occasionally not applied",
|
||||
"Reader: Fixed an issue where newly created highlights sometimes would not appear",
|
||||
"Reader: Enabled scrolling in the menu when the menu is too long"
|
||||
]
|
||||
},
|
||||
"0.9.80": {
|
||||
"date": "2025-09-19",
|
||||
"notes": [
|
||||
"Accessibility: Added support for popular screen readers on all platforms, making books accessible to readers with visual impairments",
|
||||
"Sync: Progress is now synced across different versions of the same book by aggregated metadata",
|
||||
"Library: Groups are now displayed in list view for easier organization",
|
||||
"Library: Added delete confirmation when removing books from the context menu",
|
||||
"Fonts: Added support for fonts with subfamilies when importing TTF/OTF font files",
|
||||
"Layout: Prevented layout shift in Android when the virtual keyboard appears"
|
||||
]
|
||||
},
|
||||
"0.9.78": {
|
||||
"date": "2025-09-06",
|
||||
"notes": [
|
||||
"TTS: Added option to keep a persistent TTS bar at the bottom of the screen",
|
||||
"TTS: Added previous/next sentence navigation controls in the TTS bar",
|
||||
"TTS: Added media control support on iOS lock screen and AirPods",
|
||||
"TTS: Fixed an issue where headings could not be highlighted for TTS",
|
||||
"Sync: Fixed occasional sync failures between KOReader and Readest",
|
||||
"Fonts: Grouped font styles into custom font families for easier selection",
|
||||
"Reader: Improved text selection sensitivity when using a stylus",
|
||||
"Reader: Books can now be properly closed using the system back button on Android",
|
||||
"EPUB: Fixed parsing to correctly detect the front cover in some EPUB files",
|
||||
"PDF: Added zoom level, zoom mode, and spread mode settings for PDFs",
|
||||
"Reader: Bookmarks can now be edited directly in the sidebar",
|
||||
"Layout: Prevented layout shift when the virtual keyboard appears on Android",
|
||||
"TXT: Improved chapter detection and added support for nested table of contents in TXT files"
|
||||
]
|
||||
},
|
||||
"0.9.76": {
|
||||
"date": "2025-08-30",
|
||||
"notes": [
|
||||
"Sync: Moved KOReader sync controls into the reader sidebar with a manual push/pull option",
|
||||
"Sync: Improved tolerance and reliability for KOReader sync operations",
|
||||
"Sync: Added support for more KOReader server implementations",
|
||||
"Fonts: Added support for importing multiple styles within a font family",
|
||||
"Reader: Fixed an issue where footnotes are empty in some EPUB files",
|
||||
"Theme: Applied theme background color for PDFs on macOS",
|
||||
"Reader: Added support for importing AZW3 files on Android",
|
||||
"Reader: Reduced screen flash when launching the app and opening books",
|
||||
"Layout: Fixed an issue where the grouping modal would not appear on Linux"
|
||||
]
|
||||
},
|
||||
"0.9.75": {
|
||||
"date": "2025-08-22",
|
||||
"notes": [
|
||||
"Fonts: Added support for importing local TTF and ODF fonts",
|
||||
"TTS: Reduced the pause between sentences when using Edge TTS",
|
||||
"TTS: Fixed an issue where voices list could not be opened on older versions of iOS",
|
||||
"TOC: Fixed an issue where nested TOC items would not collapse properly in some books",
|
||||
"Translator: Fixed an issue where translations in the popup did not refresh correctly",
|
||||
"Library: Added option to sort authors with last name first",
|
||||
"Layout: Fixed mismatch in close button position between the reader and library pages",
|
||||
"Layout: Book title now remains centered in the available space",
|
||||
"Layout: Fixed footer bar layout in landscape mode"
|
||||
]
|
||||
},
|
||||
"0.9.72": {
|
||||
"date": "2025-08-18",
|
||||
"notes": [
|
||||
"Fixed Edge TTS voice playback for EPUBs",
|
||||
"Reduced accidental page flips when toggling toolbars",
|
||||
"Fixed images with background colors not displaying correctly in some books",
|
||||
"Added support for custom KOReader Sync Servers on your local network (LAN)"
|
||||
]
|
||||
},
|
||||
"0.9.71": {
|
||||
"date": "2025-08-13",
|
||||
"notes": [
|
||||
"Sync: Added two ways to sync reading progress with Koreader devices",
|
||||
"Sync: Added two ways to sync reading progress with KOReader devices",
|
||||
"EPUB: Applied monospace font settings",
|
||||
"EPUB: Fixed initial text alignment for some EPUB files",
|
||||
"PDF: Fixed issue opening large PDF files on Android",
|
||||
|
||||
@@ -23,6 +23,8 @@ 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"
|
||||
PERMISSION_LINE='<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>'
|
||||
|
||||
if [[ ! -f "$PROPERTIES_FILE" ]]; then
|
||||
echo "❌ File not found: $PROPERTIES_FILE"
|
||||
@@ -36,9 +38,31 @@ mv "$tmpfile" "$PROPERTIES_FILE"
|
||||
|
||||
echo "✅ Updated $PROPERTIES_FILE"
|
||||
|
||||
# --- REMOVE PERMISSION BEFORE BUILD ---
|
||||
if grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST"; then
|
||||
echo "🧹 Removing REQUEST_INSTALL_PACKAGES from AndroidManifest.xml"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "/REQUEST_INSTALL_PACKAGES/d" "$MANIFEST"
|
||||
else
|
||||
sed -i "/REQUEST_INSTALL_PACKAGES/d" "$MANIFEST"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "🚀 Running: pnpm tauri android build"
|
||||
pnpm tauri android build
|
||||
|
||||
# --- ADD PERMISSION BACK AFTER BUILD ---
|
||||
if ! grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST"; then
|
||||
echo "♻️ Restoring REQUEST_INSTALL_PACKAGES in AndroidManifest.xml"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "/android.permission.INTERNET/a\\
|
||||
$PERMISSION_LINE
|
||||
" "$MANIFEST"
|
||||
else
|
||||
sed -i "/android.permission.INTERNET/a \ $PERMISSION_LINE" "$MANIFEST"
|
||||
fi
|
||||
fi
|
||||
|
||||
source .env.google-play.local
|
||||
if [[ -z "$GOOGLE_PLAY_JSON_KEY_FILE" ]]; then
|
||||
echo "❌ GOOGLE_PLAY_JSON_KEY_FILE is not set"
|
||||
|
||||
@@ -41,7 +41,7 @@ tauri-plugin-log = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-http = "2"
|
||||
tauri-plugin-http = { version = "2", features = ["dangerous-settings"] }
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-process = "2"
|
||||
tauri-plugin-oauth = "2"
|
||||
|
||||
@@ -87,6 +87,12 @@
|
||||
},
|
||||
{
|
||||
"url": "https://translate.googleapis.com"
|
||||
},
|
||||
{
|
||||
"url": "http://*:*"
|
||||
},
|
||||
{
|
||||
"url": "https://*:*"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
@@ -30,27 +31,55 @@
|
||||
|
||||
<data android:scheme="content" />
|
||||
<data android:scheme="file" />
|
||||
|
||||
<data android:mimeType="application/epub+zip" />
|
||||
<data android:mimeType="application/pdf" />
|
||||
<data android:mimeType="application/fb2" />
|
||||
<data android:mimeType="application/x-fb2" />
|
||||
<data android:mimeType="application/mobi"/>
|
||||
<data android:mimeType="application/azw"/>
|
||||
<data android:mimeType="application/azw3"/>
|
||||
<data android:mimeType="application/x-mobipocket-ebook" />
|
||||
<data android:mimeType="application/vnd.amazon.ebook" />
|
||||
<data android:mimeType="application/vnd.amazon.mobi8-ebook" />
|
||||
<data android:mimeType="application/vnd.comicbook+zip" />
|
||||
<data android:mimeType="application/x-cbz" />
|
||||
<data android:mimeType="application/x-mobipocket-ebook" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:mimeType="text/plain" />
|
||||
<data android:mimeType="application/octet-stream"/>
|
||||
<data android:mimeType="application/x-font-ttf"/>
|
||||
<data android:mimeType="application/x-font-otf"/>
|
||||
</intent-filter>
|
||||
|
||||
<data android:pathPattern="/.*\\.epub" />
|
||||
<data android:pathPattern="/.*\\.pdf" />
|
||||
<data android:pathPattern="/.*\\.fb2" />
|
||||
<data android:pathPattern="/.*\\.fb2.zip" />
|
||||
<data android:pathPattern="/.*\\.mobi" />
|
||||
<data android:pathPattern="/.*\\.azw" />
|
||||
<data android:pathPattern="/.*\\.cbz" />
|
||||
<data android:pathPattern="/.*\\.zip" />
|
||||
<data android:pathPattern="/.*\\.txt" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="content" />
|
||||
<data android:scheme="file" />
|
||||
<data android:mimeType="*/*" />
|
||||
<data android:pathPattern=".*\\.epub" />
|
||||
<data android:pathPattern=".*\\.pdf" />
|
||||
<data android:pathPattern=".*\\.fb2" />
|
||||
<data android:pathPattern=".*\\.fb2.zip" />
|
||||
<data android:pathPattern=".*\\.mobi" />
|
||||
<data android:pathPattern=".*\\.azw" />
|
||||
<data android:pathPattern=".*\\.azw3" />
|
||||
<data android:pathPattern=".*\\.cbz" />
|
||||
<data android:pathPattern=".*\\.zip" />
|
||||
<data android:pathPattern=".*\\.txt" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="oauth">
|
||||
@@ -59,6 +88,7 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="readest" android:host="auth-callback" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -66,7 +96,7 @@
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
||||
<intent-filter android:autoVerify="true">
|
||||
<intent-filter android:autoVerify="true" >
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,8 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="#FF323130" />
|
||||
</item>
|
||||
<item android:gravity="center" android:width="120dp" android:height="120dp">
|
||||
<bitmap android:src="@drawable/splash_icon" />
|
||||
</item>
|
||||
</layer-list>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
@@ -2,9 +2,7 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.readest" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:windowSplashScreenBackground">@android:color/transparent</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="31">@null</item>
|
||||
|
||||
<item name="android:windowBackground">@drawable/splash_background</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowTranslucentStatus">false</item>
|
||||
|
||||
+37
-1
@@ -198,6 +198,7 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
@Suppress("DEPRECATION")
|
||||
window.setDecorFitsSystemWindows(false)
|
||||
val controller = window.insetsController
|
||||
if (controller != null) {
|
||||
@@ -228,7 +229,7 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val compatController = WindowCompat.getInsetsController(window, decorView)
|
||||
compatController?.let {
|
||||
compatController.let {
|
||||
it.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
if (!isDarkMode) {
|
||||
it.isAppearanceLightStatusBars = true
|
||||
@@ -259,7 +260,9 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
}
|
||||
}.reduce { acc, flag -> acc or flag }
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
@Suppress("DEPRECATION")
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
ret.put("success", true)
|
||||
} catch (e: Exception) {
|
||||
@@ -366,4 +369,37 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
}
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@Command
|
||||
fun get_safe_area_insets(invoke: Invoke) {
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val rootView = activity.findViewById<View>(android.R.id.content)
|
||||
val windowInsets = androidx.core.view.ViewCompat.getRootWindowInsets(rootView)
|
||||
|
||||
if (windowInsets != null) {
|
||||
val insets = windowInsets.getInsets(
|
||||
WindowInsetsCompat.Type.systemBars() or
|
||||
WindowInsetsCompat.Type.displayCutout()
|
||||
)
|
||||
val density = activity.resources.displayMetrics.density
|
||||
ret.put("top", insets.top / density)
|
||||
ret.put("right", insets.right / density)
|
||||
ret.put("bottom", insets.bottom / density)
|
||||
ret.put("left", insets.left / density)
|
||||
} else {
|
||||
ret.put("top", 0)
|
||||
ret.put("right", 0)
|
||||
ret.put("bottom", 0)
|
||||
ret.put("left", 0)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ret.put("error", e.message)
|
||||
ret.put("top", 0)
|
||||
ret.put("right", 0)
|
||||
ret.put("bottom", 0)
|
||||
ret.put("left", 0)
|
||||
}
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ const COMMANDS: &[&str] = &[
|
||||
"iap_purchase_product",
|
||||
"iap_restore_purchases",
|
||||
"get_system_color_scheme",
|
||||
"get_safe_area_insets",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-get-safe-area-insets"
|
||||
description = "Enables the get_safe_area_insets command without any pre-configured scope."
|
||||
commands.allow = ["get_safe_area_insets"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-get-safe-area-insets"
|
||||
description = "Denies the get_safe_area_insets command without any pre-configured scope."
|
||||
commands.deny = ["get_safe_area_insets"]
|
||||
+27
@@ -19,6 +19,7 @@ Default permissions for the plugin
|
||||
- `allow-iap-purchase-product`
|
||||
- `allow-iap-restore-purchases`
|
||||
- `allow-get-system-color-scheme`
|
||||
- `allow-get-safe-area-insets`
|
||||
|
||||
## Permission Table
|
||||
|
||||
@@ -110,6 +111,32 @@ Denies the copy_uri_to_path command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-get-safe-area-insets`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the get_safe_area_insets command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-get-safe-area-insets`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the get_safe_area_insets command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-get-status-bar-height`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -16,4 +16,5 @@ permissions = [
|
||||
"allow-iap-purchase-product",
|
||||
"allow-iap-restore-purchases",
|
||||
"allow-get-system-color-scheme",
|
||||
"allow-get-safe-area-insets",
|
||||
]
|
||||
|
||||
+14
-2
@@ -330,6 +330,18 @@
|
||||
"const": "deny-copy-uri-to-path",
|
||||
"markdownDescription": "Denies the copy_uri_to_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_safe_area_insets command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-get-safe-area-insets",
|
||||
"markdownDescription": "Enables the get_safe_area_insets command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_safe_area_insets command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-get-safe-area-insets",
|
||||
"markdownDescription": "Denies the get_safe_area_insets command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_status_bar_height command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -475,10 +487,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`",
|
||||
"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`",
|
||||
"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`"
|
||||
"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`"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -119,3 +119,10 @@ pub(crate) async fn get_system_color_scheme<R: Runtime>(
|
||||
) -> Result<GetSystemColorSchemeResponse> {
|
||||
app.native_bridge().get_system_color_scheme()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn get_safe_area_insets<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
) -> Result<GetSafeAreaInsetsResponse> {
|
||||
app.native_bridge().get_safe_area_insets()
|
||||
}
|
||||
|
||||
@@ -102,4 +102,8 @@ impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn get_system_color_scheme(&self) -> crate::Result<GetSystemColorSchemeResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn get_safe_area_insets(&self) -> crate::Result<GetSafeAreaInsetsResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
commands::iap_purchase_product,
|
||||
commands::iap_restore_purchases,
|
||||
commands::get_system_color_scheme,
|
||||
commands::get_safe_area_insets,
|
||||
])
|
||||
.setup(|app, api| {
|
||||
#[cfg(mobile)]
|
||||
|
||||
@@ -161,3 +161,11 @@ impl<R: Runtime> NativeBridge<R> {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn get_safe_area_insets(&self) -> crate::Result<GetSafeAreaInsetsResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("get_safe_area_insets", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,3 +158,12 @@ pub struct IAPRestorePurchasesResponse {
|
||||
pub struct GetSystemColorSchemeResponse {
|
||||
pub color_scheme: String, // "light" or "dark"
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetSafeAreaInsetsResponse {
|
||||
pub top: f64,
|
||||
pub bottom: f64,
|
||||
pub left: f64,
|
||||
pub right: f64,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ extern crate cocoa;
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
|
||||
use tauri::utils::config::BackgroundThrottlingPolicy;
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::TitleBarStyle;
|
||||
|
||||
@@ -240,7 +241,9 @@ pub fn run() {
|
||||
eprintln!("Failed to initialize tauri_plugin_log: {e}");
|
||||
};
|
||||
|
||||
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default());
|
||||
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||
.background_throttling(BackgroundThrottlingPolicy::Disabled)
|
||||
.background_color(tauri::window::Color(50, 49, 48, 255));
|
||||
|
||||
#[cfg(desktop)]
|
||||
let win_builder = win_builder.inner_size(800.0, 600.0).resizable(true);
|
||||
@@ -265,7 +268,9 @@ pub fn run() {
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
builder = builder.transparent(true);
|
||||
builder = builder
|
||||
.transparent(true)
|
||||
.background_color(tauri::window::Color(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
builder
|
||||
|
||||
@@ -86,11 +86,18 @@
|
||||
},
|
||||
{
|
||||
"name": "azw",
|
||||
"ext": ["azw", "azw3"],
|
||||
"ext": ["azw"],
|
||||
"description": "AZW file",
|
||||
"mimeType": "application/vnd.amazon.ebook",
|
||||
"role": "Viewer"
|
||||
},
|
||||
{
|
||||
"name": "azw3",
|
||||
"ext": ["azw3"],
|
||||
"description": "AZW3 file",
|
||||
"mimeType": "application/vnd.amazon.mobi8-ebook",
|
||||
"role": "Viewer"
|
||||
},
|
||||
{
|
||||
"name": "fb2",
|
||||
"ext": ["fb2"],
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
|
||||
expect(originalCfi).toEqual(convertedCfi);
|
||||
expect(xpointer).toEqual({
|
||||
xpointer: '/body/DocFragment[2]/body/div/p[0]',
|
||||
xpointer: '/body/DocFragment[2]/body/div/p[1]',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
|
||||
expect(originalCfi).toEqual(convertedCfi);
|
||||
expect(xpointer).toEqual({
|
||||
xpointer: '/body/DocFragment[2]/body/div/p[1]',
|
||||
xpointer: '/body/DocFragment[2]/body/div/p[2]',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
|
||||
expect(originalCfi).toEqual(convertedCfi);
|
||||
expect(xpointer).toEqual({
|
||||
xpointer: '/body/DocFragment[2]/body/div/p[2]',
|
||||
xpointer: '/body/DocFragment[2]/body/div/p[3]',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -126,9 +126,9 @@ describe('CFIToXPointerConverter', () => {
|
||||
const convertedCfi = converter.xPointerToCFI(xpointer.pos0!, xpointer.pos1!);
|
||||
|
||||
expect(originalCfi).toEqual(convertedCfi);
|
||||
expect(xpointer.xpointer).toEqual('/body/DocFragment[3]/body/div/p[0]/text().6');
|
||||
expect(xpointer.pos0).toEqual('/body/DocFragment[3]/body/div/p[0]/text().6');
|
||||
expect(xpointer.pos1).toEqual('/body/DocFragment[3]/body/div/p[1]/text().16');
|
||||
expect(xpointer.xpointer).toEqual('/body/DocFragment[3]/body/div/p[1]/text().6');
|
||||
expect(xpointer.pos0).toEqual('/body/DocFragment[3]/body/div/p[1]/text().6');
|
||||
expect(xpointer.pos1).toEqual('/body/DocFragment[3]/body/div/p[2]/text().16');
|
||||
});
|
||||
|
||||
it('should convert range CFI within same element', () => {
|
||||
@@ -147,8 +147,8 @@ describe('CFIToXPointerConverter', () => {
|
||||
const convertedCfi = converter.xPointerToCFI(xpointer.pos0!, xpointer.pos1!);
|
||||
|
||||
expect(originalCfi).toEqual(convertedCfi);
|
||||
expect(xpointer.pos0).toMatch(/p\[0\]/);
|
||||
expect(xpointer.pos1).toMatch(/p\[2\]/);
|
||||
expect(xpointer.pos0).toMatch(/p\[1\]/);
|
||||
expect(xpointer.pos1).toMatch(/p\[3\]/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -163,7 +163,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
const convertedCfi = converter.xPointerToCFI(xpointer.xpointer);
|
||||
|
||||
expect(originalCfi).toEqual(convertedCfi);
|
||||
expect(xpointer.xpointer).toMatch(/\/body\/section\[0\]/);
|
||||
expect(xpointer.xpointer).toMatch(/\/body\/section\[1\]/);
|
||||
});
|
||||
|
||||
it('should handle elements with IDs', () => {
|
||||
@@ -172,7 +172,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
const convertedCfi = converter.xPointerToCFI(xpointer.xpointer);
|
||||
|
||||
expect(originalCfi).toEqual(convertedCfi);
|
||||
expect(xpointer.xpointer).toMatch(/\/body\/section\[1\]\/p\[0\]/);
|
||||
expect(xpointer.xpointer).toMatch(/\/body\/section\[2\]\/p\[1\]/);
|
||||
});
|
||||
|
||||
it('should handle inline elements', () => {
|
||||
@@ -181,7 +181,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
const convertedCfi = converter.xPointerToCFI(xpointer.xpointer);
|
||||
|
||||
expect(originalCfi).toEqual(convertedCfi);
|
||||
expect(xpointer.xpointer).toMatch(/\/body\/section\[1\]\/p\[1\]/);
|
||||
expect(xpointer.xpointer).toMatch(/\/body\/section\[2\]\/p\[2\]/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -212,7 +212,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
});
|
||||
|
||||
it('should convert XPointer to CFI for first element', () => {
|
||||
const xpointer = '/body/DocFragment[2]/body/div/p[0]';
|
||||
const xpointer = '/body/DocFragment[2]/body/div/p[1]';
|
||||
const cfi = converter.xPointerToCFI(xpointer);
|
||||
|
||||
// Verify by converting back to XPointer
|
||||
@@ -221,7 +221,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
});
|
||||
|
||||
it('should convert XPointer to CFI for second element', () => {
|
||||
const xpointer = '/body/DocFragment[2]/body/div/p[1]';
|
||||
const xpointer = '/body/DocFragment[2]/body/div/p[2]';
|
||||
const cfi = converter.xPointerToCFI(xpointer);
|
||||
|
||||
const backToXPointer = converter.cfiToXPointer(cfi);
|
||||
@@ -229,14 +229,14 @@ describe('CFIToXPointerConverter', () => {
|
||||
});
|
||||
|
||||
it('should convert XPointer with text offset to CFI', () => {
|
||||
const xpointer = '/body/DocFragment[2]/body/div[0]/p[0]/text().6';
|
||||
const xpointer = '/body/DocFragment[2]/body/div[0]/p[1]/text().6';
|
||||
const cfi = converter.xPointerToCFI(xpointer);
|
||||
expect(cfi).toBe('epubcfi(/6/4!/4/2/2/1:6)');
|
||||
});
|
||||
|
||||
it('should convert range XPointer to CFI', () => {
|
||||
const pos0 = '/body/DocFragment[2]/body/div/p[0]/text().6';
|
||||
const pos1 = '/body/DocFragment[2]/body/div/p[1]/text().16';
|
||||
const pos0 = '/body/DocFragment[2]/body/div/p[1]/text().6';
|
||||
const pos1 = '/body/DocFragment[2]/body/div/p[2]/text().16';
|
||||
const cfi = converter.xPointerToCFI(pos0, pos1);
|
||||
const xpointer = converter.cfiToXPointer(cfi);
|
||||
|
||||
@@ -269,7 +269,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
it('should handle CFI without spine step prefix', () => {
|
||||
// Test the adjustSpineIndex method handles CFIs that don't start with /6/n!
|
||||
const converter = new XCFI(simpleDoc, 3); // Use different spine index
|
||||
const xpointer = '/body/DocFragment[4]/body/div/p[0]';
|
||||
const xpointer = '/body/DocFragment[4]/body/div/p[1]';
|
||||
const cfi = converter.xPointerToCFI(xpointer);
|
||||
|
||||
// Verify the spine step is correctly added/adjusted
|
||||
@@ -331,7 +331,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
const cfi = 'epubcfi(/6/6!/4/2/2)'; // Empty p element
|
||||
const result = converter.cfiToXPointer(cfi);
|
||||
|
||||
expect(result.xpointer).toBe('/body/DocFragment[3]/body/div/p[0]');
|
||||
expect(result.xpointer).toBe('/body/DocFragment[3]/body/div/p[1]');
|
||||
});
|
||||
|
||||
it('should handle whitespace-only text nodes', () => {
|
||||
@@ -353,7 +353,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
const cfi = 'epubcfi(/6/6!/4/2/4)'; // Second p element
|
||||
const result = converter.cfiToXPointer(cfi);
|
||||
|
||||
expect(result.xpointer).toBe('/body/DocFragment[3]/body/div/p[1]');
|
||||
expect(result.xpointer).toBe('/body/DocFragment[3]/body/div/p[2]');
|
||||
});
|
||||
|
||||
it('should handle deeply nested elements', () => {
|
||||
@@ -379,7 +379,7 @@ describe('CFIToXPointerConverter', () => {
|
||||
const cfi = 'epubcfi(/6/6!/4/2/2/2/2)'; // Deeply nested p
|
||||
const result = converter.cfiToXPointer(cfi);
|
||||
|
||||
expect(result.xpointer).toBe('/body/DocFragment[3]/body/div/section/article/p[0]');
|
||||
expect(result.xpointer).toBe('/body/DocFragment[3]/body/div/section/article/p[1]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function AuthPage() {
|
||||
const router = useRouter();
|
||||
const { login } = useAuth();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { isDarkMode } = useThemeStore();
|
||||
const { isDarkMode, safeAreaInsets, isRoundedWindow } = useThemeStore();
|
||||
const { isTrafficLightVisible } = useTrafficLightStore();
|
||||
const { settings, setSettings, saveSettings } = useSettingsStore();
|
||||
const [port, setPort] = useState<number | null>(null);
|
||||
@@ -346,15 +346,14 @@ export default function AuthPage() {
|
||||
className={clsx(
|
||||
'bg-base-100 inset-0 flex select-none flex-col items-center overflow-hidden',
|
||||
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
|
||||
appService?.isLinuxApp && 'window-border',
|
||||
appService?.hasRoundedWindow && 'rounded-window',
|
||||
appService?.hasRoundedWindow && isRoundedWindow && 'window-border rounded-window',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex h-full w-full flex-col items-center overflow-y-auto',
|
||||
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
|
||||
)}
|
||||
className={clsx('flex h-full w-full flex-col items-center overflow-y-auto')}
|
||||
style={{
|
||||
paddingTop: `${safeAreaInsets?.top || 0}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={headerRef}
|
||||
@@ -364,6 +363,7 @@ export default function AuthPage() {
|
||||
)}
|
||||
>
|
||||
<button
|
||||
aria-label={_('Go Back')}
|
||||
onClick={handleGoBack}
|
||||
className={clsx('btn btn-ghost h-12 min-h-12 w-12 p-0 sm:h-8 sm:min-h-8 sm:w-8')}
|
||||
>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function ResetPasswordPage() {
|
||||
|
||||
return (
|
||||
<div className='flex min-h-screen items-center justify-center'>
|
||||
<div className='w-full max-w-md'>
|
||||
<div className='w-full max-w-md p-8'>
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
view='update_password'
|
||||
|
||||
@@ -37,12 +37,11 @@ export const viewport = {
|
||||
maximumScale: 1,
|
||||
userScalable: false,
|
||||
viewportFit: 'cover',
|
||||
themeColor: 'white',
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<meta
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Book } from '@/types/book';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
|
||||
import { navigateToLogin } from '@/utils/nav';
|
||||
@@ -40,24 +41,22 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
handleBookDownload,
|
||||
showBookDetailsModal,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const router = useRouter();
|
||||
const { user } = useAuth();
|
||||
const { appService } = useEnv();
|
||||
const iconSize15 = useResponsiveSize(15);
|
||||
|
||||
const stopEvent = (e: React.MouseEvent | React.TouchEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
role='none'
|
||||
className={clsx(
|
||||
'book-item flex',
|
||||
mode === 'grid' && 'h-full flex-col justify-end',
|
||||
mode === 'list' && 'h-28 flex-row gap-4 overflow-hidden',
|
||||
appService?.hasContextMenu ? 'cursor-pointer' : '',
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -115,13 +114,12 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
<div className='flex items-center justify-center gap-x-2'>
|
||||
{!appService?.isMobile && (
|
||||
<button
|
||||
aria-label={_('Show Book Details')}
|
||||
className='show-detail-button -m-2 p-2 sm:opacity-0 sm:group-hover:opacity-100'
|
||||
onPointerDown={(e) => stopEvent(e)}
|
||||
onPointerUp={(e) => stopEvent(e)}
|
||||
onPointerMove={(e) => stopEvent(e)}
|
||||
onPointerCancel={(e) => stopEvent(e)}
|
||||
onPointerLeave={(e) => stopEvent(e)}
|
||||
onClick={() => showBookDetailsModal(book)}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={() => {
|
||||
showBookDetailsModal(book);
|
||||
}}
|
||||
>
|
||||
<div className='pt-[2px] sm:pt-[1px]'>
|
||||
<LiaInfoCircleSolid size={iconSize15} />
|
||||
@@ -146,11 +144,7 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
(!book.uploadedAt || (book.uploadedAt && !book.downloadedAt)) && (
|
||||
<button
|
||||
className='show-cloud-button -m-2 p-2'
|
||||
onPointerDown={(e) => stopEvent(e)}
|
||||
onPointerUp={(e) => stopEvent(e)}
|
||||
onPointerMove={(e) => stopEvent(e)}
|
||||
onPointerCancel={(e) => stopEvent(e)}
|
||||
onPointerLeave={(e) => stopEvent(e)}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={() => {
|
||||
if (!user) {
|
||||
navigateToLogin(router);
|
||||
|
||||
@@ -8,17 +8,20 @@ import { PiPlus } from 'react-icons/pi';
|
||||
import { Book, BooksGroup } from '@/types/book';
|
||||
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useAutoFocus } from '@/hooks/useAutoFocus';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { navigateToLibrary, navigateToReader, showReaderWindow } from '@/utils/nav';
|
||||
import { formatAuthors, formatTitle } from '@/utils/book';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { isMd5 } from '@/utils/md5';
|
||||
|
||||
import Alert from '@/components/Alert';
|
||||
import Spinner from '@/components/Spinner';
|
||||
import ModalPortal from '@/components/ModalPortal';
|
||||
import BookshelfItem, { generateGridItems, generateListItems } from './BookshelfItem';
|
||||
import BookshelfItem, { generateBookshelfItems } from './BookshelfItem';
|
||||
import GroupingModal from './GroupingModal';
|
||||
|
||||
interface BookshelfProps {
|
||||
@@ -53,8 +56,10 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
const searchParams = useSearchParams();
|
||||
const { appService } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
const { safeAreaInsets } = useThemeStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showSelectModeActions, setShowSelectModeActions] = useState(false);
|
||||
const [bookIdsToDelete, setBookIdsToDelete] = useState<string[]>([]);
|
||||
const [showDeleteAlert, setShowDeleteAlert] = useState(false);
|
||||
const [showGroupingModal, setShowGroupingModal] = useState(false);
|
||||
const [queryTerm, setQueryTerm] = useState<string | null>(null);
|
||||
@@ -70,8 +75,9 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
|
||||
const { setCurrentBookshelf, setLibrary } = useLibraryStore();
|
||||
const { setSelectedBooks, getSelectedBooks, toggleSelectedBook } = useLibraryStore();
|
||||
const allBookshelfItems =
|
||||
viewMode === 'grid' ? generateGridItems(libraryBooks) : generateListItems(libraryBooks);
|
||||
const allBookshelfItems = generateBookshelfItems(libraryBooks);
|
||||
|
||||
const autofocusRef = useAutoFocus<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (isImportingBook.current) return;
|
||||
@@ -188,8 +194,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
|
||||
const getBooksToDelete = () => {
|
||||
const booksToDelete: Book[] = [];
|
||||
const selectedBooks = getSelectedBooks();
|
||||
selectedBooks.forEach((id) => {
|
||||
bookIdsToDelete.forEach((id) => {
|
||||
for (const book of libraryBooks.filter((book) => book.hash === id || book.groupId === id)) {
|
||||
if (book && !book.deletedAt) {
|
||||
booksToDelete.push(book);
|
||||
@@ -209,6 +214,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
};
|
||||
|
||||
const deleteSelectedBooks = () => {
|
||||
setBookIdsToDelete(getSelectedBooks());
|
||||
setShowSelectModeActions(false);
|
||||
setShowDeleteAlert(true);
|
||||
};
|
||||
@@ -218,6 +224,13 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
setShowGroupingModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteBooksIntent = (event: CustomEvent) => {
|
||||
const { ids } = event.detail;
|
||||
setBookIdsToDelete(ids);
|
||||
setShowSelectModeActions(false);
|
||||
setShowDeleteAlert(true);
|
||||
};
|
||||
|
||||
const bookFilter = (item: Book, queryTerm: string) => {
|
||||
if (item.deletedAt) return false;
|
||||
const searchTerm = new RegExp(queryTerm, 'i');
|
||||
@@ -239,8 +252,8 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
const bTitle = formatTitle(b.title);
|
||||
return aTitle.localeCompare(bTitle, uiLanguage || navigator.language);
|
||||
case 'author':
|
||||
const aAuthors = formatAuthors(a.author);
|
||||
const bAuthors = formatAuthors(b.author);
|
||||
const aAuthors = formatAuthors(a.author, a?.primaryLanguage || 'en', true);
|
||||
const bAuthors = formatAuthors(b.author, b?.primaryLanguage || 'en', true);
|
||||
return aAuthors.localeCompare(bAuthors, uiLanguage || navigator.language);
|
||||
case 'updated':
|
||||
return new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
|
||||
@@ -294,17 +307,28 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isSelectMode, isSelectAll, isSelectNone]);
|
||||
|
||||
useEffect(() => {
|
||||
eventDispatcher.on('delete-books', handleDeleteBooksIntent);
|
||||
return () => {
|
||||
eventDispatcher.off('delete-books', handleDeleteBooksIntent);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const selectedBooks = getSelectedBooks();
|
||||
|
||||
return (
|
||||
<div className='bookshelf'>
|
||||
<div
|
||||
ref={autofocusRef}
|
||||
tabIndex={-1}
|
||||
className={clsx(
|
||||
'bookshelf-items transform-wrapper',
|
||||
'bookshelf-items transform-wrapper focus:outline-none',
|
||||
viewMode === 'grid' && 'grid flex-1 grid-cols-3 gap-x-4 px-4 sm:gap-x-0 sm:px-2',
|
||||
viewMode === 'grid' && 'sm:grid-cols-4 md:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-12',
|
||||
viewMode === 'list' && 'flex flex-col',
|
||||
)}
|
||||
role='main'
|
||||
aria-label={_('Bookshelf')}
|
||||
>
|
||||
{filteredBookshelfItems.map((item) => (
|
||||
<BookshelfItem
|
||||
@@ -329,16 +353,16 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
/>
|
||||
))}
|
||||
{viewMode === 'grid' && !navBooksGroup && allBookshelfItems.length > 0 && (
|
||||
<div
|
||||
<button
|
||||
aria-label={_('Import Books')}
|
||||
className={clsx(
|
||||
'border-1 bg-base-100 hover:bg-base-300/50 flex items-center justify-center',
|
||||
'mx-0 my-4 aspect-[28/41] sm:mx-4',
|
||||
)}
|
||||
role='button'
|
||||
onClick={handleImportBooks}
|
||||
>
|
||||
<PiPlus className='size-10' color='gray' />
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{loading && (
|
||||
@@ -346,7 +370,12 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
<Spinner loading />
|
||||
</div>
|
||||
)}
|
||||
<div className='fixed bottom-0 left-0 right-0 z-40 pb-[calc(env(safe-area-inset-bottom)+16px)]'>
|
||||
<div
|
||||
className='fixed bottom-0 left-0 right-0 z-40'
|
||||
style={{
|
||||
paddingBottom: `${(safeAreaInsets?.bottom || 0) + 16}px`,
|
||||
}}
|
||||
>
|
||||
{isSelectMode && showSelectModeActions && (
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -425,10 +454,10 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
)}
|
||||
{showDeleteAlert && (
|
||||
<div
|
||||
className={clsx(
|
||||
'fixed bottom-0 left-0 right-0 z-50 flex justify-center',
|
||||
'pb-[calc(env(safe-area-inset-bottom)+16px)]',
|
||||
)}
|
||||
className={clsx('fixed bottom-0 left-0 right-0 z-50 flex justify-center')}
|
||||
style={{
|
||||
paddingBottom: `${(safeAreaInsets?.bottom || 0) + 16}px`,
|
||||
}}
|
||||
>
|
||||
<Alert
|
||||
title={_('Confirm Deletion')}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useLongPress } from '@/hooks/useLongPress';
|
||||
import { Menu, MenuItem } from '@tauri-apps/api/menu';
|
||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||
import { getLocalBookFilename } from '@/utils/book';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { getOSPlatform } from '@/utils/misc';
|
||||
import { throttle } from '@/utils/throttle';
|
||||
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
|
||||
@@ -21,7 +21,7 @@ import GroupItem from './GroupItem';
|
||||
|
||||
export type BookshelfItem = Book | BooksGroup;
|
||||
|
||||
export const generateGridItems = (books: Book[]): (Book | BooksGroup)[] => {
|
||||
export const generateBookshelfItems = (books: Book[]): (Book | BooksGroup)[] => {
|
||||
const groups: BooksGroup[] = books.reduce((acc: BooksGroup[], book: Book) => {
|
||||
if (book.deletedAt) return acc;
|
||||
book.groupId = book.groupId || BOOK_UNGROUPED_ID;
|
||||
@@ -50,10 +50,6 @@ export const generateGridItems = (books: Book[]): (Book | BooksGroup)[] => {
|
||||
return [...ungroupedBooks, ...groupedBooks].sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
};
|
||||
|
||||
export const generateListItems = (books: Book[]): (Book | BooksGroup)[] => {
|
||||
return books.filter((book) => !book.deletedAt).sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
};
|
||||
|
||||
export const generateGroupsList = (items: Book[]): BookGroupType[] => {
|
||||
return items
|
||||
.sort((a, b) => b.updatedAt - a.updatedAt)
|
||||
@@ -99,7 +95,6 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
toggleSelection,
|
||||
handleBookUpload,
|
||||
handleBookDownload,
|
||||
handleBookDelete,
|
||||
handleSetSelectMode,
|
||||
handleShowDetailsBook,
|
||||
}) => {
|
||||
@@ -192,7 +187,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
const showBookInFinderMenuItem = await MenuItem.new({
|
||||
text: _(fileRevealLabel),
|
||||
action: async () => {
|
||||
const folder = `${settings.localBooksDir}/${getLocalBookFilename(book)}`;
|
||||
const folder = `${settings.localBooksDir}/${book.hash}`;
|
||||
revealItemInDir(folder);
|
||||
},
|
||||
});
|
||||
@@ -217,7 +212,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
const deleteBookMenuItem = await MenuItem.new({
|
||||
text: _('Delete'),
|
||||
action: async () => {
|
||||
await handleBookDelete(book);
|
||||
eventDispatcher.dispatch('delete-books', { ids: [book.hash] });
|
||||
},
|
||||
});
|
||||
const menu = await Menu.new();
|
||||
@@ -246,9 +241,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
const deleteGroupMenuItem = await MenuItem.new({
|
||||
text: _('Delete'),
|
||||
action: async () => {
|
||||
for (const book of group.books) {
|
||||
await handleBookDelete(book);
|
||||
}
|
||||
eventDispatcher.dispatch('delete-books', { ids: [group.id] });
|
||||
},
|
||||
});
|
||||
const menu = await Menu.new();
|
||||
@@ -319,19 +312,34 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
[isSelectMode, handleSelectItem, handleOpenItem, handleContextMenu],
|
||||
);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
handleOpenItem();
|
||||
}
|
||||
if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {
|
||||
e.preventDefault();
|
||||
handleContextMenu();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx(mode === 'list' && 'sm:hover:bg-base-300/50 px-4 sm:px-6')}>
|
||||
<div
|
||||
className={clsx(
|
||||
'group',
|
||||
'visible-focus-inset-2 group',
|
||||
mode === 'grid' && 'sm:hover:bg-base-300/50 flex h-full flex-col px-0 py-4 sm:px-4',
|
||||
mode === 'list' && 'border-base-300 flex flex-col border-b py-2',
|
||||
appService?.isMobileApp && 'no-context-menu',
|
||||
pressing ? (mode === 'grid' ? 'scale-95' : 'scale-98') : 'scale-100',
|
||||
pressing && mode === 'grid' ? 'scale-95' : 'scale-100',
|
||||
)}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label={'format' in item ? item.title : item.name}
|
||||
style={{
|
||||
transition: 'transform 0.2s',
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...handlers}
|
||||
>
|
||||
<div className='flex-grow'>
|
||||
@@ -348,7 +356,12 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
showBookDetailsModal={showBookDetailsModal}
|
||||
/>
|
||||
) : (
|
||||
<GroupItem group={item} isSelectMode={isSelectMode} groupSelected={itemSelected} />
|
||||
<GroupItem
|
||||
mode={mode}
|
||||
group={item}
|
||||
isSelectMode={isSelectMode}
|
||||
groupSelected={itemSelected}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,99 @@
|
||||
import clsx from 'clsx';
|
||||
import { MdCheckCircle, MdCheckCircleOutline } from 'react-icons/md';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { MdCheckCircle, MdCheckCircleOutline, MdChevronRight, MdChevronLeft } from 'react-icons/md';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { BooksGroup } from '@/types/book';
|
||||
import { LibraryViewModeType } from '@/types/settings';
|
||||
import BookCover from '@/components/BookCover';
|
||||
|
||||
interface GroupItemProps {
|
||||
mode: LibraryViewModeType;
|
||||
group: BooksGroup;
|
||||
isSelectMode: boolean;
|
||||
groupSelected: boolean;
|
||||
}
|
||||
|
||||
const GroupItem: React.FC<GroupItemProps> = ({ group, isSelectMode, groupSelected }) => {
|
||||
const GroupItem: React.FC<GroupItemProps> = ({ mode, group, isSelectMode, groupSelected }) => {
|
||||
const { appService } = useEnv();
|
||||
const iconSize15 = useResponsiveSize(15);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
||||
const [showRightArrow, setShowRightArrow] = useState(false);
|
||||
|
||||
const checkScrollArrows = () => {
|
||||
if (mode === 'list' && scrollContainerRef.current) {
|
||||
const container = scrollContainerRef.current;
|
||||
const hasOverflow = container.scrollWidth > container.clientWidth;
|
||||
|
||||
if (hasOverflow) {
|
||||
const isAtStart = container.scrollLeft <= 5;
|
||||
const isAtEnd = container.scrollLeft >= container.scrollWidth - container.clientWidth - 5;
|
||||
setShowLeftArrow(!isAtStart);
|
||||
setShowRightArrow(!isAtEnd);
|
||||
} else {
|
||||
setShowLeftArrow(false);
|
||||
setShowRightArrow(false);
|
||||
}
|
||||
} else {
|
||||
setShowLeftArrow(false);
|
||||
setShowRightArrow(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkScrollArrows();
|
||||
if (mode === 'list' && scrollContainerRef.current) {
|
||||
const container = scrollContainerRef.current;
|
||||
setTimeout(() => {
|
||||
container.style.transform = 'translateZ(0)';
|
||||
container.scrollLeft = 0;
|
||||
}, 0);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [mode, group.books.length, scrollContainerRef.current]);
|
||||
|
||||
const handleScroll = () => {
|
||||
checkScrollArrows();
|
||||
};
|
||||
|
||||
const scrollLeft = () => {
|
||||
if (scrollContainerRef.current) {
|
||||
const container = scrollContainerRef.current;
|
||||
const scrollAmount = container.clientWidth * 0.5;
|
||||
const currentScroll = container.scrollLeft;
|
||||
const targetScroll = Math.max(0, currentScroll - scrollAmount);
|
||||
container.scrollTo({ left: targetScroll, behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
const scrollRight = () => {
|
||||
if (scrollContainerRef.current) {
|
||||
const container = scrollContainerRef.current;
|
||||
const scrollAmount = container.clientWidth * 0.5;
|
||||
const currentScroll = container.scrollLeft;
|
||||
const maxScroll = container.scrollWidth - container.clientWidth;
|
||||
const targetScroll = Math.min(maxScroll, currentScroll + scrollAmount);
|
||||
container.scrollTo({ left: targetScroll, behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
const stopEvent = (e: React.MouseEvent | React.TouchEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleLeftArrowClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
scrollLeft();
|
||||
};
|
||||
|
||||
const handleRightArrowClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
scrollRight();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -22,14 +102,99 @@ const GroupItem: React.FC<GroupItemProps> = ({ group, isSelectMode, groupSelecte
|
||||
appService?.hasContextMenu ? 'cursor-pointer' : '',
|
||||
)}
|
||||
>
|
||||
<div className='bg-base-100 relative flex aspect-[28/41] items-center justify-center overflow-hidden p-2 shadow-md'>
|
||||
<div className='grid w-full grid-cols-2 grid-rows-2 gap-1 overflow-hidden'>
|
||||
{group.books.slice(0, 4).map((book) => (
|
||||
<div key={book.hash} className='relative aspect-[28/41] h-full w-full'>
|
||||
<BookCover book={book} isPreview />
|
||||
<div
|
||||
className={clsx(
|
||||
'relative flex overflow-hidden',
|
||||
mode === 'grid' && 'bg-base-100 aspect-[28/41] items-center justify-center p-2 shadow-md',
|
||||
mode === 'list' && 'h-32 items-center justify-start gap-4 py-2',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
mode === 'grid' && 'flex h-full w-full',
|
||||
mode === 'list' && 'relative min-w-0 max-w-[85%]',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
ref={mode === 'list' ? scrollContainerRef : undefined}
|
||||
className={clsx(
|
||||
mode === 'grid' && 'grid w-full grid-cols-2 grid-rows-2 gap-1 overflow-hidden',
|
||||
mode === 'list' && 'flex h-28 gap-2 overflow-x-auto overflow-y-hidden',
|
||||
)}
|
||||
style={
|
||||
mode === 'list'
|
||||
? {
|
||||
scrollbarWidth: 'none',
|
||||
msOverflowStyle: 'none',
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
transform: 'translateZ(0)',
|
||||
willChange: 'scroll-position',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onScroll={mode === 'list' ? handleScroll : undefined}
|
||||
>
|
||||
{group.books.slice(0, mode === 'grid' ? 4 : undefined).map((book) => (
|
||||
<div
|
||||
key={book.hash}
|
||||
className={clsx(
|
||||
'relative aspect-[28/41] h-full',
|
||||
mode === 'grid' && 'w-full',
|
||||
mode === 'list' && 'flex-shrink-0',
|
||||
)}
|
||||
>
|
||||
<BookCover book={book} isPreview />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{mode === 'list' && showLeftArrow && (
|
||||
<div className='absolute left-[-0.5px] top-0 h-full w-12'>
|
||||
<div className='from-base-200/85 via-base-200/20 absolute inset-0 bg-gradient-to-r to-transparent'></div>
|
||||
<button
|
||||
onClick={handleLeftArrowClick}
|
||||
onPointerDown={(e) => stopEvent(e)}
|
||||
onPointerUp={(e) => stopEvent(e)}
|
||||
onPointerMove={(e) => stopEvent(e)}
|
||||
onPointerCancel={(e) => stopEvent(e)}
|
||||
onPointerLeave={(e) => stopEvent(e)}
|
||||
className='absolute left-2 top-1/2 -translate-y-1/2 transition-all duration-200 hover:scale-110'
|
||||
>
|
||||
<div className='bg-base-100 border-base-content/10 hover:border-base-content/30 rounded-full border p-1 shadow-sm transition-colors duration-200'>
|
||||
<MdChevronLeft
|
||||
size={16}
|
||||
className='text-base-content/50 hover:text-base-content/70'
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
{mode === 'list' && showRightArrow && (
|
||||
<div className='absolute right-[-0.5px] top-0 h-full w-12'>
|
||||
<div className='from-base-200/85 via-base-200/20 absolute inset-0 bg-gradient-to-l to-transparent'></div>
|
||||
<button
|
||||
onClick={handleRightArrowClick}
|
||||
onPointerDown={(e) => stopEvent(e)}
|
||||
onPointerUp={(e) => stopEvent(e)}
|
||||
onPointerMove={(e) => stopEvent(e)}
|
||||
onPointerCancel={(e) => stopEvent(e)}
|
||||
onPointerLeave={(e) => stopEvent(e)}
|
||||
className='absolute right-2 top-1/2 -translate-y-1/2 transition-all duration-200 hover:scale-110'
|
||||
>
|
||||
<div className='bg-base-100 border-base-content/10 hover:border-base-content/30 rounded-full border p-1 shadow-sm transition-colors duration-200'>
|
||||
<MdChevronRight
|
||||
size={16}
|
||||
className='text-base-content/50 hover:text-base-content/70'
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{mode === 'list' && (
|
||||
<div className='text-base-content/75 w-28 min-w-24 max-w-40 overflow-hidden text-ellipsis text-base font-semibold'>
|
||||
{group.name}
|
||||
</div>
|
||||
)}
|
||||
{groupSelected && (
|
||||
<div className='absolute inset-0 bg-black opacity-30 transition-opacity duration-300'></div>
|
||||
)}
|
||||
@@ -43,14 +208,16 @@ const GroupItem: React.FC<GroupItemProps> = ({ group, isSelectMode, groupSelecte
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={clsx('flex w-full flex-col pt-2')}>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<h4 className='block overflow-hidden text-ellipsis whitespace-nowrap text-xs font-semibold'>
|
||||
{group.name}
|
||||
</h4>
|
||||
{mode === 'grid' && (
|
||||
<div className={clsx('flex w-full flex-col pt-2')}>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<h4 className='block overflow-hidden text-ellipsis whitespace-nowrap text-xs font-semibold'>
|
||||
{group.name}
|
||||
</h4>
|
||||
</div>
|
||||
<div className='placeholder' style={{ height: `${iconSize15}px` }}></div>
|
||||
</div>
|
||||
<div className='placeholder' style={{ height: `${iconSize15}px` }}></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -150,29 +150,26 @@ const GroupingModal: React.FC<GroupingModalProps> = ({
|
||||
<h2 className='text-center text-lg font-bold'>{_('Group Books')}</h2>
|
||||
<div className={clsx('mt-4 grid grid-cols-1 gap-2 text-base md:grid-cols-2')}>
|
||||
{isSelectedBooksHasGroup && (
|
||||
<div
|
||||
<button
|
||||
onClick={handleRemoveFromGroup}
|
||||
role='button'
|
||||
className='flex items-center space-x-2 p-2 text-blue-500'
|
||||
>
|
||||
<HiOutlineFolderRemove size={iconSize} />
|
||||
<span>{_('Remove From Group')}</span>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
<button
|
||||
onClick={handleCreateGroup}
|
||||
role='button'
|
||||
className='flex items-center space-x-2 p-2 text-blue-500'
|
||||
>
|
||||
<HiOutlineFolderAdd size={iconSize} />
|
||||
<span>{_('Create New Group')}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{showInput && (
|
||||
<div className='mt-4 flex items-center gap-2'>
|
||||
<input
|
||||
type='text'
|
||||
autoFocus
|
||||
ref={editorRef}
|
||||
value={editGroupName}
|
||||
onChange={(e) => setEditGroupName(e.target.value)}
|
||||
|
||||
@@ -2,6 +2,7 @@ import clsx from 'clsx';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import Menu from '@/components/Menu';
|
||||
|
||||
interface ImportMenuProps {
|
||||
setIsDropdownOpen?: (open: boolean) => void;
|
||||
@@ -18,15 +19,15 @@ const ImportMenu: React.FC<ImportMenuProps> = ({ setIsDropdownOpen, onImportBook
|
||||
};
|
||||
|
||||
return (
|
||||
<ul
|
||||
tabIndex={-1}
|
||||
<Menu
|
||||
label={_('Import Books')}
|
||||
className={clsx(
|
||||
'dropdown-content bg-base-100 menu rounded-box z-[1] mt-3 w-52 p-2 shadow',
|
||||
'dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow',
|
||||
appService?.isMobile ? 'no-triangle' : 'dropdown-center',
|
||||
)}
|
||||
>
|
||||
<MenuItem label={_('From Local File')} onClick={handleImportBooks} />
|
||||
</ul>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,381 +0,0 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { md5 } from 'js-md5';
|
||||
import clsx from 'clsx';
|
||||
import { type as osType } from '@tauri-apps/plugin-os';
|
||||
import Dialog from '@/components/Dialog';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { KOSyncClient } from '@/services/sync/KOSyncClient';
|
||||
import { KoreaderSyncChecksumMethod, KoreaderSyncStrategy } from '@/types/settings';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import { getOSPlatform } from '@/utils/misc';
|
||||
|
||||
type Option = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
options: Option[];
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const StyledSelect: React.FC<SelectProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
className,
|
||||
disabled = false,
|
||||
}) => {
|
||||
return (
|
||||
<select
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={clsx(
|
||||
'select select-bordered h-12 w-full text-sm focus:outline-none focus:ring-0',
|
||||
className,
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{options.map(({ value, label }) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export const setKOSyncSettingsWindowVisible = (visible: boolean) => {
|
||||
const dialog = document.getElementById('kosync_settings_window');
|
||||
if (dialog) {
|
||||
const event = new CustomEvent('setKOSyncSettingsVisibility', {
|
||||
detail: { visible },
|
||||
});
|
||||
dialog.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
export const KOSyncSettingsWindow: React.FC = () => {
|
||||
const _ = useTranslation();
|
||||
const { settings, setSettings, saveSettings } = useSettingsStore();
|
||||
const { envConfig, appService } = useEnv();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [url, setUrl] = useState(settings.koreaderSyncServerUrl || '');
|
||||
const [username, setUsername] = useState(settings.koreaderSyncUsername || '');
|
||||
const [password, setPassword] = useState('');
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [connectionStatus, setConnectionStatus] = useState('');
|
||||
const [deviceName, setDeviceName] = useState('');
|
||||
const [osName, setOsName] = useState('');
|
||||
|
||||
const [toleranceSliderValue, setToleranceSliderValue] = useState(() => {
|
||||
const tolerance = settings.koreaderSyncPercentageTolerance;
|
||||
return tolerance && tolerance > 0 ? Math.round(-Math.log10(tolerance)) : 4;
|
||||
});
|
||||
|
||||
// Get the OS name once
|
||||
useEffect(() => {
|
||||
const getOsName = async () => {
|
||||
let name = '';
|
||||
if (appService?.appPlatform === 'tauri') {
|
||||
name = await osType();
|
||||
} else {
|
||||
const platform = getOSPlatform();
|
||||
if (platform !== 'unknown') {
|
||||
name = platform;
|
||||
}
|
||||
}
|
||||
setOsName(name ? name.charAt(0).toUpperCase() + name.slice(1) : '');
|
||||
};
|
||||
getOsName();
|
||||
}, [appService]);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultName = osName ? `Readest (${osName})` : 'Readest';
|
||||
setDeviceName(settings.koreaderSyncDeviceName || defaultName);
|
||||
}, [settings.koreaderSyncDeviceName, osName]);
|
||||
|
||||
const isConfigured = useMemo(
|
||||
() => !!settings.koreaderSyncUserkey,
|
||||
[settings.koreaderSyncUserkey],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedSaveDeviceName = useCallback(
|
||||
debounce((newDeviceName: string) => {
|
||||
const newSettings = { ...settings, koreaderSyncDeviceName: newDeviceName };
|
||||
setSettings(newSettings);
|
||||
saveSettings(envConfig, newSettings);
|
||||
}, 500),
|
||||
[settings, setSettings, saveSettings, envConfig],
|
||||
);
|
||||
|
||||
const handleDeviceNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newName = e.target.value;
|
||||
setDeviceName(newName);
|
||||
debouncedSaveDeviceName(newName);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleCustomEvent = (event: CustomEvent) => {
|
||||
setIsOpen(event.detail.visible);
|
||||
if (event.detail.visible) {
|
||||
setUrl(settings.koreaderSyncServerUrl || '');
|
||||
setUsername(settings.koreaderSyncUsername || '');
|
||||
setPassword('');
|
||||
setConnectionStatus('');
|
||||
// Sync the slider with the current settings when opening
|
||||
const tolerance = settings.koreaderSyncPercentageTolerance;
|
||||
setToleranceSliderValue(
|
||||
tolerance && tolerance > 0 ? Math.round(-Math.log10(tolerance)) : 4,
|
||||
);
|
||||
}
|
||||
};
|
||||
const el = document.getElementById('kosync_settings_window');
|
||||
el?.addEventListener('setKOSyncSettingsVisibility', handleCustomEvent as EventListener);
|
||||
return () => {
|
||||
el?.removeEventListener('setKOSyncSettingsVisibility', handleCustomEvent as EventListener);
|
||||
};
|
||||
}, [
|
||||
settings.koreaderSyncServerUrl,
|
||||
settings.koreaderSyncUsername,
|
||||
settings.koreaderSyncPercentageTolerance,
|
||||
]);
|
||||
|
||||
const handleConnect = async () => {
|
||||
setIsConnecting(true);
|
||||
|
||||
let deviceId = settings.koreaderSyncDeviceId;
|
||||
if (!deviceId) {
|
||||
deviceId = uuidv4().replace(/-/g, '').toUpperCase();
|
||||
}
|
||||
|
||||
const client = new KOSyncClient(
|
||||
url,
|
||||
username,
|
||||
md5(password),
|
||||
settings.koreaderSyncChecksumMethod,
|
||||
deviceId,
|
||||
deviceName,
|
||||
);
|
||||
const result = await client.connect(username, password);
|
||||
|
||||
if (result.success) {
|
||||
const newSettings = {
|
||||
...settings,
|
||||
koreaderSyncServerUrl: url,
|
||||
koreaderSyncUsername: username,
|
||||
koreaderSyncUserkey: md5(password),
|
||||
koreaderSyncDeviceId: deviceId,
|
||||
koreaderSyncDeviceName: deviceName,
|
||||
koreaderSyncStrategy:
|
||||
settings.koreaderSyncStrategy === 'disabled' ? 'prompt' : settings.koreaderSyncStrategy,
|
||||
};
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
} else {
|
||||
setConnectionStatus('');
|
||||
eventDispatcher.dispatch('toast', {
|
||||
message: `${_('Failed to connect')}: ${_(result.message || 'Connection error')}`,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
setIsConnecting(false);
|
||||
setPassword('');
|
||||
};
|
||||
|
||||
const handleDisconnect = async () => {
|
||||
const newSettings = {
|
||||
...settings,
|
||||
koreaderSyncStrategy: 'disabled' as KoreaderSyncStrategy,
|
||||
koreaderSyncUserkey: '',
|
||||
};
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
setUsername('');
|
||||
eventDispatcher.dispatch('toast', { message: _('Disconnected'), type: 'info' });
|
||||
};
|
||||
|
||||
const handleStrategyChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const newStrategy = e.target.value as KoreaderSyncStrategy;
|
||||
const newSettings = { ...settings, koreaderSyncStrategy: newStrategy };
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
};
|
||||
|
||||
const handleChecksumMethodChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const newMethod = e.target.value as KoreaderSyncChecksumMethod;
|
||||
const newSettings = { ...settings, koreaderSyncChecksumMethod: newMethod };
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
};
|
||||
|
||||
const handleToleranceChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const sliderValue = parseInt(e.target.value, 10);
|
||||
setToleranceSliderValue(sliderValue);
|
||||
// Calculate the actual tolerance from the slider value (e.g., 4 -> 0.0001)
|
||||
const newTolerance = Math.pow(10, -sliderValue);
|
||||
|
||||
const newSettings = { ...settings, koreaderSyncPercentageTolerance: newTolerance };
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
id='kosync_settings_window'
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title={_('KOReader Sync Settings')}
|
||||
boxClassName='sm:!min-w-[520px] sm:h-auto'
|
||||
bgClassName='!bg-black/60'
|
||||
>
|
||||
<div className='mb-4 mt-0 flex flex-col gap-4 p-2 sm:p-4'>
|
||||
{isConfigured ? (
|
||||
<>
|
||||
<div className='text-center'>
|
||||
<p className='text-base-content/80 text-sm'>
|
||||
{_('Sync as {{userDisplayName}}', {
|
||||
userDisplayName: settings.koreaderSyncUsername,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className='flex h-14 items-center justify-between'>
|
||||
<span className='text-base-content/80'>
|
||||
{_('Sync Server Connected', { username: settings.koreaderSyncUsername })}
|
||||
</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
checked={settings.koreaderSyncStrategy !== 'disabled'}
|
||||
onChange={() => handleDisconnect()}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Sync Strategy')}</span>
|
||||
</label>
|
||||
<StyledSelect
|
||||
value={settings.koreaderSyncStrategy}
|
||||
onChange={handleStrategyChange}
|
||||
options={[
|
||||
{ value: 'prompt', label: _('Ask on conflict') },
|
||||
{ value: 'silent', label: _('Always use latest') },
|
||||
{ value: 'send', label: _('Send changes only') },
|
||||
{ value: 'receive', label: _('Receive changes only') },
|
||||
{ value: 'disable', label: _('Disabled') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Checksum Method')}</span>
|
||||
</label>
|
||||
<StyledSelect
|
||||
value={settings.koreaderSyncChecksumMethod}
|
||||
onChange={handleChecksumMethodChange}
|
||||
options={[
|
||||
{ value: 'binary', label: _('File Content (recommended)') },
|
||||
{ value: 'filename', label: _('File Name') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Device Name')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={osName ? `Readest (${osName})` : 'Readest'}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={deviceName}
|
||||
onChange={handleDeviceNameChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Hidden to avoid confusing users with technical details */}
|
||||
{false && (
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Sync Tolerance')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='range'
|
||||
min='0'
|
||||
max='15'
|
||||
value={toleranceSliderValue}
|
||||
onChange={handleToleranceChange}
|
||||
className='range range-primary'
|
||||
/>
|
||||
<div className='text-base-content/70 mt-2 text-center text-xs'>
|
||||
{_('Precision: {{precision}} digits after the decimal', {
|
||||
precision: toleranceSliderValue,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className='text-base-content/70 text-center text-sm'>
|
||||
{_('Connect to your KOReader Sync server.')}
|
||||
</p>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Server URL')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='https://koreader.sync.server'
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Username')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={_('Your Username')}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
placeholder={_('Your Password')}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className='btn btn-primary mt-2 h-12 min-h-12 w-full'
|
||||
onClick={handleConnect}
|
||||
disabled={isConnecting || !url || !username || !password}
|
||||
>
|
||||
{isConnecting ? <span className='loading loading-spinner'></span> : _('Connect')}
|
||||
</button>
|
||||
{connectionStatus && (
|
||||
<div className='text-error h-4 text-center text-sm'>{connectionStatus}</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,6 @@ import { useThemeStore } from '@/store/themeStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { useSafeAreaInsets } from '@/hooks/useSafeAreaInsets';
|
||||
import { useTrafficLightStore } from '@/store/trafficLightStore';
|
||||
import { navigateToLibrary } from '@/utils/nav';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
@@ -59,7 +58,7 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const iconSize18 = useResponsiveSize(18);
|
||||
const iconSize20 = useResponsiveSize(20);
|
||||
const insets = useSafeAreaInsets();
|
||||
const { safeAreaInsets: insets } = useThemeStore();
|
||||
|
||||
useShortcuts({
|
||||
onToggleSelectMode,
|
||||
@@ -95,7 +94,7 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
cleanupTrafficLightListeners();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [appService?.hasTrafficLight]);
|
||||
|
||||
const windowButtonVisible = appService?.hasWindowBar && !isTrafficLightVisible;
|
||||
const isInGroupView = !!searchParams?.get('group');
|
||||
@@ -110,7 +109,8 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
<div
|
||||
ref={headerRef}
|
||||
className={clsx(
|
||||
'titlebar bg-base-200 z-10 flex h-[52px] w-full items-center py-2 pr-4 sm:h-[48px] sm:pr-6',
|
||||
'titlebar bg-base-200 z-10 flex h-[52px] w-full items-center py-2 pr-4 sm:h-[48px]',
|
||||
windowButtonVisible ? 'sm:pr-4' : 'sm:pr-6',
|
||||
isTrafficLightVisible ? 'pl-16' : 'pl-0 sm:pl-2',
|
||||
)}
|
||||
style={{
|
||||
@@ -172,34 +172,27 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
)}
|
||||
<span className='bg-base-content/50 mx-2 h-4 w-[0.5px]'></span>
|
||||
<Dropdown
|
||||
label={_('Import Books')}
|
||||
className={clsx(
|
||||
'exclude-title-bar-mousedown dropdown-bottom flex h-6 cursor-pointer justify-center',
|
||||
appService?.isMobile ? 'dropdown-end' : 'dropdown-center',
|
||||
)}
|
||||
buttonClassName='p-0 h-6 min-h-6 w-6 flex items-center justify-center'
|
||||
toggleButton={
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Import Books')}>
|
||||
<PiPlus className='m-0.5 h-5 w-5' />
|
||||
</div>
|
||||
}
|
||||
toggleButton={<PiPlus role='none' className='m-0.5 h-5 w-5' />}
|
||||
>
|
||||
<ImportMenu onImportBooks={onImportBooks} />
|
||||
</Dropdown>
|
||||
{appService?.isMobile ? null : (
|
||||
<button
|
||||
onClick={onToggleSelectMode}
|
||||
aria-label={_('Select Multiple Books')}
|
||||
aria-label={_('Select Books')}
|
||||
title={_('Select Books')}
|
||||
className='h-6'
|
||||
>
|
||||
<div
|
||||
className='lg:tooltip lg:tooltip-bottom cursor-pointer'
|
||||
data-tip={_('Select Books')}
|
||||
>
|
||||
<PiSelectionAllDuotone
|
||||
role='button'
|
||||
className={`h-6 w-6 ${isSelectMode ? 'fill-gray-400' : 'fill-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
<PiSelectionAllDuotone
|
||||
role='button'
|
||||
className={`h-6 w-6 ${isSelectMode ? 'fill-gray-400' : 'fill-gray-500'}`}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -224,16 +217,18 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
) : (
|
||||
<div className='flex h-full items-center gap-x-2 sm:gap-x-4'>
|
||||
<Dropdown
|
||||
label={_('View Menu')}
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<PiDotsThreeCircle size={iconSize18} />}
|
||||
toggleButton={<PiDotsThreeCircle role='none' size={iconSize18} />}
|
||||
>
|
||||
<ViewMenu />
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
label={_('Settings Menu')}
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<MdOutlineMenu size={iconSize18} />}
|
||||
toggleButton={<MdOutlineMenu role='none' size={iconSize18} />}
|
||||
>
|
||||
<SettingsMenu />
|
||||
</Dropdown>
|
||||
|
||||
@@ -21,12 +21,16 @@ const ReadingProgress: React.FC<ReadingProgressProps> = memo(
|
||||
({ book }) => {
|
||||
const progressPercentage = useMemo(() => getProgressPercentage(book), [book]);
|
||||
|
||||
if (progressPercentage === null) {
|
||||
if (progressPercentage === null || Number.isNaN(progressPercentage)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='text-neutral-content/70 flex justify-between text-xs'>
|
||||
<div
|
||||
className='text-neutral-content/70 flex justify-between text-xs'
|
||||
role='status'
|
||||
aria-label={`${progressPercentage}%`}
|
||||
>
|
||||
<span>{progressPercentage}%</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,12 +3,10 @@ import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { PiUserCircle } from 'react-icons/pi';
|
||||
import { PiUserCircleCheck } from 'react-icons/pi';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { TbSunMoon } from 'react-icons/tb';
|
||||
import { BiMoon, BiSun } from 'react-icons/bi';
|
||||
|
||||
import { setAboutDialogVisible } from '@/components/AboutWindow';
|
||||
import { setKOSyncSettingsWindowVisible } from './KOSyncSettings';
|
||||
import { isTauriAppPlatform, isWebAppPlatform } from '@/services/environment';
|
||||
import { DOWNLOAD_READEST_URL } from '@/services/constants';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
@@ -24,6 +22,7 @@ import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry';
|
||||
import UserAvatar from '@/components/UserAvatar';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import Quota from '@/components/Quota';
|
||||
import Menu from '@/components/Menu';
|
||||
|
||||
interface SettingsMenuProps {
|
||||
setIsDropdownOpen?: (isOpen: boolean) => void;
|
||||
@@ -105,7 +104,6 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
setSettings(settings);
|
||||
saveSettings(envConfig, settings);
|
||||
setIsAlwaysShowStatusBar(settings.alwaysShowStatusBar);
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
const toggleAutoUploadBooks = () => {
|
||||
@@ -159,11 +157,6 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
setIsTelemetryEnabled(settings.telemetryEnabled);
|
||||
};
|
||||
|
||||
const showKoSyncSettingsWindow = () => {
|
||||
setKOSyncSettingsWindowVisible(true);
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
const handleUpgrade = () => {
|
||||
navigateToProfile(router);
|
||||
setIsDropdownOpen?.(false);
|
||||
@@ -172,10 +165,16 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
const avatarUrl = user?.user_metadata?.['picture'] || user?.user_metadata?.['avatar_url'];
|
||||
const userFullName = user?.user_metadata?.['full_name'];
|
||||
const userDisplayName = userFullName ? userFullName.split(' ')[0] : null;
|
||||
const themeModeLabel =
|
||||
themeMode === 'dark'
|
||||
? _('Dark Mode')
|
||||
: themeMode === 'light'
|
||||
? _('Light Mode')
|
||||
: _('Auto Mode');
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
<Menu
|
||||
label={_('Settings Menu')}
|
||||
className={clsx(
|
||||
'settings-menu dropdown-content no-triangle border-base-100',
|
||||
'z-20 mt-2 max-w-[90vw] shadow-2xl',
|
||||
@@ -189,6 +188,7 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
: _('Logged in')
|
||||
}
|
||||
labelClass='!max-w-40'
|
||||
aria-label={_('View account details and quota')}
|
||||
Icon={
|
||||
avatarUrl ? (
|
||||
<UserAvatar url={avatarUrl} size={iconSize} DefaultIcon={PiUserCircleCheck} />
|
||||
@@ -197,10 +197,10 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
)
|
||||
}
|
||||
>
|
||||
<ul>
|
||||
<div onClick={handleUserProfile} className='cursor-pointer'>
|
||||
<ul className='flex flex-col'>
|
||||
<button onClick={handleUserProfile} className='w-full'>
|
||||
<Quota quotas={quotas} labelClassName='h-10 pl-3 pr-2' />
|
||||
</div>
|
||||
</button>
|
||||
<MenuItem label={_('Account')} noIcon onClick={handleUserProfile} />
|
||||
</ul>
|
||||
</MenuItem>
|
||||
@@ -209,73 +209,61 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
)}
|
||||
<MenuItem
|
||||
label={_('Auto Upload Books to Cloud')}
|
||||
Icon={isAutoUpload ? MdCheck : undefined}
|
||||
toggled={isAutoUpload}
|
||||
onClick={toggleAutoUploadBooks}
|
||||
/>
|
||||
{isTauriAppPlatform() && !appService?.isMobile && (
|
||||
<MenuItem
|
||||
label={_('Auto Import on File Open')}
|
||||
Icon={isAutoImportBooksOnOpen ? MdCheck : undefined}
|
||||
toggled={isAutoImportBooksOnOpen}
|
||||
onClick={toggleAutoImportBooksOnOpen}
|
||||
/>
|
||||
)}
|
||||
{isTauriAppPlatform() && (
|
||||
<MenuItem
|
||||
label={_('Open Last Book on Start')}
|
||||
Icon={isOpenLastBooks ? MdCheck : undefined}
|
||||
toggled={isOpenLastBooks}
|
||||
onClick={toggleOpenLastBooks}
|
||||
/>
|
||||
)}
|
||||
{appService?.hasUpdater && (
|
||||
<MenuItem
|
||||
label={_('Check Updates on Start')}
|
||||
Icon={isAutoCheckUpdates ? MdCheck : undefined}
|
||||
toggled={isAutoCheckUpdates}
|
||||
onClick={toggleAutoCheckUpdates}
|
||||
/>
|
||||
)}
|
||||
<hr className='border-base-200 my-1' />
|
||||
<hr aria-hidden='true' className='border-base-200 my-1' />
|
||||
{appService?.hasWindow && (
|
||||
<MenuItem
|
||||
label={_('Open Book in New Window')}
|
||||
Icon={settings.openBookInNewWindow ? MdCheck : undefined}
|
||||
toggled={settings.openBookInNewWindow}
|
||||
onClick={toggleOpenInNewWindow}
|
||||
/>
|
||||
)}
|
||||
{appService?.hasWindow && <MenuItem label={_('Fullscreen')} onClick={handleFullScreen} />}
|
||||
{appService?.hasWindow && (
|
||||
<MenuItem
|
||||
label={_('Always on Top')}
|
||||
Icon={isAlwaysOnTop ? MdCheck : undefined}
|
||||
onClick={toggleAlwaysOnTop}
|
||||
/>
|
||||
<MenuItem label={_('Always on Top')} toggled={isAlwaysOnTop} onClick={toggleAlwaysOnTop} />
|
||||
)}
|
||||
{appService?.isMobileApp && (
|
||||
<MenuItem
|
||||
label={_('Always Show Status Bar')}
|
||||
Icon={isAlwaysShowStatusBar ? MdCheck : undefined}
|
||||
toggled={isAlwaysShowStatusBar}
|
||||
onClick={toggleAlwaysShowStatusBar}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
label={_('Keep Screen Awake')}
|
||||
Icon={isScreenWakeLock ? MdCheck : undefined}
|
||||
toggled={isScreenWakeLock}
|
||||
onClick={toggleScreenWakeLock}
|
||||
/>
|
||||
<MenuItem label={_('Reload Page')} onClick={handleReloadPage} />
|
||||
<MenuItem
|
||||
label={
|
||||
themeMode === 'dark'
|
||||
? _('Dark Mode')
|
||||
: themeMode === 'light'
|
||||
? _('Light Mode')
|
||||
: _('Auto Mode')
|
||||
}
|
||||
label={themeModeLabel}
|
||||
Icon={themeMode === 'dark' ? BiMoon : themeMode === 'light' ? BiSun : TbSunMoon}
|
||||
onClick={cycleThemeMode}
|
||||
/>
|
||||
<hr className='border-base-200 my-1' />
|
||||
<MenuItem label={_('KOReader Sync')} onClick={showKoSyncSettingsWindow} />
|
||||
<hr className='border-base-200 my-1' />
|
||||
<hr aria-hidden='true' className='border-base-200 my-1' />
|
||||
{user && userPlan === 'free' && !appService?.isIOSApp && (
|
||||
<MenuItem label={_('Upgrade to Readest Premium')} onClick={handleUpgrade} />
|
||||
)}
|
||||
@@ -284,10 +272,10 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
<MenuItem
|
||||
label={_('Help improve Readest')}
|
||||
description={isTelemetryEnabled ? _('Sharing anonymized statistics') : ''}
|
||||
Icon={isTelemetryEnabled ? MdCheck : undefined}
|
||||
toggled={isTelemetryEnabled}
|
||||
onClick={toggleTelemetry}
|
||||
/>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { LibraryCoverFitType, LibrarySortByType, LibraryViewModeType } from '@/types/settings';
|
||||
import { navigateToLibrary } from '@/utils/nav';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import Menu from '@/components/Menu';
|
||||
|
||||
interface ViewMenuProps {
|
||||
setIsDropdownOpen?: (isOpen: boolean) => void;
|
||||
@@ -92,20 +92,20 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
className='settings-menu dropdown-content no-triangle border-base-100 z-20 mt-2 shadow-2xl'
|
||||
<Menu
|
||||
label={_('View Menu')}
|
||||
className='view-menu dropdown-content no-triangle border-base-100 z-20 mt-2 shadow-2xl'
|
||||
>
|
||||
{viewOptions.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
buttonClass='h-8'
|
||||
Icon={viewMode === option.value ? MdCheck : undefined}
|
||||
toggled={viewMode === option.value}
|
||||
onClick={() => handleSetViewMode(option.value as LibraryViewModeType)}
|
||||
/>
|
||||
))}
|
||||
<hr className='border-base-200 my-1' />
|
||||
<hr aria-hidden='true' className='border-base-200 my-1' />
|
||||
<MenuItem
|
||||
label={_('Book Covers')}
|
||||
buttonClass='h-8'
|
||||
@@ -117,11 +117,11 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
buttonClass='h-8'
|
||||
Icon={coverFit === option.value ? MdCheck : undefined}
|
||||
toggled={coverFit === option.value}
|
||||
onClick={() => handleToggleCropCovers(option.value as LibraryCoverFitType)}
|
||||
/>
|
||||
))}
|
||||
<hr className='border-base-200 my-1' />
|
||||
<hr aria-hidden='true' className='border-base-200 my-1' />
|
||||
<MenuItem
|
||||
label={_('Sort by...')}
|
||||
buttonClass='h-8'
|
||||
@@ -133,21 +133,21 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
buttonClass='h-8'
|
||||
Icon={sortBy === option.value ? MdCheck : undefined}
|
||||
toggled={sortBy === option.value}
|
||||
onClick={() => handleSetSortBy(option.value as LibrarySortByType)}
|
||||
/>
|
||||
))}
|
||||
<hr className='border-base-200 my-1' />
|
||||
<hr aria-hidden='true' className='border-base-200 my-1' />
|
||||
{sortingOptions.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value.toString()}
|
||||
label={option.label}
|
||||
buttonClass='h-8'
|
||||
Icon={isAscending === option.value ? MdCheck : undefined}
|
||||
toggled={isAscending === option.value}
|
||||
onClick={() => handleSetSortAscending(option.value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSync } from '@/hooks/useSync';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { Book } from '@/types/book';
|
||||
import { SYNC_BOOKS_INTERVAL_SEC } from '@/services/constants';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import { SYNC_BOOKS_INTERVAL_SEC } from '@/services/constants';
|
||||
|
||||
export interface UseBooksSyncProps {
|
||||
onSyncStart?: () => void;
|
||||
@@ -17,12 +17,11 @@ export const useBooksSync = ({ onSyncStart, onSyncEnd }: UseBooksSyncProps) => {
|
||||
const { appService } = useEnv();
|
||||
const { library, setLibrary } = useLibraryStore();
|
||||
const { syncedBooks, syncBooks, lastSyncedAtBooks } = useSync();
|
||||
const syncBooksPullingRef = useRef(false);
|
||||
|
||||
const pullLibrary = async () => {
|
||||
const pullLibrary = useCallback(async () => {
|
||||
if (!user) return;
|
||||
syncBooks([], 'pull');
|
||||
};
|
||||
await syncBooks([], 'pull');
|
||||
}, [user, syncBooks]);
|
||||
|
||||
const pushLibrary = async () => {
|
||||
if (!user) return;
|
||||
@@ -32,15 +31,13 @@ export const useBooksSync = ({ onSyncStart, onSyncEnd }: UseBooksSyncProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
if (syncBooksPullingRef.current) return;
|
||||
syncBooksPullingRef.current = true;
|
||||
|
||||
pullLibrary();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [user]);
|
||||
|
||||
const getNewBooks = () => {
|
||||
if (!user) return [];
|
||||
const library = useLibraryStore.getState().library;
|
||||
const newBooks = library.filter(
|
||||
(book) => lastSyncedAtBooks < book.updatedAt || lastSyncedAtBooks < (book.deletedAt ?? 0),
|
||||
);
|
||||
@@ -53,14 +50,14 @@ export const useBooksSync = ({ onSyncStart, onSyncEnd }: UseBooksSyncProps) => {
|
||||
const newBooks = getNewBooks();
|
||||
syncBooks(newBooks, 'both');
|
||||
}, SYNC_BOOKS_INTERVAL_SEC * 1000),
|
||||
[library, lastSyncedAtBooks],
|
||||
[syncBooks],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
handleAutoSync();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [library]);
|
||||
}, [library, handleAutoSync]);
|
||||
|
||||
const updateLibrary = async () => {
|
||||
if (!syncedBooks?.length) return;
|
||||
|
||||
@@ -10,25 +10,21 @@ import 'overlayscrollbars/overlayscrollbars.css';
|
||||
import { Book } from '@/types/book';
|
||||
import { AppService, DeleteAction } from '@/types/system';
|
||||
import { navigateToLogin, navigateToReader } from '@/utils/nav';
|
||||
import {
|
||||
formatAuthors,
|
||||
formatTitle,
|
||||
getFilename,
|
||||
getPrimaryLanguage,
|
||||
listFormater,
|
||||
} from '@/utils/book';
|
||||
import { formatAuthors, formatTitle, getPrimaryLanguage, listFormater } from '@/utils/book';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { ProgressPayload } from '@/utils/transfer';
|
||||
import { throttle } from '@/utils/throttle';
|
||||
import { getFilename } from '@/utils/path';
|
||||
import { parseOpenWithFiles } from '@/helpers/openWith';
|
||||
import { isTauriAppPlatform, isWebAppPlatform } from '@/services/environment';
|
||||
import { checkForAppUpdates, checkAppReleaseNotes } from '@/helpers/updater';
|
||||
import { FILE_ACCEPT_FORMATS, SUPPORTED_FILE_EXTS } from '@/services/constants';
|
||||
import { BOOK_ACCEPT_FORMATS } from '@/services/constants';
|
||||
import { impactFeedback } from '@tauri-apps/plugin-haptics';
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
@@ -37,9 +33,9 @@ import { useTheme } from '@/hooks/useTheme';
|
||||
import { useUICSS } from '@/hooks/useUICSS';
|
||||
import { useDemoBooks } from './hooks/useDemoBooks';
|
||||
import { useBooksSync } from './hooks/useBooksSync';
|
||||
import { useSafeAreaInsets } from '@/hooks/useSafeAreaInsets';
|
||||
import { useScreenWakeLock } from '@/hooks/useScreenWakeLock';
|
||||
import { useOpenWithBooks } from '@/hooks/useOpenWithBooks';
|
||||
import { SelectedFile, useFileSelector } from '@/hooks/useFileSelector';
|
||||
import { lockScreenOrientation } from '@/utils/bridge';
|
||||
import {
|
||||
tauriHandleSetAlwaysOnTop,
|
||||
@@ -48,7 +44,6 @@ import {
|
||||
} from '@/utils/window';
|
||||
|
||||
import { AboutWindow } from '@/components/AboutWindow';
|
||||
import { KOSyncSettingsWindow } from './components/KOSyncSettings';
|
||||
import { UpdaterWindow } from '@/components/UpdaterWindow';
|
||||
import { BookMetadata } from '@/libs/document';
|
||||
import { BookDetailModal } from '@/components/metadata';
|
||||
@@ -78,7 +73,8 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
setCheckLastOpenBooks,
|
||||
} = useLibraryStore();
|
||||
const _ = useTranslation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { selectFiles } = useFileSelector(appService, _);
|
||||
const { safeAreaInsets: insets, isRoundedWindow } = useThemeStore();
|
||||
const { settings, setSettings, saveSettings } = useSettingsStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const isInitiating = useRef(false);
|
||||
@@ -153,12 +149,12 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
} else {
|
||||
fileExt = file.name.split('.').pop()?.toLowerCase();
|
||||
}
|
||||
return FILE_ACCEPT_FORMATS.includes(`.${fileExt}`);
|
||||
return BOOK_ACCEPT_FORMATS.includes(`.${fileExt}`);
|
||||
});
|
||||
if (supportedFiles.length === 0) {
|
||||
eventDispatcher.dispatch('toast', {
|
||||
message: _('No supported files found. Supported formats: {{formats}}', {
|
||||
formats: FILE_ACCEPT_FORMATS,
|
||||
formats: BOOK_ACCEPT_FORMATS,
|
||||
}),
|
||||
type: 'error',
|
||||
});
|
||||
@@ -169,7 +165,14 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
impactFeedback('medium');
|
||||
}
|
||||
|
||||
await importBooks(supportedFiles);
|
||||
const selectedFiles = supportedFiles.map(
|
||||
(file) =>
|
||||
({
|
||||
file: typeof file === 'string' ? undefined : file,
|
||||
path: typeof file === 'string' ? file : undefined,
|
||||
}) as SelectedFile,
|
||||
);
|
||||
await importBooks(selectedFiles);
|
||||
};
|
||||
|
||||
const handleDragOver = (event: React.DragEvent<HTMLDivElement> | DragEvent) => {
|
||||
@@ -397,7 +400,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [demoBooks, libraryLoaded]);
|
||||
|
||||
const importBooks = async (files: (string | File)[]) => {
|
||||
const importBooks = async (files: SelectedFile[]) => {
|
||||
setLoading(true);
|
||||
const failedFiles = [];
|
||||
const errorMap: [string, string][] = [
|
||||
@@ -406,7 +409,9 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
['Unsupported format.', _('This book format is not supported.')],
|
||||
];
|
||||
const { library } = useLibraryStore.getState();
|
||||
for (const file of files) {
|
||||
for (const selectedFile of files) {
|
||||
const file = selectedFile.file || selectedFile.path;
|
||||
if (!file) continue;
|
||||
try {
|
||||
const book = await appService?.importBook(file, library);
|
||||
setLibrary([...library]);
|
||||
@@ -436,32 +441,6 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const selectFilesTauri = async () => {
|
||||
const exts = appService?.isIOSApp ? [] : SUPPORTED_FILE_EXTS;
|
||||
const files = (await appService?.selectFiles(_('Select Books'), exts)) || [];
|
||||
if (appService?.isIOSApp) {
|
||||
return files.filter((file) => {
|
||||
const fileExt = file.split('.').pop()?.toLowerCase() || 'unknown';
|
||||
return SUPPORTED_FILE_EXTS.includes(fileExt);
|
||||
});
|
||||
}
|
||||
return files;
|
||||
};
|
||||
|
||||
const selectFilesWeb = () => {
|
||||
return new Promise((resolve) => {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = FILE_ACCEPT_FORMATS;
|
||||
fileInput.multiple = true;
|
||||
fileInput.click();
|
||||
|
||||
fileInput.onchange = () => {
|
||||
resolve(fileInput.files);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const updateBookTransferProgress = throttle((bookHash: string, progress: ProgressPayload) => {
|
||||
if (progress.total === 0) return;
|
||||
const progressPct = (progress.progress / progress.total) * 100;
|
||||
@@ -610,14 +589,10 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
const handleImportBooks = async () => {
|
||||
setIsSelectMode(false);
|
||||
console.log('Importing books...');
|
||||
let files;
|
||||
|
||||
if (isTauriAppPlatform()) {
|
||||
files = (await selectFilesTauri()) as string[];
|
||||
} else {
|
||||
files = (await selectFilesWeb()) as File[];
|
||||
}
|
||||
importBooks(files);
|
||||
selectFiles({ type: 'books', multiple: true }).then((result) => {
|
||||
if (result.files.length === 0 || result.error) return;
|
||||
importBooks(result.files);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSetSelectMode = (selectMode: boolean) => {
|
||||
@@ -643,31 +618,28 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
setShowDetailsBook(book);
|
||||
};
|
||||
|
||||
if (!appService || !insets) {
|
||||
return null;
|
||||
if (!appService || !insets || checkOpenWithBooks || checkLastOpenBooks) {
|
||||
return <div className={clsx('h-[100vh]', !appService?.isLinuxApp && 'bg-base-200')} />;
|
||||
}
|
||||
|
||||
if (checkOpenWithBooks || checkLastOpenBooks) {
|
||||
return (
|
||||
loading && (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||
<Spinner loading />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
const showBookshelf = libraryLoaded || libraryBooks.length > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={pageRef}
|
||||
aria-label='Your Library'
|
||||
className={clsx(
|
||||
'library-page bg-base-200 text-base-content flex select-none flex-col overflow-hidden',
|
||||
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
|
||||
appService?.isLinuxApp && 'window-border',
|
||||
appService?.hasRoundedWindow && 'rounded-window',
|
||||
appService?.hasRoundedWindow && isRoundedWindow && 'window-border rounded-window',
|
||||
)}
|
||||
>
|
||||
<div className='top-0 z-40 w-full'>
|
||||
<div
|
||||
className='top-0 z-40 w-full'
|
||||
role='banner'
|
||||
tabIndex={-1}
|
||||
aria-label={_('Library Header')}
|
||||
>
|
||||
<LibraryHeader
|
||||
isSelectMode={isSelectMode}
|
||||
isSelectAll={isSelectAll}
|
||||
@@ -682,11 +654,13 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
<Spinner loading />
|
||||
</div>
|
||||
)}
|
||||
{libraryLoaded &&
|
||||
{showBookshelf &&
|
||||
(libraryBooks.some((book) => !book.deletedAt) ? (
|
||||
<OverlayScrollbarsComponent
|
||||
defer
|
||||
aria-label=''
|
||||
ref={osRef}
|
||||
className='flex-grow'
|
||||
options={{ scrollbars: { autoHide: 'scroll' } }}
|
||||
events={{
|
||||
initialized: (instance) => {
|
||||
@@ -754,7 +728,6 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
/>
|
||||
)}
|
||||
<AboutWindow />
|
||||
<KOSyncSettingsWindow />
|
||||
<UpdaterWindow />
|
||||
<Toast />
|
||||
</div>
|
||||
@@ -763,13 +736,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
|
||||
const LibraryPage = () => {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||
<Spinner loading />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Suspense fallback={<div className='h-[100vh]' />}>
|
||||
<LibraryPageWithSearchParams />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import LibraryPage from './library/page';
|
||||
|
||||
export default function HomePage() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
router.replace('/library');
|
||||
}, [router]);
|
||||
|
||||
return null;
|
||||
return <LibraryPage />;
|
||||
}
|
||||
|
||||
@@ -103,8 +103,7 @@ const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
|
||||
)
|
||||
}
|
||||
onClick={toggleBookmark}
|
||||
tooltip={_('Bookmark')}
|
||||
tooltipDirection='bottom'
|
||||
label={isBookmarked ? _('Remove Bookmark') : _('Add Bookmark')}
|
||||
></Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,12 @@ import clsx from 'clsx';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useSafeAreaInsets } from '@/hooks/useSafeAreaInsets';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { getGridTemplate, getInsetEdges } from '@/utils/grid';
|
||||
import { getViewInsets } from '@/utils/insets';
|
||||
import FoliateViewer from './FoliateViewer';
|
||||
@@ -27,6 +28,7 @@ interface BooksGridProps {
|
||||
}
|
||||
|
||||
const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { getConfig, getBookData } = useBookDataStore();
|
||||
const { getProgress, getViewState, getViewSettings } = useReaderStore();
|
||||
@@ -34,7 +36,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
|
||||
const { sideBarBookKey } = useSidebarStore();
|
||||
const { isFontLayoutSettingsDialogOpen, setFontLayoutSettingsDialogOpen } = useSettingsStore();
|
||||
|
||||
const screenInsets = useSafeAreaInsets();
|
||||
const { safeAreaInsets: screenInsets } = useThemeStore();
|
||||
const aspectRatio = window.innerWidth / window.innerHeight;
|
||||
const gridTemplate = getGridTemplate(bookKeys.length, aspectRatio);
|
||||
|
||||
@@ -75,6 +77,8 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
|
||||
gridTemplateColumns: gridTemplate.columns,
|
||||
gridTemplateRows: gridTemplate.rows,
|
||||
}}
|
||||
role='main'
|
||||
aria-label={_('Books Content')}
|
||||
>
|
||||
{bookKeys.map((bookKey, index) => {
|
||||
const bookData = getBookData(bookKey);
|
||||
@@ -123,6 +127,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
|
||||
bookKey={bookKey}
|
||||
bookDoc={bookDoc}
|
||||
config={config}
|
||||
gridInsets={gridInsets}
|
||||
contentInsets={contentInsets}
|
||||
/>
|
||||
{viewSettings.vertical && viewSettings.scrolled && (
|
||||
@@ -180,7 +185,6 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
|
||||
{showFooter && (
|
||||
<ProgressInfoView
|
||||
bookKey={bookKey}
|
||||
bookFormat={book.format}
|
||||
section={section}
|
||||
pageinfo={pageinfo}
|
||||
timeinfo={timeinfo}
|
||||
|
||||
@@ -7,12 +7,16 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useCustomFontStore } from '@/store/customFontStore';
|
||||
import { useParallelViewStore } from '@/store/parallelViewStore';
|
||||
import { useMouseEvent, useTouchEvent } from '../hooks/useIframeEvents';
|
||||
import { usePagination } from '../hooks/usePagination';
|
||||
import { useFoliateEvents } from '../hooks/useFoliateEvents';
|
||||
import { useProgressSync } from '../hooks/useProgressSync';
|
||||
import { useProgressAutoSave } from '../hooks/useProgressAutoSave';
|
||||
import { useAutoFocus } from '@/hooks/useAutoFocus';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useKOSync } from '../hooks/useKOSync';
|
||||
import {
|
||||
applyFixedlayoutStyles,
|
||||
@@ -21,7 +25,7 @@ import {
|
||||
getStyles,
|
||||
transformStylesheet,
|
||||
} from '@/utils/style';
|
||||
import { mountAdditionalFonts } from '@/utils/font';
|
||||
import { mountAdditionalFonts, mountCustomFont } from '@/styles/fonts';
|
||||
import { getBookDirFromLanguage, getBookDirFromWritingMode } from '@/utils/book';
|
||||
import { useUICSS } from '@/hooks/useUICSS';
|
||||
import {
|
||||
@@ -43,7 +47,9 @@ import { lockScreenOrientation } from '@/utils/bridge';
|
||||
import { useTextTranslation } from '../hooks/useTextTranslation';
|
||||
import { manageSyntaxHighlighting } from '@/utils/highlightjs';
|
||||
import { getViewInsets } from '@/utils/insets';
|
||||
import ConfirmSyncDialog from './ConfirmSyncDialog';
|
||||
import { removeTabIndex } from '@/utils/a11y';
|
||||
import Spinner from '@/components/Spinner';
|
||||
import KOSyncConflictResolver from './KOSyncResolver';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -55,14 +61,19 @@ const FoliateViewer: React.FC<{
|
||||
bookKey: string;
|
||||
bookDoc: BookDoc;
|
||||
config: BookConfig;
|
||||
gridInsets: Insets;
|
||||
contentInsets: Insets;
|
||||
}> = ({ bookKey, bookDoc, config, contentInsets: insets }) => {
|
||||
const { getView, setView: setFoliateView, setProgress } = useReaderStore();
|
||||
const { getViewSettings, setViewSettings } = useReaderStore();
|
||||
}> = ({ bookKey, bookDoc, config, gridInsets, contentInsets: insets }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService, envConfig } = useEnv();
|
||||
const { themeCode, isDarkMode } = useThemeStore();
|
||||
const { settings } = useSettingsStore();
|
||||
const { loadCustomFonts, getLoadedFonts } = useCustomFontStore();
|
||||
const { getView, setView: setFoliateView, setViewInited, setProgress } = useReaderStore();
|
||||
const { getViewState, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const { getParallels } = useParallelViewStore();
|
||||
const { getBookData } = useBookDataStore();
|
||||
const { appService } = useEnv();
|
||||
const { themeCode, isDarkMode } = useThemeStore();
|
||||
const viewState = getViewState(bookKey);
|
||||
const viewSettings = getViewSettings(bookKey);
|
||||
|
||||
const viewRef = useRef<FoliateView | null>(null);
|
||||
@@ -70,6 +81,10 @@ const FoliateViewer: React.FC<{
|
||||
const isViewCreated = useRef(false);
|
||||
const doubleClickDisabled = useRef(!!viewSettings?.disableDoubleClick);
|
||||
const [toastMessage, setToastMessage] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const docLoaded = useRef(false);
|
||||
|
||||
useAutoFocus<HTMLDivElement>({ ref: containerRef });
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setToastMessage(''), 2000);
|
||||
@@ -79,12 +94,7 @@ const FoliateViewer: React.FC<{
|
||||
useUICSS(bookKey);
|
||||
useProgressSync(bookKey);
|
||||
useProgressAutoSave(bookKey);
|
||||
const {
|
||||
syncState,
|
||||
conflictDetails,
|
||||
resolveConflictWithLocal,
|
||||
resolveConflictWithRemote,
|
||||
} = useKOSync(bookKey);
|
||||
const { syncState, conflictDetails, resolveWithLocal, resolveWithRemote } = useKOSync(bookKey);
|
||||
useTextTranslation(bookKey, viewRef.current);
|
||||
|
||||
const progressRelocateHandler = (event: Event) => {
|
||||
@@ -127,6 +137,8 @@ const FoliateViewer: React.FC<{
|
||||
};
|
||||
|
||||
const docLoadHandler = (event: Event) => {
|
||||
setLoading(false);
|
||||
docLoaded.current = true;
|
||||
const detail = (event as CustomEvent).detail;
|
||||
console.log('doc index loaded:', detail.index);
|
||||
if (detail.doc) {
|
||||
@@ -144,12 +156,18 @@ const FoliateViewer: React.FC<{
|
||||
|
||||
mountAdditionalFonts(detail.doc, isCJKLang(bookData.book?.primaryLanguage));
|
||||
|
||||
getLoadedFonts().forEach((font) => {
|
||||
mountCustomFont(detail.doc, font);
|
||||
});
|
||||
|
||||
if (bookDoc.rendition?.layout === 'pre-paginated') {
|
||||
applyFixedlayoutStyles(detail.doc, viewSettings);
|
||||
}
|
||||
|
||||
applyImageStyle(detail.doc);
|
||||
|
||||
removeTabIndex(detail.doc);
|
||||
|
||||
// Inline scripts in tauri platforms are not executed by default
|
||||
if (viewSettings.allowScript && isTauriAppPlatform()) {
|
||||
evalInlineScripts(detail.doc);
|
||||
@@ -212,7 +230,7 @@ const FoliateViewer: React.FC<{
|
||||
|
||||
const { handlePageFlip, handleContinuousScroll } = usePagination(bookKey, viewRef, containerRef);
|
||||
const mouseHandlers = useMouseEvent(bookKey, handlePageFlip, handleContinuousScroll);
|
||||
const touchHandlers = useTouchEvent(bookKey, handleContinuousScroll);
|
||||
const touchHandlers = useTouchEvent(bookKey, handlePageFlip, handleContinuousScroll);
|
||||
|
||||
useFoliateEvents(viewRef.current, {
|
||||
onLoad: docLoadHandler,
|
||||
@@ -224,6 +242,8 @@ const FoliateViewer: React.FC<{
|
||||
if (isViewCreated.current) return;
|
||||
isViewCreated.current = true;
|
||||
|
||||
setTimeout(() => setLoading(true), 200);
|
||||
|
||||
const openBook = async () => {
|
||||
console.log('Opening book', bookKey);
|
||||
await import('foliate-js/view.js');
|
||||
@@ -244,6 +264,12 @@ const FoliateViewer: React.FC<{
|
||||
}
|
||||
}
|
||||
|
||||
if (bookDoc.rendition?.layout === 'pre-paginated' && bookDoc.sections) {
|
||||
bookDoc.rendition.spread = viewSettings.spreadMode;
|
||||
const coverSide = bookDoc.dir === 'rtl' ? 'right' : 'left';
|
||||
bookDoc.sections[0]!.pageSpread = viewSettings.keepCoverSpread ? '' : coverSide;
|
||||
}
|
||||
|
||||
await view.open(bookDoc);
|
||||
// make sure we can listen renderer events after opening book
|
||||
viewRef.current = view;
|
||||
@@ -279,9 +305,15 @@ const FoliateViewer: React.FC<{
|
||||
} else {
|
||||
view.renderer.removeAttribute('animated');
|
||||
}
|
||||
view.renderer.setAttribute('max-column-count', maxColumnCount);
|
||||
view.renderer.setAttribute('max-inline-size', `${maxInlineSize}px`);
|
||||
view.renderer.setAttribute('max-block-size', `${maxBlockSize}px`);
|
||||
if (bookDoc?.rendition?.layout === 'pre-paginated') {
|
||||
view.renderer.setAttribute('zoom', viewSettings.zoomMode);
|
||||
view.renderer.setAttribute('spread', viewSettings.spreadMode);
|
||||
view.renderer.setAttribute('scale-factor', viewSettings.zoomLevel);
|
||||
} else {
|
||||
view.renderer.setAttribute('max-column-count', maxColumnCount);
|
||||
view.renderer.setAttribute('max-inline-size', `${maxInlineSize}px`);
|
||||
view.renderer.setAttribute('max-block-size', `${maxBlockSize}px`);
|
||||
}
|
||||
applyMarginAndGap();
|
||||
|
||||
const lastLocation = config.location;
|
||||
@@ -290,6 +322,7 @@ const FoliateViewer: React.FC<{
|
||||
} else {
|
||||
await view.goToFraction(0);
|
||||
}
|
||||
setViewInited(bookKey, true);
|
||||
};
|
||||
|
||||
openBook();
|
||||
@@ -298,6 +331,7 @@ const FoliateViewer: React.FC<{
|
||||
|
||||
const applyMarginAndGap = () => {
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const viewState = getViewState(bookKey);
|
||||
const viewInsets = getViewInsets(viewSettings);
|
||||
const showDoubleBorder = viewSettings.vertical && viewSettings.doubleBorder;
|
||||
const showDoubleBorderHeader = showDoubleBorder && viewSettings.showHeader;
|
||||
@@ -305,7 +339,11 @@ const FoliateViewer: React.FC<{
|
||||
const showTopHeader = viewSettings.showHeader && !viewSettings.vertical;
|
||||
const showBottomFooter = viewSettings.showFooter && !viewSettings.vertical;
|
||||
const moreTopInset = showTopHeader ? Math.max(0, 44 - insets.top) : 0;
|
||||
const moreBottomInset = showBottomFooter ? Math.max(0, 44 - insets.bottom) : 0;
|
||||
const ttsBarHeight =
|
||||
viewState?.ttsEnabled && viewSettings.showTTSBar ? 52 + gridInsets.bottom * 0.33 : 0;
|
||||
const moreBottomInset = showBottomFooter
|
||||
? Math.max(0, Math.max(ttsBarHeight, 44) - insets.bottom)
|
||||
: Math.max(0, ttsBarHeight);
|
||||
const moreRightInset = showDoubleBorderHeader ? 32 : 0;
|
||||
const moreLeftInset = showDoubleBorderFooter ? 32 : 0;
|
||||
const topMargin = (showTopHeader ? insets.top : viewInsets.top) + moreTopInset;
|
||||
@@ -335,6 +373,21 @@ const FoliateViewer: React.FC<{
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [themeCode, isDarkMode, viewSettings?.overrideColor, viewSettings?.invertImgColorInDark]);
|
||||
|
||||
useEffect(() => {
|
||||
const mountCustomFonts = async () => {
|
||||
await loadCustomFonts(envConfig);
|
||||
getLoadedFonts().forEach((font) => {
|
||||
mountCustomFont(document, font);
|
||||
const docs = viewRef.current?.renderer.getContents();
|
||||
docs?.forEach(({ doc }) => mountCustomFont(doc, font));
|
||||
});
|
||||
};
|
||||
if (settings.customFonts) {
|
||||
mountCustomFonts();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [settings.customFonts, envConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewRef.current && viewRef.current.renderer) {
|
||||
doubleClickDisabled.current = !!viewSettings?.disableDoubleClick;
|
||||
@@ -354,22 +407,28 @@ const FoliateViewer: React.FC<{
|
||||
viewSettings?.doubleBorder,
|
||||
viewSettings?.showHeader,
|
||||
viewSettings?.showFooter,
|
||||
viewSettings?.showTTSBar,
|
||||
viewState?.ttsEnabled,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className='foliate-viewer h-[100%] w-[100%]'
|
||||
tabIndex={-1}
|
||||
role='document'
|
||||
aria-label={_('Book Content')}
|
||||
className='foliate-viewer h-[100%] w-[100%] focus:outline-none'
|
||||
{...mouseHandlers}
|
||||
{...touchHandlers}
|
||||
/>
|
||||
{!docLoaded.current && loading && <Spinner loading={true} />}
|
||||
{syncState === 'conflict' && conflictDetails && (
|
||||
<ConfirmSyncDialog
|
||||
<KOSyncConflictResolver
|
||||
details={conflictDetails}
|
||||
onConfirmLocal={resolveConflictWithLocal}
|
||||
onConfirmRemote={resolveConflictWithRemote}
|
||||
onClose={resolveConflictWithLocal}
|
||||
onResolveWithLocal={resolveWithLocal}
|
||||
onResolveWithRemote={resolveWithRemote}
|
||||
onClose={resolveWithLocal}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -173,6 +173,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
role='none'
|
||||
className={clsx(
|
||||
'absolute bottom-0 left-0 z-10 hidden w-full sm:flex sm:h-[52px]',
|
||||
// show scroll bar when vertical and scrolled in desktop
|
||||
@@ -182,12 +183,15 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
onTouchStart={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
/>
|
||||
<div
|
||||
role='group'
|
||||
aria-label={_('Footer Bar')}
|
||||
className={clsx(
|
||||
'footer-bar shadow-xs bottom-0 z-50 flex w-full flex-col',
|
||||
'footer-bar shadow-xs bottom-0 z-10 flex w-full flex-col',
|
||||
'sm:h-[52px] sm:justify-center',
|
||||
'sm:bg-base-100 border-base-300/50 border-t sm:border-none',
|
||||
'transition-[opacity,transform] duration-300',
|
||||
appService?.isMobile ? 'fixed' : 'absolute',
|
||||
// See: https://github.com/readest/readest/issues/1716
|
||||
appService?.isAndroidApp && window.innerWidth < 640 ? 'fixed' : 'absolute',
|
||||
appService?.hasRoundedWindow && 'rounded-window-bottom-right',
|
||||
!isSideBarVisible && appService?.hasRoundedWindow && 'rounded-window-bottom-left',
|
||||
isHoveredAnim && 'hover-bar-anim',
|
||||
@@ -198,8 +202,8 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
: `pointer-events-none translate-y-full opacity-0 sm:translate-y-0`,
|
||||
)}
|
||||
dir={viewSettings?.rtl ? 'rtl' : 'ltr'}
|
||||
onFocus={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
onMouseLeave={() => window.innerWidth >= 640 && setHoveredBookKey('')}
|
||||
aria-hidden={!isVisible}
|
||||
>
|
||||
{/* Mobile footer bar */}
|
||||
<div
|
||||
@@ -210,11 +214,12 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
: 'pointer-events-none invisible translate-y-full overflow-hidden pb-0 pt-0 ease-in',
|
||||
)}
|
||||
style={{
|
||||
bottom: isMobile ? `${gridInsets.bottom + 64}px` : '64px',
|
||||
bottom: isMobile ? `${gridInsets.bottom * 0.33 + 64}px` : '64px',
|
||||
}}
|
||||
>
|
||||
<div className='flex w-full items-center justify-between gap-x-6'>
|
||||
<Slider
|
||||
label={_('Reading Progress')}
|
||||
heightPx={sliderHeight}
|
||||
bubbleLabel={`${Math.round(progressFraction * 100)}%`}
|
||||
initialValue={progressValid ? progressFraction * 100 : 0}
|
||||
@@ -225,34 +230,34 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowRightDoubleLine /> : <RiArrowLeftDoubleLine />}
|
||||
onClick={viewSettings?.rtl ? handleGoNextSection : handleGoPrevSection}
|
||||
tooltip={viewSettings?.rtl ? _('Next Section') : _('Previous Section')}
|
||||
label={viewSettings?.rtl ? _('Next Section') : _('Previous Section')}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowRightSLine /> : <RiArrowLeftSLine />}
|
||||
onClick={viewSettings?.rtl ? handleGoNextPage : handleGoPrevPage}
|
||||
tooltip={viewSettings?.rtl ? _('Next Page') : _('Previous Page')}
|
||||
label={viewSettings?.rtl ? _('Next Page') : _('Previous Page')}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowGoForwardLine /> : <RiArrowGoBackLine />}
|
||||
onClick={handleGoBack}
|
||||
tooltip={_('Go Back')}
|
||||
label={_('Go Back')}
|
||||
disabled={!view?.history.canGoBack}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowGoBackLine /> : <RiArrowGoForwardLine />}
|
||||
onClick={handleGoForward}
|
||||
tooltip={_('Go Forward')}
|
||||
label={_('Go Forward')}
|
||||
disabled={!view?.history.canGoForward}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowLeftSLine /> : <RiArrowRightSLine />}
|
||||
onClick={viewSettings?.rtl ? handleGoPrevPage : handleGoNextPage}
|
||||
tooltip={viewSettings?.rtl ? _('Previous Page') : _('Next Page')}
|
||||
label={viewSettings?.rtl ? _('Previous Page') : _('Next Page')}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowLeftDoubleLine /> : <RiArrowRightDoubleLine />}
|
||||
onClick={viewSettings?.rtl ? handleGoPrevSection : handleGoNextSection}
|
||||
tooltip={viewSettings?.rtl ? _('Previous Section') : _('Next Section')}
|
||||
label={viewSettings?.rtl ? _('Previous Section') : _('Next Section')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -264,10 +269,11 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
: 'pointer-events-none invisible translate-y-full overflow-hidden pb-0 pt-0 ease-in',
|
||||
)}
|
||||
style={{
|
||||
bottom: isMobile ? `${gridInsets.bottom + 64}px` : '64px',
|
||||
bottom: isMobile ? `${gridInsets.bottom * 0.33 + 64}px` : '64px',
|
||||
}}
|
||||
>
|
||||
<Slider
|
||||
label={_('Font Size')}
|
||||
initialValue={viewSettings?.defaultFontSize ?? 16}
|
||||
bubbleLabel={`${viewSettings?.defaultFontSize ?? 16}`}
|
||||
minLabel='A'
|
||||
@@ -280,6 +286,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
/>
|
||||
<div className='flex w-full items-center justify-between gap-x-6'>
|
||||
<Slider
|
||||
label={_('Page Margin')}
|
||||
initialValue={getMarginProgressValue(
|
||||
viewSettings?.marginTopPx ?? 44,
|
||||
viewSettings?.gapPercent ?? 5,
|
||||
@@ -291,6 +298,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
onChange={handleMarginChange}
|
||||
/>
|
||||
<Slider
|
||||
label={_('Line Spacing')}
|
||||
initialValue={(viewSettings?.lineHeight ?? 1.6) * 10}
|
||||
bubbleElement={<RxLineHeight size={marginIconSize} />}
|
||||
minLabel={_('Small')}
|
||||
@@ -303,22 +311,29 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'bg-base-200 z-50 mt-auto flex w-full justify-between px-8 py-4 sm:hidden',
|
||||
'bg-base-200 z-30 mt-auto flex w-full justify-between px-8 py-4 sm:hidden',
|
||||
)}
|
||||
style={{
|
||||
paddingBottom: isMobile ? `${gridInsets.bottom + 16}px` : '0px',
|
||||
paddingBottom: isMobile ? `${gridInsets.bottom * 0.33 + 16}px` : '0px',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
label={_('Table of Contents')}
|
||||
icon={<TOCIcon size={tocIconSize} className='' />}
|
||||
onClick={() => handleSetActionTab('toc')}
|
||||
/>
|
||||
<Button icon={<NoteIcon className='' />} onClick={() => handleSetActionTab('note')} />
|
||||
<Button
|
||||
label={_('Notes')}
|
||||
icon={<NoteIcon className='' />}
|
||||
onClick={() => handleSetActionTab('note')}
|
||||
/>
|
||||
<Button
|
||||
label={_('Reading Progress')}
|
||||
icon={<SliderIcon className={clsx(actionTab === 'progress' && 'text-blue-500')} />}
|
||||
onClick={() => handleSetActionTab('progress')}
|
||||
/>
|
||||
<Button
|
||||
label={_('Font & Layout')}
|
||||
icon={
|
||||
<FontIcon
|
||||
size={fontIconSize}
|
||||
@@ -328,6 +343,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
onClick={() => handleSetActionTab('font')}
|
||||
/>
|
||||
<Button
|
||||
label={_('Speak')}
|
||||
icon={<TTSIcon className={ttsEnabled ? 'text-blue-500' : ''} />}
|
||||
onClick={() => handleSetActionTab('tts')}
|
||||
/>
|
||||
@@ -336,39 +352,46 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
<div
|
||||
className='absolute hidden h-full w-full items-center gap-x-4 px-4 sm:flex'
|
||||
style={{
|
||||
bottom: isMobile ? `${gridInsets.bottom / 2}px` : '0px',
|
||||
bottom: isMobile ? `${gridInsets.bottom * 0.33}px` : '0px',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowRightDoubleLine /> : <RiArrowLeftDoubleLine />}
|
||||
onClick={viewSettings?.rtl ? handleGoNextSection : handleGoPrevSection}
|
||||
tooltip={viewSettings?.rtl ? _('Next Section') : _('Previous Section')}
|
||||
label={viewSettings?.rtl ? _('Next Section') : _('Previous Section')}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowRightSLine /> : <RiArrowLeftSLine />}
|
||||
onClick={viewSettings?.rtl ? handleGoNextPage : handleGoPrevPage}
|
||||
tooltip={viewSettings?.rtl ? _('Next Page') : _('Previous Page')}
|
||||
label={viewSettings?.rtl ? _('Next Page') : _('Previous Page')}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowGoForwardLine /> : <RiArrowGoBackLine />}
|
||||
onClick={handleGoBack}
|
||||
tooltip={_('Go Back')}
|
||||
label={_('Go Back')}
|
||||
disabled={!view?.history.canGoBack}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowGoBackLine /> : <RiArrowGoForwardLine />}
|
||||
onClick={handleGoForward}
|
||||
tooltip={_('Go Forward')}
|
||||
label={_('Go Forward')}
|
||||
disabled={!view?.history.canGoForward}
|
||||
/>
|
||||
<span className='mx-2 text-center text-sm'>
|
||||
{progressValid ? `${Math.round(progressFraction * 100)}%` : ''}
|
||||
</span>
|
||||
{progressValid && (
|
||||
<span
|
||||
title={_('Reading Progress')}
|
||||
aria-label={_('Reading Progress') + ': ' + `${Math.round(progressFraction * 100)}%`}
|
||||
className='mx-2 text-center text-sm'
|
||||
>
|
||||
<span aria-hidden='true'>{`${Math.round(progressFraction * 100)}%`}</span>
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
type='range'
|
||||
className='text-base-content mx-2 w-full'
|
||||
className='text-base-content mx-2 min-w-0 flex-1'
|
||||
min={0}
|
||||
max={100}
|
||||
aria-label={_('Jump to Location')}
|
||||
value={progressValid ? progressFraction * 100 : 0}
|
||||
onChange={(e) =>
|
||||
handleProgressChange(parseInt((e.target as HTMLInputElement).value, 10))
|
||||
@@ -377,21 +400,21 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
<Button
|
||||
icon={<FaHeadphones className={ttsEnabled ? 'text-blue-500' : ''} />}
|
||||
onClick={handleSpeakText}
|
||||
tooltip={_('Speak')}
|
||||
label={_('Speak')}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowLeftSLine /> : <RiArrowRightSLine />}
|
||||
onClick={viewSettings?.rtl ? handleGoPrevPage : handleGoNextPage}
|
||||
tooltip={viewSettings?.rtl ? _('Previous Page') : _('Next Page')}
|
||||
label={viewSettings?.rtl ? _('Previous Page') : _('Next Page')}
|
||||
/>
|
||||
<Button
|
||||
icon={viewSettings?.rtl ? <RiArrowLeftDoubleLine /> : <RiArrowRightDoubleLine />}
|
||||
onClick={viewSettings?.rtl ? handleGoPrevSection : handleGoNextSection}
|
||||
tooltip={viewSettings?.rtl ? _('Previous Section') : _('Next Section')}
|
||||
label={viewSettings?.rtl ? _('Previous Section') : _('Next Section')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<TTSControl bookKey={bookKey} />
|
||||
<TTSControl bookKey={bookKey} gridInsets={gridInsets} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,10 +7,11 @@ import { useFoliateEvents } from '../hooks/useFoliateEvents';
|
||||
import { getFootnoteStyles, getStyles, getThemeCode } from '@/utils/style';
|
||||
import { getPopupPosition, getPosition, Position } from '@/utils/sel';
|
||||
import { FootnoteHandler } from 'foliate-js/footnotes.js';
|
||||
import { mountAdditionalFonts } from '@/utils/font';
|
||||
import { mountAdditionalFonts } from '@/styles/fonts';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { FoliateView } from '@/types/view';
|
||||
import { isCJKLang } from '@/utils/lang';
|
||||
import { Overlay } from '@/components/Overlay';
|
||||
import Popup from '@/components/Popup';
|
||||
|
||||
interface FootnotePopupProps {
|
||||
@@ -226,13 +227,7 @@ const FootnotePopup: React.FC<FootnotePopupProps> = ({ bookKey, bookDoc }) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{showPopup && (
|
||||
<div
|
||||
className='fixed inset-0'
|
||||
onClick={handleDismissPopup}
|
||||
onContextMenu={handleDismissPopup}
|
||||
/>
|
||||
)}
|
||||
{showPopup && <Overlay onDismiss={handleDismissPopup} />}
|
||||
<Popup
|
||||
width={responsiveWidth}
|
||||
height={responsiveHeight}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { PiDotsThreeVerticalBold } from 'react-icons/pi';
|
||||
|
||||
import { Insets } from '@/types/misc';
|
||||
@@ -7,6 +7,7 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useTrafficLightStore } from '@/store/trafficLightStore';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import WindowButtons from '@/components/WindowButtons';
|
||||
@@ -37,9 +38,11 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
onCloseBook,
|
||||
onSetSettingsDialogOpen,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
isTrafficLightVisible,
|
||||
trafficLightInFullscreen,
|
||||
setTrafficLightVisibility,
|
||||
initializeTrafficLightStore,
|
||||
@@ -52,6 +55,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
const { isSideBarVisible } = useSidebarStore();
|
||||
const iconSize16 = useResponsiveSize(16);
|
||||
|
||||
const windowButtonVisible = appService?.hasWindowBar && !isTrafficLightVisible;
|
||||
|
||||
const handleToggleDropdown = (isOpen: boolean) => {
|
||||
setIsDropdownOpen(isOpen);
|
||||
if (!isOpen) setHoveredBookKey('');
|
||||
@@ -80,6 +85,16 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appService, isSideBarVisible, hoveredBookKey]);
|
||||
|
||||
// Check if mouse is outside header area to avoid false positive event of MouseLeave when clicking inside header on Windows
|
||||
const isMouseOutsideHeader = useCallback((clientX: number, clientY: number) => {
|
||||
if (!headerRef.current) return true;
|
||||
|
||||
const rect = headerRef.current.getBoundingClientRect();
|
||||
return (
|
||||
clientX <= rect.left || clientX >= rect.right || clientY <= rect.top || clientY >= rect.bottom
|
||||
);
|
||||
}, []);
|
||||
|
||||
const isHeaderVisible = hoveredBookKey === bookKey || isDropdownOpen;
|
||||
const trafficLightInHeader =
|
||||
appService?.hasTrafficLight && !trafficLightInFullscreen && !isSideBarVisible && isTopLeft;
|
||||
@@ -92,6 +107,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
role='none'
|
||||
className={clsx('absolute top-0 z-10 h-11 w-full')}
|
||||
onMouseEnter={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
onTouchStart={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
@@ -108,6 +124,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
/>
|
||||
<div
|
||||
ref={headerRef}
|
||||
role='group'
|
||||
aria-label={_('Header Bar')}
|
||||
className={clsx(
|
||||
`header-bar bg-base-100 absolute top-0 z-10 flex h-11 w-full items-center pr-4`,
|
||||
`shadow-xs transition-[opacity,margin-top] duration-300`,
|
||||
@@ -123,7 +141,12 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
? `${Math.max(gridInsets.top, statusBarHeight)}px`
|
||||
: `${gridInsets.top}px`,
|
||||
}}
|
||||
onMouseLeave={() => !appService?.isMobile && setHoveredBookKey('')}
|
||||
onFocus={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
|
||||
onMouseLeave={(e) => {
|
||||
if (!appService?.isMobile && isMouseOutsideHeader(e.clientX, e.clientY)) {
|
||||
setHoveredBookKey('');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className='bg-base-100 sidebar-bookmark-toggler z-20 flex h-full items-center gap-x-4 pe-2'>
|
||||
<div className='hidden sm:flex'>
|
||||
@@ -133,16 +156,30 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
<TranslationToggler bookKey={bookKey} />
|
||||
</div>
|
||||
|
||||
<div className='header-title z-15 bg-base-100 pointer-events-none absolute inset-0 hidden items-center justify-center sm:flex'>
|
||||
<h2 className='line-clamp-1 max-w-[50%] text-center text-xs font-semibold'>
|
||||
<div
|
||||
role='contentinfo'
|
||||
aria-label={_('Title') + ' - ' + bookTitle}
|
||||
className={clsx(
|
||||
'header-title z-15 bg-base-100 pointer-events-none hidden flex-1 items-center justify-center sm:flex',
|
||||
!windowButtonVisible && 'absolute inset-0',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className={clsx(
|
||||
'line-clamp-1 text-center text-xs font-semibold',
|
||||
!windowButtonVisible && 'max-w-[50%]',
|
||||
)}
|
||||
>
|
||||
{bookTitle}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-base-100 z-20 ml-auto flex h-full items-center space-x-4 ps-2'>
|
||||
<SettingsToggler />
|
||||
<NotebookToggler bookKey={bookKey} />
|
||||
<Dropdown
|
||||
label={_('View Options')}
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<PiDotsThreeVerticalBold size={iconSize16} />}
|
||||
@@ -154,16 +191,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
<WindowButtons
|
||||
className='window-buttons flex h-full items-center'
|
||||
headerRef={headerRef}
|
||||
showMinimize={
|
||||
bookKeys.length == 1 &&
|
||||
!appService?.hasTrafficLight &&
|
||||
appService?.appPlatform !== 'web'
|
||||
}
|
||||
showMaximize={
|
||||
bookKeys.length == 1 &&
|
||||
!appService?.hasTrafficLight &&
|
||||
appService?.appPlatform !== 'web'
|
||||
}
|
||||
showMinimize={bookKeys.length == 1 && windowButtonVisible}
|
||||
showMaximize={bookKeys.length == 1 && windowButtonVisible}
|
||||
onClose={() => {
|
||||
setHoveredBookKey(null);
|
||||
onCloseBook(bookKey);
|
||||
|
||||
+12
-9
@@ -3,17 +3,17 @@ import Dialog from '@/components/Dialog';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { SyncDetails } from '../hooks/useKOSync';
|
||||
|
||||
interface ConfirmSyncDialogProps {
|
||||
interface KOSyncConflictResolverProps {
|
||||
details: SyncDetails | null;
|
||||
onConfirmLocal: () => void;
|
||||
onConfirmRemote: () => void;
|
||||
onResolveWithLocal: () => void;
|
||||
onResolveWithRemote: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ConfirmSyncDialog: React.FC<ConfirmSyncDialogProps> = ({
|
||||
const KOSyncConflictResolver: React.FC<KOSyncConflictResolverProps> = ({
|
||||
details,
|
||||
onConfirmLocal,
|
||||
onConfirmRemote,
|
||||
onResolveWithLocal,
|
||||
onResolveWithRemote,
|
||||
onClose,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
@@ -28,7 +28,10 @@ const ConfirmSyncDialog: React.FC<ConfirmSyncDialogProps> = ({
|
||||
})}
|
||||
</p>
|
||||
<div className='mt-4 space-y-4'>
|
||||
<button className='btn h-auto w-full flex-col items-start py-2' onClick={onConfirmLocal}>
|
||||
<button
|
||||
className='btn h-auto w-full flex-col items-start py-2'
|
||||
onClick={onResolveWithLocal}
|
||||
>
|
||||
<span>{_('Local Progress')}</span>
|
||||
<span className='text-xs font-normal normal-case text-gray-500'>
|
||||
{details.local.preview}
|
||||
@@ -36,7 +39,7 @@ const ConfirmSyncDialog: React.FC<ConfirmSyncDialogProps> = ({
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-primary h-auto w-full flex-col items-start py-2'
|
||||
onClick={onConfirmRemote}
|
||||
onClick={onResolveWithRemote}
|
||||
>
|
||||
<span>{_('Remote Progress')}</span>
|
||||
<span className='text-xs font-normal normal-case'>{details.remote.preview}</span>
|
||||
@@ -46,4 +49,4 @@ const ConfirmSyncDialog: React.FC<ConfirmSyncDialogProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmSyncDialog;
|
||||
export default KOSyncConflictResolver;
|
||||
@@ -0,0 +1,342 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { md5 } from 'js-md5';
|
||||
import { type as osType } from '@tauri-apps/plugin-os';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { KOSyncClient } from '@/services/sync/KOSyncClient';
|
||||
import { KOSyncChecksumMethod, KOSyncStrategy } from '@/types/settings';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import { getOSPlatform } from '@/utils/misc';
|
||||
import Dialog from '@/components/Dialog';
|
||||
|
||||
type Option = {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
options: Option[];
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const StyledSelect: React.FC<SelectProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
className,
|
||||
disabled = false,
|
||||
}) => {
|
||||
return (
|
||||
<select
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={clsx(
|
||||
'select select-bordered h-12 w-full text-sm focus:outline-none focus:ring-0',
|
||||
className,
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{options.map(({ value, label, disabled = false }) => (
|
||||
<option key={value} value={value} disabled={disabled}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export const setKOSyncSettingsWindowVisible = (visible: boolean) => {
|
||||
const dialog = document.getElementById('kosync_settings_window');
|
||||
if (dialog) {
|
||||
const event = new CustomEvent('setKOSyncSettingsVisibility', {
|
||||
detail: { visible },
|
||||
});
|
||||
dialog.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
export const KOSyncSettingsWindow: React.FC = () => {
|
||||
const _ = useTranslation();
|
||||
const { settings, setSettings, saveSettings } = useSettingsStore();
|
||||
const { envConfig, appService } = useEnv();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [url, setUrl] = useState(settings.kosync.serverUrl || '');
|
||||
const [username, setUsername] = useState(settings.kosync.username || '');
|
||||
const [password, setPassword] = useState('');
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [connectionStatus, setConnectionStatus] = useState('');
|
||||
const [deviceName, setDeviceName] = useState('');
|
||||
const [osName, setOsName] = useState('');
|
||||
|
||||
// Get the OS name once
|
||||
useEffect(() => {
|
||||
const formatOsName = (name: string): string => {
|
||||
if (!name) return '';
|
||||
if (name.toLowerCase() === 'macos') return 'macOS';
|
||||
if (name.toLowerCase() === 'ios') return 'iOS';
|
||||
return name.charAt(0).toUpperCase() + name.slice(1);
|
||||
};
|
||||
|
||||
const getOsName = async () => {
|
||||
let name = '';
|
||||
if (appService?.appPlatform === 'tauri') {
|
||||
name = await osType();
|
||||
} else {
|
||||
const platform = getOSPlatform();
|
||||
if (platform !== 'unknown') {
|
||||
name = platform;
|
||||
}
|
||||
}
|
||||
setOsName(formatOsName(name));
|
||||
};
|
||||
getOsName();
|
||||
}, [appService]);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultName = osName ? `Readest (${osName})` : 'Readest';
|
||||
setDeviceName(settings.kosync.deviceName || defaultName);
|
||||
}, [settings.kosync.deviceName, osName]);
|
||||
|
||||
const isConfigured = useMemo(() => !!settings.kosync.userkey, [settings.kosync.userkey]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedSaveDeviceName = useCallback(
|
||||
debounce((newDeviceName: string) => {
|
||||
const newSettings = { ...settings, koreaderSyncDeviceName: newDeviceName };
|
||||
setSettings(newSettings);
|
||||
saveSettings(envConfig, newSettings);
|
||||
}, 500),
|
||||
[settings, setSettings, saveSettings, envConfig],
|
||||
);
|
||||
|
||||
const handleDeviceNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newName = e.target.value;
|
||||
setDeviceName(newName);
|
||||
debouncedSaveDeviceName(newName);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleCustomEvent = (event: CustomEvent) => {
|
||||
setIsOpen(event.detail.visible);
|
||||
if (event.detail.visible) {
|
||||
setUrl(settings.kosync.serverUrl || '');
|
||||
setUsername(settings.kosync.username || '');
|
||||
setPassword('');
|
||||
setConnectionStatus('');
|
||||
}
|
||||
};
|
||||
const el = document.getElementById('kosync_settings_window');
|
||||
el?.addEventListener('setKOSyncSettingsVisibility', handleCustomEvent as EventListener);
|
||||
return () => {
|
||||
el?.removeEventListener('setKOSyncSettingsVisibility', handleCustomEvent as EventListener);
|
||||
};
|
||||
}, [settings.kosync.serverUrl, settings.kosync.username]);
|
||||
|
||||
const handleConnect = async () => {
|
||||
setIsConnecting(true);
|
||||
|
||||
const config = {
|
||||
...settings.kosync,
|
||||
serverUrl: url,
|
||||
username,
|
||||
userkey: md5(password),
|
||||
deviceName,
|
||||
enabled: true,
|
||||
};
|
||||
const client = new KOSyncClient(config);
|
||||
const result = await client.connect(username, password);
|
||||
|
||||
if (result.success) {
|
||||
const newSettings = {
|
||||
...settings,
|
||||
kosync: config,
|
||||
};
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
} else {
|
||||
setConnectionStatus('');
|
||||
eventDispatcher.dispatch('toast', {
|
||||
message: `${_('Failed to connect')}: ${_(result.message || 'Connection error')}`,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
setIsConnecting(false);
|
||||
setPassword('');
|
||||
};
|
||||
|
||||
const handleDisconnect = async () => {
|
||||
const kosync = {
|
||||
...settings.kosync,
|
||||
userkey: '',
|
||||
enabled: false,
|
||||
};
|
||||
const newSettings = { ...settings, kosync };
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
setUsername('');
|
||||
eventDispatcher.dispatch('toast', { message: _('Disconnected'), type: 'info' });
|
||||
};
|
||||
|
||||
const handleStrategyChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const kosync = {
|
||||
...settings.kosync,
|
||||
strategy: e.target.value as KOSyncStrategy,
|
||||
};
|
||||
|
||||
const newSettings = { ...settings, kosync };
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
};
|
||||
|
||||
const handleChecksumMethodChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const kosync = {
|
||||
...settings.kosync,
|
||||
checksumMethod: e.target.value as KOSyncChecksumMethod,
|
||||
};
|
||||
|
||||
const newSettings = { ...settings, kosync };
|
||||
setSettings(newSettings);
|
||||
await saveSettings(envConfig, newSettings);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
id='kosync_settings_window'
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title={_('KOReader Sync Settings')}
|
||||
boxClassName='sm:!min-w-[520px] sm:h-auto'
|
||||
>
|
||||
{isOpen && (
|
||||
<div className='mb-4 mt-0 flex flex-col gap-4 p-2 sm:p-4'>
|
||||
{isConfigured ? (
|
||||
<>
|
||||
<div className='text-center'>
|
||||
<p className='text-base-content/80 text-sm'>
|
||||
{_('Sync as {{userDisplayName}}', {
|
||||
userDisplayName: settings.kosync.username,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className='flex h-14 items-center justify-between'>
|
||||
<span className='text-base-content/80'>{_('Sync Server Connected')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
checked={settings.kosync.enabled}
|
||||
onChange={() => handleDisconnect()}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Sync Strategy')}</span>
|
||||
</label>
|
||||
<StyledSelect
|
||||
value={settings.kosync.strategy}
|
||||
onChange={handleStrategyChange}
|
||||
options={[
|
||||
{ value: 'prompt', label: _('Ask on conflict') },
|
||||
{ value: 'silent', label: _('Always use latest') },
|
||||
{ value: 'send', label: _('Send changes only') },
|
||||
{ value: 'receive', label: _('Receive changes only') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Checksum Method')}</span>
|
||||
</label>
|
||||
<StyledSelect
|
||||
value={settings.kosync.checksumMethod}
|
||||
onChange={handleChecksumMethodChange}
|
||||
options={[
|
||||
{ value: 'binary', label: _('File Content (recommended)') },
|
||||
{ value: 'filename', label: _('File Name'), disabled: true },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Device Name')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={osName ? `Readest (${osName})` : 'Readest'}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={deviceName}
|
||||
onChange={handleDeviceNameChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className='text-base-content/70 text-center text-sm'>
|
||||
{_('Connect to your KOReader Sync server.')}
|
||||
</p>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Server URL')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='https://koreader.sync.server'
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
spellCheck='false'
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<form className='flex flex-col gap-4'>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Username')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={_('Your Username')}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
spellCheck='false'
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoComplete='username'
|
||||
/>
|
||||
</div>
|
||||
<div className='form-control w-full'>
|
||||
<label className='label py-1'>
|
||||
<span className='label-text font-medium'>{_('Password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
placeholder={_('Your Password')}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoComplete='current-password'
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<button
|
||||
className='btn btn-primary mt-2 h-12 min-h-12 w-full'
|
||||
onClick={handleConnect}
|
||||
disabled={isConnecting || !url || !username || !password}
|
||||
>
|
||||
{isConnecting ? <span className='loading loading-spinner'></span> : _('Connect')}
|
||||
</button>
|
||||
{connectionStatus && (
|
||||
<div className='text-error h-4 text-center text-sm'>{connectionStatus}</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -42,8 +42,7 @@ const NotebookToggler: React.FC<NotebookTogglerProps> = ({ bookKey }) => {
|
||||
)
|
||||
}
|
||||
onClick={handleToggleSidebar}
|
||||
tooltip={_('Notebook')}
|
||||
tooltipDirection='bottom'
|
||||
label={_('Notebook')}
|
||||
></Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,11 +5,11 @@ import { PageInfo, TimeInfo } from '@/types/book';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { formatReadingProgress } from '@/utils/progress';
|
||||
|
||||
interface PageInfoProps {
|
||||
bookKey: string;
|
||||
bookFormat: string;
|
||||
section?: PageInfo;
|
||||
pageinfo?: PageInfo;
|
||||
timeinfo?: TimeInfo;
|
||||
@@ -20,7 +20,6 @@ interface PageInfoProps {
|
||||
|
||||
const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
bookKey,
|
||||
bookFormat,
|
||||
section,
|
||||
pageinfo,
|
||||
timeinfo,
|
||||
@@ -30,8 +29,10 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { getBookData } = useBookDataStore();
|
||||
const { getView, getViewSettings } = useReaderStore();
|
||||
const view = getView(bookKey);
|
||||
const bookData = getBookData(bookKey);
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
|
||||
const showDoubleBorder = viewSettings.vertical && viewSettings.doubleBorder;
|
||||
@@ -46,9 +47,10 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
: '{current} / {total}'
|
||||
: '{percent}%';
|
||||
|
||||
const progressInfo = ['PDF', 'CBZ'].includes(bookFormat)
|
||||
? formatReadingProgress(section?.current, section?.total, formatTemplate)
|
||||
: formatReadingProgress(pageinfo?.current, pageinfo?.total, formatTemplate);
|
||||
const pageProgress = bookData?.isFixedLayout ? section : pageinfo;
|
||||
const progressInfo = bookData?.isFixedLayout
|
||||
? formatReadingProgress(pageProgress?.current, pageProgress?.total, formatTemplate)
|
||||
: formatReadingProgress(pageProgress?.current, pageProgress?.total, formatTemplate);
|
||||
|
||||
const timeLeft = timeinfo
|
||||
? _('{{time}} min left in chapter', { time: Math.round(timeinfo.section) })
|
||||
@@ -62,9 +64,21 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
className={clsx(
|
||||
'progressinfo absolute bottom-0 flex items-center justify-between',
|
||||
'text-neutral-content font-sans text-xs font-extralight',
|
||||
isVertical ? 'writing-vertical-rl' : 'h-[52px] w-full',
|
||||
isVertical ? 'writing-vertical-rl' : 'w-full',
|
||||
isScrolled && !isVertical && 'bg-base-100',
|
||||
)}
|
||||
aria-label={[
|
||||
pageProgress
|
||||
? _('On {{current}} of {{total}} page', {
|
||||
current: pageProgress.current + 1,
|
||||
total: pageProgress.total,
|
||||
})
|
||||
: '',
|
||||
timeLeft,
|
||||
pageLeft,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(', ')}
|
||||
style={
|
||||
isVertical
|
||||
? {
|
||||
@@ -78,16 +92,24 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
: {
|
||||
paddingInlineStart: `calc(${horizontalGap / 2}% + ${contentInsets.left}px)`,
|
||||
paddingInlineEnd: `calc(${horizontalGap / 2}% + ${contentInsets.right}px)`,
|
||||
paddingBottom: appService?.hasSafeAreaInset ? `${gridInsets.bottom * 0.67}px` : 0,
|
||||
paddingBottom: appService?.hasSafeAreaInset ? `${gridInsets.bottom * 0.33}px` : 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
{viewSettings.showRemainingTime ? (
|
||||
<span className='text-start'>{timeLeft}</span>
|
||||
) : viewSettings.showRemainingPages ? (
|
||||
<span className='text-start'>{pageLeft}</span>
|
||||
) : null}
|
||||
{viewSettings.showProgressInfo && <span className='ms-auto text-end'>{progressInfo}</span>}
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className={clsx(
|
||||
'flex items-center justify-center',
|
||||
isVertical ? 'h-full' : 'h-[52px] w-full',
|
||||
)}
|
||||
>
|
||||
{viewSettings.showRemainingTime ? (
|
||||
<span className='text-start'>{timeLeft}</span>
|
||||
) : viewSettings.showRemainingPages ? (
|
||||
<span className='text-start'>{pageLeft}</span>
|
||||
) : null}
|
||||
{viewSettings.showProgressInfo && <span className='ms-auto text-end'>{progressInfo}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import clsx from 'clsx';
|
||||
import * as React from 'react';
|
||||
import { useEffect, Suspense, useRef, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
@@ -16,24 +17,48 @@ import { useDeviceControlStore } from '@/store/deviceStore';
|
||||
import { useScreenWakeLock } from '@/hooks/useScreenWakeLock';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { interceptWindowOpen } from '@/utils/open';
|
||||
import { mountAdditionalFonts } from '@/utils/font';
|
||||
import { mountAdditionalFonts } from '@/styles/fonts';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import { getSysFontsList, setSystemUIVisibility } from '@/utils/bridge';
|
||||
import { AboutWindow } from '@/components/AboutWindow';
|
||||
import { UpdaterWindow } from '@/components/UpdaterWindow';
|
||||
import { KOSyncSettingsWindow } from './KOSyncSettings';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { getLocale } from '@/utils/misc';
|
||||
import { initDayjs } from '@/utils/time';
|
||||
import ReaderContent from './ReaderContent';
|
||||
|
||||
/*
|
||||
Z-Index Layering Guide:
|
||||
---------------------------------
|
||||
99 – Window Border (Linux only)
|
||||
• Ensures the border stays on top of all UI elements.
|
||||
50 – Loading Progress / Toast Notifications / Dialogs
|
||||
• Includes Settings, About, Updater, and KOSync dialogs.
|
||||
45 – Sidebar / Notebook (Unpinned)
|
||||
• Floats above the content but below global dialogs.
|
||||
40 – TTS Bar
|
||||
• Mini controls for TTS playback on top of the TTS Control.
|
||||
30 – TTS Control
|
||||
• Persistent TTS icon/panel.
|
||||
20 – Menu / Sidebar / Notebook (Pinned)
|
||||
• Docked navigation or note views.
|
||||
10 – Headerbar / Footbar / Ribbon
|
||||
• Top toolbar, bottom footbar and ribbon elements.
|
||||
0 – Base Content
|
||||
• Main reading area or background content.
|
||||
*/
|
||||
|
||||
const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
|
||||
const router = useRouter();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { setLibrary } = useLibraryStore();
|
||||
const { hoveredBookKey } = useReaderStore();
|
||||
const { settings, setSettings } = useSettingsStore();
|
||||
const { isSideBarVisible, setSideBarVisible } = useSidebarStore();
|
||||
const { isNotebookVisible, setNotebookVisible } = useNotebookStore();
|
||||
const { isDarkMode, systemUIAlwaysHidden, showSystemUI, dismissSystemUI } = useThemeStore();
|
||||
const { isSideBarVisible, getIsSideBarVisible, setSideBarVisible } = useSidebarStore();
|
||||
const { isNotebookVisible, getIsNotebookVisible, setNotebookVisible } = useNotebookStore();
|
||||
const { isDarkMode, systemUIAlwaysHidden, isRoundedWindow } = useThemeStore();
|
||||
const { showSystemUI, dismissSystemUI } = useThemeStore();
|
||||
const { acquireBackKeyInterception, releaseBackKeyInterception } = useDeviceControlStore();
|
||||
const [libraryLoaded, setLibraryLoaded] = useState(false);
|
||||
const isInitiating = useRef(false);
|
||||
@@ -52,8 +77,14 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
|
||||
|
||||
const handleKeyDown = (event: CustomEvent) => {
|
||||
if (event.detail.keyName === 'Back') {
|
||||
setSideBarVisible(false);
|
||||
setNotebookVisible(false);
|
||||
if (getIsSideBarVisible()) {
|
||||
setSideBarVisible(false);
|
||||
} else if (getIsNotebookVisible()) {
|
||||
setNotebookVisible(false);
|
||||
} else {
|
||||
eventDispatcher.dispatch('close-reader');
|
||||
router.back();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -61,22 +92,23 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!appService?.isAndroidApp) return;
|
||||
if (isSideBarVisible || isNotebookVisible) {
|
||||
acquireBackKeyInterception();
|
||||
eventDispatcher.onSync('native-key-down', handleKeyDown);
|
||||
}
|
||||
if (!isSideBarVisible && !isNotebookVisible) {
|
||||
acquireBackKeyInterception();
|
||||
return () => {
|
||||
releaseBackKeyInterception();
|
||||
eventDispatcher.offSync('native-key-down', handleKeyDown);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appService?.isAndroidApp]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appService?.isAndroidApp) return;
|
||||
eventDispatcher.onSync('native-key-down', handleKeyDown);
|
||||
return () => {
|
||||
if (appService?.isAndroidApp) {
|
||||
releaseBackKeyInterception();
|
||||
eventDispatcher.offSync('native-key-down', handleKeyDown);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isSideBarVisible, isNotebookVisible]);
|
||||
}, [appService?.isAndroidApp, isSideBarVisible, isNotebookVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitiating.current) return;
|
||||
@@ -106,24 +138,24 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hoveredBookKey]);
|
||||
|
||||
return (
|
||||
libraryLoaded &&
|
||||
settings.globalReadSettings && (
|
||||
<div
|
||||
className={clsx(
|
||||
`reader-page text-base-content select-none overflow-hidden`,
|
||||
appService?.isLinuxApp && 'window-border',
|
||||
appService?.hasRoundedWindow && 'rounded-window',
|
||||
)}
|
||||
>
|
||||
<Suspense>
|
||||
<ReaderContent ids={ids} settings={settings} />
|
||||
<AboutWindow />
|
||||
<UpdaterWindow />
|
||||
<Toast />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
return libraryLoaded && settings.globalReadSettings ? (
|
||||
<div
|
||||
className={clsx(
|
||||
`reader-page bg-base-100 text-base-content select-none overflow-hidden`,
|
||||
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
|
||||
appService?.hasRoundedWindow && isRoundedWindow && 'window-border rounded-window',
|
||||
)}
|
||||
>
|
||||
<Suspense fallback={<div className='h-[100vh]'></div>}>
|
||||
<ReaderContent ids={ids} settings={settings} />
|
||||
<AboutWindow />
|
||||
<UpdaterWindow />
|
||||
<KOSyncSettingsWindow />
|
||||
<Toast />
|
||||
</Suspense>
|
||||
</div>
|
||||
) : (
|
||||
<div className={clsx('h-[100vh]', !appService?.isLinuxApp && 'bg-base-100')}></div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -67,10 +67,12 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
|
||||
if (index === 0) setSideBarBookKey(key);
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleShowBookDetails = (event: CustomEvent) => {
|
||||
const book = event.detail as Book;
|
||||
setShowDetailsBook(book);
|
||||
setShowDetailsBook(event.detail as Book);
|
||||
return true;
|
||||
};
|
||||
eventDispatcher.onSync('show-book-details', handleShowBookDetails);
|
||||
@@ -78,7 +80,6 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
|
||||
return () => {
|
||||
eventDispatcher.offSync('show-book-details', handleShowBookDetails);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -94,10 +95,12 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
|
||||
}
|
||||
window.addEventListener('beforeunload', handleCloseBooks);
|
||||
eventDispatcher.on('beforereload', handleCloseBooks);
|
||||
eventDispatcher.on('close-reader', handleCloseBooks);
|
||||
eventDispatcher.on('quit-app', handleCloseBooks);
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleCloseBooks);
|
||||
eventDispatcher.off('beforereload', handleCloseBooks);
|
||||
eventDispatcher.off('close-reader', handleCloseBooks);
|
||||
eventDispatcher.off('quit-app', handleCloseBooks);
|
||||
unlistenOnCloseWindow?.then((fn) => fn());
|
||||
};
|
||||
@@ -111,7 +114,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
|
||||
if (isPrimary && book && config) {
|
||||
const settings = useSettingsStore.getState().settings;
|
||||
eventDispatcher.dispatch('sync-book-progress', { bookKey });
|
||||
eventDispatcher.dispatch('flush-koreader-sync', { bookKey });
|
||||
eventDispatcher.dispatch('flush-kosync', { bookKey });
|
||||
await saveConfig(envConfig, bookKey, config, settings);
|
||||
}
|
||||
};
|
||||
@@ -142,7 +145,16 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
|
||||
|
||||
const handleCloseBooksToLibrary = () => {
|
||||
handleCloseBooks();
|
||||
navigateToLibrary(router);
|
||||
if (isTauriAppPlatform()) {
|
||||
const currentWindow = getCurrentWindow();
|
||||
if (currentWindow.label === 'main') {
|
||||
navigateToLibrary(router);
|
||||
} else {
|
||||
currentWindow.close();
|
||||
}
|
||||
} else {
|
||||
navigateToLibrary(router);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseBook = async (bookKey: string) => {
|
||||
@@ -170,7 +182,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
|
||||
const bookData = getBookData(bookKeys[0]!);
|
||||
const viewSettings = getViewSettings(bookKeys[0]!);
|
||||
if (!bookData || !bookData.book || !bookData.bookDoc || !viewSettings) {
|
||||
setTimeout(() => setLoading(true), 300);
|
||||
setTimeout(() => setLoading(true), 200);
|
||||
return (
|
||||
loading && (
|
||||
<div className={clsx('hero hero-content', appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh')}>
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
|
||||
interface RibbonProps {
|
||||
width: string;
|
||||
}
|
||||
|
||||
const Ribbon: React.FC<RibbonProps> = ({}) => {
|
||||
const { safeAreaInsets } = useThemeStore();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'ribbon absolute inset-0 z-10 flex w-8 justify-center sm:w-6',
|
||||
'h-[calc(env(safe-area-inset-top)+44px)]',
|
||||
)}
|
||||
className={clsx('ribbon absolute inset-0 z-10 flex w-8 justify-center sm:w-6')}
|
||||
style={{
|
||||
height: `${(safeAreaInsets?.top || 0) + 44}px`,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width='100%'
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Insets } from '@/types/misc';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
interface SectionInfoProps {
|
||||
bookKey: string;
|
||||
@@ -26,6 +27,7 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
contentInsets,
|
||||
gridInsets,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { hoveredBookKey } = useReaderStore();
|
||||
const { systemUIVisible, statusBarHeight } = useThemeStore();
|
||||
@@ -51,6 +53,8 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
isVertical ? 'writing-vertical-rl max-h-[85%]' : 'top-0 h-[44px]',
|
||||
isScrolled && !isVertical && 'bg-base-100',
|
||||
)}
|
||||
role='banner'
|
||||
aria-label={section ? _('Section Title') + `: ${section}` : ''}
|
||||
style={
|
||||
isVertical
|
||||
? {
|
||||
@@ -68,7 +72,8 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
}
|
||||
}
|
||||
>
|
||||
<h2
|
||||
<span
|
||||
aria-hidden='true'
|
||||
className={clsx(
|
||||
'text-neutral-content text-center font-sans text-xs font-light',
|
||||
isVertical ? '' : 'line-clamp-1',
|
||||
@@ -76,7 +81,7 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
)}
|
||||
>
|
||||
{section || ''}
|
||||
</h2>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -18,8 +18,7 @@ const SettingsToggler = () => {
|
||||
<Button
|
||||
icon={<RiFontSize className='text-base-content' />}
|
||||
onClick={handleToggleSettings}
|
||||
tooltip={_('Font & Layout')}
|
||||
tooltipDirection='bottom'
|
||||
label={_('Font & Layout')}
|
||||
></Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,8 +33,7 @@ const SidebarToggler: React.FC<SidebarTogglerProps> = ({ bookKey }) => {
|
||||
)
|
||||
}
|
||||
onClick={handleToggleSidebar}
|
||||
tooltip={_('Sidebar')}
|
||||
tooltipDirection='bottom'
|
||||
label={_('Toggle Sidebar')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -45,16 +45,16 @@ const TranslationToggler = ({ bookKey }: { bookKey: string }) => {
|
||||
icon={
|
||||
<RiTranslateAi className={translationEnabled ? 'text-blue-500' : 'text-base-content'} />
|
||||
}
|
||||
aria-label={_('Toggle Translation')}
|
||||
disabled={!translationAvailable}
|
||||
onClick={() => setTranslationEnabled(!translationEnabled)}
|
||||
tooltip={
|
||||
label={
|
||||
translationAvailable
|
||||
? translationEnabled
|
||||
? _('Disable Translation')
|
||||
: _('Enable Translation')
|
||||
: _('Translation Not Available')
|
||||
: _('Translation Disabled')
|
||||
}
|
||||
tooltipDirection='bottom'
|
||||
></Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,9 @@ import { BiMoon, BiSun } from 'react-icons/bi';
|
||||
import { TbSunMoon } from 'react-icons/tb';
|
||||
import { MdZoomOut, MdZoomIn, MdCheck } from 'react-icons/md';
|
||||
import { MdSync, MdSyncProblem } from 'react-icons/md';
|
||||
import { IoMdExpand } from 'react-icons/io';
|
||||
import { TbArrowAutofitWidth } from 'react-icons/tb';
|
||||
import { TbColumns1, TbColumns2 } from 'react-icons/tb';
|
||||
|
||||
import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL, ZOOM_STEP } from '@/services/constants';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
@@ -37,14 +40,19 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
const router = useRouter();
|
||||
const { user } = useAuth();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { getConfig } = useBookDataStore();
|
||||
const { getView, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const { getConfig, getBookData } = useBookDataStore();
|
||||
const { getView, getViewSettings, getViewState, setViewSettings } = useReaderStore();
|
||||
const config = getConfig(bookKey)!;
|
||||
const bookData = getBookData(bookKey)!;
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const viewState = getViewState(bookKey);
|
||||
|
||||
const { themeMode, isDarkMode, setThemeMode } = useThemeStore();
|
||||
const [isScrolledMode, setScrolledMode] = useState(viewSettings!.scrolled);
|
||||
const [zoomLevel, setZoomLevel] = useState(viewSettings!.zoomLevel!);
|
||||
const [zoomMode, setZoomMode] = useState(viewSettings!.zoomMode!);
|
||||
const [spreadMode, setSpreadMode] = useState(viewSettings!.spreadMode!);
|
||||
const [keepCoverSpread, setKeepCoverSpread] = useState(viewSettings!.keepCoverSpread!);
|
||||
const [invertImgColorInDark, setInvertImgColorInDark] = useState(
|
||||
viewSettings!.invertImgColorInDark,
|
||||
);
|
||||
@@ -93,6 +101,9 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'zoomLevel', zoomLevel, true, true);
|
||||
if (bookData.bookDoc?.rendition?.layout === 'pre-paginated') {
|
||||
getView(bookKey)?.renderer.setAttribute('scale-factor', zoomLevel);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [zoomLevel]);
|
||||
|
||||
@@ -102,11 +113,40 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [invertImgColorInDark]);
|
||||
|
||||
useEffect(() => {
|
||||
if (zoomMode === viewSettings.zoomMode) return;
|
||||
viewSettings.zoomMode = zoomMode;
|
||||
getView(bookKey)?.renderer.setAttribute('zoom', zoomMode);
|
||||
setViewSettings(bookKey, viewSettings);
|
||||
saveViewSettings(envConfig, bookKey, 'zoomMode', zoomMode, true, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [zoomMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (spreadMode === viewSettings.spreadMode) return;
|
||||
viewSettings.spreadMode = spreadMode;
|
||||
getView(bookKey)?.renderer.setAttribute('spread', spreadMode);
|
||||
setViewSettings(bookKey, viewSettings);
|
||||
saveViewSettings(envConfig, bookKey, 'spreadMode', spreadMode, true, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [spreadMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (keepCoverSpread === viewSettings.keepCoverSpread) return;
|
||||
if (!bookData?.bookDoc?.sections?.length) return;
|
||||
viewSettings.keepCoverSpread = keepCoverSpread;
|
||||
const coverSide = bookData.bookDoc.dir === 'rtl' ? 'right' : 'left';
|
||||
bookData.bookDoc.sections[0]!.pageSpread = keepCoverSpread ? '' : coverSide;
|
||||
getView(bookKey)?.renderer.setAttribute('spread', spreadMode);
|
||||
setViewSettings(bookKey, viewSettings);
|
||||
saveViewSettings(envConfig, bookKey, 'keepCoverSpread', keepCoverSpread, true, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [keepCoverSpread]);
|
||||
|
||||
const lastSyncTime = Math.max(config?.lastSyncedAtConfig || 0, config?.lastSyncedAtNotes || 0);
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
className={clsx(
|
||||
'view-menu dropdown-content dropdown-right no-triangle z-20 mt-1 border',
|
||||
'bgcolor-base-200 border-base-200 shadow-2xl',
|
||||
@@ -145,7 +185,57 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr className='border-base-300 my-1' />
|
||||
{bookData.bookDoc?.rendition?.layout === 'pre-paginated' && (
|
||||
<>
|
||||
<div className={clsx('my-2 flex items-center justify-between rounded-md')}>
|
||||
<button
|
||||
onClick={setSpreadMode.bind(null, 'none')}
|
||||
className={clsx(
|
||||
'hover:bg-base-300 text-base-content rounded-full p-2',
|
||||
spreadMode === 'none' && 'bg-base-300/75',
|
||||
)}
|
||||
>
|
||||
<TbColumns1 />
|
||||
</button>
|
||||
<button
|
||||
onClick={setSpreadMode.bind(null, 'auto')}
|
||||
className={clsx(
|
||||
'hover:bg-base-300 text-base-content rounded-full p-2',
|
||||
spreadMode === 'auto' && 'bg-base-300/75',
|
||||
)}
|
||||
>
|
||||
<TbColumns2 />
|
||||
</button>
|
||||
<div className='bg-base-300 mx-2 h-6 w-[1px]' />
|
||||
<button
|
||||
onClick={setZoomMode.bind(null, 'fit-page')}
|
||||
className={clsx(
|
||||
'hover:bg-base-300 text-base-content rounded-full p-2',
|
||||
zoomMode === 'fit-page' && 'bg-base-300/75',
|
||||
)}
|
||||
>
|
||||
<IoMdExpand />
|
||||
</button>
|
||||
<button
|
||||
onClick={setZoomMode.bind(null, 'fit-width')}
|
||||
className={clsx(
|
||||
'hover:bg-base-300 text-base-content rounded-full p-2',
|
||||
zoomMode === 'fit-width' && 'bg-base-300/75',
|
||||
)}
|
||||
>
|
||||
<TbArrowAutofitWidth />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<MenuItem
|
||||
label={_('Separate Cover Page')}
|
||||
Icon={keepCoverSpread ? MdCheck : undefined}
|
||||
onClick={() => setKeepCoverSpread(!keepCoverSpread)}
|
||||
disabled={spreadMode === 'none'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<hr aria-hidden='true' className='border-base-300 my-1' />
|
||||
|
||||
<MenuItem label={_('Font & Layout')} shortcut='Shift+F' onClick={openFontLayoutMenu} />
|
||||
|
||||
@@ -154,9 +244,10 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
shortcut='Shift+J'
|
||||
Icon={isScrolledMode ? MdCheck : undefined}
|
||||
onClick={toggleScrolledMode}
|
||||
disabled={bookData.bookDoc?.rendition?.layout === 'pre-paginated'}
|
||||
/>
|
||||
|
||||
<hr className='border-base-300 my-1' />
|
||||
<hr aria-hidden='true' className='border-base-300 my-1' />
|
||||
|
||||
<MenuItem
|
||||
label={
|
||||
@@ -169,10 +260,11 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
: _('Never synced')
|
||||
}
|
||||
Icon={user ? MdSync : MdSyncProblem}
|
||||
iconClassName={user && viewState?.syncing ? 'animate-reverse-spin' : ''}
|
||||
onClick={handleSync}
|
||||
/>
|
||||
|
||||
<hr className='border-base-300 my-1' />
|
||||
<hr aria-hidden='true' className='border-base-300 my-1' />
|
||||
|
||||
{appService?.hasWindow && <MenuItem label={_('Fullscreen')} onClick={handleFullScreen} />}
|
||||
<MenuItem
|
||||
|
||||
@@ -129,7 +129,7 @@ const TranslatorPopup: React.FC<TranslatorPopupProps> = ({
|
||||
|
||||
fetchTranslation();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [text, token, sourceLang, targetLang, provider]);
|
||||
}, [text, token, sourceLang, targetLang, provider, translate]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -176,9 +176,12 @@ const TranslatorPopup: React.FC<TranslatorPopupProps> = ({
|
||||
className='bg-gray-600 text-white/75'
|
||||
value={targetLang}
|
||||
onChange={handleTargetLangChange}
|
||||
options={Object.entries(translatorLangs)
|
||||
.sort((a, b) => a[1].localeCompare(b[1]))
|
||||
.map(([code, name]) => ({ value: code, label: name }))}
|
||||
options={[
|
||||
{ value: '', label: _('System Language') },
|
||||
...Object.entries(translatorLangs)
|
||||
.sort((a, b) => a[1].localeCompare(b[1]))
|
||||
.map(([code, name]) => ({ value: code, label: name })),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{loading ? (
|
||||
|
||||
@@ -25,6 +25,7 @@ const NotebookHeader: React.FC<{
|
||||
</div>
|
||||
<div className='flex w-full items-center gap-x-4'>
|
||||
<button
|
||||
title={isPinned ? _('Unpin Notebook') : _('Pin Notebook')}
|
||||
onClick={handleTogglePin}
|
||||
className={clsx(
|
||||
'btn btn-ghost btn-circle hidden h-6 min-h-6 w-6 sm:flex',
|
||||
@@ -34,6 +35,7 @@ const NotebookHeader: React.FC<{
|
||||
{isPinned ? <MdPushPin size={iconSize14} /> : <MdOutlinePushPin size={iconSize14} />}
|
||||
</button>
|
||||
<button
|
||||
title={_('Close')}
|
||||
onClick={handleClose}
|
||||
className={'btn btn-ghost btn-circle flex h-6 min-h-6 w-6 hover:bg-transparent sm:hidden'}
|
||||
>
|
||||
@@ -42,6 +44,7 @@ const NotebookHeader: React.FC<{
|
||||
</div>
|
||||
<div className='flex items-center justify-end gap-x-4'>
|
||||
<button
|
||||
title={isSearchBarVisible ? _('Hide Search Bar') : _('Show Search Bar')}
|
||||
onClick={handleToggleSearchBar}
|
||||
className={clsx('btn btn-ghost h-8 min-h-8 w-8 p-0', isSearchBarVisible && 'bg-base-300')}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useNotebookStore } from '@/store/notebookStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
@@ -7,6 +6,8 @@ import { TextSelection } from '@/utils/sel';
|
||||
import { md5Fingerprint } from '@/utils/md5';
|
||||
import { BookNote } from '@/types/book';
|
||||
import useShortcuts from '@/hooks/useShortcuts';
|
||||
import TextEditor, { TextEditorRef } from '@/components/TextEditor';
|
||||
import TextButton from '@/components/TextButton';
|
||||
|
||||
interface NoteEditorProps {
|
||||
onSave: (selection: TextSelection, note: string) => void;
|
||||
@@ -23,110 +24,95 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
|
||||
saveNotebookAnnotationDraft,
|
||||
getNotebookAnnotationDraft,
|
||||
} = useNotebookStore();
|
||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [note, setNote] = React.useState('');
|
||||
|
||||
const editorRef = useRef<TextEditorRef>(null);
|
||||
const [note, setNote] = useState('');
|
||||
const separatorWidth = useResponsiveSize(3);
|
||||
|
||||
useEffect(() => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.focus();
|
||||
}
|
||||
}, [editorRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (notebookEditAnnotation) {
|
||||
setNote(notebookEditAnnotation.note);
|
||||
if (editorRef.current) {
|
||||
editorRef.current.value = notebookEditAnnotation.note;
|
||||
editorRef.current.focus();
|
||||
adjustHeight();
|
||||
}
|
||||
const noteText = notebookEditAnnotation.note;
|
||||
setNote(noteText);
|
||||
editorRef.current?.setValue(noteText);
|
||||
editorRef.current?.focus();
|
||||
} else if (notebookNewAnnotation) {
|
||||
const noteText = getAnnotationText();
|
||||
if (noteText) {
|
||||
const draftNote = getNotebookAnnotationDraft(md5Fingerprint(noteText)) || '';
|
||||
setNote(draftNote);
|
||||
if (editorRef.current) {
|
||||
editorRef.current.value = draftNote;
|
||||
editorRef.current.focus();
|
||||
adjustHeight();
|
||||
}
|
||||
editorRef.current?.setValue(draftNote);
|
||||
editorRef.current?.focus();
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [notebookNewAnnotation, notebookEditAnnotation]);
|
||||
|
||||
const adjustHeight = () => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.style.height = 'auto';
|
||||
editorRef.current.style.height = `${editorRef.current.scrollHeight}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const getAnnotationText = () => {
|
||||
return notebookEditAnnotation?.text || notebookNewAnnotation?.text || '';
|
||||
};
|
||||
|
||||
const handleOnChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
adjustHeight();
|
||||
setNote(e.currentTarget.value);
|
||||
const handleNoteChange = (value: string) => {
|
||||
setNote(value);
|
||||
};
|
||||
|
||||
const handleOnBlur = () => {
|
||||
if (editorRef.current && editorRef.current.value) {
|
||||
const handleBlur = () => {
|
||||
const currentValue = editorRef.current?.getValue();
|
||||
if (currentValue) {
|
||||
const noteText = getAnnotationText();
|
||||
if (noteText) {
|
||||
saveNotebookAnnotationDraft(md5Fingerprint(noteText), editorRef.current.value);
|
||||
saveNotebookAnnotationDraft(md5Fingerprint(noteText), currentValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveNote = () => {
|
||||
if (editorRef.current && notebookNewAnnotation) {
|
||||
onSave(notebookNewAnnotation, editorRef.current.value);
|
||||
} else if (editorRef.current && notebookEditAnnotation) {
|
||||
notebookEditAnnotation.note = editorRef.current.value;
|
||||
onEdit(notebookEditAnnotation);
|
||||
const currentValue = editorRef.current?.getValue();
|
||||
if (currentValue) {
|
||||
if (notebookNewAnnotation) {
|
||||
onSave(notebookNewAnnotation, currentValue);
|
||||
} else if (notebookEditAnnotation) {
|
||||
notebookEditAnnotation.note = currentValue;
|
||||
onEdit(notebookEditAnnotation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEscape = () => {
|
||||
if (notebookNewAnnotation) {
|
||||
setNotebookNewAnnotation(null);
|
||||
}
|
||||
if (notebookEditAnnotation) {
|
||||
setNotebookEditAnnotation(null);
|
||||
}
|
||||
};
|
||||
|
||||
useShortcuts({
|
||||
onSaveNote: () => {
|
||||
if (editorRef.current && editorRef.current.value) {
|
||||
const currentValue = editorRef.current?.getValue();
|
||||
if (currentValue) {
|
||||
handleSaveNote();
|
||||
}
|
||||
},
|
||||
onEscape: () => {
|
||||
if (notebookNewAnnotation) {
|
||||
setNotebookNewAnnotation(null);
|
||||
}
|
||||
if (notebookEditAnnotation) {
|
||||
setNotebookEditAnnotation(null);
|
||||
}
|
||||
},
|
||||
onEscape: handleEscape,
|
||||
});
|
||||
|
||||
const canSave = Boolean(note.trim());
|
||||
|
||||
return (
|
||||
<div className='content note-editor-container bg-base-100 mt-2 rounded-md p-2'>
|
||||
<div className='flex w-full justify-between space-x-2'>
|
||||
<div className='relative w-full'>
|
||||
<textarea
|
||||
className={clsx(
|
||||
'note-editor textarea textarea-ghost min-h-[1em] resize-none !outline-none',
|
||||
'inset-0 w-full rounded-none border-0 bg-transparent p-0',
|
||||
'content font-size-sm',
|
||||
)}
|
||||
dir='auto'
|
||||
ref={editorRef}
|
||||
value={note}
|
||||
rows={1}
|
||||
spellCheck={false}
|
||||
onChange={handleOnChange}
|
||||
onBlur={handleOnBlur}
|
||||
placeholder={_('Add your notes here...')}
|
||||
></textarea>
|
||||
</div>
|
||||
<div className='flex w-full'>
|
||||
<TextEditor
|
||||
ref={editorRef}
|
||||
value={note}
|
||||
onChange={handleNoteChange}
|
||||
onBlur={handleBlur}
|
||||
onSave={handleSaveNote}
|
||||
onEscape={handleEscape}
|
||||
placeholder={_('Add your notes here...')}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center pt-2'>
|
||||
<div
|
||||
className='me-2 mt-0.5 min-h-full self-stretch rounded-xl bg-gray-300'
|
||||
@@ -138,17 +124,12 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
|
||||
<span className='content font-size-xs text-gray-500'>{getAnnotationText()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-end p-2' dir='ltr'>
|
||||
<button
|
||||
className={clsx(
|
||||
'content btn btn-ghost font-size-sm hover:bg-transparent',
|
||||
'flex h-[1.3em] min-h-[1.3em] items-end p-0',
|
||||
editorRef.current && editorRef.current.value ? '' : 'btn-disabled !bg-opacity-0',
|
||||
)}
|
||||
onClick={handleSaveNote}
|
||||
>
|
||||
<div className='font-size-sm pr-1 align-bottom text-blue-500'>{_('Save')}</div>
|
||||
</button>
|
||||
|
||||
<div className='flex justify-end space-x-3 p-2' dir='ltr'>
|
||||
<TextButton onClick={handleEscape}>{_('Cancel')}</TextButton>
|
||||
<TextButton onClick={handleSaveNote} disabled={!canSave}>
|
||||
{_('Save')}
|
||||
</TextButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,12 +9,13 @@ import { useNotebookStore } from '@/store/notebookStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useDrag } from '@/hooks/useDrag';
|
||||
import { DragKey, useDrag } from '@/hooks/useDrag';
|
||||
import { TextSelection } from '@/utils/sel';
|
||||
import { BookNote } from '@/types/book';
|
||||
import { uniqueId } from '@/utils/misc';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { getBookDirFromLanguage } from '@/utils/book';
|
||||
import { Overlay } from '@/components/Overlay';
|
||||
import BooknoteItem from '../sidebar/BooknoteItem';
|
||||
import NotebookHeader from './Header';
|
||||
import NoteEditor from './NoteEditor';
|
||||
@@ -25,7 +26,7 @@ const MAX_NOTEBOOK_WIDTH = 0.45;
|
||||
|
||||
const Notebook: React.FC = ({}) => {
|
||||
const _ = useTranslation();
|
||||
const { updateAppTheme } = useThemeStore();
|
||||
const { updateAppTheme, safeAreaInsets } = useThemeStore();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
const { sideBarBookKey } = useSidebarStore();
|
||||
@@ -33,7 +34,8 @@ const Notebook: React.FC = ({}) => {
|
||||
const { notebookNewAnnotation, notebookEditAnnotation, setNotebookPin } = useNotebookStore();
|
||||
const { getBookData, getConfig, saveConfig, updateBooknotes } = useBookDataStore();
|
||||
const { getView, getViewSettings } = useReaderStore();
|
||||
const { setNotebookWidth, setNotebookVisible, toggleNotebookPin } = useNotebookStore();
|
||||
const { getNotebookWidth, setNotebookWidth, setNotebookVisible, toggleNotebookPin } =
|
||||
useNotebookStore();
|
||||
const { setNotebookNewAnnotation, setNotebookEditAnnotation } = useNotebookStore();
|
||||
|
||||
const [isSearchBarVisible, setIsSearchBarVisible] = useState(false);
|
||||
@@ -79,9 +81,7 @@ const Notebook: React.FC = ({}) => {
|
||||
settings.globalReadSettings.isNotebookPinned = !isNotebookPinned;
|
||||
};
|
||||
|
||||
const handleClickOverlay = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const handleClickOverlay = () => {
|
||||
setNotebookVisible(false);
|
||||
setNotebookNewAnnotation(null);
|
||||
setNotebookEditAnnotation(null);
|
||||
@@ -138,7 +138,19 @@ const Notebook: React.FC = ({}) => {
|
||||
handleNotebookResize(`${Math.round(newWidth * 10000) / 100}%`);
|
||||
};
|
||||
|
||||
const { handleDragStart } = useDrag(onDragMove);
|
||||
const onDragKeyDown = (data: { key: DragKey; step: number }) => {
|
||||
const currentWidth = parseFloat(getNotebookWidth()) / 100;
|
||||
let newWidth = currentWidth;
|
||||
|
||||
if (data.key === 'ArrowLeft') {
|
||||
newWidth = Math.max(MIN_NOTEBOOK_WIDTH, currentWidth + data.step);
|
||||
} else if (data.key === 'ArrowRight') {
|
||||
newWidth = Math.min(MAX_NOTEBOOK_WIDTH, currentWidth - data.step);
|
||||
}
|
||||
handleNotebookResize(`${Math.round(newWidth * 10000) / 100}%`);
|
||||
};
|
||||
|
||||
const { handleDragStart, handleDragKeyDown } = useDrag(onDragMove, onDragKeyDown);
|
||||
|
||||
const config = getConfig(sideBarBookKey);
|
||||
const { booknotes: allNotes = [] } = config || {};
|
||||
@@ -189,22 +201,24 @@ const Notebook: React.FC = ({}) => {
|
||||
return isNotebookVisible ? (
|
||||
<>
|
||||
{!isNotebookPinned && (
|
||||
<div className='overlay fixed inset-0 z-10 bg-black/20' onClick={handleClickOverlay} />
|
||||
<Overlay className='z-[45] bg-black/20' onDismiss={handleClickOverlay} />
|
||||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
'notebook-container bg-base-200 right-0 z-20 flex min-w-60 select-none flex-col',
|
||||
'notebook-container bg-base-200 right-0 flex min-w-60 select-none flex-col',
|
||||
'font-sans text-base font-normal sm:text-sm',
|
||||
appService?.isIOSApp ? 'h-[100vh]' : 'h-full',
|
||||
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
|
||||
appService?.hasRoundedWindow && 'rounded-window-top-right rounded-window-bottom-right',
|
||||
!isNotebookPinned && 'shadow-2xl',
|
||||
isNotebookPinned ? 'z-20' : 'z-[45] shadow-2xl',
|
||||
)}
|
||||
role='group'
|
||||
aria-label={_('Notebook')}
|
||||
dir={viewSettings?.rtl && languageDir === 'rtl' ? 'rtl' : 'ltr'}
|
||||
style={{
|
||||
width: `${notebookWidth}`,
|
||||
maxWidth: `${MAX_NOTEBOOK_WIDTH * 100}%`,
|
||||
position: isNotebookPinned ? 'relative' : 'absolute',
|
||||
paddingTop: `${safeAreaInsets?.top || 0}px`,
|
||||
}}
|
||||
>
|
||||
<style jsx>{`
|
||||
@@ -216,9 +230,15 @@ const Notebook: React.FC = ({}) => {
|
||||
}
|
||||
`}</style>
|
||||
<div
|
||||
className='drag-bar absolute left-0 top-0 -m-2 h-full w-0.5 cursor-col-resize bg-transparent p-2'
|
||||
className='drag-bar absolute -left-2 top-0 h-full w-0.5 cursor-col-resize bg-transparent p-2'
|
||||
role='slider'
|
||||
tabIndex={0}
|
||||
aria-label={_('Resize Notebook')}
|
||||
aria-orientation='horizontal'
|
||||
aria-valuenow={parseFloat(notebookWidth)}
|
||||
onMouseDown={handleDragStart}
|
||||
onTouchStart={handleDragStart}
|
||||
onKeyDown={handleDragKeyDown}
|
||||
/>
|
||||
<div className='flex-shrink-0'>
|
||||
<NotebookHeader
|
||||
@@ -263,7 +283,13 @@ const Notebook: React.FC = ({}) => {
|
||||
{filteredExcerptNotes.map((item, index) => (
|
||||
<li key={`${index}-${item.id}`} className='my-2'>
|
||||
<div
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Backspace' || e.key === 'Delete') {
|
||||
handleEditNote(item, true);
|
||||
}
|
||||
}}
|
||||
className='collapse-arrow border-base-300 bg-base-100 collapse border'
|
||||
>
|
||||
<div
|
||||
@@ -283,9 +309,11 @@ const Notebook: React.FC = ({}) => {
|
||||
<div className='collapse-content font-size-xs select-text px-3 pb-0'>
|
||||
<p className='hyphens-auto text-justify'>{item.text}</p>
|
||||
<div className='flex justify-end' dir='ltr'>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions*/}
|
||||
<div
|
||||
className='font-size-xs cursor-pointer align-bottom text-red-500 hover:text-red-600'
|
||||
onClick={handleEditNote.bind(null, item, true)}
|
||||
aria-label={_('Delete')}
|
||||
>
|
||||
{_('Delete')}
|
||||
</div>
|
||||
|
||||
@@ -33,8 +33,8 @@ const ColorInput: React.FC<ColorInputProps> = ({ label, value, onChange }) => {
|
||||
<div className='mb-3'>
|
||||
<label className='mb-1 block text-sm font-medium'>{label}</label>
|
||||
<div className='flex items-center'>
|
||||
<div
|
||||
className='border-base-200 relative mr-2 flex h-7 w-8 cursor-pointer items-center justify-center overflow-hidden rounded border'
|
||||
<button
|
||||
className='border-base-200/75 relative mr-2 flex h-7 w-8 cursor-pointer items-center justify-center overflow-hidden rounded border'
|
||||
style={{ backgroundColor: value }}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
/>
|
||||
@@ -44,7 +44,7 @@ const ColorInput: React.FC<ColorInputProps> = ({ label, value, onChange }) => {
|
||||
value={value}
|
||||
spellCheck={false}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className='bg-base-100 text-base-content border-base-200 min-w-4 max-w-36 flex-1 rounded border p-1 font-mono text-sm'
|
||||
className='bg-base-100 text-base-content border-base-200/75 min-w-4 max-w-36 flex-1 rounded border p-1 font-mono text-sm'
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -148,30 +148,27 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
<div className='flex items-center justify-between'>
|
||||
<h2 className='font-medium'>{_('Theme Mode')}</h2>
|
||||
<div className='flex gap-4'>
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Auto Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('auto')}
|
||||
>
|
||||
<TbSunMoon />
|
||||
</button>
|
||||
</div>
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Light Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('light')}
|
||||
>
|
||||
<MdOutlineLightMode />
|
||||
</button>
|
||||
</div>
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Dark Mode')}>
|
||||
<button
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('dark')}
|
||||
>
|
||||
<MdOutlineDarkMode />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
title={_('Auto Mode')}
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('auto')}
|
||||
>
|
||||
<TbSunMoon />
|
||||
</button>
|
||||
<button
|
||||
title={_('Light Mode')}
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('light')}
|
||||
>
|
||||
<MdOutlineLightMode />
|
||||
</button>
|
||||
<button
|
||||
title={_('Dark Mode')}
|
||||
className={`btn btn-ghost btn-circle btn-sm ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
|
||||
onClick={() => setThemeMode('dark')}
|
||||
>
|
||||
<MdOutlineDarkMode />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -187,7 +184,7 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
</div>
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<h2 className=''>{_('Override Book Color')}</h2>
|
||||
<h2 className='font-medium'>{_('Override Book Color')}</h2>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
@@ -200,8 +197,16 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
<h2 className='mb-2 font-medium'>{_('Theme Color')}</h2>
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
{themes.concat(customThemes).map(({ name, label, colors, isCustomizale }) => (
|
||||
<label
|
||||
<button
|
||||
key={name}
|
||||
tabIndex={0}
|
||||
onClick={() => setThemeColor(name)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
setThemeColor(name);
|
||||
}
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={`relative flex cursor-pointer flex-col items-center justify-center rounded-lg p-4 shadow-md ${
|
||||
themeColor === name ? 'ring-2 ring-indigo-500 ring-offset-2' : ''
|
||||
}`}
|
||||
@@ -213,6 +218,7 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
}}
|
||||
>
|
||||
<input
|
||||
aria-label={_(label)}
|
||||
type='radio'
|
||||
name='theme'
|
||||
value={name}
|
||||
@@ -231,15 +237,15 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
<CgColorPicker size={iconSize16} className='absolute right-2 top-2' />
|
||||
</button>
|
||||
)}
|
||||
</label>
|
||||
</button>
|
||||
))}
|
||||
<label
|
||||
<button
|
||||
className={`relative flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed p-4 shadow-md`}
|
||||
onClick={() => setShowCustomThemeEditor(true)}
|
||||
>
|
||||
<PiPlus size={iconSize24} />
|
||||
<span>{_('Custom')}</span>
|
||||
</label>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -264,7 +270,7 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
onChange={(event) => setCodeLanguage(event.target.value as CodeLanguage)}
|
||||
options={CODE_LANGUAGES.map((lang) => ({
|
||||
value: lang,
|
||||
label: lang,
|
||||
label: lang === 'auto-detect' ? _('Auto') : lang,
|
||||
}))}
|
||||
disabled={!codeHighlighting}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import { MdAdd, MdDelete } from 'react-icons/md';
|
||||
import { IoMdCloseCircleOutline } from 'react-icons/io';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useCustomFontStore } from '@/store/customFontStore';
|
||||
import { useFileSelector } from '@/hooks/useFileSelector';
|
||||
import { CustomFont, mountCustomFont } from '@/styles/fonts';
|
||||
import { parseFontInfo } from '@/utils/font';
|
||||
import { getFilename } from '@/utils/path';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
|
||||
interface CustomFontsProps {
|
||||
bookKey: string;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
type FontFamily = {
|
||||
name: string;
|
||||
fonts: CustomFont[];
|
||||
};
|
||||
|
||||
const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService, envConfig } = useEnv();
|
||||
const {
|
||||
fonts: customFonts,
|
||||
addFont,
|
||||
loadFont,
|
||||
removeFont,
|
||||
getAvailableFonts,
|
||||
saveCustomFonts,
|
||||
} = useCustomFontStore();
|
||||
const { getViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const [isDeleteMode, setIsDeleteMode] = useState(false);
|
||||
|
||||
const { selectFiles } = useFileSelector(appService, _);
|
||||
|
||||
const currentDefaultFont =
|
||||
viewSettings.defaultFont.toLowerCase() === 'serif' ? 'serif' : 'sans-serif';
|
||||
|
||||
const currentFontFamily =
|
||||
currentDefaultFont === 'serif' ? viewSettings.serifFont : viewSettings.sansSerifFont;
|
||||
|
||||
const handleImportFont = () => {
|
||||
selectFiles({ type: 'fonts', multiple: true }).then(async (result) => {
|
||||
if (result.error || result.files.length === 0) return;
|
||||
if (!(await appService!.fs.exists('', 'Fonts'))) {
|
||||
await appService!.fs.createDir('', 'Fonts');
|
||||
}
|
||||
for (const selectedFile of result.files) {
|
||||
let fontPath: string;
|
||||
let fontFile: File;
|
||||
if (selectedFile.path) {
|
||||
const filePath = selectedFile.path;
|
||||
const fileobj = await appService!.fs.openFile(filePath, 'None');
|
||||
fontPath = fileobj.name || getFilename(filePath);
|
||||
await appService!.fs.copyFile(filePath, fontPath, 'Fonts');
|
||||
fontFile = await appService!.fs.openFile(fontPath, 'Fonts');
|
||||
} else if (selectedFile.file) {
|
||||
const file = selectedFile.file;
|
||||
fontPath = getFilename(file.name);
|
||||
await appService!.fs.writeFile(fontPath, 'Fonts', file);
|
||||
fontFile = file;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
const fontInfo = parseFontInfo(await fontFile.arrayBuffer(), fontPath);
|
||||
const customFont = addFont(fontPath, {
|
||||
name: fontInfo.name,
|
||||
family: fontInfo.family,
|
||||
style: fontInfo.style,
|
||||
weight: fontInfo.weight,
|
||||
variable: fontInfo.variable,
|
||||
});
|
||||
console.log('Added custom font:', customFont);
|
||||
if (customFont && !customFont.error) {
|
||||
const loadedFont = await loadFont(envConfig, customFont.id);
|
||||
mountCustomFont(document, loadedFont);
|
||||
}
|
||||
}
|
||||
saveCustomFonts(envConfig);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteFamily = (family: FontFamily) => {
|
||||
for (const font of family.fonts) {
|
||||
if (font) {
|
||||
if (removeFont(font.id)) {
|
||||
appService!.fs.removeFile(font.path, 'Fonts');
|
||||
saveCustomFonts(envConfig);
|
||||
if (getAvailableFonts().length === 0) {
|
||||
setIsDeleteMode(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectFamily = (family: FontFamily) => {
|
||||
if (currentDefaultFont === 'serif') {
|
||||
saveViewSettings(envConfig, bookKey, 'serifFont', family.name);
|
||||
} else {
|
||||
saveViewSettings(envConfig, bookKey, 'sansSerifFont', family.name);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDeleteMode = () => {
|
||||
setIsDeleteMode(!isDeleteMode);
|
||||
};
|
||||
|
||||
const getAvailableFamilies = (fonts: CustomFont[]): FontFamily[] => {
|
||||
const familyMap = new Map<string, string[]>();
|
||||
|
||||
for (const font of fonts) {
|
||||
const family = font.family || font.name;
|
||||
if (!familyMap.has(family)) {
|
||||
familyMap.set(family, []);
|
||||
}
|
||||
familyMap.get(family)!.push(font.id);
|
||||
}
|
||||
|
||||
return Array.from(familyMap.entries()).map(([family, ids]) => ({
|
||||
name: family,
|
||||
fonts: ids.map((id) => fonts.find((f) => f.id === id)!).filter((f): f is CustomFont => !!f),
|
||||
}));
|
||||
};
|
||||
|
||||
const availableFonts = customFonts
|
||||
.filter((font) => !font.deletedAt)
|
||||
.sort((a, b) => (b.downloadedAt || 0) - (a.downloadedAt || 0));
|
||||
|
||||
const availableFamilies = getAvailableFamilies(availableFonts);
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<div className='mb-6 flex h-8 items-center justify-between'>
|
||||
<div className='breadcrumbs py-1'>
|
||||
<ul>
|
||||
<li>
|
||||
<button className='font-semibold' onClick={onBack}>
|
||||
{_('Font')}
|
||||
</button>
|
||||
</li>
|
||||
<li className='font-medium'>{_('Custom Fonts')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
{availableFonts.length > 0 && (
|
||||
<button
|
||||
onClick={toggleDeleteMode}
|
||||
className={`btn btn-ghost btn-sm text-base-content gap-2`}
|
||||
title={isDeleteMode ? _('Cancel Delete') : _('Delete Font')}
|
||||
>
|
||||
{isDeleteMode ? (
|
||||
<>{_('Cancel')}</>
|
||||
) : (
|
||||
<>
|
||||
<MdDelete className='h-4 w-4' />
|
||||
{_('Delete')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<div className='card border-primary/50 hover:border-primary/75 group h-12 border-2 transition-colors'>
|
||||
<button
|
||||
className='card-body flex cursor-pointer items-center justify-center p-2 text-center'
|
||||
onClick={handleImportFont}
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='flex items-center justify-center'>
|
||||
<MdAdd className='text-primary/85 group-hover:text-primary h-6 w-6' />
|
||||
</div>
|
||||
<div className='text-primary/85 group-hover:text-primary line-clamp-1 font-medium'>
|
||||
{_('Import Font')}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{availableFamilies.map((family) => (
|
||||
<div
|
||||
role='none'
|
||||
key={family.name}
|
||||
className={clsx(
|
||||
'card h-12 border shadow-sm',
|
||||
currentFontFamily === family.name
|
||||
? 'border-primary/50 bg-primary/50'
|
||||
: `border-base-200 bg-base-200 ${isDeleteMode ? '' : 'cursor-pointer'}`,
|
||||
)}
|
||||
onClick={!isDeleteMode ? () => handleSelectFamily(family) : undefined}
|
||||
title={family.fonts.map((f) => f.name).join('\n')}
|
||||
>
|
||||
<div className='card-body flex items-center justify-center p-2'>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: `"${family.name}", sans-serif`,
|
||||
fontWeight: 400,
|
||||
}}
|
||||
className='text-base-content line-clamp-1 break-all'
|
||||
>
|
||||
{family.name}
|
||||
</div>
|
||||
{isDeleteMode && (
|
||||
<button
|
||||
onClick={() => handleDeleteFamily(family)}
|
||||
className='btn btn-ghost btn-xs absolute right-[-10px] top-[-10px] h-6 min-h-0 w-6 p-0 hover:bg-transparent'
|
||||
title={_('Delete Font')}
|
||||
>
|
||||
<IoMdCloseCircleOutline className='text-base-content/75 h-6 w-6' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='bg-base-200/30 my-8 rounded-lg p-4'>
|
||||
<div className='text-base-content/70 text-sm sm:text-xs'>
|
||||
<div className='mb-1 indent-2 font-medium'>{_('Tips')}:</div>
|
||||
<ul className='list-outside list-disc space-y-1 ps-2'>
|
||||
<li>{_('Supported font formats: .ttf, .otf, .woff, .woff2')}</li>
|
||||
<li>{_('Custom fonts can be selected from the Font Face menu')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomFonts;
|
||||
@@ -14,10 +14,16 @@ interface DialogMenuProps {
|
||||
resetLabel?: string;
|
||||
}
|
||||
|
||||
const DialogMenu: React.FC<DialogMenuProps> = ({ setIsDropdownOpen, onReset, resetLabel }) => {
|
||||
const DialogMenu: React.FC<DialogMenuProps> = ({
|
||||
activePanel,
|
||||
setIsDropdownOpen,
|
||||
onReset,
|
||||
resetLabel,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const iconSize = useResponsiveSize(16);
|
||||
const { isFontLayoutSettingsGlobal, setFontLayoutSettingsGlobal } = useSettingsStore();
|
||||
const { setFontPanelView, isFontLayoutSettingsGlobal, setFontLayoutSettingsGlobal } =
|
||||
useSettingsStore();
|
||||
|
||||
const handleToggleGlobal = () => {
|
||||
setFontLayoutSettingsGlobal(!isFontLayoutSettingsGlobal);
|
||||
@@ -29,9 +35,13 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ setIsDropdownOpen, onReset, res
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
const handleManageCustomFont = () => {
|
||||
setFontPanelView('custom-fonts');
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
className={clsx(
|
||||
'dropdown-content dropdown-right no-triangle border-base-200 z-20 mt-1 border shadow-2xl',
|
||||
'text-base sm:text-sm',
|
||||
@@ -49,6 +59,9 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ setIsDropdownOpen, onReset, res
|
||||
onClick={handleToggleGlobal}
|
||||
/>
|
||||
<MenuItem label={resetLabel || _('Reset Settings')} onClick={handleResetToDefaults} />
|
||||
{activePanel === 'Font' && (
|
||||
<MenuItem label={_('Manage Custom Fonts')} onClick={handleManageCustomFont} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user