forked from akai/readest
Compare commits
181 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9adf789d7 | |||
| efb8f70d01 | |||
| e235cb98d2 | |||
| 4275508ccd | |||
| a424ae8b15 | |||
| 9303ec8c5f | |||
| 98faed320a | |||
| 5bbdc70420 | |||
| de21182240 | |||
| 4ae35992a1 | |||
| b91d7b7a61 | |||
| 7c0d9bf375 | |||
| ea8ab91ed3 | |||
| efd415ece2 | |||
| ec86d68b80 | |||
| 172ab382bc | |||
| ad551a04a1 | |||
| 4dc943d23e | |||
| ee93885c04 | |||
| 14c032aaff | |||
| 2f619ca745 | |||
| 8979827cf5 | |||
| ae16eeffa7 | |||
| 8e96ef3e5d | |||
| 38e24da968 | |||
| 7371efe1d1 | |||
| 3cb37bca5d | |||
| b9fa204eda | |||
| cf66665096 | |||
| 759779a98d | |||
| 68409db8a0 | |||
| 46f0e8e7f6 | |||
| 6260168caa | |||
| a8430a7be1 | |||
| 7bfaa38746 | |||
| c5b2feda48 | |||
| 59f875f590 | |||
| 8efad90932 | |||
| 5c23642ac0 | |||
| ee6500cecb | |||
| f004d7b3ea | |||
| f7d53508a6 | |||
| 53e7f2526c | |||
| 8f0e2d1642 | |||
| c807f0ccc8 | |||
| 6ba15319a8 | |||
| 3ea3a50baa | |||
| ccd467ebcf | |||
| fe25c3e995 | |||
| d4af2d3ef5 | |||
| 6ff1edb67c | |||
| 3d2847e427 | |||
| ff4d8af054 | |||
| 612302f42f | |||
| a26607a154 | |||
| 939c7049ed | |||
| 0ead07e704 | |||
| f4908c4536 | |||
| 37951c2ebf | |||
| 915f222e08 | |||
| 9446df1b9c | |||
| bfcf89b093 | |||
| b0cf087d78 | |||
| 5863586766 | |||
| 99182a1f9e | |||
| 2599f40dfb | |||
| aa66ba54c5 | |||
| 4f0ef01a17 | |||
| 267e1656db | |||
| dcecda2984 | |||
| ce88f0229a | |||
| 316c82ea52 | |||
| 25be60ce2a | |||
| 4703eec045 | |||
| ab22a83332 | |||
| a3d6ebe44d | |||
| f8804b5d3c | |||
| ee3785ad51 | |||
| 5ba40cd2d6 | |||
| 6131180a31 | |||
| 7ccf3d0632 | |||
| 39fb7f759e | |||
| d89834a53d | |||
| 3c4dd2a143 | |||
| 847efb7082 | |||
| 01db8f90fa | |||
| e70c46ff02 | |||
| 7cb1998ebb | |||
| f5fd37f40d | |||
| 2daee62b80 | |||
| ffd179bb06 | |||
| 4acc0d8e12 | |||
| a66786444f | |||
| 01ad18ca57 | |||
| a72c8f2391 | |||
| f90177713a | |||
| 041eb1b921 | |||
| 105302d241 | |||
| bd9c7b459c | |||
| 1c122752bb | |||
| 203ef8b986 | |||
| ceda806f77 | |||
| baac9dfa25 | |||
| 10aace0026 | |||
| 3e38896078 | |||
| 0fa53c5f15 | |||
| 2b6320b880 | |||
| 7ba938ea25 | |||
| 6d4ad34e32 | |||
| 3239ff0a80 | |||
| 8b7e3c6f79 | |||
| ace4b78420 | |||
| 39ac89fead | |||
| 490fe7e70c | |||
| 06a7a8ea82 | |||
| b7f4a3503f | |||
| 835ed443a5 | |||
| 3f3c6983ba | |||
| f22ce1c225 | |||
| 9990de112f | |||
| ede37757db | |||
| 0c65d44bc9 | |||
| 07c9813ec1 | |||
| 03f0ee1f7d | |||
| ebc23c4ce3 | |||
| d098487cd1 | |||
| 14f2db730a | |||
| a79f301165 | |||
| 05d086352f | |||
| ef864a32c2 | |||
| 0d93a13407 | |||
| ab0208b7ab | |||
| c52f3c0430 | |||
| accf6fb5c7 | |||
| 18c2ce1274 | |||
| 9a104b918c | |||
| 948303c43a | |||
| 82f303fcfe | |||
| 359fa8df35 | |||
| 72e6c058f5 | |||
| 7648aa47b2 | |||
| 48074f0f53 | |||
| 66bd8d74f3 | |||
| 37f3c8b829 | |||
| 8775f90bc1 | |||
| d6962055b9 | |||
| 05489282df | |||
| 58f1b5833f | |||
| c66911b060 | |||
| 54debbfe86 | |||
| 6d580565fd | |||
| f0270eec4e | |||
| cd9290240a | |||
| 2fc7bc4744 | |||
| 5370a9e7f6 | |||
| 08f348ada5 | |||
| 4452f0b810 | |||
| b7dc880a5e | |||
| 35596094c2 | |||
| e8eb409f9f | |||
| 6a74a0c5b8 | |||
| dfa0468d24 | |||
| 4869b756ea | |||
| 693c79c9b7 | |||
| e846875973 | |||
| b1f2941247 | |||
| 6688c813e6 | |||
| 664dc45fcc | |||
| 27fa753486 | |||
| 71e392544c | |||
| 7f24081205 | |||
| 89f1c43984 | |||
| f841004e7a | |||
| 0922f557bb | |||
| 0b72ab156d | |||
| dfc6cc9c8a | |||
| 83b2908266 | |||
| c25f2a7abc | |||
| de1e396f6e | |||
| 80a3861816 | |||
| fd8eeb07c2 |
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Share an idea or suggestion
|
||||
title: 'FR: [a handful of words describing the FR]'
|
||||
title: 'FR: describing your feature request'
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
run: cargo fmt --check
|
||||
- name: Clippy Check
|
||||
working-directory: apps/readest-app/src-tauri
|
||||
run: cargo clippy -- -D warnings
|
||||
run: cargo clippy -p Readest --no-deps -- -D warnings
|
||||
|
||||
build_web_app:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -56,27 +56,35 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- os: ubuntu-latest
|
||||
release: android
|
||||
rust_target: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
|
||||
- os: ubuntu-22.04
|
||||
release: linux
|
||||
arch: x86_64
|
||||
rust_target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-22.04-arm
|
||||
release: linux
|
||||
arch: aarch64
|
||||
rust_target: aarch64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
release: macos
|
||||
arch: aarch64
|
||||
rust_target: x86_64-apple-darwin,aarch64-apple-darwin
|
||||
args: '--target universal-apple-darwin'
|
||||
- os: windows-latest
|
||||
release: windows
|
||||
arch: x86_64
|
||||
rust_target: x86_64-pc-windows-msvc
|
||||
args: '--target x86_64-pc-windows-msvc'
|
||||
- os: windows-latest
|
||||
release: windows
|
||||
arch: aarch64
|
||||
rust_target: aarch64-pc-windows-msvc
|
||||
args: '--target aarch64-pc-windows-msvc --bundles nsis'
|
||||
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -94,6 +102,21 @@ jobs:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: setup Java (for Android build only)
|
||||
if: matrix.config.release == 'android'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: setup Android SDK (for Android build only)
|
||||
if: matrix.config.release == 'android'
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: install NDK (for Android build only)
|
||||
if: matrix.config.release == 'android'
|
||||
run: sdkmanager "ndk;27.0.11902837"
|
||||
|
||||
- name: install dependencies
|
||||
run: pnpm install
|
||||
|
||||
@@ -107,7 +130,13 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.config.os }}-cargo-${{ hashFiles('Cargo.lock') }}
|
||||
key: ${{ matrix.config.os }}-${{ matrix.config.release }}-${{ matrix.config.arch }}-cargo
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: contains(matrix.config.os, 'ubuntu') && matrix.config.release != 'android'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libfontconfig-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
|
||||
|
||||
- name: create .env.local file for Next.js
|
||||
run: |
|
||||
@@ -116,17 +145,76 @@ jobs:
|
||||
echo "NEXT_PUBLIC_SUPABASE_URL=${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}" >> .env.local
|
||||
echo "NEXT_PUBLIC_SUPABASE_ANON_KEY=${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}" >> .env.local
|
||||
echo "NEXT_PUBLIC_APP_PLATFORM=tauri" >> .env.local
|
||||
cp .env.local apps/readest-app/.env.local
|
||||
|
||||
- name: copy .env.local to apps/readest-app
|
||||
run: cp .env.local apps/readest-app/.env.local
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: contains(matrix.config.os, 'ubuntu')
|
||||
- name: build and upload Android apks
|
||||
if: matrix.config.release == 'android'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NDK_HOME: ${{ env.ANDROID_HOME }}/ndk/27.0.11902837
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libfontconfig-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
|
||||
cd apps/readest-app/
|
||||
rm -rf src-tauri/gen/android
|
||||
pnpm tauri android init
|
||||
pnpm tauri icon ../../data/icons/readest-book.png
|
||||
git checkout .
|
||||
|
||||
pushd src-tauri/gen/android
|
||||
echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" > keystore.properties
|
||||
echo "password=${{ secrets.ANDROID_KEY_PASSWORD }}" >> keystore.properties
|
||||
base64 -d <<< "${{ secrets.ANDROID_KEY_BASE64 }}" > $RUNNER_TEMP/keystore.jks
|
||||
echo "storeFile=$RUNNER_TEMP/keystore.jks" >> keystore.properties
|
||||
|
||||
popd
|
||||
version=${{ needs.get-release.outputs.release_version }}
|
||||
apk_path=src-tauri/gen/android/app/build/outputs/apk/universal/release
|
||||
universial_apk=Readest_${version}_universal.apk
|
||||
arm64_apk=Readest_${version}_arm64.apk
|
||||
pnpm tauri android build
|
||||
cp ${apk_path}/app-universal-release.apk $universial_apk
|
||||
pnpm tauri android build -t aarch64
|
||||
cp ${apk_path}/app-universal-release.apk $arm64_apk
|
||||
|
||||
echo "Uploading $universial_apk to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $universial_apk --clobber
|
||||
echo "Uploading $arm64_apk to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $arm64_apk --clobber
|
||||
echo "Uploading signatures to GitHub release"
|
||||
pnpm tauri signer sign $universial_apk
|
||||
pnpm tauri signer sign $arm64_apk
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $universial_apk.sig --clobber
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $arm64_apk.sig --clobber
|
||||
|
||||
- name: download and update latest.json for Android release
|
||||
if: matrix.config.release == 'android'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
cd apps/readest-app/
|
||||
curl -sL https://github.com/readest/readest/releases/latest/download/latest.json -o latest.json
|
||||
|
||||
version=${{ needs.get-release.outputs.release_version }}
|
||||
universial_apk_url="https://github.com/readest/readest/releases/download/${{ needs.get-release.outputs.release_tag }}/Readest_${version}_universal.apk"
|
||||
arm64_apk_url="https://github.com/readest/readest/releases/download/${{ needs.get-release.outputs.release_tag }}/Readest_${version}_arm64.apk"
|
||||
|
||||
universial_sig=$(cat Readest_${version}_universal.apk.sig)
|
||||
arm64_sig=$(cat Readest_${version}_arm64.apk.sig)
|
||||
|
||||
jq --arg url "$universial_apk_url" \
|
||||
--arg sig "$universial_sig" \
|
||||
'.platforms["android-universal"] = {signature: $sig, url: $url}' latest.json > tmp.$$.json && mv tmp.$$.json latest.json
|
||||
|
||||
jq --arg url "$arm64_apk_url" \
|
||||
--arg sig "$arm64_sig" \
|
||||
'.platforms["android-arm64"] = {signature: $sig, url: $url}' latest.json > tmp.$$.json && mv tmp.$$.json latest.json
|
||||
|
||||
echo "Uploading updated latest.json to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} latest.json --clobber
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
if: matrix.config.release != 'android'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
@@ -137,12 +225,20 @@ jobs:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
NODE_OPTIONS: '--max-old-space-size=8192'
|
||||
with:
|
||||
projectPath: apps/readest-app
|
||||
releaseId: ${{ needs.get-release.outputs.release_id }}
|
||||
releaseBody: ${{ needs.get-release.outputs.release_note }}
|
||||
args: ${{ matrix.config.args || '' }}
|
||||
|
||||
- name: upload release notes to GitHub release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "Uploading release notes to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} apps/readest-app/release-notes.json --clobber
|
||||
|
||||
- name: upload portable binaries (Windows only)
|
||||
if: matrix.config.os == 'windows-latest'
|
||||
env:
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[submodule "packages/foliate-js"]
|
||||
path = packages/foliate-js
|
||||
url = https://github.com/chrox/foliate-js.git
|
||||
url = https://github.com/readest/foliate-js.git
|
||||
[submodule "packages/tauri"]
|
||||
path = packages/tauri
|
||||
url = https://github.com/chrox/tauri.git
|
||||
url = https://github.com/tauri-apps/tauri.git
|
||||
|
||||
Generated
+1504
-1446
File diff suppressed because it is too large
Load Diff
+16
-1
@@ -1,3 +1,18 @@
|
||||
[workspace]
|
||||
members = ["apps/readest-app/src-tauri"]
|
||||
members = [
|
||||
"apps/readest-app/src-tauri",
|
||||
"packages/tauri/crates/tauri",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Bilingify LLC"]
|
||||
homepage = "https://readest.com"
|
||||
license = "AGPL-3.0"
|
||||
repository = "https://github.com/readest/readest"
|
||||
categories = []
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
[patch.crates-io]
|
||||
tauri = { path = "packages/tauri/crates/tauri" }
|
||||
@@ -26,13 +26,13 @@
|
||||
<a href="#screenshots">Screenshots</a> •
|
||||
<a href="#downloads">Downloads</a> •
|
||||
<a href="#getting-started">Getting Started</a> •
|
||||
<a href="#contributors">Contributors</a> •
|
||||
<a href="#troubleshooting">Troubleshooting</a> •
|
||||
<a href="#license">License</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://readest.com" target="_blank">
|
||||
<img src="./data/screenshots/landing_preview.png" alt="Readest Banner" width="100%" />
|
||||
<img src="./data/screenshots/landing_all_platforms.png" alt="Readest Banner" width="100%" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
| **Feature** | **Description** | **Status** |
|
||||
| --------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------- |
|
||||
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, PDF (experimental) | ✅ |
|
||||
| **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. | ✅ |
|
||||
@@ -54,6 +54,7 @@
|
||||
| **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. | ✅ |
|
||||
| **Text-to-Speech (TTS) Support** | Enable text-to-speech functionality for a more accessible reading experience. | ✅ |
|
||||
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
|
||||
|
||||
## Planned Features
|
||||
|
||||
@@ -62,7 +63,6 @@
|
||||
|
||||
| **Feature** | **Description** | **Priority** |
|
||||
| ------------------------------- | ------------------------------------------------------------------------------------------ | ------------ |
|
||||
| **Library Management** | Organize, sort, and manage your entire ebook library. | 🛠 |
|
||||
| **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 |
|
||||
| **Sync with Koreader** | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | 🔄 |
|
||||
| **Keyboard Navigation** | Implement vimium-style keybindings for book navigation. | 🔄 |
|
||||
@@ -80,7 +80,7 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
@@ -199,13 +199,46 @@ nix develop ./ops#android # enter a dev shell for the android app
|
||||
|
||||
Please check the [wiki][link-gh-wiki] of this project for more information on development.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 1. Readest Won’t Launch on Windows (Missing Edge WebView2 Runtime)
|
||||
|
||||
**Symptom**
|
||||
|
||||
- When you double-click readest.exe, nothing happens. No window appears, and Task Manager does not show the process.
|
||||
- This can affect both the standard installer and the portable version.
|
||||
|
||||
**Cause**
|
||||
|
||||
- Microsoft Edge WebView2 Runtime is either missing, outdated, or improperly installed on your system. Readest depends on WebView2 to render the interface on Windows.
|
||||
|
||||
**How to Fix**
|
||||
|
||||
1. Check if WebView2 is installed
|
||||
- Open “Add or Remove Programs” (a.k.a. Apps & features) on Windows. Look for “Microsoft Edge WebView2 Runtime.”
|
||||
2. Install or Update WebView2
|
||||
- Download the WebView2 Runtime directly from Microsoft: [link](https://developer.microsoft.com/en-us/microsoft-edge/webview2?form=MA13LH).
|
||||
- If you prefer an offline installer, download the offline package and run it as an Administrator.
|
||||
3. Re-run Readest
|
||||
- After installing/updating WebView2, launch readest.exe again.
|
||||
- If you still encounter problems, reboot your PC and try again.
|
||||
|
||||
**Additional Tips**
|
||||
|
||||
- If reinstalling once doesn’t work, uninstall Edge WebView2 completely, then reinstall it with Administrator privileges.
|
||||
- Verify your Windows installation has the latest updates from Microsoft.
|
||||
|
||||
**Still Stuck?**
|
||||
|
||||
- See Issue [readest/readest#358](https://github.com/readest/readest/issues/358) for further details, or head over to our [Discord][link-discord] server and open a support discussion with detailed logs of your environment and the steps you’ve taken.
|
||||
|
||||
## Contributors
|
||||
|
||||
Readest is open-source, and contributions are welcome! Feel free to open issues, suggest features, or submit pull requests. Please **review our [contributing guidelines](CONTRIBUTING.md) before you start**. We also welcome you to join our [Discord][link-discord] community for either support or contributing guidance.
|
||||
|
||||
<a href="https://github.com/readest/readest/graphs/contributors">
|
||||
<p align="left">
|
||||
<img width="300" src="https://contrib.rocks/image?repo=readest/readest" alt="A table of avatars from the project's contributors" />
|
||||
<img width="500" src="https://contrib.rocks/image?repo=readest/readest" alt="A table of avatars from the project's contributors" />
|
||||
</p>
|
||||
</a>
|
||||
|
||||
@@ -219,9 +252,10 @@ The following libraries and frameworks are used in this software:
|
||||
- [zip.js](https://github.com/gildas-lormeau/zip.js), which is licensed under the BSD-3-Clause license.
|
||||
- [fflate](https://github.com/101arrowz/fflate), which is MIT licensed.
|
||||
- [PDF.js](https://github.com/mozilla/pdf.js), which is licensed under Apache License 2.0.
|
||||
- [daisyUI](https://github.com/saadeghi/daisyui), which is MIT licensed.
|
||||
- [next.js](https://github.com/vercel/next.js), which is MIT licensed.
|
||||
- [react](https://github.com/facebook/react), which is MIT licensed.
|
||||
- [react-icons](https://github.com/react-icons/react-icons), which has various open-source licenses.
|
||||
- [react](https://github.com/facebook/react), which is MIT licensed.
|
||||
- [tauri](https://github.com/tauri-apps/tauri), which is MIT licensed.
|
||||
|
||||
The following fonts are utilized in this software, either bundled within the application or provided through web fonts:
|
||||
|
||||
@@ -4,14 +4,25 @@ NEXT_PUBLIC_POSTHOG_HOST=YOUR_POSTHOG_HOST
|
||||
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
|
||||
NEXT_PUBLIC_STORAGE_FIXED_QUOTA=1073741824
|
||||
|
||||
SUPABASE_ADMIN_KEY=YOUR_SUPABASE_ADMIN_KEY
|
||||
|
||||
DEEPL_PRO_API_KEYS=YOUR_DEEPL_PRO_API_KEYS
|
||||
DEEPL_FREE_API_KEYS=YOUR_DEEPL_FREE_API_KEYS
|
||||
|
||||
# r2, s3
|
||||
NEXT_PUBLIC_OBJECT_STORAGE_TYPE=r2
|
||||
|
||||
R2_TOKEN_VALUE=YOUR_R2_TOKEN_VALUE
|
||||
R2_ACCESS_KEY_ID=YOUR_R2_ACCESS_KEY_ID
|
||||
R2_SECRET_ACCESS_KEY=YOUR_R2_SECRET_ACCESS_KEY
|
||||
R2_BUCKET_NAME=YOUR_R2_BUCKET_NAME
|
||||
R2_ACCOUNT_ID=YOUR_R2_ACCOUNT_ID
|
||||
R2_REGION=YOUR_R2_REGION
|
||||
|
||||
S3_ENDPOINT=PLACE_HOLDER
|
||||
S3_ACCESS_KEY_ID=PLACE_HOLDER
|
||||
S3_SECRET_ACCESS_KEY=PLACE_HOLDER
|
||||
S3_BUCKET_NAME=PLACE_HOLDER
|
||||
S3_REGION=PLACE_HOLDER
|
||||
|
||||
@@ -32,7 +32,7 @@ yarn-error.log*
|
||||
/private_keys
|
||||
|
||||
# plists
|
||||
*.plist
|
||||
Entitlements*.plist
|
||||
|
||||
# local confs
|
||||
tauri.*.conf.json
|
||||
|
||||
@@ -13,9 +13,7 @@ const nextConfig = {
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
devIndicators: {
|
||||
appIsrStatus: false,
|
||||
},
|
||||
devIndicators: false,
|
||||
// Configure assetPrefix or else the server won't properly resolve your assets.
|
||||
assetPrefix: '',
|
||||
reactStrictMode: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.9.19",
|
||||
"version": "0.9.38",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev",
|
||||
@@ -42,20 +42,21 @@
|
||||
"@supabase/auth-ui-react": "^0.4.7",
|
||||
"@supabase/auth-ui-shared": "^0.1.8",
|
||||
"@supabase/supabase-js": "^2.47.7",
|
||||
"@tauri-apps/api": "2.2.0",
|
||||
"@tauri-apps/api": "2.4.1",
|
||||
"@tauri-apps/plugin-cli": "^2.2.0",
|
||||
"@tauri-apps/plugin-deep-link": "^2.2.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.2.0",
|
||||
"@tauri-apps/plugin-fs": "^2.2.0",
|
||||
"@tauri-apps/plugin-haptics": "^2.2.3",
|
||||
"@tauri-apps/plugin-http": "^2.3.0",
|
||||
"@tauri-apps/plugin-log": "^2.2.1",
|
||||
"@tauri-apps/plugin-opener": "^2.2.5",
|
||||
"@tauri-apps/plugin-os": "^2.2.0",
|
||||
"@tauri-apps/plugin-process": "^2.2.0",
|
||||
"@tauri-apps/plugin-shell": "~2.2.0",
|
||||
"@tauri-apps/plugin-updater": "^2.4.0",
|
||||
"@tauri-apps/plugin-deep-link": "^2.2.1",
|
||||
"@tauri-apps/plugin-dialog": "^2.2.1",
|
||||
"@tauri-apps/plugin-fs": "^2.2.1",
|
||||
"@tauri-apps/plugin-haptics": "^2.2.4",
|
||||
"@tauri-apps/plugin-http": "^2.4.3",
|
||||
"@tauri-apps/plugin-log": "^2.3.1",
|
||||
"@tauri-apps/plugin-opener": "^2.2.6",
|
||||
"@tauri-apps/plugin-os": "^2.2.1",
|
||||
"@tauri-apps/plugin-process": "^2.2.1",
|
||||
"@tauri-apps/plugin-shell": "~2.2.1",
|
||||
"@tauri-apps/plugin-updater": "^2.7.0",
|
||||
"@zip.js/zip.js": "^2.7.53",
|
||||
"aws4fetch": "^1.0.20",
|
||||
"clsx": "^2.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"cssbeautify": "^0.3.1",
|
||||
@@ -65,27 +66,32 @@
|
||||
"i18next-http-backend": "^3.0.1",
|
||||
"js-md5": "^0.8.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"next": "15.1.6",
|
||||
"next": "15.2.4",
|
||||
"posthog-js": "^1.205.0",
|
||||
"react": "19.0.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "19.0.0",
|
||||
"react-i18next": "^15.2.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-responsive": "^10.0.0",
|
||||
"semver": "^7.7.1",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"zustand": "5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.2.7",
|
||||
"@opennextjs/cloudflare": "^0.5.12",
|
||||
"@tauri-apps/cli": "2.4.1",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/cssbeautify": "^0.3.5",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/react": "18.3.12",
|
||||
"@types/react-color": "^3.0.13",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cpx2": "^8.0.0",
|
||||
"daisyui": "^4.12.23",
|
||||
"daisyui": "^4.12.24",
|
||||
"dotenv-cli": "^7.4.4",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-next": "15.0.3",
|
||||
@@ -97,6 +103,7 @@
|
||||
"postcss-nested": "^7.0.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.2"
|
||||
"typescript": "^5.7.2",
|
||||
"wrangler": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "حذف",
|
||||
"Delete Highlight": "حذف التمييز",
|
||||
"Dictionary": "القاموس",
|
||||
"Disable Click-to-Flip": "تعطيل التقليب بالنقر",
|
||||
"Download Readest": "تحميل ريديست",
|
||||
"Edit": "تحرير",
|
||||
"Enter your custom CSS here...": "أدخل الـ CSS المخصص هنا...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "الواصلة",
|
||||
"Identifier:": "المعرف:",
|
||||
"Import Books": "استيراد الكتب",
|
||||
"Invert Colors in Dark Mode": "عكس الألوان في الوضع المظلم",
|
||||
"Language:": "اللغة:",
|
||||
"Layout": "التخطيط",
|
||||
"Light Mode": "الوضع الفاتح",
|
||||
"Loading...": "جارٍ التحميل...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "الصفحة {{currentPage}} \\ {{totalPage}}",
|
||||
"Logged in": "تم تسجيل الدخول",
|
||||
"Logged in as {{userDisplayName}}": "تم تسجيل الدخول باسم {{userDisplayName}}",
|
||||
"Match Case": "مطابقة حالة الأحرف",
|
||||
"Match Diacritics": "مطابقة التشكيل",
|
||||
"Match Whole Words": "مطابقة الكلمات الكاملة",
|
||||
"Maximum Block Size": "أقصى مساحة رأسية",
|
||||
"Maximum Inline Size": "أقصى مساحة أفقية",
|
||||
"Maximum Number of Columns": "العدد الأقصى للأعمدة",
|
||||
"Minimum Font Size": "الحد الأدنى لحجم الخط",
|
||||
"Misc": "أخرى",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "تطبيق على جميع الكتب",
|
||||
"Apply to This Book": "تطبيق على هذا الكتاب",
|
||||
"Unable to fetch the translation. Try again later.": "تعذر جلب الترجمة. حاول مرة أخرى لاحقًا.",
|
||||
"Update Now!": "قم بالتحديث الآن!",
|
||||
"Update": "تحديث",
|
||||
"Check Update": "التحقق من التحديث",
|
||||
"Already the latest version": "الإصدار الحالي هو الإصدار الأحدث بالفعل",
|
||||
"Book Details": "تفاصيل الكتاب",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "مسافة بادئة للنص",
|
||||
"Paragraph Margin": "هامش الفقرة",
|
||||
"Override Book Layout": "تجاوز تخطيط الكتاب",
|
||||
"Add to Group": "إضافة إلى مجموعة",
|
||||
"Untitled Group": "مجموعة بدون عنوان",
|
||||
"Group Books": "تجميع الكتب",
|
||||
"Remove From Group": "إزالة من المجموعة",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "حذف نهائي",
|
||||
"Free Tier": "الفئة المجانية",
|
||||
"Plus Tier": "فئة Plus",
|
||||
"Pro Tier": "فئة Pro"
|
||||
"Pro Tier": "فئة Pro",
|
||||
"RTL Direction": "الاتجاه من اليمين إلى اليسار",
|
||||
"Maximum Column Height": "الارتفاع الأقصى للعمود",
|
||||
"Maximum Column Width": "العرض الأقصى للعمود",
|
||||
"Continuous Scroll": "التمرير المستمر",
|
||||
"Fullscreen": "ملء الشاشة",
|
||||
"No supported files found. Supported formats: {{formats}}": "لم يتم العثور على ملفات مدعومة. الصيغ المدعومة: {{formats}}",
|
||||
"Drop to Import Books": "اسحب لاستيراد الكتب",
|
||||
"Custom": "مخصص",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "العالم مسرح،\nوالناس فيه ممثلون؛\nلهم مخارج ومداخل،\nوالإنسان في حياته يلعب دورًا كثيرًا،\nوأفعاله تمر بسبع مراحل.\n\n— ويليام شكسبير",
|
||||
"Custom Theme": "سمة مخصصة",
|
||||
"Theme Name": "اسم السمة",
|
||||
"Text Color": "لون النص",
|
||||
"Background Color": "لون الخلفية",
|
||||
"Preview": "معاينة",
|
||||
"Contrast": "التباين",
|
||||
"Sunset": "غروب الشمس",
|
||||
"Double Border": "حدود مزدوجة",
|
||||
"Border Color": "لون الحدود",
|
||||
"Border Frame": "إطار الحدود",
|
||||
"Show Header": "عرض الرأس",
|
||||
"Show Footer": "عرض التذييل",
|
||||
"Small": "صغير",
|
||||
"Large": "كبير",
|
||||
"Auto": "تلقائي",
|
||||
"Language": "اللغة",
|
||||
"No annotations to export": "لا توجد تعليقات للتصدير",
|
||||
"Author": "المؤلف",
|
||||
"Exported from Readest": "تم التصدير من ريديست",
|
||||
"Highlights & Annotations": "التمييزات والتعليقات",
|
||||
"Note": "ملاحظة",
|
||||
"Copied to clipboard": "تم النسخ إلى الحافظة",
|
||||
"Export Annotations": "تصدير التعليقات",
|
||||
"Auto Import on File Open": "استيراد تلقائي عند فتح الملف",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "لم يتم التعرف على فصول.",
|
||||
"Failed to parse the EPUB file.": "فشل في تحليل ملف EPUB.",
|
||||
"This book format is not supported.": "هذا تنسيق الكتاب غير مدعوم.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "تعذر جلب الترجمة. يرجى تسجيل الدخول أولاً ثم المحاولة مرة أخرى.",
|
||||
"Group": "تجميع",
|
||||
"Always on Top": "دائمًا في المقدمة",
|
||||
"No Timeout": "لا مهلة",
|
||||
"{{value}} minute": "{{value}} دقيقة",
|
||||
"{{value}} minutes": "{{value}} دقائق",
|
||||
"{{value}} hour": "{{value}} ساعة",
|
||||
"{{value}} hours": "{{value}} ساعات",
|
||||
"CJK Font": "خط CJK",
|
||||
"Clear Search": "مسح البحث",
|
||||
"Header & Footer": "الرأس والتذييل",
|
||||
"Apply also in Scrolled Mode": "تطبيق أيضًا في وضع التمرير",
|
||||
"A new version of Readest is Available!": "إصدار جديد من ريديست متاح!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "إصدار ريديست {{newVersion}} متاح (الإصدار المثبت {{currentVersion}}).",
|
||||
"Download and install now?": "هل ترغب في تنزيل وتثبيت الآن؟",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "جارٍ تنزيل {{downloaded}} من {{contentLength}}",
|
||||
"Download finished": "اكتمل التنزيل",
|
||||
"DOWNLOAD & INSTALL": "تنزيل وتثبيت",
|
||||
"Changelog:": "سجل التغييرات:",
|
||||
"Software Update": "تحديث البرنامج",
|
||||
"Title": "العنوان",
|
||||
"Date Read": "تاريخ القراءة",
|
||||
"Date Added": "تاريخ الإضافة",
|
||||
"Format": "التنسيق",
|
||||
"Ascending": "تصاعدي",
|
||||
"Descending": "تنازلي",
|
||||
"Sort by...": "ترتيب حسب...",
|
||||
"Added:": "تاريخ الإضافة:",
|
||||
"Format:": "التنسيق:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "الوصف:",
|
||||
"No description available": "لا يوجد وصف متاح",
|
||||
"List": "قائمة",
|
||||
"Grid": "شبكة",
|
||||
"(from 'As You Like It', Act II)": "(من 'كما تحب'، الفصل الثاني)",
|
||||
"Link Color": "لون الرابط",
|
||||
"Volume Keys for Page Flip": "مفاتيح الصوت لتقليب الصفحات",
|
||||
"Clicks for Page Flip": "نقرات لتقليب الصفحات",
|
||||
"Swap Clicks Area": "تبديل منطقة النقرات"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Löschen",
|
||||
"Delete Highlight": "Hervorhebung löschen",
|
||||
"Dictionary": "Wörterbuch",
|
||||
"Disable Click-to-Flip": "Klick zum Blättern deaktivieren",
|
||||
"Download Readest": "Readest herunterladen",
|
||||
"Edit": "Bearbeiten",
|
||||
"Enter your custom CSS here...": "Geben Sie hier Ihr benutzerdefiniertes CSS ein...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Silbentrennung",
|
||||
"Identifier:": "Kennung:",
|
||||
"Import Books": "Bücher importieren",
|
||||
"Invert Colors in Dark Mode": "Farben im Dunkelmodus invertieren",
|
||||
"Language:": "Sprache:",
|
||||
"Layout": "Layout",
|
||||
"Light Mode": "Hellmodus",
|
||||
"Loading...": "Wird geladen...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Pos. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Angemeldet",
|
||||
"Logged in as {{userDisplayName}}": "Angemeldet als {{userDisplayName}}",
|
||||
"Match Case": "Groß-/Kleinschreibung beachten",
|
||||
"Match Diacritics": "Akzente beachten",
|
||||
"Match Whole Words": "Ganze Wörter",
|
||||
"Maximum Block Size": "Maximale Blockgröße",
|
||||
"Maximum Inline Size": "Maximale Zeilengröße",
|
||||
"Maximum Number of Columns": "Maximale Spaltenanzahl",
|
||||
"Minimum Font Size": "Minimale Schriftgröße",
|
||||
"Misc": "Sonstiges",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Auf alle Bücher anwenden",
|
||||
"Apply to This Book": "Auf dieses Buch anwenden",
|
||||
"Unable to fetch the translation. Try again later.": "Übersetzung konnte nicht abgerufen werden. Versuchen Sie es später erneut.",
|
||||
"Update Now!": "Jetzt aktualisieren!",
|
||||
"Update": "Aktualisieren",
|
||||
"Check Update": "Aktualisierung prüfen",
|
||||
"Already the latest version": "Bereits die neueste Version",
|
||||
"Book Details": "Buchdetails",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Texteinzug",
|
||||
"Paragraph Margin": "Absatzrand",
|
||||
"Override Book Layout": "Buch-Layout überschreiben",
|
||||
"Add to Group": "Zur Gruppe hinzufügen",
|
||||
"Untitled Group": "Unbenannte Gruppe",
|
||||
"Group Books": "Bücher gruppieren",
|
||||
"Remove From Group": "Aus Gruppe entfernen",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Dauerhaft löschen",
|
||||
"Free Tier": "Kostenlose Stufe",
|
||||
"Plus Tier": "Plus-Stufe",
|
||||
"Pro Tier": "Pro-Stufe"
|
||||
"Pro Tier": "Pro-Stufe",
|
||||
"RTL Direction": "RTL-Richtung",
|
||||
"Maximum Column Height": "Maximale Spaltenhöhe",
|
||||
"Maximum Column Width": "Maximale Spaltenbreite",
|
||||
"Continuous Scroll": "Kontinuierliches Scrollen",
|
||||
"Fullscreen": "Vollbild",
|
||||
"No supported files found. Supported formats: {{formats}}": "Keine unterstützten Dateien gefunden. Unterstützte Formate: {{formats}}",
|
||||
"Drop to Import Books": "Zum Importieren von Büchern ablegen",
|
||||
"Custom": "Benutzerdefiniert",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Die ganze Welt ist eine Bühne,\nUnd alle Männer und Frauen sind bloß Spieler;\nSie haben ihre Abgänge und ihre Auftritte,\nUnd ein Mann spielt in seinem Leben viele Rollen,\nSeine Taten sind sieben Zeitalter.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Benutzerdefiniertes Thema",
|
||||
"Theme Name": "Themenname",
|
||||
"Text Color": "Textfarbe",
|
||||
"Background Color": "Hintergrundfarbe",
|
||||
"Preview": "Vorschau",
|
||||
"Contrast": "Kontrast",
|
||||
"Sunset": "Sonnenuntergang",
|
||||
"Double Border": "Doppelter Rand",
|
||||
"Border Color": "Randfarbe",
|
||||
"Border Frame": "Rahmenrand",
|
||||
"Show Header": "Kopfzeile anzeigen",
|
||||
"Show Footer": "Fußzeile anzeigen",
|
||||
"Small": "Klein",
|
||||
"Large": "Groß",
|
||||
"Auto": "Automatisch",
|
||||
"Language": "Sprache",
|
||||
"No annotations to export": "Keine Anmerkungen zum Exportieren",
|
||||
"Author": "Autor",
|
||||
"Exported from Readest": "Exportiert von Readest",
|
||||
"Highlights & Annotations": "Hervorhebungen & Anmerkungen",
|
||||
"Note": "Notiz",
|
||||
"Copied to clipboard": "In die Zwischenablage kopiert",
|
||||
"Export Annotations": "Anmerkungen exportieren",
|
||||
"Auto Import on File Open": "Automatischer Import beim Öffnen einer Datei",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Keine Kapitel erkannt.",
|
||||
"Failed to parse the EPUB file.": "Fehler beim Parsen der EPUB-Datei.",
|
||||
"This book format is not supported.": "Dieses Buchformat wird nicht unterstützt.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Übersetzung konnte nicht abgerufen werden. Bitte zuerst anmelden und erneut versuchen.",
|
||||
"Group": "Gruppieren",
|
||||
"Always on Top": "Immer im Vordergrund",
|
||||
"No Timeout": "Kein Timeout",
|
||||
"{{value}} minute": "{{value}} Minute",
|
||||
"{{value}} minutes": "{{value}} Minuten",
|
||||
"{{value}} hour": "{{value}} Stunde",
|
||||
"{{value}} hours": "{{value}} Stunden",
|
||||
"CJK Font": "CJK-Schriftart",
|
||||
"Clear Search": "Suche löschen",
|
||||
"Header & Footer": "Kopf- & Fußzeile",
|
||||
"Apply also in Scrolled Mode": "Auch im Scroll-Modus anwenden",
|
||||
"A new version of Readest is Available!": "Eine neue Version von Readest ist verfügbar!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} ist verfügbar (installierte Version {{currentVersion}}).",
|
||||
"Download and install now?": "Jetzt herunterladen und installieren?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Herunterladen {{downloaded}} von {{contentLength}}",
|
||||
"Download finished": "Download abgeschlossen",
|
||||
"DOWNLOAD & INSTALL": "HERUNTERLADEN & INSTALLIEREN",
|
||||
"Changelog:": "Änderungsprotokoll:",
|
||||
"Software Update": "Software-Update",
|
||||
"Title": "Titel",
|
||||
"Date Read": "Datum gelesen",
|
||||
"Date Added": "Datum hinzugefügt",
|
||||
"Format": "Format",
|
||||
"Ascending": "Aufsteigend",
|
||||
"Descending": "Absteigend",
|
||||
"Sort by...": "Sortieren nach...",
|
||||
"Added:": "Hinzugefügt:",
|
||||
"Format:": "Format:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Beschreibung:",
|
||||
"No description available": "Keine Beschreibung verfügbar",
|
||||
"List": "Liste",
|
||||
"Grid": "Raster",
|
||||
"(from 'As You Like It', Act II)": "(aus 'Wie es euch gefällt', Akt II)",
|
||||
"Link Color": "Linkfarbe",
|
||||
"Volume Keys for Page Flip": "Volumen-Tasten für Seitenwechsel",
|
||||
"Clicks for Page Flip": "Tippen zum Blättern",
|
||||
"Swap Clicks Area": "Tippbereiche tauschen"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Διαγραφή",
|
||||
"Delete Highlight": "Διαγραφή επισήμανσης",
|
||||
"Dictionary": "Λεξικό",
|
||||
"Disable Click-to-Flip": "Απενεργοποίηση κλικ για γύρισμα σελίδας",
|
||||
"Download Readest": "Λήψη Readest",
|
||||
"Edit": "Επεξεργασία",
|
||||
"Enter your custom CSS here...": "Εισάγετε το προσαρμοσμένο CSS εδώ...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Συλλαβισμός",
|
||||
"Identifier:": "Αναγνωριστικό:",
|
||||
"Import Books": "Εισαγωγή βιβλίων",
|
||||
"Invert Colors in Dark Mode": "Αντιστροφή χρωμάτων στη σκοτεινή λειτουργία",
|
||||
"Language:": "Γλώσσα:",
|
||||
"Layout": "Διάταξη",
|
||||
"Light Mode": "Φωτεινή λειτουργία",
|
||||
"Loading...": "Φόρτωση...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Θέση {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Συνδεδεμένος",
|
||||
"Logged in as {{userDisplayName}}": "Συνδεδεμένος ως {{userDisplayName}}",
|
||||
"Match Case": "Ταίριασμα πεζών-κεφαλαίων",
|
||||
"Match Diacritics": "Ταίριασμα διακριτικών σημείων",
|
||||
"Match Whole Words": "Ταίριασμα ολόκληρων λέξεων",
|
||||
"Maximum Block Size": "Μέγιστο μέγεθος μπλοκ",
|
||||
"Maximum Inline Size": "Μέγιστο μέγεθος γραμμής",
|
||||
"Maximum Number of Columns": "Μέγιστος αριθμός στηλών",
|
||||
"Minimum Font Size": "Ελάχιστο μέγεθος γραμματοσειράς",
|
||||
"Misc": "Διάφορα",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Εφαρμογή σε όλα τα βιβλία",
|
||||
"Apply to This Book": "Εφαρμογή σε αυτό το βιβλίο",
|
||||
"Unable to fetch the translation. Try again later.": "Αδυναμία λήψης της μετάφρασης. Δοκιμάστε ξανά αργότερα.",
|
||||
"Update Now!": "Ενημέρωση τώρα!",
|
||||
"Update": "Ενημέρωση",
|
||||
"Check Update": "Έλεγχος ενημέρωσης",
|
||||
"Already the latest version": "Ήδη η τελευταία έκδοση",
|
||||
"Book Details": "Λεπτομέρειες βιβλίου",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Εσοχή κειμένου",
|
||||
"Paragraph Margin": "Περιθώριο παραγράφου",
|
||||
"Override Book Layout": "Παράκαμψη διάταξης βιβλίου",
|
||||
"Add to Group": "Προσθήκη στην ομάδα",
|
||||
"Untitled Group": "Ομάδα χωρίς τίτλο",
|
||||
"Group Books": "Ομαδοποίηση βιβλίων",
|
||||
"Remove From Group": "Αφαίρεση από την ομάδα",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Μόνιμη διαγραφή",
|
||||
"Free Tier": "Δωρεάν βαθμίδα",
|
||||
"Plus Tier": "Βαθμίδα Plus",
|
||||
"Pro Tier": "Βαθμίδα Pro"
|
||||
"Pro Tier": "Βαθμίδα Pro",
|
||||
"RTL Direction": "Κατεύθυνση RTL",
|
||||
"Maximum Column Height": "Μέγιστο ύψος στήλης",
|
||||
"Maximum Column Width": "Μέγιστο πλάτος στήλης",
|
||||
"Continuous Scroll": "Συνεχής κύλιση",
|
||||
"Fullscreen": "Πλήρης οθόνη",
|
||||
"No supported files found. Supported formats: {{formats}}": "Δεν βρέθηκαν υποστηριζόμενα αρχεία. Υποστηριζόμενες μορφές: {{formats}}",
|
||||
"Drop to Import Books": "Ρίξτε για εισαγωγή βιβλίων",
|
||||
"Custom": "Προσαρμοσμένο",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Όλος ο κόσμος είναι σκηνή,\nΚαι όλοι οι άνδρες και οι γυναίκες απλώς ηθοποιοί·\nΈχουν τις εξόδους και τις εισόδους τους,\nΚαι ένας άνθρωπος στον χρόνο του παίζει πολλούς ρόλους,\nΟι πράξεις του υπάρχουν σε επτά ηλικίες.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Προσαρμοσμένο θέμα",
|
||||
"Theme Name": "Όνομα θέματος",
|
||||
"Text Color": "Χρώμα κειμένου",
|
||||
"Background Color": "Χρώμα φόντου",
|
||||
"Preview": "Προεπισκόπηση",
|
||||
"Contrast": "Αντίθεση",
|
||||
"Sunset": "Ηλιοβασίλεμα",
|
||||
"Double Border": "Διπλό περιθώριο",
|
||||
"Border Color": "Χρώμα περιγράμματος",
|
||||
"Border Frame": "Πλαίσιο περιγράμματος",
|
||||
"Show Header": "Εμφάνιση κεφαλίδας",
|
||||
"Show Footer": "Εμφάνιση υποσέλιδου",
|
||||
"Small": "Μικρό",
|
||||
"Large": "Μεγάλο",
|
||||
"Auto": "Αυτόματο",
|
||||
"Language": "Γλώσσα",
|
||||
"No annotations to export": "Δεν υπάρχουν σχολιασμοί για εξαγωγή",
|
||||
"Author": "Συγγραφέας",
|
||||
"Exported from Readest": "Εξήχθη από το Readest",
|
||||
"Highlights & Annotations": "Επισημάνσεις & Σχολιασμοί",
|
||||
"Note": "Σημείωση",
|
||||
"Copied to clipboard": "Αντιγράφηκε στο πρόχειρο",
|
||||
"Export Annotations": "Εξαγωγή σχολιασμών",
|
||||
"Auto Import on File Open": "Αυτόματη εισαγωγή κατά το άνοιγμα αρχείου",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Δεν εντοπίστηκαν κεφάλαια.",
|
||||
"Failed to parse the EPUB file.": "Αποτυχία ανάλυσης του αρχείου EPUB.",
|
||||
"This book format is not supported.": "Αυτή η μορφή βιβλίου δεν υποστηρίζεται.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Αδυναμία λήψης της μετάφρασης. Παρακαλώ συνδεθείτε πρώτα και δοκιμάστε ξανά.",
|
||||
"Group": "Ομάδα",
|
||||
"Always on Top": "Πάντα στην κορυφή",
|
||||
"No Timeout": "Χωρίς χρονικό όριο",
|
||||
"{{value}} minute": "{{value}} λεπτό",
|
||||
"{{value}} minutes": "{{value}} λεπτά",
|
||||
"{{value}} hour": "{{value}} ώρα",
|
||||
"{{value}} hours": "{{value}} ώρες",
|
||||
"CJK Font": "Γραμματοσειρά CJK",
|
||||
"Clear Search": "Καθαρισμός αναζήτησης",
|
||||
"Header & Footer": "Κεφαλίδα & Υποσέλιδο",
|
||||
"Apply also in Scrolled Mode": "Εφαρμογή και σε λειτουργία κύλισης",
|
||||
"A new version of Readest is Available!": "Μια νέα έκδοση του Readest είναι διαθέσιμη!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Η έκδοση Readest {{newVersion}} είναι διαθέσιμη (έκδοση που είναι εγκατεστημένη {{currentVersion}}).",
|
||||
"Download and install now?": "Θέλετε να την κατεβάσετε και να την εγκαταστήσετε τώρα;",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Κατεβάζω {{downloaded}} από {{contentLength}}",
|
||||
"Download finished": "Η λήψη ολοκληρώθηκε",
|
||||
"DOWNLOAD & INSTALL": "ΛΗΨΗ & ΕΓΚΑΤΑΣΤΑΣΗ",
|
||||
"Changelog:": "Αλλαγές:",
|
||||
"Software Update": "Ενημέρωση λογισμικού",
|
||||
"Title": "Τίτλος",
|
||||
"Date Read": "Ημερομηνία ανάγνωσης",
|
||||
"Date Added": "Ημερομηνία προσθήκης",
|
||||
"Format": "Μορφή",
|
||||
"Ascending": "Αύξουσα",
|
||||
"Descending": "Φθίνουσα",
|
||||
"Sort by...": "Ταξινόμηση κατά...",
|
||||
"Added:": "Προστέθηκε:",
|
||||
"Format:": "Μορφή:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Περιγραφή:",
|
||||
"No description available": "Δεν είναι διαθέσιμη καμία περιγραφή",
|
||||
"List": "Λίστα",
|
||||
"Grid": "Πλέγμα",
|
||||
"(from 'As You Like It', Act II)": "('Όπως σας αρέσει', Πράξη II)",
|
||||
"Link Color": "Χρώμα συνδέσμου",
|
||||
"Volume Keys for Page Flip": "Πλήκτρα έντασης για ανατροπή σελίδας",
|
||||
"Clicks for Page Flip": "Κλικ για ανατροπή σελίδας",
|
||||
"Swap Clicks Area": "Ανταλλαγή περιοχής κλικ"
|
||||
}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
{}
|
||||
{
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Eliminar",
|
||||
"Delete Highlight": "Eliminar resaltado",
|
||||
"Dictionary": "Diccionario",
|
||||
"Disable Click-to-Flip": "Desactivar clic para voltear",
|
||||
"Download Readest": "Descargar Readest",
|
||||
"Edit": "Editar",
|
||||
"Enter your custom CSS here...": "Introduce tu CSS personalizado aquí...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Guionización",
|
||||
"Identifier:": "Identificador:",
|
||||
"Import Books": "Importar libros",
|
||||
"Invert Colors in Dark Mode": "Invertir colores en modo oscuro",
|
||||
"Language:": "Idioma:",
|
||||
"Layout": "Diseño",
|
||||
"Light Mode": "Modo claro",
|
||||
"Loading...": "Cargando...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Pos. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Sesión iniciada",
|
||||
"Logged in as {{userDisplayName}}": "Sesión iniciada como {{userDisplayName}}",
|
||||
"Match Case": "Coincidir mayúsculas y minúsculas",
|
||||
"Match Diacritics": "Coincidir diacríticos",
|
||||
"Match Whole Words": "Coincidir palabras completas",
|
||||
"Maximum Block Size": "Tamaño máximo de bloque",
|
||||
"Maximum Inline Size": "Tamaño máximo en línea",
|
||||
"Maximum Number of Columns": "Número máximo de columnas",
|
||||
"Minimum Font Size": "Tamaño de fuente mínimo",
|
||||
"Misc": "Varios",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Aplicar a todos los libros",
|
||||
"Apply to This Book": "Aplicar a este libro",
|
||||
"Unable to fetch the translation. Try again later.": "No se puede obtener la traducción. Inténtalo de nuevo más tarde.",
|
||||
"Update Now!": "¡Actualizar ahora!",
|
||||
"Update": "Actualizar",
|
||||
"Check Update": "Comprobar actualización",
|
||||
"Already the latest version": "Ya es la última versión",
|
||||
"Book Details": "Detalles del libro",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Sangría de texto",
|
||||
"Paragraph Margin": "Margen de párrafo",
|
||||
"Override Book Layout": "Sobrescribir diseño del libro",
|
||||
"Add to Group": "Añadir a grupo",
|
||||
"Untitled Group": "Grupo sin título",
|
||||
"Group Books": "Agrupar libros",
|
||||
"Remove From Group": "Eliminar del grupo",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Eliminar permanentemente",
|
||||
"Free Tier": "Nivel gratuito",
|
||||
"Plus Tier": "Nivel Plus",
|
||||
"Pro Tier": "Nivel Pro"
|
||||
"Pro Tier": "Nivel Pro",
|
||||
"RTL Direction": "Dirección RTL",
|
||||
"Maximum Column Height": "Altura máxima de columna",
|
||||
"Maximum Column Width": "Ancho máximo de columna",
|
||||
"Continuous Scroll": "Desplazamiento continuo",
|
||||
"Fullscreen": "Pantalla completa",
|
||||
"No supported files found. Supported formats: {{formats}}": "No se encontraron archivos compatibles. Formatos compatibles: {{formats}}",
|
||||
"Drop to Import Books": "Soltar para importar libros",
|
||||
"Custom": "Personalizado",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Todo el mundo es un escenario,\nY todos los hombres y mujeres son meros actores;\nTienen sus salidas y sus entradas,\nY un hombre en su tiempo interpreta muchos papeles,\nSus actos son siete edades.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema personalizado",
|
||||
"Theme Name": "Nombre del tema",
|
||||
"Text Color": "Color del texto",
|
||||
"Background Color": "Color de fondo",
|
||||
"Preview": "Vista previa",
|
||||
"Contrast": "Contraste",
|
||||
"Sunset": "Atardecer",
|
||||
"Double Border": "Doble borde",
|
||||
"Border Color": "Color del borde",
|
||||
"Border Frame": "Marco de borde",
|
||||
"Show Header": "Mostrar encabezado",
|
||||
"Show Footer": "Mostrar pie de página",
|
||||
"Small": "Pequeño",
|
||||
"Large": "Grande",
|
||||
"Auto": "Automático",
|
||||
"Language": "Idioma",
|
||||
"No annotations to export": "No hay anotaciones para exportar",
|
||||
"Author": "Autor",
|
||||
"Exported from Readest": "Exportado desde Readest",
|
||||
"Highlights & Annotations": "Resaltados y anotaciones",
|
||||
"Note": "Nota",
|
||||
"Copied to clipboard": "Copiado al portapapeles",
|
||||
"Export Annotations": "Exportar anotaciones",
|
||||
"Auto Import on File Open": "Importación automática al abrir archivo",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "No se detectaron capítulos.",
|
||||
"Failed to parse the EPUB file.": "Error al analizar el archivo EPUB.",
|
||||
"This book format is not supported.": "Este formato de libro no es compatible.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "No se puede obtener la traducción. Por favor, inicia sesión primero e inténtalo de nuevo.",
|
||||
"Group": "Grupo",
|
||||
"Always on Top": "Siempre en la parte superior",
|
||||
"No Timeout": "Sin tiempo de espera",
|
||||
"{{value}} minute": "{{value}} minuto",
|
||||
"{{value}} minutes": "{{value}} minutos",
|
||||
"{{value}} hour": "{{value}} hora",
|
||||
"{{value}} hours": "{{value}} horas",
|
||||
"CJK Font": "Fuente CJK",
|
||||
"Clear Search": "Limpiar búsqueda",
|
||||
"Header & Footer": "Encabezado y pie de página",
|
||||
"Apply also in Scrolled Mode": "Aplicar también en modo desplazamiento",
|
||||
"A new version of Readest is Available!": "¡Una nueva versión de Readest está disponible!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} está disponible (versión instalada {{currentVersion}}).",
|
||||
"Download and install now?": "Descargar e instalar ahora?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Descargando {{downloaded}} de {{contentLength}}",
|
||||
"Download finished": "Descarga finalizada",
|
||||
"DOWNLOAD & INSTALL": "DESCARGAR E INSTALAR",
|
||||
"Changelog:": "Notas de la versión:",
|
||||
"Software Update": "Actualización de software",
|
||||
"Title": "Título",
|
||||
"Date Read": "Fecha de lectura",
|
||||
"Date Added": "Fecha añadida",
|
||||
"Format": "Formato",
|
||||
"Ascending": "Ascendente",
|
||||
"Descending": "Descendente",
|
||||
"Sort by...": "Ordenar por...",
|
||||
"Added:": "Agregado:",
|
||||
"Format:": "Formato:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Descripción:",
|
||||
"No description available": "No hay descripción disponible",
|
||||
"List": "Lista",
|
||||
"Grid": "Cuadrícula",
|
||||
"(from 'As You Like It', Act II)": "(de 'Como gustéis', Acto II)",
|
||||
"Link Color": "Color del enlace",
|
||||
"Volume Keys for Page Flip": "Botones de volumen para pasar página",
|
||||
"Clicks for Page Flip": "Toques para pasar página",
|
||||
"Swap Clicks Area": "Invertir zonas de toque"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Supprimer",
|
||||
"Delete Highlight": "Supprimer le surlignage",
|
||||
"Dictionary": "Dictionnaire",
|
||||
"Disable Click-to-Flip": "Désactiver le clic pour tourner",
|
||||
"Download Readest": "Télécharger Readest",
|
||||
"Edit": "Modifier",
|
||||
"Enter your custom CSS here...": "Saisissez votre CSS personnalisé ici...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Césure",
|
||||
"Identifier:": "Identifiant :",
|
||||
"Import Books": "Importer des livres",
|
||||
"Invert Colors in Dark Mode": "Inverser les couleurs en mode sombre",
|
||||
"Language:": "Langue :",
|
||||
"Layout": "Mise en page",
|
||||
"Light Mode": "Mode clair",
|
||||
"Loading...": "Chargement...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Pos. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Connecté",
|
||||
"Logged in as {{userDisplayName}}": "Connecté en tant que {{userDisplayName}}",
|
||||
"Match Case": "Respecter la casse",
|
||||
"Match Diacritics": "Respecter les accents",
|
||||
"Match Whole Words": "Mots entiers",
|
||||
"Maximum Block Size": "Taille maximale du bloc",
|
||||
"Maximum Inline Size": "Taille maximale en ligne",
|
||||
"Maximum Number of Columns": "Nombre maximal de colonnes",
|
||||
"Minimum Font Size": "Taille minimale de police",
|
||||
"Misc": "Divers",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Appliquer à tous les livres",
|
||||
"Apply to This Book": "Appliquer à ce livre",
|
||||
"Unable to fetch the translation. Try again later.": "Impossible de récupérer la traduction. Réessayez plus tard.",
|
||||
"Update Now!": "Mettre à jour maintenant !",
|
||||
"Update": "Mettre à jour",
|
||||
"Check Update": "Vérifier la mise à jour",
|
||||
"Already the latest version": "Déjà la dernière version",
|
||||
"Book Details": "Détails du livre",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Retrait du texte",
|
||||
"Paragraph Margin": "Marge de paragraphe",
|
||||
"Override Book Layout": "Remplacer la mise en page du livre",
|
||||
"Add to Group": "Ajouter au groupe",
|
||||
"Untitled Group": "Groupe sans titre",
|
||||
"Group Books": "Regrouper les livres",
|
||||
"Remove From Group": "Retirer du groupe",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Supprimer définitivement",
|
||||
"Free Tier": "Niveau gratuit",
|
||||
"Plus Tier": "Niveau Plus",
|
||||
"Pro Tier": "Niveau Pro"
|
||||
"Pro Tier": "Niveau Pro",
|
||||
"RTL Direction": "Direction RTL",
|
||||
"Maximum Column Height": "Hauteur maximale de colonne",
|
||||
"Maximum Column Width": "Largeur maximale de colonne",
|
||||
"Continuous Scroll": "Défilement continu",
|
||||
"Fullscreen": "Plein écran",
|
||||
"No supported files found. Supported formats: {{formats}}": "Aucun fichier pris en charge trouvé. Formats pris en charge : {{formats}}",
|
||||
"Drop to Import Books": "Déposez pour importer des livres",
|
||||
"Custom": "Personnalisé",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Le monde entier est un théâtre,\nEt tous les hommes et les femmes ne sont que des acteurs ;\nIls ont leurs sorties et leurs entrées,\nEt un homme, dans sa vie, joue de nombreux rôles,\nSes actes étant sept âges.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Thème personnalisé",
|
||||
"Theme Name": "Nom du thème",
|
||||
"Text Color": "Couleur du texte",
|
||||
"Background Color": "Couleur de fond",
|
||||
"Preview": "Aperçu",
|
||||
"Contrast": "Contraste",
|
||||
"Sunset": "Coucher de soleil",
|
||||
"Double Border": "Double bordure",
|
||||
"Border Color": "Couleur de la bordure",
|
||||
"Border Frame": "Cadre de bordure",
|
||||
"Show Header": "Afficher l'en-tête",
|
||||
"Show Footer": "Afficher le pied de page",
|
||||
"Small": "Petit",
|
||||
"Large": "Grand",
|
||||
"Auto": "Automatique",
|
||||
"Language": "Langue",
|
||||
"No annotations to export": "Aucune annotation à exporter",
|
||||
"Author": "Auteur",
|
||||
"Exported from Readest": "Exporté depuis Readest",
|
||||
"Highlights & Annotations": "Surlignages et annotations",
|
||||
"Note": "Note",
|
||||
"Copied to clipboard": "Copié dans le presse-papiers",
|
||||
"Export Annotations": "Exporter les annotations",
|
||||
"Auto Import on File Open": "Importation automatique à l'ouverture du fichier",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Aucun chapitre détecté.",
|
||||
"Failed to parse the EPUB file.": "Échec de l'analyse du fichier EPUB.",
|
||||
"This book format is not supported.": "Ce format de livre n'est pas pris en charge.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Impossible de récupérer la traduction. Veuillez d'abord vous connecter et réessayer.",
|
||||
"Group": "Groupe",
|
||||
"Always on Top": "Toujours au-dessus",
|
||||
"No Timeout": "Aucun délai d'attente",
|
||||
"{{value}} minute": "{{value}} minute",
|
||||
"{{value}} minutes": "{{value}} minutes",
|
||||
"{{value}} hour": "{{value}} heure",
|
||||
"{{value}} hours": "{{value}} heures",
|
||||
"CJK Font": "Police CJK",
|
||||
"Clear Search": "Effacer la recherche",
|
||||
"Header & Footer": "En-tête et pied de page",
|
||||
"Apply also in Scrolled Mode": "Appliquer également en mode défilement",
|
||||
"A new version of Readest is Available!": "Avis de mise à jour",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} est disponible (version installée {{currentVersion}}).",
|
||||
"Download and install now?": "Télécharger et installer maintenant ?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Téléchargement {{downloaded}} sur {{contentLength}}",
|
||||
"Download finished": "Téléchargement terminé",
|
||||
"DOWNLOAD & INSTALL": "TÉLÉCHARGER ET INSTALLER",
|
||||
"Changelog:": "Journal des modifications :",
|
||||
"Software Update": "Mise à jour logicielle",
|
||||
"Title": "Titre",
|
||||
"Date Read": "Date de lecture",
|
||||
"Date Added": "Date ajoutée",
|
||||
"Format": "Format",
|
||||
"Ascending": "Ascendant",
|
||||
"Descending": "Descendant",
|
||||
"Sort by...": "Trier par...",
|
||||
"Added:": "Ajouté :",
|
||||
"Format:": "Format :",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Description :",
|
||||
"No description available": "Aucune description disponible",
|
||||
"List": "Liste",
|
||||
"Grid": "Grille",
|
||||
"(from 'As You Like It', Act II)": "(de 'Comme il vous plaira', Acte II)",
|
||||
"Link Color": "Couleur du lien",
|
||||
"Volume Keys for Page Flip": "Boutons volume pour tourner la page",
|
||||
"Clicks for Page Flip": "Taps pour tourner la page",
|
||||
"Swap Clicks Area": "Inverser zones de tap"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "हटाएं",
|
||||
"Delete Highlight": "हाइलाइट हटाएं",
|
||||
"Dictionary": "शब्दकोश",
|
||||
"Disable Click-to-Flip": "क्लिक-टू-फ्लिप अक्षम करें",
|
||||
"Download Readest": "Readest डाउनलोड करें",
|
||||
"Edit": "संपादित करें",
|
||||
"Enter your custom CSS here...": "अपना कस्टम CSS यहां दर्ज करें...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "हाइफ़नेशन",
|
||||
"Identifier:": "पहचानकर्ता:",
|
||||
"Import Books": "पुस्तकें आयात करें",
|
||||
"Invert Colors in Dark Mode": "डार्क मोड में रंग उलटें",
|
||||
"Language:": "भाषा:",
|
||||
"Layout": "लेआउट",
|
||||
"Light Mode": "लाइट मोड",
|
||||
"Loading...": "लोड हो रहा है...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "स्थान {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "लॉग इन किया",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}} के रूप में लॉग इन किया",
|
||||
"Match Case": "केस मैच करें",
|
||||
"Match Diacritics": "डायाक्रिटिक्स मैच करें",
|
||||
"Match Whole Words": "पूरे शब्द मैच करें",
|
||||
"Maximum Block Size": "अधिकतम ब्लॉक साइज़",
|
||||
"Maximum Inline Size": "अधिकतम इनलाइन साइज़",
|
||||
"Maximum Number of Columns": "कॉलम की अधिकतम संख्या",
|
||||
"Minimum Font Size": "न्यूनतम फ़ॉन्ट साइज़",
|
||||
"Misc": "विविध",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "सभी पुस्तकों पर लागू करें",
|
||||
"Apply to This Book": "इस पुस्तक पर लागू करें",
|
||||
"Unable to fetch the translation. Try again later.": "अनुवाद प्राप्त करने में असमर्थ। बाद में पुनः प्रयास करें।",
|
||||
"Update Now!": "अभी अपडेट करें!",
|
||||
"Update": "अपडेट करें",
|
||||
"Check Update": "अपडेट जांचें",
|
||||
"Already the latest version": "पहले से ही नवीनतम संस्करण",
|
||||
"Book Details": "पुस्तक विवरण",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "टेक्स्ट इंडेंट",
|
||||
"Paragraph Margin": "पैराग्राफ मार्जिन",
|
||||
"Override Book Layout": "पुस्तक लेआउट ओवरराइड करें",
|
||||
"Add to Group": "समूह में जोड़ें",
|
||||
"Untitled Group": "अभिनामित समूह",
|
||||
"Group Books": "समूह पुस्तकें",
|
||||
"Remove From Group": "समूह से हटाएं",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "स्थायी रूप से हटाएं",
|
||||
"Free Tier": "फ्री टियर",
|
||||
"Plus Tier": "प्लस टियर",
|
||||
"Pro Tier": "प्रो टियर"
|
||||
"Pro Tier": "प्रो टियर",
|
||||
"RTL Direction": "RTL दिशा",
|
||||
"Maximum Column Height": "कॉलम की अधिकतम ऊँचाई",
|
||||
"Maximum Column Width": "कॉलम की अधिकतम चौड़ाई",
|
||||
"Continuous Scroll": "निरंतर स्क्रॉल",
|
||||
"Fullscreen": "पूर्णस्क्रीन",
|
||||
"No supported files found. Supported formats: {{formats}}": "कोई समर्थित फ़ाइलें नहीं मिलीं। समर्थित प्रारूप: {{formats}}",
|
||||
"Drop to Import Books": "पुस्तकें आयात करने के लिए छोड़ें",
|
||||
"Custom": "कस्टम",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "पूरी दुनिया एक स्टेज है,\nऔर सभी आदमी और महिलाएँ केवल अभिनेता हैं;\nउनके निकास और प्रवेश होते हैं,\nऔर एक आदमी अपने समय में कई भूमिकाएँ निभाता है,\nउसके कार्य सात युग होते हैं।\n\n— विलियम शेक्सपियर",
|
||||
"Custom Theme": "कस्टम थीम",
|
||||
"Theme Name": "थीम का नाम",
|
||||
"Text Color": "टेक्स्ट रंग",
|
||||
"Background Color": "पृष्ठभूमि रंग",
|
||||
"Preview": "पूर्वावलोकन",
|
||||
"Contrast": "विरोधाभास",
|
||||
"Sunset": "सनसेट",
|
||||
"Double Border": "डबल बॉर्डर",
|
||||
"Border Color": "बॉर्डर रंग",
|
||||
"Border Frame": "बॉर्डर फ्रेम",
|
||||
"Show Header": "हेडर दिखाएं",
|
||||
"Show Footer": "फ़ुटर दिखाएं",
|
||||
"Small": "छोटा",
|
||||
"Large": "बड़ा",
|
||||
"Auto": "स्वचालित",
|
||||
"Language": "भाषा",
|
||||
"No annotations to export": "निर्यात करने के लिए कोई टिप्पणियाँ नहीं हैं",
|
||||
"Author": "लेखक",
|
||||
"Exported from Readest": "Readest से निर्यात किया गया",
|
||||
"Highlights & Annotations": "हाइलाइट और टिप्पणियाँ",
|
||||
"Note": "टिप्पणी",
|
||||
"Copied to clipboard": "क्लिपबोर्ड पर कॉपी किया गया",
|
||||
"Export Annotations": "टिप्पणियाँ निर्यात करें",
|
||||
"Auto Import on File Open": "फ़ाइल खोलने पर स्वचालित आयात",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "कोई अध्याय नहीं मिला।",
|
||||
"Failed to parse the EPUB file.": "EPUB फ़ाइल को पार्स करने में विफल।",
|
||||
"This book format is not supported.": "यह पुस्तक प्रारूप समर्थित नहीं है।",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "अनुवाद प्राप्त करने में असमर्थ। कृपया पहले लॉग इन करें और पुनः प्रयास करें।",
|
||||
"Group": "समूह",
|
||||
"Always on Top": "सर्वोच्च पर हमेशा",
|
||||
"No Timeout": "कोई समय समाप्त नहीं",
|
||||
"{{value}} minute": "{{value}} मिनट",
|
||||
"{{value}} minutes": "{{value}} मिनट",
|
||||
"{{value}} hour": "{{value}} घंटा",
|
||||
"{{value}} hours": "{{value}} घंटे",
|
||||
"CJK Font": "CJK फ़ॉन्ट",
|
||||
"Clear Search": "खोज साफ करें",
|
||||
"Header & Footer": "हेडर और फ़ुटर",
|
||||
"Apply also in Scrolled Mode": "स्क्रॉल मोड में भी लागू करें",
|
||||
"A new version of Readest is Available!": "Readest का एक नया संस्करण उपलब्ध है!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} उपलब्ध है (स्थापित संस्करण {{currentVersion}})।",
|
||||
"Download and install now?": "अब डाउनलोड और इंस्टॉल करें?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "डाउनलोड कर रहा है {{downloaded}} का {{contentLength}}",
|
||||
"Download finished": "डाउनलोड पूरा हुआ",
|
||||
"DOWNLOAD & INSTALL": "डाउनलोड और इंस्टॉल करें",
|
||||
"Changelog:": "चेंजलॉग:",
|
||||
"Software Update": "सॉफ़्टवेयर अपडेट",
|
||||
"Title": "शीर्षक",
|
||||
"Date Read": "पढ़ने की तारीख",
|
||||
"Date Added": "जोड़ा गया दिनांक",
|
||||
"Format": "फॉर्मेट",
|
||||
"Ascending": "आरोही",
|
||||
"Descending": "अवरोही",
|
||||
"Sort by...": "द्वारा क्रमबद्ध करें...",
|
||||
"Added:": "जोड़ा गया:",
|
||||
"Format:": "फॉर्मेट:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "विवरण:",
|
||||
"No description available": "कोई विवरण उपलब्ध नहीं है",
|
||||
"List": "सूची",
|
||||
"Grid": "ग्रिड",
|
||||
"(from 'As You Like It', Act II)": "('जैसा आप इसे पसंद करते हैं', अधिनियम II)",
|
||||
"Link Color": "लिंक रंग",
|
||||
"Volume Keys for Page Flip": "पृष्ठ फ़्लिप के लिए वॉल्यूम कुंजियाँ",
|
||||
"Clicks for Page Flip": "पृष्ठ फ़्लिप के लिए क्लिक करें",
|
||||
"Swap Clicks Area": "क्लिक क्षेत्र स्वैप करें"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Hapus",
|
||||
"Delete Highlight": "Hapus Sorotan",
|
||||
"Dictionary": "Kamus",
|
||||
"Disable Click-to-Flip": "Nonaktifkan Klik untuk Membalik",
|
||||
"Download Readest": "Unduh Readest",
|
||||
"Edit": "Edit",
|
||||
"Enter your custom CSS here...": "Masukkan CSS kustom Anda di sini...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Pemenggalan Kata",
|
||||
"Identifier:": "Pengenal:",
|
||||
"Import Books": "Impor Buku",
|
||||
"Invert Colors in Dark Mode": "Balik Warna di Mode Gelap",
|
||||
"Language:": "Bahasa:",
|
||||
"Layout": "Tata Letak",
|
||||
"Light Mode": "Mode Terang",
|
||||
"Loading...": "Memuat...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Lok. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Sudah Masuk",
|
||||
"Logged in as {{userDisplayName}}": "Masuk sebagai {{userDisplayName}}",
|
||||
"Match Case": "Cocokkan Huruf Besar/Kecil",
|
||||
"Match Diacritics": "Cocokkan Diakritik",
|
||||
"Match Whole Words": "Cocokkan Kata Utuh",
|
||||
"Maximum Block Size": "Ukuran Blok Maksimum",
|
||||
"Maximum Inline Size": "Ukuran Sebaris Maksimum",
|
||||
"Maximum Number of Columns": "Jumlah Kolom Maksimum",
|
||||
"Minimum Font Size": "Ukuran Font Minimum",
|
||||
"Misc": "Lain-lain",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Terapkan ke semua buku",
|
||||
"Apply to This Book": "Terapkan ke buku ini",
|
||||
"Unable to fetch the translation. Try again later.": "Tidak dapat mengambil terjemahan. Coba lagi nanti.",
|
||||
"Update Now!": "Perbarui Sekarang!",
|
||||
"Update": "Perbarui",
|
||||
"Check Update": "Periksa pembaruan",
|
||||
"Already the latest version": "Sudah versi terbaru",
|
||||
"Book Details": "Detail Buku",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Indentasi Teks",
|
||||
"Paragraph Margin": "Margin Paragraf",
|
||||
"Override Book Layout": "Ganti Tata Letak Buku",
|
||||
"Add to Group": "Tambahkan ke Grup",
|
||||
"Untitled Group": "Grup Tanpa Judul",
|
||||
"Group Books": "Grupkan Buku",
|
||||
"Remove From Group": "Hapus dari Grup",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Hapus Secara Permanen",
|
||||
"Free Tier": "Tingkat Gratis",
|
||||
"Plus Tier": "Tingkat Plus",
|
||||
"Pro Tier": "Tingkat Pro"
|
||||
"Pro Tier": "Tingkat Pro",
|
||||
"RTL Direction": "Arah RTL",
|
||||
"Maximum Column Height": "Tinggi Kolom Maksimum",
|
||||
"Maximum Column Width": "Lebar Kolom Maksimum",
|
||||
"Continuous Scroll": "Gulir Terus",
|
||||
"Fullscreen": "Layar Penuh",
|
||||
"No supported files found. Supported formats: {{formats}}": "Tidak ada file yang didukung ditemukan. Format yang didukung: {{formats}}",
|
||||
"Drop to Import Books": "Jatuhkan untuk Mengimpor Buku",
|
||||
"Custom": "Kustom",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Seluruh dunia adalah panggung,\nDan semua pria dan wanita hanyalah pemain;\nMereka memiliki keluar dan masuk mereka,\nDan seorang pria dalam hidupnya memainkan banyak peran,\nTindakannya ada tujuh zaman.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema Kustom",
|
||||
"Theme Name": "Nama Tema",
|
||||
"Text Color": "Warna Teks",
|
||||
"Background Color": "Warna Latar Belakang",
|
||||
"Preview": "Pratinjau",
|
||||
"Contrast": "Kontras",
|
||||
"Sunset": "Senja",
|
||||
"Double Border": "Garis Ganda",
|
||||
"Border Color": "Warna Garis",
|
||||
"Border Frame": "Bingkai Garis",
|
||||
"Show Header": "Tampilkan Header",
|
||||
"Show Footer": "Tampilkan Footer",
|
||||
"Small": "Kecil",
|
||||
"Large": "Besar",
|
||||
"Auto": "Otomatis",
|
||||
"Language": "Bahasa",
|
||||
"No annotations to export": "Tidak ada anotasi yang diekspor",
|
||||
"Author": "Penulis",
|
||||
"Exported from Readest": "Diekspor dari Readest",
|
||||
"Highlights & Annotations": "Sorotan & Anotasi",
|
||||
"Note": "Catatan",
|
||||
"Copied to clipboard": "Disalin ke papan klip",
|
||||
"Export Annotations": "Ekspor Anotasi",
|
||||
"Auto Import on File Open": "Impor Otomatis saat File Dibuka",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Tidak ada bab yang terdeteksi.",
|
||||
"Failed to parse the EPUB file.": "Gagal mengurai file EPUB.",
|
||||
"This book format is not supported.": "Format buku ini tidak didukung.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Tidak dapat mengambil terjemahan. Silakan masuk terlebih dahulu dan coba lagi.",
|
||||
"Group": "Grup",
|
||||
"Always on Top": "Selalu di Atas",
|
||||
"No Timeout": "Tidak Ada Waktu Habis",
|
||||
"{{value}} minute": "{{value}} menit",
|
||||
"{{value}} minutes": "{{value}} menit",
|
||||
"{{value}} hour": "{{value}} jam",
|
||||
"{{value}} hours": "{{value}} jam",
|
||||
"CJK Font": "Font CJK",
|
||||
"Clear Search": "Hapus Pencarian",
|
||||
"Header & Footer": "Header & Footer",
|
||||
"Apply also in Scrolled Mode": "Terapkan juga di Mode Gulir",
|
||||
"A new version of Readest is Available!": "Versi baru Readest Tersedia!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} tersedia (versi terinstal {{currentVersion}}).",
|
||||
"Download and install now?": "Unduh dan instal sekarang?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Mengunduh {{downloaded}} dari {{contentLength}}",
|
||||
"Download finished": "Unduhan selesai",
|
||||
"DOWNLOAD & INSTALL": "UNDUH & INSTAL",
|
||||
"Changelog:": "Catatan Perubahan:",
|
||||
"Software Update": "Pembaruan Perangkat Lunak",
|
||||
"Title": "Judul",
|
||||
"Date Read": "Tanggal Dibaca",
|
||||
"Date Added": "Tanggal Ditambahkan",
|
||||
"Format": "Format",
|
||||
"Ascending": "Naik",
|
||||
"Descending": "Turun",
|
||||
"Sort by...": "Urutkan berdasarkan...",
|
||||
"Added:": "Ditambahkan:",
|
||||
"Format:": "Format:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Deskripsi:",
|
||||
"No description available": "Tidak ada deskripsi yang tersedia",
|
||||
"List": "Daftar",
|
||||
"Grid": "Jala",
|
||||
"(from 'As You Like It', Act II)": "(dari 'Seperti yang Anda Inginkan', Bab II)",
|
||||
"Link Color": "Warna Tautan",
|
||||
"Volume Keys for Page Flip": "Volume Keys untuk Pembalikan Halaman",
|
||||
"Clicks for Page Flip": "Klik untuk Pembalikan Halaman",
|
||||
"Swap Clicks Area": "Area Klik Tukar"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Elimina",
|
||||
"Delete Highlight": "Elimina evidenziazione",
|
||||
"Dictionary": "Dizionario",
|
||||
"Disable Click-to-Flip": "Disattiva click per voltare pagina",
|
||||
"Download Readest": "Scarica Readest",
|
||||
"Edit": "Modifica",
|
||||
"Enter your custom CSS here...": "Inserisci qui il tuo CSS personalizzato...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Sillabazione",
|
||||
"Identifier:": "Identificatore:",
|
||||
"Import Books": "Importa libri",
|
||||
"Invert Colors in Dark Mode": "Inverti colori in modalità scura",
|
||||
"Language:": "Lingua:",
|
||||
"Layout": "Layout",
|
||||
"Light Mode": "Modalità chiara",
|
||||
"Loading...": "Caricamento...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Pos. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Accesso effettuato",
|
||||
"Logged in as {{userDisplayName}}": "Accesso effettuato come {{userDisplayName}}",
|
||||
"Match Case": "Maiuscole/minuscole",
|
||||
"Match Diacritics": "Corrispondenza diacritici",
|
||||
"Match Whole Words": "Parole intere",
|
||||
"Maximum Block Size": "Dimensione massima blocco",
|
||||
"Maximum Inline Size": "Dimensione massima in linea",
|
||||
"Maximum Number of Columns": "Numero massimo di colonne",
|
||||
"Minimum Font Size": "Dimensione minima font",
|
||||
"Misc": "Varie",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Applica a tutti i libri",
|
||||
"Apply to This Book": "Applica a questo libro",
|
||||
"Unable to fetch the translation. Try again later.": "Impossibile recuperare la traduzione. Riprova più tardi.",
|
||||
"Update Now!": "Aggiorna ora!",
|
||||
"Update": "Aggiorna",
|
||||
"Check Update": "Controlla aggiornamento",
|
||||
"Already the latest version": "Già l'ultima versione",
|
||||
"Book Details": "Dettagli libro",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Rientro testo",
|
||||
"Paragraph Margin": "Margine paragrafo",
|
||||
"Override Book Layout": "Sovrascrivi layout libro",
|
||||
"Add to Group": "Aggiungi al gruppo",
|
||||
"Untitled Group": "Gruppo senza titolo",
|
||||
"Group Books": "Raggruppa libri",
|
||||
"Remove From Group": "Rimuovi dal",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Elimina definitivamente",
|
||||
"Free Tier": "Piano gratuito",
|
||||
"Plus Tier": "Piano Plus",
|
||||
"Pro Tier": "Piano Pro"
|
||||
"Pro Tier": "Piano Pro",
|
||||
"RTL Direction": "Direzione RTL",
|
||||
"Maximum Column Height": "Altezza massima colonna",
|
||||
"Maximum Column Width": "Larghezza massima colonna",
|
||||
"Continuous Scroll": "Scorrimento continuo",
|
||||
"Fullscreen": "Schermo intero",
|
||||
"No supported files found. Supported formats: {{formats}}": "Nessun file supportato trovato. Formati supportati: {{formats}}",
|
||||
"Drop to Import Books": "Rilascia per importare libri",
|
||||
"Custom": "Personalizzato",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Tutto il mondo è un palcoscenico,\nE tutti gli uomini e le donne sono solo attori;\nHanno le loro uscite e le loro entrate,\nE un uomo nella sua vita interpreta molte parti,\nI suoi atti sono sette età.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema personalizzato",
|
||||
"Theme Name": "Nome tema",
|
||||
"Text Color": "Colore testo",
|
||||
"Background Color": "Colore sfondo",
|
||||
"Preview": "Anteprima",
|
||||
"Contrast": "Contrasto",
|
||||
"Sunset": "Tramonto",
|
||||
"Double Border": "Doppio bordo",
|
||||
"Border Color": "Colore bordo",
|
||||
"Border Frame": "Cornice bordo",
|
||||
"Show Header": "Mostra intestazione",
|
||||
"Show Footer": "Mostra piè di pagina",
|
||||
"Small": "Piccolo",
|
||||
"Large": "Grande",
|
||||
"Auto": "Automatico",
|
||||
"Language": "Lingua",
|
||||
"No annotations to export": "Nessuna annotazione da esportare",
|
||||
"Author": "Autore",
|
||||
"Exported from Readest": "Esportato da Readest",
|
||||
"Highlights & Annotations": "Evidenziazioni e annotazioni",
|
||||
"Note": "Nota",
|
||||
"Copied to clipboard": "Copiato negli appunti",
|
||||
"Export Annotations": "Esporta annotazioni",
|
||||
"Auto Import on File Open": "Importazione automatica all'apertura del file",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Nessun capitolo rilevato.",
|
||||
"Failed to parse the EPUB file.": "Impossibile analizzare il file EPUB.",
|
||||
"This book format is not supported.": "Questo formato di libro non è supportato.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Impossibile recuperare la traduzione. Accedi prima e riprova.",
|
||||
"Group": "Gruppo",
|
||||
"Always on Top": "Sempre in primo piano",
|
||||
"No Timeout": "Nessun timeout",
|
||||
"{{value}} minute": "{{value}} minuto",
|
||||
"{{value}} minutes": "{{value}} minuti",
|
||||
"{{value}} hour": "{{value}} ora",
|
||||
"{{value}} hours": "{{value}} ore",
|
||||
"CJK Font": "Font CJK",
|
||||
"Clear Search": "Cancella ricerca",
|
||||
"Header & Footer": "Intestazione e piè di pagina",
|
||||
"Apply also in Scrolled Mode": "Applica anche in modalità scorrimento",
|
||||
"A new version of Readest is Available!": "È disponibile una nuova versione di Readest!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} è disponibile (versione installata {{currentVersion}}).",
|
||||
"Download and install now?": "Scarica e installa ora?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Scaricando {{downloaded}} di {{contentLength}}",
|
||||
"Download finished": "Download completato",
|
||||
"DOWNLOAD & INSTALL": "SCARICA E INSTALLA",
|
||||
"Changelog:": "Note di rilascio:",
|
||||
"Software Update": "Aggiornamento software",
|
||||
"Title": "Titolo",
|
||||
"Date Read": "Data di lettura",
|
||||
"Date Added": "Data aggiunta",
|
||||
"Format": "Formato",
|
||||
"Ascending": "Crescente",
|
||||
"Descending": "Decrescente",
|
||||
"Sort by...": "Ordina per...",
|
||||
"Added:": "Aggiunto:",
|
||||
"Format:": "Formato:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Descrizione:",
|
||||
"No description available": "Nessuna descrizione disponibile",
|
||||
"List": "Elenco",
|
||||
"Grid": "Griglia",
|
||||
"(from 'As You Like It', Act II)": "(da 'Come vi piace', Atto II)",
|
||||
"Link Color": "Colore link",
|
||||
"Volume Keys for Page Flip": "Tasti volume per girare pagina",
|
||||
"Clicks for Page Flip": "Clic per girare pagina",
|
||||
"Swap Clicks Area": "Inverti area dei clic"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "削除",
|
||||
"Delete Highlight": "ハイライトを削除",
|
||||
"Dictionary": "辞書",
|
||||
"Disable Click-to-Flip": "クリックめくりを無効化",
|
||||
"Download Readest": "Readestをダウンロード",
|
||||
"Edit": "編集",
|
||||
"Enter your custom CSS here...": "ここにカスタムCSSを入力...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "ハイフネーション",
|
||||
"Identifier:": "識別子:",
|
||||
"Import Books": "書籍をインポート",
|
||||
"Invert Colors in Dark Mode": "ダークモードで色を反転",
|
||||
"Language:": "言語:",
|
||||
"Layout": "レイアウト",
|
||||
"Light Mode": "ライトモード",
|
||||
"Loading...": "読み込み中...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "位置 {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "ログイン済み",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}}としてログイン中",
|
||||
"Match Case": "大文字・小文字を区別",
|
||||
"Match Diacritics": "アクセント記号を区別",
|
||||
"Match Whole Words": "単語全体を一致",
|
||||
"Maximum Block Size": "最大ブロックサイズ",
|
||||
"Maximum Inline Size": "最大インラインサイズ",
|
||||
"Maximum Number of Columns": "最大列数",
|
||||
"Minimum Font Size": "最小フォントサイズ",
|
||||
"Misc": "その他",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "すべての書籍に適用",
|
||||
"Apply to This Book": "この書籍に適用",
|
||||
"Unable to fetch the translation. Try again later.": "翻訳を取得できません。後で再試行してください。",
|
||||
"Update Now!": "今すぐ更新!",
|
||||
"Update": "更新",
|
||||
"Check Update": "更新を確認",
|
||||
"Already the latest version": "すでに最新バージョン",
|
||||
"Book Details": "書籍の詳細",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "テキストインデント",
|
||||
"Paragraph Margin": "段落マージン",
|
||||
"Override Book Layout": "書籍のレイアウトを上書き",
|
||||
"Add to Group": "グループに追加",
|
||||
"Untitled Group": "無題のグループ",
|
||||
"Group Books": "書籍をグループ化",
|
||||
"Remove From Group": "グループから削除",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "完全に削除",
|
||||
"Free Tier": "無料プラン",
|
||||
"Plus Tier": "Plusプラン",
|
||||
"Pro Tier": "Proプラン"
|
||||
"Pro Tier": "Proプラン",
|
||||
"RTL Direction": "RTL方向",
|
||||
"Maximum Column Height": "最大列高",
|
||||
"Maximum Column Width": "最大列幅",
|
||||
"Continuous Scroll": "連続スクロール",
|
||||
"Fullscreen": "全画面",
|
||||
"No supported files found. Supported formats: {{formats}}": "サポートされているファイルが見つかりません。サポートされている形式:{{formats}}",
|
||||
"Drop to Import Books": "書籍をインポートするにはドロップ",
|
||||
"Custom": "カスタム",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "全世界は舞台であり、\nそしてすべての男性と女性は単なる役者です。\n彼らは出入り口を持っており、\nそして一人の男性は時に多くの役を演じます。\n彼の行為は七つの時代です。\n\n— ウィリアム・シェイクスピア",
|
||||
"Custom Theme": "カスタムテーマ",
|
||||
"Theme Name": "テーマ名",
|
||||
"Text Color": "テキストカラー",
|
||||
"Background Color": "背景色",
|
||||
"Preview": "プレビュー",
|
||||
"Contrast": "コントラスト",
|
||||
"Sunset": "サンセット",
|
||||
"Double Border": "二重枠",
|
||||
"Border Color": "ボーダーカラー",
|
||||
"Border Frame": "ボーダーフレーム",
|
||||
"Show Header": "ヘッダーを表示",
|
||||
"Show Footer": "フッターを表示",
|
||||
"Small": "小",
|
||||
"Large": "大",
|
||||
"Auto": "自動",
|
||||
"Language": "言語",
|
||||
"No annotations to export": "エクスポートする注釈はありません",
|
||||
"Author": "著者",
|
||||
"Exported from Readest": "Readestからエクスポート",
|
||||
"Highlights & Annotations": "ハイライトと注釈",
|
||||
"Note": "メモ",
|
||||
"Copied to clipboard": "クリップボードにコピー",
|
||||
"Export Annotations": "注釈をエクスポート",
|
||||
"Auto Import on File Open": "ファイルを開くと自動インポート",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "章が検出されません。",
|
||||
"Failed to parse the EPUB file.": "EPUBファイルの解析に失敗しました。",
|
||||
"This book format is not supported.": "この書籍形式はサポートされていません。",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "翻訳を取得できません。まずログインして再試行してください。",
|
||||
"Group": "グループ",
|
||||
"Always on Top": "常に最前面",
|
||||
"No Timeout": "タイムアウトなし",
|
||||
"{{value}} minute": "{{value}}分",
|
||||
"{{value}} minutes": "{{value}}分",
|
||||
"{{value}} hour": "{{value}}時間",
|
||||
"{{value}} hours": "{{value}}時間",
|
||||
"CJK Font": "CJKフォント",
|
||||
"Clear Search": "検索をクリア",
|
||||
"Header & Footer": "ヘッダーとフッター",
|
||||
"Apply also in Scrolled Mode": "スクロールモードでも適用",
|
||||
"A new version of Readest is Available!": "新しいバージョンのReadestが利用可能です!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}}が利用可能です(インストールされたバージョン{{currentVersion}})。",
|
||||
"Download and install now?": "今すぐダウンロードしてインストールしますか?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "ダウンロード中 {{downloaded}} / {{contentLength}}",
|
||||
"Download finished": "ダウンロード完了",
|
||||
"DOWNLOAD & INSTALL": "更新",
|
||||
"Changelog:": "変更ログ:",
|
||||
"Software Update": "ソフトウェアの更新",
|
||||
"Title": "タイトル",
|
||||
"Date Read": "読書日",
|
||||
"Date Added": "追加日",
|
||||
"Format": "形式",
|
||||
"Ascending": "昇順",
|
||||
"Descending": "降順",
|
||||
"Sort by...": "並べ替え...",
|
||||
"Added:": "追加日:",
|
||||
"Format:": "形式:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "説明:",
|
||||
"No description available": "説明はありません",
|
||||
"List": "リスト",
|
||||
"Grid": "グリッド",
|
||||
"(from 'As You Like It', Act II)": "(「お気に召すまま」第2幕)",
|
||||
"Link Color": "リンクカラー",
|
||||
"Volume Keys for Page Flip": "音量キーでページめくり",
|
||||
"Clicks for Page Flip": "タップでページめくり",
|
||||
"Swap Clicks Area": "タップ領域を入れ替え"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "삭제",
|
||||
"Delete Highlight": "하이라이트 삭제",
|
||||
"Dictionary": "사전",
|
||||
"Disable Click-to-Flip": "클릭 넘기기 비활성화",
|
||||
"Download Readest": "Readest 다운로드",
|
||||
"Edit": "편집",
|
||||
"Enter your custom CSS here...": "여기에 사용자 정의 CSS를 입력하세요...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "하이픈 넣기",
|
||||
"Identifier:": "식별자:",
|
||||
"Import Books": "책 가져오기",
|
||||
"Invert Colors in Dark Mode": "다크 모드에서 색상 반전",
|
||||
"Language:": "언어:",
|
||||
"Layout": "레이아웃",
|
||||
"Light Mode": "라이트 모드",
|
||||
"Loading...": "로딩 중...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "위치 {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "로그인됨",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}}(으)로 로그인됨",
|
||||
"Match Case": "대소문자 구분",
|
||||
"Match Diacritics": "발음 구별 부호 구분",
|
||||
"Match Whole Words": "전체 단어 일치",
|
||||
"Maximum Block Size": "최대 블록 크기",
|
||||
"Maximum Inline Size": "최대 인라인 크기",
|
||||
"Maximum Number of Columns": "최대 열 수",
|
||||
"Minimum Font Size": "최소 글꼴 크기",
|
||||
"Misc": "기타",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "모든 책에 적용",
|
||||
"Apply to This Book": "이 책에 적용",
|
||||
"Unable to fetch the translation. Try again later.": "번역을 가져올 수 없습니다. 나중에 다시 시도하세요.",
|
||||
"Update Now!": "지금 업데이트!",
|
||||
"Update": "업데이트",
|
||||
"Check Update": "업데이트 확인",
|
||||
"Already the latest version": "이미 최신 버전",
|
||||
"Book Details": "책 세부 정보",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "들여쓰기",
|
||||
"Paragraph Margin": "단락 여백",
|
||||
"Override Book Layout": "책 레이아웃 덮어쓰기",
|
||||
"Add to Group": "그룹에 추가",
|
||||
"Untitled Group": "제목 없는 그룹",
|
||||
"Group Books": "책 그룹화",
|
||||
"Remove From Group": "그룹에서 제거",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "영구 삭제",
|
||||
"Free Tier": "무료 등급",
|
||||
"Plus Tier": "플러스 등급",
|
||||
"Pro Tier": "프로 등급"
|
||||
"Pro Tier": "프로 등급",
|
||||
"RTL Direction": "RTL 방향",
|
||||
"Maximum Column Height": "최대 열 높이",
|
||||
"Maximum Column Width": "최대 열 너비",
|
||||
"Continuous Scroll": "연속 스크롤",
|
||||
"Fullscreen": "전체 화면",
|
||||
"No supported files found. Supported formats: {{formats}}": "지원되는 파일이 없습니다. 지원되는 형식: {{formats}}",
|
||||
"Drop to Import Books": "책 가져오기 위해 드롭",
|
||||
"Custom": "사용자 정의",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "세상은 무대이며,\n그리고 모든 남자와 여자는 단지 배우일 뿐입니다;\n그들은 나가는 곳과 들어오는 곳이 있으며,\n그리고 한 남자는 그의 시간에 많은 역할을 합니다,\n그의 행동은 일곱 시대입니다.\n\n— 윌리엄 셰익스피어",
|
||||
"Custom Theme": "사용자 정의 테마",
|
||||
"Theme Name": "테마 이름",
|
||||
"Text Color": "텍스트 색상",
|
||||
"Background Color": "배경 색상",
|
||||
"Preview": "미리보기",
|
||||
"Contrast": "대비",
|
||||
"Sunset": "일몰",
|
||||
"Double Border": "이중 테두리",
|
||||
"Border Color": "테두리 색상",
|
||||
"Border Frame": "테두리 프레임",
|
||||
"Show Header": "헤더 표시",
|
||||
"Show Footer": "푸터 표시",
|
||||
"Small": "작음",
|
||||
"Large": "큼",
|
||||
"Auto": "자동",
|
||||
"Language": "언어",
|
||||
"No annotations to export": "내보낼 주석이 없습니다",
|
||||
"Author": "저자",
|
||||
"Exported from Readest": "Readest에서 내보냄",
|
||||
"Highlights & Annotations": "하이라이트 및 주석",
|
||||
"Note": "노트",
|
||||
"Copied to clipboard": "클립보드에 복사",
|
||||
"Export Annotations": "주석 내보내기",
|
||||
"Auto Import on File Open": "파일 열 때 자동 가져오기",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "장 발견되지 않음.",
|
||||
"Failed to parse the EPUB file.": "EPUB 파일 구문 분석 실패.",
|
||||
"This book format is not supported.": "이 책 형식은 지원되지 않습니다.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "번역을 가져올 수 없습니다. 먼저 로그인하고 다시 시도하세요.",
|
||||
"Group": "그룹",
|
||||
"Always on Top": "항상 위에",
|
||||
"No Timeout": "타임아웃 없음",
|
||||
"{{value}} minute": "{{value}} 분",
|
||||
"{{value}} minutes": "{{value}} 분",
|
||||
"{{value}} hour": "{{value}} 시간",
|
||||
"{{value}} hours": "{{value}} 시간",
|
||||
"CJK Font": "CJK 글꼴",
|
||||
"Clear Search": "검색 지우기",
|
||||
"Header & Footer": "헤더 및 푸터",
|
||||
"Apply also in Scrolled Mode": "스크롤 모드에서도 적용",
|
||||
"A new version of Readest is Available!": "Readest의 새 버전이 있습니다!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}}가 사용 가능 (설치된 버전 {{currentVersion}}).",
|
||||
"Download and install now?": "지금 다운로드하고 설치하시겠습니까?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "다운로드 중 {{downloaded}} / {{contentLength}}",
|
||||
"Download finished": "다운로드 완료",
|
||||
"DOWNLOAD & INSTALL": "다운로드 및 설치",
|
||||
"Changelog:": "변경 로그:",
|
||||
"Software Update": "소프트웨어 업데이트",
|
||||
"Title": "제목",
|
||||
"Date Read": "읽은 날짜",
|
||||
"Date Added": "추가된 날짜",
|
||||
"Format": "형식",
|
||||
"Ascending": "오름차순",
|
||||
"Descending": "내림차순",
|
||||
"Sort by...": "정렬 기준...",
|
||||
"Added:": "추가됨:",
|
||||
"Format:": "형식:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "설명:",
|
||||
"No description available": "설명 없음",
|
||||
"List": "목록",
|
||||
"Grid": "격자",
|
||||
"(from 'As You Like It', Act II)": "(당신이 좋아하는 대로, 2막)",
|
||||
"Link Color": "링크 색상",
|
||||
"Volume Keys for Page Flip": "페이지 넘김을 위한 볼륨 키",
|
||||
"Clicks for Page Flip": "페이지 넘김을 위한 클릭",
|
||||
"Swap Clicks Area": "클릭 영역 교환"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Usuń",
|
||||
"Delete Highlight": "Usuń zaznaczenie",
|
||||
"Dictionary": "Słownik",
|
||||
"Disable Click-to-Flip": "Wyłącz przewracanie kliknięciem",
|
||||
"Download Readest": "Pobierz Readest",
|
||||
"Edit": "Edytuj",
|
||||
"Enter your custom CSS here...": "Wprowadź własny CSS tutaj...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Dzielenie wyrazów",
|
||||
"Identifier:": "Identyfikator:",
|
||||
"Import Books": "Importuj książki",
|
||||
"Invert Colors in Dark Mode": "Odwróć kolory w trybie ciemnym",
|
||||
"Language:": "Język:",
|
||||
"Layout": "Układ",
|
||||
"Light Mode": "Tryb jasny",
|
||||
"Loading...": "Ładowanie...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Poz. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Zalogowano",
|
||||
"Logged in as {{userDisplayName}}": "Zalogowano jako {{userDisplayName}}",
|
||||
"Match Case": "Uwzględnij wielkość liter",
|
||||
"Match Diacritics": "Uwzględnij znaki diakrytyczne",
|
||||
"Match Whole Words": "Uwzględnij całe słowa",
|
||||
"Maximum Block Size": "Maksymalny rozmiar bloku",
|
||||
"Maximum Inline Size": "Maksymalny rozmiar linii",
|
||||
"Maximum Number of Columns": "Maksymalna liczba kolumn",
|
||||
"Minimum Font Size": "Minimalny rozmiar czcionki",
|
||||
"Misc": "Różne",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Zastosuj do wszystkich książek",
|
||||
"Apply to This Book": "Zastosuj do tej książki",
|
||||
"Unable to fetch the translation. Try again later.": "Nie udało się pobrać tłumaczenia. Spróbuj ponownie później.",
|
||||
"Update Now!": "Zaktualizuj teraz!",
|
||||
"Update": "Zaktualizuj",
|
||||
"Check Update": "Sprawdź aktualizację",
|
||||
"Already the latest version": "Już najnowsza wersja",
|
||||
"Book Details": "Szczegóły książki",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Wcięcie tekstu",
|
||||
"Paragraph Margin": "Margines akapitu",
|
||||
"Override Book Layout": "Nadpisz układ książki",
|
||||
"Add to Group": "Dodaj do grupy",
|
||||
"Untitled Group": "Grupa bez tytułu",
|
||||
"Group Books": "Grupuj książki",
|
||||
"Remove From Group": "Usuń z grupy",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Usuń trwale",
|
||||
"Free Tier": "Plan darmowy",
|
||||
"Plus Tier": "Plan Plus",
|
||||
"Pro Tier": "Plan Pro"
|
||||
"Pro Tier": "Plan Pro",
|
||||
"RTL Direction": "Kierunek RTL",
|
||||
"Maximum Column Height": "Maksymalna wysokość kolumny",
|
||||
"Maximum Column Width": "Maksymalna szerokość kolumny",
|
||||
"Continuous Scroll": "Przewijanie ciągłe",
|
||||
"Fullscreen": "Pełny ekran",
|
||||
"No supported files found. Supported formats: {{formats}}": "Nie znaleziono obsługiwanych plików. Obsługiwane formaty: {{formats}}",
|
||||
"Drop to Import Books": "Upuść, aby zaimportować książki",
|
||||
"Custom": "Niestandardowy",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Cały świat jest sceną,\nA wszyscy mężczyźni i kobiety są tylko aktorami;\nMają swoje wyjścia i wejścia,\nI jeden człowiek w swoim czasie gra wiele ról,\nJego czyny to siedem wieków.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Niestandardowy motyw",
|
||||
"Theme Name": "Nazwa motywu",
|
||||
"Text Color": "Kolor tekstu",
|
||||
"Background Color": "Kolor tła",
|
||||
"Preview": "Podgląd",
|
||||
"Contrast": "Kontrast",
|
||||
"Sunset": "Zachód słońca",
|
||||
"Double Border": "Podwójna ramka",
|
||||
"Border Color": "Kolor ramki",
|
||||
"Border Frame": "Ramka",
|
||||
"Show Header": "Pokaż nagłówek",
|
||||
"Show Footer": "Pokaż stopkę",
|
||||
"Small": "Mały",
|
||||
"Large": "Duży",
|
||||
"Auto": "Automatyczny",
|
||||
"Language": "Język",
|
||||
"No annotations to export": "Brak adnotacji do eksportu",
|
||||
"Author": "Autor",
|
||||
"Exported from Readest": "Eksportowane z Readest",
|
||||
"Highlights & Annotations": "Zaznaczenia i adnotacje",
|
||||
"Note": "Notatka",
|
||||
"Copied to clipboard": "Skopiowano do schowka",
|
||||
"Export Annotations": "Eksportuj adnotacje",
|
||||
"Auto Import on File Open": "Automatyczne importowanie przy otwieraniu pliku",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Nie wykryto rozdziałów.",
|
||||
"Failed to parse the EPUB file.": "Nie udało się przeanalizować pliku EPUB.",
|
||||
"This book format is not supported.": "Ten format książki nie jest obsługiwany.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Nie można pobrać tłumaczenia. Proszę najpierw się zalogować i spróbować ponownie.",
|
||||
"Group": "Grupa",
|
||||
"Always on Top": "Zawsze na wierzchu",
|
||||
"No Timeout": "Brak limitu czasu",
|
||||
"{{value}} minute": "{{value}} minuta",
|
||||
"{{value}} minutes": "{{value}} minut",
|
||||
"{{value}} hour": "{{value}} godzina",
|
||||
"{{value}} hours": "{{value}} godziny",
|
||||
"CJK Font": "Czcionka CJK",
|
||||
"Clear Search": "Wyczyść wyszukiwanie",
|
||||
"Header & Footer": "Nagłówek i stopka",
|
||||
"Apply also in Scrolled Mode": "Zastosuj również w trybie przewijania",
|
||||
"A new version of Readest is Available!": "Nowa wersja Readest jest dostępna!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} jest dostępny (zainstalowana wersja {{currentVersion}}).",
|
||||
"Download and install now?": "Pobierz i zainstaluj teraz?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Pobieranie {{downloaded}} z {{contentLength}}",
|
||||
"Download finished": "Pobieranie zakończone",
|
||||
"DOWNLOAD & INSTALL": "POBIERZ I ZAINSTALUJ",
|
||||
"Changelog:": "Dziennik zmian:",
|
||||
"Software Update": "Aktualizacja oprogramowania",
|
||||
"Title": "Tytuł",
|
||||
"Date Read": "Data przeczytania",
|
||||
"Date Added": "Data dodania",
|
||||
"Format": "Format",
|
||||
"Ascending": "Rosnąco",
|
||||
"Descending": "Malejąco",
|
||||
"Sort by...": "Sortuj według...",
|
||||
"Added:": "Data dodania:",
|
||||
"Format:": "Format:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Opis:",
|
||||
"No description available": "Brak opisu",
|
||||
"List": "Lista",
|
||||
"Grid": "Siatka",
|
||||
"(from 'As You Like It', Act II)": "(z „Jak wam się podoba”, Akt II)",
|
||||
"Link Color": "Kolor linku",
|
||||
"Volume Keys for Page Flip": "Klucze głośności do przewracania stron",
|
||||
"Clicks for Page Flip": "Kliknięcia do przewracania stron",
|
||||
"Swap Clicks Area": "Zamień obszar kliknięcia"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Excluir",
|
||||
"Delete Highlight": "Excluir Destaque",
|
||||
"Dictionary": "Dicionário",
|
||||
"Disable Click-to-Flip": "Desativar Clique para Virar",
|
||||
"Download Readest": "Baixar Readest",
|
||||
"Edit": "Editar",
|
||||
"Enter your custom CSS here...": "Insira seu CSS personalizado aqui...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Hifenização",
|
||||
"Identifier:": "Identificador:",
|
||||
"Import Books": "Importar Livros",
|
||||
"Invert Colors in Dark Mode": "Inverter Cores no Modo Escuro",
|
||||
"Language:": "Idioma:",
|
||||
"Layout": "Layout",
|
||||
"Light Mode": "Modo Claro",
|
||||
"Loading...": "Carregando...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Loc. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Conectado",
|
||||
"Logged in as {{userDisplayName}}": "Conectado como {{userDisplayName}}",
|
||||
"Match Case": "Diferenciar Maiúsculas e Minúsculas",
|
||||
"Match Diacritics": "Corresponder Acentos",
|
||||
"Match Whole Words": "Corresponder Palavras Inteiras",
|
||||
"Maximum Block Size": "Tamanho Máximo do Bloco",
|
||||
"Maximum Inline Size": "Tamanho Máximo em Linha",
|
||||
"Maximum Number of Columns": "Número Máximo de Colunas",
|
||||
"Minimum Font Size": "Tamanho Mínimo da Fonte",
|
||||
"Misc": "Diversos",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Aplicar a todos os livros",
|
||||
"Apply to This Book": "Aplicar a este livro",
|
||||
"Unable to fetch the translation. Try again later.": "Não foi possível buscar a tradução. Tente novamente mais tarde.",
|
||||
"Update Now!": "Atualizar Agora!",
|
||||
"Update": "Atualizar",
|
||||
"Check Update": "Verificar atualização",
|
||||
"Already the latest version": "Já é a versão mais recente",
|
||||
"Book Details": "Detalhes do Livro",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Recuo de Texto",
|
||||
"Paragraph Margin": "Margem de Parágrafo",
|
||||
"Override Book Layout": "Sobrescrever Layout do Livro",
|
||||
"Add to Group": "Adicionar ao Grupo",
|
||||
"Untitled Group": "Grupo Sem Título",
|
||||
"Group Books": "Agrupar Livros",
|
||||
"Remove From Group": "Remover do Grupo",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Excluir permanentemente",
|
||||
"Free Tier": "Plano gratuito",
|
||||
"Plus Tier": "Plano Plus",
|
||||
"Pro Tier": "Plano Pro"
|
||||
"Pro Tier": "Plano Pro",
|
||||
"RTL Direction": "Direção RTL",
|
||||
"Maximum Column Height": "Altura Máxima da Coluna",
|
||||
"Maximum Column Width": "Largura Máxima da Coluna",
|
||||
"Continuous Scroll": "Rolagem Contínua",
|
||||
"Fullscreen": "Tela Cheia",
|
||||
"No supported files found. Supported formats: {{formats}}": "Nenhum arquivo suportado encontrado. Formatos suportados: {{formats}}",
|
||||
"Drop to Import Books": "Arraste para Importar Livros",
|
||||
"Custom": "Personalizado",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Todo o mundo é um palco,\nE todos os homens e mulheres são meros atores;\nEles têm suas saídas e suas entradas,\nE um homem em seu tempo desempenha muitos papéis,\nSeus atos sendo sete idades.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema Personalizado",
|
||||
"Theme Name": "Nome do Tema",
|
||||
"Text Color": "Cor do Texto",
|
||||
"Background Color": "Cor de Fundo",
|
||||
"Preview": "Visualizar",
|
||||
"Contrast": "Contraste",
|
||||
"Sunset": "Pôr do Sol",
|
||||
"Double Border": "Borda Dupla",
|
||||
"Border Color": "Cor da Borda",
|
||||
"Border Frame": "Moldura da Borda",
|
||||
"Show Header": "Mostrar Cabeçalho",
|
||||
"Show Footer": "Mostrar Rodapé",
|
||||
"Small": "Pequeno",
|
||||
"Large": "Grande",
|
||||
"Auto": "Automático",
|
||||
"Language": "Idioma",
|
||||
"No annotations to export": "Nenhuma anotação para exportar",
|
||||
"Author": "Autor",
|
||||
"Exported from Readest": "Exportado do Readest",
|
||||
"Highlights & Annotations": "Destaques e Anotações",
|
||||
"Note": "Nota",
|
||||
"Copied to clipboard": "Copiado para a área de transferência",
|
||||
"Export Annotations": "Exportar Anotações",
|
||||
"Auto Import on File Open": "Importação Automática ao Abrir Arquivo",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Nenhum capítulo detectado.",
|
||||
"Failed to parse the EPUB file.": "Falha ao analisar o arquivo EPUB.",
|
||||
"This book format is not supported.": "Este formato de livro não é suportado.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Não foi possível buscar a tradução. Faça login primeiro e tente novamente.",
|
||||
"Group": "Grupo",
|
||||
"Always on Top": "Sempre no Topo",
|
||||
"No Timeout": "Sem Tempo Limite",
|
||||
"{{value}} minute": "{{value}} minuto",
|
||||
"{{value}} minutes": "{{value}} minutos",
|
||||
"{{value}} hour": "{{value}} hora",
|
||||
"{{value}} hours": "{{value}} horas",
|
||||
"CJK Font": "Fonte CJK",
|
||||
"Clear Search": "Limpar Pesquisa",
|
||||
"Header & Footer": "Cabeçalho e Rodapé",
|
||||
"Apply also in Scrolled Mode": "Aplicar também no Modo Rolado",
|
||||
"A new version of Readest is Available!": "Uma nova versão do Readest está disponível!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} está disponível (versão instalada {{currentVersion}}).",
|
||||
"Download and install now?": "Baixar e instalar agora?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Baixando {{downloaded}} de {{contentLength}}",
|
||||
"Download finished": "Download concluído",
|
||||
"DOWNLOAD & INSTALL": "BAIXAR E INSTALAR",
|
||||
"Changelog:": "Notas de versão:",
|
||||
"Software Update": "Atualização de Software",
|
||||
"Title": "Título",
|
||||
"Date Read": "Data de leitura",
|
||||
"Date Added": "Data de adição",
|
||||
"Format": "Formato",
|
||||
"Ascending": "Crescente",
|
||||
"Descending": "Decrescente",
|
||||
"Sort by...": "Ordenar por...",
|
||||
"Added:": "Adicionado:",
|
||||
"Format:": "Formato:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Descrição:",
|
||||
"No description available": "Nenhuma descrição disponível",
|
||||
"List": "Lista",
|
||||
"Grid": "Grade",
|
||||
"(from 'As You Like It', Act II)": "(de 'Como Gostais', Ato II)",
|
||||
"Link Color": "Cor do Link",
|
||||
"Volume Keys for Page Flip": "Teclas de Volume para Virar Página",
|
||||
"Clicks for Page Flip": "Cliques para Virar Página",
|
||||
"Swap Clicks Area": "Trocar Área de Cliques"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Удалить",
|
||||
"Delete Highlight": "Удалить выделение",
|
||||
"Dictionary": "Словарь",
|
||||
"Disable Click-to-Flip": "Отключить переворот по клику",
|
||||
"Download Readest": "Скачать Readest",
|
||||
"Edit": "Редактировать",
|
||||
"Enter your custom CSS here...": "Введите ваш пользовательский CSS здесь...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Перенос слов",
|
||||
"Identifier:": "Идентификатор:",
|
||||
"Import Books": "Импорт книг",
|
||||
"Invert Colors in Dark Mode": "Инвертировать цвета в тёмной теме",
|
||||
"Language:": "Язык:",
|
||||
"Layout": "Макет",
|
||||
"Light Mode": "Светлая тема",
|
||||
"Loading...": "Загрузка...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Поз. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Выполнен вход",
|
||||
"Logged in as {{userDisplayName}}": "Вход выполнен как {{userDisplayName}}",
|
||||
"Match Case": "Учитывать регистр",
|
||||
"Match Diacritics": "Учитывать диакритические знаки",
|
||||
"Match Whole Words": "Только целые слова",
|
||||
"Maximum Block Size": "Максимальный размер блока",
|
||||
"Maximum Inline Size": "Максимальный размер строки",
|
||||
"Maximum Number of Columns": "Максимальное количество колонок",
|
||||
"Minimum Font Size": "Минимальный размер шрифта",
|
||||
"Misc": "Разное",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Применить ко всем книгам",
|
||||
"Apply to This Book": "Применить к этой книге",
|
||||
"Unable to fetch the translation. Try again later.": "Не удалось получить перевод. Попробуйте позже.",
|
||||
"Update Now!": "Обновить сейчас!",
|
||||
"Update": "Обновить",
|
||||
"Check Update": "Проверить обновление",
|
||||
"Already the latest version": "Уже последняя версия",
|
||||
"Book Details": "Детали книги",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Отступ текста",
|
||||
"Paragraph Margin": "Отступ абзаца",
|
||||
"Override Book Layout": "Переопределить макет книги",
|
||||
"Add to Group": "Добавить в группу",
|
||||
"Untitled Group": "Группа без названия",
|
||||
"Group Books": "Группировать книги",
|
||||
"Remove From Group": "Удалить из группы",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Удалить навсегда",
|
||||
"Free Tier": "Бесплатный тариф",
|
||||
"Plus Tier": "Тариф Plus",
|
||||
"Pro Tier": "Тариф Pro"
|
||||
"Pro Tier": "Тариф Pro",
|
||||
"RTL Direction": "Направление RTL",
|
||||
"Maximum Column Height": "Максимальная высота колонки",
|
||||
"Maximum Column Width": "Максимальная ширина колонки",
|
||||
"Continuous Scroll": "Непрерывная прокрутка",
|
||||
"Fullscreen": "Полноэкранный режим",
|
||||
"No supported files found. Supported formats: {{formats}}": "Поддерживаемые файлы не найдены. Поддерживаемые форматы: {{formats}}",
|
||||
"Drop to Import Books": "Перетащите сюда книги для импорта",
|
||||
"Custom": "Пользовательский",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Весь мир — театр,\nИ все мужчины и женщины — просто актёры;\nУ них есть свои выходы и входы,\nИ один человек в своё время играет много ролей,\nЕго поступки — это семь веков.\n\n— Уильям Шекспир",
|
||||
"Custom Theme": "Пользовательская тема",
|
||||
"Theme Name": "Название темы",
|
||||
"Text Color": "Цвет текста",
|
||||
"Background Color": "Цвет фона",
|
||||
"Preview": "Предпросмотр",
|
||||
"Contrast": "Контраст",
|
||||
"Sunset": "Закат",
|
||||
"Double Border": "Двойная граница",
|
||||
"Border Color": "Цвет границы",
|
||||
"Border Frame": "Граница рамки",
|
||||
"Show Header": "Показать заголовок",
|
||||
"Show Footer": "Показать нижний колонтитул",
|
||||
"Small": "Маленький",
|
||||
"Large": "Большой",
|
||||
"Auto": "Авто",
|
||||
"Language": "Язык",
|
||||
"No annotations to export": "Нет аннотаций для экспорта",
|
||||
"Author": "Автор",
|
||||
"Exported from Readest": "Экспортировано из Readest",
|
||||
"Highlights & Annotations": "Выделения и аннотации",
|
||||
"Note": "Заметка",
|
||||
"Copied to clipboard": "Скопировано в буфер обмена",
|
||||
"Export Annotations": "Экспортировать аннотации",
|
||||
"Auto Import on File Open": "Автоматический импорт при открытии файла",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Не обнаружено глав.",
|
||||
"Failed to parse the EPUB file.": "Не удалось разобрать файл EPUB.",
|
||||
"This book format is not supported.": "Этот формат книги не поддерживается.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Не удалось получить перевод. Пожалуйста, войдите в систему и попробуйте снова.",
|
||||
"Group": "Группа",
|
||||
"Always on Top": "Всегда сверху",
|
||||
"No Timeout": "Нет таймаута",
|
||||
"{{value}} minute": "{{value}} минута",
|
||||
"{{value}} minutes": "{{value}} минуты",
|
||||
"{{value}} hour": "{{value}} час",
|
||||
"{{value}} hours": "{{value}} часа",
|
||||
"CJK Font": "CJK Шрифт",
|
||||
"Clear Search": "Очистить поиск",
|
||||
"Header & Footer": "Заголовок и нижний колонтитул",
|
||||
"Apply also in Scrolled Mode": "Применить также в прокручиваемом режиме",
|
||||
"A new version of Readest is Available!": "Доступна новая версия Readest!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} доступен (установленная версия {{currentVersion}}).",
|
||||
"Download and install now?": "Скачать и установить сейчас?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Скачано {{downloaded}} из {{contentLength}}",
|
||||
"Download finished": "Загрузка завершена",
|
||||
"DOWNLOAD & INSTALL": "СКАЧАТЬ И УСТАНОВИТЬ",
|
||||
"Changelog:": "Изменения:",
|
||||
"Software Update": "Обновление программного обеспечения",
|
||||
"Title": "Название",
|
||||
"Date Read": "Дата прочтения",
|
||||
"Date Added": "Дата добавления",
|
||||
"Format": "Формат",
|
||||
"Ascending": "По возрастанию",
|
||||
"Descending": "По убыванию",
|
||||
"Sort by...": "Сортировать по...",
|
||||
"Added:": "Добавлено:",
|
||||
"Format:": "Формат:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Описание:",
|
||||
"No description available": "Нет доступного описания",
|
||||
"List": "Список",
|
||||
"Grid": "Сетка",
|
||||
"(from 'As You Like It', Act II)": "(из 'Как вам это понравится', Акт II)",
|
||||
"Link Color": "Цвет ссылки",
|
||||
"Volume Keys for Page Flip": "Клавиши громкости для перелистывания страниц",
|
||||
"Clicks for Page Flip": "Клики для перелистывания страниц",
|
||||
"Swap Clicks Area": "Переключить область кликов"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Sil",
|
||||
"Delete Highlight": "Vurguyu Sil",
|
||||
"Dictionary": "Sözlük",
|
||||
"Disable Click-to-Flip": "Tıkla-Çevir'i Devre Dışı Bırak",
|
||||
"Download Readest": "Readest'i İndir",
|
||||
"Edit": "Düzenle",
|
||||
"Enter your custom CSS here...": "Özel CSS'nizi buraya girin...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Heceleme",
|
||||
"Identifier:": "Tanımlayıcı:",
|
||||
"Import Books": "Kitapları İçe Aktar",
|
||||
"Invert Colors in Dark Mode": "Karanlık Modda Renkleri Ters Çevir",
|
||||
"Language:": "Dil:",
|
||||
"Layout": "Düzen",
|
||||
"Light Mode": "Aydınlık Mod",
|
||||
"Loading...": "Yükleniyor...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Konum {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Giriş Yapıldı",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}} olarak giriş yapıldı",
|
||||
"Match Case": "Büyük/Küçük Harf Eşleştir",
|
||||
"Match Diacritics": "Aksan İşaretlerini Eşleştir",
|
||||
"Match Whole Words": "Tam Kelimeleri Eşleştir",
|
||||
"Maximum Block Size": "Maksimum Blok Boyutu",
|
||||
"Maximum Inline Size": "Maksimum Satır İçi Boyut",
|
||||
"Maximum Number of Columns": "Maksimum Sütun Sayısı",
|
||||
"Minimum Font Size": "Minimum Yazı Boyutu",
|
||||
"Misc": "Diğer",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Tüm kitaplara uygula",
|
||||
"Apply to This Book": "Bu kitaba uygula",
|
||||
"Unable to fetch the translation. Try again later.": "Çeviri alınamıyor. Daha sonra tekrar deneyin.",
|
||||
"Update Now!": "Şimdi Güncelle!",
|
||||
"Update": "Güncelle",
|
||||
"Check Update": "Güncellemeyi kontrol et",
|
||||
"Already the latest version": "Zaten en son sürüm",
|
||||
"Book Details": "Kitap Detayları",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Metin Girintisi",
|
||||
"Paragraph Margin": "Paragraf Kenarı",
|
||||
"Override Book Layout": "Kitap Düzenini Geçersiz Kıl",
|
||||
"Add to Group": "Gruba Ekle",
|
||||
"Untitled Group": "Başlıksız Grup",
|
||||
"Group Books": "Kitapları Grupla",
|
||||
"Remove From Group": "Grubu Kaldır",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Kalıcı Olarak Sil",
|
||||
"Free Tier": "Ücretsiz Paket",
|
||||
"Plus Tier": "Plus Paket",
|
||||
"Pro Tier": "Pro Paket"
|
||||
"Pro Tier": "Pro Paket",
|
||||
"RTL Direction": "RTL Yönü",
|
||||
"Maximum Column Height": "Maksimum Sütun Yüksekliği",
|
||||
"Maximum Column Width": "Maksimum Sütun Genişliği",
|
||||
"Continuous Scroll": "Sürekli Kaydırma",
|
||||
"Fullscreen": "Tam Ekran",
|
||||
"No supported files found. Supported formats: {{formats}}": "Desteklenen dosya bulunamadı. Desteklenen formatlar: {{formats}}",
|
||||
"Drop to Import Books": "Kitapları İçe Aktarmak İçin Bırak",
|
||||
"Custom": "Özel",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Tüm dünya bir sahnedir,\nVe tüm erkekler ve kadınlar sadece oyunculardır;\nOnların çıkışları ve girişleri vardır,\nVe bir adam zamanında birçok rol oynar,\nOnun eylemleri yedi yaşındadır.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Özel Tema",
|
||||
"Theme Name": "Tema Adı",
|
||||
"Text Color": "Metin Rengi",
|
||||
"Background Color": "Arka Plan Rengi",
|
||||
"Preview": "Önizleme",
|
||||
"Contrast": "Kontrast",
|
||||
"Sunset": "Gün Batımı",
|
||||
"Double Border": "Çift Sınır",
|
||||
"Border Color": "Sınır Rengi",
|
||||
"Border Frame": "Sınır Çerçevesi",
|
||||
"Show Header": "Başlık Göster",
|
||||
"Show Footer": "Altbilgi Göster",
|
||||
"Small": "Küçük",
|
||||
"Large": "Büyük",
|
||||
"Auto": "Otomatik",
|
||||
"Language": "Dil",
|
||||
"No annotations to export": "Dışa aktarılacak not yok",
|
||||
"Author": "Yazar",
|
||||
"Exported from Readest": "Readest'ten dışa aktarıldı",
|
||||
"Highlights & Annotations": "Vurgular ve Notlar",
|
||||
"Note": "Not",
|
||||
"Copied to clipboard": "Panoya kopyalandı",
|
||||
"Export Annotations": "Notları Dışa Aktar",
|
||||
"Auto Import on File Open": "Dosya Açıldığında Otomatik İçe Aktar",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Bölüm bulunamadı.",
|
||||
"Failed to parse the EPUB file.": "EPUB dosyası ayrıştırılamadı.",
|
||||
"This book format is not supported.": "Bu kitap formatı desteklenmiyor.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Çeviri alınamıyor. Lütfen önce giriş yapın ve tekrar deneyin.",
|
||||
"Group": "Grup",
|
||||
"Always on Top": "Her Zaman Üstte",
|
||||
"No Timeout": "Zaman Aşımı Yok",
|
||||
"{{value}} minute": "{{value}} dakika",
|
||||
"{{value}} minutes": "{{value}} dakika",
|
||||
"{{value}} hour": "{{value}} saat",
|
||||
"{{value}} hours": "{{value}} saat",
|
||||
"CJK Font": "CJK Yazı Tipi",
|
||||
"Clear Search": "Aramayı Temizle",
|
||||
"Header & Footer": "Başlık ve Altbilgi",
|
||||
"Apply also in Scrolled Mode": "Kaydırılmış Modda da Uygula",
|
||||
"A new version of Readest is Available!": "Yeni bir Readest sürümü mevcut!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} mevcut (yüklü sürüm {{currentVersion}}).",
|
||||
"Download and install now?": "Şimdi indirip yükleyelim mi?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "İndiriliyor {{downloaded}}/{{contentLength}}",
|
||||
"Download finished": "İndirme tamamlandı",
|
||||
"DOWNLOAD & INSTALL": "İNDİR & YÜKLE",
|
||||
"Changelog:": "Değişiklik Günlüğü:",
|
||||
"Software Update": "Yazılım Güncellemesi",
|
||||
"Title": "Başlık",
|
||||
"Date Read": "Okunma Tarihi",
|
||||
"Date Added": "Eklenme Tarihi",
|
||||
"Format": "Biçim",
|
||||
"Ascending": "Artan",
|
||||
"Descending": "Azalan",
|
||||
"Sort by...": "Şuna göre sırala...",
|
||||
"Added:": "Eklenme:",
|
||||
"Format:": "Biçim:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Tanım:",
|
||||
"No description available": "Tanım mevcut değil",
|
||||
"List": "Liste",
|
||||
"Grid": "Izgara",
|
||||
"(from 'As You Like It', Act II)": "(As You Like It, 2. Perde)",
|
||||
"Link Color": "Bağlantı Rengi",
|
||||
"Volume Keys for Page Flip": "Sayfa Çevirme için Ses Seviyesi Tuşları",
|
||||
"Clicks for Page Flip": "Sayfa Çevirme için Tıklamalar",
|
||||
"Swap Clicks Area": "Tıklama Alanını Değiştir"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Видалити",
|
||||
"Delete Highlight": "Видалити виділення",
|
||||
"Dictionary": "Словник",
|
||||
"Disable Click-to-Flip": "Вимкнути гортання кліком",
|
||||
"Download Readest": "Завантажити Readest",
|
||||
"Edit": "Редагувати",
|
||||
"Enter your custom CSS here...": "Введіть ваш користувацький CSS тут...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Перенесення слів",
|
||||
"Identifier:": "Ідентифікатор:",
|
||||
"Import Books": "Імпортувати книги",
|
||||
"Invert Colors in Dark Mode": "Інвертувати кольори в темному режимі",
|
||||
"Language:": "Мова:",
|
||||
"Layout": "Макет",
|
||||
"Light Mode": "Світлий режим",
|
||||
"Loading...": "Завантаження...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Поз. {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Увійшли",
|
||||
"Logged in as {{userDisplayName}}": "Увійшли як {{userDisplayName}}",
|
||||
"Match Case": "Враховувати регістр",
|
||||
"Match Diacritics": "Враховувати діакритичні знаки",
|
||||
"Match Whole Words": "Шукати цілі слова",
|
||||
"Maximum Block Size": "Максимальний розмір блоку",
|
||||
"Maximum Inline Size": "Максимальний розмір рядка",
|
||||
"Maximum Number of Columns": "Максимальна кількість колонок",
|
||||
"Minimum Font Size": "Мінімальний розмір шрифту",
|
||||
"Misc": "Різне",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Застосувати до всіх книг",
|
||||
"Apply to This Book": "Застосувати до цієї книги",
|
||||
"Unable to fetch the translation. Try again later.": "Не вдалося отримати переклад. Спробуйте пізніше.",
|
||||
"Update Now!": "Оновити зараз!",
|
||||
"Update": "Оновити",
|
||||
"Check Update": "Перевірити оновлення",
|
||||
"Already the latest version": "Вже остання версія",
|
||||
"Book Details": "Деталі книги",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Відступ тексту",
|
||||
"Paragraph Margin": "Відступ абзацу",
|
||||
"Override Book Layout": "Перевизначити макет книги",
|
||||
"Add to Group": "Додати до групи",
|
||||
"Untitled Group": "Група без назви",
|
||||
"Group Books": "Групувати книги",
|
||||
"Remove From Group": "Видалити з групи",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Видалити назавжди",
|
||||
"Free Tier": "Безкоштовний тариф",
|
||||
"Plus Tier": "Тариф Plus",
|
||||
"Pro Tier": "Тариф Pro"
|
||||
"Pro Tier": "Тариф Pro",
|
||||
"RTL Direction": "Напрямок RTL",
|
||||
"Maximum Column Height": "Максимальна висота колонки",
|
||||
"Maximum Column Width": "Максимальна ширина колонки",
|
||||
"Continuous Scroll": "Постійна прокрутка",
|
||||
"Fullscreen": "Повноекранний",
|
||||
"No supported files found. Supported formats: {{formats}}": "Не знайдено підтримуваних файлів. Підтримувані формати: {{formats}}",
|
||||
"Drop to Import Books": "Перетягніть для імпорту книг",
|
||||
"Custom": "Користувацький",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Весь світ — театр,\nІ всі чоловіки та жінки — просто актори;\nУ них є свої виходи та входи,\nІ один чоловік у свій час грає багато ролей,\nЙого вчинки — це сім віков.\n\n— Вільям Шекспір",
|
||||
"Custom Theme": "Користувацька тема",
|
||||
"Theme Name": "Назва теми",
|
||||
"Text Color": "Колір тексту",
|
||||
"Background Color": "Колір фону",
|
||||
"Preview": "Попередній перегляд",
|
||||
"Contrast": "Контраст",
|
||||
"Sunset": "Захід сонця",
|
||||
"Double Border": "Подвійна рамка",
|
||||
"Border Color": "Колір рамки",
|
||||
"Border Frame": "Рамка рамки",
|
||||
"Show Header": "Показати заголовок",
|
||||
"Show Footer": "Показати нижню частину",
|
||||
"Small": "Малий",
|
||||
"Large": "Великий",
|
||||
"Auto": "Автоматичний",
|
||||
"Language": "Мова",
|
||||
"No annotations to export": "Немає анотацій для експорту",
|
||||
"Author": "Автор",
|
||||
"Exported from Readest": "Експортовано з Readest",
|
||||
"Highlights & Annotations": "Виділення та анотації",
|
||||
"Note": "Примітка",
|
||||
"Copied to clipboard": "Скопійовано в буфер обміну",
|
||||
"Export Annotations": "Експортувати анотації",
|
||||
"Auto Import on File Open": "Автоматичний імпорт при відкритті файлу",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Не виявлено жодного розділу.",
|
||||
"Failed to parse the EPUB file.": "Не вдалося розібрати файл EPUB.",
|
||||
"This book format is not supported.": "Цей формат книги не підтримується.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Не вдалося отримати переклад. Будь ласка, спочатку увійдіть у систему та спробуйте ще раз.",
|
||||
"Group": "Група",
|
||||
"Always on Top": "Завжди зверху",
|
||||
"No Timeout": "Без тайм-ауту",
|
||||
"{{value}} minute": "{{value}} хвилина",
|
||||
"{{value}} minutes": "{{value}} хвилин",
|
||||
"{{value}} hour": "{{value}} година",
|
||||
"{{value}} hours": "{{value}} години",
|
||||
"CJK Font": "CJK шрифт",
|
||||
"Clear Search": "Очистити пошук",
|
||||
"Header & Footer": "Заголовок і нижній колонтитул",
|
||||
"Apply also in Scrolled Mode": "Застосувати також у прокрученому режимі",
|
||||
"A new version of Readest is Available!": "Доступна нова версія Readest!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} доступна (встановлена версія {{currentVersion}}).",
|
||||
"Download and install now?": "Завантажити та встановити зараз?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Завантаження {{downloaded}} з {{contentLength}}",
|
||||
"Download finished": "Завантаження завершено",
|
||||
"DOWNLOAD & INSTALL": "ЗАВАНТАЖИТИ І ВСТАНОВИТИ",
|
||||
"Changelog:": "Журнал змін:",
|
||||
"Software Update": "Оновлення програмного забезпечення",
|
||||
"Title": "Назва",
|
||||
"Date Read": "Дата прочитання",
|
||||
"Date Added": "Дата додавання",
|
||||
"Format": "Формат",
|
||||
"Ascending": "За зростанням",
|
||||
"Descending": "За спаданням",
|
||||
"Sort by...": "Сортувати за...",
|
||||
"Added:": "Додано:",
|
||||
"Format:": "Формат:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Опис:",
|
||||
"No description available": "Опис недоступний",
|
||||
"List": "Список",
|
||||
"Grid": "Сітка",
|
||||
"(from 'As You Like It', Act II)": "(з 'Як вам це подобається', Акт II)",
|
||||
"Link Color": "Колір посилання",
|
||||
"Volume Keys for Page Flip": "Клавіші гучності для перевороту сторінки",
|
||||
"Clicks for Page Flip": "Клацання для перевороту сторінки",
|
||||
"Swap Clicks Area": "Змінити область клацання"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "Xóa",
|
||||
"Delete Highlight": "Xóa văn bản được đánh dấu",
|
||||
"Dictionary": "Từ điển",
|
||||
"Disable Click-to-Flip": "Tắt tính năng nhấp để lật trang",
|
||||
"Download Readest": "Tải Readest",
|
||||
"Edit": "Chỉnh sửa",
|
||||
"Enter your custom CSS here...": "Nhập CSS tùy chỉnh của bạn vào đây...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "Gạch nối từ",
|
||||
"Identifier:": "Định danh:",
|
||||
"Import Books": "Nhập sách",
|
||||
"Invert Colors in Dark Mode": "Đảo màu trong chế độ tối",
|
||||
"Language:": "Ngôn ngữ:",
|
||||
"Layout": "Bố cục",
|
||||
"Light Mode": "Chế độ sáng",
|
||||
"Loading...": "Đang tải...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "Vị trí {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "Đã đăng nhập",
|
||||
"Logged in as {{userDisplayName}}": "Đăng nhập với tên {{userDisplayName}}",
|
||||
"Match Case": "Phân biệt chữ hoa/thường",
|
||||
"Match Diacritics": "Phân biệt dấu",
|
||||
"Match Whole Words": "Khớp toàn bộ từ",
|
||||
"Maximum Block Size": "Kích thước khối tối đa",
|
||||
"Maximum Inline Size": "Kích thước nội tuyến tối đa",
|
||||
"Maximum Number of Columns": "Số cột tối đa",
|
||||
"Minimum Font Size": "Cỡ chữ tối thiểu",
|
||||
"Misc": "Khác",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "Áp dụng cho tất cả sách",
|
||||
"Apply to This Book": "Áp dụng cho sách này",
|
||||
"Unable to fetch the translation. Try again later.": "Không thể tải dịch vụ. Vui lòng thử lại sau.",
|
||||
"Update Now!": "Cập nhật ngay!",
|
||||
"Update": "Cập nhật",
|
||||
"Check Update": "Kiểm tra cập nhật",
|
||||
"Already the latest version": "Đã là phiên bản mới nhất",
|
||||
"Book Details": "Chi tiết sách",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "Thụt lề",
|
||||
"Paragraph Margin": "Lề đoạn",
|
||||
"Override Book Layout": "Ghi đè bố cục sách",
|
||||
"Add to Group": "Thêm vào nhóm",
|
||||
"Untitled Group": "Nhóm không có tiêu đề",
|
||||
"Group Books": "Nhóm sách",
|
||||
"Remove From Group": "Xóa khỏi nhóm",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "Xóa vĩnh viễn",
|
||||
"Free Tier": "Gói miễn phí",
|
||||
"Plus Tier": "Gói Plus",
|
||||
"Pro Tier": "Gói Pro"
|
||||
"Pro Tier": "Gói Pro",
|
||||
"RTL Direction": "Hướng RTL",
|
||||
"Maximum Column Height": "Chiều cao cột tối đa",
|
||||
"Maximum Column Width": "Chiều rộng cột tối đa",
|
||||
"Continuous Scroll": "Cuộn liên tục",
|
||||
"Fullscreen": "Toàn màn hình",
|
||||
"No supported files found. Supported formats: {{formats}}": "Không tìm thấy tệp được hỗ trợ. Định dạng được hỗ trợ: {{formats}}",
|
||||
"Drop to Import Books": "Kéo và thả để nhập sách",
|
||||
"Custom": "Tùy chỉnh",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Cả thế giới này là một sân khấu,\nVà tất cả đàn ông và phụ nữ chỉ là những diễn viên;\nHọ có cửa ra và cửa vào của mình,\nVà một người trong cuộc đời mình đóng nhiều vai,\nHành động của mình là bảy tuổi.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Chủ đề tùy chỉnh",
|
||||
"Theme Name": "Tên chủ đề",
|
||||
"Text Color": "Màu văn bản",
|
||||
"Background Color": "Màu nền",
|
||||
"Preview": "Xem trước",
|
||||
"Contrast": "Tương phản",
|
||||
"Sunset": "Hoàng hôn",
|
||||
"Double Border": "Đôi viền",
|
||||
"Border Color": "Màu viền",
|
||||
"Border Frame": "Khung viền",
|
||||
"Show Header": "Hiển thị tiêu đề",
|
||||
"Show Footer": "Hiển thị chân trang",
|
||||
"Small": "Nhỏ",
|
||||
"Large": "Lớn",
|
||||
"Auto": "Tự động",
|
||||
"Language": "Ngôn ngữ",
|
||||
"No annotations to export": "Không có chú thích để xuất",
|
||||
"Author": "Tác giả",
|
||||
"Exported from Readest": "Đã xuất từ Readest",
|
||||
"Highlights & Annotations": "Điểm nổi bật & Chú thích",
|
||||
"Note": "Ghi chú",
|
||||
"Copied to clipboard": "Đã sao chép vào bảng ghi",
|
||||
"Export Annotations": "Xuất chú thích",
|
||||
"Auto Import on File Open": "Tự động nhập khi mở tệp",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Không phát hiện thấy chương nào.",
|
||||
"Failed to parse the EPUB file.": "Không thể phân tích tệp EPUB.",
|
||||
"This book format is not supported.": "Định dạng sách này không được hỗ trợ.",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Không thể tải dịch vụ. Vui lòng đăng nhập trước và thử lại.",
|
||||
"Group": "Nhóm",
|
||||
"Always on Top": "Luôn ở trên cùng",
|
||||
"No Timeout": "Không có thời gian chờ",
|
||||
"{{value}} minute": "{{value}} phút",
|
||||
"{{value}} minutes": "{{value}} phút",
|
||||
"{{value}} hour": "{{value}} giờ",
|
||||
"{{value}} hours": "{{value}} giờ",
|
||||
"CJK Font": "Phông chữ CJK",
|
||||
"Clear Search": "Xóa tìm kiếm",
|
||||
"Header & Footer": "Đầu trang & Chân trang",
|
||||
"Apply also in Scrolled Mode": "Áp dụng cũng trong chế độ cuộn",
|
||||
"A new version of Readest is Available!": "Đã có phiên bản mới của Readest!",
|
||||
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} đã có sẵn (phiên bản đã cài đặt {{currentVersion}}).",
|
||||
"Download and install now?": "Tải xuống và cài đặt ngay bây giờ?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "Đang tải {{downloaded}} trong {{contentLength}}",
|
||||
"Download finished": "Tải xuống hoàn tất",
|
||||
"DOWNLOAD & INSTALL": "TẢI XUỐNG & CÀI ĐẶT",
|
||||
"Changelog:": "Thay đổi:",
|
||||
"Software Update": "Cập nhật phần mềm",
|
||||
"Title": "Tiêu đề",
|
||||
"Date Read": "Ngày đã đọc",
|
||||
"Date Added": "Ngày thêm",
|
||||
"Format": "Định dạng",
|
||||
"Ascending": "Tăng dần",
|
||||
"Descending": "Giảm dần",
|
||||
"Sort by...": "Sắp xếp theo...",
|
||||
"Added:": "Đã thêm:",
|
||||
"Format:": "Định dạng:",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Description:": "Miêu tả:",
|
||||
"No description available": "Không có mô tả nào",
|
||||
"List": "Danh sách",
|
||||
"Grid": "Lưới",
|
||||
"(from 'As You Like It', Act II)": "(từ 'Như bạn thích', Hành động II)",
|
||||
"Link Color": "Màu liên kết",
|
||||
"Volume Keys for Page Flip": "Phím âm lượng để lật trang",
|
||||
"Clicks for Page Flip": "Nhấp để lật trang",
|
||||
"Swap Clicks Area": "Đổi khu vực nhấp"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "删除",
|
||||
"Delete Highlight": "删除划线",
|
||||
"Dictionary": "词典",
|
||||
"Disable Click-to-Flip": "禁用点击翻页",
|
||||
"Download Readest": "下载 Readest",
|
||||
"Edit": "编辑",
|
||||
"Enter your custom CSS here...": "在此处输入您的自定义 CSS...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "断字",
|
||||
"Identifier:": "识别码",
|
||||
"Import Books": "导入书籍",
|
||||
"Invert Colors in Dark Mode": "深色主题下反色",
|
||||
"Language:": "语言",
|
||||
"Layout": "布局",
|
||||
"Light Mode": "浅色主题",
|
||||
"Loading...": "加载中...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "位置 {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "已登录",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}} 已登录",
|
||||
"Match Case": "匹配大小写",
|
||||
"Match Diacritics": "匹配重音符号",
|
||||
"Match Whole Words": "匹配整个单词",
|
||||
"Maximum Block Size": "内容最大高度",
|
||||
"Maximum Inline Size": "每栏最大宽度",
|
||||
"Maximum Number of Columns": "分栏数",
|
||||
"Minimum Font Size": "最小字号",
|
||||
"Misc": "杂项",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "应用于所有书籍",
|
||||
"Apply to This Book": "应用于此书籍",
|
||||
"Unable to fetch the translation. Try again later.": "无法获取翻译,请稍后重试。",
|
||||
"Update Now!": "立即更新!",
|
||||
"Update": "更新",
|
||||
"Check Update": "检查更新",
|
||||
"Already the latest version": "已经是最新版本",
|
||||
"Book Details": "书籍详情",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "首行缩进",
|
||||
"Paragraph Margin": "段间距",
|
||||
"Override Book Layout": "覆盖版面布局",
|
||||
"Add to Group": "添加到分组",
|
||||
"Untitled Group": "无标题分组",
|
||||
"Group Books": "分组书籍",
|
||||
"Remove From Group": "从分组中移除",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "永久删除",
|
||||
"Free Tier": "免费版",
|
||||
"Plus Tier": "增强版",
|
||||
"Pro Tier": "专业版"
|
||||
"Pro Tier": "专业版",
|
||||
"RTL Direction": "从右向左",
|
||||
"Maximum Column Height": "最大列高",
|
||||
"Maximum Column Width": "最大列宽",
|
||||
"Continuous Scroll": "连续滚动",
|
||||
"Fullscreen": "全屏显示",
|
||||
"No supported files found. Supported formats: {{formats}}": "未找到支持的文件。支持的格式:{{formats}}",
|
||||
"Drop to Import Books": "拖放导入书籍",
|
||||
"Custom": "自定义",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "世界是座舞台, \n所有男女都只是演员; \n各有其出场和入场; \n每个人皆扮演许多角色, \n而演出共分为七个阶段。\n\n——威廉·莎士比亚",
|
||||
"Custom Theme": "自定义主题",
|
||||
"Theme Name": "主题名称",
|
||||
"Text Color": "文本颜色",
|
||||
"Background Color": "背景颜色",
|
||||
"Preview": "预览",
|
||||
"Contrast": "对比",
|
||||
"Sunset": "日落",
|
||||
"Double Border": "显示版框",
|
||||
"Border Color": "版框颜色",
|
||||
"Border Frame": "页面版框",
|
||||
"Show Header": "显示页眉",
|
||||
"Show Footer": "显示页脚",
|
||||
"Small": "小",
|
||||
"Large": "大",
|
||||
"Auto": "自动",
|
||||
"Language": "语言",
|
||||
"No annotations to export": "没有要导出的笔记",
|
||||
"Author": "作者",
|
||||
"Exported from Readest": "从 Readest 导出",
|
||||
"Highlights & Annotations": "划线和笔记",
|
||||
"Note": "笔记",
|
||||
"Copied to clipboard": "已复制到剪贴板",
|
||||
"Export Annotations": "导出笔记",
|
||||
"Auto Import on File Open": "打开文件时自动导入",
|
||||
"LXGW WenKai GB Screen": "霞鹜文楷",
|
||||
"LXGW WenKai TC": "霞鶩文楷",
|
||||
"No chapters detected.": "未检测到章节。",
|
||||
"Failed to parse the EPUB file.": "无法解析 EPUB 文件。",
|
||||
"This book format is not supported.": "不支持此书籍格式。",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "无法获取翻译。请登录后重试。",
|
||||
"Group": "分组",
|
||||
"Always on Top": "窗口置顶",
|
||||
"No Timeout": "关闭定时",
|
||||
"{{value}} minute": "{{value}}分钟",
|
||||
"{{value}} minutes": "{{value}}分钟",
|
||||
"{{value}} hour": "{{value}}小时",
|
||||
"{{value}} hours": "{{value}}小时",
|
||||
"CJK Font": "中文字体",
|
||||
"Clear Search": "清除搜索",
|
||||
"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}})。",
|
||||
"Download and install now?": "立即下载并安装?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "正在下载 {{downloaded}} / {{contentLength}}",
|
||||
"Download finished": "下载完成",
|
||||
"DOWNLOAD & INSTALL": "下载并安装",
|
||||
"Changelog:": "更新日志:",
|
||||
"Software Update": "软件更新",
|
||||
"Title": "书名",
|
||||
"Date Read": "阅读日期",
|
||||
"Date Added": "添加日期",
|
||||
"Format": "格式",
|
||||
"Ascending": "升序",
|
||||
"Descending": "降序",
|
||||
"Sort by...": "排序方式...",
|
||||
"Added:": "添加日期",
|
||||
"Format:": "格式",
|
||||
"GuanKiapTsingKhai-T": "原俠正楷-T",
|
||||
"Description:": "简介",
|
||||
"No description available": "暂无简介",
|
||||
"List": "列表",
|
||||
"Grid": "网格",
|
||||
"(from 'As You Like It', Act II)": "出自《皆大欢喜》,第二幕",
|
||||
"Link Color": "链接颜色",
|
||||
"Volume Keys for Page Flip": "音量键翻页",
|
||||
"Clicks for Page Flip": "点击翻页",
|
||||
"Swap Clicks Area": "交换点击区域"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"Delete": "刪除",
|
||||
"Delete Highlight": "刪除劃線",
|
||||
"Dictionary": "詞典",
|
||||
"Disable Click-to-Flip": "禁用點擊翻頁",
|
||||
"Download Readest": "下載 Readest",
|
||||
"Edit": "編輯",
|
||||
"Enter your custom CSS here...": "在此處輸入您的自定義 CSS...",
|
||||
@@ -53,19 +52,15 @@
|
||||
"Hyphenation": "斷字",
|
||||
"Identifier:": "識別碼",
|
||||
"Import Books": "導入書籍",
|
||||
"Invert Colors in Dark Mode": "深色主題下反色",
|
||||
"Language:": "語言",
|
||||
"Layout": "版面",
|
||||
"Light Mode": "淺色主題",
|
||||
"Loading...": "載入中...",
|
||||
"Loc. {{currentPage}} / {{totalPage}}": "位置 {{currentPage}} / {{totalPage}}",
|
||||
"Logged in": "已登入",
|
||||
"Logged in as {{userDisplayName}}": "{{userDisplayName}} 已登入",
|
||||
"Match Case": "匹配大小寫",
|
||||
"Match Diacritics": "匹配重音符號",
|
||||
"Match Whole Words": "匹配整個單詞",
|
||||
"Maximum Block Size": "內容最大高度",
|
||||
"Maximum Inline Size": "每欄最大寬度",
|
||||
"Maximum Number of Columns": "分欄數",
|
||||
"Minimum Font Size": "最小字號",
|
||||
"Misc": "雜項",
|
||||
@@ -130,8 +125,6 @@
|
||||
"Apply to All Books": "應用於所有書籍",
|
||||
"Apply to This Book": "應用於此書籍",
|
||||
"Unable to fetch the translation. Try again later.": "無法獲取翻譯,請稍後再試。",
|
||||
"Update Now!": "立即更新!",
|
||||
"Update": "更新",
|
||||
"Check Update": "檢查更新",
|
||||
"Already the latest version": "已經是最新版本",
|
||||
"Book Details": "書籍詳情",
|
||||
@@ -156,7 +149,6 @@
|
||||
"Text Indent": "首行縮進",
|
||||
"Paragraph Margin": "段間距",
|
||||
"Override Book Layout": "覆蓋版面佈局",
|
||||
"Add to Group": "添加到組",
|
||||
"Untitled Group": "無標題組",
|
||||
"Group Books": "分組書籍",
|
||||
"Remove From Group": "從組中移除",
|
||||
@@ -230,5 +222,82 @@
|
||||
"Delete Permanently": "永久刪除",
|
||||
"Free Tier": "免費方案",
|
||||
"Plus Tier": "Plus方案",
|
||||
"Pro Tier": "專業方案"
|
||||
"Pro Tier": "專業方案",
|
||||
"RTL Direction": "從右向左",
|
||||
"Maximum Column Height": "最大列高",
|
||||
"Maximum Column Width": "最大列寬",
|
||||
"Continuous Scroll": "連續滾動",
|
||||
"Fullscreen": "全螢幕",
|
||||
"No supported files found. Supported formats: {{formats}}": "未找到支援的檔案。支援的格式:{{formats}}",
|
||||
"Drop to Import Books": "拖放導入書籍",
|
||||
"Custom": "自定義",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "世界是一個舞台,\n所有的男人和女人都只是演員:\n他們有出口,也有入口;\n一個人在他的時代扮演許多角色。 \n\n——威廉·莎士比亞",
|
||||
"Custom Theme": "自定義主題",
|
||||
"Theme Name": "主題名稱",
|
||||
"Text Color": "文字顏色",
|
||||
"Background Color": "背景顏色",
|
||||
"Preview": "預覽",
|
||||
"Contrast": "對比",
|
||||
"Sunset": "日落",
|
||||
"Double Border": "顯示邊框",
|
||||
"Border Color": "邊框顏色",
|
||||
"Border Frame": "頁面邊框",
|
||||
"Show Header": "顯示頁眉",
|
||||
"Show Footer": "顯示頁腳",
|
||||
"Small": "小",
|
||||
"Large": "大",
|
||||
"Auto": "自動",
|
||||
"Language": "語言",
|
||||
"No annotations to export": "沒有要匯出的筆記",
|
||||
"Author": "作者",
|
||||
"Exported from Readest": "從 Readest 匯出",
|
||||
"Highlights & Annotations": "高亮和筆記",
|
||||
"Note": "筆記",
|
||||
"Copied to clipboard": "已複製到剪貼板",
|
||||
"Export Annotations": "匯出筆記",
|
||||
"Auto Import on File Open": "打開文件時自動導入",
|
||||
"LXGW WenKai GB Screen": "霞鶩文楷 SC",
|
||||
"LXGW WenKai TC": "霞鶩文楷 TC",
|
||||
"No chapters detected.": "未檢測到章節。",
|
||||
"Failed to parse the EPUB file.": "無法解析 EPUB 文件。",
|
||||
"This book format is not supported.": "不支援此書籍格式。",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "無法獲取翻譯。請先登入然後重試。",
|
||||
"Group": "分組",
|
||||
"Always on Top": "視窗置頂",
|
||||
"No Timeout": "關閉定時",
|
||||
"{{value}} minute": "{{value}}分鐘",
|
||||
"{{value}} minutes": "{{value}}分鐘",
|
||||
"{{value}} hour": "{{value}}小時",
|
||||
"{{value}} hours": "{{value}}小時",
|
||||
"CJK Font": "中文字體",
|
||||
"Clear Search": "清除搜索",
|
||||
"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}})。",
|
||||
"Download and install now?": "立即下載並安裝?",
|
||||
"Downloading {{downloaded}} of {{contentLength}}": "正在下載 {{downloaded}} / {{contentLength}}",
|
||||
"Download finished": "下載完成",
|
||||
"DOWNLOAD & INSTALL": "下載並安裝",
|
||||
"Changelog:": "更新日誌:",
|
||||
"Software Update": "軟體更新",
|
||||
"Title": "書名",
|
||||
"Date Read": "閱讀日期",
|
||||
"Date Added": "添加日期",
|
||||
"Format": "格式",
|
||||
"Ascending": "升序",
|
||||
"Descending": "降序",
|
||||
"Sort by...": "排序方式...",
|
||||
"Added:": "添加日期",
|
||||
"Format:": "格式",
|
||||
"GuanKiapTsingKhai-T": "原俠正楷-T",
|
||||
"Description:": "簡介",
|
||||
"No description available": "暫無簡介",
|
||||
"List": "列表",
|
||||
"Grid": "網格",
|
||||
"(from 'As You Like It', Act II)": "(出自《皆大歡喜》,第二幕)",
|
||||
"Link Color": "連結顏色",
|
||||
"Volume Keys for Page Flip": "音量鍵翻頁",
|
||||
"Clicks for Page Flip": "點擊翻頁",
|
||||
"Swap Clicks Area": "交換點擊區域"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,169 @@
|
||||
{
|
||||
"releases": {
|
||||
"0.9.38": {
|
||||
"date": "2025-04-29",
|
||||
"notes": [
|
||||
"Page Turning with Volume Keys: You can now turn pages using your device’s volume buttons",
|
||||
"Expanded English Voices: More English voices are available across all en locales",
|
||||
"Theme Editor: Added a primary color picker for easier customization",
|
||||
"System Fonts: Retrieve system fonts on iOS and Android, with improved font weight and style detection",
|
||||
"Popup Footnotes: Popup footnotes now inherit the book's font for consistent appearance",
|
||||
"Better Text Selection: Preserves text selection anchors when spanning paginated content",
|
||||
"Android Compatibility: Navigation bar auto-hides correctly on Android 10 and below",
|
||||
"Popup Dialogs: Back key now properly dismisses popup dialogs on Android"
|
||||
]
|
||||
},
|
||||
"0.9.37": {
|
||||
"date": "2025-04-25",
|
||||
"notes": [
|
||||
"Full-Screen Toggle: You can now press F11 to quickly toggle full-screen mode",
|
||||
"List View: A new list layout is available for your bookshelf",
|
||||
"Book Details: Book descriptions from metadata are now shown in the details view",
|
||||
"Enhanced Android behavior: better navigation bar handling and status bar dismissal when resuming",
|
||||
"TTS now defaults to the book’s metadata language when needed",
|
||||
"Synced custom themes reliably across sessions"
|
||||
]
|
||||
},
|
||||
"0.9.36": {
|
||||
"date": "2025-04-20",
|
||||
"notes": [
|
||||
"📥 New download and upload buttons in the book detail dialog for easier file management",
|
||||
"📂 You can now open files directly in Readest from your file manager on Android and iOS",
|
||||
"🛠️ Fixed an issue where files with quotation marks in the filename could not be opened",
|
||||
"📖 Improved the immersive reading experience on the reader page for Android and iOS",
|
||||
"🌐 Added GuanKiapTsingKhai-T to the list of available CJK fonts"
|
||||
]
|
||||
},
|
||||
"0.9.35": {
|
||||
"date": "2025-04-15",
|
||||
"notes": [
|
||||
"🛠️ Fixed an issue where books without cover images can not be synced across devices",
|
||||
"📱 Added in-app update support for Android",
|
||||
"📚 You can now sort books by title or author in your library"
|
||||
]
|
||||
},
|
||||
"0.9.33": {
|
||||
"date": "2025-04-13",
|
||||
"notes": [
|
||||
"New: Redesigned update dialog with a progress bar during downloads for a smoother update experience.",
|
||||
"New: You can now set header and footer visibility separately for paginated and scroll modes.",
|
||||
"Fixed layout issues in the bottom configuration panel when using RTL (right-to-left) languages.",
|
||||
"Fixed TXT file import issues on Android by improving support for content provider file paths.",
|
||||
"Fixed transformed punctuations when highlighting text with vertical layout.",
|
||||
"Improved login flow on macOS by handling OAuth with the native ASWebAuthenticationSession."
|
||||
]
|
||||
},
|
||||
"0.9.32": {
|
||||
"date": "2025-04-09",
|
||||
"notes": [
|
||||
"No more resetting of font size and color when you apply custom book styles",
|
||||
"The underline and squiggly highlight decorations have been adjusted to sit neatly between text lines",
|
||||
"With more font options available, the font menu is no longer forced in-line",
|
||||
"LXGW WenKai TC is now part of our CJK font list"
|
||||
]
|
||||
},
|
||||
"0.9.31": {
|
||||
"date": "2025-04-08",
|
||||
"notes": [
|
||||
"iOS: Text-to-Speech (TTS) now works in the background",
|
||||
"Android: Import files from native file manager",
|
||||
"Desktop: Add option to keep window on top",
|
||||
"Vertical layout for highlighting tools",
|
||||
"Added options to auto-stop TTS after timeout",
|
||||
"Added book search functionality in bookshelf",
|
||||
"Improved font support for CJK users"
|
||||
]
|
||||
},
|
||||
"0.9.30": {
|
||||
"date": "2025-04-05",
|
||||
"notes": [
|
||||
"Added fixed storage quata when running the app in self-hosted mode",
|
||||
"Link colors will only change when you override fonts",
|
||||
"Added cache for native files for better performance"
|
||||
]
|
||||
},
|
||||
"0.9.29": {
|
||||
"date": "2025-04-02",
|
||||
"notes": [
|
||||
"Improved Login on Android without needing to rely on external Chrome browser",
|
||||
"Local large book files now load more efficiently on Android",
|
||||
"Balanced the load of online service between different types of users to keep things running smoothly for everyone"
|
||||
]
|
||||
},
|
||||
"0.9.28": {
|
||||
"date": "2025-03-30",
|
||||
"notes": [
|
||||
"Improved vertical reading mode with better support for Chinese punctuations",
|
||||
"Fixed TXT file importing issue on desktop apps — TXT books now load reliably and display as expected",
|
||||
"Improved annotation experience — highlighting tools are now properly dismissed when adding notes to avoid UI conflicts"
|
||||
]
|
||||
},
|
||||
"0.9.27": {
|
||||
"date": "2025-03-29",
|
||||
"notes": [
|
||||
"Added option to swap click-to-flip area and reset password",
|
||||
"Added global shortcuts for full-text search",
|
||||
"Added support for both R2 and S3 storage in cloud sync",
|
||||
"Improved TTS with fewer unintended pauses, faster startup, and support for preferred voices",
|
||||
"Various fixes for rendering styles and UI layout"
|
||||
]
|
||||
},
|
||||
"0.9.26": {
|
||||
"date": "2025-03-27",
|
||||
"notes": [
|
||||
"Support open book from file manager without importing",
|
||||
"Add LXGW WenKai font in Serif fonts list",
|
||||
"Various fixes on layout and styles on Windows and Android"
|
||||
]
|
||||
},
|
||||
"0.9.25": {
|
||||
"date": "2025-03-24",
|
||||
"notes": [
|
||||
"Support exporting annotations in markdown format",
|
||||
"Support importing TXT files",
|
||||
"Support setting reader language in the UI",
|
||||
"Add bottom action bar on mobile platforms",
|
||||
"Various fixes on layout settings and styles"
|
||||
]
|
||||
},
|
||||
"0.9.23": {
|
||||
"date": "2025-03-19",
|
||||
"notes": [
|
||||
"Add options to show/hide header and footer",
|
||||
"Add responsive window size for popup footnotes",
|
||||
"Fix theme color not applied on Safari browsers",
|
||||
"Fix progress not updated to 100% when finished reading"
|
||||
]
|
||||
},
|
||||
"0.9.22": {
|
||||
"date": "2025-03-16",
|
||||
"notes": [
|
||||
"Add theme color editor for more customization",
|
||||
"Add border frame for vertical layout",
|
||||
"Fix layout glitches on macOS in scroll mode",
|
||||
"Various fixes and enhancements on translator, footnote, and scrollbar"
|
||||
]
|
||||
},
|
||||
"0.9.21": {
|
||||
"date": "2025-03-10",
|
||||
"notes": [
|
||||
"Fix column height in vertical layout on mobile",
|
||||
"Fix drag handle height not constant on mobile",
|
||||
"Add fullscreen option on desktop",
|
||||
"Add drag and drop to import books on desktop",
|
||||
"Various fixes and enhancements on updater, footerbar and note"
|
||||
]
|
||||
},
|
||||
"0.9.20": {
|
||||
"date": "2025-03-10",
|
||||
"notes": [
|
||||
"Fix column height in vertical layout on mobile",
|
||||
"Fix drag handle height not constant on mobile",
|
||||
"Add fullscreen option on desktop",
|
||||
"Add drag and drop to import books on desktop",
|
||||
"Various fixes and enhancements on updater, footerbar and note"
|
||||
]
|
||||
},
|
||||
"0.9.19": {
|
||||
"date": "2025-03-07",
|
||||
"notes": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "Readest"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
description = "Your online library"
|
||||
authors = ["Bilingify LLC"]
|
||||
license = ""
|
||||
@@ -15,7 +15,7 @@ name = "readestlib"
|
||||
crate-type = ["staticlib", "cdylib", "lib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.3", features = [] }
|
||||
tauri-build = { version = "2.1.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
@@ -31,33 +31,35 @@ reqwest = { version = "0.12", default-features = false, features = [
|
||||
"stream",
|
||||
] }
|
||||
# FIXME: remove the devtools feature in production
|
||||
tauri = { version = "2.1.1", features = [ "protocol-asset", "devtools"] }
|
||||
tauri-build = "2.0.3"
|
||||
tauri-plugin-log = "2.0.2"
|
||||
tauri-plugin-fs = "2.0.3"
|
||||
tauri-plugin-dialog = "2.0.3"
|
||||
tauri-plugin-os = "2.0.1"
|
||||
tauri-plugin-http = "2.0.3"
|
||||
tauri-plugin-devtools = "2.0.0"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-process = "2"
|
||||
tauri = { version = "2.4.1", features = [ "protocol-asset", "devtools"] }
|
||||
tauri-build = "2.1.1"
|
||||
tauri-plugin-log = "2.3.1"
|
||||
tauri-plugin-fs = "2.2.1"
|
||||
tauri-plugin-dialog = "2.2.1"
|
||||
tauri-plugin-os = "2.2.1"
|
||||
tauri-plugin-http = "2.4.3"
|
||||
tauri-plugin-shell = "2.2.1"
|
||||
tauri-plugin-process = "2.2.1"
|
||||
tauri-plugin-oauth = "2"
|
||||
tauri-plugin-opener = "2.2.2"
|
||||
tauri-plugin-deep-link = "2"
|
||||
tauri-plugin-opener = "2.2.6"
|
||||
tauri-plugin-deep-link = "2.2.1"
|
||||
tauri-plugin-sign-in-with-apple = "1.0.2"
|
||||
tauri-plugin-haptics = "2.2.3"
|
||||
tauri-plugin-safari-auth = { path = "./plugins/tauri-plugin-safari-auth" }
|
||||
[patch.crates-io]
|
||||
tauri = { path = "../../../packages/tauri/crates/tauri" }
|
||||
tauri-plugin-haptics = "2.2.4"
|
||||
tauri-plugin-native-bridge = { path = "./plugins/tauri-plugin-native-bridge" }
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
rand = "0.8"
|
||||
cocoa = "0.25"
|
||||
objc = "0.2.7"
|
||||
rand = "0.8"
|
||||
objc-foundation = "0.1.1"
|
||||
objc_id = "0.1.1"
|
||||
block = "0.1.6"
|
||||
objc2 = "0.6"
|
||||
objc2-authentication-services = "0.3"
|
||||
objc2-foundation = { version = "0.3", features = ["NSError", "NSArray"] }
|
||||
|
||||
[target.'cfg(any(target_os = "macos", windows, target_os = "linux"))'.dependencies]
|
||||
tauri-plugin-cli = "2"
|
||||
tauri-plugin-single-instance = "2.2.0"
|
||||
tauri-plugin-updater = "2"
|
||||
tauri-plugin-window-state = "2.2.0"
|
||||
font-enumeration = "0.9.0"
|
||||
tauri-plugin-single-instance = "2.2.3"
|
||||
tauri-plugin-updater = "2.7.0"
|
||||
tauri-plugin-window-state = "2.2.2"
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<false/>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UIHomeIndicatorAutoHidden</key>
|
||||
<true/>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>EPUB Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.idpf.epub-container</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>PDF Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.adobe.pdf</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>FB2 Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.readest.fb2</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>CBZ Archive</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.readest.cbz</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MOBI Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.mobipocket.mobi</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>AZW Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.amazon.azw</string>
|
||||
<string>com.amazon.azw3</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Text File</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.plain-text</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.idpf.epub-container</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>EPUB Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
<string>public.content</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>epub</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/epub+zip</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.readest.fb2</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>FB2 Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.xml</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>fb2</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/xml</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.readest.cbz</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>CBZ Archive</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.archive</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>cbz</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/x-cbz</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.mobipocket.mobi</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>MOBI Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>mobi</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/x-mobipocket-ebook</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.amazon.azw</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>AZW Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>azw</string>
|
||||
<string>azw3</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/vnd.amazon.ebook</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": ["main"],
|
||||
"windows": ["main", "updater"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"fs:default",
|
||||
@@ -39,12 +39,26 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "fs:allow-cache-read",
|
||||
"allow": [
|
||||
{
|
||||
"path": "$CACHEDIR/**/*"
|
||||
},
|
||||
{
|
||||
"path": "/private/var/mobile/Containers/Data/Application/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
{
|
||||
"url": "https://*.deepl.com"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/readest/*"
|
||||
},
|
||||
{
|
||||
"url": "https://*.cloudflarestorage.com"
|
||||
}
|
||||
@@ -57,11 +71,17 @@
|
||||
"core:window:allow-center",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-unmaximize",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-is-maximized",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-set-fullscreen",
|
||||
"core:window:allow-set-always-on-top",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"core:path:allow-resolve-directory",
|
||||
"log:default",
|
||||
"shell:default",
|
||||
"process:default",
|
||||
"process:allow-exit",
|
||||
@@ -74,7 +94,7 @@
|
||||
"haptics:allow-impact-feedback",
|
||||
"haptics:allow-notification-feedback",
|
||||
"haptics:allow-selection-feedback",
|
||||
"safari-auth:default",
|
||||
"native-bridge:default",
|
||||
"deep-link:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "desktop-capability",
|
||||
"windows": ["main"],
|
||||
"windows": ["main", "updater"],
|
||||
"platforms": ["linux", "macOS", "windows"],
|
||||
"permissions": [
|
||||
"updater:default",
|
||||
"cli:default"
|
||||
]
|
||||
"permissions": ["updater:default", "cli:default"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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"/>
|
||||
|
||||
<!-- AndroidTV support -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:largeHeap="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.readest"
|
||||
android:usesCleartextTraffic="${usesCleartextTraffic}">
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/main_activity_title"
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
|
||||
<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="application/epub+zip" />
|
||||
<data android:mimeType="application/pdf" />
|
||||
<data android:mimeType="application/fb2" />
|
||||
<data android:mimeType="application/x-fb2" />
|
||||
<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: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>
|
||||
|
||||
<intent-filter android:label="oauth">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<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" />
|
||||
<!-- AndroidTV support -->
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
||||
<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" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="web.readest.com" />
|
||||
|
||||
</intent-filter>
|
||||
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
+71
-23
@@ -1,33 +1,81 @@
|
||||
package com.bilingify.readest
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsetsController
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import android.view.KeyEvent
|
||||
import android.webkit.WebView
|
||||
import android.util.Log
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import com.readest.native_bridge.KeyDownInterceptor
|
||||
|
||||
class MainActivity : TauriActivity(), KeyDownInterceptor {
|
||||
private lateinit var wv: WebView
|
||||
private var interceptVolumeKeysEnabled = false
|
||||
private var interceptBackKeyEnabled = false
|
||||
|
||||
override fun onWebViewCreate(webView: WebView) {
|
||||
wv = webView
|
||||
}
|
||||
|
||||
private val keyEventMap = mapOf(
|
||||
KeyEvent.KEYCODE_BACK to "Back",
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN to "VolumeDown",
|
||||
KeyEvent.KEYCODE_VOLUME_UP to "VolumeUp"
|
||||
)
|
||||
|
||||
override fun interceptVolumeKeys(enabled: Boolean) {
|
||||
Log.d("MainActivity", "Intercept volume keys: $enabled")
|
||||
interceptVolumeKeysEnabled = enabled
|
||||
}
|
||||
|
||||
override fun interceptBackKey(enabled: Boolean) {
|
||||
Log.d("MainActivity", "Intercept back key: $enabled")
|
||||
interceptBackKeyEnabled = enabled
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
val keyName = keyEventMap[keyCode]
|
||||
if (keyName != null) {
|
||||
wv.evaluateJavascript(
|
||||
"""
|
||||
try {
|
||||
window.onNativeKeyDown("$keyName", $keyCode)
|
||||
} catch (err) {
|
||||
false
|
||||
}
|
||||
""".trimIndent()
|
||||
) { result ->
|
||||
run {
|
||||
if (result.equals("true", ignoreCase = true)) {
|
||||
Log.d("Key Event", "Key event $keyName intercepted")
|
||||
}
|
||||
}
|
||||
}
|
||||
return when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
if (interceptVolumeKeysEnabled) {
|
||||
true
|
||||
} else {
|
||||
super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
KeyEvent.KEYCODE_BACK -> {
|
||||
if (interceptBackKeyEnabled) {
|
||||
true
|
||||
} else {
|
||||
super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
else -> super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
class MainActivity : TauriActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
hideSystemUI()
|
||||
}
|
||||
|
||||
private fun hideSystemUI() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.decorView.windowInsetsController?.let { controller ->
|
||||
controller.hide(WindowInsets.Type.systemBars())
|
||||
controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.readest" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowTranslucentStatus">false</item>
|
||||
<item name="android:windowTranslucentNavigation">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:fitsSystemWindows">false</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "tauri-plugin-native-bridge"
|
||||
version = "0.1.0"
|
||||
authors = [ "You" ]
|
||||
description = "a bridge between tauri app and native OS functionality"
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
exclude = ["/examples", "/dist-js", "/guest-js", "/node_modules"]
|
||||
links = "tauri-plugin-native-bridge"
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.3.1" }
|
||||
serde = "1.0"
|
||||
thiserror = "2"
|
||||
schemars = "0.8"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.0.4", features = ["build"] }
|
||||
schemars = "0.8"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", windows, target_os = "linux"))'.dependencies]
|
||||
font-enumeration = "0.9.0"
|
||||
@@ -0,0 +1 @@
|
||||
# Tauri Plugin native-bridge
|
||||
+2
-2
@@ -4,7 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.plugin.safari_auth"
|
||||
namespace = "com.readest.native_bridge"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
@@ -33,9 +33,9 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
||||
implementation("androidx.browser:browser:1.8.0")
|
||||
implementation("com.google.android.material:material:1.7.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package com.plugin.safari_auth
|
||||
package com.readest.native_bridge
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
@@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.plugin.safari_auth", appContext.packageName)
|
||||
assertEquals("com.readest.native_bridge", appContext.packageName)
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.readest.native_bridge
|
||||
|
||||
import android.util.Log
|
||||
|
||||
class NativeBridge {
|
||||
}
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
package com.readest.native_bridge
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.KeyEvent
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager
|
||||
import android.view.WindowInsetsController
|
||||
import android.graphics.Color
|
||||
import android.webkit.WebView
|
||||
import android.graphics.fonts.SystemFonts
|
||||
import android.graphics.fonts.Font
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import app.tauri.annotation.Command
|
||||
import app.tauri.annotation.InvokeArg
|
||||
import app.tauri.annotation.TauriPlugin
|
||||
import app.tauri.plugin.JSObject
|
||||
import app.tauri.plugin.Plugin
|
||||
import app.tauri.plugin.Invoke
|
||||
import org.json.JSONArray
|
||||
import java.io.*
|
||||
|
||||
@InvokeArg
|
||||
class AuthRequestArgs {
|
||||
var authUrl: String? = null
|
||||
}
|
||||
|
||||
@InvokeArg
|
||||
class CopyURIRequestArgs {
|
||||
var uri: String? = null
|
||||
var dst: String? = null
|
||||
}
|
||||
|
||||
@InvokeArg
|
||||
class InstallPackageRequestArgs {
|
||||
var path: String? = null
|
||||
}
|
||||
|
||||
@InvokeArg
|
||||
class SetSystemUIVisibilityRequestArgs {
|
||||
var visible: Boolean? = false
|
||||
var darkMode: Boolean? = false
|
||||
}
|
||||
|
||||
@InvokeArg
|
||||
class InterceptKeysRequestArgs {
|
||||
var volumeKeys: Boolean? = null
|
||||
var backKey: Boolean? = null
|
||||
}
|
||||
|
||||
interface KeyDownInterceptor {
|
||||
fun interceptVolumeKeys(enabled: Boolean)
|
||||
fun interceptBackKey(enabled: Boolean)
|
||||
}
|
||||
|
||||
@TauriPlugin
|
||||
class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
private val implementation = NativeBridge()
|
||||
private var redirectScheme = "readest"
|
||||
private var redirectHost = "auth-callback"
|
||||
|
||||
companion object {
|
||||
var pendingInvoke: Invoke? = null
|
||||
}
|
||||
|
||||
override fun load(webView: WebView) {
|
||||
super.load(webView)
|
||||
handleIntent(activity.intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
handleIntent(intent)
|
||||
}
|
||||
|
||||
private fun handleIntent(intent: Intent?) {
|
||||
val uri = intent?.data ?: return
|
||||
Log.e("NativeBridgePlugin", "Received intent: $uri")
|
||||
when {
|
||||
uri.scheme == "readest" && uri.host == "auth-callback" -> {
|
||||
val result = JSObject().apply {
|
||||
put("redirectUrl", uri.toString())
|
||||
}
|
||||
pendingInvoke?.resolve(result)
|
||||
pendingInvoke = null
|
||||
}
|
||||
|
||||
intent.action == Intent.ACTION_VIEW -> {
|
||||
try {
|
||||
activity.contentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e("NativeBridgePlugin", "Failed to take persistable URI permission: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Command
|
||||
fun auth_with_custom_tab(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(AuthRequestArgs::class.java)
|
||||
val uri = Uri.parse(args.authUrl)
|
||||
|
||||
val customTabsIntent = CustomTabsIntent.Builder().build()
|
||||
customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
|
||||
|
||||
Log.d("NativeBridgePlugin", "Launching OAuth URL: ${args.authUrl}")
|
||||
customTabsIntent.launchUrl(activity, uri)
|
||||
|
||||
pendingInvoke = invoke
|
||||
}
|
||||
|
||||
@Command
|
||||
fun copy_uri_to_path(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(CopyURIRequestArgs::class.java)
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val uri = Uri.parse(args.uri ?: "")
|
||||
val dst = File(args.dst ?: "")
|
||||
val inputStream = activity.contentResolver.openInputStream(uri)
|
||||
|
||||
if (inputStream != null) {
|
||||
dst.outputStream().use { output ->
|
||||
inputStream.use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
ret.put("success", true)
|
||||
} else {
|
||||
ret.put("success", false)
|
||||
ret.put("error", "Failed to open input stream from URI")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ret.put("success", false)
|
||||
ret.put("error", e.message)
|
||||
}
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun install_package(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(InstallPackageRequestArgs::class.java)
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val file = File(args.path ?: "")
|
||||
if (file.exists()) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
val apkUri = FileProvider.getUriForFile(activity, "${activity.packageName}.fileprovider", file)
|
||||
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
val packageManager = activity.packageManager
|
||||
val resolveInfos = packageManager.queryIntentActivities(intent, 0)
|
||||
for (resolveInfo in resolveInfos) {
|
||||
val packageName = resolveInfo.activityInfo.packageName
|
||||
activity.grantUriPermission(packageName, apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
activity.startActivity(intent)
|
||||
ret.put("success", true)
|
||||
} else {
|
||||
ret.put("success", false)
|
||||
ret.put("error", "File does not exist")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ret.put("success", false)
|
||||
ret.put("error", e.message)
|
||||
}
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun set_system_ui_visibility(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(SetSystemUIVisibilityRequestArgs::class.java)
|
||||
val visible = args.visible ?: false
|
||||
var isDarkMode = args.darkMode ?: false
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val window = activity.window
|
||||
val decorView = window.decorView
|
||||
if (!visible) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
window.attributes.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.setDecorFitsSystemWindows(false)
|
||||
val controller = window.insetsController
|
||||
if (controller != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
} else {
|
||||
val compatController = WindowCompat.getInsetsController(window, decorView)
|
||||
compatController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
|
||||
if (isDarkMode) {
|
||||
controller.setSystemBarsAppearance(
|
||||
0,
|
||||
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
|
||||
)
|
||||
} else {
|
||||
controller.setSystemBarsAppearance(
|
||||
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
|
||||
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
|
||||
)
|
||||
}
|
||||
if (visible) {
|
||||
controller.show(WindowInsets.Type.statusBars())
|
||||
} else {
|
||||
controller.hide(WindowInsets.Type.systemBars())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val compatController = WindowCompat.getInsetsController(window, decorView)
|
||||
compatController?.let {
|
||||
it.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
if (!isDarkMode) {
|
||||
it.isAppearanceLightStatusBars = true
|
||||
} else {
|
||||
it.isAppearanceLightStatusBars = false
|
||||
}
|
||||
if (visible) {
|
||||
it.show(WindowInsetsCompat.Type.statusBars())
|
||||
} else {
|
||||
it.hide(WindowInsetsCompat.Type.systemBars())
|
||||
}
|
||||
}
|
||||
}
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
ret.put("success", true)
|
||||
} catch (e: Exception) {
|
||||
ret.put("success", false)
|
||||
ret.put("error", e.message)
|
||||
}
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun get_status_bar_height(invoke: Invoke) {
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val resourceId = activity.resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
val height = if (resourceId > 0) {
|
||||
activity.resources.getDimensionPixelSize(resourceId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
ret.put("height", height)
|
||||
} catch (e: Exception) {
|
||||
ret.put("height", -1)
|
||||
ret.put("error", e.message)
|
||||
}
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun get_sys_fonts_list(invoke: Invoke) {
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val fontList = mutableListOf<String>()
|
||||
val fontFileList = mutableListOf<String>()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val systemFonts = SystemFonts.getAvailableFonts()
|
||||
for (font in systemFonts) {
|
||||
val file = font.getFile()?: continue
|
||||
if (file.isFile && (file.name.endsWith(".ttf", true) || file.name.endsWith(".otf", true))) {
|
||||
fontFileList.add(file.name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val fontDirs = listOf("/system/fonts", "/system/font", "/data/fonts")
|
||||
for (dirPath in fontDirs) {
|
||||
val dir = File(dirPath)
|
||||
if (dir.exists() && dir.isDirectory) {
|
||||
dir.listFiles()?.forEach { file ->
|
||||
if (file.isFile && (file.name.endsWith(".ttf", true) || file.name.endsWith(".otf", true))) {
|
||||
fontFileList.add(file.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (fileFileName in fontFileList) {
|
||||
var fontName = fileFileName
|
||||
.replace(Regex("\\.(ttf|otf)$", RegexOption.IGNORE_CASE), "")
|
||||
.trim()
|
||||
}
|
||||
ret.put("fonts", JSONArray(fontList))
|
||||
} catch (e: Exception) {
|
||||
ret.put("error", e.message)
|
||||
}
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun intercept_keys(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(InterceptKeysRequestArgs::class.java)
|
||||
if (activity is KeyDownInterceptor) {
|
||||
when (args.backKey) {
|
||||
true -> (activity as KeyDownInterceptor).interceptBackKey(true)
|
||||
false -> (activity as KeyDownInterceptor).interceptBackKey(false)
|
||||
else -> {}
|
||||
}
|
||||
when (args.volumeKeys) {
|
||||
true -> (activity as KeyDownInterceptor).interceptVolumeKeys(true)
|
||||
false -> (activity as KeyDownInterceptor).interceptVolumeKeys(false)
|
||||
else -> {}
|
||||
}
|
||||
} else {
|
||||
Log.e("NativeBridgePlugin", "Activity does not implement KeyDownInterceptor")
|
||||
}
|
||||
invoke.resolve()
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.plugin.safari_auth
|
||||
package com.readest.native_bridge
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
const COMMANDS: &[&str] = &[
|
||||
"auth_with_safari",
|
||||
"auth_with_custom_tab",
|
||||
"copy_uri_to_path",
|
||||
"use_background_audio",
|
||||
"install_package",
|
||||
"set_system_ui_visibility",
|
||||
"get_status_bar_height",
|
||||
"get_sys_fonts_list",
|
||||
"intercept_keys",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS)
|
||||
.android_path("android")
|
||||
.ios_path("ios")
|
||||
.build();
|
||||
}
|
||||
+4
-4
@@ -4,7 +4,7 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "tauri-plugin-safari-auth",
|
||||
name: "tauri-plugin-native-bridge",
|
||||
platforms: [
|
||||
.macOS(.v10_13),
|
||||
.iOS(.v13),
|
||||
@@ -12,9 +12,9 @@ let package = Package(
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "tauri-plugin-safari-auth",
|
||||
name: "tauri-plugin-native-bridge",
|
||||
type: .static,
|
||||
targets: ["tauri-plugin-safari-auth"]),
|
||||
targets: ["tauri-plugin-native-bridge"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "Tauri", path: "../.tauri/tauri-api")
|
||||
@@ -23,7 +23,7 @@ let package = Package(
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "tauri-plugin-safari-auth",
|
||||
name: "tauri-plugin-native-bridge",
|
||||
dependencies: [
|
||||
.byName(name: "Tauri")
|
||||
],
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# Tauri Plugin safari-auth
|
||||
# Tauri Plugin native-bridge
|
||||
|
||||
A description of this package.
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
import AVFoundation
|
||||
import AuthenticationServices
|
||||
import CoreText
|
||||
import MediaPlayer
|
||||
import ObjectiveC
|
||||
import SwiftRs
|
||||
import Tauri
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
func getLocalizedDisplayName(familyName: String) -> String? {
|
||||
let fontDescriptor = CTFontDescriptorCreateWithAttributes(
|
||||
[
|
||||
kCTFontFamilyNameAttribute: familyName
|
||||
] as CFDictionary)
|
||||
|
||||
let font = CTFontCreateWithFontDescriptor(fontDescriptor, 0.0, nil)
|
||||
|
||||
var actualLanguage: Unmanaged<CFString>?
|
||||
if let localizedName = CTFontCopyLocalizedName(font, kCTFontFamilyNameKey, &actualLanguage) {
|
||||
return localizedName as String
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
class SafariAuthRequestArgs: Decodable {
|
||||
let authUrl: String
|
||||
}
|
||||
|
||||
class UseBackgroundAudioRequestArgs: Decodable {
|
||||
let enabled: Bool
|
||||
}
|
||||
|
||||
class SetSystemUIVisibilityRequestArgs: Decodable {
|
||||
let visible: Bool
|
||||
let darkMode: Bool
|
||||
}
|
||||
|
||||
class InterceptKeysRequestArgs: Decodable {
|
||||
let backKey: Bool?
|
||||
let volumeKeys: Bool?
|
||||
}
|
||||
|
||||
class VolumeKeyHandler: NSObject {
|
||||
private var audioSession: AVAudioSession?
|
||||
private var originalVolume: Float = 0.0
|
||||
private var volumeView: MPVolumeView?
|
||||
private var isIntercepting = false
|
||||
private var webView: WKWebView?
|
||||
private var volumeSlider: UISlider?
|
||||
|
||||
func startInterception(webView: WKWebView) {
|
||||
if isIntercepting {
|
||||
return
|
||||
}
|
||||
|
||||
self.webView = webView
|
||||
isIntercepting = true
|
||||
|
||||
audioSession = AVAudioSession.sharedInstance()
|
||||
do {
|
||||
try audioSession?.setActive(true)
|
||||
} catch {
|
||||
print("Failed to activate audio session: \(error)")
|
||||
}
|
||||
|
||||
originalVolume = audioSession?.outputVolume ?? 0.1
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.setupHiddenVolumeView()
|
||||
}
|
||||
|
||||
audioSession?.addObserver(self, forKeyPath: "outputVolume", options: [.new], context: nil)
|
||||
}
|
||||
|
||||
func stopInterception() {
|
||||
if !isIntercepting {
|
||||
return
|
||||
}
|
||||
|
||||
isIntercepting = false
|
||||
audioSession?.removeObserver(self, forKeyPath: "outputVolume")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.setSystemVolume(self?.originalVolume ?? 0.1)
|
||||
self?.volumeView?.removeFromSuperview()
|
||||
self?.volumeView = nil
|
||||
self?.volumeSlider = nil
|
||||
}
|
||||
|
||||
do {
|
||||
try audioSession?.setActive(false)
|
||||
} catch {
|
||||
print("Failed to deactivate audio session: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func setSystemVolume(_ volume: Float) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.volumeSlider?.value = volume
|
||||
}
|
||||
}
|
||||
|
||||
private func setupHiddenVolumeView() {
|
||||
assert(Thread.isMainThread, "setupHiddenVolumeView must be called on main thread")
|
||||
|
||||
let frame = CGRect(x: -1000, y: -1000, width: 1, height: 1)
|
||||
volumeView = MPVolumeView(frame: frame)
|
||||
volumeSlider = volumeView?.subviews.first(where: { $0 is UISlider }) as? UISlider
|
||||
if let window = UIApplication.shared.windows.first {
|
||||
window.addSubview(volumeView!)
|
||||
}
|
||||
setSystemVolume(originalVolume)
|
||||
}
|
||||
|
||||
override func observeValue(
|
||||
forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?,
|
||||
context: UnsafeMutableRawPointer?
|
||||
) {
|
||||
if keyPath == "outputVolume", let audioSession = self.audioSession, isIntercepting {
|
||||
let currentVolume = audioSession.outputVolume
|
||||
|
||||
if currentVolume > originalVolume {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.webView?.evaluateJavaScript(
|
||||
"window.onNativeKeyDown('VolumeUp');", completionHandler: nil)
|
||||
self?.setSystemVolume(self?.originalVolume ?? 0.1)
|
||||
}
|
||||
} else if currentVolume < originalVolume {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.webView?.evaluateJavaScript(
|
||||
"window.onNativeKeyDown('VolumeDown');", completionHandler: nil)
|
||||
self?.setSystemVolume(self?.originalVolume ?? 0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NativeBridgePlugin: Plugin {
|
||||
private var authSession: ASWebAuthenticationSession?
|
||||
private var webView: WKWebView?
|
||||
|
||||
@objc public override func load(webview: WKWebView) {
|
||||
self.webView = webview
|
||||
print("NativeBridgePlugin loaded")
|
||||
}
|
||||
|
||||
private struct AssociatedKeys {
|
||||
static var volumeKeyHandler = "volumeKeyHandler"
|
||||
static var interceptingVolumeKeys = "interceptingVolumeKeys"
|
||||
}
|
||||
|
||||
private var volumeKeyHandler: VolumeKeyHandler? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AssociatedKeys.volumeKeyHandler) as? VolumeKeyHandler
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(
|
||||
self, &AssociatedKeys.volumeKeyHandler, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
private var interceptingVolumeKeys: Bool {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AssociatedKeys.interceptingVolumeKeys) as? Bool
|
||||
?? false
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(
|
||||
self, &AssociatedKeys.interceptingVolumeKeys, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func use_background_audio(_ invoke: Invoke) {
|
||||
do {
|
||||
let args = try invoke.parseArgs(UseBackgroundAudioRequestArgs.self)
|
||||
let enabled = args.enabled
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
if enabled {
|
||||
try session.setCategory(.playback, mode: .default, options: [.mixWithOthers])
|
||||
try session.setActive(true)
|
||||
print("AVAudioSession activated")
|
||||
} else {
|
||||
try session.setActive(false)
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
||||
print("AVAudioSession deactivated")
|
||||
}
|
||||
invoke.resolve()
|
||||
} catch {
|
||||
print("Failed to set up audio session:", error)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func auth_with_safari(_ invoke: Invoke) throws {
|
||||
let args = try invoke.parseArgs(SafariAuthRequestArgs.self)
|
||||
let authUrl = URL(string: args.authUrl)!
|
||||
|
||||
authSession = ASWebAuthenticationSession(url: authUrl, callbackURLScheme: "readest") {
|
||||
[weak self] callbackURL, error in
|
||||
guard let strongSelf = self else { return }
|
||||
|
||||
if let error = error {
|
||||
Logger.error("Auth session error: \(error.localizedDescription)")
|
||||
invoke.reject(error.localizedDescription)
|
||||
return
|
||||
}
|
||||
|
||||
if let callbackURL = callbackURL {
|
||||
Logger.info("Auth session callback URL: \(callbackURL.absoluteString)")
|
||||
strongSelf.authSession?.cancel()
|
||||
strongSelf.authSession = nil
|
||||
invoke.resolve(["redirectUrl": callbackURL.absoluteString])
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
authSession?.presentationContextProvider = self
|
||||
}
|
||||
|
||||
let started = authSession?.start() ?? false
|
||||
Logger.info("Auth session start result: \(started)")
|
||||
}
|
||||
|
||||
@objc public func set_system_ui_visibility(_ invoke: Invoke) throws {
|
||||
let args = try invoke.parseArgs(SetSystemUIVisibilityRequestArgs.self)
|
||||
let visible = args.visible
|
||||
let darkMode = args.darkMode
|
||||
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.isIdleTimerDisabled = !visible
|
||||
UIApplication.shared.setStatusBarHidden(!visible, with: .none)
|
||||
|
||||
let windows = UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.flatMap { $0.windows }
|
||||
|
||||
let keyWindow = windows.first(where: { $0.isKeyWindow }) ?? windows.first
|
||||
if let keyWindow = keyWindow {
|
||||
keyWindow.overrideUserInterfaceStyle = darkMode ? .dark : .light
|
||||
keyWindow.layoutIfNeeded()
|
||||
} else {
|
||||
print("No key window found")
|
||||
}
|
||||
}
|
||||
invoke.resolve(["success": true])
|
||||
}
|
||||
|
||||
@objc public func get_sys_fonts_list(_ invoke: Invoke) throws {
|
||||
var fontList: [String] = []
|
||||
|
||||
for family in UIFont.familyNames.sorted() {
|
||||
if let localized = getLocalizedDisplayName(familyName: family) {
|
||||
fontList.append(localized)
|
||||
} else {
|
||||
let fontNames = UIFont.fontNames(forFamilyName: family)
|
||||
if fontNames.isEmpty {
|
||||
fontList.append(family)
|
||||
} else {
|
||||
fontList.append(contentsOf: fontNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invoke.resolve(["fonts": fontList])
|
||||
}
|
||||
|
||||
private func interceptVolumeKeys(_ intercept: Bool) {
|
||||
interceptingVolumeKeys = intercept
|
||||
|
||||
if intercept {
|
||||
if volumeKeyHandler == nil {
|
||||
volumeKeyHandler = VolumeKeyHandler()
|
||||
}
|
||||
|
||||
if let webView = self.webView {
|
||||
volumeKeyHandler?.startInterception(webView: webView)
|
||||
}
|
||||
} else {
|
||||
volumeKeyHandler?.stopInterception()
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func intercept_keys(_ invoke: Invoke) {
|
||||
do {
|
||||
let args = try invoke.parseArgs(InterceptKeysRequestArgs.self)
|
||||
|
||||
if let volumeKeys = args.volumeKeys {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.interceptVolumeKeys(volumeKeys)
|
||||
}
|
||||
}
|
||||
|
||||
invoke.resolve()
|
||||
} catch {
|
||||
invoke.reject(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_native_bridge")
|
||||
func initPlugin() -> Plugin {
|
||||
return NativeBridgePlugin()
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension NativeBridgePlugin: ASWebAuthenticationPresentationContextProviding {
|
||||
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
return UIApplication.shared.windows.first ?? UIWindow()
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import XCTest
|
||||
@testable import NativeBridgePlugin
|
||||
|
||||
final class NativeBridgePluginTests: XCTestCase {
|
||||
func testNativeBridge() throws {
|
||||
let plugin = NativeBridgePlugin()
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-auth-with-custom-tab"
|
||||
description = "Enables the auth_with_custom_tab command without any pre-configured scope."
|
||||
commands.allow = ["auth_with_custom_tab"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-auth-with-custom-tab"
|
||||
description = "Denies the auth_with_custom_tab command without any pre-configured scope."
|
||||
commands.deny = ["auth_with_custom_tab"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-copy-uri-to-path"
|
||||
description = "Enables the copy_uri_to_path command without any pre-configured scope."
|
||||
commands.allow = ["copy_uri_to_path"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-copy-uri-to-path"
|
||||
description = "Denies the copy_uri_to_path command without any pre-configured scope."
|
||||
commands.deny = ["copy_uri_to_path"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-get-status-bar-height"
|
||||
description = "Enables the get_status_bar_height command without any pre-configured scope."
|
||||
commands.allow = ["get_status_bar_height"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-get-status-bar-height"
|
||||
description = "Denies the get_status_bar_height command without any pre-configured scope."
|
||||
commands.deny = ["get_status_bar_height"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-get-sys-fonts-list"
|
||||
description = "Enables the get_sys_fonts_list command without any pre-configured scope."
|
||||
commands.allow = ["get_sys_fonts_list"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-get-sys-fonts-list"
|
||||
description = "Denies the get_sys_fonts_list command without any pre-configured scope."
|
||||
commands.deny = ["get_sys_fonts_list"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-install-package"
|
||||
description = "Enables the install_package command without any pre-configured scope."
|
||||
commands.allow = ["install_package"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-install-package"
|
||||
description = "Denies the install_package command without any pre-configured scope."
|
||||
commands.deny = ["install_package"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-intercept-keys"
|
||||
description = "Enables the intercept_keys command without any pre-configured scope."
|
||||
commands.allow = ["intercept_keys"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-intercept-keys"
|
||||
description = "Denies the intercept_keys command without any pre-configured scope."
|
||||
commands.deny = ["intercept_keys"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-set-system-ui-visibility"
|
||||
description = "Enables the set_system_ui_visibility command without any pre-configured scope."
|
||||
commands.allow = ["set_system_ui_visibility"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-set-system-ui-visibility"
|
||||
description = "Denies the set_system_ui_visibility command without any pre-configured scope."
|
||||
commands.deny = ["set_system_ui_visibility"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-use-background-audio"
|
||||
description = "Enables the use_background_audio command without any pre-configured scope."
|
||||
commands.allow = ["use_background_audio"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-use-background-audio"
|
||||
description = "Denies the use_background_audio command without any pre-configured scope."
|
||||
commands.deny = ["use_background_audio"]
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
## Default Permission
|
||||
|
||||
Default permissions for the plugin
|
||||
|
||||
#### This default permission set includes the following:
|
||||
|
||||
- `allow-auth-with-safari`
|
||||
- `allow-auth-with-custom-tab`
|
||||
- `allow-copy-uri-to-path`
|
||||
- `allow-use-background-audio`
|
||||
- `allow-install-package`
|
||||
- `allow-set-system-ui-visibility`
|
||||
- `allow-get-status-bar-height`
|
||||
- `allow-get-sys-fonts-list`
|
||||
- `allow-intercept-keys`
|
||||
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-auth-with-custom-tab`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the auth_with_custom_tab command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-auth-with-custom-tab`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the auth_with_custom_tab command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-auth-with-safari`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the auth_with_safari command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-auth-with-safari`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the auth_with_safari command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-copy-uri-to-path`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the copy_uri_to_path command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-copy-uri-to-path`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the copy_uri_to_path command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-get-status-bar-height`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the get_status_bar_height command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-get-status-bar-height`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the get_status_bar_height command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-get-sys-fonts-list`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the get_sys_fonts_list command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-get-sys-fonts-list`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the get_sys_fonts_list command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-install-package`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the install_package command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-install-package`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the install_package command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-intercept-keys`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the intercept_keys command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-intercept-keys`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the intercept_keys command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-set-system-ui-visibility`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the set_system_ui_visibility command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-set-system-ui-visibility`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the set_system_ui_visibility command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-use-background-audio`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the use_background_audio command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-use-background-audio`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the use_background_audio command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = [
|
||||
"allow-auth-with-safari",
|
||||
"allow-auth-with-custom-tab",
|
||||
"allow-copy-uri-to-path",
|
||||
"allow-use-background-audio",
|
||||
"allow-install-package",
|
||||
"allow-set-system-ui-visibility",
|
||||
"allow-get-status-bar-height",
|
||||
"allow-get-sys-fonts-list",
|
||||
"allow-intercept-keys",
|
||||
]
|
||||
+105
-6
@@ -49,7 +49,7 @@
|
||||
"minimum": 1.0
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.",
|
||||
"description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -111,7 +111,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.",
|
||||
"description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -294,20 +294,119 @@
|
||||
"PermissionKind": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Enables the auth_with_custom_tab command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-auth-with-custom-tab",
|
||||
"markdownDescription": "Enables the auth_with_custom_tab command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the auth_with_custom_tab command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-auth-with-custom-tab",
|
||||
"markdownDescription": "Denies the auth_with_custom_tab command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the auth_with_safari command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-auth-with-safari"
|
||||
"const": "allow-auth-with-safari",
|
||||
"markdownDescription": "Enables the auth_with_safari command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the auth_with_safari command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-auth-with-safari"
|
||||
"const": "deny-auth-with-safari",
|
||||
"markdownDescription": "Denies the auth_with_safari command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"description": "Enables the copy_uri_to_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "default"
|
||||
"const": "allow-copy-uri-to-path",
|
||||
"markdownDescription": "Enables the copy_uri_to_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the copy_uri_to_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-copy-uri-to-path",
|
||||
"markdownDescription": "Denies the copy_uri_to_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_status_bar_height command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-get-status-bar-height",
|
||||
"markdownDescription": "Enables the get_status_bar_height command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_status_bar_height command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-get-status-bar-height",
|
||||
"markdownDescription": "Denies the get_status_bar_height command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_sys_fonts_list command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-get-sys-fonts-list",
|
||||
"markdownDescription": "Enables the get_sys_fonts_list command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_sys_fonts_list command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-get-sys-fonts-list",
|
||||
"markdownDescription": "Denies the get_sys_fonts_list command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install_package command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-install-package",
|
||||
"markdownDescription": "Enables the install_package command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the install_package command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-install-package",
|
||||
"markdownDescription": "Denies the install_package command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the intercept_keys command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-intercept-keys",
|
||||
"markdownDescription": "Enables the intercept_keys command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the intercept_keys command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-intercept-keys",
|
||||
"markdownDescription": "Denies the intercept_keys command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the set_system_ui_visibility command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-set-system-ui-visibility",
|
||||
"markdownDescription": "Enables the set_system_ui_visibility command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the set_system_ui_visibility command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-set-system-ui-visibility",
|
||||
"markdownDescription": "Denies the set_system_ui_visibility command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the use_background_audio command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-use-background-audio",
|
||||
"markdownDescription": "Enables the use_background_audio command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the use_background_audio command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-use-background-audio",
|
||||
"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`",
|
||||
"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`"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use tauri::{command, AppHandle, Runtime};
|
||||
|
||||
use crate::models::*;
|
||||
use crate::NativeBridgeExt;
|
||||
use crate::Result;
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn auth_with_safari<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: AuthRequest,
|
||||
) -> Result<AuthResponse> {
|
||||
app.native_bridge().auth_with_safari(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn auth_with_custom_tab<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: AuthRequest,
|
||||
) -> Result<AuthResponse> {
|
||||
app.native_bridge().auth_with_custom_tab(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn copy_uri_to_path<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: CopyURIRequest,
|
||||
) -> Result<CopyURIResponse> {
|
||||
app.native_bridge().copy_uri_to_path(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn use_background_audio<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: UseBackgroundAudioRequest,
|
||||
) -> Result<()> {
|
||||
app.native_bridge().use_background_audio(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn install_package<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: InstallPackageRequest,
|
||||
) -> Result<InstallPackageResponse> {
|
||||
app.native_bridge().install_package(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn set_system_ui_visibility<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: SetSystemUIVisibilityRequest,
|
||||
) -> Result<SetSystemUIVisibilityResponse> {
|
||||
app.native_bridge().set_system_ui_visibility(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn get_status_bar_height<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
) -> Result<GetStatusBarHeightResponse> {
|
||||
app.native_bridge().get_status_bar_height()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn get_sys_fonts_list<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
) -> Result<GetSysFontsListResponse> {
|
||||
app.native_bridge().get_sys_fonts_list()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn intercept_keys<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: InterceptKeysRequest,
|
||||
) -> Result<()> {
|
||||
app.native_bridge().intercept_keys(payload)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use tauri::{plugin::PluginApi, AppHandle, Runtime};
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
||||
app: &AppHandle<R>,
|
||||
_api: PluginApi<R, C>,
|
||||
) -> crate::Result<NativeBridge<R>> {
|
||||
Ok(NativeBridge(app.clone()))
|
||||
}
|
||||
|
||||
/// Access to the native-bridge APIs.
|
||||
pub struct NativeBridge<R: Runtime>(AppHandle<R>);
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn auth_with_safari(&self, _payload: AuthRequest) -> crate::Result<AuthResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn auth_with_custom_tab(&self, _payload: AuthRequest) -> crate::Result<AuthResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn copy_uri_to_path(&self, _payload: CopyURIRequest) -> crate::Result<CopyURIResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn use_background_audio(&self, _payload: UseBackgroundAudioRequest) -> crate::Result<()> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn install_package(
|
||||
&self,
|
||||
_payload: InstallPackageRequest,
|
||||
) -> crate::Result<InstallPackageResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn set_system_ui_visibility(
|
||||
&self,
|
||||
_payload: SetSystemUIVisibilityRequest,
|
||||
) -> crate::Result<SetSystemUIVisibilityResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn get_status_bar_height(&self) -> crate::Result<GetStatusBarHeightResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn get_sys_fonts_list(&self) -> crate::Result<GetSysFontsListResponse> {
|
||||
let font_collection = font_enumeration::Collection::new().unwrap();
|
||||
let mut fonts = Vec::new();
|
||||
for font in font_collection.all() {
|
||||
fonts.push(font.font_name.clone());
|
||||
}
|
||||
Ok(GetSysFontsListResponse { fonts, error: None })
|
||||
}
|
||||
|
||||
pub fn intercept_keys(&self, _payload: InterceptKeysRequest) -> crate::Result<()> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
}
|
||||
+2
@@ -6,6 +6,8 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub enum Error {
|
||||
#[error("Unsupported platform for this plugin")]
|
||||
UnsupportedPlatformError,
|
||||
#[error("Native bridge error: {0}")]
|
||||
NativeBridgeError(String),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[cfg(mobile)]
|
||||
@@ -0,0 +1,59 @@
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Manager, Runtime,
|
||||
};
|
||||
|
||||
pub use models::*;
|
||||
|
||||
#[cfg(desktop)]
|
||||
mod desktop;
|
||||
#[cfg(mobile)]
|
||||
mod mobile;
|
||||
|
||||
mod commands;
|
||||
mod error;
|
||||
mod models;
|
||||
mod platform;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
|
||||
#[cfg(desktop)]
|
||||
use desktop::NativeBridge;
|
||||
#[cfg(mobile)]
|
||||
use mobile::NativeBridge;
|
||||
|
||||
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the native-bridge APIs.
|
||||
pub trait NativeBridgeExt<R: Runtime> {
|
||||
fn native_bridge(&self) -> &NativeBridge<R>;
|
||||
}
|
||||
|
||||
impl<R: Runtime, T: Manager<R>> crate::NativeBridgeExt<R> for T {
|
||||
fn native_bridge(&self) -> &NativeBridge<R> {
|
||||
self.state::<NativeBridge<R>>().inner()
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the plugin.
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("native-bridge")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::auth_with_safari,
|
||||
commands::auth_with_custom_tab,
|
||||
commands::copy_uri_to_path,
|
||||
commands::use_background_audio,
|
||||
commands::install_package,
|
||||
commands::set_system_ui_visibility,
|
||||
commands::get_status_bar_height,
|
||||
commands::get_sys_fonts_list,
|
||||
commands::intercept_keys,
|
||||
])
|
||||
.setup(|app, api| {
|
||||
#[cfg(mobile)]
|
||||
let native_bridge = mobile::init(app, api)?;
|
||||
#[cfg(desktop)]
|
||||
let native_bridge = desktop::init(app, api)?;
|
||||
app.manage(native_bridge);
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use tauri::{
|
||||
plugin::{PluginApi, PluginHandle},
|
||||
AppHandle, Runtime,
|
||||
};
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
tauri::ios_plugin_binding!(init_plugin_native_bridge);
|
||||
|
||||
// initializes the Kotlin or Swift plugin classes
|
||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
||||
_app: &AppHandle<R>,
|
||||
api: PluginApi<R, C>,
|
||||
) -> crate::Result<NativeBridge<R>> {
|
||||
#[cfg(target_os = "android")]
|
||||
let handle = api.register_android_plugin("com.readest.native_bridge", "NativeBridgePlugin")?;
|
||||
#[cfg(target_os = "ios")]
|
||||
let handle = api.register_ios_plugin(init_plugin_native_bridge)?;
|
||||
Ok(NativeBridge(handle))
|
||||
}
|
||||
|
||||
/// Access to the native-bridge APIs.
|
||||
pub struct NativeBridge<R: Runtime>(PluginHandle<R>);
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn auth_with_safari(&self, payload: AuthRequest) -> crate::Result<AuthResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("auth_with_safari", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn auth_with_custom_tab(&self, payload: AuthRequest) -> crate::Result<AuthResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("auth_with_custom_tab", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn copy_uri_to_path(&self, payload: CopyURIRequest) -> crate::Result<CopyURIResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("copy_uri_to_path", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn use_background_audio(&self, payload: UseBackgroundAudioRequest) -> crate::Result<()> {
|
||||
self.0
|
||||
.run_mobile_plugin("use_background_audio", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn install_package(
|
||||
&self,
|
||||
payload: InstallPackageRequest,
|
||||
) -> crate::Result<InstallPackageResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("install_package", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn set_system_ui_visibility(
|
||||
&self,
|
||||
payload: SetSystemUIVisibilityRequest,
|
||||
) -> crate::Result<SetSystemUIVisibilityResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("set_system_ui_visibility", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn get_status_bar_height(&self) -> crate::Result<GetStatusBarHeightResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("get_status_bar_height", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn get_sys_fonts_list(&self) -> crate::Result<GetSysFontsListResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("get_sys_fonts_list", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn intercept_keys(&self, payload: InterceptKeysRequest) -> crate::Result<()> {
|
||||
self.0
|
||||
.run_mobile_plugin("intercept_keys", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthRequest {
|
||||
pub auth_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthResponse {
|
||||
pub redirect_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CopyURIRequest {
|
||||
pub uri: String,
|
||||
pub dst: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CopyURIResponse {
|
||||
pub success: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UseBackgroundAudioRequest {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InstallPackageRequest {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InstallPackageResponse {
|
||||
pub success: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetSystemUIVisibilityRequest {
|
||||
pub visible: bool,
|
||||
pub dark_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetSystemUIVisibilityResponse {
|
||||
pub success: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetStatusBarHeightResponse {
|
||||
pub height: u32,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetSysFontsListResponse {
|
||||
pub fonts: Vec<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InterceptKeysRequest {
|
||||
pub volume_keys: Option<bool>,
|
||||
pub back_key: Option<bool>,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
@@ -1,17 +0,0 @@
|
||||
[package]
|
||||
name = "tauri-plugin-safari-auth"
|
||||
version = "0.1.0"
|
||||
authors = [ "chrox" ]
|
||||
description = "OAuth with ASWebAuthenticationSession on iOS"
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
exclude = ["/examples", "/dist-js", "/guest-js", "/node_modules"]
|
||||
links = "tauri-plugin-safari-auth"
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.2.4" }
|
||||
serde = "1.0"
|
||||
thiserror = "2"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.0.3", features = ["build"] }
|
||||
@@ -1 +0,0 @@
|
||||
# Tauri Plugin safari-auth
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
package com.plugin.safari_auth
|
||||
|
||||
import android.util.Log
|
||||
|
||||
class Example {
|
||||
fun pong(value: String): String {
|
||||
Log.i("Pong", value)
|
||||
return value
|
||||
}
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.plugin.safari_auth
|
||||
|
||||
import android.app.Activity
|
||||
import app.tauri.annotation.Command
|
||||
import app.tauri.annotation.InvokeArg
|
||||
import app.tauri.annotation.TauriPlugin
|
||||
import app.tauri.plugin.JSObject
|
||||
import app.tauri.plugin.Plugin
|
||||
import app.tauri.plugin.Invoke
|
||||
|
||||
@InvokeArg
|
||||
class PingArgs {
|
||||
var value: String? = null
|
||||
}
|
||||
|
||||
@TauriPlugin
|
||||
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
|
||||
private val implementation = Example()
|
||||
|
||||
@Command
|
||||
fun ping(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(PingArgs::class.java)
|
||||
|
||||
val ret = JSObject()
|
||||
ret.put("value", implementation.pong(args.value ?: "default value :("))
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
const COMMANDS: &[&str] = &["auth_with_safari"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS)
|
||||
.android_path("android")
|
||||
.ios_path("ios")
|
||||
.build();
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
import AuthenticationServices
|
||||
import SwiftRs
|
||||
import Tauri
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
class RequestArgs: Decodable {
|
||||
let authUrl: String
|
||||
}
|
||||
|
||||
struct Response: Encodable {
|
||||
let redirectUrl: String
|
||||
}
|
||||
|
||||
class SafariAuthPlugin: Plugin {
|
||||
private var authSession: ASWebAuthenticationSession?
|
||||
|
||||
@objc public func auth_with_safari(_ invoke: Invoke) throws {
|
||||
let args = try invoke.parseArgs(RequestArgs.self)
|
||||
let authUrl = URL(string: args.authUrl)!
|
||||
|
||||
authSession = ASWebAuthenticationSession(url: authUrl, callbackURLScheme: "readest") {
|
||||
[weak self] callbackURL, error in
|
||||
guard let strongSelf = self else { return }
|
||||
|
||||
if let error = error {
|
||||
Logger.error("Auth session error: \(error.localizedDescription)")
|
||||
invoke.reject(error.localizedDescription)
|
||||
return
|
||||
}
|
||||
|
||||
if let callbackURL = callbackURL {
|
||||
Logger.info("Auth session callback URL: \(callbackURL.absoluteString)")
|
||||
strongSelf.authSession?.cancel()
|
||||
strongSelf.authSession = nil
|
||||
invoke.resolve(["redirectUrl": callbackURL.absoluteString])
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
authSession?.presentationContextProvider = self
|
||||
}
|
||||
|
||||
let started = authSession?.start() ?? false
|
||||
Logger.info("Auth session start result: \(started)")
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_safari_auth")
|
||||
func initPlugin() -> Plugin {
|
||||
return SafariAuthPlugin()
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension SafariAuthPlugin: ASWebAuthenticationPresentationContextProviding {
|
||||
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
return UIApplication.shared.windows.first ?? UIWindow()
|
||||
}
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
import XCTest
|
||||
@testable import SafariAuthPlugin
|
||||
|
||||
final class SafariAuthPluginTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
let plugin = SafariAuthPlugin()
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
## Default Permission
|
||||
|
||||
Default permissions for the plugin
|
||||
|
||||
- `allow-auth-with-safari`
|
||||
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`safari-auth:allow-auth-with-safari`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the auth_with_safari command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`safari-auth:deny-auth-with-safari`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the auth_with_safari command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1,3 +0,0 @@
|
||||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = ["allow-auth-with-safari"]
|
||||
@@ -1,13 +0,0 @@
|
||||
use tauri::{command, AppHandle, Runtime};
|
||||
|
||||
use crate::models::*;
|
||||
use crate::Result;
|
||||
use crate::SafariAuthExt;
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn auth_with_safari<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: SafariAuthRequest,
|
||||
) -> Result<SafariAuthResponse> {
|
||||
app.safari_auth().auth_with_safari(payload)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use tauri::{plugin::PluginApi, AppHandle, Runtime};
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
||||
app: &AppHandle<R>,
|
||||
_api: PluginApi<R, C>,
|
||||
) -> crate::Result<SafariAuth<R>> {
|
||||
Ok(SafariAuth(app.clone()))
|
||||
}
|
||||
|
||||
/// Access to the safari-auth APIs.
|
||||
pub struct SafariAuth<R: Runtime>(AppHandle<R>);
|
||||
|
||||
impl<R: Runtime> SafariAuth<R> {
|
||||
pub fn auth_with_safari(
|
||||
&self,
|
||||
_payload: SafariAuthRequest,
|
||||
) -> crate::Result<SafariAuthResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Manager, Runtime,
|
||||
};
|
||||
|
||||
pub use models::*;
|
||||
|
||||
#[cfg(desktop)]
|
||||
mod desktop;
|
||||
#[cfg(mobile)]
|
||||
mod mobile;
|
||||
|
||||
mod commands;
|
||||
mod error;
|
||||
mod models;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
|
||||
#[cfg(desktop)]
|
||||
use desktop::SafariAuth;
|
||||
#[cfg(mobile)]
|
||||
use mobile::SafariAuth;
|
||||
|
||||
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the safari-auth APIs.
|
||||
pub trait SafariAuthExt<R: Runtime> {
|
||||
fn safari_auth(&self) -> &SafariAuth<R>;
|
||||
}
|
||||
|
||||
impl<R: Runtime, T: Manager<R>> crate::SafariAuthExt<R> for T {
|
||||
fn safari_auth(&self) -> &SafariAuth<R> {
|
||||
self.state::<SafariAuth<R>>().inner()
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the plugin.
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("safari-auth")
|
||||
.invoke_handler(tauri::generate_handler![commands::auth_with_safari])
|
||||
.setup(|app, api| {
|
||||
#[cfg(mobile)]
|
||||
let safari_auth = mobile::init(app, api)?;
|
||||
#[cfg(desktop)]
|
||||
let safari_auth = desktop::init(app, api)?;
|
||||
app.manage(safari_auth);
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use tauri::{
|
||||
plugin::{PluginApi, PluginHandle},
|
||||
AppHandle, Runtime,
|
||||
};
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
tauri::ios_plugin_binding!(init_plugin_safari_auth);
|
||||
|
||||
// initializes the Kotlin or Swift plugin classes
|
||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
||||
_app: &AppHandle<R>,
|
||||
api: PluginApi<R, C>,
|
||||
) -> crate::Result<SafariAuth<R>> {
|
||||
#[cfg(target_os = "android")]
|
||||
let handle = api.register_android_plugin("com.bilingify.safari_auth", "ExamplePlugin")?;
|
||||
#[cfg(target_os = "ios")]
|
||||
let handle = api.register_ios_plugin(init_plugin_safari_auth)?;
|
||||
|
||||
Ok(SafariAuth(handle))
|
||||
}
|
||||
|
||||
/// Access to the safari-auth APIs.
|
||||
pub struct SafariAuth<R: Runtime>(PluginHandle<R>);
|
||||
|
||||
impl<R: Runtime> SafariAuth<R> {
|
||||
pub fn auth_with_safari(
|
||||
&self,
|
||||
payload: SafariAuthRequest,
|
||||
) -> crate::Result<SafariAuthResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("auth_with_safari", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SafariAuthRequest {
|
||||
pub auth_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SafariAuthResponse {
|
||||
pub redirect_url: String,
|
||||
}
|
||||
@@ -6,13 +6,6 @@ extern crate cocoa;
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod menu;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod traffic_light;
|
||||
#[cfg(target_os = "macos")]
|
||||
use traffic_light::set_traffic_lights;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::TitleBarStyle;
|
||||
|
||||
@@ -23,6 +16,8 @@ use tauri::{AppHandle, Listener, Manager, Url};
|
||||
#[cfg(desktop)]
|
||||
use tauri_plugin_fs::FsExt;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
mod transfer_file;
|
||||
use tauri::{command, Emitter, WebviewUrl, WebviewWindowBuilder, Window};
|
||||
use tauri_plugin_oauth::start;
|
||||
@@ -46,12 +41,40 @@ fn allow_file_in_scopes(app: &AppHandle, files: Vec<PathBuf>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn get_files_from_argv(argv: Vec<String>) -> Vec<PathBuf> {
|
||||
let mut files = Vec::new();
|
||||
// NOTICE: `args` may include URL protocol (`your-app-protocol://`)
|
||||
// or arguments (`--`) if your app supports them.
|
||||
// files may also be passed as `file://path/to/file`
|
||||
for (_, maybe_file) in argv.iter().enumerate().skip(1) {
|
||||
// skip flags like -f or --flag
|
||||
if maybe_file.starts_with("-") {
|
||||
continue;
|
||||
}
|
||||
// handle `file://` path urls and skip other urls
|
||||
if let Ok(url) = Url::parse(maybe_file) {
|
||||
if let Ok(path) = url.to_file_path() {
|
||||
files.push(path);
|
||||
} else {
|
||||
files.push(PathBuf::from(maybe_file))
|
||||
}
|
||||
} else {
|
||||
files.push(PathBuf::from(maybe_file))
|
||||
}
|
||||
}
|
||||
files
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn set_window_open_with_files(app: &AppHandle, files: Vec<PathBuf>) {
|
||||
let files = files
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
let file = f.to_string_lossy().replace("\\", "\\\\");
|
||||
let file = f
|
||||
.to_string_lossy()
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"");
|
||||
format!("\"{file}\"",)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@@ -63,6 +86,15 @@ fn set_window_open_with_files(app: &AppHandle, files: Vec<PathBuf>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn set_rounded_window(app: &AppHandle, rounded: bool) {
|
||||
let window = app.get_webview_window("main").unwrap();
|
||||
let script = format!("window.IS_ROUNDED = {};", rounded);
|
||||
if let Err(e) = window.eval(&script) {
|
||||
eprintln!("Failed to set IS_ROUNDED variable: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn start_server(window: Window) -> Result<u16, String> {
|
||||
start(move |url| {
|
||||
@@ -73,17 +105,6 @@ async fn start_server(window: Window) -> Result<u16, String> {
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[command]
|
||||
async fn list_fonts() -> Result<Vec<String>, String> {
|
||||
let font_collection = font_enumeration::Collection::new().unwrap();
|
||||
let mut fonts = Vec::new();
|
||||
for font in font_collection.all() {
|
||||
fonts.push(font.family_name.clone());
|
||||
}
|
||||
Ok(fonts)
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
#[allow(dead_code)]
|
||||
struct Payload {
|
||||
@@ -101,15 +122,18 @@ pub fn run() {
|
||||
download_file,
|
||||
upload_file,
|
||||
#[cfg(target_os = "macos")]
|
||||
set_traffic_lights,
|
||||
#[cfg(desktop)]
|
||||
list_fonts
|
||||
macos::safari_auth::auth_with_safari,
|
||||
#[cfg(target_os = "macos")]
|
||||
macos::apple_auth::start_apple_sign_in,
|
||||
#[cfg(target_os = "macos")]
|
||||
macos::traffic_light::set_traffic_lights,
|
||||
])
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_native_bridge::init())
|
||||
.plugin(tauri_plugin_fs::init());
|
||||
|
||||
#[cfg(desktop)]
|
||||
@@ -118,6 +142,10 @@ pub fn run() {
|
||||
.get_webview_window("main")
|
||||
.expect("no main window")
|
||||
.set_focus();
|
||||
let files = get_files_from_argv(argv.clone());
|
||||
if !files.is_empty() {
|
||||
allow_file_in_scopes(app, files.clone());
|
||||
}
|
||||
app.emit("single-instance", Payload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}));
|
||||
@@ -131,14 +159,14 @@ pub fn run() {
|
||||
let builder = builder.plugin(tauri_plugin_window_state::Builder::default().build());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let builder = builder.plugin(traffic_light::init());
|
||||
let builder = builder.plugin(macos::traffic_light::init());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let builder = builder.plugin(macos::safari_auth::init());
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
let builder = builder.plugin(tauri_plugin_sign_in_with_apple::init());
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
let builder = builder.plugin(tauri_plugin_safari_auth::init());
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "android"))]
|
||||
let builder = builder.plugin(tauri_plugin_haptics::init());
|
||||
|
||||
@@ -146,26 +174,7 @@ pub fn run() {
|
||||
.setup(|#[allow(unused_variables)] app| {
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
let mut files = Vec::new();
|
||||
// NOTICE: `args` may include URL protocol (`your-app-protocol://`)
|
||||
// or arguments (`--`) if your app supports them.
|
||||
// files may also be passed as `file://path/to/file`
|
||||
for maybe_file in std::env::args().skip(1) {
|
||||
// skip flags like -f or --flag
|
||||
if maybe_file.starts_with("-") {
|
||||
continue;
|
||||
}
|
||||
// handle `file://` path urls and skip other urls
|
||||
if let Ok(url) = Url::parse(&maybe_file) {
|
||||
if let Ok(path) = url.to_file_path() {
|
||||
files.push(path);
|
||||
} else {
|
||||
files.push(PathBuf::from(maybe_file))
|
||||
}
|
||||
} else {
|
||||
files.push(PathBuf::from(maybe_file))
|
||||
}
|
||||
}
|
||||
let files = get_files_from_argv(std::env::args().collect());
|
||||
if !files.is_empty() {
|
||||
let app_handle = app.handle().clone();
|
||||
allow_file_in_scopes(&app_handle, files.clone());
|
||||
@@ -182,33 +191,58 @@ pub fn run() {
|
||||
|
||||
let app_handle = app.handle().clone();
|
||||
app.listen("window-ready", move |_| {
|
||||
app_handle.get_webview_window("main").unwrap()
|
||||
.eval("window.__READEST_CLI_ACCESS = true; window.__READEST_UPDATER_ACCESS = true;")
|
||||
app_handle
|
||||
.get_webview_window("main")
|
||||
.unwrap()
|
||||
.eval("window.__READEST_CLI_ACCESS = true;")
|
||||
.expect("Failed to set cli access config");
|
||||
|
||||
set_rounded_window(&app_handle, true);
|
||||
#[cfg(target_os = "windows")]
|
||||
if tauri_plugin_os::version()
|
||||
<= tauri_plugin_os::Version::from_string("10.0.19045")
|
||||
{
|
||||
set_rounded_window(&app_handle, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
app.deep_link().register_all()?;
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
fn has_xdg_mime() -> bool {
|
||||
std::process::Command::new("which")
|
||||
.arg("xdg-mime")
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
if has_xdg_mime() {
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
app.deep_link().register_all()?;
|
||||
} else {
|
||||
println!("xdg-mime not found; skipping deep link setup on Linux.");
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
) {
|
||||
eprintln!("Failed to initialize tauri_plugin_log: {}", e);
|
||||
};
|
||||
|
||||
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default());
|
||||
|
||||
#[cfg(desktop)]
|
||||
let win_builder = win_builder
|
||||
.inner_size(800.0, 600.0)
|
||||
.resizable(true);
|
||||
let win_builder = win_builder.inner_size(800.0, 600.0).resizable(true);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let win_builder = win_builder
|
||||
@@ -225,8 +259,9 @@ pub fn run() {
|
||||
.title("Readest");
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
let version = tauri_plugin_os::version();
|
||||
if version <= tauri_plugin_os::Version::from_string("10.0.19045") {
|
||||
if tauri_plugin_os::version()
|
||||
<= tauri_plugin_os::Version::from_string("10.0.19045")
|
||||
{
|
||||
win_builder = win_builder.shadow(false);
|
||||
} else {
|
||||
win_builder = win_builder.shadow(true);
|
||||
@@ -243,7 +278,7 @@ pub fn run() {
|
||||
// win.open_devtools();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
menu::setup_macos_menu(app.handle())?;
|
||||
macos::menu::setup_macos_menu(app.handle())?;
|
||||
|
||||
app.handle().emit("window-ready", ()).unwrap();
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{
|
||||
define_class, msg_send, AllocAnyThread, DefinedClass, MainThreadMarker, MainThreadOnly,
|
||||
};
|
||||
use objc2_authentication_services::{
|
||||
ASAuthorization, ASAuthorizationAppleIDCredential, ASAuthorizationAppleIDProvider,
|
||||
ASAuthorizationController, ASAuthorizationControllerDelegate, ASAuthorizationRequest,
|
||||
ASAuthorizationScopeEmail, ASAuthorizationScopeFullName,
|
||||
};
|
||||
use objc2_foundation::{NSArray, NSError, NSObject, NSObjectProtocol, NSString};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use tauri::{command, AppHandle, Emitter};
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppleIDAuthorizationRequest {
|
||||
pub scope: Vec<String>,
|
||||
pub nonce: Option<String>,
|
||||
pub state: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppleIDAuthorizationResponse {
|
||||
pub user_identifier: Option<String>,
|
||||
pub given_name: Option<String>,
|
||||
pub family_name: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub authorization_code: Option<String>,
|
||||
pub identity_token: Option<String>,
|
||||
pub state: Option<String>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static APPLE_SIGN_IN_DELEGATE: RefCell<Option<Retained<ASAuthorizationControllerDelegateImpl>>> = RefCell::new(None);
|
||||
static AUTHORIZATION_CONTROLLER: RefCell<Option<Retained<ASAuthorizationController>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ivars {
|
||||
app: AppHandle,
|
||||
}
|
||||
|
||||
define_class!(
|
||||
#[unsafe(super(NSObject))]
|
||||
#[thread_kind = MainThreadOnly]
|
||||
#[name = "ASAuthorizationControllerDelegateImpl"]
|
||||
#[ivars = Ivars]
|
||||
pub struct ASAuthorizationControllerDelegateImpl;
|
||||
|
||||
unsafe impl NSObjectProtocol for ASAuthorizationControllerDelegateImpl {}
|
||||
|
||||
unsafe impl ASAuthorizationControllerDelegate for ASAuthorizationControllerDelegateImpl {
|
||||
#[unsafe(method(authorizationController:didCompleteWithAuthorization:))]
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn authorizationController_didCompleteWithAuthorization(
|
||||
&self,
|
||||
_controller: &ASAuthorizationController,
|
||||
authorization: &ASAuthorization,
|
||||
) {
|
||||
if let Ok(credential) = authorization
|
||||
.credential()
|
||||
.downcast::<ASAuthorizationAppleIDCredential>()
|
||||
{
|
||||
let user_identifier = {
|
||||
let user = credential.user();
|
||||
if user.len() > 0 {
|
||||
Some(user.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let given_name = credential
|
||||
.fullName()
|
||||
.and_then(|name| name.givenName())
|
||||
.map(|nsstr| nsstr.to_string());
|
||||
let family_name = credential
|
||||
.fullName()
|
||||
.and_then(|name| name.familyName())
|
||||
.map(|nsstr| nsstr.to_string());
|
||||
let email = credential.email().map(|nsstr| nsstr.to_string());
|
||||
|
||||
let authorization_code = credential
|
||||
.authorizationCode()
|
||||
.and_then(|code_data| String::from_utf8(code_data.to_vec()).ok());
|
||||
|
||||
let identity_token = credential
|
||||
.identityToken()
|
||||
.and_then(|token_data| String::from_utf8(token_data.to_vec()).ok());
|
||||
|
||||
let state = credential.state().map(|nsstr| nsstr.to_string());
|
||||
|
||||
let resp = AppleIDAuthorizationResponse {
|
||||
user_identifier,
|
||||
given_name,
|
||||
family_name,
|
||||
email,
|
||||
authorization_code,
|
||||
identity_token,
|
||||
state,
|
||||
};
|
||||
let _ = self.ivars().app.emit("apple-sign-in-complete", json!(resp));
|
||||
log::info!("Apple authorization complete");
|
||||
} else {
|
||||
let _ = self
|
||||
.ivars()
|
||||
.app
|
||||
.emit("apple-sign-in-error", "Invalid credential type");
|
||||
log::error!("Invalid credential type received");
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(method(authorizationController:didCompleteWithError:))]
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn authorizationController_didCompleteWithError(
|
||||
&self,
|
||||
_controller: &ASAuthorizationController,
|
||||
error: &NSError,
|
||||
) {
|
||||
let code = error.code();
|
||||
let description = error.localizedDescription().to_string();
|
||||
let _ = self.ivars().app.emit(
|
||||
"apple-sign-in-error",
|
||||
json!({
|
||||
"code": code,
|
||||
"description": description,
|
||||
}),
|
||||
);
|
||||
|
||||
log::error!(
|
||||
"Authorization error: code={}, description={}",
|
||||
code,
|
||||
description
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl ASAuthorizationControllerDelegateImpl {
|
||||
fn new(app: AppHandle) -> Retained<Self> {
|
||||
let mtm = MainThreadMarker::new().expect("must run on main thread");
|
||||
let this = Self::alloc(mtm).set_ivars(Ivars { app });
|
||||
unsafe { msg_send![super(this), init] }
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn start_apple_sign_in(app: AppHandle, payload: AppleIDAuthorizationRequest) {
|
||||
unsafe {
|
||||
let provider = ASAuthorizationAppleIDProvider::new();
|
||||
let request = provider.createRequest();
|
||||
|
||||
if let Some(ref nonce) = payload.nonce {
|
||||
let ns_nonce = NSString::from_str(nonce);
|
||||
request.setNonce(Some(&ns_nonce));
|
||||
}
|
||||
|
||||
if let Some(ref state) = payload.state {
|
||||
let ns_state = NSString::from_str(state);
|
||||
request.setState(Some(&ns_state));
|
||||
}
|
||||
|
||||
let mut scopes = Vec::new();
|
||||
for scope in payload.scope.iter() {
|
||||
match scope.as_str() {
|
||||
"email" => scopes.push(ASAuthorizationScopeEmail),
|
||||
"fullName" => scopes.push(ASAuthorizationScopeFullName),
|
||||
_ => {
|
||||
log::warn!("[Apple Sign-In] Unsupported scope: {}", scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !scopes.is_empty() {
|
||||
request.setRequestedScopes(Some(&*NSArray::from_slice(&scopes)));
|
||||
}
|
||||
|
||||
let auth_request = &request as &ASAuthorizationRequest;
|
||||
let controller = ASAuthorizationController::initWithAuthorizationRequests(
|
||||
ASAuthorizationController::alloc(),
|
||||
&*NSArray::from_slice(&[auth_request]),
|
||||
);
|
||||
|
||||
let delegate = ASAuthorizationControllerDelegateImpl::new(app.clone());
|
||||
APPLE_SIGN_IN_DELEGATE.with(|cell| {
|
||||
*cell.borrow_mut() = Some(delegate.clone());
|
||||
});
|
||||
|
||||
AUTHORIZATION_CONTROLLER.with(|cell| {
|
||||
*cell.borrow_mut() = Some(controller.clone());
|
||||
});
|
||||
|
||||
controller.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
|
||||
log::info!("Starting Apple Sign-In authorization request");
|
||||
controller.performRequests();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod apple_auth;
|
||||
pub mod menu;
|
||||
pub mod safari_auth;
|
||||
pub mod traffic_light;
|
||||
@@ -0,0 +1,255 @@
|
||||
use std::ffi::CStr;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use block::ConcreteBlock;
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL, YES};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc_foundation::{INSString, NSString};
|
||||
use objc_id::Id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use tauri::{
|
||||
command,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Emitter, Manager, Runtime, State, Window,
|
||||
};
|
||||
|
||||
pub struct ThreadSafeObjcPointer(*mut Object, PhantomData<*mut Object>);
|
||||
|
||||
unsafe impl Send for ThreadSafeObjcPointer {}
|
||||
unsafe impl Sync for ThreadSafeObjcPointer {}
|
||||
|
||||
impl ThreadSafeObjcPointer {
|
||||
pub fn new(ptr: *mut Object) -> Self {
|
||||
Self(ptr, PhantomData)
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut Object {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.0.is_null()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthSession {
|
||||
auth_session: Option<ThreadSafeObjcPointer>,
|
||||
}
|
||||
|
||||
impl Default for AuthSession {
|
||||
fn default() -> Self {
|
||||
Self { auth_session: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AuthSession {
|
||||
fn drop(&mut self) {
|
||||
if let Some(session) = &self.auth_session {
|
||||
if !session.is_null() {
|
||||
unsafe {
|
||||
let _: () = msg_send![session.as_ptr(), cancel];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SafariAuthRequestArgs {
|
||||
auth_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthResult {
|
||||
redirect_url: String,
|
||||
}
|
||||
|
||||
fn setup_presentation_context_provider() -> &'static Class {
|
||||
let class_name = "TauriPresentationContextProvider";
|
||||
let mut decl = match Class::get(class_name) {
|
||||
Some(class) => return class,
|
||||
None => ClassDecl::new(class_name, class!(NSObject)).unwrap(),
|
||||
};
|
||||
|
||||
extern "C" fn presentation_anchor_for_web_auth_session(
|
||||
this: &Object,
|
||||
_sel: Sel,
|
||||
_session: *mut Object,
|
||||
) -> *mut Object {
|
||||
unsafe {
|
||||
let window: *mut Object = *this.get_ivar("window");
|
||||
window
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
decl.add_ivar::<*mut Object>("window");
|
||||
|
||||
let sel = sel!(presentationAnchorForWebAuthenticationSession:);
|
||||
decl.add_method(
|
||||
sel,
|
||||
presentation_anchor_for_web_auth_session
|
||||
as extern "C" fn(&Object, Sel, *mut Object) -> *mut Object,
|
||||
);
|
||||
}
|
||||
|
||||
decl.register()
|
||||
}
|
||||
|
||||
unsafe fn create_provider(window: *mut Object) -> *mut Object {
|
||||
let class = setup_presentation_context_provider();
|
||||
let provider: *mut Object = msg_send![class, alloc];
|
||||
let provider: *mut Object = msg_send![provider, init];
|
||||
|
||||
(*provider).set_ivar("window", window);
|
||||
|
||||
provider
|
||||
}
|
||||
|
||||
unsafe fn nsstring_to_string(ns_string: *mut Object) -> String {
|
||||
if ns_string.is_null() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let utf8_ptr: *const i8 = msg_send![ns_string, UTF8String];
|
||||
if utf8_ptr.is_null() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
CStr::from_ptr(utf8_ptr).to_string_lossy().into_owned()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn auth_with_safari<R: Runtime>(
|
||||
window: Window<R>,
|
||||
state: State<'_, Arc<Mutex<AuthSession>>>,
|
||||
payload: SafariAuthRequestArgs,
|
||||
) -> Result<AuthResult, String> {
|
||||
let auth_url_str = NSString::from_str(&payload.auth_url);
|
||||
let url_class = Class::get("NSURL").unwrap();
|
||||
let auth_url: Id<Object> = unsafe {
|
||||
let url: *mut Object = msg_send![url_class, URLWithString:auth_url_str];
|
||||
if url.is_null() {
|
||||
return Err("Failed to create URL".to_string());
|
||||
}
|
||||
Id::from_ptr(url)
|
||||
};
|
||||
|
||||
let auth_session_class = match Class::get("ASWebAuthenticationSession") {
|
||||
Some(class) => class,
|
||||
None => {
|
||||
return Err(
|
||||
"ASWebAuthenticationSession class not found. This requires macOS 10.15+"
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let window_clone = window.clone();
|
||||
let state_clone = Arc::clone(&*state);
|
||||
let completion_block =
|
||||
ConcreteBlock::new(move |callback_url: *mut Object, error: *mut Object| {
|
||||
let mut auth_session = match state_clone.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if !error.is_null() {
|
||||
unsafe {
|
||||
let description: *mut Object = msg_send![error, localizedDescription];
|
||||
let error_description = nsstring_to_string(description);
|
||||
log::error!("Auth session error: {}", error_description);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if !callback_url.is_null() {
|
||||
let url_string_value = unsafe {
|
||||
let abs_str: *mut Object = msg_send![callback_url, absoluteString];
|
||||
nsstring_to_string(abs_str)
|
||||
};
|
||||
|
||||
log::info!("Auth session callback URL: {}", url_string_value);
|
||||
|
||||
if let Some(session) = &auth_session.auth_session {
|
||||
unsafe {
|
||||
let _: () = msg_send![session.as_ptr(), cancel];
|
||||
}
|
||||
}
|
||||
|
||||
auth_session.auth_session = None;
|
||||
|
||||
let result = AuthResult {
|
||||
redirect_url: url_string_value,
|
||||
};
|
||||
|
||||
let _ = window_clone.emit("safari-auth-complete", result);
|
||||
}
|
||||
});
|
||||
|
||||
let completion_block = completion_block.copy();
|
||||
|
||||
let callback_scheme = NSString::from_str("readest");
|
||||
let auth_session_ptr: *mut Object = unsafe {
|
||||
let alloc: *mut Object = msg_send![auth_session_class, alloc];
|
||||
if alloc.is_null() {
|
||||
return Err("Failed to allocate ASWebAuthenticationSession".to_string());
|
||||
}
|
||||
|
||||
let init: *mut Object = msg_send![
|
||||
alloc,
|
||||
initWithURL:auth_url
|
||||
callbackURLScheme:callback_scheme
|
||||
completionHandler:&*completion_block
|
||||
];
|
||||
|
||||
if init.is_null() {
|
||||
return Err("Failed to initialize ASWebAuthenticationSession".to_string());
|
||||
}
|
||||
init
|
||||
};
|
||||
|
||||
unsafe {
|
||||
match window.ns_window() {
|
||||
Ok(ns_window) => {
|
||||
let ns_window = ns_window as *mut Object;
|
||||
let provider = create_provider(ns_window);
|
||||
|
||||
let _: () = msg_send![auth_session_ptr, setPresentationContextProvider:provider];
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("Failed to get NSWindow for presentation context: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let started: BOOL = unsafe { msg_send![auth_session_ptr, start] };
|
||||
|
||||
log::info!("Auth session start result: {}", started == YES);
|
||||
|
||||
if started != YES {
|
||||
return Err("Failed to start authentication session".to_string());
|
||||
}
|
||||
|
||||
let mut auth_session_guard = state.lock().unwrap();
|
||||
auth_session_guard.auth_session = Some(ThreadSafeObjcPointer::new(auth_session_ptr));
|
||||
|
||||
Ok(AuthResult {
|
||||
redirect_url: "pending".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("safari_auth")
|
||||
.setup(|app, _| {
|
||||
app.manage(Arc::new(Mutex::new(AuthSession::default())));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![auth_with_safari])
|
||||
.build()
|
||||
}
|
||||
+16
-17
@@ -30,7 +30,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[command]
|
||||
pub fn set_traffic_lights(window: Window, visible: bool, x: f64, y: f64) {
|
||||
unsafe {
|
||||
@@ -47,7 +46,6 @@ pub fn set_traffic_lights(window: Window, visible: bool, x: f64, y: f64) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, visible: bool, x: f64, y: f64) {
|
||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||
use cocoa::foundation::NSRect;
|
||||
@@ -83,13 +81,11 @@ fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, visible: bool,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Debug)]
|
||||
struct WindowState<R: Runtime> {
|
||||
window: Window<R>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
use cocoa::base::{id, BOOL};
|
||||
@@ -148,13 +144,14 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
|
||||
.expect("NS window should exist on state to handle resize")
|
||||
as id;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
TRAFFIC_LIGHTS_VISIBLE,
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
if state.window.label() == "main" {
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
TRAFFIC_LIGHTS_VISIBLE,
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
@@ -281,12 +278,14 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
|
||||
.expect("Failed to emit event");
|
||||
|
||||
let id = state.window.ns_window().expect("Failed to emit event") as id;
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
TRAFFIC_LIGHTS_VISIBLE,
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
if state.window.label() == "main" {
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
TRAFFIC_LIGHTS_VISIBLE,
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
@@ -17,15 +17,20 @@
|
||||
"default-src": "'self' 'unsafe-inline' blob: data: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost",
|
||||
"connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com",
|
||||
"img-src": "'self' blob: data: asset: http://asset.localhost https://*",
|
||||
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com",
|
||||
"font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://fonts.gstatic.com https://db.onlinewebfonts.com https://cdn.jsdelivr.net",
|
||||
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com https://fontsapi.zeoseven.com",
|
||||
"font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://db.onlinewebfonts.com https://cdn.jsdelivr.net https://fonts.gstatic.com https://fontsapi.zeoseven.com",
|
||||
"frame-src": "'self' blob: asset: http://asset.localhost",
|
||||
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' blob: asset: http://asset.localhost https://*.sentry.io https://*.posthog.com"
|
||||
},
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": {
|
||||
"allow": ["$RESOURCE/**", "$APPDATA/**/*", "$TEMP/**/*"],
|
||||
"allow": [
|
||||
"$RESOURCE/**",
|
||||
"$APPDATA/**/*",
|
||||
"$TEMP/**/*",
|
||||
"/private/var/mobile/Containers/Data/Application/**/*"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,27 @@ export default function AuthCallback() {
|
||||
|
||||
const accessToken = params.get('access_token');
|
||||
const refreshToken = params.get('refresh_token');
|
||||
const type = params.get('type');
|
||||
const next = params.get('next') ?? '/';
|
||||
const error = params.get('error');
|
||||
const errorDescription = params.get('error_description');
|
||||
const errorCode = params.get('error_code');
|
||||
|
||||
handleAuthCallback({ accessToken, refreshToken, next, login, navigate: router.push });
|
||||
handleAuthCallback({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
type,
|
||||
next,
|
||||
error,
|
||||
errorCode,
|
||||
errorDescription,
|
||||
login,
|
||||
navigate: router.push,
|
||||
});
|
||||
|
||||
return null;
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||
<span className='loading loading-infinity loading-xl w-20'></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTheme } from '@/hooks/useTheme';
|
||||
|
||||
export default function AuthErrorPage() {
|
||||
const router = useRouter();
|
||||
useTheme();
|
||||
useTheme({ systemUIVisible: false });
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
|
||||
@@ -12,17 +12,21 @@ import { IoArrowBack } from 'react-icons/io5';
|
||||
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { supabase } from '@/utils/supabase';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useTrafficLightStore } from '@/store/trafficLightStore';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
|
||||
import { start, cancel, onUrl, onInvalidUrl } from '@fabianlars/tauri-plugin-oauth';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { handleAuthCallback } from '@/helpers/auth';
|
||||
import { getAppleIdAuth, Scope } from './utils/appleIdAuth';
|
||||
import { authWithSafari } from './utils/safariAuth';
|
||||
import { authWithCustomTab, authWithSafari } from './utils/nativeAuth';
|
||||
import { READEST_WEB_BASE_URL } from '@/services/constants';
|
||||
import WindowButtons from '@/components/WindowButtons';
|
||||
|
||||
type OAuthProvider = 'google' | 'apple' | 'azure' | 'github';
|
||||
|
||||
@@ -38,8 +42,9 @@ interface ProviderLoginProp {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const WEB_AUTH_CALLBACK = 'https://web.readest.com/auth/callback';
|
||||
const DEEPLINK_CALLBACK = 'readest://auth/callback';
|
||||
const WEB_AUTH_CALLBACK = `${READEST_WEB_BASE_URL}/auth/callback`;
|
||||
const DEEPLINK_CALLBACK = 'readest://auth-callback';
|
||||
const USE_APPLE_SIGN_IN = process.env['NEXT_PUBLIC_USE_APPLE_SIGN_IN'] === 'true';
|
||||
|
||||
const ProviderLogin: React.FC<ProviderLoginProp> = ({ provider, handleSignIn, Icon, label }) => {
|
||||
return (
|
||||
@@ -47,11 +52,11 @@ const ProviderLogin: React.FC<ProviderLoginProp> = ({ provider, handleSignIn, Ic
|
||||
onClick={() => handleSignIn(provider)}
|
||||
className={clsx(
|
||||
'mb-2 flex w-64 items-center justify-center rounded border p-2.5',
|
||||
'bg-base-100 border-gray-300 shadow-sm transition hover:bg-gray-50',
|
||||
'bg-base-100 border-base-300 hover:bg-base-200 shadow-sm transition',
|
||||
)}
|
||||
>
|
||||
<Icon />
|
||||
<span className='text-neutral-content/70 px-2 text-sm'>{label}</span>
|
||||
<span className='text-base-content/75 px-2 text-sm'>{label}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -61,22 +66,34 @@ export default function AuthPage() {
|
||||
const router = useRouter();
|
||||
const { login } = useAuth();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { isDarkMode } = useTheme();
|
||||
const { isDarkMode } = useThemeStore();
|
||||
const { isTrafficLightVisible } = useTrafficLightStore();
|
||||
const { settings, setSettings, saveSettings } = useSettingsStore();
|
||||
const [port, setPort] = useState<number | null>(null);
|
||||
const isOAuthServerRunning = useRef(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useTheme({ systemUIVisible: false });
|
||||
|
||||
const getTauriRedirectTo = (isOAuth: boolean) => {
|
||||
if (process.env.NODE_ENV === 'production' || appService?.isMobile) {
|
||||
if (appService?.isIOSApp) {
|
||||
if (process.env.NODE_ENV === 'production' || appService?.isMobile || USE_APPLE_SIGN_IN) {
|
||||
if (appService?.isMobile) {
|
||||
return isOAuth ? DEEPLINK_CALLBACK : WEB_AUTH_CALLBACK;
|
||||
} else if (appService?.isAndroidApp) {
|
||||
return WEB_AUTH_CALLBACK;
|
||||
}
|
||||
return DEEPLINK_CALLBACK;
|
||||
}
|
||||
return `http://localhost:${port}`; // only for development env on Desktop
|
||||
// For development env on Desktop, use a custom OAuth callback server
|
||||
// it's possible to register a custom URL scheme for the app
|
||||
// but this is not supported by macOS, so we use a local server instead
|
||||
return `http://localhost:${port}`;
|
||||
};
|
||||
|
||||
const getWebRedirectTo = () => {
|
||||
return process.env.NODE_ENV === 'production'
|
||||
? WEB_AUTH_CALLBACK
|
||||
: `${window.location.origin}/auth/callback`;
|
||||
};
|
||||
|
||||
const tauriSignInApple = async () => {
|
||||
@@ -87,15 +104,19 @@ export default function AuthPage() {
|
||||
const request = {
|
||||
scope: ['fullName', 'email'] as Scope[],
|
||||
};
|
||||
const appleAuthResponse = await getAppleIdAuth(request);
|
||||
if (appleAuthResponse.identityToken) {
|
||||
const { error } = await supabase.auth.signInWithIdToken({
|
||||
provider: 'apple',
|
||||
token: appleAuthResponse.identityToken,
|
||||
});
|
||||
if (error) {
|
||||
console.error('Authentication error:', error);
|
||||
if (appService?.isIOSApp || USE_APPLE_SIGN_IN) {
|
||||
const appleAuthResponse = await getAppleIdAuth(request);
|
||||
if (appleAuthResponse.identityToken) {
|
||||
const { error } = await supabase.auth.signInWithIdToken({
|
||||
provider: 'apple',
|
||||
token: appleAuthResponse.identityToken,
|
||||
});
|
||||
if (error) {
|
||||
console.error('Authentication error:', error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('Sign in with Apple on this platform is not supported yet');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,11 +139,16 @@ export default function AuthPage() {
|
||||
}
|
||||
// Open the OAuth URL in a ASWebAuthenticationSession on iOS to comply with Apple's guidelines
|
||||
// for other platforms, open the OAuth URL in the default browser
|
||||
if (appService?.isIOSApp) {
|
||||
if (appService?.isIOSApp || appService?.isMacOSApp) {
|
||||
const res = await authWithSafari({ authUrl: data.url });
|
||||
if (res) {
|
||||
handleOAuthUrl(res.redirectUrl);
|
||||
}
|
||||
} else if (appService?.isAndroidApp) {
|
||||
const res = await authWithCustomTab({ authUrl: data.url });
|
||||
if (res) {
|
||||
handleOAuthUrl(res.redirectUrl);
|
||||
}
|
||||
} else {
|
||||
await openUrl(data.url);
|
||||
}
|
||||
@@ -136,16 +162,17 @@ export default function AuthPage() {
|
||||
const params = new URLSearchParams(hash);
|
||||
const accessToken = params.get('access_token');
|
||||
const refreshToken = params.get('refresh_token');
|
||||
const type = params.get('type');
|
||||
const next = params.get('next') ?? '/';
|
||||
if (accessToken) {
|
||||
handleAuthCallback({ accessToken, refreshToken, next, login, navigate: router.push });
|
||||
handleAuthCallback({ accessToken, refreshToken, type, next, login, navigate: router.push });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const startTauriOAuth = async () => {
|
||||
try {
|
||||
if (process.env.NODE_ENV === 'production' || appService?.isMobile) {
|
||||
if (process.env.NODE_ENV === 'production' || appService?.isMobile || USE_APPLE_SIGN_IN) {
|
||||
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
||||
const currentWindow = getCurrentWindow();
|
||||
currentWindow.listen('single-instance', ({ event, payload }) => {
|
||||
@@ -235,13 +262,6 @@ export default function AuthPage() {
|
||||
link_text: _('Forgot your password?'),
|
||||
confirmation_text: _('Check your email for the password reset link'),
|
||||
},
|
||||
update_password: {
|
||||
password_label: _('New Password'),
|
||||
password_input_placeholder: _('Your new password'),
|
||||
button_label: _('Update password'),
|
||||
loading_button_label: _('Updating password ...'),
|
||||
confirmation_text: _('Your password has been updated'),
|
||||
},
|
||||
verify_otp: {
|
||||
email_input_label: _('Email address'),
|
||||
email_input_placeholder: _('Your email address'),
|
||||
@@ -298,22 +318,36 @@ export default function AuthPage() {
|
||||
return isTauriAppPlatform() ? (
|
||||
<div
|
||||
className={clsx(
|
||||
'mt-6 flex',
|
||||
'fixed inset-0 z-0 flex select-none flex-col items-center overflow-y-auto',
|
||||
'bg-base-100 border-base-200 border',
|
||||
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
|
||||
appService?.hasTrafficLight && 'pt-11',
|
||||
)}
|
||||
>
|
||||
<button
|
||||
onClick={handleGoBack}
|
||||
<div
|
||||
ref={headerRef}
|
||||
className={clsx(
|
||||
'btn btn-ghost fixed left-4 h-8 min-h-8 w-8 p-0',
|
||||
appService?.hasSafeAreaInset && 'top-[calc(env(safe-area-inset-top)+16px)]',
|
||||
appService?.hasTrafficLight && 'top-11',
|
||||
'fixed z-10 flex w-full items-center justify-between py-2 pe-6 ps-4',
|
||||
appService?.hasTrafficLight && 'pt-11',
|
||||
)}
|
||||
>
|
||||
<IoArrowBack className='text-base-content' />
|
||||
</button>
|
||||
<div style={{ maxWidth: '420px', margin: 'auto', padding: '2rem' }}>
|
||||
<button onClick={handleGoBack} className={clsx('btn btn-ghost h-8 min-h-8 w-8 p-0')}>
|
||||
<IoArrowBack className='text-base-content' />
|
||||
</button>
|
||||
|
||||
{appService?.hasWindowBar && (
|
||||
<WindowButtons
|
||||
headerRef={headerRef}
|
||||
showMinimize={!isTrafficLightVisible}
|
||||
showMaximize={!isTrafficLightVisible}
|
||||
showClose={!isTrafficLightVisible}
|
||||
onClose={handleGoBack}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={clsx('z-20 pb-8', appService?.hasTrafficLight ? 'mt-24' : 'mt-12')}
|
||||
style={{ maxWidth: '420px' }}
|
||||
>
|
||||
<ProviderLogin
|
||||
provider='google'
|
||||
handleSignIn={tauriSignIn}
|
||||
@@ -322,7 +356,7 @@ export default function AuthPage() {
|
||||
/>
|
||||
<ProviderLogin
|
||||
provider='apple'
|
||||
handleSignIn={appService?.isIOSApp ? tauriSignInApple : tauriSignIn}
|
||||
handleSignIn={appService?.isIOSApp || USE_APPLE_SIGN_IN ? tauriSignInApple : tauriSignIn}
|
||||
Icon={FaApple}
|
||||
label={_('Sign in with Apple')}
|
||||
/>
|
||||
@@ -332,7 +366,7 @@ export default function AuthPage() {
|
||||
Icon={FaGithub}
|
||||
label={_('Sign in with GitHub')}
|
||||
/>
|
||||
<hr className='my-3 mt-6 w-64 border-t border-gray-200' />
|
||||
<hr className='border-base-300 my-3 mt-6 w-64 border-t' />
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
@@ -358,7 +392,7 @@ export default function AuthPage() {
|
||||
theme={isDarkMode ? 'dark' : 'light'}
|
||||
magicLink={true}
|
||||
providers={['google', 'apple', 'github']}
|
||||
redirectTo='/auth/callback'
|
||||
redirectTo={getWebRedirectTo()}
|
||||
localization={getAuthLocalization()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user