forked from akai/readest
Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c25b85a8a | |||
| c536450ab0 | |||
| c83e380c5a | |||
| 894a7551aa | |||
| f875ba88ac | |||
| d9a6cffe78 | |||
| 1704736bc8 | |||
| 6ce9a263ee | |||
| 038eca4267 | |||
| f0722ec0fe | |||
| fd299a61a7 | |||
| 36b7386e30 | |||
| 2670b0dc0b | |||
| c44705e269 | |||
| 7e618d456e | |||
| b28ac99a9e | |||
| 2d7d6b08a9 | |||
| b1419f9c01 | |||
| 32ea42a835 | |||
| 6083de3293 | |||
| 1c9cfa49b3 | |||
| f85d6d4293 | |||
| 017d0b0b39 | |||
| 3146ae48a7 | |||
| 7f26e45ae7 | |||
| 7d97826e4b | |||
| 9c9c79176d | |||
| ed476a4fce | |||
| 44b4f7995b | |||
| da5e3a0bd3 | |||
| ba4678cc23 | |||
| e5eb3014b4 | |||
| ed77b0bc7f | |||
| 434a44e62c | |||
| aaee04c290 | |||
| 48e2bfa82c | |||
| c04f19ffb4 | |||
| 4a624e3902 | |||
| f53cee9616 | |||
| 30385ee5ec | |||
| b30dfb3e23 | |||
| c0d6102857 | |||
| 4537c55e84 | |||
| aff94c0ab8 | |||
| fd70836308 | |||
| 1b0c94b9a5 | |||
| 7cb523eefc | |||
| 941be80cc4 | |||
| 5eecc735aa | |||
| cfe51d01ee | |||
| 20ae09c52d | |||
| b868146129 | |||
| a312080f7c | |||
| 462ca46fee | |||
| 93228c4b2a | |||
| 2ff10f781f | |||
| 9614c62360 | |||
| 5ffaac5e67 | |||
| 71af91608f | |||
| 9603b61776 | |||
| eed84a059a | |||
| 604ef65719 | |||
| 483d536ca4 | |||
| 69a51c5880 | |||
| ca8d25341e | |||
| 8a263235ed | |||
| fb41ff5d0c | |||
| a5e09e8454 | |||
| 730ee21651 | |||
| dea43445c3 | |||
| 4f0ae78d43 | |||
| d9116d619a | |||
| c080e6fdd3 | |||
| d3752dadc6 | |||
| e21ef53a3d | |||
| 71e97998b6 | |||
| 22f9c45232 | |||
| a42897ec5c | |||
| 58a5c1625c | |||
| d53fc09e1e | |||
| 11fecb5dc0 | |||
| f4587663b5 | |||
| b76a2adf61 | |||
| 988fbc8c85 | |||
| f944ad9b9f | |||
| 335d91b9c9 | |||
| 173404eaad | |||
| 674fed5230 | |||
| 12d284cd22 | |||
| 77037c8adb | |||
| 6984393ed1 | |||
| 3abef6ea75 | |||
| 4ae1ab7dd0 | |||
| 69fb22119b | |||
| 3a6c18c6d5 | |||
| 7db1bc460d | |||
| a460e609fa | |||
| 4b4ebdbdaa | |||
| 9358a06839 | |||
| a7baf6cc9f | |||
| 15dc669f35 | |||
| c853957512 | |||
| 8a4e22e423 | |||
| 8a43c58fd4 | |||
| 54fdf5f1fd | |||
| fe50b513b3 | |||
| 17c7fa8f41 | |||
| 2533560d11 | |||
| 5850a16afd | |||
| 7063d62b13 | |||
| 0bd6a217ae | |||
| b7df294d78 | |||
| 5aa78f2554 | |||
| e740571c33 | |||
| e6d9913f4e | |||
| 1869a863a3 | |||
| 524de92f5e | |||
| c1530cc5c4 | |||
| 730fadb834 | |||
| 383e5c61b1 | |||
| 6d42086fa7 | |||
| 5a20fae204 | |||
| 34fd64c5c4 | |||
| 0874fb0764 | |||
| e03ed5b604 | |||
| 41edc89ac7 | |||
| f0a470398d | |||
| 51008d81fb | |||
| 9828904674 | |||
| 2670d835b3 | |||
| 8b7bafc4b6 | |||
| ca759e0246 | |||
| 5141be1c3f | |||
| 669d3950e2 | |||
| b95895cecf | |||
| 80e11bb0ce | |||
| de3a539621 | |||
| b425bfdc89 | |||
| 1d1fbdffdb | |||
| 50cd7f80c6 | |||
| 6eb7d91122 | |||
| 1fb468b3a6 | |||
| 3c7d95cf10 | |||
| ba3f060cc4 | |||
| 11bc7497e8 | |||
| fb5d149413 | |||
| 42b47d73b7 | |||
| 00f36af03a | |||
| b78466ca93 | |||
| 4e6f146b8f | |||
| cbdd4940d0 | |||
| d022cb984a | |||
| 8de6fa267e | |||
| b08b7de8e9 | |||
| a232a39f0e | |||
| fad7966fc4 | |||
| a1487fd60c | |||
| 9606e315d4 | |||
| 978673268b | |||
| 70158a7f15 | |||
| 18d65a2c5b | |||
| 1b0c2afad7 | |||
| cef444d374 | |||
| 75f6efe27a | |||
| 852f9f40ec | |||
| b9dadc0f4f |
@@ -55,7 +55,7 @@ jobs:
|
||||
cache: pnpm
|
||||
|
||||
- name: cache Next.js build
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: apps/readest-app/.next/cache
|
||||
key: nextjs-${{ matrix.config.platform }}-${{ github.sha }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
tag_name: process.env.release_tag,
|
||||
})
|
||||
const notes = process.env.release_note.split(/(?:\d\.\s)/).filter(Boolean);
|
||||
const notes = process.env.release_note.split(/\d+\.\s/).filter(Boolean);
|
||||
const formattedNotes = notes.map(note => `* ${note.trim()}`).join("\n");
|
||||
const body = `## Release Highlight\n${formattedNotes}\n\n${data.body}`;
|
||||
github.rest.repos.updateRelease({
|
||||
@@ -95,10 +95,12 @@ jobs:
|
||||
run: |
|
||||
version=${{ needs.get-release.outputs.release_version }}
|
||||
plugin_zip="Readest-${version}-1.koplugin.zip"
|
||||
meta_file="apps/readest.koplugin/_meta.lua"
|
||||
perl -i -pe "s/^}/ version = \"${version}\",\n}/" "${meta_file}"
|
||||
|
||||
cd apps/readest.koplugin
|
||||
zip -r ../../${plugin_zip} .
|
||||
cd ../..
|
||||
cd apps
|
||||
zip -r ../${plugin_zip} readest.koplugin
|
||||
cd ..
|
||||
|
||||
echo "Uploading ${plugin_zip} to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} ${plugin_zip} --clobber
|
||||
@@ -284,10 +286,15 @@ jobs:
|
||||
echo "Uploading updated latest.json to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} latest.json --clobber
|
||||
|
||||
- name: Override tauri-cli with custom AppImage format (Linux)
|
||||
if: matrix.config.release == 'linux'
|
||||
run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage --force
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
if: matrix.config.release != 'android'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: 'true'
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
@@ -321,7 +328,7 @@ jobs:
|
||||
echo "Building Portable Binaries"
|
||||
pushd apps/readest-app/
|
||||
echo "NEXT_PUBLIC_PORTABLE_APP=true" >> .env.local
|
||||
pnpm tauri build
|
||||
pnpm tauri build ${{ matrix.config.args }}
|
||||
|
||||
popd
|
||||
echo "Uploading Portable Binaries"
|
||||
@@ -346,6 +353,44 @@ jobs:
|
||||
echo "Uploading $bin_file to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file --clobber
|
||||
|
||||
echo "Signing portable binary"
|
||||
pnpm tauri signer sign $bin_file
|
||||
echo "Uploading signature to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file.sig --clobber
|
||||
|
||||
- name: download and update latest.json for Windows portable release
|
||||
if: matrix.config.os == 'windows-latest'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -sL https://github.com/readest/readest/releases/latest/download/latest.json -o latest.json
|
||||
|
||||
version=${{ needs.get-release.outputs.release_version }}
|
||||
arch=${{ matrix.config.arch }}
|
||||
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
bin_file="Readest_${version}_x64-portable.exe"
|
||||
platform_key="windows-x86_64-portable"
|
||||
elif [ "$arch" = "aarch64" ]; then
|
||||
bin_file="Readest_${version}_arm64-portable.exe"
|
||||
platform_key="windows-aarch64-portable"
|
||||
else
|
||||
echo "Unknown architecture: $arch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
portable_url="https://github.com/readest/readest/releases/download/${{ needs.get-release.outputs.release_tag }}/$bin_file"
|
||||
portable_sig=$(cat $bin_file.sig)
|
||||
|
||||
jq --arg url "$portable_url" \
|
||||
--arg sig "$portable_sig" \
|
||||
--arg key "$platform_key" \
|
||||
'.platforms[$key] = {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
|
||||
|
||||
upload-to-r2:
|
||||
needs: [get-release, build-tauri]
|
||||
uses: ./.github/workflows/upload-to-r2.yml
|
||||
|
||||
@@ -43,3 +43,5 @@ fastlane/report.xml
|
||||
|
||||
*.koplugin.zip
|
||||
|
||||
# nix
|
||||
result*
|
||||
|
||||
Generated
+718
-618
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@ members = [
|
||||
"packages/tauri/crates/tauri",
|
||||
"packages/tauri/crates/tauri-utils",
|
||||
"packages/tauri/crates/tauri-build",
|
||||
"packages/tauri-plugins/plugins/deep-link",
|
||||
"packages/tauri-plugins/plugins/single-instance"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -19,6 +21,7 @@ schemars = "0.8"
|
||||
serde_json = "1"
|
||||
thiserror = "2"
|
||||
glob = "0.3"
|
||||
zbus = "5.9"
|
||||
dunce = "1"
|
||||
url = "2"
|
||||
|
||||
@@ -35,3 +38,5 @@ rust-version = "1.77.2"
|
||||
tauri = { path = "packages/tauri/crates/tauri" }
|
||||
tauri-utils = { path = "packages/tauri/crates/tauri-utils" }
|
||||
tauri-build = { path = "packages/tauri/crates/tauri-build" }
|
||||
tauri-plugin-deep-link = { path = "packages/tauri-plugins/plugins/deep-link" }
|
||||
tauri-plugin-single-instance = { path = "packages/tauri-plugins/plugins/single-instance" }
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
| **Translate with DeepL and Yandex** | From a single sentence to the entire book—translate instantly. | ✅ |
|
||||
| **Text-to-Speech (TTS) Support** | Enjoy smooth, multilingual narration—even within a single book. | ✅ |
|
||||
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
|
||||
| **OPDS/Calibre Integration** | Integrate OPDS/Calibre to access online libraries and catalogs. | ✅ |
|
||||
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
|
||||
|
||||
## Planned Features
|
||||
@@ -72,7 +73,6 @@
|
||||
| ------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------ |
|
||||
| [**Sync with Koreader**][link-kosync-wiki] | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | 🛠 |
|
||||
| **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 |
|
||||
| **Support OPDS/Calibre** | Integrate OPDS/Calibre to access online libraries and catalogs. | 🔄 |
|
||||
| **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 |
|
||||
| **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 |
|
||||
| **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🔄 |
|
||||
@@ -267,8 +267,8 @@ If Readest has been useful to you, consider supporting its development. You can
|
||||
### Sponsors
|
||||
|
||||
<p align="center">
|
||||
<a title="Browser testing via LambdaTest" href="https://www.lambdatest.com/?utm_source=readest&utm_medium=sponsor" target="_blank">
|
||||
<img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" />
|
||||
<a title="Browser testing via TestMu AI" href="https://www.testmu.ai?utm_source=readest&utm_medium=sponsor" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/readest/readest/refs/heads/main/data/sponsors/testmu-ai-logo.png" style="vertical-align: middle;" width="250" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -28,3 +28,5 @@ S3_ACCESS_KEY_ID=PLACE_HOLDER
|
||||
S3_SECRET_ACCESS_KEY=PLACE_HOLDER
|
||||
S3_BUCKET_NAME=PLACE_HOLDER
|
||||
S3_REGION=PLACE_HOLDER
|
||||
|
||||
TEMP_STORAGE_PUBLIC_BUCKET_NAME=PLACE_HOLDER
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
NEXT_PUBLIC_APP_PLATFORM=tauri
|
||||
NEXT_PUBLIC_APP_PLATFORM=tauri
|
||||
|
||||
DBUS_ID=com.bilingify.readest
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import withPWAInit from '@ducanh2912/next-pwa';
|
||||
import withSerwistInit from '@serwist/next';
|
||||
import withBundleAnalyzer from '@next/bundle-analyzer';
|
||||
|
||||
const isDev = process.env['NODE_ENV'] === 'development';
|
||||
@@ -33,7 +33,6 @@ const nextConfig = {
|
||||
'i18next-browser-languagedetector',
|
||||
'react-i18next',
|
||||
'i18next',
|
||||
'@ducanh2912/next-pwa',
|
||||
'@tauri-apps',
|
||||
'highlight.js',
|
||||
'foliate-js',
|
||||
@@ -64,39 +63,19 @@ const nextConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
const withPWA = withPWAInit({
|
||||
dest: 'public',
|
||||
disable: isDev || appPlatform !== 'web',
|
||||
cacheStartUrl: false,
|
||||
dynamicStartUrl: false,
|
||||
cacheOnFrontEndNav: true,
|
||||
aggressiveFrontEndNavCaching: true,
|
||||
reloadOnOnline: true,
|
||||
swcMinify: true,
|
||||
fallbacks: {
|
||||
document: '/offline',
|
||||
},
|
||||
workboxOptions: {
|
||||
disableDevLogs: true,
|
||||
manifestTransforms: [
|
||||
(manifestEntries) => {
|
||||
const manifest = manifestEntries.filter((entry) => {
|
||||
const url = entry.url;
|
||||
return (
|
||||
!url.includes('dynamic-css-manifest.json') &&
|
||||
!url.includes('middleware-manifest.json') &&
|
||||
!url.includes('react-loadable-manifest.json') &&
|
||||
!url.includes('build-manifest.json') &&
|
||||
!url.includes('_buildManifest.js') &&
|
||||
!url.includes('_ssgManifest.js') &&
|
||||
!url.includes('_headers')
|
||||
);
|
||||
});
|
||||
return { manifest };
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const pwaDisabled = isDev || appPlatform !== 'web';
|
||||
|
||||
const withPWA = pwaDisabled
|
||||
? (config) => config
|
||||
: withSerwistInit({
|
||||
swSrc: 'src/sw.ts',
|
||||
swDest: 'public/sw.js',
|
||||
cacheOnNavigation: true,
|
||||
reloadOnOnline: true,
|
||||
disable: false,
|
||||
register: true,
|
||||
scope: '/',
|
||||
});
|
||||
|
||||
const withAnalyzer = withBundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.9.94",
|
||||
"version": "0.9.98",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev",
|
||||
@@ -14,13 +14,15 @@
|
||||
"lint": "eslint .",
|
||||
"test": "dotenv -e .env -e .env.test.local vitest",
|
||||
"tauri": "tauri",
|
||||
"clippy": "cargo clippy -p Readest --no-deps -- -D warnings",
|
||||
"prepare-public-vendor": "mkdirp ./public/vendor/pdfjs ./public/vendor/simplecc",
|
||||
"copy-pdfjs-js": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/legacy/build/{pdf.worker.min.mjs,pdf.mjs,pdf.d.mts}\" ./public/vendor/pdfjs",
|
||||
"copy-pdfjs-js": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/legacy/build/{pdf.worker.min.mjs,pdf.min.mjs,pdf.d.mts}\" ./public/vendor/pdfjs",
|
||||
"copy-pdfjs-wasm": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/wasm/{openjpeg.wasm,qcms_bg.wasm}\" ./public/vendor/pdfjs",
|
||||
"copy-pdfjs-fonts": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/{cmaps,standard_fonts}/*\" ./public/vendor/pdfjs",
|
||||
"copy-flatten-pdfjs-annotation-layer-css": "npx postcss \"../../packages/foliate-js/vendor/pdfjs/annotation_layer_builder.css\" --no-map -u postcss-nested > ./public/vendor/pdfjs/annotation_layer_builder.css",
|
||||
"copy-flatten-pdfjs-text-layer-css": "npx postcss \"../../packages/foliate-js/vendor/pdfjs/text_layer_builder.css\" --no-map -u postcss-nested > ./public/vendor/pdfjs/text_layer_builder.css",
|
||||
"copy-flatten-pdfjs-css": "pnpm copy-flatten-pdfjs-annotation-layer-css && pnpm copy-flatten-pdfjs-text-layer-css",
|
||||
"copy-pdfjs": "pnpm copy-pdfjs-js && pnpm copy-pdfjs-fonts && pnpm copy-flatten-pdfjs-css",
|
||||
"copy-pdfjs": "pnpm copy-pdfjs-js && pnpm copy-pdfjs-wasm && pnpm copy-pdfjs-fonts && pnpm copy-flatten-pdfjs-css",
|
||||
"copy-simplecc": "cpx \"../../packages/simplecc-wasm/dist/web/*\" ./public/vendor/simplecc",
|
||||
"setup-pdfjs": "pnpm prepare-public-vendor && pnpm copy-pdfjs",
|
||||
"setup-simplecc": "pnpm prepare-public-vendor && pnpm copy-simplecc",
|
||||
@@ -37,7 +39,7 @@
|
||||
"release-ios-appstore": "dotenv -e .env.ios-appstore.local -- bash scripts/release-ios-appstore.sh",
|
||||
"release-google-play": "dotenv -e .env.google-play.local -- bash scripts/release-google-play.sh",
|
||||
"config-wrangler": "sed -i \"s/\\${TRANSLATIONS_KV_ID}/$TRANSLATIONS_KV_ID/g\" wrangler.toml",
|
||||
"preview": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare preview --ip 0.0.0.0",
|
||||
"preview": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare preview --ip 0.0.0.0 --port 3001",
|
||||
"deploy": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare deploy",
|
||||
"upload": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare upload",
|
||||
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
|
||||
@@ -53,27 +55,29 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.735.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.735.0",
|
||||
"@ducanh2912/next-pwa": "^10.2.9",
|
||||
"@choochmeque/tauri-plugin-sharekit-api": "^0.3.0",
|
||||
"@fabianlars/tauri-plugin-oauth": "2",
|
||||
"@opennextjs/cloudflare": "^1.13.1",
|
||||
"@opennextjs/cloudflare": "^1.14.7",
|
||||
"@serwist/next": "^9.4.2",
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.4.0",
|
||||
"@supabase/auth-ui-react": "^0.4.7",
|
||||
"@supabase/auth-ui-shared": "^0.1.8",
|
||||
"@supabase/supabase-js": "^2.76.1",
|
||||
"@tauri-apps/api": "2.9.0",
|
||||
"@tauri-apps/api": "2.9.1",
|
||||
"@tauri-apps/plugin-cli": "^2.4.1",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.5",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
"@tauri-apps/plugin-fs": "^2.4.4",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.6",
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-haptics": "^2.3.2",
|
||||
"@tauri-apps/plugin-http": "^2.5.4",
|
||||
"@tauri-apps/plugin-log": "^2.7.1",
|
||||
"@tauri-apps/plugin-opener": "^2.5.2",
|
||||
"@tauri-apps/plugin-http": "^2.5.6",
|
||||
"@tauri-apps/plugin-log": "^2.8.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.3",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-shell": "~2.3.3",
|
||||
"@tauri-apps/plugin-shell": "~2.3.4",
|
||||
"@tauri-apps/plugin-updater": "^2.9.0",
|
||||
"@tauri-apps/plugin-websocket": "~2.4.2",
|
||||
"@zip.js/zip.js": "^2.7.53",
|
||||
"abortcontroller-polyfill": "^1.7.8",
|
||||
"app-store-server-api": "^0.17.1",
|
||||
@@ -84,7 +88,7 @@
|
||||
"dompurify": "^3.3.0",
|
||||
"foliate-js": "workspace:*",
|
||||
"franc-min": "^6.2.0",
|
||||
"google-auth-library": "^10.4.1",
|
||||
"google-auth-library": "^10.5.0",
|
||||
"googleapis": "^164.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"i18next": "^24.2.0",
|
||||
@@ -92,10 +96,11 @@
|
||||
"i18next-http-backend": "^3.0.1",
|
||||
"iso-639-2": "^3.0.2",
|
||||
"iso-639-3": "^3.0.1",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"js-md5": "^0.8.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"marked": "^15.0.12",
|
||||
"next": "16.0.3",
|
||||
"next": "16.0.10",
|
||||
"overlayscrollbars": "^2.11.4",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"posthog-js": "^1.246.0",
|
||||
@@ -105,19 +110,21 @@
|
||||
"react-i18next": "^15.2.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-responsive": "^10.0.0",
|
||||
"react-virtuoso": "^4.17.0",
|
||||
"react-window": "^1.8.11",
|
||||
"semver": "^7.7.1",
|
||||
"stripe": "^18.2.1",
|
||||
"styled-jsx": "^5.1.7",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"uuid": "^11.1.0",
|
||||
"ws": "^8.18.3",
|
||||
"zod": "^4.0.8",
|
||||
"zustand": "5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^15.4.2",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tauri-apps/cli": "2.9.4",
|
||||
"@tauri-apps/cli": "2.9.6",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
@@ -130,9 +137,10 @@
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
"cpx2": "^8.0.0",
|
||||
@@ -149,10 +157,11 @@
|
||||
"postcss-cli": "^11.0.0",
|
||||
"postcss-nested": "^7.0.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"serwist": "^9.3.0",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"typescript": "^5.7.2",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4",
|
||||
"wrangler": "^4.50.0"
|
||||
"vitest": "^4.0.15",
|
||||
"wrangler": "^4.56.0"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -172,9 +172,6 @@
|
||||
"Token": "الرمز",
|
||||
"Your OTP token": "رمز OTP الذي وصلك",
|
||||
"Verify token": "التحقق من الرمز",
|
||||
"Sign in with Google": "تسجيل الدخول عبر Google",
|
||||
"Sign in with Apple": "تسجيل الدخول عبر Apple",
|
||||
"Sign in with GitHub": "تسجيل الدخول عبر GitHub",
|
||||
"Account": "الحساب",
|
||||
"Failed to delete user. Please try again later.": "فشل حذف المستخدم. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||
"Community Support": "دعم المجتمع",
|
||||
@@ -356,8 +353,8 @@
|
||||
"{{count}} pages left in chapter_many": "تبقّت {{count}} صفحة في هذا الفصل",
|
||||
"{{count}} pages left in chapter_other": "تبقّى {{count}} صفحة في هذا الفصل",
|
||||
"Show Remaining Pages": "عرض الصفحات المتبقية",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "إدارة الاشتراك",
|
||||
"Coming Soon": "قريبًا",
|
||||
@@ -715,9 +712,7 @@
|
||||
"Validating...": "جارٍ التحقق...",
|
||||
"View All": "عرض الكل",
|
||||
"Forward": "إلى الأمام",
|
||||
"OPDS Catalog": "كتالوج OPDS",
|
||||
"Home": "الصفحة الرئيسية",
|
||||
"Library": "المكتبة",
|
||||
"{{count}} items_zero": "{{count}} عناصر",
|
||||
"{{count}} items_one": "{{count}} عنصر",
|
||||
"{{count}} items_two": "{{count}} عنصران",
|
||||
@@ -741,5 +736,199 @@
|
||||
"Last": "الأخير",
|
||||
"Cannot Load Page": "تعذر تحميل الصفحة",
|
||||
"An error occurred": "حدث خطأ ما",
|
||||
"Online Library": "المكتبة عبر الإنترنت"
|
||||
"Online Library": "المكتبة عبر الإنترنت",
|
||||
"URL must start with http:// or https://": "يجب أن يبدأ عنوان URL بـ http:// أو https://",
|
||||
"Title, Author, Tag, etc...": "العنوان، المؤلف، العلامة، إلخ...",
|
||||
"Query": "استعلام",
|
||||
"Subject": "موضوع",
|
||||
"Enter {{terms}}": "أدخل {{terms}}",
|
||||
"No search results found": "لم يتم العثور على نتائج بحث",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "فشل في تحميل تغذية OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "البحث في {{title}}",
|
||||
"Manage Storage": "إدارة التخزين",
|
||||
"Failed to load files": "فشل في تحميل الملفات",
|
||||
"Deleted {{count}} file(s)_zero": "لم يتم حذف أي ملفات",
|
||||
"Deleted {{count}} file(s)_one": "تم حذف ملف واحد",
|
||||
"Deleted {{count}} file(s)_two": "تم حذف ملفين",
|
||||
"Deleted {{count}} file(s)_few": "تم حذف {{count}} ملفات",
|
||||
"Deleted {{count}} file(s)_many": "تم حذف {{count}} ملفًا",
|
||||
"Deleted {{count}} file(s)_other": "تم حذف {{count}} ملف",
|
||||
"Failed to delete {{count}} file(s)_zero": "فشل حذف أي ملفات",
|
||||
"Failed to delete {{count}} file(s)_one": "فشل حذف ملف واحد",
|
||||
"Failed to delete {{count}} file(s)_two": "فشل حذف ملفين",
|
||||
"Failed to delete {{count}} file(s)_few": "فشل حذف {{count}} ملفات",
|
||||
"Failed to delete {{count}} file(s)_many": "فشل حذف {{count}} ملفًا",
|
||||
"Failed to delete {{count}} file(s)_other": "فشل حذف {{count}} ملف",
|
||||
"Failed to delete files": "فشل حذف الملفات",
|
||||
"Total Files": "إجمالي الملفات",
|
||||
"Total Size": "إجمالي الحجم",
|
||||
"Quota": "الحصة",
|
||||
"Used": "المستخدم",
|
||||
"Files": "الملفات",
|
||||
"Search files...": "ابحث في الملفات...",
|
||||
"Newest First": "الأحدث أولاً",
|
||||
"Oldest First": "الأقدم أولاً",
|
||||
"Largest First": "الأكبر أولاً",
|
||||
"Smallest First": "الأصغر أولاً",
|
||||
"Name A-Z": "الاسم من أ إلى ي",
|
||||
"Name Z-A": "الاسم من ي إلى أ",
|
||||
"{{count}} selected_zero": "لا يوجد عناصر محددة",
|
||||
"{{count}} selected_one": "عنصر واحد محدد",
|
||||
"{{count}} selected_two": "عنصران محددان",
|
||||
"{{count}} selected_few": "{{count}} عناصر محددة",
|
||||
"{{count}} selected_many": "{{count}} عنصرًا محددًا",
|
||||
"{{count}} selected_other": "{{count}} عنصر محدد",
|
||||
"Delete Selected": "حذف المحدد",
|
||||
"Created": "تاريخ الإنشاء",
|
||||
"No files found": "لا توجد ملفات",
|
||||
"No files uploaded yet": "لم يتم رفع أي ملفات بعد",
|
||||
"files": "الملفات",
|
||||
"Page {{current}} of {{total}}": "الصفحة {{current}} من {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_zero": "هل أنت متأكد من حذف العناصر المحددة؟ (لا توجد عناصر)",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "هل أنت متأكد من حذف ملف واحد محدد؟",
|
||||
"Are you sure to delete {{count}} selected file(s)?_two": "هل أنت متأكد من حذف ملفين محددين؟",
|
||||
"Are you sure to delete {{count}} selected file(s)?_few": "هل أنت متأكد من حذف {{count}} ملفات محددة؟",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "هل أنت متأكد من حذف {{count}} ملفًا محددًا؟",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "هل أنت متأكد من حذف {{count}} ملف محدد؟",
|
||||
"Cloud Storage Usage": "استخدام التخزين السحابي",
|
||||
"Rename Group": "إعادة تسمية المجموعة",
|
||||
"From Directory": "من الدليل",
|
||||
"Successfully imported {{count}} book(s)_zero": "لم يتم استيراد أي كتب",
|
||||
"Successfully imported {{count}} book(s)_one": "تم استيراد كتاب واحد",
|
||||
"Successfully imported {{count}} book(s)_two": "تم استيراد كتابين",
|
||||
"Successfully imported {{count}} book(s)_few": "تم استيراد {{count}} كتب",
|
||||
"Successfully imported {{count}} book(s)_many": "تم استيراد {{count}} كتابًا",
|
||||
"Successfully imported {{count}} book(s)_other": "تم استيراد {{count}} كتاب",
|
||||
"Count": "العدد",
|
||||
"Start Page": "الصفحة الأولى",
|
||||
"Search in OPDS Catalog...": "البحث في كتالوج OPDS...",
|
||||
"Please log in to use advanced TTS features": "يرجى تسجيل الدخول لاستخدام ميزات تحويل النص إلى كلام المتقدمة.",
|
||||
"Word limit of 30 words exceeded.": "تم تجاوز حد الكلمات البالغ 30 كلمة.",
|
||||
"Proofread": "التدقيق اللغوي",
|
||||
"Current selection": "النص المحدد الحالي",
|
||||
"All occurrences in this book": "جميع الحالات في هذا الكتاب",
|
||||
"All occurrences in your library": "جميع الحالات في مكتبتك",
|
||||
"Selected text:": "النص المحدد:",
|
||||
"Replace with:": "استبدال بـ:",
|
||||
"Enter text...": "أدخل النص...",
|
||||
"Case sensitive:": "حساس لحالة الأحرف:",
|
||||
"Scope:": "النطاق:",
|
||||
"Selection": "التحديد",
|
||||
"Library": "المكتبة",
|
||||
"Yes": "نعم",
|
||||
"No": "لا",
|
||||
"Proofread Replacement Rules": "قواعد استبدال التدقيق اللغوي",
|
||||
"Selected Text Rules": "قواعد النص المحدد",
|
||||
"No selected text replacement rules": "لا توجد قواعد استبدال نص محدد",
|
||||
"Book Specific Rules": "قواعد خاصة بالكتاب",
|
||||
"No book-level replacement rules": "لا توجد قواعد استبدال على مستوى الكتاب",
|
||||
"Disable Quick Action": "تعطيل الإجراء السريع",
|
||||
"Enable Quick Action on Selection": "تمكين الإجراء السريع عند التحديد",
|
||||
"None": "لا شيء",
|
||||
"Annotation Tools": "أدوات التعليق",
|
||||
"Enable Quick Actions": "تمكين الإجراءات السريعة",
|
||||
"Quick Action": "الإجراء السريع",
|
||||
"Copy to Notebook": "نسخ إلى الدفتر",
|
||||
"Copy text after selection": "نسخ النص بعد التحديد",
|
||||
"Highlight text after selection": "تمييز النص بعد التحديد",
|
||||
"Annotate text after selection": "تعليق على النص بعد التحديد",
|
||||
"Search text after selection": "البحث في النص بعد التحديد",
|
||||
"Look up text in dictionary after selection": "البحث عن النص في القاموس بعد التحديد",
|
||||
"Look up text in Wikipedia after selection": "البحث عن النص في ويكيبيديا بعد التحديد",
|
||||
"Translate text after selection": "ترجمة النص بعد التحديد",
|
||||
"Read text aloud after selection": "قراءة النص بصوت عالٍ بعد التحديد",
|
||||
"Proofread text after selection": "تدقيق النص بعد التحديد",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} نشط، {{pendingCount}} قيد الانتظار",
|
||||
"{{failedCount}} failed": "{{failedCount}} فشل",
|
||||
"Waiting...": "جارٍ الانتظار...",
|
||||
"Failed": "فشل",
|
||||
"Completed": "مكتمل",
|
||||
"Cancelled": "ملغى",
|
||||
"Retry": "إعادة المحاولة",
|
||||
"Active": "نشط",
|
||||
"Transfer Queue": "قائمة انتظار النقل",
|
||||
"Upload All": "رفع الكل",
|
||||
"Download All": "تنزيل الكل",
|
||||
"Resume Transfers": "استئناف النقل",
|
||||
"Pause Transfers": "إيقاف النقل مؤقتًا",
|
||||
"Pending": "قيد الانتظار",
|
||||
"No transfers": "لا توجد عمليات نقل",
|
||||
"Retry All": "إعادة محاولة الكل",
|
||||
"Clear Completed": "مسح المكتمل",
|
||||
"Clear Failed": "مسح الفاشل",
|
||||
"Upload queued: {{title}}": "الرفع في قائمة الانتظار: {{title}}",
|
||||
"Download queued: {{title}}": "التنزيل في قائمة الانتظار: {{title}}",
|
||||
"Book not found in library": "الكتاب غير موجود في المكتبة",
|
||||
"Unknown error": "خطأ غير معروف",
|
||||
"Please log in to continue": "يرجى تسجيل الدخول للمتابعة",
|
||||
"Cloud File Transfers": "نقل الملفات السحابية",
|
||||
"Show Search Results": "عرض نتائج البحث",
|
||||
"Search results for '{{term}}'": "نتائج البحث عن «{{term}}»",
|
||||
"Close Search": "إغلاق البحث",
|
||||
"Previous Result": "النتيجة السابقة",
|
||||
"Next Result": "النتيجة التالية",
|
||||
"Bookmarks": "الإشارات المرجعية",
|
||||
"Annotations": "التعليقات التوضيحية",
|
||||
"Show Results": "عرض النتائج",
|
||||
"Clear search": "مسح البحث",
|
||||
"Clear search history": "مسح سجل البحث",
|
||||
"Quick action disabled": "الإجراء السريع معطل",
|
||||
"Tap to Toggle Footer": "انقر لتبديل التذييل",
|
||||
"Exported successfully": "تم التصدير بنجاح",
|
||||
"Book exported successfully.": "تم تصدير الكتاب بنجاح.",
|
||||
"Failed to export the book.": "فشل تصدير الكتاب.",
|
||||
"Export Book": "تصدير الكتاب",
|
||||
"Whole word:": "كلمة كاملة:",
|
||||
"Error": "خطأ",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "تعذر تحميل المقال. حاول البحث مباشرة على {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "تعذر تحميل الكلمة. حاول البحث مباشرة على {{link}}.",
|
||||
"Date Published": "تاريخ النشر",
|
||||
"Only for TTS:": "فقط لـ TTS:",
|
||||
"Uploaded": "تم الرفع",
|
||||
"Downloaded": "تم التنزيل",
|
||||
"Deleted": "تم الحذف",
|
||||
"Note:": "ملاحظة:",
|
||||
"Time:": "الوقت:",
|
||||
"Format Options": "خيارات التنسيق",
|
||||
"Export Date": "تاريخ التصدير",
|
||||
"Chapter Titles": "عناوين الفصول",
|
||||
"Chapter Separator": "فاصل الفصول",
|
||||
"Highlights": "التمييزات",
|
||||
"Note Date": "تاريخ الملاحظة",
|
||||
"Advanced": "متقدم",
|
||||
"Hide": "إخفاء",
|
||||
"Show": "إظهار",
|
||||
"Use Custom Template": "استخدام قالب مخصص",
|
||||
"Export Template": "قالب التصدير",
|
||||
"Template Syntax:": "بناء جملة القالب:",
|
||||
"Insert value": "إدراج قيمة",
|
||||
"Format date (locale)": "تنسيق التاريخ (محلي)",
|
||||
"Format date (custom)": "تنسيق التاريخ (مخصص)",
|
||||
"Conditional": "شرطي",
|
||||
"Loop": "حلقة",
|
||||
"Available Variables:": "المتغيرات المتاحة:",
|
||||
"Book title": "عنوان الكتاب",
|
||||
"Book author": "مؤلف الكتاب",
|
||||
"Export date": "تاريخ التصدير",
|
||||
"Array of chapters": "مصفوفة الفصول",
|
||||
"Chapter title": "عنوان الفصل",
|
||||
"Array of annotations": "مصفوفة التعليقات",
|
||||
"Highlighted text": "النص المميز",
|
||||
"Annotation note": "ملاحظة التعليق",
|
||||
"Update time": "وقت التحديث",
|
||||
"Date Format Tokens:": "رموز تنسيق التاريخ:",
|
||||
"Year (4 digits)": "السنة (4 أرقام)",
|
||||
"Month (01-12)": "الشهر (01-12)",
|
||||
"Day (01-31)": "اليوم (01-31)",
|
||||
"Hour (00-23)": "الساعة (00-23)",
|
||||
"Minute (00-59)": "الدقيقة (00-59)",
|
||||
"Second (00-59)": "الثانية (00-59)",
|
||||
"Show Source": "إظهار المصدر",
|
||||
"No content to preview": "لا يوجد محتوى للمعاينة",
|
||||
"Export": "تصدير",
|
||||
"Set Timeout": "تعيين المهلة",
|
||||
"Select Voice": "اختر الصوت",
|
||||
"Toggle Sticky Bottom TTS Bar": "تبديل شريط TTS الثابت",
|
||||
"Display what I'm reading on Discord": "عرض ما أقرأه على Discord",
|
||||
"Show on Discord": "عرض على Discord"
|
||||
}
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
"Token": "টোকেন",
|
||||
"Your OTP token": "আপনার ওটিপি টোকেন",
|
||||
"Verify token": "টোকেন যাচাই করুন",
|
||||
"Sign in with Google": "গুগল দিয়ে সাইন ইন",
|
||||
"Sign in with Apple": "অ্যাপল দিয়ে সাইন ইন",
|
||||
"Sign in with GitHub": "গিটহাব দিয়ে সাইন ইন",
|
||||
"New Password": "নতুন পাসওয়ার্ড",
|
||||
"Your new password": "আপনার নতুন পাসওয়ার্ড",
|
||||
"Update password": "পাসওয়ার্ড আপডেট করুন",
|
||||
@@ -488,8 +485,8 @@
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Source Han Serif CN VF": "Source Han Serif CN VF",
|
||||
"Huiwen-mincho": "Huiwen-mincho",
|
||||
"Source Han Serif CN": "Source Han Serif CN",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa_OldSong",
|
||||
"Google Books": "গুগল বুকস",
|
||||
"Open Library": "ওপেন লাইব্রেরি",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "যাচাই করা হচ্ছে...",
|
||||
"View All": "সব দেখুন",
|
||||
"Forward": "ফরোয়ার্ড",
|
||||
"OPDS Catalog": "OPDS ক্যাটালগ",
|
||||
"Home": "হোম",
|
||||
"Library": "লাইব্রেরি",
|
||||
"{{count}} items_one": "{{count}} আইটেম",
|
||||
"{{count}} items_other": "{{count}} আইটেম",
|
||||
"Download completed": "ডাউনলোড সম্পন্ন হয়েছে",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "শেষ",
|
||||
"Cannot Load Page": "পৃষ্ঠা লোড করা যায়নি",
|
||||
"An error occurred": "একটি ত্রুটি ঘটেছে",
|
||||
"Online Library": "অনলাইন লাইব্রেরি"
|
||||
"Online Library": "অনলাইন লাইব্রেরি",
|
||||
"URL must start with http:// or https://": "URL অবশ্যই http:// বা https:// দিয়ে শুরু হতে হবে",
|
||||
"Title, Author, Tag, etc...": "শিরোনাম, লেখক, ট্যাগ, ইত্যাদি...",
|
||||
"Query": "কোয়েরি",
|
||||
"Subject": "বিষয়",
|
||||
"Enter {{terms}}": "{{terms}} লিখুন",
|
||||
"No search results found": "কোনও অনুসন্ধান ফলাফল পাওয়া যায়নি",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ফিড লোড করতে ব্যর্থ: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} এ অনুসন্ধান করুন",
|
||||
"Manage Storage": "স্টোরেজ ম্যানেজমেন্ট",
|
||||
"Failed to load files": "ফাইল লোড করতে ব্যর্থ",
|
||||
"Deleted {{count}} file(s)_one": "{{count}}টি ফাইল মুছে ফেলা হয়েছে",
|
||||
"Deleted {{count}} file(s)_other": "{{count}}টি ফাইল মুছে ফেলা হয়েছে",
|
||||
"Failed to delete {{count}} file(s)_one": "{{count}}টি ফাইল মুছতে ব্যর্থ",
|
||||
"Failed to delete {{count}} file(s)_other": "{{count}}টি ফাইল মুছতে ব্যর্থ",
|
||||
"Failed to delete files": "ফাইল মুছতে ব্যর্থ",
|
||||
"Total Files": "মোট ফাইল",
|
||||
"Total Size": "মোট আকার",
|
||||
"Quota": "কোটা",
|
||||
"Used": "ব্যবহৃত",
|
||||
"Files": "ফাইল",
|
||||
"Search files...": "ফাইল খুঁজুন...",
|
||||
"Newest First": "নতুন আগে",
|
||||
"Oldest First": "পুরনো আগে",
|
||||
"Largest First": "বড় আগে",
|
||||
"Smallest First": "ছোট আগে",
|
||||
"Name A-Z": "নাম A-Z",
|
||||
"Name Z-A": "নাম Z-A",
|
||||
"{{count}} selected_one": "{{count}}টি নির্বাচিত",
|
||||
"{{count}} selected_other": "{{count}}টি নির্বাচিত",
|
||||
"Delete Selected": "নির্বাচিত মুছুন",
|
||||
"Created": "তৈরি হয়েছে",
|
||||
"No files found": "কোনো ফাইল পাওয়া যায়নি",
|
||||
"No files uploaded yet": "এখনও কোনো ফাইল আপলোড করা হয়নি",
|
||||
"files": "ফাইল",
|
||||
"Page {{current}} of {{total}}": "{{total}}টির মধ্যে {{current}} পৃষ্ঠা",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত ফাইল মুছতে চান?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত ফাইল মুছতে চান?",
|
||||
"Cloud Storage Usage": "ক্লাউড স্টোরেজ ব্যবহৃত",
|
||||
"Rename Group": "গ্রুপের নাম পরিবর্তন করুন",
|
||||
"From Directory": "ডিরেক্টরি থেকে",
|
||||
"Successfully imported {{count}} book(s)_one": "সফলভাবে ১টি বই আমদানি করা হয়েছে",
|
||||
"Successfully imported {{count}} book(s)_other": "সফলভাবে {{count}}টি বই আমদানি করা হয়েছে",
|
||||
"Count": "গণনা",
|
||||
"Start Page": "শুরু পৃষ্ঠা",
|
||||
"Search in OPDS Catalog...": "OPDS ক্যাটালগে অনুসন্ধান করুন...",
|
||||
"Please log in to use advanced TTS features": "উন্নত TTS বৈশিষ্ট্যগুলি ব্যবহার করতে লগইন করুন।",
|
||||
"Word limit of 30 words exceeded.": "৩০ শব্দের শব্দসীমা অতিক্রম হয়েছে।",
|
||||
"Proofread": "প্রুফরিড",
|
||||
"Current selection": "বর্তমান নির্বাচন",
|
||||
"All occurrences in this book": "এই বইয়ে সমস্ত ঘটনা",
|
||||
"All occurrences in your library": "আপনার লাইব্রেরির সমস্ত ঘটনা",
|
||||
"Selected text:": "নির্বাচিত টেক্সট:",
|
||||
"Replace with:": "এর সাথে প্রতিস্থাপন করুন:",
|
||||
"Enter text...": "টেক্সট লিখুন...",
|
||||
"Case sensitive:": "কেস সংবেদনশীল:",
|
||||
"Scope:": "পরিসর:",
|
||||
"Selection": "নির্বাচন",
|
||||
"Library": "লাইব্রেরি",
|
||||
"Yes": "হ্যাঁ",
|
||||
"No": "না",
|
||||
"Proofread Replacement Rules": "প্রুফরিড বিকল্প নিয়ম",
|
||||
"Selected Text Rules": "নির্বাচিত টেক্সট নিয়ম",
|
||||
"No selected text replacement rules": "কোনও নির্বাচিত টেক্সট বিকল্প নিয়ম নেই",
|
||||
"Book Specific Rules": "বই নির্দিষ্ট নিয়ম",
|
||||
"No book-level replacement rules": "কোনও বই-স্তরের বিকল্প নিয়ম নেই",
|
||||
"Disable Quick Action": "দ্রুত ক্রিয়া নিষ্ক্রিয় করুন",
|
||||
"Enable Quick Action on Selection": "নির্বাচনের উপর দ্রুত ক্রিয়া সক্ষম করুন",
|
||||
"None": "কোনোটিই নয়",
|
||||
"Annotation Tools": "অ্যনোটেশন সরঞ্জামসমূহ",
|
||||
"Enable Quick Actions": "দ্রুত ক্রিয়া সক্ষম করুন",
|
||||
"Quick Action": "দ্রুত ক্রিয়া",
|
||||
"Copy to Notebook": "নোটবুকে কপি করুন",
|
||||
"Copy text after selection": "নির্বাচনের পরে টেক্সট কপি করুন",
|
||||
"Highlight text after selection": "নির্বাচনের পরে টেক্সট হাইলাইট করুন",
|
||||
"Annotate text after selection": "নির্বাচনের পরে টেক্সট মন্তব্য করুন",
|
||||
"Search text after selection": "নির্বাচনের পরে টেক্সট অনুসন্ধান করুন",
|
||||
"Look up text in dictionary after selection": "নির্বাচনের পরে টেক্সট অভিধানে দেখুন",
|
||||
"Look up text in Wikipedia after selection": "নির্বাচনের পরে টেক্সট উইকিপিডিয়ায় দেখুন",
|
||||
"Translate text after selection": "নির্বাচনের পরে টেক্সট অনুবাদ করুন",
|
||||
"Read text aloud after selection": "নির্বাচনের পরে টেক্সট উচ্চারণ করুন",
|
||||
"Proofread text after selection": "নির্বাচনের পরে টেক্সট প্রুফরিড করুন",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} সক্রিয়, {{pendingCount}} অপেক্ষমাণ",
|
||||
"{{failedCount}} failed": "{{failedCount}} ব্যর্থ",
|
||||
"Waiting...": "অপেক্ষা করছে...",
|
||||
"Failed": "ব্যর্থ",
|
||||
"Completed": "সম্পন্ন",
|
||||
"Cancelled": "বাতিল",
|
||||
"Retry": "পুনরায় চেষ্টা করুন",
|
||||
"Active": "সক্রিয়",
|
||||
"Transfer Queue": "স্থানান্তর সারি",
|
||||
"Upload All": "সব আপলোড করুন",
|
||||
"Download All": "সব ডাউনলোড করুন",
|
||||
"Resume Transfers": "স্থানান্তর পুনরায় শুরু করুন",
|
||||
"Pause Transfers": "স্থানান্তর বিরতি দিন",
|
||||
"Pending": "অপেক্ষমাণ",
|
||||
"No transfers": "কোনো স্থানান্তর নেই",
|
||||
"Retry All": "সব পুনরায় চেষ্টা করুন",
|
||||
"Clear Completed": "সম্পন্ন মুছুন",
|
||||
"Clear Failed": "ব্যর্থ মুছুন",
|
||||
"Upload queued: {{title}}": "আপলোড সারিতে: {{title}}",
|
||||
"Download queued: {{title}}": "ডাউনলোড সারিতে: {{title}}",
|
||||
"Book not found in library": "লাইব্রেরিতে বই পাওয়া যায়নি",
|
||||
"Unknown error": "অজানা ত্রুটি",
|
||||
"Please log in to continue": "চালিয়ে যেতে লগইন করুন",
|
||||
"Cloud File Transfers": "ক্লাউড ফাইল স্থানান্তরসমূহ",
|
||||
"Show Search Results": "অনুসন্ধান ফলাফল দেখান",
|
||||
"Search results for '{{term}}'": "'{{term}}' এর ফলাফল",
|
||||
"Close Search": "অনুসন্ধান বন্ধ করুন",
|
||||
"Previous Result": "আগের ফলাফল",
|
||||
"Next Result": "পরবর্তী ফলাফল",
|
||||
"Bookmarks": "বুকমার্ক",
|
||||
"Annotations": "টীকা",
|
||||
"Show Results": "ফলাফল দেখুন",
|
||||
"Clear search": "অনুসন্ধান সাফ করুন",
|
||||
"Clear search history": "অনুসন্ধান ইতিহাস সাফ করুন",
|
||||
"Quick action disabled": "দ্রুত কার্যক্রম অক্ষম",
|
||||
"Tap to Toggle Footer": "ফুটার টগল করতে আলতো চাপুন",
|
||||
"Exported successfully": "সফলভাবে রপ্তানি হয়েছে",
|
||||
"Book exported successfully.": "বইটি সফলভাবে রপ্তানি হয়েছে।",
|
||||
"Failed to export the book.": "বই রপ্তানি করতে ব্যর্থ।",
|
||||
"Export Book": "বই রপ্তানি করুন",
|
||||
"Whole word:": "সম্পূর্ণ শব্দ:",
|
||||
"Error": "ত্রুটি",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "নিবন্ধ লোড করতে অক্ষম। সরাসরি {{link}} এ অনুসন্ধান করার চেষ্টা করুন।",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "শব্দ লোড করতে অক্ষম। সরাসরি {{link}} এ অনুসন্ধান করার চেষ্টা করুন।",
|
||||
"Date Published": "প্রকাশনার তারিখ",
|
||||
"Only for TTS:": "শুধুমাত্র TTS-এর জন্য:",
|
||||
"Uploaded": "আপলোড হয়েছে",
|
||||
"Downloaded": "ডাউনলোড হয়েছে",
|
||||
"Deleted": "মুছে ফেলা হয়েছে",
|
||||
"Note:": "নোট:",
|
||||
"Time:": "সময়:",
|
||||
"Format Options": "বিন্যাস বিকল্প",
|
||||
"Export Date": "রপ্তানি তারিখ",
|
||||
"Chapter Titles": "অধ্যায়ের শিরোনাম",
|
||||
"Chapter Separator": "অধ্যায় বিভাজক",
|
||||
"Highlights": "হাইলাইট",
|
||||
"Note Date": "নোট তারিখ",
|
||||
"Advanced": "উন্নত",
|
||||
"Hide": "লুকান",
|
||||
"Show": "দেখান",
|
||||
"Use Custom Template": "কাস্টম টেমপ্লেট ব্যবহার করুন",
|
||||
"Export Template": "রপ্তানি টেমপ্লেট",
|
||||
"Template Syntax:": "টেমপ্লেট সিনট্যাক্স:",
|
||||
"Insert value": "মান সন্নিবেশ করুন",
|
||||
"Format date (locale)": "তারিখ বিন্যাস (স্থানীয়)",
|
||||
"Format date (custom)": "তারিখ বিন্যাস (কাস্টম)",
|
||||
"Conditional": "শর্তসাপেক্ষ",
|
||||
"Loop": "লুপ",
|
||||
"Available Variables:": "উপলব্ধ ভেরিয়েবল:",
|
||||
"Book title": "বইয়ের শিরোনাম",
|
||||
"Book author": "বইয়ের লেখক",
|
||||
"Export date": "রপ্তানি তারিখ",
|
||||
"Array of chapters": "অধ্যায়ের তালিকা",
|
||||
"Chapter title": "অধ্যায়ের শিরোনাম",
|
||||
"Array of annotations": "টীকাগুলির তালিকা",
|
||||
"Highlighted text": "হাইলাইট করা পাঠ্য",
|
||||
"Annotation note": "টীকা নোট",
|
||||
"Update time": "আপডেট সময়",
|
||||
"Date Format Tokens:": "তারিখ বিন্যাস টোকেন:",
|
||||
"Year (4 digits)": "বছর (4 সংখ্যা)",
|
||||
"Month (01-12)": "মাস (01-12)",
|
||||
"Day (01-31)": "দিন (01-31)",
|
||||
"Hour (00-23)": "ঘণ্টা (00-23)",
|
||||
"Minute (00-59)": "মিনিট (00-59)",
|
||||
"Second (00-59)": "সেকেন্ড (00-59)",
|
||||
"Show Source": "উৎস দেখান",
|
||||
"No content to preview": "প্রিভিউ করার জন্য কোনো কন্টেন্ট নেই",
|
||||
"Export": "রপ্তানি",
|
||||
"Set Timeout": "টাইমআউট সেট করুন",
|
||||
"Select Voice": "ভয়েস নির্বাচন করুন",
|
||||
"Toggle Sticky Bottom TTS Bar": "স্থির TTS বার টগল করুন",
|
||||
"Display what I'm reading on Discord": "Discord-এ পড়ছি যা দেখান",
|
||||
"Show on Discord": "Discord-এ দেখান"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "བཀོལ་ཐོགས།",
|
||||
"Your OTP token": "ཁྱེད་ཀྱི་ OTP བཀོལ་ཐོགས།",
|
||||
"Verify token": "བཀོལ་ཐོགས་བདེན་སྦྱོང་།",
|
||||
"Sign in with Google": "Google བེད་སྤྱད་ནས་ནང་བསྐྱོད།",
|
||||
"Sign in with Apple": "Apple བེད་སྤྱད་ནས་ནང་བསྐྱོད།",
|
||||
"Sign in with GitHub": "GitHub བེད་སྤྱད་ནས་ནང་བསྐྱོད།",
|
||||
"Account": "རྩིས་ཐོ།",
|
||||
"Failed to delete user. Please try again later.": "སྤྱོད་མཁན་བསུབ་མ་ཐུབ། ཅུང་ཙམ་སྒུག་ནས་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད་རོགས།",
|
||||
"Community Support": "སྡེ་ཁུལ་གྱི་རྒྱབ་སྐྱོར།",
|
||||
@@ -336,8 +333,8 @@
|
||||
"Reset Settings": "སྒྲིག་བཀོད་བསྐྱར་སྒྲིག",
|
||||
"{{count}} pages left in chapter_other": "ལེའུ་འདིར་ད་དུང་ {{count}} ཤོག་ལྷེ་ལྷག་ཡོད།",
|
||||
"Show Remaining Pages": "ལྷག་མའི་ཤོག་ལྷེའི་གྲངས་ཀ་མངོན་པ།",
|
||||
"Source Han Serif CN VF": "སི་ཡོན་སུང་ཐི།",
|
||||
"Huiwen-mincho": "ཧུའེ་ཝུན་མིང་ཁྲའོ་ཐི།",
|
||||
"Source Han Serif CN": "སི་ཡོན་སུང་ཐི།",
|
||||
"Huiwen-MinchoGBK": "ཧུའེ་ཝུན་མིང་ཁྲའོ་ཐི།",
|
||||
"KingHwa_OldSong": "ཅིང་ཧྭ་ལའོ་སུང་ཐི།",
|
||||
"Manage Subscription": "མཚམས་སྦྱོར་དོ་དམ།",
|
||||
"Coming Soon": "ཉེ་བར་སྤྱོད་འགོ་འཛུགས་རྒྱུ།",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "བདེན་སྦྱོར་བཞིན་...",
|
||||
"View All": "ཡོངས་ལྟ་བ་",
|
||||
"Forward": "མདུན་དུ་",
|
||||
"OPDS Catalog": "OPDS དཀར་ཆག",
|
||||
"Home": "གཙོ་ངོས་",
|
||||
"Library": "དེབ་མཛོད་",
|
||||
"{{count}} items_other": "{{count}} རྣམ་གྲངས་",
|
||||
"Download completed": "ཕབ་ལེན་རྫོགས་སོང་།",
|
||||
"Download failed": "ཕབ་ལེན་ཕམ་པ་",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "མཐའ་མ།",
|
||||
"Cannot Load Page": "ཤོག་ངོས་འགུལ་སྐྱོང་བྱེད་ཐུབ་མེད།",
|
||||
"An error occurred": "ནོར་འཁྲུལ་ཞིག་བྱུང་སོང་།",
|
||||
"Online Library": "དྲ་རྒྱུན་དེབ་མཛོད།"
|
||||
"Online Library": "དྲ་རྒྱུན་དེབ་མཛོད།",
|
||||
"URL must start with http:// or https://": "URL ནི་ http:// ཡང་ https:// ནས་འགོ་བཙུགས་དགོ།",
|
||||
"Title, Author, Tag, etc...": "མིང་།, རྩོམ་པ།, མཚོན་འགྲེལ།, དེ་ལས་སྐུགས་...",
|
||||
"Query": "འཚོལ་ཞིབ་",
|
||||
"Subject": "དོན་ཚན་",
|
||||
"Enter {{terms}}": "{{terms}} ལ་འགྲོ།",
|
||||
"No search results found": "འཚོལ་ཞིབ་རྫོགས་མ་ཐུབ།",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS འཕྲིན་འདེམས་བྱས་མ་ཐུབ།: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} ནང་འཚོལ།",
|
||||
"Manage Storage": "སྣོད་གསོག་དོ་དམ་བྱེད་པ",
|
||||
"Failed to load files": "ཡིག་ཆ་སྣོན་པ་ཕམ་པ",
|
||||
"Deleted {{count}} file(s)_other": "ཡིག་ཆ་ {{count}} བསུབས་ཟིན་པ",
|
||||
"Failed to delete {{count}} file(s)_other": "ཡིག་ཆ་ {{count}} བསུབས་པ་ཕམ་པ",
|
||||
"Failed to delete files": "ཡིག་ཆ་བསུབས་པ་ཕམ་པ",
|
||||
"Total Files": "ཡིག་ཆ་ཡོངས་བསྡོམས",
|
||||
"Total Size": "ཆེས་ཆེར་ཆེ་ཆུང",
|
||||
"Quota": "ཁུལ་ཚད",
|
||||
"Used": "ལག་ལེན་བྱས་ཟིན་པ",
|
||||
"Files": "ཡིག་ཆ",
|
||||
"Search files...": "ཡིག་ཆ་འཚོལ...",
|
||||
"Newest First": "གསར་ཤོས་སྔོན་དུ",
|
||||
"Oldest First": "རྙིང་ཤོས་སྔོན་དུ",
|
||||
"Largest First": "ཆེ་ཤོས་སྔོན་དུ",
|
||||
"Smallest First": "ཆུང་ཤོས་སྔོན་དུ",
|
||||
"Name A-Z": "མིང་ A-Z",
|
||||
"Name Z-A": "མིང་ Z-A",
|
||||
"{{count}} selected_other": "{{count}} ཡིག་ཆ་འདེམས་ཟིན་པ",
|
||||
"Delete Selected": "འདེམས་པ་བསུབས་པ",
|
||||
"Created": "སྤེལ་བྱས་ཟིན་པ",
|
||||
"No files found": "ཡིག་ཆ་མ་རྙེད་པ",
|
||||
"No files uploaded yet": "ད་ཚུན་ཡིག་ཆ་སྣོན་མི་འདུག",
|
||||
"files": "ཡིག་ཆ",
|
||||
"Page {{current}} of {{total}}": "ཤོག་ངོས་ {{total}} ནས་ {{current}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "ཁྱེད་ཀྱིས་འདེམས་པའི་ཡིག་ཆ་ {{count}} བསུབས་དགོས་པ་ངེས་ཡིན་ན?",
|
||||
"Cloud Storage Usage": "སྤྲིན་གནས་སྣོད་གསོག་ལུས་སྐོར།",
|
||||
"Rename Group": "ཚོགས་མིང་བསྒྱུར་བ།",
|
||||
"From Directory": "སྐོར་འདེམས་པ་ནས།",
|
||||
"Successfully imported {{count}} book(s)_other": "སྤྲིན་ནས་ཕབ་སྟེ་འབེབས་ {{count}} དེབ་འདེམས་སོང་",
|
||||
"Count": "ཨང་",
|
||||
"Start Page": "ཤོག་ངོས་དང་པོ",
|
||||
"Search in OPDS Catalog...": "OPDS དཀར་ཆག་ནང་འཚོལ།...",
|
||||
"Please log in to use advanced TTS features": "དབང་བསྐྱོད་ཀྱི TTS རྣམ་པ་ཚུགས་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Word limit of 30 words exceeded.": "ཚིག་གཅིག 30 ལྟར་འདོད་མེད།",
|
||||
"Proofread": "མིག་སྔའི་བཤད་རྒྱུན།",
|
||||
"Current selection": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"All occurrences in this book": "དེབ་ནང་གི་འདི་ཚུགས་ཀྱི་འཚོལ་ཞིབ།",
|
||||
"All occurrences in your library": "ཁྱོད་ཀྱི་དེབ་མཛོད་ནང་གི་འདི་ཚུགས་ཀྱི་འཚོལ་ཞིབ།",
|
||||
"Selected text:": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Replace with:": "བརྗེ་བ།:",
|
||||
"Enter text...": "ཡིག་ཆ་ལ་འགྲོ།...",
|
||||
"Case sensitive:": "གནས་སྟངས་འདི་ལ་འབད་དགོས།",
|
||||
"Scope:": "གནས་ཚུལ།:",
|
||||
"Selection": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Library": "དེབ་མཛོད།",
|
||||
"Yes": "ཨིན་",
|
||||
"No": "མེད་",
|
||||
"Proofread Replacement Rules": "མིག་སྔའི་བཤད་རྒྱུན་བརྗེ་བའི་རྣམ་པ།",
|
||||
"Selected Text Rules": "བདམས་པའི་ཡིག་ཆ་བརྗེ་སྒྱུར་སྲིད་སྒྲིག",
|
||||
"No selected text replacement rules": "བདམས་པའི་ཡིག་ཆ་བརྗེ་སྒྱུར་སྲིད་སྒྲིག་མེད་པ",
|
||||
"Book Specific Rules": "དེབ་སྒོས་ཀྱི་བརྗེ་སྒྱུར་སྲིད་སྒྲིག",
|
||||
"No book-level replacement rules": "དེབ་རིམ་གྱི་བརྗེ་སྒྱུར་སྲིད་སྒྲིག་མེད་པ",
|
||||
"Disable Quick Action": "མགྱོགས་མྱུར་བྱ་བ་སྤང་",
|
||||
"Enable Quick Action on Selection": "འདེམས་པའི་སྐབས་མགྱོགས་མྱུར་བྱ་བ་སྤྱོད་",
|
||||
"None": "མེད་",
|
||||
"Annotation Tools": "མཆན་འགྲེལ་ལག་ཆ་",
|
||||
"Enable Quick Actions": "མགྱོགས་མྱུར་བྱ་བ་སྤྱོད་",
|
||||
"Quick Action": "མགྱོགས་མྱུར་བྱ་བ་",
|
||||
"Copy to Notebook": "ཟིན་དེབ་ནང་འདྲ་བཤུས་",
|
||||
"Copy text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་འདྲ་བཤུས་",
|
||||
"Highlight text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་མངོན་གསལ་བྱེད་",
|
||||
"Annotate text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་མཆན་འགྲེལ་བྱེད་",
|
||||
"Search text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་འཚོལ་ཞིབ་བྱེད་",
|
||||
"Look up text in dictionary after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཚིག་མཛོད་ནང་འཚོལ་",
|
||||
"Look up text in Wikipedia after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཝེ་ཀི་པི་ཌི་ཡི་ནང་འཚོལ་",
|
||||
"Translate text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་སྐད་བསྒྱུར་བྱེད་",
|
||||
"Read text aloud after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་སྐད་ཀྱིས་ཀློག་",
|
||||
"Proofread text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཞིབ་བཤེར་བྱེད་",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} བྱེད་བཞིན་པ་, {{pendingCount}} སྒུག་བཞིན་པ་",
|
||||
"{{failedCount}} failed": "{{failedCount}} ཕམ་པ་",
|
||||
"Waiting...": "སྒུག་བཞིན་པ་...",
|
||||
"Failed": "ཕམ་པ་",
|
||||
"Completed": "གྲུབ་སོང་",
|
||||
"Cancelled": "དོར་སོང་",
|
||||
"Retry": "བསྐྱར་ཚོད་",
|
||||
"Active": "བྱེད་བཞིན་པ་",
|
||||
"Transfer Queue": "སྐྱེལ་འདྲེན་སྒྲིག་གཅོད་",
|
||||
"Upload All": "ཚང་མ་འཇོག་",
|
||||
"Download All": "ཚང་མ་ལེན་",
|
||||
"Resume Transfers": "སྐྱེལ་འདྲེན་མུ་མཐུད་",
|
||||
"Pause Transfers": "སྐྱེལ་འདྲེན་མཚམས་འཇོག་",
|
||||
"Pending": "སྒུག་བཞིན་པ་",
|
||||
"No transfers": "སྐྱེལ་འདྲེན་མེད་",
|
||||
"Retry All": "ཚང་མ་བསྐྱར་ཚོད་",
|
||||
"Clear Completed": "གྲུབ་པ་གསལ་བ་",
|
||||
"Clear Failed": "ཕམ་པ་གསལ་བ་",
|
||||
"Upload queued: {{title}}": "འཇོག་པའི་སྒྲིག་གཅོད་ནང་བཅུག་: {{title}}",
|
||||
"Download queued: {{title}}": "ལེན་པའི་སྒྲིག་གཅོད་ནང་བཅུག་: {{title}}",
|
||||
"Book not found in library": "དཔེ་མཛོད་ནང་དཔེ་ཆ་རྙེད་མ་སོང་",
|
||||
"Unknown error": "མ་ཤེས་པའི་ནོར་འཁྲུལ་",
|
||||
"Please log in to continue": "མུ་མཐུད་པར་ནང་འཛུལ་བྱོས་",
|
||||
"Cloud File Transfers": "སྤྲིན་གནས་ཡིག་ཆ་སྐྱེལ་འདྲེན།",
|
||||
"Show Search Results": "འཚོལ་བཤེར་འབྲས་བུ་སྟོན།",
|
||||
"Search results for '{{term}}'": "'{{term}}'ཡི་འབྲས་བུ།",
|
||||
"Close Search": "འཚོལ་བཤེར་སྒོ་རྒྱག",
|
||||
"Previous Result": "སྔོན་མའི་འབྲས་བུ།",
|
||||
"Next Result": "རྗེས་མའི་འབྲས་བུ།",
|
||||
"Bookmarks": "དཔེ་རྟགས།",
|
||||
"Annotations": "མཆན།",
|
||||
"Show Results": "འབྲས་བུ་སྟོན།",
|
||||
"Clear search": "འཚོལ་བཤེར་གཙང་སེལ།",
|
||||
"Clear search history": "འཚོལ་བཤེར་ལོ་རྒྱུས་གཙང་སེལ།",
|
||||
"Quick action disabled": "མྱུར་མགྱོགས་བྱ་འགུལ་བཀག་འགོག",
|
||||
"Tap to Toggle Footer": "ཞབས་མཇུག་སྒོ་འབྱེད་བྱེད་པར་གནོན།",
|
||||
"Exported successfully": "ཕྱིར་འདོན་ལེགས་གྲུབ་བྱུང་།",
|
||||
"Book exported successfully.": "དཔེ་དེབ་ཕྱིར་འདོན་ལེགས་གྲུབ་བྱུང་།",
|
||||
"Failed to export the book.": "དཔེ་དེབ་ཕྱིར་འདོན་མི་ཐུབ།",
|
||||
"Export Book": "དཔེ་དེབ་ཕྱིར་འདོན།",
|
||||
"Whole word:": "ཚིག་གྲུབ་ཆ་ཚང་:",
|
||||
"Error": "ནོར་འཁྲུལ།",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "རྩོམ་ཡིག་མངོན་མི་ཐུབ། {{link}} ཐད་ཀར་འཚོལ་བ་བྱོས།",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "ཚིག་མངོན་མི་ཐུབ། {{link}} ཐད་ཀར་འཚོལ་བ་བྱོས།",
|
||||
"Date Published": "པར་སྐྲུན་ཚེས་གྲངས།",
|
||||
"Only for TTS:": "TTS ལ་ཁོ་ན།:",
|
||||
"Uploaded": "ཡར་སྐྱེལ་བྱས་ཟིན།",
|
||||
"Downloaded": "མར་ལེན་བྱས་ཟིན།",
|
||||
"Deleted": "བསུབས་ཟིན།",
|
||||
"Note:": "གསལ་བཤད།:",
|
||||
"Time:": "དུས་ཚོད།:",
|
||||
"Format Options": "རྣམ་གཞག་གདམ་ག",
|
||||
"Export Date": "ཕྱིར་འདོན་ཚེས་གྲངས།",
|
||||
"Chapter Titles": "ལེའུའི་མཚན།",
|
||||
"Chapter Separator": "ལེའུའི་དབར་མཚམས།",
|
||||
"Highlights": "གཙོ་གནད།",
|
||||
"Note Date": "གསལ་བཤད་ཚེས་གྲངས།",
|
||||
"Advanced": "མཐོ་རིམ།",
|
||||
"Hide": "སྦས་པ།",
|
||||
"Show": "མངོན་པ།",
|
||||
"Use Custom Template": "རང་བཟོའི་དཔེ་གཞི་བེད་སྤྱོད།",
|
||||
"Export Template": "ཕྱིར་འདོན་དཔེ་གཞི།",
|
||||
"Template Syntax:": "དཔེ་གཞིའི་སྒྲིག་གཞི།:",
|
||||
"Insert value": "གྲངས་ཐང་བཙུགས་པ།",
|
||||
"Format date (locale)": "ཚེས་གྲངས་རྣམ་གཞག (ས་གནས།)",
|
||||
"Format date (custom)": "ཚེས་གྲངས་རྣམ་གཞག (རང་བཟོ།)",
|
||||
"Conditional": "དམིགས་བསལ།",
|
||||
"Loop": "འཁོར་ལོ།",
|
||||
"Available Variables:": "བེད་སྤྱད་རུང་བའི་འགྱུར་ཅན།:",
|
||||
"Book title": "དེབ་མཚན།",
|
||||
"Book author": "དེབ་རྩོམ་པ་པོ།",
|
||||
"Export date": "ཕྱིར་འདོན་ཚེས་གྲངས།",
|
||||
"Array of chapters": "ལེའུའི་ལེབ་ངོས།",
|
||||
"Chapter title": "ལེའུའི་མཚན།",
|
||||
"Array of annotations": "གསལ་བཤད་ལེབ་ངོས།",
|
||||
"Highlighted text": "གཙོ་གནད་ཡིག་རྐྱང་།",
|
||||
"Annotation note": "གསལ་བཤད་ཟིན་བྲིས།",
|
||||
"Update time": "གསར་བཅོས་དུས་ཚོད།",
|
||||
"Date Format Tokens:": "ཚེས་གྲངས་རྣམ་གཞག་རྟགས།:",
|
||||
"Year (4 digits)": "ལོ། (གྲངས་ཐང་4)",
|
||||
"Month (01-12)": "ཟླ། (01-12)",
|
||||
"Day (01-31)": "ཚེས། (01-31)",
|
||||
"Hour (00-23)": "ཆུ་ཚོད། (00-23)",
|
||||
"Minute (00-59)": "སྐར་མ། (00-59)",
|
||||
"Second (00-59)": "སྐར་ཆ། (00-59)",
|
||||
"Show Source": "འབྱུང་ཁུངས་མངོན་པ།",
|
||||
"No content to preview": "སྔོན་ལྟ་བྱེད་པའི་དོན་རྐྱེན་མེད།",
|
||||
"Export": "ཕྱིར་འདོན།",
|
||||
"Set Timeout": "དུས་ཚོད་སྒྲིག་པ།",
|
||||
"Select Voice": "སྐད་གདངས་འདེམས།",
|
||||
"Toggle Sticky Bottom TTS Bar": "TTS སྡོམ་ཐིག་བརྗེ་བ།",
|
||||
"Display what I'm reading on Discord": "Discord ཐོག་ཀློག་བཞིན་པའི་དཔེ་ཆ་སྟོན།",
|
||||
"Show on Discord": "Discord ཐོག་སྟོན།"
|
||||
}
|
||||
|
||||
@@ -172,9 +172,6 @@
|
||||
"Token": "Token",
|
||||
"Your OTP token": "Ihr OTP-Token",
|
||||
"Verify token": "Token überprüfen",
|
||||
"Sign in with Google": "Mit Google anmelden",
|
||||
"Sign in with Apple": "Mit Apple anmelden",
|
||||
"Sign in with GitHub": "Mit GitHub anmelden",
|
||||
"Account": "Konto",
|
||||
"Failed to delete user. Please try again later.": "Benutzer konnte nicht gelöscht werden. Bitte versuchen Sie es später erneut.",
|
||||
"Community Support": "Community-Support",
|
||||
@@ -340,8 +337,8 @@
|
||||
"{{count}} pages left in chapter_one": "{{count}} Seite verbleibend im Kapitel",
|
||||
"{{count}} pages left in chapter_other": "{{count}} Seiten verbleibend im Kapitel",
|
||||
"Show Remaining Pages": "Verbleibende Seiten anzeigen",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Abo verwalten",
|
||||
"Coming Soon": "Demnächst verfügbar",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "Wird überprüft...",
|
||||
"View All": "Alle anzeigen",
|
||||
"Forward": "Weiter",
|
||||
"OPDS Catalog": "OPDS-Katalog",
|
||||
"Home": "Start",
|
||||
"Library": "Bibliothek",
|
||||
"{{count}} items_one": "{{count}} Element",
|
||||
"{{count}} items_other": "{{count}} Elemente",
|
||||
"Download completed": "Download abgeschlossen",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "Letzte",
|
||||
"Cannot Load Page": "Seite kann nicht geladen werden",
|
||||
"An error occurred": "Ein Fehler ist aufgetreten",
|
||||
"Online Library": "Online-Bibliothek"
|
||||
"Online Library": "Online-Bibliothek",
|
||||
"URL must start with http:// or https://": "Die URL muss mit http:// oder https:// beginnen",
|
||||
"Title, Author, Tag, etc...": "Titel, Autor, Tag, etc...",
|
||||
"Query": "Suchbegriff",
|
||||
"Subject": "Thema",
|
||||
"Enter {{terms}}": "Gib {{terms}} ein",
|
||||
"No search results found": "Keine Suchergebnisse gefunden",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS-Feed konnte nicht geladen werden: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Suche in {{title}}",
|
||||
"Manage Storage": "Speicher verwalten",
|
||||
"Failed to load files": "Dateien konnten nicht geladen werden",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} Datei gelöscht",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} Dateien gelöscht",
|
||||
"Failed to delete {{count}} file(s)_one": "Löschen von {{count}} Datei fehlgeschlagen",
|
||||
"Failed to delete {{count}} file(s)_other": "Löschen von {{count}} Dateien fehlgeschlagen",
|
||||
"Failed to delete files": "Dateien konnten nicht gelöscht werden",
|
||||
"Total Files": "Gesamtdateien",
|
||||
"Total Size": "Gesamtgröße",
|
||||
"Quota": "Kontingent",
|
||||
"Used": "Verwendet",
|
||||
"Files": "Dateien",
|
||||
"Search files...": "Dateien suchen...",
|
||||
"Newest First": "Neueste zuerst",
|
||||
"Oldest First": "Älteste zuerst",
|
||||
"Largest First": "Größte zuerst",
|
||||
"Smallest First": "Kleinste zuerst",
|
||||
"Name A-Z": "Name A–Z",
|
||||
"Name Z-A": "Name Z–A",
|
||||
"{{count}} selected_one": "{{count}} ausgewählt",
|
||||
"{{count}} selected_other": "{{count}} ausgewählt",
|
||||
"Delete Selected": "Ausgewählte löschen",
|
||||
"Created": "Erstellt",
|
||||
"No files found": "Keine Dateien gefunden",
|
||||
"No files uploaded yet": "Noch keine Dateien hochgeladen",
|
||||
"files": "Dateien",
|
||||
"Page {{current}} of {{total}}": "Seite {{current}} von {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Möchten Sie {{count}} ausgewählte Datei wirklich löschen?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Möchten Sie {{count}} ausgewählte Dateien wirklich löschen?",
|
||||
"Cloud Storage Usage": "Cloud-Speichernutzung",
|
||||
"Rename Group": "Gruppe umbenennen",
|
||||
"From Directory": "Aus Verzeichnis",
|
||||
"Successfully imported {{count}} book(s)_one": "Erfolgreich 1 Buch importiert",
|
||||
"Successfully imported {{count}} book(s)_other": "Erfolgreich {{count}} Bücher importiert",
|
||||
"Count": "Anzahl",
|
||||
"Start Page": "Startseite",
|
||||
"Search in OPDS Catalog...": "Im OPDS-Katalog suchen...",
|
||||
"Please log in to use advanced TTS features": "Bitte melde dich an, um erweiterte TTS-Funktionen zu nutzen",
|
||||
"Word limit of 30 words exceeded.": "Das Wortlimit von 30 Wörtern wurde überschritten.",
|
||||
"Proofread": "Korrekturlesen",
|
||||
"Current selection": "Aktuelle Auswahl",
|
||||
"All occurrences in this book": "Alle Vorkommen in diesem Buch",
|
||||
"All occurrences in your library": "Alle Vorkommen in Ihrer Bibliothek",
|
||||
"Selected text:": "Ausgewählter Text:",
|
||||
"Replace with:": "Ersetzen durch:",
|
||||
"Enter text...": "Text eingeben …",
|
||||
"Case sensitive:": "Groß-/Kleinschreibung:",
|
||||
"Scope:": "Geltungsbereich:",
|
||||
"Selection": "Auswahl",
|
||||
"Library": "Bibliothek",
|
||||
"Yes": "Ja",
|
||||
"No": "Nein",
|
||||
"Proofread Replacement Rules": "Korrektur-Ersetzungsregeln",
|
||||
"Selected Text Rules": "Regeln für ausgewählten Text",
|
||||
"No selected text replacement rules": "Keine Ersetzungsregeln für ausgewählten Text",
|
||||
"Book Specific Rules": "Buchspezifische Regeln",
|
||||
"No book-level replacement rules": "Keine Ersetzungsregeln auf Buchebene",
|
||||
"Disable Quick Action": "Schnellaktion deaktivieren",
|
||||
"Enable Quick Action on Selection": "Schnellaktion bei Auswahl aktivieren",
|
||||
"None": "Keine",
|
||||
"Annotation Tools": "Anmerkungswerkzeuge",
|
||||
"Enable Quick Actions": "Schnellaktionen aktivieren",
|
||||
"Quick Action": "Schnellaktion",
|
||||
"Copy to Notebook": "In Notizbuch kopieren",
|
||||
"Copy text after selection": "Text nach Auswahl kopieren",
|
||||
"Highlight text after selection": "Text nach Auswahl hervorheben",
|
||||
"Annotate text after selection": "Text nach Auswahl annotieren",
|
||||
"Search text after selection": "Text nach Auswahl suchen",
|
||||
"Look up text in dictionary after selection": "Text nach Auswahl im Wörterbuch nachschlagen",
|
||||
"Look up text in Wikipedia after selection": "Text nach Auswahl in Wikipedia nachschlagen",
|
||||
"Translate text after selection": "Text nach Auswahl übersetzen",
|
||||
"Read text aloud after selection": "Text nach Auswahl vorlesen",
|
||||
"Proofread text after selection": "Text nach Auswahl Korrektur lesen",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} aktiv, {{pendingCount}} ausstehend",
|
||||
"{{failedCount}} failed": "{{failedCount}} fehlgeschlagen",
|
||||
"Waiting...": "Warten...",
|
||||
"Failed": "Fehlgeschlagen",
|
||||
"Completed": "Abgeschlossen",
|
||||
"Cancelled": "Abgebrochen",
|
||||
"Retry": "Erneut versuchen",
|
||||
"Active": "Aktiv",
|
||||
"Transfer Queue": "Übertragungswarteschlange",
|
||||
"Upload All": "Alle hochladen",
|
||||
"Download All": "Alle herunterladen",
|
||||
"Resume Transfers": "Übertragungen fortsetzen",
|
||||
"Pause Transfers": "Übertragungen pausieren",
|
||||
"Pending": "Ausstehend",
|
||||
"No transfers": "Keine Übertragungen",
|
||||
"Retry All": "Alle erneut versuchen",
|
||||
"Clear Completed": "Abgeschlossene löschen",
|
||||
"Clear Failed": "Fehlgeschlagene löschen",
|
||||
"Upload queued: {{title}}": "Upload in Warteschlange: {{title}}",
|
||||
"Download queued: {{title}}": "Download in Warteschlange: {{title}}",
|
||||
"Book not found in library": "Buch nicht in der Bibliothek gefunden",
|
||||
"Unknown error": "Unbekannter Fehler",
|
||||
"Please log in to continue": "Bitte melden Sie sich an, um fortzufahren",
|
||||
"Cloud File Transfers": "Cloud-Dateiübertragungen",
|
||||
"Show Search Results": "Suchergebnisse anzeigen",
|
||||
"Search results for '{{term}}'": "Ergebnisse für '{{term}}'",
|
||||
"Close Search": "Suche schließen",
|
||||
"Previous Result": "Vorheriges Ergebnis",
|
||||
"Next Result": "Nächstes Ergebnis",
|
||||
"Bookmarks": "Lesezeichen",
|
||||
"Annotations": "Anmerkungen",
|
||||
"Show Results": "Ergebnisse anzeigen",
|
||||
"Clear search": "Suche löschen",
|
||||
"Clear search history": "Suchverlauf löschen",
|
||||
"Quick action disabled": "Schnellaktion deaktiviert",
|
||||
"Tap to Toggle Footer": "Tippen, um Fußzeile umzuschalten",
|
||||
"Exported successfully": "Erfolgreich exportiert",
|
||||
"Book exported successfully.": "Buch erfolgreich exportiert.",
|
||||
"Failed to export the book.": "Buch konnte nicht exportiert werden.",
|
||||
"Export Book": "Buch exportieren",
|
||||
"Whole word:": "Ganzes Wort:",
|
||||
"Error": "Fehler",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Artikel kann nicht geladen werden. Versuchen Sie die direkte Suche auf {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Wort kann nicht geladen werden. Versuchen Sie die direkte Suche auf {{link}}.",
|
||||
"Date Published": "Veröffentlichungsdatum",
|
||||
"Only for TTS:": "Nur für TTS:",
|
||||
"Uploaded": "Hochgeladen",
|
||||
"Downloaded": "Heruntergeladen",
|
||||
"Deleted": "Gelöscht",
|
||||
"Note:": "Notiz:",
|
||||
"Time:": "Zeit:",
|
||||
"Format Options": "Formatoptionen",
|
||||
"Export Date": "Exportdatum",
|
||||
"Chapter Titles": "Kapiteltitel",
|
||||
"Chapter Separator": "Kapiteltrennzeichen",
|
||||
"Highlights": "Markierungen",
|
||||
"Note Date": "Notizdatum",
|
||||
"Advanced": "Erweitert",
|
||||
"Hide": "Ausblenden",
|
||||
"Show": "Anzeigen",
|
||||
"Use Custom Template": "Benutzerdefinierte Vorlage verwenden",
|
||||
"Export Template": "Exportvorlage",
|
||||
"Template Syntax:": "Vorlagensyntax:",
|
||||
"Insert value": "Wert einfügen",
|
||||
"Format date (locale)": "Datum formatieren (Gebietsschema)",
|
||||
"Format date (custom)": "Datum formatieren (benutzerdefiniert)",
|
||||
"Conditional": "Bedingung",
|
||||
"Loop": "Schleife",
|
||||
"Available Variables:": "Verfügbare Variablen:",
|
||||
"Book title": "Buchtitel",
|
||||
"Book author": "Buchautor",
|
||||
"Export date": "Exportdatum",
|
||||
"Array of chapters": "Array von Kapiteln",
|
||||
"Chapter title": "Kapiteltitel",
|
||||
"Array of annotations": "Array von Anmerkungen",
|
||||
"Highlighted text": "Markierter Text",
|
||||
"Annotation note": "Anmerkungsnotiz",
|
||||
"Update time": "Aktualisierungszeit",
|
||||
"Date Format Tokens:": "Datumsformat-Token:",
|
||||
"Year (4 digits)": "Jahr (4 Ziffern)",
|
||||
"Month (01-12)": "Monat (01-12)",
|
||||
"Day (01-31)": "Tag (01-31)",
|
||||
"Hour (00-23)": "Stunde (00-23)",
|
||||
"Minute (00-59)": "Minute (00-59)",
|
||||
"Second (00-59)": "Sekunde (00-59)",
|
||||
"Show Source": "Quelle anzeigen",
|
||||
"No content to preview": "Kein Inhalt zur Vorschau",
|
||||
"Export": "Exportieren",
|
||||
"Set Timeout": "Zeitlimit festlegen",
|
||||
"Select Voice": "Stimme auswählen",
|
||||
"Toggle Sticky Bottom TTS Bar": "Fixierte TTS-Leiste umschalten",
|
||||
"Display what I'm reading on Discord": "Zeige was ich auf Discord lese",
|
||||
"Show on Discord": "Auf Discord zeigen"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Κωδικός επαλήθευσης",
|
||||
"Your OTP token": "Ο κωδικός OTP σας",
|
||||
"Verify token": "Επαλήθευση κωδικού",
|
||||
"Sign in with Google": "Σύνδεση με Google",
|
||||
"Sign in with Apple": "Σύνδεση με Apple",
|
||||
"Sign in with GitHub": "Σύνδεση με GitHub",
|
||||
"Account": "Λογαριασμός",
|
||||
"Failed to delete user. Please try again later.": "Αποτυχία διαγραφής χρήστη. Παρακαλώ δοκιμάστε ξανά αργότερα.",
|
||||
"Community Support": "Υποστήριξη κοινότητας",
|
||||
@@ -341,8 +338,8 @@
|
||||
"{{count}} pages left in chapter_one": "Μένει {{count}} σελίδα",
|
||||
"{{count}} pages left in chapter_other": "Μένουν {{count}} σελίδες",
|
||||
"Show Remaining Pages": "Εμφάνιση υπολοίπων",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Διαχείριση συνδρομής",
|
||||
"Coming Soon": "Έρχεται σύντομα",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "Γίνεται έλεγχος...",
|
||||
"View All": "Προβολή όλων",
|
||||
"Forward": "Μπροστά",
|
||||
"OPDS Catalog": "Κατάλογος OPDS",
|
||||
"Home": "Αρχική",
|
||||
"Library": "Βιβλιοθήκη",
|
||||
"{{count}} items_one": "{{count}} στοιχείο",
|
||||
"{{count}} items_other": "{{count}} στοιχεία",
|
||||
"Download completed": "Η λήψη ολοκληρώθηκε",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "Τελευταίο",
|
||||
"Cannot Load Page": "Δεν είναι δυνατή η φόρτωση της σελίδας",
|
||||
"An error occurred": "Προέκυψε σφάλμα",
|
||||
"Online Library": "Online Βιβλιοθήκη"
|
||||
"Online Library": "Online Βιβλιοθήκη",
|
||||
"URL must start with http:// or https://": "Το URL πρέπει να ξεκινά με http:// ή https://",
|
||||
"Title, Author, Tag, etc...": "Τίτλος, Συγγραφέας, Ετικέτα, κ.λπ...",
|
||||
"Query": "Ερώτημα",
|
||||
"Subject": "Θέμα",
|
||||
"Enter {{terms}}": "Εισαγάγετε {{terms}}",
|
||||
"No search results found": "Δεν βρέθηκαν αποτελέσματα αναζήτησης",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Αποτυχία φόρτωσης ροής OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Αναζήτηση στο {{title}}",
|
||||
"Manage Storage": "Διαχείριση αποθήκευσης",
|
||||
"Failed to load files": "Αποτυχία φόρτωσης αρχείων",
|
||||
"Deleted {{count}} file(s)_one": "Διαγράφηκε {{count}} αρχείο",
|
||||
"Deleted {{count}} file(s)_other": "Διαγράφηκαν {{count}} αρχεία",
|
||||
"Failed to delete {{count}} file(s)_one": "Αποτυχία διαγραφής {{count}} αρχείου",
|
||||
"Failed to delete {{count}} file(s)_other": "Αποτυχία διαγραφής {{count}} αρχείων",
|
||||
"Failed to delete files": "Αποτυχία διαγραφής αρχείων",
|
||||
"Total Files": "Σύνολο αρχείων",
|
||||
"Total Size": "Συνολικό μέγεθος",
|
||||
"Quota": "Όριο",
|
||||
"Used": "Χρησιμοποιήθηκε",
|
||||
"Files": "Αρχεία",
|
||||
"Search files...": "Αναζήτηση αρχείων...",
|
||||
"Newest First": "Νεότερα πρώτα",
|
||||
"Oldest First": "Παλαιότερα πρώτα",
|
||||
"Largest First": "Μεγαλύτερα πρώτα",
|
||||
"Smallest First": "Μικρότερα πρώτα",
|
||||
"Name A-Z": "Όνομα A–Z",
|
||||
"Name Z-A": "Όνομα Z–A",
|
||||
"{{count}} selected_one": "{{count}} επιλεγμένο",
|
||||
"{{count}} selected_other": "{{count}} επιλεγμένα",
|
||||
"Delete Selected": "Διαγραφή επιλεγμένων",
|
||||
"Created": "Δημιουργήθηκε",
|
||||
"No files found": "Δεν βρέθηκαν αρχεία",
|
||||
"No files uploaded yet": "Δεν έχουν ανέβει αρχεία ακόμη",
|
||||
"files": "αρχεία",
|
||||
"Page {{current}} of {{total}}": "Σελίδα {{current}} από {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Σίγουρα θέλετε να διαγράψετε {{count}} επιλεγμένο αρχείο;",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Σίγουρα θέλετε να διαγράψετε {{count}} επιλεγμένα αρχεία;",
|
||||
"Cloud Storage Usage": "Χρήση αποθήκευσης cloud",
|
||||
"Rename Group": "Μετονομασία ομάδας",
|
||||
"From Directory": "Από κατάλογο",
|
||||
"Successfully imported {{count}} book(s)_one": "Επιτυχής εισαγωγή 1 βιβλίου",
|
||||
"Successfully imported {{count}} book(s)_other": "Επιτυχής εισαγωγή {{count}} βιβλίων",
|
||||
"Count": "Πλήθος",
|
||||
"Start Page": "Αρχική Σελίδα",
|
||||
"Search in OPDS Catalog...": "Αναζήτηση στον κατάλογο OPDS...",
|
||||
"Please log in to use advanced TTS features": "Παρακαλώ συνδεθείτε για να χρησιμοποιήσετε προηγμένες λειτουργίες TTS",
|
||||
"Word limit of 30 words exceeded.": "Υπέρβαση ορίου 30 λέξεων.",
|
||||
"Proofread": "Διόρθωση",
|
||||
"Current selection": "Τρέχουσα επιλογή",
|
||||
"All occurrences in this book": "Όλες οι εμφανίσεις σε αυτό το βιβλίο",
|
||||
"All occurrences in your library": "Όλες οι εμφανίσεις στη βιβλιοθήκη σας",
|
||||
"Selected text:": "Επιλεγμένο κείμενο:",
|
||||
"Replace with:": "Αντικατάσταση με:",
|
||||
"Enter text...": "Εισαγωγή κειμένου…",
|
||||
"Case sensitive:": "Διάκριση πεζών-κεφαλαίων:",
|
||||
"Scope:": "Εύρος εφαρμογής:",
|
||||
"Selection": "Επιλογή",
|
||||
"Library": "Βιβλιοθήκη",
|
||||
"Yes": "Ναι",
|
||||
"No": "Όχι",
|
||||
"Proofread Replacement Rules": "Κανόνες αντικατάστασης διόρθωσης",
|
||||
"Selected Text Rules": "Κανόνες επιλεγμένου κειμένου",
|
||||
"No selected text replacement rules": "Δεν υπάρχουν κανόνες αντικατάστασης για το επιλεγμένο κείμενο",
|
||||
"Book Specific Rules": "Κανόνες για συγκεκριμένο βιβλίο",
|
||||
"No book-level replacement rules": "Δεν υπάρχουν κανόνες αντικατάστασης σε επίπεδο βιβλίου",
|
||||
"Disable Quick Action": "Απενεργοποίηση γρήγορης ενέργειας",
|
||||
"Enable Quick Action on Selection": "Ενεργοποίηση γρήγορης ενέργειας κατά την επιλογή",
|
||||
"None": "Καμία",
|
||||
"Annotation Tools": "Εργαλεία σχολιασμού",
|
||||
"Enable Quick Actions": "Ενεργοποίηση γρήγορων ενεργειών",
|
||||
"Quick Action": "Γρήγορη ενέργεια",
|
||||
"Copy to Notebook": "Αντιγραφή στο σημειωματάριο",
|
||||
"Copy text after selection": "Αντιγραφή κειμένου μετά την επιλογή",
|
||||
"Highlight text after selection": "Επισήμανση κειμένου μετά την επιλογή",
|
||||
"Annotate text after selection": "Σχολιασμός κειμένου μετά την επιλογή",
|
||||
"Search text after selection": "Αναζήτηση κειμένου μετά την επιλογή",
|
||||
"Look up text in dictionary after selection": "Αναζήτηση κειμένου στο λεξικό μετά την επιλογή",
|
||||
"Look up text in Wikipedia after selection": "Αναζήτηση κειμένου στη Wikipedia μετά την επιλογή",
|
||||
"Translate text after selection": "Μετάφραση κειμένου μετά την επιλογή",
|
||||
"Read text aloud after selection": "Ανάγνωση κειμένου μετά την επιλογή",
|
||||
"Proofread text after selection": "Διόρθωση κειμένου μετά την επιλογή",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} ενεργές, {{pendingCount}} σε αναμονή",
|
||||
"{{failedCount}} failed": "{{failedCount}} απέτυχαν",
|
||||
"Waiting...": "Αναμονή...",
|
||||
"Failed": "Απέτυχε",
|
||||
"Completed": "Ολοκληρώθηκε",
|
||||
"Cancelled": "Ακυρώθηκε",
|
||||
"Retry": "Επανάληψη",
|
||||
"Active": "Ενεργές",
|
||||
"Transfer Queue": "Ουρά μεταφορών",
|
||||
"Upload All": "Ανέβασμα όλων",
|
||||
"Download All": "Λήψη όλων",
|
||||
"Resume Transfers": "Συνέχιση μεταφορών",
|
||||
"Pause Transfers": "Παύση μεταφορών",
|
||||
"Pending": "Σε αναμονή",
|
||||
"No transfers": "Χωρίς μεταφορές",
|
||||
"Retry All": "Επανάληψη όλων",
|
||||
"Clear Completed": "Εκκαθάριση ολοκληρωμένων",
|
||||
"Clear Failed": "Εκκαθάριση αποτυχημένων",
|
||||
"Upload queued: {{title}}": "Ανέβασμα στην ουρά: {{title}}",
|
||||
"Download queued: {{title}}": "Λήψη στην ουρά: {{title}}",
|
||||
"Book not found in library": "Το βιβλίο δεν βρέθηκε στη βιβλιοθήκη",
|
||||
"Unknown error": "Άγνωστο σφάλμα",
|
||||
"Please log in to continue": "Παρακαλώ συνδεθείτε για να συνεχίσετε",
|
||||
"Cloud File Transfers": "Μεταφορές αρχείων στο cloud",
|
||||
"Show Search Results": "Εμφάνιση αποτελεσμάτων",
|
||||
"Search results for '{{term}}'": "Αποτελέσματα για '{{term}}'",
|
||||
"Close Search": "Κλείσιμο αναζήτησης",
|
||||
"Previous Result": "Προηγούμενο αποτέλεσμα",
|
||||
"Next Result": "Επόμενο αποτέλεσμα",
|
||||
"Bookmarks": "Σελιδοδείκτες",
|
||||
"Annotations": "Σχολιασμοί",
|
||||
"Show Results": "Εμφάνιση αποτελεσμάτων",
|
||||
"Clear search": "Εκκαθάριση αναζήτησης",
|
||||
"Clear search history": "Εκκαθάριση ιστορικού αναζήτησης",
|
||||
"Quick action disabled": "Η γρήγορη ενέργεια απενεργοποιήθηκε",
|
||||
"Tap to Toggle Footer": "Πατήστε για εναλλαγή υποσέλιδου",
|
||||
"Exported successfully": "Εξαγωγή επιτυχής",
|
||||
"Book exported successfully.": "Το βιβλίο εξήχθη επιτυχώς.",
|
||||
"Failed to export the book.": "Αποτυχία εξαγωγής του βιβλίου.",
|
||||
"Export Book": "Εξαγωγή βιβλίου",
|
||||
"Whole word:": "Ολόκληρη λέξη:",
|
||||
"Error": "Σφάλμα",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Αδυναμία φόρτωσης του άρθρου. Δοκιμάστε να αναζητήσετε απευθείας στο {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Αδυναμία φόρτωσης της λέξης. Δοκιμάστε να αναζητήσετε απευθείας στο {{link}}.",
|
||||
"Date Published": "Ημερομηνία δημοσίευσης",
|
||||
"Only for TTS:": "Μόνο για TTS:",
|
||||
"Uploaded": "Μεταφορτώθηκε",
|
||||
"Downloaded": "Λήφθηκε",
|
||||
"Deleted": "Διαγράφηκε",
|
||||
"Note:": "Σημείωση:",
|
||||
"Time:": "Ώρα:",
|
||||
"Format Options": "Επιλογές μορφοποίησης",
|
||||
"Export Date": "Ημερομηνία εξαγωγής",
|
||||
"Chapter Titles": "Τίτλοι κεφαλαίων",
|
||||
"Chapter Separator": "Διαχωριστικό κεφαλαίων",
|
||||
"Highlights": "Επισημάνσεις",
|
||||
"Note Date": "Ημερομηνία σημείωσης",
|
||||
"Advanced": "Για προχωρημένους",
|
||||
"Hide": "Απόκρυψη",
|
||||
"Show": "Εμφάνιση",
|
||||
"Use Custom Template": "Χρήση προσαρμοσμένου προτύπου",
|
||||
"Export Template": "Πρότυπο εξαγωγής",
|
||||
"Template Syntax:": "Σύνταξη προτύπου:",
|
||||
"Insert value": "Εισαγωγή τιμής",
|
||||
"Format date (locale)": "Μορφοποίηση ημερομηνίας (τοπική)",
|
||||
"Format date (custom)": "Μορφοποίηση ημερομηνίας (προσαρμοσμένη)",
|
||||
"Conditional": "Υπό όρους",
|
||||
"Loop": "Βρόχος",
|
||||
"Available Variables:": "Διαθέσιμες μεταβλητές:",
|
||||
"Book title": "Τίτλος βιβλίου",
|
||||
"Book author": "Συγγραφέας βιβλίου",
|
||||
"Export date": "Ημερομηνία εξαγωγής",
|
||||
"Array of chapters": "Πίνακας κεφαλαίων",
|
||||
"Chapter title": "Τίτλος κεφαλαίου",
|
||||
"Array of annotations": "Πίνακας σχολιασμών",
|
||||
"Highlighted text": "Επισημασμένο κείμενο",
|
||||
"Annotation note": "Σημείωση σχολιασμού",
|
||||
"Update time": "Ώρα ενημέρωσης",
|
||||
"Date Format Tokens:": "Σύμβολα μορφοποίησης ημερομηνίας:",
|
||||
"Year (4 digits)": "Έτος (4 ψηφία)",
|
||||
"Month (01-12)": "Μήνας (01-12)",
|
||||
"Day (01-31)": "Ημέρα (01-31)",
|
||||
"Hour (00-23)": "Ώρα (00-23)",
|
||||
"Minute (00-59)": "Λεπτό (00-59)",
|
||||
"Second (00-59)": "Δευτερόλεπτο (00-59)",
|
||||
"Show Source": "Εμφάνιση πηγής",
|
||||
"No content to preview": "Δεν υπάρχει περιεχόμενο για προεπισκόπηση",
|
||||
"Export": "Εξαγωγή",
|
||||
"Set Timeout": "Ορισμός χρονικού ορίου",
|
||||
"Select Voice": "Επιλογή φωνής",
|
||||
"Toggle Sticky Bottom TTS Bar": "Εναλλαγή καρφιτσωμένης μπάρας TTS",
|
||||
"Display what I'm reading on Discord": "Εμφάνιση του βιβλίου που διαβάζω στο Discord",
|
||||
"Show on Discord": "Εμφάνιση στο Discord"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
{
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Are you sure to delete {{count}} selected book(s)?_one": "Are you sure to delete {{count}} selected book?",
|
||||
"Are you sure to delete {{count}} selected book(s)?_other": "Are you sure to delete {{count}} selected books?",
|
||||
"Search in {{count}} Book(s)..._one": "Search in {{count}} book...",
|
||||
"Search in {{count}} Book(s)..._other": "Search in {{count}} books...",
|
||||
"{{count}} pages left in chapter_one": "{{count}} page left in chapter",
|
||||
"{{count}} pages left in chapter_other": "{{count}} pages left in chapter"
|
||||
"{{count}} pages left in chapter_other": "{{count}} pages left in chapter",
|
||||
"Deleted {{count}} file(s)_one": "Deleted {{count}} file",
|
||||
"Deleted {{count}} file(s)_other": "Deleted {{count}} files",
|
||||
"Failed to delete {{count}} file(s)_one": "Failed to delete {{count}} file",
|
||||
"Failed to delete {{count}} file(s)_other": "Failed to delete {{count}} files",
|
||||
"{{count}} selected_one": "{{count}} selected",
|
||||
"{{count}} selected_other": "{{count}} selected",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Are you sure to delete {{count}} selected file?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Are you sure to delete {{count}} selected files?",
|
||||
"Successfully imported {{count}} book(s)_one": "Successfully imported {{count}} book",
|
||||
"Successfully imported {{count}} book(s)_other": "Successfully imported {{count}} books"
|
||||
}
|
||||
|
||||
@@ -200,9 +200,6 @@
|
||||
"Token": "Código de verificación",
|
||||
"Your OTP token": "Tu código OTP",
|
||||
"Verify token": "Verificar código",
|
||||
"Sign in with Google": "Iniciar sesión con Google",
|
||||
"Sign in with Apple": "Iniciar sesión con Apple",
|
||||
"Sign in with GitHub": "Iniciar sesión con GitHub",
|
||||
"Account": "Cuenta",
|
||||
"Failed to delete user. Please try again later.": "Error al eliminar usuario. Por favor, inténtelo de nuevo más tarde.",
|
||||
"Community Support": "Soporte comunitario",
|
||||
@@ -372,8 +369,8 @@
|
||||
"{{count}} pages left in chapter_many": "{{count}} páginas restantes en el capítulo",
|
||||
"{{count}} pages left in chapter_other": "{{count}} páginas restantes en el capítulo",
|
||||
"Show Remaining Pages": "Mostrar resto",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Gestionar suscripción",
|
||||
"Coming Soon": "Próximamente",
|
||||
@@ -703,9 +700,7 @@
|
||||
"Validating...": "Validando...",
|
||||
"View All": "Ver todo",
|
||||
"Forward": "Adelante",
|
||||
"OPDS Catalog": "Catálogo OPDS",
|
||||
"Home": "Inicio",
|
||||
"Library": "Biblioteca",
|
||||
"{{count}} items_one": "{{count}} elemento",
|
||||
"{{count}} items_many": "{{count}} elementos",
|
||||
"{{count}} items_other": "{{count}} elementos",
|
||||
@@ -726,5 +721,184 @@
|
||||
"Last": "Último",
|
||||
"Cannot Load Page": "No se puede cargar la página",
|
||||
"An error occurred": "Ocurrió un error",
|
||||
"Online Library": "Biblioteca en línea"
|
||||
"Online Library": "Biblioteca en línea",
|
||||
"URL must start with http:// or https://": "URL debe comenzar con http:// o https://",
|
||||
"Title, Author, Tag, etc...": "Titulo, Autor, Etiqueta, etc...",
|
||||
"Query": "Consulta",
|
||||
"Subject": "Asunto",
|
||||
"Enter {{terms}}": "Ingrese {{terms}}",
|
||||
"No search results found": "No se encontraron resultados de búsqueda",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Error al cargar el feed OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Buscar en {{title}}",
|
||||
"Manage Storage": "Administrar almacenamiento",
|
||||
"Failed to load files": "Error al cargar los archivos",
|
||||
"Deleted {{count}} file(s)_one": "Se eliminó {{count}} archivo",
|
||||
"Deleted {{count}} file(s)_many": "Se eliminaron {{count}} archivos",
|
||||
"Deleted {{count}} file(s)_other": "Se eliminaron {{count}} archivos",
|
||||
"Failed to delete {{count}} file(s)_one": "Error al eliminar {{count}} archivo",
|
||||
"Failed to delete {{count}} file(s)_many": "Error al eliminar {{count}} archivos",
|
||||
"Failed to delete {{count}} file(s)_other": "Error al eliminar {{count}} archivos",
|
||||
"Failed to delete files": "Error al eliminar los archivos",
|
||||
"Total Files": "Total de archivos",
|
||||
"Total Size": "Tamaño total",
|
||||
"Quota": "Cuota",
|
||||
"Used": "Usado",
|
||||
"Files": "Archivos",
|
||||
"Search files...": "Buscar archivos...",
|
||||
"Newest First": "Más nuevos primero",
|
||||
"Oldest First": "Más antiguos primero",
|
||||
"Largest First": "Más grandes primero",
|
||||
"Smallest First": "Más pequeños primero",
|
||||
"Name A-Z": "Nombre A–Z",
|
||||
"Name Z-A": "Nombre Z–A",
|
||||
"{{count}} selected_one": "{{count}} seleccionado",
|
||||
"{{count}} selected_many": "{{count}} seleccionados",
|
||||
"{{count}} selected_other": "{{count}} seleccionados",
|
||||
"Delete Selected": "Eliminar seleccionados",
|
||||
"Created": "Creado",
|
||||
"No files found": "No se encontraron archivos",
|
||||
"No files uploaded yet": "Aún no se han subido archivos",
|
||||
"files": "archivos",
|
||||
"Page {{current}} of {{total}}": "Página {{current}} de {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "¿Seguro que deseas eliminar {{count}} archivo seleccionado?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "¿Seguro que deseas eliminar {{count}} archivos seleccionados?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "¿Seguro que deseas eliminar {{count}} archivos seleccionados?",
|
||||
"Cloud Storage Usage": "Uso de almacenamiento en la nube",
|
||||
"Rename Group": "Renombrar grupo",
|
||||
"From Directory": "Desde el directorio",
|
||||
"Successfully imported {{count}} book(s)_one": "Se importó correctamente 1 libro",
|
||||
"Successfully imported {{count}} book(s)_many": "Se importaron correctamente {{count}} libros",
|
||||
"Successfully imported {{count}} book(s)_other": "Se importaron correctamente {{count}} libros",
|
||||
"Count": "Cuenta",
|
||||
"Start Page": "Página de inicio",
|
||||
"Search in OPDS Catalog...": "Buscar en el catálogo OPDS...",
|
||||
"Please log in to use advanced TTS features": "Por favor, inicie sesión para usar funciones avanzadas de TTS",
|
||||
"Word limit of 30 words exceeded.": "Se superó el límite de 30 palabras.",
|
||||
"Proofread": "Corrección",
|
||||
"Current selection": "Selección actual",
|
||||
"All occurrences in this book": "Todas las apariciones en este libro",
|
||||
"All occurrences in your library": "Todas las apariciones en tu biblioteca",
|
||||
"Selected text:": "Texto seleccionado:",
|
||||
"Replace with:": "Reemplazar por:",
|
||||
"Enter text...": "Introduce texto…",
|
||||
"Case sensitive:": "Distinguir mayúsculas y minúsculas:",
|
||||
"Scope:": "Ámbito:",
|
||||
"Selection": "Selección",
|
||||
"Library": "Biblioteca",
|
||||
"Yes": "Sí",
|
||||
"No": "No",
|
||||
"Proofread Replacement Rules": "Reglas de reemplazo de corrección",
|
||||
"Selected Text Rules": "Reglas de texto seleccionado",
|
||||
"No selected text replacement rules": "No hay reglas de reemplazo para el texto seleccionado",
|
||||
"Book Specific Rules": "Reglas específicas del libro",
|
||||
"No book-level replacement rules": "No hay reglas de reemplazo a nivel de libro",
|
||||
"Disable Quick Action": "Desactivar acción rápida",
|
||||
"Enable Quick Action on Selection": "Activar acción rápida al seleccionar",
|
||||
"None": "Ninguna",
|
||||
"Annotation Tools": "Herramientas de anotación",
|
||||
"Enable Quick Actions": "Activar acciones rápidas",
|
||||
"Quick Action": "Acción rápida",
|
||||
"Copy to Notebook": "Copiar al cuaderno",
|
||||
"Copy text after selection": "Copiar texto después de la selección",
|
||||
"Highlight text after selection": "Resaltar texto después de la selección",
|
||||
"Annotate text after selection": "Anotar texto después de la selección",
|
||||
"Search text after selection": "Buscar texto después de la selección",
|
||||
"Look up text in dictionary after selection": "Buscar texto en el diccionario después de la selección",
|
||||
"Look up text in Wikipedia after selection": "Buscar texto en Wikipedia después de la selección",
|
||||
"Translate text after selection": "Traducir texto después de la selección",
|
||||
"Read text aloud after selection": "Leer texto en voz alta después de la selección",
|
||||
"Proofread text after selection": "Corregir texto después de la selección",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} activas, {{pendingCount}} pendientes",
|
||||
"{{failedCount}} failed": "{{failedCount}} fallidas",
|
||||
"Waiting...": "Esperando...",
|
||||
"Failed": "Fallido",
|
||||
"Completed": "Completado",
|
||||
"Cancelled": "Cancelado",
|
||||
"Retry": "Reintentar",
|
||||
"Active": "Activas",
|
||||
"Transfer Queue": "Cola de transferencias",
|
||||
"Upload All": "Subir todos",
|
||||
"Download All": "Descargar todos",
|
||||
"Resume Transfers": "Reanudar transferencias",
|
||||
"Pause Transfers": "Pausar transferencias",
|
||||
"Pending": "Pendientes",
|
||||
"No transfers": "Sin transferencias",
|
||||
"Retry All": "Reintentar todos",
|
||||
"Clear Completed": "Limpiar completados",
|
||||
"Clear Failed": "Limpiar fallidos",
|
||||
"Upload queued: {{title}}": "Subida en cola: {{title}}",
|
||||
"Download queued: {{title}}": "Descarga en cola: {{title}}",
|
||||
"Book not found in library": "Libro no encontrado en la biblioteca",
|
||||
"Unknown error": "Error desconocido",
|
||||
"Please log in to continue": "Inicia sesión para continuar",
|
||||
"Cloud File Transfers": "Transferencias en la nube",
|
||||
"Show Search Results": "Mostrar resultados",
|
||||
"Search results for '{{term}}'": "Resultados para '{{term}}'",
|
||||
"Close Search": "Cerrar búsqueda",
|
||||
"Previous Result": "Resultado anterior",
|
||||
"Next Result": "Resultado siguiente",
|
||||
"Bookmarks": "Marcadores",
|
||||
"Annotations": "Anotaciones",
|
||||
"Show Results": "Mostrar resultados",
|
||||
"Clear search": "Borrar búsqueda",
|
||||
"Clear search history": "Borrar historial de búsqueda",
|
||||
"Quick action disabled": "Acción rápida desactivada",
|
||||
"Tap to Toggle Footer": "Toca para alternar pie de página",
|
||||
"Exported successfully": "Exportado con éxito",
|
||||
"Book exported successfully.": "Libro exportado con éxito.",
|
||||
"Failed to export the book.": "Error al exportar el libro.",
|
||||
"Export Book": "Exportar libro",
|
||||
"Whole word:": "Palabra completa:",
|
||||
"Error": "Error",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "No se puede cargar el artículo. Intenta buscar directamente en {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "No se puede cargar la palabra. Intenta buscar directamente en {{link}}.",
|
||||
"Date Published": "Fecha de publicación",
|
||||
"Only for TTS:": "Solo para TTS:",
|
||||
"Uploaded": "Subido",
|
||||
"Downloaded": "Descargado",
|
||||
"Deleted": "Eliminado",
|
||||
"Note:": "Nota:",
|
||||
"Time:": "Hora:",
|
||||
"Format Options": "Opciones de formato",
|
||||
"Export Date": "Fecha de exportación",
|
||||
"Chapter Titles": "Títulos de capítulos",
|
||||
"Chapter Separator": "Separador de capítulos",
|
||||
"Highlights": "Resaltados",
|
||||
"Note Date": "Fecha de nota",
|
||||
"Advanced": "Avanzado",
|
||||
"Hide": "Ocultar",
|
||||
"Show": "Mostrar",
|
||||
"Use Custom Template": "Usar plantilla personalizada",
|
||||
"Export Template": "Plantilla de exportación",
|
||||
"Template Syntax:": "Sintaxis de plantilla:",
|
||||
"Insert value": "Insertar valor",
|
||||
"Format date (locale)": "Formatear fecha (regional)",
|
||||
"Format date (custom)": "Formatear fecha (personalizado)",
|
||||
"Conditional": "Condicional",
|
||||
"Loop": "Bucle",
|
||||
"Available Variables:": "Variables disponibles:",
|
||||
"Book title": "Título del libro",
|
||||
"Book author": "Autor del libro",
|
||||
"Export date": "Fecha de exportación",
|
||||
"Array of chapters": "Lista de capítulos",
|
||||
"Chapter title": "Título del capítulo",
|
||||
"Array of annotations": "Lista de anotaciones",
|
||||
"Highlighted text": "Texto resaltado",
|
||||
"Annotation note": "Nota de anotación",
|
||||
"Update time": "Hora de actualización",
|
||||
"Date Format Tokens:": "Tokens de formato de fecha:",
|
||||
"Year (4 digits)": "Año (4 dígitos)",
|
||||
"Month (01-12)": "Mes (01-12)",
|
||||
"Day (01-31)": "Día (01-31)",
|
||||
"Hour (00-23)": "Hora (00-23)",
|
||||
"Minute (00-59)": "Minuto (00-59)",
|
||||
"Second (00-59)": "Segundo (00-59)",
|
||||
"Show Source": "Mostrar fuente",
|
||||
"No content to preview": "Sin contenido para previsualizar",
|
||||
"Export": "Exportar",
|
||||
"Set Timeout": "Establecer tiempo límite",
|
||||
"Select Voice": "Seleccionar voz",
|
||||
"Toggle Sticky Bottom TTS Bar": "Alternar barra TTS fija",
|
||||
"Display what I'm reading on Discord": "Mostrar lo que estoy leyendo en Discord",
|
||||
"Show on Discord": "Mostrar en Discord"
|
||||
}
|
||||
|
||||
@@ -172,9 +172,6 @@
|
||||
"Token": "توکن",
|
||||
"Your OTP token": "کد یکبارمصرف شما",
|
||||
"Verify token": "تأیید کد",
|
||||
"Sign in with Google": "ورود با گوگل",
|
||||
"Sign in with Apple": "ورود با اپل",
|
||||
"Sign in with GitHub": "ورود با گیتهاب",
|
||||
"Account": "حساب",
|
||||
"Failed to delete user. Please try again later.": "حذف کاربر ناموفق بود. لطفاً بعداً دوباره تلاش کنید.",
|
||||
"Community Support": "پشتیبانی جامعه",
|
||||
@@ -340,8 +337,8 @@
|
||||
"{{count}} pages left in chapter_one": "{{count}} صفحه تا انتهای فصل",
|
||||
"{{count}} pages left in chapter_other": "{{count}} صفحه تا انتهای فصل",
|
||||
"Show Remaining Pages": "نمایش صفحات باقیمانده",
|
||||
"Source Han Serif CN VF": "Source Han Serif CN VF",
|
||||
"Huiwen-mincho": "Huiwen-mincho",
|
||||
"Source Han Serif CN": "Source Han Serif CN",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa_OldSong",
|
||||
"Manage Subscription": "مدیریت اشتراک",
|
||||
"Coming Soon": "بهزودی",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "در حال بررسی...",
|
||||
"View All": "مشاهده همه",
|
||||
"Forward": "بعدی",
|
||||
"OPDS Catalog": "فهرست OPDS",
|
||||
"Home": "خانه",
|
||||
"Library": "کتابخانه",
|
||||
"{{count}} items_one": "{{count}} مورد",
|
||||
"{{count}} items_other": "{{count}} مورد",
|
||||
"Download completed": "دانلود کامل شد",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "آخرین",
|
||||
"Cannot Load Page": "بارگذاری صفحه امکانپذیر نیست",
|
||||
"An error occurred": "خطایی رخ داد",
|
||||
"Online Library": "کتابخانه آنلاین"
|
||||
"Online Library": "کتابخانه آنلاین",
|
||||
"URL must start with http:// or https://": "آدرس باید با http:// یا https:// شروع شود",
|
||||
"Title, Author, Tag, etc...": "عنوان، نویسنده، برچسب و غیره...",
|
||||
"Query": "پرسوجو",
|
||||
"Subject": "موضوع",
|
||||
"Enter {{terms}}": "وارد کردن {{terms}}",
|
||||
"No search results found": "هیچ نتیجهای یافت نشد",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "بارگذاری فید OPDS ناموفق بود: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "جستجو در {{title}}",
|
||||
"Manage Storage": "مدیریت فضای ذخیرهسازی",
|
||||
"Failed to load files": "بارگیری فایلها ناموفق بود",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} فایل حذف شد",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} فایل حذف شدند",
|
||||
"Failed to delete {{count}} file(s)_one": "حذف {{count}} فایل ناموفق بود",
|
||||
"Failed to delete {{count}} file(s)_other": "حذف {{count}} فایل ناموفق بود",
|
||||
"Failed to delete files": "حذف فایلها ناموفق بود",
|
||||
"Total Files": "کل فایلها",
|
||||
"Total Size": "اندازه کل",
|
||||
"Quota": "سهمیه",
|
||||
"Used": "استفادهشده",
|
||||
"Files": "فایلها",
|
||||
"Search files...": "جستجوی فایلها...",
|
||||
"Newest First": "جدیدترینها",
|
||||
"Oldest First": "قدیمیترینها",
|
||||
"Largest First": "بزرگترینها",
|
||||
"Smallest First": "کوچکترینها",
|
||||
"Name A-Z": "نام A–Z",
|
||||
"Name Z-A": "نام Z–A",
|
||||
"{{count}} selected_one": "{{count}} مورد انتخاب شد",
|
||||
"{{count}} selected_other": "{{count}} مورد انتخاب شدند",
|
||||
"Delete Selected": "حذف موارد انتخابشده",
|
||||
"Created": "ایجاد شده",
|
||||
"No files found": "هیچ فایلی پیدا نشد",
|
||||
"No files uploaded yet": "هنوز فایلی بارگذاری نشده است",
|
||||
"files": "فایلها",
|
||||
"Page {{current}} of {{total}}": "صفحه {{current}} از {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "آیا از حذف {{count}} فایل انتخابشده مطمئن هستید؟",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "آیا از حذف {{count}} فایل انتخابشده مطمئن هستید؟",
|
||||
"Cloud Storage Usage": "استفاده از فضای ذخیرهسازی ابری",
|
||||
"Rename Group": "تغییر نام گروه",
|
||||
"From Directory": "از مسیر",
|
||||
"Successfully imported {{count}} book(s)_one": "با موفقیت 1 کتاب وارد شد",
|
||||
"Successfully imported {{count}} book(s)_other": "با موفقیت {{count}} کتاب وارد شد",
|
||||
"Count": "تعداد",
|
||||
"Start Page": "صفحه شروع",
|
||||
"Search in OPDS Catalog...": "جستجو در فهرست OPDS...",
|
||||
"Please log in to use advanced TTS features": "لطفاً برای استفاده از ویژگیهای پیشرفته TTS وارد شوید.",
|
||||
"Word limit of 30 words exceeded.": "محدودیت ۳۰ واژهای تجاوز شد.",
|
||||
"Proofread": "بازبینی",
|
||||
"Current selection": "انتخاب فعلی",
|
||||
"All occurrences in this book": "همه موارد در این کتاب",
|
||||
"All occurrences in your library": "همه موارد در کتابخانه شما",
|
||||
"Selected text:": "متن انتخابشده:",
|
||||
"Replace with:": "جایگزین با:",
|
||||
"Enter text...": "متن را وارد کنید…",
|
||||
"Case sensitive:": "حساس به حروف بزرگ و کوچک:",
|
||||
"Scope:": "دامنه:",
|
||||
"Selection": "انتخاب",
|
||||
"Library": "کتابخانه",
|
||||
"Yes": "بله",
|
||||
"No": "خیر",
|
||||
"Proofread Replacement Rules": "قوانین جایگزینی بازبینی",
|
||||
"Selected Text Rules": "قوانین متن انتخابشده",
|
||||
"No selected text replacement rules": "هیچ قانون جایگزینی برای متن انتخابشده وجود ندارد",
|
||||
"Book Specific Rules": "قوانین مخصوص کتاب",
|
||||
"No book-level replacement rules": "هیچ قانون جایگزینی در سطح کتاب وجود ندارد",
|
||||
"Disable Quick Action": "غیرفعالسازی اقدام سریع",
|
||||
"Enable Quick Action on Selection": "فعالسازی اقدام سریع هنگام انتخاب",
|
||||
"None": "هیچکدام",
|
||||
"Annotation Tools": "ابزارهای یادداشتگذاری",
|
||||
"Enable Quick Actions": "فعالسازی اقدامات سریع",
|
||||
"Quick Action": "اقدام سریع",
|
||||
"Copy to Notebook": "کپی به دفترچه",
|
||||
"Copy text after selection": "کپی متن پس از انتخاب",
|
||||
"Highlight text after selection": "برجستهسازی متن پس از انتخاب",
|
||||
"Annotate text after selection": "یادداشتگذاری متن پس از انتخاب",
|
||||
"Search text after selection": "جستجوی متن پس از انتخاب",
|
||||
"Look up text in dictionary after selection": "جستجوی متن در فرهنگ لغت پس از انتخاب",
|
||||
"Look up text in Wikipedia after selection": "جستجوی متن در ویکیپدیا پس از انتخاب",
|
||||
"Translate text after selection": "ترجمه متن پس از انتخاب",
|
||||
"Read text aloud after selection": "خواندن متن با صدای بلند پس از انتخاب",
|
||||
"Proofread text after selection": "بازبینی متن پس از انتخاب",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} فعال، {{pendingCount}} در انتظار",
|
||||
"{{failedCount}} failed": "{{failedCount}} ناموفق",
|
||||
"Waiting...": "در انتظار...",
|
||||
"Failed": "ناموفق",
|
||||
"Completed": "تکمیل شده",
|
||||
"Cancelled": "لغو شده",
|
||||
"Retry": "تلاش مجدد",
|
||||
"Active": "فعال",
|
||||
"Transfer Queue": "صف انتقال",
|
||||
"Upload All": "بارگذاری همه",
|
||||
"Download All": "دانلود همه",
|
||||
"Resume Transfers": "ادامه انتقالها",
|
||||
"Pause Transfers": "توقف انتقالها",
|
||||
"Pending": "در انتظار",
|
||||
"No transfers": "بدون انتقال",
|
||||
"Retry All": "تلاش مجدد همه",
|
||||
"Clear Completed": "پاک کردن تکمیل شدهها",
|
||||
"Clear Failed": "پاک کردن ناموفقها",
|
||||
"Upload queued: {{title}}": "بارگذاری در صف: {{title}}",
|
||||
"Download queued: {{title}}": "دانلود در صف: {{title}}",
|
||||
"Book not found in library": "کتاب در کتابخانه یافت نشد",
|
||||
"Unknown error": "خطای ناشناخته",
|
||||
"Please log in to continue": "لطفاً برای ادامه وارد شوید",
|
||||
"Cloud File Transfers": "انتقال فایلهای ابری",
|
||||
"Show Search Results": "نمایش نتایج جستجو",
|
||||
"Search results for '{{term}}'": "نتایج برای «{{term}}»",
|
||||
"Close Search": "بستن جستجو",
|
||||
"Previous Result": "نتیجه قبلی",
|
||||
"Next Result": "نتیجه بعدی",
|
||||
"Bookmarks": "نشانکها",
|
||||
"Annotations": "یادداشتها",
|
||||
"Show Results": "نمایش نتایج",
|
||||
"Clear search": "پاک کردن جستجو",
|
||||
"Clear search history": "پاک کردن تاریخچه جستجو",
|
||||
"Quick action disabled": "عملکرد سریع غیرفعال است",
|
||||
"Tap to Toggle Footer": "برای تغییر پاورقی ضربه بزنید",
|
||||
"Exported successfully": "صادرات موفق",
|
||||
"Book exported successfully.": "کتاب با موفقیت صادر شد.",
|
||||
"Failed to export the book.": "صادر کردن کتاب ناموفق بود.",
|
||||
"Export Book": "صادر کردن کتاب",
|
||||
"Whole word:": "کلمه کامل:",
|
||||
"Error": "خطا",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "بارگذاری مقاله امکانپذیر نیست. سعی کنید مستقیماً در {{link}} جستجو کنید.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "بارگذاری کلمه امکانپذیر نیست. سعی کنید مستقیماً در {{link}} جستجو کنید.",
|
||||
"Date Published": "تاریخ انتشار",
|
||||
"Only for TTS:": "فقط برای TTS:",
|
||||
"Uploaded": "بارگذاری شد",
|
||||
"Downloaded": "دانلود شد",
|
||||
"Deleted": "حذف شد",
|
||||
"Note:": "یادداشت:",
|
||||
"Time:": "زمان:",
|
||||
"Format Options": "گزینههای قالب",
|
||||
"Export Date": "تاریخ خروجی",
|
||||
"Chapter Titles": "عناوین فصل",
|
||||
"Chapter Separator": "جداکننده فصل",
|
||||
"Highlights": "برجستهها",
|
||||
"Note Date": "تاریخ یادداشت",
|
||||
"Advanced": "پیشرفته",
|
||||
"Hide": "پنهان کردن",
|
||||
"Show": "نمایش",
|
||||
"Use Custom Template": "استفاده از الگوی سفارشی",
|
||||
"Export Template": "الگوی خروجی",
|
||||
"Template Syntax:": "نحو الگو:",
|
||||
"Insert value": "درج مقدار",
|
||||
"Format date (locale)": "قالببندی تاریخ (محلی)",
|
||||
"Format date (custom)": "قالببندی تاریخ (سفارشی)",
|
||||
"Conditional": "شرطی",
|
||||
"Loop": "حلقه",
|
||||
"Available Variables:": "متغیرهای موجود:",
|
||||
"Book title": "عنوان کتاب",
|
||||
"Book author": "نویسنده کتاب",
|
||||
"Export date": "تاریخ خروجی",
|
||||
"Array of chapters": "آرایه فصلها",
|
||||
"Chapter title": "عنوان فصل",
|
||||
"Array of annotations": "آرایه یادداشتها",
|
||||
"Highlighted text": "متن برجستهشده",
|
||||
"Annotation note": "یادداشت حاشیه",
|
||||
"Update time": "زمان بهروزرسانی",
|
||||
"Date Format Tokens:": "نشانههای قالب تاریخ:",
|
||||
"Year (4 digits)": "سال (۴ رقم)",
|
||||
"Month (01-12)": "ماه (۰۱-۱۲)",
|
||||
"Day (01-31)": "روز (۰۱-۳۱)",
|
||||
"Hour (00-23)": "ساعت (۰۰-۲۳)",
|
||||
"Minute (00-59)": "دقیقه (۰۰-۵۹)",
|
||||
"Second (00-59)": "ثانیه (۰۰-۵۹)",
|
||||
"Show Source": "نمایش منبع",
|
||||
"No content to preview": "محتوایی برای پیشنمایش وجود ندارد",
|
||||
"Export": "خروجی",
|
||||
"Set Timeout": "تنظیم مهلت زمانی",
|
||||
"Select Voice": "انتخاب صدا",
|
||||
"Toggle Sticky Bottom TTS Bar": "تغییر نوار TTS ثابت",
|
||||
"Display what I'm reading on Discord": "نمایش کتاب در حال خواندن در Discord",
|
||||
"Show on Discord": "نمایش در Discord"
|
||||
}
|
||||
|
||||
@@ -172,9 +172,6 @@
|
||||
"Token": "Code de vérification",
|
||||
"Your OTP token": "Votre code OTP",
|
||||
"Verify token": "Vérifier le code",
|
||||
"Sign in with Google": "Se connecter avec Google",
|
||||
"Sign in with Apple": "Se connecter avec Apple",
|
||||
"Sign in with GitHub": "Se connecter avec GitHub",
|
||||
"Account": "Compte",
|
||||
"Failed to delete user. Please try again later.": "Échec de la suppression de l'utilisateur. Veuillez réessayer plus tard.",
|
||||
"Community Support": "Support communautaire",
|
||||
@@ -344,8 +341,8 @@
|
||||
"{{count}} pages left in chapter_many": "{{count}} pages restantes dans le chapitre",
|
||||
"{{count}} pages left in chapter_other": "{{count}} pages restantes dans le chapitre",
|
||||
"Show Remaining Pages": "Voir restantes",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Gérer l’abonnement",
|
||||
"Coming Soon": "Bientôt disponible",
|
||||
@@ -703,9 +700,7 @@
|
||||
"Validating...": "Validation...",
|
||||
"View All": "Tout afficher",
|
||||
"Forward": "Suivant",
|
||||
"OPDS Catalog": "Catalogue OPDS",
|
||||
"Home": "Accueil",
|
||||
"Library": "Bibliothèque",
|
||||
"{{count}} items_one": "{{count}} élément",
|
||||
"{{count}} items_many": "{{count}} éléments",
|
||||
"{{count}} items_other": "{{count}} éléments",
|
||||
@@ -726,5 +721,184 @@
|
||||
"Last": "Dernier",
|
||||
"Cannot Load Page": "Impossible de charger la page",
|
||||
"An error occurred": "Une erreur est survenue",
|
||||
"Online Library": "Bibliothèque en ligne"
|
||||
"Online Library": "Bibliothèque en ligne",
|
||||
"URL must start with http:// or https://": "URL doit commencer par http:// ou https://",
|
||||
"Title, Author, Tag, etc...": "Titre, Auteur, Tag, etc...",
|
||||
"Query": "Requête",
|
||||
"Subject": "Sujet",
|
||||
"Enter {{terms}}": "Entrez {{terms}}",
|
||||
"No search results found": "Aucun résultat trouvé",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Échec du chargement du flux OPDS : {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Rechercher dans {{title}}",
|
||||
"Manage Storage": "Gérer le stockage",
|
||||
"Failed to load files": "Échec du chargement des fichiers",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} fichier supprimé",
|
||||
"Deleted {{count}} file(s)_many": "{{count}} fichiers supprimés",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} fichiers supprimés",
|
||||
"Failed to delete {{count}} file(s)_one": "Échec de la suppression de {{count}} fichier",
|
||||
"Failed to delete {{count}} file(s)_many": "Échec de la suppression de {{count}} fichiers",
|
||||
"Failed to delete {{count}} file(s)_other": "Échec de la suppression de {{count}} fichiers",
|
||||
"Failed to delete files": "Échec de la suppression des fichiers",
|
||||
"Total Files": "Nombre total de fichiers",
|
||||
"Total Size": "Taille totale",
|
||||
"Quota": "Quota",
|
||||
"Used": "Utilisé",
|
||||
"Files": "Fichiers",
|
||||
"Search files...": "Rechercher des fichiers...",
|
||||
"Newest First": "Les plus récents",
|
||||
"Oldest First": "Les plus anciens",
|
||||
"Largest First": "Les plus volumineux",
|
||||
"Smallest First": "Les moins volumineux",
|
||||
"Name A-Z": "Nom A-Z",
|
||||
"Name Z-A": "Nom Z-A",
|
||||
"{{count}} selected_one": "{{count}} sélectionné",
|
||||
"{{count}} selected_many": "{{count}} sélectionnés",
|
||||
"{{count}} selected_other": "{{count}} sélectionnés",
|
||||
"Delete Selected": "Supprimer la sélection",
|
||||
"Created": "Créé",
|
||||
"No files found": "Aucun fichier trouvé",
|
||||
"No files uploaded yet": "Aucun fichier téléchargé pour le moment",
|
||||
"files": "fichiers",
|
||||
"Page {{current}} of {{total}}": "Page {{current}} sur {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Voulez-vous vraiment supprimer {{count}} fichier sélectionné ?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Voulez-vous vraiment supprimer {{count}} fichiers sélectionnés ?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Voulez-vous vraiment supprimer {{count}} fichiers sélectionnés ?",
|
||||
"Cloud Storage Usage": "Utilisation du stockage cloud",
|
||||
"Rename Group": "Renommer le groupe",
|
||||
"From Directory": "Depuis le répertoire",
|
||||
"Successfully imported {{count}} book(s)_one": "Importation réussie de 1 livre",
|
||||
"Successfully imported {{count}} book(s)_many": "Importation réussie de {{count}} livres",
|
||||
"Successfully imported {{count}} book(s)_other": "Importation réussie de {{count}} livres",
|
||||
"Count": "Nombre",
|
||||
"Start Page": "Page de départ",
|
||||
"Search in OPDS Catalog...": "Rechercher dans le catalogue OPDS...",
|
||||
"Please log in to use advanced TTS features": "Veuillez vous connecter pour utiliser les fonctionnalités avancées de TTS",
|
||||
"Word limit of 30 words exceeded.": "La limite de 30 mots a été dépassée.",
|
||||
"Proofread": "Correction",
|
||||
"Current selection": "Sélection actuelle",
|
||||
"All occurrences in this book": "Toutes les occurrences dans ce livre",
|
||||
"All occurrences in your library": "Toutes les occurrences dans votre bibliothèque",
|
||||
"Selected text:": "Texte sélectionné :",
|
||||
"Replace with:": "Remplacer par :",
|
||||
"Enter text...": "Saisir du texte…",
|
||||
"Case sensitive:": "Respecter la casse :",
|
||||
"Scope:": "Portée :",
|
||||
"Selection": "Sélection",
|
||||
"Library": "Bibliothèque",
|
||||
"Yes": "Oui",
|
||||
"No": "Non",
|
||||
"Proofread Replacement Rules": "Règles de remplacement de correction",
|
||||
"Selected Text Rules": "Règles du texte sélectionné",
|
||||
"No selected text replacement rules": "Aucune règle de remplacement pour le texte sélectionné",
|
||||
"Book Specific Rules": "Règles spécifiques au livre",
|
||||
"No book-level replacement rules": "Aucune règle de remplacement au niveau du livre",
|
||||
"Disable Quick Action": "Désactiver l’action rapide",
|
||||
"Enable Quick Action on Selection": "Activer l’action rapide lors de la sélection",
|
||||
"None": "Aucune",
|
||||
"Annotation Tools": "Outils d’annotation",
|
||||
"Enable Quick Actions": "Activer les actions rapides",
|
||||
"Quick Action": "Action rapide",
|
||||
"Copy to Notebook": "Copier dans le carnet",
|
||||
"Copy text after selection": "Copier le texte après la sélection",
|
||||
"Highlight text after selection": "Mettre en surbrillance le texte après la sélection",
|
||||
"Annotate text after selection": "Annoter le texte après la sélection",
|
||||
"Search text after selection": "Rechercher le texte après la sélection",
|
||||
"Look up text in dictionary after selection": "Chercher le texte dans le dictionnaire après la sélection",
|
||||
"Look up text in Wikipedia after selection": "Chercher le texte dans Wikipédia après la sélection",
|
||||
"Translate text after selection": "Traduire le texte après la sélection",
|
||||
"Read text aloud after selection": "Lire le texte à haute voix après la sélection",
|
||||
"Proofread text after selection": "Relire le texte après la sélection",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} actifs, {{pendingCount}} en attente",
|
||||
"{{failedCount}} failed": "{{failedCount}} échoués",
|
||||
"Waiting...": "En attente...",
|
||||
"Failed": "Échoué",
|
||||
"Completed": "Terminé",
|
||||
"Cancelled": "Annulé",
|
||||
"Retry": "Réessayer",
|
||||
"Active": "Actifs",
|
||||
"Transfer Queue": "File de transfert",
|
||||
"Upload All": "Tout téléverser",
|
||||
"Download All": "Tout télécharger",
|
||||
"Resume Transfers": "Reprendre les transferts",
|
||||
"Pause Transfers": "Suspendre les transferts",
|
||||
"Pending": "En attente",
|
||||
"No transfers": "Aucun transfert",
|
||||
"Retry All": "Tout réessayer",
|
||||
"Clear Completed": "Effacer les terminés",
|
||||
"Clear Failed": "Effacer les échoués",
|
||||
"Upload queued: {{title}}": "Téléversement en file: {{title}}",
|
||||
"Download queued: {{title}}": "Téléchargement en file: {{title}}",
|
||||
"Book not found in library": "Livre non trouvé dans la bibliothèque",
|
||||
"Unknown error": "Erreur inconnue",
|
||||
"Please log in to continue": "Veuillez vous connecter pour continuer",
|
||||
"Cloud File Transfers": "Transferts de fichiers cloud",
|
||||
"Show Search Results": "Afficher les résultats",
|
||||
"Search results for '{{term}}'": "Résultats pour « {{term}} »",
|
||||
"Close Search": "Fermer la recherche",
|
||||
"Previous Result": "Résultat précédent",
|
||||
"Next Result": "Résultat suivant",
|
||||
"Bookmarks": "Signets",
|
||||
"Annotations": "Annotations",
|
||||
"Show Results": "Afficher les résultats",
|
||||
"Clear search": "Effacer la recherche",
|
||||
"Clear search history": "Effacer l'historique de recherche",
|
||||
"Quick action disabled": "Action rapide désactivée",
|
||||
"Tap to Toggle Footer": "Appuyez pour afficher/masquer le pied de page",
|
||||
"Exported successfully": "Exporté avec succès",
|
||||
"Book exported successfully.": "Livre exporté avec succès.",
|
||||
"Failed to export the book.": "Échec de l'exportation du livre.",
|
||||
"Export Book": "Exporter le livre",
|
||||
"Whole word:": "Mot entier :",
|
||||
"Error": "Erreur",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Impossible de charger l'article. Essayez de rechercher directement sur {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Impossible de charger le mot. Essayez de rechercher directement sur {{link}}.",
|
||||
"Date Published": "Date de publication",
|
||||
"Only for TTS:": "Uniquement pour TTS :",
|
||||
"Uploaded": "Téléversé",
|
||||
"Downloaded": "Téléchargé",
|
||||
"Deleted": "Supprimé",
|
||||
"Note:": "Note :",
|
||||
"Time:": "Heure :",
|
||||
"Format Options": "Options de format",
|
||||
"Export Date": "Date d'export",
|
||||
"Chapter Titles": "Titres de chapitres",
|
||||
"Chapter Separator": "Séparateur de chapitres",
|
||||
"Highlights": "Surlignages",
|
||||
"Note Date": "Date de note",
|
||||
"Advanced": "Avancé",
|
||||
"Hide": "Masquer",
|
||||
"Show": "Afficher",
|
||||
"Use Custom Template": "Utiliser un modèle personnalisé",
|
||||
"Export Template": "Modèle d'export",
|
||||
"Template Syntax:": "Syntaxe du modèle :",
|
||||
"Insert value": "Insérer une valeur",
|
||||
"Format date (locale)": "Formater la date (locale)",
|
||||
"Format date (custom)": "Formater la date (personnalisé)",
|
||||
"Conditional": "Conditionnel",
|
||||
"Loop": "Boucle",
|
||||
"Available Variables:": "Variables disponibles :",
|
||||
"Book title": "Titre du livre",
|
||||
"Book author": "Auteur du livre",
|
||||
"Export date": "Date d'export",
|
||||
"Array of chapters": "Liste de chapitres",
|
||||
"Chapter title": "Titre du chapitre",
|
||||
"Array of annotations": "Liste d'annotations",
|
||||
"Highlighted text": "Texte surligné",
|
||||
"Annotation note": "Note d'annotation",
|
||||
"Update time": "Heure de mise à jour",
|
||||
"Date Format Tokens:": "Jetons de format de date :",
|
||||
"Year (4 digits)": "Année (4 chiffres)",
|
||||
"Month (01-12)": "Mois (01-12)",
|
||||
"Day (01-31)": "Jour (01-31)",
|
||||
"Hour (00-23)": "Heure (00-23)",
|
||||
"Minute (00-59)": "Minute (00-59)",
|
||||
"Second (00-59)": "Seconde (00-59)",
|
||||
"Show Source": "Afficher la source",
|
||||
"No content to preview": "Aucun contenu à prévisualiser",
|
||||
"Export": "Exporter",
|
||||
"Set Timeout": "Définir le délai",
|
||||
"Select Voice": "Sélectionner la voix",
|
||||
"Toggle Sticky Bottom TTS Bar": "Basculer la barre TTS fixée",
|
||||
"Display what I'm reading on Discord": "Afficher ce que je lis sur Discord",
|
||||
"Show on Discord": "Afficher sur Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "टोकन",
|
||||
"Your OTP token": "आपका ओटीपी टोकन",
|
||||
"Verify token": "टोकन सत्यापित करें",
|
||||
"Sign in with Google": "Google के साथ साइन इन करें",
|
||||
"Sign in with Apple": "Apple के साथ साइन इन करें",
|
||||
"Sign in with GitHub": "GitHub के साथ साइन इन करें",
|
||||
"Account": "खाता",
|
||||
"Failed to delete user. Please try again later.": "उपयोगकर्ता को हटाने में विफल। कृपया बाद में पुनः प्रयास करें।",
|
||||
"Community Support": "सामुदायिक सहायता",
|
||||
@@ -341,8 +338,8 @@
|
||||
"{{count}} pages left in chapter_one": "इस अध्याय में {{count}} पृष्ठ शेष हैं",
|
||||
"{{count}} pages left in chapter_other": "इस अध्याय में {{count}} पृष्ठ शेष हैं",
|
||||
"Show Remaining Pages": "शेष पृष्ठ दिखाएँ",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "सदस्यता प्रबंधित करें",
|
||||
"Coming Soon": "जल्द आ रहा है",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "मान्य किया जा रहा है...",
|
||||
"View All": "सभी देखें",
|
||||
"Forward": "आगे",
|
||||
"OPDS Catalog": "OPDS कैटलॉग",
|
||||
"Home": "होम",
|
||||
"Library": "लाइब्रेरी",
|
||||
"{{count}} items_one": "{{count}} आइटम",
|
||||
"{{count}} items_other": "{{count}} आइटम",
|
||||
"Download completed": "डाउनलोड पूरा हुआ",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "अंतिम",
|
||||
"Cannot Load Page": "पृष्ठ लोड नहीं किया जा सका",
|
||||
"An error occurred": "एक त्रुटि हुई",
|
||||
"Online Library": "ऑनलाइन लाइब्रेरी"
|
||||
"Online Library": "ऑनलाइन लाइब्रेरी",
|
||||
"URL must start with http:// or https://": "URL http:// या https:// से शुरू होना चाहिए",
|
||||
"Title, Author, Tag, etc...": "शीर्षक, लेखक, टैग, आदि...",
|
||||
"Query": "प्रश्न",
|
||||
"Subject": "विषय",
|
||||
"Enter {{terms}}": "{{terms}} दर्ज करें",
|
||||
"No search results found": "कोई खोज परिणाम नहीं मिला",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS फ़ीड लोड करने में विफल: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} में खोजें",
|
||||
"Manage Storage": "स्टोरेज प्रबंधित करें",
|
||||
"Failed to load files": "फ़ाइलें लोड करने में विफल",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} फ़ाइल हटाई गई",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} फ़ाइलें हटाई गईं",
|
||||
"Failed to delete {{count}} file(s)_one": "{{count}} फ़ाइल हटाने में विफल",
|
||||
"Failed to delete {{count}} file(s)_other": "{{count}} फ़ाइलें हटाने में विफल",
|
||||
"Failed to delete files": "फ़ाइलें हटाने में विफल",
|
||||
"Total Files": "कुल फ़ाइलें",
|
||||
"Total Size": "कुल आकार",
|
||||
"Quota": "कोटा",
|
||||
"Used": "उपयोग किया गया",
|
||||
"Files": "फ़ाइलें",
|
||||
"Search files...": "फ़ाइलें खोजें...",
|
||||
"Newest First": "नवीनतम पहले",
|
||||
"Oldest First": "सबसे पुराने पहले",
|
||||
"Largest First": "सबसे बड़ी पहले",
|
||||
"Smallest First": "सबसे छोटी पहले",
|
||||
"Name A-Z": "नाम A-Z",
|
||||
"Name Z-A": "नाम Z-A",
|
||||
"{{count}} selected_one": "{{count}} चयनित",
|
||||
"{{count}} selected_other": "{{count}} चयनित",
|
||||
"Delete Selected": "चयनित हटाएँ",
|
||||
"Created": "बनाई गई",
|
||||
"No files found": "कोई फ़ाइल नहीं मिली",
|
||||
"No files uploaded yet": "अभी तक कोई फ़ाइल अपलोड नहीं की गई",
|
||||
"files": "फ़ाइलें",
|
||||
"Page {{current}} of {{total}}": "पृष्ठ {{current}} / {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "क्या आप वाकई {{count}} चयनित फ़ाइल हटाना चाहते हैं?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "क्या आप वाकई {{count}} चयनित फ़ाइलें हटाना चाहते हैं?",
|
||||
"Cloud Storage Usage": "क्लाउड स्टोरेज उपयोग",
|
||||
"Rename Group": "समूह का नाम बदलें",
|
||||
"From Directory": "निर्देशिका से",
|
||||
"Successfully imported {{count}} book(s)_one": "सफलतापूर्वक 1 पुस्तक आयात की गई",
|
||||
"Successfully imported {{count}} book(s)_other": "सफलतापूर्वक {{count}} पुस्तकों का आयात किया गया",
|
||||
"Count": "गणना",
|
||||
"Start Page": "प्रारंभ पृष्ठ",
|
||||
"Search in OPDS Catalog...": "OPDS कैटलॉग में खोजें...",
|
||||
"Please log in to use advanced TTS features": "उन्नत TTS सुविधाओं का उपयोग करने के लिए कृपया लॉग इन करें।",
|
||||
"Word limit of 30 words exceeded.": "30 शब्दों की सीमा पार हो गई है।",
|
||||
"Proofread": "प्रूफ़रीड",
|
||||
"Current selection": "वर्तमान चयन",
|
||||
"All occurrences in this book": "इस पुस्तक में सभी स्थान",
|
||||
"All occurrences in your library": "आपकी लाइब्रेरी में सभी स्थान",
|
||||
"Selected text:": "चयनित पाठ:",
|
||||
"Replace with:": "से बदलें:",
|
||||
"Enter text...": "पाठ दर्ज करें…",
|
||||
"Case sensitive:": "अक्षर संवेदनशील:",
|
||||
"Scope:": "क्षेत्र:",
|
||||
"Selection": "चयन",
|
||||
"Library": "लाइब्रेरी",
|
||||
"Yes": "हाँ",
|
||||
"No": "नहीं",
|
||||
"Proofread Replacement Rules": "प्रूफ़रीड प्रतिस्थापन नियम",
|
||||
"Selected Text Rules": "चयनित पाठ के नियम",
|
||||
"No selected text replacement rules": "चयनित पाठ के लिए कोई प्रतिस्थापन नियम नहीं हैं",
|
||||
"Book Specific Rules": "पुस्तक-विशिष्ट नियम",
|
||||
"No book-level replacement rules": "पुस्तक स्तर पर कोई प्रतिस्थापन नियम नहीं हैं",
|
||||
"Disable Quick Action": "त्वरित क्रिया अक्षम करें",
|
||||
"Enable Quick Action on Selection": "चयन पर त्वरित क्रिया सक्षम करें",
|
||||
"None": "कोई नहीं",
|
||||
"Annotation Tools": "टिप्पणी उपकरण",
|
||||
"Enable Quick Actions": "त्वरित क्रियाएँ सक्षम करें",
|
||||
"Quick Action": "त्वरित क्रिया",
|
||||
"Copy to Notebook": "नोटबुक में कॉपी करें",
|
||||
"Copy text after selection": "पाठ कॉपी करें चयन के बाद",
|
||||
"Highlight text after selection": "चयन के बाद पाठ हाइलाइट करें",
|
||||
"Annotate text after selection": "चयन के बाद पाठ पर टिप्पणी करें",
|
||||
"Search text after selection": "चयन के बाद पाठ खोजें",
|
||||
"Look up text in dictionary after selection": "चयन के बाद शब्दकोश में पाठ देखें",
|
||||
"Look up text in Wikipedia after selection": "चयन के बाद विकिपीडिया में पाठ देखें",
|
||||
"Translate text after selection": "चयन के बाद पाठ का अनुवाद करें",
|
||||
"Read text aloud after selection": "चयन के बाद पाठ को जोर से पढ़ें",
|
||||
"Proofread text after selection": "चयन के बाद पाठ को प्रूफरीड करें",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} सक्रिय, {{pendingCount}} लंबित",
|
||||
"{{failedCount}} failed": "{{failedCount}} विफल",
|
||||
"Waiting...": "प्रतीक्षा कर रहे हैं...",
|
||||
"Failed": "विफल",
|
||||
"Completed": "पूर्ण",
|
||||
"Cancelled": "रद्द",
|
||||
"Retry": "पुनः प्रयास करें",
|
||||
"Active": "सक्रिय",
|
||||
"Transfer Queue": "स्थानांतरण कतार",
|
||||
"Upload All": "सभी अपलोड करें",
|
||||
"Download All": "सभी डाउनलोड करें",
|
||||
"Resume Transfers": "स्थानांतरण जारी रखें",
|
||||
"Pause Transfers": "स्थानांतरण रोकें",
|
||||
"Pending": "लंबित",
|
||||
"No transfers": "कोई स्थानांतरण नहीं",
|
||||
"Retry All": "सभी पुनः प्रयास करें",
|
||||
"Clear Completed": "पूर्ण साफ़ करें",
|
||||
"Clear Failed": "विफल साफ़ करें",
|
||||
"Upload queued: {{title}}": "अपलोड कतार में: {{title}}",
|
||||
"Download queued: {{title}}": "डाउनलोड कतार में: {{title}}",
|
||||
"Book not found in library": "पुस्तकालय में पुस्तक नहीं मिली",
|
||||
"Unknown error": "अज्ञात त्रुटि",
|
||||
"Please log in to continue": "जारी रखने के लिए लॉगिन करें",
|
||||
"Cloud File Transfers": "क्लाउड फ़ाइल स्थानांतरण",
|
||||
"Show Search Results": "खोज परिणाम दिखाएं",
|
||||
"Search results for '{{term}}'": "'{{term}}' के परिणाम",
|
||||
"Close Search": "खोज बंद करें",
|
||||
"Previous Result": "पिछला परिणाम",
|
||||
"Next Result": "अगला परिणाम",
|
||||
"Bookmarks": "बुकमार्क",
|
||||
"Annotations": "टिप्पणियाँ",
|
||||
"Show Results": "परिणाम दिखाएं",
|
||||
"Clear search": "खोज साफ़ करें",
|
||||
"Clear search history": "खोज इतिहास साफ़ करें",
|
||||
"Quick action disabled": "त्वरित क्रिया अक्षम",
|
||||
"Tap to Toggle Footer": "फुटर टॉगल करने के लिए टैप करें",
|
||||
"Exported successfully": "सफलतापूर्वक निर्यात किया गया",
|
||||
"Book exported successfully.": "पुस्तक सफलतापूर्वक निर्यात की गई।",
|
||||
"Failed to export the book.": "पुस्तक निर्यात करने में विफल।",
|
||||
"Export Book": "पुस्तक निर्यात करें",
|
||||
"Whole word:": "पूरा शब्द:",
|
||||
"Error": "त्रुटि",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "लेख लोड करने में असमर्थ। सीधे {{link}} पर खोजने का प्रयास करें।",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "शब्द लोड करने में असमर्थ। सीधे {{link}} पर खोजने का प्रयास करें।",
|
||||
"Date Published": "प्रकाशन तिथि",
|
||||
"Only for TTS:": "केवल TTS के लिए:",
|
||||
"Uploaded": "अपलोड किया गया",
|
||||
"Downloaded": "डाउनलोड किया गया",
|
||||
"Deleted": "हटाया गया",
|
||||
"Note:": "नोट:",
|
||||
"Time:": "समय:",
|
||||
"Format Options": "प्रारूप विकल्प",
|
||||
"Export Date": "निर्यात तिथि",
|
||||
"Chapter Titles": "अध्याय शीर्षक",
|
||||
"Chapter Separator": "अध्याय विभाजक",
|
||||
"Highlights": "हाइलाइट्स",
|
||||
"Note Date": "नोट तिथि",
|
||||
"Advanced": "उन्नत",
|
||||
"Hide": "छिपाएं",
|
||||
"Show": "दिखाएं",
|
||||
"Use Custom Template": "कस्टम टेम्पलेट उपयोग करें",
|
||||
"Export Template": "निर्यात टेम्पलेट",
|
||||
"Template Syntax:": "टेम्पलेट सिंटैक्स:",
|
||||
"Insert value": "मान डालें",
|
||||
"Format date (locale)": "तिथि फ़ॉर्मेट करें (स्थानीय)",
|
||||
"Format date (custom)": "तिथि फ़ॉर्मेट करें (कस्टम)",
|
||||
"Conditional": "सशर्त",
|
||||
"Loop": "लूप",
|
||||
"Available Variables:": "उपलब्ध चर:",
|
||||
"Book title": "पुस्तक शीर्षक",
|
||||
"Book author": "पुस्तक लेखक",
|
||||
"Export date": "निर्यात तिथि",
|
||||
"Array of chapters": "अध्यायों की सूची",
|
||||
"Chapter title": "अध्याय शीर्षक",
|
||||
"Array of annotations": "एनोटेशन की सूची",
|
||||
"Highlighted text": "हाइलाइट किया गया पाठ",
|
||||
"Annotation note": "एनोटेशन नोट",
|
||||
"Update time": "अपडेट समय",
|
||||
"Date Format Tokens:": "तिथि प्रारूप टोकन:",
|
||||
"Year (4 digits)": "वर्ष (4 अंक)",
|
||||
"Month (01-12)": "माह (01-12)",
|
||||
"Day (01-31)": "दिन (01-31)",
|
||||
"Hour (00-23)": "घंटा (00-23)",
|
||||
"Minute (00-59)": "मिनट (00-59)",
|
||||
"Second (00-59)": "सेकंड (00-59)",
|
||||
"Show Source": "स्रोत दिखाएं",
|
||||
"No content to preview": "पूर्वावलोकन के लिए कोई सामग्री नहीं",
|
||||
"Export": "निर्यात करें",
|
||||
"Set Timeout": "टाइमआउट सेट करें",
|
||||
"Select Voice": "आवाज़ चुनें",
|
||||
"Toggle Sticky Bottom TTS Bar": "स्थिर TTS बार टॉगल करें",
|
||||
"Display what I'm reading on Discord": "Discord पर पढ़ रही किताब दिखाएं",
|
||||
"Show on Discord": "Discord पर दिखाएं"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Token",
|
||||
"Your OTP token": "Token OTP Anda",
|
||||
"Verify token": "Verifikasi token",
|
||||
"Sign in with Google": "Masuk dengan Google",
|
||||
"Sign in with Apple": "Masuk dengan Apple",
|
||||
"Sign in with GitHub": "Masuk dengan GitHub",
|
||||
"Account": "Akun",
|
||||
"Failed to delete user. Please try again later.": "Gagal menghapus pengguna. Silakan coba lagi nanti.",
|
||||
"Community Support": "Dukungan Komunitas",
|
||||
@@ -337,8 +334,8 @@
|
||||
"Reset Settings": "Atur Ulang Pengaturan",
|
||||
"{{count}} pages left in chapter_other": "{{count}} halaman tersisa di bab ini",
|
||||
"Show Remaining Pages": "Tampilkan halaman tersisa",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Kelola Langganan",
|
||||
"Coming Soon": "Segera Hadir",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "Memvalidasi...",
|
||||
"View All": "Lihat Semua",
|
||||
"Forward": "Maju",
|
||||
"OPDS Catalog": "Katalog OPDS",
|
||||
"Home": "Beranda",
|
||||
"Library": "Perpustakaan",
|
||||
"{{count}} items_other": "{{count}} item",
|
||||
"Download completed": "Unduhan selesai",
|
||||
"Download failed": "Unduhan gagal",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "Terakhir",
|
||||
"Cannot Load Page": "Tidak dapat memuat halaman",
|
||||
"An error occurred": "Terjadi kesalahan",
|
||||
"Online Library": "Perpustakaan Online"
|
||||
"Online Library": "Perpustakaan Online",
|
||||
"URL must start with http:// or https://": "URL harus diawali dengan http:// atau https://",
|
||||
"Title, Author, Tag, etc...": "Judul, Penulis, Tag, dll...",
|
||||
"Query": "Kueri",
|
||||
"Subject": "Subjek",
|
||||
"Enter {{terms}}": "Masukkan {{terms}}",
|
||||
"No search results found": "Tidak ada hasil pencarian ditemukan",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Gagal memuat feed OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Cari di {{title}}",
|
||||
"Manage Storage": "Kelola Penyimpanan",
|
||||
"Failed to load files": "Gagal memuat file",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} file dihapus",
|
||||
"Failed to delete {{count}} file(s)_other": "Gagal menghapus {{count}} file",
|
||||
"Failed to delete files": "Gagal menghapus file",
|
||||
"Total Files": "Total File",
|
||||
"Total Size": "Total Ukuran",
|
||||
"Quota": "Kuota",
|
||||
"Used": "Digunakan",
|
||||
"Files": "File",
|
||||
"Search files...": "Cari file...",
|
||||
"Newest First": "Terbaru",
|
||||
"Oldest First": "Terlama",
|
||||
"Largest First": "Terbesar",
|
||||
"Smallest First": "Terkecil",
|
||||
"Name A-Z": "Nama A-Z",
|
||||
"Name Z-A": "Nama Z-A",
|
||||
"{{count}} selected_other": "{{count}} dipilih",
|
||||
"Delete Selected": "Hapus yang Dipilih",
|
||||
"Created": "Dibuat",
|
||||
"No files found": "Tidak ada file ditemukan",
|
||||
"No files uploaded yet": "Belum ada file diunggah",
|
||||
"files": "file",
|
||||
"Page {{current}} of {{total}}": "Halaman {{current}} dari {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Yakin ingin menghapus {{count}} file yang dipilih?",
|
||||
"Cloud Storage Usage": "Penggunaan Penyimpanan Cloud",
|
||||
"Rename Group": "Ganti Nama Grup",
|
||||
"From Directory": "Dari Direktori",
|
||||
"Successfully imported {{count}} book(s)_other": "Berhasil mengimpor {{count}} buku",
|
||||
"Count": "Jumlah",
|
||||
"Start Page": "Halaman Awal",
|
||||
"Search in OPDS Catalog...": "Cari di Katalog OPDS...",
|
||||
"Please log in to use advanced TTS features": "Silakan masuk untuk menggunakan fitur TTS lanjutan",
|
||||
"Word limit of 30 words exceeded.": "Batas 30 kata telah terlampaui.",
|
||||
"Proofread": "Koreksi",
|
||||
"Current selection": "Pilihan saat ini",
|
||||
"All occurrences in this book": "Semua kemunculan di buku ini",
|
||||
"All occurrences in your library": "Semua kemunculan di perpustakaan Anda",
|
||||
"Selected text:": "Teks terpilih:",
|
||||
"Replace with:": "Ganti dengan:",
|
||||
"Enter text...": "Masukkan teks…",
|
||||
"Case sensitive:": "Peka huruf besar dan kecil:",
|
||||
"Scope:": "Cakupan:",
|
||||
"Selection": "Pilihan",
|
||||
"Library": "Perpustakaan",
|
||||
"Yes": "Ya",
|
||||
"No": "Tidak",
|
||||
"Proofread Replacement Rules": "Aturan penggantian koreksi",
|
||||
"Selected Text Rules": "Aturan teks terpilih",
|
||||
"No selected text replacement rules": "Tidak ada aturan penggantian untuk teks terpilih",
|
||||
"Book Specific Rules": "Aturan khusus buku",
|
||||
"No book-level replacement rules": "Tidak ada aturan penggantian tingkat buku",
|
||||
"Disable Quick Action": "Nonaktifkan Aksi Cepat",
|
||||
"Enable Quick Action on Selection": "Aktifkan Aksi Cepat saat memilih",
|
||||
"None": "Tidak ada",
|
||||
"Annotation Tools": "Alat Anotasi",
|
||||
"Enable Quick Actions": "Aktifkan Aksi Cepat",
|
||||
"Quick Action": "Aksi Cepat",
|
||||
"Copy to Notebook": "Salin ke Buku Catatan",
|
||||
"Copy text after selection": "Salin teks setelah pemilihan",
|
||||
"Highlight text after selection": "Sorot teks setelah pemilihan",
|
||||
"Annotate text after selection": "Anotasi teks setelah pemilihan",
|
||||
"Search text after selection": "Cari teks setelah pemilihan",
|
||||
"Look up text in dictionary after selection": "Cari teks di kamus setelah pemilihan",
|
||||
"Look up text in Wikipedia after selection": "Cari teks di Wikipedia setelah pemilihan",
|
||||
"Translate text after selection": "Terjemahkan teks setelah pemilihan",
|
||||
"Read text aloud after selection": "Bacakan teks setelah pemilihan",
|
||||
"Proofread text after selection": "Periksa teks setelah pemilihan",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} aktif, {{pendingCount}} menunggu",
|
||||
"{{failedCount}} failed": "{{failedCount}} gagal",
|
||||
"Waiting...": "Menunggu...",
|
||||
"Failed": "Gagal",
|
||||
"Completed": "Selesai",
|
||||
"Cancelled": "Dibatalkan",
|
||||
"Retry": "Coba lagi",
|
||||
"Active": "Aktif",
|
||||
"Transfer Queue": "Antrian Transfer",
|
||||
"Upload All": "Unggah Semua",
|
||||
"Download All": "Unduh Semua",
|
||||
"Resume Transfers": "Lanjutkan Transfer",
|
||||
"Pause Transfers": "Jeda Transfer",
|
||||
"Pending": "Menunggu",
|
||||
"No transfers": "Tidak ada transfer",
|
||||
"Retry All": "Coba Semua Lagi",
|
||||
"Clear Completed": "Hapus Selesai",
|
||||
"Clear Failed": "Hapus Gagal",
|
||||
"Upload queued: {{title}}": "Unggahan dalam antrian: {{title}}",
|
||||
"Download queued: {{title}}": "Unduhan dalam antrian: {{title}}",
|
||||
"Book not found in library": "Buku tidak ditemukan di perpustakaan",
|
||||
"Unknown error": "Kesalahan tidak diketahui",
|
||||
"Please log in to continue": "Silakan masuk untuk melanjutkan",
|
||||
"Cloud File Transfers": "Transfer File Cloud",
|
||||
"Show Search Results": "Tampilkan hasil pencarian",
|
||||
"Search results for '{{term}}'": "Hasil untuk '{{term}}'",
|
||||
"Close Search": "Tutup pencarian",
|
||||
"Previous Result": "Hasil sebelumnya",
|
||||
"Next Result": "Hasil berikutnya",
|
||||
"Bookmarks": "Penanda",
|
||||
"Annotations": "Anotasi",
|
||||
"Show Results": "Tampilkan Hasil",
|
||||
"Clear search": "Hapus pencarian",
|
||||
"Clear search history": "Hapus riwayat pencarian",
|
||||
"Quick action disabled": "Tindakan cepat dinonaktifkan",
|
||||
"Tap to Toggle Footer": "Ketuk untuk beralih footer",
|
||||
"Exported successfully": "Berhasil diekspor",
|
||||
"Book exported successfully.": "Buku berhasil diekspor.",
|
||||
"Failed to export the book.": "Gagal mengekspor buku.",
|
||||
"Export Book": "Ekspor Buku",
|
||||
"Whole word:": "Kata utuh:",
|
||||
"Error": "Kesalahan",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Tidak dapat memuat artikel. Coba cari langsung di {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Tidak dapat memuat kata. Coba cari langsung di {{link}}.",
|
||||
"Date Published": "Tanggal terbit",
|
||||
"Only for TTS:": "Hanya untuk TTS:",
|
||||
"Uploaded": "Diunggah",
|
||||
"Downloaded": "Diunduh",
|
||||
"Deleted": "Dihapus",
|
||||
"Note:": "Catatan:",
|
||||
"Time:": "Waktu:",
|
||||
"Format Options": "Opsi Format",
|
||||
"Export Date": "Tanggal Ekspor",
|
||||
"Chapter Titles": "Judul Bab",
|
||||
"Chapter Separator": "Pemisah Bab",
|
||||
"Highlights": "Sorotan",
|
||||
"Note Date": "Tanggal Catatan",
|
||||
"Advanced": "Lanjutan",
|
||||
"Hide": "Sembunyikan",
|
||||
"Show": "Tampilkan",
|
||||
"Use Custom Template": "Gunakan Template Kustom",
|
||||
"Export Template": "Template Ekspor",
|
||||
"Template Syntax:": "Sintaks Template:",
|
||||
"Insert value": "Masukkan nilai",
|
||||
"Format date (locale)": "Format tanggal (lokal)",
|
||||
"Format date (custom)": "Format tanggal (kustom)",
|
||||
"Conditional": "Kondisional",
|
||||
"Loop": "Loop",
|
||||
"Available Variables:": "Variabel yang Tersedia:",
|
||||
"Book title": "Judul buku",
|
||||
"Book author": "Penulis buku",
|
||||
"Export date": "Tanggal ekspor",
|
||||
"Array of chapters": "Daftar bab",
|
||||
"Chapter title": "Judul bab",
|
||||
"Array of annotations": "Daftar anotasi",
|
||||
"Highlighted text": "Teks yang disorot",
|
||||
"Annotation note": "Catatan anotasi",
|
||||
"Update time": "Waktu pembaruan",
|
||||
"Date Format Tokens:": "Token Format Tanggal:",
|
||||
"Year (4 digits)": "Tahun (4 digit)",
|
||||
"Month (01-12)": "Bulan (01-12)",
|
||||
"Day (01-31)": "Hari (01-31)",
|
||||
"Hour (00-23)": "Jam (00-23)",
|
||||
"Minute (00-59)": "Menit (00-59)",
|
||||
"Second (00-59)": "Detik (00-59)",
|
||||
"Show Source": "Tampilkan Sumber",
|
||||
"No content to preview": "Tidak ada konten untuk dipratinjau",
|
||||
"Export": "Ekspor",
|
||||
"Set Timeout": "Atur batas waktu",
|
||||
"Select Voice": "Pilih suara",
|
||||
"Toggle Sticky Bottom TTS Bar": "Alihkan bilah TTS tetap",
|
||||
"Display what I'm reading on Discord": "Tampilkan buku yang sedang dibaca di Discord",
|
||||
"Show on Discord": "Tampilkan di Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Token",
|
||||
"Your OTP token": "Il tuo token OTP",
|
||||
"Verify token": "Verifica token",
|
||||
"Sign in with Google": "Accedi con Google",
|
||||
"Sign in with Apple": "Accedi con Apple",
|
||||
"Sign in with GitHub": "Accedi con GitHub",
|
||||
"Account": "Account",
|
||||
"Failed to delete user. Please try again later.": "Impossibile eliminare l'utente. Riprova più tardi.",
|
||||
"Community Support": "Supporto della community",
|
||||
@@ -345,8 +342,8 @@
|
||||
"{{count}} pages left in chapter_many": "{{count}} pagine rimaste nel capitolo",
|
||||
"{{count}} pages left in chapter_other": "{{count}} pagine rimaste nel capitolo",
|
||||
"Show Remaining Pages": "Mostra pagine rimanenti",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Gestisci Abbonamento",
|
||||
"Coming Soon": "Prossimamente",
|
||||
@@ -703,9 +700,7 @@
|
||||
"Validating...": "Convalida in corso...",
|
||||
"View All": "Vedi tutto",
|
||||
"Forward": "Avanti",
|
||||
"OPDS Catalog": "Catalogo OPDS",
|
||||
"Home": "Home",
|
||||
"Library": "Libreria",
|
||||
"{{count}} items_one": "{{count}} elemento",
|
||||
"{{count}} items_many": "{{count}} elementi",
|
||||
"{{count}} items_other": "{{count}} elementi",
|
||||
@@ -726,5 +721,184 @@
|
||||
"Last": "Ultimo",
|
||||
"Cannot Load Page": "Impossibile caricare la pagina",
|
||||
"An error occurred": "Si è verificato un errore",
|
||||
"Online Library": "Libreria online"
|
||||
"Online Library": "Libreria online",
|
||||
"URL must start with http:// or https://": "URL deve iniziare con http:// o https://",
|
||||
"Title, Author, Tag, etc...": "Titolo, Autore, Tag, ecc...",
|
||||
"Query": "Query",
|
||||
"Subject": "Soggetto",
|
||||
"Enter {{terms}}": "Inserisci {{terms}}",
|
||||
"No search results found": "Nessun risultato di ricerca trovato",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Impossibile caricare il feed OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Cerca in {{title}}",
|
||||
"Manage Storage": "Gestisci archiviazione",
|
||||
"Failed to load files": "Impossibile caricare i file",
|
||||
"Deleted {{count}} file(s)_one": "È stato eliminato {{count}} file",
|
||||
"Deleted {{count}} file(s)_many": "Sono stati eliminati {{count}} file",
|
||||
"Deleted {{count}} file(s)_other": "Sono stati eliminati {{count}} file",
|
||||
"Failed to delete {{count}} file(s)_one": "Impossibile eliminare {{count}} file",
|
||||
"Failed to delete {{count}} file(s)_many": "Impossibile eliminare {{count}} file",
|
||||
"Failed to delete {{count}} file(s)_other": "Impossibile eliminare {{count}} file",
|
||||
"Failed to delete files": "Impossibile eliminare i file",
|
||||
"Total Files": "File totali",
|
||||
"Total Size": "Dimensione totale",
|
||||
"Quota": "Quota",
|
||||
"Used": "Utilizzato",
|
||||
"Files": "File",
|
||||
"Search files...": "Cerca file...",
|
||||
"Newest First": "Più recenti",
|
||||
"Oldest First": "Più vecchi",
|
||||
"Largest First": "Più grandi",
|
||||
"Smallest First": "Più piccoli",
|
||||
"Name A-Z": "Nome A-Z",
|
||||
"Name Z-A": "Nome Z-A",
|
||||
"{{count}} selected_one": "{{count}} selezionato",
|
||||
"{{count}} selected_many": "{{count}} selezionati",
|
||||
"{{count}} selected_other": "{{count}} selezionati",
|
||||
"Delete Selected": "Elimina selezionati",
|
||||
"Created": "Creato",
|
||||
"No files found": "Nessun file trovato",
|
||||
"No files uploaded yet": "Nessun file caricato",
|
||||
"files": "file",
|
||||
"Page {{current}} of {{total}}": "Pagina {{current}} di {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Sei sicuro di voler eliminare {{count}} file selezionato?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Sei sicuro di voler eliminare {{count}} file selezionati?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Sei sicuro di voler eliminare {{count}} file selezionati?",
|
||||
"Cloud Storage Usage": "Utilizzo archiviazione cloud",
|
||||
"Rename Group": "Rinomina gruppo",
|
||||
"From Directory": "Da directory",
|
||||
"Successfully imported {{count}} book(s)_one": "Importato con successo 1 libro",
|
||||
"Successfully imported {{count}} book(s)_many": "Importati con successo {{count}} libri",
|
||||
"Successfully imported {{count}} book(s)_other": "Importati con successo {{count}} libri",
|
||||
"Count": "Conteggio",
|
||||
"Start Page": "Pagina iniziale",
|
||||
"Search in OPDS Catalog...": "Cerca nel catalogo OPDS...",
|
||||
"Please log in to use advanced TTS features": "Effettua il login per utilizzare le funzionalità TTS avanzate",
|
||||
"Word limit of 30 words exceeded.": "È stato superato il limite di 30 parole.",
|
||||
"Proofread": "Revisione",
|
||||
"Current selection": "Selezione corrente",
|
||||
"All occurrences in this book": "Tutte le occorrenze in questo libro",
|
||||
"All occurrences in your library": "Tutte le occorrenze nella tua libreria",
|
||||
"Selected text:": "Testo selezionato:",
|
||||
"Replace with:": "Sostituisci con:",
|
||||
"Enter text...": "Inserisci testo…",
|
||||
"Case sensitive:": "Maiuscole/minuscole:",
|
||||
"Scope:": "Ambito:",
|
||||
"Selection": "Selezione",
|
||||
"Library": "Libreria",
|
||||
"Yes": "Sì",
|
||||
"No": "No",
|
||||
"Proofread Replacement Rules": "Regole di sostituzione per la revisione",
|
||||
"Selected Text Rules": "Regole per il testo selezionato",
|
||||
"No selected text replacement rules": "Nessuna regola di sostituzione per il testo selezionato",
|
||||
"Book Specific Rules": "Regole specifiche del libro",
|
||||
"No book-level replacement rules": "Nessuna regola di sostituzione a livello di libro",
|
||||
"Disable Quick Action": "Disattiva azione rapida",
|
||||
"Enable Quick Action on Selection": "Attiva azione rapida sulla selezione",
|
||||
"None": "Nessuno",
|
||||
"Annotation Tools": "Strumenti di annotazione",
|
||||
"Enable Quick Actions": "Attiva azioni rapide",
|
||||
"Quick Action": "Azione rapida",
|
||||
"Copy to Notebook": "Copia nel taccuino",
|
||||
"Copy text after selection": "Copia testo dopo la selezione",
|
||||
"Highlight text after selection": "Evidenzia testo dopo la selezione",
|
||||
"Annotate text after selection": "Annota testo dopo la selezione",
|
||||
"Search text after selection": "Cerca testo dopo la selezione",
|
||||
"Look up text in dictionary after selection": "Cerca testo nel dizionario dopo la selezione",
|
||||
"Look up text in Wikipedia after selection": "Cerca testo in Wikipedia dopo la selezione",
|
||||
"Translate text after selection": "Traduci testo dopo la selezione",
|
||||
"Read text aloud after selection": "Leggi ad alta voce il testo dopo la selezione",
|
||||
"Proofread text after selection": "Correggi il testo dopo la selezione",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} attivi, {{pendingCount}} in attesa",
|
||||
"{{failedCount}} failed": "{{failedCount}} falliti",
|
||||
"Waiting...": "In attesa...",
|
||||
"Failed": "Fallito",
|
||||
"Completed": "Completato",
|
||||
"Cancelled": "Annullato",
|
||||
"Retry": "Riprova",
|
||||
"Active": "Attivi",
|
||||
"Transfer Queue": "Coda trasferimenti",
|
||||
"Upload All": "Carica tutti",
|
||||
"Download All": "Scarica tutti",
|
||||
"Resume Transfers": "Riprendi trasferimenti",
|
||||
"Pause Transfers": "Sospendi trasferimenti",
|
||||
"Pending": "In attesa",
|
||||
"No transfers": "Nessun trasferimento",
|
||||
"Retry All": "Riprova tutti",
|
||||
"Clear Completed": "Cancella completati",
|
||||
"Clear Failed": "Cancella falliti",
|
||||
"Upload queued: {{title}}": "Caricamento in coda: {{title}}",
|
||||
"Download queued: {{title}}": "Download in coda: {{title}}",
|
||||
"Book not found in library": "Libro non trovato nella libreria",
|
||||
"Unknown error": "Errore sconosciuto",
|
||||
"Please log in to continue": "Accedi per continuare",
|
||||
"Cloud File Transfers": "Trasferimenti file cloud",
|
||||
"Show Search Results": "Mostra risultati",
|
||||
"Search results for '{{term}}'": "Risultati per '{{term}}'",
|
||||
"Close Search": "Chiudi ricerca",
|
||||
"Previous Result": "Risultato precedente",
|
||||
"Next Result": "Risultato successivo",
|
||||
"Bookmarks": "Segnalibri",
|
||||
"Annotations": "Annotazioni",
|
||||
"Show Results": "Mostra risultati",
|
||||
"Clear search": "Cancella ricerca",
|
||||
"Clear search history": "Cancella cronologia ricerche",
|
||||
"Quick action disabled": "Azione rapida disattivata",
|
||||
"Tap to Toggle Footer": "Tocca per mostrare/nascondere il piè di pagina",
|
||||
"Exported successfully": "Esportato con successo",
|
||||
"Book exported successfully.": "Libro esportato con successo.",
|
||||
"Failed to export the book.": "Impossibile esportare il libro.",
|
||||
"Export Book": "Esporta libro",
|
||||
"Whole word:": "Parola intera:",
|
||||
"Error": "Errore",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Impossibile caricare l'articolo. Prova a cercare direttamente su {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Impossibile caricare la parola. Prova a cercare direttamente su {{link}}.",
|
||||
"Date Published": "Data di pubblicazione",
|
||||
"Only for TTS:": "Solo per TTS:",
|
||||
"Uploaded": "Caricato",
|
||||
"Downloaded": "Scaricato",
|
||||
"Deleted": "Eliminato",
|
||||
"Note:": "Nota:",
|
||||
"Time:": "Ora:",
|
||||
"Format Options": "Opzioni formato",
|
||||
"Export Date": "Data esportazione",
|
||||
"Chapter Titles": "Titoli capitoli",
|
||||
"Chapter Separator": "Separatore capitoli",
|
||||
"Highlights": "Evidenziazioni",
|
||||
"Note Date": "Data nota",
|
||||
"Advanced": "Avanzate",
|
||||
"Hide": "Nascondi",
|
||||
"Show": "Mostra",
|
||||
"Use Custom Template": "Usa modello personalizzato",
|
||||
"Export Template": "Modello esportazione",
|
||||
"Template Syntax:": "Sintassi modello:",
|
||||
"Insert value": "Inserisci valore",
|
||||
"Format date (locale)": "Formatta data (locale)",
|
||||
"Format date (custom)": "Formatta data (personalizzato)",
|
||||
"Conditional": "Condizionale",
|
||||
"Loop": "Ciclo",
|
||||
"Available Variables:": "Variabili disponibili:",
|
||||
"Book title": "Titolo libro",
|
||||
"Book author": "Autore libro",
|
||||
"Export date": "Data esportazione",
|
||||
"Array of chapters": "Elenco capitoli",
|
||||
"Chapter title": "Titolo capitolo",
|
||||
"Array of annotations": "Elenco annotazioni",
|
||||
"Highlighted text": "Testo evidenziato",
|
||||
"Annotation note": "Nota annotazione",
|
||||
"Update time": "Ora aggiornamento",
|
||||
"Date Format Tokens:": "Token formato data:",
|
||||
"Year (4 digits)": "Anno (4 cifre)",
|
||||
"Month (01-12)": "Mese (01-12)",
|
||||
"Day (01-31)": "Giorno (01-31)",
|
||||
"Hour (00-23)": "Ora (00-23)",
|
||||
"Minute (00-59)": "Minuto (00-59)",
|
||||
"Second (00-59)": "Secondo (00-59)",
|
||||
"Show Source": "Mostra sorgente",
|
||||
"No content to preview": "Nessun contenuto da visualizzare",
|
||||
"Export": "Esporta",
|
||||
"Set Timeout": "Imposta timeout",
|
||||
"Select Voice": "Seleziona voce",
|
||||
"Toggle Sticky Bottom TTS Bar": "Attiva/disattiva barra TTS fissa",
|
||||
"Display what I'm reading on Discord": "Mostra cosa sto leggendo su Discord",
|
||||
"Show on Discord": "Mostra su Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "トークン",
|
||||
"Your OTP token": "あなたのOTPトークン",
|
||||
"Verify token": "トークンを確認",
|
||||
"Sign in with Google": "Googleでサインイン",
|
||||
"Sign in with Apple": "Appleでサインイン",
|
||||
"Sign in with GitHub": "GitHubでサインイン",
|
||||
"Account": "アカウント",
|
||||
"Failed to delete user. Please try again later.": "ユーザーの削除に失敗しました。後ほど再試行してください。",
|
||||
"Community Support": "コミュニティサポート",
|
||||
@@ -337,8 +334,8 @@
|
||||
"Reset Settings": "設定をリセット",
|
||||
"{{count}} pages left in chapter_other": "{{count}} ページ残り",
|
||||
"Show Remaining Pages": "残りを表示",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "サブスクリプションを管理",
|
||||
"Coming Soon": "近日公開",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "検証中...",
|
||||
"View All": "すべて表示",
|
||||
"Forward": "進む",
|
||||
"OPDS Catalog": "OPDSカタログ",
|
||||
"Home": "ホーム",
|
||||
"Library": "ライブラリ",
|
||||
"{{count}} items_other": "{{count}} 件",
|
||||
"Download completed": "ダウンロード完了",
|
||||
"Download failed": "ダウンロード失敗",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "最後",
|
||||
"Cannot Load Page": "ページを読み込めません",
|
||||
"An error occurred": "エラーが発生しました",
|
||||
"Online Library": "オンラインライブラリ"
|
||||
"Online Library": "オンラインライブラリ",
|
||||
"URL must start with http:// or https://": "URLはhttp://またはhttps://で始まる必要があります",
|
||||
"Title, Author, Tag, etc...": "タイトル、著者、タグなど...",
|
||||
"Query": "クエリ",
|
||||
"Subject": "件名",
|
||||
"Enter {{terms}}": "{{terms}}を入力してください",
|
||||
"No search results found": "検索結果が見つかりません",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDSフィードの読み込みに失敗しました: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}}内を検索",
|
||||
"Manage Storage": "ストレージ管理",
|
||||
"Failed to load files": "ファイルの読み込みに失敗しました",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} 件のファイルを削除しました",
|
||||
"Failed to delete {{count}} file(s)_other": "{{count}} 件のファイルの削除に失敗しました",
|
||||
"Failed to delete files": "ファイルの削除に失敗しました",
|
||||
"Total Files": "ファイル合計",
|
||||
"Total Size": "合計サイズ",
|
||||
"Quota": "容量制限",
|
||||
"Used": "使用済み",
|
||||
"Files": "ファイル",
|
||||
"Search files...": "ファイルを検索...",
|
||||
"Newest First": "新しい順",
|
||||
"Oldest First": "古い順",
|
||||
"Largest First": "大きい順",
|
||||
"Smallest First": "小さい順",
|
||||
"Name A-Z": "名前 A-Z",
|
||||
"Name Z-A": "名前 Z-A",
|
||||
"{{count}} selected_other": "{{count}} 件選択済み",
|
||||
"Delete Selected": "選択した項目を削除",
|
||||
"Created": "作成日",
|
||||
"No files found": "ファイルが見つかりません",
|
||||
"No files uploaded yet": "まだファイルがアップロードされていません",
|
||||
"files": "ファイル",
|
||||
"Page {{current}} of {{total}}": "ページ {{current}} / {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "選択した {{count}} 件のファイルを削除してもよろしいですか?",
|
||||
"Cloud Storage Usage": "クラウドストレージ使用量",
|
||||
"Rename Group": "グループ名を変更",
|
||||
"From Directory": "ディレクトリから",
|
||||
"Successfully imported {{count}} book(s)_other": "成功裏に{{count}}冊の本をインポートしました",
|
||||
"Count": "件数",
|
||||
"Start Page": "開始ページ",
|
||||
"Search in OPDS Catalog...": "OPDSカタログ内を検索...",
|
||||
"Please log in to use advanced TTS features": "高度なTTS機能を使用するにはログインしてください",
|
||||
"Word limit of 30 words exceeded.": "30語の上限を超えています。",
|
||||
"Proofread": "校正",
|
||||
"Current selection": "現在の選択",
|
||||
"All occurrences in this book": "この本内のすべての出現箇所",
|
||||
"All occurrences in your library": "ライブラリ内のすべての出現箇所",
|
||||
"Selected text:": "選択されたテキスト:",
|
||||
"Replace with:": "置換後:",
|
||||
"Enter text...": "テキストを入力…",
|
||||
"Case sensitive:": "大文字と小文字を区別:",
|
||||
"Scope:": "適用範囲:",
|
||||
"Selection": "選択",
|
||||
"Library": "ライブラリ",
|
||||
"Yes": "はい",
|
||||
"No": "いいえ",
|
||||
"Proofread Replacement Rules": "校正置換ルール",
|
||||
"Selected Text Rules": "選択テキストのルール",
|
||||
"No selected text replacement rules": "選択されたテキストの置換ルールはありません",
|
||||
"Book Specific Rules": "書籍別ルール",
|
||||
"No book-level replacement rules": "書籍レベルの置換ルールはありません",
|
||||
"Disable Quick Action": "クイックアクションを無効にする",
|
||||
"Enable Quick Action on Selection": "選択時にクイックアクションを有効にする",
|
||||
"None": "なし",
|
||||
"Annotation Tools": "注釈ツール",
|
||||
"Enable Quick Actions": "クイックアクションを有効にする",
|
||||
"Quick Action": "クイックアクション",
|
||||
"Copy to Notebook": "ノートにコピー",
|
||||
"Copy text after selection": "選択後のテキストをコピー",
|
||||
"Highlight text after selection": "選択後のテキストをハイライト",
|
||||
"Annotate text after selection": "選択後のテキストに注釈を付ける",
|
||||
"Search text after selection": "選択後のテキストを検索",
|
||||
"Look up text in dictionary after selection": "選択後のテキストを辞書で調べる",
|
||||
"Look up text in Wikipedia after selection": "選択後のテキストをWikipediaで調べる",
|
||||
"Translate text after selection": "選択後のテキストを翻訳する",
|
||||
"Read text aloud after selection": "選択後のテキストを音読する",
|
||||
"Proofread text after selection": "選択後のテキストを校正する",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} 進行中、{{pendingCount}} 待機中",
|
||||
"{{failedCount}} failed": "{{failedCount}} 失敗",
|
||||
"Waiting...": "待機中...",
|
||||
"Failed": "失敗",
|
||||
"Completed": "完了",
|
||||
"Cancelled": "キャンセル",
|
||||
"Retry": "再試行",
|
||||
"Active": "進行中",
|
||||
"Transfer Queue": "転送キュー",
|
||||
"Upload All": "すべてアップロード",
|
||||
"Download All": "すべてダウンロード",
|
||||
"Resume Transfers": "転送を再開",
|
||||
"Pause Transfers": "転送を一時停止",
|
||||
"Pending": "待機中",
|
||||
"No transfers": "転送なし",
|
||||
"Retry All": "すべて再試行",
|
||||
"Clear Completed": "完了を消去",
|
||||
"Clear Failed": "失敗を消去",
|
||||
"Upload queued: {{title}}": "アップロードをキューに追加: {{title}}",
|
||||
"Download queued: {{title}}": "ダウンロードをキューに追加: {{title}}",
|
||||
"Book not found in library": "ライブラリに本が見つかりません",
|
||||
"Unknown error": "不明なエラー",
|
||||
"Please log in to continue": "続行するにはログインしてください",
|
||||
"Cloud File Transfers": "クラウドファイル転送",
|
||||
"Show Search Results": "検索結果を表示",
|
||||
"Search results for '{{term}}'": "「{{term}}」を含む結果",
|
||||
"Close Search": "検索を閉じる",
|
||||
"Previous Result": "前の結果",
|
||||
"Next Result": "次の結果",
|
||||
"Bookmarks": "ブックマーク",
|
||||
"Annotations": "注釈",
|
||||
"Show Results": "結果を表示",
|
||||
"Clear search": "検索をクリア",
|
||||
"Clear search history": "検索履歴をクリア",
|
||||
"Quick action disabled": "クイックアクション無効",
|
||||
"Tap to Toggle Footer": "タップでフッターを切り替え",
|
||||
"Exported successfully": "エクスポート成功",
|
||||
"Book exported successfully.": "書籍のエクスポートに成功しました。",
|
||||
"Failed to export the book.": "書籍のエクスポートに失敗しました。",
|
||||
"Export Book": "書籍をエクスポート",
|
||||
"Whole word:": "単語全体:",
|
||||
"Error": "エラー",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "記事を読み込めません。{{link}}で直接検索してみてください。",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "単語を読み込めません。{{link}}で直接検索してみてください。",
|
||||
"Date Published": "出版日",
|
||||
"Only for TTS:": "TTSのみ:",
|
||||
"Uploaded": "アップロード済み",
|
||||
"Downloaded": "ダウンロード済み",
|
||||
"Deleted": "削除済み",
|
||||
"Note:": "ノート:",
|
||||
"Time:": "時刻:",
|
||||
"Format Options": "形式オプション",
|
||||
"Export Date": "エクスポート日",
|
||||
"Chapter Titles": "章タイトル",
|
||||
"Chapter Separator": "章区切り",
|
||||
"Highlights": "ハイライト",
|
||||
"Note Date": "ノート日付",
|
||||
"Advanced": "詳細設定",
|
||||
"Hide": "非表示",
|
||||
"Show": "表示",
|
||||
"Use Custom Template": "カスタムテンプレート使用",
|
||||
"Export Template": "エクスポートテンプレート",
|
||||
"Template Syntax:": "テンプレート構文:",
|
||||
"Insert value": "値を挿入",
|
||||
"Format date (locale)": "日付形式(ロケール)",
|
||||
"Format date (custom)": "日付形式(カスタム)",
|
||||
"Conditional": "条件分岐",
|
||||
"Loop": "ループ",
|
||||
"Available Variables:": "利用可能な変数:",
|
||||
"Book title": "本のタイトル",
|
||||
"Book author": "著者",
|
||||
"Export date": "エクスポート日",
|
||||
"Array of chapters": "章の配列",
|
||||
"Chapter title": "章タイトル",
|
||||
"Array of annotations": "注釈の配列",
|
||||
"Highlighted text": "ハイライトテキスト",
|
||||
"Annotation note": "注釈ノート",
|
||||
"Update time": "更新時刻",
|
||||
"Date Format Tokens:": "日付形式トークン:",
|
||||
"Year (4 digits)": "年(4桁)",
|
||||
"Month (01-12)": "月(01-12)",
|
||||
"Day (01-31)": "日(01-31)",
|
||||
"Hour (00-23)": "時(00-23)",
|
||||
"Minute (00-59)": "分(00-59)",
|
||||
"Second (00-59)": "秒(00-59)",
|
||||
"Show Source": "ソース表示",
|
||||
"No content to preview": "プレビューするコンテンツがありません",
|
||||
"Export": "エクスポート",
|
||||
"Set Timeout": "タイムアウト設定",
|
||||
"Select Voice": "音声選択",
|
||||
"Toggle Sticky Bottom TTS Bar": "TTSバー固定切替",
|
||||
"Display what I'm reading on Discord": "読書中の本をDiscordに表示",
|
||||
"Show on Discord": "Discordに表示"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "토큰",
|
||||
"Your OTP token": "당신의 OTP 토큰",
|
||||
"Verify token": "토큰 확인",
|
||||
"Sign in with Google": "Google로 로그인",
|
||||
"Sign in with Apple": "Apple로 로그인",
|
||||
"Sign in with GitHub": "GitHub로 로그인",
|
||||
"Account": "계정",
|
||||
"Failed to delete user. Please try again later.": "사용자 삭제에 실패했습니다. 나중에 다시 시도해 주세요.",
|
||||
"Community Support": "커뮤니티 지원",
|
||||
@@ -337,8 +334,8 @@
|
||||
"Reset Settings": "설정 재설정",
|
||||
"{{count}} pages left in chapter_other": "남은 페이지 {{count}}장",
|
||||
"Show Remaining Pages": "남은 페이지 보기",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "구독 관리",
|
||||
"Coming Soon": "곧 출시 예정",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "검증 중...",
|
||||
"View All": "모두 보기",
|
||||
"Forward": "다음",
|
||||
"OPDS Catalog": "OPDS 카탈로그",
|
||||
"Home": "홈",
|
||||
"Library": "라이브러리",
|
||||
"{{count}} items_other": "{{count}}개 항목",
|
||||
"Download completed": "다운로드 완료",
|
||||
"Download failed": "다운로드 실패",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "마지막",
|
||||
"Cannot Load Page": "페이지를 불러올 수 없습니다",
|
||||
"An error occurred": "오류가 발생했습니다",
|
||||
"Online Library": "온라인 라이브러리"
|
||||
"Online Library": "온라인 라이브러리",
|
||||
"URL must start with http:// or https://": "URL은 http:// 또는 https://로 시작해야 합니다",
|
||||
"Title, Author, Tag, etc...": "제목, 저자, 태그 등...",
|
||||
"Query": "쿼리",
|
||||
"Subject": "주제",
|
||||
"Enter {{terms}}": "{{terms}} 입력",
|
||||
"No search results found": "검색 결과가 없습니다",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS 피드를 불러오지 못했습니다: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}}에서 검색",
|
||||
"Manage Storage": "저장소 관리",
|
||||
"Failed to load files": "파일 로드 실패",
|
||||
"Deleted {{count}} file(s)_other": "{{count}}개의 파일이 삭제되었습니다",
|
||||
"Failed to delete {{count}} file(s)_other": "{{count}}개의 파일 삭제 실패",
|
||||
"Failed to delete files": "파일 삭제 실패",
|
||||
"Total Files": "총 파일",
|
||||
"Total Size": "총 용량",
|
||||
"Quota": "쿼터",
|
||||
"Used": "사용됨",
|
||||
"Files": "파일",
|
||||
"Search files...": "파일 검색...",
|
||||
"Newest First": "최신순",
|
||||
"Oldest First": "오래된순",
|
||||
"Largest First": "큰 파일순",
|
||||
"Smallest First": "작은 파일순",
|
||||
"Name A-Z": "이름 A-Z",
|
||||
"Name Z-A": "이름 Z-A",
|
||||
"{{count}} selected_other": "{{count}}개 선택됨",
|
||||
"Delete Selected": "선택 삭제",
|
||||
"Created": "생성됨",
|
||||
"No files found": "파일이 없습니다",
|
||||
"No files uploaded yet": "아직 업로드된 파일이 없습니다",
|
||||
"files": "파일",
|
||||
"Page {{current}} of {{total}}": "페이지 {{current}} / {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "선택한 {{count}}개의 파일을 삭제하시겠습니까?",
|
||||
"Cloud Storage Usage": "클라우드 저장소 사용량",
|
||||
"Rename Group": "그룹 이름 바꾸기",
|
||||
"From Directory": "디렉토리에서",
|
||||
"Successfully imported {{count}} book(s)_other": "성공적으로 {{count}} 권의 책이 가져와졌습니다",
|
||||
"Count": "개수",
|
||||
"Start Page": "시작 페이지",
|
||||
"Search in OPDS Catalog...": "OPDS 카탈로그에서 검색...",
|
||||
"Please log in to use advanced TTS features": "고급 TTS 기능을 사용하려면 로그인하세요",
|
||||
"Word limit of 30 words exceeded.": "30단어 제한을 초과했습니다.",
|
||||
"Proofread": "교정",
|
||||
"Current selection": "현재 선택",
|
||||
"All occurrences in this book": "이 책의 모든 위치",
|
||||
"All occurrences in your library": "라이브러리의 모든 위치",
|
||||
"Selected text:": "선택한 텍스트:",
|
||||
"Replace with:": "다음으로 바꾸기:",
|
||||
"Enter text...": "텍스트 입력…",
|
||||
"Case sensitive:": "대소문자 구분:",
|
||||
"Scope:": "적용 범위:",
|
||||
"Selection": "선택",
|
||||
"Library": "라이브러리",
|
||||
"Yes": "예",
|
||||
"No": "아니요",
|
||||
"Proofread Replacement Rules": "교정 대체 규칙",
|
||||
"Selected Text Rules": "선택한 텍스트 규칙",
|
||||
"No selected text replacement rules": "선택한 텍스트에 대한 대체 규칙이 없습니다",
|
||||
"Book Specific Rules": "책별 규칙",
|
||||
"No book-level replacement rules": "책 수준의 대체 규칙이 없습니다",
|
||||
"Disable Quick Action": "빠른 작업 비활성화",
|
||||
"Enable Quick Action on Selection": "선택 시 빠른 작업 활성화",
|
||||
"None": "없음",
|
||||
"Annotation Tools": "주석 도구",
|
||||
"Enable Quick Actions": "빠른 작업 활성화",
|
||||
"Quick Action": "빠른 작업",
|
||||
"Copy to Notebook": "노트에 복사",
|
||||
"Copy text after selection": "선택 후 텍스트 복사",
|
||||
"Highlight text after selection": "선택 후 텍스트 강조",
|
||||
"Annotate text after selection": "선택 후 텍스트 주석 달기",
|
||||
"Search text after selection": "선택 후 텍스트 검색",
|
||||
"Look up text in dictionary after selection": "선택 후 텍스트를 사전에서 찾기",
|
||||
"Look up text in Wikipedia after selection": "선택 후 텍스트를 위키피디아에서 찾기",
|
||||
"Translate text after selection": "선택 후 텍스트 번역",
|
||||
"Read text aloud after selection": "선택 후 텍스트 음성 읽기",
|
||||
"Proofread text after selection": "선택 후 텍스트 교정",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} 진행 중, {{pendingCount}} 대기 중",
|
||||
"{{failedCount}} failed": "{{failedCount}} 실패",
|
||||
"Waiting...": "대기 중...",
|
||||
"Failed": "실패",
|
||||
"Completed": "완료됨",
|
||||
"Cancelled": "취소됨",
|
||||
"Retry": "재시도",
|
||||
"Active": "진행 중",
|
||||
"Transfer Queue": "전송 대기열",
|
||||
"Upload All": "모두 업로드",
|
||||
"Download All": "모두 다운로드",
|
||||
"Resume Transfers": "전송 재개",
|
||||
"Pause Transfers": "전송 일시정지",
|
||||
"Pending": "대기 중",
|
||||
"No transfers": "전송 없음",
|
||||
"Retry All": "모두 재시도",
|
||||
"Clear Completed": "완료된 항목 지우기",
|
||||
"Clear Failed": "실패한 항목 지우기",
|
||||
"Upload queued: {{title}}": "업로드 대기열에 추가됨: {{title}}",
|
||||
"Download queued: {{title}}": "다운로드 대기열에 추가됨: {{title}}",
|
||||
"Book not found in library": "라이브러리에서 책을 찾을 수 없음",
|
||||
"Unknown error": "알 수 없는 오류",
|
||||
"Please log in to continue": "계속하려면 로그인하세요",
|
||||
"Cloud File Transfers": "클라우드 파일 전송",
|
||||
"Show Search Results": "검색 결과 보기",
|
||||
"Search results for '{{term}}'": "'{{term}}' 검색 결과",
|
||||
"Close Search": "검색 닫기",
|
||||
"Previous Result": "이전 결과",
|
||||
"Next Result": "다음 결과",
|
||||
"Bookmarks": "북마크",
|
||||
"Annotations": "주석",
|
||||
"Show Results": "결과 표시",
|
||||
"Clear search": "검색 지우기",
|
||||
"Clear search history": "검색 기록 지우기",
|
||||
"Quick action disabled": "빠른 작업 비활성화됨",
|
||||
"Tap to Toggle Footer": "탭하여 바닥글 전환",
|
||||
"Exported successfully": "내보내기 성공",
|
||||
"Book exported successfully.": "책을 성공적으로 내보냈습니다.",
|
||||
"Failed to export the book.": "책 내보내기에 실패했습니다.",
|
||||
"Export Book": "책 내보내기",
|
||||
"Whole word:": "전체 단어:",
|
||||
"Error": "오류",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "문서를 로드할 수 없습니다. {{link}}에서 직접 검색해 보세요.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "단어를 로드할 수 없습니다. {{link}}에서 직접 검색해 보세요.",
|
||||
"Date Published": "출판일",
|
||||
"Only for TTS:": "TTS 전용:",
|
||||
"Uploaded": "업로드됨",
|
||||
"Downloaded": "다운로드됨",
|
||||
"Deleted": "삭제됨",
|
||||
"Note:": "노트:",
|
||||
"Time:": "시간:",
|
||||
"Format Options": "형식 옵션",
|
||||
"Export Date": "내보내기 날짜",
|
||||
"Chapter Titles": "챕터 제목",
|
||||
"Chapter Separator": "챕터 구분 기호",
|
||||
"Highlights": "하이라이트",
|
||||
"Note Date": "노트 날짜",
|
||||
"Advanced": "고급",
|
||||
"Hide": "숨기기",
|
||||
"Show": "표시",
|
||||
"Use Custom Template": "사용자 지정 템플릿 사용",
|
||||
"Export Template": "내보내기 템플릿",
|
||||
"Template Syntax:": "템플릿 구문:",
|
||||
"Insert value": "값 삽입",
|
||||
"Format date (locale)": "날짜 형식 (로케일)",
|
||||
"Format date (custom)": "날짜 형식 (사용자 지정)",
|
||||
"Conditional": "조건부",
|
||||
"Loop": "반복",
|
||||
"Available Variables:": "사용 가능한 변수:",
|
||||
"Book title": "책 제목",
|
||||
"Book author": "책 저자",
|
||||
"Export date": "내보내기 날짜",
|
||||
"Array of chapters": "챕터 배열",
|
||||
"Chapter title": "챕터 제목",
|
||||
"Array of annotations": "주석 배열",
|
||||
"Highlighted text": "하이라이트된 텍스트",
|
||||
"Annotation note": "주석 노트",
|
||||
"Update time": "업데이트 시간",
|
||||
"Date Format Tokens:": "날짜 형식 토큰:",
|
||||
"Year (4 digits)": "연도 (4자리)",
|
||||
"Month (01-12)": "월 (01-12)",
|
||||
"Day (01-31)": "일 (01-31)",
|
||||
"Hour (00-23)": "시 (00-23)",
|
||||
"Minute (00-59)": "분 (00-59)",
|
||||
"Second (00-59)": "초 (00-59)",
|
||||
"Show Source": "소스 표시",
|
||||
"No content to preview": "미리 볼 내용 없음",
|
||||
"Export": "내보내기",
|
||||
"Set Timeout": "시간 제한 설정",
|
||||
"Select Voice": "음성 선택",
|
||||
"Toggle Sticky Bottom TTS Bar": "고정 TTS 바 전환",
|
||||
"Display what I'm reading on Discord": "Discord에 읽는 책 표시",
|
||||
"Show on Discord": "Discord에 표시"
|
||||
}
|
||||
|
||||
@@ -25,9 +25,6 @@
|
||||
"Your OTP token": "Token OTP anda",
|
||||
"Verify token": "Sahkan token",
|
||||
"Go Back": "Kembali",
|
||||
"Sign in with Google": "Log masuk dengan Google",
|
||||
"Sign in with Apple": "Log masuk dengan Apple",
|
||||
"Sign in with GitHub": "Log masuk dengan GitHub",
|
||||
"New Password": "Kata Laluan Baharu",
|
||||
"Your new password": "Kata laluan baharu anda",
|
||||
"Update password": "Kemas kini kata laluan",
|
||||
@@ -649,8 +646,8 @@
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Source Han Serif CN VF": "Source Han Serif CN VF",
|
||||
"Huiwen-mincho": "Huiwen-mincho",
|
||||
"Source Han Serif CN": "Source Han Serif CN",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa_OldSong",
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "Mengesahkan...",
|
||||
"View All": "Lihat Semua",
|
||||
"Forward": "Maju",
|
||||
"OPDS Catalog": "Katalog OPDS",
|
||||
"Home": "Laman Utama",
|
||||
"Library": "Perpustakaan",
|
||||
"{{count}} items_other": "{{count}} item",
|
||||
"Download completed": "Muat turun selesai",
|
||||
"Download failed": "Muat turun gagal",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "Terakhir",
|
||||
"Cannot Load Page": "Tidak dapat memuatkan halaman",
|
||||
"An error occurred": "Ralat telah berlaku",
|
||||
"Online Library": "Perpustakaan Dalam Talian"
|
||||
"Online Library": "Perpustakaan Dalam Talian",
|
||||
"URL must start with http:// or https://": "URL mesti bermula dengan http:// atau https://",
|
||||
"Title, Author, Tag, etc...": "Tajuk, Pengarang, Tag, dll...",
|
||||
"Query": "Pertanyaan",
|
||||
"Subject": "Subjek",
|
||||
"Enter {{terms}}": "Masukkan {{terms}}",
|
||||
"No search results found": "Tiada hasil carian ditemui",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Gagal memuatkan suapan OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Cari dalam {{title}}",
|
||||
"Manage Storage": "Urus Penyimpanan",
|
||||
"Failed to load files": "Gagal memuatkan fail",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} fail telah dipadam",
|
||||
"Failed to delete {{count}} file(s)_other": "Gagal memadam {{count}} fail",
|
||||
"Failed to delete files": "Gagal memadam fail",
|
||||
"Total Files": "Jumlah Fail",
|
||||
"Total Size": "Jumlah Saiz",
|
||||
"Quota": "Kuota",
|
||||
"Used": "Digunakan",
|
||||
"Files": "Fail",
|
||||
"Search files...": "Cari fail...",
|
||||
"Newest First": "Terbaru dahulu",
|
||||
"Oldest First": "Tertua dahulu",
|
||||
"Largest First": "Terbesar dahulu",
|
||||
"Smallest First": "Terkecil dahulu",
|
||||
"Name A-Z": "Nama A-Z",
|
||||
"Name Z-A": "Nama Z-A",
|
||||
"{{count}} selected_other": "{{count}} dipilih",
|
||||
"Delete Selected": "Padam Dipilih",
|
||||
"Created": "Dicipta",
|
||||
"No files found": "Tiada fail dijumpai",
|
||||
"No files uploaded yet": "Belum ada fail dimuat naik",
|
||||
"files": "fail",
|
||||
"Page {{current}} of {{total}}": "Halaman {{current}} daripada {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Adakah anda pasti mahu memadam {{count}} fail yang dipilih?",
|
||||
"Cloud Storage Usage": "Penggunaan Storan Awan",
|
||||
"Rename Group": "Namakan Semula Kumpulan",
|
||||
"From Directory": "Dari Direktori",
|
||||
"Successfully imported {{count}} book(s)_other": "Berjaya mengimport {{count}} buku",
|
||||
"Count": "Jumlah",
|
||||
"Start Page": "Halaman Awal",
|
||||
"Search in OPDS Catalog...": "Cari dalam Katalog OPDS...",
|
||||
"Please log in to use advanced TTS features": "Sila log masuk untuk menggunakan ciri TTS lanjutan",
|
||||
"Word limit of 30 words exceeded.": "Had 30 perkataan telah dilebihi.",
|
||||
"Proofread": "Semakan",
|
||||
"Current selection": "Pilihan semasa",
|
||||
"All occurrences in this book": "Semua kejadian dalam buku ini",
|
||||
"All occurrences in your library": "Semua kejadian dalam perpustakaan anda",
|
||||
"Selected text:": "Teks dipilih:",
|
||||
"Replace with:": "Ganti dengan:",
|
||||
"Enter text...": "Masukkan teks…",
|
||||
"Case sensitive:": "Peka huruf besar dan kecil:",
|
||||
"Scope:": "Skop:",
|
||||
"Selection": "Pilihan",
|
||||
"Library": "Perpustakaan",
|
||||
"Yes": "Ya",
|
||||
"No": "Tidak",
|
||||
"Proofread Replacement Rules": "Peraturan penggantian semakan",
|
||||
"Selected Text Rules": "Peraturan teks dipilih",
|
||||
"No selected text replacement rules": "Tiada peraturan penggantian untuk teks dipilih",
|
||||
"Book Specific Rules": "Peraturan khusus buku",
|
||||
"No book-level replacement rules": "Tiada peraturan penggantian peringkat buku",
|
||||
"Disable Quick Action": "Nyahaktifkan Tindakan Pantas",
|
||||
"Enable Quick Action on Selection": "Aktifkan Tindakan Pantas pada Pilihan",
|
||||
"None": "Tiada",
|
||||
"Annotation Tools": "Alat Anotasi",
|
||||
"Enable Quick Actions": "Aktifkan Tindakan Pantas",
|
||||
"Quick Action": "Tindakan Pantas",
|
||||
"Copy to Notebook": "Salin ke Buku Nota",
|
||||
"Copy text after selection": "Salin teks selepas pilihan",
|
||||
"Highlight text after selection": "Sorot teks selepas pilihan",
|
||||
"Annotate text after selection": "Anotasi teks selepas pilihan",
|
||||
"Search text after selection": "Cari teks selepas pilihan",
|
||||
"Look up text in dictionary after selection": "Cari teks dalam kamus selepas pilihan",
|
||||
"Look up text in Wikipedia after selection": "Cari teks dalam Wikipedia selepas pilihan",
|
||||
"Translate text after selection": "Terjemah teks selepas pilihan",
|
||||
"Read text aloud after selection": "Baca teks dengan kuat selepas pilihan",
|
||||
"Proofread text after selection": "Semak teks selepas pilihan",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} aktif, {{pendingCount}} menunggu",
|
||||
"{{failedCount}} failed": "{{failedCount}} gagal",
|
||||
"Waiting...": "Menunggu...",
|
||||
"Failed": "Gagal",
|
||||
"Completed": "Selesai",
|
||||
"Cancelled": "Dibatalkan",
|
||||
"Retry": "Cuba lagi",
|
||||
"Active": "Aktif",
|
||||
"Transfer Queue": "Baris Gilir Pemindahan",
|
||||
"Upload All": "Muat Naik Semua",
|
||||
"Download All": "Muat Turun Semua",
|
||||
"Resume Transfers": "Sambung Pemindahan",
|
||||
"Pause Transfers": "Jeda Pemindahan",
|
||||
"Pending": "Menunggu",
|
||||
"No transfers": "Tiada pemindahan",
|
||||
"Retry All": "Cuba Semua Lagi",
|
||||
"Clear Completed": "Kosongkan Selesai",
|
||||
"Clear Failed": "Kosongkan Gagal",
|
||||
"Upload queued: {{title}}": "Muat naik dalam baris gilir: {{title}}",
|
||||
"Download queued: {{title}}": "Muat turun dalam baris gilir: {{title}}",
|
||||
"Book not found in library": "Buku tidak dijumpai dalam perpustakaan",
|
||||
"Unknown error": "Ralat tidak diketahui",
|
||||
"Please log in to continue": "Sila log masuk untuk meneruskan",
|
||||
"Cloud File Transfers": "Pemindahan Fail Awan",
|
||||
"Show Search Results": "Tunjukkan hasil carian",
|
||||
"Search results for '{{term}}'": "Hasil untuk '{{term}}'",
|
||||
"Close Search": "Tutup carian",
|
||||
"Previous Result": "Hasil sebelumnya",
|
||||
"Next Result": "Hasil seterusnya",
|
||||
"Bookmarks": "Penanda buku",
|
||||
"Annotations": "Anotasi",
|
||||
"Show Results": "Tunjukkan Hasil",
|
||||
"Clear search": "Kosongkan carian",
|
||||
"Clear search history": "Kosongkan sejarah carian",
|
||||
"Quick action disabled": "Tindakan pantas dilumpuhkan",
|
||||
"Tap to Toggle Footer": "Ketik untuk togol pengaki",
|
||||
"Exported successfully": "Berjaya dieksport",
|
||||
"Book exported successfully.": "Buku berjaya dieksport.",
|
||||
"Failed to export the book.": "Gagal mengeksport buku.",
|
||||
"Export Book": "Eksport Buku",
|
||||
"Whole word:": "Perkataan penuh:",
|
||||
"Error": "Ralat",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Tidak dapat memuatkan artikel. Cuba cari terus di {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Tidak dapat memuatkan perkataan. Cuba cari terus di {{link}}.",
|
||||
"Date Published": "Tarikh diterbitkan",
|
||||
"Only for TTS:": "Hanya untuk TTS:",
|
||||
"Uploaded": "Dimuat naik",
|
||||
"Downloaded": "Dimuat turun",
|
||||
"Deleted": "Dipadam",
|
||||
"Note:": "Nota:",
|
||||
"Time:": "Masa:",
|
||||
"Format Options": "Pilihan Format",
|
||||
"Export Date": "Tarikh Eksport",
|
||||
"Chapter Titles": "Tajuk Bab",
|
||||
"Chapter Separator": "Pemisah Bab",
|
||||
"Highlights": "Serlahan",
|
||||
"Note Date": "Tarikh Nota",
|
||||
"Advanced": "Lanjutan",
|
||||
"Hide": "Sembunyikan",
|
||||
"Show": "Tunjukkan",
|
||||
"Use Custom Template": "Gunakan Templat Tersuai",
|
||||
"Export Template": "Templat Eksport",
|
||||
"Template Syntax:": "Sintaks Templat:",
|
||||
"Insert value": "Masukkan nilai",
|
||||
"Format date (locale)": "Format tarikh (tempatan)",
|
||||
"Format date (custom)": "Format tarikh (tersuai)",
|
||||
"Conditional": "Bersyarat",
|
||||
"Loop": "Gelung",
|
||||
"Available Variables:": "Pembolehubah Tersedia:",
|
||||
"Book title": "Tajuk buku",
|
||||
"Book author": "Pengarang buku",
|
||||
"Export date": "Tarikh eksport",
|
||||
"Array of chapters": "Senarai bab",
|
||||
"Chapter title": "Tajuk bab",
|
||||
"Array of annotations": "Senarai anotasi",
|
||||
"Highlighted text": "Teks yang diserlahkan",
|
||||
"Annotation note": "Nota anotasi",
|
||||
"Update time": "Masa kemas kini",
|
||||
"Date Format Tokens:": "Token Format Tarikh:",
|
||||
"Year (4 digits)": "Tahun (4 digit)",
|
||||
"Month (01-12)": "Bulan (01-12)",
|
||||
"Day (01-31)": "Hari (01-31)",
|
||||
"Hour (00-23)": "Jam (00-23)",
|
||||
"Minute (00-59)": "Minit (00-59)",
|
||||
"Second (00-59)": "Saat (00-59)",
|
||||
"Show Source": "Tunjukkan Sumber",
|
||||
"No content to preview": "Tiada kandungan untuk pratonton",
|
||||
"Export": "Eksport",
|
||||
"Set Timeout": "Tetapkan masa tamat",
|
||||
"Select Voice": "Pilih suara",
|
||||
"Toggle Sticky Bottom TTS Bar": "Togol bar TTS melekit",
|
||||
"Display what I'm reading on Discord": "Papar buku yang sedang dibaca di Discord",
|
||||
"Show on Discord": "Papar di Discord"
|
||||
}
|
||||
|
||||
@@ -23,9 +23,6 @@
|
||||
"Token": "Token",
|
||||
"Your OTP token": "Uw OTP-token",
|
||||
"Verify token": "Token verifiëren",
|
||||
"Sign in with Google": "Inloggen met Google",
|
||||
"Sign in with Apple": "Inloggen met Apple",
|
||||
"Sign in with GitHub": "Inloggen met GitHub",
|
||||
"New Password": "Nieuw wachtwoord",
|
||||
"Your new password": "Uw nieuwe wachtwoord",
|
||||
"Update password": "Wachtwoord bijwerken",
|
||||
@@ -341,8 +338,8 @@
|
||||
"{{count}} pages left in chapter_one": "Nog {{count}} pagina in hoofdstuk",
|
||||
"{{count}} pages left in chapter_other": "Nog {{count}} pagina’s in hoofdstuk",
|
||||
"Show Remaining Pages": "Toon resterende pagina’s",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Abonnement beheren",
|
||||
"Coming Soon": "Binnenkort beschikbaar",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "Valideren...",
|
||||
"View All": "Alles bekijken",
|
||||
"Forward": "Verder",
|
||||
"OPDS Catalog": "OPDS-catalogus",
|
||||
"Home": "Startpagina",
|
||||
"Library": "Bibliotheek",
|
||||
"{{count}} items_one": "{{count}} item",
|
||||
"{{count}} items_other": "{{count}} items",
|
||||
"Download completed": "Download voltooid",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "Laatste",
|
||||
"Cannot Load Page": "Pagina kan niet worden geladen",
|
||||
"An error occurred": "Er is een fout opgetreden",
|
||||
"Online Library": "Online Bibliotheek"
|
||||
"Online Library": "Online Bibliotheek",
|
||||
"URL must start with http:// or https://": "URL moet beginnen met http:// of https://",
|
||||
"Title, Author, Tag, etc...": "Titel, Auteur, Tag, enzovoort...",
|
||||
"Query": "Zoekopdracht",
|
||||
"Subject": "Onderwerp",
|
||||
"Enter {{terms}}": "Voer {{terms}} in",
|
||||
"No search results found": "Geen zoekresultaten gevonden",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Het laden van de OPDS-feed is mislukt: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Zoeken in {{title}}",
|
||||
"Manage Storage": "Opslag beheren",
|
||||
"Failed to load files": "Bestanden laden mislukt",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} bestand verwijderd",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} bestanden verwijderd",
|
||||
"Failed to delete {{count}} file(s)_one": "Kon {{count}} bestand niet verwijderen",
|
||||
"Failed to delete {{count}} file(s)_other": "Kon {{count}} bestanden niet verwijderen",
|
||||
"Failed to delete files": "Bestanden verwijderen mislukt",
|
||||
"Total Files": "Totaal aantal bestanden",
|
||||
"Total Size": "Totale grootte",
|
||||
"Quota": "Quota",
|
||||
"Used": "Gebruikt",
|
||||
"Files": "Bestanden",
|
||||
"Search files...": "Bestanden zoeken...",
|
||||
"Newest First": "Nieuwste eerst",
|
||||
"Oldest First": "Oudste eerst",
|
||||
"Largest First": "Grootste eerst",
|
||||
"Smallest First": "Kleinste eerst",
|
||||
"Name A-Z": "Naam A-Z",
|
||||
"Name Z-A": "Naam Z-A",
|
||||
"{{count}} selected_one": "{{count}} geselecteerd",
|
||||
"{{count}} selected_other": "{{count}} geselecteerd",
|
||||
"Delete Selected": "Verwijder geselecteerde",
|
||||
"Created": "Gemaakt",
|
||||
"No files found": "Geen bestanden gevonden",
|
||||
"No files uploaded yet": "Nog geen bestanden geüpload",
|
||||
"files": "bestanden",
|
||||
"Page {{current}} of {{total}}": "Pagina {{current}} van {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Weet je zeker dat je {{count}} geselecteerd bestand wilt verwijderen?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Weet je zeker dat je {{count}} geselecteerde bestanden wilt verwijderen?",
|
||||
"Cloud Storage Usage": "Cloudopslaggebruik",
|
||||
"Rename Group": "Groep hernoemen",
|
||||
"From Directory": "Vanuit map",
|
||||
"Successfully imported {{count}} book(s)_one": "Succesvol 1 boek geïmporteerd",
|
||||
"Successfully imported {{count}} book(s)_other": "Succesvol {{count}} boeken geïmporteerd",
|
||||
"Count": "Aantal",
|
||||
"Start Page": "Startpagina",
|
||||
"Search in OPDS Catalog...": "Zoeken in OPDS-catalogus...",
|
||||
"Please log in to use advanced TTS features": "Log in om geavanceerde TTS-functies te gebruiken",
|
||||
"Word limit of 30 words exceeded.": "De limiet van 30 woorden is overschreden.",
|
||||
"Proofread": "Correctie",
|
||||
"Current selection": "Huidige selectie",
|
||||
"All occurrences in this book": "Alle voorkomens in dit boek",
|
||||
"All occurrences in your library": "Alle voorkomens in je bibliotheek",
|
||||
"Selected text:": "Geselecteerde tekst:",
|
||||
"Replace with:": "Vervangen door:",
|
||||
"Enter text...": "Tekst invoeren…",
|
||||
"Case sensitive:": "Hoofdlettergevoelig:",
|
||||
"Scope:": "Bereik:",
|
||||
"Selection": "Selectie",
|
||||
"Library": "Bibliotheek",
|
||||
"Yes": "Ja",
|
||||
"No": "Nee",
|
||||
"Proofread Replacement Rules": "Correctie-vervangingsregels",
|
||||
"Selected Text Rules": "Regels voor geselecteerde tekst",
|
||||
"No selected text replacement rules": "Geen vervangingsregels voor geselecteerde tekst",
|
||||
"Book Specific Rules": "Boekspecifieke regels",
|
||||
"No book-level replacement rules": "Geen vervangingsregels op boekniveau",
|
||||
"Disable Quick Action": "Snelle actie uitschakelen",
|
||||
"Enable Quick Action on Selection": "Snelle actie bij selectie inschakelen",
|
||||
"None": "Geen",
|
||||
"Annotation Tools": "Annotatiehulpmiddelen",
|
||||
"Enable Quick Actions": "Snelle acties inschakelen",
|
||||
"Quick Action": "Snelle actie",
|
||||
"Copy to Notebook": "Kopiëren naar notitieboek",
|
||||
"Copy text after selection": "Kopieer tekst na selectie",
|
||||
"Highlight text after selection": "Markeer tekst na selectie",
|
||||
"Annotate text after selection": "Annoteren tekst na selectie",
|
||||
"Search text after selection": "Zoek tekst na selectie",
|
||||
"Look up text in dictionary after selection": "Zoek tekst op in woordenboek na selectie",
|
||||
"Look up text in Wikipedia after selection": "Zoek tekst op in Wikipedia na selectie",
|
||||
"Translate text after selection": "Vertaal tekst na selectie",
|
||||
"Read text aloud after selection": "Lees tekst hardop na selectie",
|
||||
"Proofread text after selection": "Corrigeer tekst na selectie",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} actief, {{pendingCount}} wachtend",
|
||||
"{{failedCount}} failed": "{{failedCount}} mislukt",
|
||||
"Waiting...": "Wachten...",
|
||||
"Failed": "Mislukt",
|
||||
"Completed": "Voltooid",
|
||||
"Cancelled": "Geannuleerd",
|
||||
"Retry": "Opnieuw proberen",
|
||||
"Active": "Actief",
|
||||
"Transfer Queue": "Overdrachtwachtrij",
|
||||
"Upload All": "Alles uploaden",
|
||||
"Download All": "Alles downloaden",
|
||||
"Resume Transfers": "Overdrachten hervatten",
|
||||
"Pause Transfers": "Overdrachten pauzeren",
|
||||
"Pending": "Wachtend",
|
||||
"No transfers": "Geen overdrachten",
|
||||
"Retry All": "Alles opnieuw proberen",
|
||||
"Clear Completed": "Voltooide wissen",
|
||||
"Clear Failed": "Mislukte wissen",
|
||||
"Upload queued: {{title}}": "Upload in wachtrij: {{title}}",
|
||||
"Download queued: {{title}}": "Download in wachtrij: {{title}}",
|
||||
"Book not found in library": "Boek niet gevonden in bibliotheek",
|
||||
"Unknown error": "Onbekende fout",
|
||||
"Please log in to continue": "Log in om door te gaan",
|
||||
"Cloud File Transfers": "Cloud Bestandoverdrachten",
|
||||
"Show Search Results": "Zoekresultaten tonen",
|
||||
"Search results for '{{term}}'": "Resultaten voor '{{term}}'",
|
||||
"Close Search": "Zoekopdracht sluiten",
|
||||
"Previous Result": "Vorig resultaat",
|
||||
"Next Result": "Volgend resultaat",
|
||||
"Bookmarks": "Bladwijzers",
|
||||
"Annotations": "Aantekeningen",
|
||||
"Show Results": "Resultaten tonen",
|
||||
"Clear search": "Zoekopdracht wissen",
|
||||
"Clear search history": "Zoekgeschiedenis wissen",
|
||||
"Quick action disabled": "Snelle actie uitgeschakeld",
|
||||
"Tap to Toggle Footer": "Tik om voettekst te wisselen",
|
||||
"Exported successfully": "Succesvol geëxporteerd",
|
||||
"Book exported successfully.": "Boek succesvol geëxporteerd.",
|
||||
"Failed to export the book.": "Exporteren van het boek mislukt.",
|
||||
"Export Book": "Boek exporteren",
|
||||
"Whole word:": "Heel woord:",
|
||||
"Error": "Fout",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Kan het artikel niet laden. Probeer direct te zoeken op {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Kan het woord niet laden. Probeer direct te zoeken op {{link}}.",
|
||||
"Date Published": "Publicatiedatum",
|
||||
"Only for TTS:": "Alleen voor TTS:",
|
||||
"Uploaded": "Geüpload",
|
||||
"Downloaded": "Gedownload",
|
||||
"Deleted": "Verwijderd",
|
||||
"Note:": "Notitie:",
|
||||
"Time:": "Tijd:",
|
||||
"Format Options": "Formaatopties",
|
||||
"Export Date": "Exportdatum",
|
||||
"Chapter Titles": "Hoofdstuktitels",
|
||||
"Chapter Separator": "Hoofdstukscheiding",
|
||||
"Highlights": "Markeringen",
|
||||
"Note Date": "Notitiedatum",
|
||||
"Advanced": "Geavanceerd",
|
||||
"Hide": "Verbergen",
|
||||
"Show": "Tonen",
|
||||
"Use Custom Template": "Aangepast sjabloon gebruiken",
|
||||
"Export Template": "Exportsjabloon",
|
||||
"Template Syntax:": "Sjabloonsyntaxis:",
|
||||
"Insert value": "Waarde invoegen",
|
||||
"Format date (locale)": "Datum opmaken (lokaal)",
|
||||
"Format date (custom)": "Datum opmaken (aangepast)",
|
||||
"Conditional": "Voorwaardelijk",
|
||||
"Loop": "Lus",
|
||||
"Available Variables:": "Beschikbare variabelen:",
|
||||
"Book title": "Boektitel",
|
||||
"Book author": "Boekauteur",
|
||||
"Export date": "Exportdatum",
|
||||
"Array of chapters": "Reeks hoofdstukken",
|
||||
"Chapter title": "Hoofdstuktitel",
|
||||
"Array of annotations": "Reeks annotaties",
|
||||
"Highlighted text": "Gemarkeerde tekst",
|
||||
"Annotation note": "Annotatienotitie",
|
||||
"Update time": "Updatetijd",
|
||||
"Date Format Tokens:": "Datumformaattokens:",
|
||||
"Year (4 digits)": "Jaar (4 cijfers)",
|
||||
"Month (01-12)": "Maand (01-12)",
|
||||
"Day (01-31)": "Dag (01-31)",
|
||||
"Hour (00-23)": "Uur (00-23)",
|
||||
"Minute (00-59)": "Minuut (00-59)",
|
||||
"Second (00-59)": "Seconde (00-59)",
|
||||
"Show Source": "Bron tonen",
|
||||
"No content to preview": "Geen inhoud om te bekijken",
|
||||
"Export": "Exporteren",
|
||||
"Set Timeout": "Time-out instellen",
|
||||
"Select Voice": "Stem selecteren",
|
||||
"Toggle Sticky Bottom TTS Bar": "Vaste TTS-balk schakelen",
|
||||
"Display what I'm reading on Discord": "Toon wat ik lees op Discord",
|
||||
"Show on Discord": "Toon op Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Token",
|
||||
"Your OTP token": "Twój OTP token",
|
||||
"Verify token": "Weryfikacja tokena",
|
||||
"Sign in with Google": "Zaloguj się za pomocą Google",
|
||||
"Sign in with Apple": "Zaloguj się za pomocą Apple",
|
||||
"Sign in with GitHub": "Zaloguj się za pomocą GitHub",
|
||||
"Account": "Konto",
|
||||
"Failed to delete user. Please try again later.": "Nie udało się usunąć użytkownika. Spróbuj ponownie później.",
|
||||
"Community Support": "Wsparcie społeczności",
|
||||
@@ -349,8 +346,8 @@
|
||||
"{{count}} pages left in chapter_many": "Pozostało {{count}} stron w rozdziale",
|
||||
"{{count}} pages left in chapter_other": "Pozostało {{count}} stron w rozdziale",
|
||||
"Show Remaining Pages": "Pokaż pozostałe strony",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Zarządzaj subskrypcją",
|
||||
"Coming Soon": "Wkrótce dostępne",
|
||||
@@ -707,9 +704,7 @@
|
||||
"Validating...": "Weryfikacja...",
|
||||
"View All": "Pokaż wszystkie",
|
||||
"Forward": "Dalej",
|
||||
"OPDS Catalog": "Katalog OPDS",
|
||||
"Home": "Strona główna",
|
||||
"Library": "Biblioteka",
|
||||
"{{count}} items_one": "{{count}} element",
|
||||
"{{count}} items_few": "{{count}} elementy",
|
||||
"{{count}} items_many": "{{count}} elementów",
|
||||
@@ -731,5 +726,189 @@
|
||||
"Last": "Ostatnia",
|
||||
"Cannot Load Page": "Nie można załadować strony",
|
||||
"An error occurred": "Wystąpił błąd",
|
||||
"Online Library": "Biblioteka online"
|
||||
"Online Library": "Biblioteka online",
|
||||
"URL must start with http:// or https://": "URL musi zaczynać się od http:// lub https://",
|
||||
"Title, Author, Tag, etc...": "Tytuł, Autor, Tag, itp...",
|
||||
"Query": "Zapytanie",
|
||||
"Subject": "Temat",
|
||||
"Enter {{terms}}": "Wprowadź {{terms}}",
|
||||
"No search results found": "Nie znaleziono wyników wyszukiwania",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Nie udało się załadować kanału OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Szukaj w {{title}}",
|
||||
"Manage Storage": "Zarządzaj pamięcią",
|
||||
"Failed to load files": "Nie udało się wczytać plików",
|
||||
"Deleted {{count}} file(s)_one": "Usunięto {{count}} plik",
|
||||
"Deleted {{count}} file(s)_few": "Usunięto {{count}} pliki",
|
||||
"Deleted {{count}} file(s)_many": "Usunięto {{count}} plików",
|
||||
"Deleted {{count}} file(s)_other": "Usunięto {{count}} pliku",
|
||||
"Failed to delete {{count}} file(s)_one": "Nie udało się usunąć {{count}} pliku",
|
||||
"Failed to delete {{count}} file(s)_few": "Nie udało się usunąć {{count}} plików",
|
||||
"Failed to delete {{count}} file(s)_many": "Nie udało się usunąć {{count}} plików",
|
||||
"Failed to delete {{count}} file(s)_other": "Nie udało się usunąć {{count}} pliku",
|
||||
"Failed to delete files": "Nie udało się usunąć plików",
|
||||
"Total Files": "Łączna liczba plików",
|
||||
"Total Size": "Łączny rozmiar",
|
||||
"Quota": "Limit",
|
||||
"Used": "Użyto",
|
||||
"Files": "Pliki",
|
||||
"Search files...": "Szukaj plików...",
|
||||
"Newest First": "Najnowsze najpierw",
|
||||
"Oldest First": "Najstarsze najpierw",
|
||||
"Largest First": "Największe najpierw",
|
||||
"Smallest First": "Najmniejsze najpierw",
|
||||
"Name A-Z": "Nazwa A-Z",
|
||||
"Name Z-A": "Nazwa Z-A",
|
||||
"{{count}} selected_one": "Wybrano {{count}} plik",
|
||||
"{{count}} selected_few": "Wybrano {{count}} pliki",
|
||||
"{{count}} selected_many": "Wybrano {{count}} plików",
|
||||
"{{count}} selected_other": "Wybrano {{count}} pliku",
|
||||
"Delete Selected": "Usuń wybrane",
|
||||
"Created": "Utworzono",
|
||||
"No files found": "Nie znaleziono plików",
|
||||
"No files uploaded yet": "Nie przesłano jeszcze żadnych plików",
|
||||
"files": "pliki",
|
||||
"Page {{current}} of {{total}}": "Strona {{current}} z {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Czy na pewno chcesz usunąć {{count}} wybrany plik?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_few": "Czy na pewno chcesz usunąć {{count}} wybrane pliki?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Czy na pewno chcesz usunąć {{count}} wybranych plików?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Czy na pewno chcesz usunąć {{count}} wybrany plik?",
|
||||
"Cloud Storage Usage": "Użycie pamięci w chmurze",
|
||||
"Rename Group": "Zmień nazwę grupy",
|
||||
"From Directory": "Z katalogu",
|
||||
"Successfully imported {{count}} book(s)_one": "Pomyślnie zaimportowano 1 książkę",
|
||||
"Successfully imported {{count}} book(s)_few": "Pomyślnie zaimportowano {{count}} książki",
|
||||
"Successfully imported {{count}} book(s)_many": "Pomyślnie zaimportowano {{count}} książek",
|
||||
"Successfully imported {{count}} book(s)_other": "Pomyślnie zaimportowano {{count}} książek",
|
||||
"Count": "Liczba",
|
||||
"Start Page": "Strona startowa",
|
||||
"Search in OPDS Catalog...": "Szukaj w katalogu OPDS...",
|
||||
"Please log in to use advanced TTS features": "Zaloguj się, aby korzystać z zaawansowanych funkcji TTS",
|
||||
"Word limit of 30 words exceeded.": "Przekroczono limit 30 słów.",
|
||||
"Proofread": "Korekta",
|
||||
"Current selection": "Bieżące zaznaczenie",
|
||||
"All occurrences in this book": "Wszystkie wystąpienia w tej książce",
|
||||
"All occurrences in your library": "Wszystkie wystąpienia w Twojej bibliotece",
|
||||
"Selected text:": "Zaznaczony tekst:",
|
||||
"Replace with:": "Zastąp przez:",
|
||||
"Enter text...": "Wpisz tekst…",
|
||||
"Case sensitive:": "Uwzględniaj wielkość liter:",
|
||||
"Scope:": "Zakres:",
|
||||
"Selection": "Zaznaczenie",
|
||||
"Library": "Biblioteka",
|
||||
"Yes": "Tak",
|
||||
"No": "Nie",
|
||||
"Proofread Replacement Rules": "Reguły zastępowania korekty",
|
||||
"Selected Text Rules": "Reguły dla zaznaczonego tekstu",
|
||||
"No selected text replacement rules": "Brak reguł zastępowania dla zaznaczonego tekstu",
|
||||
"Book Specific Rules": "Reguły specyficzne dla książki",
|
||||
"No book-level replacement rules": "Brak reguł zastępowania na poziomie książki",
|
||||
"Disable Quick Action": "Wyłącz szybką akcję",
|
||||
"Enable Quick Action on Selection": "Włącz szybką akcję przy zaznaczeniu",
|
||||
"None": "Brak",
|
||||
"Annotation Tools": "Narzędzia do adnotacji",
|
||||
"Enable Quick Actions": "Włącz szybkie akcje",
|
||||
"Quick Action": "Szybka akcja",
|
||||
"Copy to Notebook": "Kopiuj do notatnika",
|
||||
"Copy text after selection": "Kopiuj zaznaczony tekst",
|
||||
"Highlight text after selection": "Podświetl zaznaczony tekst",
|
||||
"Annotate text after selection": "Dodaj adnotację do zaznaczonego tekstu",
|
||||
"Search text after selection": "Wyszukaj zaznaczony tekst",
|
||||
"Look up text in dictionary after selection": "Sprawdź zaznaczony tekst w słowniku",
|
||||
"Look up text in Wikipedia after selection": "Sprawdź zaznaczony tekst w Wikipedii",
|
||||
"Translate text after selection": "Przetłumacz zaznaczony tekst",
|
||||
"Read text aloud after selection": "Odczytaj zaznaczony tekst na głos",
|
||||
"Proofread text after selection": "Sprawdź zaznaczony tekst",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} aktywnych, {{pendingCount}} oczekujących",
|
||||
"{{failedCount}} failed": "{{failedCount}} nieudanych",
|
||||
"Waiting...": "Oczekiwanie...",
|
||||
"Failed": "Nieudane",
|
||||
"Completed": "Ukończone",
|
||||
"Cancelled": "Anulowane",
|
||||
"Retry": "Ponów",
|
||||
"Active": "Aktywne",
|
||||
"Transfer Queue": "Kolejka transferów",
|
||||
"Upload All": "Wyślij wszystko",
|
||||
"Download All": "Pobierz wszystko",
|
||||
"Resume Transfers": "Wznów transfery",
|
||||
"Pause Transfers": "Wstrzymaj transfery",
|
||||
"Pending": "Oczekujące",
|
||||
"No transfers": "Brak transferów",
|
||||
"Retry All": "Ponów wszystko",
|
||||
"Clear Completed": "Wyczyść ukończone",
|
||||
"Clear Failed": "Wyczyść nieudane",
|
||||
"Upload queued: {{title}}": "Wysyłanie w kolejce: {{title}}",
|
||||
"Download queued: {{title}}": "Pobieranie w kolejce: {{title}}",
|
||||
"Book not found in library": "Książka nie została znaleziona w bibliotece",
|
||||
"Unknown error": "Nieznany błąd",
|
||||
"Please log in to continue": "Zaloguj się, aby kontynuować",
|
||||
"Cloud File Transfers": "Transfery plików w chmurze",
|
||||
"Show Search Results": "Pokaż wyniki wyszukiwania",
|
||||
"Search results for '{{term}}'": "Wyniki dla '{{term}}'",
|
||||
"Close Search": "Zamknij wyszukiwanie",
|
||||
"Previous Result": "Poprzedni wynik",
|
||||
"Next Result": "Następny wynik",
|
||||
"Bookmarks": "Zakładki",
|
||||
"Annotations": "Adnotacje",
|
||||
"Show Results": "Pokaż wyniki",
|
||||
"Clear search": "Wyczyść wyszukiwanie",
|
||||
"Clear search history": "Wyczyść historię wyszukiwania",
|
||||
"Quick action disabled": "Szybka akcja wyłączona",
|
||||
"Tap to Toggle Footer": "Dotknij, aby przełączyć stopkę",
|
||||
"Exported successfully": "Wyeksportowano pomyślnie",
|
||||
"Book exported successfully.": "Książka została wyeksportowana pomyślnie.",
|
||||
"Failed to export the book.": "Nie udało się wyeksportować książki.",
|
||||
"Export Book": "Eksportuj książkę",
|
||||
"Whole word:": "Całe słowo:",
|
||||
"Error": "Błąd",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Nie można załadować artykułu. Spróbuj wyszukać bezpośrednio na {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Nie można załadować słowa. Spróbuj wyszukać bezpośrednio na {{link}}.",
|
||||
"Date Published": "Data wydania",
|
||||
"Only for TTS:": "Tylko dla TTS:",
|
||||
"Uploaded": "Przesłano",
|
||||
"Downloaded": "Pobrano",
|
||||
"Deleted": "Usunięto",
|
||||
"Note:": "Notatka:",
|
||||
"Time:": "Czas:",
|
||||
"Format Options": "Opcje formatu",
|
||||
"Export Date": "Data eksportu",
|
||||
"Chapter Titles": "Tytuły rozdziałów",
|
||||
"Chapter Separator": "Separator rozdziałów",
|
||||
"Highlights": "Zaznaczenia",
|
||||
"Note Date": "Data notatki",
|
||||
"Advanced": "Zaawansowane",
|
||||
"Hide": "Ukryj",
|
||||
"Show": "Pokaż",
|
||||
"Use Custom Template": "Użyj własnego szablonu",
|
||||
"Export Template": "Szablon eksportu",
|
||||
"Template Syntax:": "Składnia szablonu:",
|
||||
"Insert value": "Wstaw wartość",
|
||||
"Format date (locale)": "Formatuj datę (lokalizacja)",
|
||||
"Format date (custom)": "Formatuj datę (własny)",
|
||||
"Conditional": "Warunkowy",
|
||||
"Loop": "Pętla",
|
||||
"Available Variables:": "Dostępne zmienne:",
|
||||
"Book title": "Tytuł książki",
|
||||
"Book author": "Autor książki",
|
||||
"Export date": "Data eksportu",
|
||||
"Array of chapters": "Tablica rozdziałów",
|
||||
"Chapter title": "Tytuł rozdziału",
|
||||
"Array of annotations": "Tablica adnotacji",
|
||||
"Highlighted text": "Zaznaczony tekst",
|
||||
"Annotation note": "Notatka adnotacji",
|
||||
"Update time": "Czas aktualizacji",
|
||||
"Date Format Tokens:": "Tokeny formatu daty:",
|
||||
"Year (4 digits)": "Rok (4 cyfry)",
|
||||
"Month (01-12)": "Miesiąc (01-12)",
|
||||
"Day (01-31)": "Dzień (01-31)",
|
||||
"Hour (00-23)": "Godzina (00-23)",
|
||||
"Minute (00-59)": "Minuta (00-59)",
|
||||
"Second (00-59)": "Sekunda (00-59)",
|
||||
"Show Source": "Pokaż źródło",
|
||||
"No content to preview": "Brak treści do podglądu",
|
||||
"Export": "Eksportuj",
|
||||
"Set Timeout": "Ustaw limit czasu",
|
||||
"Select Voice": "Wybierz głos",
|
||||
"Toggle Sticky Bottom TTS Bar": "Przełącz przypiętą dolną belkę TTS",
|
||||
"Display what I'm reading on Discord": "Wyświetl to, co czytam na Discord",
|
||||
"Show on Discord": "Pokaż na Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Token",
|
||||
"Your OTP token": "Seu token OTP",
|
||||
"Verify token": "Verificar token",
|
||||
"Sign in with Google": "Entrar com o Google",
|
||||
"Sign in with Apple": "Entrar com o Apple",
|
||||
"Sign in with GitHub": "Entrar com o GitHub",
|
||||
"Account": "Conta",
|
||||
"Failed to delete user. Please try again later.": "Falha ao excluir usuário. Por favor, tente novamente mais tarde.",
|
||||
"Community Support": "Suporte da comunidade",
|
||||
@@ -345,8 +342,8 @@
|
||||
"{{count}} pages left in chapter_many": "Faltam {{count}} páginas neste capítulo",
|
||||
"{{count}} pages left in chapter_other": "Faltam {{count}} páginas neste capítulo",
|
||||
"Show Remaining Pages": "Mostrar páginas restantes",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Gerenciar assinatura",
|
||||
"Coming Soon": "Em breve",
|
||||
@@ -703,9 +700,7 @@
|
||||
"Validating...": "Validando...",
|
||||
"View All": "Ver todos",
|
||||
"Forward": "Avançar",
|
||||
"OPDS Catalog": "Catálogo OPDS",
|
||||
"Home": "Início",
|
||||
"Library": "Biblioteca",
|
||||
"{{count}} items_one": "{{count}} item",
|
||||
"{{count}} items_many": "{{count}} itens",
|
||||
"{{count}} items_other": "{{count}} item",
|
||||
@@ -726,5 +721,184 @@
|
||||
"Last": "Ostatnia",
|
||||
"Cannot Load Page": "Nie można załadować strony",
|
||||
"An error occurred": "Wystąpił błąd",
|
||||
"Online Library": "Biblioteka online"
|
||||
"Online Library": "Biblioteca online",
|
||||
"URL must start with http:// or https://": "URL deve começar com http:// ou https://",
|
||||
"Title, Author, Tag, etc...": "Título, Autor, Etiqueta, etc...",
|
||||
"Query": "Consulta",
|
||||
"Subject": "Assunto",
|
||||
"Enter {{terms}}": "Insira {{terms}}",
|
||||
"No search results found": "Nenhum resultado encontrado",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Falha ao carregar o feed OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Pesquisar em {{title}}",
|
||||
"Manage Storage": "Gerir Armazenamento",
|
||||
"Failed to load files": "Falha ao carregar arquivos",
|
||||
"Deleted {{count}} file(s)_one": "Apagado {{count}} ficheiro",
|
||||
"Deleted {{count}} file(s)_many": "Apagados {{count}} ficheiros",
|
||||
"Deleted {{count}} file(s)_other": "Apagado {{count}} ficheiro",
|
||||
"Failed to delete {{count}} file(s)_one": "Falha ao apagar {{count}} ficheiro",
|
||||
"Failed to delete {{count}} file(s)_many": "Falha ao apagar {{count}} ficheiros",
|
||||
"Failed to delete {{count}} file(s)_other": "Falha ao apagar {{count}} ficheiro",
|
||||
"Failed to delete files": "Falha ao apagar arquivos",
|
||||
"Total Files": "Total de Arquivos",
|
||||
"Total Size": "Tamanho Total",
|
||||
"Quota": "Quota",
|
||||
"Used": "Usado",
|
||||
"Files": "Arquivos",
|
||||
"Search files...": "Procurar arquivos...",
|
||||
"Newest First": "Mais Recentes Primeiro",
|
||||
"Oldest First": "Mais Antigos Primeiro",
|
||||
"Largest First": "Maiores Primeiro",
|
||||
"Smallest First": "Menores Primeiro",
|
||||
"Name A-Z": "Nome A-Z",
|
||||
"Name Z-A": "Nome Z-A",
|
||||
"{{count}} selected_one": "{{count}} selecionado",
|
||||
"{{count}} selected_many": "{{count}} selecionados",
|
||||
"{{count}} selected_other": "{{count}} selecionado",
|
||||
"Delete Selected": "Apagar Selecionados",
|
||||
"Created": "Criado",
|
||||
"No files found": "Nenhum arquivo encontrado",
|
||||
"No files uploaded yet": "Nenhum arquivo enviado ainda",
|
||||
"files": "arquivos",
|
||||
"Page {{current}} of {{total}}": "Página {{current}} de {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Tem certeza de que deseja apagar {{count}} ficheiro selecionado?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Tem certeza de que deseja apagar {{count}} ficheiros selecionados?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Tem certeza de que deseja apagar {{count}} ficheiro selecionado?",
|
||||
"Cloud Storage Usage": "Uso de Armazenamento na Nuvem",
|
||||
"Rename Group": "Renomear Grupo",
|
||||
"From Directory": "Do Diretório",
|
||||
"Successfully imported {{count}} book(s)_one": "Importado com sucesso 1 livro",
|
||||
"Successfully imported {{count}} book(s)_many": "Importados com sucesso {{count}} livros",
|
||||
"Successfully imported {{count}} book(s)_other": "Importados com sucesso {{count}} livros",
|
||||
"Count": "Contagem",
|
||||
"Start Page": "Página Inicial",
|
||||
"Search in OPDS Catalog...": "Pesquisar no Catálogo OPDS...",
|
||||
"Please log in to use advanced TTS features": "Por favor, faça login para usar recursos avançados de TTS",
|
||||
"Word limit of 30 words exceeded.": "O limite de 30 palavras foi excedido.",
|
||||
"Proofread": "Revisão",
|
||||
"Current selection": "Seleção atual",
|
||||
"All occurrences in this book": "Todas as ocorrências neste livro",
|
||||
"All occurrences in your library": "Todas as ocorrências na sua biblioteca",
|
||||
"Selected text:": "Texto selecionado:",
|
||||
"Replace with:": "Substituir por:",
|
||||
"Enter text...": "Introduzir texto…",
|
||||
"Case sensitive:": "Diferenciar maiúsculas e minúsculas:",
|
||||
"Scope:": "Âmbito:",
|
||||
"Selection": "Seleção",
|
||||
"Library": "Biblioteca",
|
||||
"Yes": "Sim",
|
||||
"No": "Não",
|
||||
"Proofread Replacement Rules": "Regras de substituição da revisão",
|
||||
"Selected Text Rules": "Regras do texto selecionado",
|
||||
"No selected text replacement rules": "Não existem regras de substituição para o texto selecionado",
|
||||
"Book Specific Rules": "Regras específicas do livro",
|
||||
"No book-level replacement rules": "Não existem regras de substituição ao nível do livro",
|
||||
"Disable Quick Action": "Desativar Ação Rápida",
|
||||
"Enable Quick Action on Selection": "Ativar Ação Rápida na Seleção",
|
||||
"None": "Nenhum",
|
||||
"Annotation Tools": "Ferramentas de Anotação",
|
||||
"Enable Quick Actions": "Ativar Ações Rápidas",
|
||||
"Quick Action": "Ação Rápida",
|
||||
"Copy to Notebook": "Copiar para o Caderno",
|
||||
"Copy text after selection": "Copiar texto após a seleção",
|
||||
"Highlight text after selection": "Destacar texto após a seleção",
|
||||
"Annotate text after selection": "Anotar texto após a seleção",
|
||||
"Search text after selection": "Pesquisar texto após a seleção",
|
||||
"Look up text in dictionary after selection": "Procurar texto no dicionário após a seleção",
|
||||
"Look up text in Wikipedia after selection": "Procurar texto na Wikipedia após a seleção",
|
||||
"Translate text after selection": "Traduzir texto após a seleção",
|
||||
"Read text aloud after selection": "Ler texto em voz alta após a seleção",
|
||||
"Proofread text after selection": "Revisar texto após a seleção",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} ativos, {{pendingCount}} pendentes",
|
||||
"{{failedCount}} failed": "{{failedCount}} falharam",
|
||||
"Waiting...": "Aguardando...",
|
||||
"Failed": "Falhou",
|
||||
"Completed": "Concluído",
|
||||
"Cancelled": "Cancelado",
|
||||
"Retry": "Tentar novamente",
|
||||
"Active": "Ativos",
|
||||
"Transfer Queue": "Fila de Transferências",
|
||||
"Upload All": "Enviar Todos",
|
||||
"Download All": "Baixar Todos",
|
||||
"Resume Transfers": "Retomar Transferências",
|
||||
"Pause Transfers": "Pausar Transferências",
|
||||
"Pending": "Pendentes",
|
||||
"No transfers": "Sem transferências",
|
||||
"Retry All": "Tentar Todos Novamente",
|
||||
"Clear Completed": "Limpar Concluídos",
|
||||
"Clear Failed": "Limpar Falhos",
|
||||
"Upload queued: {{title}}": "Upload na fila: {{title}}",
|
||||
"Download queued: {{title}}": "Download na fila: {{title}}",
|
||||
"Book not found in library": "Livro não encontrado na biblioteca",
|
||||
"Unknown error": "Erro desconhecido",
|
||||
"Please log in to continue": "Faça login para continuar",
|
||||
"Cloud File Transfers": "Transferências de Arquivos",
|
||||
"Show Search Results": "Mostrar resultados",
|
||||
"Search results for '{{term}}'": "Resultados para '{{term}}'",
|
||||
"Close Search": "Fechar pesquisa",
|
||||
"Previous Result": "Resultado anterior",
|
||||
"Next Result": "Próximo resultado",
|
||||
"Bookmarks": "Marcadores",
|
||||
"Annotations": "Anotações",
|
||||
"Show Results": "Mostrar resultados",
|
||||
"Clear search": "Limpar pesquisa",
|
||||
"Clear search history": "Limpar histórico de pesquisa",
|
||||
"Quick action disabled": "Ação rápida desativada",
|
||||
"Tap to Toggle Footer": "Toque para alternar rodapé",
|
||||
"Exported successfully": "Exportado com sucesso",
|
||||
"Book exported successfully.": "Livro exportado com sucesso.",
|
||||
"Failed to export the book.": "Falha ao exportar o livro.",
|
||||
"Export Book": "Exportar livro",
|
||||
"Whole word:": "Palavra inteira:",
|
||||
"Error": "Erro",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Não foi possível carregar o artigo. Tente pesquisar diretamente em {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Não foi possível carregar a palavra. Tente pesquisar diretamente em {{link}}.",
|
||||
"Date Published": "Data de publicação",
|
||||
"Only for TTS:": "Apenas para TTS:",
|
||||
"Uploaded": "Enviado",
|
||||
"Downloaded": "Baixado",
|
||||
"Deleted": "Excluído",
|
||||
"Note:": "Nota:",
|
||||
"Time:": "Hora:",
|
||||
"Format Options": "Opções de formato",
|
||||
"Export Date": "Data de exportação",
|
||||
"Chapter Titles": "Títulos dos capítulos",
|
||||
"Chapter Separator": "Separador de capítulos",
|
||||
"Highlights": "Destaques",
|
||||
"Note Date": "Data da nota",
|
||||
"Advanced": "Avançado",
|
||||
"Hide": "Ocultar",
|
||||
"Show": "Mostrar",
|
||||
"Use Custom Template": "Usar modelo personalizado",
|
||||
"Export Template": "Modelo de exportação",
|
||||
"Template Syntax:": "Sintaxe do modelo:",
|
||||
"Insert value": "Inserir valor",
|
||||
"Format date (locale)": "Formatar data (local)",
|
||||
"Format date (custom)": "Formatar data (personalizado)",
|
||||
"Conditional": "Condicional",
|
||||
"Loop": "Loop",
|
||||
"Available Variables:": "Variáveis disponíveis:",
|
||||
"Book title": "Título do livro",
|
||||
"Book author": "Autor do livro",
|
||||
"Export date": "Data de exportação",
|
||||
"Array of chapters": "Lista de capítulos",
|
||||
"Chapter title": "Título do capítulo",
|
||||
"Array of annotations": "Lista de anotações",
|
||||
"Highlighted text": "Texto destacado",
|
||||
"Annotation note": "Nota de anotação",
|
||||
"Update time": "Hora de atualização",
|
||||
"Date Format Tokens:": "Tokens de formato de data:",
|
||||
"Year (4 digits)": "Ano (4 dígitos)",
|
||||
"Month (01-12)": "Mês (01-12)",
|
||||
"Day (01-31)": "Dia (01-31)",
|
||||
"Hour (00-23)": "Hora (00-23)",
|
||||
"Minute (00-59)": "Minuto (00-59)",
|
||||
"Second (00-59)": "Segundo (00-59)",
|
||||
"Show Source": "Mostrar fonte",
|
||||
"No content to preview": "Sem conteúdo para visualizar",
|
||||
"Export": "Exportar",
|
||||
"Set Timeout": "Definir tempo limite",
|
||||
"Select Voice": "Selecionar voz",
|
||||
"Toggle Sticky Bottom TTS Bar": "Alternar barra TTS fixada",
|
||||
"Display what I'm reading on Discord": "Mostrar o que estou lendo no Discord",
|
||||
"Show on Discord": "Mostrar no Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Токен",
|
||||
"Your OTP token": "Ваш OTP токен",
|
||||
"Verify token": "Проверить токен",
|
||||
"Sign in with Google": "Войти через Google",
|
||||
"Sign in with Apple": "Войти через Apple",
|
||||
"Sign in with GitHub": "Войти через GitHub",
|
||||
"Account": "Аккаунт",
|
||||
"Failed to delete user. Please try again later.": "Не удалось удалить пользователя. Пожалуйста, повторите попытку позже.",
|
||||
"Community Support": "Поддержка сообщества",
|
||||
@@ -349,8 +346,8 @@
|
||||
"{{count}} pages left in chapter_many": "Осталось {{count}} страниц в главе",
|
||||
"{{count}} pages left in chapter_other": "Осталось {{count}} страниц в главе",
|
||||
"Show Remaining Pages": "Показать оставшиеся страницы",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Управление подпиской",
|
||||
"Coming Soon": "Скоро будет",
|
||||
@@ -707,9 +704,7 @@
|
||||
"Validating...": "Проверка...",
|
||||
"View All": "Посмотреть все",
|
||||
"Forward": "Вперёд",
|
||||
"OPDS Catalog": "Каталог OPDS",
|
||||
"Home": "Главная",
|
||||
"Library": "Библиотека",
|
||||
"{{count}} items_one": "{{count}} элемент",
|
||||
"{{count}} items_few": "{{count}} элемента",
|
||||
"{{count}} items_many": "{{count}} элементов",
|
||||
@@ -731,5 +726,189 @@
|
||||
"Last": "Последняя",
|
||||
"Cannot Load Page": "Не удалось загрузить страницу",
|
||||
"An error occurred": "Произошла ошибка",
|
||||
"Online Library": "Онлайн библиотека"
|
||||
"Online Library": "Онлайн библиотека",
|
||||
"URL must start with http:// or https://": "URL должен начинаться с http:// или https://",
|
||||
"Title, Author, Tag, etc...": "Название, Автор, Тег и т.д...",
|
||||
"Query": "Запрос",
|
||||
"Subject": "Тема",
|
||||
"Enter {{terms}}": "Введите {{terms}}",
|
||||
"No search results found": "Результаты поиска не найдены",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Не удалось загрузить ленту OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Поиск в {{title}}",
|
||||
"Manage Storage": "Управление хранилищем",
|
||||
"Failed to load files": "Не удалось загрузить файлы",
|
||||
"Deleted {{count}} file(s)_one": "Удалён {{count}} файл",
|
||||
"Deleted {{count}} file(s)_few": "Удалено {{count}} файла",
|
||||
"Deleted {{count}} file(s)_many": "Удалено {{count}} файлов",
|
||||
"Deleted {{count}} file(s)_other": "Удалён {{count}} файл",
|
||||
"Failed to delete {{count}} file(s)_one": "Не удалось удалить {{count}} файл",
|
||||
"Failed to delete {{count}} file(s)_few": "Не удалось удалить {{count}} файла",
|
||||
"Failed to delete {{count}} file(s)_many": "Не удалось удалить {{count}} файлов",
|
||||
"Failed to delete {{count}} file(s)_other": "Не удалось удалить {{count}} файл",
|
||||
"Failed to delete files": "Не удалось удалить файлы",
|
||||
"Total Files": "Всего файлов",
|
||||
"Total Size": "Общий размер",
|
||||
"Quota": "Квота",
|
||||
"Used": "Использовано",
|
||||
"Files": "Файлы",
|
||||
"Search files...": "Поиск файлов...",
|
||||
"Newest First": "Сначала новые",
|
||||
"Oldest First": "Сначала старые",
|
||||
"Largest First": "Сначала крупные",
|
||||
"Smallest First": "Сначала мелкие",
|
||||
"Name A-Z": "Имя A-Z",
|
||||
"Name Z-A": "Имя Z-A",
|
||||
"{{count}} selected_one": "{{count}} выбранный",
|
||||
"{{count}} selected_few": "{{count}} выбранных",
|
||||
"{{count}} selected_many": "{{count}} выбранных",
|
||||
"{{count}} selected_other": "{{count}} выбранный",
|
||||
"Delete Selected": "Удалить выбранные",
|
||||
"Created": "Создано",
|
||||
"No files found": "Файлы не найдены",
|
||||
"No files uploaded yet": "Файлы ещё не загружены",
|
||||
"files": "файлы",
|
||||
"Page {{current}} of {{total}}": "Страница {{current}} из {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Вы уверены, что хотите удалить {{count}} выбранный файл?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_few": "Вы уверены, что хотите удалить {{count}} выбранных файла?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Вы уверены, что хотите удалить {{count}} выбранных файлов?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Вы уверены, что хотите удалить {{count}} выбранный файл?",
|
||||
"Cloud Storage Usage": "Использование облачного хранилища",
|
||||
"Rename Group": "Переименовать группу",
|
||||
"From Directory": "Из каталога",
|
||||
"Successfully imported {{count}} book(s)_one": "Успешно импортирован 1 книга",
|
||||
"Successfully imported {{count}} book(s)_few": "Успешно импортировано {{count}} книги",
|
||||
"Successfully imported {{count}} book(s)_many": "Успешно импортировано {{count}} книг",
|
||||
"Successfully imported {{count}} book(s)_other": "Успешно импортировано {{count}} книг",
|
||||
"Count": "Количество",
|
||||
"Start Page": "Начальная страница",
|
||||
"Search in OPDS Catalog...": "Поиск в каталоге OPDS...",
|
||||
"Please log in to use advanced TTS features": "Пожалуйста, войдите в систему, чтобы использовать расширенные функции TTS",
|
||||
"Word limit of 30 words exceeded.": "Превышен лимит в 30 слов.",
|
||||
"Proofread": "Корректура",
|
||||
"Current selection": "Текущее выделение",
|
||||
"All occurrences in this book": "Все вхождения в этой книге",
|
||||
"All occurrences in your library": "Все вхождения в вашей библиотеке",
|
||||
"Selected text:": "Выделенный текст:",
|
||||
"Replace with:": "Заменить на:",
|
||||
"Enter text...": "Введите текст…",
|
||||
"Case sensitive:": "Учитывать регистр:",
|
||||
"Scope:": "Область применения:",
|
||||
"Selection": "Выделение",
|
||||
"Library": "Библиотека",
|
||||
"Yes": "Да",
|
||||
"No": "Нет",
|
||||
"Proofread Replacement Rules": "Правила замены для корректуры",
|
||||
"Selected Text Rules": "Правила для выделенного текста",
|
||||
"No selected text replacement rules": "Нет правил замены для выделенного текста",
|
||||
"Book Specific Rules": "Правила для конкретной книги",
|
||||
"No book-level replacement rules": "Нет правил замены на уровне книги",
|
||||
"Disable Quick Action": "Отключить быстрые действия",
|
||||
"Enable Quick Action on Selection": "Включить быстрые действия при выборе",
|
||||
"None": "Нет",
|
||||
"Annotation Tools": "Инструменты для аннотаций",
|
||||
"Enable Quick Actions": "Включить быстрые действия",
|
||||
"Quick Action": "Быстрое действие",
|
||||
"Copy to Notebook": "Скопировать в блокнот",
|
||||
"Copy text after selection": "Копировать текст после выделения",
|
||||
"Highlight text after selection": "Выделить текст после выделения",
|
||||
"Annotate text after selection": "Аннотировать текст после выделения",
|
||||
"Search text after selection": "Искать текст после выделения",
|
||||
"Look up text in dictionary after selection": "Искать текст в словаре после выделения",
|
||||
"Look up text in Wikipedia after selection": "Искать текст в Википедии после выделения",
|
||||
"Translate text after selection": "Перевести текст после выделения",
|
||||
"Read text aloud after selection": "Прочитать текст вслух после выделения",
|
||||
"Proofread text after selection": "Корректировать текст после выделения",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} активных, {{pendingCount}} ожидающих",
|
||||
"{{failedCount}} failed": "{{failedCount}} неудачных",
|
||||
"Waiting...": "Ожидание...",
|
||||
"Failed": "Ошибка",
|
||||
"Completed": "Завершено",
|
||||
"Cancelled": "Отменено",
|
||||
"Retry": "Повторить",
|
||||
"Active": "Активные",
|
||||
"Transfer Queue": "Очередь передачи",
|
||||
"Upload All": "Загрузить все",
|
||||
"Download All": "Скачать все",
|
||||
"Resume Transfers": "Возобновить передачу",
|
||||
"Pause Transfers": "Приостановить передачу",
|
||||
"Pending": "Ожидающие",
|
||||
"No transfers": "Нет передач",
|
||||
"Retry All": "Повторить все",
|
||||
"Clear Completed": "Очистить завершенные",
|
||||
"Clear Failed": "Очистить неудачные",
|
||||
"Upload queued: {{title}}": "Загрузка в очереди: {{title}}",
|
||||
"Download queued: {{title}}": "Скачивание в очереди: {{title}}",
|
||||
"Book not found in library": "Книга не найдена в библиотеке",
|
||||
"Unknown error": "Неизвестная ошибка",
|
||||
"Please log in to continue": "Войдите, чтобы продолжить",
|
||||
"Cloud File Transfers": "Передача файлов в облако",
|
||||
"Show Search Results": "Показать результаты",
|
||||
"Search results for '{{term}}'": "Результаты для «{{term}}»",
|
||||
"Close Search": "Закрыть поиск",
|
||||
"Previous Result": "Предыдущий результат",
|
||||
"Next Result": "Следующий результат",
|
||||
"Bookmarks": "Закладки",
|
||||
"Annotations": "Аннотации",
|
||||
"Show Results": "Показать результаты",
|
||||
"Clear search": "Очистить поиск",
|
||||
"Clear search history": "Очистить историю поиска",
|
||||
"Quick action disabled": "Быстрое действие отключено",
|
||||
"Tap to Toggle Footer": "Нажмите для переключения нижнего колонтитула",
|
||||
"Exported successfully": "Успешно экспортировано",
|
||||
"Book exported successfully.": "Книга успешно экспортирована.",
|
||||
"Failed to export the book.": "Не удалось экспортировать книгу.",
|
||||
"Export Book": "Экспорт книги",
|
||||
"Whole word:": "Слово целиком:",
|
||||
"Error": "Ошибка",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Не удалось загрузить статью. Попробуйте искать напрямую на {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Не удалось загрузить слово. Попробуйте искать напрямую на {{link}}.",
|
||||
"Date Published": "Дата публикации",
|
||||
"Only for TTS:": "Только для TTS:",
|
||||
"Uploaded": "Загружено",
|
||||
"Downloaded": "Скачано",
|
||||
"Deleted": "Удалено",
|
||||
"Note:": "Примечание:",
|
||||
"Time:": "Время:",
|
||||
"Format Options": "Параметры формата",
|
||||
"Export Date": "Дата экспорта",
|
||||
"Chapter Titles": "Названия глав",
|
||||
"Chapter Separator": "Разделитель глав",
|
||||
"Highlights": "Выделения",
|
||||
"Note Date": "Дата примечания",
|
||||
"Advanced": "Дополнительно",
|
||||
"Hide": "Скрыть",
|
||||
"Show": "Показать",
|
||||
"Use Custom Template": "Использовать свой шаблон",
|
||||
"Export Template": "Шаблон экспорта",
|
||||
"Template Syntax:": "Синтаксис шаблона:",
|
||||
"Insert value": "Вставить значение",
|
||||
"Format date (locale)": "Форматировать дату (локаль)",
|
||||
"Format date (custom)": "Форматировать дату (свой)",
|
||||
"Conditional": "Условный",
|
||||
"Loop": "Цикл",
|
||||
"Available Variables:": "Доступные переменные:",
|
||||
"Book title": "Название книги",
|
||||
"Book author": "Автор книги",
|
||||
"Export date": "Дата экспорта",
|
||||
"Array of chapters": "Массив глав",
|
||||
"Chapter title": "Название главы",
|
||||
"Array of annotations": "Массив примечаний",
|
||||
"Highlighted text": "Выделенный текст",
|
||||
"Annotation note": "Примечание аннотации",
|
||||
"Update time": "Время обновления",
|
||||
"Date Format Tokens:": "Токены формата даты:",
|
||||
"Year (4 digits)": "Год (4 цифры)",
|
||||
"Month (01-12)": "Месяц (01-12)",
|
||||
"Day (01-31)": "День (01-31)",
|
||||
"Hour (00-23)": "Час (00-23)",
|
||||
"Minute (00-59)": "Минута (00-59)",
|
||||
"Second (00-59)": "Секунда (00-59)",
|
||||
"Show Source": "Показать источник",
|
||||
"No content to preview": "Нет содержимого для просмотра",
|
||||
"Export": "Экспортировать",
|
||||
"Set Timeout": "Установить тайм-аут",
|
||||
"Select Voice": "Выбрать голос",
|
||||
"Toggle Sticky Bottom TTS Bar": "Переключить закреплённую панель TTS",
|
||||
"Display what I'm reading on Discord": "Показывать книгу в Discord",
|
||||
"Show on Discord": "Показать в Discord"
|
||||
}
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
"Token": "ටෝකනය",
|
||||
"Your OTP token": "ඔබේ OTP ටෝකනය",
|
||||
"Verify token": "ටෝකනය තහවුරු කරන්න",
|
||||
"Sign in with Google": "Google සමඟ ඇතුල් වන්න",
|
||||
"Sign in with Apple": "Apple සමඟ ඇතුල් වන්න",
|
||||
"Sign in with GitHub": "GitHub සමඟ ඇතුල් වන්න",
|
||||
"New Password": "නව මුරපදය",
|
||||
"Your new password": "ඔබේ නව මුරපදය",
|
||||
"Update password": "මුරපදය යාවත්කාලීන කරන්න",
|
||||
@@ -488,8 +485,8 @@
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Source Han Serif CN VF": "Source Han Serif CN VF",
|
||||
"Huiwen-mincho": "Huiwen-mincho",
|
||||
"Source Han Serif CN": "Source Han Serif CN",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa_OldSong",
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "තහවුරු කරමින්...",
|
||||
"View All": "සියල්ල බලන්න",
|
||||
"Forward": "ඉදිරියට",
|
||||
"OPDS Catalog": "OPDS දත්තසමුදා",
|
||||
"Home": "මුල් පිටුව",
|
||||
"Library": "පුස්තකාලය",
|
||||
"{{count}} items_one": "{{count}} අයිතමය",
|
||||
"{{count}} items_other": "{{count}} අයිතම",
|
||||
"Download completed": "බාගත කිරීම සම්පූර්ණයි",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "අවසාන",
|
||||
"Cannot Load Page": "පිටුවට ප්රවේශ විය නොහැක",
|
||||
"An error occurred": "දෝෂයක් සිදු විය",
|
||||
"Online Library": "ඔන්ලයින් පුස්තකාලය"
|
||||
"Online Library": "ඔන්ලයින් පුස්තකාලය",
|
||||
"URL must start with http:// or https://": "URL එක http:// හෝ https:// සමඟ ආරම්භ විය යුතුය",
|
||||
"Title, Author, Tag, etc...": "ශීර්ෂය, කතුවරයා, ටැග්, ආදිය...",
|
||||
"Query": "විමසුම",
|
||||
"Subject": "විෂය",
|
||||
"Enter {{terms}}": "{{terms}} ඇතුළත් කරන්න",
|
||||
"No search results found": "සෙවුම් ප්රතිඵල නොමැත",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ආහාරය පූරණය කිරීමට අසමත් විය: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} තුළ සෙවීම",
|
||||
"Manage Storage": "ගබඩා කළමනාකරණය කරන්න",
|
||||
"Failed to load files": "ගොනු උඩුගත කිරීමට නොහැකි විය",
|
||||
"Deleted {{count}} file(s)_one": "ගොනුව මකා දමා ඇත",
|
||||
"Deleted {{count}} file(s)_other": "ගොනු මකා දමා ඇත",
|
||||
"Failed to delete {{count}} file(s)_one": "ගොනුව මකා දැමිය නොහැකි විය",
|
||||
"Failed to delete {{count}} file(s)_other": "ගොනු මකා දැමිය නොහැකි විය",
|
||||
"Failed to delete files": "ගොනු මකා දැමිය නොහැකි විය",
|
||||
"Total Files": "මුළු ගොනු",
|
||||
"Total Size": "මුළු ප්රමාණය",
|
||||
"Quota": "කොටස",
|
||||
"Used": "භාවිතා කරන ලදී",
|
||||
"Files": "ගොනු",
|
||||
"Search files...": "ගොනු සොයන්න...",
|
||||
"Newest First": "නවතම පළමුව",
|
||||
"Oldest First": "පැරණිම පළමුව",
|
||||
"Largest First": "විශාලතම පළමුව",
|
||||
"Smallest First": "කුඩාතම පළමුව",
|
||||
"Name A-Z": "නම A-Z",
|
||||
"Name Z-A": "නම Z-A",
|
||||
"{{count}} selected_one": "තෝරාගත් ගොනුව",
|
||||
"{{count}} selected_other": "තෝරාගත් ගොනු",
|
||||
"Delete Selected": "තෝරාගත් මකා දමන්න",
|
||||
"Created": "තනන ලදී",
|
||||
"No files found": "ගොනු හමු නොවීය",
|
||||
"No files uploaded yet": "ගොනු තවම උඩුගත කර නැත",
|
||||
"files": "ගොනු",
|
||||
"Page {{current}} of {{total}}": "පිටුව {{current}} / {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "තෝරාගත් ගොනුව මකන්නට විශ්වාසද?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "තෝරාගත් ගොනු මකන්නට විශ්වාසද?",
|
||||
"Cloud Storage Usage": "කලාප ගබඩා භාවිතය",
|
||||
"Rename Group": "කණ්ඩායම නැවත නම් කරන්න",
|
||||
"From Directory": "ෆෝල්ඩරයෙන්",
|
||||
"Successfully imported {{count}} book(s)_one": "සාර්ථකව ආයාත කළ 1 පොත",
|
||||
"Successfully imported {{count}} book(s)_other": "සාර්ථකව ආයාත කළ {{count}} පොත්",
|
||||
"Count": "ගණන",
|
||||
"Start Page": "ආරම්භක පිටුව",
|
||||
"Search in OPDS Catalog...": "OPDS දත්තසමුදා තුළ සෙවීම...",
|
||||
"Please log in to use advanced TTS features": "උසස් TTS විශේෂාංග භාවිතා කිරීමට කරුණාකර පිවිසෙන්න",
|
||||
"Word limit of 30 words exceeded.": "වචන 30ක සීමාව ඉක්මවා ඇත.",
|
||||
"Proofread": "සංශෝධනය",
|
||||
"Current selection": "වත්මන් තේරීම",
|
||||
"All occurrences in this book": "මෙම පොත තුළ ඇති සියලුම පෙනීම්",
|
||||
"All occurrences in your library": "ඔබගේ පුස්තකාලයේ ඇති සියලුම පෙනීම්",
|
||||
"Selected text:": "තේරූ පෙළ:",
|
||||
"Replace with:": "මෙයින් ප්රතිස්ථාපනය කරන්න:",
|
||||
"Enter text...": "පෙළ ඇතුළත් කරන්න…",
|
||||
"Case sensitive:": "අකුරු විශාල/කුඩා භේදය සලකා බලන්න:",
|
||||
"Scope:": "පරාසය:",
|
||||
"Selection": "තේරීම",
|
||||
"Library": "පුස්තකාලය",
|
||||
"Yes": "ඔව්",
|
||||
"No": "නැහැ",
|
||||
"Proofread Replacement Rules": "සංශෝධන ප්රතිස්ථාපන නියමයන්",
|
||||
"Selected Text Rules": "තේරූ පෙළ සඳහා නියමයන්",
|
||||
"No selected text replacement rules": "තේරූ පෙළ සඳහා ප්රතිස්ථාපන නියමයන් නොමැත",
|
||||
"Book Specific Rules": "පොතට විශේෂිත නියමයන්",
|
||||
"No book-level replacement rules": "පොත් මට්ටමේ ප්රතිස්ථාපන නියමයන් නොමැත",
|
||||
"Disable Quick Action": "තට්ටු ක්රියාව අක්රිය කරන්න",
|
||||
"Enable Quick Action on Selection": "තෝරා ගැනීමේදී තට්ටු ක්රියාව සක්රීය කරන්න",
|
||||
"None": "කිසිවක් නැත",
|
||||
"Annotation Tools": "සටහන් මෙවලම්",
|
||||
"Enable Quick Actions": "තට්ටු ක්රියාවන් සක්රීය කරන්න",
|
||||
"Quick Action": "තට්ටු ක්රියාව",
|
||||
"Copy to Notebook": "සටහන් පොතට පිටපත් කරන්න",
|
||||
"Copy text after selection": "තේරීමෙන් පසු පෙළ පිටපත් කරන්න",
|
||||
"Highlight text after selection": "තේරීමෙන් පසු පෙළ හයිලයිට් කරන්න",
|
||||
"Annotate text after selection": "තේරීමෙන් පසු පෙළ සටහන් කරන්න",
|
||||
"Search text after selection": "තේරීමෙන් පසු පෙළ සෙවීම",
|
||||
"Look up text in dictionary after selection": "තේරීමෙන් පසු ශබ්දකෝෂයේ පෙළ සෙවීම",
|
||||
"Look up text in Wikipedia after selection": "තේරීමෙන් පසු විකිපීඩියාවේ පෙළ සෙවීම",
|
||||
"Translate text after selection": "තේරීමෙන් පසු පෙළ පරිවර්තනය කරන්න",
|
||||
"Read text aloud after selection": "තේරීමෙන් පසු පෙළ උච්චාරණය කරන්න",
|
||||
"Proofread text after selection": "තේරීමෙන් පසු පෙළ සංශෝධනය කරන්න",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} සක්රීය, {{pendingCount}} බලාපොරොත්තුවේ",
|
||||
"{{failedCount}} failed": "{{failedCount}} අසාර්ථක",
|
||||
"Waiting...": "බලාපොරොත්තුවේ...",
|
||||
"Failed": "අසාර්ථකයි",
|
||||
"Completed": "සම්පූර්ණයි",
|
||||
"Cancelled": "අවලංගුයි",
|
||||
"Retry": "නැවත උත්සාහ කරන්න",
|
||||
"Active": "සක්රීය",
|
||||
"Transfer Queue": "හුවමාරු පෝලිම",
|
||||
"Upload All": "සියල්ල උඩුගත කරන්න",
|
||||
"Download All": "සියල්ල බාගන්න",
|
||||
"Resume Transfers": "හුවමාරු නැවත ආරම්භ කරන්න",
|
||||
"Pause Transfers": "හුවමාරු විරාම කරන්න",
|
||||
"Pending": "බලාපොරොත්තුවේ",
|
||||
"No transfers": "හුවමාරු නැත",
|
||||
"Retry All": "සියල්ල නැවත උත්සාහ කරන්න",
|
||||
"Clear Completed": "සම්පූර්ණ කළ ඒවා මකන්න",
|
||||
"Clear Failed": "අසාර්ථක ඒවා මකන්න",
|
||||
"Upload queued: {{title}}": "උඩුගත කිරීම පෝලිමේ: {{title}}",
|
||||
"Download queued: {{title}}": "බාගත කිරීම පෝලිමේ: {{title}}",
|
||||
"Book not found in library": "පුස්තකාලයේ පොත හමු නොවීය",
|
||||
"Unknown error": "නොදන්නා දෝෂයක්",
|
||||
"Please log in to continue": "ඉදිරියට යාමට පිවිසෙන්න",
|
||||
"Cloud File Transfers": "කලාප ගොනු හුවමාරු",
|
||||
"Show Search Results": "සෙවුම් ප්රතිඵල පෙන්වන්න",
|
||||
"Search results for '{{term}}'": "'{{term}}' සඳහා ප්රතිඵල",
|
||||
"Close Search": "සෙවුම වසන්න",
|
||||
"Previous Result": "පෙර ප්රතිඵලය",
|
||||
"Next Result": "ඊළඟ ප්රතිඵලය",
|
||||
"Bookmarks": "පොත් සලකුණු",
|
||||
"Annotations": "අනුසටහන්",
|
||||
"Show Results": "ප්රතිඵල පෙන්වන්න",
|
||||
"Clear search": "සෙවුම හිස් කරන්න",
|
||||
"Clear search history": "සෙවුම් ඉතිහාසය හිස් කරන්න",
|
||||
"Quick action disabled": "ඉක්මන් ක්රියාව අක්රීයයි",
|
||||
"Tap to Toggle Footer": "පාදකය ටොගල් කිරීමට තට්ටු කරන්න",
|
||||
"Exported successfully": "සාර්ථකව අපනයනය කරන ලදී",
|
||||
"Book exported successfully.": "පොත සාර්ථකව අපනයනය කරන ලදී.",
|
||||
"Failed to export the book.": "පොත අපනයනය කිරීමට අසමත් විය.",
|
||||
"Export Book": "පොත අපනයනය කරන්න",
|
||||
"Whole word:": "සම්පූර්ණ වචනය:",
|
||||
"Error": "දෝෂය",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "ලිපිය පූරණය කළ නොහැක. {{link}} හි කෙලින්ම සෙවීමට උත්සාහ කරන්න.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "වචනය පූරණය කළ නොහැක. {{link}} හි කෙලින්ම සෙවීමට උත්සාහ කරන්න.",
|
||||
"Date Published": "ප්රකාශන දිනය",
|
||||
"Only for TTS:": "TTS සඳහා පමණක්:",
|
||||
"Uploaded": "උඩුගත කරන ලදී",
|
||||
"Downloaded": "බාගත කරන ලදී",
|
||||
"Deleted": "මකා දමන ලදී",
|
||||
"Note:": "සටහන:",
|
||||
"Time:": "කාලය:",
|
||||
"Format Options": "ආකෘති විකල්ප",
|
||||
"Export Date": "අපනයන දිනය",
|
||||
"Chapter Titles": "කොටස් මාතෘකා",
|
||||
"Chapter Separator": "කොටස් වෙන්කරු",
|
||||
"Highlights": "ඉස්මතු කිරීම්",
|
||||
"Note Date": "සටහන දිනය",
|
||||
"Advanced": "උසස්",
|
||||
"Hide": "සඟවන්න",
|
||||
"Show": "පෙන්වන්න",
|
||||
"Use Custom Template": "අභිරුචි සැකිල්ල භාවිතා කරන්න",
|
||||
"Export Template": "අපනයන සැකිල්ල",
|
||||
"Template Syntax:": "සැකිල්ලේ ව්යවස්ථාව:",
|
||||
"Insert value": "වටිනාකම ඇතුළත් කරන්න",
|
||||
"Format date (locale)": "දිනය ආකෘතිය (දේශීය)",
|
||||
"Format date (custom)": "අභිරුචි දිනය ආකෘතිය",
|
||||
"Conditional": "ශර්තමය",
|
||||
"Loop": "ලූප්",
|
||||
"Available Variables:": "ලබා ගත හැකි විචල්ය:",
|
||||
"Book title": "පොත් මාතෘකාව",
|
||||
"Book author": "පොත් රචකයා",
|
||||
"Export date": "අපනයන දිනය",
|
||||
"Array of chapters": "කොටස් අරය",
|
||||
"Chapter title": "කොටස් මාතෘකාව",
|
||||
"Array of annotations": "අනුසටහන් අරය",
|
||||
"Highlighted text": "ඉස්මතු කළ පෙළ",
|
||||
"Annotation note": "අනුසටහන් සටහන",
|
||||
"Update time": "යාවත්කාලීන කාලය",
|
||||
"Date Format Tokens:": "දිනය ආකෘති ටෝකන:",
|
||||
"Year (4 digits)": "වසර (අංක 4)",
|
||||
"Month (01-12)": "මාසය (01-12)",
|
||||
"Day (01-31)": "දින (01-31)",
|
||||
"Hour (00-23)": "පැය (00-23)",
|
||||
"Minute (00-59)": "මිනිත්තු (00-59)",
|
||||
"Second (00-59)": "තත්පර (00-59)",
|
||||
"Show Source": "මූලාශ්රය පෙන්වන්න",
|
||||
"No content to preview": "පෙරදසුන සඳහා අන්තර්ගතයක් නැත",
|
||||
"Export": "අපනයනය කරන්න",
|
||||
"Set Timeout": "කාල සීමාව සකසන්න",
|
||||
"Select Voice": "හඬ තෝරන්න",
|
||||
"Toggle Sticky Bottom TTS Bar": "ඇලවූ TTS තීරුව මාරු කරන්න",
|
||||
"Display what I'm reading on Discord": "Discord හි කියවන පොත පෙන්වන්න",
|
||||
"Show on Discord": "Discord හි පෙන්වන්න"
|
||||
}
|
||||
|
||||
@@ -25,9 +25,6 @@
|
||||
"Your OTP token": "Din OTP-token",
|
||||
"Verify token": "Verifiera token",
|
||||
"Go Back": "Tillbaka",
|
||||
"Sign in with Google": "Logga in med Google",
|
||||
"Sign in with Apple": "Logga in med Apple",
|
||||
"Sign in with GitHub": "Logga in med GitHub",
|
||||
"New Password": "Nytt lösenord",
|
||||
"Your new password": "Ditt nya lösenord",
|
||||
"Update password": "Uppdatera lösenord",
|
||||
@@ -573,8 +570,8 @@
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Source Han Serif CN VF": "Source Han Serif CN VF",
|
||||
"Huiwen-mincho": "Huiwen-mincho",
|
||||
"Source Han Serif CN": "Source Han Serif CN",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa_OldSong",
|
||||
"Google Books": "Google Böcker",
|
||||
"Open Library": "Open Library",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "Verifierar...",
|
||||
"View All": "Visa alla",
|
||||
"Forward": "Framåt",
|
||||
"OPDS Catalog": "OPDS-katalog",
|
||||
"Home": "Start",
|
||||
"Library": "Bibliotek",
|
||||
"{{count}} items_one": "{{count}} objekt",
|
||||
"{{count}} items_other": "{{count}} objekt",
|
||||
"Download completed": "Nedladdning klar",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "Sista",
|
||||
"Cannot Load Page": "Kan inte ladda sidan",
|
||||
"An error occurred": "Ett fel uppstod",
|
||||
"Online Library": "Onlinebibliotek"
|
||||
"Online Library": "Onlinebibliotek",
|
||||
"URL must start with http:// or https://": "URL måste börja med http:// eller https://",
|
||||
"Title, Author, Tag, etc...": "Titel, författare, tagg, etc...",
|
||||
"Query": "Fråga",
|
||||
"Subject": "Ämne",
|
||||
"Enter {{terms}}": "Ange {{terms}}",
|
||||
"No search results found": "Inga sökresultat hittades",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Misslyckades med att ladda OPDS-flöde: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Sök i {{title}}",
|
||||
"Manage Storage": "Hantera lagring",
|
||||
"Failed to load files": "Misslyckades att ladda filer",
|
||||
"Deleted {{count}} file(s)_one": "Raderade filen",
|
||||
"Deleted {{count}} file(s)_other": "Raderade filerna",
|
||||
"Failed to delete {{count}} file(s)_one": "Kunde inte radera filen",
|
||||
"Failed to delete {{count}} file(s)_other": "Kunde inte radera filerna",
|
||||
"Failed to delete files": "Kunde inte radera filer",
|
||||
"Total Files": "Totalt antal filer",
|
||||
"Total Size": "Total storlek",
|
||||
"Quota": "Kvot",
|
||||
"Used": "Använd",
|
||||
"Files": "Filer",
|
||||
"Search files...": "Sök filer...",
|
||||
"Newest First": "Nyast först",
|
||||
"Oldest First": "Äldst först",
|
||||
"Largest First": "Störst först",
|
||||
"Smallest First": "Minskst först",
|
||||
"Name A-Z": "Namn A-Ö",
|
||||
"Name Z-A": "Namn Ö-A",
|
||||
"{{count}} selected_one": "Vald fil",
|
||||
"{{count}} selected_other": "Valda filer",
|
||||
"Delete Selected": "Radera valda",
|
||||
"Created": "Skapad",
|
||||
"No files found": "Inga filer hittades",
|
||||
"No files uploaded yet": "Inga filer har laddats upp än",
|
||||
"files": "filer",
|
||||
"Page {{current}} of {{total}}": "Sida {{current}} av {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Är du säker på att du vill radera filen?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Är du säker på att du vill radera filerna?",
|
||||
"Cloud Storage Usage": "Användning av molnlagring",
|
||||
"Rename Group": "Byt namn på grupp",
|
||||
"From Directory": "Från katalog",
|
||||
"Successfully imported {{count}} book(s)_one": "Importerat 1 bok",
|
||||
"Successfully imported {{count}} book(s)_other": "Importerat {{count}} böcker",
|
||||
"Count": "Antal",
|
||||
"Start Page": "Start sida",
|
||||
"Search in OPDS Catalog...": "Sök i OPDS-katalog...",
|
||||
"Please log in to use advanced TTS features": "Logga in för att använda avancerade TTS-funktioner",
|
||||
"Word limit of 30 words exceeded.": "Gränsen på 30 ord har överskridits.",
|
||||
"Proofread": "Korrekturläsning",
|
||||
"Current selection": "Aktuell markering",
|
||||
"All occurrences in this book": "Alla förekomster i den här boken",
|
||||
"All occurrences in your library": "Alla förekomster i ditt bibliotek",
|
||||
"Selected text:": "Markerad text:",
|
||||
"Replace with:": "Ersätt med:",
|
||||
"Enter text...": "Ange text…",
|
||||
"Case sensitive:": "Skiftlägeskänslig:",
|
||||
"Scope:": "Omfattning:",
|
||||
"Selection": "Markering",
|
||||
"Library": "Bibliotek",
|
||||
"Yes": "Ja",
|
||||
"No": "Nej",
|
||||
"Proofread Replacement Rules": "Ersättningsregler för korrekturläsning",
|
||||
"Selected Text Rules": "Regler för markerad text",
|
||||
"No selected text replacement rules": "Inga ersättningsregler för markerad text",
|
||||
"Book Specific Rules": "Bokspecifika regler",
|
||||
"No book-level replacement rules": "Inga ersättningsregler på boknivå",
|
||||
"Disable Quick Action": "Inaktivera snabbåtgärd",
|
||||
"Enable Quick Action on Selection": "Aktivera snabbåtgärd vid val",
|
||||
"None": "Ingen",
|
||||
"Annotation Tools": "Anteckningsverktyg",
|
||||
"Enable Quick Actions": "Aktivera snabba åtgärder",
|
||||
"Quick Action": "Snabbåtgärd",
|
||||
"Copy to Notebook": "Kopiera till anteckningsbok",
|
||||
"Copy text after selection": "Kopiera text efter markering",
|
||||
"Highlight text after selection": "Markera text efter markering",
|
||||
"Annotate text after selection": "Anteckna text efter markering",
|
||||
"Search text after selection": "Sök text efter markering",
|
||||
"Look up text in dictionary after selection": "Slå upp text i ordbok efter markering",
|
||||
"Look up text in Wikipedia after selection": "Slå upp text i Wikipedia efter markering",
|
||||
"Translate text after selection": "Översätt text efter markering",
|
||||
"Read text aloud after selection": "Läs upp text efter markering",
|
||||
"Proofread text after selection": "Korrekturläs text efter markering",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} aktiva, {{pendingCount}} väntande",
|
||||
"{{failedCount}} failed": "{{failedCount}} misslyckades",
|
||||
"Waiting...": "Väntar...",
|
||||
"Failed": "Misslyckades",
|
||||
"Completed": "Slutförd",
|
||||
"Cancelled": "Avbruten",
|
||||
"Retry": "Försök igen",
|
||||
"Active": "Aktiva",
|
||||
"Transfer Queue": "Överföringskö",
|
||||
"Upload All": "Ladda upp alla",
|
||||
"Download All": "Ladda ner alla",
|
||||
"Resume Transfers": "Återuppta överföringar",
|
||||
"Pause Transfers": "Pausa överföringar",
|
||||
"Pending": "Väntande",
|
||||
"No transfers": "Inga överföringar",
|
||||
"Retry All": "Försök alla igen",
|
||||
"Clear Completed": "Rensa slutförda",
|
||||
"Clear Failed": "Rensa misslyckade",
|
||||
"Upload queued: {{title}}": "Uppladdning köad: {{title}}",
|
||||
"Download queued: {{title}}": "Nedladdning köad: {{title}}",
|
||||
"Book not found in library": "Boken hittades inte i biblioteket",
|
||||
"Unknown error": "Okänt fel",
|
||||
"Please log in to continue": "Logga in för att fortsätta",
|
||||
"Cloud File Transfers": "Molnfilöverföringar",
|
||||
"Show Search Results": "Visa sökresultat",
|
||||
"Search results for '{{term}}'": "Resultat för '{{term}}'",
|
||||
"Close Search": "Stäng sökning",
|
||||
"Previous Result": "Föregående resultat",
|
||||
"Next Result": "Nästa resultat",
|
||||
"Bookmarks": "Bokmärken",
|
||||
"Annotations": "Anteckningar",
|
||||
"Show Results": "Visa resultat",
|
||||
"Clear search": "Rensa sökning",
|
||||
"Clear search history": "Rensa sökhistorik",
|
||||
"Quick action disabled": "Snabbåtgärd inaktiverad",
|
||||
"Tap to Toggle Footer": "Tryck för att växla sidfot",
|
||||
"Exported successfully": "Exporterad",
|
||||
"Book exported successfully.": "Boken har exporterats.",
|
||||
"Failed to export the book.": "Det gick inte att exportera boken.",
|
||||
"Export Book": "Exportera bok",
|
||||
"Whole word:": "Hela ordet:",
|
||||
"Error": "Fel",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Kan inte ladda artikeln. Försök söka direkt på {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Kan inte ladda ordet. Försök söka direkt på {{link}}.",
|
||||
"Date Published": "Publiceringsdatum",
|
||||
"Only for TTS:": "Endast för TTS:",
|
||||
"Uploaded": "Uppladdad",
|
||||
"Downloaded": "Nedladdad",
|
||||
"Deleted": "Borttagen",
|
||||
"Note:": "Anteckning:",
|
||||
"Time:": "Tid:",
|
||||
"Format Options": "Formatalternativ",
|
||||
"Export Date": "Exportdatum",
|
||||
"Chapter Titles": "Kapiteltitlar",
|
||||
"Chapter Separator": "Kapitelavgränsare",
|
||||
"Highlights": "Markeringar",
|
||||
"Note Date": "Anteckningsdatum",
|
||||
"Advanced": "Avancerat",
|
||||
"Hide": "Dölj",
|
||||
"Show": "Visa",
|
||||
"Use Custom Template": "Använd anpassad mall",
|
||||
"Export Template": "Exportmall",
|
||||
"Template Syntax:": "Mallsyntax:",
|
||||
"Insert value": "Infoga värde",
|
||||
"Format date (locale)": "Formatera datum (lokal)",
|
||||
"Format date (custom)": "Formatera datum (anpassad)",
|
||||
"Conditional": "Villkorlig",
|
||||
"Loop": "Loop",
|
||||
"Available Variables:": "Tillgängliga variabler:",
|
||||
"Book title": "Boktitel",
|
||||
"Book author": "Bokförfattare",
|
||||
"Export date": "Exportdatum",
|
||||
"Array of chapters": "Lista med kapitel",
|
||||
"Chapter title": "Kapiteltitel",
|
||||
"Array of annotations": "Lista med anteckningar",
|
||||
"Highlighted text": "Markerad text",
|
||||
"Annotation note": "Anteckningsnotering",
|
||||
"Update time": "Uppdateringstid",
|
||||
"Date Format Tokens:": "Datumformattecken:",
|
||||
"Year (4 digits)": "År (4 siffror)",
|
||||
"Month (01-12)": "Månad (01-12)",
|
||||
"Day (01-31)": "Dag (01-31)",
|
||||
"Hour (00-23)": "Timme (00-23)",
|
||||
"Minute (00-59)": "Minut (00-59)",
|
||||
"Second (00-59)": "Sekund (00-59)",
|
||||
"Show Source": "Visa källa",
|
||||
"No content to preview": "Inget innehåll att förhandsgranska",
|
||||
"Export": "Exportera",
|
||||
"Set Timeout": "Ställ in timeout",
|
||||
"Select Voice": "Välj röst",
|
||||
"Toggle Sticky Bottom TTS Bar": "Växla fast TTS-fält",
|
||||
"Display what I'm reading on Discord": "Visa vad jag läser på Discord",
|
||||
"Show on Discord": "Visa på Discord"
|
||||
}
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
"Token": "டோக்கன்",
|
||||
"Your OTP token": "உங்கள் OTP டோக்கன்",
|
||||
"Verify token": "டோக்கனை சரிபார்க்கவும்",
|
||||
"Sign in with Google": "Google மூலம் உள்நுழையவும்",
|
||||
"Sign in with Apple": "Apple மூலம் உள்நுழையவும்",
|
||||
"Sign in with GitHub": "GitHub மூலம் உள்நுழையவும்",
|
||||
"New Password": "புதிய கடவுச்சொல்",
|
||||
"Your new password": "உங்கள் புதிய கடவுச்சொல்",
|
||||
"Update password": "கடவுச்சொல் புதுப்பிக்கவும்",
|
||||
@@ -488,8 +485,8 @@
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Source Han Serif CN VF": "Source Han Serif CN VF",
|
||||
"Huiwen-mincho": "Huiwen-mincho",
|
||||
"Source Han Serif CN": "Source Han Serif CN",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa_OldSong",
|
||||
"Google Books": "Google Books",
|
||||
"Open Library": "Open Library",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "சரிபார்க்கப்படுகிறது...",
|
||||
"View All": "அனைத்தையும் பார்க்கவும்",
|
||||
"Forward": "முன்னேற்று",
|
||||
"OPDS Catalog": "OPDS பட்டியல்",
|
||||
"Home": "முகப்பு",
|
||||
"Library": "நூலகம்",
|
||||
"{{count}} items_one": "{{count}} பொருள்",
|
||||
"{{count}} items_other": "{{count}} பொருட்கள்",
|
||||
"Download completed": "பதிவிறக்கம் முடிந்தது",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "இறுதி",
|
||||
"Cannot Load Page": "பக்கம் ஏற்ற முடியவில்லை",
|
||||
"An error occurred": "ஒரு பிழை ஏற்பட்டது",
|
||||
"Online Library": "ஆன்லைன் நூலகம்"
|
||||
"Online Library": "ஆன்லைன் நூலகம்",
|
||||
"URL must start with http:// or https://": "URL http:// அல்லது https:// கொண்டு தொடங்க வேண்டும்",
|
||||
"Title, Author, Tag, etc...": "தலைப்பு, ஆசிரியர், குறிச்சொல், மற்றும் பல...",
|
||||
"Query": "கேள்வி",
|
||||
"Subject": "பொருள்",
|
||||
"Enter {{terms}}": "{{terms}} உள்ளிடவும்",
|
||||
"No search results found": "தேடல் முடிவுகள் இல்லை",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ஊட்டத்தை ஏற்ற முடியவில்லை: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} இல் தேடவும்",
|
||||
"Manage Storage": "சேமிப்பை நிர்வகி",
|
||||
"Failed to load files": "கோப்புகளை ஏற்ற முடியவில்லை",
|
||||
"Deleted {{count}} file(s)_one": "கோப்பு நீக்கப்பட்டது",
|
||||
"Deleted {{count}} file(s)_other": "கோப்புகள் நீக்கப்பட்டன",
|
||||
"Failed to delete {{count}} file(s)_one": "கோப்பை நீக்க முடியவில்லை",
|
||||
"Failed to delete {{count}} file(s)_other": "கோப்புகளை நீக்க முடியவில்லை",
|
||||
"Failed to delete files": "கோப்புகளை நீக்க முடியவில்லை",
|
||||
"Total Files": "மொத்த கோப்புகள்",
|
||||
"Total Size": "மொத்த அளவு",
|
||||
"Quota": "கோட்டா",
|
||||
"Used": "பயன்படுத்தப்பட்டது",
|
||||
"Files": "கோப்புகள்",
|
||||
"Search files...": "கோப்புகளைத் தேடு...",
|
||||
"Newest First": "புதியவை முதலில்",
|
||||
"Oldest First": "பழையவை முதலில்",
|
||||
"Largest First": "பெரியவை முதலில்",
|
||||
"Smallest First": "சிறியவை முதலில்",
|
||||
"Name A-Z": "பெயர் A-ஆல்",
|
||||
"Name Z-A": "பெயர் ஆல்-A",
|
||||
"{{count}} selected_one": "தேர்ந்தெடுக்கப்பட்ட கோப்பு",
|
||||
"{{count}} selected_other": "தேர்ந்தெடுக்கப்பட்ட கோப்புகள்",
|
||||
"Delete Selected": "தேர்ந்தெடுத்தவை நீக்கு",
|
||||
"Created": "உருவாக்கப்பட்டது",
|
||||
"No files found": "கோப்புகள் இல்லை",
|
||||
"No files uploaded yet": "இன்னும் எந்த கோப்பும் பதிவேற்றப்படவில்லை",
|
||||
"files": "கோப்புகள்",
|
||||
"Page {{current}} of {{total}}": "பக்கம் {{current}} / {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "இந்த கோப்பை நீக்க விரும்புகிறீர்களா?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "இந்த கோப்புகளை நீக்க விரும்புகிறீர்களா?",
|
||||
"Cloud Storage Usage": "மேக சேமிப்பு பயன்பாடு",
|
||||
"Rename Group": "குழுவை மறுபெயரிடவும்",
|
||||
"From Directory": "கோப்புறையிலிருந்து",
|
||||
"Successfully imported {{count}} book(s)_one": "வெற்றிகரமாக 1 புத்தகம் இறக்குமதி செய்யப்பட்டது",
|
||||
"Successfully imported {{count}} book(s)_other": "வெற்றிகரமாக {{count}} புத்தகங்கள் இறக்குமதி செய்யப்பட்டது",
|
||||
"Count": "எண்ணிக்கை",
|
||||
"Start Page": "தொடக்கப் பக்கம்",
|
||||
"Search in OPDS Catalog...": "OPDS பட்டியலில் தேடவும்...",
|
||||
"Please log in to use advanced TTS features": "மேம்பட்ட TTS அம்சங்களை பயன்படுத்த உள்நுழையவும்",
|
||||
"Word limit of 30 words exceeded.": "30 சொற்களின் வரம்பை மீறியுள்ளது.",
|
||||
"Proofread": "திருத்தம்",
|
||||
"Current selection": "தற்போதைய தேர்வு",
|
||||
"All occurrences in this book": "இந்த புத்தகத்தில் உள்ள அனைத்து நிகழ்வுகளும்",
|
||||
"All occurrences in your library": "உங்கள் நூலகத்தில் உள்ள அனைத்து நிகழ்வுகளும்",
|
||||
"Selected text:": "தேர்ந்தெடுக்கப்பட்ட உரை:",
|
||||
"Replace with:": "இதனால் மாற்றவும்:",
|
||||
"Enter text...": "உரையை உள்ளிடவும்…",
|
||||
"Case sensitive:": "எழுத்து பெரிய/சிறிய வேறுபாட்டை கருத்தில் கொள்ளவும்:",
|
||||
"Scope:": "வளயம்:",
|
||||
"Selection": "தேர்வு",
|
||||
"Library": "நூலகம்",
|
||||
"Yes": "ஆம்",
|
||||
"No": "இல்லை",
|
||||
"Proofread Replacement Rules": "திருத்த மாற்று விதிகள்",
|
||||
"Selected Text Rules": "தேர்ந்தெடுக்கப்பட்ட உரைக்கான விதிகள்",
|
||||
"No selected text replacement rules": "தேர்ந்தெடுக்கப்பட்ட உரைக்கான மாற்று விதிகள் இல்லை",
|
||||
"Book Specific Rules": "புத்தகத்திற்கு குறிப்பிட்ட விதிகள்",
|
||||
"No book-level replacement rules": "புத்தக மட்டத்தில் மாற்று விதிகள் இல்லை",
|
||||
"Disable Quick Action": "விரைவு செயலை முடக்கு",
|
||||
"Enable Quick Action on Selection": "தேர்வில் விரைவு செயலை இயக்கு",
|
||||
"None": "எதுவும் இல்லை",
|
||||
"Annotation Tools": "கருத்துரை கருவிகள்",
|
||||
"Enable Quick Actions": "விரைவு செயல்களை இயக்கு",
|
||||
"Quick Action": "விரைவு செயல்",
|
||||
"Copy to Notebook": "குறிப்பேட்டியில் நகலெடு",
|
||||
"Copy text after selection": "உரையை தேர்வுக்குப் பிறகு நகலெடு",
|
||||
"Highlight text after selection": "உரையை தேர்வுக்குப் பிறகு முத்திரை இடு",
|
||||
"Annotate text after selection": "உரையை தேர்வுக்குப் பிறகு கருத்துரை இடு",
|
||||
"Search text after selection": "உரையை தேர்வுக்குப் பிறகு தேடு",
|
||||
"Look up text in dictionary after selection": "தேர்வுக்குப் பிறகு அகராதியில் உரையைத் தேடு",
|
||||
"Look up text in Wikipedia after selection": "தேர்வுக்குப் பிறகு விக்கிப்பீடியாவில் உரையைத் தேடு",
|
||||
"Translate text after selection": "தேர்வுக்குப் பிறகு உரையை மொழிபெயர்",
|
||||
"Read text aloud after selection": "தேர்வுக்குப் பிறகு உரையை ஓதுக",
|
||||
"Proofread text after selection": "தேர்வுக்குப் பிறகு உரையை திருத்துக",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} செயலில், {{pendingCount}} காத்திருப்பு",
|
||||
"{{failedCount}} failed": "{{failedCount}} தோல்வி",
|
||||
"Waiting...": "காத்திருக்கிறது...",
|
||||
"Failed": "தோல்வி",
|
||||
"Completed": "முடிந்தது",
|
||||
"Cancelled": "ரத்து செய்யப்பட்டது",
|
||||
"Retry": "மீண்டும் முயற்சிக்கவும்",
|
||||
"Active": "செயலில்",
|
||||
"Transfer Queue": "பரிமாற்ற வரிசை",
|
||||
"Upload All": "அனைத்தையும் பதிவேற்றவும்",
|
||||
"Download All": "அனைத்தையும் பதிவிறக்கவும்",
|
||||
"Resume Transfers": "பரிமாற்றங்களை தொடரவும்",
|
||||
"Pause Transfers": "பரிமாற்றங்களை இடைநிறுத்தவும்",
|
||||
"Pending": "காத்திருப்பு",
|
||||
"No transfers": "பரிமாற்றங்கள் இல்லை",
|
||||
"Retry All": "அனைத்தையும் மீண்டும் முயற்சிக்கவும்",
|
||||
"Clear Completed": "முடிந்ததை அழிக்கவும்",
|
||||
"Clear Failed": "தோல்வியை அழிக்கவும்",
|
||||
"Upload queued: {{title}}": "பதிவேற்றம் வரிசையில்: {{title}}",
|
||||
"Download queued: {{title}}": "பதிவிறக்கம் வரிசையில்: {{title}}",
|
||||
"Book not found in library": "நூலகத்தில் புத்தகம் கிடைக்கவில்லை",
|
||||
"Unknown error": "தெரியாத பிழை",
|
||||
"Please log in to continue": "தொடர உள்நுழையவும்",
|
||||
"Cloud File Transfers": "மேகக் கோப்பு பரிமாற்றங்கள்",
|
||||
"Show Search Results": "தேடல் முடிவுகளைக் காட்டு",
|
||||
"Search results for '{{term}}'": "'{{term}}' க்கான முடிவுகள்",
|
||||
"Close Search": "தேடலை மூடு",
|
||||
"Previous Result": "முந்தைய முடிவு",
|
||||
"Next Result": "அடுத்த முடிவு",
|
||||
"Bookmarks": "புக்மார்க்குகள்",
|
||||
"Annotations": "சிறுகுறிப்புகள்",
|
||||
"Show Results": "முடிவுகளைக் காட்டு",
|
||||
"Clear search": "தேடலை அழி",
|
||||
"Clear search history": "தேடல் வரலாற்றை அழி",
|
||||
"Quick action disabled": "விரைவு செயல் முடக்கப்பட்டது",
|
||||
"Tap to Toggle Footer": "அடிக்குறிப்பை மாற்ற தட்டவும்",
|
||||
"Exported successfully": "ஏற்றுமதி வெற்றி",
|
||||
"Book exported successfully.": "புத்தகம் வெற்றிகரமாக ஏற்றுமதி செய்யப்பட்டது.",
|
||||
"Failed to export the book.": "புத்தகத்தை ஏற்றுமதி செய்ய இயலவில்லை.",
|
||||
"Export Book": "புத்தகத்தை ஏற்றுமதி செய்",
|
||||
"Whole word:": "முழு சொல்:",
|
||||
"Error": "பிழை",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "கட்டுரையை ஏற்ற முடியவில்லை. நேரடியாக {{link}} இல் தேடவும்.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "சொல்லை ஏற்ற முடியவில்லை. நேரடியாக {{link}} இல் தேடவும்.",
|
||||
"Date Published": "வெளியீட்டு தேதி",
|
||||
"Only for TTS:": "TTS க்கு மட்டும்:",
|
||||
"Uploaded": "பதிவேற்றப்பட்டது",
|
||||
"Downloaded": "பதிவிறக்கப்பட்டது",
|
||||
"Deleted": "நீக்கப்பட்டது",
|
||||
"Note:": "குறிப்பு:",
|
||||
"Time:": "நேரம்:",
|
||||
"Format Options": "வடிவமைப்பு விருப்பங்கள்",
|
||||
"Export Date": "ஏற்றுமதி தேதி",
|
||||
"Chapter Titles": "அத்தியாயத் தலைப்புகள்",
|
||||
"Chapter Separator": "அத்தியாய பிரிப்பான்",
|
||||
"Highlights": "சிறப்பம்சங்கள்",
|
||||
"Note Date": "குறிப்பு தேதி",
|
||||
"Advanced": "மேம்பட்ட",
|
||||
"Hide": "மறை",
|
||||
"Show": "காட்டு",
|
||||
"Use Custom Template": "தனிப்பயன் வார்ப்புரு பயன்படுத்து",
|
||||
"Export Template": "ஏற்றுமதி வார்ப்புரு",
|
||||
"Template Syntax:": "வார்ப்புரு தொடரியல்:",
|
||||
"Insert value": "மதிப்பு செருகு",
|
||||
"Format date (locale)": "தேதி வடிவமைப்பு (உள்ளூர்)",
|
||||
"Format date (custom)": "தேதி வடிவமைப்பு (தனிப்பயன்)",
|
||||
"Conditional": "நிபந்தனை",
|
||||
"Loop": "சுழற்சி",
|
||||
"Available Variables:": "கிடைக்கும் மாறிகள்:",
|
||||
"Book title": "புத்தகத் தலைப்பு",
|
||||
"Book author": "புத்தக ஆசிரியர்",
|
||||
"Export date": "ஏற்றுமதி தேதி",
|
||||
"Array of chapters": "அத்தியாயங்களின் பட்டியல்",
|
||||
"Chapter title": "அத்தியாய தலைப்பு",
|
||||
"Array of annotations": "குறிப்புகளின் பட்டியல்",
|
||||
"Highlighted text": "முன்னிலைப்படுத்தப்பட்ட உரை",
|
||||
"Annotation note": "குறிப்பு குறிப்பு",
|
||||
"Update time": "புதுப்பிப்பு நேரம்",
|
||||
"Date Format Tokens:": "தேதி வடிவமைப்பு டோக்கன்கள்:",
|
||||
"Year (4 digits)": "ஆண்டு (4 இலக்கங்கள்)",
|
||||
"Month (01-12)": "மாதம் (01-12)",
|
||||
"Day (01-31)": "நாள் (01-31)",
|
||||
"Hour (00-23)": "மணி (00-23)",
|
||||
"Minute (00-59)": "நிமிடம் (00-59)",
|
||||
"Second (00-59)": "விநாடி (00-59)",
|
||||
"Show Source": "மூலத்தைக் காட்டு",
|
||||
"No content to preview": "முன்னோட்டத்திற்கு உள்ளடக்கம் இல்லை",
|
||||
"Export": "ஏற்றுமதி",
|
||||
"Set Timeout": "நேர வரம்பை அமைக்கவும்",
|
||||
"Select Voice": "குரலைத் தேர்ந்தெடுக்கவும்",
|
||||
"Toggle Sticky Bottom TTS Bar": "நிலையான TTS பட்டியை மாற்றவும்",
|
||||
"Display what I'm reading on Discord": "Discord இல் படிக்கும் புத்தகத்தை காட்டு",
|
||||
"Show on Discord": "Discord இல் காட்டு"
|
||||
}
|
||||
|
||||
@@ -23,9 +23,6 @@
|
||||
"Token": "รหัสยืนยัน",
|
||||
"Your OTP token": "รหัส OTP ของคุณ",
|
||||
"Verify token": "ยืนยันรหัส",
|
||||
"Sign in with Google": "เข้าสู่ระบบด้วย Google",
|
||||
"Sign in with Apple": "เข้าสู่ระบบด้วย Apple",
|
||||
"Sign in with GitHub": "เข้าสู่ระบบด้วย GitHub",
|
||||
"New Password": "รหัสผ่านใหม่",
|
||||
"Your new password": "รหัสผ่านใหม่ของคุณ",
|
||||
"Update password": "อัปเดตรหัสผ่าน",
|
||||
@@ -379,8 +376,8 @@
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Source Han Serif CN VF": "Source Han Serif CN VF",
|
||||
"Huiwen-mincho": "Huiwen-mincho",
|
||||
"Source Han Serif CN": "Source Han Serif CN",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa_OldSong",
|
||||
"Azure Translator": "Azure Translator",
|
||||
"DeepL": "DeepL",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "กำลังตรวจสอบ...",
|
||||
"View All": "ดูทั้งหมด",
|
||||
"Forward": "ไปข้างหน้า",
|
||||
"OPDS Catalog": "แคตตาล็อก OPDS",
|
||||
"Home": "หน้าแรก",
|
||||
"Library": "ห้องสมุด",
|
||||
"{{count}} items_other": "{{count}} รายการ",
|
||||
"Download completed": "ดาวน์โหลดเสร็จสิ้น",
|
||||
"Download failed": "ดาวน์โหลดล้มเหลว",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "สุดท้าย",
|
||||
"Cannot Load Page": "ไม่สามารถโหลดหน้าหน้าได้",
|
||||
"An error occurred": "เกิดข้อผิดพลาด",
|
||||
"Online Library": "ห้องสมุดออนไลน์"
|
||||
"Online Library": "ห้องสมุดออนไลน์",
|
||||
"URL must start with http:// or https://": "URL ต้องเริ่มต้นด้วย http:// หรือ https://",
|
||||
"Title, Author, Tag, etc...": "ชื่อเรื่อง ผู้แต่ง แท็ก ฯลฯ...",
|
||||
"Query": "แบบสอบถาม",
|
||||
"Subject": "หัวข้อ",
|
||||
"Enter {{terms}}": "ป้อน {{terms}}",
|
||||
"No search results found": "ไม่พบผลการค้นหา",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "ไม่สามารถโหลดฟีด OPDS ได้: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "ค้นหาใน {{title}}",
|
||||
"Manage Storage": "จัดการพื้นที่เก็บข้อมูล",
|
||||
"Failed to load files": "ไม่สามารถโหลดไฟล์ได้",
|
||||
"Deleted {{count}} file(s)_other": "ลบไฟล์เรียบร้อยแล้ว",
|
||||
"Failed to delete {{count}} file(s)_other": "ลบไฟล์ไม่สำเร็จ",
|
||||
"Failed to delete files": "ลบไฟล์ไม่สำเร็จ",
|
||||
"Total Files": "จำนวนไฟล์ทั้งหมด",
|
||||
"Total Size": "ขนาดรวมทั้งหมด",
|
||||
"Quota": "โควต้า",
|
||||
"Used": "ใช้ไปแล้ว",
|
||||
"Files": "ไฟล์",
|
||||
"Search files...": "ค้นหาไฟล์...",
|
||||
"Newest First": "ใหม่ที่สุดก่อน",
|
||||
"Oldest First": "เก่าที่สุดก่อน",
|
||||
"Largest First": "ใหญ่ที่สุดก่อน",
|
||||
"Smallest First": "เล็กที่สุดก่อน",
|
||||
"Name A-Z": "ชื่อ A-ฮ",
|
||||
"Name Z-A": "ชื่อ ฮ-A",
|
||||
"{{count}} selected_other": "เลือกไฟล์แล้ว",
|
||||
"Delete Selected": "ลบไฟล์ที่เลือก",
|
||||
"Created": "สร้างเมื่อ",
|
||||
"No files found": "ไม่พบไฟล์",
|
||||
"No files uploaded yet": "ยังไม่มีการอัปโหลดไฟล์",
|
||||
"files": "ไฟล์",
|
||||
"Page {{current}} of {{total}}": "หน้า {{current}} จาก {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "คุณแน่ใจหรือว่าต้องการลบไฟล์ที่เลือก?",
|
||||
"Cloud Storage Usage": "การใช้งานพื้นที่เก็บข้อมูลคลาวด์",
|
||||
"Rename Group": "เปลี่ยนชื่อกลุ่ม",
|
||||
"From Directory": "จากไดเรกทอรี",
|
||||
"Successfully imported {{count}} book(s)_other": "นำเข้า {{count}} หนังสือเรียบร้อยแล้ว",
|
||||
"Count": "นับ",
|
||||
"Start Page": "หน้าเริ่มต้น",
|
||||
"Search in OPDS Catalog...": "ค้นหาในแคตตาล็อก OPDS...",
|
||||
"Please log in to use advanced TTS features": "กรุณาเข้าสู่ระบบเพื่อใช้ฟีเจอร์ TTS ขั้นสูง",
|
||||
"Word limit of 30 words exceeded.": "เกินขีดจำกัด 30 คำแล้ว",
|
||||
"Proofread": "ตรวจทาน",
|
||||
"Current selection": "การเลือกปัจจุบัน",
|
||||
"All occurrences in this book": "การปรากฏทั้งหมดในหนังสือเล่มนี้",
|
||||
"All occurrences in your library": "การปรากฏทั้งหมดในคลังหนังสือของคุณ",
|
||||
"Selected text:": "ข้อความที่เลือก:",
|
||||
"Replace with:": "แทนที่ด้วย:",
|
||||
"Enter text...": "ป้อนข้อความ…",
|
||||
"Case sensitive:": "คำนึงถึงตัวพิมพ์เล็ก/ใหญ่:",
|
||||
"Scope:": "ขอบเขต:",
|
||||
"Selection": "การเลือก",
|
||||
"Library": "คลังหนังสือ",
|
||||
"Yes": "ใช่",
|
||||
"No": "ไม่",
|
||||
"Proofread Replacement Rules": "กฎการแทนที่สำหรับการตรวจทาน",
|
||||
"Selected Text Rules": "กฎสำหรับข้อความที่เลือก",
|
||||
"No selected text replacement rules": "ไม่มีกฎการแทนที่สำหรับข้อความที่เลือก",
|
||||
"Book Specific Rules": "กฎเฉพาะของหนังสือ",
|
||||
"No book-level replacement rules": "ไม่มีกฎการแทนที่ระดับหนังสือ",
|
||||
"Disable Quick Action": "ปิดใช้งานการดำเนินการด่วน",
|
||||
"Enable Quick Action on Selection": "เปิดใช้งานการดำเนินการด่วนเมื่อเลือก",
|
||||
"None": "ไม่มี",
|
||||
"Annotation Tools": "เครื่องมือหมายเหตุ",
|
||||
"Enable Quick Actions": "เปิดใช้งานการดำเนินการด่วน",
|
||||
"Quick Action": "การดำเนินการด่วน",
|
||||
"Copy to Notebook": "คัดลอกไปยังสมุดบันทึก",
|
||||
"Copy text after selection": "คัดลอกข้อความหลังการเลือก",
|
||||
"Highlight text after selection": "ไฮไลต์ข้อความหลังการเลือก",
|
||||
"Annotate text after selection": "เพิ่มหมายเหตุข้อความหลังการเลือก",
|
||||
"Search text after selection": "ค้นหาข้อความหลังการเลือก",
|
||||
"Look up text in dictionary after selection": "ค้นหาข้อความในพจนานุกรมหลังการเลือก",
|
||||
"Look up text in Wikipedia after selection": "ค้นหาข้อความในวิกิพีเดียหลังการเลือก",
|
||||
"Translate text after selection": "แปลข้อความหลังการเลือก",
|
||||
"Read text aloud after selection": "อ่านข้อความออกเสียงหลังการเลือก",
|
||||
"Proofread text after selection": "ตรวจทานข้อความหลังการเลือก",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} กำลังดำเนินการ, {{pendingCount}} รอดำเนินการ",
|
||||
"{{failedCount}} failed": "{{failedCount}} ล้มเหลว",
|
||||
"Waiting...": "กำลังรอ...",
|
||||
"Failed": "ล้มเหลว",
|
||||
"Completed": "เสร็จสิ้น",
|
||||
"Cancelled": "ยกเลิก",
|
||||
"Retry": "ลองใหม่",
|
||||
"Active": "กำลังดำเนินการ",
|
||||
"Transfer Queue": "คิวการถ่ายโอน",
|
||||
"Upload All": "อัปโหลดทั้งหมด",
|
||||
"Download All": "ดาวน์โหลดทั้งหมด",
|
||||
"Resume Transfers": "ดำเนินการถ่ายโอนต่อ",
|
||||
"Pause Transfers": "หยุดการถ่ายโอนชั่วคราว",
|
||||
"Pending": "รอดำเนินการ",
|
||||
"No transfers": "ไม่มีการถ่ายโอน",
|
||||
"Retry All": "ลองใหม่ทั้งหมด",
|
||||
"Clear Completed": "ล้างที่เสร็จสิ้น",
|
||||
"Clear Failed": "ล้างที่ล้มเหลว",
|
||||
"Upload queued: {{title}}": "อัปโหลดอยู่ในคิว: {{title}}",
|
||||
"Download queued: {{title}}": "ดาวน์โหลดอยู่ในคิว: {{title}}",
|
||||
"Book not found in library": "ไม่พบหนังสือในห้องสมุด",
|
||||
"Unknown error": "ข้อผิดพลาดที่ไม่รู้จัก",
|
||||
"Please log in to continue": "กรุณาเข้าสู่ระบบเพื่อดำเนินการต่อ",
|
||||
"Cloud File Transfers": "การถ่ายโอนไฟล์คลาวด์",
|
||||
"Show Search Results": "แสดงผลการค้นหา",
|
||||
"Search results for '{{term}}'": "ผลลัพธ์สำหรับ '{{term}}'",
|
||||
"Close Search": "ปิดการค้นหา",
|
||||
"Previous Result": "ผลลัพธ์ก่อนหน้า",
|
||||
"Next Result": "ผลลัพธ์ถัดไป",
|
||||
"Bookmarks": "บุ๊กมาร์ก",
|
||||
"Annotations": "คำอธิบายประกอบ",
|
||||
"Show Results": "แสดงผลลัพธ์",
|
||||
"Clear search": "ล้างการค้นหา",
|
||||
"Clear search history": "ล้างประวัติการค้นหา",
|
||||
"Quick action disabled": "ปิดใช้งานการดำเนินการด่วน",
|
||||
"Tap to Toggle Footer": "แตะเพื่อสลับส่วนท้าย",
|
||||
"Exported successfully": "ส่งออกสำเร็จ",
|
||||
"Book exported successfully.": "ส่งออกหนังสือสำเร็จ",
|
||||
"Failed to export the book.": "ส่งออกหนังสือล้มเหลว",
|
||||
"Export Book": "ส่งออกหนังสือ",
|
||||
"Whole word:": "คำทั้งคำ:",
|
||||
"Error": "ข้อผิดพลาด",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "ไม่สามารถโหลดบทความได้ ลองค้นหาโดยตรงที่ {{link}}",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "ไม่สามารถโหลดคำได้ ลองค้นหาโดยตรงที่ {{link}}",
|
||||
"Date Published": "วันที่เผยแพร่",
|
||||
"Only for TTS:": "สำหรับ TTS เท่านั้น:",
|
||||
"Uploaded": "อัปโหลดแล้ว",
|
||||
"Downloaded": "ดาวน์โหลดแล้ว",
|
||||
"Deleted": "ลบแล้ว",
|
||||
"Note:": "หมายเหตุ:",
|
||||
"Time:": "เวลา:",
|
||||
"Format Options": "ตัวเลือกรูปแบบ",
|
||||
"Export Date": "วันที่ส่งออก",
|
||||
"Chapter Titles": "ชื่อบท",
|
||||
"Chapter Separator": "ตัวคั่นบท",
|
||||
"Highlights": "ไฮไลต์",
|
||||
"Note Date": "วันที่หมายเหตุ",
|
||||
"Advanced": "ขั้นสูง",
|
||||
"Hide": "ซ่อน",
|
||||
"Show": "แสดง",
|
||||
"Use Custom Template": "ใช้เทมเพลตที่กำหนดเอง",
|
||||
"Export Template": "เทมเพลตส่งออก",
|
||||
"Template Syntax:": "ไวยากรณ์เทมเพลต:",
|
||||
"Insert value": "แทรกค่า",
|
||||
"Format date (locale)": "จัดรูปแบบวันที่ (ท้องถิ่น)",
|
||||
"Format date (custom)": "จัดรูปแบบวันที่ (กำหนดเอง)",
|
||||
"Conditional": "เงื่อนไข",
|
||||
"Loop": "ลูป",
|
||||
"Available Variables:": "ตัวแปรที่พร้อมใช้งาน:",
|
||||
"Book title": "ชื่อหนังสือ",
|
||||
"Book author": "ผู้แต่งหนังสือ",
|
||||
"Export date": "วันที่ส่งออก",
|
||||
"Array of chapters": "อาร์เรย์ของบท",
|
||||
"Chapter title": "ชื่อบท",
|
||||
"Array of annotations": "อาร์เรย์ของคำอธิบาย",
|
||||
"Highlighted text": "ข้อความที่ไฮไลต์",
|
||||
"Annotation note": "หมายเหตุคำอธิบาย",
|
||||
"Update time": "เวลาอัปเดต",
|
||||
"Date Format Tokens:": "โทเค็นรูปแบบวันที่:",
|
||||
"Year (4 digits)": "ปี (4 หลัก)",
|
||||
"Month (01-12)": "เดือน (01-12)",
|
||||
"Day (01-31)": "วัน (01-31)",
|
||||
"Hour (00-23)": "ชั่วโมง (00-23)",
|
||||
"Minute (00-59)": "นาที (00-59)",
|
||||
"Second (00-59)": "วินาที (00-59)",
|
||||
"Show Source": "แสดงซอร์ส",
|
||||
"No content to preview": "ไม่มีเนื้อหาให้แสดงตัวอย่าง",
|
||||
"Export": "ส่งออก",
|
||||
"Set Timeout": "ตั้งค่าหมดเวลา",
|
||||
"Select Voice": "เลือกเสียง",
|
||||
"Toggle Sticky Bottom TTS Bar": "สลับแถบ TTS แบบติด",
|
||||
"Display what I'm reading on Discord": "แสดงหนังสือที่กำลังอ่านบน Discord",
|
||||
"Show on Discord": "แสดงบน Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Token",
|
||||
"Your OTP token": "OTP token'ınız",
|
||||
"Verify token": "Token'ı doğrula",
|
||||
"Sign in with Google": "Google ile giriş yap",
|
||||
"Sign in with Apple": "Apple ile giriş yap",
|
||||
"Sign in with GitHub": "GitHub ile giriş yap",
|
||||
"Account": "Hesap",
|
||||
"Failed to delete user. Please try again later.": "Kullanıcı silinemedi. Lütfen daha sonra tekrar deneyin.",
|
||||
"Community Support": "Topluluk Desteği",
|
||||
@@ -341,8 +338,8 @@
|
||||
"{{count}} pages left in chapter_one": "Bu bölümde {{count}} sayfa kaldı",
|
||||
"{{count}} pages left in chapter_other": "Bu bölümde {{count}} sayfa kaldı",
|
||||
"Show Remaining Pages": "Kalan sayfaları göster",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Aboneliği Yönet",
|
||||
"Coming Soon": "Yakında",
|
||||
@@ -699,9 +696,7 @@
|
||||
"Validating...": "Doğrulanıyor...",
|
||||
"View All": "Tümünü Görüntüle",
|
||||
"Forward": "İleri",
|
||||
"OPDS Catalog": "OPDS Kataloğu",
|
||||
"Home": "Ana Sayfa",
|
||||
"Library": "Kütüphane",
|
||||
"{{count}} items_one": "{{count}} öğe",
|
||||
"{{count}} items_other": "{{count}} öğe",
|
||||
"Download completed": "İndirme tamamlandı",
|
||||
@@ -721,5 +716,179 @@
|
||||
"Last": "Son",
|
||||
"Cannot Load Page": "Sayfa yüklenemiyor",
|
||||
"An error occurred": "Bir hata oluştu",
|
||||
"Online Library": "Çevrimiçi Kütüphane"
|
||||
"Online Library": "Çevrimiçi Kütüphane",
|
||||
"URL must start with http:// or https://": "URL http:// veya https:// ile başlamalıdır",
|
||||
"Title, Author, Tag, etc...": "Başlık, Yazar, Etiket, vb...",
|
||||
"Query": "Sorgu",
|
||||
"Subject": "Konu",
|
||||
"Enter {{terms}}": "{{terms}} girin",
|
||||
"No search results found": "Arama sonucu bulunamadı",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS beslemesi yüklenemedi: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} içinde ara",
|
||||
"Manage Storage": "Depolamayı Yönet",
|
||||
"Failed to load files": "Dosyalar yüklenemedi",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} dosya silindi",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} dosya silindi",
|
||||
"Failed to delete {{count}} file(s)_one": "{{count}} dosya silinemedi",
|
||||
"Failed to delete {{count}} file(s)_other": "{{count}} dosya silinemedi",
|
||||
"Failed to delete files": "Dosyalar silinemedi",
|
||||
"Total Files": "Toplam Dosya",
|
||||
"Total Size": "Toplam Boyut",
|
||||
"Quota": "Kota",
|
||||
"Used": "Kullanıldı",
|
||||
"Files": "Dosyalar",
|
||||
"Search files...": "Dosyaları ara...",
|
||||
"Newest First": "En Yeni Önce",
|
||||
"Oldest First": "En Eski Önce",
|
||||
"Largest First": "En Büyük Önce",
|
||||
"Smallest First": "En Küçük Önce",
|
||||
"Name A-Z": "İsim A-Z",
|
||||
"Name Z-A": "İsim Z-A",
|
||||
"{{count}} selected_one": "{{count}} seçili dosya",
|
||||
"{{count}} selected_other": "{{count}} seçili dosya",
|
||||
"Delete Selected": "Seçilenleri Sil",
|
||||
"Created": "Oluşturulma Tarihi",
|
||||
"No files found": "Dosya bulunamadı",
|
||||
"No files uploaded yet": "Henüz dosya yüklenmedi",
|
||||
"files": "dosyalar",
|
||||
"Page {{current}} of {{total}}": "{{total}} sayfa içinde {{current}}. sayfa",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Seçilen {{count}} dosyayı silmek istediğinizden emin misiniz?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Seçilen {{count}} dosyayı silmek istediğinizden emin misiniz?",
|
||||
"Cloud Storage Usage": "Bulut Depolama Kullanımı",
|
||||
"Rename Group": "Grubu Yeniden Adlandır",
|
||||
"From Directory": "Dizinden",
|
||||
"Successfully imported {{count}} book(s)_one": "Başarıyla 1 kitap içe aktarıldı",
|
||||
"Successfully imported {{count}} book(s)_other": "Başarıyla {{count}} kitap içe aktarıldı",
|
||||
"Count": "Sayım",
|
||||
"Start Page": "Başlangıç Sayfası",
|
||||
"Search in OPDS Catalog...": "OPDS Kataloğunda ara...",
|
||||
"Please log in to use advanced TTS features": "Gelişmiş TTS özelliklerini kullanmak için lütfen giriş yapın",
|
||||
"Word limit of 30 words exceeded.": "30 kelime sınırı aşıldı.",
|
||||
"Proofread": "Düzelt",
|
||||
"Current selection": "Mevcut seçim",
|
||||
"All occurrences in this book": "Bu kitaptaki tüm geçenler",
|
||||
"All occurrences in your library": "Kütüphanenizdeki tüm geçenler",
|
||||
"Selected text:": "Seçili metin:",
|
||||
"Replace with:": "Şununla değiştir:",
|
||||
"Enter text...": "Metin girin…",
|
||||
"Case sensitive:": "Büyük/küçük harfe duyarlı:",
|
||||
"Scope:": "Kapsam:",
|
||||
"Selection": "Seçim",
|
||||
"Library": "Kütüphane",
|
||||
"Yes": "Evet",
|
||||
"No": "Hayır",
|
||||
"Proofread Replacement Rules": "Düzeltme Değiştirme Kuralları",
|
||||
"Selected Text Rules": "Seçili Metin Kuralları",
|
||||
"No selected text replacement rules": "Seçili metin için değiştirme kuralı yok",
|
||||
"Book Specific Rules": "Kitaba Özel Kurallar",
|
||||
"No book-level replacement rules": "Kitap düzeyinde değiştirme kuralı yok",
|
||||
"Disable Quick Action": "Hızlı işlemi devre dışı bırak",
|
||||
"Enable Quick Action on Selection": "Seçimde hızlı işlemi etkinleştir",
|
||||
"None": "Hiçbiri",
|
||||
"Annotation Tools": "Not araçları",
|
||||
"Enable Quick Actions": "Hızlı işlemleri etkinleştir",
|
||||
"Quick Action": "Hızlı işlem",
|
||||
"Copy to Notebook": "Deftere kopyala",
|
||||
"Copy text after selection": "Seçimden sonra metni kopyala",
|
||||
"Highlight text after selection": "Seçimden sonra metni vurgula",
|
||||
"Annotate text after selection": "Seçimden sonra metni not al",
|
||||
"Search text after selection": "Seçimden sonra metni ara",
|
||||
"Look up text in dictionary after selection": "Seçimden sonra metni sözlükte ara",
|
||||
"Look up text in Wikipedia after selection": "Seçimden sonra metni Vikipedya'da ara",
|
||||
"Translate text after selection": "Seçimden sonra metni çevir",
|
||||
"Read text aloud after selection": "Seçimden sonra metni sesli oku",
|
||||
"Proofread text after selection": "Seçimden sonra metni düzelt",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} aktif, {{pendingCount}} beklemede",
|
||||
"{{failedCount}} failed": "{{failedCount}} başarısız",
|
||||
"Waiting...": "Bekleniyor...",
|
||||
"Failed": "Başarısız",
|
||||
"Completed": "Tamamlandı",
|
||||
"Cancelled": "İptal edildi",
|
||||
"Retry": "Yeniden dene",
|
||||
"Active": "Aktif",
|
||||
"Transfer Queue": "Transfer Kuyruğu",
|
||||
"Upload All": "Tümünü Yükle",
|
||||
"Download All": "Tümünü İndir",
|
||||
"Resume Transfers": "Transferleri Sürdür",
|
||||
"Pause Transfers": "Transferleri Duraklat",
|
||||
"Pending": "Beklemede",
|
||||
"No transfers": "Transfer yok",
|
||||
"Retry All": "Tümünü Yeniden Dene",
|
||||
"Clear Completed": "Tamamlananları Temizle",
|
||||
"Clear Failed": "Başarısızları Temizle",
|
||||
"Upload queued: {{title}}": "Yükleme sıraya alındı: {{title}}",
|
||||
"Download queued: {{title}}": "İndirme sıraya alındı: {{title}}",
|
||||
"Book not found in library": "Kitap kütüphanede bulunamadı",
|
||||
"Unknown error": "Bilinmeyen hata",
|
||||
"Please log in to continue": "Devam etmek için giriş yapın",
|
||||
"Cloud File Transfers": "Bulut Dosya Transferleri",
|
||||
"Show Search Results": "Arama sonuçlarını göster",
|
||||
"Search results for '{{term}}'": "'{{term}}' için sonuçlar",
|
||||
"Close Search": "Aramayı kapat",
|
||||
"Previous Result": "Önceki sonuç",
|
||||
"Next Result": "Sonraki sonuç",
|
||||
"Bookmarks": "Yer İşaretleri",
|
||||
"Annotations": "Açıklamalar",
|
||||
"Show Results": "Sonuçları Göster",
|
||||
"Clear search": "Aramayı temizle",
|
||||
"Clear search history": "Arama geçmişini temizle",
|
||||
"Quick action disabled": "Hızlı eylem devre dışı",
|
||||
"Tap to Toggle Footer": "Altbilgiyi değiştirmek için dokunun",
|
||||
"Exported successfully": "Başarıyla dışa aktarıldı",
|
||||
"Book exported successfully.": "Kitap başarıyla dışa aktarıldı.",
|
||||
"Failed to export the book.": "Kitap dışa aktarılamadı.",
|
||||
"Export Book": "Kitabı Dışa Aktar",
|
||||
"Whole word:": "Tam kelime:",
|
||||
"Error": "Hata",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Makale yüklenemiyor. Doğrudan {{link}} üzerinde arama yapmayı deneyin.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Kelime yüklenemiyor. Doğrudan {{link}} üzerinde arama yapmayı deneyin.",
|
||||
"Date Published": "Yayın tarihi",
|
||||
"Only for TTS:": "Sadece TTS için:",
|
||||
"Uploaded": "Yüklendi",
|
||||
"Downloaded": "İndirildi",
|
||||
"Deleted": "Silindi",
|
||||
"Note:": "Not:",
|
||||
"Time:": "Saat:",
|
||||
"Format Options": "Format Seçenekleri",
|
||||
"Export Date": "Dışa Aktarma Tarihi",
|
||||
"Chapter Titles": "Bölüm Başlıkları",
|
||||
"Chapter Separator": "Bölüm Ayracı",
|
||||
"Highlights": "Vurgular",
|
||||
"Note Date": "Not Tarihi",
|
||||
"Advanced": "Gelişmiş",
|
||||
"Hide": "Gizle",
|
||||
"Show": "Göster",
|
||||
"Use Custom Template": "Özel Şablon Kullan",
|
||||
"Export Template": "Dışa Aktarma Şablonu",
|
||||
"Template Syntax:": "Şablon Sözdizimi:",
|
||||
"Insert value": "Değer ekle",
|
||||
"Format date (locale)": "Tarihi biçimlendir (yerel)",
|
||||
"Format date (custom)": "Tarihi biçimlendir (özel)",
|
||||
"Conditional": "Koşullu",
|
||||
"Loop": "Döngü",
|
||||
"Available Variables:": "Kullanılabilir Değişkenler:",
|
||||
"Book title": "Kitap başlığı",
|
||||
"Book author": "Kitap yazarı",
|
||||
"Export date": "Dışa aktarma tarihi",
|
||||
"Array of chapters": "Bölümler dizisi",
|
||||
"Chapter title": "Bölüm başlığı",
|
||||
"Array of annotations": "Notlar dizisi",
|
||||
"Highlighted text": "Vurgulanan metin",
|
||||
"Annotation note": "Not açıklaması",
|
||||
"Update time": "Güncelleme zamanı",
|
||||
"Date Format Tokens:": "Tarih Biçimi Belirteçleri:",
|
||||
"Year (4 digits)": "Yıl (4 basamak)",
|
||||
"Month (01-12)": "Ay (01-12)",
|
||||
"Day (01-31)": "Gün (01-31)",
|
||||
"Hour (00-23)": "Saat (00-23)",
|
||||
"Minute (00-59)": "Dakika (00-59)",
|
||||
"Second (00-59)": "Saniye (00-59)",
|
||||
"Show Source": "Kaynağı Göster",
|
||||
"No content to preview": "Önizlenecek içerik yok",
|
||||
"Export": "Dışa Aktar",
|
||||
"Set Timeout": "Zaman aşımını ayarla",
|
||||
"Select Voice": "Ses seç",
|
||||
"Toggle Sticky Bottom TTS Bar": "Sabit TTS çubuğunu değiştir",
|
||||
"Display what I'm reading on Discord": "Discord'da okuduğumu göster",
|
||||
"Show on Discord": "Discord'da göster"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Токен",
|
||||
"Your OTP token": "Ваш OTP-токен",
|
||||
"Verify token": "Перевірити токен",
|
||||
"Sign in with Google": "Увійти через Google",
|
||||
"Sign in with Apple": "Увійти через Apple",
|
||||
"Sign in with GitHub": "Увійти через GitHub",
|
||||
"Account": "Обліковий запис",
|
||||
"Failed to delete user. Please try again later.": "Не вдалося видалити користувача. Будь ласка, спробуйте пізніше.",
|
||||
"Community Support": "Підтримка спільноти",
|
||||
@@ -349,8 +346,8 @@
|
||||
"{{count}} pages left in chapter_many": "Залишилось {{count}} сторінок у розділі",
|
||||
"{{count}} pages left in chapter_other": "Залишилось {{count}} сторінок у розділі",
|
||||
"Show Remaining Pages": "Показати залишок сторінок",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Керувати підпискою",
|
||||
"Coming Soon": "Незабаром",
|
||||
@@ -707,9 +704,7 @@
|
||||
"Validating...": "Перевірка...",
|
||||
"View All": "Переглянути все",
|
||||
"Forward": "Вперед",
|
||||
"OPDS Catalog": "Каталог OPDS",
|
||||
"Home": "Головна",
|
||||
"Library": "Бібліотека",
|
||||
"{{count}} items_one": "{{count}} елемент",
|
||||
"{{count}} items_few": "{{count}} елементи",
|
||||
"{{count}} items_many": "{{count}} елементів",
|
||||
@@ -731,5 +726,189 @@
|
||||
"Last": "Остання",
|
||||
"Cannot Load Page": "Не вдалося завантажити сторінку",
|
||||
"An error occurred": "Сталася помилка",
|
||||
"Online Library": "Онлайн бібліотека"
|
||||
"Online Library": "Онлайн бібліотека",
|
||||
"URL must start with http:// or https://": "URL повинен починатися з http:// або https://",
|
||||
"Title, Author, Tag, etc...": "Назва, автор, тег тощо...",
|
||||
"Query": "Запит",
|
||||
"Subject": "Тема",
|
||||
"Enter {{terms}}": "Введіть {{terms}}",
|
||||
"No search results found": "Результатів пошуку не знайдено",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Не вдалося завантажити стрічку OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Пошук у {{title}}",
|
||||
"Manage Storage": "Керування сховищем",
|
||||
"Failed to load files": "Не вдалося завантажити файли",
|
||||
"Deleted {{count}} file(s)_one": "Видалено {{count}} файл",
|
||||
"Deleted {{count}} file(s)_few": "Видалено {{count}} файли",
|
||||
"Deleted {{count}} file(s)_many": "Видалено {{count}} файлів",
|
||||
"Deleted {{count}} file(s)_other": "Видалено {{count}} файлів",
|
||||
"Failed to delete {{count}} file(s)_one": "Не вдалося видалити {{count}} файл",
|
||||
"Failed to delete {{count}} file(s)_few": "Не вдалося видалити {{count}} файли",
|
||||
"Failed to delete {{count}} file(s)_many": "Не вдалося видалити {{count}} файлів",
|
||||
"Failed to delete {{count}} file(s)_other": "Не вдалося видалити {{count}} файлів",
|
||||
"Failed to delete files": "Не вдалося видалити файли",
|
||||
"Total Files": "Всього файлів",
|
||||
"Total Size": "Загальний розмір",
|
||||
"Quota": "Квота",
|
||||
"Used": "Використано",
|
||||
"Files": "Файли",
|
||||
"Search files...": "Пошук файлів...",
|
||||
"Newest First": "Спершу новіші",
|
||||
"Oldest First": "Спершу старіші",
|
||||
"Largest First": "Спершу найбільші",
|
||||
"Smallest First": "Спершу найменші",
|
||||
"Name A-Z": "Ім'я A-Z",
|
||||
"Name Z-A": "Ім'я Z-A",
|
||||
"{{count}} selected_one": "Вибрано {{count}} файл",
|
||||
"{{count}} selected_few": "Вибрано {{count}} файли",
|
||||
"{{count}} selected_many": "Вибрано {{count}} файлів",
|
||||
"{{count}} selected_other": "Вибрано {{count}} файлів",
|
||||
"Delete Selected": "Видалити вибране",
|
||||
"Created": "Створено",
|
||||
"No files found": "Файли не знайдено",
|
||||
"No files uploaded yet": "Файли ще не завантажені",
|
||||
"files": "файли",
|
||||
"Page {{current}} of {{total}}": "Сторінка {{current}} з {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Ви впевнені, що хочете видалити {{count}} файл?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_few": "Ви впевнені, що хочете видалити {{count}} файли?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Ви впевнені, що хочете видалити {{count}} файлів?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Ви впевнені, що хочете видалити {{count}} файлів?",
|
||||
"Cloud Storage Usage": "Використання хмарного сховища",
|
||||
"Rename Group": "Перейменувати групу",
|
||||
"From Directory": "З каталогу",
|
||||
"Successfully imported {{count}} book(s)_one": "Успішно імпортовано 1 книгу",
|
||||
"Successfully imported {{count}} book(s)_few": "Успішно імпортовано {{count}} книги",
|
||||
"Successfully imported {{count}} book(s)_many": "Успішно імпортовано {{count}} книг",
|
||||
"Successfully imported {{count}} book(s)_other": "Успішно імпортовано {{count}} книг",
|
||||
"Count": "Кількість",
|
||||
"Start Page": "Початкова сторінка",
|
||||
"Search in OPDS Catalog...": "Пошук у каталозі OPDS...",
|
||||
"Please log in to use advanced TTS features": "Будь ласка, увійдіть, щоб використовувати розширені функції TTS",
|
||||
"Word limit of 30 words exceeded.": "Перевищено ліміт у 30 слів.",
|
||||
"Proofread": "Вичитування",
|
||||
"Current selection": "Поточне виділення",
|
||||
"All occurrences in this book": "Усі входження в цій книзі",
|
||||
"All occurrences in your library": "Усі входження у вашій бібліотеці",
|
||||
"Selected text:": "Виділений текст:",
|
||||
"Replace with:": "Замінити на:",
|
||||
"Enter text...": "Введіть текст…",
|
||||
"Case sensitive:": "З урахуванням регістру:",
|
||||
"Scope:": "Область застосування:",
|
||||
"Selection": "Виділення",
|
||||
"Library": "Бібліотека",
|
||||
"Yes": "Так",
|
||||
"No": "Ні",
|
||||
"Proofread Replacement Rules": "Правила заміни для вичитування",
|
||||
"Selected Text Rules": "Правила для виділеного тексту",
|
||||
"No selected text replacement rules": "Немає правил заміни для виділеного тексту",
|
||||
"Book Specific Rules": "Правила для конкретної книги",
|
||||
"No book-level replacement rules": "Немає правил заміни на рівні книги",
|
||||
"Disable Quick Action": "Вимкнути швидку дію",
|
||||
"Enable Quick Action on Selection": "Увімкнути швидку дію при виборі",
|
||||
"None": "Жоден",
|
||||
"Annotation Tools": "Інструменти анотацій",
|
||||
"Enable Quick Actions": "Увімкнути швидкі дії",
|
||||
"Quick Action": "Швидка дія",
|
||||
"Copy to Notebook": "Скопіювати до блокнота",
|
||||
"Copy text after selection": "Скопіювати текст після виділення",
|
||||
"Highlight text after selection": "Виділити текст після виділення",
|
||||
"Annotate text after selection": "Додати анотацію до тексту після виділення",
|
||||
"Search text after selection": "Шукати текст після виділення",
|
||||
"Look up text in dictionary after selection": "Шукати текст у словнику після виділення",
|
||||
"Look up text in Wikipedia after selection": "Шукати текст у Вікіпедії після виділення",
|
||||
"Translate text after selection": "Перекласти текст після виділення",
|
||||
"Read text aloud after selection": "Прочитати текст вголос після виділення",
|
||||
"Proofread text after selection": "Вичитати текст після виділення",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} активних, {{pendingCount}} очікують",
|
||||
"{{failedCount}} failed": "{{failedCount}} невдалих",
|
||||
"Waiting...": "Очікування...",
|
||||
"Failed": "Помилка",
|
||||
"Completed": "Завершено",
|
||||
"Cancelled": "Скасовано",
|
||||
"Retry": "Повторити",
|
||||
"Active": "Активні",
|
||||
"Transfer Queue": "Черга передачі",
|
||||
"Upload All": "Завантажити все",
|
||||
"Download All": "Завантажити все",
|
||||
"Resume Transfers": "Продовжити передачу",
|
||||
"Pause Transfers": "Призупинити передачу",
|
||||
"Pending": "Очікують",
|
||||
"No transfers": "Немає передач",
|
||||
"Retry All": "Повторити все",
|
||||
"Clear Completed": "Очистити завершені",
|
||||
"Clear Failed": "Очистити невдалі",
|
||||
"Upload queued: {{title}}": "Завантаження в черзі: {{title}}",
|
||||
"Download queued: {{title}}": "Завантаження в черзі: {{title}}",
|
||||
"Book not found in library": "Книгу не знайдено в бібліотеці",
|
||||
"Unknown error": "Невідома помилка",
|
||||
"Please log in to continue": "Увійдіть, щоб продовжити",
|
||||
"Cloud File Transfers": "Передача файлів у хмару",
|
||||
"Show Search Results": "Показати результати",
|
||||
"Search results for '{{term}}'": "Результати для «{{term}}»",
|
||||
"Close Search": "Закрити пошук",
|
||||
"Previous Result": "Попередній результат",
|
||||
"Next Result": "Наступний результат",
|
||||
"Bookmarks": "Закладки",
|
||||
"Annotations": "Анотації",
|
||||
"Show Results": "Показати результати",
|
||||
"Clear search": "Очистити пошук",
|
||||
"Clear search history": "Очистити історію пошуку",
|
||||
"Quick action disabled": "Швидку дію вимкнено",
|
||||
"Tap to Toggle Footer": "Торкніться, щоб перемкнути нижній колонтитул",
|
||||
"Exported successfully": "Успішно експортовано",
|
||||
"Book exported successfully.": "Книгу успішно експортовано.",
|
||||
"Failed to export the book.": "Не вдалося експортувати книгу.",
|
||||
"Export Book": "Експортувати книгу",
|
||||
"Whole word:": "Ціле слово:",
|
||||
"Error": "Помилка",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Не вдалося завантажити статтю. Спробуйте шукати безпосередньо на {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Не вдалося завантажити слово. Спробуйте шукати безпосередньо на {{link}}.",
|
||||
"Date Published": "Дата публікації",
|
||||
"Only for TTS:": "Тільки для TTS:",
|
||||
"Uploaded": "Завантажено",
|
||||
"Downloaded": "Звантажено",
|
||||
"Deleted": "Видалено",
|
||||
"Note:": "Примітка:",
|
||||
"Time:": "Час:",
|
||||
"Format Options": "Параметри формату",
|
||||
"Export Date": "Дата експорту",
|
||||
"Chapter Titles": "Назви розділів",
|
||||
"Chapter Separator": "Роздільник розділів",
|
||||
"Highlights": "Виділення",
|
||||
"Note Date": "Дата примітки",
|
||||
"Advanced": "Розширені",
|
||||
"Hide": "Сховати",
|
||||
"Show": "Показати",
|
||||
"Use Custom Template": "Використовувати власний шаблон",
|
||||
"Export Template": "Шаблон експорту",
|
||||
"Template Syntax:": "Синтаксис шаблону:",
|
||||
"Insert value": "Вставити значення",
|
||||
"Format date (locale)": "Форматувати дату (локаль)",
|
||||
"Format date (custom)": "Форматувати дату (власний)",
|
||||
"Conditional": "Умовний",
|
||||
"Loop": "Цикл",
|
||||
"Available Variables:": "Доступні змінні:",
|
||||
"Book title": "Назва книги",
|
||||
"Book author": "Автор книги",
|
||||
"Export date": "Дата експорту",
|
||||
"Array of chapters": "Масив розділів",
|
||||
"Chapter title": "Назва розділу",
|
||||
"Array of annotations": "Масив приміток",
|
||||
"Highlighted text": "Виділений текст",
|
||||
"Annotation note": "Примітка анотації",
|
||||
"Update time": "Час оновлення",
|
||||
"Date Format Tokens:": "Токени формату дати:",
|
||||
"Year (4 digits)": "Рік (4 цифри)",
|
||||
"Month (01-12)": "Місяць (01-12)",
|
||||
"Day (01-31)": "День (01-31)",
|
||||
"Hour (00-23)": "Година (00-23)",
|
||||
"Minute (00-59)": "Хвилина (00-59)",
|
||||
"Second (00-59)": "Секунда (00-59)",
|
||||
"Show Source": "Показати джерело",
|
||||
"No content to preview": "Немає вмісту для перегляду",
|
||||
"Export": "Експортувати",
|
||||
"Set Timeout": "Встановити тайм-аут",
|
||||
"Select Voice": "Вибрати голос",
|
||||
"Toggle Sticky Bottom TTS Bar": "Перемкнути закріплену панель TTS",
|
||||
"Display what I'm reading on Discord": "Показувати книгу в Discord",
|
||||
"Show on Discord": "Показати в Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "Mã token",
|
||||
"Your OTP token": "Mã OTP của bạn",
|
||||
"Verify token": "Xác minh mã token",
|
||||
"Sign in with Google": "Đăng nhập với Google",
|
||||
"Sign in with Apple": "Đăng nhập với Apple",
|
||||
"Sign in with GitHub": "Đăng nhập với GitHub",
|
||||
"Account": "Tài khoản",
|
||||
"Failed to delete user. Please try again later.": "Không thể xóa người dùng. Vui lòng thử lại sau.",
|
||||
"Community Support": "Hỗ trợ cộng đồng",
|
||||
@@ -337,8 +334,8 @@
|
||||
"Reset Settings": "Đặt lại cài đặt",
|
||||
"{{count}} pages left in chapter_other": "Còn {{count}} trang trong chương",
|
||||
"Show Remaining Pages": "Hiển thị trang còn lại",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Quản lý đăng ký",
|
||||
"Coming Soon": "Sắp ra mắt",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "Đang xác thực...",
|
||||
"View All": "Xem tất cả",
|
||||
"Forward": "Tiếp",
|
||||
"OPDS Catalog": "Danh mục OPDS",
|
||||
"Home": "Trang chủ",
|
||||
"Library": "Thư viện",
|
||||
"{{count}} items_other": "{{count}} mục",
|
||||
"Download completed": "Tải xuống hoàn tất",
|
||||
"Download failed": "Tải xuống thất bại",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "Cuối cùng",
|
||||
"Cannot Load Page": "Không thể tải trang",
|
||||
"An error occurred": "Đã xảy ra lỗi",
|
||||
"Online Library": "Thư viện trực tuyến"
|
||||
"Online Library": "Thư viện trực tuyến",
|
||||
"URL must start with http:// or https://": "URL phải bắt đầu bằng http:// hoặc https://",
|
||||
"Title, Author, Tag, etc...": "Tiêu đề, Tác giả, Thẻ, v.v...",
|
||||
"Query": "Truy vấn",
|
||||
"Subject": "Chủ đề",
|
||||
"Enter {{terms}}": "Nhập {{terms}}",
|
||||
"No search results found": "Không tìm thấy kết quả tìm kiếm",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Không tải được nguồn cấp OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Tìm kiếm trong {{title}}",
|
||||
"Manage Storage": "Quản lý bộ nhớ",
|
||||
"Failed to load files": "Không tải được tệp",
|
||||
"Deleted {{count}} file(s)_other": "Đã xóa {{count}} tệp",
|
||||
"Failed to delete {{count}} file(s)_other": "Không xóa được {{count}} tệp",
|
||||
"Failed to delete files": "Không xóa được tệp",
|
||||
"Total Files": "Tổng số tệp",
|
||||
"Total Size": "Tổng dung lượng",
|
||||
"Quota": "Dung lượng cho phép",
|
||||
"Used": "Đã sử dụng",
|
||||
"Files": "Tệp",
|
||||
"Search files...": "Tìm tệp...",
|
||||
"Newest First": "Mới nhất trước",
|
||||
"Oldest First": "Cũ nhất trước",
|
||||
"Largest First": "Lớn nhất trước",
|
||||
"Smallest First": "Nhỏ nhất trước",
|
||||
"Name A-Z": "Tên A-Z",
|
||||
"Name Z-A": "Tên Z-A",
|
||||
"{{count}} selected_other": "Đã chọn {{count}} tệp",
|
||||
"Delete Selected": "Xóa các mục đã chọn",
|
||||
"Created": "Đã tạo",
|
||||
"No files found": "Không tìm thấy tệp",
|
||||
"No files uploaded yet": "Chưa tải lên tệp nào",
|
||||
"files": "tệp",
|
||||
"Page {{current}} of {{total}}": "Trang {{current}} trên {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Bạn có chắc muốn xóa {{count}} tệp đã chọn không?",
|
||||
"Cloud Storage Usage": "Sử dụng lưu trữ đám mây",
|
||||
"Rename Group": "Đổi tên nhóm",
|
||||
"From Directory": "Từ thư mục",
|
||||
"Successfully imported {{count}} book(s)_other": "Đã nhập thành công {{count}} sách",
|
||||
"Count": "Số lượng",
|
||||
"Start Page": "Trang bắt đầu",
|
||||
"Search in OPDS Catalog...": "Tìm kiếm trong danh mục OPDS...",
|
||||
"Please log in to use advanced TTS features": "Vui lòng đăng nhập để sử dụng các tính năng TTS nâng cao",
|
||||
"Word limit of 30 words exceeded.": "Đã vượt quá giới hạn 30 từ.",
|
||||
"Proofread": "Hiệu đính",
|
||||
"Current selection": "Vùng chọn hiện tại",
|
||||
"All occurrences in this book": "Tất cả các lần xuất hiện trong sách này",
|
||||
"All occurrences in your library": "Tất cả các lần xuất hiện trong thư viện của bạn",
|
||||
"Selected text:": "Văn bản đã chọn:",
|
||||
"Replace with:": "Thay thế bằng:",
|
||||
"Enter text...": "Nhập văn bản…",
|
||||
"Case sensitive:": "Phân biệt chữ hoa/thường:",
|
||||
"Scope:": "Phạm vi:",
|
||||
"Selection": "Vùng chọn",
|
||||
"Library": "Thư viện",
|
||||
"Yes": "Có",
|
||||
"No": "Không",
|
||||
"Proofread Replacement Rules": "Quy tắc thay thế hiệu đính",
|
||||
"Selected Text Rules": "Quy tắc cho văn bản đã chọn",
|
||||
"No selected text replacement rules": "Không có quy tắc thay thế cho văn bản đã chọn",
|
||||
"Book Specific Rules": "Quy tắc dành riêng cho sách",
|
||||
"No book-level replacement rules": "Không có quy tắc thay thế ở cấp sách",
|
||||
"Disable Quick Action": "Vô hiệu hóa Hành động Nhanh",
|
||||
"Enable Quick Action on Selection": "Bật Hành động Nhanh khi chọn",
|
||||
"None": "Không có",
|
||||
"Annotation Tools": "Công cụ chú thích",
|
||||
"Enable Quick Actions": "Bật Hành động Nhanh",
|
||||
"Quick Action": "Hành động Nhanh",
|
||||
"Copy to Notebook": "Sao chép vào Sổ tay",
|
||||
"Copy text after selection": "Sao chép văn bản sau khi chọn",
|
||||
"Highlight text after selection": "Đánh dấu văn bản sau khi chọn",
|
||||
"Annotate text after selection": "Chú thích văn bản sau khi chọn",
|
||||
"Search text after selection": "Tìm kiếm văn bản sau khi chọn",
|
||||
"Look up text in dictionary after selection": "Tra cứu văn bản trong từ điển sau khi chọn",
|
||||
"Look up text in Wikipedia after selection": "Tra cứu văn bản trong Wikipedia sau khi chọn",
|
||||
"Translate text after selection": "Dịch văn bản sau khi chọn",
|
||||
"Read text aloud after selection": "Đọc to văn bản sau khi chọn",
|
||||
"Proofread text after selection": "Hiệu đính văn bản sau khi chọn",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} đang hoạt động, {{pendingCount}} đang chờ",
|
||||
"{{failedCount}} failed": "{{failedCount}} thất bại",
|
||||
"Waiting...": "Đang chờ...",
|
||||
"Failed": "Thất bại",
|
||||
"Completed": "Hoàn thành",
|
||||
"Cancelled": "Đã hủy",
|
||||
"Retry": "Thử lại",
|
||||
"Active": "Đang hoạt động",
|
||||
"Transfer Queue": "Hàng đợi truyền",
|
||||
"Upload All": "Tải lên tất cả",
|
||||
"Download All": "Tải xuống tất cả",
|
||||
"Resume Transfers": "Tiếp tục truyền",
|
||||
"Pause Transfers": "Tạm dừng truyền",
|
||||
"Pending": "Đang chờ",
|
||||
"No transfers": "Không có truyền",
|
||||
"Retry All": "Thử lại tất cả",
|
||||
"Clear Completed": "Xóa đã hoàn thành",
|
||||
"Clear Failed": "Xóa thất bại",
|
||||
"Upload queued: {{title}}": "Tải lên trong hàng đợi: {{title}}",
|
||||
"Download queued: {{title}}": "Tải xuống trong hàng đợi: {{title}}",
|
||||
"Book not found in library": "Không tìm thấy sách trong thư viện",
|
||||
"Unknown error": "Lỗi không xác định",
|
||||
"Please log in to continue": "Vui lòng đăng nhập để tiếp tục",
|
||||
"Cloud File Transfers": "Truyền tệp đám mây",
|
||||
"Show Search Results": "Hiển thị kết quả tìm kiếm",
|
||||
"Search results for '{{term}}'": "Kết quả cho '{{term}}'",
|
||||
"Close Search": "Đóng tìm kiếm",
|
||||
"Previous Result": "Kết quả trước",
|
||||
"Next Result": "Kết quả tiếp theo",
|
||||
"Bookmarks": "Dấu trang",
|
||||
"Annotations": "Chú thích",
|
||||
"Show Results": "Hiển thị kết quả",
|
||||
"Clear search": "Xóa tìm kiếm",
|
||||
"Clear search history": "Xóa lịch sử tìm kiếm",
|
||||
"Quick action disabled": "Đã tắt thao tác nhanh",
|
||||
"Tap to Toggle Footer": "Nhấn để bật/tắt chân trang",
|
||||
"Exported successfully": "Xuất thành công",
|
||||
"Book exported successfully.": "Sách đã được xuất thành công.",
|
||||
"Failed to export the book.": "Xuất sách thất bại.",
|
||||
"Export Book": "Xuất sách",
|
||||
"Whole word:": "Toàn bộ từ:",
|
||||
"Error": "Lỗi",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "Không thể tải bài viết. Hãy thử tìm kiếm trực tiếp trên {{link}}.",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "Không thể tải từ. Hãy thử tìm kiếm trực tiếp trên {{link}}.",
|
||||
"Date Published": "Ngày xuất bản",
|
||||
"Only for TTS:": "Chỉ dành cho TTS:",
|
||||
"Uploaded": "Đã tải lên",
|
||||
"Downloaded": "Đã tải xuống",
|
||||
"Deleted": "Đã xóa",
|
||||
"Note:": "Ghi chú:",
|
||||
"Time:": "Thời gian:",
|
||||
"Format Options": "Tùy chọn định dạng",
|
||||
"Export Date": "Ngày xuất",
|
||||
"Chapter Titles": "Tiêu đề chương",
|
||||
"Chapter Separator": "Dấu phân cách chương",
|
||||
"Highlights": "Đánh dấu",
|
||||
"Note Date": "Ngày ghi chú",
|
||||
"Advanced": "Nâng cao",
|
||||
"Hide": "Ẩn",
|
||||
"Show": "Hiển thị",
|
||||
"Use Custom Template": "Sử dụng mẫu tùy chỉnh",
|
||||
"Export Template": "Mẫu xuất",
|
||||
"Template Syntax:": "Cú pháp mẫu:",
|
||||
"Insert value": "Chèn giá trị",
|
||||
"Format date (locale)": "Định dạng ngày (địa phương)",
|
||||
"Format date (custom)": "Định dạng ngày (tùy chỉnh)",
|
||||
"Conditional": "Điều kiện",
|
||||
"Loop": "Vòng lặp",
|
||||
"Available Variables:": "Biến có sẵn:",
|
||||
"Book title": "Tiêu đề sách",
|
||||
"Book author": "Tác giả sách",
|
||||
"Export date": "Ngày xuất",
|
||||
"Array of chapters": "Danh sách chương",
|
||||
"Chapter title": "Tiêu đề chương",
|
||||
"Array of annotations": "Danh sách chú thích",
|
||||
"Highlighted text": "Văn bản được đánh dấu",
|
||||
"Annotation note": "Ghi chú chú thích",
|
||||
"Update time": "Thời gian cập nhật",
|
||||
"Date Format Tokens:": "Token định dạng ngày:",
|
||||
"Year (4 digits)": "Năm (4 chữ số)",
|
||||
"Month (01-12)": "Tháng (01-12)",
|
||||
"Day (01-31)": "Ngày (01-31)",
|
||||
"Hour (00-23)": "Giờ (00-23)",
|
||||
"Minute (00-59)": "Phút (00-59)",
|
||||
"Second (00-59)": "Giây (00-59)",
|
||||
"Show Source": "Hiển thị nguồn",
|
||||
"No content to preview": "Không có nội dung để xem trước",
|
||||
"Export": "Xuất",
|
||||
"Set Timeout": "Đặt thời gian chờ",
|
||||
"Select Voice": "Chọn giọng nói",
|
||||
"Toggle Sticky Bottom TTS Bar": "Bật/tắt thanh TTS cố định",
|
||||
"Display what I'm reading on Discord": "Hiển thị sách đang đọc trên Discord",
|
||||
"Show on Discord": "Hiện trên Discord"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "令牌",
|
||||
"Your OTP token": "您的 OTP 令牌",
|
||||
"Verify token": "验证令牌",
|
||||
"Sign in with Google": "使用 Google 登录",
|
||||
"Sign in with Apple": "使用 Apple 登录",
|
||||
"Sign in with GitHub": "使用 GitHub 登录",
|
||||
"Account": "账户",
|
||||
"Failed to delete user. Please try again later.": "删除用户失败。请稍后重试。",
|
||||
"Community Support": "社区支持",
|
||||
@@ -337,8 +334,8 @@
|
||||
"Reset Settings": "重置设置",
|
||||
"{{count}} pages left in chapter_other": "本章剩余 {{count}} 页",
|
||||
"Show Remaining Pages": "显示剩余页数",
|
||||
"Source Han Serif CN VF": "思源宋体",
|
||||
"Huiwen-mincho": "汇文明朝体",
|
||||
"Source Han Serif CN": "思源宋体",
|
||||
"Huiwen-MinchoGBK": "汇文明朝体",
|
||||
"KingHwa_OldSong": "京华老宋体",
|
||||
"Manage Subscription": "管理订阅",
|
||||
"Coming Soon": "即将上线",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "验证中...",
|
||||
"View All": "查看全部",
|
||||
"Forward": "前进",
|
||||
"OPDS Catalog": "OPDS 目录",
|
||||
"Home": "首页",
|
||||
"Library": "图书馆",
|
||||
"{{count}} items_other": "{{count}} 项",
|
||||
"Download completed": "下载完成",
|
||||
"Download failed": "下载失败",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "末页",
|
||||
"Cannot Load Page": "无法加载页面",
|
||||
"An error occurred": "发生错误",
|
||||
"Online Library": "在线书库"
|
||||
"Online Library": "在线书库",
|
||||
"URL must start with http:// or https://": "URL 必须以 http:// 或 https:// 开头",
|
||||
"Title, Author, Tag, etc...": "标题,作者,标签等...",
|
||||
"Query": "查询",
|
||||
"Subject": "主题",
|
||||
"Enter {{terms}}": "输入 {{terms}}",
|
||||
"No search results found": "未找到搜索结果",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "加载 OPDS 源失败:{{status}} {{statusText}}",
|
||||
"Search in {{title}}": "在 {{title}} 中搜索",
|
||||
"Manage Storage": "管理存储",
|
||||
"Failed to load files": "加载文件失败",
|
||||
"Deleted {{count}} file(s)_other": "已删除 {{count}} 个文件",
|
||||
"Failed to delete {{count}} file(s)_other": "删除 {{count}} 个文件失败",
|
||||
"Failed to delete files": "删除文件失败",
|
||||
"Total Files": "文件总数",
|
||||
"Total Size": "总大小",
|
||||
"Quota": "配额",
|
||||
"Used": "已使用",
|
||||
"Files": "文件",
|
||||
"Search files...": "搜索文件...",
|
||||
"Newest First": "最新优先",
|
||||
"Oldest First": "最旧优先",
|
||||
"Largest First": "最大优先",
|
||||
"Smallest First": "最小优先",
|
||||
"Name A-Z": "名称 A-Z",
|
||||
"Name Z-A": "名称 Z-A",
|
||||
"{{count}} selected_other": "已选择 {{count}} 个文件",
|
||||
"Delete Selected": "删除选中项",
|
||||
"Created": "创建时间",
|
||||
"No files found": "未找到文件",
|
||||
"No files uploaded yet": "尚未上传文件",
|
||||
"files": "文件",
|
||||
"Page {{current}} of {{total}}": "第 {{current}} 页,共 {{total}} 页",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "确定要删除已选择的 {{count}} 个文件吗?",
|
||||
"Cloud Storage Usage": "云存储使用情况",
|
||||
"Rename Group": "重命名分组",
|
||||
"From Directory": "从文件夹导入",
|
||||
"Successfully imported {{count}} book(s)_other": "成功导入 {{count}} 本书",
|
||||
"Count": "数量",
|
||||
"Start Page": "起始页",
|
||||
"Search in OPDS Catalog...": "在 OPDS 目录中搜索...",
|
||||
"Please log in to use advanced TTS features": "请登录以使用高级 TTS 功能",
|
||||
"Word limit of 30 words exceeded.": "已超过 30 个词的限制。",
|
||||
"Proofread": "校对",
|
||||
"Current selection": "当前选中内容",
|
||||
"All occurrences in this book": "本书中的所有出现位置",
|
||||
"All occurrences in your library": "整个书库中的所有出现位置",
|
||||
"Selected text:": "选中文本:",
|
||||
"Replace with:": "替换为:",
|
||||
"Enter text...": "请输入文本…",
|
||||
"Case sensitive:": "区分大小写:",
|
||||
"Scope:": "作用范围:",
|
||||
"Selection": "选中内容",
|
||||
"Library": "书库",
|
||||
"Yes": "是",
|
||||
"No": "否",
|
||||
"Proofread Replacement Rules": "校对替换规则",
|
||||
"Selected Text Rules": "选中文本规则",
|
||||
"No selected text replacement rules": "暂无选中文本的替换规则",
|
||||
"Book Specific Rules": "书籍范围规则",
|
||||
"No book-level replacement rules": "暂无书籍级别的替换规则",
|
||||
"Disable Quick Action": "禁用快速操作",
|
||||
"Enable Quick Action on Selection": "在选择时启用快速操作",
|
||||
"None": "无",
|
||||
"Annotation Tools": "注释工具",
|
||||
"Enable Quick Actions": "启用快捷操作",
|
||||
"Quick Action": "快捷操作",
|
||||
"Copy to Notebook": "复制到笔记本",
|
||||
"Copy text after selection": "复制选中文本",
|
||||
"Highlight text after selection": "划线选中文本",
|
||||
"Annotate text after selection": "注释选中文本",
|
||||
"Search text after selection": "搜索选中文本",
|
||||
"Look up text in dictionary after selection": "在字典中查找选中文本",
|
||||
"Look up text in Wikipedia after selection": "在维基百科中查找选中文本",
|
||||
"Translate text after selection": "翻译选中文本",
|
||||
"Read text aloud after selection": "朗读选中文本",
|
||||
"Proofread text after selection": "校对选中文本",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} 进行中,{{pendingCount}} 等待中",
|
||||
"{{failedCount}} failed": "{{failedCount}} 失败",
|
||||
"Waiting...": "等待中...",
|
||||
"Failed": "失败",
|
||||
"Completed": "已完成",
|
||||
"Cancelled": "已取消",
|
||||
"Retry": "重试",
|
||||
"Active": "进行中",
|
||||
"Transfer Queue": "传输队列",
|
||||
"Upload All": "全部上传",
|
||||
"Download All": "全部下载",
|
||||
"Resume Transfers": "继续传输",
|
||||
"Pause Transfers": "暂停传输",
|
||||
"Pending": "等待中",
|
||||
"No transfers": "没有传输",
|
||||
"Retry All": "全部重试",
|
||||
"Clear Completed": "清除已完成",
|
||||
"Clear Failed": "清除失败",
|
||||
"Upload queued: {{title}}": "上传已排队:{{title}}",
|
||||
"Download queued: {{title}}": "下载已排队:{{title}}",
|
||||
"Book not found in library": "在书库中找不到书籍",
|
||||
"Unknown error": "未知错误",
|
||||
"Please log in to continue": "请登录以继续",
|
||||
"Cloud File Transfers": "云文件传输",
|
||||
"Show Search Results": "显示搜索结果",
|
||||
"Search results for '{{term}}'": "包含\"{{term}}\"的结果",
|
||||
"Close Search": "关闭搜索",
|
||||
"Previous Result": "上一个结果",
|
||||
"Next Result": "下一个结果",
|
||||
"Bookmarks": "书签",
|
||||
"Annotations": "注释",
|
||||
"Show Results": "显示结果",
|
||||
"Clear search": "清除搜索",
|
||||
"Clear search history": "清除搜索历史",
|
||||
"Quick action disabled": "快捷操作已禁用",
|
||||
"Tap to Toggle Footer": "点击切换页脚",
|
||||
"Exported successfully": "导出成功",
|
||||
"Book exported successfully.": "书籍导出成功。",
|
||||
"Failed to export the book.": "书籍导出失败。",
|
||||
"Export Book": "导出书籍",
|
||||
"Whole word:": "全词匹配:",
|
||||
"Only for TTS:": "仅用于TTS:",
|
||||
"Error": "错误",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "无法加载文章。请直接在 {{link}} 上搜索。",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "无法加载词汇。请直接在 {{link}} 上搜索。",
|
||||
"Date Published": "出版日期",
|
||||
"Uploaded": "已上传",
|
||||
"Downloaded": "已下载",
|
||||
"Deleted": "已删除",
|
||||
"Note:": "笔记:",
|
||||
"Time:": "时间:",
|
||||
"Format Options": "格式选项",
|
||||
"Export Date": "导出日期",
|
||||
"Chapter Titles": "章节标题",
|
||||
"Chapter Separator": "章节分隔符",
|
||||
"Highlights": "高亮",
|
||||
"Note Date": "笔记日期",
|
||||
"Advanced": "高级",
|
||||
"Hide": "隐藏",
|
||||
"Show": "显示",
|
||||
"Use Custom Template": "使用自定义模板",
|
||||
"Export Template": "导出模板",
|
||||
"Template Syntax:": "模板语法:",
|
||||
"Insert value": "插入值",
|
||||
"Format date (locale)": "格式化日期(区域)",
|
||||
"Format date (custom)": "格式化日期(自定义)",
|
||||
"Conditional": "条件",
|
||||
"Loop": "循环",
|
||||
"Available Variables:": "可用变量:",
|
||||
"Book title": "书名",
|
||||
"Book author": "作者",
|
||||
"Export date": "导出日期",
|
||||
"Array of chapters": "章节数组",
|
||||
"Chapter title": "章节标题",
|
||||
"Array of annotations": "标注数组",
|
||||
"Highlighted text": "高亮文本",
|
||||
"Annotation note": "标注笔记",
|
||||
"Update time": "更新时间",
|
||||
"Date Format Tokens:": "日期格式标记:",
|
||||
"Year (4 digits)": "年(4位)",
|
||||
"Month (01-12)": "月(01-12)",
|
||||
"Day (01-31)": "日(01-31)",
|
||||
"Hour (00-23)": "时(00-23)",
|
||||
"Minute (00-59)": "分(00-59)",
|
||||
"Second (00-59)": "秒(00-59)",
|
||||
"Show Source": "显示源码",
|
||||
"No content to preview": "无内容可预览",
|
||||
"Export": "导出",
|
||||
"Set Timeout": "设置超时",
|
||||
"Select Voice": "选择语音",
|
||||
"Toggle Sticky Bottom TTS Bar": "切换固定TTS栏",
|
||||
"Display what I'm reading on Discord": "在Discord显示阅读状态",
|
||||
"Show on Discord": "在Discord显示"
|
||||
}
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
"Token": "令牌",
|
||||
"Your OTP token": "您的 OTP 令牌",
|
||||
"Verify token": "驗證令牌",
|
||||
"Sign in with Google": "使用 Google 登入",
|
||||
"Sign in with Apple": "使用 Apple 登入",
|
||||
"Sign in with GitHub": "使用 GitHub 登入",
|
||||
"Account": "帳戶",
|
||||
"Failed to delete user. Please try again later.": "刪除使用者失敗。請稍後再試。",
|
||||
"Community Support": "社群支援",
|
||||
@@ -337,8 +334,8 @@
|
||||
"Reset Settings": "重置設置",
|
||||
"{{count}} pages left in chapter_other": "本章剩餘 {{count}} 頁",
|
||||
"Show Remaining Pages": "顯示剩餘頁數",
|
||||
"Source Han Serif CN VF": "思源宋體",
|
||||
"Huiwen-mincho": "匯文明朝體",
|
||||
"Source Han Serif CN": "思源宋體",
|
||||
"Huiwen-MinchoGBK": "匯文明朝體",
|
||||
"KingHwa_OldSong": "京華老宋體",
|
||||
"Manage Subscription": "管理訂閱",
|
||||
"Coming Soon": "即將推出",
|
||||
@@ -695,9 +692,7 @@
|
||||
"Validating...": "驗證中...",
|
||||
"View All": "檢視全部",
|
||||
"Forward": "前往",
|
||||
"OPDS Catalog": "OPDS 目錄",
|
||||
"Home": "首頁",
|
||||
"Library": "圖書館",
|
||||
"{{count}} items_other": "{{count}} 項",
|
||||
"Download completed": "下載完成",
|
||||
"Download failed": "下載失敗",
|
||||
@@ -716,5 +711,174 @@
|
||||
"Last": "最後一頁",
|
||||
"Cannot Load Page": "無法載入頁面",
|
||||
"An error occurred": "發生錯誤",
|
||||
"Online Library": "線上書庫"
|
||||
"Online Library": "線上書庫",
|
||||
"URL must start with http:// or https://": "URL 必須以 http:// 或 https:// 開頭",
|
||||
"Title, Author, Tag, etc...": "標題、作者、標籤等...",
|
||||
"Query": "查詢",
|
||||
"Subject": "主題",
|
||||
"Enter {{terms}}": "輸入 {{terms}}",
|
||||
"No search results found": "未找到搜尋結果",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "載入 OPDS 資料源失敗:{{status}} {{statusText}}",
|
||||
"Search in {{title}}": "在 {{title}} 中搜尋",
|
||||
"Manage Storage": "管理儲存",
|
||||
"Failed to load files": "載入檔案失敗",
|
||||
"Deleted {{count}} file(s)_other": "已刪除 {{count}} 個檔案",
|
||||
"Failed to delete {{count}} file(s)_other": "刪除 {{count}} 個檔案失敗",
|
||||
"Failed to delete files": "刪除檔案失敗",
|
||||
"Total Files": "檔案總數",
|
||||
"Total Size": "總大小",
|
||||
"Quota": "配額",
|
||||
"Used": "已使用",
|
||||
"Files": "檔案",
|
||||
"Search files...": "搜尋檔案...",
|
||||
"Newest First": "最新優先",
|
||||
"Oldest First": "最舊優先",
|
||||
"Largest First": "最大優先",
|
||||
"Smallest First": "最小優先",
|
||||
"Name A-Z": "名稱 A-Z",
|
||||
"Name Z-A": "名稱 Z-A",
|
||||
"{{count}} selected_other": "已選擇 {{count}} 個檔案",
|
||||
"Delete Selected": "刪除所選",
|
||||
"Created": "建立時間",
|
||||
"No files found": "找不到檔案",
|
||||
"No files uploaded yet": "尚未上傳檔案",
|
||||
"files": "檔案",
|
||||
"Page {{current}} of {{total}}": "第 {{current}} 頁,共 {{total}} 頁",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "確定要刪除已選擇的 {{count}} 個檔案嗎?",
|
||||
"Cloud Storage Usage": "雲端儲存使用情況",
|
||||
"Rename Group": "重新命名",
|
||||
"From Directory": "從目錄導入",
|
||||
"Successfully imported {{count}} book(s)_other": "成功導入 {{count}} 本書",
|
||||
"Count": "數量",
|
||||
"Start Page": "起始頁",
|
||||
"Search in OPDS Catalog...": "在 OPDS 目錄中搜尋...",
|
||||
"Please log in to use advanced TTS features": "請登入以使用進階 TTS 功能",
|
||||
"Word limit of 30 words exceeded.": "已超過 30 個詞的限制。",
|
||||
"Proofread": "校對",
|
||||
"Current selection": "目前選取內容",
|
||||
"All occurrences in this book": "本書中的所有出現位置",
|
||||
"All occurrences in your library": "整個書庫中的所有出現位置",
|
||||
"Selected text:": "已選取文字:",
|
||||
"Replace with:": "取代為:",
|
||||
"Enter text...": "請輸入文字…",
|
||||
"Case sensitive:": "區分大小寫:",
|
||||
"Scope:": "適用範圍:",
|
||||
"Selection": "選取內容",
|
||||
"Library": "書庫",
|
||||
"Yes": "是",
|
||||
"No": "否",
|
||||
"Proofread Replacement Rules": "校對取代規則",
|
||||
"Selected Text Rules": "已選取文字規則",
|
||||
"No selected text replacement rules": "沒有已選取文字的取代規則",
|
||||
"Book Specific Rules": "書籍層級規則",
|
||||
"No book-level replacement rules": "沒有書籍層級的取代規則",
|
||||
"Disable Quick Action": "停用快速操作",
|
||||
"Enable Quick Action on Selection": "選取時啟用快速操作",
|
||||
"None": "無",
|
||||
"Annotation Tools": "註解工具",
|
||||
"Enable Quick Actions": "啟用快速操作",
|
||||
"Quick Action": "快速操作",
|
||||
"Copy to Notebook": "複製到筆記本",
|
||||
"Copy text after selection": "複製選取後的文字",
|
||||
"Highlight text after selection": "劃線選取後的文字",
|
||||
"Annotate text after selection": "註解選取後的文字",
|
||||
"Search text after selection": "搜尋選取後的文字",
|
||||
"Look up text in dictionary after selection": "在字典中查找選取後的文字",
|
||||
"Look up text in Wikipedia after selection": "在維基百科中查找選取後的文字",
|
||||
"Translate text after selection": "翻譯選取後的文字",
|
||||
"Read text aloud after selection": "朗讀選取後的文字",
|
||||
"Proofread text after selection": "校對選取後的文字",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} 進行中,{{pendingCount}} 等待中",
|
||||
"{{failedCount}} failed": "{{failedCount}} 失敗",
|
||||
"Waiting...": "等待中...",
|
||||
"Failed": "失敗",
|
||||
"Completed": "已完成",
|
||||
"Cancelled": "已取消",
|
||||
"Retry": "重試",
|
||||
"Active": "進行中",
|
||||
"Transfer Queue": "傳輸佇列",
|
||||
"Upload All": "全部上傳",
|
||||
"Download All": "全部下載",
|
||||
"Resume Transfers": "繼續傳輸",
|
||||
"Pause Transfers": "暫停傳輸",
|
||||
"Pending": "等待中",
|
||||
"No transfers": "沒有傳輸",
|
||||
"Retry All": "全部重試",
|
||||
"Clear Completed": "清除已完成",
|
||||
"Clear Failed": "清除失敗",
|
||||
"Upload queued: {{title}}": "上傳已排隊:{{title}}",
|
||||
"Download queued: {{title}}": "下載已排隊:{{title}}",
|
||||
"Book not found in library": "在書庫中找不到書籍",
|
||||
"Unknown error": "未知錯誤",
|
||||
"Please log in to continue": "請登入以繼續",
|
||||
"Cloud File Transfers": "雲端檔案傳輸",
|
||||
"Show Search Results": "顯示搜尋結果",
|
||||
"Search results for '{{term}}'": "包含「{{term}}」的結果",
|
||||
"Close Search": "關閉搜尋",
|
||||
"Previous Result": "上一個結果",
|
||||
"Next Result": "下一個結果",
|
||||
"Bookmarks": "書籤",
|
||||
"Annotations": "註解",
|
||||
"Show Results": "顯示結果",
|
||||
"Clear search": "清除搜尋",
|
||||
"Clear search history": "清除搜尋歷史",
|
||||
"Quick action disabled": "快捷操作已停用",
|
||||
"Tap to Toggle Footer": "點擊切換頁尾",
|
||||
"Exported successfully": "匯出成功",
|
||||
"Book exported successfully.": "書籍匯出成功。",
|
||||
"Failed to export the book.": "書籍匯出失敗。",
|
||||
"Export Book": "匯出書籍",
|
||||
"Whole word:": "全詞匹配:",
|
||||
"Error": "錯誤",
|
||||
"Unable to load the article. Try searching directly on {{link}}.": "無法載入文章。請直接在 {{link}} 上搜尋。",
|
||||
"Unable to load the word. Try searching directly on {{link}}.": "無法載入詞彙。請直接在 {{link}} 上搜尋。",
|
||||
"Date Published": "出版日期",
|
||||
"Only for TTS:": "僅用於TTS:",
|
||||
"Uploaded": "已上傳",
|
||||
"Downloaded": "已下載",
|
||||
"Deleted": "已刪除",
|
||||
"Note:": "筆記:",
|
||||
"Time:": "時間:",
|
||||
"Format Options": "格式選項",
|
||||
"Export Date": "匯出日期",
|
||||
"Chapter Titles": "章節標題",
|
||||
"Chapter Separator": "章節分隔符",
|
||||
"Highlights": "標記",
|
||||
"Note Date": "筆記日期",
|
||||
"Advanced": "進階",
|
||||
"Hide": "隱藏",
|
||||
"Show": "顯示",
|
||||
"Use Custom Template": "使用自訂模板",
|
||||
"Export Template": "匯出模板",
|
||||
"Template Syntax:": "模板語法:",
|
||||
"Insert value": "插入值",
|
||||
"Format date (locale)": "格式化日期(區域)",
|
||||
"Format date (custom)": "格式化日期(自訂)",
|
||||
"Conditional": "條件",
|
||||
"Loop": "迴圈",
|
||||
"Available Variables:": "可用變數:",
|
||||
"Book title": "書名",
|
||||
"Book author": "作者",
|
||||
"Export date": "匯出日期",
|
||||
"Array of chapters": "章節陣列",
|
||||
"Chapter title": "章節標題",
|
||||
"Array of annotations": "標註陣列",
|
||||
"Highlighted text": "標記文字",
|
||||
"Annotation note": "標註筆記",
|
||||
"Update time": "更新時間",
|
||||
"Date Format Tokens:": "日期格式標記:",
|
||||
"Year (4 digits)": "年(4位)",
|
||||
"Month (01-12)": "月(01-12)",
|
||||
"Day (01-31)": "日(01-31)",
|
||||
"Hour (00-23)": "時(00-23)",
|
||||
"Minute (00-59)": "分(00-59)",
|
||||
"Second (00-59)": "秒(00-59)",
|
||||
"Show Source": "顯示原始碼",
|
||||
"No content to preview": "無內容可預覽",
|
||||
"Export": "匯出",
|
||||
"Set Timeout": "設定逾時",
|
||||
"Select Voice": "選擇語音",
|
||||
"Toggle Sticky Bottom TTS Bar": "切換固定TTS欄",
|
||||
"Display what I'm reading on Discord": "在Discord顯示閱讀狀態",
|
||||
"Show on Discord": "在Discord顯示"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,80 @@
|
||||
{
|
||||
"releases": {
|
||||
"0.9.98": {
|
||||
"date": "2026-01-21",
|
||||
"notes": [
|
||||
"Bookshelf: Added the ability to export original book files directly from the book details dialog",
|
||||
"Bookshelf: Added a new sorting option to organize books by publish date",
|
||||
"Bookshelf: Improved performance for large library with faster and more stable imports",
|
||||
"OPDS: Improved download reliability by correctly detecting filenames for PDF and CBZ files",
|
||||
"Proofreading: Added support for whole-word replacement to make text corrections more precise",
|
||||
"Notes: Added the ability to export notes using a customizable template",
|
||||
"PDF: Added support for panning within PDF documents for easier navigation",
|
||||
"PDF: Improved performance by pre-rendering the next page for smoother page transitions",
|
||||
"Text-to-Speech: Added support for word replacement to improve pronunciation during playback",
|
||||
"Text-to-Speech: Fixed an issue where playback speed settings were not applied correctly on Linux",
|
||||
"E-ink: Improved reading stability by preventing unwanted scrolling when animations are disabled or on e-ink devices",
|
||||
"Discord: Added Discord Rich Presence to display your current reading status on Desktop platforms",
|
||||
"Android: Fixed an issue that could cause occasional crashes during app startup",
|
||||
"Fonts: Improved web font loading for faster and more stable text rendering"
|
||||
]
|
||||
},
|
||||
"0.9.97": {
|
||||
"date": "2026-01-10",
|
||||
"notes": [
|
||||
"Annotations: Added annotation bubble icons directly on text with instant note popups",
|
||||
"Annotations: Enabled quick highlighting when selecting text",
|
||||
"Annotations: Added the ability to edit the text range of existing highlights",
|
||||
"Annotations: Added an annotation navigation bar for easier browsing",
|
||||
"OPDS: Fixed an issue where CBZ files could not be downloaded from OPDS catalogs",
|
||||
"OPDS: Improved compatibility with Komga OPDS servers",
|
||||
"OPDS: Added support for OPDS servers using self-signed SSL certificates",
|
||||
"PDF: Fixed an issue where some PDF files failed to open",
|
||||
"E-ink: Optimized colors and layout for improved e-ink device readability",
|
||||
"Search: Added search term history for quicker repeated searches",
|
||||
"Text-to-Speech: Reduced excessive pauses between sentences for smoother playback",
|
||||
"File Transfer: Introduced a background transfer queue for uploading and downloading books",
|
||||
"Proofreading: Added a proofread tool that can replace text directly within books"
|
||||
]
|
||||
},
|
||||
"0.9.96": {
|
||||
"date": "2025-12-19",
|
||||
"notes": [
|
||||
"TTS: Resolved an issue where Edge TTS was blocked in certain regions",
|
||||
"OPDS: Improved compatibility with older versions of Calibre Web",
|
||||
"OPDS: Added an instant search bar for faster browsing in OPDS catalogs",
|
||||
"OPDS: Added a curated book catalog from Standard Ebooks",
|
||||
"Bookshelf: Added a “Group Books” action to the context menu",
|
||||
"Comics: Fixed layout issues to improve comic book reading",
|
||||
"Layout: Footnote popups now adapt correctly to different screen sizes",
|
||||
"Layout: Fixed an issue where the maximum inline width was not applied to EPUBs",
|
||||
"Sync: Improved file downloading by correctly handling special characters in filenames",
|
||||
"UI: Added an option to temporarily dismiss the reading progress bar",
|
||||
"Settings: Screen brightness adjustments now apply only to the reader view",
|
||||
"iOS: You can now open files directly in Readest from the Files app",
|
||||
"macOS: Added a global menu option to open files with Readest"
|
||||
]
|
||||
},
|
||||
"0.9.95": {
|
||||
"date": "2025-12-08",
|
||||
"notes": [
|
||||
"OPDS: You can now search, browse, and download ebooks directly from OPDS catalogs",
|
||||
"OPDS: Improved compatibility with Calibre Web for reliable downloads",
|
||||
"OPDS: Fixed an issue where book covers from self-hosted Calibre OPDS servers did not display correctly",
|
||||
"Cloud Storage: You can view and delete files stored in your cloud storage directly from the app",
|
||||
"Bookshelf: Added support for importing books from a folder recursively",
|
||||
"Bookshelf: You can now rename your bookshelf groups",
|
||||
"EPUB: Added support for SVG covers from Standard Ebooks",
|
||||
"Annotations: Keyboard shortcuts no longer copy selected text into the notebook",
|
||||
"Footnotes: Footnote popups now scale properly on small screens",
|
||||
"Touch/Styli: Removed the context menu for smoother interaction on touch and stylus devices",
|
||||
"Layout: Devices with unfoldable screens now automatically switch to a two-column layout",
|
||||
"PDF: Improved zoomed-in navigation and hand-tool behavior for smoother page navigation",
|
||||
"Android: Fixed highlighting of the current sentence when using native Android text-to-speech",
|
||||
"Android: Back button handling now works correctly on Android 15 and above",
|
||||
"Android: You can now open files shared from other apps"
|
||||
]
|
||||
},
|
||||
"0.9.94": {
|
||||
"date": "2025-12-02",
|
||||
"notes": [
|
||||
|
||||
@@ -26,6 +26,7 @@ serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
thiserror = "2"
|
||||
walkdir = "2"
|
||||
tokio = { version = "1", features = ["fs"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
futures-util = "0.3"
|
||||
@@ -35,7 +36,7 @@ reqwest = { version = "0.12", default-features = false, features = [
|
||||
"json",
|
||||
"stream",
|
||||
] }
|
||||
tauri = { version = "2.5.1", features = [ "protocol-asset" ] }
|
||||
tauri = { version = "2", features = [ "protocol-asset" ] }
|
||||
tauri-build = "2"
|
||||
tauri-plugin-log = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
@@ -52,6 +53,8 @@ tauri-plugin-haptics = "2"
|
||||
tauri-plugin-persisted-scope = "2"
|
||||
tauri-plugin-native-bridge = { path = "./plugins/tauri-plugin-native-bridge" }
|
||||
tauri-plugin-native-tts = { path = "./plugins/tauri-plugin-native-tts" }
|
||||
tauri-plugin-websocket = "2"
|
||||
tauri-plugin-sharekit = "0.3"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
rand = "0.8"
|
||||
@@ -69,3 +72,4 @@ tauri-plugin-cli = "2"
|
||||
tauri-plugin-single-instance = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
tauri-plugin-window-state = "2"
|
||||
discord-rich-presence = "1.0.0"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -19,6 +19,8 @@
|
||||
<string>EPUB Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.idpf.epub-container</string>
|
||||
@@ -30,6 +32,8 @@
|
||||
<string>PDF Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.adobe.pdf</string>
|
||||
@@ -41,6 +45,8 @@
|
||||
<string>FB2 Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.readest.fb2</string>
|
||||
@@ -52,6 +58,8 @@
|
||||
<string>CBZ Archive</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.readest.cbz</string>
|
||||
@@ -63,6 +71,8 @@
|
||||
<string>MOBI Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.mobipocket.mobi</string>
|
||||
@@ -74,6 +84,8 @@
|
||||
<string>AZW Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.amazon.azw</string>
|
||||
@@ -86,6 +98,8 @@
|
||||
<string>Text File</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.plain-text</string>
|
||||
@@ -93,6 +107,153 @@
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<key>UTImportedTypeDeclarations</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.composite-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.adobe.pdf</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>PDF Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
<string>public.composite-content</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>pdf</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/pdf</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>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>public.plain-text</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Text File</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>txt</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>text/plain</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
@@ -44,9 +44,22 @@ fn build_windows_thumbnail() {
|
||||
}
|
||||
|
||||
let dll_name = "windows_thumbnail.dll";
|
||||
let dll_src = dll_crate_dir.join("target").join(&profile).join(dll_name);
|
||||
let dll_dest = dll_crate_dir.join("target").join(dll_name);
|
||||
let candidate_paths = [
|
||||
dll_crate_dir.join("target").join(&profile).join(dll_name),
|
||||
dll_crate_dir
|
||||
.join("target")
|
||||
.join(&target_triple)
|
||||
.join(&profile)
|
||||
.join(dll_name),
|
||||
];
|
||||
|
||||
fs::copy(&dll_src, &dll_dest).expect("Failed to copy windows_thumbnail DLL");
|
||||
let dll_src = candidate_paths
|
||||
.iter()
|
||||
.find(|p| p.exists())
|
||||
.expect("Failed to find built windows_thumbnail DLL");
|
||||
|
||||
let dll_dest = &dll_crate_dir.join("target").join(dll_name);
|
||||
|
||||
fs::copy(dll_src, dll_dest).expect("Failed to copy windows_thumbnail DLL");
|
||||
println!("cargo:rerun-if-changed={}", dll_dest.display());
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"websocket:default",
|
||||
"dialog:default",
|
||||
"os:default",
|
||||
"core:window:default",
|
||||
@@ -151,12 +152,23 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "shell:allow-spawn",
|
||||
"allow": [
|
||||
{
|
||||
"name": "start-readest",
|
||||
"cmd": "cmd",
|
||||
"args": ["/C", "start", "", { "validator": "^.*Readest(.*)\\.exe$" }]
|
||||
}
|
||||
]
|
||||
},
|
||||
"haptics:allow-vibrate",
|
||||
"haptics:allow-impact-feedback",
|
||||
"haptics:allow-notification-feedback",
|
||||
"haptics:allow-selection-feedback",
|
||||
"native-bridge:default",
|
||||
"deep-link:default",
|
||||
"sharekit:default",
|
||||
"native-tts:default",
|
||||
"deep-link:default"
|
||||
"native-bridge:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:largeHeap="true"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.readest"
|
||||
android:hardwareAccelerated="true"
|
||||
|
||||
+159
-3
@@ -2,22 +2,29 @@ package com.bilingify.readest
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.webkit.WebView
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.app.ActivityManager
|
||||
import android.content.res.Configuration
|
||||
import android.window.OnBackInvokedCallback
|
||||
import android.window.OnBackInvokedDispatcher
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import app.tauri.plugin.JSArray
|
||||
import app.tauri.plugin.JSObject
|
||||
import com.readest.native_bridge.KeyDownInterceptor
|
||||
import com.readest.native_bridge.NativeBridgePlugin
|
||||
|
||||
class MainActivity : TauriActivity(), KeyDownInterceptor {
|
||||
private lateinit var wv: WebView
|
||||
private var wv: WebView? = null
|
||||
private var interceptVolumeKeysEnabled = false
|
||||
private var interceptBackKeyEnabled = false
|
||||
|
||||
@@ -41,10 +48,78 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
|
||||
interceptBackKeyEnabled = enabled
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
val action = when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> "touchstart"
|
||||
MotionEvent.ACTION_UP -> "touchend"
|
||||
MotionEvent.ACTION_CANCEL -> "touchcancel"
|
||||
MotionEvent.ACTION_POINTER_DOWN -> "touchstart"
|
||||
MotionEvent.ACTION_POINTER_UP -> "touchend"
|
||||
else -> null
|
||||
}
|
||||
|
||||
action?.let { eventType ->
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
val x = event.getX(pointerIndex)
|
||||
val y = event.getY(pointerIndex)
|
||||
val pressure = event.getPressure(pointerIndex)
|
||||
|
||||
wv?.evaluateJavascript(
|
||||
"""
|
||||
try {
|
||||
if (window.onNativeTouch) {
|
||||
window.onNativeTouch({
|
||||
type: "$eventType",
|
||||
pointerId: $pointerId,
|
||||
x: $x,
|
||||
y: $y,
|
||||
pressure: $pressure,
|
||||
pointerCount: ${event.pointerCount},
|
||||
timestamp: ${event.eventTime}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Native touch error:', err);
|
||||
}
|
||||
""".trimIndent(),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
val keyCode = event.keyCode
|
||||
val keyName = keyEventMap[keyCode]
|
||||
|
||||
if (keyName != null) {
|
||||
val shouldIntercept = when (keyCode) {
|
||||
KeyEvent.KEYCODE_BACK -> interceptBackKeyEnabled
|
||||
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> interceptVolumeKeysEnabled
|
||||
else -> false
|
||||
}
|
||||
|
||||
if (shouldIntercept) {
|
||||
wv?.evaluateJavascript(
|
||||
"""
|
||||
try { window.onNativeKeyDown("$keyName", $keyCode); } catch (_) {}
|
||||
""".trimIndent(),
|
||||
null
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
val keyName = keyEventMap[keyCode]
|
||||
if (keyName != null) {
|
||||
wv.evaluateJavascript(
|
||||
wv?.evaluateJavascript(
|
||||
"""
|
||||
try {
|
||||
window.onNativeKeyDown("$keyName", $keyCode)
|
||||
@@ -84,6 +159,8 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
handleIncomingIntent(intent)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setTaskDescription(
|
||||
ActivityManager.TaskDescription(
|
||||
@@ -93,6 +170,41 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
onBackInvokedDispatcher.registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
|
||||
OnBackInvokedCallback {
|
||||
Log.d("MainActivity", "Back invoked callback triggered ${interceptBackKeyEnabled}")
|
||||
if (interceptBackKeyEnabled) {
|
||||
Log.d("MainActivity", "Back intercepted (OnBackInvokedCallback)")
|
||||
wv?.evaluateJavascript(
|
||||
"""window.onNativeKeyDown("Back", ${KeyEvent.KEYCODE_BACK});""",
|
||||
null
|
||||
)
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (interceptBackKeyEnabled) {
|
||||
Log.d("MainActivity", "Back intercepted (OnBackPressedDispatcher)")
|
||||
wv?.evaluateJavascript(
|
||||
"""window.onNativeKeyDown("Back", ${KeyEvent.KEYCODE_BACK});""",
|
||||
null
|
||||
)
|
||||
} else {
|
||||
isEnabled = false
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
@@ -100,4 +212,48 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
|
||||
|
||||
NativeBridgePlugin.getInstance()?.handleActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
intent?.let { handleIncomingIntent(it) }
|
||||
}
|
||||
|
||||
private fun handleIncomingIntent(intent: Intent) {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
if (intent.type != null) {
|
||||
handleSingleFile(intent)
|
||||
}
|
||||
}
|
||||
Intent.ACTION_SEND_MULTIPLE -> {
|
||||
if (intent.type != null) {
|
||||
handleMultipleFiles(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSingleFile(intent: Intent) {
|
||||
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
uri?.let { fileUri ->
|
||||
val payload = JSObject().apply {
|
||||
var urls = JSArray()
|
||||
urls.put(fileUri.toString())
|
||||
put("urls", urls)
|
||||
}
|
||||
NativeBridgePlugin.getInstance()?.triggerEvent("shared-intent", payload)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMultipleFiles(intent: Intent) {
|
||||
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
uris?.let { fileUris ->
|
||||
val payload = JSObject().apply {
|
||||
var urls = JSArray()
|
||||
fileUris.forEach { urls.put(it.toString()) }
|
||||
put("urls", urls)
|
||||
}
|
||||
NativeBridgePlugin.getInstance()?.triggerEvent("shared-intent", payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,18 @@
|
||||
WriteRegStr HKCR ".cbr\ShellEx\${SHELL_THUMBNAIL_HANDLER}" "" "${CLSID_READEST_THUMBNAIL}"
|
||||
|
||||
DetailPrint "Thumbnail provider registered successfully."
|
||||
|
||||
Delete "$DESKTOP\Readest.lnk"
|
||||
Delete "$SMPROGRAMS\Readest\Readest.lnk"
|
||||
RMDir "$SMPROGRAMS\Readest"
|
||||
|
||||
; Create new shortcuts pointing to current installation
|
||||
CreateShortcut "$DESKTOP\Readest.lnk" "$INSTDIR\Readest.exe"
|
||||
CreateDirectory "$SMPROGRAMS\Readest"
|
||||
CreateShortcut "$SMPROGRAMS\Readest\Readest.lnk" "$INSTDIR\Readest.exe"
|
||||
|
||||
DetailPrint "Shortcuts updated."
|
||||
|
||||
; Refresh shell to apply changes - SHCNE_ASSOCCHANGED
|
||||
System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0, p 0, p 0)'
|
||||
!macroend
|
||||
|
||||
+17
@@ -8,6 +8,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@@ -16,12 +18,27 @@ class BillingManager(private val activity: Activity) : PurchasesUpdatedListener
|
||||
private val productsCache = mutableMapOf<String, ProductDetails>()
|
||||
private var purchaseCallback: ((PurchaseData?) -> Unit)? = null
|
||||
private val scope = CoroutineScope(Dispatchers.Main)
|
||||
private val isGooglePlayAvailable: Boolean by lazy {
|
||||
val availability = GoogleApiAvailability.getInstance()
|
||||
val resultCode = availability.isGooglePlayServicesAvailable(activity)
|
||||
resultCode == ConnectionResult.SUCCESS
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BillingManager"
|
||||
}
|
||||
|
||||
fun isBillingAvailable(): Boolean {
|
||||
return isGooglePlayAvailable
|
||||
}
|
||||
|
||||
fun initialize(callback: (Boolean) -> Unit) {
|
||||
if (!isGooglePlayAvailable) {
|
||||
Log.d(TAG, "Google Play Services not available, skipping billing setup")
|
||||
callback(false)
|
||||
return
|
||||
}
|
||||
|
||||
billingClient = BillingClient.newBuilder(activity)
|
||||
.setListener(this)
|
||||
.enablePendingPurchases()
|
||||
|
||||
+26
-6
@@ -495,13 +495,19 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
val args = invoke.parseArgs(SetScreenBrightnessRequestArgs::class.java)
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val brightness = (args.brightness ?: 0.5).toFloat()
|
||||
if (brightness < 0.0 || brightness > 1.0) {
|
||||
invoke.reject("Brightness must be between 0.0 and 1.0")
|
||||
return
|
||||
}
|
||||
val brightness = args.brightness?.toFloat()
|
||||
val layoutParams = activity.window.attributes
|
||||
layoutParams.screenBrightness = brightness
|
||||
|
||||
if (brightness == null || brightness < 0.0) {
|
||||
layoutParams.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||
} else {
|
||||
if (brightness > 1.0) {
|
||||
invoke.reject("Brightness must be between 0.0 and 1.0, or null to use system brightness")
|
||||
return
|
||||
}
|
||||
layoutParams.screenBrightness = brightness
|
||||
}
|
||||
|
||||
activity.window.attributes = layoutParams
|
||||
ret.put("success", true)
|
||||
} catch (e: Exception) {
|
||||
@@ -511,6 +517,14 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun iap_is_available(invoke: Invoke) {
|
||||
val isAvailable = billingManager.isBillingAvailable()
|
||||
val result = JSObject()
|
||||
result.put("available", isAvailable)
|
||||
invoke.resolve(result)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun iap_initialize(invoke: Invoke) {
|
||||
billingManager.initialize { success ->
|
||||
@@ -773,4 +787,10 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
fun triggerEvent(eventName: String, payload: JSObject) {
|
||||
activity.runOnUiThread {
|
||||
trigger(eventName, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const COMMANDS: &[&str] = &[
|
||||
"get_sys_fonts_list",
|
||||
"intercept_keys",
|
||||
"lock_screen_orientation",
|
||||
"iap_is_available",
|
||||
"iap_initialize",
|
||||
"iap_fetch_products",
|
||||
"iap_purchase_product",
|
||||
@@ -20,6 +21,8 @@ const COMMANDS: &[&str] = &[
|
||||
"get_external_sdcard_path",
|
||||
"open_external_url",
|
||||
"select_directory",
|
||||
"register_listener",
|
||||
"remove_listener",
|
||||
"request_manage_storage_permission",
|
||||
"check_permissions",
|
||||
"request_permissions",
|
||||
|
||||
+69
-2
@@ -53,6 +53,11 @@ class SetScreenBrightnessRequestArgs: Decodable {
|
||||
let brightness: Float?
|
||||
}
|
||||
|
||||
class CopyUriToPathRequestArgs: Decodable {
|
||||
let uri: String?
|
||||
let dst: String?
|
||||
}
|
||||
|
||||
struct InitializeRequest: Decodable {
|
||||
let publicKey: String?
|
||||
}
|
||||
@@ -576,7 +581,6 @@ class NativeBridgePlugin: Plugin {
|
||||
let darkMode = args.darkMode
|
||||
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.isIdleTimerDisabled = !visible
|
||||
UIApplication.shared.setStatusBarHidden(!visible, with: .none)
|
||||
|
||||
let windows = UIApplication.shared.connectedScenes
|
||||
@@ -706,6 +710,10 @@ class NativeBridgePlugin: Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func iap_is_available(_ invoke: Invoke) {
|
||||
invoke.resolve(["available": true])
|
||||
}
|
||||
|
||||
@objc public func iap_initialize(_ invoke: Invoke) {
|
||||
StoreKitManager.shared.initialize()
|
||||
invoke.resolve(["success": true])
|
||||
@@ -803,7 +811,13 @@ class NativeBridgePlugin: Plugin {
|
||||
|
||||
let brightness = args.brightness ?? 0.5
|
||||
|
||||
if brightness < 0.0 || brightness > 1.0 {
|
||||
if brightness < 0.0 {
|
||||
// Revert to system brightness - iOS doesn't have a direct "system brightness" setting
|
||||
// We will restore the brightness that was set before the app modified it
|
||||
return invoke.resolve(["success": true])
|
||||
}
|
||||
|
||||
if brightness > 1.0 {
|
||||
return invoke.reject("Brightness must be between 0.0 and 1.0")
|
||||
}
|
||||
|
||||
@@ -812,6 +826,59 @@ class NativeBridgePlugin: Plugin {
|
||||
}
|
||||
invoke.resolve(["success": true])
|
||||
}
|
||||
|
||||
@objc public func copy_uri_to_path(_ invoke: Invoke) {
|
||||
guard let args = try? invoke.parseArgs(CopyUriToPathRequestArgs.self) else {
|
||||
return invoke.reject("Failed to parse arguments")
|
||||
}
|
||||
|
||||
guard let uriString = args.uri, let dstPath = args.dst else {
|
||||
return invoke.reject("URI and destination path must be provided")
|
||||
}
|
||||
|
||||
guard let uri = URL(string: uriString) else {
|
||||
return invoke.reject("Invalid URI")
|
||||
}
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let dstURL = URL(fileURLWithPath: dstPath)
|
||||
|
||||
do {
|
||||
let didStartAccessing = uri.startAccessingSecurityScopedResource()
|
||||
defer {
|
||||
if didStartAccessing {
|
||||
uri.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
|
||||
var shouldCopy = false
|
||||
|
||||
if fileManager.fileExists(atPath: dstURL.path) {
|
||||
let srcAttributes = try fileManager.attributesOfItem(atPath: uri.path)
|
||||
let dstAttributes = try fileManager.attributesOfItem(atPath: dstURL.path)
|
||||
|
||||
let srcModDate = srcAttributes[.modificationDate] as? Date ?? Date.distantPast
|
||||
let dstModDate = dstAttributes[.modificationDate] as? Date ?? Date.distantPast
|
||||
|
||||
if srcModDate > dstModDate {
|
||||
try fileManager.removeItem(at: dstURL)
|
||||
shouldCopy = true
|
||||
} else {
|
||||
shouldCopy = false
|
||||
}
|
||||
} else {
|
||||
shouldCopy = true
|
||||
}
|
||||
|
||||
if shouldCopy {
|
||||
try fileManager.copyItem(at: uri, to: dstURL)
|
||||
}
|
||||
|
||||
invoke.resolve(["success": true])
|
||||
} catch {
|
||||
invoke.reject("Failed to copy file: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_native_bridge")
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-iap-is-available"
|
||||
description = "Enables the iap_is_available command without any pre-configured scope."
|
||||
commands.allow = ["iap_is_available"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-iap-is-available"
|
||||
description = "Denies the iap_is_available command without any pre-configured scope."
|
||||
commands.deny = ["iap_is_available"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-register-listener"
|
||||
description = "Enables the register_listener command without any pre-configured scope."
|
||||
commands.allow = ["register_listener"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-register-listener"
|
||||
description = "Denies the register_listener command without any pre-configured scope."
|
||||
commands.deny = ["register_listener"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-remove-listener"
|
||||
description = "Enables the remove_listener command without any pre-configured scope."
|
||||
commands.allow = ["remove_listener"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-remove-listener"
|
||||
description = "Denies the remove_listener command without any pre-configured scope."
|
||||
commands.deny = ["remove_listener"]
|
||||
+81
@@ -14,6 +14,7 @@ Default permissions for the plugin
|
||||
- `allow-get-sys-fonts-list`
|
||||
- `allow-intercept-keys`
|
||||
- `allow-lock-screen-orientation`
|
||||
- `allow-iap-is-available`
|
||||
- `allow-iap-initialize`
|
||||
- `allow-iap-fetch-products`
|
||||
- `allow-iap-purchase-product`
|
||||
@@ -26,6 +27,8 @@ Default permissions for the plugin
|
||||
- `allow-open-external-url`
|
||||
- `allow-select-directory`
|
||||
- `allow-request-manage-storage-permission`
|
||||
- `allow-register-listener`
|
||||
- `allow-remove-listener`
|
||||
- `allow-check-permissions`
|
||||
- `allow-request-permissions`
|
||||
- `allow-checkPermissions`
|
||||
@@ -407,6 +410,32 @@ Denies the iap_initialize command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-iap-is-available`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the iap_is_available command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-iap-is-available`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the iap_is_available command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-iap-purchase-product`
|
||||
|
||||
</td>
|
||||
@@ -563,6 +592,58 @@ Denies the open_external_url command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-register-listener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the register_listener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-register-listener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the register_listener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-remove-listener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the remove_listener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-remove-listener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the remove_listener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-request-permissions`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -11,6 +11,7 @@ permissions = [
|
||||
"allow-get-sys-fonts-list",
|
||||
"allow-intercept-keys",
|
||||
"allow-lock-screen-orientation",
|
||||
"allow-iap-is-available",
|
||||
"allow-iap-initialize",
|
||||
"allow-iap-fetch-products",
|
||||
"allow-iap-purchase-product",
|
||||
@@ -23,6 +24,8 @@ permissions = [
|
||||
"allow-open-external-url",
|
||||
"allow-select-directory",
|
||||
"allow-request-manage-storage-permission",
|
||||
"allow-register-listener",
|
||||
"allow-remove-listener",
|
||||
"allow-check-permissions",
|
||||
"allow-request-permissions",
|
||||
"allow-checkPermissions",
|
||||
|
||||
+38
-2
@@ -462,6 +462,18 @@
|
||||
"const": "deny-iap-initialize",
|
||||
"markdownDescription": "Denies the iap_initialize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the iap_is_available command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-iap-is-available",
|
||||
"markdownDescription": "Enables the iap_is_available command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the iap_is_available command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-iap-is-available",
|
||||
"markdownDescription": "Denies the iap_is_available command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the iap_purchase_product command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -534,6 +546,30 @@
|
||||
"const": "deny-open-external-url",
|
||||
"markdownDescription": "Denies the open_external_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-register-listener",
|
||||
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-register-listener",
|
||||
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the remove_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-remove-listener",
|
||||
"markdownDescription": "Enables the remove_listener command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the remove_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-remove-listener",
|
||||
"markdownDescription": "Denies the remove_listener command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the request-permissions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -631,10 +667,10 @@
|
||||
"markdownDescription": "Denies the use_background_audio command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-select-directory`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`",
|
||||
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-is-available`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-select-directory`\n- `allow-request-manage-storage-permission`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`",
|
||||
"type": "string",
|
||||
"const": "default",
|
||||
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-select-directory`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
|
||||
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-is-available`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-select-directory`\n- `allow-request-manage-storage-permission`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use tauri::{command, AppHandle, Runtime};
|
||||
use std::path::PathBuf;
|
||||
use tauri::{command, AppHandle, Runtime, State};
|
||||
|
||||
use crate::models::*;
|
||||
use crate::DirectoryCallbackState;
|
||||
use crate::NativeBridgeExt;
|
||||
use crate::Result;
|
||||
|
||||
@@ -82,6 +84,13 @@ pub(crate) async fn lock_screen_orientation<R: Runtime>(
|
||||
app.native_bridge().lock_screen_orientation(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn iap_is_available<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
) -> Result<IAPIsAvailableResponse> {
|
||||
app.native_bridge().iap_is_available()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn iap_initialize<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
@@ -160,8 +169,21 @@ pub(crate) async fn open_external_url<R: Runtime>(
|
||||
#[command]
|
||||
pub(crate) async fn select_directory<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
callback_state: State<'_, DirectoryCallbackState<R>>,
|
||||
) -> Result<SelectDirectoryResponse> {
|
||||
app.native_bridge().select_directory()
|
||||
let result = app.native_bridge().select_directory()?;
|
||||
|
||||
if let Some(dir_path) = &result.path {
|
||||
let path = PathBuf::from(dir_path);
|
||||
|
||||
if let Ok(callback_guard) = callback_state.callback.lock() {
|
||||
if let Some(callback) = callback_guard.as_ref() {
|
||||
callback(&app, &path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
||||
@@ -74,6 +74,10 @@ impl<R: Runtime> NativeBridge<R> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn iap_is_available(&self) -> crate::Result<IAPIsAvailableResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn iap_initialize(
|
||||
&self,
|
||||
_payload: IAPInitializeRequest,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Manager, Runtime,
|
||||
@@ -17,6 +18,9 @@ mod platform;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use tauri::AppHandle;
|
||||
|
||||
#[cfg(desktop)]
|
||||
use desktop::NativeBridge;
|
||||
#[cfg(mobile)]
|
||||
@@ -33,6 +37,20 @@ impl<R: Runtime, T: Manager<R>> crate::NativeBridgeExt<R> for T {
|
||||
}
|
||||
}
|
||||
|
||||
type DirectoryCallback<R> = Box<dyn Fn(&AppHandle<R>, &PathBuf) + Send + Sync>;
|
||||
|
||||
pub struct DirectoryCallbackState<R: Runtime> {
|
||||
pub callback: Arc<Mutex<Option<DirectoryCallback<R>>>>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Default for DirectoryCallbackState<R> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
callback: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the plugin.
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("native-bridge")
|
||||
@@ -47,6 +65,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
commands::get_sys_fonts_list,
|
||||
commands::intercept_keys,
|
||||
commands::lock_screen_orientation,
|
||||
commands::iap_is_available,
|
||||
commands::iap_initialize,
|
||||
commands::iap_fetch_products,
|
||||
commands::iap_purchase_product,
|
||||
@@ -66,7 +85,18 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
#[cfg(desktop)]
|
||||
let native_bridge = desktop::init(app, api)?;
|
||||
app.manage(native_bridge);
|
||||
app.manage(DirectoryCallbackState::<R>::default());
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn register_select_directory_callback<R: Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
callback: impl Fn(&AppHandle<R>, &PathBuf) + Send + Sync + 'static,
|
||||
) {
|
||||
if let Some(state) = app.try_state::<DirectoryCallbackState<R>>() {
|
||||
let mut cb = state.callback.lock().unwrap();
|
||||
*cb = Some(Box::new(callback));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,14 @@ impl<R: Runtime> NativeBridge<R> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn iap_is_available(&self) -> crate::Result<IAPIsAvailableResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("iap_is_available", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn iap_initialize(
|
||||
&self,
|
||||
|
||||
@@ -113,6 +113,12 @@ pub struct Purchase {
|
||||
pub purchase_state: String, // "purchased", "pending", "cancelled", "restored"
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPIsAvailableResponse {
|
||||
pub available: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPInitializeRequest {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
+14
-4
@@ -383,7 +383,10 @@ class NativeTTSPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
val args = invoke.parseArgs(SetVoiceArgs::class.java)
|
||||
try {
|
||||
val voices = textToSpeech?.voices
|
||||
val targetVoice = voices?.find { it.name == args.voice }
|
||||
val targetVoice = voices?.find { voice ->
|
||||
val languageTag = voice.locale.toLanguageTag()
|
||||
voice.name == args.voice || (languageTag.contains(voice.name) && languageTag == args.voice)
|
||||
}
|
||||
|
||||
if (targetVoice != null) {
|
||||
val result = textToSpeech?.setVoice(targetVoice)
|
||||
@@ -404,10 +407,17 @@ class NativeTTSPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
fun get_all_voices(invoke: Invoke) {
|
||||
try {
|
||||
val voices = textToSpeech?.voices?.map { voice ->
|
||||
val voiceName = voice.name
|
||||
val language = voice.locale.toLanguageTag()
|
||||
val (id, name) = if (language.contains(voiceName)) {
|
||||
language to language
|
||||
} else {
|
||||
voiceName to voiceName
|
||||
}
|
||||
JSObject().apply {
|
||||
put("id", voice.name)
|
||||
put("name", voice.name)
|
||||
put("lang", voice.locale.toLanguageTag())
|
||||
put("id", id)
|
||||
put("name", name)
|
||||
put("lang", language)
|
||||
put("disabled", false)
|
||||
}
|
||||
} ?: emptyList()
|
||||
|
||||
@@ -4,29 +4,29 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "tauri-plugin-native-tts",
|
||||
platforms: [
|
||||
.macOS(.v10_13),
|
||||
.iOS(.v13),
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "tauri-plugin-native-tts",
|
||||
type: .static,
|
||||
targets: ["tauri-plugin-native-tts"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "Tauri", path: "../.tauri/tauri-api")
|
||||
],
|
||||
targets: [
|
||||
// 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-native-tts",
|
||||
dependencies: [
|
||||
.byName(name: "Tauri")
|
||||
],
|
||||
path: "Sources")
|
||||
]
|
||||
name: "tauri-plugin-native-tts",
|
||||
platforms: [
|
||||
.macOS(.v10_13),
|
||||
.iOS(.v14),
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "tauri-plugin-native-tts",
|
||||
type: .static,
|
||||
targets: ["tauri-plugin-native-tts"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "Tauri", path: "../.tauri/tauri-api")
|
||||
],
|
||||
targets: [
|
||||
// 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-native-tts",
|
||||
dependencies: [
|
||||
.byName(name: "Tauri")
|
||||
],
|
||||
path: "Sources")
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
use std::process::Command;
|
||||
|
||||
/// Known e-ink device manufacturers and brands (case-insensitive matching)
|
||||
const EINK_MANUFACTURERS: &[&str] = &[
|
||||
"onyx", // BOOX devices
|
||||
"boox", // BOOX devices (alternate)
|
||||
"amazon", // Kindle devices
|
||||
"kobo", // Kobo e-readers
|
||||
"remarkable", // reMarkable tablets
|
||||
"pocketbook", // PocketBook e-readers
|
||||
"boyue", // Boyue/Likebook devices
|
||||
"likebook", // Likebook devices
|
||||
"dasung", // Dasung e-ink monitors
|
||||
"bigme", // Bigme e-readers
|
||||
"hisense", // Hisense e-ink phones (A5, A7, etc.)
|
||||
"hanvon", // Hanvon e-readers
|
||||
"tolino", // Tolino e-readers
|
||||
"bookeen", // Bookeen e-readers
|
||||
"supernote", // Supernote devices
|
||||
"mobiscribe", // Mobiscribe e-readers
|
||||
"xiaomi", // Xiaomi InkPalm (needs model check)
|
||||
"meebook", // Meebook e-readers
|
||||
];
|
||||
|
||||
/// Known e-ink device models (for manufacturers that also make non-e-ink devices)
|
||||
const EINK_MODELS: &[&str] = &[
|
||||
"kindle",
|
||||
"a5pro",
|
||||
"a7cc", // Hisense e-ink models
|
||||
"a7e",
|
||||
"a9",
|
||||
"inkpalm", // Xiaomi InkPalm
|
||||
"eink",
|
||||
"e-ink",
|
||||
"paper",
|
||||
"note air",
|
||||
"note2",
|
||||
"note3",
|
||||
"note5",
|
||||
"nova",
|
||||
"poke",
|
||||
"leaf",
|
||||
"page",
|
||||
"tab ultra",
|
||||
"max lumi",
|
||||
];
|
||||
|
||||
/// Get Android system property using getprop command
|
||||
fn get_system_property(prop: &str) -> Option<String> {
|
||||
Command::new("getprop")
|
||||
.arg(prop)
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|output| {
|
||||
if output.status.success() {
|
||||
let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the current Android device is an e-ink device
|
||||
pub fn is_eink_device() -> bool {
|
||||
// Get device manufacturer and model
|
||||
let manufacturer = get_system_property("ro.product.manufacturer")
|
||||
.or_else(|| get_system_property("ro.product.brand"))
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
let model = get_system_property("ro.product.model")
|
||||
.or_else(|| get_system_property("ro.product.device"))
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
let device = get_system_property("ro.product.device")
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
// Check if manufacturer matches known e-ink manufacturers
|
||||
for eink_manufacturer in EINK_MANUFACTURERS {
|
||||
if manufacturer.contains(eink_manufacturer) {
|
||||
// Special case for manufacturers that make both e-ink and non-e-ink devices
|
||||
if *eink_manufacturer == "hisense" || *eink_manufacturer == "xiaomi" {
|
||||
// Need to also check the model for these manufacturers
|
||||
for eink_model in EINK_MODELS {
|
||||
if model.contains(eink_model) || device.contains(eink_model) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if model matches known e-ink models
|
||||
for eink_model in EINK_MODELS {
|
||||
if model.contains(eink_model) || device.contains(eink_model) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for e-ink specific system properties
|
||||
if let Some(eink_support) = get_system_property("ro.eink.support") {
|
||||
if eink_support == "1" || eink_support.to_lowercase() == "true" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for BOOX specific property
|
||||
if get_system_property("ro.onyx.devicename").is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod eink;
|
||||
|
||||
pub use eink::is_eink_device;
|
||||
@@ -0,0 +1,96 @@
|
||||
use std::path::Path;
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_fs::FsExt;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct ScannedFile {
|
||||
pub path: String,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_dir(
|
||||
app: AppHandle,
|
||||
path: String,
|
||||
recursive: bool,
|
||||
extensions: Vec<String>,
|
||||
) -> Result<Vec<ScannedFile>, String> {
|
||||
let scope = app.fs_scope();
|
||||
let path_buf = std::path::PathBuf::from(&path);
|
||||
|
||||
if !scope.is_allowed(&path_buf) {
|
||||
return Err("Permission denied: Path not in filesystem scope".to_string());
|
||||
}
|
||||
|
||||
let mut files = Vec::new();
|
||||
|
||||
let normalized_extensions: Vec<String> =
|
||||
extensions.iter().map(|ext| ext.to_lowercase()).collect();
|
||||
|
||||
if recursive {
|
||||
for entry_result in WalkDir::new(&path).into_iter() {
|
||||
match entry_result {
|
||||
Ok(entry) => {
|
||||
if entry.file_type().is_file() {
|
||||
if let Some(scanned_file) =
|
||||
process_file_entry(entry.path(), &normalized_extensions)
|
||||
{
|
||||
files.push(scanned_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("RUST: Skipping file due to error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match std::fs::read_dir(&path_buf) {
|
||||
Ok(entries) => {
|
||||
for entry_result in entries {
|
||||
match entry_result {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
if let Some(scanned_file) =
|
||||
process_file_entry(&path, &normalized_extensions)
|
||||
{
|
||||
files.push(scanned_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("RUST: Skipping entry due to error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!("Failed to read directory: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
fn process_file_entry(path: &Path, extensions: &[String]) -> Option<ScannedFile> {
|
||||
if extensions.is_empty() || extensions.contains(&"*".to_string()) {
|
||||
let size = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
|
||||
return Some(ScannedFile {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
size,
|
||||
});
|
||||
} else if let Some(ext) = path.extension() {
|
||||
let ext_str = ext.to_string_lossy().to_lowercase();
|
||||
if extensions.contains(&ext_str) {
|
||||
let size = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
|
||||
return Some(ScannedFile {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
size,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient};
|
||||
use serde::Deserialize;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::State;
|
||||
|
||||
const DISCORD_APP_ID: &str = "1462683110612144348";
|
||||
const MAX_TITLE_LENGTH: usize = 128;
|
||||
const MAX_AUTHOR_LENGTH: usize = 128;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DiscordRpcClient {
|
||||
client: Option<DiscordIpcClient>,
|
||||
current_book_hash: Option<String>,
|
||||
}
|
||||
|
||||
impl DiscordRpcClient {
|
||||
pub fn new() -> Self {
|
||||
DiscordRpcClient {
|
||||
client: None,
|
||||
current_book_hash: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_connected(&mut self) -> Result<(), String> {
|
||||
if self.client.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut client = DiscordIpcClient::new(DISCORD_APP_ID);
|
||||
match client.connect() {
|
||||
Ok(_) => {
|
||||
log::info!("Successfully connected to Discord");
|
||||
self.client = Some(client);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to connect to Discord: {}", e);
|
||||
Err(format!("Failed to connect to Discord: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnect(&mut self) {
|
||||
if let Some(mut client) = self.client.take() {
|
||||
let _ = client.close();
|
||||
log::debug!("Disconnected from Discord");
|
||||
}
|
||||
self.current_book_hash = None;
|
||||
}
|
||||
|
||||
fn truncate_string(s: &str, max_len: usize) -> String {
|
||||
if s.len() <= max_len {
|
||||
s.to_string()
|
||||
} else {
|
||||
format!("{}...", &s[..max_len - 3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BookPresenceData {
|
||||
book_hash: String,
|
||||
title: String,
|
||||
author: Option<String>,
|
||||
cover_url: Option<String>,
|
||||
session_start: i64,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
#[tauri::command]
|
||||
pub async fn update_book_presence(
|
||||
state: State<'_, Arc<Mutex<DiscordRpcClient>>>,
|
||||
presence: BookPresenceData,
|
||||
) -> Result<(), String> {
|
||||
let mut client = state
|
||||
.lock()
|
||||
.map_err(|e| format!("Mutex lock error: {}", e))?;
|
||||
|
||||
if let Err(e) = client.ensure_connected() {
|
||||
log::debug!("Discord not available: {}", e);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let BookPresenceData {
|
||||
book_hash,
|
||||
title,
|
||||
author,
|
||||
cover_url,
|
||||
session_start,
|
||||
} = presence;
|
||||
|
||||
// Truncate title and author to avoid Discord API limits
|
||||
let truncated_title = DiscordRpcClient::truncate_string(&title, MAX_TITLE_LENGTH);
|
||||
let state_text = if let Some(ref author_name) = author {
|
||||
let truncated_author = DiscordRpcClient::truncate_string(author_name, MAX_AUTHOR_LENGTH);
|
||||
format!("by {}", truncated_author)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let mut activity_builder = activity::Activity::new().details(&truncated_title);
|
||||
|
||||
if !state_text.is_empty() {
|
||||
activity_builder = activity_builder.state(&state_text);
|
||||
}
|
||||
|
||||
activity_builder =
|
||||
activity_builder.timestamps(activity::Timestamps::new().start(session_start / 1000));
|
||||
|
||||
let large_image = cover_url
|
||||
.as_deref()
|
||||
.filter(|url| url.starts_with("https://"))
|
||||
.unwrap_or("book_icon");
|
||||
let assets_builder = activity::Assets::new()
|
||||
.large_image(large_image)
|
||||
.large_text(&truncated_title);
|
||||
|
||||
activity_builder = activity_builder.assets(assets_builder);
|
||||
|
||||
let button = activity::Button::new("Read on Readest", "https://web.readest.com");
|
||||
activity_builder = activity_builder.buttons(vec![button]);
|
||||
|
||||
if let Some(ref mut discord_client) = client.client {
|
||||
match discord_client.set_activity(activity_builder) {
|
||||
Ok(_) => {
|
||||
log::info!("Successfully updated Discord presence");
|
||||
client.current_book_hash = Some(book_hash);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to update Discord activity: {}", e);
|
||||
client.disconnect();
|
||||
Err(format!("Failed to update Discord activity: {}", e))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err("Discord client not initialized".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
#[tauri::command]
|
||||
pub async fn clear_book_presence(
|
||||
state: State<'_, Arc<Mutex<DiscordRpcClient>>>,
|
||||
) -> Result<(), String> {
|
||||
let mut client = state
|
||||
.lock()
|
||||
.map_err(|e| format!("Mutex lock error: {}", e))?;
|
||||
|
||||
if let Some(ref mut discord_client) = client.client {
|
||||
match discord_client.clear_activity() {
|
||||
Ok(_) => {
|
||||
log::info!("Successfully cleared Discord presence");
|
||||
client.current_book_hash = None;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to clear Discord activity: {}", e);
|
||||
client.disconnect();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("No Discord client to clear");
|
||||
client.current_book_hash = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
#[tauri::command]
|
||||
pub async fn update_book_presence(_presence: BookPresenceData) -> Result<(), String> {
|
||||
Ok(()) // No-op on non-desktop platforms
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
#[tauri::command]
|
||||
pub async fn clear_book_presence() -> Result<(), String> {
|
||||
Ok(()) // No-op on non-desktop platforms
|
||||
}
|
||||
@@ -9,22 +9,29 @@ extern crate objc;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
mod android;
|
||||
|
||||
use tauri::utils::config::BackgroundThrottlingPolicy;
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::TitleBarStyle;
|
||||
|
||||
#[cfg(desktop)]
|
||||
use std::path::PathBuf;
|
||||
#[cfg(desktop)]
|
||||
use tauri::{AppHandle, Listener, Manager, Url};
|
||||
#[cfg(desktop)]
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
|
||||
#[cfg(desktop)]
|
||||
use tauri::{Listener, Url};
|
||||
mod dir_scanner;
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
mod discord_rpc;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
mod transfer_file;
|
||||
use tauri::{command, Emitter, WebviewUrl, WebviewWindowBuilder, Window};
|
||||
#[cfg(target_os = "android")]
|
||||
use tauri_plugin_native_bridge::register_select_directory_callback;
|
||||
#[cfg(target_os = "android")]
|
||||
use tauri_plugin_native_bridge::{NativeBridgeExt, OpenExternalUrlRequest};
|
||||
use tauri_plugin_oauth::start;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
@@ -37,31 +44,30 @@ fn allow_file_in_scopes(app: &AppHandle, files: Vec<PathBuf>) {
|
||||
let asset_protocol_scope = app.asset_protocol_scope();
|
||||
for file in &files {
|
||||
if let Err(e) = fs_scope.allow_file(file) {
|
||||
eprintln!("Failed to allow file in fs_scope: {e}");
|
||||
log::error!("Failed to allow file in fs_scope: {e}");
|
||||
} else {
|
||||
println!("Allowed file in fs_scope: {file:?}");
|
||||
log::debug!("Allowed file in fs_scope: {file:?}");
|
||||
}
|
||||
if let Err(e) = asset_protocol_scope.allow_file(file) {
|
||||
eprintln!("Failed to allow file in asset_protocol_scope: {e}");
|
||||
log::error!("Failed to allow file in asset_protocol_scope: {e}");
|
||||
} else {
|
||||
println!("Allowed file in asset_protocol_scope: {file:?}");
|
||||
log::debug!("Allowed file in asset_protocol_scope: {file:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn allow_dir_in_scopes(app: &AppHandle, dir: &PathBuf) {
|
||||
let fs_scope = app.fs_scope();
|
||||
let asset_protocol_scope = app.asset_protocol_scope();
|
||||
if let Err(e) = fs_scope.allow_directory(dir, true) {
|
||||
eprintln!("Failed to allow directory in fs_scope: {e}");
|
||||
log::error!("Failed to allow directory in fs_scope: {e}");
|
||||
} else {
|
||||
println!("Allowed directory in fs_scope: {dir:?}");
|
||||
log::info!("Allowed directory in fs_scope: {dir:?}");
|
||||
}
|
||||
if let Err(e) = asset_protocol_scope.allow_directory(dir, true) {
|
||||
eprintln!("Failed to allow directory in asset_protocol_scope: {e}");
|
||||
log::error!("Failed to allow directory in asset_protocol_scope: {e}");
|
||||
} else {
|
||||
println!("Allowed directory in asset_protocol_scope: {dir:?}");
|
||||
log::info!("Allowed directory in asset_protocol_scope: {dir:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +142,7 @@ fn get_executable_dir() -> String {
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
#[allow(dead_code)]
|
||||
struct Payload {
|
||||
struct SingleInstancePayload {
|
||||
args: Vec<String>,
|
||||
cwd: String,
|
||||
}
|
||||
@@ -144,6 +150,12 @@ struct Payload {
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let builder = tauri::Builder::default()
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_websocket::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_oauth::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
@@ -152,12 +164,17 @@ pub fn run() {
|
||||
upload_file,
|
||||
get_environment_variable,
|
||||
get_executable_dir,
|
||||
dir_scanner::read_dir,
|
||||
#[cfg(target_os = "macos")]
|
||||
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,
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
discord_rpc::update_book_presence,
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
discord_rpc::clear_book_presence,
|
||||
])
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_persisted_scope::init())
|
||||
@@ -166,6 +183,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_sharekit::init())
|
||||
.plugin(tauri_plugin_native_bridge::init())
|
||||
.plugin(tauri_plugin_native_tts::init());
|
||||
|
||||
@@ -179,7 +197,7 @@ pub fn run() {
|
||||
if !files.is_empty() {
|
||||
allow_file_in_scopes(app, files.clone());
|
||||
}
|
||||
app.emit("single-instance", Payload { args: argv, cwd })
|
||||
app.emit("single-instance", SingleInstancePayload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}));
|
||||
|
||||
@@ -205,6 +223,13 @@ pub fn run() {
|
||||
|
||||
builder
|
||||
.setup(|#[allow(unused_variables)] app| {
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
{
|
||||
use std::sync::{Arc, Mutex};
|
||||
let discord_client = Arc::new(Mutex::new(discord_rpc::DiscordRpcClient::new()));
|
||||
app.manage(discord_client);
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
let files = get_files_from_argv(std::env::args().collect());
|
||||
@@ -223,6 +248,11 @@ pub fn run() {
|
||||
allow_dir_in_scopes(app.handle(), &PathBuf::from(get_executable_dir()));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
register_select_directory_callback(app.handle(), move |app, path| {
|
||||
allow_dir_in_scopes(app, path);
|
||||
});
|
||||
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
app.handle().plugin(tauri_plugin_cli::init())?;
|
||||
@@ -256,42 +286,54 @@ pub fn run() {
|
||||
let _ = app.deep_link().register_all();
|
||||
}
|
||||
|
||||
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}");
|
||||
// Check for e-ink device on Android before building the window
|
||||
#[cfg(target_os = "android")]
|
||||
let is_eink = android::is_eink_device();
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let is_eink = false;
|
||||
|
||||
let eink_script = if is_eink {
|
||||
"window.__READEST_IS_EINK = true;"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let init_script = format!(
|
||||
r#"
|
||||
{eink_script}
|
||||
window.addEventListener('DOMContentLoaded', function() {{
|
||||
document.documentElement.classList.add('edge-to-edge');
|
||||
const isTauriLocal = window.location.protocol === 'tauri:' ||
|
||||
window.location.protocol === 'about:' ||
|
||||
window.location.hostname === 'tauri.localhost';
|
||||
const needsSafeArea = !isTauriLocal;
|
||||
if (needsSafeArea && !document.getElementById('safe-area-style')) {{
|
||||
const style = document.createElement('style');
|
||||
style.id = 'safe-area-style';
|
||||
style.textContent = `
|
||||
body {{
|
||||
padding-top: env(safe-area-inset-top) !important;
|
||||
padding-bottom: env(safe-area-inset-bottom) !important;
|
||||
padding-left: env(safe-area-inset-left) !important;
|
||||
padding-right: env(safe-area-inset-right) !important;
|
||||
}}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}}
|
||||
}});
|
||||
"#,
|
||||
eink_script = eink_script
|
||||
);
|
||||
|
||||
let app_handle = app.handle().clone();
|
||||
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||
.background_throttling(BackgroundThrottlingPolicy::Disabled)
|
||||
.background_color(tauri::window::Color(50, 49, 48, 255))
|
||||
.initialization_script(
|
||||
r#"
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
document.documentElement.classList.add('edge-to-edge');
|
||||
const isTauriLocal = window.location.protocol === 'tauri:' ||
|
||||
window.location.protocol === 'about:' ||
|
||||
window.location.hostname === 'tauri.localhost';
|
||||
const needsSafeArea = !isTauriLocal;
|
||||
if (needsSafeArea && !document.getElementById('safe-area-style')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'safe-area-style';
|
||||
style.textContent = `
|
||||
body {
|
||||
padding-top: env(safe-area-inset-top) !important;
|
||||
padding-bottom: env(safe-area-inset-bottom) !important;
|
||||
padding-left: env(safe-area-inset-left) !important;
|
||||
padding-right: env(safe-area-inset-right) !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
});
|
||||
"#,
|
||||
)
|
||||
.background_color(if is_eink {
|
||||
tauri::window::Color(255, 255, 255, 255)
|
||||
} else {
|
||||
tauri::window::Color(50, 49, 48, 255)
|
||||
})
|
||||
.initialization_script(&init_script)
|
||||
.on_navigation(move |url| {
|
||||
if url.scheme() == "alipays" || url.scheme() == "alipay" {
|
||||
let url_str = url.as_str().to_string();
|
||||
|
||||
@@ -38,8 +38,8 @@ pub struct AppleIDAuthorizationResponse {
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static APPLE_SIGN_IN_DELEGATE: RefCell<Option<Retained<ASAuthorizationControllerDelegateImpl>>> = RefCell::new(None);
|
||||
static AUTHORIZATION_CONTROLLER: RefCell<Option<Retained<ASAuthorizationController>>> = RefCell::new(None);
|
||||
static APPLE_SIGN_IN_DELEGATE: RefCell<Option<Retained<ASAuthorizationControllerDelegateImpl>>> = const { RefCell::new(None) };
|
||||
static AUTHORIZATION_CONTROLLER: RefCell<Option<Retained<ASAuthorizationController>>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -70,7 +70,7 @@ define_class!(
|
||||
{
|
||||
let user_identifier = {
|
||||
let user = credential.user();
|
||||
if user.len() > 0 {
|
||||
if !user.is_empty() {
|
||||
Some(user.to_string())
|
||||
} else {
|
||||
None
|
||||
@@ -183,7 +183,7 @@ pub fn start_apple_sign_in(app: AppHandle, payload: AppleIDAuthorizationRequest)
|
||||
let auth_request = &request as &ASAuthorizationRequest;
|
||||
let controller = ASAuthorizationController::initWithAuthorizationRequests(
|
||||
ASAuthorizationController::alloc(),
|
||||
&*NSArray::from_slice(&[auth_request]),
|
||||
&NSArray::from_slice(&[auth_request]),
|
||||
);
|
||||
|
||||
let delegate = ASAuthorizationControllerDelegateImpl::new(app.clone());
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
use crate::allow_file_in_scopes;
|
||||
use std::path::PathBuf;
|
||||
use tauri::menu::MenuEvent;
|
||||
use tauri::menu::{SubmenuBuilder, HELP_SUBMENU_ID};
|
||||
use tauri::menu::{MenuItemBuilder, SubmenuBuilder, HELP_SUBMENU_ID};
|
||||
use tauri::AppHandle;
|
||||
use tauri::Emitter;
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
#[allow(dead_code)]
|
||||
struct OpenFilesPayload {
|
||||
files: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn setup_macos_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
let global_menu = app.menu().unwrap();
|
||||
|
||||
@@ -10,6 +19,23 @@ pub fn setup_macos_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
global_menu.remove(&item)?;
|
||||
}
|
||||
|
||||
let open_item = MenuItemBuilder::new("Open...")
|
||||
.id("open_file")
|
||||
.accelerator("Cmd+O")
|
||||
.build(app)?;
|
||||
|
||||
if let Some(file_menu) = global_menu.items()?.iter().find(|item| {
|
||||
if let Some(submenu) = item.as_submenu() {
|
||||
submenu.text().ok().as_deref() == Some("File")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
if let Some(file_submenu) = file_menu.as_submenu() {
|
||||
file_submenu.insert(&open_item, 0)?;
|
||||
}
|
||||
}
|
||||
|
||||
global_menu.append(
|
||||
&SubmenuBuilder::new(app, "Help")
|
||||
.text("privacy_policy", "Privacy Policy")
|
||||
@@ -28,7 +54,9 @@ pub fn setup_macos_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
|
||||
pub fn handle_menu_event(app: &AppHandle, event: &MenuEvent) {
|
||||
let opener = app.opener();
|
||||
if event.id() == "privacy_policy" {
|
||||
if event.id() == "open_file" {
|
||||
handle_open_file(app);
|
||||
} else if event.id() == "privacy_policy" {
|
||||
let _ = opener.open_url("https://readest.com/privacy-policy", None::<&str>);
|
||||
} else if event.id() == "report_issue" {
|
||||
let _ = opener.open_url("https://github.com/readest/readest/issues", None::<&str>);
|
||||
@@ -36,3 +64,25 @@ pub fn handle_menu_event(app: &AppHandle, event: &MenuEvent) {
|
||||
let _ = opener.open_url("https://readest.com/support", None::<&str>);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_open_file(app: &AppHandle) {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
let app_handle = app.clone();
|
||||
|
||||
app.dialog()
|
||||
.file()
|
||||
.add_filter(
|
||||
"Files",
|
||||
&["epub", "pdf", "mobi", "azw", "azw3", "fb2", "cbz", "txt"],
|
||||
)
|
||||
.pick_file(move |file_path| {
|
||||
if let Some(path) = file_path {
|
||||
let payload = OpenFilesPayload {
|
||||
files: vec![path.to_string()],
|
||||
};
|
||||
allow_file_in_scopes(&app_handle, vec![PathBuf::from(path.to_string())]);
|
||||
let _ = app_handle.emit("open-files", payload);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,16 +35,11 @@ impl ThreadSafeObjcPointer {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
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 {
|
||||
|
||||
@@ -108,15 +108,19 @@ pub async fn download_file(
|
||||
headers: HashMap<String, String>,
|
||||
body: Option<String>,
|
||||
single_threaded: Option<bool>,
|
||||
skip_ssl_verification: Option<bool>,
|
||||
on_progress: Channel<ProgressPayload>,
|
||||
) -> Result<()> {
|
||||
) -> Result<HashMap<String, String>> {
|
||||
use futures::stream::{self, StreamExt};
|
||||
use std::cmp::min;
|
||||
use tokio::io::AsyncSeekExt;
|
||||
|
||||
const PART_SIZE: u64 = 1024 * 1024;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.danger_accept_invalid_certs(skip_ssl_verification.unwrap_or(false))
|
||||
.danger_accept_invalid_hostnames(skip_ssl_verification.unwrap_or(false))
|
||||
.build()?;
|
||||
let force_single = single_threaded.unwrap_or(false);
|
||||
|
||||
async fn single_threaded_download(
|
||||
@@ -126,7 +130,7 @@ pub async fn download_file(
|
||||
headers: &HashMap<String, String>,
|
||||
body: &Option<String>,
|
||||
on_progress: Channel<ProgressPayload>,
|
||||
) -> Result<()> {
|
||||
) -> Result<HashMap<String, String>> {
|
||||
let mut request = if let Some(body) = body {
|
||||
client.post(url).body(body.clone())
|
||||
} else {
|
||||
@@ -145,6 +149,13 @@ pub async fn download_file(
|
||||
));
|
||||
}
|
||||
|
||||
let mut resp_headers = HashMap::new();
|
||||
for (key, value) in response.headers().iter() {
|
||||
if let Ok(val_str) = value.to_str() {
|
||||
resp_headers.insert(key.to_string(), val_str.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let total = response.content_length().unwrap_or(0);
|
||||
let mut file = BufWriter::new(File::create(file_path).await?);
|
||||
let mut stream = response.bytes_stream();
|
||||
@@ -161,7 +172,7 @@ pub async fn download_file(
|
||||
}
|
||||
file.flush().await?;
|
||||
|
||||
Ok(())
|
||||
Ok(resp_headers)
|
||||
}
|
||||
|
||||
if force_single {
|
||||
@@ -189,6 +200,13 @@ pub async fn download_file(
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut resp_headers = HashMap::new();
|
||||
for (key, value) in range_resp.headers().iter() {
|
||||
if let Ok(val_str) = value.to_str() {
|
||||
resp_headers.insert(key.to_string(), val_str.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if !accept_ranges || total == 0 {
|
||||
return single_threaded_download(&client, url, file_path, &headers, &body, on_progress)
|
||||
.await;
|
||||
@@ -256,7 +274,7 @@ pub async fn download_file(
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
Ok(resp_headers)
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
"csp": {
|
||||
"default-src": "'self' 'unsafe-inline' blob: data: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost",
|
||||
"connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost http://*:* https://*:* https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com https://translate.googleapis.com https://translate.toil.cc https://*.microsofttranslator.com https://edge.microsoft.com https://*.googleusercontent.com",
|
||||
"img-src": "'self' blob: data: asset: http://asset.localhost https://*",
|
||||
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com https://chinese-fonts-cdn.netlify.app https://cdnjs.cloudflare.com",
|
||||
"font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://db.onlinewebfonts.com https://cdn.jsdelivr.net https://fonts.gstatic.com https://chinese-fonts-cdn.netlify.app https://cdnjs.cloudflare.com",
|
||||
"img-src": "'self' blob: data: asset: http://asset.localhost https://* https://*:* http://* http://*:*",
|
||||
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com https://cdnjs.cloudflare.com https://storage.readest.com",
|
||||
"font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://db.onlinewebfonts.com https://cdn.jsdelivr.net https://fonts.gstatic.com https://cdnjs.cloudflare.com https://storage.readest.com",
|
||||
"frame-src": "'self' blob: asset: http://asset.localhost https://*.stripe.com",
|
||||
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' blob: asset: http://asset.localhost https://*.sentry.io https://*.posthog.com https://*.stripe.com"
|
||||
},
|
||||
@@ -72,7 +72,8 @@
|
||||
},
|
||||
"iOS": {
|
||||
"developmentTeam": "J5W48D69VR",
|
||||
"minimumSystemVersion": "14.0"
|
||||
"infoPlist": "./Info-ios.plist",
|
||||
"minimumSystemVersion": "15.0"
|
||||
},
|
||||
"fileAssociations": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
|
||||
import { EnvProvider } from '@/context/EnvContext';
|
||||
import ProofreadPopup from '@/app/reader/components/annotator/ProofreadPopup';
|
||||
|
||||
vi.mock('@/services/environment', async () => {
|
||||
const actual = await vi.importActual('@/services/environment');
|
||||
|
||||
const mockAppService = {
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
// Add any other methods from AppService interface
|
||||
};
|
||||
|
||||
return {
|
||||
...actual,
|
||||
default: {
|
||||
getAppService: vi.fn().mockResolvedValue(mockAppService),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
global.ResizeObserver = class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
};
|
||||
|
||||
function renderWithProviders(ui: React.ReactNode) {
|
||||
return render(<EnvProvider>{ui}</EnvProvider>);
|
||||
}
|
||||
|
||||
describe('ProofreadPopup Component', () => {
|
||||
const mockOnConfirm = vi.fn();
|
||||
const mockOnClose = vi.fn();
|
||||
|
||||
const defaultProps = {
|
||||
bookKey: 'test-book',
|
||||
isVertical: false,
|
||||
selectedText: 'test word',
|
||||
selection: {
|
||||
key: 'test-book',
|
||||
text: 'test word',
|
||||
cfi: 'epubcfi(/6/2[chapter1]!/4/1:0)',
|
||||
range: {
|
||||
deleteContents: vi.fn(),
|
||||
insertNode: vi.fn(),
|
||||
startContainer: document.createTextNode('test word here'),
|
||||
endContainer: document.createTextNode('test word here'),
|
||||
startOffset: 5,
|
||||
endOffset: 9,
|
||||
} as unknown as Range,
|
||||
index: 0,
|
||||
},
|
||||
position: { point: { x: 100, y: 100 } },
|
||||
trianglePosition: { point: { x: 100, y: 100 } },
|
||||
popupWidth: 440,
|
||||
popupHeight: 200,
|
||||
onConfirm: mockOnConfirm,
|
||||
onDismiss: mockOnClose,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render default replacement scope options', () => {
|
||||
renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText('Current selection')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the replacement text input field', () => {
|
||||
renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter text...');
|
||||
expect(input).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the case sensitive checkbox', () => {
|
||||
const { container } = renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText('Case sensitive:')).toBeTruthy();
|
||||
expect(container.querySelector('input[type="checkbox"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the Apply button', () => {
|
||||
renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText('Apply')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display selected text preview', () => {
|
||||
renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText(/Selected text:/)).toBeTruthy();
|
||||
expect(screen.getByText(/"test word"/)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Case Sensitive Checkbox', () => {
|
||||
it('should be checked by default (case-sensitive)', () => {
|
||||
const { container } = renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
const checkbox = container.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||
expect(checkbox.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle when clicked', async () => {
|
||||
const { container } = renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
const checkbox = container.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||
expect(checkbox.checked).toBe(true);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox.checked).toBe(false);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox.checked).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Replacement Text Input', () => {
|
||||
it('should update value when user types', () => {
|
||||
renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter text...') as HTMLInputElement;
|
||||
fireEvent.change(input, { target: { value: 'new text' } });
|
||||
|
||||
expect(input.value).toBe('new text');
|
||||
});
|
||||
|
||||
it('should trim whitespace from replacement text', () => {
|
||||
renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter text...');
|
||||
fireEvent.change(input, { target: { value: ' trimmed ' } });
|
||||
|
||||
const confirmButton = screen.getByText('Apply');
|
||||
fireEvent.click(confirmButton);
|
||||
|
||||
expect(mockOnConfirm).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replacement: 'trimmed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Scope Selection Handlers', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const createValidSelection = () => ({
|
||||
...defaultProps,
|
||||
selection: {
|
||||
...defaultProps.selection,
|
||||
text: 'word',
|
||||
cfi: 'epubcfi(/6/4[chap01ref]!/4/2/1:0)',
|
||||
range: {
|
||||
deleteContents: vi.fn(),
|
||||
insertNode: vi.fn(),
|
||||
startContainer: document.createTextNode('test word here'),
|
||||
endContainer: document.createTextNode('test word here'),
|
||||
startOffset: 5,
|
||||
endOffset: 9,
|
||||
} as unknown as Range,
|
||||
},
|
||||
});
|
||||
|
||||
it('should call onConfirm with correct scope for "selection"', async () => {
|
||||
renderWithProviders(<ProofreadPopup {...createValidSelection()} />);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter text...');
|
||||
fireEvent.change(input, { target: { value: 'replacement' } });
|
||||
|
||||
const applyButton = screen.getByText('Apply');
|
||||
fireEvent.click(applyButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnConfirm).toHaveBeenCalledWith(expect.objectContaining({ scope: 'selection' }));
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onConfirm with correct scope for "book"', async () => {
|
||||
renderWithProviders(<ProofreadPopup {...createValidSelection()} />);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter text...');
|
||||
fireEvent.change(input, { target: { value: 'replacement' } });
|
||||
|
||||
const scopeSelect = screen.getByRole('combobox');
|
||||
fireEvent.change(scopeSelect, { target: { value: 'book' } });
|
||||
|
||||
const applyButton = screen.getByText('Apply');
|
||||
fireEvent.click(applyButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnConfirm).toHaveBeenCalledWith(expect.objectContaining({ scope: 'book' }));
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onConfirm with correct scope for "library"', async () => {
|
||||
renderWithProviders(<ProofreadPopup {...createValidSelection()} />);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter text...');
|
||||
fireEvent.change(input, { target: { value: 'replacement' } });
|
||||
|
||||
const scopeSelect = screen.getByRole('combobox');
|
||||
fireEvent.change(scopeSelect, { target: { value: 'library' } });
|
||||
|
||||
const applyButton = screen.getByText('Apply');
|
||||
fireEvent.click(applyButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnConfirm).toHaveBeenCalledWith(expect.objectContaining({ scope: 'library' }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Click Outside Behavior', () => {
|
||||
it('should not call onClose when clicking inside the menu', () => {
|
||||
renderWithProviders(<ProofreadPopup {...defaultProps} />);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter text...');
|
||||
fireEvent.mouseDown(input);
|
||||
|
||||
expect(mockOnClose).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,564 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, fireEvent, cleanup, within } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
import {
|
||||
ProofreadRulesManager,
|
||||
setProofreadRulesVisibility,
|
||||
} from '@/app/reader/components/ProofreadRules';
|
||||
import BookMenu from '@/app/reader/components/sidebar/BookMenu';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { ProofreadRule } from '@/types/book';
|
||||
|
||||
// ------------------------------
|
||||
// NEXT.JS ROUTER MOCK
|
||||
// ------------------------------
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: vi.fn(),
|
||||
replace: vi.fn(),
|
||||
prefetch: vi.fn(),
|
||||
}),
|
||||
useSearchParams: () => ({
|
||||
get: () => null,
|
||||
toString: () => '',
|
||||
}),
|
||||
}));
|
||||
|
||||
// ------------------------------
|
||||
// TRANSLATION MOCK
|
||||
// ------------------------------
|
||||
vi.mock('@/hooks/useTranslation', () => ({
|
||||
useTranslation: () => (key: string) => key,
|
||||
}));
|
||||
vi.mock('@/services/translators/cache', () => ({
|
||||
initCache: vi.fn(),
|
||||
loadCacheFromDB: vi.fn(),
|
||||
pruneCache: vi.fn(),
|
||||
}));
|
||||
|
||||
// ------------------------------
|
||||
// ENV PROVIDER WRAPPER
|
||||
// ------------------------------
|
||||
// mock environment module so EnvProvider uses fake values
|
||||
vi.mock('@/services/environment', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
|
||||
return {
|
||||
...(typeof actual === 'object' && actual !== null ? actual : {}), // keep all real exports (e.g., isTauriAppPlatform)
|
||||
|
||||
default: {
|
||||
...(typeof actual === 'object' &&
|
||||
actual !== null &&
|
||||
'default' in actual &&
|
||||
typeof actual.default === 'object' &&
|
||||
actual.default !== null
|
||||
? actual.default
|
||||
: {}), // keep all real default fields
|
||||
API_BASE: 'http://localhost',
|
||||
ENABLE_TRANSLATOR: false,
|
||||
getAppService: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
import { EnvProvider } from '@/context/EnvContext';
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
|
||||
function renderWithProviders(ui: React.ReactNode) {
|
||||
return render(
|
||||
<EnvProvider>
|
||||
<AuthProvider>{ui}</AuthProvider>
|
||||
</EnvProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('ProofreadRulesManager', () => {
|
||||
beforeEach(() => {
|
||||
// Reset stores
|
||||
(useSettingsStore.setState as unknown as (state: unknown) => void)({
|
||||
settings: {
|
||||
globalViewSettings: { proofreadRules: [] },
|
||||
kosync: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
(useReaderStore.setState as unknown as (state: unknown) => void)({ viewStates: {} });
|
||||
useSidebarStore.setState({ sideBarBookKey: null });
|
||||
(useBookDataStore.setState as unknown as (state: unknown) => void)({ booksData: {} });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('renders book and library (global) proofreading rules from stores', async () => {
|
||||
// Arrange: populate stores
|
||||
(useSettingsStore.setState as unknown as (state: unknown) => void)({
|
||||
settings: {
|
||||
globalViewSettings: {
|
||||
proofreadRules: [
|
||||
{
|
||||
id: 'g1',
|
||||
scope: 'library',
|
||||
pattern: 'foo',
|
||||
replacement: 'bar',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 1,
|
||||
wholeWord: true,
|
||||
},
|
||||
{
|
||||
id: 'g2',
|
||||
scope: 'library',
|
||||
pattern: 'hello',
|
||||
replacement: 'world',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 2,
|
||||
wholeWord: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
kosync: { enabled: false },
|
||||
},
|
||||
});
|
||||
|
||||
(useReaderStore.setState as unknown as (state: unknown) => void)({
|
||||
viewStates: {
|
||||
book1: {
|
||||
viewSettings: {
|
||||
proofreadRules: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useSidebarStore.setState({ sideBarBookKey: 'book1' });
|
||||
|
||||
// Act: render and open dialog
|
||||
renderWithProviders(<ProofreadRulesManager />);
|
||||
// wait a tick so the component's effect attaches the event listener
|
||||
await Promise.resolve();
|
||||
// open via helper which dispatches the custom event
|
||||
setProofreadRulesVisibility(true);
|
||||
|
||||
// Assert
|
||||
const dialog = await screen.findByRole('dialog');
|
||||
expect(dialog).toBeTruthy();
|
||||
// Library (global) rules
|
||||
expect(screen.getByText('foo')).toBeTruthy();
|
||||
expect(screen.getByText("'bar'")).toBeTruthy();
|
||||
expect(screen.getByText('hello')).toBeTruthy();
|
||||
expect(screen.getByText("'world'")).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders selection rules separately from book/library rules', async () => {
|
||||
// Arrange: populate stores with a selection rule persisted in book config
|
||||
(useSettingsStore.setState as unknown as (state: unknown) => void)({
|
||||
settings: {
|
||||
globalViewSettings: { proofreadRules: [] },
|
||||
kosync: { enabled: false },
|
||||
},
|
||||
});
|
||||
|
||||
const selectionRule: ProofreadRule = {
|
||||
id: 's1',
|
||||
scope: 'selection',
|
||||
pattern: 'only-once',
|
||||
replacement: 'single-hit',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 1,
|
||||
wholeWord: true,
|
||||
cfi: 'epubcfi(/6/14!/4/2,/1:0,/1:4)',
|
||||
sectionHref: 'chapter1.html',
|
||||
};
|
||||
|
||||
const bookRule: ProofreadRule = {
|
||||
id: 'b1',
|
||||
scope: 'book',
|
||||
pattern: 'book-wide',
|
||||
replacement: 'book-hit',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 2,
|
||||
wholeWord: true,
|
||||
};
|
||||
|
||||
(useReaderStore.setState as unknown as (state: unknown) => void)({
|
||||
viewStates: {
|
||||
book1: {
|
||||
viewSettings: {
|
||||
proofreadRules: [selectionRule, bookRule],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(useBookDataStore.setState as unknown as (state: unknown) => void)({
|
||||
booksData: {
|
||||
book1: {
|
||||
id: 'book1',
|
||||
book: null,
|
||||
file: null,
|
||||
config: {
|
||||
viewSettings: {
|
||||
proofreadRules: [selectionRule, bookRule],
|
||||
},
|
||||
},
|
||||
bookDoc: null,
|
||||
isFixedLayout: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useSidebarStore.setState({ sideBarBookKey: 'book1' });
|
||||
|
||||
// Act: render and open dialog
|
||||
renderWithProviders(<ProofreadRulesManager />);
|
||||
await Promise.resolve();
|
||||
setProofreadRulesVisibility(true);
|
||||
|
||||
// Assert
|
||||
const dialog = await screen.findByRole('dialog');
|
||||
expect(dialog).toBeTruthy();
|
||||
|
||||
// Single Instance Rules section
|
||||
expect(screen.getByText('Selected Text Rules')).toBeTruthy();
|
||||
expect(screen.getByText('only-once')).toBeTruthy();
|
||||
expect(screen.getByText("'single-hit'")).toBeTruthy();
|
||||
|
||||
// Book section should still show book-wide rule
|
||||
expect(screen.getByText('book-wide')).toBeTruthy();
|
||||
expect(screen.getByText("'book-hit'")).toBeTruthy();
|
||||
});
|
||||
|
||||
it('displays correct scope labels for different rule types', async () => {
|
||||
const selectionRule: ProofreadRule = {
|
||||
id: 's1',
|
||||
scope: 'selection',
|
||||
pattern: 'select-text',
|
||||
replacement: 'replaced',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 1,
|
||||
wholeWord: true,
|
||||
cfi: 'epubcfi(/6/14!/4/2,/1:0,/1:4)',
|
||||
sectionHref: 'chapter1.html',
|
||||
};
|
||||
|
||||
const bookRule: ProofreadRule = {
|
||||
id: 'b1',
|
||||
scope: 'book',
|
||||
pattern: 'book-pattern',
|
||||
replacement: 'book-replaced',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 2,
|
||||
wholeWord: true,
|
||||
};
|
||||
|
||||
const libraryRule: ProofreadRule = {
|
||||
id: 'l1',
|
||||
scope: 'library',
|
||||
pattern: 'library-pattern',
|
||||
replacement: 'library-replaced',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 3,
|
||||
wholeWord: true,
|
||||
};
|
||||
|
||||
(useSettingsStore.setState as unknown as (state: unknown) => void)({
|
||||
settings: {
|
||||
globalViewSettings: {
|
||||
proofreadRules: [libraryRule],
|
||||
},
|
||||
kosync: { enabled: false },
|
||||
},
|
||||
});
|
||||
|
||||
(useReaderStore.setState as unknown as (state: unknown) => void)({
|
||||
viewStates: {
|
||||
book1: {
|
||||
viewSettings: {
|
||||
proofreadRules: [selectionRule, bookRule],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(useBookDataStore.setState as unknown as (state: unknown) => void)({
|
||||
booksData: {
|
||||
book1: {
|
||||
id: 'book1',
|
||||
book: null,
|
||||
file: null,
|
||||
config: {
|
||||
viewSettings: {
|
||||
proofreadRules: [selectionRule, bookRule],
|
||||
},
|
||||
},
|
||||
bookDoc: null,
|
||||
isFixedLayout: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useSidebarStore.setState({ sideBarBookKey: 'book1' });
|
||||
|
||||
renderWithProviders(<ProofreadRulesManager />);
|
||||
await Promise.resolve();
|
||||
setProofreadRulesVisibility(true);
|
||||
|
||||
const dialog = await screen.findByRole('dialog');
|
||||
expect(dialog).toBeTruthy();
|
||||
|
||||
const selectionRuleElement = screen.getByText('select-text').closest('li');
|
||||
expect(within(selectionRuleElement!).getByText(/Selection/)).toBeTruthy();
|
||||
|
||||
const bookRuleElement = screen.getByText('book-pattern').closest('li');
|
||||
expect(within(bookRuleElement!).getByText(/Book/)).toBeTruthy();
|
||||
|
||||
const libraryRuleElement = screen.getByText('library-pattern').closest('li');
|
||||
expect(within(libraryRuleElement!).getByText(/Library/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows case sensitivity status for each rule', async () => {
|
||||
const caseSensitiveRule: ProofreadRule = {
|
||||
id: 'cs1',
|
||||
scope: 'book',
|
||||
pattern: 'case-sensitive',
|
||||
replacement: 'CS-REPLACED',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 1,
|
||||
wholeWord: true,
|
||||
};
|
||||
|
||||
const caseInsensitiveRule: ProofreadRule = {
|
||||
id: 'ci1',
|
||||
scope: 'book',
|
||||
pattern: 'case-insensitive',
|
||||
replacement: 'CI-REPLACED',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: false,
|
||||
order: 2,
|
||||
wholeWord: true,
|
||||
};
|
||||
|
||||
(useSettingsStore.setState as unknown as (state: unknown) => void)({
|
||||
settings: {
|
||||
globalViewSettings: { proofreadRules: [] },
|
||||
kosync: { enabled: false },
|
||||
},
|
||||
});
|
||||
|
||||
(useReaderStore.setState as unknown as (state: unknown) => void)({
|
||||
viewStates: {
|
||||
book1: {
|
||||
viewSettings: {
|
||||
proofreadRules: [caseSensitiveRule, caseInsensitiveRule],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(useBookDataStore.setState as unknown as (state: unknown) => void)({
|
||||
booksData: {
|
||||
book1: {
|
||||
id: 'book1',
|
||||
book: null,
|
||||
file: null,
|
||||
config: {
|
||||
viewSettings: {
|
||||
proofreadRules: [caseSensitiveRule, caseInsensitiveRule],
|
||||
},
|
||||
},
|
||||
bookDoc: null,
|
||||
isFixedLayout: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useSidebarStore.setState({ sideBarBookKey: 'book1' });
|
||||
|
||||
renderWithProviders(<ProofreadRulesManager />);
|
||||
await Promise.resolve();
|
||||
setProofreadRulesVisibility(true);
|
||||
|
||||
const dialog = await screen.findByRole('dialog');
|
||||
expect(dialog).toBeTruthy();
|
||||
|
||||
const csRuleElement = screen.getByText('case-sensitive').closest('li');
|
||||
expect(within(csRuleElement!).getByText(/Case sensitive:/)).toBeTruthy();
|
||||
expect(within(csRuleElement!).getAllByText(/Yes/)).toBeTruthy();
|
||||
|
||||
const ciRuleElement = screen.getByText('case-insensitive').closest('li');
|
||||
expect(within(ciRuleElement!).getByText(/Case sensitive:/)).toBeTruthy();
|
||||
expect(within(ciRuleElement!).getAllByText(/No/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('opens when BookMenu item is clicked (integration)', async () => {
|
||||
// Arrange stores
|
||||
(useSettingsStore.setState as unknown as (state: unknown) => void)({
|
||||
settings: {
|
||||
globalViewSettings: { proofreadRules: [] },
|
||||
kosync: { enabled: false },
|
||||
},
|
||||
});
|
||||
(useReaderStore.setState as unknown as (state: unknown) => void)({
|
||||
viewStates: {
|
||||
book1: { viewSettings: { proofreadRules: [] } },
|
||||
},
|
||||
});
|
||||
useSidebarStore.setState({ sideBarBookKey: 'book1' });
|
||||
|
||||
// Render both menu and window
|
||||
renderWithProviders(
|
||||
<div>
|
||||
<BookMenu />
|
||||
<ProofreadRulesManager />
|
||||
</div>,
|
||||
);
|
||||
|
||||
// wait a tick so effects attach
|
||||
await Promise.resolve();
|
||||
|
||||
// Click the menu item
|
||||
const menuItem = screen.getByRole('menuitem', { name: 'Proofread' });
|
||||
fireEvent.click(menuItem);
|
||||
|
||||
// The dialog should open
|
||||
const dialog = await screen.findByRole('dialog');
|
||||
|
||||
expect(within(dialog).getByText('Proofread Replacement Rules')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows empty state messages when no rules exist', async () => {
|
||||
(useSettingsStore.setState as unknown as (state: unknown) => void)({
|
||||
settings: {
|
||||
globalViewSettings: { proofreadRules: [] },
|
||||
kosync: { enabled: false },
|
||||
},
|
||||
});
|
||||
|
||||
(useReaderStore.setState as unknown as (state: unknown) => void)({
|
||||
viewStates: {
|
||||
book1: {
|
||||
viewSettings: {
|
||||
proofreadRules: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useSidebarStore.setState({ sideBarBookKey: 'book1' });
|
||||
|
||||
renderWithProviders(<ProofreadRulesManager />);
|
||||
await Promise.resolve();
|
||||
setProofreadRulesVisibility(true);
|
||||
|
||||
const dialog = await screen.findByRole('dialog');
|
||||
expect(dialog).toBeTruthy();
|
||||
|
||||
// Check for empty state messages
|
||||
expect(screen.getByText('No selected text replacement rules')).toBeTruthy();
|
||||
expect(screen.getByText('No book-level replacement rules')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('merges book and library rules correctly in book section', async () => {
|
||||
const libraryRule: ProofreadRule = {
|
||||
id: 'l1',
|
||||
scope: 'library',
|
||||
pattern: 'library-wide',
|
||||
replacement: 'LIBRARY',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 1,
|
||||
wholeWord: true,
|
||||
};
|
||||
|
||||
const bookRule: ProofreadRule = {
|
||||
id: 'b1',
|
||||
scope: 'book',
|
||||
pattern: 'book-specific',
|
||||
replacement: 'BOOK',
|
||||
enabled: true,
|
||||
isRegex: false,
|
||||
caseSensitive: true,
|
||||
order: 2,
|
||||
wholeWord: true,
|
||||
};
|
||||
|
||||
(useSettingsStore.setState as unknown as (state: unknown) => void)({
|
||||
settings: {
|
||||
globalViewSettings: {
|
||||
proofreadRules: [libraryRule],
|
||||
},
|
||||
kosync: { enabled: false },
|
||||
},
|
||||
});
|
||||
|
||||
(useReaderStore.setState as unknown as (state: unknown) => void)({
|
||||
viewStates: {
|
||||
book1: {
|
||||
viewSettings: {
|
||||
proofreadRules: [bookRule],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(useBookDataStore.setState as unknown as (state: unknown) => void)({
|
||||
booksData: {
|
||||
book1: {
|
||||
id: 'book1',
|
||||
book: null,
|
||||
file: null,
|
||||
config: {
|
||||
viewSettings: {
|
||||
proofreadRules: [bookRule],
|
||||
},
|
||||
},
|
||||
bookDoc: null,
|
||||
isFixedLayout: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useSidebarStore.setState({ sideBarBookKey: 'book1' });
|
||||
|
||||
renderWithProviders(<ProofreadRulesManager />);
|
||||
await Promise.resolve();
|
||||
setProofreadRulesVisibility(true);
|
||||
|
||||
const dialog = await screen.findByRole('dialog');
|
||||
expect(dialog).toBeTruthy();
|
||||
|
||||
// Both library and book rules should appear in the Book Specific Rules section
|
||||
expect(screen.getByText('library-wide')).toBeTruthy();
|
||||
expect(screen.getByText('book-specific')).toBeTruthy();
|
||||
|
||||
// But they should both be under Book Specific Rules section
|
||||
const bookSection = screen.getByText('Book Specific Rules').parentElement;
|
||||
expect(within(bookSection!).getByText('library-wide')).toBeTruthy();
|
||||
expect(within(bookSection!).getByText('book-specific')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,378 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { makeSafeFilename } from '../../utils/misc';
|
||||
|
||||
describe('makeSafeFilename', () => {
|
||||
describe('Basic sanitization', () => {
|
||||
it('should replace unsafe characters with underscore', () => {
|
||||
expect(makeSafeFilename('file<name>.txt')).toBe('file_name_.txt');
|
||||
expect(makeSafeFilename('file>name.txt')).toBe('file_name.txt');
|
||||
expect(makeSafeFilename('file:name.txt')).toBe('file_name.txt');
|
||||
expect(makeSafeFilename('file"name.txt')).toBe('file_name.txt');
|
||||
expect(makeSafeFilename('file/name.txt')).toBe('file_name.txt');
|
||||
expect(makeSafeFilename('file\\name.txt')).toBe('file_name.txt');
|
||||
expect(makeSafeFilename('file|name.txt')).toBe('file_name.txt');
|
||||
expect(makeSafeFilename('file?name.txt')).toBe('file_name.txt');
|
||||
expect(makeSafeFilename('file*name.txt')).toBe('file_name.txt');
|
||||
});
|
||||
|
||||
it('should replace multiple unsafe characters', () => {
|
||||
expect(makeSafeFilename('file<>:"|?*.txt')).toBe('file_______.txt');
|
||||
});
|
||||
|
||||
it('should use custom replacement character', () => {
|
||||
expect(makeSafeFilename('file<name>.txt', '-')).toBe('file-name-.txt');
|
||||
expect(makeSafeFilename('file:name.txt', '')).toBe('filename.txt');
|
||||
});
|
||||
|
||||
it('should handle control characters', () => {
|
||||
expect(makeSafeFilename('file\x00name.txt')).toBe('file_name.txt');
|
||||
expect(makeSafeFilename('file\x1Fname.txt')).toBe('file_name.txt');
|
||||
});
|
||||
|
||||
it('should trim whitespace from result', () => {
|
||||
expect(makeSafeFilename(' filename.txt ')).toBe('filename.txt');
|
||||
expect(makeSafeFilename('filename.txt ')).toBe('filename.txt');
|
||||
expect(makeSafeFilename(' filename.txt')).toBe('filename.txt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Reserved filenames (Windows)', () => {
|
||||
it('should handle reserved names case-insensitively', () => {
|
||||
expect(makeSafeFilename('CON')).toBe('CON_');
|
||||
expect(makeSafeFilename('con')).toBe('con_');
|
||||
expect(makeSafeFilename('Con')).toBe('Con_');
|
||||
expect(makeSafeFilename('PRN')).toBe('PRN_');
|
||||
expect(makeSafeFilename('AUX')).toBe('AUX_');
|
||||
expect(makeSafeFilename('NUL')).toBe('NUL_');
|
||||
});
|
||||
|
||||
it('should handle reserved names with port numbers', () => {
|
||||
expect(makeSafeFilename('COM1')).toBe('COM1_');
|
||||
expect(makeSafeFilename('COM9')).toBe('COM9_');
|
||||
expect(makeSafeFilename('LPT1')).toBe('LPT1_');
|
||||
expect(makeSafeFilename('LPT9')).toBe('LPT9_');
|
||||
});
|
||||
|
||||
it('should not affect reserved names with extensions', () => {
|
||||
// Reserved names only apply to the base name without extension
|
||||
const result = makeSafeFilename('CON.txt');
|
||||
expect(result).toBe('CON.txt'); // This might be CON_.txt depending on implementation
|
||||
});
|
||||
|
||||
it('should not affect similar but non-reserved names', () => {
|
||||
expect(makeSafeFilename('CONFIG')).toBe('CONFIG');
|
||||
expect(makeSafeFilename('CONSOLE')).toBe('CONSOLE');
|
||||
expect(makeSafeFilename('PRINTER')).toBe('PRINTER');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multi-byte UTF-8 characters', () => {
|
||||
it('should preserve single multi-byte characters', () => {
|
||||
expect(makeSafeFilename('文件.txt')).toBe('文件.txt');
|
||||
expect(makeSafeFilename('ファイル.txt')).toBe('ファイル.txt');
|
||||
expect(makeSafeFilename('파일.txt')).toBe('파일.txt');
|
||||
});
|
||||
|
||||
it('should preserve emoji characters', () => {
|
||||
expect(makeSafeFilename('📚 Book.txt')).toBe('📚 Book.txt');
|
||||
expect(makeSafeFilename('🎉🎊🎈.txt')).toBe('🎉🎊🎈.txt');
|
||||
expect(makeSafeFilename('Test 😀.txt')).toBe('Test 😀.txt');
|
||||
});
|
||||
|
||||
it('should handle mixed ASCII and multi-byte characters', () => {
|
||||
expect(makeSafeFilename('Book-书籍-本.txt')).toBe('Book-书籍-本.txt');
|
||||
expect(makeSafeFilename('Test_测试_テスト.txt')).toBe('Test_测试_テスト.txt');
|
||||
});
|
||||
|
||||
it('should preserve complex emoji (with modifiers and ZWJ)', () => {
|
||||
expect(makeSafeFilename('👨👩👧👦.txt')).toBe('👨👩👧👦.txt'); // Family emoji with ZWJ
|
||||
expect(makeSafeFilename('👍🏽.txt')).toBe('👍🏽.txt'); // Thumbs up with skin tone modifier
|
||||
});
|
||||
|
||||
it('should handle combining characters', () => {
|
||||
expect(makeSafeFilename('café.txt')).toBe('café.txt'); // é is composed
|
||||
expect(makeSafeFilename('naïve.txt')).toBe('naïve.txt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Byte length truncation (250 bytes max)', () => {
|
||||
it('should not truncate short filenames', () => {
|
||||
const shortName = 'short.txt';
|
||||
expect(makeSafeFilename(shortName)).toBe(shortName);
|
||||
});
|
||||
|
||||
it('should truncate long ASCII filenames', () => {
|
||||
const longName = 'a'.repeat(260) + '.txt';
|
||||
const result = makeSafeFilename(longName);
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
});
|
||||
|
||||
it('should truncate at exactly 250 bytes', () => {
|
||||
const longName = 'x'.repeat(255); // More than 250 bytes
|
||||
const result = makeSafeFilename(longName);
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBe(250);
|
||||
});
|
||||
|
||||
it('should preserve valid UTF-8 when truncating multi-byte characters', () => {
|
||||
// Chinese characters: each is 3 bytes in UTF-8
|
||||
const chineseChars = '书'.repeat(100); // 300 bytes total
|
||||
const result = makeSafeFilename(chineseChars);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
// Verify no broken UTF-8 by encoding and decoding
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�'); // No replacement characters
|
||||
});
|
||||
|
||||
it('should handle Japanese characters when truncating', () => {
|
||||
// Japanese hiragana: each is 3 bytes in UTF-8
|
||||
const japaneseChars = 'あ'.repeat(100); // 300 bytes
|
||||
const result = makeSafeFilename(japaneseChars);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�');
|
||||
});
|
||||
|
||||
it('should handle emoji when truncating', () => {
|
||||
// Most emoji are 4 bytes in UTF-8
|
||||
const emojiString = '😀'.repeat(70); // 280 bytes
|
||||
const result = makeSafeFilename(emojiString);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�');
|
||||
});
|
||||
|
||||
it('should handle mixed-width characters when truncating', () => {
|
||||
// Mix of 1-byte (ASCII), 2-byte (Latin extended), 3-byte (CJK), 4-byte (emoji)
|
||||
const mixedString = 'Test测试тест😀'.repeat(20); // Over 250 bytes
|
||||
const result = makeSafeFilename(mixedString);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�');
|
||||
});
|
||||
|
||||
it('should handle Korean characters when truncating', () => {
|
||||
// Korean characters: each is 3 bytes in UTF-8
|
||||
const koreanChars = '가'.repeat(100); // 300 bytes
|
||||
const result = makeSafeFilename(koreanChars);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle empty string', () => {
|
||||
expect(makeSafeFilename('')).toBe('');
|
||||
});
|
||||
|
||||
it('should handle string with only unsafe characters', () => {
|
||||
expect(makeSafeFilename('<>:"|?*')).toBe('_______');
|
||||
});
|
||||
|
||||
it('should handle string that becomes empty after sanitization and trimming', () => {
|
||||
const result = makeSafeFilename(' ');
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle very long extension', () => {
|
||||
const longExt = '.txt'.repeat(50);
|
||||
const filename = 'file' + longExt;
|
||||
const result = makeSafeFilename(filename);
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
});
|
||||
|
||||
it('should handle filename with only whitespace', () => {
|
||||
expect(makeSafeFilename(' ')).toBe('');
|
||||
});
|
||||
|
||||
it('should handle complex real-world Chinese book title', () => {
|
||||
const longTitle =
|
||||
'这是一个非常长的中文书名用来测试文件名处理功能这个标题包含了很多汉字字符'.repeat(3) +
|
||||
'.epub';
|
||||
const result = makeSafeFilename(longTitle);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
// Verify UTF-8 validity
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�');
|
||||
});
|
||||
|
||||
it('should handle complex real-world Chinese book title', () => {
|
||||
const longTitle =
|
||||
'榎宮祐 - NO GAME NO LIFE 遊戲人生 02 遊戲玩家兄妹似乎盯上獸耳娘的國家了'.repeat(3);
|
||||
const result = makeSafeFilename(longTitle);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
// Verify UTF-8 validity
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�');
|
||||
});
|
||||
|
||||
it('should handle complex real-world Chinese book titles with varying zero padding', () => {
|
||||
const testCases = Array.from({ length: 31 }, (_, i) => {
|
||||
const padding = '0'.repeat(i) + '2';
|
||||
return `榎宮祐 - ${'NO GAME NO LIFE'.repeat(12)} 遊戲人生 ${padding} 遊戲玩家兄妹似乎盯上獸耳娘的國家了`;
|
||||
});
|
||||
|
||||
for (const longTitle of testCases) {
|
||||
const result = makeSafeFilename(longTitle);
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle right-to-left text (Arabic)', () => {
|
||||
const arabicName = 'كتاب'.repeat(50) + '.pdf'; // Over 250 bytes
|
||||
const result = makeSafeFilename(arabicName);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
});
|
||||
|
||||
it('should handle Cyrillic characters', () => {
|
||||
const cyrillicName = 'Книга'.repeat(60) + '.txt'; // Over 250 bytes
|
||||
const result = makeSafeFilename(cyrillicName);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
});
|
||||
|
||||
it('should handle Thai characters', () => {
|
||||
const thaiName = 'หนังสือ'.repeat(50) + '.pdf'; // Over 250 bytes
|
||||
const result = makeSafeFilename(thaiName);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Combined sanitization and truncation', () => {
|
||||
it('should sanitize and truncate in correct order', () => {
|
||||
const unsafeLongName = '<'.repeat(260) + '.txt';
|
||||
const result = makeSafeFilename(unsafeLongName);
|
||||
|
||||
// Should replace < with _, then truncate
|
||||
expect(result).not.toContain('<');
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
});
|
||||
|
||||
it('should handle reserved name that needs truncation', () => {
|
||||
// Edge case: reserved name with very long content
|
||||
const longReservedLike = 'CON' + 'x'.repeat(260);
|
||||
const result = makeSafeFilename(longReservedLike);
|
||||
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
});
|
||||
|
||||
it('should sanitize, handle reserved names, and truncate multi-byte characters', () => {
|
||||
const complexName = 'CON:文件'.repeat(50) + '😀'.repeat(20);
|
||||
const result = makeSafeFilename(complexName);
|
||||
|
||||
// Should not contain unsafe characters
|
||||
expect(result).not.toContain(':');
|
||||
|
||||
// Should be within byte limit
|
||||
const byteLength = new TextEncoder().encode(result).length;
|
||||
expect(byteLength).toBeLessThanOrEqual(250);
|
||||
|
||||
// Should be valid UTF-8
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
const decoded = new TextDecoder().decode(encoded);
|
||||
expect(decoded).toBe(result);
|
||||
expect(decoded).not.toContain('�');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world scenarios', () => {
|
||||
it('should handle typical book title with author', () => {
|
||||
const bookTitle = 'The Great Gatsby - F. Scott Fitzgerald.epub';
|
||||
expect(makeSafeFilename(bookTitle)).toBe('The Great Gatsby - F. Scott Fitzgerald.epub');
|
||||
});
|
||||
|
||||
it('should handle Chinese book with long title', () => {
|
||||
const chineseBook = '红楼梦:中国古典文学四大名著之一(全120回完整版).epub';
|
||||
const result = makeSafeFilename(chineseBook);
|
||||
expect(result).toBe(chineseBook); // Should fit within 250 bytes
|
||||
});
|
||||
|
||||
it('should handle Japanese light novel title', () => {
|
||||
const japaneseTitle = 'ソードアート・オンライン:アリシゼーション編.epub';
|
||||
expect(makeSafeFilename(japaneseTitle)).toBe(japaneseTitle);
|
||||
});
|
||||
|
||||
it('should handle filename with unsafe characters and emoji', () => {
|
||||
const unsafeEmoji = '📚 Book: "Title" <Part 1>.epub';
|
||||
const result = makeSafeFilename(unsafeEmoji);
|
||||
expect(result).toBe('📚 Book_ _Title_ _Part 1_.epub');
|
||||
});
|
||||
|
||||
it('should handle Windows-style path in filename', () => {
|
||||
const windowsPath = 'C:\\Users\\Documents\\book.pdf';
|
||||
const result = makeSafeFilename(windowsPath);
|
||||
expect(result).toBe('C__Users_Documents_book.pdf');
|
||||
});
|
||||
|
||||
it('should handle URL in filename', () => {
|
||||
const url = 'https://example.com/book.pdf';
|
||||
const result = makeSafeFilename(url);
|
||||
expect(result).toBe('https___example.com_book.pdf');
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ describe.concurrent('suite', () => {
|
||||
beforeAll(async () => {
|
||||
const wasmPath = join(process.cwd(), 'public/vendor/simplecc/simplecc_wasm_bg.wasm');
|
||||
const wasmBuffer = await readFile(wasmPath);
|
||||
await init(wasmBuffer);
|
||||
await init({ module_or_path: wasmBuffer });
|
||||
});
|
||||
|
||||
test('basic s2t and t2s', () => {
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { getWordCount } from '../../utils/word';
|
||||
|
||||
describe('Word Limit Feature', () => {
|
||||
describe('getWordCount', () => {
|
||||
it('should count single word correctly', () => {
|
||||
expect(getWordCount('hello')).toBe(1);
|
||||
});
|
||||
|
||||
it('should count multiple words correctly', () => {
|
||||
expect(getWordCount('hello world')).toBe(2);
|
||||
expect(getWordCount('the quick brown fox')).toBe(4);
|
||||
});
|
||||
|
||||
it('should handle multiple spaces between words', () => {
|
||||
expect(getWordCount('hello world')).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle leading and trailing spaces', () => {
|
||||
expect(getWordCount(' hello world ')).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle newlines and tabs', () => {
|
||||
expect(getWordCount('hello\nworld')).toBe(2);
|
||||
expect(getWordCount('hello\tworld')).toBe(2);
|
||||
expect(getWordCount('hello\n\t world')).toBe(2);
|
||||
});
|
||||
|
||||
it('should return 0 for empty string', () => {
|
||||
expect(getWordCount('')).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 for whitespace only', () => {
|
||||
expect(getWordCount(' ')).toBe(0);
|
||||
expect(getWordCount('\n\t ')).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle punctuation as part of words', () => {
|
||||
expect(getWordCount("don't")).toBe(1);
|
||||
expect(getWordCount('hello, world!')).toBe(2);
|
||||
});
|
||||
|
||||
it('should count exactly 30 words', () => {
|
||||
const thirtyWords = Array(30).fill('word').join(' ');
|
||||
expect(getWordCount(thirtyWords)).toBe(30);
|
||||
});
|
||||
|
||||
it('should count more than 30 words', () => {
|
||||
const thirtyOneWords = Array(31).fill('word').join(' ');
|
||||
expect(getWordCount(thirtyOneWords)).toBe(31);
|
||||
});
|
||||
|
||||
it('should handle unicode characters', () => {
|
||||
expect(getWordCount('你好 世界')).toBe(2);
|
||||
expect(getWordCount('café résumé')).toBe(2);
|
||||
expect(getWordCount('🎉 hello 🎊 world')).toBe(4);
|
||||
});
|
||||
|
||||
it('should handle numbers as words', () => {
|
||||
expect(getWordCount('1 2 3 4 5')).toBe(5);
|
||||
expect(getWordCount('chapter 1 section 2')).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle very long words', () => {
|
||||
const longWord = 'a'.repeat(1000);
|
||||
expect(getWordCount(longWord)).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle mixed content with newlines', () => {
|
||||
const text = `Line one with words.
|
||||
Line two with more words.
|
||||
Line three.`;
|
||||
// "Line one with words." = 4, "Line two with more words." = 5, "Line three." = 2 = 11 total
|
||||
expect(getWordCount(text)).toBe(11);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Case Sensitivity Matching', () => {
|
||||
// Helper function to simulate matching logic
|
||||
const matchText = (text: string, pattern: string, caseSensitive: boolean): boolean => {
|
||||
if (caseSensitive) {
|
||||
return text === pattern;
|
||||
}
|
||||
return text.toLowerCase() === pattern.toLowerCase();
|
||||
};
|
||||
|
||||
describe('Case-Sensitive Mode', () => {
|
||||
it('should match exact case only', () => {
|
||||
expect(matchText('Where', 'Where', true)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not match different case', () => {
|
||||
expect(matchText('where', 'Where', true)).toBe(false);
|
||||
expect(matchText('WHERE', 'Where', true)).toBe(false);
|
||||
expect(matchText('wHeRe', 'Where', true)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle all uppercase pattern', () => {
|
||||
expect(matchText('HELLO', 'HELLO', true)).toBe(true);
|
||||
expect(matchText('hello', 'HELLO', true)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle all lowercase pattern', () => {
|
||||
expect(matchText('hello', 'hello', true)).toBe(true);
|
||||
expect(matchText('Hello', 'hello', true)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Case-Insensitive Mode', () => {
|
||||
it('should match same case', () => {
|
||||
expect(matchText('where', 'where', false)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match different cases', () => {
|
||||
expect(matchText('where', 'Where', false)).toBe(true);
|
||||
expect(matchText('Where', 'where', false)).toBe(true);
|
||||
expect(matchText('WHERE', 'where', false)).toBe(true);
|
||||
expect(matchText('wHeRe', 'where', false)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match title case to lowercase', () => {
|
||||
expect(matchText('The', 'the', false)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match with mixed input', () => {
|
||||
expect(matchText('HeLLo', 'HELLO', false)).toBe(true);
|
||||
expect(matchText('HeLLo', 'hello', false)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world examples', () => {
|
||||
it('should handle "the" in different cases', () => {
|
||||
const pattern = 'the';
|
||||
|
||||
// Case-insensitive (default behavior)
|
||||
expect(matchText('The', pattern, false)).toBe(true);
|
||||
expect(matchText('the', pattern, false)).toBe(true);
|
||||
expect(matchText('THE', pattern, false)).toBe(true);
|
||||
|
||||
// Case-sensitive
|
||||
expect(matchText('The', pattern, true)).toBe(false);
|
||||
expect(matchText('the', pattern, true)).toBe(true);
|
||||
expect(matchText('THE', pattern, true)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle proper nouns correctly when case-sensitive', () => {
|
||||
const pattern = 'John';
|
||||
|
||||
// Case-sensitive - only exact match
|
||||
expect(matchText('John', pattern, true)).toBe(true);
|
||||
expect(matchText('john', pattern, true)).toBe(false);
|
||||
expect(matchText('JOHN', pattern, true)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { READEST_OPDS_USER_AGENT } from '@/services/constants';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
@@ -33,9 +34,9 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
console.log(`[OPDS Proxy] ${method}: ${url}`);
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 10000);
|
||||
const timeout = setTimeout(() => controller.abort(), 20000);
|
||||
const headers: HeadersInit = {
|
||||
'User-Agent': 'Readest/1.0 (OPDS Browser)',
|
||||
'User-Agent': READEST_OPDS_USER_AGENT,
|
||||
Accept: 'application/atom+xml, application/xml, text/xml, application/json, */*',
|
||||
};
|
||||
|
||||
@@ -54,7 +55,6 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
if (!response.ok) {
|
||||
console.error(`[OPDS Proxy] HTTP ${response.status} for ${url}`);
|
||||
if (method === 'HEAD') {
|
||||
console.log(`[OPDS Proxy] Response headers:`, response.headers);
|
||||
if (response.status === 401) {
|
||||
return new NextResponse(null, {
|
||||
status: 403,
|
||||
@@ -83,7 +83,16 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
},
|
||||
});
|
||||
}
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
return new NextResponse(data, {
|
||||
status: response.status,
|
||||
headers: {
|
||||
...Object.fromEntries(response.headers.entries()),
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('Content-Type') || 'text/xml';
|
||||
@@ -94,6 +103,7 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
return new NextResponse(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
...Object.fromEntries(response.headers.entries()),
|
||||
'Content-Type': contentType,
|
||||
'Content-Length': contentLength || '',
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
@@ -104,28 +114,33 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
});
|
||||
}
|
||||
|
||||
if (stream === 'true' || (contentLength && parseInt(contentLength) > 1024 * 1024)) {
|
||||
if (stream === 'true' && contentLength && parseInt(contentLength) > 1024 * 1024) {
|
||||
console.log(`[OPDS Proxy] Streaming: ${url}`);
|
||||
|
||||
return new NextResponse(response.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
...Object.fromEntries(response.headers.entries()),
|
||||
'Content-Type': contentType,
|
||||
'X-Content-Length': contentLength || '',
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Access-Control-Expose-Headers': 'X-Content-Length',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const data = await response.text();
|
||||
console.log(`[OPDS Proxy] Success: ${url} (${data.length} bytes)`);
|
||||
const buf = await response.arrayBuffer();
|
||||
const length = buf.byteLength;
|
||||
console.log(`[OPDS Proxy] Success: ${url} (${length} bytes)`);
|
||||
|
||||
return new NextResponse(data, {
|
||||
return new NextResponse(buf, {
|
||||
status: 200,
|
||||
headers: {
|
||||
...Object.fromEntries(response.headers.entries()),
|
||||
'Content-Type': contentType,
|
||||
'Content-Length': length.toString(),
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { EdgeSpeechTTS, EdgeTTSPayload } from '@/libs/edgeTTS';
|
||||
import { validateUserAndToken } from '@/utils/access';
|
||||
|
||||
const getLangFromVoice = (voiceId: string): string => {
|
||||
const match = voiceId.match(/^([a-z]{2}-[A-Z]{2})/);
|
||||
return match ? match[1]! : 'en-US';
|
||||
};
|
||||
|
||||
const isValidVoice = (voiceId: string): boolean => {
|
||||
return EdgeSpeechTTS.voices.some((v) => v.id === voiceId);
|
||||
};
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const { user, token } = await validateUserAndToken(request.headers.get('authorization'));
|
||||
if (!user || !token) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { input: text, voice, speed = 1.0 } = body;
|
||||
let { rate, lang } = body;
|
||||
|
||||
if (!text || typeof text !== 'string') {
|
||||
return NextResponse.json(
|
||||
{ error: { message: 'Missing or invalid "input" field', type: 'invalid_request_error' } },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
if (!voice || typeof voice !== 'string') {
|
||||
return NextResponse.json(
|
||||
{ error: { message: 'Missing or invalid "voice" field', type: 'invalid_request_error' } },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
if (!isValidVoice(voice)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
message: `Invalid voice "${voice}". Use GET /api/tts/edge to list available voices.`,
|
||||
type: 'invalid_request_error',
|
||||
},
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
lang = lang || getLangFromVoice(voice);
|
||||
|
||||
// Calculate rate (OpenAI speed ranges from 0.25 to 4.0, Edge TTS rate is 0.5 to 2.0)
|
||||
const clampedSpeed = Math.max(0.25, Math.min(4.0, speed));
|
||||
let mappedSpeed: number;
|
||||
if (clampedSpeed <= 1.0) {
|
||||
mappedSpeed = 0.5 + ((clampedSpeed - 0.25) / (1.0 - 0.25)) * (1.0 - 0.5);
|
||||
} else {
|
||||
mappedSpeed = 1.0 + ((clampedSpeed - 1.0) / (4.0 - 1.0)) * (2.0 - 1.0);
|
||||
}
|
||||
rate = rate || mappedSpeed;
|
||||
|
||||
const payload: EdgeTTSPayload = {
|
||||
lang,
|
||||
text,
|
||||
voice,
|
||||
rate,
|
||||
pitch: 1.0,
|
||||
};
|
||||
|
||||
const tts = new EdgeSpeechTTS();
|
||||
const response = await tts.create(payload);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
|
||||
return new NextResponse(arrayBuffer, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'audio/mpeg',
|
||||
'Content-Length': arrayBuffer.byteLength.toString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Edge TTS API error:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
message: error instanceof Error ? error.message : 'Internal server error',
|
||||
type: 'internal_error',
|
||||
},
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { user, token } = await validateUserAndToken(request.headers.get('authorization'));
|
||||
if (!user || !token) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const query = request.nextUrl.searchParams;
|
||||
const lang = query.get('lang') || '';
|
||||
let voices = EdgeSpeechTTS.voices;
|
||||
if (lang) {
|
||||
voices = voices.filter((v) => v.lang.toLowerCase().includes(lang.toLowerCase()));
|
||||
}
|
||||
|
||||
const formattedVoices = voices.map((voice) => ({
|
||||
id: voice.id,
|
||||
name: voice.name,
|
||||
language: voice.lang,
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
voices: formattedVoices,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error listing voices:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
message: 'Failed to list voices',
|
||||
type: 'internal_error',
|
||||
},
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@ import { useRouter } from 'next/navigation';
|
||||
import { Auth } from '@supabase/auth-ui-react';
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared';
|
||||
import { FcGoogle } from 'react-icons/fc';
|
||||
import { FaApple } from 'react-icons/fa';
|
||||
import { FaGithub } from 'react-icons/fa';
|
||||
import { FaApple, FaGithub, FaDiscord } from 'react-icons/fa';
|
||||
import { IoArrowBack } from 'react-icons/io5';
|
||||
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
@@ -29,7 +28,7 @@ import { getAppleIdAuth, Scope } from './utils/appleIdAuth';
|
||||
import { authWithCustomTab, authWithSafari } from './utils/nativeAuth';
|
||||
import WindowButtons from '@/components/WindowButtons';
|
||||
|
||||
type OAuthProvider = 'google' | 'apple' | 'azure' | 'github';
|
||||
type OAuthProvider = 'google' | 'apple' | 'azure' | 'github' | 'discord';
|
||||
|
||||
interface SingleInstancePayload {
|
||||
args: string[];
|
||||
@@ -390,7 +389,7 @@ export default function AuthPage() {
|
||||
provider='google'
|
||||
handleSignIn={tauriSignIn}
|
||||
Icon={FcGoogle}
|
||||
label={_('Sign in with Google')}
|
||||
label={_('Sign in with {{provider}}', { provider: 'Google' })}
|
||||
/>
|
||||
<ProviderLogin
|
||||
provider='apple'
|
||||
@@ -398,13 +397,19 @@ export default function AuthPage() {
|
||||
appService?.isIOSApp || USE_APPLE_SIGN_IN ? tauriSignInApple : tauriSignIn
|
||||
}
|
||||
Icon={FaApple}
|
||||
label={_('Sign in with Apple')}
|
||||
label={_('Sign in with {{provider}}', { provider: 'Apple' })}
|
||||
/>
|
||||
<ProviderLogin
|
||||
provider='github'
|
||||
handleSignIn={tauriSignIn}
|
||||
Icon={FaGithub}
|
||||
label={_('Sign in with GitHub')}
|
||||
label={_('Sign in with {{provider}}', { provider: 'GitHub' })}
|
||||
/>
|
||||
<ProviderLogin
|
||||
provider='discord'
|
||||
handleSignIn={tauriSignIn}
|
||||
Icon={FaDiscord}
|
||||
label={_('Sign in with {{provider}}', { provider: 'Discord' })}
|
||||
/>
|
||||
<hr className='border-base-300 my-3 mt-6 w-64 border-t' />
|
||||
<div className='w-full'>
|
||||
@@ -434,7 +439,7 @@ export default function AuthPage() {
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
theme={isDarkMode ? 'dark' : 'light'}
|
||||
magicLink={true}
|
||||
providers={['google', 'apple', 'github']}
|
||||
providers={['google', 'apple', 'github', 'discord']}
|
||||
redirectTo={getWebRedirectTo()}
|
||||
localization={getAuthLocalization()}
|
||||
/>
|
||||
|
||||
@@ -41,7 +41,10 @@ export const viewport = {
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang='en'>
|
||||
<html
|
||||
lang='en'
|
||||
className={process.env['NEXT_PUBLIC_APP_PLATFORM'] === 'tauri' ? 'edge-to-edge' : ''}
|
||||
>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<meta
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
|
||||
import { navigateToLogin } from '@/utils/nav';
|
||||
import { formatAuthors } from '@/utils/book';
|
||||
import { formatAuthors, formatDescription } from '@/utils/book';
|
||||
import ReadingProgress from './ReadingProgress';
|
||||
import BookCover from '@/components/BookCover';
|
||||
|
||||
@@ -26,7 +26,7 @@ interface BookItemProps {
|
||||
bookSelected: boolean;
|
||||
transferProgress: number | null;
|
||||
handleBookUpload: (book: Book) => void;
|
||||
handleBookDownload: (book: Book) => void;
|
||||
handleBookDownload: (book: Book, options?: { redownload?: boolean; queued?: boolean }) => void;
|
||||
showBookDetailsModal: (book: Book) => void;
|
||||
}
|
||||
|
||||
@@ -61,13 +61,19 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'relative flex aspect-[28/41] justify-center',
|
||||
'bookitem-main relative flex aspect-[28/41] justify-center rounded',
|
||||
coverFit === 'crop' && 'overflow-hidden shadow-md',
|
||||
mode === 'grid' && 'items-end',
|
||||
mode === 'list' && 'min-w-20 items-center',
|
||||
)}
|
||||
>
|
||||
<BookCover mode={mode} book={book} coverFit={coverFit} showSpine={false} />
|
||||
<BookCover
|
||||
mode={mode}
|
||||
book={book}
|
||||
coverFit={coverFit}
|
||||
showSpine={false}
|
||||
imageClassName='rounded shadow-md'
|
||||
/>
|
||||
{bookSelected && (
|
||||
<div className='absolute inset-0 bg-black opacity-30 transition-opacity duration-300'></div>
|
||||
)}
|
||||
@@ -85,7 +91,7 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
className={clsx(
|
||||
'flex w-full flex-col p-0',
|
||||
mode === 'grid' && 'pt-2',
|
||||
mode === 'list' && 'py-2',
|
||||
mode === 'list' && 'gap-2 py-0',
|
||||
)}
|
||||
>
|
||||
<div className={clsx('min-w-0 flex-1', mode === 'list' && 'flex flex-col gap-2')}>
|
||||
@@ -104,6 +110,11 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{mode === 'list' && (
|
||||
<h4 className='text-neutral-content line-clamp-1 text-sm'>
|
||||
{formatDescription(book.metadata?.description)}
|
||||
</h4>
|
||||
)}
|
||||
<div
|
||||
className={clsx('flex items-center', book.progress ? 'justify-between' : 'justify-end')}
|
||||
style={{
|
||||
@@ -154,7 +165,7 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
if (!book.uploadedAt) {
|
||||
handleBookUpload(book);
|
||||
} else if (!book.downloadedAt) {
|
||||
handleBookDownload(book);
|
||||
handleBookDownload(book, { queued: true });
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -30,7 +30,10 @@ interface BookshelfProps {
|
||||
isSelectAll: boolean;
|
||||
isSelectNone: boolean;
|
||||
handleImportBooks: () => void;
|
||||
handleBookDownload: (book: Book) => Promise<boolean>;
|
||||
handleBookDownload: (
|
||||
book: Book,
|
||||
options?: { redownload?: boolean; queued?: boolean },
|
||||
) => Promise<boolean>;
|
||||
handleBookUpload: (book: Book, syncBooks?: boolean) => Promise<boolean>;
|
||||
handleBookDelete: (book: Book, syncBooks?: boolean) => Promise<boolean>;
|
||||
handleSetSelectMode: (selectMode: boolean) => void;
|
||||
@@ -74,6 +77,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
const [showGroupingModal, setShowGroupingModal] = useState(false);
|
||||
const [importBookUrl] = useState(searchParams?.get('url') || '');
|
||||
|
||||
const abortDeletionRef = useRef(false);
|
||||
const isImportingBook = useRef(false);
|
||||
const iconSize15 = useResponsiveSize(15);
|
||||
const autofocusRef = useAutoFocus<HTMLDivElement>();
|
||||
@@ -216,9 +220,13 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
|
||||
const confirmDelete = async () => {
|
||||
const books = getBooksToDelete();
|
||||
const concurrency = 4;
|
||||
const concurrency = 20;
|
||||
|
||||
for (let i = 0; i < books.length; i += concurrency) {
|
||||
if (abortDeletionRef.current) {
|
||||
abortDeletionRef.current = false;
|
||||
break;
|
||||
}
|
||||
const batch = books.slice(i, i + concurrency);
|
||||
await Promise.all(batch.map((book) => handleBookDelete(book, false)));
|
||||
}
|
||||
@@ -304,6 +312,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
}
|
||||
setLoading={setLoading}
|
||||
toggleSelection={toggleSelection}
|
||||
handleGroupBooks={groupSelectedBooks}
|
||||
handleBookUpload={handleBookUpload}
|
||||
handleBookDownload={handleBookDownload}
|
||||
handleBookDelete={handleBookDelete}
|
||||
@@ -329,7 +338,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
<button
|
||||
aria-label={_('Import Books')}
|
||||
className={clsx(
|
||||
'border-1 bg-base-100 hover:bg-base-300/50',
|
||||
'bookitem-main bg-base-100 hover:bg-base-300/50',
|
||||
'flex items-center justify-center',
|
||||
'aspect-[28/41] w-full',
|
||||
)}
|
||||
@@ -377,7 +386,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
)}
|
||||
{showDeleteAlert && (
|
||||
<div
|
||||
className={clsx('fixed bottom-0 left-0 right-0 z-50 flex justify-center')}
|
||||
className={clsx('delete-alert fixed bottom-0 left-0 right-0 z-50 flex justify-center')}
|
||||
style={{
|
||||
paddingBottom: `${(safeAreaInsets?.bottom || 0) + 16}px`,
|
||||
}}
|
||||
@@ -388,6 +397,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
count: getBooksToDelete().length,
|
||||
})}
|
||||
onCancel={() => {
|
||||
abortDeletionRef.current = true;
|
||||
setShowDeleteAlert(false);
|
||||
setShowSelectModeActions(true);
|
||||
}}
|
||||
|
||||
@@ -81,7 +81,11 @@ interface BookshelfItemProps {
|
||||
transferProgress: number | null;
|
||||
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
toggleSelection: (hash: string) => void;
|
||||
handleBookDownload: (book: Book) => Promise<boolean>;
|
||||
handleGroupBooks: () => void;
|
||||
handleBookDownload: (
|
||||
book: Book,
|
||||
options?: { redownload?: boolean; queued?: boolean },
|
||||
) => Promise<boolean>;
|
||||
handleBookUpload: (book: Book, syncBooks?: boolean) => Promise<boolean>;
|
||||
handleBookDelete: (book: Book, syncBooks?: boolean) => Promise<boolean>;
|
||||
handleSetSelectMode: (selectMode: boolean) => void;
|
||||
@@ -97,6 +101,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
transferProgress,
|
||||
setLoading,
|
||||
toggleSelection,
|
||||
handleGroupBooks,
|
||||
handleBookUpload,
|
||||
handleBookDownload,
|
||||
handleSetSelectMode,
|
||||
@@ -110,9 +115,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
const { updateBook } = useLibraryStore();
|
||||
|
||||
const showBookDetailsModal = useCallback(async (book: Book) => {
|
||||
if (await makeBookAvailable(book)) {
|
||||
handleShowDetailsBook(book);
|
||||
}
|
||||
handleShowDetailsBook(book);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -129,7 +132,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
let available = false;
|
||||
const loadingTimeout = setTimeout(() => setLoading(true), 200);
|
||||
try {
|
||||
available = await handleBookDownload(book);
|
||||
available = await handleBookDownload(book, { queued: false });
|
||||
await updateBook(envConfig, book);
|
||||
} finally {
|
||||
if (loadingTimeout) clearTimeout(loadingTimeout);
|
||||
@@ -188,6 +191,16 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
toggleSelection(book.hash);
|
||||
},
|
||||
});
|
||||
const groupBooksMenuItem = await MenuItem.new({
|
||||
text: _('Group Books'),
|
||||
action: async () => {
|
||||
if (!isSelectMode) handleSetSelectMode(true);
|
||||
if (!itemSelected) {
|
||||
toggleSelection(book.hash);
|
||||
}
|
||||
handleGroupBooks();
|
||||
},
|
||||
});
|
||||
const showBookInFinderMenuItem = await MenuItem.new({
|
||||
text: _(fileRevealLabel),
|
||||
action: async () => {
|
||||
@@ -204,7 +217,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
const downloadBookMenuItem = await MenuItem.new({
|
||||
text: _('Download Book'),
|
||||
action: async () => {
|
||||
handleBookDownload(book);
|
||||
handleBookDownload(book, { queued: true });
|
||||
},
|
||||
});
|
||||
const uploadBookMenuItem = await MenuItem.new({
|
||||
@@ -221,6 +234,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
});
|
||||
const menu = await Menu.new();
|
||||
menu.append(selectBookMenuItem);
|
||||
menu.append(groupBooksMenuItem);
|
||||
menu.append(showBookDetailsMenuItem);
|
||||
menu.append(showBookInFinderMenuItem);
|
||||
if (book.uploadedAt && !book.downloadedAt) {
|
||||
@@ -242,6 +256,16 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
toggleSelection(group.id);
|
||||
},
|
||||
});
|
||||
const groupBooksMenuItem = await MenuItem.new({
|
||||
text: _('Group Books'),
|
||||
action: async () => {
|
||||
if (!isSelectMode) handleSetSelectMode(true);
|
||||
if (!itemSelected) {
|
||||
toggleSelection(group.id);
|
||||
}
|
||||
handleGroupBooks();
|
||||
},
|
||||
});
|
||||
const deleteGroupMenuItem = await MenuItem.new({
|
||||
text: _('Delete'),
|
||||
action: async () => {
|
||||
@@ -250,6 +274,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
});
|
||||
const menu = await Menu.new();
|
||||
menu.append(selectGroupMenuItem);
|
||||
menu.append(groupBooksMenuItem);
|
||||
menu.append(deleteGroupMenuItem);
|
||||
menu.popup();
|
||||
};
|
||||
@@ -336,7 +361,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
|
||||
'sm:hover:bg-base-300/50 flex h-full flex-col px-0 py-2 sm:px-4 sm:py-4',
|
||||
mode === 'list' && 'border-base-300 flex flex-col border-b py-2',
|
||||
appService?.isMobileApp && 'no-context-menu',
|
||||
pressing && mode === 'grid' ? 'scale-95' : 'scale-100',
|
||||
pressing && mode === 'grid' ? 'not-eink:scale-95' : 'scale-100',
|
||||
)}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
|
||||
@@ -99,7 +99,7 @@ const GroupItem: React.FC<GroupItemProps> = ({ mode, group, isSelectMode, groupS
|
||||
<div className={clsx('group-item', appService?.hasContextMenu ? 'cursor-pointer' : '')}>
|
||||
<div
|
||||
className={clsx(
|
||||
'relative flex overflow-hidden',
|
||||
'groupitem-main relative flex overflow-hidden rounded',
|
||||
mode === 'grid' && 'bg-base-100 aspect-[28/41] items-center justify-center shadow-md',
|
||||
mode === 'list' && 'items-center justify-start gap-4 py-2',
|
||||
)}
|
||||
@@ -139,7 +139,7 @@ const GroupItem: React.FC<GroupItemProps> = ({ mode, group, isSelectMode, groupS
|
||||
mode === 'list' && 'flex-shrink-0',
|
||||
)}
|
||||
>
|
||||
<BookCover book={book} isPreview />
|
||||
<BookCover book={book} isPreview imageClassName='rounded-[2px]' />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { MdCheck, MdChevronRight } from 'react-icons/md';
|
||||
import { MdCheck, MdChevronRight, MdEdit } from 'react-icons/md';
|
||||
import { HiOutlineFolder, HiOutlineFolderAdd, HiOutlineFolderRemove } from 'react-icons/hi';
|
||||
import { IoMdArrowBack } from 'react-icons/io';
|
||||
|
||||
@@ -31,14 +31,23 @@ const GroupingModal: React.FC<GroupingModalProps> = ({
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { setLibrary, addGroup, getGroups, getGroupsByParent, getParentPath, refreshGroups } =
|
||||
useLibraryStore();
|
||||
const {
|
||||
setLibrary,
|
||||
addGroup,
|
||||
getGroups,
|
||||
getGroupId,
|
||||
getGroupsByParent,
|
||||
getParentPath,
|
||||
refreshGroups,
|
||||
} = useLibraryStore();
|
||||
|
||||
const [currentPath, setCurrentPath] = useState<string | undefined>(undefined);
|
||||
const [showInput, setShowInput] = useState(false);
|
||||
const [editGroupName, setEditGroupName] = useState('');
|
||||
const [selectedGroup, setSelectedGroup] = useState<BookGroupType | null>(null);
|
||||
const [newGroup, setNewGroup] = useState<BookGroupType | null>(null);
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const [originalGroupName, setOriginalGroupName] = useState<string | null>(null);
|
||||
|
||||
const divRef = useKeyDownActions({ onCancel, onConfirm });
|
||||
const editorRef = useRef<HTMLInputElement>(null);
|
||||
@@ -59,6 +68,11 @@ const GroupingModal: React.FC<GroupingModalProps> = ({
|
||||
.map((hash) => libraryBooks.find((book) => book.hash === hash)?.groupId)
|
||||
.some((group) => group && group !== BOOK_UNGROUPED_NAME);
|
||||
|
||||
const canRenameGroup = selectedBooks.length === 1 && selectedBooks.every((id) => !isMd5(id));
|
||||
const currentGroupForRename = canRenameGroup
|
||||
? allGroups.find((group) => group.id === selectedBooks[0])
|
||||
: null;
|
||||
|
||||
const generateNextUntitledGroupName = () => {
|
||||
const baseName = _('Untitled Group');
|
||||
const basePattern = parentGroupName
|
||||
@@ -86,6 +100,18 @@ const GroupingModal: React.FC<GroupingModalProps> = ({
|
||||
const nextName = generateNextUntitledGroupName();
|
||||
setEditGroupName(nextName);
|
||||
setShowInput(true);
|
||||
setIsRenaming(false);
|
||||
setOriginalGroupName(null);
|
||||
setSelectedGroup(null);
|
||||
};
|
||||
|
||||
const handleRenameGroup = () => {
|
||||
if (!currentGroupForRename) return;
|
||||
|
||||
setEditGroupName(currentGroupForRename.name);
|
||||
setOriginalGroupName(currentGroupForRename.name);
|
||||
setShowInput(true);
|
||||
setIsRenaming(true);
|
||||
};
|
||||
|
||||
const handleRemoveFromGroup = () => {
|
||||
@@ -112,17 +138,44 @@ const GroupingModal: React.FC<GroupingModalProps> = ({
|
||||
const handleConfirmCreateGroup = () => {
|
||||
let groupName = editGroupName.trim();
|
||||
if (groupName) {
|
||||
if (currentPath && !groupName.startsWith(currentPath + '/')) {
|
||||
groupName = `${currentPath}/${groupName}`;
|
||||
}
|
||||
if (isRenaming && originalGroupName) {
|
||||
// Renaming existing group
|
||||
const oldGroupName = originalGroupName;
|
||||
|
||||
const newGroup = addGroup(groupName);
|
||||
setNewGroup(newGroup);
|
||||
setSelectedGroup(newGroup);
|
||||
setShowInput(false);
|
||||
const parentGroup = getParentPath(groupName);
|
||||
if (parentGroup) {
|
||||
setCurrentPath(parentGroup);
|
||||
// Update the group name for all books in this group and nested groups
|
||||
libraryBooks.forEach((book) => {
|
||||
if (book.groupName === oldGroupName) {
|
||||
book.groupName = groupName;
|
||||
book.groupId = getGroupId(book.groupName);
|
||||
book.updatedAt = Date.now();
|
||||
} else if (book.groupName?.startsWith(oldGroupName + '/')) {
|
||||
book.groupName = book.groupName.replace(oldGroupName, groupName);
|
||||
book.groupId = getGroupId(book.groupName);
|
||||
book.updatedAt = Date.now();
|
||||
}
|
||||
});
|
||||
|
||||
setLibrary([...libraryBooks]);
|
||||
appService?.saveLibraryBooks(libraryBooks);
|
||||
|
||||
refreshGroups();
|
||||
setShowInput(false);
|
||||
setIsRenaming(false);
|
||||
setOriginalGroupName(null);
|
||||
} else {
|
||||
// Creating new group
|
||||
if (currentPath && !groupName.startsWith(currentPath + '/')) {
|
||||
groupName = `${currentPath}/${groupName}`;
|
||||
}
|
||||
|
||||
const newGroup = addGroup(groupName);
|
||||
setNewGroup(newGroup);
|
||||
setSelectedGroup(newGroup);
|
||||
setShowInput(false);
|
||||
const parentGroup = getParentPath(groupName);
|
||||
if (parentGroup) {
|
||||
setCurrentPath(parentGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -205,25 +258,32 @@ const GroupingModal: React.FC<GroupingModalProps> = ({
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className={clsx('mt-4 grid grid-cols-1 gap-2 text-base md:grid-cols-2')}>
|
||||
{isSelectedBooksHasGroup && (
|
||||
<button
|
||||
onClick={handleRemoveFromGroup}
|
||||
className='flex items-center space-x-2 p-2 text-blue-500'
|
||||
>
|
||||
<HiOutlineFolderRemove size={iconSize} />
|
||||
<span className='truncate'>{_('Remove From Group')}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleRemoveFromGroup}
|
||||
className='flex items-center space-x-2 p-2 text-blue-500 disabled:text-gray-400'
|
||||
disabled={!isSelectedBooksHasGroup}
|
||||
>
|
||||
<HiOutlineFolderRemove size={iconSize} />
|
||||
<span className='truncate'>{_('Remove From Group')}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCreateGroup}
|
||||
className='flex items-center space-x-2 p-2 text-blue-500'
|
||||
className='flex items-center space-x-2 p-2 text-blue-500 disabled:text-gray-400'
|
||||
>
|
||||
<HiOutlineFolderAdd size={iconSize} />
|
||||
<span className='truncate'>{_('Create New Group')}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleRenameGroup}
|
||||
className='flex items-center space-x-2 p-2 text-blue-500 disabled:text-gray-400'
|
||||
disabled={!canRenameGroup}
|
||||
>
|
||||
<MdEdit size={iconSize} />
|
||||
<span className='truncate'>{_('Rename Group')}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Create group input */}
|
||||
{/* Create/Rename group input */}
|
||||
{showInput && (
|
||||
<div className='mt-4 space-y-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
@@ -234,7 +294,11 @@ const GroupingModal: React.FC<GroupingModalProps> = ({
|
||||
onChange={(e) => setEditGroupName(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') handleConfirmCreateGroup();
|
||||
if (e.key === 'Escape') setShowInput(false);
|
||||
if (e.key === 'Escape') {
|
||||
setShowInput(false);
|
||||
setIsRenaming(false);
|
||||
setOriginalGroupName(null);
|
||||
}
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className='input input-ghost w-full border-0 px-2 text-base !outline-none sm:text-sm'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { IoFileTray } from 'react-icons/io5';
|
||||
import { MdRssFeed } from 'react-icons/md';
|
||||
@@ -9,20 +8,26 @@ import Menu from '@/components/Menu';
|
||||
|
||||
interface ImportMenuProps {
|
||||
setIsDropdownOpen?: (open: boolean) => void;
|
||||
onImportBooks: () => void;
|
||||
onImportBooksFromFiles: () => void;
|
||||
onImportBooksFromDirectory?: () => void;
|
||||
onOpenCatalogManager: () => void;
|
||||
}
|
||||
|
||||
const ImportMenu: React.FC<ImportMenuProps> = ({
|
||||
setIsDropdownOpen,
|
||||
onImportBooks,
|
||||
onImportBooksFromFiles,
|
||||
onImportBooksFromDirectory,
|
||||
onOpenCatalogManager,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
|
||||
const handleImportBooks = () => {
|
||||
onImportBooks();
|
||||
const handleImportFromFiles = () => {
|
||||
onImportBooksFromFiles();
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
const handleImportFromDirectory = () => {
|
||||
onImportBooksFromDirectory?.();
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
@@ -33,17 +38,21 @@ const ImportMenu: React.FC<ImportMenuProps> = ({
|
||||
|
||||
return (
|
||||
<Menu
|
||||
className={clsx(
|
||||
'dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow',
|
||||
appService?.isMobile ? 'no-triangle' : 'dropdown-center',
|
||||
)}
|
||||
className={clsx('dropdown-content bg-base-100 rounded-box z-[1] mt-3 p-2 shadow')}
|
||||
onCancel={() => setIsDropdownOpen?.(false)}
|
||||
>
|
||||
<MenuItem
|
||||
label={_('From Local File')}
|
||||
Icon={<IoFileTray className='h-5 w-5' />}
|
||||
onClick={handleImportBooks}
|
||||
onClick={handleImportFromFiles}
|
||||
/>
|
||||
{onImportBooksFromDirectory && (
|
||||
<MenuItem
|
||||
label={_('From Directory')}
|
||||
Icon={<IoFileTray className='h-5 w-5' />}
|
||||
onClick={handleImportFromDirectory}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
label={_('Online Library')}
|
||||
Icon={<MdRssFeed className='h-5 w-5' />}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTrafficLight } from '@/hooks/useTrafficLight';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
@@ -26,7 +25,8 @@ import ViewMenu from './ViewMenu';
|
||||
interface LibraryHeaderProps {
|
||||
isSelectMode: boolean;
|
||||
isSelectAll: boolean;
|
||||
onImportBooks: () => void;
|
||||
onImportBooksFromFiles: () => void;
|
||||
onImportBooksFromDirectory?: () => void;
|
||||
onOpenCatalogManager: () => void;
|
||||
onToggleSelectMode: () => void;
|
||||
onSelectAll: () => void;
|
||||
@@ -36,7 +36,8 @@ interface LibraryHeaderProps {
|
||||
const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
isSelectMode,
|
||||
isSelectAll,
|
||||
onImportBooks,
|
||||
onImportBooksFromFiles,
|
||||
onImportBooksFromDirectory,
|
||||
onOpenCatalogManager,
|
||||
onToggleSelectMode,
|
||||
onSelectAll,
|
||||
@@ -46,13 +47,11 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { appService } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
const { systemUIVisible, statusBarHeight } = useThemeStore();
|
||||
const { currentBookshelf } = useLibraryStore();
|
||||
const { isTrafficLightVisible } = useTrafficLight();
|
||||
const [searchQuery, setSearchQuery] = useState(searchParams?.get('q') ?? '');
|
||||
|
||||
const viewSettings = settings.globalViewSettings;
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const iconSize18 = useResponsiveSize(18);
|
||||
const { safeAreaInsets: insets } = useThemeStore();
|
||||
@@ -108,7 +107,7 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
<div className='flex w-full items-center justify-between space-x-6 sm:space-x-12'>
|
||||
<div className='exclude-title-bar-mousedown relative flex w-full items-center pl-4'>
|
||||
<div className='relative flex h-9 w-full items-center sm:h-7'>
|
||||
<span className='text-base-content/50 absolute left-3'>
|
||||
<span className='text-base-content/50 absolute ps-3'>
|
||||
<FaSearch className='h-4 w-4' />
|
||||
</span>
|
||||
<input
|
||||
@@ -124,10 +123,8 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
onChange={handleSearchChange}
|
||||
spellCheck='false'
|
||||
className={clsx(
|
||||
'input rounded-badge h-9 w-full pl-10 pr-[30%] sm:h-7',
|
||||
viewSettings?.isEink
|
||||
? 'border-1 border-base-content focus:border-base-content'
|
||||
: 'bg-base-300/45 border-none',
|
||||
'search-input input h-9 w-full rounded-full pr-[30%] ps-10 sm:h-7',
|
||||
'bg-base-300/45 border-0',
|
||||
'font-sans text-sm font-light',
|
||||
'placeholder:text-base-content/50 truncate',
|
||||
'focus:outline-none focus:ring-0',
|
||||
@@ -152,14 +149,14 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
<Dropdown
|
||||
label={_('Import Books')}
|
||||
className={clsx(
|
||||
'exclude-title-bar-mousedown dropdown-bottom flex h-6 cursor-pointer justify-center',
|
||||
isMobile ? 'dropdown-end' : 'dropdown-center',
|
||||
'exclude-title-bar-mousedown dropdown-bottom dropdown-center flex h-6 cursor-pointer justify-center',
|
||||
)}
|
||||
buttonClassName='p-0 h-6 min-h-6 w-6 flex items-center justify-center !bg-transparent'
|
||||
buttonClassName='p-0 h-6 min-h-6 w-6 flex touch-target items-center justify-center !bg-transparent'
|
||||
toggleButton={<PiPlus role='none' className='m-0.5 h-5 w-5' />}
|
||||
>
|
||||
<ImportMenu
|
||||
onImportBooks={onImportBooks}
|
||||
onImportBooksFromFiles={onImportBooksFromFiles}
|
||||
onImportBooksFromDirectory={onImportBooksFromDirectory}
|
||||
onOpenCatalogManager={onOpenCatalogManager}
|
||||
/>
|
||||
</Dropdown>
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
RiLoader2Line,
|
||||
} from 'react-icons/ri';
|
||||
import { documentDir, join } from '@tauri-apps/api/path';
|
||||
import { invoke, PermissionState } from '@tauri-apps/api/core';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
@@ -20,6 +19,7 @@ import { formatBytes } from '@/utils/book';
|
||||
import { getOSPlatform } from '@/utils/misc';
|
||||
import { getExternalSDCardPath } from '@/utils/bridge';
|
||||
import { FILE_REVEAL_LABELS, FILE_REVEAL_PLATFORMS } from '@/utils/os';
|
||||
import { requestStoragePermission } from '@/utils/permission';
|
||||
import Dialog from '@/components/Dialog';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
@@ -42,10 +42,6 @@ interface MigrationProgress {
|
||||
currentFile?: string;
|
||||
}
|
||||
|
||||
interface Permissions {
|
||||
manageStorage: PermissionState;
|
||||
}
|
||||
|
||||
export const MigrateDataWindow = () => {
|
||||
const _ = useTranslation();
|
||||
const { appService, envConfig } = useEnv();
|
||||
@@ -158,13 +154,7 @@ export const MigrateDataWindow = () => {
|
||||
setErrorMessage('');
|
||||
|
||||
if (!dir.includes('Android/data')) {
|
||||
let permission = await invoke<Permissions>('plugin:native-bridge|checkPermissions');
|
||||
if (permission.manageStorage !== 'granted') {
|
||||
permission = await invoke<Permissions>(
|
||||
'plugin:native-bridge|request_manage_storage_permission',
|
||||
);
|
||||
}
|
||||
if (permission.manageStorage !== 'granted') return;
|
||||
if (!(await requestStoragePermission())) return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { clsx } from 'clsx';
|
||||
import { CatalogManager } from '@/app/opds/CatelogManager';
|
||||
import { CatalogManager } from '@/app/opds/components/CatelogManager';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import Dialog from '@/components/Dialog';
|
||||
|
||||
@@ -17,12 +17,9 @@ export function CatalogDialog({ onClose }: CatalogDialogProps) {
|
||||
bgClassName={'sm:!bg-black/75'}
|
||||
boxClassName='sm:min-w-[520px] sm:w-3/4 sm:h-[85%] sm:!max-w-screen-sm'
|
||||
>
|
||||
<div className={clsx('bg-base-100 relative flex flex-col overflow-y-auto')}>
|
||||
<div className={clsx('bg-base-100 relative flex flex-col overflow-y-auto pb-4')}>
|
||||
<CatalogManager />
|
||||
</div>
|
||||
<form method='dialog' className='modal-backdrop'>
|
||||
<button onClick={onClose}>{_('Close')}</button>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ const SelectModeActions: React.FC<SelectModeActionsProps> = ({
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex items-center justify-center shadow-lg',
|
||||
'bg-base-300 text-base-content text-xs',
|
||||
'text-base-content flex items-center justify-center text-xs shadow-lg',
|
||||
'not-eink:bg-base-300 eink:bg-base-100 eink:border eink:border-base-content',
|
||||
'mx-auto w-fit space-x-6 rounded-lg p-4',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from 'next/navigation';
|
||||
import { PiUserCircle, PiUserCircleCheck, PiGear } from 'react-icons/pi';
|
||||
import { PiSun, PiMoon } from 'react-icons/pi';
|
||||
import { TbSunMoon } from 'react-icons/tb';
|
||||
import { MdCloudSync } from 'react-icons/md';
|
||||
|
||||
import { invoke, PermissionState } from '@tauri-apps/api/core';
|
||||
import { isTauriAppPlatform, isWebAppPlatform } from '@/services/environment';
|
||||
@@ -15,11 +16,13 @@ import { useQuotaStats } from '@/hooks/useQuotaStats';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { useTransferQueue } from '@/hooks/useTransferQueue';
|
||||
import { navigateToLogin, navigateToProfile } from '@/utils/nav';
|
||||
import { tauriHandleSetAlwaysOnTop, tauriHandleToggleFullScreen } from '@/utils/window';
|
||||
import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry';
|
||||
import { setAboutDialogVisible } from '@/components/AboutWindow';
|
||||
import { setMigrateDataDirDialogVisible } from '@/app/library/components/MigrateDataWindow';
|
||||
import { requestStoragePermission } from '@/utils/permission';
|
||||
import { saveSysSettings } from '@/helpers/settings';
|
||||
import { selectDirectory } from '@/utils/bridge';
|
||||
import UserAvatar from '@/components/UserAvatar';
|
||||
@@ -60,6 +63,13 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
);
|
||||
const iconSize = useResponsiveSize(16);
|
||||
|
||||
const { stats, hasActiveTransfers, setIsTransferQueueOpen } = useTransferQueue();
|
||||
|
||||
const openTransferQueue = () => {
|
||||
setIsTransferQueueOpen(true);
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
const showAboutReadest = () => {
|
||||
setAboutDialogVisible(true);
|
||||
setIsDropdownOpen?.(false);
|
||||
@@ -175,13 +185,7 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
};
|
||||
|
||||
const handleSetSavedBookCoverForLockScreen = async () => {
|
||||
let permission = await invoke<Permissions>('plugin:native-bridge|checkPermissions');
|
||||
if (permission.manageStorage !== 'granted') {
|
||||
permission = await invoke<Permissions>(
|
||||
'plugin:native-bridge|request_manage_storage_permission',
|
||||
);
|
||||
}
|
||||
if (permission.manageStorage !== 'granted' && appService?.distChannel === 'readest') return;
|
||||
if (!(await requestStoragePermission()) && appService?.distChannel === 'readest') return;
|
||||
|
||||
const newValue = settings.savedBookCoverForLockScreen ? '' : 'default';
|
||||
if (newValue) {
|
||||
@@ -228,7 +232,7 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
return (
|
||||
<Menu
|
||||
className={clsx(
|
||||
'settings-menu dropdown-content no-triangle border-base-100',
|
||||
'settings-menu dropdown-content no-triangle',
|
||||
'z-20 mt-2 max-w-[90vw] shadow-2xl',
|
||||
)}
|
||||
onCancel={() => setIsDropdownOpen?.(false)}
|
||||
@@ -260,6 +264,23 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
) : (
|
||||
<MenuItem label={_('Sign In')} Icon={PiUserCircle} onClick={handleUserLogin}></MenuItem>
|
||||
)}
|
||||
{user && (
|
||||
<MenuItem
|
||||
label={_('Cloud File Transfers')}
|
||||
Icon={MdCloudSync}
|
||||
description={
|
||||
hasActiveTransfers
|
||||
? _('{{activeCount}} active, {{pendingCount}} pending', {
|
||||
activeCount: stats.active,
|
||||
pendingCount: stats.pending,
|
||||
})
|
||||
: stats.failed > 0
|
||||
? _('{{failedCount}} failed', { failedCount: stats.failed })
|
||||
: ''
|
||||
}
|
||||
onClick={openTransferQueue}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
label={_('Auto Upload Books to Cloud')}
|
||||
toggled={isAutoUpload}
|
||||
@@ -334,7 +355,7 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
noIcon={!appService?.isAndroidApp}
|
||||
onClick={handleSetRootDir}
|
||||
/>
|
||||
{appService?.isAndroidApp && (
|
||||
{appService?.isAndroidApp && appService?.distChannel !== 'playstore' && (
|
||||
<MenuItem
|
||||
label={_('Save Book Cover')}
|
||||
tooltip={_('Auto-save last book cover')}
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
MdClose,
|
||||
MdRefresh,
|
||||
MdPause,
|
||||
MdPlayArrow,
|
||||
MdCloudUpload,
|
||||
MdCloudDownload,
|
||||
MdCheckCircle,
|
||||
MdError,
|
||||
MdCancel,
|
||||
MdDeleteSweep,
|
||||
} from 'react-icons/md';
|
||||
import { useTransferQueue } from '@/hooks/useTransferQueue';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { useKeyDownActions } from '@/hooks/useKeyDownActions';
|
||||
import { useLibraryStore } from '@/store/libraryStore';
|
||||
import { TransferItem, TransferStatus, useTransferStore } from '@/store/transferStore';
|
||||
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
const formatSpeed = (bytesPerSec: number): string => {
|
||||
return `${formatBytes(bytesPerSec)}/s`;
|
||||
};
|
||||
|
||||
const formatDateTime = (timestamp: number): string => {
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const isToday = date.toDateString() === now.toDateString();
|
||||
const yesterday = new Date(now);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const isYesterday = date.toDateString() === yesterday.toDateString();
|
||||
|
||||
const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
if (isToday) {
|
||||
return timeStr;
|
||||
} else if (isYesterday) {
|
||||
return `Yesterday ${timeStr}`;
|
||||
} else {
|
||||
return date.toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' + timeStr;
|
||||
}
|
||||
};
|
||||
|
||||
const StatusIcon: React.FC<{
|
||||
status: TransferStatus;
|
||||
type: 'upload' | 'download' | 'delete';
|
||||
size: number;
|
||||
}> = ({ status, type, size }) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return <MdCheckCircle className='text-success' size={size} />;
|
||||
case 'failed':
|
||||
return <MdError className='text-error' size={size} />;
|
||||
case 'cancelled':
|
||||
return <MdCancel className='text-warning' size={size} />;
|
||||
case 'in_progress':
|
||||
case 'pending':
|
||||
default:
|
||||
return type === 'upload' ? (
|
||||
<MdCloudUpload className='text-primary' size={size} />
|
||||
) : type === 'delete' ? (
|
||||
<MdDeleteSweep className='text-primary' size={size} />
|
||||
) : (
|
||||
<MdCloudDownload className='text-primary' size={size} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const TransferItemRow: React.FC<{
|
||||
transfer: TransferItem;
|
||||
onCancel: (id: string) => void;
|
||||
onRetry: (id: string) => void;
|
||||
iconSize: number;
|
||||
}> = ({ transfer, onCancel, onRetry, iconSize }) => {
|
||||
const _ = useTranslation();
|
||||
|
||||
const completedLabel = {
|
||||
upload: _('Uploaded'),
|
||||
download: _('Downloaded'),
|
||||
delete: _('Deleted'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='hover:bg-base-200 flex items-center gap-3 rounded-lg p-3'>
|
||||
<StatusIcon status={transfer.status} type={transfer.type} size={iconSize} />
|
||||
|
||||
<div className='min-w-0 flex-1'>
|
||||
<div className='truncate font-medium'>{transfer.bookTitle}</div>
|
||||
<div className='text-base-content/60 text-xs'>
|
||||
{transfer.status === 'in_progress' && (
|
||||
<>
|
||||
{Math.round(transfer.progress)}% - {formatSpeed(transfer.transferSpeed)}
|
||||
</>
|
||||
)}
|
||||
{transfer.status === 'pending' && transfer.error && (
|
||||
<span className='text-warning'>{transfer.error}</span>
|
||||
)}
|
||||
{transfer.status === 'pending' && !transfer.error && _('Waiting...')}
|
||||
{transfer.status === 'failed' && (
|
||||
<span className='text-error'>{transfer.error || _('Failed')}</span>
|
||||
)}
|
||||
{transfer.status === 'completed' && (completedLabel[transfer.type] || _('Completed'))}
|
||||
{transfer.status === 'cancelled' && _('Cancelled')}
|
||||
{' · '}
|
||||
{formatDateTime(transfer.completedAt || transfer.startedAt || transfer.createdAt)}
|
||||
</div>
|
||||
|
||||
{transfer.status === 'in_progress' && (
|
||||
<div className='bg-base-300 mt-1 h-1.5 w-full overflow-hidden rounded-full'>
|
||||
<div
|
||||
className='bg-primary h-full transition-all'
|
||||
style={{ width: `${transfer.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-1'>
|
||||
{(transfer.status === 'failed' || transfer.status === 'cancelled') && (
|
||||
<button
|
||||
onClick={() => onRetry(transfer.id)}
|
||||
className='btn btn-ghost btn-sm btn-circle'
|
||||
aria-label={_('Retry')}
|
||||
>
|
||||
<MdRefresh size={iconSize} />
|
||||
</button>
|
||||
)}
|
||||
{['pending', 'in_progress'].includes(transfer.status) && (
|
||||
<button
|
||||
onClick={() => onCancel(transfer.id)}
|
||||
className='btn btn-ghost btn-sm btn-circle'
|
||||
aria-label={_('Cancel')}
|
||||
>
|
||||
<MdClose size={iconSize} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type FilterType = 'all' | 'active' | 'completed' | 'failed';
|
||||
|
||||
const TransferQueuePanel: React.FC = () => {
|
||||
const _ = useTranslation();
|
||||
const iconSize = useResponsiveSize(18);
|
||||
const setIsOpen = useTransferStore((state) => state.setIsTransferQueueOpen);
|
||||
const getVisibleLibrary = useLibraryStore((state) => state.getVisibleLibrary);
|
||||
const {
|
||||
transfers,
|
||||
stats,
|
||||
isQueuePaused,
|
||||
cancelTransfer,
|
||||
retryTransfer,
|
||||
retryAllFailed,
|
||||
pauseQueue,
|
||||
resumeQueue,
|
||||
clearCompleted,
|
||||
clearFailed,
|
||||
queueUpload,
|
||||
queueDownload,
|
||||
} = useTransferQueue();
|
||||
|
||||
const [filter, setFilter] = useState<FilterType>('all');
|
||||
|
||||
const onClose = () => setIsOpen(false);
|
||||
const divRef = useKeyDownActions({ onCancel: onClose, onConfirm: onClose });
|
||||
|
||||
const booksToUpload = getVisibleLibrary().filter((book) => book.downloadedAt && !book.uploadedAt);
|
||||
const booksToDownload = getVisibleLibrary().filter(
|
||||
(book) => book.uploadedAt && !book.downloadedAt,
|
||||
);
|
||||
|
||||
const handleUploadAll = () => {
|
||||
booksToUpload.forEach((book) => queueUpload(book));
|
||||
};
|
||||
|
||||
const handleDownloadAll = () => {
|
||||
booksToDownload.forEach((book) => queueDownload(book));
|
||||
};
|
||||
|
||||
const filteredTransfers = transfers
|
||||
.filter((t) => {
|
||||
switch (filter) {
|
||||
case 'active':
|
||||
return ['pending', 'in_progress'].includes(t.status);
|
||||
case 'completed':
|
||||
return t.status === 'completed';
|
||||
case 'failed':
|
||||
return t.status === 'failed' || t.status === 'cancelled';
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Sort by status priority then by createdAt
|
||||
const statusOrder: Record<TransferStatus, number> = {
|
||||
in_progress: 0,
|
||||
pending: 1,
|
||||
failed: 2,
|
||||
cancelled: 3,
|
||||
completed: 4,
|
||||
};
|
||||
const statusDiff = statusOrder[a.status] - statusOrder[b.status];
|
||||
if (statusDiff !== 0) return statusDiff;
|
||||
return b.createdAt - a.createdAt;
|
||||
});
|
||||
|
||||
const filterLabels: Record<FilterType, string> = {
|
||||
all: _('All'),
|
||||
active: _('Active'),
|
||||
completed: _('Completed'),
|
||||
failed: _('Failed'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<div className='absolute inset-0 bg-black/50' onClick={onClose} />
|
||||
<div
|
||||
ref={divRef}
|
||||
className={clsx(
|
||||
'modal-box bg-base-100 relative flex max-h-[85%] min-h-[65%] w-[95%] flex-col rounded-2xl p-0 shadow-xl',
|
||||
'min-w-64 max-w-lg overflow-hidden',
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className='border-base-300 flex items-center justify-between border-b p-4'>
|
||||
<h2 className='text-lg font-semibold'>{_('Transfer Queue')}</h2>
|
||||
<div className='flex items-center gap-2'>
|
||||
{booksToUpload.length > 0 && (
|
||||
<button
|
||||
onClick={handleUploadAll}
|
||||
className='btn btn-ghost btn-sm gap-1'
|
||||
title={_('Upload All')}
|
||||
aria-label={_('Upload All')}
|
||||
>
|
||||
<MdCloudUpload size={iconSize} />
|
||||
<span className='text-xs'>{booksToUpload.length}</span>
|
||||
</button>
|
||||
)}
|
||||
{booksToDownload.length > 0 && (
|
||||
<button
|
||||
onClick={handleDownloadAll}
|
||||
className='btn btn-ghost btn-sm gap-1'
|
||||
title={_('Download All')}
|
||||
aria-label={_('Download All')}
|
||||
>
|
||||
<MdCloudDownload size={iconSize} />
|
||||
<span className='text-xs'>{booksToDownload.length}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={isQueuePaused ? resumeQueue : pauseQueue}
|
||||
className='btn btn-ghost btn-sm btn-circle'
|
||||
title={isQueuePaused ? _('Resume Transfers') : _('Pause Transfers')}
|
||||
aria-label={isQueuePaused ? _('Resume Transfers') : _('Pause Transfers')}
|
||||
>
|
||||
{isQueuePaused ? <MdPlayArrow size={iconSize} /> : <MdPause size={iconSize} />}
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className='btn btn-ghost btn-sm btn-circle'
|
||||
title={_('Close')}
|
||||
aria-label={_('Close')}
|
||||
>
|
||||
<MdClose size={iconSize} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats bar */}
|
||||
<div className='bg-base-200 flex items-center gap-4 px-4 py-2 text-sm'>
|
||||
<span>
|
||||
{_('Active')}: {stats.active}
|
||||
</span>
|
||||
<span>
|
||||
{_('Pending')}: {stats.pending}
|
||||
</span>
|
||||
<span>
|
||||
{_('Completed')}: {stats.completed}
|
||||
</span>
|
||||
<span>
|
||||
{_('Failed')}: {stats.failed}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Filter tabs */}
|
||||
<div className='border-base-300 flex gap-2 border-b p-4'>
|
||||
{(['all', 'active', 'completed', 'failed'] as const).map((f) => (
|
||||
<button
|
||||
key={f}
|
||||
onClick={() => setFilter(f)}
|
||||
className={clsx(
|
||||
'rounded-lg px-3 py-1 text-sm transition-colors',
|
||||
filter === f ? 'bg-primary text-primary-content' : 'bg-base-200 hover:bg-base-300',
|
||||
)}
|
||||
>
|
||||
{filterLabels[f]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Transfer list */}
|
||||
<div className='flex-1 overflow-y-auto p-2'>
|
||||
{filteredTransfers.length === 0 ? (
|
||||
<div className='text-base-content/60 py-8 text-center'>{_('No transfers')}</div>
|
||||
) : (
|
||||
filteredTransfers.map((transfer) => (
|
||||
<TransferItemRow
|
||||
key={transfer.id}
|
||||
transfer={transfer}
|
||||
onCancel={cancelTransfer}
|
||||
onRetry={retryTransfer}
|
||||
iconSize={iconSize}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer actions */}
|
||||
<div className='border-base-300 flex items-center justify-between border-t p-4'>
|
||||
<div className='flex items-center gap-2'>
|
||||
{stats.failed > 0 && (
|
||||
<button onClick={retryAllFailed} className='btn btn-ghost btn-sm gap-1'>
|
||||
<MdRefresh size={iconSize - 2} />
|
||||
{_('Retry All')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
{stats.completed > 0 && (
|
||||
<button onClick={clearCompleted} className='btn btn-ghost btn-sm gap-1'>
|
||||
<MdDeleteSweep size={iconSize - 2} />
|
||||
{_('Clear Completed')}
|
||||
</button>
|
||||
)}
|
||||
{stats.failed > 0 && (
|
||||
<button onClick={clearFailed} className='btn btn-ghost btn-sm gap-1'>
|
||||
<MdDeleteSweep size={iconSize - 2} />
|
||||
{_('Clear Failed')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransferQueuePanel;
|
||||
@@ -44,6 +44,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
{ label: _('Format'), value: 'format' },
|
||||
{ label: _('Date Read'), value: 'updated' },
|
||||
{ label: _('Date Added'), value: 'created' },
|
||||
{ label: _('Date Published'), value: 'published' },
|
||||
];
|
||||
|
||||
const sortingOptions = [
|
||||
@@ -99,7 +100,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
|
||||
return (
|
||||
<Menu
|
||||
className='view-menu dropdown-content no-triangle border-base-100 z-20 mt-2 shadow-2xl'
|
||||
className='view-menu dropdown-content no-triangle z-20 mt-2 shadow-2xl'
|
||||
onCancel={() => setIsDropdownOpen?.(false)}
|
||||
>
|
||||
{viewOptions.map((option) => (
|
||||
|
||||
@@ -11,7 +11,7 @@ export const useBooksSync = () => {
|
||||
const { user } = useAuth();
|
||||
const { appService } = useEnv();
|
||||
const { library, isSyncing, setLibrary, setIsSyncing, setSyncProgress } = useLibraryStore();
|
||||
const { syncedBooks, syncBooks, lastSyncedAtBooks } = useSync();
|
||||
const { useSyncInited, syncedBooks, syncBooks, lastSyncedAtBooks } = useSync();
|
||||
const isPullingRef = useRef(false);
|
||||
|
||||
const getNewBooks = useCallback(() => {
|
||||
@@ -64,8 +64,7 @@ export const useBooksSync = () => {
|
||||
return;
|
||||
}
|
||||
handleAutoSync();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [library, handleAutoSync]);
|
||||
}, [user, library, handleAutoSync]);
|
||||
|
||||
const pushLibrary = useCallback(async () => {
|
||||
if (!user) return;
|
||||
@@ -76,9 +75,9 @@ export const useBooksSync = () => {
|
||||
}, [user, syncBooks, getNewBooks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
if (!user || !useSyncInited) return;
|
||||
pullLibrary();
|
||||
}, [user, pullLibrary]);
|
||||
}, [user, useSyncInited, pullLibrary]);
|
||||
|
||||
const updateLibrary = async () => {
|
||||
if (isSyncing) return;
|
||||
@@ -107,13 +106,16 @@ export const useBooksSync = () => {
|
||||
return oldBook;
|
||||
};
|
||||
|
||||
const oldBooksBatchSize = 20;
|
||||
const oldBooksBatchSize = 100;
|
||||
for (let i = 0; i < oldBooksNeedsDownload.length; i += oldBooksBatchSize) {
|
||||
const batch = oldBooksNeedsDownload.slice(i, i + oldBooksBatchSize);
|
||||
await appService?.downloadBookCovers(batch);
|
||||
}
|
||||
|
||||
const updatedLibrary = await Promise.all(library.map(processOldBook));
|
||||
setLibrary(updatedLibrary);
|
||||
appService?.saveLibraryBooks(updatedLibrary);
|
||||
|
||||
const bookHashesInLibrary = new Set(updatedLibrary.map((book) => book.hash));
|
||||
const newBooks = syncedBooks.filter(
|
||||
(newBook) =>
|
||||
@@ -136,11 +138,12 @@ export const useBooksSync = () => {
|
||||
await appService?.downloadBookCovers(batch);
|
||||
await Promise.all(batch.map(processNewBook));
|
||||
const progress = Math.min((i + batchSize) / newBooks.length, 1);
|
||||
setLibrary([...updatedLibrary]);
|
||||
setSyncProgress(progress);
|
||||
setLibrary([...updatedLibrary]);
|
||||
appService?.saveLibraryBooks(updatedLibrary);
|
||||
}
|
||||
setLibrary(updatedLibrary);
|
||||
appService?.saveLibraryBooks(updatedLibrary);
|
||||
} catch (err) {
|
||||
console.error('Error updating new books:', err);
|
||||
} finally {
|
||||
if (newBooks.length > 0) {
|
||||
setIsSyncing(false);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user