mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-28 17:48:26 +01:00
Compare commits
3 Commits
autoskip
...
sync-subti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccdd7770c9 | ||
|
|
24cb679c0b | ||
|
|
af50b023ef |
75
.github/workflows/artifact-comment.yml
vendored
75
.github/workflows/artifact-comment.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 🔍 Get PR and Artifacts
|
- name: 🔍 Get PR and Artifacts
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
// Check if we're running from a fork (more precise detection)
|
// Check if we're running from a fork (more precise detection)
|
||||||
@@ -188,17 +188,6 @@ jobs:
|
|||||||
if (latestAppsRun) {
|
if (latestAppsRun) {
|
||||||
console.log(`Getting individual job statuses for run ${latestAppsRun.id} (status: ${latestAppsRun.status}, conclusion: ${latestAppsRun.conclusion || 'none'})`);
|
console.log(`Getting individual job statuses for run ${latestAppsRun.id} (status: ${latestAppsRun.status}, conclusion: ${latestAppsRun.conclusion || 'none'})`);
|
||||||
|
|
||||||
// Map job names to our build targets. Declared outside the try so
|
|
||||||
// the catch fallback can reuse the same keys.
|
|
||||||
const jobMappings = {
|
|
||||||
'Android Phone': ['🤖 Build Android APK (Phone)', 'build-android-phone'],
|
|
||||||
'Android TV': ['🤖 Build Android APK (TV)', 'build-android-tv'],
|
|
||||||
'iOS': ['🍎 Build iOS IPA (Phone)', 'build-ios-phone'],
|
|
||||||
'iOS Unsigned': ['🍎 Build iOS IPA (Phone - Unsigned)', 'build-ios-phone-unsigned'],
|
|
||||||
'tvOS': ['🍎 Build tvOS IPA', 'build-ios-tv'],
|
|
||||||
'tvOS Unsigned': ['🍎 Build tvOS IPA (Unsigned)', 'build-ios-tv-unsigned']
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get all jobs for this workflow run
|
// Get all jobs for this workflow run
|
||||||
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
|
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
|
||||||
@@ -227,6 +216,13 @@ jobs:
|
|||||||
return; // Exit early
|
return; // Exit early
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map job names to our build targets
|
||||||
|
const jobMappings = {
|
||||||
|
'Android Phone': ['🤖 Build Android APK (Phone)', 'build-android-phone'],
|
||||||
|
'Android TV': ['🤖 Build Android APK (TV)', 'build-android-tv'],
|
||||||
|
'iOS Phone': ['🍎 Build iOS IPA (Phone)', 'build-ios-phone']
|
||||||
|
};
|
||||||
|
|
||||||
// Create individual status for each job
|
// Create individual status for each job
|
||||||
for (const [platform, jobNames] of Object.entries(jobMappings)) {
|
for (const [platform, jobNames] of Object.entries(jobMappings)) {
|
||||||
const job = jobs.jobs.find(j =>
|
const job = jobs.jobs.find(j =>
|
||||||
@@ -240,9 +236,7 @@ jobs:
|
|||||||
conclusion: job.conclusion,
|
conclusion: job.conclusion,
|
||||||
url: job.html_url,
|
url: job.html_url,
|
||||||
runId: latestAppsRun.id,
|
runId: latestAppsRun.id,
|
||||||
created_at: job.started_at || latestAppsRun.created_at,
|
created_at: job.started_at || latestAppsRun.created_at
|
||||||
started_at: job.started_at,
|
|
||||||
completed_at: job.completed_at
|
|
||||||
};
|
};
|
||||||
console.log(`Mapped ${platform} to job: ${job.name} (${job.status}/${job.conclusion || 'none'})`);
|
console.log(`Mapped ${platform} to job: ${job.name} (${job.status}/${job.conclusion || 'none'})`);
|
||||||
} else {
|
} else {
|
||||||
@@ -253,30 +247,22 @@ jobs:
|
|||||||
conclusion: latestAppsRun.conclusion,
|
conclusion: latestAppsRun.conclusion,
|
||||||
url: latestAppsRun.html_url,
|
url: latestAppsRun.html_url,
|
||||||
runId: latestAppsRun.id,
|
runId: latestAppsRun.id,
|
||||||
created_at: latestAppsRun.created_at,
|
created_at: latestAppsRun.created_at
|
||||||
started_at: latestAppsRun.run_started_at,
|
|
||||||
completed_at: latestAppsRun.updated_at
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Failed to get jobs for run ${latestAppsRun.id}:`, error.message);
|
console.log(`Failed to get jobs for run ${latestAppsRun.id}:`, error.message);
|
||||||
// Fallback to workflow-level status for every build target.
|
// Fallback to workflow-level status
|
||||||
// Keys must match jobMappings / buildTargets statusKey values.
|
buildStatuses['Android Phone'] = buildStatuses['Android TV'] = buildStatuses['iOS Phone'] = {
|
||||||
const fallbackStatus = {
|
|
||||||
name: latestAppsRun.name,
|
name: latestAppsRun.name,
|
||||||
status: latestAppsRun.status,
|
status: latestAppsRun.status,
|
||||||
conclusion: latestAppsRun.conclusion,
|
conclusion: latestAppsRun.conclusion,
|
||||||
url: latestAppsRun.html_url,
|
url: latestAppsRun.html_url,
|
||||||
runId: latestAppsRun.id,
|
runId: latestAppsRun.id,
|
||||||
created_at: latestAppsRun.created_at,
|
created_at: latestAppsRun.created_at
|
||||||
started_at: latestAppsRun.run_started_at,
|
|
||||||
completed_at: latestAppsRun.updated_at
|
|
||||||
};
|
};
|
||||||
for (const platform of Object.keys(jobMappings)) {
|
|
||||||
buildStatuses[platform] = fallbackStatus;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect artifacts if any job has completed successfully
|
// Collect artifacts if any job has completed successfully
|
||||||
@@ -367,12 +353,10 @@ jobs:
|
|||||||
|
|
||||||
// Process each expected build target individually
|
// Process each expected build target individually
|
||||||
const buildTargets = [
|
const buildTargets = [
|
||||||
{ name: 'Android Phone', platform: '🤖', device: '📱 Phone', statusKey: 'Android Phone', artifactPattern: /android.*phone/i },
|
{ name: 'Android Phone', platform: '🤖', device: '📱', statusKey: 'Android Phone', artifactPattern: /android.*phone/i },
|
||||||
{ name: 'Android TV', platform: '🤖', device: '📺 TV', statusKey: 'Android TV', artifactPattern: /android.*tv/i },
|
{ name: 'Android TV', platform: '🤖', device: '📺', statusKey: 'Android TV', artifactPattern: /android.*tv/i },
|
||||||
{ name: 'iOS', platform: '🍎', device: '📱 Phone', statusKey: 'iOS', artifactPattern: /ios.*phone.*ipa(?!.*unsigned)/i },
|
{ name: 'iOS Phone', platform: '🍎', device: '📱', statusKey: 'iOS Phone', artifactPattern: /ios.*phone/i },
|
||||||
{ name: 'iOS Unsigned', platform: '🍎', device: '📱 Phone Unsigned', statusKey: 'iOS Unsigned', artifactPattern: /ios.*phone.*unsigned/i },
|
{ name: 'iOS TV', platform: '🍎', device: '📺', statusKey: 'iOS TV', artifactPattern: /ios.*tv/i }
|
||||||
{ name: 'tvOS', platform: '🍎', device: '📺 TV', statusKey: 'tvOS', artifactPattern: /ios.*tv.*ipa(?!.*unsigned)/i },
|
|
||||||
{ name: 'tvOS Unsigned', platform: '🍎', device: '📺 TV Unsigned', statusKey: 'tvOS Unsigned', artifactPattern: /ios.*tv.*unsigned/i }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const target of buildTargets) {
|
for (const target of buildTargets) {
|
||||||
@@ -387,31 +371,16 @@ jobs:
|
|||||||
let status = '⏳ Pending';
|
let status = '⏳ Pending';
|
||||||
let downloadLink = '*Waiting for build...*';
|
let downloadLink = '*Waiting for build...*';
|
||||||
|
|
||||||
// tvOS builds are temporarily disabled until feat/tv-interface
|
// Special case for iOS TV - show as disabled
|
||||||
// is merged - show them as disabled instead of stuck pending.
|
if (target.name === 'iOS TV') {
|
||||||
if (target.name === 'tvOS' || target.name === 'tvOS Unsigned') {
|
|
||||||
status = '💤 Disabled';
|
status = '💤 Disabled';
|
||||||
downloadLink = '*Disabled until feat/tv-interface is merged*';
|
downloadLink = '*Disabled for now*';
|
||||||
} else if (matchingStatus) {
|
} else if (matchingStatus) {
|
||||||
if (matchingStatus.conclusion === 'success' && matchingArtifact) {
|
if (matchingStatus.conclusion === 'success' && matchingArtifact) {
|
||||||
status = '✅ Complete';
|
status = '✅ Complete';
|
||||||
const directLink = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${matchingArtifact.workflow_run.id}/artifacts/${matchingArtifact.id}`;
|
const directLink = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${matchingArtifact.workflow_run.id}/artifacts/${matchingArtifact.id}`;
|
||||||
const fileType = target.name.includes('Android') ? 'APK' : 'IPA';
|
const fileType = target.name.includes('Android') ? 'APK' : 'IPA';
|
||||||
|
downloadLink = `[📥 Download ${fileType}](${directLink})`;
|
||||||
// Format file size
|
|
||||||
const sizeInMB = (matchingArtifact.size_in_bytes / (1024 * 1024)).toFixed(1);
|
|
||||||
const sizeInfo = `(${sizeInMB} MB)`;
|
|
||||||
|
|
||||||
// Calculate build duration
|
|
||||||
let durationInfo = '';
|
|
||||||
if (matchingStatus.started_at && matchingStatus.completed_at) {
|
|
||||||
const durationMs = new Date(matchingStatus.completed_at) - new Date(matchingStatus.started_at);
|
|
||||||
const durationMin = Math.floor(durationMs / 60000);
|
|
||||||
const durationSec = Math.floor((durationMs % 60000) / 1000);
|
|
||||||
durationInfo = ` - ${durationMin}m ${durationSec}s`;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadLink = `[📥 Download ${fileType}](${directLink}) ${sizeInfo}${durationInfo}`;
|
|
||||||
} else if (matchingStatus.conclusion === 'failure') {
|
} else if (matchingStatus.conclusion === 'failure') {
|
||||||
status = `❌ [Failed](${matchingStatus.url})`;
|
status = `❌ [Failed](${matchingStatus.url})`;
|
||||||
downloadLink = '*Build failed*';
|
downloadLink = '*Build failed*';
|
||||||
@@ -439,7 +408,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commentBody += `| ${target.platform} ${target.name} | ${target.device} | ${status} | ${downloadLink} |\n`;
|
commentBody += `| ${target.platform} ${target.name.split(' ')[0]} | ${target.device} ${target.name.split(' ')[1]} | ${status} | ${downloadLink} |\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
commentBody += `\n`;
|
commentBody += `\n`;
|
||||||
|
|||||||
226
.github/workflows/build-apps.yml
vendored
226
.github/workflows/build-apps.yml
vendored
@@ -41,12 +41,12 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
- name: 🍞 Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: 💾 Cache Bun dependencies
|
- name: 💾 Cache Bun dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-develop-${{ hashFiles('bun.lock') }}
|
key: ${{ runner.os }}-${{ runner.arch }}-bun-develop-${{ hashFiles('bun.lock') }}
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
bun run submodule-reload
|
bun run submodule-reload
|
||||||
|
|
||||||
- name: 💾 Cache Gradle global
|
- name: 💾 Cache Gradle global
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
run: bun run prebuild
|
run: bun run prebuild
|
||||||
|
|
||||||
- name: 💾 Cache project Gradle (.gradle)
|
- name: 💾 Cache project Gradle (.gradle)
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: android/.gradle
|
path: android/.gradle
|
||||||
key: ${{ runner.os }}-android-gradle-develop-${{ hashFiles('android/**/build.gradle', 'android/gradle/wrapper/gradle-wrapper.properties') }}
|
key: ${{ runner.os }}-android-gradle-develop-${{ hashFiles('android/**/build.gradle', 'android/gradle/wrapper/gradle-wrapper.properties') }}
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: 📤 Upload APK artifact
|
- name: 📤 Upload APK artifact
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: streamyfin-android-phone-apk-${{ env.DATE_TAG }}
|
name: streamyfin-android-phone-apk-${{ env.DATE_TAG }}
|
||||||
path: |
|
path: |
|
||||||
@@ -124,12 +124,12 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
- name: 🍞 Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: 💾 Cache Bun dependencies
|
- name: 💾 Cache Bun dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-develop-${{ hashFiles('bun.lock') }}
|
key: ${{ runner.os }}-${{ runner.arch }}-bun-develop-${{ hashFiles('bun.lock') }}
|
||||||
@@ -143,7 +143,7 @@ jobs:
|
|||||||
bun run submodule-reload
|
bun run submodule-reload
|
||||||
|
|
||||||
- name: 💾 Cache Gradle global
|
- name: 💾 Cache Gradle global
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
run: bun run prebuild:tv
|
run: bun run prebuild:tv
|
||||||
|
|
||||||
- name: 💾 Cache project Gradle (.gradle)
|
- name: 💾 Cache project Gradle (.gradle)
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: android/.gradle
|
path: android/.gradle
|
||||||
key: ${{ runner.os }}-android-gradle-develop-${{ hashFiles('android/**/build.gradle', 'android/gradle/wrapper/gradle-wrapper.properties') }}
|
key: ${{ runner.os }}-android-gradle-develop-${{ hashFiles('android/**/build.gradle', 'android/gradle/wrapper/gradle-wrapper.properties') }}
|
||||||
@@ -171,7 +171,7 @@ jobs:
|
|||||||
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: 📤 Upload APK artifact
|
- name: 📤 Upload APK artifact
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: streamyfin-android-tv-apk-${{ env.DATE_TAG }}
|
name: streamyfin-android-tv-apk-${{ env.DATE_TAG }}
|
||||||
path: |
|
path: |
|
||||||
@@ -195,12 +195,12 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
- name: 🍞 Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: 💾 Cache Bun dependencies
|
- name: 💾 Cache Bun dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
||||||
@@ -216,12 +216,12 @@ jobs:
|
|||||||
run: bun run prebuild
|
run: bun run prebuild
|
||||||
|
|
||||||
- name: 🔧 Setup Xcode
|
- name: 🔧 Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1
|
||||||
with:
|
with:
|
||||||
xcode-version: "26.2"
|
xcode-version: "26.2"
|
||||||
|
|
||||||
- name: 🏗️ Setup EAS
|
- name: 🏗️ Setup EAS
|
||||||
uses: expo/expo-github-action@b184ff86a3c926240f1b6db41764c83a01c02eef # main
|
uses: expo/expo-github-action@main
|
||||||
with:
|
with:
|
||||||
eas-version: latest
|
eas-version: latest
|
||||||
token: ${{ secrets.EXPO_TOKEN }}
|
token: ${{ secrets.EXPO_TOKEN }}
|
||||||
@@ -236,7 +236,7 @@ jobs:
|
|||||||
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: 📤 Upload IPA artifact
|
- name: 📤 Upload IPA artifact
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: streamyfin-ios-phone-ipa-${{ env.DATE_TAG }}
|
name: streamyfin-ios-phone-ipa-${{ env.DATE_TAG }}
|
||||||
path: build-*.ipa
|
path: build-*.ipa
|
||||||
@@ -259,12 +259,12 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
- name: 🍞 Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: 💾 Cache Bun dependencies
|
- name: 💾 Cache Bun dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
||||||
@@ -280,7 +280,7 @@ jobs:
|
|||||||
run: bun run prebuild
|
run: bun run prebuild
|
||||||
|
|
||||||
- name: 🔧 Setup Xcode
|
- name: 🔧 Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1
|
||||||
with:
|
with:
|
||||||
xcode-version: "26.2"
|
xcode-version: "26.2"
|
||||||
|
|
||||||
@@ -293,133 +293,73 @@ jobs:
|
|||||||
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: 📤 Upload IPA artifact
|
- name: 📤 Upload IPA artifact
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: streamyfin-ios-phone-unsigned-ipa-${{ env.DATE_TAG }}
|
name: streamyfin-ios-phone-unsigned-ipa-${{ env.DATE_TAG }}
|
||||||
path: build/*.ipa
|
path: build/*.ipa
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
build-ios-tv:
|
# Disabled for now - uncomment when ready to build iOS TV
|
||||||
# Temporarily disabled until feat/tv-interface is merged (TV UI not ready).
|
# build-ios-tv:
|
||||||
# Re-enable by removing the `false &&` prefix below.
|
# if: (!contains(github.event.head_commit.message, '[skip ci]') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'streamyfin/streamyfin'))
|
||||||
if: false && (!contains(github.event.head_commit.message, '[skip ci]') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'streamyfin/streamyfin'))
|
# runs-on: macos-26
|
||||||
runs-on: macos-26
|
# name: 🍎 Build iOS IPA (TV)
|
||||||
name: 🍎 Build tvOS IPA
|
# permissions:
|
||||||
permissions:
|
# contents: read
|
||||||
contents: read
|
#
|
||||||
|
# steps:
|
||||||
steps:
|
# - name: 📥 Checkout code
|
||||||
- name: 📥 Checkout code
|
# uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
# with:
|
||||||
with:
|
# ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
# fetch-depth: 0
|
||||||
fetch-depth: 0
|
# submodules: recursive
|
||||||
submodules: recursive
|
# show-progress: false
|
||||||
show-progress: false
|
#
|
||||||
|
# - name: 🍞 Setup Bun
|
||||||
- name: 🍞 Setup Bun
|
# uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
# with:
|
||||||
with:
|
# bun-version: latest
|
||||||
bun-version: latest
|
#
|
||||||
|
# - name: 💾 Cache Bun dependencies
|
||||||
- name: 💾 Cache Bun dependencies
|
# uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
# with:
|
||||||
with:
|
# path: ~/.bun/install/cache
|
||||||
path: ~/.bun/install/cache
|
# key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
||||||
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
# restore-keys: |
|
||||||
restore-keys: |
|
# ${{ runner.os }}-bun-cache
|
||||||
${{ runner.os }}-bun-cache
|
#
|
||||||
|
# - name: 📦 Install dependencies and reload submodules
|
||||||
- name: 📦 Install dependencies and reload submodules
|
# run: |
|
||||||
run: |
|
# bun install --frozen-lockfile
|
||||||
bun install --frozen-lockfile
|
# bun run submodule-reload
|
||||||
bun run submodule-reload
|
#
|
||||||
|
# - name: 🛠️ Generate project files
|
||||||
- name: 🛠️ Generate project files
|
# run: bun run prebuild:tv
|
||||||
run: bun run prebuild:tv
|
#
|
||||||
|
# - name: 🔧 Setup Xcode
|
||||||
- name: 🔧 Setup Xcode
|
# uses: maxim-lobanov/setup-xcode@v1
|
||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
# with:
|
||||||
with:
|
# xcode-version: '26.0.1'
|
||||||
xcode-version: "26.2"
|
#
|
||||||
|
# - name: 🏗️ Setup EAS
|
||||||
- name: 🏗️ Setup EAS
|
# uses: expo/expo-github-action@main
|
||||||
uses: expo/expo-github-action@b184ff86a3c926240f1b6db41764c83a01c02eef # main
|
# with:
|
||||||
with:
|
# eas-version: latest
|
||||||
eas-version: latest
|
# token: ${{ secrets.EXPO_TOKEN }}
|
||||||
token: ${{ secrets.EXPO_TOKEN }}
|
# eas-cache: true
|
||||||
eas-cache: true
|
#
|
||||||
|
# - name: 🚀 Build iOS app
|
||||||
- name: 🚀 Build iOS app
|
# env:
|
||||||
env:
|
# EXPO_TV: 1
|
||||||
EXPO_TV: 1
|
# run: eas build -p ios --local --non-interactive
|
||||||
run: eas build -p ios --local --non-interactive
|
#
|
||||||
|
# - name: 📅 Set date tag
|
||||||
- name: 📅 Set date tag
|
# run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
||||||
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
#
|
||||||
|
# - name: 📤 Upload IPA artifact
|
||||||
- name: 📤 Upload IPA artifact
|
# uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
# with:
|
||||||
with:
|
# name: streamyfin-ios-tv-ipa-${{ env.DATE_TAG }}
|
||||||
name: streamyfin-ios-tv-ipa-${{ env.DATE_TAG }}
|
# path: build-*.ipa
|
||||||
path: build-*.ipa
|
# retention-days: 7
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
build-ios-tv-unsigned:
|
|
||||||
# Temporarily disabled until feat/tv-interface is merged (TV UI not ready).
|
|
||||||
# Re-enable by removing the `false &&` prefix below.
|
|
||||||
if: false && (!contains(github.event.head_commit.message, '[skip ci]'))
|
|
||||||
runs-on: macos-26
|
|
||||||
name: 🍎 Build tvOS IPA (Unsigned)
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 📥 Checkout code
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
||||||
fetch-depth: 0
|
|
||||||
submodules: recursive
|
|
||||||
show-progress: false
|
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
|
|
||||||
- name: 💾 Cache Bun dependencies
|
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ~/.bun/install/cache
|
|
||||||
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-bun-cache
|
|
||||||
|
|
||||||
- name: 📦 Install dependencies and reload submodules
|
|
||||||
run: |
|
|
||||||
bun install --frozen-lockfile
|
|
||||||
bun run submodule-reload
|
|
||||||
|
|
||||||
- name: 🛠️ Generate project files
|
|
||||||
run: bun run prebuild:tv
|
|
||||||
|
|
||||||
- name: 🔧 Setup Xcode
|
|
||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
|
||||||
with:
|
|
||||||
xcode-version: "26.2"
|
|
||||||
|
|
||||||
- name: 🚀 Build iOS app
|
|
||||||
env:
|
|
||||||
EXPO_TV: 1
|
|
||||||
run: bun run ios:unsigned-build:tv ${{ github.event_name == 'pull_request' && '-- --verbose' || '' }}
|
|
||||||
|
|
||||||
- name: 📅 Set date tag
|
|
||||||
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: 📤 Upload IPA artifact
|
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
||||||
with:
|
|
||||||
name: streamyfin-ios-tv-unsigned-ipa-${{ env.DATE_TAG }}
|
|
||||||
path: build/*.ipa
|
|
||||||
retention-days: 7
|
|
||||||
|
|||||||
4
.github/workflows/check-lockfile.yml
vendored
4
.github/workflows/check-lockfile.yml
vendored
@@ -27,12 +27,12 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
- name: 🍞 Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: 💾 Cache Bun dependencies
|
- name: 💾 Cache Bun dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.bun/install/cache
|
~/.bun/install/cache
|
||||||
|
|||||||
6
.github/workflows/ci-codeql.yml
vendored
6
.github/workflows/ci-codeql.yml
vendored
@@ -27,13 +27,13 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: 🏁 Initialize CodeQL
|
- name: 🏁 Initialize CodeQL
|
||||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended,security-and-quality
|
queries: +security-extended,security-and-quality
|
||||||
|
|
||||||
- name: 🛠️ Autobuild
|
- name: 🛠️ Autobuild
|
||||||
uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/autobuild@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||||
|
|
||||||
- name: 🧪 Perform CodeQL Analysis
|
- name: 🧪 Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||||
|
|||||||
2
.github/workflows/crowdin.yml
vendored
2
.github/workflows/crowdin.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 🌐 Sync Translations with Crowdin
|
- name: 🌐 Sync Translations with Crowdin
|
||||||
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2
|
uses: crowdin/github-action@b4b468cffefb50bdd99dd83e5d2eaeb63c880380 # v2.14.0
|
||||||
with:
|
with:
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: true
|
upload_translations: true
|
||||||
|
|||||||
12
.github/workflows/linting.yml
vendored
12
.github/workflows/linting.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
|
- uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
||||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||||
with:
|
with:
|
||||||
header: pr-title-lint-error
|
header: pr-title-lint-error
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
||||||
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
|
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
||||||
with:
|
with:
|
||||||
header: pr-title-lint-error
|
header: pr-title-lint-error
|
||||||
delete: true
|
delete: true
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Dependency Review
|
- name: Dependency Review
|
||||||
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
|
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||||
with:
|
with:
|
||||||
fail-on-severity: high
|
fail-on-severity: high
|
||||||
base-ref: ${{ github.event.pull_request.base.sha || 'develop' }}
|
base-ref: ${{ github.event.pull_request.base.sha || 'develop' }}
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
- name: 🍞 Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
@@ -107,12 +107,12 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: "🟢 Setup Node.js"
|
- name: "🟢 Setup Node.js"
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '24.x'
|
node-version: '24.x'
|
||||||
|
|
||||||
- name: "🍞 Setup Bun"
|
- name: "🍞 Setup Bun"
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/update-issue-form.yml
vendored
6
.github/workflows/update-issue-form.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: "🟢 Setup Node.js"
|
- name: "🟢 Setup Node.js"
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '24.x'
|
node-version: '24.x'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: 🔍 Extract minor version from app.json
|
- name: 🔍 Extract minor version from app.json
|
||||||
id: minor
|
id: minor
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # main
|
uses: actions/github-script@main
|
||||||
with:
|
with:
|
||||||
result-encoding: string
|
result-encoding: string
|
||||||
script: |
|
script: |
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
dry_run: no-push
|
dry_run: no-push
|
||||||
|
|
||||||
- name: 📬 Commit and create pull request
|
- name: 📬 Commit and create pull request
|
||||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||||
with:
|
with:
|
||||||
add-paths: .github/ISSUE_TEMPLATE/bug_report.yml
|
add-paths: .github/ISSUE_TEMPLATE/bug_report.yml
|
||||||
branch: ci-update-bug-report
|
branch: ci-update-bug-report
|
||||||
|
|||||||
@@ -61,10 +61,7 @@ export default function Page() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
logsFile.write(JSON.stringify(filteredLogs));
|
logsFile.write(JSON.stringify(filteredLogs));
|
||||||
await Sharing.shareAsync(logsFile.uri, {
|
await Sharing.shareAsync(logsFile.uri, { mimeType: "txt", UTI: "txt" });
|
||||||
mimeType: "text/plain",
|
|
||||||
UTI: "public.plain-text",
|
|
||||||
});
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
writeErrorLog("Something went wrong attempting to export", e);
|
writeErrorLog("Something went wrong attempting to export", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
|
||||||
import { useNavigation } from "expo-router";
|
|
||||||
import { TFunction } from "i18next";
|
|
||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { Text } from "@/components/common/Text";
|
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
|
||||||
import { ListItem } from "@/components/list/ListItem";
|
|
||||||
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
|
||||||
import { SegmentSkipMode, useSettings } from "@/utils/atoms/settings";
|
|
||||||
|
|
||||||
type SkipSettingKey =
|
|
||||||
| "skipIntro"
|
|
||||||
| "skipOutro"
|
|
||||||
| "skipRecap"
|
|
||||||
| "skipCommercial"
|
|
||||||
| "skipPreview";
|
|
||||||
|
|
||||||
const SEGMENTS: ReadonlyArray<{ key: SkipSettingKey; labelKey: string }> = [
|
|
||||||
{ key: "skipIntro", labelKey: "skip_intro" },
|
|
||||||
{ key: "skipOutro", labelKey: "skip_outro" },
|
|
||||||
{ key: "skipRecap", labelKey: "skip_recap" },
|
|
||||||
{ key: "skipCommercial", labelKey: "skip_commercial" },
|
|
||||||
{ key: "skipPreview", labelKey: "skip_preview" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const SEGMENT_SKIP_OPTIONS = (
|
|
||||||
t: TFunction<"translation", undefined>,
|
|
||||||
): Array<{ label: string; value: SegmentSkipMode }> => [
|
|
||||||
{ label: t("home.settings.other.segment_skip_auto"), value: "auto" },
|
|
||||||
{ label: t("home.settings.other.segment_skip_ask"), value: "ask" },
|
|
||||||
{ label: t("home.settings.other.segment_skip_none"), value: "none" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function SegmentSkipPage() {
|
|
||||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigation = useNavigation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
navigation.setOptions({
|
|
||||||
title: t("home.settings.other.segment_skip_settings"),
|
|
||||||
});
|
|
||||||
}, [navigation, t]);
|
|
||||||
|
|
||||||
const options = useMemo(() => SEGMENT_SKIP_OPTIONS(t), [t]);
|
|
||||||
|
|
||||||
if (!settings) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View className='px-4'>
|
|
||||||
<ListGroup>
|
|
||||||
{SEGMENTS.map(({ key, labelKey }) => {
|
|
||||||
const current = settings[key];
|
|
||||||
const locked = pluginSettings?.[key]?.locked ?? false;
|
|
||||||
const groups = [
|
|
||||||
{
|
|
||||||
options: options.map((o) => ({
|
|
||||||
type: "radio" as const,
|
|
||||||
label: o.label,
|
|
||||||
value: o.value,
|
|
||||||
selected: o.value === current,
|
|
||||||
disabled: locked,
|
|
||||||
onPress: () => {
|
|
||||||
if (locked) return;
|
|
||||||
updateSettings({ [key]: o.value });
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<ListItem
|
|
||||||
key={key}
|
|
||||||
title={t(`home.settings.other.${labelKey}`)}
|
|
||||||
subtitle={t(`home.settings.other.${labelKey}_description`)}
|
|
||||||
disabled={locked}
|
|
||||||
>
|
|
||||||
<PlatformDropdown
|
|
||||||
groups={groups}
|
|
||||||
trigger={
|
|
||||||
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
|
||||||
<Text className='mr-1 text-[#8E8D91]'>
|
|
||||||
{t(`home.settings.other.segment_skip_${current}`)}
|
|
||||||
</Text>
|
|
||||||
<Ionicons
|
|
||||||
name='chevron-expand-sharp'
|
|
||||||
size={18}
|
|
||||||
color='#5A5960'
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
title={t(`home.settings.other.${labelKey}`)}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ListGroup>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -134,7 +134,7 @@ export default function page() {
|
|||||||
const audioIndexFromUrl = audioIndexStr
|
const audioIndexFromUrl = audioIndexStr
|
||||||
? Number.parseInt(audioIndexStr, 10)
|
? Number.parseInt(audioIndexStr, 10)
|
||||||
: undefined;
|
: undefined;
|
||||||
const subtitleIndex = subtitleIndexStr
|
const subtitleIndexFromUrl = subtitleIndexStr
|
||||||
? Number.parseInt(subtitleIndexStr, 10)
|
? Number.parseInt(subtitleIndexStr, 10)
|
||||||
: -1;
|
: -1;
|
||||||
const bitrateValue = bitrateValueStr
|
const bitrateValue = bitrateValueStr
|
||||||
@@ -161,6 +161,24 @@ export default function page() {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, [audioIndexFromUrl, offline, downloadedItem?.userData?.audioStreamIndex]);
|
}, [audioIndexFromUrl, offline, downloadedItem?.userData?.audioStreamIndex]);
|
||||||
|
|
||||||
|
// Resolve subtitle index: use URL param if provided, otherwise use stored index for offline playback
|
||||||
|
const subtitleIndex = useMemo(() => {
|
||||||
|
if (subtitleIndexFromUrl !== undefined) {
|
||||||
|
return subtitleIndexFromUrl;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
offline &&
|
||||||
|
downloadedItem?.userData?.subtitleStreamIndex !== undefined
|
||||||
|
) {
|
||||||
|
return downloadedItem.userData.subtitleStreamIndex;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}, [
|
||||||
|
subtitleIndexFromUrl,
|
||||||
|
offline,
|
||||||
|
downloadedItem?.userData?.subtitleStreamIndex,
|
||||||
|
]);
|
||||||
|
|
||||||
// Get the playback speed for this item based on settings
|
// Get the playback speed for this item based on settings
|
||||||
const { playbackSpeed: initialPlaybackSpeed } = usePlaybackSpeed(
|
const { playbackSpeed: initialPlaybackSpeed } = usePlaybackSpeed(
|
||||||
item,
|
item,
|
||||||
@@ -406,8 +424,8 @@ export default function page() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
ItemId: item.Id,
|
ItemId: item.Id,
|
||||||
AudioStreamIndex: audioIndex ? audioIndex : undefined,
|
AudioStreamIndex: audioIndex,
|
||||||
SubtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
|
SubtitleStreamIndex: subtitleIndex,
|
||||||
MediaSourceId: mediaSourceId,
|
MediaSourceId: mediaSourceId,
|
||||||
PositionTicks: msToTicks(progress.get()),
|
PositionTicks: msToTicks(progress.get()),
|
||||||
IsPaused: !isPlaying,
|
IsPaused: !isPlaying,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import useDefaultPlaySettings from "@/hooks/useDefaultPlaySettings";
|
|||||||
import { useImageColorsReturn } from "@/hooks/useImageColorsReturn";
|
import { useImageColorsReturn } from "@/hooks/useImageColorsReturn";
|
||||||
import { useOrientation } from "@/hooks/useOrientation";
|
import { useOrientation } from "@/hooks/useOrientation";
|
||||||
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||||
|
import { useDownload } from "@/providers/DownloadProvider";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
@@ -53,6 +54,9 @@ export const ItemContent: React.FC<ItemContentProps> = React.memo(
|
|||||||
({ item, itemWithSources }) => {
|
({ item, itemWithSources }) => {
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const isOffline = useOfflineMode();
|
const isOffline = useOfflineMode();
|
||||||
|
const { getDownloadedItemById } = useDownload();
|
||||||
|
const downloadedItem =
|
||||||
|
isOffline && item.Id ? getDownloadedItemById(item.Id) : null;
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
const { orientation } = useOrientation();
|
const { orientation } = useOrientation();
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
@@ -91,17 +95,29 @@ export const ItemContent: React.FC<ItemContentProps> = React.memo(
|
|||||||
|
|
||||||
// Needs to automatically change the selected to the default values for default indexes.
|
// Needs to automatically change the selected to the default values for default indexes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// When offline, use the indices stored in userData (the last-used tracks for this file)
|
||||||
|
// rather than the server's defaults, so MediaSourceButton reflects what will actually play.
|
||||||
|
const offlineUserData = downloadedItem?.userData;
|
||||||
|
|
||||||
setSelectedOptions(() => ({
|
setSelectedOptions(() => ({
|
||||||
bitrate: defaultBitrate,
|
bitrate: defaultBitrate,
|
||||||
mediaSource: defaultMediaSource ?? undefined,
|
mediaSource: defaultMediaSource ?? undefined,
|
||||||
subtitleIndex: defaultSubtitleIndex ?? -1,
|
subtitleIndex:
|
||||||
audioIndex: defaultAudioIndex,
|
offlineUserData && !offlineUserData.isTranscoded
|
||||||
|
? offlineUserData.subtitleStreamIndex
|
||||||
|
: (defaultSubtitleIndex ?? -1),
|
||||||
|
audioIndex:
|
||||||
|
offlineUserData && !offlineUserData.isTranscoded
|
||||||
|
? offlineUserData.audioStreamIndex
|
||||||
|
: defaultAudioIndex,
|
||||||
}));
|
}));
|
||||||
}, [
|
}, [
|
||||||
defaultAudioIndex,
|
defaultAudioIndex,
|
||||||
defaultBitrate,
|
defaultBitrate,
|
||||||
defaultSubtitleIndex,
|
defaultSubtitleIndex,
|
||||||
defaultMediaSource,
|
defaultMediaSource,
|
||||||
|
downloadedItem?.userData?.audioStreamIndex,
|
||||||
|
downloadedItem?.userData?.subtitleStreamIndex,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -232,14 +248,12 @@ export const ItemContent: React.FC<ItemContentProps> = React.memo(
|
|||||||
colors={itemColors}
|
colors={itemColors}
|
||||||
/>
|
/>
|
||||||
<View className='w-1' />
|
<View className='w-1' />
|
||||||
{!isOffline && (
|
<MediaSourceButton
|
||||||
<MediaSourceButton
|
selectedOptions={selectedOptions}
|
||||||
selectedOptions={selectedOptions}
|
setSelectedOptions={setSelectedOptions}
|
||||||
setSelectedOptions={setSelectedOptions}
|
item={itemWithSources}
|
||||||
item={itemWithSources}
|
colors={itemColors}
|
||||||
colors={itemColors}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{item.Type === "Episode" && (
|
{item.Type === "Episode" && (
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
||||||
import type { ThemeColors } from "@/hooks/useImageColorsReturn";
|
import type { ThemeColors } from "@/hooks/useImageColorsReturn";
|
||||||
|
import { useDownload } from "@/providers/DownloadProvider";
|
||||||
|
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
||||||
import { BITRATES } from "./BitRateSheet";
|
import { BITRATES } from "./BitRateSheet";
|
||||||
import type { SelectedOptions } from "./ItemContent";
|
import type { SelectedOptions } from "./ItemContent";
|
||||||
import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown";
|
import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown";
|
||||||
@@ -28,6 +30,14 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const isOffline = useOfflineMode();
|
||||||
|
const { getDownloadedItemById } = useDownload();
|
||||||
|
|
||||||
|
// For transcoded downloads there's only one burned-in track — nothing to pick
|
||||||
|
const isTranscodedDownload = useMemo(() => {
|
||||||
|
if (!isOffline || !item?.Id) return false;
|
||||||
|
return getDownloadedItemById(item.Id)?.userData?.isTranscoded === true;
|
||||||
|
}, [isOffline, item?.Id, getDownloadedItemById]);
|
||||||
|
|
||||||
const effectiveColors = colors || {
|
const effectiveColors = colors || {
|
||||||
primary: "#7c3aed",
|
primary: "#7c3aed",
|
||||||
@@ -72,34 +82,36 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
const optionGroups: OptionGroup[] = useMemo(() => {
|
const optionGroups: OptionGroup[] = useMemo(() => {
|
||||||
const groups: OptionGroup[] = [];
|
const groups: OptionGroup[] = [];
|
||||||
|
|
||||||
// Bitrate group
|
if (!isOffline) {
|
||||||
groups.push({
|
// Bitrate group
|
||||||
title: t("item_card.quality"),
|
|
||||||
options: BITRATES.map((bitrate) => ({
|
|
||||||
type: "radio" as const,
|
|
||||||
label: bitrate.key,
|
|
||||||
value: bitrate,
|
|
||||||
selected: bitrate.value === selectedOptions.bitrate?.value,
|
|
||||||
onPress: () =>
|
|
||||||
setSelectedOptions((prev) => prev && { ...prev, bitrate }),
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Media Source group (only if multiple sources)
|
|
||||||
if (item?.MediaSources && item.MediaSources.length > 1) {
|
|
||||||
groups.push({
|
groups.push({
|
||||||
title: t("item_card.video"),
|
title: t("item_card.quality"),
|
||||||
options: item.MediaSources.map((source) => ({
|
options: BITRATES.map((bitrate) => ({
|
||||||
type: "radio" as const,
|
type: "radio" as const,
|
||||||
label: getMediaSourceDisplayName(source),
|
label: bitrate.key,
|
||||||
value: source,
|
value: bitrate,
|
||||||
selected: source.Id === selectedOptions.mediaSource?.Id,
|
selected: bitrate.value === selectedOptions.bitrate?.value,
|
||||||
onPress: () =>
|
onPress: () =>
|
||||||
setSelectedOptions(
|
setSelectedOptions((prev) => prev && { ...prev, bitrate }),
|
||||||
(prev) => prev && { ...prev, mediaSource: source },
|
|
||||||
),
|
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Media Source group (only if multiple sources)
|
||||||
|
if (item?.MediaSources && item.MediaSources.length > 1) {
|
||||||
|
groups.push({
|
||||||
|
title: t("item_card.video"),
|
||||||
|
options: item.MediaSources.map((source) => ({
|
||||||
|
type: "radio" as const,
|
||||||
|
label: getMediaSourceDisplayName(source),
|
||||||
|
value: source,
|
||||||
|
selected: source.Id === selectedOptions.mediaSource?.Id,
|
||||||
|
onPress: () =>
|
||||||
|
setSelectedOptions(
|
||||||
|
(prev) => prev && { ...prev, mediaSource: source },
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio track group
|
// Audio track group
|
||||||
@@ -150,6 +162,7 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
return groups;
|
return groups;
|
||||||
}, [
|
}, [
|
||||||
item,
|
item,
|
||||||
|
isOffline,
|
||||||
selectedOptions,
|
selectedOptions,
|
||||||
audioStreams,
|
audioStreams,
|
||||||
subtitleStreams,
|
subtitleStreams,
|
||||||
@@ -178,6 +191,8 @@ export const MediaSourceButton: React.FC<Props> = ({
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isTranscodedDownload) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlatformDropdown
|
<PlatformDropdown
|
||||||
groups={optionGroups}
|
groups={optionGroups}
|
||||||
|
|||||||
@@ -96,14 +96,23 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
|
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
itemId: item.Id!,
|
itemId: item.Id!,
|
||||||
audioIndex: selectedOptions.audioIndex?.toString() ?? "",
|
|
||||||
subtitleIndex: selectedOptions.subtitleIndex?.toString() ?? "",
|
|
||||||
mediaSourceId: selectedOptions.mediaSource?.Id ?? "",
|
mediaSourceId: selectedOptions.mediaSource?.Id ?? "",
|
||||||
bitrateValue: selectedOptions.bitrate?.value?.toString() ?? "",
|
bitrateValue: selectedOptions.bitrate?.value?.toString() ?? "",
|
||||||
playbackPosition: item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
playbackPosition: item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
offline: isOffline ? "true" : "false",
|
offline: isOffline ? "true" : "false",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (selectedOptions.audioIndex !== undefined) {
|
||||||
|
queryParams.set("audioIndex", selectedOptions.audioIndex.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedOptions.subtitleIndex !== undefined) {
|
||||||
|
queryParams.set(
|
||||||
|
"subtitleIndex",
|
||||||
|
selectedOptions.subtitleIndex.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const queryString = queryParams.toString();
|
const queryString = queryParams.toString();
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -292,6 +301,29 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
t,
|
t,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const buildOfflineQueryParams = useCallback(
|
||||||
|
(downloadedItem: NonNullable<ReturnType<typeof getDownloadedItemById>>) => {
|
||||||
|
const isTranscoded = downloadedItem.userData?.isTranscoded === true;
|
||||||
|
const audioIdx = isTranscoded
|
||||||
|
? downloadedItem.userData?.audioStreamIndex
|
||||||
|
: selectedOptions.audioIndex;
|
||||||
|
const subtitleIdx = isTranscoded
|
||||||
|
? downloadedItem.userData?.subtitleStreamIndex
|
||||||
|
: selectedOptions.subtitleIndex;
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
itemId: item.Id!,
|
||||||
|
offline: "true",
|
||||||
|
playbackPosition:
|
||||||
|
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
|
});
|
||||||
|
if (audioIdx !== undefined) params.set("audioIndex", audioIdx.toString());
|
||||||
|
if (subtitleIdx !== undefined)
|
||||||
|
params.set("subtitleIndex", subtitleIdx.toString());
|
||||||
|
return params;
|
||||||
|
},
|
||||||
|
[item, selectedOptions],
|
||||||
|
);
|
||||||
|
|
||||||
const onPress = useCallback(async () => {
|
const onPress = useCallback(async () => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
@@ -302,13 +334,7 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
|
|
||||||
// If already in offline mode, play downloaded file directly
|
// If already in offline mode, play downloaded file directly
|
||||||
if (isOffline && downloadedItem) {
|
if (isOffline && downloadedItem) {
|
||||||
const queryParams = new URLSearchParams({
|
goToPlayer(buildOfflineQueryParams(downloadedItem).toString());
|
||||||
itemId: item.Id!,
|
|
||||||
offline: "true",
|
|
||||||
playbackPosition:
|
|
||||||
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
|
||||||
});
|
|
||||||
goToPlayer(queryParams.toString());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,13 +357,9 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
hideModal();
|
hideModal();
|
||||||
const queryParams = new URLSearchParams({
|
goToPlayer(
|
||||||
itemId: item.Id!,
|
buildOfflineQueryParams(downloadedItem).toString(),
|
||||||
offline: "true",
|
);
|
||||||
playbackPosition:
|
|
||||||
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
|
||||||
});
|
|
||||||
goToPlayer(queryParams.toString());
|
|
||||||
}}
|
}}
|
||||||
color='purple'
|
color='purple'
|
||||||
>
|
>
|
||||||
@@ -374,13 +396,7 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
{
|
{
|
||||||
text: t("player.downloaded_file_yes"),
|
text: t("player.downloaded_file_yes"),
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
const queryParams = new URLSearchParams({
|
goToPlayer(buildOfflineQueryParams(downloadedItem).toString());
|
||||||
itemId: item.Id!,
|
|
||||||
offline: "true",
|
|
||||||
playbackPosition:
|
|
||||||
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
|
||||||
});
|
|
||||||
goToPlayer(queryParams.toString());
|
|
||||||
},
|
},
|
||||||
isPreferred: true,
|
isPreferred: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { BITRATES } from "@/components/BitrateSelector";
|
|||||||
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
||||||
import { PLAYBACK_SPEEDS } from "@/components/PlaybackSpeedSelector";
|
import { PLAYBACK_SPEEDS } from "@/components/PlaybackSpeedSelector";
|
||||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
|
||||||
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||||
import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings";
|
import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
@@ -16,7 +15,6 @@ import { ListGroup } from "../list/ListGroup";
|
|||||||
import { ListItem } from "../list/ListItem";
|
import { ListItem } from "../list/ListItem";
|
||||||
|
|
||||||
export const PlaybackControlsSettings: React.FC = () => {
|
export const PlaybackControlsSettings: React.FC = () => {
|
||||||
const router = useRouter();
|
|
||||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -250,15 +248,6 @@ export const PlaybackControlsSettings: React.FC = () => {
|
|||||||
title={t("home.settings.other.max_auto_play_episode_count")}
|
title={t("home.settings.other.max_auto_play_episode_count")}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* Media Segment Skip Settings */}
|
|
||||||
<ListItem
|
|
||||||
title={t("home.settings.other.segment_skip_settings")}
|
|
||||||
subtitle={t("home.settings.other.segment_skip_settings_description")}
|
|
||||||
onPress={() => router.push("/settings/segment-skip/page")}
|
|
||||||
>
|
|
||||||
<Ionicons name='chevron-forward' size={20} color='#8E8D91' />
|
|
||||||
</ListItem>
|
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
</DisabledSetting>
|
</DisabledSetting>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ interface BottomControlsProps {
|
|||||||
showRemoteBubble: boolean;
|
showRemoteBubble: boolean;
|
||||||
currentTime: number;
|
currentTime: number;
|
||||||
remainingTime: number;
|
remainingTime: number;
|
||||||
showSkipSegmentButton: boolean;
|
showSkipButton: boolean;
|
||||||
skipSegmentButtonText: string;
|
showSkipCreditButton: boolean;
|
||||||
showSkipOutroButton: boolean;
|
|
||||||
skipOutroButtonText: string;
|
|
||||||
hasContentAfterCredits: boolean;
|
hasContentAfterCredits: boolean;
|
||||||
onSkipSegment: () => void;
|
skipIntro: () => void;
|
||||||
onSkipOutro: () => void;
|
skipCredit: () => void;
|
||||||
nextItem?: BaseItemDto | null;
|
nextItem?: BaseItemDto | null;
|
||||||
handleNextEpisodeAutoPlay: () => void;
|
handleNextEpisodeAutoPlay: () => void;
|
||||||
handleNextEpisodeManual: () => void;
|
handleNextEpisodeManual: () => void;
|
||||||
@@ -68,13 +66,11 @@ export const BottomControls: FC<BottomControlsProps> = ({
|
|||||||
showRemoteBubble,
|
showRemoteBubble,
|
||||||
currentTime,
|
currentTime,
|
||||||
remainingTime,
|
remainingTime,
|
||||||
showSkipSegmentButton,
|
showSkipButton,
|
||||||
skipSegmentButtonText,
|
showSkipCreditButton,
|
||||||
showSkipOutroButton,
|
|
||||||
skipOutroButtonText,
|
|
||||||
hasContentAfterCredits,
|
hasContentAfterCredits,
|
||||||
onSkipSegment,
|
skipIntro,
|
||||||
onSkipOutro,
|
skipCredit,
|
||||||
nextItem,
|
nextItem,
|
||||||
handleNextEpisodeAutoPlay,
|
handleNextEpisodeAutoPlay,
|
||||||
handleNextEpisodeManual,
|
handleNextEpisodeManual,
|
||||||
@@ -138,18 +134,19 @@ export const BottomControls: FC<BottomControlsProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
<View className='flex flex-row space-x-2 shrink-0'>
|
<View className='flex flex-row space-x-2 shrink-0'>
|
||||||
<SkipButton
|
<SkipButton
|
||||||
showButton={showSkipSegmentButton}
|
showButton={showSkipButton}
|
||||||
onPress={onSkipSegment}
|
onPress={skipIntro}
|
||||||
buttonText={skipSegmentButtonText}
|
buttonText='Skip Intro'
|
||||||
/>
|
/>
|
||||||
{/* Outro button defers to "Next Episode" when credits run to the
|
{/* Smart Skip Credits behavior:
|
||||||
video end and a next episode exists. */}
|
- Show "Skip Credits" if there's content after credits OR no next episode
|
||||||
|
- Show "Next Episode" if credits extend to video end AND next episode exists */}
|
||||||
<SkipButton
|
<SkipButton
|
||||||
showButton={
|
showButton={
|
||||||
showSkipOutroButton && (hasContentAfterCredits || !nextItem)
|
showSkipCreditButton && (hasContentAfterCredits || !nextItem)
|
||||||
}
|
}
|
||||||
onPress={onSkipOutro}
|
onPress={skipCredit}
|
||||||
buttonText={skipOutroButtonText}
|
buttonText='Skip Credits'
|
||||||
/>
|
/>
|
||||||
{settings.autoPlayNextEpisode !== false &&
|
{settings.autoPlayNextEpisode !== false &&
|
||||||
(settings.maxAutoPlayEpisodeCount.value === -1 ||
|
(settings.maxAutoPlayEpisodeCount.value === -1 ||
|
||||||
@@ -160,7 +157,7 @@ export const BottomControls: FC<BottomControlsProps> = ({
|
|||||||
!nextItem
|
!nextItem
|
||||||
? false
|
? false
|
||||||
: // Show during credits if no content after, OR near end of video
|
: // Show during credits if no content after, OR near end of video
|
||||||
(showSkipOutroButton && !hasContentAfterCredits) ||
|
(showSkipCreditButton && !hasContentAfterCredits) ||
|
||||||
remainingTime < 10000
|
remainingTime < 10000
|
||||||
}
|
}
|
||||||
onFinish={handleNextEpisodeAutoPlay}
|
onFinish={handleNextEpisodeAutoPlay}
|
||||||
|
|||||||
@@ -4,15 +4,7 @@ import type {
|
|||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
} from "@jellyfin/sdk/lib/generated-client";
|
} from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useLocalSearchParams } from "expo-router";
|
import { useLocalSearchParams } from "expo-router";
|
||||||
import {
|
import { type FC, useCallback, useEffect, useState } from "react";
|
||||||
type FC,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { StyleSheet, useWindowDimensions, View } from "react-native";
|
import { StyleSheet, useWindowDimensions, View } from "react-native";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Easing,
|
Easing,
|
||||||
@@ -24,17 +16,17 @@ import Animated, {
|
|||||||
} from "react-native-reanimated";
|
} from "react-native-reanimated";
|
||||||
import ContinueWatchingOverlay from "@/components/video-player/controls/ContinueWatchingOverlay";
|
import ContinueWatchingOverlay from "@/components/video-player/controls/ContinueWatchingOverlay";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
|
import { useCreditSkipper } from "@/hooks/useCreditSkipper";
|
||||||
import { useHaptic } from "@/hooks/useHaptic";
|
import { useHaptic } from "@/hooks/useHaptic";
|
||||||
|
import { useIntroSkipper } from "@/hooks/useIntroSkipper";
|
||||||
import { usePlaybackManager } from "@/hooks/usePlaybackManager";
|
import { usePlaybackManager } from "@/hooks/usePlaybackManager";
|
||||||
import { useSegmentSkipper } from "@/hooks/useSegmentSkipper";
|
|
||||||
import { useTrickplay } from "@/hooks/useTrickplay";
|
import { useTrickplay } from "@/hooks/useTrickplay";
|
||||||
import type { TechnicalInfo } from "@/modules/mpv-player";
|
import type { TechnicalInfo } from "@/modules/mpv-player";
|
||||||
import { DownloadedItem } from "@/providers/Downloads/types";
|
import { DownloadedItem } from "@/providers/Downloads/types";
|
||||||
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
|
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
|
||||||
import { useSegments } from "@/utils/segments";
|
import { ticksToMs } from "@/utils/time";
|
||||||
import { msToSeconds, ticksToMs } from "@/utils/time";
|
|
||||||
import { BottomControls } from "./BottomControls";
|
import { BottomControls } from "./BottomControls";
|
||||||
import { CenterControls } from "./CenterControls";
|
import { CenterControls } from "./CenterControls";
|
||||||
import { CONTROLS_CONSTANTS } from "./constants";
|
import { CONTROLS_CONSTANTS } from "./constants";
|
||||||
@@ -50,9 +42,6 @@ import { useControlsTimeout } from "./useControlsTimeout";
|
|||||||
import { PlaybackSpeedScope } from "./utils/playback-speed-settings";
|
import { PlaybackSpeedScope } from "./utils/playback-speed-settings";
|
||||||
import { type AspectRatio } from "./VideoScalingModeSelector";
|
import { type AspectRatio } from "./VideoScalingModeSelector";
|
||||||
|
|
||||||
// No-op function to avoid creating new references on every render
|
|
||||||
const noop = () => {};
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
@@ -121,24 +110,6 @@ export const Controls: FC<Props> = ({
|
|||||||
const [episodeView, setEpisodeView] = useState(false);
|
const [episodeView, setEpisodeView] = useState(false);
|
||||||
const [showAudioSlider, setShowAudioSlider] = useState(false);
|
const [showAudioSlider, setShowAudioSlider] = useState(false);
|
||||||
|
|
||||||
// Ref to track pending play timeout for cleanup and cancellation
|
|
||||||
const playTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
||||||
|
|
||||||
// Mutable ref tracking isPlaying to avoid stale closures in seekMs timeout
|
|
||||||
const playingRef = useRef(isPlaying);
|
|
||||||
useEffect(() => {
|
|
||||||
playingRef.current = isPlaying;
|
|
||||||
}, [isPlaying]);
|
|
||||||
|
|
||||||
// Clean up timeout on unmount
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (playTimeoutRef.current) {
|
|
||||||
clearTimeout(playTimeoutRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { height: screenHeight, width: screenWidth } = useWindowDimensions();
|
const { height: screenHeight, width: screenWidth } = useWindowDimensions();
|
||||||
const { previousItem, nextItem } = usePlaybackManager({
|
const { previousItem, nextItem } = usePlaybackManager({
|
||||||
item,
|
item,
|
||||||
@@ -329,140 +300,27 @@ export const Controls: FC<Props> = ({
|
|||||||
subtitleIndex: string;
|
subtitleIndex: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// Fetch all segments for the current item
|
const { showSkipButton, skipIntro } = useIntroSkipper(
|
||||||
const { data: segments } = useSegments(
|
item.Id!,
|
||||||
item.Id ?? "",
|
currentTime,
|
||||||
|
seek,
|
||||||
|
play,
|
||||||
offline,
|
offline,
|
||||||
downloadedFiles,
|
|
||||||
api,
|
api,
|
||||||
|
downloadedFiles,
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentTimeSeconds = msToSeconds(currentTime);
|
const { showSkipCreditButton, skipCredit, hasContentAfterCredits } =
|
||||||
const maxSeconds = maxMs ? msToSeconds(maxMs) : undefined;
|
useCreditSkipper(
|
||||||
|
item.Id!,
|
||||||
// Segment hook deals in seconds; player API in ms. The 200ms delayed play()
|
currentTime,
|
||||||
// is a workaround: some seeks otherwise resume from the pre-seek position.
|
seek,
|
||||||
const seekMs = useCallback(
|
play,
|
||||||
(timeInSeconds: number) => {
|
offline,
|
||||||
if (playTimeoutRef.current) {
|
api,
|
||||||
clearTimeout(playTimeoutRef.current);
|
downloadedFiles,
|
||||||
}
|
maxMs,
|
||||||
seek(timeInSeconds * 1000);
|
);
|
||||||
playTimeoutRef.current = setTimeout(() => {
|
|
||||||
// playingRef avoids a stale closure: re-check current isPlaying.
|
|
||||||
if (playingRef.current) {
|
|
||||||
play();
|
|
||||||
}
|
|
||||||
playTimeoutRef.current = null;
|
|
||||||
}, 200);
|
|
||||||
},
|
|
||||||
[seek, play],
|
|
||||||
);
|
|
||||||
|
|
||||||
const introSkipper = useSegmentSkipper({
|
|
||||||
segments: segments?.introSegments || [],
|
|
||||||
segmentType: "Intro",
|
|
||||||
currentTime: currentTimeSeconds,
|
|
||||||
seek: seekMs,
|
|
||||||
isPaused: !isPlaying,
|
|
||||||
});
|
|
||||||
|
|
||||||
const outroSkipper = useSegmentSkipper({
|
|
||||||
segments: segments?.creditSegments || [],
|
|
||||||
segmentType: "Outro",
|
|
||||||
currentTime: currentTimeSeconds,
|
|
||||||
totalDuration: maxSeconds,
|
|
||||||
seek: seekMs,
|
|
||||||
isPaused: !isPlaying,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recapSkipper = useSegmentSkipper({
|
|
||||||
segments: segments?.recapSegments || [],
|
|
||||||
segmentType: "Recap",
|
|
||||||
currentTime: currentTimeSeconds,
|
|
||||||
seek: seekMs,
|
|
||||||
isPaused: !isPlaying,
|
|
||||||
});
|
|
||||||
|
|
||||||
const commercialSkipper = useSegmentSkipper({
|
|
||||||
segments: segments?.commercialSegments || [],
|
|
||||||
segmentType: "Commercial",
|
|
||||||
currentTime: currentTimeSeconds,
|
|
||||||
seek: seekMs,
|
|
||||||
isPaused: !isPlaying,
|
|
||||||
});
|
|
||||||
|
|
||||||
const previewSkipper = useSegmentSkipper({
|
|
||||||
segments: segments?.previewSegments || [],
|
|
||||||
segmentType: "Preview",
|
|
||||||
currentTime: currentTimeSeconds,
|
|
||||||
seek: seekMs,
|
|
||||||
isPaused: !isPlaying,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Priority when multiple segments overlap: Commercial > Recap > Intro > Preview > Outro.
|
|
||||||
const activeSegment = useMemo(() => {
|
|
||||||
if (commercialSkipper.currentSegment)
|
|
||||||
return {
|
|
||||||
type: "Commercial" as const,
|
|
||||||
currentSegment: commercialSkipper.currentSegment,
|
|
||||||
skipSegment: commercialSkipper.skipSegment,
|
|
||||||
};
|
|
||||||
if (recapSkipper.currentSegment)
|
|
||||||
return {
|
|
||||||
type: "Recap" as const,
|
|
||||||
currentSegment: recapSkipper.currentSegment,
|
|
||||||
skipSegment: recapSkipper.skipSegment,
|
|
||||||
};
|
|
||||||
if (introSkipper.currentSegment)
|
|
||||||
return {
|
|
||||||
type: "Intro" as const,
|
|
||||||
currentSegment: introSkipper.currentSegment,
|
|
||||||
skipSegment: introSkipper.skipSegment,
|
|
||||||
};
|
|
||||||
if (previewSkipper.currentSegment)
|
|
||||||
return {
|
|
||||||
type: "Preview" as const,
|
|
||||||
currentSegment: previewSkipper.currentSegment,
|
|
||||||
skipSegment: previewSkipper.skipSegment,
|
|
||||||
};
|
|
||||||
if (outroSkipper.currentSegment)
|
|
||||||
return {
|
|
||||||
type: "Outro" as const,
|
|
||||||
currentSegment: outroSkipper.currentSegment,
|
|
||||||
skipSegment: outroSkipper.skipSegment,
|
|
||||||
};
|
|
||||||
return null;
|
|
||||||
}, [
|
|
||||||
commercialSkipper.currentSegment,
|
|
||||||
commercialSkipper.skipSegment,
|
|
||||||
recapSkipper.currentSegment,
|
|
||||||
recapSkipper.skipSegment,
|
|
||||||
introSkipper.currentSegment,
|
|
||||||
introSkipper.skipSegment,
|
|
||||||
previewSkipper.currentSegment,
|
|
||||||
previewSkipper.skipSegment,
|
|
||||||
outroSkipper.currentSegment,
|
|
||||||
outroSkipper.skipSegment,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Outro gets a dedicated button (so it can compose with Next Episode logic);
|
|
||||||
// every other segment type shares the generic skip button.
|
|
||||||
const showSkipSegmentButton =
|
|
||||||
!!activeSegment && activeSegment.type !== "Outro";
|
|
||||||
const onSkipSegment = activeSegment?.skipSegment ?? noop;
|
|
||||||
const showSkipOutroButton = activeSegment?.type === "Outro";
|
|
||||||
const onSkipOutro = outroSkipper.skipSegment;
|
|
||||||
const hasContentAfterCredits =
|
|
||||||
outroSkipper.currentSegment && maxSeconds
|
|
||||||
? outroSkipper.currentSegment.endTime < maxSeconds
|
|
||||||
: false;
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const skipSegmentButtonText = activeSegment
|
|
||||||
? t(`player.skip_${activeSegment.type.toLowerCase()}`)
|
|
||||||
: t("player.skip_intro");
|
|
||||||
const skipOutroButtonText = t("player.skip_outro");
|
|
||||||
|
|
||||||
const goToItemCommon = useCallback(
|
const goToItemCommon = useCallback(
|
||||||
(item: BaseItemDto) => {
|
(item: BaseItemDto) => {
|
||||||
@@ -675,13 +533,11 @@ export const Controls: FC<Props> = ({
|
|||||||
showRemoteBubble={showRemoteBubble}
|
showRemoteBubble={showRemoteBubble}
|
||||||
currentTime={currentTime}
|
currentTime={currentTime}
|
||||||
remainingTime={remainingTime}
|
remainingTime={remainingTime}
|
||||||
showSkipSegmentButton={showSkipSegmentButton}
|
showSkipButton={showSkipButton}
|
||||||
skipSegmentButtonText={skipSegmentButtonText}
|
showSkipCreditButton={showSkipCreditButton}
|
||||||
showSkipOutroButton={showSkipOutroButton}
|
|
||||||
skipOutroButtonText={skipOutroButtonText}
|
|
||||||
hasContentAfterCredits={hasContentAfterCredits}
|
hasContentAfterCredits={hasContentAfterCredits}
|
||||||
onSkipSegment={onSkipSegment}
|
skipIntro={skipIntro}
|
||||||
onSkipOutro={onSkipOutro}
|
skipCredit={skipCredit}
|
||||||
nextItem={nextItem}
|
nextItem={nextItem}
|
||||||
handleNextEpisodeAutoPlay={handleNextEpisodeAutoPlay}
|
handleNextEpisodeAutoPlay={handleNextEpisodeAutoPlay}
|
||||||
handleNextEpisodeManual={handleNextEpisodeManual}
|
handleNextEpisodeManual={handleNextEpisodeManual}
|
||||||
|
|||||||
109
hooks/useCreditSkipper.ts
Normal file
109
hooks/useCreditSkipper.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { Api } from "@jellyfin/sdk";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { DownloadedItem } from "@/providers/Downloads/types";
|
||||||
|
import { useSegments } from "@/utils/segments";
|
||||||
|
import { msToSeconds, secondsToMs } from "@/utils/time";
|
||||||
|
import { useHaptic } from "./useHaptic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to handle skipping credits in a media player.
|
||||||
|
* The player reports time values in milliseconds.
|
||||||
|
*/
|
||||||
|
export const useCreditSkipper = (
|
||||||
|
itemId: string,
|
||||||
|
currentTime: number,
|
||||||
|
seek: (ms: number) => void,
|
||||||
|
play: () => void,
|
||||||
|
isOffline = false,
|
||||||
|
api: Api | null = null,
|
||||||
|
downloadedFiles: DownloadedItem[] | undefined = undefined,
|
||||||
|
totalDuration?: number,
|
||||||
|
) => {
|
||||||
|
const [showSkipCreditButton, setShowSkipCreditButton] = useState(false);
|
||||||
|
const lightHapticFeedback = useHaptic("light");
|
||||||
|
|
||||||
|
// Convert ms to seconds for comparison with timestamps
|
||||||
|
const currentTimeSeconds = msToSeconds(currentTime);
|
||||||
|
|
||||||
|
const totalDurationInSeconds =
|
||||||
|
totalDuration != null ? msToSeconds(totalDuration) : undefined;
|
||||||
|
|
||||||
|
// Regular function (not useCallback) to match useIntroSkipper pattern
|
||||||
|
const wrappedSeek = (seconds: number) => {
|
||||||
|
seek(secondsToMs(seconds));
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: segments } = useSegments(
|
||||||
|
itemId,
|
||||||
|
isOffline,
|
||||||
|
downloadedFiles,
|
||||||
|
api,
|
||||||
|
);
|
||||||
|
const creditTimestamps = segments?.creditSegments?.[0];
|
||||||
|
|
||||||
|
// Determine if there's content after credits (credits don't extend to video end)
|
||||||
|
// Use a 5-second buffer to account for timing discrepancies
|
||||||
|
const hasContentAfterCredits = (() => {
|
||||||
|
if (
|
||||||
|
!creditTimestamps ||
|
||||||
|
totalDurationInSeconds == null ||
|
||||||
|
!Number.isFinite(totalDurationInSeconds)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const creditsEndToVideoEnd =
|
||||||
|
totalDurationInSeconds - creditTimestamps.endTime;
|
||||||
|
// If credits end more than 5 seconds before video ends, there's content after
|
||||||
|
return creditsEndToVideoEnd > 5;
|
||||||
|
})();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (creditTimestamps) {
|
||||||
|
const shouldShow =
|
||||||
|
currentTimeSeconds > creditTimestamps.startTime &&
|
||||||
|
currentTimeSeconds < creditTimestamps.endTime;
|
||||||
|
|
||||||
|
setShowSkipCreditButton(shouldShow);
|
||||||
|
} else {
|
||||||
|
// Reset button state when no credit timestamps exist
|
||||||
|
if (showSkipCreditButton) {
|
||||||
|
setShowSkipCreditButton(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [creditTimestamps, currentTimeSeconds, showSkipCreditButton]);
|
||||||
|
|
||||||
|
const skipCredit = useCallback(() => {
|
||||||
|
if (!creditTimestamps) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
lightHapticFeedback();
|
||||||
|
|
||||||
|
// Calculate the target seek position
|
||||||
|
let seekTarget = creditTimestamps.endTime;
|
||||||
|
|
||||||
|
// If we have total duration, ensure we don't seek past the end of the video.
|
||||||
|
// Some media sources report credit end times that exceed the actual video duration,
|
||||||
|
// which causes the player to pause/stop when seeking past the end.
|
||||||
|
// Leave a small buffer (2 seconds) to trigger the natural end-of-video flow
|
||||||
|
// (next episode countdown, etc.) instead of an abrupt pause.
|
||||||
|
if (totalDurationInSeconds && seekTarget >= totalDurationInSeconds) {
|
||||||
|
seekTarget = Math.max(0, totalDurationInSeconds - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
wrappedSeek(seekTarget);
|
||||||
|
setTimeout(() => {
|
||||||
|
play();
|
||||||
|
}, 200);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[CREDIT_SKIPPER] Error skipping credit", error);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
creditTimestamps,
|
||||||
|
lightHapticFeedback,
|
||||||
|
wrappedSeek,
|
||||||
|
play,
|
||||||
|
totalDurationInSeconds,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { showSkipCreditButton, skipCredit, hasContentAfterCredits };
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
|
import { getDownloadedItemById } from "@/providers/Downloads/database";
|
||||||
import { usePlaySettings } from "@/providers/PlaySettingsProvider";
|
import { usePlaySettings } from "@/providers/PlaySettingsProvider";
|
||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
|
|
||||||
@@ -15,12 +16,27 @@ export const useDownloadedFileOpener = () => {
|
|||||||
console.error("Attempted to open a file without an ID.");
|
console.error("Attempted to open a file without an ID.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const downloadedItem = getDownloadedItemById(item.Id);
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
itemId: item.Id,
|
itemId: item.Id,
|
||||||
offline: "true",
|
offline: "true",
|
||||||
playbackPosition:
|
playbackPosition:
|
||||||
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (downloadedItem?.userData?.audioStreamIndex !== undefined) {
|
||||||
|
queryParams.set(
|
||||||
|
"audioIndex",
|
||||||
|
downloadedItem.userData.audioStreamIndex.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (downloadedItem?.userData?.subtitleStreamIndex !== undefined) {
|
||||||
|
queryParams.set(
|
||||||
|
"subtitleIndex",
|
||||||
|
downloadedItem.userData.subtitleStreamIndex.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
router.push(`/player/direct-player?${queryParams.toString()}`);
|
router.push(`/player/direct-player?${queryParams.toString()}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
68
hooks/useIntroSkipper.ts
Normal file
68
hooks/useIntroSkipper.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { Api } from "@jellyfin/sdk";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { DownloadedItem } from "@/providers/Downloads/types";
|
||||||
|
import { useSegments } from "@/utils/segments";
|
||||||
|
import { msToSeconds, secondsToMs } from "@/utils/time";
|
||||||
|
import { useHaptic } from "./useHaptic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to handle skipping intros in a media player.
|
||||||
|
* MPV player uses milliseconds for time.
|
||||||
|
*
|
||||||
|
* @param {number} currentTime - The current playback time in milliseconds.
|
||||||
|
*/
|
||||||
|
export const useIntroSkipper = (
|
||||||
|
itemId: string,
|
||||||
|
currentTime: number,
|
||||||
|
seek: (ms: number) => void,
|
||||||
|
play: () => void,
|
||||||
|
isOffline = false,
|
||||||
|
api: Api | null = null,
|
||||||
|
downloadedFiles: DownloadedItem[] | undefined = undefined,
|
||||||
|
) => {
|
||||||
|
const [showSkipButton, setShowSkipButton] = useState(false);
|
||||||
|
// Convert ms to seconds for comparison with timestamps
|
||||||
|
const currentTimeSeconds = msToSeconds(currentTime);
|
||||||
|
const lightHapticFeedback = useHaptic("light");
|
||||||
|
|
||||||
|
const wrappedSeek = (seconds: number) => {
|
||||||
|
seek(secondsToMs(seconds));
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: segments } = useSegments(
|
||||||
|
itemId,
|
||||||
|
isOffline,
|
||||||
|
downloadedFiles,
|
||||||
|
api,
|
||||||
|
);
|
||||||
|
const introTimestamps = segments?.introSegments?.[0];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (introTimestamps) {
|
||||||
|
const shouldShow =
|
||||||
|
currentTimeSeconds > introTimestamps.startTime &&
|
||||||
|
currentTimeSeconds < introTimestamps.endTime;
|
||||||
|
|
||||||
|
setShowSkipButton(shouldShow);
|
||||||
|
} else {
|
||||||
|
if (showSkipButton) {
|
||||||
|
setShowSkipButton(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [introTimestamps, currentTimeSeconds, showSkipButton]);
|
||||||
|
|
||||||
|
const skipIntro = useCallback(() => {
|
||||||
|
if (!introTimestamps) return;
|
||||||
|
try {
|
||||||
|
lightHapticFeedback();
|
||||||
|
wrappedSeek(introTimestamps.endTime);
|
||||||
|
setTimeout(() => {
|
||||||
|
play();
|
||||||
|
}, 200);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[INTRO_SKIPPER] Error skipping intro", error);
|
||||||
|
}
|
||||||
|
}, [introTimestamps, lightHapticFeedback, wrappedSeek, play]);
|
||||||
|
|
||||||
|
return { showSkipButton, skipIntro };
|
||||||
|
};
|
||||||
@@ -186,6 +186,20 @@ export const usePlaybackManager = ({
|
|||||||
: playedPercentage,
|
: playedPercentage,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Sync selected audio/subtitle tracks so next playback resumes with
|
||||||
|
// the same tracks the user had active — but only for non-transcoded
|
||||||
|
// downloads where the user can freely switch tracks.
|
||||||
|
userData: localItem.userData.isTranscoded
|
||||||
|
? localItem.userData
|
||||||
|
: {
|
||||||
|
...localItem.userData,
|
||||||
|
audioStreamIndex:
|
||||||
|
playbackProgressInfo.AudioStreamIndex ??
|
||||||
|
localItem.userData.audioStreamIndex,
|
||||||
|
subtitleStreamIndex:
|
||||||
|
playbackProgressInfo.SubtitleStreamIndex ??
|
||||||
|
localItem.userData.subtitleStreamIndex,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
// Force invalidate queries so they refetch from updated local database
|
// Force invalidate queries so they refetch from updated local database
|
||||||
queryClient.invalidateQueries({ queryKey: ["item", itemId] });
|
queryClient.invalidateQueries({ queryKey: ["item", itemId] });
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
||||||
import { MediaTimeSegment } from "@/providers/Downloads/types";
|
|
||||||
import { SegmentSkipMode, useSettings } from "@/utils/atoms/settings";
|
|
||||||
import { useHaptic } from "./useHaptic";
|
|
||||||
|
|
||||||
type SegmentType = "Intro" | "Outro" | "Recap" | "Commercial" | "Preview";
|
|
||||||
|
|
||||||
const SEGMENT_TO_SETTING: Record<
|
|
||||||
SegmentType,
|
|
||||||
"skipIntro" | "skipOutro" | "skipRecap" | "skipCommercial" | "skipPreview"
|
|
||||||
> = {
|
|
||||||
Intro: "skipIntro",
|
|
||||||
Outro: "skipOutro",
|
|
||||||
Recap: "skipRecap",
|
|
||||||
Commercial: "skipCommercial",
|
|
||||||
Preview: "skipPreview",
|
|
||||||
};
|
|
||||||
|
|
||||||
interface UseSegmentSkipperProps {
|
|
||||||
segments: MediaTimeSegment[];
|
|
||||||
segmentType: SegmentType;
|
|
||||||
currentTime: number;
|
|
||||||
totalDuration?: number;
|
|
||||||
seek: (time: number) => void;
|
|
||||||
isPaused: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UseSegmentSkipperReturn {
|
|
||||||
currentSegment: MediaTimeSegment | null;
|
|
||||||
skipSegment: (useHaptics?: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic hook to handle all media segment types (intro, outro, recap, commercial, preview)
|
|
||||||
* Supports three modes: 'none' (disabled), 'ask' (show button), 'auto' (auto-skip)
|
|
||||||
*/
|
|
||||||
export const useSegmentSkipper = ({
|
|
||||||
segments,
|
|
||||||
segmentType,
|
|
||||||
currentTime,
|
|
||||||
totalDuration,
|
|
||||||
seek,
|
|
||||||
isPaused,
|
|
||||||
}: UseSegmentSkipperProps): UseSegmentSkipperReturn => {
|
|
||||||
const { settings } = useSettings();
|
|
||||||
const haptic = useHaptic();
|
|
||||||
const autoSkipTriggeredRef = useRef<string | null>(null);
|
|
||||||
|
|
||||||
const skipMode: SegmentSkipMode =
|
|
||||||
settings?.[SEGMENT_TO_SETTING[segmentType]] ?? "none";
|
|
||||||
|
|
||||||
const currentSegment = useMemo(
|
|
||||||
() =>
|
|
||||||
segments.find(
|
|
||||||
(s) => currentTime >= s.startTime && currentTime < s.endTime,
|
|
||||||
) ?? null,
|
|
||||||
[segments, currentTime],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Refs let the auto-skip effect avoid re-running when skipSegment/haptic
|
|
||||||
// identities change (haptic is unstable when disabled).
|
|
||||||
const seekRef = useRef(seek);
|
|
||||||
const hapticRef = useRef(haptic);
|
|
||||||
useEffect(() => {
|
|
||||||
seekRef.current = seek;
|
|
||||||
hapticRef.current = haptic;
|
|
||||||
});
|
|
||||||
|
|
||||||
const skipSegment = useCallback(
|
|
||||||
(useHaptics = true) => {
|
|
||||||
if (!currentSegment || skipMode === "none") return;
|
|
||||||
|
|
||||||
// Outro endTime sometimes exceeds the actual file duration. Keep a 2s
|
|
||||||
// buffer so the player's natural end-of-video flow (next-episode
|
|
||||||
// countdown, etc.) still fires instead of stalling at the exact end.
|
|
||||||
let target = currentSegment.endTime;
|
|
||||||
if (
|
|
||||||
segmentType === "Outro" &&
|
|
||||||
totalDuration != null &&
|
|
||||||
Number.isFinite(totalDuration) &&
|
|
||||||
target >= totalDuration
|
|
||||||
) {
|
|
||||||
target = Math.max(0, totalDuration - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
seekRef.current(target);
|
|
||||||
|
|
||||||
if (useHaptics) hapticRef.current();
|
|
||||||
},
|
|
||||||
[currentSegment, segmentType, totalDuration, skipMode],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (skipMode !== "auto" || isPaused || !currentSegment) {
|
|
||||||
if (!currentSegment) autoSkipTriggeredRef.current = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const segmentId = `${currentSegment.startTime}-${currentSegment.endTime}`;
|
|
||||||
if (autoSkipTriggeredRef.current === segmentId) return;
|
|
||||||
autoSkipTriggeredRef.current = segmentId;
|
|
||||||
skipSegment(false);
|
|
||||||
}, [currentSegment, skipMode, isPaused, skipSegment]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentSegment: skipMode === "none" ? null : currentSegment,
|
|
||||||
skipSegment,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -171,11 +171,7 @@ final class MPVLayerRenderer {
|
|||||||
// Enable composite OSD mode - renders subtitles directly onto video frames using GPU
|
// Enable composite OSD mode - renders subtitles directly onto video frames using GPU
|
||||||
// This is better for PiP as subtitles are baked into the video
|
// This is better for PiP as subtitles are baked into the video
|
||||||
// NOTE: Must be set BEFORE the #if targetEnvironment check or tvOS will freeze on player exit
|
// NOTE: Must be set BEFORE the #if targetEnvironment check or tvOS will freeze on player exit
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
checkError(mpv_set_option_string(handle, "avfoundation-composite-osd", "no"))
|
|
||||||
#else
|
|
||||||
checkError(mpv_set_option_string(handle, "avfoundation-composite-osd", "yes"))
|
checkError(mpv_set_option_string(handle, "avfoundation-composite-osd", "yes"))
|
||||||
#endif
|
|
||||||
|
|
||||||
// Hardware decoding with VideoToolbox
|
// Hardware decoding with VideoToolbox
|
||||||
// On simulator, use software decoding since VideoToolbox is not available
|
// On simulator, use software decoding since VideoToolbox is not available
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -15,7 +15,6 @@
|
|||||||
"android:tv": "cross-env EXPO_TV=1 expo run:android",
|
"android:tv": "cross-env EXPO_TV=1 expo run:android",
|
||||||
"build:android:local": "cd android && cross-env NODE_ENV=production ./gradlew assembleRelease",
|
"build:android:local": "cd android && cross-env NODE_ENV=production ./gradlew assembleRelease",
|
||||||
"ios:unsigned-build": "cross-env EXPO_TV=0 bun scripts/ios/build-ios.ts --production",
|
"ios:unsigned-build": "cross-env EXPO_TV=0 bun scripts/ios/build-ios.ts --production",
|
||||||
"ios:unsigned-build:tv": "cross-env EXPO_TV=1 bun scripts/ios/build-ios.ts --production",
|
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"typecheck": "node scripts/typecheck.js",
|
"typecheck": "node scripts/typecheck.js",
|
||||||
"check": "biome check . --max-diagnostics 1000",
|
"check": "biome check . --max-diagnostics 1000",
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
"@gorhom/bottom-sheet": "5.2.8",
|
"@gorhom/bottom-sheet": "5.2.8",
|
||||||
"@jellyfin/sdk": "^0.13.0",
|
"@jellyfin/sdk": "^0.13.0",
|
||||||
"@react-native-community/netinfo": "^12.0.0",
|
"@react-native-community/netinfo": "^11.4.1",
|
||||||
"@react-navigation/material-top-tabs": "7.4.9",
|
"@react-navigation/material-top-tabs": "7.4.9",
|
||||||
"@react-navigation/native": "^7.0.14",
|
"@react-navigation/native": "^7.0.14",
|
||||||
"@shopify/flash-list": "2.0.2",
|
"@shopify/flash-list": "2.0.2",
|
||||||
@@ -71,14 +70,14 @@
|
|||||||
"expo-system-ui": "~6.0.9",
|
"expo-system-ui": "~6.0.9",
|
||||||
"expo-task-manager": "14.0.9",
|
"expo-task-manager": "14.0.9",
|
||||||
"expo-web-browser": "~15.0.10",
|
"expo-web-browser": "~15.0.10",
|
||||||
"i18next": "^26.0.0",
|
"i18next": "^25.0.0",
|
||||||
"jotai": "2.16.2",
|
"jotai": "2.16.2",
|
||||||
"lodash": "4.17.23",
|
"lodash": "4.17.23",
|
||||||
"nativewind": "^2.0.11",
|
"nativewind": "^2.0.11",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-i18next": "17.0.8",
|
"react-i18next": "16.5.4",
|
||||||
"react-native": "0.81.5",
|
"react-native": "0.81.5",
|
||||||
"react-native-awesome-slider": "^2.9.0",
|
"react-native-awesome-slider": "^2.9.0",
|
||||||
"react-native-bottom-tabs": "1.1.0",
|
"react-native-bottom-tabs": "1.1.0",
|
||||||
@@ -118,16 +117,16 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.28.6",
|
"@babel/core": "7.28.6",
|
||||||
"@biomejs/biome": "2.3.11",
|
"@biomejs/biome": "2.3.11",
|
||||||
"@react-native-community/cli": "20.1.3",
|
"@react-native-community/cli": "20.1.1",
|
||||||
"@react-native-tvos/config-tv": "0.1.6",
|
"@react-native-tvos/config-tv": "0.1.4",
|
||||||
"@types/jest": "29.5.14",
|
"@types/jest": "29.5.14",
|
||||||
"@types/lodash": "4.17.23",
|
"@types/lodash": "4.17.23",
|
||||||
"@types/react": "19.1.17",
|
"@types/react": "19.1.17",
|
||||||
"@types/react-test-renderer": "19.1.0",
|
"@types/react-test-renderer": "19.1.0",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"expo-doctor": "1.19.7",
|
"expo-doctor": "1.17.14",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"lint-staged": "17.0.5",
|
"lint-staged": "16.2.7",
|
||||||
"react-test-renderer": "19.2.3",
|
"react-test-renderer": "19.2.3",
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ export interface MediaTimeSegment {
|
|||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Segment {
|
||||||
|
startTime: number;
|
||||||
|
endTime: number;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
/** Represents a single downloaded media item with all necessary metadata for offline playback. */
|
/** Represents a single downloaded media item with all necessary metadata for offline playback. */
|
||||||
export interface DownloadedItem {
|
export interface DownloadedItem {
|
||||||
/** The Jellyfin item DTO. */
|
/** The Jellyfin item DTO. */
|
||||||
@@ -50,12 +56,6 @@ export interface DownloadedItem {
|
|||||||
introSegments?: MediaTimeSegment[];
|
introSegments?: MediaTimeSegment[];
|
||||||
/** The credit segments for the item. */
|
/** The credit segments for the item. */
|
||||||
creditSegments?: MediaTimeSegment[];
|
creditSegments?: MediaTimeSegment[];
|
||||||
/** The recap segments for the item. */
|
|
||||||
recapSegments?: MediaTimeSegment[];
|
|
||||||
/** The commercial segments for the item. */
|
|
||||||
commercialSegments?: MediaTimeSegment[];
|
|
||||||
/** The preview segments for the item. */
|
|
||||||
previewSegments?: MediaTimeSegment[];
|
|
||||||
/** The user data for the item. */
|
/** The user data for the item. */
|
||||||
userData: UserData;
|
userData: UserData;
|
||||||
}
|
}
|
||||||
@@ -144,12 +144,6 @@ export type JobStatus = {
|
|||||||
introSegments?: MediaTimeSegment[];
|
introSegments?: MediaTimeSegment[];
|
||||||
/** Pre-downloaded credit segments (optional) - downloaded before video starts */
|
/** Pre-downloaded credit segments (optional) - downloaded before video starts */
|
||||||
creditSegments?: MediaTimeSegment[];
|
creditSegments?: MediaTimeSegment[];
|
||||||
/** Pre-downloaded recap segments (optional) - downloaded before video starts */
|
|
||||||
recapSegments?: MediaTimeSegment[];
|
|
||||||
/** Pre-downloaded commercial segments (optional) - downloaded before video starts */
|
|
||||||
commercialSegments?: MediaTimeSegment[];
|
|
||||||
/** Pre-downloaded preview segments (optional) - downloaded before video starts */
|
|
||||||
previewSegments?: MediaTimeSegment[];
|
|
||||||
/** The audio stream index selected for this download */
|
/** The audio stream index selected for this download */
|
||||||
audioStreamIndex?: number;
|
audioStreamIndex?: number;
|
||||||
/** The subtitle stream index selected for this download */
|
/** The subtitle stream index selected for this download */
|
||||||
|
|||||||
@@ -5,13 +5,10 @@ import {
|
|||||||
type ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import { BackHandler, Platform } from "react-native";
|
|
||||||
|
|
||||||
interface ModalOptions {
|
interface ModalOptions {
|
||||||
enableDynamicSizing?: boolean;
|
enableDynamicSizing?: boolean;
|
||||||
snapPoints?: (string | number)[];
|
snapPoints?: (string | number)[];
|
||||||
@@ -76,25 +73,6 @@ export const GlobalModalProvider: React.FC<GlobalModalProviderProps> = ({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (Platform.OS !== "android") return;
|
|
||||||
|
|
||||||
const onBackPress = () => {
|
|
||||||
if (isVisible) {
|
|
||||||
hideModal();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const subscription = BackHandler.addEventListener(
|
|
||||||
"hardwareBackPress",
|
|
||||||
onBackPress,
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => subscription.remove();
|
|
||||||
}, [isVisible, hideModal]);
|
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
showModal,
|
showModal,
|
||||||
hideModal,
|
hideModal,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"username_placeholder": "Benutzername",
|
"username_placeholder": "Benutzername",
|
||||||
"password_placeholder": "Passwort",
|
"password_placeholder": "Passwort",
|
||||||
"login_button": "Anmelden",
|
"login_button": "Anmelden",
|
||||||
"quick_connect": "Quick Connect",
|
"quick_connect": "Schnellverbindung",
|
||||||
"enter_code_to_login": "Gib den Code {{code}} ein, um dich anzumelden",
|
"enter_code_to_login": "Gib den Code {{code}} ein, um dich anzumelden",
|
||||||
"failed_to_initiate_quick_connect": "Fehler beim Initiieren der Schnellverbindung",
|
"failed_to_initiate_quick_connect": "Fehler beim Initiieren der Schnellverbindung",
|
||||||
"got_it": "Verstanden",
|
"got_it": "Verstanden",
|
||||||
@@ -30,48 +30,48 @@
|
|||||||
"connect_button": "Verbinden",
|
"connect_button": "Verbinden",
|
||||||
"previous_servers": "Vorherige Server",
|
"previous_servers": "Vorherige Server",
|
||||||
"clear_button": "Löschen",
|
"clear_button": "Löschen",
|
||||||
"swipe_to_remove": "Wischen, um zu entfernen",
|
"swipe_to_remove": "Swipe to remove",
|
||||||
"search_for_local_servers": "Nach lokalen Servern suchen",
|
"search_for_local_servers": "Nach lokalen Servern suchen",
|
||||||
"searching": "Suche...",
|
"searching": "Suche...",
|
||||||
"servers": "Server",
|
"servers": "Server",
|
||||||
"saved": "Gespeichert",
|
"saved": "Saved",
|
||||||
"session_expired": "Sitzung abgelaufen",
|
"session_expired": "Session Expired",
|
||||||
"please_login_again": "Ihre Sitzung ist abgelaufen. Bitte erneut anmelden.",
|
"please_login_again": "Your saved session has expired. Please log in again.",
|
||||||
"remove_saved_login": "Gespeicherte Zugangsdaten entfernen",
|
"remove_saved_login": "Remove Saved Login",
|
||||||
"remove_saved_login_description": "Hiermit werden ihre gespeicherten Zugangsdaten für diesen Server entfernt. Sie müssen sich dann erneut anmelden.",
|
"remove_saved_login_description": "This will remove your saved credentials for this server. You'll need to enter your username and password again next time.",
|
||||||
"accounts_count": "{{count}} Konten",
|
"accounts_count": "{{count}} accounts",
|
||||||
"select_account": "Konto auswählen",
|
"select_account": "Select Account",
|
||||||
"add_account": "Konto hinzufügen",
|
"add_account": "Add Account",
|
||||||
"remove_account_description": "Hiermit werden die gespeicherten Zugangsdaten für {{username}} entfernt."
|
"remove_account_description": "This will remove the saved credentials for {{username}}."
|
||||||
},
|
},
|
||||||
"save_account": {
|
"save_account": {
|
||||||
"title": "Konto speichern",
|
"title": "Save Account",
|
||||||
"save_for_later": "Dieses Konto speichern",
|
"save_for_later": "Save this account",
|
||||||
"security_option": "Sicherheitseinstellung",
|
"security_option": "Security Option",
|
||||||
"no_protection": "Keine",
|
"no_protection": "No protection",
|
||||||
"no_protection_desc": "Schnellanmeldung ohne Authentifizierung",
|
"no_protection_desc": "Quick login without authentication",
|
||||||
"pin_code": "PIN",
|
"pin_code": "PIN code",
|
||||||
"pin_code_desc": "4-stellige PIN bei Konto-Wechsel erforderlich",
|
"pin_code_desc": "4-digit PIN required when switching",
|
||||||
"password": "Passwort wiederholen",
|
"password": "Re-enter password",
|
||||||
"password_desc": "Passwort bei Konto-Wechsel erforderlich",
|
"password_desc": "Password required when switching",
|
||||||
"save_button": "Speichern",
|
"save_button": "Save",
|
||||||
"cancel_button": "Abbrechen"
|
"cancel_button": "Cancel"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"enter_pin": "PIN eingeben",
|
"enter_pin": "Enter PIN",
|
||||||
"enter_pin_for": "PIN für {{username}} eingeben",
|
"enter_pin_for": "Enter PIN for {{username}}",
|
||||||
"enter_4_digits": "4 Ziffern eingeben",
|
"enter_4_digits": "Enter 4 digits",
|
||||||
"invalid_pin": "Ungültige PIN",
|
"invalid_pin": "Invalid PIN",
|
||||||
"setup_pin": "PIN festlegen",
|
"setup_pin": "Set Up PIN",
|
||||||
"confirm_pin": "PIN bestätigen",
|
"confirm_pin": "Confirm PIN",
|
||||||
"pins_dont_match": "PIN stimmt nicht überein",
|
"pins_dont_match": "PINs don't match",
|
||||||
"forgot_pin": "PIN vergessen?",
|
"forgot_pin": "Forgot PIN?",
|
||||||
"forgot_pin_desc": "Ihre gespeicherten Zugangsdaten werden entfernt"
|
"forgot_pin_desc": "Your saved credentials will be removed"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"enter_password": "Passwort eingeben",
|
"enter_password": "Enter Password",
|
||||||
"enter_password_for": "Passwort für {{username}} eingeben",
|
"enter_password_for": "Enter password for {{username}}",
|
||||||
"invalid_password": "Ungültiges Passwort"
|
"invalid_password": "Invalid password"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"checking_server_connection": "Überprüfe Serververbindung...",
|
"checking_server_connection": "Überprüfe Serververbindung...",
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
"error_message": "Etwas ist schiefgelaufen.\nBitte melde dich ab und wieder an.",
|
"error_message": "Etwas ist schiefgelaufen.\nBitte melde dich ab und wieder an.",
|
||||||
"continue_watching": "Weiterschauen",
|
"continue_watching": "Weiterschauen",
|
||||||
"next_up": "Als nächstes",
|
"next_up": "Als nächstes",
|
||||||
"continue_and_next_up": "\"Weiterschauen\" und \"Als Nächstes\"",
|
"continue_and_next_up": "Continue & Next Up",
|
||||||
"recently_added_in": "Kürzlich hinzugefügt in {{libraryName}}",
|
"recently_added_in": "Kürzlich hinzugefügt in {{libraryName}}",
|
||||||
"suggested_movies": "Empfohlene Filme",
|
"suggested_movies": "Empfohlene Filme",
|
||||||
"suggested_episodes": "Empfohlene Episoden",
|
"suggested_episodes": "Empfohlene Episoden",
|
||||||
@@ -120,36 +120,36 @@
|
|||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Aussehen",
|
"title": "Aussehen",
|
||||||
"merge_next_up_continue_watching": "\"Weiterschauen\" und \"Als Nächstes\" kombinieren",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
"hide_remote_session_button": "Button für Remote-Sitzung ausblenden"
|
"hide_remote_session_button": "Hide Remote Session Button"
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"title": "Netzwerk",
|
"title": "Network",
|
||||||
"local_network": "Lokales Netzwerk",
|
"local_network": "Local Network",
|
||||||
"auto_switch_enabled": "Zuhause automatisch wechseln",
|
"auto_switch_enabled": "Auto-switch when at home",
|
||||||
"auto_switch_description": "Im WLAN Zuhause automatisch zu lokaler URL wechseln",
|
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
|
||||||
"local_url": "Lokale URL",
|
"local_url": "Local URL",
|
||||||
"local_url_hint": "Lokale Server-URL eingeben (zB. http://192.168.1.100:8096)",
|
"local_url_hint": "Enter your local server address (e.g., http://192.168.1.100:8096)",
|
||||||
"local_url_placeholder": "http://192.168.1.100:8096",
|
"local_url_placeholder": "http://192.168.1.100:8096",
|
||||||
"home_wifi_networks": "Private WLAN-Netze",
|
"home_wifi_networks": "Home WiFi Networks",
|
||||||
"add_current_network": "{{ssid}} hinzufügen",
|
"add_current_network": "Add \"{{ssid}}\"",
|
||||||
"not_connected_to_wifi": "Nicht mit WLAN verbunden",
|
"not_connected_to_wifi": "Not connected to WiFi",
|
||||||
"no_networks_configured": "Keine Netzwerke konfiguriert",
|
"no_networks_configured": "No networks configured",
|
||||||
"add_network_hint": "Füge dein privates WLAN-Netz hinzu um automatischen Wechsel zu aktivieren",
|
"add_network_hint": "Add your home WiFi network to enable auto-switching",
|
||||||
"current_wifi": "Aktuelles WLAN-Netz",
|
"current_wifi": "Current WiFi",
|
||||||
"using_url": "Verwendet",
|
"using_url": "Using",
|
||||||
"local": "Lokale URL",
|
"local": "Local URL",
|
||||||
"remote": "Remote URL",
|
"remote": "Remote URL",
|
||||||
"not_connected": "Nicht verbunden",
|
"not_connected": "Not connected",
|
||||||
"current_server": "Aktueller Server",
|
"current_server": "Current Server",
|
||||||
"remote_url": "Remote URL",
|
"remote_url": "Remote URL",
|
||||||
"active_url": "Aktive URL",
|
"active_url": "Active URL",
|
||||||
"not_configured": "Nicht konfiguriert",
|
"not_configured": "Not configured",
|
||||||
"network_added": "Netzwerk hinzugefügt",
|
"network_added": "Network added",
|
||||||
"network_already_added": "Netzwerk bereits hinzugefügt",
|
"network_already_added": "Network already added",
|
||||||
"no_wifi_connected": "Nicht mit WLAN verbunden",
|
"no_wifi_connected": "Not connected to WiFi",
|
||||||
"permission_denied": "Standortberechtigung nicht verfügbar",
|
"permission_denied": "Location permission denied",
|
||||||
"permission_denied_explanation": "Standortberechtigung ist nötig um WLAN-Netze für den automatischen Wechsel zu erkennen. Bitte in den Einstellungen aktivieren."
|
"permission_denied_explanation": "Location permission is required to detect WiFi network for auto-switching. Please enable it in Settings."
|
||||||
},
|
},
|
||||||
"user_info": {
|
"user_info": {
|
||||||
"user_info_title": "Benutzerinformationen",
|
"user_info_title": "Benutzerinformationen",
|
||||||
@@ -159,82 +159,82 @@
|
|||||||
"app_version": "App-Version"
|
"app_version": "App-Version"
|
||||||
},
|
},
|
||||||
"quick_connect": {
|
"quick_connect": {
|
||||||
"quick_connect_title": "Quick Connect",
|
"quick_connect_title": "Schnellverbindung",
|
||||||
"authorize_button": "Quick Connect autorisieren",
|
"authorize_button": "Schnellverbindung autorisieren",
|
||||||
"enter_the_quick_connect_code": "Quick Connect-Code eingeben...",
|
"enter_the_quick_connect_code": "Gib den Schnellverbindungscode ein...",
|
||||||
"success": "Erfolgreich verbunden",
|
"success": "Erfolg",
|
||||||
"quick_connect_autorized": "Quick Connect autorisiert",
|
"quick_connect_autorized": "Schnellverbindung autorisiert",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"invalid_code": "Ungültiger Code",
|
"invalid_code": "Ungültiger Code",
|
||||||
"authorize": "Autorisieren"
|
"authorize": "Autorisieren"
|
||||||
},
|
},
|
||||||
"media_controls": {
|
"media_controls": {
|
||||||
"media_controls_title": "Mediensteuerung",
|
"media_controls_title": "Mediensteuerung",
|
||||||
"forward_skip_length": "Vorspullänge",
|
"forward_skip_length": "Vorspulzeit",
|
||||||
"rewind_length": "Rückspullänge",
|
"rewind_length": "Rückspulzeit",
|
||||||
"seconds_unit": "s"
|
"seconds_unit": "s"
|
||||||
},
|
},
|
||||||
"gesture_controls": {
|
"gesture_controls": {
|
||||||
"gesture_controls_title": "Gestensteuerung",
|
"gesture_controls_title": "Gestensteuerung",
|
||||||
"horizontal_swipe_skip": "Horizontal Wischen zum Überspringen",
|
"horizontal_swipe_skip": "Horizontales Wischen zum Überspringen",
|
||||||
"horizontal_swipe_skip_description": "Wische links/rechts, wenn Steuerelemente ausgeblendet sind um zu überspringen",
|
"horizontal_swipe_skip_description": "Wische links/rechts, wenn Steuerelemente ausgeblendet werden um zu überspringen",
|
||||||
"left_side_brightness": "Helligkeitsregler Links",
|
"left_side_brightness": "Helligkeitskontrolle der linken Seite",
|
||||||
"left_side_brightness_description": "Links nach oben/unten wischen um Helligkeit anzupassen",
|
"left_side_brightness_description": "Wischen Sie auf der linken Seite nach oben/runter, um die Helligkeit anzupassen",
|
||||||
"right_side_volume": "Lautstärkeregler Rechts",
|
"right_side_volume": "Lautstärkeregelung der rechten Seite",
|
||||||
"right_side_volume_description": "Rechts nach oben/unten wischen um Lautstärke anzupassen",
|
"right_side_volume_description": "Auf der rechten Seite nach oben/unten wischen, um Lautstärke anzupassen",
|
||||||
"hide_volume_slider": "Lautstärkeregler ausblenden",
|
"hide_volume_slider": "Hide Volume Slider",
|
||||||
"hide_volume_slider_description": "Lautstärkeregler im Videoplayer ausblenden",
|
"hide_volume_slider_description": "Hide the volume slider in the video player",
|
||||||
"hide_brightness_slider": "Helligkeitsregler ausblenden",
|
"hide_brightness_slider": "Hide Brightness Slider",
|
||||||
"hide_brightness_slider_description": "Helligkeitsregler im Videoplayer ausblenden"
|
"hide_brightness_slider_description": "Hide the brightness slider in the video player"
|
||||||
},
|
},
|
||||||
"audio": {
|
"audio": {
|
||||||
"audio_title": "Audio",
|
"audio_title": "Audio",
|
||||||
"set_audio_track": "Audiospur aus dem vorherigen Element übernehmen",
|
"set_audio_track": "Audiospur aus dem vorherigen Element festlegen",
|
||||||
"audio_language": "Audio-Sprache",
|
"audio_language": "Audio-Sprache",
|
||||||
"audio_hint": "Standardsprache für Audio auswählen.",
|
"audio_hint": "Wähl die Standardsprache für Audio aus.",
|
||||||
"none": "Keine",
|
"none": "Keine",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
"transcode_mode": {
|
"transcode_mode": {
|
||||||
"title": "Audio-Transcoding",
|
"title": "Audio Transcoding",
|
||||||
"description": "Legt fest, wie Surround-Audio (7.1, TrueHD, DTS-HD) behandelt wird",
|
"description": "Controls how surround audio (7.1, TrueHD, DTS-HD) is handled",
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"stereo": "Stereo erzwingen",
|
"stereo": "Force Stereo",
|
||||||
"5_1": "5.1 erlauben",
|
"5_1": "Allow 5.1",
|
||||||
"passthrough": "Passthrough"
|
"passthrough": "Passthrough"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
"subtitle_title": "Untertitel",
|
"subtitle_title": "Untertitel",
|
||||||
"subtitle_hint": "Untertitel-Erscheinungsbild und Verhalten konfigurieren.",
|
"subtitle_hint": "Konfigurier die Untertitel-Präferenzen.",
|
||||||
"subtitle_language": "Untertitel-Sprache",
|
"subtitle_language": "Untertitel-Sprache",
|
||||||
"subtitle_mode": "Untertitel-Modus",
|
"subtitle_mode": "Untertitel-Modus",
|
||||||
"set_subtitle_track": "Untertitel-Spur aus dem vorherigen Element übernehmen",
|
"set_subtitle_track": "Untertitel-Spur aus dem vorherigen Element festlegen",
|
||||||
"subtitle_size": "Untertitel-Größe",
|
"subtitle_size": "Untertitel-Größe",
|
||||||
"none": "Keine",
|
"none": "Keine",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
"loading": "Lädt",
|
"loading": "Lädt",
|
||||||
"modes": {
|
"modes": {
|
||||||
"Default": "Standard",
|
"Default": "Standard",
|
||||||
"Smart": "Smart",
|
"Smart": "Intelligent",
|
||||||
"Always": "Immer",
|
"Always": "Immer",
|
||||||
"None": "Keine",
|
"None": "Keine",
|
||||||
"OnlyForced": "Nur erzwungene"
|
"OnlyForced": "Nur erzwungen"
|
||||||
},
|
},
|
||||||
"text_color": "Textfarbe",
|
"text_color": "Textfarbe",
|
||||||
"background_color": "Hintergrundfarbe",
|
"background_color": "Hintergrundfarbe",
|
||||||
"outline_color": "Konturfarbe",
|
"outline_color": "Konturfarbe",
|
||||||
"outline_thickness": "Konturdicke",
|
"outline_thickness": "Umriss Dicke",
|
||||||
"background_opacity": "Hintergrundtransparenz",
|
"background_opacity": "Hintergrundtransparenz",
|
||||||
"outline_opacity": "Konturtransparenz",
|
"outline_opacity": "Kontur-Deckkraft",
|
||||||
"bold_text": "Fettgedruckter Text",
|
"bold_text": "Bold Text",
|
||||||
"colors": {
|
"colors": {
|
||||||
"Black": "Schwarz",
|
"Black": "Schwarz",
|
||||||
"Gray": "Grau",
|
"Gray": "Grau",
|
||||||
"Silver": "Silber",
|
"Silver": "Silber",
|
||||||
"White": "Weiß",
|
"White": "Weiß",
|
||||||
"Maroon": "Rotbraun",
|
"Maroon": "Marotte",
|
||||||
"Red": "Rot",
|
"Red": "Rot",
|
||||||
"Fuchsia": "Magenta",
|
"Fuchsia": "Fuchsia",
|
||||||
"Yellow": "Gelb",
|
"Yellow": "Gelb",
|
||||||
"Olive": "Olivgrün",
|
"Olive": "Olivgrün",
|
||||||
"Green": "Grün",
|
"Green": "Grün",
|
||||||
@@ -251,29 +251,29 @@
|
|||||||
"Normal": "Normal",
|
"Normal": "Normal",
|
||||||
"Thick": "Dick"
|
"Thick": "Dick"
|
||||||
},
|
},
|
||||||
"subtitle_color": "Untertitelfarbe",
|
"subtitle_color": "Subtitle Color",
|
||||||
"subtitle_background_color": "Hintergrundfarbe",
|
"subtitle_background_color": "Background Color",
|
||||||
"subtitle_font": "Untertitel-Schriftart",
|
"subtitle_font": "Subtitle Font",
|
||||||
"ksplayer_title": "KSPlayer Einstellungen",
|
"ksplayer_title": "KSPlayer Settings",
|
||||||
"hardware_decode": "Hardware Decoding",
|
"hardware_decode": "Hardware Decoding",
|
||||||
"hardware_decode_description": "Hardwarebeschleunigung für Video Decoding verwenden. Deaktivieren wenn Wiedergabeprobleme auftreten."
|
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues."
|
||||||
},
|
},
|
||||||
"vlc_subtitles": {
|
"vlc_subtitles": {
|
||||||
"title": "VLC Untertitel-Einstellungen",
|
"title": "VLC Subtitle Settings",
|
||||||
"hint": "Anpassen des Untertitel-Erscheinungsbildes für VLC. Änderungen werden bei der nächsten Wiedergabe übernommen.",
|
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
|
||||||
"text_color": "Schriftfarbe",
|
"text_color": "Text Color",
|
||||||
"background_color": "Hintergrundfarbe",
|
"background_color": "Background Color",
|
||||||
"background_opacity": "Hintergrundtransparenz",
|
"background_opacity": "Background Opacity",
|
||||||
"outline_color": "Konturfarbe",
|
"outline_color": "Outline Color",
|
||||||
"outline_opacity": "Konturtransparenz",
|
"outline_opacity": "Outline Opacity",
|
||||||
"outline_thickness": "Konturdicke",
|
"outline_thickness": "Outline Thickness",
|
||||||
"bold": "Fettgedruckter Text",
|
"bold": "Bold Text",
|
||||||
"margin": "Unterer Abstand"
|
"margin": "Bottom Margin"
|
||||||
},
|
},
|
||||||
"video_player": {
|
"video_player": {
|
||||||
"title": "Videoplayer",
|
"title": "Video Player",
|
||||||
"video_player": "Videoplayer",
|
"video_player": "Video Player",
|
||||||
"video_player_description": "Videoplayer auf iOS auswählen.",
|
"video_player_description": "Choose which video player to use on iOS.",
|
||||||
"ksplayer": "KSPlayer",
|
"ksplayer": "KSPlayer",
|
||||||
"vlc": "VLC"
|
"vlc": "VLC"
|
||||||
},
|
},
|
||||||
@@ -282,7 +282,7 @@
|
|||||||
"video_orientation": "Videoausrichtung",
|
"video_orientation": "Videoausrichtung",
|
||||||
"orientation": "Ausrichtung",
|
"orientation": "Ausrichtung",
|
||||||
"orientations": {
|
"orientations": {
|
||||||
"DEFAULT": "Geräteausrichtung folgen",
|
"DEFAULT": "Standard",
|
||||||
"ALL": "Alle",
|
"ALL": "Alle",
|
||||||
"PORTRAIT": "Hochformat",
|
"PORTRAIT": "Hochformat",
|
||||||
"PORTRAIT_UP": "Hochformat oben",
|
"PORTRAIT_UP": "Hochformat oben",
|
||||||
@@ -294,54 +294,54 @@
|
|||||||
"UNKNOWN": "Unbekannt"
|
"UNKNOWN": "Unbekannt"
|
||||||
},
|
},
|
||||||
"safe_area_in_controls": "Sicherer Bereich in den Steuerungen",
|
"safe_area_in_controls": "Sicherer Bereich in den Steuerungen",
|
||||||
"video_player": "Videoplayer",
|
"video_player": "Video player",
|
||||||
"video_players": {
|
"video_players": {
|
||||||
"VLC_3": "VLC 3",
|
"VLC_3": "VLC 3",
|
||||||
"VLC_4": "VLC 4 (Experimentell + PiP)"
|
"VLC_4": "VLC 4 (Experimentell + PiP)"
|
||||||
},
|
},
|
||||||
"show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen",
|
"show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen",
|
||||||
"show_large_home_carousel": "Zeige große Startseiten-Übersicht (Beta)",
|
"show_large_home_carousel": "Zeige Großes Heimkarussell (Beta)",
|
||||||
"hide_libraries": "Bibliotheken ausblenden",
|
"hide_libraries": "Bibliotheken ausblenden",
|
||||||
"select_liraries_you_want_to_hide": "Bibliotheken auswählen die aus dem Bibliothekstab und der Startseite ausgeblendet werden sollen.",
|
"select_liraries_you_want_to_hide": "Wähl die Bibliotheken aus, die du im Bibliothekstab und auf der Startseite ausblenden möchtest.",
|
||||||
"disable_haptic_feedback": "Haptisches Feedback deaktivieren",
|
"disable_haptic_feedback": "Haptisches Feedback deaktivieren",
|
||||||
"default_quality": "Standardqualität",
|
"default_quality": "Standardqualität",
|
||||||
"default_playback_speed": "Standard-Wiedergabegeschwindigkeit",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
"auto_play_next_episode": "Automatisch nächste Episode abspielen",
|
"auto_play_next_episode": "Auto-play Next Episode",
|
||||||
"max_auto_play_episode_count": "Maximale automatisch abzuspielende Episodenanzahl",
|
"max_auto_play_episode_count": "Max. automatische Wiedergabe Episodenanzahl",
|
||||||
"disabled": "Deaktiviert"
|
"disabled": "Deaktiviert"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"downloads_title": "Downloads"
|
"downloads_title": "Downloads"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Musik",
|
"title": "Music",
|
||||||
"playback_title": "Wiedergabe",
|
"playback_title": "Playback",
|
||||||
"playback_description": "Konfigurieren, wie Musik abgespielt wird.",
|
"playback_description": "Configure how music is played.",
|
||||||
"prefer_downloaded": "Bevorzuge heruntergeladene Titel",
|
"prefer_downloaded": "Prefer Downloaded Songs",
|
||||||
"caching_title": "Caching",
|
"caching_title": "Caching",
|
||||||
"caching_description": "Automatisches Caching anstehender Titel für bessere Wiedergabe.",
|
"caching_description": "Automatically cache upcoming tracks for smoother playback.",
|
||||||
"lookahead_enabled": "Look-Ahead Caching aktivieren",
|
"lookahead_enabled": "Enable Look-Ahead Caching",
|
||||||
"lookahead_count": "Titel vorher in den Cache laden",
|
"lookahead_count": "Tracks to Pre-cache",
|
||||||
"max_cache_size": "Maximale Cache-Größe"
|
"max_cache_size": "Max Cache Size"
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"plugins_title": "Plugins",
|
"plugins_title": "Erweiterungen",
|
||||||
"jellyseerr": {
|
"jellyseerr": {
|
||||||
"jellyseerr_warning": "Diese Integration ist in einer frühen Entwicklungsphase und kann jederzeit geändert werden.",
|
"jellyseerr_warning": "Diese integration ist in einer frühen Entwicklungsphase. Erwarte Veränderungen.",
|
||||||
"server_url": "Server URL",
|
"server_url": "Server Adresse",
|
||||||
"server_url_hint": "Beispiel: http(s)://your-host.url\n(Port hinzufügen, falls erforderlich)",
|
"server_url_hint": "Beispiel: http(s)://your-host.url\n(Portnummer hinzufügen, falls erforderlich)",
|
||||||
"server_url_placeholder": "Seerr URL",
|
"server_url_placeholder": "Jellyseerr URL...",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"password_placeholder": "Passwort für Jellyfin Benutzer {{username}} eingeben",
|
"password_placeholder": "Passwort für Jellyfin Benutzer {{username}} eingeben",
|
||||||
"login_button": "Anmelden",
|
"login_button": "Anmelden",
|
||||||
"total_media_requests": "Gesamtanfragen",
|
"total_media_requests": "Gesamtanfragen",
|
||||||
"movie_quota_limit": "Film-Anfragelimit",
|
"movie_quota_limit": "Film-Anfragelimit",
|
||||||
"movie_quota_days": "Film-Anfragetagelimit",
|
"movie_quota_days": "Film-Anfragetage",
|
||||||
"tv_quota_limit": "Serien-Anfragelimit",
|
"tv_quota_limit": "TV-Anfragelimit",
|
||||||
"tv_quota_days": "Serien-Anfragetagelimit",
|
"tv_quota_days": "TV-Anfragetage",
|
||||||
"reset_jellyseerr_config_button": "Seerr-Konfiguration zurücksetzen",
|
"reset_jellyseerr_config_button": "Setze Jellyseerr-Konfiguration zurück",
|
||||||
"unlimited": "Unlimitiert",
|
"unlimited": "Unlimitiert",
|
||||||
"plus_n_more": "+{{n}} weitere",
|
"plus_n_more": "+{{n}} more",
|
||||||
"order_by": {
|
"order_by": {
|
||||||
"DEFAULT": "Standard",
|
"DEFAULT": "Standard",
|
||||||
"VOTE_COUNT_AND_AVERAGE": "Stimmenanzahl und Durchschnitt",
|
"VOTE_COUNT_AND_AVERAGE": "Stimmenanzahl und Durchschnitt",
|
||||||
@@ -352,71 +352,71 @@
|
|||||||
"enable_marlin_search": "Aktiviere Marlin Search",
|
"enable_marlin_search": "Aktiviere Marlin Search",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"server_url_placeholder": "http(s)://domain.org:port",
|
"server_url_placeholder": "http(s)://domain.org:port",
|
||||||
"marlin_search_hint": "URL für den Marlin Server eingeben. Die URL sollte http oder https enthalten und optional den Port.",
|
"marlin_search_hint": "Gib die URL für den Marlin Server ein. Die URL sollte http oder https enthalten und optional den Port.",
|
||||||
"read_more_about_marlin": "Erfahre mehr über Marlin.",
|
"read_more_about_marlin": "Erfahre mehr über Marlin.",
|
||||||
"save_button": "Speichern",
|
"save_button": "Speichern",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Gespeichert",
|
"saved": "Gespeichert",
|
||||||
"refreshed": "Einstellungen vom Server aktualisiert"
|
"refreshed": "Settings refreshed from server"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Einstellungen vom Server aktualisieren"
|
"refresh_from_server": "Refresh Settings from Server"
|
||||||
},
|
},
|
||||||
"streamystats": {
|
"streamystats": {
|
||||||
"enable_streamystats": "Streamystats aktivieren",
|
"enable_streamystats": "Enable Streamystats",
|
||||||
"disable_streamystats": "Streamystats deaktivieren",
|
"disable_streamystats": "Disable Streamystats",
|
||||||
"enable_search": "Zum Suchen verwenden",
|
"enable_search": "Use for Search",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"server_url_placeholder": "http(s)://streamystats.example.com",
|
"server_url_placeholder": "http(s)://streamystats.example.com",
|
||||||
"streamystats_search_hint": "URL für den Streamystats-Server eingeben.",
|
"streamystats_search_hint": "Enter the URL for your Streamystats server. The URL should include http or https and optionally the port.",
|
||||||
"read_more_about_streamystats": "Mehr über Streamystats erfahren.",
|
"read_more_about_streamystats": "Read More About Streamystats.",
|
||||||
"save_button": "Speichern",
|
"save_button": "Save",
|
||||||
"save": "Gespeichert",
|
"save": "Save",
|
||||||
"features_title": "Features",
|
"features_title": "Features",
|
||||||
"home_sections_title": "Startseitenbereiche",
|
"home_sections_title": "Home Sections",
|
||||||
"enable_movie_recommendations": "Filmempfehlungen",
|
"enable_movie_recommendations": "Movie Recommendations",
|
||||||
"enable_series_recommendations": "Serienempfehlungen",
|
"enable_series_recommendations": "Series Recommendations",
|
||||||
"enable_promoted_watchlists": "Empfohlene Merklisten",
|
"enable_promoted_watchlists": "Promoted Watchlists",
|
||||||
"hide_watchlists_tab": "Merklisten-Tab ausblenden",
|
"hide_watchlists_tab": "Hide Watchlists Tab",
|
||||||
"home_sections_hint": "Zeige personalisierte Empfehlungen und empfohlene Merklisten von Streamystats auf der Startseite.",
|
"home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.",
|
||||||
"recommended_movies": "Empfohlene Filme",
|
"recommended_movies": "Recommended Movies",
|
||||||
"recommended_series": "Empfohlene Serien",
|
"recommended_series": "Recommended Series",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Gespeichert",
|
"saved": "Saved",
|
||||||
"refreshed": "Einstellungen vom Server aktualisiert",
|
"refreshed": "Settings refreshed from server",
|
||||||
"disabled": "Streamystats deaktiviert"
|
"disabled": "Streamystats disabled"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Einstellungen vom Server aktualisieren"
|
"refresh_from_server": "Refresh Settings from Server"
|
||||||
},
|
},
|
||||||
"kefinTweaks": {
|
"kefinTweaks": {
|
||||||
"watchlist_enabler": "Merklisten-Integration aktivieren",
|
"watchlist_enabler": "Enable our Watchlist integration",
|
||||||
"watchlist_button": "Merklisten-Integration umschalten"
|
"watchlist_button": "Toggle Watchlist integration"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"storage_title": "Speicher",
|
"storage_title": "Speicher",
|
||||||
"app_usage": "App {{usedSpace}}%",
|
"app_usage": "App {{usedSpace}}%",
|
||||||
"device_usage": "Gerät {{availableSpace}}%",
|
"device_usage": "Gerät {{availableSpace}}%",
|
||||||
"size_used": "{{used}} von {{total}} genutzt",
|
"size_used": "{{used}} von {{total}} benutzt",
|
||||||
"delete_all_downloaded_files": "Alle heruntergeladenen Dateien löschen",
|
"delete_all_downloaded_files": "Alle Downloads löschen",
|
||||||
"music_cache_title": "Musik-Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Beim Anhören Titel automatisch in den Cache laden um bessere Wiedergabe und Offline-Wiedergabe zu ermöglichen",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"enable_music_cache": "Musik-Cache aktivieren",
|
"enable_music_cache": "Enable Music Cache",
|
||||||
"clear_music_cache": "Musik-Cache leeren",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
"music_cache_size": "{{size}} gechached",
|
"music_cache_size": "{{size}} cached",
|
||||||
"music_cache_cleared": "Musik-Cache geleert",
|
"music_cache_cleared": "Music cache cleared",
|
||||||
"delete_all_downloaded_songs": "Alle heruntergeladenen Titel löschen",
|
"delete_all_downloaded_songs": "Delete All Downloaded Songs",
|
||||||
"downloaded_songs_size": "{{size}} heruntergeladen",
|
"downloaded_songs_size": "{{size}} downloaded",
|
||||||
"downloaded_songs_deleted": "Heruntergeladene Titel gelöscht"
|
"downloaded_songs_deleted": "Downloaded songs deleted"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"title": "Einführung",
|
"title": "Intro ",
|
||||||
"show_intro": "Einführung anzeigen",
|
"show_intro": "Show intro",
|
||||||
"reset_intro": "Einführung zurücksetzen"
|
"reset_intro": "Reset intro"
|
||||||
},
|
},
|
||||||
"logs": {
|
"logs": {
|
||||||
"logs_title": "Logs",
|
"logs_title": "Logs",
|
||||||
"export_logs": "Logs exportieren",
|
"export_logs": "Export logs",
|
||||||
"click_for_more_info": "Für mehr Informationen klicken",
|
"click_for_more_info": "Click for more info",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
"no_logs_available": "Keine Logs verfügbar",
|
"no_logs_available": "Keine Logs verfügbar",
|
||||||
"delete_all_logs": "Alle Logs löschen"
|
"delete_all_logs": "Alle Logs löschen"
|
||||||
@@ -438,21 +438,21 @@
|
|||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"downloads_title": "Downloads",
|
"downloads_title": "Downloads",
|
||||||
"tvseries": "Serien",
|
"tvseries": "TV-Serien",
|
||||||
"movies": "Filme",
|
"movies": "Filme",
|
||||||
"queue": "Warteschlange",
|
"queue": "Warteschlange",
|
||||||
"other_media": "Andere Medien",
|
"other_media": "Andere Medien",
|
||||||
"queue_hint": "Warteschlange und aktive Downloads gehen verloren wenn die App neu gestartet wird",
|
"queue_hint": "Warteschlange und aktive Downloads gehen verloren bei App-Neustart",
|
||||||
"no_items_in_queue": "Keine Elemente in der Warteschlange",
|
"no_items_in_queue": "Keine Elemente in der Warteschlange",
|
||||||
"no_downloaded_items": "Keine heruntergeladenen Elemente",
|
"no_downloaded_items": "Keine heruntergeladenen Elemente",
|
||||||
"delete_all_movies_button": "Alle Filme löschen",
|
"delete_all_movies_button": "Alle Filme löschen",
|
||||||
"delete_all_tvseries_button": "Alle Serien löschen",
|
"delete_all_tvseries_button": "Alle TV-Serien löschen",
|
||||||
"delete_all_button": "Alles löschen",
|
"delete_all_button": "Alles löschen",
|
||||||
"delete_all_other_media_button": "Alle anderen Medien löschen",
|
"delete_all_other_media_button": "Andere Medien löschen",
|
||||||
"active_download": "Aktiver Download",
|
"active_download": "Aktiver Download",
|
||||||
"no_active_downloads": "Keine aktiven Downloads",
|
"no_active_downloads": "Keine aktiven Downloads",
|
||||||
"active_downloads": "Aktive Downloads",
|
"active_downloads": "Aktive Downloads",
|
||||||
"new_app_version_requires_re_download": "Neue App-Version erfordert erneutes Herunterladen",
|
"new_app_version_requires_re_download": "Die neue App-Version erfordert das erneute Herunterladen.",
|
||||||
"new_app_version_requires_re_download_description": "Die neue App-Version erfordert das erneute Herunterladen von Filmen und Serien. Bitte lösche alle heruntergeladenen Elemente und starte den Download erneut.",
|
"new_app_version_requires_re_download_description": "Die neue App-Version erfordert das erneute Herunterladen von Filmen und Serien. Bitte lösche alle heruntergeladenen Elemente und starte den Download erneut.",
|
||||||
"back": "Zurück",
|
"back": "Zurück",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
@@ -463,8 +463,8 @@
|
|||||||
"you_are_not_allowed_to_download_files": "Du hast keine Berechtigung, Dateien herunterzuladen",
|
"you_are_not_allowed_to_download_files": "Du hast keine Berechtigung, Dateien herunterzuladen",
|
||||||
"deleted_all_movies_successfully": "Alle Filme erfolgreich gelöscht!",
|
"deleted_all_movies_successfully": "Alle Filme erfolgreich gelöscht!",
|
||||||
"failed_to_delete_all_movies": "Fehler beim Löschen aller Filme",
|
"failed_to_delete_all_movies": "Fehler beim Löschen aller Filme",
|
||||||
"deleted_all_tvseries_successfully": "Alle Serien erfolgreich gelöscht!",
|
"deleted_all_tvseries_successfully": "Alle TV-Serien erfolgreich gelöscht!",
|
||||||
"failed_to_delete_all_tvseries": "Fehler beim Löschen aller Serien",
|
"failed_to_delete_all_tvseries": "Fehler beim Löschen aller TV-Serien",
|
||||||
"deleted_media_successfully": "Andere Medien erfolgreich gelöscht!",
|
"deleted_media_successfully": "Andere Medien erfolgreich gelöscht!",
|
||||||
"failed_to_delete_media": "Fehler beim Löschen anderer Medien",
|
"failed_to_delete_media": "Fehler beim Löschen anderer Medien",
|
||||||
"download_deleted": "Download gelöscht",
|
"download_deleted": "Download gelöscht",
|
||||||
@@ -486,7 +486,7 @@
|
|||||||
"all_files_folders_and_jobs_deleted_successfully": "Alle Dateien, Ordner und Jobs erfolgreich gelöscht",
|
"all_files_folders_and_jobs_deleted_successfully": "Alle Dateien, Ordner und Jobs erfolgreich gelöscht",
|
||||||
"failed_to_clean_cache_directory": "Fehler beim Bereinigen des Cache-Verzeichnisses",
|
"failed_to_clean_cache_directory": "Fehler beim Bereinigen des Cache-Verzeichnisses",
|
||||||
"could_not_get_download_url_for_item": "Download-URL für {{itemName}} konnte nicht geladen werden",
|
"could_not_get_download_url_for_item": "Download-URL für {{itemName}} konnte nicht geladen werden",
|
||||||
"go_to_downloads": "Zu Downloads gehen",
|
"go_to_downloads": "Gehe zu den Downloads",
|
||||||
"file_deleted": "{{item}} gelöscht"
|
"file_deleted": "{{item}} gelöscht"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,18 +499,18 @@
|
|||||||
"subtitle": "Untertitel",
|
"subtitle": "Untertitel",
|
||||||
"play": "Abspielen",
|
"play": "Abspielen",
|
||||||
"none": "Keine",
|
"none": "Keine",
|
||||||
"track": "Spur",
|
"track": "Track",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Cancel",
|
||||||
"delete": "Löschen",
|
"delete": "Delete",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"remove": "Entfernen",
|
"remove": "Remove",
|
||||||
"next": "Weiter",
|
"next": "Next",
|
||||||
"back": "Zurück",
|
"back": "Back",
|
||||||
"continue": "Fortsetzen",
|
"continue": "Continue",
|
||||||
"verifying": "Verifiziere..."
|
"verifying": "Verifying..."
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"search": "Suchen...",
|
"search": "Suche...",
|
||||||
"x_items": "{{count}} Elemente",
|
"x_items": "{{count}} Elemente",
|
||||||
"library": "Bibliothek",
|
"library": "Bibliothek",
|
||||||
"discover": "Entdecken",
|
"discover": "Entdecken",
|
||||||
@@ -521,33 +521,33 @@
|
|||||||
"episodes": "Episoden",
|
"episodes": "Episoden",
|
||||||
"collections": "Sammlungen",
|
"collections": "Sammlungen",
|
||||||
"actors": "Schauspieler",
|
"actors": "Schauspieler",
|
||||||
"artists": "Künstler",
|
"artists": "Artists",
|
||||||
"albums": "Alben",
|
"albums": "Albums",
|
||||||
"songs": "Titel",
|
"songs": "Songs",
|
||||||
"playlists": "Playlists",
|
"playlists": "Playlists",
|
||||||
"request_movies": "Film anfragen",
|
"request_movies": "Film anfragen",
|
||||||
"request_series": "Serie anfragen",
|
"request_series": "Serie anfragen",
|
||||||
"recently_added": "Kürzlich hinzugefügt",
|
"recently_added": "Kürzlich hinzugefügt",
|
||||||
"recent_requests": "Kürzlich angefragt",
|
"recent_requests": "Kürzlich angefragt",
|
||||||
"plex_watchlist": "Plex Merkliste",
|
"plex_watchlist": "Plex Watchlist",
|
||||||
"trending": "Beliebt",
|
"trending": "In den Trends",
|
||||||
"popular_movies": "Beliebte Filme",
|
"popular_movies": "Beliebte Filme",
|
||||||
"movie_genres": "Film-Genres",
|
"movie_genres": "Film-Genres",
|
||||||
"upcoming_movies": "Kommende Filme",
|
"upcoming_movies": "Kommende Filme",
|
||||||
"studios": "Studios",
|
"studios": "Studios",
|
||||||
"popular_tv": "Beliebte Serien",
|
"popular_tv": "Beliebte TV-Serien",
|
||||||
"tv_genres": "Serien-Genres",
|
"tv_genres": "TV-Serien-Genres",
|
||||||
"upcoming_tv": "Kommende Serien",
|
"upcoming_tv": "Kommende TV-Serien",
|
||||||
"networks": "Sender",
|
"networks": "Netzwerke",
|
||||||
"tmdb_movie_keyword": "TMDB Film-Schlüsselwort",
|
"tmdb_movie_keyword": "TMDB Film-Schlüsselwort",
|
||||||
"tmdb_movie_genre": "TMDB Film-Genre",
|
"tmdb_movie_genre": "TMDB Film-Genre",
|
||||||
"tmdb_tv_keyword": "TMDB Serien-Schlüsselwort",
|
"tmdb_tv_keyword": "TMDB TV-Serien-Schlüsselwort",
|
||||||
"tmdb_tv_genre": "TMDB Serien-Genre",
|
"tmdb_tv_genre": "TMDB TV-Serien-Genre",
|
||||||
"tmdb_search": "TMDB Suche",
|
"tmdb_search": "TMDB Suche",
|
||||||
"tmdb_studio": "TMDB Studio",
|
"tmdb_studio": "TMDB Studio",
|
||||||
"tmdb_network": "TMDB Netzwerk",
|
"tmdb_network": "TMDB Netzwerk",
|
||||||
"tmdb_movie_streaming_services": "TMDB Film-Streaming-Dienste",
|
"tmdb_movie_streaming_services": "TMDB Film-Streaming-Dienste",
|
||||||
"tmdb_tv_streaming_services": "TMDB Serien-Streaming-Dienste"
|
"tmdb_tv_streaming_services": "TMDB TV-Serien-Streaming-Dienste"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"no_results": "Keine Ergebnisse",
|
"no_results": "Keine Ergebnisse",
|
||||||
@@ -572,7 +572,7 @@
|
|||||||
"genres": "Genres",
|
"genres": "Genres",
|
||||||
"years": "Jahre",
|
"years": "Jahre",
|
||||||
"sort_by": "Sortieren nach",
|
"sort_by": "Sortieren nach",
|
||||||
"filter_by": "Filtern nach",
|
"filter_by": "Filter By",
|
||||||
"sort_order": "Sortierreihenfolge",
|
"sort_order": "Sortierreihenfolge",
|
||||||
"tags": "Tags"
|
"tags": "Tags"
|
||||||
}
|
}
|
||||||
@@ -585,7 +585,7 @@
|
|||||||
"boxsets": "Boxsets",
|
"boxsets": "Boxsets",
|
||||||
"playlists": "Wiedergabelisten",
|
"playlists": "Wiedergabelisten",
|
||||||
"noDataTitle": "Noch keine Favoriten",
|
"noDataTitle": "Noch keine Favoriten",
|
||||||
"noData": "Elemente als Favoriten markieren, um sie hier anzuzeigen."
|
"noData": "Markiere Elemente als Favoriten, damit sie hier für einen schnellen Zugriff angezeigt werden."
|
||||||
},
|
},
|
||||||
"custom_links": {
|
"custom_links": {
|
||||||
"no_links": "Keine Links"
|
"no_links": "Keine Links"
|
||||||
@@ -593,7 +593,7 @@
|
|||||||
"player": {
|
"player": {
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"failed_to_get_stream_url": "Fehler beim Abrufen der Stream-URL",
|
"failed_to_get_stream_url": "Fehler beim Abrufen der Stream-URL",
|
||||||
"an_error_occured_while_playing_the_video": "Ein Fehler ist beim Abspielen des Videos aufgetreten. Logs in den Einstellungen überprüfen.",
|
"an_error_occured_while_playing_the_video": "Ein Fehler ist beim Abspielen des Videos aufgetreten. Überprüf die Logs in den Einstellungen.",
|
||||||
"client_error": "Client-Fehler",
|
"client_error": "Client-Fehler",
|
||||||
"could_not_create_stream_for_chromecast": "Konnte keinen Stream für Chromecast erstellen",
|
"could_not_create_stream_for_chromecast": "Konnte keinen Stream für Chromecast erstellen",
|
||||||
"message_from_server": "Nachricht vom Server: {{message}}",
|
"message_from_server": "Nachricht vom Server: {{message}}",
|
||||||
@@ -602,17 +602,17 @@
|
|||||||
"audio_tracks": "Audiospuren:",
|
"audio_tracks": "Audiospuren:",
|
||||||
"playback_state": "Wiedergabestatus:",
|
"playback_state": "Wiedergabestatus:",
|
||||||
"index": "Index:",
|
"index": "Index:",
|
||||||
"continue_watching": "Fortsetzen",
|
"continue_watching": "Weiterschauen",
|
||||||
"go_back": "Zurück",
|
"go_back": "Zurück",
|
||||||
"downloaded_file_title": "Diese Datei wurde bereits heruntergeladen",
|
"downloaded_file_title": "Diese Datei wurde heruntergeladen",
|
||||||
"downloaded_file_message": "Heruntergeladene Datei abspielen?",
|
"downloaded_file_message": "Möchten Sie die heruntergeladene Datei abspielen?",
|
||||||
"downloaded_file_yes": "Ja",
|
"downloaded_file_yes": "Ja",
|
||||||
"downloaded_file_no": "Nein",
|
"downloaded_file_no": "Nein",
|
||||||
"downloaded_file_cancel": "Abbrechen"
|
"downloaded_file_cancel": "Abbrechen"
|
||||||
},
|
},
|
||||||
"item_card": {
|
"item_card": {
|
||||||
"next_up": "Als Nächstes",
|
"next_up": "Als Nächstes",
|
||||||
"no_items_to_display": "Keine Elemente",
|
"no_items_to_display": "Keine Elemente zum Anzeigen",
|
||||||
"cast_and_crew": "Besetzung und Crew",
|
"cast_and_crew": "Besetzung und Crew",
|
||||||
"series": "Serien",
|
"series": "Serien",
|
||||||
"seasons": "Staffeln",
|
"seasons": "Staffeln",
|
||||||
@@ -630,7 +630,7 @@
|
|||||||
"subtitles": "Untertitel",
|
"subtitles": "Untertitel",
|
||||||
"show_more": "Mehr anzeigen",
|
"show_more": "Mehr anzeigen",
|
||||||
"show_less": "Weniger anzeigen",
|
"show_less": "Weniger anzeigen",
|
||||||
"appeared_in": "Erschien in",
|
"appeared_in": "Erschienen in",
|
||||||
"could_not_load_item": "Konnte Element nicht laden",
|
"could_not_load_item": "Konnte Element nicht laden",
|
||||||
"none": "Keine",
|
"none": "Keine",
|
||||||
"download": {
|
"download": {
|
||||||
@@ -639,13 +639,13 @@
|
|||||||
"download_episode": "Episode herunterladen",
|
"download_episode": "Episode herunterladen",
|
||||||
"download_movie": "Film herunterladen",
|
"download_movie": "Film herunterladen",
|
||||||
"download_x_item": "{{item_count}} Elemente herunterladen",
|
"download_x_item": "{{item_count}} Elemente herunterladen",
|
||||||
"download_unwatched_only": "Nur Ungesehene",
|
"download_unwatched_only": "Nur unbeobachtete",
|
||||||
"download_button": "Herunterladen"
|
"download_button": "Herunterladen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"live_tv": {
|
"live_tv": {
|
||||||
"next": "Nächste",
|
"next": "Nächster",
|
||||||
"previous": "Vorherige",
|
"previous": "Vorheriger",
|
||||||
"coming_soon": "Demnächst",
|
"coming_soon": "Demnächst",
|
||||||
"on_now": "Jetzt",
|
"on_now": "Jetzt",
|
||||||
"shows": "Serien",
|
"shows": "Serien",
|
||||||
@@ -658,10 +658,10 @@
|
|||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"whats_wrong": "Was stimmt nicht?",
|
"whats_wrong": "Hast du Probleme?",
|
||||||
"issue_type": "Art des Problems",
|
"issue_type": "Fehlerart",
|
||||||
"select_an_issue": "Wähle die Art des Problems aus",
|
"select_an_issue": "Wähle einen Fehlerart aus",
|
||||||
"types": "Problem-Arten",
|
"types": "Arten",
|
||||||
"describe_the_issue": "(optional) Beschreibe das Problem",
|
"describe_the_issue": "(optional) Beschreibe das Problem",
|
||||||
"submit_button": "Absenden",
|
"submit_button": "Absenden",
|
||||||
"report_issue_button": "Fehler melden",
|
"report_issue_button": "Fehler melden",
|
||||||
@@ -671,7 +671,7 @@
|
|||||||
"cast": "Besetzung",
|
"cast": "Besetzung",
|
||||||
"details": "Details",
|
"details": "Details",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"original_title": "Originaltitel",
|
"original_title": "Original Titel",
|
||||||
"series_type": "Serien Typ",
|
"series_type": "Serien Typ",
|
||||||
"release_dates": "Veröffentlichungsdaten",
|
"release_dates": "Veröffentlichungsdaten",
|
||||||
"first_air_date": "Erstausstrahlungsdatum",
|
"first_air_date": "Erstausstrahlungsdatum",
|
||||||
@@ -687,10 +687,10 @@
|
|||||||
"request_as": "Anfragen als",
|
"request_as": "Anfragen als",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"quality_profile": "Qualitätsprofil",
|
"quality_profile": "Qualitätsprofil",
|
||||||
"root_folder": "Stammverzeichnis",
|
"root_folder": "Root-Ordner",
|
||||||
"season_all": "Staffeln (alle)",
|
"season_all": "Season (all)",
|
||||||
"season_number": "Staffel {{season_number}}",
|
"season_number": "Staffel {{season_number}}",
|
||||||
"number_episodes": "{{episode_number}} Episoden",
|
"number_episodes": "{{episode_number}} Folgen",
|
||||||
"born": "Geboren",
|
"born": "Geboren",
|
||||||
"appearances": "Auftritte",
|
"appearances": "Auftritte",
|
||||||
"approve": "Genehmigen",
|
"approve": "Genehmigen",
|
||||||
@@ -698,9 +698,9 @@
|
|||||||
"requested_by": "Angefragt von {{user}}",
|
"requested_by": "Angefragt von {{user}}",
|
||||||
"unknown_user": "Unbekannter Nutzer",
|
"unknown_user": "Unbekannter Nutzer",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"jellyseer_does_not_meet_requirements": "Seerr-Server erfüllt nicht die minimalen Versionsanforderungen. Bitte den Seerr-Server auf mindestens 2.0.0 aktualisieren.",
|
"jellyseer_does_not_meet_requirements": "Jellyseerr Server erfüllt nicht die Anforderungsversion. Bitte aktualisiere deinen Jellyseerr Server auf mindestens 2.0.0",
|
||||||
"jellyseerr_test_failed": "Seerr-Test fehlgeschlagen. Bitte erneut versuchen.",
|
"jellyseerr_test_failed": "Jellyseerr-Test fehlgeschlagen. Bitte versuche es erneut.",
|
||||||
"failed_to_test_jellyseerr_server_url": "Fehler beim Test der Seerr-Server-URL",
|
"failed_to_test_jellyseerr_server_url": "Fehler beim Testen der Jellyseerr-Server-URL",
|
||||||
"issue_submitted": "Problem eingereicht!",
|
"issue_submitted": "Problem eingereicht!",
|
||||||
"requested_item": "{{item}} angefragt!",
|
"requested_item": "{{item}} angefragt!",
|
||||||
"you_dont_have_permission_to_request": "Du hast keine Berechtigung Anfragen zu stellen",
|
"you_dont_have_permission_to_request": "Du hast keine Berechtigung Anfragen zu stellen",
|
||||||
@@ -715,131 +715,131 @@
|
|||||||
"home": "Startseite",
|
"home": "Startseite",
|
||||||
"search": "Suche",
|
"search": "Suche",
|
||||||
"library": "Bibliothek",
|
"library": "Bibliothek",
|
||||||
"custom_links": "Links",
|
"custom_links": "Benutzerdefinierte Links",
|
||||||
"favorites": "Favoriten"
|
"favorites": "Favoriten"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Musik",
|
"title": "Music",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"suggestions": "Vorschläge",
|
"suggestions": "Suggestions",
|
||||||
"albums": "Alben",
|
"albums": "Albums",
|
||||||
"artists": "Künstler",
|
"artists": "Artists",
|
||||||
"playlists": "Playlists",
|
"playlists": "Playlists",
|
||||||
"tracks": "Titel"
|
"tracks": "tracks"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"all": "Alle"
|
"all": "All"
|
||||||
},
|
},
|
||||||
"recently_added": "Kürzlich hinzugefügt",
|
"recently_added": "Recently Added",
|
||||||
"recently_played": "Vor kurzem gehört",
|
"recently_played": "Recently Played",
|
||||||
"frequently_played": "Oft gehört",
|
"frequently_played": "Frequently Played",
|
||||||
"explore": "Entdecken",
|
"explore": "Explore",
|
||||||
"top_tracks": "Top-Titel",
|
"top_tracks": "Top Tracks",
|
||||||
"play": "Abspielen",
|
"play": "Play",
|
||||||
"shuffle": "Shuffle",
|
"shuffle": "Shuffle",
|
||||||
"play_top_tracks": "Top-Tracks abspielen",
|
"play_top_tracks": "Play Top Tracks",
|
||||||
"no_suggestions": "Keine Vorschläge verfügbar",
|
"no_suggestions": "No suggestions available",
|
||||||
"no_albums": "Keine Alben gefunden",
|
"no_albums": "No albums found",
|
||||||
"no_artists": "Keine Künstler gefunden",
|
"no_artists": "No artists found",
|
||||||
"no_playlists": "Keine Playlists gefunden",
|
"no_playlists": "No playlists found",
|
||||||
"album_not_found": "Album nicht gefunden",
|
"album_not_found": "Album not found",
|
||||||
"artist_not_found": "Künstler nicht gefunden",
|
"artist_not_found": "Artist not found",
|
||||||
"playlist_not_found": "Playlist nicht gefunden",
|
"playlist_not_found": "Playlist not found",
|
||||||
"track_options": {
|
"track_options": {
|
||||||
"play_next": "Als Nächstes wiedergeben",
|
"play_next": "Play Next",
|
||||||
"add_to_queue": "Zur Warteschlange hinzufügen",
|
"add_to_queue": "Add to Queue",
|
||||||
"add_to_playlist": "Zur Playlist hinzufügen",
|
"add_to_playlist": "Add to Playlist",
|
||||||
"download": "Herunterladen",
|
"download": "Download",
|
||||||
"downloaded": "Heruntergeladen",
|
"downloaded": "Downloaded",
|
||||||
"downloading": "Wird heruntergeladen...",
|
"downloading": "Downloading...",
|
||||||
"cached": "Gecached",
|
"cached": "Cached",
|
||||||
"delete_download": "Download löschen",
|
"delete_download": "Delete Download",
|
||||||
"delete_cache": "Aus dem Cache löschen",
|
"delete_cache": "Remove from Cache",
|
||||||
"go_to_artist": "Zum Künstler gehen",
|
"go_to_artist": "Go to Artist",
|
||||||
"go_to_album": "Zum Album gehen",
|
"go_to_album": "Go to Album",
|
||||||
"add_to_favorites": "Zu Favoriten hinzufügen",
|
"add_to_favorites": "Add to Favorites",
|
||||||
"remove_from_favorites": "Aus Favoriten entfernen",
|
"remove_from_favorites": "Remove from Favorites",
|
||||||
"remove_from_playlist": "Aus Playlist entfernen"
|
"remove_from_playlist": "Remove from Playlist"
|
||||||
},
|
},
|
||||||
"playlists": {
|
"playlists": {
|
||||||
"create_playlist": "Playlist erstellen",
|
"create_playlist": "Create Playlist",
|
||||||
"playlist_name": "Playlist Name",
|
"playlist_name": "Playlist Name",
|
||||||
"enter_name": "Playlist Name eingeben",
|
"enter_name": "Enter playlist name",
|
||||||
"create": "Erstellen",
|
"create": "Create",
|
||||||
"search_playlists": "Playlisten durchsuchen...",
|
"search_playlists": "Search playlists...",
|
||||||
"added_to": "Zu {{name}} hinzugefügt",
|
"added_to": "Added to {{name}}",
|
||||||
"added": "Zur Playlist hinzugefügt",
|
"added": "Added to playlist",
|
||||||
"removed_from": "Aus {{name}} entfernt",
|
"removed_from": "Removed from {{name}}",
|
||||||
"removed": "Aus Playlist entfernt",
|
"removed": "Removed from playlist",
|
||||||
"created": "Playlist erstellt",
|
"created": "Playlist created",
|
||||||
"create_new": "Neue Playlist erstellen",
|
"create_new": "Create New Playlist",
|
||||||
"failed_to_add": "Fehler beim Hinzufügen zur Playlist",
|
"failed_to_add": "Failed to add to playlist",
|
||||||
"failed_to_remove": "Fehler beim Entfernen aus der Playlist",
|
"failed_to_remove": "Failed to remove from playlist",
|
||||||
"failed_to_create": "Fehler beim Erstellen der Playlist",
|
"failed_to_create": "Failed to create playlist",
|
||||||
"delete_playlist": "Playlist löschen",
|
"delete_playlist": "Delete Playlist",
|
||||||
"delete_confirm": "Bist Du sicher, dass Du \"{{name}}\" löschen möchtest? Das kann nicht rückgängig gemacht werden.",
|
"delete_confirm": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"deleted": "Playlist gelöscht",
|
"deleted": "Playlist deleted",
|
||||||
"failed_to_delete": "Fehler beim Löschen der Playlist"
|
"failed_to_delete": "Failed to delete playlist"
|
||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
"title": "Sortieren nach",
|
"title": "Sort By",
|
||||||
"alphabetical": "Alphabetisch",
|
"alphabetical": "Alphabetical",
|
||||||
"date_created": "Erstellungsdatum"
|
"date_created": "Date Created"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchlists": {
|
"watchlists": {
|
||||||
"title": "Merklisten",
|
"title": "Watchlists",
|
||||||
"my_watchlists": "Meine Merklisten",
|
"my_watchlists": "My Watchlists",
|
||||||
"public_watchlists": "Öffentliche Merklisten",
|
"public_watchlists": "Public Watchlists",
|
||||||
"create_title": "Merkliste erstellen",
|
"create_title": "Create Watchlist",
|
||||||
"edit_title": "Merkliste bearbeiten",
|
"edit_title": "Edit Watchlist",
|
||||||
"create_button": "Merkliste erstellen",
|
"create_button": "Create Watchlist",
|
||||||
"save_button": "Änderungen speichern",
|
"save_button": "Save Changes",
|
||||||
"delete_button": "Löschen",
|
"delete_button": "Delete",
|
||||||
"remove_button": "Entfernen",
|
"remove_button": "Remove",
|
||||||
"cancel_button": "Abbrechen",
|
"cancel_button": "Cancel",
|
||||||
"name_label": "Name",
|
"name_label": "Name",
|
||||||
"name_placeholder": "Merklistenname eingeben",
|
"name_placeholder": "Enter watchlist name",
|
||||||
"description_label": "Beschreibung",
|
"description_label": "Description",
|
||||||
"description_placeholder": "Beschreibung eingeben (optional)",
|
"description_placeholder": "Enter description (optional)",
|
||||||
"is_public_label": "Öffentliche Merkliste",
|
"is_public_label": "Public Watchlist",
|
||||||
"is_public_description": "Anderen erlauben diese Merkliste anzusehen",
|
"is_public_description": "Allow others to view this watchlist",
|
||||||
"allowed_type_label": "Inhaltstyp",
|
"allowed_type_label": "Content Type",
|
||||||
"sort_order_label": "Standard-Sortierreihenfolge",
|
"sort_order_label": "Default Sort Order",
|
||||||
"empty_title": "Keine Merklisten",
|
"empty_title": "No Watchlists",
|
||||||
"empty_description": "Erstelle deine erste Merkliste um deine Medien zu organisieren",
|
"empty_description": "Create your first watchlist to start organizing your media",
|
||||||
"empty_watchlist": "Diese Merkliste ist leer",
|
"empty_watchlist": "This watchlist is empty",
|
||||||
"empty_watchlist_hint": "Füge Elemente aus deiner Bibliothek zu dieser Merkliste hinzu",
|
"empty_watchlist_hint": "Add items from your library to this watchlist",
|
||||||
"not_configured_title": "Streamystats nicht konfiguriert",
|
"not_configured_title": "Streamystats Not Configured",
|
||||||
"not_configured_description": "Streamystats in den Einstellungen konfigurieren, um Merklisten zu verwenden",
|
"not_configured_description": "Configure Streamystats in settings to use watchlists",
|
||||||
"go_to_settings": "Gehe zu Einstellungen",
|
"go_to_settings": "Go to Settings",
|
||||||
"add_to_watchlist": "Zur Merkliste hinzufügen",
|
"add_to_watchlist": "Add to Watchlist",
|
||||||
"remove_from_watchlist": "Von Merkliste entfernen",
|
"remove_from_watchlist": "Remove from Watchlist",
|
||||||
"select_watchlist": "Merkliste auswählen",
|
"select_watchlist": "Select Watchlist",
|
||||||
"create_new": "Neue Merkliste erstellen",
|
"create_new": "Create New Watchlist",
|
||||||
"item": "Element",
|
"item": "item",
|
||||||
"items": "Elemente",
|
"items": "items",
|
||||||
"public": "Öffentlich",
|
"public": "Public",
|
||||||
"private": "Privat",
|
"private": "Private",
|
||||||
"you": "Du",
|
"you": "You",
|
||||||
"by_owner": "Von einem anderen Benutzer",
|
"by_owner": "By another user",
|
||||||
"not_found": "Merkliste nicht gefunden",
|
"not_found": "Watchlist not found",
|
||||||
"delete_confirm_title": "Merkliste löschen",
|
"delete_confirm_title": "Delete Watchlist",
|
||||||
"delete_confirm_message": "Bist Du sicher, dass Du \"{{name}}\" löschen möchtest? Das kann nicht rückgängig gemacht werden.",
|
"delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"remove_item_title": "Von Merkliste entfernen",
|
"remove_item_title": "Remove from Watchlist",
|
||||||
"remove_item_message": "\"{{name}}\" von dieser Merkliste entfernen?",
|
"remove_item_message": "Remove \"{{name}}\" from this watchlist?",
|
||||||
"loading": "Lade Merklisten...",
|
"loading": "Loading watchlists...",
|
||||||
"no_compatible_watchlists": "Keine kompatiblen Merklisten",
|
"no_compatible_watchlists": "No compatible watchlists",
|
||||||
"create_one_first": "Erstelle eine Merkliste, welche diesen Inhaltstyp akzeptiert"
|
"create_one_first": "Create a watchlist that accepts this content type"
|
||||||
},
|
},
|
||||||
"playback_speed": {
|
"playback_speed": {
|
||||||
"title": "Wiedergabegeschwindigkeit",
|
"title": "Playback Speed",
|
||||||
"apply_to": "Anwenden auf",
|
"apply_to": "Apply To",
|
||||||
"speed": "Geschwindigkeit",
|
"speed": "Speed",
|
||||||
"scope": {
|
"scope": {
|
||||||
"media": "Nur hier",
|
"media": "This media only",
|
||||||
"show": "Nur diese Serie",
|
"show": "This show",
|
||||||
"all": "Alle (Standard)"
|
"all": "All media (default)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,31 +24,6 @@
|
|||||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
"player": {
|
|
||||||
"skip_intro": "Skip Intro",
|
|
||||||
"skip_outro": "Skip Outro",
|
|
||||||
"skip_recap": "Skip Recap",
|
|
||||||
"skip_commercial": "Skip Commercial",
|
|
||||||
"skip_preview": "Skip Preview",
|
|
||||||
"error": "Error",
|
|
||||||
"failed_to_get_stream_url": "Failed to get the stream URL",
|
|
||||||
"an_error_occured_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
|
||||||
"client_error": "Client Error",
|
|
||||||
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
|
||||||
"message_from_server": "Message from Server: {{message}}",
|
|
||||||
"next_episode": "Next Episode",
|
|
||||||
"refresh_tracks": "Refresh Tracks",
|
|
||||||
"audio_tracks": "Audio Tracks:",
|
|
||||||
"playback_state": "Playback State:",
|
|
||||||
"index": "Index:",
|
|
||||||
"continue_watching": "Continue Watching",
|
|
||||||
"go_back": "Go Back",
|
|
||||||
"downloaded_file_title": "You have this file downloaded",
|
|
||||||
"downloaded_file_message": "Do you want to play the downloaded file?",
|
|
||||||
"downloaded_file_yes": "Yes",
|
|
||||||
"downloaded_file_no": "No",
|
|
||||||
"downloaded_file_cancel": "Cancel"
|
|
||||||
},
|
|
||||||
"server": {
|
"server": {
|
||||||
"enter_url_to_jellyfin_server": "Enter the URL to your Jellyfin server",
|
"enter_url_to_jellyfin_server": "Enter the URL to your Jellyfin server",
|
||||||
"server_url_placeholder": "http(s)://your-server.com",
|
"server_url_placeholder": "http(s)://your-server.com",
|
||||||
@@ -333,21 +308,6 @@
|
|||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
"auto_play_next_episode": "Auto-play Next Episode",
|
"auto_play_next_episode": "Auto-play Next Episode",
|
||||||
"max_auto_play_episode_count": "Max Auto Play Episode Count",
|
"max_auto_play_episode_count": "Max Auto Play Episode Count",
|
||||||
"segment_skip_settings": "Segment Skip Settings",
|
|
||||||
"segment_skip_settings_description": "Configure skip behavior for intros, credits, and other segments",
|
|
||||||
"skip_intro": "Skip Intro",
|
|
||||||
"skip_intro_description": "Action when intro segment is detected",
|
|
||||||
"skip_outro": "Skip Outro/Credits",
|
|
||||||
"skip_outro_description": "Action when outro/credits segment is detected",
|
|
||||||
"skip_recap": "Skip Recap",
|
|
||||||
"skip_recap_description": "Action when recap segment is detected",
|
|
||||||
"skip_commercial": "Skip Commercial",
|
|
||||||
"skip_commercial_description": "Action when commercial segment is detected",
|
|
||||||
"skip_preview": "Skip Preview",
|
|
||||||
"skip_preview_description": "Action when preview segment is detected",
|
|
||||||
"segment_skip_none": "None",
|
|
||||||
"segment_skip_ask": "Show Skip Button",
|
|
||||||
"segment_skip_auto": "Auto Skip",
|
|
||||||
"disabled": "Disabled"
|
"disabled": "Disabled"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
@@ -630,6 +590,26 @@
|
|||||||
"custom_links": {
|
"custom_links": {
|
||||||
"no_links": "No Links"
|
"no_links": "No Links"
|
||||||
},
|
},
|
||||||
|
"player": {
|
||||||
|
"error": "Error",
|
||||||
|
"failed_to_get_stream_url": "Failed to get the stream URL",
|
||||||
|
"an_error_occured_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
||||||
|
"client_error": "Client Error",
|
||||||
|
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
||||||
|
"message_from_server": "Message from Server: {{message}}",
|
||||||
|
"next_episode": "Next Episode",
|
||||||
|
"refresh_tracks": "Refresh Tracks",
|
||||||
|
"audio_tracks": "Audio Tracks:",
|
||||||
|
"playback_state": "Playback State:",
|
||||||
|
"index": "Index:",
|
||||||
|
"continue_watching": "Continue Watching",
|
||||||
|
"go_back": "Go Back",
|
||||||
|
"downloaded_file_title": "You have this file downloaded",
|
||||||
|
"downloaded_file_message": "Do you want to play the downloaded file?",
|
||||||
|
"downloaded_file_yes": "Yes",
|
||||||
|
"downloaded_file_no": "No",
|
||||||
|
"downloaded_file_cancel": "Cancel"
|
||||||
|
},
|
||||||
"item_card": {
|
"item_card": {
|
||||||
"next_up": "Next Up",
|
"next_up": "Next Up",
|
||||||
"no_items_to_display": "No Items to Display",
|
"no_items_to_display": "No Items to Display",
|
||||||
|
|||||||
@@ -39,39 +39,39 @@
|
|||||||
"please_login_again": "Su sesión guardada ha caducado. Por favor, inicie sesión de nuevo.",
|
"please_login_again": "Su sesión guardada ha caducado. Por favor, inicie sesión de nuevo.",
|
||||||
"remove_saved_login": "Eliminar inicio de sesión guardado",
|
"remove_saved_login": "Eliminar inicio de sesión guardado",
|
||||||
"remove_saved_login_description": "Esto eliminará tus credenciales guardadas para este servidor. Tendrás que volver a introducir tu nombre de usuario y contraseña la próxima vez.",
|
"remove_saved_login_description": "Esto eliminará tus credenciales guardadas para este servidor. Tendrás que volver a introducir tu nombre de usuario y contraseña la próxima vez.",
|
||||||
"accounts_count": "{{count}} cuentas",
|
"accounts_count": "{{count}} accounts",
|
||||||
"select_account": "Seleccione una cuenta",
|
"select_account": "Select Account",
|
||||||
"add_account": "Añadir cuenta",
|
"add_account": "Add Account",
|
||||||
"remove_account_description": "Esto eliminará las credenciales guardadas para {{username}}."
|
"remove_account_description": "This will remove the saved credentials for {{username}}."
|
||||||
},
|
},
|
||||||
"save_account": {
|
"save_account": {
|
||||||
"title": "Guardar Cuenta",
|
"title": "Save Account",
|
||||||
"save_for_later": "Guardar esta cuenta",
|
"save_for_later": "Save this account",
|
||||||
"security_option": "Opciones de seguridad",
|
"security_option": "Security Option",
|
||||||
"no_protection": "Sin Protección",
|
"no_protection": "No protection",
|
||||||
"no_protection_desc": "Inicio de sesión rápido sin autenticación",
|
"no_protection_desc": "Quick login without authentication",
|
||||||
"pin_code": "Código PIN",
|
"pin_code": "PIN code",
|
||||||
"pin_code_desc": "PIN de 4 dígitos requerido al cambiar",
|
"pin_code_desc": "4-digit PIN required when switching",
|
||||||
"password": "Vuelva a introducir la contraseña",
|
"password": "Re-enter password",
|
||||||
"password_desc": "Contraseña requerida al cambiar",
|
"password_desc": "Password required when switching",
|
||||||
"save_button": "Guardar",
|
"save_button": "Save",
|
||||||
"cancel_button": "Cancelar"
|
"cancel_button": "Cancel"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"enter_pin": "Introduce el PIN",
|
"enter_pin": "Enter PIN",
|
||||||
"enter_pin_for": "Introduzca el PIN para {{username}}",
|
"enter_pin_for": "Enter PIN for {{username}}",
|
||||||
"enter_4_digits": "Introduce 4 dígitos",
|
"enter_4_digits": "Enter 4 digits",
|
||||||
"invalid_pin": "PIN inválido",
|
"invalid_pin": "Invalid PIN",
|
||||||
"setup_pin": "Configurar PIN",
|
"setup_pin": "Set Up PIN",
|
||||||
"confirm_pin": "Confirmar PIN",
|
"confirm_pin": "Confirm PIN",
|
||||||
"pins_dont_match": "Los códigos PIN no coinciden",
|
"pins_dont_match": "PINs don't match",
|
||||||
"forgot_pin": "¿Olvidó el PIN?",
|
"forgot_pin": "Forgot PIN?",
|
||||||
"forgot_pin_desc": "Sus credenciales guardadas serán eliminadas"
|
"forgot_pin_desc": "Your saved credentials will be removed"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"enter_password": "Introduzca la contraseña",
|
"enter_password": "Enter Password",
|
||||||
"enter_password_for": "Introduzca la contraseña para {{username}}",
|
"enter_password_for": "Enter password for {{username}}",
|
||||||
"invalid_password": "Contraseña inválida"
|
"invalid_password": "Invalid password"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"checking_server_connection": "Comprobando conexión con el servidor...",
|
"checking_server_connection": "Comprobando conexión con el servidor...",
|
||||||
@@ -124,32 +124,32 @@
|
|||||||
"hide_remote_session_button": "Ocultar botón de sesión remota"
|
"hide_remote_session_button": "Ocultar botón de sesión remota"
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"title": "Cadena",
|
"title": "Network",
|
||||||
"local_network": "Red local",
|
"local_network": "Local Network",
|
||||||
"auto_switch_enabled": "Cambiar automáticamente en casa",
|
"auto_switch_enabled": "Auto-switch when at home",
|
||||||
"auto_switch_description": "Cambiar automáticamente a la URL local cuando se conecta a la WiFi de casa",
|
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
|
||||||
"local_url": "URL local",
|
"local_url": "Local URL",
|
||||||
"local_url_hint": "Introduzca la dirección de su servidor local (por ejemplo, http://192.168.1.100:8096)",
|
"local_url_hint": "Enter your local server address (e.g., http://192.168.1.100:8096)",
|
||||||
"local_url_placeholder": "http://192.168.1.100:8096",
|
"local_url_placeholder": "http://192.168.1.100:8096",
|
||||||
"home_wifi_networks": "Redes WiFi domésticas",
|
"home_wifi_networks": "Home WiFi Networks",
|
||||||
"add_current_network": "Añadir \"{{ssid}}\"",
|
"add_current_network": "Add \"{{ssid}}\"",
|
||||||
"not_connected_to_wifi": "No está conectado a WiFi",
|
"not_connected_to_wifi": "Not connected to WiFi",
|
||||||
"no_networks_configured": "No hay redes configuradas",
|
"no_networks_configured": "No networks configured",
|
||||||
"add_network_hint": "Añade tu red WiFi doméstica para activar el cambio automático",
|
"add_network_hint": "Add your home WiFi network to enable auto-switching",
|
||||||
"current_wifi": "WiFi actual",
|
"current_wifi": "Current WiFi",
|
||||||
"using_url": "Utilizando",
|
"using_url": "Using",
|
||||||
"local": "URL local",
|
"local": "Local URL",
|
||||||
"remote": "URL Remota",
|
"remote": "Remote URL",
|
||||||
"not_connected": "Sin conexión",
|
"not_connected": "Not connected",
|
||||||
"current_server": "Servidor actual",
|
"current_server": "Current Server",
|
||||||
"remote_url": "URL Remota",
|
"remote_url": "Remote URL",
|
||||||
"active_url": "URL Activa",
|
"active_url": "Active URL",
|
||||||
"not_configured": "Sin configurar",
|
"not_configured": "Not configured",
|
||||||
"network_added": "Red añadida",
|
"network_added": "Network added",
|
||||||
"network_already_added": "Red ya añadida",
|
"network_already_added": "Network already added",
|
||||||
"no_wifi_connected": "Sin conexión a WiFi",
|
"no_wifi_connected": "Not connected to WiFi",
|
||||||
"permission_denied": "Permiso de ubicación denegado",
|
"permission_denied": "Location permission denied",
|
||||||
"permission_denied_explanation": "Se necesita el permiso de ubicación para detectar la red WiFi para cambiar automáticamente. Por favor, actívala en Configuración."
|
"permission_denied_explanation": "Location permission is required to detect WiFi network for auto-switching. Please enable it in Settings."
|
||||||
},
|
},
|
||||||
"user_info": {
|
"user_info": {
|
||||||
"user_info_title": "Información de usuario",
|
"user_info_title": "Información de usuario",
|
||||||
@@ -195,12 +195,12 @@
|
|||||||
"none": "Ninguno",
|
"none": "Ninguno",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"transcode_mode": {
|
"transcode_mode": {
|
||||||
"title": "Transcodificación de audio",
|
"title": "Audio Transcoding",
|
||||||
"description": "Controla cómo el audio envolvente (7.1, TrueHD, DTS-HD) es manejado",
|
"description": "Controls how surround audio (7.1, TrueHD, DTS-HD) is handled",
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"stereo": "Forzar salida estéreo",
|
"stereo": "Force Stereo",
|
||||||
"5_1": "Permitir 5.1",
|
"5_1": "Allow 5.1",
|
||||||
"passthrough": "Directo"
|
"passthrough": "Passthrough"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
@@ -259,16 +259,16 @@
|
|||||||
"hardware_decode_description": "Utilizar la aceleración de hardware para la decodificación de vídeo. Deshabilite si experimenta problemas de reproducción."
|
"hardware_decode_description": "Utilizar la aceleración de hardware para la decodificación de vídeo. Deshabilite si experimenta problemas de reproducción."
|
||||||
},
|
},
|
||||||
"vlc_subtitles": {
|
"vlc_subtitles": {
|
||||||
"title": "Configuración de subtítulos VLC",
|
"title": "VLC Subtitle Settings",
|
||||||
"hint": "Personalizar la apariencia de los subtítulos para el reproductor VLC. Los cambios tendrán efecto en la próxima reproducción.",
|
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
|
||||||
"text_color": "Color del texto",
|
"text_color": "Text Color",
|
||||||
"background_color": "Color del fondo",
|
"background_color": "Background Color",
|
||||||
"background_opacity": "Opacidad del fondo",
|
"background_opacity": "Background Opacity",
|
||||||
"outline_color": "Color del contorno",
|
"outline_color": "Outline Color",
|
||||||
"outline_opacity": "Opacidad del contorno",
|
"outline_opacity": "Outline Opacity",
|
||||||
"outline_thickness": "Grosor del contorno",
|
"outline_thickness": "Outline Thickness",
|
||||||
"bold": "Texto en negrita",
|
"bold": "Bold Text",
|
||||||
"margin": "Margen inferior"
|
"margin": "Bottom Margin"
|
||||||
},
|
},
|
||||||
"video_player": {
|
"video_player": {
|
||||||
"title": "Reproductor de vídeo",
|
"title": "Reproductor de vídeo",
|
||||||
@@ -300,13 +300,13 @@
|
|||||||
"VLC_4": "VLC 4 (Experimental + PiP)"
|
"VLC_4": "VLC 4 (Experimental + PiP)"
|
||||||
},
|
},
|
||||||
"show_custom_menu_links": "Mostrar enlaces de menú personalizados",
|
"show_custom_menu_links": "Mostrar enlaces de menú personalizados",
|
||||||
"show_large_home_carousel": "Mostrar carrusel del menú principal grande (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Ocultar bibliotecas",
|
"hide_libraries": "Ocultar bibliotecas",
|
||||||
"select_liraries_you_want_to_hide": "Selecciona las bibliotecas que quieres ocultar de la pestaña Bibliotecas y de Inicio.",
|
"select_liraries_you_want_to_hide": "Selecciona las bibliotecas que quieres ocultar de la pestaña Bibliotecas y de Inicio.",
|
||||||
"disable_haptic_feedback": "Desactivar feedback háptico",
|
"disable_haptic_feedback": "Desactivar feedback háptico",
|
||||||
"default_quality": "Calidad por defecto",
|
"default_quality": "Calidad por defecto",
|
||||||
"default_playback_speed": "Velocidad de reproducción predeterminada",
|
"default_playback_speed": "Velocidad de reproducción predeterminada",
|
||||||
"auto_play_next_episode": "Reproducir automáticamente el siguiente episodio",
|
"auto_play_next_episode": "Auto-play Next Episode",
|
||||||
"max_auto_play_episode_count": "Máximo número de episodios de Auto Play",
|
"max_auto_play_episode_count": "Máximo número de episodios de Auto Play",
|
||||||
"disabled": "Deshabilitado"
|
"disabled": "Deshabilitado"
|
||||||
},
|
},
|
||||||
@@ -317,10 +317,10 @@
|
|||||||
"title": "Música",
|
"title": "Música",
|
||||||
"playback_title": "Reproducir",
|
"playback_title": "Reproducir",
|
||||||
"playback_description": "Configurar cómo se reproduce la música.",
|
"playback_description": "Configurar cómo se reproduce la música.",
|
||||||
"prefer_downloaded": "Preferir las canciones descargadas",
|
"prefer_downloaded": "Prefer Downloaded Songs",
|
||||||
"caching_title": "Almacenando en caché",
|
"caching_title": "Almacenando en caché",
|
||||||
"caching_description": "Cachear automáticamente las próximas canciones para una reproducción más suave.",
|
"caching_description": "Automatically cache upcoming tracks for smoother playback.",
|
||||||
"lookahead_enabled": "Activar el look-Ahead Cache",
|
"lookahead_enabled": "Enable Look-Ahead Caching",
|
||||||
"lookahead_count": "",
|
"lookahead_count": "",
|
||||||
"max_cache_size": "Tamaño máximo del caché"
|
"max_cache_size": "Tamaño máximo del caché"
|
||||||
},
|
},
|
||||||
@@ -399,7 +399,7 @@
|
|||||||
"size_used": "{{used}} de {{total}} usado",
|
"size_used": "{{used}} de {{total}} usado",
|
||||||
"delete_all_downloaded_files": "Eliminar todos los archivos descargados",
|
"delete_all_downloaded_files": "Eliminar todos los archivos descargados",
|
||||||
"music_cache_title": "Caché de música",
|
"music_cache_title": "Caché de música",
|
||||||
"music_cache_description": "Cachear automáticamente las canciones mientras escuchas una reproducción más suave y soporte sin conexión",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"enable_music_cache": "Activar Caché de Música",
|
"enable_music_cache": "Activar Caché de Música",
|
||||||
"clear_music_cache": "Borrar Caché de Música",
|
"clear_music_cache": "Borrar Caché de Música",
|
||||||
"music_cache_size": "Caché {{Tamaño}}",
|
"music_cache_size": "Caché {{Tamaño}}",
|
||||||
@@ -504,10 +504,10 @@
|
|||||||
"delete": "Borrar",
|
"delete": "Borrar",
|
||||||
"ok": "Aceptar",
|
"ok": "Aceptar",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"next": "Siguiente",
|
"next": "Next",
|
||||||
"back": "Atrás",
|
"back": "Back",
|
||||||
"continue": "Continuar",
|
"continue": "Continue",
|
||||||
"verifying": "Verificando..."
|
"verifying": "Verifying..."
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"search": "Buscar...",
|
"search": "Buscar...",
|
||||||
@@ -753,8 +753,8 @@
|
|||||||
"downloaded": "Descargado",
|
"downloaded": "Descargado",
|
||||||
"downloading": "Descargando...",
|
"downloading": "Descargando...",
|
||||||
"cached": "En caché",
|
"cached": "En caché",
|
||||||
"delete_download": "Eliminar descarga",
|
"delete_download": "Delete Download",
|
||||||
"delete_cache": "Borrar del caché",
|
"delete_cache": "Remove from Cache",
|
||||||
"go_to_artist": "Ir al artista",
|
"go_to_artist": "Ir al artista",
|
||||||
"go_to_album": "Ir al álbum",
|
"go_to_album": "Ir al álbum",
|
||||||
"add_to_favorites": "Añadir a Favoritos",
|
"add_to_favorites": "Añadir a Favoritos",
|
||||||
|
|||||||
@@ -305,39 +305,24 @@
|
|||||||
"select_liraries_you_want_to_hide": "Sélectionnez les bibliothèques que vous souhaitez masquer dans l'onglet Bibliothèque et les sections de la page d'accueil.",
|
"select_liraries_you_want_to_hide": "Sélectionnez les bibliothèques que vous souhaitez masquer dans l'onglet Bibliothèque et les sections de la page d'accueil.",
|
||||||
"disable_haptic_feedback": "Désactiver le retour haptique",
|
"disable_haptic_feedback": "Désactiver le retour haptique",
|
||||||
"default_quality": "Qualité par défaut",
|
"default_quality": "Qualité par défaut",
|
||||||
"default_playback_speed": "Vitesse de lecture par défaut",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
"auto_play_next_episode": "Lecture automatique de l'épisode suivant",
|
"auto_play_next_episode": "Auto-play Next Episode",
|
||||||
"max_auto_play_episode_count": "Nombre d'épisodes en lecture automatique max",
|
"max_auto_play_episode_count": "Nombre d'épisodes en lecture automatique max",
|
||||||
"disabled": "Désactivé",
|
"disabled": "Désactivé"
|
||||||
"segment_skip_settings": "Saut de segments",
|
|
||||||
"segment_skip_settings_description": "Configurer le saut pour les intros, génériques et autres segments",
|
|
||||||
"skip_intro": "Sauter l'intro",
|
|
||||||
"skip_intro_description": "Action lorsqu'un segment d'intro est détecté",
|
|
||||||
"skip_outro": "Sauter générique / outro",
|
|
||||||
"skip_outro_description": "Action lorsqu'un segment de générique/outro est détecté",
|
|
||||||
"skip_recap": "Sauter le résumé",
|
|
||||||
"skip_recap_description": "Action lorsqu'un segment de résumé est détecté",
|
|
||||||
"skip_commercial": "Sauter la publicité",
|
|
||||||
"skip_commercial_description": "Action lorsqu'un segment publicitaire est détecté",
|
|
||||||
"skip_preview": "Sauter l'aperçu",
|
|
||||||
"skip_preview_description": "Action lorsqu'un segment d'aperçu est détecté",
|
|
||||||
"segment_skip_none": "Aucune",
|
|
||||||
"segment_skip_ask": "Afficher le bouton",
|
|
||||||
"segment_skip_auto": "Saut automatique"
|
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"downloads_title": "Téléchargements"
|
"downloads_title": "Téléchargements"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Musique",
|
"title": "Music",
|
||||||
"playback_title": "Lecture",
|
"playback_title": "Playback",
|
||||||
"playback_description": "Configurer le mode de lecture de la musique.",
|
"playback_description": "Configure how music is played.",
|
||||||
"prefer_downloaded": "Supprimer toutes les musiques téléchargées",
|
"prefer_downloaded": "Prefer Downloaded Songs",
|
||||||
"caching_title": "Mise en cache",
|
"caching_title": "Caching",
|
||||||
"caching_description": "Mettre automatiquement en cache les pistes à venir pour une lecture plus fluide.",
|
"caching_description": "Automatically cache upcoming tracks for smoother playback.",
|
||||||
"lookahead_enabled": "Activer la mise en cache guidée",
|
"lookahead_enabled": "Enable Look-Ahead Caching",
|
||||||
"lookahead_count": "Pistes à pré-mettre en cache",
|
"lookahead_count": "Tracks to Pre-cache",
|
||||||
"max_cache_size": "Taille max de cache"
|
"max_cache_size": "Max Cache Size"
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"plugins_title": "Plugins",
|
"plugins_title": "Plugins",
|
||||||
@@ -372,19 +357,19 @@
|
|||||||
"save_button": "Enregistrer",
|
"save_button": "Enregistrer",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Enregistré",
|
"saved": "Enregistré",
|
||||||
"refreshed": "Paramètres actualisés depuis le serveur"
|
"refreshed": "Settings refreshed from server"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Rafraîchir les paramètres depuis le serveur"
|
"refresh_from_server": "Refresh Settings from Server"
|
||||||
},
|
},
|
||||||
"streamystats": {
|
"streamystats": {
|
||||||
"enable_streamystats": "Activer Streamystats",
|
"enable_streamystats": "Enable Streamystats",
|
||||||
"disable_streamystats": "Désactiver Streamystats",
|
"disable_streamystats": "Disable Streamystats",
|
||||||
"enable_search": "Utiliser pour la recherche",
|
"enable_search": "Use for Search",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"server_url_placeholder": "http(s)://streamystats.example.com",
|
"server_url_placeholder": "http(s)://streamystats.example.com",
|
||||||
"streamystats_search_hint": "Entrez l'URL de votre serveur Streamystats. L'URL doit inclure http ou https et éventuellement le port.",
|
"streamystats_search_hint": "Enter the URL for your Streamystats server. The URL should include http or https and optionally the port.",
|
||||||
"read_more_about_streamystats": "En savoir plus sur Streamystats.",
|
"read_more_about_streamystats": "Read More About Streamystats.",
|
||||||
"save_button": "Enregistrer",
|
"save_button": "Save",
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"features_title": "Fonctionnalités",
|
"features_title": "Fonctionnalités",
|
||||||
"home_sections_title": "Sections de la page d´accueil",
|
"home_sections_title": "Sections de la page d´accueil",
|
||||||
@@ -587,7 +572,7 @@
|
|||||||
"genres": "Genres",
|
"genres": "Genres",
|
||||||
"years": "Années",
|
"years": "Années",
|
||||||
"sort_by": "Trier par",
|
"sort_by": "Trier par",
|
||||||
"filter_by": "Filtrer par",
|
"filter_by": "Filter By",
|
||||||
"sort_order": "Ordre de tri",
|
"sort_order": "Ordre de tri",
|
||||||
"tags": "Tags"
|
"tags": "Tags"
|
||||||
}
|
}
|
||||||
@@ -606,11 +591,6 @@
|
|||||||
"no_links": "Aucuns liens"
|
"no_links": "Aucuns liens"
|
||||||
},
|
},
|
||||||
"player": {
|
"player": {
|
||||||
"skip_intro": "Passer l'intro",
|
|
||||||
"skip_outro": "Passer l'outro",
|
|
||||||
"skip_recap": "Passer le résumé",
|
|
||||||
"skip_commercial": "Passer la pub",
|
|
||||||
"skip_preview": "Passer l'aperçu",
|
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"failed_to_get_stream_url": "Échec de l'obtention de l'URL du flux",
|
"failed_to_get_stream_url": "Échec de l'obtention de l'URL du flux",
|
||||||
"an_error_occured_while_playing_the_video": "Une erreur s’est produite lors de la lecture de la vidéo. Vérifiez les journaux dans les paramètres.",
|
"an_error_occured_while_playing_the_video": "Une erreur s’est produite lors de la lecture de la vidéo. Vérifiez les journaux dans les paramètres.",
|
||||||
@@ -739,127 +719,127 @@
|
|||||||
"favorites": "Favoris"
|
"favorites": "Favoris"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Musique",
|
"title": "Music",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"suggestions": "Suggestions",
|
"suggestions": "Suggestions",
|
||||||
"albums": "Albums",
|
"albums": "Albums",
|
||||||
"artists": "Artistes",
|
"artists": "Artists",
|
||||||
"playlists": "Playlists",
|
"playlists": "Playlists",
|
||||||
"tracks": "morceaux"
|
"tracks": "tracks"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"all": "Toutes"
|
"all": "All"
|
||||||
},
|
},
|
||||||
"recently_added": "Ajoutés récemment",
|
"recently_added": "Recently Added",
|
||||||
"recently_played": "Récemment joué",
|
"recently_played": "Recently Played",
|
||||||
"frequently_played": "Fréquemment joué",
|
"frequently_played": "Frequently Played",
|
||||||
"explore": "Explorez",
|
"explore": "Explore",
|
||||||
"top_tracks": "Top chansons",
|
"top_tracks": "Top Tracks",
|
||||||
"play": "Lecture",
|
"play": "Play",
|
||||||
"shuffle": "Aléatoire",
|
"shuffle": "Shuffle",
|
||||||
"play_top_tracks": "Jouer les pistes les plus populaires",
|
"play_top_tracks": "Play Top Tracks",
|
||||||
"no_suggestions": "Pas de suggestion disponible",
|
"no_suggestions": "No suggestions available",
|
||||||
"no_albums": "Pas d'albums trouvés",
|
"no_albums": "No albums found",
|
||||||
"no_artists": "Pas d'artistes trouvé",
|
"no_artists": "No artists found",
|
||||||
"no_playlists": "Pas de playlists trouvées",
|
"no_playlists": "No playlists found",
|
||||||
"album_not_found": "Album introuvable",
|
"album_not_found": "Album not found",
|
||||||
"artist_not_found": "Artiste introuvable",
|
"artist_not_found": "Artist not found",
|
||||||
"playlist_not_found": "Playlist introuvable",
|
"playlist_not_found": "Playlist not found",
|
||||||
"track_options": {
|
"track_options": {
|
||||||
"play_next": "Lecture suivante",
|
"play_next": "Play Next",
|
||||||
"add_to_queue": "Ajouter à la file d'attente",
|
"add_to_queue": "Add to Queue",
|
||||||
"add_to_playlist": "Ajouter à la playlist",
|
"add_to_playlist": "Add to Playlist",
|
||||||
"download": "Télécharger",
|
"download": "Download",
|
||||||
"downloaded": "Téléchargé",
|
"downloaded": "Downloaded",
|
||||||
"downloading": "Téléchargement en cours...",
|
"downloading": "Downloading...",
|
||||||
"cached": "En cache",
|
"cached": "Cached",
|
||||||
"delete_download": "Supprimer un téléchargement",
|
"delete_download": "Delete Download",
|
||||||
"delete_cache": "Supprimer du cache",
|
"delete_cache": "Remove from Cache",
|
||||||
"go_to_artist": "Voir l'artiste",
|
"go_to_artist": "Go to Artist",
|
||||||
"go_to_album": "Aller à l’album",
|
"go_to_album": "Go to Album",
|
||||||
"add_to_favorites": "Ajouter aux favoris",
|
"add_to_favorites": "Add to Favorites",
|
||||||
"remove_from_favorites": "Retirer des favoris",
|
"remove_from_favorites": "Remove from Favorites",
|
||||||
"remove_from_playlist": "Retirer de la playlist"
|
"remove_from_playlist": "Remove from Playlist"
|
||||||
},
|
},
|
||||||
"playlists": {
|
"playlists": {
|
||||||
"create_playlist": "Créer une Playlist",
|
"create_playlist": "Create Playlist",
|
||||||
"playlist_name": "Nom de la Playlist",
|
"playlist_name": "Playlist Name",
|
||||||
"enter_name": "Entrer le nom de la playlist",
|
"enter_name": "Enter playlist name",
|
||||||
"create": "Créer",
|
"create": "Create",
|
||||||
"search_playlists": "Rechercher des playlists...",
|
"search_playlists": "Search playlists...",
|
||||||
"added_to": "Ajouté à {{name}}",
|
"added_to": "Added to {{name}}",
|
||||||
"added": "Ajouté à la playlist",
|
"added": "Added to playlist",
|
||||||
"removed_from": "Retiré de {{name}}",
|
"removed_from": "Removed from {{name}}",
|
||||||
"removed": "Retiré de la playlist",
|
"removed": "Removed from playlist",
|
||||||
"created": "Playlist créée",
|
"created": "Playlist created",
|
||||||
"create_new": "Créer une nouvelle playlist",
|
"create_new": "Create New Playlist",
|
||||||
"failed_to_add": "Échec de l'ajout à la playlist",
|
"failed_to_add": "Failed to add to playlist",
|
||||||
"failed_to_remove": "Échec de la suppression de la playlist",
|
"failed_to_remove": "Failed to remove from playlist",
|
||||||
"failed_to_create": "Échec de la suppression de la playlist",
|
"failed_to_create": "Failed to create playlist",
|
||||||
"delete_playlist": "Supprimer la playlist",
|
"delete_playlist": "Delete Playlist",
|
||||||
"delete_confirm": "Êtes-vous sûr de vouloir supprimer « {{ name }} » ? Cette action est irréversible.",
|
"delete_confirm": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"deleted": "Playlist supprimée",
|
"deleted": "Playlist deleted",
|
||||||
"failed_to_delete": "Échec de la suppression de la playlist"
|
"failed_to_delete": "Failed to delete playlist"
|
||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
"title": "Trier par",
|
"title": "Sort By",
|
||||||
"alphabetical": "Ordre alphabétique",
|
"alphabetical": "Alphabetical",
|
||||||
"date_created": "Date de création"
|
"date_created": "Date Created"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchlists": {
|
"watchlists": {
|
||||||
"title": "Watchlists",
|
"title": "Watchlists",
|
||||||
"my_watchlists": "My Watchlists",
|
"my_watchlists": "My Watchlists",
|
||||||
"public_watchlists": "Watchlist publique",
|
"public_watchlists": "Public Watchlists",
|
||||||
"create_title": "Créer une Watchlist",
|
"create_title": "Create Watchlist",
|
||||||
"edit_title": "Modifier la Watchlist",
|
"edit_title": "Edit Watchlist",
|
||||||
"create_button": "Créer une Watchlist",
|
"create_button": "Create Watchlist",
|
||||||
"save_button": "Enregistrer les modifications",
|
"save_button": "Save Changes",
|
||||||
"delete_button": "Supprimer",
|
"delete_button": "Delete",
|
||||||
"remove_button": "Retirer",
|
"remove_button": "Remove",
|
||||||
"cancel_button": "Annuler",
|
"cancel_button": "Cancel",
|
||||||
"name_label": "Nom",
|
"name_label": "Name",
|
||||||
"name_placeholder": "Entrer le nom de la playlist",
|
"name_placeholder": "Enter watchlist name",
|
||||||
"description_label": "Description",
|
"description_label": "Description",
|
||||||
"description_placeholder": "Entrez la description (facultatif)",
|
"description_placeholder": "Enter description (optional)",
|
||||||
"is_public_label": "Public Watchlist",
|
"is_public_label": "Public Watchlist",
|
||||||
"is_public_description": "Autoriser d'autres personnes à voir cette liste de suivi",
|
"is_public_description": "Allow others to view this watchlist",
|
||||||
"allowed_type_label": "Type de contenu",
|
"allowed_type_label": "Content Type",
|
||||||
"sort_order_label": "Ordre de tri par défaut",
|
"sort_order_label": "Default Sort Order",
|
||||||
"empty_title": "Pas de Watchlists",
|
"empty_title": "No Watchlists",
|
||||||
"empty_description": "Créez votre première liste de suivi pour commencer à organiser vos médias",
|
"empty_description": "Create your first watchlist to start organizing your media",
|
||||||
"empty_watchlist": "Cette liste de suivi est vide",
|
"empty_watchlist": "This watchlist is empty",
|
||||||
"empty_watchlist_hint": "Ajouter des éléments de votre bibliothèque à cette liste de suivi",
|
"empty_watchlist_hint": "Add items from your library to this watchlist",
|
||||||
"not_configured_title": "Streamystats non configuré",
|
"not_configured_title": "Streamystats Not Configured",
|
||||||
"not_configured_description": "Configurer Streamystats dans les paramètres pour utiliser les listes de suivi",
|
"not_configured_description": "Configure Streamystats in settings to use watchlists",
|
||||||
"go_to_settings": "Accédez aux Paramètres",
|
"go_to_settings": "Go to Settings",
|
||||||
"add_to_watchlist": "Ajouter à la Watchlist",
|
"add_to_watchlist": "Add to Watchlist",
|
||||||
"remove_from_watchlist": "Retirer de la Watchlist",
|
"remove_from_watchlist": "Remove from Watchlist",
|
||||||
"select_watchlist": "Sélectionner la liste de suivi",
|
"select_watchlist": "Select Watchlist",
|
||||||
"create_new": "Créer une Watchlist",
|
"create_new": "Create New Watchlist",
|
||||||
"item": "médias",
|
"item": "item",
|
||||||
"items": "élément",
|
"items": "items",
|
||||||
"public": "Publique",
|
"public": "Public",
|
||||||
"private": "Privée",
|
"private": "Private",
|
||||||
"you": "Vous-même",
|
"you": "You",
|
||||||
"by_owner": "Par un autre utilisateur",
|
"by_owner": "By another user",
|
||||||
"not_found": "Playlist introuvable",
|
"not_found": "Watchlist not found",
|
||||||
"delete_confirm_title": "Supprimer la Watchlist",
|
"delete_confirm_title": "Delete Watchlist",
|
||||||
"delete_confirm_message": "Tous les médias (par défaut)",
|
"delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"remove_item_title": "Retirer de la Watchlist",
|
"remove_item_title": "Remove from Watchlist",
|
||||||
"remove_item_message": "Retirer «{{name}}» de cette liste de suivi?",
|
"remove_item_message": "Remove \"{{name}}\" from this watchlist?",
|
||||||
"loading": "Chargement des listes de suivi...",
|
"loading": "Loading watchlists...",
|
||||||
"no_compatible_watchlists": "Aucune liste de suivi compatible",
|
"no_compatible_watchlists": "No compatible watchlists",
|
||||||
"create_one_first": "Créer une liste de suivi qui accepte ce type de contenu"
|
"create_one_first": "Create a watchlist that accepts this content type"
|
||||||
},
|
},
|
||||||
"playback_speed": {
|
"playback_speed": {
|
||||||
"title": "Vitesse de lecture",
|
"title": "Playback Speed",
|
||||||
"apply_to": "Appliquer à",
|
"apply_to": "Apply To",
|
||||||
"speed": "Vitesse",
|
"speed": "Speed",
|
||||||
"scope": {
|
"scope": {
|
||||||
"media": "Ce média uniquement",
|
"media": "This media only",
|
||||||
"show": "Cette série",
|
"show": "This show",
|
||||||
"all": "Tous les médias (par défaut)"
|
"all": "All media (default)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,9 @@
|
|||||||
"search_for_local_servers": "Ricerca dei server locali",
|
"search_for_local_servers": "Ricerca dei server locali",
|
||||||
"searching": "Cercando...",
|
"searching": "Cercando...",
|
||||||
"servers": "Server",
|
"servers": "Server",
|
||||||
"saved": "Salvato",
|
"saved": "Saved",
|
||||||
"session_expired": "Session Expired",
|
"session_expired": "Session Expired",
|
||||||
"please_login_again": "La tua sessione è scaduta. Si prega di eseguire nuovamente l'accesso.",
|
"please_login_again": "Your saved session has expired. Please log in again.",
|
||||||
"remove_saved_login": "Remove Saved Login",
|
"remove_saved_login": "Remove Saved Login",
|
||||||
"remove_saved_login_description": "This will remove your saved credentials for this server. You'll need to enter your username and password again next time.",
|
"remove_saved_login_description": "This will remove your saved credentials for this server. You'll need to enter your username and password again next time.",
|
||||||
"accounts_count": "{{count}} accounts",
|
"accounts_count": "{{count}} accounts",
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"title": "Network",
|
"title": "Network",
|
||||||
"local_network": "",
|
"local_network": "Local Network",
|
||||||
"auto_switch_enabled": "Auto-switch when at home",
|
"auto_switch_enabled": "Auto-switch when at home",
|
||||||
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
|
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
|
||||||
"local_url": "Local URL",
|
"local_url": "Local URL",
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
"no_networks_configured": "No networks configured",
|
"no_networks_configured": "No networks configured",
|
||||||
"add_network_hint": "Add your home WiFi network to enable auto-switching",
|
"add_network_hint": "Add your home WiFi network to enable auto-switching",
|
||||||
"current_wifi": "Current WiFi",
|
"current_wifi": "Current WiFi",
|
||||||
"using_url": "Sta utilizzando",
|
"using_url": "Using",
|
||||||
"local": "Local URL",
|
"local": "Local URL",
|
||||||
"remote": "Remote URL",
|
"remote": "Remote URL",
|
||||||
"not_connected": "Not connected",
|
"not_connected": "Not connected",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"connect_button": "Verbinden",
|
"connect_button": "Verbinden",
|
||||||
"previous_servers": "vorige servers",
|
"previous_servers": "vorige servers",
|
||||||
"clear_button": "Wissen",
|
"clear_button": "Wissen",
|
||||||
"swipe_to_remove": "Swipe om te verwijderen.",
|
"swipe_to_remove": "Swipe to remove",
|
||||||
"search_for_local_servers": "Zoek naar lokale servers",
|
"search_for_local_servers": "Zoek naar lokale servers",
|
||||||
"searching": "Zoeken...",
|
"searching": "Zoeken...",
|
||||||
"servers": "Servers",
|
"servers": "Servers",
|
||||||
@@ -40,38 +40,38 @@
|
|||||||
"remove_saved_login": "Opgeslagen login verwijderen",
|
"remove_saved_login": "Opgeslagen login verwijderen",
|
||||||
"remove_saved_login_description": "Hiermee worden uw opgeslagen gegevens voor deze server verwijderd. U moet uw gebruikersnaam en wachtwoord de volgende keer opnieuw invoeren.",
|
"remove_saved_login_description": "Hiermee worden uw opgeslagen gegevens voor deze server verwijderd. U moet uw gebruikersnaam en wachtwoord de volgende keer opnieuw invoeren.",
|
||||||
"accounts_count": "{{count}} accounts",
|
"accounts_count": "{{count}} accounts",
|
||||||
"select_account": "Account selecteren",
|
"select_account": "Select Account",
|
||||||
"add_account": "Account toevoegen",
|
"add_account": "Add Account",
|
||||||
"remove_account_description": "Hiermee worden de opgeslagen inloggegevens voor {{username}} verwijderd."
|
"remove_account_description": "This will remove the saved credentials for {{username}}."
|
||||||
},
|
},
|
||||||
"save_account": {
|
"save_account": {
|
||||||
"title": "Account opslaan",
|
"title": "Save Account",
|
||||||
"save_for_later": "Dit account opslaan",
|
"save_for_later": "Save this account",
|
||||||
"security_option": "Beveiligingsopties",
|
"security_option": "Security Option",
|
||||||
"no_protection": "Geen beveiliging",
|
"no_protection": "No protection",
|
||||||
"no_protection_desc": "Snelle login zonder authenticatie",
|
"no_protection_desc": "Quick login without authentication",
|
||||||
"pin_code": "Pincode",
|
"pin_code": "PIN code",
|
||||||
"pin_code_desc": "4-cijferige pincode vereist bij wisselen",
|
"pin_code_desc": "4-digit PIN required when switching",
|
||||||
"password": "Wachtwoord opnieuw invoeren",
|
"password": "Re-enter password",
|
||||||
"password_desc": "Wachtwoord vereist bij wisselen",
|
"password_desc": "Password required when switching",
|
||||||
"save_button": "Opslaan",
|
"save_button": "Save",
|
||||||
"cancel_button": "Annuleren"
|
"cancel_button": "Cancel"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"enter_pin": "Pincode invoeren",
|
"enter_pin": "Enter PIN",
|
||||||
"enter_pin_for": "Pincode voor {{username}} invoeren",
|
"enter_pin_for": "Enter PIN for {{username}}",
|
||||||
"enter_4_digits": "Voer 6 cijfers in",
|
"enter_4_digits": "Enter 4 digits",
|
||||||
"invalid_pin": "Ongeldige pincode",
|
"invalid_pin": "Invalid PIN",
|
||||||
"setup_pin": "Pincode instellen",
|
"setup_pin": "Set Up PIN",
|
||||||
"confirm_pin": "Pincode bevestigen",
|
"confirm_pin": "Confirm PIN",
|
||||||
"pins_dont_match": "Pincodes komen niet overeen",
|
"pins_dont_match": "PINs don't match",
|
||||||
"forgot_pin": "Pincode vergeten?",
|
"forgot_pin": "Forgot PIN?",
|
||||||
"forgot_pin_desc": "Je opgeslagen inloggegevens worden verwijderd"
|
"forgot_pin_desc": "Your saved credentials will be removed"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"enter_password": "Voer wachtwoord in",
|
"enter_password": "Enter Password",
|
||||||
"enter_password_for": "Voer wachtwoord voor {{username}} in",
|
"enter_password_for": "Enter password for {{username}}",
|
||||||
"invalid_password": "Ongeldig wachtwoord"
|
"invalid_password": "Invalid password"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"checking_server_connection": "Serververbinding controleren...",
|
"checking_server_connection": "Serververbinding controleren...",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"server_unreachable": "Server onbereikbaar",
|
"server_unreachable": "Server onbereikbaar",
|
||||||
"server_unreachable_message": "Kon de server niet bereiken.\nControleer uw netwerkverbinding.",
|
"server_unreachable_message": "Kon de server niet bereiken.\nControleer uw netwerkverbinding.",
|
||||||
"oops": "Oeps!",
|
"oops": "Oeps!",
|
||||||
"error_message": "Er ging iets fout\nProbeer opnieuw in te loggen.",
|
"error_message": "Er ging iets fout\nGelieve af en aan te melden.",
|
||||||
"continue_watching": "Verder Kijken",
|
"continue_watching": "Verder Kijken",
|
||||||
"next_up": "Volgende",
|
"next_up": "Volgende",
|
||||||
"continue_and_next_up": "Doorgaan & Volgende",
|
"continue_and_next_up": "Doorgaan & Volgende",
|
||||||
@@ -124,32 +124,32 @@
|
|||||||
"hide_remote_session_button": "Verberg Knop voor Externe Sessie"
|
"hide_remote_session_button": "Verberg Knop voor Externe Sessie"
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"title": "Netwerk",
|
"title": "Network",
|
||||||
"local_network": "Lokaal netwerk",
|
"local_network": "Local Network",
|
||||||
"auto_switch_enabled": "Automatisch wisselen wanneer thuis",
|
"auto_switch_enabled": "Auto-switch when at home",
|
||||||
"auto_switch_description": "Automatisch wisselen naar lokale URL wanneer verbonden met thuisnetwerk",
|
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
|
||||||
"local_url": "Lokale URL",
|
"local_url": "Local URL",
|
||||||
"local_url_hint": "Voer uw lokale serveradres in (bijv. http://192.168.1.100:8096)",
|
"local_url_hint": "Enter your local server address (e.g., http://192.168.1.100:8096)",
|
||||||
"local_url_placeholder": "http://192.168.1.100:8096",
|
"local_url_placeholder": "http://192.168.1.100:8096",
|
||||||
"home_wifi_networks": "Wi-Fi netwerken",
|
"home_wifi_networks": "Home WiFi Networks",
|
||||||
"add_current_network": "Voeg \"{{ssid}} \" toe",
|
"add_current_network": "Add \"{{ssid}}\"",
|
||||||
"not_connected_to_wifi": "Niet verbonden met Wi-Fi",
|
"not_connected_to_wifi": "Not connected to WiFi",
|
||||||
"no_networks_configured": "Geen netwerken geconfigureerd",
|
"no_networks_configured": "No networks configured",
|
||||||
"add_network_hint": "Voeg je thuisnetwerk toe om automatisch wisselen in te schakelen",
|
"add_network_hint": "Add your home WiFi network to enable auto-switching",
|
||||||
"current_wifi": "Huidige Wi-Fi",
|
"current_wifi": "Current WiFi",
|
||||||
"using_url": "Gebruik makend van",
|
"using_url": "Using",
|
||||||
"local": "Lokale URL",
|
"local": "Local URL",
|
||||||
"remote": "Externe URL",
|
"remote": "Remote URL",
|
||||||
"not_connected": "Niet verbonden",
|
"not_connected": "Not connected",
|
||||||
"current_server": "Huidige Server",
|
"current_server": "Current Server",
|
||||||
"remote_url": "Externe URL",
|
"remote_url": "Remote URL",
|
||||||
"active_url": "Actieve URL",
|
"active_url": "Active URL",
|
||||||
"not_configured": "Niet geconfigureerd",
|
"not_configured": "Not configured",
|
||||||
"network_added": "Netwerk toegevoegd",
|
"network_added": "Network added",
|
||||||
"network_already_added": "Netwerk reeds toegevoegd",
|
"network_already_added": "Network already added",
|
||||||
"no_wifi_connected": "Niet verbonden met Wi-Fi",
|
"no_wifi_connected": "Not connected to WiFi",
|
||||||
"permission_denied": "Locatie toestemming geweigerd",
|
"permission_denied": "Location permission denied",
|
||||||
"permission_denied_explanation": "Locatie permissie is vereist om Wifi-netwerk te kunnen detecteren voor automatisch wisselen. Schakel het in via Instellingen."
|
"permission_denied_explanation": "Location permission is required to detect WiFi network for auto-switching. Please enable it in Settings."
|
||||||
},
|
},
|
||||||
"user_info": {
|
"user_info": {
|
||||||
"user_info_title": "Gebruiker Info",
|
"user_info_title": "Gebruiker Info",
|
||||||
@@ -195,11 +195,11 @@
|
|||||||
"none": "Geen",
|
"none": "Geen",
|
||||||
"language": "Taal",
|
"language": "Taal",
|
||||||
"transcode_mode": {
|
"transcode_mode": {
|
||||||
"title": "Audio-transcoding",
|
"title": "Audio Transcoding",
|
||||||
"description": "Bepaalt hoe surround audio (7.1, TrueHD, DTS-HD) wordt behandeld",
|
"description": "Controls how surround audio (7.1, TrueHD, DTS-HD) is handled",
|
||||||
"auto": "Automatisch",
|
"auto": "Auto",
|
||||||
"stereo": "Stereo forceren",
|
"stereo": "Force Stereo",
|
||||||
"5_1": "5.1 toestaan",
|
"5_1": "Allow 5.1",
|
||||||
"passthrough": "Passthrough"
|
"passthrough": "Passthrough"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -231,7 +231,7 @@
|
|||||||
"Black": "Zwart",
|
"Black": "Zwart",
|
||||||
"Gray": "Grijs",
|
"Gray": "Grijs",
|
||||||
"Silver": "Zilver",
|
"Silver": "Zilver",
|
||||||
"White": "Wit",
|
"White": "wit",
|
||||||
"Maroon": "Kastanjebruin",
|
"Maroon": "Kastanjebruin",
|
||||||
"Red": "Rood",
|
"Red": "Rood",
|
||||||
"Fuchsia": "Fuchsia",
|
"Fuchsia": "Fuchsia",
|
||||||
@@ -259,14 +259,14 @@
|
|||||||
"hardware_decode_description": "Gebruik hardware acceleratie voor video-decodering. Uitschakelen als u problemen met afspelen ondervindt."
|
"hardware_decode_description": "Gebruik hardware acceleratie voor video-decodering. Uitschakelen als u problemen met afspelen ondervindt."
|
||||||
},
|
},
|
||||||
"vlc_subtitles": {
|
"vlc_subtitles": {
|
||||||
"title": "VLC ondertitel instellingen",
|
"title": "VLC Subtitle Settings",
|
||||||
"hint": "Aanpassen van ondertiteling voor VLC-speler. Wijzigingen worden toegepast bij het afspelen.",
|
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
|
||||||
"text_color": "Tekstkleur",
|
"text_color": "Text Color",
|
||||||
"background_color": "Achtergrondkleur",
|
"background_color": "Background Color",
|
||||||
"background_opacity": "Doorzichtigheid achtergrond",
|
"background_opacity": "Background Opacity",
|
||||||
"outline_color": "Kleur omlijning",
|
"outline_color": "Outline Color",
|
||||||
"outline_opacity": "Omtrek opaciteit",
|
"outline_opacity": "Outline Opacity",
|
||||||
"outline_thickness": "Omtrek dikte",
|
"outline_thickness": "Outline Thickness",
|
||||||
"bold": "Bold Text",
|
"bold": "Bold Text",
|
||||||
"margin": "Bottom Margin"
|
"margin": "Bottom Margin"
|
||||||
},
|
},
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"disable_haptic_feedback": "Haptische feedback uitschakelen",
|
"disable_haptic_feedback": "Haptische feedback uitschakelen",
|
||||||
"default_quality": "Standaard kwaliteit",
|
"default_quality": "Standaard kwaliteit",
|
||||||
"default_playback_speed": "Standaard Afspeelsnelheid",
|
"default_playback_speed": "Standaard Afspeelsnelheid",
|
||||||
"auto_play_next_episode": "Volgende aflevering automatisch afspelen",
|
"auto_play_next_episode": "Auto-play Next Episode",
|
||||||
"max_auto_play_episode_count": "Max Automatisch Aflevering Aantal",
|
"max_auto_play_episode_count": "Max Automatisch Aflevering Aantal",
|
||||||
"disabled": "Uitgeschakeld"
|
"disabled": "Uitgeschakeld"
|
||||||
},
|
},
|
||||||
@@ -378,12 +378,12 @@
|
|||||||
"enable_promoted_watchlists": "Gepromote Kijklijst",
|
"enable_promoted_watchlists": "Gepromote Kijklijst",
|
||||||
"hide_watchlists_tab": "Hide Watchlists Tab",
|
"hide_watchlists_tab": "Hide Watchlists Tab",
|
||||||
"home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.",
|
"home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.",
|
||||||
"recommended_movies": "Aanbevolen films",
|
"recommended_movies": "Recommended Movies",
|
||||||
"recommended_series": "Aanbevolen serie",
|
"recommended_series": "Recommended Series",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Opgeslagen",
|
"saved": "Saved",
|
||||||
"refreshed": "Settings refreshed from server",
|
"refreshed": "Settings refreshed from server",
|
||||||
"disabled": "Streamystats uitgeschakeld"
|
"disabled": "Streamystats disabled"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Refresh Settings from Server"
|
"refresh_from_server": "Refresh Settings from Server"
|
||||||
},
|
},
|
||||||
@@ -402,24 +402,24 @@
|
|||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"enable_music_cache": "Enable Music Cache",
|
"enable_music_cache": "Enable Music Cache",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
"music_cache_size": "{{size}} gecached",
|
"music_cache_size": "{{size}} cached",
|
||||||
"music_cache_cleared": "Muziek cache gewist",
|
"music_cache_cleared": "Music cache cleared",
|
||||||
"delete_all_downloaded_songs": "Verwijder alle gedownloade liedjes",
|
"delete_all_downloaded_songs": "Delete All Downloaded Songs",
|
||||||
"downloaded_songs_size": "{{size}} gedownload",
|
"downloaded_songs_size": "{{size}} downloaded",
|
||||||
"downloaded_songs_deleted": "Downloaded songs deleted"
|
"downloaded_songs_deleted": "Downloaded songs deleted"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"title": "Intro",
|
"title": "Intro",
|
||||||
"show_intro": "Toon intro",
|
"show_intro": "Toon intro",
|
||||||
"reset_intro": "Reset Intro"
|
"reset_intro": "intro opnieuw instellen"
|
||||||
},
|
},
|
||||||
"logs": {
|
"logs": {
|
||||||
"logs_title": "Logboek",
|
"logs_title": "Logboek",
|
||||||
"export_logs": "Export logs",
|
"export_logs": "Export logs",
|
||||||
"click_for_more_info": "Klik voor meer info",
|
"click_for_more_info": "Click for more info",
|
||||||
"level": "Niveau",
|
"level": "Niveau",
|
||||||
"no_logs_available": "Geen logs beschikbaar",
|
"no_logs_available": "Geen logs beschikbaar",
|
||||||
"delete_all_logs": "Alle logs verwijderen"
|
"delete_all_logs": "Verwijder alle logs"
|
||||||
},
|
},
|
||||||
"languages": {
|
"languages": {
|
||||||
"title": "Talen",
|
"title": "Talen",
|
||||||
@@ -500,14 +500,14 @@
|
|||||||
"play": "Afspelen",
|
"play": "Afspelen",
|
||||||
"none": "Geen",
|
"none": "Geen",
|
||||||
"track": "Spoor",
|
"track": "Spoor",
|
||||||
"cancel": "Annuleren",
|
"cancel": "Cancel",
|
||||||
"delete": "Verwijderen",
|
"delete": "Delete",
|
||||||
"ok": "Oké",
|
"ok": "OK",
|
||||||
"remove": "Verwijderen",
|
"remove": "Remove",
|
||||||
"next": "Volgende",
|
"next": "Next",
|
||||||
"back": "Terug",
|
"back": "Back",
|
||||||
"continue": "Doorgaan",
|
"continue": "Continue",
|
||||||
"verifying": "Verifiëren..."
|
"verifying": "Verifying..."
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"search": "Zoek...",
|
"search": "Zoek...",
|
||||||
@@ -521,10 +521,10 @@
|
|||||||
"episodes": "Afleveringen",
|
"episodes": "Afleveringen",
|
||||||
"collections": "Collecties",
|
"collections": "Collecties",
|
||||||
"actors": "Acteurs",
|
"actors": "Acteurs",
|
||||||
"artists": "Artiesten",
|
"artists": "Artists",
|
||||||
"albums": "Albums",
|
"albums": "Albums",
|
||||||
"songs": "Nummers",
|
"songs": "Songs",
|
||||||
"playlists": "Afspeellijsten",
|
"playlists": "Playlists",
|
||||||
"request_movies": "Vraag films aan",
|
"request_movies": "Vraag films aan",
|
||||||
"request_series": "Vraag series aan",
|
"request_series": "Vraag series aan",
|
||||||
"recently_added": "Recent Toegevoegd",
|
"recently_added": "Recent Toegevoegd",
|
||||||
@@ -572,7 +572,7 @@
|
|||||||
"genres": "Genres",
|
"genres": "Genres",
|
||||||
"years": "Jaren",
|
"years": "Jaren",
|
||||||
"sort_by": "Sorteren op",
|
"sort_by": "Sorteren op",
|
||||||
"filter_by": "Filteren op",
|
"filter_by": "Filter By",
|
||||||
"sort_order": "Sorteer volgorde",
|
"sort_order": "Sorteer volgorde",
|
||||||
"tags": "Labels"
|
"tags": "Labels"
|
||||||
}
|
}
|
||||||
@@ -719,127 +719,127 @@
|
|||||||
"favorites": "Favorieten"
|
"favorites": "Favorieten"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Muziek",
|
"title": "Music",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"suggestions": "Suggesties",
|
"suggestions": "Suggestions",
|
||||||
"albums": "Albums",
|
"albums": "Albums",
|
||||||
"artists": "Artiesten",
|
"artists": "Artists",
|
||||||
"playlists": "Afspeellijsten",
|
"playlists": "Playlists",
|
||||||
"tracks": "Nummers"
|
"tracks": "tracks"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"all": "Alle"
|
"all": "All"
|
||||||
},
|
},
|
||||||
"recently_added": "Recent toegevoegd",
|
"recently_added": "Recently Added",
|
||||||
"recently_played": "Onlangs afgespeeld",
|
"recently_played": "Recently Played",
|
||||||
"frequently_played": "Vaak afgespeeld",
|
"frequently_played": "Frequently Played",
|
||||||
"explore": "Ontdek",
|
"explore": "Explore",
|
||||||
"top_tracks": "Top Tracks",
|
"top_tracks": "Top Tracks",
|
||||||
"play": "Afspelen",
|
"play": "Play",
|
||||||
"shuffle": "Shuffle",
|
"shuffle": "Shuffle",
|
||||||
"play_top_tracks": "Play Top Tracks",
|
"play_top_tracks": "Play Top Tracks",
|
||||||
"no_suggestions": "Geen suggesties beschikbaar",
|
"no_suggestions": "No suggestions available",
|
||||||
"no_albums": "Geen albums gevonden",
|
"no_albums": "No albums found",
|
||||||
"no_artists": "Geen artiesten gevonden",
|
"no_artists": "No artists found",
|
||||||
"no_playlists": "Geen afspeellijsten gevonden",
|
"no_playlists": "No playlists found",
|
||||||
"album_not_found": "Album niet gevonden",
|
"album_not_found": "Album not found",
|
||||||
"artist_not_found": "Artiest niet gevonden",
|
"artist_not_found": "Artist not found",
|
||||||
"playlist_not_found": "Afspeellijst niet gevonden",
|
"playlist_not_found": "Playlist not found",
|
||||||
"track_options": {
|
"track_options": {
|
||||||
"play_next": "Speel volgende af",
|
"play_next": "Play Next",
|
||||||
"add_to_queue": "Toevoegen aan wachtrij",
|
"add_to_queue": "Add to Queue",
|
||||||
"add_to_playlist": "Voeg toe aan afspeellijst",
|
"add_to_playlist": "Add to Playlist",
|
||||||
"download": "Downloaden",
|
"download": "Download",
|
||||||
"downloaded": "Gedownload",
|
"downloaded": "Downloaded",
|
||||||
"downloading": "Downloaden...",
|
"downloading": "Downloading...",
|
||||||
"cached": "Gecached",
|
"cached": "Cached",
|
||||||
"delete_download": "Download verwijderen",
|
"delete_download": "Delete Download",
|
||||||
"delete_cache": "Verwijderen uit cache",
|
"delete_cache": "Remove from Cache",
|
||||||
"go_to_artist": "Ga naar artiest",
|
"go_to_artist": "Go to Artist",
|
||||||
"go_to_album": "Ga naar album",
|
"go_to_album": "Go to Album",
|
||||||
"add_to_favorites": "Toevoegen aan favorieten",
|
"add_to_favorites": "Add to Favorites",
|
||||||
"remove_from_favorites": "Verwijderen uit favorieten",
|
"remove_from_favorites": "Remove from Favorites",
|
||||||
"remove_from_playlist": "Verwijder uit afspeellijst"
|
"remove_from_playlist": "Remove from Playlist"
|
||||||
},
|
},
|
||||||
"playlists": {
|
"playlists": {
|
||||||
"create_playlist": "Afspeellijst aanmaken",
|
"create_playlist": "Create Playlist",
|
||||||
"playlist_name": "Afspeellijst naam",
|
"playlist_name": "Playlist Name",
|
||||||
"enter_name": "Enter playlist name",
|
"enter_name": "Enter playlist name",
|
||||||
"create": "Aanmaken",
|
"create": "Create",
|
||||||
"search_playlists": "Playlist zoeken...",
|
"search_playlists": "Search playlists...",
|
||||||
"added_to": "Toegevoegd aan {{name}}",
|
"added_to": "Added to {{name}}",
|
||||||
"added": "Toegevoegd aan playlist",
|
"added": "Added to playlist",
|
||||||
"removed_from": "Verwijderd uit {{name}}",
|
"removed_from": "Removed from {{name}}",
|
||||||
"removed": "Verwijderd uit playlist",
|
"removed": "Removed from playlist",
|
||||||
"created": "Playlist created",
|
"created": "Playlist created",
|
||||||
"create_new": "Create New Playlist",
|
"create_new": "Create New Playlist",
|
||||||
"failed_to_add": "Failed to add to playlist",
|
"failed_to_add": "Failed to add to playlist",
|
||||||
"failed_to_remove": "Verwijderen uit afspeellijst is mislukt",
|
"failed_to_remove": "Failed to remove from playlist",
|
||||||
"failed_to_create": "Het maken van de afspeellijst is mislukt",
|
"failed_to_create": "Failed to create playlist",
|
||||||
"delete_playlist": "Afspeellijst verwijderen",
|
"delete_playlist": "Delete Playlist",
|
||||||
"delete_confirm": "Weet u zeker dat u \"{{name}}\"wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
|
"delete_confirm": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"deleted": "Afspeellijst verwijderd.",
|
"deleted": "Playlist deleted",
|
||||||
"failed_to_delete": "Verwijderen van afspeellijst mislukt"
|
"failed_to_delete": "Failed to delete playlist"
|
||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
"title": "Sorteren op",
|
"title": "Sort By",
|
||||||
"alphabetical": "Alfabetisch",
|
"alphabetical": "Alphabetical",
|
||||||
"date_created": "Aanmaakdatum"
|
"date_created": "Date Created"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchlists": {
|
"watchlists": {
|
||||||
"title": "Watchlist",
|
"title": "Watchlists",
|
||||||
"my_watchlists": "Mijn watchlists",
|
"my_watchlists": "My Watchlists",
|
||||||
"public_watchlists": "Public Watchlists",
|
"public_watchlists": "Public Watchlists",
|
||||||
"create_title": "Create Watchlist",
|
"create_title": "Create Watchlist",
|
||||||
"edit_title": "Edit Watchlist",
|
"edit_title": "Edit Watchlist",
|
||||||
"create_button": "Create Watchlist",
|
"create_button": "Create Watchlist",
|
||||||
"save_button": "Wijzigingen opslaan",
|
"save_button": "Save Changes",
|
||||||
"delete_button": "Verwijder",
|
"delete_button": "Delete",
|
||||||
"remove_button": "Verwijderen",
|
"remove_button": "Remove",
|
||||||
"cancel_button": "Annuleren",
|
"cancel_button": "Cancel",
|
||||||
"name_label": "Naam",
|
"name_label": "Name",
|
||||||
"name_placeholder": "Voer naam van kijklijst in",
|
"name_placeholder": "Enter watchlist name",
|
||||||
"description_label": "Beschrijving",
|
"description_label": "Description",
|
||||||
"description_placeholder": "Voer beschrijving in (optioneel)",
|
"description_placeholder": "Enter description (optional)",
|
||||||
"is_public_label": "Openbare Kijklijst",
|
"is_public_label": "Public Watchlist",
|
||||||
"is_public_description": "Sta anderen toe om deze kijklijst te bekijken",
|
"is_public_description": "Allow others to view this watchlist",
|
||||||
"allowed_type_label": "Inhoudstype",
|
"allowed_type_label": "Content Type",
|
||||||
"sort_order_label": "Standaard Sortering",
|
"sort_order_label": "Default Sort Order",
|
||||||
"empty_title": "Geen Kijklijsten",
|
"empty_title": "No Watchlists",
|
||||||
"empty_description": "Maak je eerste kijklijst om je media te organiseren",
|
"empty_description": "Create your first watchlist to start organizing your media",
|
||||||
"empty_watchlist": "Deze watchlist is leeg",
|
"empty_watchlist": "This watchlist is empty",
|
||||||
"empty_watchlist_hint": "Voeg items uit je bibliotheek toe aan deze kijklijst",
|
"empty_watchlist_hint": "Add items from your library to this watchlist",
|
||||||
"not_configured_title": "Streamystats niet geconfigureerd",
|
"not_configured_title": "Streamystats Not Configured",
|
||||||
"not_configured_description": "Configureer Streamystats in instellingen om kijklijsten te gebruiken",
|
"not_configured_description": "Configure Streamystats in settings to use watchlists",
|
||||||
"go_to_settings": "Ga naar Instellingen",
|
"go_to_settings": "Go to Settings",
|
||||||
"add_to_watchlist": "Voeg toe aan kijklijst",
|
"add_to_watchlist": "Add to Watchlist",
|
||||||
"remove_from_watchlist": "Verwijder van kijklijst",
|
"remove_from_watchlist": "Remove from Watchlist",
|
||||||
"select_watchlist": "Selecteer kijklijst",
|
"select_watchlist": "Select Watchlist",
|
||||||
"create_new": "Nieuwe kijklijst aanmaken",
|
"create_new": "Create New Watchlist",
|
||||||
"item": "item",
|
"item": "item",
|
||||||
"items": "items",
|
"items": "items",
|
||||||
"public": "Publiek",
|
"public": "Public",
|
||||||
"private": "Privé",
|
"private": "Private",
|
||||||
"you": "Jij",
|
"you": "You",
|
||||||
"by_owner": "Door een andere gebruiker",
|
"by_owner": "By another user",
|
||||||
"not_found": "Kijklijst niet gevonden",
|
"not_found": "Watchlist not found",
|
||||||
"delete_confirm_title": "Verwijder kijklijst",
|
"delete_confirm_title": "Delete Watchlist",
|
||||||
"delete_confirm_message": "Weet u zeker dat u \"{{name}}\"wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
|
"delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"remove_item_title": "Verwijder van watchlist",
|
"remove_item_title": "Remove from Watchlist",
|
||||||
"remove_item_message": "Verwijder \"{{name}}\" uit deze watchlist?",
|
"remove_item_message": "Remove \"{{name}}\" from this watchlist?",
|
||||||
"loading": "Laden van watchlists...",
|
"loading": "Loading watchlists...",
|
||||||
"no_compatible_watchlists": "Geen compatibele watchlist",
|
"no_compatible_watchlists": "No compatible watchlists",
|
||||||
"create_one_first": "Maak een watchlist aan die dit inhoudstype accepteert"
|
"create_one_first": "Create a watchlist that accepts this content type"
|
||||||
},
|
},
|
||||||
"playback_speed": {
|
"playback_speed": {
|
||||||
"title": "Afspeelsnelheid",
|
"title": "Playback Speed",
|
||||||
"apply_to": "Toepassen op",
|
"apply_to": "Apply To",
|
||||||
"speed": "Snelheid",
|
"speed": "Speed",
|
||||||
"scope": {
|
"scope": {
|
||||||
"media": "Alleen deze media",
|
"media": "This media only",
|
||||||
"show": "Deze serie",
|
"show": "This show",
|
||||||
"all": "Alle media (standaard)"
|
"all": "All media (default)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,44 +34,44 @@
|
|||||||
"search_for_local_servers": "Поиск локальных серверов",
|
"search_for_local_servers": "Поиск локальных серверов",
|
||||||
"searching": "Поиск...",
|
"searching": "Поиск...",
|
||||||
"servers": "Сервера",
|
"servers": "Сервера",
|
||||||
"saved": "Сохранено",
|
"saved": "Saved",
|
||||||
"session_expired": "Сессия истекла",
|
"session_expired": "Session Expired",
|
||||||
"please_login_again": "Ваша сессия истекла. Пожалуйста, войдите снова.",
|
"please_login_again": "Your saved session has expired. Please log in again.",
|
||||||
"remove_saved_login": "Удалить сохраненный аккаунт",
|
"remove_saved_login": "Remove Saved Login",
|
||||||
"remove_saved_login_description": "Ваши сохранённые данные для входа от этого сервера будут удалены. Вам придётся ввести ваши логин и пароль ещё раз.",
|
"remove_saved_login_description": "This will remove your saved credentials for this server. You'll need to enter your username and password again next time.",
|
||||||
"accounts_count": "{{count}} аккаунтов",
|
"accounts_count": "{{count}} accounts",
|
||||||
"select_account": "Выбрать аккаунт",
|
"select_account": "Select Account",
|
||||||
"add_account": "Добавить аккаунт",
|
"add_account": "Add Account",
|
||||||
"remove_account_description": "Данные для входа {{username}} будут удалены."
|
"remove_account_description": "This will remove the saved credentials for {{username}}."
|
||||||
},
|
},
|
||||||
"save_account": {
|
"save_account": {
|
||||||
"title": "Сохранить аккаунт",
|
"title": "Save Account",
|
||||||
"save_for_later": "Сохранить этот аккаунт",
|
"save_for_later": "Save this account",
|
||||||
"security_option": "Опции безопасности",
|
"security_option": "Security Option",
|
||||||
"no_protection": "Без защиты",
|
"no_protection": "No protection",
|
||||||
"no_protection_desc": "Быстрый вход без ввода данных",
|
"no_protection_desc": "Quick login without authentication",
|
||||||
"pin_code": "PIN-код",
|
"pin_code": "PIN code",
|
||||||
"pin_code_desc": "При переключении будет требоваться 4-значный PIN",
|
"pin_code_desc": "4-digit PIN required when switching",
|
||||||
"password": "Пароль",
|
"password": "Re-enter password",
|
||||||
"password_desc": "При переключении будет требоваться пароль",
|
"password_desc": "Password required when switching",
|
||||||
"save_button": "Сохранить",
|
"save_button": "Save",
|
||||||
"cancel_button": "Отмена"
|
"cancel_button": "Cancel"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"enter_pin": "Введите PIN",
|
"enter_pin": "Enter PIN",
|
||||||
"enter_pin_for": "Введите PIN для {{username}}",
|
"enter_pin_for": "Enter PIN for {{username}}",
|
||||||
"enter_4_digits": "Введите 4 цифры",
|
"enter_4_digits": "Enter 4 digits",
|
||||||
"invalid_pin": "Некорректный PIN",
|
"invalid_pin": "Invalid PIN",
|
||||||
"setup_pin": "Установить PIN",
|
"setup_pin": "Set Up PIN",
|
||||||
"confirm_pin": "Подтвердите PIN",
|
"confirm_pin": "Confirm PIN",
|
||||||
"pins_dont_match": "PIN-коды не совпадают",
|
"pins_dont_match": "PINs don't match",
|
||||||
"forgot_pin": "Забыли PIN?",
|
"forgot_pin": "Forgot PIN?",
|
||||||
"forgot_pin_desc": "Ваши данные для входа будут удалены"
|
"forgot_pin_desc": "Your saved credentials will be removed"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"enter_password": "Введите пароль",
|
"enter_password": "Enter Password",
|
||||||
"enter_password_for": "Введите пароль для {{username}}",
|
"enter_password_for": "Enter password for {{username}}",
|
||||||
"invalid_password": "Неверный пароль"
|
"invalid_password": "Invalid password"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"checking_server_connection": "Проверка соединения с сервером...",
|
"checking_server_connection": "Проверка соединения с сервером...",
|
||||||
@@ -82,12 +82,12 @@
|
|||||||
"go_to_downloads": "В загрузки",
|
"go_to_downloads": "В загрузки",
|
||||||
"retry": "Повторить",
|
"retry": "Повторить",
|
||||||
"server_unreachable": "Сервер недоступен",
|
"server_unreachable": "Сервер недоступен",
|
||||||
"server_unreachable_message": "Не удалось соединиться с сервером.\nПожалуйста, проверьте настройки сети.",
|
"server_unreachable_message": "Could not reach the server.\nPlease check your network connection.",
|
||||||
"oops": "Упс!",
|
"oops": "Упс!",
|
||||||
"error_message": "Что-то пошло не так.\nПожалуйста выйдите и зайдите снова.",
|
"error_message": "Что-то пошло не так.\nПожалуйста выйдите и зайдите снова.",
|
||||||
"continue_watching": "Продолжить",
|
"continue_watching": "Продолжить просмотр",
|
||||||
"next_up": "Далее",
|
"next_up": "Следующее",
|
||||||
"continue_and_next_up": "Продолжить и Далее",
|
"continue_and_next_up": "Continue & Next Up",
|
||||||
"recently_added_in": "Недавно добавлено в {{libraryName}}",
|
"recently_added_in": "Недавно добавлено в {{libraryName}}",
|
||||||
"suggested_movies": "Предложенные фильмы",
|
"suggested_movies": "Предложенные фильмы",
|
||||||
"suggested_episodes": "Предложенные серии",
|
"suggested_episodes": "Предложенные серии",
|
||||||
@@ -110,46 +110,46 @@
|
|||||||
"settings_title": "Настройки",
|
"settings_title": "Настройки",
|
||||||
"log_out_button": "Выйти",
|
"log_out_button": "Выйти",
|
||||||
"categories": {
|
"categories": {
|
||||||
"title": "Категории"
|
"title": "Categories"
|
||||||
},
|
},
|
||||||
"playback_controls": {
|
"playback_controls": {
|
||||||
"title": "Воспроизведение и управление"
|
"title": "Playback & Controls"
|
||||||
},
|
},
|
||||||
"audio_subtitles": {
|
"audio_subtitles": {
|
||||||
"title": "Аудио и субтитры"
|
"title": "Audio & Subtitles"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Внешний вид",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Объединить «Продолжить» и «Далее»",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
"hide_remote_session_button": "Скрыть кнопку «Удалённый сеанс»"
|
"hide_remote_session_button": "Hide Remote Session Button"
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"title": "Сеть",
|
"title": "Network",
|
||||||
"local_network": "Локальная сеть",
|
"local_network": "Local Network",
|
||||||
"auto_switch_enabled": "Переключаться дома автоматически",
|
"auto_switch_enabled": "Auto-switch when at home",
|
||||||
"auto_switch_description": "Автоматически переключаться на локальный URL при присоединении к домашней WiFi сети",
|
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
|
||||||
"local_url": "Локальный URL",
|
"local_url": "Local URL",
|
||||||
"local_url_hint": "Введите локальный URL вашего сервера (e.g., http://192.168.1.100:8096)",
|
"local_url_hint": "Enter your local server address (e.g., http://192.168.1.100:8096)",
|
||||||
"local_url_placeholder": "http://192.168.1.100:8096",
|
"local_url_placeholder": "http://192.168.1.100:8096",
|
||||||
"home_wifi_networks": "Домашние WiFi сети",
|
"home_wifi_networks": "Home WiFi Networks",
|
||||||
"add_current_network": "Добавить \"{{ssid}}\"",
|
"add_current_network": "Add \"{{ssid}}\"",
|
||||||
"not_connected_to_wifi": "Нет WiFi соединения",
|
"not_connected_to_wifi": "Not connected to WiFi",
|
||||||
"no_networks_configured": "Нет настроенных сетей",
|
"no_networks_configured": "No networks configured",
|
||||||
"add_network_hint": "Добавьте вашу домашнюю сеть WiFi для включения автоматического переключения",
|
"add_network_hint": "Add your home WiFi network to enable auto-switching",
|
||||||
"current_wifi": "Текущая WiFi сеть",
|
"current_wifi": "Current WiFi",
|
||||||
"using_url": "Используется",
|
"using_url": "Using",
|
||||||
"local": "Локальный",
|
"local": "Local URL",
|
||||||
"remote": "Внешний",
|
"remote": "Remote URL",
|
||||||
"not_connected": "Нет соединения",
|
"not_connected": "Not connected",
|
||||||
"current_server": "Текущий сервер",
|
"current_server": "Current Server",
|
||||||
"remote_url": "Внешний URL",
|
"remote_url": "Remote URL",
|
||||||
"active_url": "Активный URL",
|
"active_url": "Active URL",
|
||||||
"not_configured": "Не настроено",
|
"not_configured": "Not configured",
|
||||||
"network_added": "Сеть добавлена",
|
"network_added": "Network added",
|
||||||
"network_already_added": "Сеть уже добавлена",
|
"network_already_added": "Network already added",
|
||||||
"no_wifi_connected": "Нет WiFi соединения",
|
"no_wifi_connected": "Not connected to WiFi",
|
||||||
"permission_denied": "Нет доступа к местоположению",
|
"permission_denied": "Location permission denied",
|
||||||
"permission_denied_explanation": "Разрешение на доступ к местоположению обязательно для обнаружения WiFi сети при автоматическом переключении. Пожалуйста, включите его в настройках."
|
"permission_denied_explanation": "Location permission is required to detect WiFi network for auto-switching. Please enable it in Settings."
|
||||||
},
|
},
|
||||||
"user_info": {
|
"user_info": {
|
||||||
"user_info_title": "Информация о пользователе",
|
"user_info_title": "Информация о пользователе",
|
||||||
@@ -170,22 +170,22 @@
|
|||||||
},
|
},
|
||||||
"media_controls": {
|
"media_controls": {
|
||||||
"media_controls_title": "Медиа-контроль",
|
"media_controls_title": "Медиа-контроль",
|
||||||
"forward_skip_length": "Шаг перемотки вперёд",
|
"forward_skip_length": "Длина пропуска вперед",
|
||||||
"rewind_length": "Шаг перемотки назад",
|
"rewind_length": "Длина перемотки",
|
||||||
"seconds_unit": "c"
|
"seconds_unit": "c"
|
||||||
},
|
},
|
||||||
"gesture_controls": {
|
"gesture_controls": {
|
||||||
"gesture_controls_title": "Управление жестами",
|
"gesture_controls_title": "Управление жестами",
|
||||||
"horizontal_swipe_skip": "Горизонтальный свайп для перемотки",
|
"horizontal_swipe_skip": "Горизонтальный свайп, чтобы пропустить",
|
||||||
"horizontal_swipe_skip_description": "Проведите влево/вправо, когда элементы управления скрыты, чтобы пропустить",
|
"horizontal_swipe_skip_description": "Проведите влево/вправо, когда элементы управления скрыты, чтобы пропустить",
|
||||||
"left_side_brightness": "Управление яркостью левой стороны",
|
"left_side_brightness": "Управление яркостью левой стороны",
|
||||||
"left_side_brightness_description": "Смахните вверх/вниз на левой стороне для настройки яркости",
|
"left_side_brightness_description": "Смахните вверх/вниз на левой стороне для настройки яркости",
|
||||||
"right_side_volume": "Управление громкостью справа",
|
"right_side_volume": "Управление громкостью справа",
|
||||||
"right_side_volume_description": "Свайп вверх/вниз с правой стороны для настройки громкости",
|
"right_side_volume_description": "Свайп вверх/вниз с правой стороны для настройки громкости",
|
||||||
"hide_volume_slider": "Скрыть индикатор громкости",
|
"hide_volume_slider": "Hide Volume Slider",
|
||||||
"hide_volume_slider_description": "Скрывает индикатор громкости в плеере",
|
"hide_volume_slider_description": "Hide the volume slider in the video player",
|
||||||
"hide_brightness_slider": "Скрыть индикатор яркости",
|
"hide_brightness_slider": "Hide Brightness Slider",
|
||||||
"hide_brightness_slider_description": "Скрывает индикатор яркости в плеере"
|
"hide_brightness_slider_description": "Hide the brightness slider in the video player"
|
||||||
},
|
},
|
||||||
"audio": {
|
"audio": {
|
||||||
"audio_title": "Аудио",
|
"audio_title": "Аудио",
|
||||||
@@ -195,17 +195,17 @@
|
|||||||
"none": "Отсутствует",
|
"none": "Отсутствует",
|
||||||
"language": "Язык",
|
"language": "Язык",
|
||||||
"transcode_mode": {
|
"transcode_mode": {
|
||||||
"title": "Перекодировка аудио",
|
"title": "Audio Transcoding",
|
||||||
"description": "Управляет обработкой пространственного звука (7.1, TrueHD, DTS-HD)",
|
"description": "Controls how surround audio (7.1, TrueHD, DTS-HD) is handled",
|
||||||
"auto": "Авто",
|
"auto": "Auto",
|
||||||
"stereo": "Принудительно в стерео",
|
"stereo": "Force Stereo",
|
||||||
"5_1": "Разрешить 5.1",
|
"5_1": "Allow 5.1",
|
||||||
"passthrough": "Ничего не изменять"
|
"passthrough": "Passthrough"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
"subtitle_title": "Субтитры",
|
"subtitle_title": "Субтитры",
|
||||||
"subtitle_hint": "Настройки отображения субтитров",
|
"subtitle_hint": "Настроить субтитры.",
|
||||||
"subtitle_language": "Язык субтитров",
|
"subtitle_language": "Язык субтитров",
|
||||||
"subtitle_mode": "Режим субтитров",
|
"subtitle_mode": "Режим субтитров",
|
||||||
"set_subtitle_track": "Устанавливать субтитры из предыдущего элемента",
|
"set_subtitle_track": "Устанавливать субтитры из предыдущего элемента",
|
||||||
@@ -226,24 +226,24 @@
|
|||||||
"outline_thickness": "Толщина контура",
|
"outline_thickness": "Толщина контура",
|
||||||
"background_opacity": "Прозрачность фона",
|
"background_opacity": "Прозрачность фона",
|
||||||
"outline_opacity": "Прозрачность контура",
|
"outline_opacity": "Прозрачность контура",
|
||||||
"bold_text": "Жирный",
|
"bold_text": "Bold Text",
|
||||||
"colors": {
|
"colors": {
|
||||||
"Black": "Черный",
|
"Black": "Черный",
|
||||||
"Gray": "Серый",
|
"Gray": "Серый",
|
||||||
"Silver": "Серебристый",
|
"Silver": "Серебряный",
|
||||||
"White": "Белый",
|
"White": "Белый",
|
||||||
"Maroon": "Бордовый",
|
"Maroon": "Марун",
|
||||||
"Red": "Красный",
|
"Red": "Красный",
|
||||||
"Fuchsia": "Пурпурный",
|
"Fuchsia": "Fuchsia",
|
||||||
"Yellow": "Жёлтый",
|
"Yellow": "Жёлтый",
|
||||||
"Olive": "Оливковый",
|
"Olive": "Олив",
|
||||||
"Green": "Зелёный",
|
"Green": "Зелёный",
|
||||||
"Teal": "Бирюзовый",
|
"Teal": "Бирюзовый",
|
||||||
"Lime": "Лаймовый",
|
"Lime": "Лаймовый",
|
||||||
"Purple": "Фиолетовый",
|
"Purple": "Фиолетовый",
|
||||||
"Navy": "Тёмно-синий",
|
"Navy": "Тёмно-синий",
|
||||||
"Blue": "Синий",
|
"Blue": "Синий",
|
||||||
"Aqua": "Голубой"
|
"Aqua": "Акваа"
|
||||||
},
|
},
|
||||||
"thickness": {
|
"thickness": {
|
||||||
"None": "Отсутствует",
|
"None": "Отсутствует",
|
||||||
@@ -251,29 +251,29 @@
|
|||||||
"Normal": "Обычный",
|
"Normal": "Обычный",
|
||||||
"Thick": "Толстый"
|
"Thick": "Толстый"
|
||||||
},
|
},
|
||||||
"subtitle_color": "Цвет субтитров",
|
"subtitle_color": "Subtitle Color",
|
||||||
"subtitle_background_color": "Цвет фона",
|
"subtitle_background_color": "Background Color",
|
||||||
"subtitle_font": "Шрифт субтитров",
|
"subtitle_font": "Subtitle Font",
|
||||||
"ksplayer_title": "Настройки KSPlayer",
|
"ksplayer_title": "KSPlayer Settings",
|
||||||
"hardware_decode": "Аппаратное декодирование",
|
"hardware_decode": "Hardware Decoding",
|
||||||
"hardware_decode_description": "Использовать аппаратное ускорение для декодирования видео. Выключите, если наблюдаете проблемы с воспроизведением."
|
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues."
|
||||||
},
|
},
|
||||||
"vlc_subtitles": {
|
"vlc_subtitles": {
|
||||||
"title": "Настройки субтитров в VLC",
|
"title": "VLC Subtitle Settings",
|
||||||
"hint": "Настройте внешний вид субтитров в VLC плеере. Изменения применятся при следующем воспроизведении.",
|
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
|
||||||
"text_color": "Цвет текста",
|
"text_color": "Text Color",
|
||||||
"background_color": "Цвет фона",
|
"background_color": "Background Color",
|
||||||
"background_opacity": "Прозрачность фона",
|
"background_opacity": "Background Opacity",
|
||||||
"outline_color": "Цвет контура",
|
"outline_color": "Outline Color",
|
||||||
"outline_opacity": "Прозрачность контура",
|
"outline_opacity": "Outline Opacity",
|
||||||
"outline_thickness": "Толщина контура",
|
"outline_thickness": "Outline Thickness",
|
||||||
"bold": "Жирный",
|
"bold": "Bold Text",
|
||||||
"margin": "Отступ снизу"
|
"margin": "Bottom Margin"
|
||||||
},
|
},
|
||||||
"video_player": {
|
"video_player": {
|
||||||
"title": "Видеоплеер",
|
"title": "Video Player",
|
||||||
"video_player": "Видеоплеер",
|
"video_player": "Video Player",
|
||||||
"video_player_description": "Выберите видеоплеер в iOS.",
|
"video_player_description": "Choose which video player to use on iOS.",
|
||||||
"ksplayer": "KSPlayer",
|
"ksplayer": "KSPlayer",
|
||||||
"vlc": "VLC"
|
"vlc": "VLC"
|
||||||
},
|
},
|
||||||
@@ -294,19 +294,19 @@
|
|||||||
"UNKNOWN": "Неизвестное"
|
"UNKNOWN": "Неизвестное"
|
||||||
},
|
},
|
||||||
"safe_area_in_controls": "Безопасная зона в элементах управления",
|
"safe_area_in_controls": "Безопасная зона в элементах управления",
|
||||||
"video_player": "Видеоплеер",
|
"video_player": "Видео прейер",
|
||||||
"video_players": {
|
"video_players": {
|
||||||
"VLC_3": "VLC 3",
|
"VLC_3": "VLC 3",
|
||||||
"VLC_4": "VLC 4 (Экспериментальный + PiP)"
|
"VLC_4": "VLC 4 (Экспериментальный + PiP)"
|
||||||
},
|
},
|
||||||
"show_custom_menu_links": "Показать ссылки кастомного меню",
|
"show_custom_menu_links": "Показать ссылки кастомного меню",
|
||||||
"show_large_home_carousel": "Показывать большую карусель (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Скрыть библиотеки",
|
"hide_libraries": "Скрыть библиотеки",
|
||||||
"select_liraries_you_want_to_hide": "Выберите Библиотеки, которое хотите спрятать из вкладки Библиотеки и домашней страницы.",
|
"select_liraries_you_want_to_hide": "Выберите Библиотеки, которое хотите спрятать из вкладки Библиотеки и домашней страницы.",
|
||||||
"disable_haptic_feedback": "Отключить тактильную обратную связь",
|
"disable_haptic_feedback": "Отключить тактильную обратную связь",
|
||||||
"default_quality": "Качество по умолчанию",
|
"default_quality": "Качество по умолчанию",
|
||||||
"default_playback_speed": "Скорость воспроизведения по умолчанию",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
"auto_play_next_episode": "Автоматически воспроизводить следующий эпизод",
|
"auto_play_next_episode": "Auto-play Next Episode",
|
||||||
"max_auto_play_episode_count": "Максимальное количество автовоспроизведения эпизодов",
|
"max_auto_play_episode_count": "Максимальное количество автовоспроизведения эпизодов",
|
||||||
"disabled": "Отключено"
|
"disabled": "Отключено"
|
||||||
},
|
},
|
||||||
@@ -314,15 +314,15 @@
|
|||||||
"downloads_title": "Загрузки"
|
"downloads_title": "Загрузки"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Музыка",
|
"title": "Music",
|
||||||
"playback_title": "Воспроизведение",
|
"playback_title": "Playback",
|
||||||
"playback_description": "Настройте воспроизведение музыки.",
|
"playback_description": "Configure how music is played.",
|
||||||
"prefer_downloaded": "Предпочитать скачанные песни",
|
"prefer_downloaded": "Prefer Downloaded Songs",
|
||||||
"caching_title": "Кеширование",
|
"caching_title": "Caching",
|
||||||
"caching_description": "Автоматически предкешировать следующие треки для стабильного воспроизведения.",
|
"caching_description": "Automatically cache upcoming tracks for smoother playback.",
|
||||||
"lookahead_enabled": "Включить предкеширование",
|
"lookahead_enabled": "Enable Look-Ahead Caching",
|
||||||
"lookahead_count": "Сколько предкешировать",
|
"lookahead_count": "Tracks to Pre-cache",
|
||||||
"max_cache_size": "Максимальное число предкешированных треков"
|
"max_cache_size": "Max Cache Size"
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"plugins_title": "Плагины",
|
"plugins_title": "Плагины",
|
||||||
@@ -357,39 +357,39 @@
|
|||||||
"save_button": "Сохранить",
|
"save_button": "Сохранить",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Сохранено",
|
"saved": "Сохранено",
|
||||||
"refreshed": "Настройки обновлены с сервера"
|
"refreshed": "Settings refreshed from server"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Обновить настройки с сервера"
|
"refresh_from_server": "Refresh Settings from Server"
|
||||||
},
|
},
|
||||||
"streamystats": {
|
"streamystats": {
|
||||||
"enable_streamystats": "Включить Streamystats",
|
"enable_streamystats": "Enable Streamystats",
|
||||||
"disable_streamystats": "Выключить Streamystats",
|
"disable_streamystats": "Disable Streamystats",
|
||||||
"enable_search": "Использовать в поиске",
|
"enable_search": "Use for Search",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"server_url_placeholder": "http(s)://streamystats.example.com",
|
"server_url_placeholder": "http(s)://streamystats.example.com",
|
||||||
"streamystats_search_hint": "Введите URL вашего сервера Streamystats. URL должен включать http/https и порт при необходимости.",
|
"streamystats_search_hint": "Enter the URL for your Streamystats server. The URL should include http or https and optionally the port.",
|
||||||
"read_more_about_streamystats": "Узнать больше про Streamystats.",
|
"read_more_about_streamystats": "Read More About Streamystats.",
|
||||||
"save_button": "Сохранить",
|
"save_button": "Save",
|
||||||
"save": "Сохранить",
|
"save": "Save",
|
||||||
"features_title": "Функции",
|
"features_title": "Features",
|
||||||
"home_sections_title": "Показывать на главной",
|
"home_sections_title": "Home Sections",
|
||||||
"enable_movie_recommendations": "Рекомендации фильмов",
|
"enable_movie_recommendations": "Movie Recommendations",
|
||||||
"enable_series_recommendations": "Рекомендации сериалов",
|
"enable_series_recommendations": "Series Recommendations",
|
||||||
"enable_promoted_watchlists": "Продвигаемые списки просмотра",
|
"enable_promoted_watchlists": "Promoted Watchlists",
|
||||||
"hide_watchlists_tab": "Скрыть вкладку со списками",
|
"hide_watchlists_tab": "Hide Watchlists Tab",
|
||||||
"home_sections_hint": "Показывать персонализированные рекомендации и подходящие списки просмотров из Streamystats на главной странице.",
|
"home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.",
|
||||||
"recommended_movies": "Рекомендованные фильмы",
|
"recommended_movies": "Recommended Movies",
|
||||||
"recommended_series": "Рекомендованные сериалы",
|
"recommended_series": "Recommended Series",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Сохранено",
|
"saved": "Saved",
|
||||||
"refreshed": "Настройки обновлены с сервера",
|
"refreshed": "Settings refreshed from server",
|
||||||
"disabled": "Streamystats отключен"
|
"disabled": "Streamystats disabled"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Обновить настройки с сервера"
|
"refresh_from_server": "Refresh Settings from Server"
|
||||||
},
|
},
|
||||||
"kefinTweaks": {
|
"kefinTweaks": {
|
||||||
"watchlist_enabler": "Включить интеграцию со списками просмотра",
|
"watchlist_enabler": "Enable our Watchlist integration",
|
||||||
"watchlist_button": "Изменить интеграцию со списками просмотра"
|
"watchlist_button": "Toggle Watchlist integration"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
@@ -398,18 +398,18 @@
|
|||||||
"device_usage": "Устройство {{availableSpace}}%",
|
"device_usage": "Устройство {{availableSpace}}%",
|
||||||
"size_used": "{{used}} из {{total}} использовано",
|
"size_used": "{{used}} из {{total}} использовано",
|
||||||
"delete_all_downloaded_files": "Удалить все загруженные файлы",
|
"delete_all_downloaded_files": "Удалить все загруженные файлы",
|
||||||
"music_cache_title": "Кеш музыки",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Автоматически прекешировать песни по мере прослушивания для плавного воспроизведения и поддержки отсутствия интернета",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"enable_music_cache": "Кешировать музыку",
|
"enable_music_cache": "Enable Music Cache",
|
||||||
"clear_music_cache": "Очистить кеш музыки",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
"music_cache_size": "{{size}} кешировано",
|
"music_cache_size": "{{size}} cached",
|
||||||
"music_cache_cleared": "Кеш музыки очищен",
|
"music_cache_cleared": "Music cache cleared",
|
||||||
"delete_all_downloaded_songs": "Удалить все скачанные песни",
|
"delete_all_downloaded_songs": "Delete All Downloaded Songs",
|
||||||
"downloaded_songs_size": "{{size}} скачано",
|
"downloaded_songs_size": "{{size}} downloaded",
|
||||||
"downloaded_songs_deleted": "Скачанные песни удалены"
|
"downloaded_songs_deleted": "Downloaded songs deleted"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"title": "Вступление",
|
"title": "Intro",
|
||||||
"show_intro": "Показать вступление",
|
"show_intro": "Показать вступление",
|
||||||
"reset_intro": "Сбросить вступление"
|
"reset_intro": "Сбросить вступление"
|
||||||
},
|
},
|
||||||
@@ -441,24 +441,24 @@
|
|||||||
"tvseries": "Сериалы",
|
"tvseries": "Сериалы",
|
||||||
"movies": "Фильмы",
|
"movies": "Фильмы",
|
||||||
"queue": "Очередь",
|
"queue": "Очередь",
|
||||||
"other_media": "Прочие файлы",
|
"other_media": "Другие медиа",
|
||||||
"queue_hint": "Очередь очистится после перезапуска",
|
"queue_hint": "Очередь и загрузки будут удалены при перезагрузке приложения",
|
||||||
"no_items_in_queue": "Нет элементов в очереди",
|
"no_items_in_queue": "Нет элементов в очереди",
|
||||||
"no_downloaded_items": "Нет загруженных файлов",
|
"no_downloaded_items": "Нет загруженых предметов",
|
||||||
"delete_all_movies_button": "Удалить все фильмы",
|
"delete_all_movies_button": "Удалить все фильмы",
|
||||||
"delete_all_tvseries_button": "Удалить все сериалы",
|
"delete_all_tvseries_button": "Удалить все сериалы",
|
||||||
"delete_all_button": "Удалить все",
|
"delete_all_button": "Удалить все",
|
||||||
"delete_all_other_media_button": "Удалить прочие файлы",
|
"delete_all_other_media_button": "Удалить другой материал",
|
||||||
"active_download": "Загружается",
|
"active_download": "Активно загружается",
|
||||||
"no_active_downloads": "Нет активных загрузок",
|
"no_active_downloads": "Нет активных загрузок",
|
||||||
"active_downloads": "Активные",
|
"active_downloads": "Активные загрузки",
|
||||||
"new_app_version_requires_re_download": "Новая версия приложения требует повторной загрузки",
|
"new_app_version_requires_re_download": "Новая версия приложения требует повторной загрузки",
|
||||||
"new_app_version_requires_re_download_description": "Новая версия приложения требует повторной загрузки. Пожалуйста удалите всё и попробуйте заново.",
|
"new_app_version_requires_re_download_description": "Новая версия приложения требует повторной загрузки. Пожалуйста удалите всё и попробуйте заново.",
|
||||||
"back": "Назад",
|
"back": "Назад",
|
||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
"something_went_wrong": "Что-то пошло не так",
|
"something_went_wrong": "Что-то пошло не так",
|
||||||
"could_not_get_stream_url_from_jellyfin": "Не удалось получить ссылку трансляции из Jellyfin",
|
"could_not_get_stream_url_from_jellyfin": "Не удалось получить ссылку трансляции из Jellyfin",
|
||||||
"eta": "Осталось {{eta}}",
|
"eta": "ETA {{eta}}",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"you_are_not_allowed_to_download_files": "Нет разрешения на скачивание файлов.",
|
"you_are_not_allowed_to_download_files": "Нет разрешения на скачивание файлов.",
|
||||||
"deleted_all_movies_successfully": "Все фильмы были успешно удалены!",
|
"deleted_all_movies_successfully": "Все фильмы были успешно удалены!",
|
||||||
@@ -467,64 +467,64 @@
|
|||||||
"failed_to_delete_all_tvseries": "Возникла ошибка при удалении всех сериалов",
|
"failed_to_delete_all_tvseries": "Возникла ошибка при удалении всех сериалов",
|
||||||
"deleted_media_successfully": "Другие носители успешно удалены!",
|
"deleted_media_successfully": "Другие носители успешно удалены!",
|
||||||
"failed_to_delete_media": "Не удалось удалить другой файл",
|
"failed_to_delete_media": "Не удалось удалить другой файл",
|
||||||
"download_deleted": "Удалено",
|
"download_deleted": "Загрузка удалена",
|
||||||
"download_cancelled": "Загрузка отменена",
|
"download_cancelled": "Загрузка отменена",
|
||||||
"could_not_delete_download": "Не удалось удалить загрузку",
|
"could_not_delete_download": "Не удалось удалить загрузку",
|
||||||
"download_paused": "На паузе",
|
"download_paused": "Загрузка приостановлена",
|
||||||
"could_not_pause_download": "Не удалось приостановить загрузку",
|
"could_not_pause_download": "Не удалось приостановить загрузку",
|
||||||
"download_resumed": "Продолжено",
|
"download_resumed": "Загрузка возобновлена",
|
||||||
"could_not_resume_download": "Не удалось продолжить загрузку",
|
"could_not_resume_download": "Не удалось продолжить загрузку",
|
||||||
"download_completed": "Завершено",
|
"download_completed": "Загрузка завершена",
|
||||||
"download_failed": "Не удалось загрузить",
|
"download_failed": "Download Failed",
|
||||||
"download_failed_for_item": "Загрузка {{item}} провалилась с ошибкой: {{error}}",
|
"download_failed_for_item": "Загрузка {{item}} провалилась с ошибкой: {{error}}",
|
||||||
"download_completed_for_item": "{{item}} успешно загружен",
|
"download_completed_for_item": "{{item}} успешно загружен",
|
||||||
"download_started_for_item": "Загрузка началась для {{item}}",
|
"download_started_for_item": "Загрузка началась для {{item}}",
|
||||||
"failed_to_start_download": "Не удалось начать загрузку",
|
"failed_to_start_download": "Не удалось начать загрузку",
|
||||||
"item_already_downloading": "{{item}} уже загружается",
|
"item_already_downloading": "{{item}} is already downloading",
|
||||||
"all_files_deleted": "Все загрузки удалены",
|
"all_files_deleted": "All Downloads Deleted Successfully",
|
||||||
"files_deleted_by_type": "{{count}} {{type}} удалён(о)",
|
"files_deleted_by_type": "{{count}} {{type}} deleted",
|
||||||
"all_files_folders_and_jobs_deleted_successfully": "Все файлы, папки, и задачи были успешно удалены",
|
"all_files_folders_and_jobs_deleted_successfully": "Все файлы, папки, и задачи были успешно удалены",
|
||||||
"failed_to_clean_cache_directory": "Не удалось очистить директорию кэша",
|
"failed_to_clean_cache_directory": "Не удалось очистить директорию кэша",
|
||||||
"could_not_get_download_url_for_item": "Не удалось получить URL загрузки для {{itemName}}",
|
"could_not_get_download_url_for_item": "Не удалось получить URL загрузки для {{itemName}}",
|
||||||
"go_to_downloads": "В загрузки",
|
"go_to_downloads": "В загрузки",
|
||||||
"file_deleted": "{{item}} удалён"
|
"file_deleted": "{{item}} deleted"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"select": "Выбрать",
|
"select": "Выбрать",
|
||||||
"no_trailer_available": "Трейлер недоступен",
|
"no_trailer_available": "Прицеп недоступен",
|
||||||
"video": "Видео",
|
"video": "Видео",
|
||||||
"audio": "Звук",
|
"audio": "Звук",
|
||||||
"subtitle": "Субтитры",
|
"subtitle": "Субтитры",
|
||||||
"play": "Воспроизвести",
|
"play": "Играть",
|
||||||
"none": "Отсутствует",
|
"none": "None",
|
||||||
"track": "Трек",
|
"track": "Track",
|
||||||
"cancel": "Отмена",
|
"cancel": "Cancel",
|
||||||
"delete": "Удалить",
|
"delete": "Delete",
|
||||||
"ok": "ОК",
|
"ok": "OK",
|
||||||
"remove": "Удалить",
|
"remove": "Remove",
|
||||||
"next": "Вперед",
|
"next": "Next",
|
||||||
"back": "Назад",
|
"back": "Back",
|
||||||
"continue": "Продолжить",
|
"continue": "Continue",
|
||||||
"verifying": "Проверка..."
|
"verifying": "Verifying..."
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"search": "Поиск...",
|
"search": "Поиск...",
|
||||||
"x_items": "{{count}} элементов",
|
"x_items": "{{count}} предметов",
|
||||||
"library": "Библиотека",
|
"library": "Библиотека",
|
||||||
"discover": "Найти новое",
|
"discover": "Найти новое",
|
||||||
"no_results": "Ничего не найдено",
|
"no_results": "Нет результатов",
|
||||||
"no_results_found_for": "Ничего не найдено по запросу",
|
"no_results_found_for": "Не было результатов при поиске",
|
||||||
"movies": "Фильмы",
|
"movies": "Фильмы",
|
||||||
"series": "Сериалы",
|
"series": "Сериалы",
|
||||||
"episodes": "Серии",
|
"episodes": "Серии",
|
||||||
"collections": "Коллекции",
|
"collections": "Коллекции",
|
||||||
"actors": "Актеры",
|
"actors": "Актеры",
|
||||||
"artists": "Артисты",
|
"artists": "Artists",
|
||||||
"albums": "Альбомы",
|
"albums": "Albums",
|
||||||
"songs": "Песни",
|
"songs": "Songs",
|
||||||
"playlists": "Плейлисты",
|
"playlists": "Playlists",
|
||||||
"request_movies": "Запросить фильмы",
|
"request_movies": "Запросить фильмы",
|
||||||
"request_series": "Запросить сериалы",
|
"request_series": "Запросить сериалы",
|
||||||
"recently_added": "Недавно добавлено",
|
"recently_added": "Недавно добавлено",
|
||||||
@@ -553,7 +553,7 @@
|
|||||||
"no_results": "Нет результатов",
|
"no_results": "Нет результатов",
|
||||||
"no_libraries_found": "Библиотеки не найдены",
|
"no_libraries_found": "Библиотеки не найдены",
|
||||||
"item_types": {
|
"item_types": {
|
||||||
"movies": "Фильмы",
|
"movies": "фильмы",
|
||||||
"series": "Сериалы",
|
"series": "Сериалы",
|
||||||
"boxsets": "Коллекции",
|
"boxsets": "Коллекции",
|
||||||
"items": "элементы"
|
"items": "элементы"
|
||||||
@@ -571,9 +571,9 @@
|
|||||||
"filters": {
|
"filters": {
|
||||||
"genres": "Жанры",
|
"genres": "Жанры",
|
||||||
"years": "Года",
|
"years": "Года",
|
||||||
"sort_by": "Сортировка",
|
"sort_by": "Сортировать по",
|
||||||
"filter_by": "Фильтр",
|
"filter_by": "Filter By",
|
||||||
"sort_order": "Порядок",
|
"sort_order": "Порядок сортировки",
|
||||||
"tags": "Тэги"
|
"tags": "Тэги"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -604,14 +604,14 @@
|
|||||||
"index": "Индекс:",
|
"index": "Индекс:",
|
||||||
"continue_watching": "Продолжить просмотр",
|
"continue_watching": "Продолжить просмотр",
|
||||||
"go_back": "Назад",
|
"go_back": "Назад",
|
||||||
"downloaded_file_title": "Этот файл уже скачан",
|
"downloaded_file_title": "You have this file downloaded",
|
||||||
"downloaded_file_message": "Хотите воспроизвести скачанный файл?",
|
"downloaded_file_message": "Do you want to play the downloaded file?",
|
||||||
"downloaded_file_yes": "Да",
|
"downloaded_file_yes": "Yes",
|
||||||
"downloaded_file_no": "Нет",
|
"downloaded_file_no": "No",
|
||||||
"downloaded_file_cancel": "Отмена"
|
"downloaded_file_cancel": "Cancel"
|
||||||
},
|
},
|
||||||
"item_card": {
|
"item_card": {
|
||||||
"next_up": "Далее",
|
"next_up": "Следующее",
|
||||||
"no_items_to_display": "Нет элементов для отображения",
|
"no_items_to_display": "Нет элементов для отображения",
|
||||||
"cast_and_crew": "Актеры и съемочная группа",
|
"cast_and_crew": "Актеры и съемочная группа",
|
||||||
"series": "Серии",
|
"series": "Серии",
|
||||||
@@ -644,7 +644,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"live_tv": {
|
"live_tv": {
|
||||||
"next": "Далее",
|
"next": "Следующая",
|
||||||
"previous": "Предыдущая",
|
"previous": "Предыдущая",
|
||||||
"coming_soon": "Скоро",
|
"coming_soon": "Скоро",
|
||||||
"on_now": "Сейчас в эфире",
|
"on_now": "Сейчас в эфире",
|
||||||
@@ -675,7 +675,7 @@
|
|||||||
"series_type": "Тип сериала",
|
"series_type": "Тип сериала",
|
||||||
"release_dates": "Дата релиза",
|
"release_dates": "Дата релиза",
|
||||||
"first_air_date": "Первая дата выхода в эфир",
|
"first_air_date": "Первая дата выхода в эфир",
|
||||||
"next_air_date": "Ближайшая дата выхода в эфир",
|
"next_air_date": "Следующая дата выхода в эфир",
|
||||||
"revenue": "Прибыль",
|
"revenue": "Прибыль",
|
||||||
"budget": "Бюджет",
|
"budget": "Бюджет",
|
||||||
"original_language": "Оригинальный язык",
|
"original_language": "Оригинальный язык",
|
||||||
@@ -693,10 +693,10 @@
|
|||||||
"number_episodes": "{{episode_number}} серий",
|
"number_episodes": "{{episode_number}} серий",
|
||||||
"born": "Рожден",
|
"born": "Рожден",
|
||||||
"appearances": "Появления",
|
"appearances": "Появления",
|
||||||
"approve": "Одобрить",
|
"approve": "Approve",
|
||||||
"decline": "Отклонить",
|
"decline": "Decline",
|
||||||
"requested_by": "Запрошено {{user}}",
|
"requested_by": "Requested by {{user}}",
|
||||||
"unknown_user": "Неизвестный пользователь",
|
"unknown_user": "Unknown User",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"jellyseer_does_not_meet_requirements": "Сервер Jellyseerr не соответствует минимальным требованиям версии! Пожалуйста, обновите до версии не ниже 2.0.0",
|
"jellyseer_does_not_meet_requirements": "Сервер Jellyseerr не соответствует минимальным требованиям версии! Пожалуйста, обновите до версии не ниже 2.0.0",
|
||||||
"jellyseerr_test_failed": "Тест Jellyseerr не пройден. Попробуйте еще раз.",
|
"jellyseerr_test_failed": "Тест Jellyseerr не пройден. Попробуйте еще раз.",
|
||||||
@@ -705,141 +705,141 @@
|
|||||||
"requested_item": "Запрошено {{item}}!",
|
"requested_item": "Запрошено {{item}}!",
|
||||||
"you_dont_have_permission_to_request": "У вас нет разрешения на запрос!",
|
"you_dont_have_permission_to_request": "У вас нет разрешения на запрос!",
|
||||||
"something_went_wrong_requesting_media": "Что-то пошло не так при запросе медиафайлов!",
|
"something_went_wrong_requesting_media": "Что-то пошло не так при запросе медиафайлов!",
|
||||||
"request_approved": "Запрос одобрен!",
|
"request_approved": "Request Approved!",
|
||||||
"request_declined": "Запрос отклонён!",
|
"request_declined": "Request Declined!",
|
||||||
"failed_to_approve_request": "Не удалось одобрить запрос",
|
"failed_to_approve_request": "Failed to Approve Request",
|
||||||
"failed_to_decline_request": "Не удалось отклонить запрос"
|
"failed_to_decline_request": "Failed to Decline Request"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"home": "Главная",
|
"home": "Дом",
|
||||||
"search": "Поиск",
|
"search": "Поиск",
|
||||||
"library": "Библиотека",
|
"library": "Библиотека",
|
||||||
"custom_links": "Ссылки",
|
"custom_links": "Кастомные ссылки",
|
||||||
"favorites": "Избранное"
|
"favorites": "Избранное"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Музыка",
|
"title": "Music",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"suggestions": "Рекомендации",
|
"suggestions": "Suggestions",
|
||||||
"albums": "Альбомы",
|
"albums": "Albums",
|
||||||
"artists": "Исполнители",
|
"artists": "Artists",
|
||||||
"playlists": "Плейлисты",
|
"playlists": "Playlists",
|
||||||
"tracks": "треки"
|
"tracks": "tracks"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"all": "Все"
|
"all": "All"
|
||||||
},
|
},
|
||||||
"recently_added": "Недавно добавлено",
|
"recently_added": "Recently Added",
|
||||||
"recently_played": "Недавно воспроизведено",
|
"recently_played": "Recently Played",
|
||||||
"frequently_played": "Часто играет",
|
"frequently_played": "Frequently Played",
|
||||||
"explore": "Найти новое",
|
"explore": "Explore",
|
||||||
"top_tracks": "Топ",
|
"top_tracks": "Top Tracks",
|
||||||
"play": "Воспроизвести",
|
"play": "Play",
|
||||||
"shuffle": "Перемешать",
|
"shuffle": "Shuffle",
|
||||||
"play_top_tracks": "Воспроизвести топ",
|
"play_top_tracks": "Play Top Tracks",
|
||||||
"no_suggestions": "Нет рекомендаций",
|
"no_suggestions": "No suggestions available",
|
||||||
"no_albums": "Альбомы не найдены",
|
"no_albums": "No albums found",
|
||||||
"no_artists": "Исполнители не найдены",
|
"no_artists": "No artists found",
|
||||||
"no_playlists": "Плейлисты не найдены",
|
"no_playlists": "No playlists found",
|
||||||
"album_not_found": "Альбом не найден",
|
"album_not_found": "Album not found",
|
||||||
"artist_not_found": "Исполнитель не найден",
|
"artist_not_found": "Artist not found",
|
||||||
"playlist_not_found": "Плейлист не найден",
|
"playlist_not_found": "Playlist not found",
|
||||||
"track_options": {
|
"track_options": {
|
||||||
"play_next": "Далее",
|
"play_next": "Play Next",
|
||||||
"add_to_queue": "Добавить в очередь",
|
"add_to_queue": "Add to Queue",
|
||||||
"add_to_playlist": "Добавить в плейлист",
|
"add_to_playlist": "Add to Playlist",
|
||||||
"download": "Скачать",
|
"download": "Download",
|
||||||
"downloaded": "Скачано",
|
"downloaded": "Downloaded",
|
||||||
"downloading": "Скачивается...",
|
"downloading": "Downloading...",
|
||||||
"cached": "Кешировано",
|
"cached": "Cached",
|
||||||
"delete_download": "Удалить загрузку",
|
"delete_download": "Delete Download",
|
||||||
"delete_cache": "Удалить из кеша",
|
"delete_cache": "Remove from Cache",
|
||||||
"go_to_artist": "К исполнителю",
|
"go_to_artist": "Go to Artist",
|
||||||
"go_to_album": "К альбому",
|
"go_to_album": "Go to Album",
|
||||||
"add_to_favorites": "В избранное",
|
"add_to_favorites": "Add to Favorites",
|
||||||
"remove_from_favorites": "Удалить из избранного",
|
"remove_from_favorites": "Remove from Favorites",
|
||||||
"remove_from_playlist": "Удалить из плейлиста"
|
"remove_from_playlist": "Remove from Playlist"
|
||||||
},
|
},
|
||||||
"playlists": {
|
"playlists": {
|
||||||
"create_playlist": "Создать плейлист",
|
"create_playlist": "Create Playlist",
|
||||||
"playlist_name": "Название плейлиста",
|
"playlist_name": "Playlist Name",
|
||||||
"enter_name": "Введите название плейлиста",
|
"enter_name": "Enter playlist name",
|
||||||
"create": "Создать",
|
"create": "Create",
|
||||||
"search_playlists": "Поиск плейлистов...",
|
"search_playlists": "Search playlists...",
|
||||||
"added_to": "Добавлено в {{name}}",
|
"added_to": "Added to {{name}}",
|
||||||
"added": "Добавлено в плейлист",
|
"added": "Added to playlist",
|
||||||
"removed_from": "Удалено из {{name}}",
|
"removed_from": "Removed from {{name}}",
|
||||||
"removed": "Удалено из плейлиста",
|
"removed": "Removed from playlist",
|
||||||
"created": "Плейлист создан",
|
"created": "Playlist created",
|
||||||
"create_new": "Добавить новый плейлист",
|
"create_new": "Create New Playlist",
|
||||||
"failed_to_add": "Не удалось добавить в плейлист",
|
"failed_to_add": "Failed to add to playlist",
|
||||||
"failed_to_remove": "Не удалось удалить из плейлиста",
|
"failed_to_remove": "Failed to remove from playlist",
|
||||||
"failed_to_create": "Не удалось создать плейлист",
|
"failed_to_create": "Failed to create playlist",
|
||||||
"delete_playlist": "Удалить плейлист",
|
"delete_playlist": "Delete Playlist",
|
||||||
"delete_confirm": "Вы уверены, что хотите удалить \"{{name}}\"? Это действие необратимо.",
|
"delete_confirm": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"deleted": "Плейлист удалён",
|
"deleted": "Playlist deleted",
|
||||||
"failed_to_delete": "Не удалось удалить плейлист"
|
"failed_to_delete": "Failed to delete playlist"
|
||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
"title": "Сортировка",
|
"title": "Sort By",
|
||||||
"alphabetical": "По алфавиту",
|
"alphabetical": "Alphabetical",
|
||||||
"date_created": "По дате создания"
|
"date_created": "Date Created"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchlists": {
|
"watchlists": {
|
||||||
"title": "Списки просмотров",
|
"title": "Watchlists",
|
||||||
"my_watchlists": "Мои списки",
|
"my_watchlists": "My Watchlists",
|
||||||
"public_watchlists": "Публичные списки",
|
"public_watchlists": "Public Watchlists",
|
||||||
"create_title": "Создать список",
|
"create_title": "Create Watchlist",
|
||||||
"edit_title": "Редактировать список",
|
"edit_title": "Edit Watchlist",
|
||||||
"create_button": "Создать список",
|
"create_button": "Create Watchlist",
|
||||||
"save_button": "Сохранить изменения",
|
"save_button": "Save Changes",
|
||||||
"delete_button": "Удалить",
|
"delete_button": "Delete",
|
||||||
"remove_button": "Удалить",
|
"remove_button": "Remove",
|
||||||
"cancel_button": "Отмена",
|
"cancel_button": "Cancel",
|
||||||
"name_label": "Название",
|
"name_label": "Name",
|
||||||
"name_placeholder": "Введите название списка",
|
"name_placeholder": "Enter watchlist name",
|
||||||
"description_label": "Описание",
|
"description_label": "Description",
|
||||||
"description_placeholder": "Введите описание (не обязательно)",
|
"description_placeholder": "Enter description (optional)",
|
||||||
"is_public_label": "Публичный",
|
"is_public_label": "Public Watchlist",
|
||||||
"is_public_description": "Разрешить остальным пользователям видеть этот список",
|
"is_public_description": "Allow others to view this watchlist",
|
||||||
"allowed_type_label": "Тип контента",
|
"allowed_type_label": "Content Type",
|
||||||
"sort_order_label": "Сортировка по умолчанию",
|
"sort_order_label": "Default Sort Order",
|
||||||
"empty_title": "Нет списков",
|
"empty_title": "No Watchlists",
|
||||||
"empty_description": "Создайте ваш первый список для управления вашими медиа",
|
"empty_description": "Create your first watchlist to start organizing your media",
|
||||||
"empty_watchlist": "Этот список пуст",
|
"empty_watchlist": "This watchlist is empty",
|
||||||
"empty_watchlist_hint": "Добавляйте элементы из библиотеки в этот список",
|
"empty_watchlist_hint": "Add items from your library to this watchlist",
|
||||||
"not_configured_title": "Streamystats не настроен",
|
"not_configured_title": "Streamystats Not Configured",
|
||||||
"not_configured_description": "Настройте Streamystats для использования функционала списков",
|
"not_configured_description": "Configure Streamystats in settings to use watchlists",
|
||||||
"go_to_settings": "В настройки",
|
"go_to_settings": "Go to Settings",
|
||||||
"add_to_watchlist": "Добавить в список просмотра",
|
"add_to_watchlist": "Add to Watchlist",
|
||||||
"remove_from_watchlist": "Удалить из списка просмотра",
|
"remove_from_watchlist": "Remove from Watchlist",
|
||||||
"select_watchlist": "Выбрать список",
|
"select_watchlist": "Select Watchlist",
|
||||||
"create_new": "Создать новый список",
|
"create_new": "Create New Watchlist",
|
||||||
"item": "элемент",
|
"item": "item",
|
||||||
"items": "элементы",
|
"items": "items",
|
||||||
"public": "Публичный",
|
"public": "Public",
|
||||||
"private": "Личный",
|
"private": "Private",
|
||||||
"you": "Ваш",
|
"you": "You",
|
||||||
"by_owner": "Другим пользователем",
|
"by_owner": "By another user",
|
||||||
"not_found": "Список не найден",
|
"not_found": "Watchlist not found",
|
||||||
"delete_confirm_title": "Удалить список",
|
"delete_confirm_title": "Delete Watchlist",
|
||||||
"delete_confirm_message": "Вы уверены, что хотите удалить список \"{{name}}\"? Это действие необратимо.",
|
"delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"remove_item_title": "Удалить из списка",
|
"remove_item_title": "Remove from Watchlist",
|
||||||
"remove_item_message": "Удалить \"{{name}}\" из списка?",
|
"remove_item_message": "Remove \"{{name}}\" from this watchlist?",
|
||||||
"loading": "Загрузка списков...",
|
"loading": "Loading watchlists...",
|
||||||
"no_compatible_watchlists": "Нет совместимых списков",
|
"no_compatible_watchlists": "No compatible watchlists",
|
||||||
"create_one_first": "Создайте список просмотра с подходящим типом контента"
|
"create_one_first": "Create a watchlist that accepts this content type"
|
||||||
},
|
},
|
||||||
"playback_speed": {
|
"playback_speed": {
|
||||||
"title": "Скорость воспроизведения",
|
"title": "Playback Speed",
|
||||||
"apply_to": "Применять к",
|
"apply_to": "Apply To",
|
||||||
"speed": "Скорость",
|
"speed": "Speed",
|
||||||
"scope": {
|
"scope": {
|
||||||
"media": "Только в этот раз",
|
"media": "This media only",
|
||||||
"show": "Ко всему сериалу",
|
"show": "This show",
|
||||||
"all": "Ко всем файлам (по умолчанию)"
|
"all": "All media (default)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,88 +7,88 @@
|
|||||||
"username_placeholder": "Kullanıcı adı",
|
"username_placeholder": "Kullanıcı adı",
|
||||||
"password_placeholder": "Şifre",
|
"password_placeholder": "Şifre",
|
||||||
"login_button": "Giriş yap",
|
"login_button": "Giriş yap",
|
||||||
"quick_connect": "Hızlı Bağlan",
|
"quick_connect": "Hızlı Bağlantı",
|
||||||
"enter_code_to_login": "Giriş yapmak için {{code}} kodunu girin",
|
"enter_code_to_login": "Giriş yapmak için {{code}} kodunu girin",
|
||||||
"failed_to_initiate_quick_connect": "Hızlı Bağlan başlatılamadı",
|
"failed_to_initiate_quick_connect": "Quick Connect başlatılamadı",
|
||||||
"got_it": "Anlaşıldı",
|
"got_it": "Anlaşıldı",
|
||||||
"connection_failed": "Bağlantı başarısız",
|
"connection_failed": "Bağlantı başarısız",
|
||||||
"could_not_connect_to_server": "Sunucuya bağlanılamadı. Lütfen URL'yi ve ağ bağlantınızı kontrol edin.",
|
"could_not_connect_to_server": "Sunucuya bağlanılamadı. Lütfen URL'yi ve ağ bağlantınızı kontrol edin",
|
||||||
"an_unexpected_error_occured": "Beklenmedik bir hata oluştu",
|
"an_unexpected_error_occured": "Beklenmedik bir hata oluştu",
|
||||||
"change_server": "Sunucu değiştir",
|
"change_server": "Sunucuyu değiştir",
|
||||||
"invalid_username_or_password": "Geçersiz kullanıcı adı veya şifre",
|
"invalid_username_or_password": "Geçersiz kullanıcı adı veya şifre",
|
||||||
"user_does_not_have_permission_to_log_in": "Kullanıcının giriş yapma izni yok",
|
"user_does_not_have_permission_to_log_in": "Kullanıcının giriş yapma izni yok",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Sunucunun yanıt vermesi çok uzun sürüyor, lütfen daha sonra tekrar deneyin",
|
"server_is_taking_too_long_to_respond_try_again_later": "Sunucu yanıt vermekte çok uzun sürüyor, lütfen tekrar deneyin",
|
||||||
"server_received_too_many_requests_try_again_later": "Sunucu çok fazla istek aldı, lütfen daha sonra tekrar deneyin.",
|
"server_received_too_many_requests_try_again_later": "Sunucu çok fazla istek aldı, lütfen tekrar deneyin.",
|
||||||
"there_is_a_server_error": "Sunucu hatası var",
|
"there_is_a_server_error": "Sunucu hatası var",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Beklenmedik bir hata oluştu. Sunucu URL'sini doğru girdiğinizden emin misiniz?",
|
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Beklenmedik bir hata oluştu. Sunucu URL'sini doğru girdiğinizden emin oldunuz mu?",
|
||||||
"too_old_server_text": "Desteklenmeyen Jellyfin Sunucu sürümü bulundu.",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "Lütfen Jellyfin'i en son sürüme güncelleyin."
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"enter_url_to_jellyfin_server": "Jellyfin sunucunusun URL adresini girin",
|
"enter_url_to_jellyfin_server": "Jellyfin sunucunusun URL'sini girin",
|
||||||
"server_url_placeholder": "http(s)://sunucunuz.com",
|
"server_url_placeholder": "http(s)://sunucunuz.com",
|
||||||
"connect_button": "Bağlan",
|
"connect_button": "Bağlan",
|
||||||
"previous_servers": "Önceki sunucular",
|
"previous_servers": "Önceki sunucular",
|
||||||
"clear_button": "Temizle",
|
"clear_button": "Temizle",
|
||||||
"swipe_to_remove": "Kaldırmak için kaydırın",
|
"swipe_to_remove": "Swipe to remove",
|
||||||
"search_for_local_servers": "Yerel sunucuları ara",
|
"search_for_local_servers": "Yerel sunucuları ara",
|
||||||
"searching": "Aranıyor...",
|
"searching": "Aranıyor...",
|
||||||
"servers": "Sunucular",
|
"servers": "Sunucular",
|
||||||
"saved": "Kaydedildi",
|
"saved": "Saved",
|
||||||
"session_expired": "Oturum süresi doldu",
|
"session_expired": "Session Expired",
|
||||||
"please_login_again": "Kaydedilmiş oturumunuzun süresi doldu. Lütfen tekrar giriş yapın.",
|
"please_login_again": "Your saved session has expired. Please log in again.",
|
||||||
"remove_saved_login": "Kayıtlı oturumu kaldır",
|
"remove_saved_login": "Remove Saved Login",
|
||||||
"remove_saved_login_description": "Bu sunucu için kaydedilmiş kimlik bilgileriniz kaldırılacaktır. Bir sonraki sefere kullanıcı adı ve şifrenizi yeniden girmeniz gerekecek.",
|
"remove_saved_login_description": "This will remove your saved credentials for this server. You'll need to enter your username and password again next time.",
|
||||||
"accounts_count": "{{count}} hesap",
|
"accounts_count": "{{count}} accounts",
|
||||||
"select_account": "Hesap Seç",
|
"select_account": "Select Account",
|
||||||
"add_account": "Hesap Ekle",
|
"add_account": "Add Account",
|
||||||
"remove_account_description": "{{username}} için kayıtlı bilgiler kaldırılacaktır."
|
"remove_account_description": "This will remove the saved credentials for {{username}}."
|
||||||
},
|
},
|
||||||
"save_account": {
|
"save_account": {
|
||||||
"title": "Hesabı Kaydet",
|
"title": "Save Account",
|
||||||
"save_for_later": "Bu hesabı kaydet",
|
"save_for_later": "Save this account",
|
||||||
"security_option": "Güvenlik Seçeneği",
|
"security_option": "Security Option",
|
||||||
"no_protection": "No protection",
|
"no_protection": "No protection",
|
||||||
"no_protection_desc": "Kimlik doğrulamasız hızlı giriş",
|
"no_protection_desc": "Quick login without authentication",
|
||||||
"pin_code": "PIN kodu",
|
"pin_code": "PIN code",
|
||||||
"pin_code_desc": "Geçiş yaparken 4 haneli PIN kodu gereklidir",
|
"pin_code_desc": "4-digit PIN required when switching",
|
||||||
"password": "Şifrenizi tekrar girin ",
|
"password": "Re-enter password",
|
||||||
"password_desc": "Geçiş yaparken şifre gereklidir",
|
"password_desc": "Password required when switching",
|
||||||
"save_button": "Kaydet",
|
"save_button": "Save",
|
||||||
"cancel_button": "Vazgeç"
|
"cancel_button": "Cancel"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"enter_pin": "PIN kodunu girin",
|
"enter_pin": "Enter PIN",
|
||||||
"enter_pin_for": "{{username}} için PIN kodunu girin",
|
"enter_pin_for": "Enter PIN for {{username}}",
|
||||||
"enter_4_digits": "4 hane girin",
|
"enter_4_digits": "Enter 4 digits",
|
||||||
"invalid_pin": "Geçersiz PIN kodu",
|
"invalid_pin": "Invalid PIN",
|
||||||
"setup_pin": "PIN kodunu ayarla",
|
"setup_pin": "Set Up PIN",
|
||||||
"confirm_pin": "PIN kodunu onayla",
|
"confirm_pin": "Confirm PIN",
|
||||||
"pins_dont_match": "PIN kodları eşleşmiyor",
|
"pins_dont_match": "PINs don't match",
|
||||||
"forgot_pin": "PIN kodunu mu unuttunuz?",
|
"forgot_pin": "Forgot PIN?",
|
||||||
"forgot_pin_desc": "Kayıtlı bilgileriniz kaldırılacaktır"
|
"forgot_pin_desc": "Your saved credentials will be removed"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"enter_password": "Şifrenizi girin",
|
"enter_password": "Enter Password",
|
||||||
"enter_password_for": "{{username}} için şifrenizi girin",
|
"enter_password_for": "Enter password for {{username}}",
|
||||||
"invalid_password": "Geçersiz şifre"
|
"invalid_password": "Invalid password"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"checking_server_connection": "Sunucu bağlantısı kontrol ediliyor...",
|
"checking_server_connection": "Checking server connection...",
|
||||||
"no_internet": "İnternet Yok",
|
"no_internet": "İnternet Yok",
|
||||||
"no_items": "Öge Yok",
|
"no_items": "Öge Yok",
|
||||||
"no_internet_message": "Endişelenmeyin, indirilmiş içerikleri izleyebilirsiniz.",
|
"no_internet_message": "Endişelenmeyin, hala\ndownloaded içerik izleyebilirsiniz.",
|
||||||
"checking_server_connection_message": "Sunucuya bağlantı kontrol ediliyor",
|
"checking_server_connection_message": "Checking connection to server",
|
||||||
"go_to_downloads": "İndirilenlere git",
|
"go_to_downloads": "İndirmelere Git",
|
||||||
"retry": "Tekrar dene",
|
"retry": "Retry",
|
||||||
"server_unreachable": "Sunucuya ulaşılamıyor",
|
"server_unreachable": "Server Unreachable",
|
||||||
"server_unreachable_message": "Sunucuya bağlanılamadı. Lütfen ağ bağlantınızı kontrol edin.",
|
"server_unreachable_message": "Could not reach the server.\nPlease check your network connection.",
|
||||||
"oops": "Hups!",
|
"oops": "Hups!",
|
||||||
"error_message": "Bir şeyler ters gitti.\nLütfen çıkış yapıp tekrar giriş yapın.",
|
"error_message": "Bir şeyler ters gitti.\nLütfen çıkış yapın ve tekrar giriş yapın.",
|
||||||
"continue_watching": "İzlemeye Devam Et",
|
"continue_watching": "İzlemeye Devam Et",
|
||||||
"next_up": "Sonraki",
|
"next_up": "Sonraki",
|
||||||
"continue_and_next_up": "İzlemeye Devam Et & Sıradakiler",
|
"continue_and_next_up": "Continue & Next Up",
|
||||||
"recently_added_in": "{{libraryName}} Kütüphanesine Son Eklenenler",
|
"recently_added_in": "{{libraryName}}'de Yakınlarda Eklendi",
|
||||||
"suggested_movies": "Önerilen Filmler",
|
"suggested_movies": "Önerilen Filmler",
|
||||||
"suggested_episodes": "Önerilen Bölümler",
|
"suggested_episodes": "Önerilen Bölümler",
|
||||||
"intro": {
|
"intro": {
|
||||||
@@ -110,52 +110,52 @@
|
|||||||
"settings_title": "Ayarlar",
|
"settings_title": "Ayarlar",
|
||||||
"log_out_button": "Çıkış Yap",
|
"log_out_button": "Çıkış Yap",
|
||||||
"categories": {
|
"categories": {
|
||||||
"title": "Kategoriler"
|
"title": "Categories"
|
||||||
},
|
},
|
||||||
"playback_controls": {
|
"playback_controls": {
|
||||||
"title": "Oynatma & Kontroller"
|
"title": "Playback & Controls"
|
||||||
},
|
},
|
||||||
"audio_subtitles": {
|
"audio_subtitles": {
|
||||||
"title": "Ses & Altyazılar"
|
"title": "Audio & Subtitles"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Görünüm",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "İzlemeye Devam Et & Sıradakiler'i birleştir",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
"hide_remote_session_button": "Uzak Oturum Butonunu Gizle"
|
"hide_remote_session_button": "Hide Remote Session Button"
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"title": "Ağ",
|
"title": "Network",
|
||||||
"local_network": "Yerel Ağ",
|
"local_network": "Local Network",
|
||||||
"auto_switch_enabled": "Evdeyken otomatik geçiş yap",
|
"auto_switch_enabled": "Auto-switch when at home",
|
||||||
"auto_switch_description": "Ev WiFi'sine bağlanınca otomatik olarak yerek URL adresine geçiş yap",
|
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
|
||||||
"local_url": "Yerel URL Adresi",
|
"local_url": "Local URL",
|
||||||
"local_url_hint": "Yerel sunucu adresinizi girin (http://192.168.1.100:8096, gibi)",
|
"local_url_hint": "Enter your local server address (e.g., http://192.168.1.100:8096)",
|
||||||
"local_url_placeholder": "http://192.168.1.100:8096",
|
"local_url_placeholder": "http://192.168.1.100:8096",
|
||||||
"home_wifi_networks": "Ev WiFi ağları",
|
"home_wifi_networks": "Home WiFi Networks",
|
||||||
"add_current_network": "\"{{ssid}}\"'yi ekle",
|
"add_current_network": "Add \"{{ssid}}\"",
|
||||||
"not_connected_to_wifi": "WiFi'a bağlı değil",
|
"not_connected_to_wifi": "Not connected to WiFi",
|
||||||
"no_networks_configured": "Herhangi bir ağ ayarlanmadı",
|
"no_networks_configured": "No networks configured",
|
||||||
"add_network_hint": "Otomatik geçişi etkinleştirmek için ev WiFi'nizi ekleyin",
|
"add_network_hint": "Add your home WiFi network to enable auto-switching",
|
||||||
"current_wifi": "Şu anki WiFi",
|
"current_wifi": "Current WiFi",
|
||||||
"using_url": "Kullanılıyor",
|
"using_url": "Using",
|
||||||
"local": "Yerel URL Adresi",
|
"local": "Local URL",
|
||||||
"remote": "Uzak URL Adresi",
|
"remote": "Remote URL",
|
||||||
"not_connected": "Bağlı değil",
|
"not_connected": "Not connected",
|
||||||
"current_server": "Geçerli Sunucu",
|
"current_server": "Current Server",
|
||||||
"remote_url": "Uzak URL Adresi",
|
"remote_url": "Remote URL",
|
||||||
"active_url": "Aktif URL Adresi",
|
"active_url": "Active URL",
|
||||||
"not_configured": "Yapılandırılmamış",
|
"not_configured": "Not configured",
|
||||||
"network_added": "Ağ eklendi",
|
"network_added": "Network added",
|
||||||
"network_already_added": "Ağ zaten eklendi",
|
"network_already_added": "Network already added",
|
||||||
"no_wifi_connected": "WiFi'a bağlı değil",
|
"no_wifi_connected": "Not connected to WiFi",
|
||||||
"permission_denied": "Konum izni reddedildi",
|
"permission_denied": "Location permission denied",
|
||||||
"permission_denied_explanation": "Otomatik geçiş yapabilmek için WiFi ağını algılayabilmek için konum izni gereklidir. Lütfen Ayarlarda etkinleştirin."
|
"permission_denied_explanation": "Location permission is required to detect WiFi network for auto-switching. Please enable it in Settings."
|
||||||
},
|
},
|
||||||
"user_info": {
|
"user_info": {
|
||||||
"user_info_title": "Kullanıcı Bilgisi",
|
"user_info_title": "Kullanıcı Bilgisi",
|
||||||
"user": "Kullanıcı",
|
"user": "Kullanıcı",
|
||||||
"server": "Sunucu",
|
"server": "Sunucu",
|
||||||
"token": "Erişim Anahtarı",
|
"token": "Token",
|
||||||
"app_version": "Uygulama Sürümü"
|
"app_version": "Uygulama Sürümü"
|
||||||
},
|
},
|
||||||
"quick_connect": {
|
"quick_connect": {
|
||||||
@@ -172,20 +172,20 @@
|
|||||||
"media_controls_title": "Medya Kontrolleri",
|
"media_controls_title": "Medya Kontrolleri",
|
||||||
"forward_skip_length": "İleri Sarma Uzunluğu",
|
"forward_skip_length": "İleri Sarma Uzunluğu",
|
||||||
"rewind_length": "Geri Sarma Uzunluğu",
|
"rewind_length": "Geri Sarma Uzunluğu",
|
||||||
"seconds_unit": "sn"
|
"seconds_unit": "s"
|
||||||
},
|
},
|
||||||
"gesture_controls": {
|
"gesture_controls": {
|
||||||
"gesture_controls_title": "Hareketle Kontrol",
|
"gesture_controls_title": "Gesture Controls",
|
||||||
"horizontal_swipe_skip": "Atlamak için yatay kaydırma",
|
"horizontal_swipe_skip": "Horizontal Swipe to Skip",
|
||||||
"horizontal_swipe_skip_description": "Kontroller gizliyken sola/sağa kaydırarak atlama",
|
"horizontal_swipe_skip_description": "Swipe left/right when controls are hidden to skip",
|
||||||
"left_side_brightness": "Sol Taraf Parlaklık Kontrolü",
|
"left_side_brightness": "Left Side Brightness Control",
|
||||||
"left_side_brightness_description": "Sol tarafta aşağı/yukarı kaydırarak parlaklık ayarı",
|
"left_side_brightness_description": "Swipe up/down on left side to adjust brightness",
|
||||||
"right_side_volume": "Sağ Taraf Ses Kontrolü",
|
"right_side_volume": "Right Side Volume Control",
|
||||||
"right_side_volume_description": "Sağ tarafta aşağı/yukarı kaydırarak ses ayarı",
|
"right_side_volume_description": "Swipe up/down on right side to adjust volume",
|
||||||
"hide_volume_slider": "Ses Ayarını Gizle",
|
"hide_volume_slider": "Hide Volume Slider",
|
||||||
"hide_volume_slider_description": "Video oynatıcıda ses ayarını gizle",
|
"hide_volume_slider_description": "Hide the volume slider in the video player",
|
||||||
"hide_brightness_slider": "Parlaklık Ayarını Gizle",
|
"hide_brightness_slider": "Hide Brightness Slider",
|
||||||
"hide_brightness_slider_description": "Video oynatıcıda parlaklık ayarını gizle"
|
"hide_brightness_slider_description": "Hide the brightness slider in the video player"
|
||||||
},
|
},
|
||||||
"audio": {
|
"audio": {
|
||||||
"audio_title": "Ses",
|
"audio_title": "Ses",
|
||||||
@@ -195,12 +195,12 @@
|
|||||||
"none": "Yok",
|
"none": "Yok",
|
||||||
"language": "Dil",
|
"language": "Dil",
|
||||||
"transcode_mode": {
|
"transcode_mode": {
|
||||||
"title": "Ses Kod Dönüştürmesi",
|
"title": "Audio Transcoding",
|
||||||
"description": "Surround sesin (7.1, TrueHD, DTS-HD) nasıl işleneceğini kontrol eder.",
|
"description": "Controls how surround audio (7.1, TrueHD, DTS-HD) is handled",
|
||||||
"auto": "Oto",
|
"auto": "Auto",
|
||||||
"stereo": "Stereo'ya zorla",
|
"stereo": "Force Stereo",
|
||||||
"5_1": "5.1'e izin ver",
|
"5_1": "Allow 5.1",
|
||||||
"passthrough": "Doğrudan geçiş"
|
"passthrough": "Passthrough"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
@@ -220,60 +220,60 @@
|
|||||||
"None": "Yok",
|
"None": "Yok",
|
||||||
"OnlyForced": "Sadece Zorunlu"
|
"OnlyForced": "Sadece Zorunlu"
|
||||||
},
|
},
|
||||||
"text_color": "Metin Rengi",
|
"text_color": "Text Color",
|
||||||
"background_color": "Arkaplan Rengi",
|
"background_color": "Background Color",
|
||||||
"outline_color": "Kenarlık Rengi",
|
"outline_color": "Outline Color",
|
||||||
"outline_thickness": "Kenarlık kalınlığı",
|
"outline_thickness": "Outline Thickness",
|
||||||
"background_opacity": "Arkaplan Opaklığı",
|
"background_opacity": "Background Opacity",
|
||||||
"outline_opacity": "Kenarlık Opaklığı",
|
"outline_opacity": "Outline Opacity",
|
||||||
"bold_text": "Kalın Metin",
|
"bold_text": "Bold Text",
|
||||||
"colors": {
|
"colors": {
|
||||||
"Black": "Siyah",
|
"Black": "Black",
|
||||||
"Gray": "Gri",
|
"Gray": "Gray",
|
||||||
"Silver": "Gümüş",
|
"Silver": "Silver",
|
||||||
"White": "Beyaz",
|
"White": "White",
|
||||||
"Maroon": "Kestane",
|
"Maroon": "Maroon",
|
||||||
"Red": "Kırmızı",
|
"Red": "Red",
|
||||||
"Fuchsia": "Fuşya",
|
"Fuchsia": "Fuchsia",
|
||||||
"Yellow": "Sarı",
|
"Yellow": "Yellow",
|
||||||
"Olive": "Zeytin yeşili",
|
"Olive": "Olive",
|
||||||
"Green": "Yeşil",
|
"Green": "Green",
|
||||||
"Teal": "Deniz mavisi",
|
"Teal": "Teal",
|
||||||
"Lime": "Limon",
|
"Lime": "Lime",
|
||||||
"Purple": "Mor",
|
"Purple": "Purple",
|
||||||
"Navy": "Lacivert",
|
"Navy": "Navy",
|
||||||
"Blue": "Mavi",
|
"Blue": "Blue",
|
||||||
"Aqua": "Açık Mavi"
|
"Aqua": "Aqua"
|
||||||
},
|
},
|
||||||
"thickness": {
|
"thickness": {
|
||||||
"None": "Hiçbiri",
|
"None": "Hiçbiri",
|
||||||
"Thin": "İnce",
|
"Thin": "Thin",
|
||||||
"Normal": "Normal",
|
"Normal": "Normal",
|
||||||
"Thick": "Kalın"
|
"Thick": "Thick"
|
||||||
},
|
},
|
||||||
"subtitle_color": "Altyazı Rengi",
|
"subtitle_color": "Subtitle Color",
|
||||||
"subtitle_background_color": "Arkaplan Rengi",
|
"subtitle_background_color": "Background Color",
|
||||||
"subtitle_font": "Altyazı Yazı Tipi",
|
"subtitle_font": "Subtitle Font",
|
||||||
"ksplayer_title": "KSPlayer Ayarları",
|
"ksplayer_title": "KSPlayer Settings",
|
||||||
"hardware_decode": "Donanımsal Kod Çözme",
|
"hardware_decode": "Hardware Decoding",
|
||||||
"hardware_decode_description": "Video kod çözme için donanımsal hızlandırma kullan. Oynatma sorunları yaşıyorsanız devre dışı bırakın."
|
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues."
|
||||||
},
|
},
|
||||||
"vlc_subtitles": {
|
"vlc_subtitles": {
|
||||||
"title": "VLC Altyazı Ayarları",
|
"title": "VLC Subtitle Settings",
|
||||||
"hint": "VLC oynatıcı için altyazı görünümünü değiştirin. Değişiklikler bir sonraki oynatmada etkili olacak.",
|
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
|
||||||
"text_color": "Metin Rengi",
|
"text_color": "Text Color",
|
||||||
"background_color": "Arkaplan Rengi",
|
"background_color": "Background Color",
|
||||||
"background_opacity": "Arkaplan Opaklığı",
|
"background_opacity": "Background Opacity",
|
||||||
"outline_color": "Kenarlık Rengi",
|
"outline_color": "Outline Color",
|
||||||
"outline_opacity": "Kenarlık Opaklığı",
|
"outline_opacity": "Outline Opacity",
|
||||||
"outline_thickness": "Kenarlık Kalınlığı",
|
"outline_thickness": "Outline Thickness",
|
||||||
"bold": "Kalın Metin",
|
"bold": "Bold Text",
|
||||||
"margin": "Alt Kenar Boşluğu"
|
"margin": "Bottom Margin"
|
||||||
},
|
},
|
||||||
"video_player": {
|
"video_player": {
|
||||||
"title": "Video oynatıcısı",
|
"title": "Video Player",
|
||||||
"video_player": "Video oynatıcısı",
|
"video_player": "Video Player",
|
||||||
"video_player_description": "iOS'da hangi video oynatıcının kullanılacağını seçin.",
|
"video_player_description": "Choose which video player to use on iOS.",
|
||||||
"ksplayer": "KSPlayer",
|
"ksplayer": "KSPlayer",
|
||||||
"vlc": "VLC"
|
"vlc": "VLC"
|
||||||
},
|
},
|
||||||
@@ -297,7 +297,7 @@
|
|||||||
"video_player": "Video player",
|
"video_player": "Video player",
|
||||||
"video_players": {
|
"video_players": {
|
||||||
"VLC_3": "VLC 3",
|
"VLC_3": "VLC 3",
|
||||||
"VLC_4": "VLC 4 (Deneysel + PiP)"
|
"VLC_4": "VLC 4 (Experimental + PiP)"
|
||||||
},
|
},
|
||||||
"show_custom_menu_links": "Özel Menü Bağlantılarını Göster",
|
"show_custom_menu_links": "Özel Menü Bağlantılarını Göster",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
@@ -305,24 +305,24 @@
|
|||||||
"select_liraries_you_want_to_hide": "Kütüphane sekmesinden ve ana sayfa bölümlerinden gizlemek istediğiniz kütüphaneleri seçin.",
|
"select_liraries_you_want_to_hide": "Kütüphane sekmesinden ve ana sayfa bölümlerinden gizlemek istediğiniz kütüphaneleri seçin.",
|
||||||
"disable_haptic_feedback": "Dokunsal Geri Bildirimi Devre Dışı Bırak",
|
"disable_haptic_feedback": "Dokunsal Geri Bildirimi Devre Dışı Bırak",
|
||||||
"default_quality": "Varsayılan kalite",
|
"default_quality": "Varsayılan kalite",
|
||||||
"default_playback_speed": "Varsayılan Oynatma Hızı",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
"auto_play_next_episode": "Otomatik Sonraki Bölümü Oynat",
|
"auto_play_next_episode": "Auto-play Next Episode",
|
||||||
"max_auto_play_episode_count": "En Fazla Otomatik Oynatılacak Bölüm Sayısı",
|
"max_auto_play_episode_count": "Max Auto Play Episode Count",
|
||||||
"disabled": "Devre dışı"
|
"disabled": "Devre dışı"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"downloads_title": "İndirmeler"
|
"downloads_title": "İndirmeler"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Müzik",
|
"title": "Music",
|
||||||
"playback_title": "Oynatma",
|
"playback_title": "Playback",
|
||||||
"playback_description": "Müziğin nasıl çalınacağını ayarlayın.",
|
"playback_description": "Configure how music is played.",
|
||||||
"prefer_downloaded": "İndirilmiş Şarkıları Tercih Et",
|
"prefer_downloaded": "Prefer Downloaded Songs",
|
||||||
"caching_title": "Önbellekleme",
|
"caching_title": "Caching",
|
||||||
"caching_description": "Akıcı oynatım için gelecek şarkıları otomatik önbelleğe al.",
|
"caching_description": "Automatically cache upcoming tracks for smoother playback.",
|
||||||
"lookahead_enabled": "Enable Look-Ahead Caching",
|
"lookahead_enabled": "Enable Look-Ahead Caching",
|
||||||
"lookahead_count": "Önden Önbelleklenecek Parça Sayısı",
|
"lookahead_count": "Tracks to Pre-cache",
|
||||||
"max_cache_size": "Maksimum Önbellek Boyutu"
|
"max_cache_size": "Max Cache Size"
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"plugins_title": "Eklentiler",
|
"plugins_title": "Eklentiler",
|
||||||
@@ -345,7 +345,7 @@
|
|||||||
"order_by": {
|
"order_by": {
|
||||||
"DEFAULT": "Varsayılan",
|
"DEFAULT": "Varsayılan",
|
||||||
"VOTE_COUNT_AND_AVERAGE": "Vote count and average",
|
"VOTE_COUNT_AND_AVERAGE": "Vote count and average",
|
||||||
"POPULARITY": "Popülerlik"
|
"POPULARITY": "Popularity"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"marlin_search": {
|
"marlin_search": {
|
||||||
@@ -357,35 +357,35 @@
|
|||||||
"save_button": "Kaydet",
|
"save_button": "Kaydet",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Kaydedildi",
|
"saved": "Kaydedildi",
|
||||||
"refreshed": "Ayarlar sunucudan yeniden alındı"
|
"refreshed": "Settings refreshed from server"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Ayarları Sunucudan Yeniden Al"
|
"refresh_from_server": "Refresh Settings from Server"
|
||||||
},
|
},
|
||||||
"streamystats": {
|
"streamystats": {
|
||||||
"enable_streamystats": "Streamystats'ı Etkinleştir",
|
"enable_streamystats": "Enable Streamystats",
|
||||||
"disable_streamystats": "Streamystats'ı Devre Dışı Bırak",
|
"disable_streamystats": "Disable Streamystats",
|
||||||
"enable_search": "Arama için kullan",
|
"enable_search": "Use for Search",
|
||||||
"url": "URL Adresi",
|
"url": "URL",
|
||||||
"server_url_placeholder": "http(s)://streamystats.example.com",
|
"server_url_placeholder": "http(s)://streamystats.example.com",
|
||||||
"streamystats_search_hint": "Streamystats sunucu URL'sini girin. URL, http veya https içermeli ve isteğe bağlı olarak portu içerebilir.",
|
"streamystats_search_hint": "Enter the URL for your Streamystats server. The URL should include http or https and optionally the port.",
|
||||||
"read_more_about_streamystats": "Streamystats hakkında daha fazla bilgi.",
|
"read_more_about_streamystats": "Read More About Streamystats.",
|
||||||
"save_button": "Kaydet",
|
"save_button": "Save",
|
||||||
"save": "Kaydet",
|
"save": "Save",
|
||||||
"features_title": "Özellikler",
|
"features_title": "Features",
|
||||||
"home_sections_title": "Home Sections",
|
"home_sections_title": "Home Sections",
|
||||||
"enable_movie_recommendations": "Film Önerileri",
|
"enable_movie_recommendations": "Movie Recommendations",
|
||||||
"enable_series_recommendations": "Dizi Önerileri",
|
"enable_series_recommendations": "Series Recommendations",
|
||||||
"enable_promoted_watchlists": "Promoted Watchlists",
|
"enable_promoted_watchlists": "Promoted Watchlists",
|
||||||
"hide_watchlists_tab": "Hide Watchlists Tab",
|
"hide_watchlists_tab": "Hide Watchlists Tab",
|
||||||
"home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.",
|
"home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.",
|
||||||
"recommended_movies": "Önerilen Filmler",
|
"recommended_movies": "Recommended Movies",
|
||||||
"recommended_series": "Önerilen Diziler",
|
"recommended_series": "Recommended Series",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Kaydedildi",
|
"saved": "Saved",
|
||||||
"refreshed": "Ayarlar sunucudan yeniden alındı",
|
"refreshed": "Settings refreshed from server",
|
||||||
"disabled": "Streamystats devre dışı"
|
"disabled": "Streamystats disabled"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Ayarları Sunucudan Yeniden Al"
|
"refresh_from_server": "Refresh Settings from Server"
|
||||||
},
|
},
|
||||||
"kefinTweaks": {
|
"kefinTweaks": {
|
||||||
"watchlist_enabler": "Enable our Watchlist integration",
|
"watchlist_enabler": "Enable our Watchlist integration",
|
||||||
@@ -398,18 +398,18 @@
|
|||||||
"device_usage": "Cihaz {{availableSpace}}%",
|
"device_usage": "Cihaz {{availableSpace}}%",
|
||||||
"size_used": "{{used}} / {{total}} kullanıldı",
|
"size_used": "{{used}} / {{total}} kullanıldı",
|
||||||
"delete_all_downloaded_files": "Tüm indirilen dosyaları sil",
|
"delete_all_downloaded_files": "Tüm indirilen dosyaları sil",
|
||||||
"music_cache_title": "Müzik Ön Belleği",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"enable_music_cache": "Müzik Ön Belleğini Etkinleştir",
|
"enable_music_cache": "Enable Music Cache",
|
||||||
"clear_music_cache": "Müzik Ön Belleğini Temizle",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
"music_cache_size": "{{size}} ön belleklendi",
|
"music_cache_size": "{{size}} cached",
|
||||||
"music_cache_cleared": "Müzik ön belleği temizlendi",
|
"music_cache_cleared": "Music cache cleared",
|
||||||
"delete_all_downloaded_songs": "Tüm İndirilen Müzikleri Sil",
|
"delete_all_downloaded_songs": "Delete All Downloaded Songs",
|
||||||
"downloaded_songs_size": "{{size}} indirildi",
|
"downloaded_songs_size": "{{size}} downloaded",
|
||||||
"downloaded_songs_deleted": "İndirilen müzikler silindi"
|
"downloaded_songs_deleted": "Downloaded songs deleted"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"title": "Giriş",
|
"title": "Intro",
|
||||||
"show_intro": "Tanıtımı Göster",
|
"show_intro": "Tanıtımı Göster",
|
||||||
"reset_intro": "Tanıtımı Sıfırla"
|
"reset_intro": "Tanıtımı Sıfırla"
|
||||||
},
|
},
|
||||||
@@ -417,7 +417,7 @@
|
|||||||
"logs_title": "Günlükler",
|
"logs_title": "Günlükler",
|
||||||
"export_logs": "Export logs",
|
"export_logs": "Export logs",
|
||||||
"click_for_more_info": "Click for more info",
|
"click_for_more_info": "Click for more info",
|
||||||
"level": "Düzey",
|
"level": "Level",
|
||||||
"no_logs_available": "Günlükler mevcut değil",
|
"no_logs_available": "Günlükler mevcut değil",
|
||||||
"delete_all_logs": "Tüm günlükleri sil"
|
"delete_all_logs": "Tüm günlükleri sil"
|
||||||
},
|
},
|
||||||
@@ -433,22 +433,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sessions": {
|
"sessions": {
|
||||||
"title": "Oturumlar",
|
"title": "Sessions",
|
||||||
"no_active_sessions": "Aktif Oturum Yok"
|
"no_active_sessions": "No Active Sessions"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"downloads_title": "İndirilenler",
|
"downloads_title": "İndirilenler",
|
||||||
"tvseries": "Diziler",
|
"tvseries": "Diziler",
|
||||||
"movies": "Filmler",
|
"movies": "Filmler",
|
||||||
"queue": "Sıra",
|
"queue": "Sıra",
|
||||||
"other_media": "Diğer medya",
|
"other_media": "Other media",
|
||||||
"queue_hint": "Sıra ve indirmeler uygulama yeniden başlatıldığında kaybolacaktır",
|
"queue_hint": "Sıra ve indirmeler uygulama yeniden başlatıldığında kaybolacaktır",
|
||||||
"no_items_in_queue": "Sırada öğe yok",
|
"no_items_in_queue": "Sırada öğe yok",
|
||||||
"no_downloaded_items": "İndirilen öğe yok",
|
"no_downloaded_items": "İndirilen öğe yok",
|
||||||
"delete_all_movies_button": "Tüm Filmleri Sil",
|
"delete_all_movies_button": "Tüm Filmleri Sil",
|
||||||
"delete_all_tvseries_button": "Tüm Dizileri Sil",
|
"delete_all_tvseries_button": "Tüm Dizileri Sil",
|
||||||
"delete_all_button": "Tümünü Sil",
|
"delete_all_button": "Tümünü Sil",
|
||||||
"delete_all_other_media_button": "Diğer medyayı sil",
|
"delete_all_other_media_button": "Delete other media",
|
||||||
"active_download": "Aktif indirme",
|
"active_download": "Aktif indirme",
|
||||||
"no_active_downloads": "Aktif indirme yok",
|
"no_active_downloads": "Aktif indirme yok",
|
||||||
"active_downloads": "Aktif indirmeler",
|
"active_downloads": "Aktif indirmeler",
|
||||||
@@ -465,49 +465,49 @@
|
|||||||
"failed_to_delete_all_movies": "Filmler silinemedi",
|
"failed_to_delete_all_movies": "Filmler silinemedi",
|
||||||
"deleted_all_tvseries_successfully": "Tüm diziler başarıyla silindi!",
|
"deleted_all_tvseries_successfully": "Tüm diziler başarıyla silindi!",
|
||||||
"failed_to_delete_all_tvseries": "Diziler silinemedi",
|
"failed_to_delete_all_tvseries": "Diziler silinemedi",
|
||||||
"deleted_media_successfully": "Diğer medya başarıyla silindi!",
|
"deleted_media_successfully": "Deleted other media Successfully!",
|
||||||
"failed_to_delete_media": "Failed to Delete other media",
|
"failed_to_delete_media": "Failed to Delete other media",
|
||||||
"download_deleted": "İndirme silindi",
|
"download_deleted": "Download Deleted",
|
||||||
"download_cancelled": "İndirme iptal edildi",
|
"download_cancelled": "İndirme iptal edildi",
|
||||||
"could_not_delete_download": "İndirme Silinemedi",
|
"could_not_delete_download": "Could Not Delete Download",
|
||||||
"download_paused": "İndirme Duraklatıldı",
|
"download_paused": "Download Paused",
|
||||||
"could_not_pause_download": "İndirme Duraklatılamadı",
|
"could_not_pause_download": "Could Not Pause Download",
|
||||||
"download_resumed": "İndirme Devam Ediyor",
|
"download_resumed": "Download Resumed",
|
||||||
"could_not_resume_download": "İndirme Devam Ettirilemedi",
|
"could_not_resume_download": "Could Not Resume Download",
|
||||||
"download_completed": "İndirme tamamlandı",
|
"download_completed": "İndirme tamamlandı",
|
||||||
"download_failed": "İndirme başarısız oldu",
|
"download_failed": "Download Failed",
|
||||||
"download_failed_for_item": "{{item}} için indirme başarısız oldu - {{error}}",
|
"download_failed_for_item": "{{item}} için indirme başarısız oldu - {{error}}",
|
||||||
"download_completed_for_item": "{{item}} için indirme tamamlandı",
|
"download_completed_for_item": "{{item}} için indirme tamamlandı",
|
||||||
"download_started_for_item": "{{item}} için indirme başladı",
|
"download_started_for_item": "Download Started for {{item}}",
|
||||||
"failed_to_start_download": "İndirme başlatılamadı",
|
"failed_to_start_download": "Failed to start download",
|
||||||
"item_already_downloading": "{{item}} zaten indiriliyor",
|
"item_already_downloading": "{{item}} is already downloading",
|
||||||
"all_files_deleted": "Bütün indirilenler başarıyla silindi",
|
"all_files_deleted": "All Downloads Deleted Successfully",
|
||||||
"files_deleted_by_type": "{{count}} {{type}} silindi",
|
"files_deleted_by_type": "{{count}} {{type}} deleted",
|
||||||
"all_files_folders_and_jobs_deleted_successfully": "Tüm dosyalar, klasörler ve işler başarıyla silindi",
|
"all_files_folders_and_jobs_deleted_successfully": "Tüm dosyalar, klasörler ve işler başarıyla silindi",
|
||||||
"failed_to_clean_cache_directory": "Önbellek dizini temizlenemedi",
|
"failed_to_clean_cache_directory": "Failed to clean cache directory",
|
||||||
"could_not_get_download_url_for_item": "{{itemName}} için indirme URL'si alınamadı",
|
"could_not_get_download_url_for_item": "Could not get download URL for {{itemName}}",
|
||||||
"go_to_downloads": "İndirmelere git",
|
"go_to_downloads": "İndirmelere git",
|
||||||
"file_deleted": "{{item}} silindi"
|
"file_deleted": "{{item}} deleted"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"select": "Seç",
|
"select": "Select",
|
||||||
"no_trailer_available": "Fragman mevcut değil",
|
"no_trailer_available": "No trailer available",
|
||||||
"video": "Video",
|
"video": "Video",
|
||||||
"audio": "Ses",
|
"audio": "Ses",
|
||||||
"subtitle": "Altyazı",
|
"subtitle": "Altyazı",
|
||||||
"play": "Oynat",
|
"play": "Play",
|
||||||
"none": "Hiçbiri",
|
"none": "None",
|
||||||
"track": "Parça",
|
"track": "Track",
|
||||||
"cancel": "Vazgeç",
|
"cancel": "Cancel",
|
||||||
"delete": "Sil",
|
"delete": "Delete",
|
||||||
"ok": "Tamam",
|
"ok": "OK",
|
||||||
"remove": "Kaldır",
|
"remove": "Remove",
|
||||||
"next": "Sonraki",
|
"next": "Next",
|
||||||
"back": "Geri",
|
"back": "Back",
|
||||||
"continue": "Devam",
|
"continue": "Continue",
|
||||||
"verifying": "Doğrulanıyor..."
|
"verifying": "Verifying..."
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"search": "Ara...",
|
"search": "Ara...",
|
||||||
@@ -521,10 +521,10 @@
|
|||||||
"episodes": "Bölümler",
|
"episodes": "Bölümler",
|
||||||
"collections": "Koleksiyonlar",
|
"collections": "Koleksiyonlar",
|
||||||
"actors": "Oyuncular",
|
"actors": "Oyuncular",
|
||||||
"artists": "Sanatçılar",
|
"artists": "Artists",
|
||||||
"albums": "Albümler",
|
"albums": "Albums",
|
||||||
"songs": "Şarkılar",
|
"songs": "Songs",
|
||||||
"playlists": "Çalma listeleri",
|
"playlists": "Playlists",
|
||||||
"request_movies": "Film Talep Et",
|
"request_movies": "Film Talep Et",
|
||||||
"request_series": "Dizi Talep Et",
|
"request_series": "Dizi Talep Et",
|
||||||
"recently_added": "Son Eklenenler",
|
"recently_added": "Son Eklenenler",
|
||||||
@@ -572,7 +572,7 @@
|
|||||||
"genres": "Türler",
|
"genres": "Türler",
|
||||||
"years": "Yıllar",
|
"years": "Yıllar",
|
||||||
"sort_by": "Sırala",
|
"sort_by": "Sırala",
|
||||||
"filter_by": "Filtrele",
|
"filter_by": "Filter By",
|
||||||
"sort_order": "Sıralama düzeni",
|
"sort_order": "Sıralama düzeni",
|
||||||
"tags": "Etiketler"
|
"tags": "Etiketler"
|
||||||
}
|
}
|
||||||
@@ -604,11 +604,11 @@
|
|||||||
"index": "İndeks:",
|
"index": "İndeks:",
|
||||||
"continue_watching": "İzlemeye devam et",
|
"continue_watching": "İzlemeye devam et",
|
||||||
"go_back": "Geri",
|
"go_back": "Geri",
|
||||||
"downloaded_file_title": "Bu dosya indirilmiş",
|
"downloaded_file_title": "You have this file downloaded",
|
||||||
"downloaded_file_message": "İndirilmiş dosyayı oynatmak ister misiniz?",
|
"downloaded_file_message": "Do you want to play the downloaded file?",
|
||||||
"downloaded_file_yes": "Evet",
|
"downloaded_file_yes": "Yes",
|
||||||
"downloaded_file_no": "Hayır",
|
"downloaded_file_no": "No",
|
||||||
"downloaded_file_cancel": "Vazgeç"
|
"downloaded_file_cancel": "Cancel"
|
||||||
},
|
},
|
||||||
"item_card": {
|
"item_card": {
|
||||||
"next_up": "Sıradaki",
|
"next_up": "Sıradaki",
|
||||||
@@ -624,7 +624,7 @@
|
|||||||
"no_similar_items_found": "Benzer öge bulunamadı",
|
"no_similar_items_found": "Benzer öge bulunamadı",
|
||||||
"video": "Video",
|
"video": "Video",
|
||||||
"more_details": "Daha fazla detay",
|
"more_details": "Daha fazla detay",
|
||||||
"media_options": "Medya Seçenekleri",
|
"media_options": "Media Options",
|
||||||
"quality": "Kalite",
|
"quality": "Kalite",
|
||||||
"audio": "Ses",
|
"audio": "Ses",
|
||||||
"subtitles": "Altyazı",
|
"subtitles": "Altyazı",
|
||||||
@@ -639,7 +639,7 @@
|
|||||||
"download_episode": "Bölümü indir",
|
"download_episode": "Bölümü indir",
|
||||||
"download_movie": "Filmi indir",
|
"download_movie": "Filmi indir",
|
||||||
"download_x_item": "{{item_count}} tane ögeyi indir",
|
"download_x_item": "{{item_count}} tane ögeyi indir",
|
||||||
"download_unwatched_only": "Yalnızca İzlenmemişler",
|
"download_unwatched_only": "Unwatched Only",
|
||||||
"download_button": "İndir"
|
"download_button": "İndir"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -693,10 +693,10 @@
|
|||||||
"number_episodes": "Bölüm {{episode_number}}",
|
"number_episodes": "Bölüm {{episode_number}}",
|
||||||
"born": "Doğum",
|
"born": "Doğum",
|
||||||
"appearances": "Görünmeler",
|
"appearances": "Görünmeler",
|
||||||
"approve": "Onayla",
|
"approve": "Approve",
|
||||||
"decline": "Reddet",
|
"decline": "Decline",
|
||||||
"requested_by": "{{user}} tarafından istendi",
|
"requested_by": "Requested by {{user}}",
|
||||||
"unknown_user": "Bilinmeyen Kullanıcı",
|
"unknown_user": "Unknown User",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"jellyseer_does_not_meet_requirements": "Jellyseerr sunucusu minimum sürüm gereksinimlerini karşılamıyor! Lütfen en az 2.0.0 sürümüne güncelleyin",
|
"jellyseer_does_not_meet_requirements": "Jellyseerr sunucusu minimum sürüm gereksinimlerini karşılamıyor! Lütfen en az 2.0.0 sürümüne güncelleyin",
|
||||||
"jellyseerr_test_failed": "Jellyseerr testi başarısız oldu. Lütfen tekrar deneyin.",
|
"jellyseerr_test_failed": "Jellyseerr testi başarısız oldu. Lütfen tekrar deneyin.",
|
||||||
@@ -705,10 +705,10 @@
|
|||||||
"requested_item": "{{item}} talep edildi!",
|
"requested_item": "{{item}} talep edildi!",
|
||||||
"you_dont_have_permission_to_request": "İstek göndermeye izniniz yok!",
|
"you_dont_have_permission_to_request": "İstek göndermeye izniniz yok!",
|
||||||
"something_went_wrong_requesting_media": "Medya talep edilirken bir şeyler ters gitti!",
|
"something_went_wrong_requesting_media": "Medya talep edilirken bir şeyler ters gitti!",
|
||||||
"request_approved": "İstek Onaylandı!",
|
"request_approved": "Request Approved!",
|
||||||
"request_declined": "İstek Reddedildi!",
|
"request_declined": "Request Declined!",
|
||||||
"failed_to_approve_request": "İsteği Onaylama Başarısız Oldu",
|
"failed_to_approve_request": "Failed to Approve Request",
|
||||||
"failed_to_decline_request": "İsteği Reddetme Başarısız Oldu"
|
"failed_to_decline_request": "Failed to Decline Request"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
@@ -719,127 +719,127 @@
|
|||||||
"favorites": "Favoriler"
|
"favorites": "Favoriler"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Müzik",
|
"title": "Music",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"suggestions": "Öneriler",
|
"suggestions": "Suggestions",
|
||||||
"albums": "Albümler",
|
"albums": "Albums",
|
||||||
"artists": "Sanatçılar",
|
"artists": "Artists",
|
||||||
"playlists": "Çalma listeleri",
|
"playlists": "Playlists",
|
||||||
"tracks": "parçalar"
|
"tracks": "tracks"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"all": "Tümü"
|
"all": "All"
|
||||||
},
|
},
|
||||||
"recently_added": "Son Eklenenler",
|
"recently_added": "Recently Added",
|
||||||
"recently_played": "Son Oynatılanlar",
|
"recently_played": "Recently Played",
|
||||||
"frequently_played": "Sık Oynatılanlar",
|
"frequently_played": "Frequently Played",
|
||||||
"explore": "Keşfet",
|
"explore": "Explore",
|
||||||
"top_tracks": "En Popülar Parçalar",
|
"top_tracks": "Top Tracks",
|
||||||
"play": "Oynat",
|
"play": "Play",
|
||||||
"shuffle": "Karıştır",
|
"shuffle": "Shuffle",
|
||||||
"play_top_tracks": "En Çok Oynatılan Parçaları Oynat",
|
"play_top_tracks": "Play Top Tracks",
|
||||||
"no_suggestions": "Öneri mevcut değil",
|
"no_suggestions": "No suggestions available",
|
||||||
"no_albums": "Hiç albüm bulunamadı",
|
"no_albums": "No albums found",
|
||||||
"no_artists": "Hiç sanatçı bulunamadı",
|
"no_artists": "No artists found",
|
||||||
"no_playlists": "Hiç çalma listesi bulunamadı",
|
"no_playlists": "No playlists found",
|
||||||
"album_not_found": "Albüm bulunamadı",
|
"album_not_found": "Album not found",
|
||||||
"artist_not_found": "Sanatçı bulunamadı",
|
"artist_not_found": "Artist not found",
|
||||||
"playlist_not_found": "Çalma listesi bulunamadı",
|
"playlist_not_found": "Playlist not found",
|
||||||
"track_options": {
|
"track_options": {
|
||||||
"play_next": "Sıradakini Çal",
|
"play_next": "Play Next",
|
||||||
"add_to_queue": "Sıraya Ekle",
|
"add_to_queue": "Add to Queue",
|
||||||
"add_to_playlist": "Çalma listesine ekle",
|
"add_to_playlist": "Add to Playlist",
|
||||||
"download": "İndir",
|
"download": "Download",
|
||||||
"downloaded": "İndirildi",
|
"downloaded": "Downloaded",
|
||||||
"downloading": "İndiriliyor...",
|
"downloading": "Downloading...",
|
||||||
"cached": "Önbellekte",
|
"cached": "Cached",
|
||||||
"delete_download": "İndirmeyi Sil",
|
"delete_download": "Delete Download",
|
||||||
"delete_cache": "Ön bellekten kaldır",
|
"delete_cache": "Remove from Cache",
|
||||||
"go_to_artist": "Sanatçıya Git",
|
"go_to_artist": "Go to Artist",
|
||||||
"go_to_album": "Albüme Git",
|
"go_to_album": "Go to Album",
|
||||||
"add_to_favorites": "Favorilere Ekle",
|
"add_to_favorites": "Add to Favorites",
|
||||||
"remove_from_favorites": "Favorilerden Kaldır",
|
"remove_from_favorites": "Remove from Favorites",
|
||||||
"remove_from_playlist": "Çalma Listesinden Kaldır"
|
"remove_from_playlist": "Remove from Playlist"
|
||||||
},
|
},
|
||||||
"playlists": {
|
"playlists": {
|
||||||
"create_playlist": "Çalma Listesi Oluştur",
|
"create_playlist": "Create Playlist",
|
||||||
"playlist_name": "Çalma Listesi Adı",
|
"playlist_name": "Playlist Name",
|
||||||
"enter_name": "Çalma listesi adı girin",
|
"enter_name": "Enter playlist name",
|
||||||
"create": "Oluştur",
|
"create": "Create",
|
||||||
"search_playlists": "Çalma listelerini ara...",
|
"search_playlists": "Search playlists...",
|
||||||
"added_to": "Şu çalma listesine eklendi: {{name}}",
|
"added_to": "Added to {{name}}",
|
||||||
"added": "Çalma listesine eklendi",
|
"added": "Added to playlist",
|
||||||
"removed_from": "Şu çalma listesinden kaldırıldı: {{name}}",
|
"removed_from": "Removed from {{name}}",
|
||||||
"removed": "Çalma listesinden kaldır",
|
"removed": "Removed from playlist",
|
||||||
"created": "Çalma listesi oluşturuldu",
|
"created": "Playlist created",
|
||||||
"create_new": "Yeni Çalma Listesi Oluştur",
|
"create_new": "Create New Playlist",
|
||||||
"failed_to_add": "Çalma listesine eklenemedi",
|
"failed_to_add": "Failed to add to playlist",
|
||||||
"failed_to_remove": "Çalma listesinden kaldırılamadı",
|
"failed_to_remove": "Failed to remove from playlist",
|
||||||
"failed_to_create": "Çalma listesi oluşturulamadı",
|
"failed_to_create": "Failed to create playlist",
|
||||||
"delete_playlist": "Çalma Listesini Sil",
|
"delete_playlist": "Delete Playlist",
|
||||||
"delete_confirm": "\"{{name}}\" adlı çalma listesini silmek istediğinize emin misiniz? Bu işlem geri alınamaz.",
|
"delete_confirm": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"deleted": "Çalma listesi silindi",
|
"deleted": "Playlist deleted",
|
||||||
"failed_to_delete": "Çalma listesi oluşturulamadı"
|
"failed_to_delete": "Failed to delete playlist"
|
||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
"title": "Sırala",
|
"title": "Sort By",
|
||||||
"alphabetical": "Alfabetik",
|
"alphabetical": "Alphabetical",
|
||||||
"date_created": "Oluşturulma Tarihi"
|
"date_created": "Date Created"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchlists": {
|
"watchlists": {
|
||||||
"title": "İzleme listeleri",
|
"title": "Watchlists",
|
||||||
"my_watchlists": "İzleme listelerim",
|
"my_watchlists": "My Watchlists",
|
||||||
"public_watchlists": "Herkese açık izleme listeleri",
|
"public_watchlists": "Public Watchlists",
|
||||||
"create_title": "İzleme listesi oluştur",
|
"create_title": "Create Watchlist",
|
||||||
"edit_title": "İzleme listesini düzenle",
|
"edit_title": "Edit Watchlist",
|
||||||
"create_button": "İzleme listesi oluştur",
|
"create_button": "Create Watchlist",
|
||||||
"save_button": "Değişiklikleri Kaydet",
|
"save_button": "Save Changes",
|
||||||
"delete_button": "Sil",
|
"delete_button": "Delete",
|
||||||
"remove_button": "Kaldır",
|
"remove_button": "Remove",
|
||||||
"cancel_button": "Vazgeç",
|
"cancel_button": "Cancel",
|
||||||
"name_label": "Name",
|
"name_label": "Name",
|
||||||
"name_placeholder": "İzleme listesi adını girin",
|
"name_placeholder": "Enter watchlist name",
|
||||||
"description_label": "Açıklama",
|
"description_label": "Description",
|
||||||
"description_placeholder": "Açıklama girin (isteğe bağlı)",
|
"description_placeholder": "Enter description (optional)",
|
||||||
"is_public_label": "Herkese açık izleme listesi",
|
"is_public_label": "Public Watchlist",
|
||||||
"is_public_description": "Başkalarının da bu izleme listesini görmesine izin ver",
|
"is_public_description": "Allow others to view this watchlist",
|
||||||
"allowed_type_label": "İçerik Türü",
|
"allowed_type_label": "Content Type",
|
||||||
"sort_order_label": "Varsayılan Sıralama",
|
"sort_order_label": "Default Sort Order",
|
||||||
"empty_title": "İzleme listesi yok",
|
"empty_title": "No Watchlists",
|
||||||
"empty_description": "Create your first watchlist to start organizing your media",
|
"empty_description": "Create your first watchlist to start organizing your media",
|
||||||
"empty_watchlist": "Bu izleme listesi boş",
|
"empty_watchlist": "This watchlist is empty",
|
||||||
"empty_watchlist_hint": "Kütüphanenizdeki nesneleri bu izleme listesine ekleyin",
|
"empty_watchlist_hint": "Add items from your library to this watchlist",
|
||||||
"not_configured_title": "Streamystats ayarlanmamış",
|
"not_configured_title": "Streamystats Not Configured",
|
||||||
"not_configured_description": "İzleme listelerini kullanmak için ayarlardan Streamystats'ı ayarlayın",
|
"not_configured_description": "Configure Streamystats in settings to use watchlists",
|
||||||
"go_to_settings": "Ayarlara git",
|
"go_to_settings": "Go to Settings",
|
||||||
"add_to_watchlist": "İzleme Listesine Ekle",
|
"add_to_watchlist": "Add to Watchlist",
|
||||||
"remove_from_watchlist": "İzleme Listesinden Kaldır",
|
"remove_from_watchlist": "Remove from Watchlist",
|
||||||
"select_watchlist": "İzleme Listesi Seç",
|
"select_watchlist": "Select Watchlist",
|
||||||
"create_new": "Yeni İzleme Listesi Oluştur",
|
"create_new": "Create New Watchlist",
|
||||||
"item": "öğe",
|
"item": "item",
|
||||||
"items": "öğeler",
|
"items": "items",
|
||||||
"public": "Herkese Açık",
|
"public": "Public",
|
||||||
"private": "Özel",
|
"private": "Private",
|
||||||
"you": "Siz",
|
"you": "You",
|
||||||
"by_owner": "Başka kullanıcı tarafından",
|
"by_owner": "By another user",
|
||||||
"not_found": "İzleme listesi bulunamadı",
|
"not_found": "Watchlist not found",
|
||||||
"delete_confirm_title": "İzleme listesini sil",
|
"delete_confirm_title": "Delete Watchlist",
|
||||||
"delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
"delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
|
||||||
"remove_item_title": "İzleme Listesinden Kaldır",
|
"remove_item_title": "Remove from Watchlist",
|
||||||
"remove_item_message": "{{name}} bu izleme listesinden kaldırılsın mı?",
|
"remove_item_message": "Remove \"{{name}}\" from this watchlist?",
|
||||||
"loading": "İzleme listeleri yükleniyor...",
|
"loading": "Loading watchlists...",
|
||||||
"no_compatible_watchlists": "Uyumlu izleme listesi yok",
|
"no_compatible_watchlists": "No compatible watchlists",
|
||||||
"create_one_first": "Bu içerik türünü kabul eden bir izleme listesi oluşturun"
|
"create_one_first": "Create a watchlist that accepts this content type"
|
||||||
},
|
},
|
||||||
"playback_speed": {
|
"playback_speed": {
|
||||||
"title": "Oynatma Hızı",
|
"title": "Playback Speed",
|
||||||
"apply_to": "Şuna Uygula",
|
"apply_to": "Apply To",
|
||||||
"speed": "Hız",
|
"speed": "Speed",
|
||||||
"scope": {
|
"scope": {
|
||||||
"media": "Yalnızca bu medyada",
|
"media": "This media only",
|
||||||
"show": "Bu dizide",
|
"show": "This show",
|
||||||
"all": "Bütün medyalarda (varsayılan)"
|
"all": "All media (default)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,9 +134,6 @@ export enum VideoPlayer {
|
|||||||
MPV = 0,
|
MPV = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Segment skip behavior options
|
|
||||||
export type SegmentSkipMode = "none" | "ask" | "auto";
|
|
||||||
|
|
||||||
// Audio transcoding mode - controls how surround audio is handled
|
// Audio transcoding mode - controls how surround audio is handled
|
||||||
// This controls server-side transcoding behavior for audio streams.
|
// This controls server-side transcoding behavior for audio streams.
|
||||||
// MPV decodes via FFmpeg and supports most formats, but mobile devices
|
// MPV decodes via FFmpeg and supports most formats, but mobile devices
|
||||||
@@ -184,12 +181,6 @@ export type Settings = {
|
|||||||
maxAutoPlayEpisodeCount: MaxAutoPlayEpisodeCount;
|
maxAutoPlayEpisodeCount: MaxAutoPlayEpisodeCount;
|
||||||
autoPlayEpisodeCount: number;
|
autoPlayEpisodeCount: number;
|
||||||
autoPlayNextEpisode: boolean;
|
autoPlayNextEpisode: boolean;
|
||||||
// Media segment skip preferences
|
|
||||||
skipIntro: SegmentSkipMode;
|
|
||||||
skipOutro: SegmentSkipMode;
|
|
||||||
skipRecap: SegmentSkipMode;
|
|
||||||
skipCommercial: SegmentSkipMode;
|
|
||||||
skipPreview: SegmentSkipMode;
|
|
||||||
// Playback speed settings
|
// Playback speed settings
|
||||||
defaultPlaybackSpeed: number;
|
defaultPlaybackSpeed: number;
|
||||||
playbackSpeedPerMedia: Record<string, number>;
|
playbackSpeedPerMedia: Record<string, number>;
|
||||||
@@ -275,12 +266,6 @@ export const defaultValues: Settings = {
|
|||||||
maxAutoPlayEpisodeCount: { key: "3", value: 3 },
|
maxAutoPlayEpisodeCount: { key: "3", value: 3 },
|
||||||
autoPlayEpisodeCount: 0,
|
autoPlayEpisodeCount: 0,
|
||||||
autoPlayNextEpisode: true,
|
autoPlayNextEpisode: true,
|
||||||
// Media segment skip defaults
|
|
||||||
skipIntro: "ask",
|
|
||||||
skipOutro: "ask",
|
|
||||||
skipRecap: "ask",
|
|
||||||
skipCommercial: "ask",
|
|
||||||
skipPreview: "ask",
|
|
||||||
// Playback speed defaults
|
// Playback speed defaults
|
||||||
defaultPlaybackSpeed: 1.0,
|
defaultPlaybackSpeed: 1.0,
|
||||||
playbackSpeedPerMedia: {},
|
playbackSpeedPerMedia: {},
|
||||||
|
|||||||
@@ -1,40 +1,46 @@
|
|||||||
import { Api } from "@jellyfin/sdk";
|
import { Api } from "@jellyfin/sdk";
|
||||||
import { MediaSegmentType } from "@jellyfin/sdk/lib/generated-client/models/media-segment-type";
|
|
||||||
import { getMediaSegmentsApi } from "@jellyfin/sdk/lib/utils/api/media-segments-api";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { DownloadedItem, MediaTimeSegment } from "@/providers/Downloads/types";
|
import { DownloadedItem, MediaTimeSegment } from "@/providers/Downloads/types";
|
||||||
import { getAuthHeaders } from "./jellyfin/jellyfin";
|
import { getAuthHeaders } from "./jellyfin/jellyfin";
|
||||||
|
|
||||||
export interface SegmentBuckets {
|
// New Jellyfin 10.11+ Media Segments API types
|
||||||
introSegments: MediaTimeSegment[];
|
interface MediaSegmentDto {
|
||||||
creditSegments: MediaTimeSegment[];
|
Id: string;
|
||||||
recapSegments: MediaTimeSegment[];
|
ItemId: string;
|
||||||
commercialSegments: MediaTimeSegment[];
|
Type: "Intro" | "Outro" | "Recap" | "Commercial" | "Preview";
|
||||||
previewSegments: MediaTimeSegment[];
|
StartTicks: number;
|
||||||
|
EndTicks: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy endpoints (intro-skipper / chapter-credits plugins on pre-10.11 servers)
|
interface MediaSegmentsResponse {
|
||||||
|
Items: MediaSegmentDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy API types (for fallback)
|
||||||
interface IntroTimestamps {
|
interface IntroTimestamps {
|
||||||
IntroStart: number;
|
EpisodeId: string;
|
||||||
|
HideSkipPromptAt: number;
|
||||||
IntroEnd: number;
|
IntroEnd: number;
|
||||||
|
IntroStart: number;
|
||||||
|
ShowSkipPromptAt: number;
|
||||||
Valid: boolean;
|
Valid: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreditTimestamps {
|
interface CreditTimestamps {
|
||||||
Credits: { Start: number; End: number; Valid: boolean };
|
Introduction: {
|
||||||
|
Start: number;
|
||||||
|
End: number;
|
||||||
|
Valid: boolean;
|
||||||
|
};
|
||||||
|
Credits: {
|
||||||
|
Start: number;
|
||||||
|
End: number;
|
||||||
|
Valid: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const TICKS_PER_SECOND = 10_000_000;
|
const TICKS_PER_SECOND = 10000000;
|
||||||
const ticksToSeconds = (ticks: number): number => ticks / TICKS_PER_SECOND;
|
|
||||||
|
|
||||||
const emptyBuckets = (): SegmentBuckets => ({
|
|
||||||
introSegments: [],
|
|
||||||
creditSegments: [],
|
|
||||||
recapSegments: [],
|
|
||||||
commercialSegments: [],
|
|
||||||
previewSegments: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useSegments = (
|
export const useSegments = (
|
||||||
itemId: string,
|
itemId: string,
|
||||||
@@ -42,6 +48,7 @@ export const useSegments = (
|
|||||||
downloadedFiles: DownloadedItem[] | undefined,
|
downloadedFiles: DownloadedItem[] | undefined,
|
||||||
api: Api | null,
|
api: Api | null,
|
||||||
) => {
|
) => {
|
||||||
|
// Memoize the lookup so the array is only traversed when dependencies change
|
||||||
const downloadedItem = React.useMemo(
|
const downloadedItem = React.useMemo(
|
||||||
() => downloadedFiles?.find((d) => d.item.Id === itemId),
|
() => downloadedFiles?.find((d) => d.item.Id === itemId),
|
||||||
[downloadedFiles, itemId],
|
[downloadedFiles, itemId],
|
||||||
@@ -58,110 +65,141 @@ export const useSegments = (
|
|||||||
}
|
}
|
||||||
return fetchAndParseSegments(itemId, api);
|
return fetchAndParseSegments(itemId, api);
|
||||||
},
|
},
|
||||||
enabled: !!itemId && (isOffline ? !!downloadedItem : !!api),
|
enabled: isOffline ? !!downloadedItem : !!api,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSegmentsForItem = (item: DownloadedItem): SegmentBuckets => ({
|
export const getSegmentsForItem = (
|
||||||
introSegments: item.introSegments || [],
|
item: DownloadedItem,
|
||||||
creditSegments: item.creditSegments || [],
|
): {
|
||||||
recapSegments: item.recapSegments || [],
|
introSegments: MediaTimeSegment[];
|
||||||
commercialSegments: item.commercialSegments || [],
|
creditSegments: MediaTimeSegment[];
|
||||||
previewSegments: item.previewSegments || [],
|
} => {
|
||||||
});
|
return {
|
||||||
|
introSegments: item.introSegments || [],
|
||||||
|
creditSegments: item.creditSegments || [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/** Jellyfin 10.11+ unified MediaSegments API. Returns null so the caller can fall back. */
|
/**
|
||||||
|
* Converts Jellyfin ticks to seconds
|
||||||
|
*/
|
||||||
|
const ticksToSeconds = (ticks: number): number => ticks / TICKS_PER_SECOND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches segments using the new Jellyfin 10.11+ MediaSegments API
|
||||||
|
*/
|
||||||
const fetchMediaSegments = async (
|
const fetchMediaSegments = async (
|
||||||
itemId: string,
|
itemId: string,
|
||||||
api: Api,
|
api: Api,
|
||||||
): Promise<SegmentBuckets | null> => {
|
): Promise<{
|
||||||
|
introSegments: MediaTimeSegment[];
|
||||||
|
creditSegments: MediaTimeSegment[];
|
||||||
|
} | null> => {
|
||||||
try {
|
try {
|
||||||
const response = await getMediaSegmentsApi(api).getItemSegments({
|
const response = await api.axiosInstance.get<MediaSegmentsResponse>(
|
||||||
itemId,
|
`${api.basePath}/MediaSegments/${itemId}`,
|
||||||
includeSegmentTypes: [
|
{
|
||||||
MediaSegmentType.Intro,
|
headers: getAuthHeaders(api),
|
||||||
MediaSegmentType.Outro,
|
params: {
|
||||||
MediaSegmentType.Recap,
|
includeSegmentTypes: ["Intro", "Outro"],
|
||||||
MediaSegmentType.Commercial,
|
},
|
||||||
MediaSegmentType.Preview,
|
},
|
||||||
],
|
);
|
||||||
});
|
|
||||||
|
|
||||||
const buckets = emptyBuckets();
|
const introSegments: MediaTimeSegment[] = [];
|
||||||
for (const segment of response.data.Items ?? []) {
|
const creditSegments: MediaTimeSegment[] = [];
|
||||||
if (segment.StartTicks == null || segment.EndTicks == null) continue;
|
|
||||||
|
response.data.Items.forEach((segment) => {
|
||||||
const timeSegment: MediaTimeSegment = {
|
const timeSegment: MediaTimeSegment = {
|
||||||
startTime: ticksToSeconds(segment.StartTicks),
|
startTime: ticksToSeconds(segment.StartTicks),
|
||||||
endTime: ticksToSeconds(segment.EndTicks),
|
endTime: ticksToSeconds(segment.EndTicks),
|
||||||
text: segment.Type ?? "",
|
text: segment.Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (segment.Type) {
|
switch (segment.Type) {
|
||||||
case MediaSegmentType.Intro:
|
case "Intro":
|
||||||
buckets.introSegments.push(timeSegment);
|
introSegments.push(timeSegment);
|
||||||
break;
|
break;
|
||||||
case MediaSegmentType.Outro:
|
case "Outro":
|
||||||
buckets.creditSegments.push(timeSegment);
|
creditSegments.push(timeSegment);
|
||||||
break;
|
break;
|
||||||
case MediaSegmentType.Recap:
|
// Optionally handle other types like Recap, Commercial, Preview
|
||||||
buckets.recapSegments.push(timeSegment);
|
default:
|
||||||
break;
|
|
||||||
case MediaSegmentType.Commercial:
|
|
||||||
buckets.commercialSegments.push(timeSegment);
|
|
||||||
break;
|
|
||||||
case MediaSegmentType.Preview:
|
|
||||||
buckets.previewSegments.push(timeSegment);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return buckets;
|
return { introSegments, creditSegments };
|
||||||
} catch {
|
} catch (_error) {
|
||||||
|
// Return null to indicate we should try legacy endpoints
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Pre-10.11 fallback: third-party intro-skipper / chapter-credits plugin endpoints. */
|
/**
|
||||||
|
* Fetches segments using legacy pre-10.11 endpoints
|
||||||
|
*/
|
||||||
const fetchLegacySegments = async (
|
const fetchLegacySegments = async (
|
||||||
itemId: string,
|
itemId: string,
|
||||||
api: Api,
|
api: Api,
|
||||||
): Promise<SegmentBuckets> => {
|
): Promise<{
|
||||||
const buckets = emptyBuckets();
|
introSegments: MediaTimeSegment[];
|
||||||
|
creditSegments: MediaTimeSegment[];
|
||||||
|
}> => {
|
||||||
|
const introSegments: MediaTimeSegment[] = [];
|
||||||
|
const creditSegments: MediaTimeSegment[] = [];
|
||||||
|
|
||||||
const [introRes, creditRes] = await Promise.allSettled([
|
try {
|
||||||
api.axiosInstance.get<IntroTimestamps>(
|
const [introRes, creditRes] = await Promise.allSettled([
|
||||||
`${api.basePath}/Episode/${itemId}/IntroTimestamps`,
|
api.axiosInstance.get<IntroTimestamps>(
|
||||||
{ headers: getAuthHeaders(api) },
|
`${api.basePath}/Episode/${itemId}/IntroTimestamps`,
|
||||||
),
|
{ headers: getAuthHeaders(api) },
|
||||||
api.axiosInstance.get<CreditTimestamps>(
|
),
|
||||||
`${api.basePath}/Episode/${itemId}/Timestamps`,
|
api.axiosInstance.get<CreditTimestamps>(
|
||||||
{ headers: getAuthHeaders(api) },
|
`${api.basePath}/Episode/${itemId}/Timestamps`,
|
||||||
),
|
{ headers: getAuthHeaders(api) },
|
||||||
]);
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
if (introRes.status === "fulfilled" && introRes.value.data.Valid) {
|
if (introRes.status === "fulfilled" && introRes.value.data.Valid) {
|
||||||
buckets.introSegments.push({
|
introSegments.push({
|
||||||
startTime: introRes.value.data.IntroStart,
|
startTime: introRes.value.data.IntroStart,
|
||||||
endTime: introRes.value.data.IntroEnd,
|
endTime: introRes.value.data.IntroEnd,
|
||||||
text: "Intro",
|
text: "Intro",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
creditRes.status === "fulfilled" &&
|
||||||
|
creditRes.value.data.Credits.Valid
|
||||||
|
) {
|
||||||
|
creditSegments.push({
|
||||||
|
startTime: creditRes.value.data.Credits.Start,
|
||||||
|
endTime: creditRes.value.data.Credits.End,
|
||||||
|
text: "Credits",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch legacy segments", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (creditRes.status === "fulfilled" && creditRes.value.data.Credits.Valid) {
|
return { introSegments, creditSegments };
|
||||||
buckets.creditSegments.push({
|
|
||||||
startTime: creditRes.value.data.Credits.Start,
|
|
||||||
endTime: creditRes.value.data.Credits.End,
|
|
||||||
text: "Outro",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return buckets;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchAndParseSegments = async (
|
export const fetchAndParseSegments = async (
|
||||||
itemId: string,
|
itemId: string,
|
||||||
api: Api,
|
api: Api,
|
||||||
): Promise<SegmentBuckets> => {
|
): Promise<{
|
||||||
|
introSegments: MediaTimeSegment[];
|
||||||
|
creditSegments: MediaTimeSegment[];
|
||||||
|
}> => {
|
||||||
|
// Try new API first (Jellyfin 10.11+)
|
||||||
const newSegments = await fetchMediaSegments(itemId, api);
|
const newSegments = await fetchMediaSegments(itemId, api);
|
||||||
return newSegments ?? fetchLegacySegments(itemId, api);
|
if (newSegments) {
|
||||||
|
return newSegments;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to legacy endpoints
|
||||||
|
return fetchLegacySegments(itemId, api);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user