Compare commits

..

32 Commits

Author SHA1 Message Date
Gauvain
e1f7968352 fix(navigation): dismiss the keyboard when using the header back button
Leaving a screen with the keyboard up (e.g. the Jellyseerr login) left it
lingering over the previous screen. Dismiss it before navigating back.
2026-06-16 23:42:31 +02:00
Gauvain
f8e0baa0c0 refactor(kefin-tweaks): use the standard ListItem/disabledByAdmin pattern
Rebuild the KefinTweaks toggle on ListGroup + ListItem like the other
plugin settings: drop the bespoke card, the red label, the custom switch
colours, and the `t("Watchlist On")`/`t("Watchlist Off")` literal-as-key
strings. The admin lock now shows via the ListItem subtitle instead of
wrapping the whole screen in DisabledSetting.
2026-06-16 23:42:25 +02:00
Gauvain
9ed49e040e fix(jellyseerr): lock only the server URL, keep the password editable
The page greyed out the whole settings screen when the admin locked the
server URL, so the user couldn't even type their password to sign in to
the pinned server. Now only the URL field is disabled (greyed + "Disabled
by admin"); the password input stays editable.
2026-06-16 23:42:20 +02:00
Gauvain
e2acc40c49 refactor(settings): move "refresh from server" to the plugins index
The button pulls the centralised Streamyfin plugin settings for every
plugin, so it belongs on the plugins list page rather than buried inside
the Streamystats screen.
2026-06-16 23:42:12 +02:00
Gauvain
3d84b558fb fix(marlin-search): show the admin-lock notice via the ListItem subtitle
The "Disabled by admin" notice was rendered by DisabledSetting inside the
rounded, overflow-hidden card, which clipped the text. Switch to the same
pattern as the Streamystats settings: plain ListItems with the
`disabledByAdmin` prop, so the notice renders as the row subtitle and the
URL/toggle disable per-field.
2026-06-16 23:27:52 +02:00
Gauvain
8270c4c30c feat(quick-connect): add a paste button to the authorize code input
Codes often arrive over SMS/another app; add a "Paste code" button under
the PIN input that reads the clipboard (expo-clipboard, probed optionally)
and keeps the 6 digits.
2026-06-16 23:25:16 +02:00
Gauvain
4d9fc1d615 fix(library): scroll the grid back to the top when filters change
Resetting or changing a filter/sort refetches from page 1, but the
FlashList kept its previous offset, leaving the user stranded mid-list.
Hold a FlashListRef and scrollToOffset(0) whenever the active
filters/sort change.
2026-06-16 23:25:06 +02:00
Gauvain
3478f7a040 Merge branch 'develop' into fix/ui-and-bugs 2026-06-16 21:54:56 +02:00
Gauvain
32adb5c43a fix(ui): unify the header back icon and fix its Android alignment
Use the Settings chevron-left for HeaderBackButton everywhere (was
Ionicons arrow-back) so every back button matches. On Android, replace
the `rounded-full p-2` (which pushed both the arrow and the title too far
right) with a 16px right margin, like the Settings back button.
2026-06-16 21:50:00 +02:00
Gauvain
b6da8c0784 fix(settings): align the Settings/Plugins back chevron with the edge
Drop the `pl-0.5` that pushed the back chevron slightly to the right on
the Settings and Plugins headers, matching the other back buttons.
2026-06-16 21:49:57 +02:00
Gauvain
fc53a7d760 fix(logs): keep the filter bar below the header on iOS
The logs page rendered its filter bar in a plain View with no top inset,
so under the transparent iOS header it sat behind the header and the
filter buttons weren't tappable. Use a root ScrollView with
contentInsetAdjustmentBehavior='automatic' (like the sibling settings
pages) and make the filter bar a sticky header, so iOS insets the content
below the header and the bar stays pinned while logs scroll.
2026-06-16 21:49:35 +02:00
Gauvain
3035a9019c Merge branch 'develop' into fix/ui-and-bugs 2026-06-16 21:03:12 +02:00
Gauvain
182a2cf1e6 Merge remote-tracking branch 'origin/develop' into fix/ui-and-bugs
# Conflicts:
#	bun.lock
#	package.json
2026-06-16 20:59:00 +02:00
Gauvain
6195db2a83 Merge branch 'develop' into fix/ui-and-bugs 2026-06-15 01:36:00 +02:00
Gauvino
599096f883 fix(review): address second CodeRabbit pass
- streamystats: derive toggle enablement from the same effective URL the
  input renders (locked admin URL no longer disables every switch)
- FilterSheet: use the deep-equality rule for toggling that rendering
  already uses — option objects are recreated across renders
- DownloadCard: take t from useTranslation so badge labels re-render on
  language change
- fileOperations: count trickplay bytes in the storage total, matching
  the per-item size model
- PendingAccountSaveModal: warn instead of silently swallowing a failed
  account save
2026-06-12 16:23:08 +02:00
Gauvino
3247bf709c fix(review): address CodeRabbit feedback
- swap direct i18next t imports for the useTranslation hook so the four
  touched components re-render on language change
- localize the buffer seconds unit via a buffer_seconds key instead of a
  literal trailing s
- reword the useAppRouter guard comment to match its real scope
2026-06-12 15:00:49 +02:00
Gauvain
2af252d639 Merge branch 'develop' into fix/ui-and-bugs 2026-06-12 11:03:08 +02:00
Gauvain
1636523d48 fix(login): ask how to protect a saved account after the login succeeds
The protection picker used to show before the login attempt, so a wrong
password still walked the user through choosing a PIN/password for an
account that never logged in - and a Quick Connect login could not save
the account at all.

Login flows now only flag the intent (pendingAccountSaveAtom); the
picker is a global PendingAccountSaveModal mounted at the root, shown
once the session is authorized - the login screen unmounts on success,
so it cannot host the modal itself. Works identically for the password
and Quick Connect flows; the credential is saved from the live session
token (saveCurrentAccount). Cancelling saves nothing, and a logout
before answering drops the intent.
2026-06-11 00:42:59 +02:00
Gauvain
855957707a fix(notifications): drop deprecated handler flags and payload logging
- shouldShowAlert is deprecated in expo-notifications: specify
  shouldShowBanner and shouldShowList instead (same behavior).
- The foreground listener logged the entire notification object, which
  touches the deprecated dataString getter (another deprecation warning)
  and dumps noisy payloads into the console - log only the title.
2026-06-11 00:42:59 +02:00
Gauvain
4bad8ae054 fix(filters): keep the search input responsive on large option lists
Typing in the filter-sheet search re-filtered and re-rendered up to 100
option rows per keystroke. On large lists (2000+ tags) that blocked the
JS thread long enough for the controlled TextInput to snap back to a
stale value - letters were dropped and deleted text reappeared.

Defer the search value (useDeferredValue) so the keystroke render stays
cheap and the filtering/list update runs after, and memoize the row
elements so urgent renders don't rebuild them.
2026-06-11 00:42:58 +02:00
Gauvain
16188ac2a3 fix(login): show the Quick Connect code in an auto-dismissing sheet
The code was shown in a native Alert, which has no programmatic
dismiss: after another device authorized the code and polling logged
the user in, the alert stayed open on top of the app.

Replace it with an in-app bottom sheet that closes itself once the
session is authorized. Dismissing only hides the code - polling
continues so login still completes if the code is authorized
afterwards; polling stops when leaving the login screen (parity with
TVLogin). The code can be tapped to copy (expo-clipboard, probed via
requireOptionalNativeModule so builds without the native module just
no-op).
2026-06-11 00:42:57 +02:00
Gauvain
d12d62863e fix(filters): present the filter sheet from the press handler
On the new architecture with Reanimated 4, BottomSheetModal.present()
called from a useEffect after a state update silently no-ops: the press
registered, open flipped to true, the effect called present() on a
valid ref - and nothing mounted (no onChange, nothing in the native
tree). Sheets that present() directly inside their press handler
(downloads, account picker) kept working, which is what pinned it down.

FilterSheet now takes a modalRef and the opener presents imperatively
from the gesture handler. The [open] effect only handles closing, and
never dismisses a modal that was never presented. The sheet also opens
immediately with a loader while options load, instead of the old
data-loaded press gate that left the button silently dead.

This restores genre/year/tag/sort filters in libraries and collections,
and the same pattern is applied to the bitrate/media-source/track
sheets that share FilterSheet.
2026-06-11 00:42:56 +02:00
Gauvain
7eb65ba430 fix(auth): clear the session on any 401 via a response interceptor
When the server revokes the token (device/session deleted), a 401 can
surface from any authenticated request. Nothing cleaned it up: the dead
token stayed in storage, every reload re-fired authenticated calls (401
spam, uncaught rejections) and the app lingered half-authenticated.

A response interceptor on the authenticated api clears the session once
on the first 401 so the app drops cleanly to the login screen. It only
attaches when api.accessToken is set, so a wrong-password 401 on the
login screen is never treated as session expiry. Saved credentials are
kept for quick re-login.
2026-06-11 00:42:55 +02:00
Gauvain
43d469f398 fix(auth): stop the offline splash hang and soften handled auth logs
On a cold start without network, startup awaited getCurrentUser on an
axios instance with no timeout, so the splash hung for the full OS TCP
timeout (75-120 s). Render from the cached user immediately and run the
token validation/refresh in the background; setInitialLoaded moves to a
finally so every path dismisses the splash.

Handled failures (quick-login with a revoked token, background
validation while offline) now log as warnings, and the background path
logs only status/message - axios errors carry the Authorization header.
2026-06-11 00:42:55 +02:00
Gauvain
d397233991 fix(filters): memoize useFilterOptions and drop debug logging
The hook returned a fresh array on every render (and console.logged
it). The unstable identity cascaded into list-header re-creation and,
under heavy re-rendering, tripped React maximum-update-depth.
2026-06-11 00:42:54 +02:00
Gauvain
7f020120b3 fix(settings): enforce admin-locked settings at write time
updateSettings persisted any key into user storage, including ones the
admin locked via the Streamyfin plugin. The read memo already overrides
locked keys at runtime, but the write still landed in storage and
several settings screens never disable their controls, so locked
settings appeared changeable. Strip locked keys before persisting.
2026-06-11 00:42:53 +02:00
Gauvain
aec3444829 fix(nav): drop duplicate pushes from rapid taps
Tapping an item twice before the pushed screen rendered stacked the
screen twice. A push blurs the source screen synchronously in the
navigation state, so a second tap sees an unfocused screen and is
dropped (focus-based guard, no timers).
2026-06-11 00:42:52 +02:00
Gauvain
24f9c38098 fix(downloads): key the series poster cache read on SeriesId
The cached base64 poster was read in a useMemo with empty deps, so
recycled list cells kept showing the first-rendered series poster.
2026-06-11 00:42:51 +02:00
Gauvain
1dd1940334 fix(downloads): compute storage usage from live file sizes
The storage bar showed 0.00% because calculateTotalDownloadedSize
summed the stored videoFileSize, which is 0 for items downloaded before
the size was recorded (or when fileInfo.size was undefined). Stat the
file on disk and fall back to the stored value.
2026-06-11 00:42:51 +02:00
Gauvain
1e537bc11e fix(downloads): confirm before deleting all downloaded files
The "Delete all downloaded files" row wiped everything on a single tap.
Ask for confirmation first (destructive action, cannot be undone).
2026-06-10 22:29:51 +02:00
Gauvain
b163c2abb4 fix(settings): restore plugin list order
Jellyseerr / Streamystats / Marlin Search rows were reordered by
mistake; put them back in the original order.
2026-06-10 22:29:48 +02:00
Gauvain
0d47c8d43a feat(i18n): localize hardcoded UI strings and fix misspelled keys
Move remaining hardcoded English strings (player menus, technical-info
overlay, music/now-playing, live TV, TV search badges, MPV subtitle
settings, accessibility labels, not-found screen, session picker) to
en.json, and correct misspelled keys (occured -> occurred, autorized ->
authorized, liraries -> libraries, jellyseer -> jellyseerr) along with
their usages.
2026-06-10 22:29:16 +02:00
104 changed files with 4292 additions and 1203 deletions

View File

@@ -1,21 +0,0 @@
name: Refresh PR build comment
description: >-
Nudge artifact-comment.yml (via workflow_dispatch) so the PR build-status
comment reflects live per-platform progress as each build job finishes.
runs:
using: composite
steps:
# workflow_dispatch fires even when triggered by the GITHUB_TOKEN, and
# artifact-comment's concurrency group collapses simultaneous nudges, so
# this can't spam the comment. Skipped on forks (their read-only token
# cannot dispatch). github.token is used because composite actions cannot
# read the secrets context.
- if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
continue-on-error: true
shell: bash
env:
GH_TOKEN: ${{ github.token }}
HEAD_REF: ${{ github.head_ref }}
REPO: ${{ github.repository }}
run: gh workflow run artifact-comment.yml --ref "$HEAD_REF" -R "$REPO"

View File

@@ -144,7 +144,7 @@ jobs:
)
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
console.log(`Found ${buildRuns.length} build workflow runs for this commit`);
console.log(`Found ${buildRuns.length} non-cancelled build workflow runs for this commit`);
// Log current status of each build for debugging
buildRuns.forEach(run => {
@@ -184,35 +184,21 @@ jobs:
const latestAndroidRun = findBestRun('Android APK Build');
const latestIOSRun = findBestRun('iOS IPA Build');
// Map our build targets to their job display names. Exact name is
// tried first so a signed target never collides with its
// "(Unsigned)" sibling (whose name contains the signed name).
const jobMappings = {
'Android Phone': ['🤖 Build Android APK (Phone)'],
'Android TV': ['🤖 Build Android APK (TV)'],
'iOS': ['🍎 Build iOS IPA (Phone)'],
'iOS Unsigned': ['🍎 Build iOS IPA (Phone - Unsigned)'],
'tvOS': ['🍎 Build tvOS IPA'],
'tvOS Unsigned': ['🍎 Build tvOS IPA (Unsigned)']
};
// Prefer an exact name match over a substring match so
// '...(Phone)' doesn't swallow '...(Phone - Unsigned)'.
const findJobForTarget = (jobs, jobNames) =>
jobs.find(j => jobNames.some(name => j.name === name)) ||
jobs.find(j => jobNames.some(name => j.name.includes(name)));
// Format a millisecond duration as "Xm Ys".
const fmtDuration = (ms) => {
const min = Math.floor(ms / 60000);
const sec = Math.floor((ms % 60000) / 1000);
return `${min}m ${sec}s`;
};
// For the consolidated workflow, get individual job statuses
if (latestAppsRun) {
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 {
// Get all jobs for this workflow run
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
@@ -243,8 +229,10 @@ jobs:
// Create individual status for each job
for (const [platform, jobNames] of Object.entries(jobMappings)) {
const job = findJobForTarget(jobs.jobs, jobNames);
const job = jobs.jobs.find(j =>
jobNames.some(name => j.name.includes(name) || j.name === name)
);
if (job) {
buildStatuses[platform] = {
name: job.name,
@@ -370,43 +358,6 @@ jobs:
console.log(`- Artifact: ${artifact.name} (from run ${artifact.workflow_run.id})`);
});
// Pull per-job durations from the latest successful develop build so
// in-progress / queued targets can show a realistic ETA instead of
// an open-ended spinner. Best-effort: any failure just drops the ETA.
let referenceDurations = {};
try {
const { data: devRuns } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'build-apps.yml',
branch: 'develop',
status: 'success',
per_page: 1
});
if (devRuns.workflow_runs.length > 0) {
const refRun = devRuns.workflow_runs[0];
const { data: refJobs } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: refRun.id
});
for (const [platform, jobNames] of Object.entries(jobMappings)) {
const job = findJobForTarget(refJobs.jobs, jobNames);
if (job && job.conclusion === 'success' && job.started_at && job.completed_at) {
referenceDurations[platform] = new Date(job.completed_at) - new Date(job.started_at);
}
}
console.log(`Reference durations from develop run ${refRun.id}:`,
Object.fromEntries(Object.entries(referenceDurations).map(([k, v]) => [k, fmtDuration(v)])));
} else {
console.log('No successful develop build found for ETA reference');
}
} catch (error) {
console.log('Failed to fetch develop reference durations:', error.message);
}
// Build comment body with progressive status for individual builds
let commentBody = `## 🔧 Build Status for PR #${pr.number}\n\n`;
commentBody += `🔗 **Commit**: [\`${targetCommitSha.substring(0, 7)}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${targetCommitSha})\n\n`; // Progressive build status and downloads table
@@ -418,9 +369,9 @@ jobs:
const buildTargets = [
{ name: 'Android Phone', platform: '🤖', device: '📱 Phone', statusKey: 'Android Phone', artifactPattern: /android.*phone/i },
{ name: 'Android TV', platform: '🤖', device: '📺 TV', statusKey: 'Android TV', artifactPattern: /android.*tv/i },
{ name: 'iOS', platform: '🍎', device: '📱 Phone', statusKey: 'iOS', artifactPattern: /^(?!.*unsigned).*ios.*phone.*ipa/i },
{ name: 'iOS', platform: '🍎', device: '📱 Phone', statusKey: 'iOS', artifactPattern: /ios.*phone.*ipa(?!.*unsigned)/i },
{ name: 'iOS Unsigned', platform: '🍎', device: '📱 Phone Unsigned', statusKey: 'iOS Unsigned', artifactPattern: /ios.*phone.*unsigned/i },
{ name: 'tvOS', platform: '🍎', device: '📺 TV', statusKey: 'tvOS', artifactPattern: /^(?!.*unsigned).*ios.*tv.*ipa/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 }
];
@@ -456,9 +407,11 @@ jobs:
let durationInfo = '';
if (matchingStatus.started_at && matchingStatus.completed_at) {
const durationMs = new Date(matchingStatus.completed_at) - new Date(matchingStatus.started_at);
durationInfo = ` - ${fmtDuration(durationMs)}`;
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') {
status = `❌ [Failed](${matchingStatus.url})`;
@@ -468,16 +421,10 @@ jobs:
downloadLink = '*Build cancelled*';
} else if (matchingStatus.status === 'in_progress') {
status = `🔄 [Building...](${matchingStatus.url})`;
const ref = referenceDurations[target.statusKey];
downloadLink = ref
? `*Building… ~${fmtDuration(ref)} (avg on develop)*`
: '*Build in progress...*';
downloadLink = '*Build in progress...*';
} else if (matchingStatus.status === 'queued') {
status = `⏳ [Queued](${matchingStatus.url})`;
const ref = referenceDurations[target.statusKey];
downloadLink = ref
? `*Waiting to start… ~${fmtDuration(ref)} once running (avg on develop)*`
: '*Waiting to start...*';
downloadLink = '*Waiting to start...*';
} else if (matchingStatus.status === 'completed' && !matchingStatus.conclusion) {
// Workflow completed but conclusion not yet available (rare edge case)
status = `🔄 [Finishing...](${matchingStatus.url})`;
@@ -498,27 +445,26 @@ jobs:
commentBody += `\n`;
// Static rundown of the build optimisations + what each artifact
// installs on. Always shown (even mid-build) so testers know what
// to expect before downloads are ready.
commentBody += `<details>\n`;
commentBody += `<summary>📦 Build details &amp; device compatibility</summary>\n\n`;
commentBody += `These CI builds are trimmed for size and speed. What that means for installing them:\n\n`;
commentBody += `| Artifact | Architectures | Installs on |\n`;
commentBody += `|---|---|---|\n`;
commentBody += `| 🤖 Android Phone APK | \`arm64-v8a\` | Every 64-bit Android phone (all since ~2017). **Not** an x86_64 emulator or a 32-bit device. |\n`;
commentBody += `| 📺 Android TV APK | \`arm64-v8a\` + \`armeabi-v7a\` | Modern boxes **and** older / cheap 32-bit Android TV sticks. No x86_64. |\n`;
commentBody += `| 🍎 iOS / tvOS IPA | \`arm64\` | iPhone / Apple TV (all current devices). |\n\n`;
commentBody += `**Why no x86_64?** That slice only runs on Android emulators / Chromebooks, never a real phone or TV box — dropping it shrinks the APK and speeds up the build. Local \`bun run android\` is unaffected (it still builds x86_64 from \`app.json\`).\n\n`;
commentBody += `**Runners:** Android on \`ubuntu-26.04\`; iOS / tvOS on Apple Silicon (\`macos-26\`). The size/speed win comes from the ABI trim above, not the runner.\n`;
commentBody += `</details>\n\n`;
// Installation instructions only matter once something is downloadable.
// Show installation instructions if we have any artifacts
if (allArtifacts.length > 0) {
commentBody += `### 🔧 Installation Instructions\n\n`;
commentBody += `- **Android APK**: Download and install directly on your device (enable "Install from unknown sources")\n`;
commentBody += `- **iOS IPA**: Install using [AltStore](https://altstore.io/), [Sideloadly](https://sideloadly.io/), or Xcode\n\n`;
commentBody += `> ⚠️ **Note**: Artifacts expire in 7 days from build date\n\n`;
// Collapsible rundown of the build optimisations + what each
// artifact actually installs on, so testers grab the right file.
commentBody += `<details>\n`;
commentBody += `<summary>📦 Build details &amp; device compatibility</summary>\n\n`;
commentBody += `These CI builds are trimmed for size and speed. What that means for installing them:\n\n`;
commentBody += `| Artifact | Architectures | Installs on |\n`;
commentBody += `|---|---|---|\n`;
commentBody += `| 🤖 Android Phone APK | \`arm64-v8a\` | Every 64-bit Android phone (all since ~2017). **Not** an x86_64 emulator or a 32-bit device. |\n`;
commentBody += `| 📺 Android TV APK | \`arm64-v8a\` + \`armeabi-v7a\` | Modern boxes **and** older / cheap 32-bit Android TV sticks. No x86_64. |\n`;
commentBody += `| 🍎 iOS / tvOS IPA | \`arm64\` | iPhone / Apple TV (all current devices). |\n\n`;
commentBody += `**Why no x86_64?** That slice only runs on Android emulators / Chromebooks, never a real phone or TV box — dropping it shrinks the APK and speeds up the build. Local \`bun run android\` is unaffected (it still builds x86_64 from \`app.json\`).\n\n`;
commentBody += `**Runners:** Android on \`ubuntu-26.04\`; iOS / tvOS on Apple Silicon (\`macos-26\`). The size/speed win comes from the ABI trim above, not the runner.\n`;
commentBody += `</details>\n\n`;
} else {
commentBody += `⏳ **Builds are starting up...** This comment will update automatically as each build completes.\n\n`;
}

View File

@@ -27,7 +27,6 @@ jobs:
name: 🤖 Build Android APK (Phone)
permissions:
contents: read
actions: write # dispatch artifact-comment.yml to refresh the PR comment
steps:
- name: 🗑️ Free Disk Space
@@ -43,7 +42,7 @@ jobs:
swap-storage: false
- name: 📥 Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
@@ -73,7 +72,7 @@ jobs:
# ubuntu-26.04 defaults to JDK 25, which breaks the RN/AGP native build
# (Kotlin falls back to JVM_23, the foojay toolchain + CMake configure
# fail). Pin Temurin 17 for a deterministic Android build.
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: "17"
@@ -118,16 +117,12 @@ jobs:
android/app/build/outputs/apk/release/*.apk
retention-days: 7
- name: 🔄 Refresh PR build comment
uses: ./.github/actions/refresh-pr-comment
build-android-tv:
if: (!contains(github.event.head_commit.message, '[skip ci]'))
runs-on: ubuntu-26.04
name: 🤖 Build Android APK (TV)
permissions:
contents: read
actions: write # dispatch artifact-comment.yml to refresh the PR comment
steps:
- name: 🗑️ Free Disk Space
@@ -143,7 +138,7 @@ jobs:
swap-storage: false
- name: 📥 Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
@@ -173,7 +168,7 @@ jobs:
# ubuntu-26.04 defaults to JDK 25, which breaks the RN/AGP native build
# (Kotlin falls back to JVM_23, the foojay toolchain + CMake configure
# fail). Pin Temurin 17 for a deterministic Android build.
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: "17"
@@ -217,20 +212,16 @@ jobs:
android/app/build/outputs/apk/release/*.apk
retention-days: 7
- name: 🔄 Refresh PR build comment
uses: ./.github/actions/refresh-pr-comment
build-ios-phone:
if: (!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
name: 🍎 Build iOS IPA (Phone)
permissions:
contents: read
actions: write # dispatch artifact-comment.yml to refresh the PR comment
steps:
- name: 📥 Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
@@ -289,20 +280,16 @@ jobs:
path: build-*.ipa
retention-days: 7
- name: 🔄 Refresh PR build comment
uses: ./.github/actions/refresh-pr-comment
build-ios-phone-unsigned:
if: (!contains(github.event.head_commit.message, '[skip ci]'))
runs-on: macos-26
name: 🍎 Build iOS IPA (Phone - Unsigned)
permissions:
contents: read
actions: write # dispatch artifact-comment.yml to refresh the PR comment
steps:
- name: 📥 Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
@@ -352,9 +339,6 @@ jobs:
path: build/*.ipa
retention-days: 7
- name: 🔄 Refresh PR build comment
uses: ./.github/actions/refresh-pr-comment
build-ios-tv:
# Disabled: EAS has no provisioning profiles / distribution cert for the tvOS
# targets (app + StreamyfinTopShelf extension), so non-interactive signed
@@ -365,11 +349,10 @@ jobs:
name: 🍎 Build tvOS IPA
permissions:
contents: read
actions: write # dispatch artifact-comment.yml to refresh the PR comment
steps:
- name: 📥 Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
@@ -435,11 +418,10 @@ jobs:
name: 🍎 Build tvOS IPA (Unsigned)
permissions:
contents: read
actions: write # dispatch artifact-comment.yml to refresh the PR comment
steps:
- name: 📥 Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
@@ -488,6 +470,3 @@ jobs:
name: streamyfin-ios-tv-unsigned-ipa-${{ env.DATE_TAG }}
path: build/*.ipa
retention-days: 7
- name: 🔄 Refresh PR build comment
uses: ./.github/actions/refresh-pr-comment

View File

@@ -19,7 +19,7 @@ jobs:
steps:
- name: 📥 Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
show-progress: false

View File

@@ -27,7 +27,7 @@ jobs:
steps:
- name: 📥 Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: 🏁 Initialize CodeQL
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: 📥 Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0

View File

@@ -21,7 +21,7 @@ jobs:
contents: read
steps:
- name: 📥 Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: 🍞 Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0

View File

@@ -51,7 +51,7 @@ jobs:
contents: read
steps:
- name: Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
@@ -68,7 +68,7 @@ jobs:
runs-on: ubuntu-26.04
steps:
- name: 🛒 Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
submodules: recursive
@@ -104,7 +104,7 @@ jobs:
steps:
- name: "📥 Checkout PR code"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
submodules: recursive
@@ -114,7 +114,7 @@ jobs:
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
# renovate: datasource=node-version depName=node versioning=node
node-version: "24.17.0"
node-version: "24.16.0"
- name: "🍞 Setup Bun"
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0

View File

@@ -64,7 +64,7 @@ jobs:
steps:
- name: 📥 Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
submodules: recursive
@@ -184,7 +184,7 @@ jobs:
actions: read # required for `gh run download` to list/fetch this run's artifacts
steps:
- name: 📥 Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
show-progress: false

View File

@@ -27,7 +27,7 @@ jobs:
security-events: write # upload SARIF to code scanning
steps:
- name: 📥 Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# Trivy's own action caches the vulnerability DB + binary internally
# (cache-trivy-* / trivy-binary-* entries), so no manual ~/.cache/trivy

View File

@@ -26,7 +26,7 @@ jobs:
pull-requests: write
steps:
- name: 📥 Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# On `release` events GITHUB_SHA is the tagged commit — without this the
# script would regenerate the form from the tag's (stale) copy and the bot

View File

@@ -73,7 +73,6 @@ export default function IndexLayout() {
headerLeft: () => (
<Pressable
onPress={() => _router.back()}
className='pl-0.5'
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
>
<Feather name='chevron-left' size={28} color='white' />
@@ -158,7 +157,6 @@ export default function IndexLayout() {
headerLeft: () => (
<Pressable
onPress={() => _router.back()}
className='pl-0.5'
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
>
<Feather name='chevron-left' size={28} color='white' />

View File

@@ -645,7 +645,7 @@ export default function SettingsTV() {
formatValue={(v) => `${v.toFixed(1)}x`}
/>
<TVSettingsStepper
label='Vertical Margin'
label={t("home.settings.subtitles.mpv_subtitle_margin_y")}
value={settings.mpvSubtitleMarginY ?? 0}
onDecrease={() => {
const newValue = Math.max(
@@ -663,11 +663,11 @@ export default function SettingsTV() {
}}
/>
<TVSettingsOptionButton
label='Horizontal Alignment'
label={t("home.settings.subtitles.mpv_subtitle_align_x")}
value={alignXLabel}
onPress={() =>
showOptions({
title: "Horizontal Alignment",
title: t("home.settings.subtitles.mpv_subtitle_align_x"),
options: alignXOptions,
onSelect: (value) =>
updateSettings({
@@ -677,11 +677,11 @@ export default function SettingsTV() {
}
/>
<TVSettingsOptionButton
label='Vertical Alignment'
label={t("home.settings.subtitles.mpv_subtitle_align_y")}
value={alignYLabel}
onPress={() =>
showOptions({
title: "Vertical Alignment",
title: t("home.settings.subtitles.mpv_subtitle_align_y"),
options: alignYOptions,
onSelect: (value) =>
updateSettings({

View File

@@ -71,7 +71,7 @@ export default function AppearanceHideLibrariesPage() {
))}
</ListGroup>
<Text className='px-4 text-xs text-neutral-500 mt-1'>
{t("home.settings.other.select_liraries_you_want_to_hide")}
{t("home.settings.other.select_libraries_you_want_to_hide")}
</Text>
</DisabledSetting>
</ScrollView>

View File

@@ -60,7 +60,7 @@ export default function HideLibrariesPage() {
))}
</ListGroup>
<Text className='px-4 text-xs text-neutral-500 mt-1'>
{t("home.settings.other.select_liraries_you_want_to_hide")}
{t("home.settings.other.select_libraries_you_want_to_hide")}
</Text>
</DisabledSetting>
);

View File

@@ -88,8 +88,15 @@ export default function Page() {
}, [share, loading]);
return (
<View className='flex-1'>
<View className='flex flex-row justify-end py-2 px-4 space-x-2'>
<ScrollView
// Like the sibling settings pages, let iOS auto-inset the content below the
// transparent header (no manual header-height math). The filter bar is a
// sticky header so it stays pinned just under the header while logs scroll.
contentInsetAdjustmentBehavior='automatic'
stickyHeaderIndices={[0]}
contentContainerStyle={{ paddingBottom: insets.bottom }}
>
<View className='flex flex-row justify-end py-2 px-4 space-x-2 bg-black'>
<FilterButton
id={orderFilterId}
queryKey='log'
@@ -112,67 +119,62 @@ export default function Page() {
multiple={true}
/>
</View>
<ScrollView
className='pb-4 px-4'
contentContainerStyle={{ paddingBottom: insets.bottom }}
>
<View className='flex flex-col space-y-2'>
{filteredLogs?.map((log, index) => (
<View className='bg-neutral-900 rounded-xl p-3' key={index}>
<TouchableOpacity
disabled={!log.data}
onPress={() =>
setState((v) => ({
...v,
[log.timestamp]: !v[log.timestamp],
}))
}
>
<View className='flex flex-row justify-between'>
<Text
className={`mb-1
<View className='flex flex-col space-y-2 px-4'>
{filteredLogs?.map((log, index) => (
<View className='bg-neutral-900 rounded-xl p-3' key={index}>
<TouchableOpacity
disabled={!log.data}
onPress={() =>
setState((v) => ({
...v,
[log.timestamp]: !v[log.timestamp],
}))
}
>
<View className='flex flex-row justify-between'>
<Text
className={`mb-1
${log.level === "INFO" && "text-blue-500"}
${log.level === "ERROR" && "text-red-500"}
${log.level === "DEBUG" && "text-purple-500"}
`}
>
{log.level}
</Text>
<Text className='text-xs'>
{new Date(log.timestamp).toLocaleString()}
</Text>
</View>
<Text selectable className='text-xs'>
{log.message}
>
{log.level}
</Text>
</TouchableOpacity>
{log.data && (
<>
{!state[log.timestamp] && (
<Text className='text-xs mt-0.5'>
{t("home.settings.logs.click_for_more_info")}
</Text>
)}
<Collapsible collapsed={!state[log.timestamp]}>
<View className='mt-2 flex flex-col space-y-2'>
<ScrollView className='rounded-xl' style={codeBlockStyle}>
<Text>{JSON.stringify(log.data, null, 2)}</Text>
</ScrollView>
</View>
</Collapsible>
</>
)}
</View>
))}
{filteredLogs?.length === 0 && (
<Text className='opacity-50'>
{t("home.settings.logs.no_logs_available")}
</Text>
)}
</View>
</ScrollView>
</View>
<Text className='text-xs'>
{new Date(log.timestamp).toLocaleString()}
</Text>
</View>
<Text selectable className='text-xs'>
{log.message}
</Text>
</TouchableOpacity>
{log.data && (
<>
{!state[log.timestamp] && (
<Text className='text-xs mt-0.5'>
{t("home.settings.logs.click_for_more_info")}
</Text>
)}
<Collapsible collapsed={!state[log.timestamp]}>
<View className='mt-2 flex flex-col space-y-2'>
<ScrollView className='rounded-xl' style={codeBlockStyle}>
<Text>{JSON.stringify(log.data, null, 2)}</Text>
</ScrollView>
</View>
</Collapsible>
</>
)}
</View>
))}
{filteredLogs?.length === 0 && (
<Text className='opacity-50'>
{t("home.settings.logs.no_logs_available")}
</Text>
)}
</View>
</ScrollView>
);
}

View File

@@ -1,11 +1,8 @@
import { ScrollView } from "react-native";
import { ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import DisabledSetting from "@/components/settings/DisabledSetting";
import { JellyseerrSettings } from "@/components/settings/Jellyseerr";
import { useSettings } from "@/utils/atoms/settings";
export default function JellyseerrPluginPage() {
const { pluginSettings } = useSettings();
const insets = useSafeAreaInsets();
return (
@@ -16,12 +13,9 @@ export default function JellyseerrPluginPage() {
paddingRight: insets.right,
}}
>
<DisabledSetting
disabled={pluginSettings?.jellyseerrServerUrl?.locked === true}
className='p-4'
>
<View className='p-4'>
<JellyseerrSettings />
</DisabledSetting>
</View>
</ScrollView>
);
}

View File

@@ -1,11 +1,8 @@
import { ScrollView } from "react-native";
import { ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import DisabledSetting from "@/components/settings/DisabledSetting";
import { KefinTweaksSettings } from "@/components/settings/KefinTweaks";
import { useSettings } from "@/utils/atoms/settings";
export default function KefinTweaksPage() {
const { pluginSettings } = useSettings();
const insets = useSafeAreaInsets();
return (
@@ -16,12 +13,9 @@ export default function KefinTweaksPage() {
paddingRight: insets.right,
}}
>
<DisabledSetting
disabled={pluginSettings?.useKefinTweaks?.locked === true}
className='p-4'
>
<View className='px-4'>
<KefinTweaksSettings />
</DisabledSetting>
</View>
</ScrollView>
);
}

View File

@@ -1,5 +1,5 @@
import { useNavigation } from "expo-router";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
Linking,
@@ -14,22 +14,22 @@ import { toast } from "sonner-native";
import { Text } from "@/components/common/Text";
import { ListGroup } from "@/components/list/ListGroup";
import { ListItem } from "@/components/list/ListItem";
import DisabledSetting from "@/components/settings/DisabledSetting";
import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient";
import { useSettings } from "@/utils/atoms/settings";
export default function MarlinSearchPage() {
const navigation = useNavigation();
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const { settings, updateSettings, pluginSettings } = useSettings();
const queryClient = useNetworkAwareQueryClient();
const [value, setValue] = useState<string>(settings?.marlinServerUrl || "");
const searchEngineLocked = pluginSettings?.searchEngine?.locked === true;
const marlinUrlLocked = pluginSettings?.marlinServerUrl?.locked === true;
const hasStreamystats = !!pluginSettings?.streamyStatsServerUrl?.value;
const onSave = (val: string) => {
updateSettings({
marlinServerUrl: !val.endsWith("/") ? val : val.slice(0, -1),
@@ -41,15 +41,8 @@ export default function MarlinSearchPage() {
Linking.openURL("https://github.com/fredrikburmester/marlin-search");
};
const disabled = useMemo(() => {
return (
pluginSettings?.searchEngine?.locked === true &&
pluginSettings?.marlinServerUrl?.locked === true
);
}, [pluginSettings]);
useEffect(() => {
if (!pluginSettings?.marlinServerUrl?.locked) {
if (!marlinUrlLocked) {
navigation.setOptions({
headerRight: () => (
<TouchableOpacity onPress={() => onSave(value)} className='px-2'>
@@ -60,7 +53,7 @@ export default function MarlinSearchPage() {
),
});
}
}, [navigation, value, pluginSettings?.marlinServerUrl?.locked, t]);
}, [navigation, value, marlinUrlLocked, t]);
if (!settings) return null;
@@ -72,52 +65,39 @@ export default function MarlinSearchPage() {
paddingRight: insets.right,
}}
>
<DisabledSetting disabled={disabled} className='px-4'>
<View className='px-4'>
<ListGroup>
<DisabledSetting
disabled={
pluginSettings?.searchEngine?.locked === true ||
!!pluginSettings?.streamyStatsServerUrl?.value
}
showText={!pluginSettings?.marlinServerUrl?.locked}
{/* disabledByAdmin renders the "Disabled by admin" notice as the row's
subtitle (same pattern as the Streamystats settings) — no clipping. */}
<ListItem
title={t(
"home.settings.plugins.marlin_search.enable_marlin_search",
)}
disabledByAdmin={searchEngineLocked}
onPress={() => {
updateSettings({ searchEngine: "Jellyfin" });
queryClient.invalidateQueries({ queryKey: ["search"] });
}}
>
<ListItem
title={t(
"home.settings.plugins.marlin_search.enable_marlin_search",
)}
onPress={() => {
updateSettings({ searchEngine: "Jellyfin" });
<Switch
value={settings.searchEngine === "Marlin"}
disabled={searchEngineLocked || hasStreamystats}
onValueChange={(val) => {
updateSettings({ searchEngine: val ? "Marlin" : "Jellyfin" });
queryClient.invalidateQueries({ queryKey: ["search"] });
}}
>
<Switch
value={settings.searchEngine === "Marlin"}
disabled={!!pluginSettings?.streamyStatsServerUrl?.value}
onValueChange={(value) => {
updateSettings({
searchEngine: value ? "Marlin" : "Jellyfin",
});
queryClient.invalidateQueries({ queryKey: ["search"] });
}}
/>
</ListItem>
</DisabledSetting>
/>
</ListItem>
</ListGroup>
<DisabledSetting
disabled={pluginSettings?.marlinServerUrl?.locked === true}
showText={!pluginSettings?.searchEngine?.locked}
className='mt-2 flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4'
>
<View
className={"flex flex-row items-center bg-neutral-900 h-11 pr-4"}
<ListGroup className='mt-2'>
<ListItem
title={t("home.settings.plugins.marlin_search.url")}
disabledByAdmin={marlinUrlLocked}
>
<Text className='mr-4'>
{t("home.settings.plugins.marlin_search.url")}
</Text>
<TextInput
editable={settings.searchEngine === "Marlin"}
className='text-white'
editable={!marlinUrlLocked && settings.searchEngine === "Marlin"}
className='text-white text-right flex-1'
placeholder={t(
"home.settings.plugins.marlin_search.server_url_placeholder",
)}
@@ -128,15 +108,16 @@ export default function MarlinSearchPage() {
textContentType='URL'
onChangeText={(text) => setValue(text)}
/>
</View>
</DisabledSetting>
</ListItem>
</ListGroup>
<Text className='px-4 text-xs text-neutral-500 mt-1'>
{t("home.settings.plugins.marlin_search.marlin_search_hint")}{" "}
<Text className='text-blue-500' onPress={handleOpenLink}>
{t("home.settings.plugins.marlin_search.read_more_about_marlin")}
</Text>
</Text>
</DisabledSetting>
</View>
</ScrollView>
);
}

View File

@@ -1,9 +1,21 @@
import { Platform, ScrollView, View } from "react-native";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { Platform, ScrollView, TouchableOpacity, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { toast } from "sonner-native";
import { Text } from "@/components/common/Text";
import { PluginSettings } from "@/components/settings/PluginSettings";
import { useSettings } from "@/utils/atoms/settings";
export default function PluginsPage() {
const insets = useSafeAreaInsets();
const { t } = useTranslation();
const { refreshStreamyfinPluginSettings } = useSettings();
const handleRefreshFromServer = useCallback(async () => {
await refreshStreamyfinPluginSettings();
toast.success(t("home.settings.plugins.streamystats.toasts.refreshed"));
}, [refreshStreamyfinPluginSettings, t]);
return (
<ScrollView
@@ -18,6 +30,17 @@ export default function PluginsPage() {
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
>
<PluginSettings />
{/* Pulls the centralised Streamyfin plugin settings for every plugin,
so it lives on the plugins index rather than inside Streamystats. */}
<TouchableOpacity
onPress={handleRefreshFromServer}
className='py-3 rounded-xl bg-neutral-800'
>
<Text className='text-center text-blue-500'>
{t("home.settings.plugins.streamystats.refresh_from_server")}
</Text>
</TouchableOpacity>
</View>
</ScrollView>
);

View File

@@ -22,12 +22,7 @@ export default function StreamystatsPage() {
const navigation = useNavigation();
const insets = useSafeAreaInsets();
const {
settings,
updateSettings,
pluginSettings,
refreshStreamyfinPluginSettings,
} = useSettings();
const { settings, updateSettings, pluginSettings } = useSettings();
const queryClient = useNetworkAwareQueryClient();
// Local state for all editable fields
@@ -49,7 +44,21 @@ export default function StreamystatsPage() {
);
const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true;
const isStreamystatsEnabled = !!url;
const searchLocked = pluginSettings?.searchEngine?.locked === true;
const movieRecsLocked =
pluginSettings?.streamyStatsMovieRecommendations?.locked === true;
const seriesRecsLocked =
pluginSettings?.streamyStatsSeriesRecommendations?.locked === true;
const promotedWatchlistsLocked =
pluginSettings?.streamyStatsPromotedWatchlists?.locked === true;
const hideWatchlistsTabLocked =
pluginSettings?.hideWatchlistsTab?.locked === true;
// The input renders the locked admin URL; enablement must follow the same
// effective value or every toggle stays disabled until local state syncs.
const effectiveUrl = isUrlLocked
? (settings?.streamyStatsServerUrl ?? "")
: url;
const isStreamystatsEnabled = !!effectiveUrl;
const onSave = useCallback(() => {
const cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url;
@@ -113,17 +122,6 @@ export default function StreamystatsPage() {
Linking.openURL("https://github.com/fredrikburmester/streamystats");
};
const handleRefreshFromServer = useCallback(async () => {
const newPluginSettings = await refreshStreamyfinPluginSettings();
// Update local state with new values
const newUrl = newPluginSettings?.streamyStatsServerUrl?.value || "";
setUrl(newUrl);
if (newUrl) {
setUseForSearch(true);
}
toast.success(t("home.settings.plugins.streamystats.toasts.refreshed"));
}, [refreshStreamyfinPluginSettings, t]);
if (!settings) return null;
return (
@@ -146,7 +144,7 @@ export default function StreamystatsPage() {
placeholder={t(
"home.settings.plugins.streamystats.server_url_placeholder",
)}
value={url}
value={effectiveUrl}
keyboardType='url'
returnKeyType='done'
autoCapitalize='none'
@@ -171,11 +169,18 @@ export default function StreamystatsPage() {
>
<ListItem
title={t("home.settings.plugins.streamystats.enable_search")}
disabledByAdmin={pluginSettings?.searchEngine?.locked === true}
disabledByAdmin={searchLocked}
>
{/* Locked controls show the live admin value and can't be toggled —
local form state would let the switch flip while the write guard
drops the change. */}
<Switch
value={useForSearch}
disabled={!isStreamystatsEnabled}
value={
searchLocked
? settings?.searchEngine === "Streamystats"
: useForSearch
}
disabled={!isStreamystatsEnabled || searchLocked}
onValueChange={setUseForSearch}
/>
</ListItem>
@@ -183,52 +188,62 @@ export default function StreamystatsPage() {
title={t(
"home.settings.plugins.streamystats.enable_movie_recommendations",
)}
disabledByAdmin={
pluginSettings?.streamyStatsMovieRecommendations?.locked === true
}
disabledByAdmin={movieRecsLocked}
>
<Switch
value={movieRecs}
value={
movieRecsLocked
? (settings?.streamyStatsMovieRecommendations ?? false)
: movieRecs
}
onValueChange={setMovieRecs}
disabled={!isStreamystatsEnabled}
disabled={!isStreamystatsEnabled || movieRecsLocked}
/>
</ListItem>
<ListItem
title={t(
"home.settings.plugins.streamystats.enable_series_recommendations",
)}
disabledByAdmin={
pluginSettings?.streamyStatsSeriesRecommendations?.locked === true
}
disabledByAdmin={seriesRecsLocked}
>
<Switch
value={seriesRecs}
value={
seriesRecsLocked
? (settings?.streamyStatsSeriesRecommendations ?? false)
: seriesRecs
}
onValueChange={setSeriesRecs}
disabled={!isStreamystatsEnabled}
disabled={!isStreamystatsEnabled || seriesRecsLocked}
/>
</ListItem>
<ListItem
title={t(
"home.settings.plugins.streamystats.enable_promoted_watchlists",
)}
disabledByAdmin={
pluginSettings?.streamyStatsPromotedWatchlists?.locked === true
}
disabledByAdmin={promotedWatchlistsLocked}
>
<Switch
value={promotedWatchlists}
value={
promotedWatchlistsLocked
? (settings?.streamyStatsPromotedWatchlists ?? false)
: promotedWatchlists
}
onValueChange={setPromotedWatchlists}
disabled={!isStreamystatsEnabled}
disabled={!isStreamystatsEnabled || promotedWatchlistsLocked}
/>
</ListItem>
<ListItem
title={t("home.settings.plugins.streamystats.hide_watchlists_tab")}
disabledByAdmin={pluginSettings?.hideWatchlistsTab?.locked === true}
disabledByAdmin={hideWatchlistsTabLocked}
>
<Switch
value={hideWatchlistsTab}
value={
hideWatchlistsTabLocked
? (settings?.hideWatchlistsTab ?? false)
: hideWatchlistsTab
}
onValueChange={setHideWatchlistsTab}
disabled={!isStreamystatsEnabled}
disabled={!isStreamystatsEnabled || hideWatchlistsTabLocked}
/>
</ListItem>
</ListGroup>
@@ -236,15 +251,6 @@ export default function StreamystatsPage() {
{t("home.settings.plugins.streamystats.home_sections_hint")}
</Text>
<TouchableOpacity
onPress={handleRefreshFromServer}
className='mt-6 py-3 rounded-xl bg-neutral-800'
>
<Text className='text-center text-blue-500'>
{t("home.settings.plugins.streamystats.refresh_from_server")}
</Text>
</TouchableOpacity>
{/* Disable button - only show if URL is not locked and Streamystats is enabled */}
{!isUrlLocked && isStreamystatsEnabled && (
<TouchableOpacity

View File

@@ -9,12 +9,12 @@ import {
getItemsApi,
getUserLibraryApi,
} from "@jellyfin/sdk/lib/utils/api";
import { FlashList } from "@shopify/flash-list";
import { FlashList, type FlashListRef } from "@shopify/flash-list";
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import { Image } from "expo-image";
import { useLocalSearchParams, useNavigation } from "expo-router";
import { useAtom } from "jotai";
import React, { useCallback, useEffect, useMemo } from "react";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import {
FlatList,
@@ -376,6 +376,21 @@ const Page = () => {
);
}, [data]);
const flashListRef = useRef<FlashListRef<BaseItemDto>>(null);
// Reset the grid to the top whenever the active filters/sort change (e.g.
// pressing reset) — otherwise the list stays stuck at the previous offset.
useEffect(() => {
flashListRef.current?.scrollToOffset({ offset: 0, animated: false });
}, [
selectedGenres,
selectedYears,
selectedTags,
sortBy,
sortOrder,
filterBy,
]);
const renderItem = useCallback(
({ item, index }: { item: BaseItemDto; index: number }) => (
<TouchableItemRouter
@@ -868,6 +883,7 @@ const Page = () => {
if (!Platform.isTV) {
return (
<FlashList
ref={flashListRef}
key={orientation}
ListEmptyComponent={
<View className='flex flex-col items-center justify-center h-full'>

View File

@@ -89,7 +89,7 @@ export default function ArtistsScreen() {
return (
<View className='flex-1 justify-center items-center bg-black px-6'>
<Text className='text-neutral-500 text-center'>
Missing music library id.
{t("music.missing_library_id")}
</Text>
</View>
);

View File

@@ -122,7 +122,7 @@ export default function PlaylistsScreen() {
return (
<View className='flex-1 justify-center items-center bg-black px-6'>
<Text className='text-neutral-500 text-center'>
Missing music library id.
{t("music.missing_library_id")}
</Text>
</View>
);

View File

@@ -226,7 +226,7 @@ export default function SuggestionsScreen() {
return (
<View className='flex-1 justify-center items-center bg-black px-6'>
<Text className='text-neutral-500 text-center'>
Missing music library id.
{t("music.missing_library_id")}
</Text>
</View>
);

View File

@@ -14,6 +14,7 @@ import React, {
useRef,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import {
ActivityIndicator,
Dimensions,
@@ -72,6 +73,7 @@ const ARTWORK_SIZE = SCREEN_WIDTH - 80;
type ViewMode = "player" | "queue";
export default function NowPlayingScreen() {
const { t } = useTranslation();
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const router = useRouter();
@@ -230,7 +232,9 @@ export default function NowPlayingScreen() {
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
}}
>
<Text className='text-neutral-500'>No track playing</Text>
<Text className='text-neutral-500'>
{t("music.no_track_playing")}
</Text>
</View>
</BottomSheetModalProvider>
);
@@ -267,7 +271,7 @@ export default function NowPlayingScreen() {
: "text-neutral-500"
}
>
Now Playing
{t("music.now_playing")}
</Text>
</TouchableOpacity>
<TouchableOpacity
@@ -718,6 +722,7 @@ const QueueView: React.FC<QueueViewProps> = ({
onRemoveFromQueue,
onReorderQueue,
}) => {
const { t } = useTranslation();
const renderQueueItem = useCallback(
({ item, drag, isActive, getIndex }: RenderItemParams<BaseItemDto>) => {
const index = getIndex() ?? 0;
@@ -831,13 +836,15 @@ const QueueView: React.FC<QueueViewProps> = ({
ListHeaderComponent={
<View className='px-4 py-2'>
<Text className='text-neutral-400 text-xs uppercase tracking-wider'>
{history.length > 0 ? "Playing from queue" : "Up next"}
{history.length > 0
? t("music.playing_from_queue")
: t("music.up_next")}
</Text>
</View>
}
ListEmptyComponent={
<View className='flex-1 items-center justify-center py-20'>
<Text className='text-neutral-500'>Queue is empty</Text>
<Text className='text-neutral-500'>{t("music.queue_empty")}</Text>
</View>
}
/>

View File

@@ -1267,7 +1267,7 @@ export default function DirectPlayerPage() {
console.error("Video Error:", e.nativeEvent);
Alert.alert(
t("player.error"),
t("player.an_error_occured_while_playing_the_video"),
t("player.an_error_occurred_while_playing_the_video"),
);
writeToLog("ERROR", "Video Error", e.nativeEvent);
}}

View File

@@ -192,6 +192,7 @@ const SubtitleResultCard = React.forwardRef<
>(({ result, hasTVPreferredFocus, isDownloading, onPress }, ref) => {
const { focused, handleFocus, handleBlur, animatedStyle } =
useTVFocusAnimation({ scaleAmount: 1.03 });
const { t } = useTranslation();
return (
<Pressable
@@ -328,7 +329,7 @@ const SubtitleResultCard = React.forwardRef<
]}
>
<Text style={[styles.flagText, { fontSize: scaleSize(10) }]}>
Hash Match
{t("player.hash_match")}
</Text>
</View>
)}

View File

@@ -1,17 +1,20 @@
import { Link, Stack } from "expo-router";
import { useTranslation } from "react-i18next";
import { StyleSheet } from "react-native";
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
export default function NotFoundScreen() {
const { t } = useTranslation();
return (
<>
<Stack.Screen options={{ title: "Oops!" }} />
<Stack.Screen options={{ title: t("home.oops") }} />
<ThemedView style={styles.container}>
<ThemedText type='title'>This screen doesn't exist.</ThemedText>
<ThemedText type='title'>{t("not_found.title")}</ThemedText>
<Link href={"/home"} style={styles.link}>
<ThemedText type='link'>Go to home screen!</ThemedText>
<ThemedText type='link'>{t("not_found.go_home")}</ThemedText>
</Link>
</ThemedView>
</>

View File

@@ -10,6 +10,7 @@ import * as Device from "expo-device";
import { DarkTheme, ThemeProvider } from "expo-router/react-navigation";
import { Platform } from "react-native";
import { GlobalModal } from "@/components/GlobalModal";
import { PendingAccountSaveModal } from "@/components/PendingAccountSaveModal";
import { enableTVMenuKeyInterception } from "@/hooks/useTVBackHandler";
import i18n from "@/i18n";
import { DownloadProvider } from "@/providers/DownloadProvider";
@@ -84,7 +85,8 @@ configureReanimatedLogger({
if (!Platform.isTV) {
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
@@ -333,9 +335,12 @@ function Layout() {
notificationListener.current =
Notifications?.addNotificationReceivedListener(
(notification: Notification) => {
// Log only the title — serializing the whole notification touches
// the deprecated dataString getter (deprecation warning) and dumps
// noisy payloads into the console.
console.log(
"Notification received while app running",
notification,
"Notification received while app running:",
notification.request.content.title,
);
},
);
@@ -530,6 +535,7 @@ function Layout() {
closeButton
/>
{!Platform.isTV && <GlobalModal />}
{!Platform.isTV && <PendingAccountSaveModal />}
</ThemeProvider>
</IntroSheetProvider>
</BottomSheetModalProvider>

View File

@@ -31,6 +31,7 @@
"expo-brightness": "~56.0.5",
"expo-build-properties": "~56.0.18",
"expo-camera": "~56.0.8",
"expo-clipboard": "~56.0.4",
"expo-constants": "~56.0.18",
"expo-crypto": "~56.0.4",
"expo-dev-client": "~56.0.20",
@@ -111,7 +112,7 @@
"cross-env": "10.1.0",
"expo-doctor": "1.19.9",
"husky": "9.1.7",
"lint-staged": "17.0.7",
"lint-staged": "17.0.5",
"react-test-renderer": "19.2.3",
"typescript": "6.0.3",
},
@@ -946,6 +947,8 @@
"expo-camera": ["expo-camera@56.0.8", "", { "dependencies": { "barcode-detector": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-UDOpUUMisFRmCv1XQV1MJCKGAH2CsIC1Rs6P9Bbc6JLVmbxEKAd5dK68y6cScOdWURxVfJ0PRcjYnSuc8ayyIQ=="],
"expo-clipboard": ["expo-clipboard@56.0.4", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-qb4DYlkiowHYHaUYVT2FN9nk/nI1xShXOUYsI7J9dVpQCOHcGFjCBPX1VAvEW4Ye4/Aagd6IuhOVAq/+scBOiA=="],
"expo-constants": ["expo-constants@56.0.18", "", { "dependencies": { "@expo/env": "~2.3.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-8AMtbDGl/WVPnWlmbpGmvcdnNCy9E4PFnwdVwj600vljkMDPSxcAcjw8GVXEPk3PpZ+ngTqsrkltWyj0UKYAxw=="],
"expo-crypto": ["expo-crypto@56.0.4", "", { "peerDependencies": { "expo": "*" } }, "sha512-fRNEhoXRXgAWBpe3/hq5X+KXTit3OZqdiAGts1YvNEUHQb+H5591mpPac0Yw+sZg9pXcrjRnzo5AxvZaENpc7g=="],
@@ -1270,7 +1273,7 @@
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"lint-staged": ["lint-staged@17.0.7", "", { "dependencies": { "listr2": "^10.2.1", "picomatch": "^4.0.4", "string-argv": "^0.3.2", "tinyexec": "^1.2.4" }, "optionalDependencies": { "yaml": "^2.9.0" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-JrSobt+tW3rH8IOMi8tDZd3foorM5yPEkLD/V2NxobgHrFfHWGee4MOLVuZeScgxftEwbHrPHIFA/ZL+nUJeuA=="],
"lint-staged": ["lint-staged@17.0.5", "", { "dependencies": { "listr2": "^10.2.1", "picomatch": "^4.0.4", "string-argv": "^0.3.2", "tinyexec": "^1.1.2" }, "optionalDependencies": { "yaml": "^2.8.4" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-d12yC+/e8RhBjZtaxZn71FyrgU/P5e+uAPifhCLwdosQZP/zamSdKRWDC30ocVIbzDKiFG1McHc/LUgB92GIPw=="],
"listr2": ["listr2@10.2.1", "", { "dependencies": { "cli-truncate": "^5.2.0", "eventemitter3": "^5.0.4", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^10.0.0" } }, "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q=="],

View File

@@ -1,4 +1,5 @@
import { useMemo, useState } from "react";
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
import { useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Platform, TouchableOpacity, View } from "react-native";
import { Text } from "./common/Text";
@@ -61,6 +62,7 @@ export const BitrateSheet: React.FC<Props> = ({
const isTv = Platform.isTV;
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const sheetModalRef = useRef<BottomSheetModal | null>(null);
const sorted = useMemo(() => {
if (inverted)
@@ -92,7 +94,10 @@ export const BitrateSheet: React.FC<Props> = ({
</Text>
<TouchableOpacity
className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between'
onPress={() => setOpen(true)}
onPress={() => {
setOpen(true);
sheetModalRef.current?.present();
}}
>
<Text numberOfLines={1}>
{BITRATES.find((b) => b.value === selected?.value)?.key}
@@ -103,6 +108,7 @@ export const BitrateSheet: React.FC<Props> = ({
<FilterSheet
open={open}
setOpen={setOpen}
modalRef={sheetModalRef}
title={t("item_card.quality")}
data={sorted}
values={selected ? [selected] : []}

View File

@@ -1,8 +1,9 @@
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
import type {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client/models";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Platform, TouchableOpacity, View } from "react-native";
import { Text } from "./common/Text";
@@ -23,6 +24,7 @@ export const MediaSourceSheet: React.FC<Props> = ({
const isTv = Platform.isTV;
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const sheetModalRef = useRef<BottomSheetModal | null>(null);
const getDisplayName = useCallback((source: MediaSourceInfo) => {
const videoStream = source.MediaStreams?.find((x) => x.Type === "Video");
@@ -44,7 +46,10 @@ export const MediaSourceSheet: React.FC<Props> = ({
<Text className='opacity-50 mb-1 text-xs'>{t("item_card.video")}</Text>
<TouchableOpacity
className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center'
onPress={() => setOpen(true)}
onPress={() => {
setOpen(true);
sheetModalRef.current?.present();
}}
>
<Text numberOfLines={1}>{selectedName}</Text>
</TouchableOpacity>
@@ -53,6 +58,7 @@ export const MediaSourceSheet: React.FC<Props> = ({
<FilterSheet
open={open}
setOpen={setOpen}
modalRef={sheetModalRef}
title={t("item_card.video")}
data={item.MediaSources || []}
values={selected ? [selected] : []}

View File

@@ -0,0 +1,45 @@
import { useAtom, useAtomValue } from "jotai";
import type React from "react";
import { useEffect } from "react";
import { Platform } from "react-native";
import { SaveAccountModal } from "@/components/SaveAccountModal";
import {
pendingAccountSaveAtom,
useJellyfin,
userAtom,
} from "@/providers/JellyfinProvider";
/**
* Post-login save-account prompt. Login flows (password or Quick Connect)
* only flag the intent via pendingAccountSaveAtom; the protection picker
* shows here, AFTER the session is authorized — the login screen itself
* unmounts as soon as the user is set, so it can't host the modal.
*/
export const PendingAccountSaveModal: React.FC = () => {
const [pending, setPending] = useAtom(pendingAccountSaveAtom);
const user = useAtomValue(userAtom);
const { saveCurrentAccount } = useJellyfin();
// A logout before answering drops the intent — it must not resurface on
// the next (possibly different) login.
useEffect(() => {
if (!user && pending) setPending(null);
}, [user, pending, setPending]);
if (Platform.isTV) return null;
return (
<SaveAccountModal
visible={!!pending && !!user}
username={user?.Name ?? ""}
onClose={() => setPending(null)}
onSave={(securityType, pinCode) => {
const serverName = pending?.serverName;
setPending(null);
saveCurrentAccount({ securityType, pinCode, serverName }).catch(
(error) => console.warn("Failed to save account:", error),
);
}}
/>
);
};

View File

@@ -1,6 +1,7 @@
import { Ionicons } from "@expo/vector-icons";
import { BottomSheetScrollView } from "@gorhom/bottom-sheet";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Platform, StyleSheet, TouchableOpacity, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Text } from "@/components/common/Text";
@@ -209,6 +210,7 @@ const PlatformDropdownComponent = ({
expoUIConfig,
bottomSheetConfig,
}: PlatformDropdownProps) => {
const { t } = useTranslation();
const { showModal, hideModal, isVisible } = useGlobalModal();
// Handle controlled open state for Android
@@ -380,7 +382,7 @@ const PlatformDropdownComponent = ({
return (
<TouchableOpacity onPress={handlePress} activeOpacity={0.7}>
{trigger || <Text className='text-white'>Open Menu</Text>}
{trigger || <Text className='text-white'>{t("common.open_menu")}</Text>}
</TouchableOpacity>
);
};

View File

@@ -502,8 +502,8 @@ export const PlayButton: React.FC<Props> = ({
return (
<TouchableOpacity
disabled={!item}
accessibilityLabel='Play button'
accessibilityHint='Tap to play the media'
accessibilityLabel={t("accessibility.play_button")}
accessibilityHint={t("accessibility.play_hint")}
onPress={onPress}
className={"relative flex-1"}
>

View File

@@ -2,6 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import { useAtom } from "jotai";
import { useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { TouchableOpacity, View } from "react-native";
import Animated, {
Easing,
@@ -36,6 +37,7 @@ export const PlayButton: React.FC<Props> = ({
colors,
...props
}: Props) => {
const { t } = useTranslation();
const [globalColorAtom] = useAtom(itemThemeColorAtom);
// Use colors prop if provided, otherwise fallback to global atom
@@ -168,8 +170,8 @@ export const PlayButton: React.FC<Props> = ({
return (
<TouchableOpacity
accessibilityLabel='Play button'
accessibilityHint='Tap to play the media'
accessibilityLabel={t("accessibility.play_button")}
accessibilityHint={t("accessibility.play_hint")}
onPress={onPress}
className={"relative"}
{...props}

View File

@@ -6,6 +6,7 @@ import {
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
import { useAtomValue } from "jotai";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import {
FlatList,
Modal,
@@ -31,6 +32,7 @@ export const PlayInRemoteSessionButton: React.FC<Props> = ({
const [modalVisible, setModalVisible] = useState(false);
const api = useAtomValue(apiAtom);
const { sessions, isLoading } = useAllSessions({} as useSessionsProps);
const { t } = useTranslation();
const handlePlayInSession = async (sessionId: string) => {
if (!api || !item.Id) return;
@@ -65,7 +67,9 @@ export const PlayInRemoteSessionButton: React.FC<Props> = ({
<View style={styles.centeredView}>
<View style={styles.modalView}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>Select Session</Text>
<Text style={styles.modalTitle}>
{t("home.sessions.select_session")}
</Text>
<TouchableOpacity onPress={() => setModalVisible(false)}>
<Ionicons name='close' size={24} color='white' />
</TouchableOpacity>
@@ -78,7 +82,7 @@ export const PlayInRemoteSessionButton: React.FC<Props> = ({
</View>
) : !sessions || sessions.length === 0 ? (
<Text style={styles.noSessionsText}>
No active sessions found
{t("home.sessions.no_active_sessions")}
</Text>
) : (
<FlatList
@@ -98,7 +102,7 @@ export const PlayInRemoteSessionButton: React.FC<Props> = ({
</Text>
{session.NowPlayingItem && (
<Text style={styles.nowPlaying} numberOfLines={1}>
Now playing:{" "}
{t("home.sessions.now_playing")}{" "}
{session.NowPlayingItem.SeriesName
? `${session.NowPlayingItem.SeriesName} :`
: ""}

View File

@@ -1,5 +1,6 @@
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
import type { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
import { useMemo, useState } from "react";
import { useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Platform, TouchableOpacity, View } from "react-native";
import { Text } from "./common/Text";
@@ -49,6 +50,7 @@ export const TrackSheet: React.FC<Props> = ({
return streams;
}, [streams, streamType, noneOption]);
const [open, setOpen] = useState(false);
const sheetModalRef = useRef<BottomSheetModal | null>(null);
if (isTv || (streams && streams.length === 0)) return null;
@@ -58,7 +60,10 @@ export const TrackSheet: React.FC<Props> = ({
<Text className='opacity-50 mb-1 text-xs'>{title}</Text>
<TouchableOpacity
className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between'
onPress={() => setOpen(true)}
onPress={() => {
setOpen(true);
sheetModalRef.current?.present();
}}
>
<Text numberOfLines={1}>
{selected === -1 && streamType === "Subtitle"
@@ -70,6 +75,7 @@ export const TrackSheet: React.FC<Props> = ({
<FilterSheet
open={open}
setOpen={setOpen}
modalRef={sheetModalRef}
title={title}
data={addNoneToSubtitles || []}
values={

View File

@@ -1,6 +1,6 @@
import { Ionicons } from "@expo/vector-icons";
import { Feather } from "@expo/vector-icons";
import { BlurView, type BlurViewProps } from "expo-blur";
import { Platform } from "react-native";
import { Keyboard, Platform } from "react-native";
import { Pressable, type PressableProps } from "react-native-gesture-handler";
import useRouter from "@/hooks/useAppRouter";
@@ -16,30 +16,37 @@ export const HeaderBackButton: React.FC<Props> = ({
}) => {
const router = useRouter();
// Dismiss the keyboard before navigating — otherwise it lingers over the
// previous screen (e.g. leaving the Jellyseerr login while typing).
const handleBack = () => {
Keyboard.dismiss();
router.back();
};
if (Platform.OS === "ios") {
return (
<Pressable
onPress={() => router.back()}
onPress={handleBack}
className='flex items-center justify-center w-9 h-9'
{...pressableProps}
>
<Ionicons name='arrow-back' size={24} color='white' />
<Feather name='chevron-left' size={28} color='white' />
</Pressable>
);
}
if (background === "transparent" && Platform.OS !== "android")
return (
<Pressable onPress={() => router.back()} {...pressableProps}>
<Pressable onPress={handleBack} {...pressableProps}>
<BlurView
{...props}
intensity={100}
className='overflow-hidden rounded-full p-2'
>
<Ionicons
<Feather
className='drop-shadow-2xl'
name='arrow-back'
size={24}
name='chevron-left'
size={28}
color='white'
/>
</BlurView>
@@ -48,14 +55,17 @@ export const HeaderBackButton: React.FC<Props> = ({
return (
<Pressable
onPress={() => router.back()}
className=' rounded-full p-2'
onPress={handleBack}
// Match the Settings page back button: chevron flush to the edge with a
// 16px gap before the title (the old `p-2` pushed both arrow and title
// too far right). drop-shadow keeps it readable over images.
style={{ marginRight: 16 }}
{...pressableProps}
>
<Ionicons
<Feather
className='drop-shadow-2xl'
name='arrow-back'
size={24}
name='chevron-left'
size={28}
color='white'
/>
</Pressable>

View File

@@ -1,7 +1,7 @@
import { Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { t } from "i18next";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import {
ActivityIndicator,
TouchableOpacity,
@@ -35,6 +35,7 @@ interface DownloadCardProps extends TouchableOpacityProps {
}
export const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
const { t } = useTranslation();
const { cancelDownload } = useDownload();
const router = useRouter();
const queryClient = useNetworkAwareQueryClient();
@@ -173,7 +174,9 @@ export const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
{isTranscoding && (
<View className='bg-purple-600/20 px-2 py-0.5 rounded-md mt-1 self-start'>
<Text className='text-xs text-purple-400'>Transcoding</Text>
<Text className='text-xs text-purple-400'>
{t("home.downloads.transcoding")}
</Text>
</View>
)}

View File

@@ -16,9 +16,12 @@ export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({ items }) => {
const { showActionSheetWithOptions } = useActionSheet();
const router = useRouter();
// Keyed on SeriesId so recycled FlashList cells re-read the correct poster
// instead of freezing the first-rendered series' image (empty deps bug).
const base64Image = useMemo(() => {
return storage.getString(items[0].SeriesId!);
}, []);
const seriesId = items[0]?.SeriesId;
return seriesId ? storage.getString(seriesId) : undefined;
}, [items[0]?.SeriesId]);
const deleteSeries = useCallback(
async () =>

View File

@@ -1,6 +1,7 @@
import { FontAwesome, Ionicons } from "@expo/vector-icons";
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { useRef, useState } from "react";
import { TouchableOpacity, View, type ViewProps } from "react-native";
import { Text } from "@/components/common/Text";
import { FilterSheet } from "./FilterSheet";
@@ -34,8 +35,9 @@ export const FilterButton = <T,>({
...props
}: FilterButtonProps<T>) => {
const [open, setOpen] = useState(false);
const sheetModalRef = useRef<BottomSheetModal | null>(null);
const { data: filters } = useQuery<T[]>({
const { data: filters, isLoading } = useQuery<T[]>({
queryKey: ["filters", title, queryKey, id],
queryFn,
staleTime: 0,
@@ -44,9 +46,15 @@ export const FilterButton = <T,>({
return (
<>
{/* present() must be called here, inside the press handler: calling it
from an effect after a state update silently no-ops on the new
architecture and the sheet never appears. Opening immediately also
replaces the old data-loaded gate that left the button silently
dead while options were still loading (the sheet shows a loader). */}
<TouchableOpacity
onPress={() => {
filters?.length && setOpen(true);
setOpen(true);
sheetModalRef.current?.present();
}}
>
<View
@@ -89,6 +97,8 @@ export const FilterButton = <T,>({
title={title}
open={open}
setOpen={setOpen}
modalRef={sheetModalRef}
loading={isLoading}
data={filters}
values={values}
set={set}

View File

@@ -7,7 +7,14 @@ import {
} from "@gorhom/bottom-sheet";
import { isEqual } from "lodash";
import type React from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
useCallback,
useDeferredValue,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import {
StyleSheet,
@@ -19,11 +26,21 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Text } from "@/components/common/Text";
import { Button } from "../Button";
import { Input } from "../common/Input";
import { Loader } from "../Loader";
interface Props<T> extends ViewProps {
open: boolean;
setOpen: (open: boolean) => void;
/**
* Modal ref the opener must use to present() the sheet from inside its
* press handler. On the new architecture with Reanimated 4, present()
* called from an effect after a state update silently no-ops — the sheet
* mounts nothing. Presenting straight from the gesture handler works.
*/
modalRef: React.RefObject<BottomSheetModal | null>;
data?: T[] | null;
/** True while the options are loading — shows a loader inside the sheet. */
loading?: boolean;
values: T[];
set: (value: T[]) => void;
title: string;
@@ -66,16 +83,18 @@ const LIMIT = 100;
export const FilterSheet = <T,>({
values,
data: _data,
loading = false,
open,
set,
setOpen,
modalRef,
title,
searchFilter,
renderItemLabel,
disableSearch = false,
multiple = false,
}: Props<T>) => {
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const bottomSheetModalRef = modalRef;
const snapPoints = useMemo(() => ["85%"], []);
const { t } = useTranslation();
const insets = useSafeAreaInsets();
@@ -84,19 +103,24 @@ export const FilterSheet = <T,>({
const [offset, setOffset] = useState<number>(0);
const [search, setSearch] = useState<string>("");
// Filtering and re-rendering the option list on every keystroke blocks the
// JS thread on large lists (2000+ tags); the controlled input then snaps the
// native text back to a stale value (lost/reappearing letters). Deferring the
// value keeps the keystroke render cheap and runs the list update after.
const deferredSearch = useDeferredValue(search);
const [showSearch, setShowSearch] = useState<boolean>(false);
const filteredData = useMemo(() => {
if (!search) return _data;
if (!deferredSearch) return _data;
const results = [];
for (let i = 0; i < (_data?.length || 0); i++) {
if (_data && searchFilter?.(_data[i], search)) {
if (_data && searchFilter?.(_data[i], deferredSearch)) {
results.push(_data[i]);
}
}
return results.slice(0, 100);
}, [search, _data, searchFilter]);
}, [deferredSearch, _data, searchFilter]);
useEffect(() => {
if (!data || data.length === 0 || disableSearch) return;
@@ -127,21 +151,28 @@ export const FilterSheet = <T,>({
setData(newData);
}, [offset, _data]);
// Opening is imperative (see the modalRef prop); this effect only closes.
// It also never calls dismiss() on a modal that was never presented.
const wasPresentedRef = useRef(false);
useEffect(() => {
if (open) bottomSheetModalRef.current?.present();
else bottomSheetModalRef.current?.dismiss();
if (!open && wasPresentedRef.current) {
bottomSheetModalRef.current?.dismiss();
}
}, [open]);
const handleSheetChanges = useCallback((index: number) => {
if (index === -1) {
if (index >= 0) {
wasPresentedRef.current = true;
} else if (index === -1) {
wasPresentedRef.current = false;
setOpen(false);
}
}, []);
const renderData = useMemo(() => {
if (search.length > 0 && showSearch) return filteredData;
if (deferredSearch.length > 0 && showSearch) return filteredData;
return data;
}, [search, filteredData, data]);
}, [deferredSearch, showSearch, filteredData, data]);
const renderBackdrop = useCallback(
(props: BottomSheetBackdropProps) => (
@@ -154,6 +185,54 @@ export const FilterSheet = <T,>({
[],
);
// Memoized so typing in the search input (urgent render with an unchanged
// deferred value) doesn't rebuild up to 100 row elements per keystroke.
const renderedRows = useMemo(
() =>
renderData?.map((item, index) => (
<View key={index}>
<TouchableOpacity
onPress={() => {
// Match the deep-equality rule used to render the selected
// state below — option objects are recreated across renders,
// so reference checks would re-add an already selected item.
const isSelected = values.some((value) => isEqual(value, item));
if (multiple) {
if (!isSelected) set(values.concat(item));
else set(values.filter((value) => !isEqual(value, item)));
setTimeout(() => {
setOpen(false);
}, 250);
} else {
if (!isSelected) {
set([item]);
setTimeout(() => {
setOpen(false);
}, 250);
}
}
}}
className=' bg-neutral-800 px-4 py-3 flex flex-row items-center justify-between'
>
<Text className='flex shrink'>{renderItemLabel(item)}</Text>
{values.some((i) => isEqual(i, item)) ? (
<Ionicons name='radio-button-on' size={24} color='white' />
) : (
<Ionicons name='radio-button-off' size={24} color='white' />
)}
</TouchableOpacity>
<View
style={{
height: StyleSheet.hairlineWidth,
}}
className='h-1 divide-neutral-700 '
/>
</View>
)),
[renderData, values, multiple, set, setOpen, renderItemLabel],
);
return (
<BottomSheetModal
ref={bottomSheetModalRef}
@@ -182,9 +261,15 @@ export const FilterSheet = <T,>({
}}
>
<Text className='font-bold text-2xl'>{title}</Text>
<Text className='mb-2 text-neutral-500'>
{t("search.x_items", { count: _data?.length })}
</Text>
{loading ? (
<View className='my-8 flex items-center justify-center'>
<Loader />
</View>
) : (
<Text className='mb-2 text-neutral-500'>
{t("search.x_items", { count: _data?.length })}
</Text>
)}
{showSearch && (
<Input
placeholder={t("search.search")}
@@ -203,43 +288,7 @@ export const FilterSheet = <T,>({
}}
className='mb-4 flex flex-col rounded-xl overflow-hidden'
>
{renderData?.map((item, index) => (
<View key={index}>
<TouchableOpacity
onPress={() => {
if (multiple) {
if (!values.includes(item)) set(values.concat(item));
else set(values.filter((v) => v !== item));
setTimeout(() => {
setOpen(false);
}, 250);
} else {
if (!values.includes(item)) {
set([item]);
setTimeout(() => {
setOpen(false);
}, 250);
}
}
}}
className=' bg-neutral-800 px-4 py-3 flex flex-row items-center justify-between'
>
<Text className='flex shrink'>{renderItemLabel(item)}</Text>
{values.some((i) => isEqual(i, item)) ? (
<Ionicons name='radio-button-on' size={24} color='white' />
) : (
<Ionicons name='radio-button-off' size={24} color='white' />
)}
</TouchableOpacity>
<View
style={{
height: StyleSheet.hairlineWidth,
}}
className='h-1 divide-neutral-700 '
/>
</View>
))}
{renderedRows}
</View>
{data.length < (_data?.length || 0) && (
<Button

View File

@@ -1,5 +1,6 @@
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import React from "react";
import { useTranslation } from "react-i18next";
import { Animated, Pressable, StyleSheet, View } from "react-native";
import { Text } from "@/components/common/Text";
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
@@ -22,6 +23,7 @@ export const TVGuideProgramCell: React.FC<TVGuideProgramCellProps> = ({
disabled = false,
refSetter,
}) => {
const { t } = useTranslation();
const typography = useScaledTVTypography();
const { focused, handleFocus, handleBlur } = useTVFocusAnimation({
scaleAmount: 1,
@@ -68,7 +70,7 @@ export const TVGuideProgramCell: React.FC<TVGuideProgramCellProps> = ({
<Text
style={[styles.liveBadgeText, { fontSize: typography.callout }]}
>
LIVE
{t("player.live")}
</Text>
</View>
)}

View File

@@ -235,7 +235,7 @@ export const TVLiveTVPage: React.FC = () => {
marginBottom: 24,
}}
>
Live TV
{t("live_tv.title")}
</Text>
{/* Tab Bar */}

View File

@@ -3,7 +3,7 @@ import type { PublicSystemInfo } from "@jellyfin/sdk/lib/generated-client";
import { Image } from "expo-image";
import { useLocalSearchParams, useNavigation } from "expo-router";
import { t } from "i18next";
import { useAtomValue } from "jotai";
import { useAtomValue, useSetAtom } from "jotai";
import { useCallback, useEffect, useState } from "react";
import {
Alert,
@@ -20,14 +20,16 @@ import { Button } from "@/components/Button";
import { Input } from "@/components/common/Input";
import { Text } from "@/components/common/Text";
import JellyfinServerDiscovery from "@/components/JellyfinServerDiscovery";
import { QuickConnectCodeModal } from "@/components/login/QuickConnectCodeModal";
import { PreviousServersList } from "@/components/PreviousServersList";
import { SaveAccountModal } from "@/components/SaveAccountModal";
import { Colors } from "@/constants/Colors";
import { apiAtom, useJellyfin } from "@/providers/JellyfinProvider";
import type {
AccountSecurityType,
SavedServer,
} from "@/utils/secureCredentials";
import {
apiAtom,
pendingAccountSaveAtom,
useJellyfin,
userAtom,
} from "@/providers/JellyfinProvider";
import type { SavedServer } from "@/utils/secureCredentials";
const CredentialsSchema = z.object({
username: z.string().min(1, t("login.username_required")),
@@ -37,14 +39,17 @@ export const Login: React.FC = () => {
const api = useAtomValue(apiAtom);
const navigation = useNavigation();
const params = useLocalSearchParams();
const user = useAtomValue(userAtom);
const {
setServer,
login,
removeServer,
initiateQuickConnect,
stopQuickConnectPolling,
loginWithSavedCredential,
loginWithPassword,
} = useJellyfin();
const setPendingAccountSave = useSetAtom(pendingAccountSaveAtom);
const {
apiUrl: _apiUrl,
@@ -64,13 +69,43 @@ export const Login: React.FC = () => {
password: _password || "",
});
// Save account state
// Quick Connect code shown in the in-app sheet while polling for authorization
const [quickConnectCode, setQuickConnectCode] = useState<string | null>(null);
// Close the code sheet as soon as the session is authorized — the native
// Alert used before had no programmatic dismiss and stayed open after login.
// A Quick Connect login with "save account" on flags the post-login save:
// the protection picker shows globally once the session exists (this screen
// unmounts on login, so it can't host the modal).
useEffect(() => {
if (user) {
if (quickConnectCode && saveAccount) {
setPendingAccountSave({ serverName });
}
setQuickConnectCode(null);
}
}, [user]);
// Stop Quick Connect polling when leaving the login page (parity with TVLogin)
useEffect(() => {
return () => {
stopQuickConnectPolling();
};
}, [stopQuickConnectPolling]);
// Going back to server selection keeps this component mounted (same screen,
// different state), so the unmount cleanup above doesn't run. Without this a
// code authorized after leaving would silently log the user in later.
useEffect(() => {
if (!api?.basePath) {
stopQuickConnectPolling();
setQuickConnectCode(null);
}
}, [api?.basePath, stopQuickConnectPolling]);
// Save account state — only the intent lives here; the protection picker is
// the global PendingAccountSaveModal, shown after the login succeeds.
const [saveAccount, setSaveAccount] = useState(false);
const [showSaveModal, setShowSaveModal] = useState(false);
const [pendingLogin, setPendingLogin] = useState<{
username: string;
password: string;
} | null>(null);
// Handle URL params for server connection
useEffect(() => {
@@ -117,55 +152,34 @@ export const Login: React.FC = () => {
const result = CredentialsSchema.safeParse(credentials);
if (!result.success) return;
if (saveAccount) {
setPendingLogin({
username: credentials.username,
password: credentials.password,
});
setShowSaveModal(true);
} else {
await performLogin(credentials.username, credentials.password);
const ok = await performLogin(credentials.username, credentials.password);
// The protection picker shows AFTER a successful login (global modal) —
// never for a failed one.
if (ok && saveAccount) {
setPendingAccountSave({ serverName });
}
};
const performLogin = async (
username: string,
password: string,
options?: {
saveAccount?: boolean;
securityType?: AccountSecurityType;
pinCode?: string;
},
) => {
): Promise<boolean> => {
setLoading(true);
try {
await login(username, password, serverName, options);
await login(username, password, serverName);
return true;
} catch (error) {
if (error instanceof Error) {
Alert.alert(t("login.connection_failed"), error.message);
} else {
Alert.alert(
t("login.connection_failed"),
t("login.an_unexpected_error_occured"),
t("login.an_unexpected_error_occurred"),
);
}
return false;
} finally {
setLoading(false);
setPendingLogin(null);
}
};
const handleSaveAccountConfirm = async (
securityType: AccountSecurityType,
pinCode?: string,
) => {
setShowSaveModal(false);
if (pendingLogin) {
await performLogin(pendingLogin.username, pendingLogin.password, {
saveAccount: true,
securityType,
pinCode,
});
}
};
@@ -259,15 +273,7 @@ export const Login: React.FC = () => {
try {
const code = await initiateQuickConnect();
if (code) {
Alert.alert(
t("login.quick_connect"),
t("login.enter_code_to_login", { code: code }),
[
{
text: t("login.got_it"),
},
],
);
setQuickConnectCode(code);
}
} catch (_error) {
Alert.alert(
@@ -402,7 +408,7 @@ export const Login: React.FC = () => {
{t("server.enter_url_to_jellyfin_server")}
</Text>
<Input
aria-label='Server URL'
aria-label={t("server.server_url")}
placeholder={t("server.server_url_placeholder")}
onChangeText={setServerURL}
value={serverURL}
@@ -444,14 +450,11 @@ export const Login: React.FC = () => {
)}
</KeyboardAvoidingView>
<SaveAccountModal
visible={showSaveModal}
onClose={() => {
setShowSaveModal(false);
setPendingLogin(null);
}}
onSave={handleSaveAccountConfirm}
username={pendingLogin?.username || credentials.username}
{/* Dismissing only hides the code — polling continues so the login still
completes if the code is authorized from another device afterwards. */}
<QuickConnectCodeModal
code={quickConnectCode}
onClose={() => setQuickConnectCode(null)}
/>
</SafeAreaView>
);

View File

@@ -0,0 +1,137 @@
import { Ionicons } from "@expo/vector-icons";
import {
BottomSheetBackdrop,
type BottomSheetBackdropProps,
BottomSheetModal,
BottomSheetView,
} from "@gorhom/bottom-sheet";
import { requireOptionalNativeModule } from "expo-modules-core";
import type React from "react";
import { useCallback, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { TouchableOpacity, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { toast } from "sonner-native";
import { Button } from "../Button";
import { Text } from "../common/Text";
interface Props {
/** The Quick Connect code to display, or null when hidden. */
code: string | null;
onClose: () => void;
}
/**
* Shows the Quick Connect code while the app polls for authorization.
* In-app sheet instead of a native Alert so it can dismiss itself once the
* session is authorized — a native alert has no programmatic dismiss and
* lingers over the app after login completes.
*/
export const QuickConnectCodeModal: React.FC<Props> = ({ code, onClose }) => {
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const snapPoints = useMemo(() => ["50%"], []);
const isPresentedRef = useRef(false);
// Keep the last code around so the dismiss animation doesn't flash empty
// when the parent clears the code to close the sheet.
const lastCodeRef = useRef<string | null>(null);
if (code) lastCodeRef.current = code;
useEffect(() => {
if (code) {
bottomSheetModalRef.current?.present();
} else if (isPresentedRef.current) {
bottomSheetModalRef.current?.dismiss();
isPresentedRef.current = false;
}
}, [code]);
const handleSheetChanges = useCallback(
(index: number) => {
if (index >= 0) {
isPresentedRef.current = true;
} else if (index === -1 && isPresentedRef.current) {
isPresentedRef.current = false;
onClose();
}
},
[onClose],
);
const renderBackdrop = useCallback(
(props: BottomSheetBackdropProps) => (
<BottomSheetBackdrop
{...props}
disappearsOnIndex={-1}
appearsOnIndex={0}
/>
),
[],
);
const copyCode = useCallback(async () => {
const value = code ?? lastCodeRef.current;
if (!value) return;
// Builds that don't ship the expo-clipboard native module yet: probe with
// requireOptionalNativeModule (returns null instead of throwing/logging)
// and skip — importing the JS wrapper there would error out.
if (!requireOptionalNativeModule("ExpoClipboard")) return;
const Clipboard = await import("expo-clipboard");
await Clipboard.setStringAsync(value);
toast.success(t("login.code_copied"));
}, [code, t]);
return (
<BottomSheetModal
ref={bottomSheetModalRef}
snapPoints={snapPoints}
onChange={handleSheetChanges}
handleIndicatorStyle={{ backgroundColor: "white" }}
backgroundStyle={{ backgroundColor: "#171717" }}
backdropComponent={renderBackdrop}
>
<BottomSheetView
style={{
flex: 1,
paddingLeft: Math.max(16, insets.left),
paddingRight: Math.max(16, insets.right),
paddingBottom: Math.max(16, insets.bottom),
}}
>
<View className='flex-1'>
<Text className='font-bold text-2xl text-neutral-100'>
{t("login.quick_connect")}
</Text>
<TouchableOpacity
className='mt-6 p-6 border border-neutral-800 rounded-xl bg-neutral-900 flex flex-row items-center justify-center'
onPress={copyCode}
>
<Text
className='text-center font-bold text-5xl text-neutral-100'
style={{ letterSpacing: 10 }}
>
{code ?? lastCodeRef.current}
</Text>
<Ionicons
name='copy-outline'
size={22}
color='white'
style={{ opacity: 0.4, marginLeft: 16 }}
/>
</TouchableOpacity>
<Text className='mt-2 text-neutral-500 text-center text-xs'>
{t("login.tap_code_to_copy")}
</Text>
<Text className='mt-3 mb-5 text-neutral-400 text-center px-4'>
{t("login.quick_connect_instructions")}
</Text>
<Button className='mt-auto' color='purple' onPress={onClose}>
{t("login.got_it")}
</Button>
</View>
</BottomSheetView>
</BottomSheetModal>
);
};

View File

@@ -437,7 +437,7 @@ export const TVLogin: React.FC = () => {
} else {
Alert.alert(
t("login.connection_failed"),
t("login.an_unexpected_error_occured"),
t("login.an_unexpected_error_occurred"),
);
}
} finally {
@@ -499,7 +499,7 @@ export const TVLogin: React.FC = () => {
const message =
error instanceof Error
? error.message
: t("login.an_unexpected_error_occured");
: t("login.an_unexpected_error_occurred");
Alert.alert(t("login.connection_failed"), message);
goToQRScreen();
} finally {
@@ -523,7 +523,7 @@ export const TVLogin: React.FC = () => {
} else {
Alert.alert(
t("login.connection_failed"),
t("login.an_unexpected_error_occured"),
t("login.an_unexpected_error_occurred"),
);
}
} finally {
@@ -768,7 +768,7 @@ export const TVLogin: React.FC = () => {
const message =
error instanceof Error
? error.message
: t("login.an_unexpected_error_occured");
: t("login.an_unexpected_error_occurred");
Alert.alert(t("login.connection_failed"), message);
goToQRScreen();
});

View File

@@ -1,4 +1,5 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Animated, Pressable, View } from "react-native";
import { Text } from "@/components/common/Text";
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
@@ -88,6 +89,8 @@ export const TVSearchTabBadges: React.FC<TVSearchTabBadgesProps> = ({
showDiscover,
disabled = false,
}) => {
const { t } = useTranslation();
if (!showDiscover) {
return null;
}
@@ -101,13 +104,13 @@ export const TVSearchTabBadges: React.FC<TVSearchTabBadgesProps> = ({
}}
>
<TVSearchTabBadge
label='Library'
label={t("search.library")}
isSelected={searchType === "Library"}
onPress={() => setSearchType("Library")}
disabled={disabled}
/>
<TVSearchTabBadge
label='Discover'
label={t("search.discover")}
isSelected={searchType === "Discover"}
onPress={() => setSearchType("Discover")}
disabled={disabled}

View File

@@ -20,7 +20,10 @@ export const JellyseerrSettings = () => {
const { t } = useTranslation();
const [user] = useAtom(userAtom);
const { settings, updateSettings } = useSettings();
const { settings, updateSettings, pluginSettings } = useSettings();
// Only the server URL is admin-lockable — the password stays editable so
// the user can still sign in to the admin-pinned Jellyseerr server.
const urlLocked = pluginSettings?.jellyseerrServerUrl?.locked === true;
const [jellyseerrPassword, setJellyseerrPassword] = useState<
string | undefined
@@ -115,30 +118,41 @@ export const JellyseerrSettings = () => {
</>
) : (
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
<Text className='font-bold mb-1'>
{t("home.settings.plugins.jellyseerr.server_url")}
</Text>
<View className='flex flex-col shrink mb-2'>
<Text className='text-xs text-gray-600'>
{t("home.settings.plugins.jellyseerr.server_url_hint")}
<View style={{ opacity: urlLocked ? 0.5 : 1 }}>
<Text className='font-bold mb-1'>
{t("home.settings.plugins.jellyseerr.server_url")}
</Text>
</View>
<Input
className='border border-neutral-800 mb-2'
placeholder={t(
"home.settings.plugins.jellyseerr.server_url_placeholder",
<View className='flex flex-col shrink mb-2'>
<Text className='text-xs text-gray-600'>
{t("home.settings.plugins.jellyseerr.server_url_hint")}
</Text>
</View>
<Input
className='border border-neutral-800 mb-2'
placeholder={t(
"home.settings.plugins.jellyseerr.server_url_placeholder",
)}
value={
urlLocked
? settings?.jellyseerrServerUrl
: (jellyseerrServerUrl ?? settings?.jellyseerrServerUrl)
}
defaultValue={
settings?.jellyseerrServerUrl ?? jellyseerrServerUrl
}
keyboardType='url'
returnKeyType='done'
autoCapitalize='none'
textContentType='URL'
onChangeText={setjellyseerrServerUrl}
editable={!urlLocked && !loginToJellyseerrMutation.isPending}
/>
{urlLocked && (
<Text className='text-xs text-red-600 mb-2'>
Disabled by admin
</Text>
)}
value={jellyseerrServerUrl ?? settings?.jellyseerrServerUrl}
defaultValue={
settings?.jellyseerrServerUrl ?? jellyseerrServerUrl
}
keyboardType='url'
returnKeyType='done'
autoCapitalize='none'
textContentType='URL'
onChangeText={setjellyseerrServerUrl}
editable={!loginToJellyseerrMutation.isPending}
/>
</View>
<View>
<Text className='font-bold mb-2'>
{t("home.settings.plugins.jellyseerr.password")}

View File

@@ -1,33 +1,28 @@
import { useTranslation } from "react-i18next";
import { Switch, Text, View } from "react-native";
import { Switch } from "react-native";
import { useSettings } from "@/utils/atoms/settings";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
export const KefinTweaksSettings = () => {
const { settings, updateSettings } = useSettings();
const { settings, updateSettings, pluginSettings } = useSettings();
const { t } = useTranslation();
const isEnabled = settings?.useKefinTweaks ?? false;
const locked = pluginSettings?.useKefinTweaks?.locked === true;
return (
<View className=''>
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
<Text className='text-xs text-red-600 mb-2'>
{t("home.settings.plugins.kefinTweaks.watchlist_enabler")}
</Text>
<View className='flex flex-row items-center justify-between mt-2'>
<Text className='text-white'>
{isEnabled ? t("Watchlist On") : t("Watchlist Off")}
</Text>
<Switch
value={isEnabled}
onValueChange={(value) => updateSettings({ useKefinTweaks: value })}
trackColor={{ false: "#555", true: "purple" }}
thumbColor={isEnabled ? "#fff" : "#ccc"}
/>
</View>
</View>
</View>
<ListGroup>
<ListItem
title={t("home.settings.plugins.kefinTweaks.watchlist_enabler")}
disabledByAdmin={locked}
>
<Switch
value={isEnabled}
disabled={locked}
onValueChange={(value) => updateSettings({ useKefinTweaks: value })}
/>
</ListItem>
</ListGroup>
);
};

View File

@@ -1,5 +1,6 @@
import { Ionicons } from "@expo/vector-icons";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Platform, Switch, View, type ViewProps } from "react-native";
import { Stepper } from "@/components/inputs/Stepper";
import { Text } from "../common/Text";
@@ -17,20 +18,21 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
const isTv = Platform.isTV;
const media = useMedia();
const { settings, updateSettings } = media;
const { t } = useTranslation();
const alignXOptions: AlignX[] = ["left", "center", "right"];
const alignYOptions: AlignY[] = ["top", "center", "bottom"];
const alignXLabels: Record<AlignX, string> = {
left: "Left",
center: "Center",
right: "Right",
left: t("home.settings.subtitles.align.left"),
center: t("home.settings.subtitles.align.center"),
right: t("home.settings.subtitles.align.right"),
};
const alignYLabels: Record<AlignY, string> = {
top: "Top",
center: "Center",
bottom: "Bottom",
top: t("home.settings.subtitles.align.top"),
center: t("home.settings.subtitles.align.center"),
bottom: t("home.settings.subtitles.align.bottom"),
};
const alignXOptionGroups = useMemo(() => {
@@ -60,16 +62,18 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
return (
<View {...props}>
<ListGroup
title='MPV Subtitle Settings'
title={t("home.settings.subtitles.mpv_settings_title")}
description={
<Text className='text-[#8E8D91] text-xs'>
Advanced subtitle customization for MPV player
{t("home.settings.subtitles.mpv_settings_description")}
</Text>
}
>
{!isTv && (
<>
<ListItem title='Vertical Margin'>
<ListItem
title={t("home.settings.subtitles.mpv_subtitle_margin_y")}
>
<Stepper
value={settings.mpvSubtitleMarginY ?? 0}
step={5}
@@ -81,7 +85,7 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
/>
</ListItem>
<ListItem title='Horizontal Alignment'>
<ListItem title={t("home.settings.subtitles.mpv_subtitle_align_x")}>
<PlatformDropdown
groups={alignXOptionGroups}
trigger={
@@ -96,11 +100,11 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
/>
</View>
}
title='Horizontal Alignment'
title={t("home.settings.subtitles.mpv_subtitle_align_x")}
/>
</ListItem>
<ListItem title='Vertical Alignment'>
<ListItem title={t("home.settings.subtitles.mpv_subtitle_align_y")}>
<PlatformDropdown
groups={alignYOptionGroups}
trigger={
@@ -115,13 +119,13 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
/>
</View>
}
title='Vertical Alignment'
title={t("home.settings.subtitles.mpv_subtitle_align_y")}
/>
</ListItem>
</>
)}
<ListItem title='Opaque Background'>
<ListItem title={t("home.settings.subtitles.opaque_background")}>
<Switch
value={settings.mpvSubtitleBackgroundEnabled ?? false}
onValueChange={(value) =>
@@ -131,7 +135,7 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
</ListItem>
{settings.mpvSubtitleBackgroundEnabled && (
<ListItem title='Background Opacity'>
<ListItem title={t("home.settings.subtitles.background_opacity")}>
<Stepper
value={settings.mpvSubtitleBackgroundOpacity ?? 75}
step={5}

View File

@@ -20,12 +20,7 @@ export const PluginSettings = () => {
>
<ListItem
onPress={() => router.push("/settings/plugins/jellyseerr/page")}
title={"Jellyseerr"}
showArrow
/>
<ListItem
onPress={() => router.push("/settings/plugins/marlin-search/page")}
title='Marlin Search'
title='Jellyseerr'
showArrow
/>
<ListItem
@@ -33,6 +28,11 @@ export const PluginSettings = () => {
title='Streamystats'
showArrow
/>
<ListItem
onPress={() => router.push("/settings/plugins/marlin-search/page")}
title='Marlin Search'
showArrow
/>
<ListItem
onPress={() => router.push("/settings/plugins/kefinTweaks/page")}
title='KefinTweaks'

View File

@@ -1,3 +1,4 @@
import { Feather } from "@expo/vector-icons";
import {
BottomSheetBackdrop,
type BottomSheetBackdropProps,
@@ -5,11 +6,13 @@ import {
BottomSheetView,
} from "@gorhom/bottom-sheet";
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
import { requireOptionalNativeModule } from "expo-modules-core";
import { useAtom } from "jotai";
import type React from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Alert, Platform, View, type ViewProps } from "react-native";
import { Pressable } from "react-native-gesture-handler";
import { useHaptic } from "@/hooks/useHaptic";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { Button } from "../Button";
@@ -58,7 +61,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
successHapticFeedback();
Alert.alert(
t("home.settings.quick_connect.success"),
t("home.settings.quick_connect.quick_connect_autorized"),
t("home.settings.quick_connect.quick_connect_authorized"),
);
setQuickConnectCode(undefined);
bottomSheetModalRef?.current?.close();
@@ -79,6 +82,15 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
}
}, [api, user, quickConnectCode]);
const pasteCode = useCallback(async () => {
// Builds without the expo-clipboard native module: probe first (no-op).
if (!requireOptionalNativeModule("ExpoClipboard")) return;
const Clipboard = await import("expo-clipboard");
const text = await Clipboard.getStringAsync();
const digits = (text || "").replace(/\D/g, "").slice(0, 6);
if (digits) setQuickConnectCode(digits);
}, []);
if (isTv) return null;
return (
@@ -130,6 +142,15 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
style={{ paddingHorizontal: 16 }}
autoFocus
/>
<Pressable
onPress={pasteCode}
className='flex-row items-center justify-center self-center'
>
<Feather name='clipboard' size={15} color='#a3a3a3' />
<Text className='text-neutral-400 ml-2'>
{t("home.settings.quick_connect.paste_code")}
</Text>
</Pressable>
</View>
</View>
<Button

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { Platform, View } from "react-native";
import { Alert, Platform, View } from "react-native";
import { toast } from "sonner-native";
import { Text } from "@/components/common/Text";
import { Colors } from "@/constants/Colors";
@@ -12,6 +12,7 @@ import { ListItem } from "../list/ListItem";
export const StorageSettings = () => {
const { deleteAllFiles, appSizeUsage } = useDownload();
const { t } = useTranslation();
const queryClient = useQueryClient();
const successHapticFeedback = useHaptic("success");
const errorHapticFeedback = useHaptic("error");
@@ -27,16 +28,38 @@ export const StorageSettings = () => {
used: (app.total - app.remaining) / app.total,
};
},
// Keep the bar moving while a download is writing to disk.
refetchInterval: 10 * 1000,
});
const onDeleteClicked = async () => {
try {
await deleteAllFiles();
successHapticFeedback();
} catch (_e) {
errorHapticFeedback();
toast.error(t("home.settings.toasts.error_deleting_files"));
}
const onDeleteClicked = () => {
Alert.alert(
t("home.settings.storage.delete_all_downloaded_files_confirm"),
t("home.settings.storage.delete_all_downloaded_files_confirm_desc"),
[
{
text: t("common.cancel"),
style: "cancel",
},
{
text: t("common.ok"),
style: "destructive",
onPress: async () => {
try {
await deleteAllFiles();
successHapticFeedback();
} catch (_e) {
errorHapticFeedback();
toast.error(t("home.settings.toasts.error_deleting_files"));
} finally {
// Reflect the freed space immediately instead of waiting for
// the next poll.
queryClient.invalidateQueries({ queryKey: ["appSize"] });
}
},
},
],
);
};
const calculatePercentage = (value: number, total: number) => {

View File

@@ -3,6 +3,7 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { Image } from "expo-image";
import { useAtomValue } from "jotai";
import React, { useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
Animated,
Easing,
@@ -106,6 +107,7 @@ export const TVPosterCard: React.FC<TVPosterCardProps> = ({
scaleAmount = 1.05,
imageUrlGetter,
}) => {
const { t } = useTranslation();
const api = useAtomValue(apiAtom);
const posterSizes = useScaledTVPosterSizes();
const typography = useScaledTVTypography();
@@ -371,7 +373,7 @@ export const TVPosterCard: React.FC<TVPosterCardProps> = ({
fontWeight: "700",
}}
>
Now Playing
{t("music.now_playing")}
</Text>
</View>
) : null;

View File

@@ -1,5 +1,6 @@
import { Ionicons } from "@expo/vector-icons";
import React from "react";
import { useTranslation } from "react-i18next";
import {
ActivityIndicator,
Animated,
@@ -28,6 +29,7 @@ export const TVSubtitleResultCard = React.forwardRef<
const styles = createStyles(typography);
const { focused, handleFocus, handleBlur, animatedStyle } =
useTVFocusAnimation({ scaleAmount: 1.03 });
const { t } = useTranslation();
return (
<Pressable
@@ -152,7 +154,7 @@ export const TVSubtitleResultCard = React.forwardRef<
},
]}
>
<Text style={styles.flagText}>Hash Match</Text>
<Text style={styles.flagText}>{t("player.hash_match")}</Text>
</View>
)}
{result.hearingImpaired && (

View File

@@ -183,7 +183,7 @@ export const BottomControls: FC<BottomControlsProps> = ({
<SkipButton
showButton={showSkipButton}
onPress={skipIntro}
buttonText='Skip Intro'
buttonText={t("player.skip_intro")}
/>
{/* Smart Skip Credits behavior:
- Show "Skip Credits" if there's content after credits OR no next episode
@@ -193,7 +193,7 @@ export const BottomControls: FC<BottomControlsProps> = ({
showSkipCreditButton && (hasContentAfterCredits || !nextItem)
}
onPress={skipCredit}
buttonText='Skip Credits'
buttonText={t("player.skip_credits")}
/>
{settings.autoPlayNextEpisode !== false &&
(settings.maxAutoPlayEpisodeCount.value === -1 ||

View File

@@ -27,7 +27,7 @@ const ContinueWatchingOverlay: React.FC<ContinueWatchingOverlayProps> = ({
}
>
<Text className='text-2xl font-bold text-white py-4 '>
Are you still watching ?
{t("player.still_watching")}
</Text>
<Button
onPress={() => {

View File

@@ -4,6 +4,7 @@ import type {
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client";
import { type FC, useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { Platform, TouchableOpacity, View } from "react-native";
import useRouter from "@/hooks/useAppRouter";
import { useControlsSafeAreaInsets } from "@/hooks/useControlsSafeAreaInsets";
@@ -57,6 +58,7 @@ export const HeaderControls: FC<HeaderControlsProps> = ({
showTechnicalInfo = false,
onToggleTechnicalInfo,
}) => {
const { t } = useTranslation();
const router = useRouter();
const insets = useControlsSafeAreaInsets();
const lightHapticFeedback = useHaptic("light");
@@ -127,8 +129,8 @@ export const HeaderControls: FC<HeaderControlsProps> = ({
onPress={toggleOrientation}
disabled={isTogglingOrientation}
className='aspect-square flex flex-col rounded-xl items-center justify-center p-2'
accessibilityLabel='Toggle screen orientation'
accessibilityHint='Toggles the screen orientation between portrait and landscape'
accessibilityLabel={t("accessibility.toggle_orientation")}
accessibilityHint={t("accessibility.toggle_orientation_hint")}
>
<MaterialIcons
name='screen-rotation'

View File

@@ -7,6 +7,7 @@ import {
useMemo,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import { Platform, StyleSheet, Text, View } from "react-native";
import Animated, {
Easing,
@@ -184,6 +185,7 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
currentAudioIndex,
}) => {
const typography = useScaledTVTypography();
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const safeInsets = useControlsSafeAreaInsets();
const [info, setInfo] = useState<TechnicalInfo | null>(null);
@@ -312,13 +314,13 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
)}
{info?.videoCodec && (
<Text style={textStyle}>
Video: {formatCodec(info.videoCodec)}
{t("player.technical_info.video")} {formatCodec(info.videoCodec)}
{info.fps ? ` @ ${formatFps(info.fps)} fps` : ""}
</Text>
)}
{info?.audioCodec && (
<Text style={textStyle}>
Audio: {formatCodec(info.audioCodec)}
{t("player.technical_info.audio")} {formatCodec(info.audioCodec)}
{streamInfo?.audioChannels
? ` ${formatAudioChannels(streamInfo.audioChannels)}`
: ""}
@@ -326,12 +328,13 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
)}
{streamInfo?.subtitleCodec && (
<Text style={textStyle}>
Subtitle: {formatCodec(streamInfo.subtitleCodec)}
{t("player.technical_info.subtitle")}{" "}
{formatCodec(streamInfo.subtitleCodec)}
</Text>
)}
{(info?.videoBitrate || info?.audioBitrate) && (
<Text style={textStyle}>
Bitrate:{" "}
{t("player.technical_info.bitrate")}{" "}
{info.videoBitrate
? formatBitrate(info.videoBitrate)
: info.audioBitrate
@@ -341,21 +344,27 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
)}
{info?.cacheSeconds !== undefined && (
<Text style={textStyle}>
Buffer: {info.cacheSeconds.toFixed(1)}s
{t("player.technical_info.buffer_seconds", {
seconds: info.cacheSeconds.toFixed(1),
})}
</Text>
)}
{info?.voDriver && (
<Text style={textStyle}>
VO: {info.voDriver}
{t("player.technical_info.vo")} {info.voDriver}
{info.hwdec ? ` / ${info.hwdec}` : ""}
</Text>
)}
{info?.droppedFrames !== undefined && info.droppedFrames > 0 && (
<Text style={[textStyle, styles.warningText]}>
Dropped: {info.droppedFrames} frames
{t("player.technical_info.dropped_frames", {
count: info.droppedFrames,
})}
</Text>
)}
{!info && !playMethod && <Text style={textStyle}>Loading...</Text>}
{!info && !playMethod && (
<Text style={textStyle}>{t("player.technical_info.loading")}</Text>
)}
</View>
</Animated.View>
);

View File

@@ -1,5 +1,6 @@
import { Ionicons } from "@expo/vector-icons";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Platform, View } from "react-native";
import {
type OptionGroup,
@@ -54,6 +55,7 @@ export const AspectRatioSelector: React.FC<AspectRatioSelectorProps> = ({
onRatioChange,
disabled = false,
}) => {
const { t } = useTranslation();
const lightHapticFeedback = useHaptic("light");
const handleRatioSelect = (ratio: AspectRatio) => {
@@ -66,7 +68,10 @@ export const AspectRatioSelector: React.FC<AspectRatioSelectorProps> = ({
{
options: ASPECT_RATIO_OPTIONS.map((option) => ({
type: "radio" as const,
label: option.label,
label:
option.id === "default"
? t("player.aspect_ratio_original")
: option.label,
value: option.id,
selected: option.id === currentRatio,
onPress: () => handleRatioSelect(option.id),
@@ -94,7 +99,7 @@ export const AspectRatioSelector: React.FC<AspectRatioSelectorProps> = ({
return (
<PlatformDropdown
title='Aspect Ratio'
title={t("player.aspect_ratio")}
groups={optionGroups}
trigger={trigger}
bottomSheetConfig={{

View File

@@ -1,6 +1,7 @@
import { Ionicons } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { useCallback, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { Platform, View } from "react-native";
import { BITRATES } from "@/components/BitrateSelector";
import {
@@ -47,6 +48,7 @@ const DropdownView = ({
const { settings, updateSettings } = useSettings();
const router = useRouter();
const isOffline = useOfflineMode();
const { t } = useTranslation();
const { subtitleIndex, audioIndex, bitrateValue, playbackPosition } =
useLocalSearchParams<{
@@ -101,7 +103,7 @@ const DropdownView = ({
// Quality Section
if (!isOffline) {
groups.push({
title: "Quality",
title: t("player.menu.quality"),
options:
BITRATES?.map((bitrate) => ({
type: "radio" as const,
@@ -116,7 +118,7 @@ const DropdownView = ({
// Subtitle Section
if (subtitleTracks && subtitleTracks.length > 0) {
groups.push({
title: "Subtitles",
title: t("player.menu.subtitles"),
options: subtitleTracks.map((sub) => ({
type: "radio" as const,
label: sub.name,
@@ -128,7 +130,7 @@ const DropdownView = ({
// Subtitle Scale Section
groups.push({
title: "Subtitle Scale",
title: t("player.menu.subtitle_scale"),
options: SUBTITLE_SCALE_PRESETS.map((preset) => ({
type: "radio" as const,
label: preset.label,
@@ -142,7 +144,7 @@ const DropdownView = ({
// Audio Section
if (audioTracks && audioTracks.length > 0) {
groups.push({
title: "Audio",
title: t("player.menu.audio"),
options: audioTracks.map((track) => ({
type: "radio" as const,
label: track.name,
@@ -156,7 +158,7 @@ const DropdownView = ({
// Speed Section
if (setPlaybackSpeed) {
groups.push({
title: "Speed",
title: t("player.menu.speed"),
options: PLAYBACK_SPEEDS.map((speed) => ({
type: "radio" as const,
label: speed.label,
@@ -174,8 +176,8 @@ const DropdownView = ({
{
type: "action" as const,
label: showTechnicalInfo
? "Hide Technical Info"
: "Show Technical Info",
? t("player.menu.hide_technical_info")
: t("player.menu.show_technical_info"),
onPress: onToggleTechnicalInfo,
},
],
@@ -185,6 +187,7 @@ const DropdownView = ({
return groups;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
t,
isOffline,
bitrateValue,
changeBitrate,
@@ -217,7 +220,7 @@ const DropdownView = ({
return (
<PlatformDropdown
title='Playback Options'
title={t("player.menu.playback_options")}
groups={optionGroups}
trigger={trigger}
expoUIConfig={{}}

View File

@@ -3,6 +3,7 @@ import { Alert } from "react-native";
import { type SharedValue, useSharedValue } from "react-native-reanimated";
import { useTVBackPress } from "@/hooks/useTVBackPress";
import { useTVEventHandler } from "@/hooks/useTVEventHandler";
import i18n from "@/i18n";
interface UseRemoteControlProps {
showControls: boolean;
@@ -124,17 +125,23 @@ export function useRemoteControl({
// Controls are hidden, so confirm before leaving playback.
Alert.alert(
"Stop Playback",
i18n.t("player.stopPlayback"),
videoTitleRef.current
? `Stop playing "${videoTitleRef.current}"?`
: "Are you sure you want to stop playback?",
? i18n.t("player.stopPlayingTitle", {
title: videoTitleRef.current,
})
: i18n.t("player.stopPlayingConfirm"),
[
{
text: "Cancel",
text: i18n.t("common.cancel"),
style: "cancel",
onPress: () => onCancelExitRef.current?.(),
},
{ text: "Stop", style: "destructive", onPress: onBackRef.current },
{
text: i18n.t("common.stop"),
style: "destructive",
onPress: onBackRef.current,
},
],
);
return true;

View File

@@ -1,13 +1,19 @@
// Imported from expo-router's bundled copy, NOT "@react-navigation/*": as of
// SDK 56 expo-router's Metro check rejects direct @react-navigation imports.
import { useRouter } from "expo-router";
import { useCallback, useMemo } from "react";
import { NavigationContext } from "expo-router/react-navigation";
import { useCallback, useContext, useMemo } from "react";
import { useOfflineMode } from "@/providers/OfflineModeProvider";
/**
* Drop-in replacement for expo-router's useRouter that automatically
* preserves offline state across navigation.
* preserves offline state across navigation and guards against duplicate
* screens from rapid taps.
*
* - For object-form navigation, automatically adds offline=true when in offline context
* - For string URLs, passes through unchanged (caller handles offline param)
* - push() is a no-op while the source screen is not focused, so taps fired
* before the pushed screen has rendered (slow devices) can't stack duplicates
*
* @example
* import useRouter from "@/hooks/useAppRouter";
@@ -19,8 +25,18 @@ export function useAppRouter() {
const router = useRouter();
const isOffline = useOfflineMode();
// Optional: undefined when used outside a navigator (root layout, providers).
// When present it reflects the focus state of the screen this hook lives in.
const navigation = useContext(NavigationContext);
const push = useCallback(
(href: Parameters<typeof router.push>[0]) => {
// Rapid-push guard: a push blurs the source screen synchronously in the
// navigation state (only the native render is slow). Any further push from
// this screen — duplicate or not — is dropped until focus returns, so taps
// fired before the pushed screen renders can't stack screens.
// No navigation context => nothing to guard (deep-link pushes from root).
if (navigation?.isFocused?.() === false) return;
if (typeof href === "string") {
router.push(href as any);
} else {
@@ -36,7 +52,7 @@ export function useAppRouter() {
} as any);
}
},
[router, isOffline],
[router, isOffline, navigation],
);
const replace = useCallback(

View File

@@ -143,7 +143,7 @@ export class JellyseerrApi {
if (inRange(status, 200, 299)) {
if (data.version < "2.0.0") {
const error = t(
"jellyseerr.toasts.jellyseer_does_not_meet_requirements",
"jellyseerr.toasts.jellyseerr_does_not_meet_requirements",
);
toast.error(error);
throw Error(error);

View File

@@ -2,8 +2,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
<service
android:name=".DownloadService"

View File

@@ -9,7 +9,6 @@ import android.content.pm.ServiceInfo
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.os.SystemClock
import android.util.Log
import androidx.core.app.NotificationCompat
@@ -28,7 +27,6 @@ class DownloadService : Service() {
private var currentDownloadTitle = "Preparing download..."
private var currentProgress = 0
private var isForegroundStarted = false
private var wakeLock: PowerManager.WakeLock? = null
inner class DownloadServiceBinder : Binder() {
fun getService(): DownloadService = this@DownloadService
@@ -38,12 +36,6 @@ class DownloadService : Service() {
super.onCreate()
Log.d(TAG, "DownloadService created")
createNotificationChannel()
val pm = getSystemService(PowerManager::class.java)
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Streamyfin::DownloadWakeLock")
wakeLock?.acquire()
Log.d(TAG, "Wake lock acquired")
}
override fun onBind(intent: Intent?): IBinder {
@@ -101,8 +93,6 @@ class DownloadService : Service() {
}
override fun onDestroy() {
wakeLock?.let { if (it.isHeld) it.release() }
Log.d(TAG, "Wake lock released")
Log.d(TAG, "DownloadService destroyed")
super.onDestroy()
}

View File

@@ -54,6 +54,7 @@
"expo-brightness": "~56.0.5",
"expo-build-properties": "~56.0.18",
"expo-camera": "~56.0.8",
"expo-clipboard": "~56.0.4",
"expo-constants": "~56.0.18",
"expo-crypto": "~56.0.4",
"expo-dev-client": "~56.0.20",
@@ -134,7 +135,7 @@
"cross-env": "10.1.0",
"expo-doctor": "1.19.9",
"husky": "9.1.7",
"lint-staged": "17.0.7",
"lint-staged": "17.0.5",
"react-test-renderer": "19.2.3",
"typescript": "6.0.3"
},

View File

@@ -96,5 +96,24 @@ export function getDownloadedItemSize(id: string): number {
*/
export function calculateTotalDownloadedSize(): number {
const items = getAllDownloadedItems();
return items.reduce((sum, item) => sum + (item.videoFileSize || 0), 0);
return items.reduce((sum, item) => {
// Trickplay bytes count too — getDownloadedItemSize models per-item size
// as video + trickplay, the total must match.
const trickplaySize = item.trickPlayData?.size ?? 0;
// Read the live file size on disk so the total reflects actual usage and
// self-heals items whose stored videoFileSize is 0 (old schema, or
// `fileInfo.size` was undefined at download time). Fall back to the stored
// value if the file can't be stat'd.
if (item.videoFilePath) {
try {
const file = new File(filePathToUri(item.videoFilePath));
if (file.exists) {
return sum + (file.size ?? item.videoFileSize ?? 0) + trickplaySize;
}
} catch (error) {
console.warn("Failed to stat downloaded file for size:", error);
}
}
return sum + (item.videoFileSize ?? 0) + trickplaySize;
}, 0);
}

View File

@@ -289,7 +289,24 @@ export function useDownloadOperations({
);
const appSizeUsage = useCallback(async () => {
const totalSize = calculateTotalDownloadedSize();
let totalSize = calculateTotalDownloadedSize();
// Also count in-progress downloads (they write straight to their final
// path) so the growing file shows up as app usage instead of drifting
// into the generic device share until completion.
for (const process of processes) {
try {
const file = new File(
Paths.document,
`${generateFilename(process.item)}.mp4`,
);
if (file.exists) {
totalSize += file.size ?? 0;
}
} catch {
// File not created yet — ignore.
}
}
try {
const [freeDiskStorage, totalDiskCapacity] = await Promise.all([
@@ -310,7 +327,7 @@ export function useDownloadOperations({
appSize: totalSize,
};
}
}, []);
}, [processes]);
return {
startBackgroundDownload,

View File

@@ -15,6 +15,7 @@ import {
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useTranslation } from "react-i18next";
@@ -91,6 +92,12 @@ export const apiAtom = atom<Api | null>(initialApi);
export const userAtom = atom<UserDto | null>(initialUser);
export const wsAtom = atom<WebSocket | null>(null);
export const cacheVersionAtom = atom<number>(0);
// Set by a login flow that wants the account saved: the protection picker
// shows AFTER the session is authorized (the login screen unmounts on
// success, so the modal lives at the root — see PendingAccountSaveModal).
export const pendingAccountSaveAtom = atom<{ serverName?: string } | null>(
null,
);
interface LoginOptions {
saveAccount?: boolean;
@@ -108,6 +115,11 @@ interface JellyfinContextValue {
serverName?: string,
options?: LoginOptions,
) => Promise<void>;
saveCurrentAccount: (options?: {
securityType?: AccountSecurityType;
pinCode?: string;
serverName?: string;
}) => Promise<void>;
logout: () => Promise<void>;
initiateQuickConnect: () => Promise<string | undefined>;
stopQuickConnectPolling: () => void;
@@ -165,6 +177,46 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
const { clearAllJellyseerData, setJellyseerrUser } = useJellyseerr();
const queryClient = useQueryClient();
// --- Session-expiry handling ----------------------------------------------
// When the server revokes the token (e.g. the device/session is deleted), a
// 401 can surface from any authenticated request. Without central handling
// the dead token stays in storage, so every reload re-fires authed calls →
// 401 spam + uncaught rejections, and the app lingers in a half-authenticated
// state. A single response interceptor on the authenticated api clears the
// session on the first 401 so the app drops cleanly to the login screen.
const sessionExpiredRef = useRef(false);
const handleSessionExpired = useCallback(() => {
if (sessionExpiredRef.current) return; // run once per session
sessionExpiredRef.current = true;
storage.remove("token");
storage.remove("user");
setUser(null);
setApi(null);
queryClient.clear();
storage.remove("REACT_QUERY_OFFLINE_CACHE");
// Saved credentials are kept so the user can quick-login again.
}, [setUser, setApi, queryClient]);
useEffect(() => {
// Only guard an authenticated session. A pre-auth api (login screen) keeps
// its own handling — a wrong-password 401 is not a session expiry.
if (!api?.accessToken) return;
sessionExpiredRef.current = false; // re-arm for this fresh session
const interceptorId = api.axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error?.response?.status === 401) {
handleSessionExpired();
}
return Promise.reject(error);
},
);
return () => {
api.axiosInstance.interceptors.response.eject(interceptorId);
};
}, [api, handleSessionExpired]);
const headers = useMemo(() => {
if (!deviceId) return {};
return {
@@ -307,6 +359,37 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
},
});
// Persist the CURRENT session to secure storage — used by the post-login
// save-account modal (the protection picker shows AFTER a successful
// login, for both the password and Quick Connect flows).
const saveCurrentAccount = useCallback(
async (options?: {
securityType?: AccountSecurityType;
pinCode?: string;
serverName?: string;
}) => {
const token = storage.getString("token");
if (!api?.basePath || !user?.Id || !user.Name || !token) return;
const securityType = options?.securityType || "none";
let pinHash: string | undefined;
if (securityType === "pin" && options?.pinCode) {
pinHash = await hashPIN(options.pinCode);
}
await saveAccountCredential({
serverUrl: api.basePath,
serverName: options?.serverName || "",
token,
userId: user.Id,
username: user.Name,
savedAt: Date.now(),
securityType,
pinHash,
primaryImageTag: user.PrimaryImageTag ?? undefined,
});
},
[api?.basePath, user],
);
const loginMutation = useMutation({
mutationFn: async ({
username,
@@ -386,7 +469,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
default:
throw new Error(
t(
"login.an_unexpected_error_occured_did_you_enter_the_correct_url",
"login.an_unexpected_error_occurred_did_you_enter_the_correct_url",
),
);
}
@@ -509,7 +592,9 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
}
},
onError: (error) => {
console.error("Quick login failed:", error);
// Expected, handled case (e.g. revoked token → "Session Expired", or
// server unreachable): the UI surfaces the message, so warn, don't error.
console.warn("Quick login failed:", error);
},
});
@@ -620,54 +705,62 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
setUser(storedUser);
}
// Dismiss splash screen with cached data immediately,
// fetch fresh user data in the background
setInitialLoaded(true);
// Validate the token and refresh user data in the background. Do NOT
// await this: the Jellyfin SDK axios instance has no timeout, so when
// offline this call hangs for the full OS TCP timeout (75-120s) and
// blocks splash dismissal. The cached storedUser (set above) is enough
// to render; on success we just refresh it.
getUserApi(apiInstance)
.getCurrentUser()
.then(async (response) => {
setUser(response.data);
try {
const response = await getUserApi(apiInstance).getCurrentUser();
setUser(response.data);
// Migrate current session to secure storage if not already saved
if (storedUser?.Id && storedUser?.Name) {
const existingCredential = await getAccountCredential(
serverUrl,
storedUser.Id,
);
if (!existingCredential) {
await saveAccountCredential({
// Migrate current session to secure storage if not already saved
if (storedUser?.Id && storedUser?.Name) {
const existingCredential = await getAccountCredential(
serverUrl,
serverName: "",
token,
userId: storedUser.Id,
username: storedUser.Name,
savedAt: Date.now(),
securityType: "none",
primaryImageTag: response.data.PrimaryImageTag ?? undefined,
});
} else if (
response.data.PrimaryImageTag !==
existingCredential.primaryImageTag
) {
// Update image tag if it has changed
addAccountToServer(serverUrl, existingCredential.serverName, {
userId: existingCredential.userId,
username: existingCredential.username,
securityType: existingCredential.securityType,
savedAt: existingCredential.savedAt,
primaryImageTag: response.data.PrimaryImageTag ?? undefined,
});
storedUser.Id,
);
if (!existingCredential) {
await saveAccountCredential({
serverUrl,
serverName: "",
token,
userId: storedUser.Id,
username: storedUser.Name,
savedAt: Date.now(),
securityType: "none",
primaryImageTag: response.data.PrimaryImageTag ?? undefined,
});
} else if (
response.data.PrimaryImageTag !==
existingCredential.primaryImageTag
) {
// Update image tag if it has changed
addAccountToServer(serverUrl, existingCredential.serverName, {
userId: existingCredential.userId,
username: existingCredential.username,
securityType: existingCredential.securityType,
savedAt: existingCredential.savedAt,
primaryImageTag: response.data.PrimaryImageTag ?? undefined,
});
}
}
}
} catch (e) {
// Background fetch failed — app already rendered with cached data
console.warn("Background user fetch failed, using cached data:", e);
}
} else {
setInitialLoaded(true);
})
.catch((e) => {
// Expected, handled case (offline, or a token the server rejects —
// the UI prompts re-login): warn, don't error. Log only
// status/message — never the raw error (axios errors carry the
// request config incl. the Authorization header / token).
console.warn(
"Background user validation failed:",
e?.response?.status ?? e?.message ?? "unknown error",
);
});
}
} catch (e) {
console.error(e);
} finally {
setInitialLoaded(true);
}
};
@@ -681,6 +774,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
removeServer: () => removeServerMutation.mutateAsync(),
login: (username, password, serverName, options) =>
loginMutation.mutateAsync({ username, password, serverName, options }),
saveCurrentAccount,
logout: () => logoutMutation.mutateAsync(),
initiateQuickConnect,
stopQuickConnectPolling,

View File

@@ -65,7 +65,7 @@ final class TopShelfProvider: TVTopShelfContentProvider {
let item = TVTopShelfSectionedItem(identifier: cacheItem.id)
item.title = cacheItem.title
item.imageShape = .hdtv
item.imageShape = .poster
item.displayAction = TVTopShelfAction(url: route)
if let playRoute = cacheItem.playRoute, let playURL = URL(string: playRoute) {

View File

@@ -261,6 +261,43 @@
"None": "لا شيء",
"OnlyForced": "فقط الإجبارية"
},
"text_color": "لون النص",
"background_color": "لون الخلفية",
"outline_color": "لون إطار الخط",
"outline_thickness": "سمك إطار الخط",
"background_opacity": "شفافية الخلفية",
"outline_opacity": "شفافية إطار الخط",
"bold_text": "خط عريض",
"colors": {
"Black": "أسود",
"Gray": "رمادي",
"Silver": "فضي",
"White": "أبيض",
"Maroon": "أحمر داكن",
"Red": "أحمر",
"Fuchsia": "وردي",
"Yellow": "أصفر",
"Olive": "‫أخضر زيتوني‬‎",
"Green": "أخضر",
"Teal": "أزرق مخضر",
"Lime": "ليموني",
"Purple": "بنفسجي",
"Navy": "كحلي",
"Blue": "أزرق",
"Aqua": "أزرق بحري"
},
"thickness": {
"None": "لا شيء",
"Thin": "نحيف",
"Normal": "عادي",
"Thick": "سميك"
},
"subtitle_color": "لون الترجمة",
"subtitle_background_color": "لون الخلفية",
"subtitle_font": "خط الترجمة",
"ksplayer_title": "إعدادات KSPlayer",
"hardware_decode": "فك الترميز بواسطة الجهاز",
"hardware_decode_description": "استخدم تسريع العتاد لفك ترميز الفيديو. قم بتعطيله إذا واجهت مشكلات في التشغيل.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "إعدادات ترجمة VLC",
"hint": "تخصيص مظهر الترجمة لمشغل VLC. تصبح التغييرات سارية المفعول عند التشغيل التالي.",
"text_color": "لون النص",
"background_color": "لون الخلفية",
"background_opacity": "شفافية الخلفية",
"outline_color": "لون إطار الخط",
"outline_opacity": "شفافية إطار الخط",
"outline_thickness": "سمك إطار الخط",
"bold": "خط عريض",
"margin": "الهامش السفلي"
},
"video_player": {
"title": "مشغل الفيديو",
"video_player": "مشغل الفيديو",
"video_player_description": "اختر مشغل الفيديو الذي سيتم استخدامه على نظام iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "أخرى",
"video_orientation": "اتجاه الفيديو",
@@ -295,6 +351,11 @@
"UNKNOWN": "غير معروف"
},
"safe_area_in_controls": "المنطقة الآمنة لعناصر التحكم",
"video_player": "مشغل الفيديو",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (تجريبي + صورة داخل صورة)"
},
"show_custom_menu_links": "إظهار روابط القائمة المخصصة",
"show_large_home_carousel": "إظهار شريط العرض الكبير (تجريبي)",
"hide_libraries": "إخفاء المكتبات",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "الحد الأقصى لعدد الحلقات التي يتم تشغيلها تلقائيًا",
"disabled": "معطل"
},
"downloads": {
"downloads_title": "التنزيلات"
},
"music": {
"title": "الموسيقى",
"playback_title": "التشغيل",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "الإضافات",
"jellyseerr": {
"jellyseerr_warning": "هذا الربط في مراحله الأولى. توقع حدوث تغييرات.",
"server_url": "رابط الخادم",
"server_url_hint": "مثال: http(s)://your-host.url\n(أضف المنفذ إذا لزم الأمر)",
"server_url_placeholder": "رابط Seerr...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "اقرأ المزيد عن مارلن.",
"save_button": "حفظ",
"toasts": {
"saved": "تم الحفظ"
}
"saved": "تم الحفظ",
"refreshed": "تم تحديث الإعدادات من الخادم"
},
"refresh_from_server": "تحديث الإعدادات من الخادم"
},
"streamystats": {
"enable_streamystats": "تفعيل Streamystats",
"disable_streamystats": "تعطيل Streamystats",
"enable_search": "استخدم للبحث",
"url": "الرابط",
"server_url_placeholder": "http(s)://streamystats.example.com",
"streamystats_search_hint": "أدخل رابط خادم Streamystats الخاص بك. يجب أن يتضمن الرابط البروتوكول http أو https مع رقم المنفذ اختيارياً.",
"read_more_about_streamystats": "اقرأ المزيد عن Streamystats.",
"save_button": "حفظ",
"save": "حفظ",
"features_title": "المميزات",
"home_sections_title": "أقسام الرئيسية",
"enable_movie_recommendations": "توصيات الأفلام",
"enable_series_recommendations": "توصيات المسلسلات",
"enable_promoted_watchlists": "قوائم مشاهدة مختارة",
@@ -375,7 +445,8 @@
"refresh_from_server": "تحديث الإعدادات من الخادم"
},
"kefinTweaks": {
"watchlist_enabler": "تفعيل الربط مع قائمة المشاهدة الخاصة بنا"
"watchlist_enabler": "تفعيل الربط مع قائمة المشاهدة الخاصة بنا",
"watchlist_button": "تبديل حالة ربط قائمة المشاهدة"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "حذف جميع الملفات التي تم تنزيلها",
"music_cache_title": "التخزين المؤقت للموسيقى",
"music_cache_description": "تخزين الأغاني تلقائياً أثناء الاستماع لضمان تشغيل أكثر سلاسة ودعم الاستماع بدون اتصال",
"enable_music_cache": "تمكين التخزين المؤقت للموسيقى",
"clear_music_cache": "مسح التخزين المؤقت للموسيقى",
"music_cache_size": "تم تخزين {{size}} مؤقتاً",
"music_cache_cleared": "تم مسح التخزين المؤقت للموسيقى",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "النظام"
},
"toasts": {
"error_deleting_files": "خطأ في حذف الملفات"
"error_deleting_files": "خطأ في حذف الملفات",
"background_downloads_enabled": "تم تفعيل التنزيلات في الخلفية",
"background_downloads_disabled": "تم تعطيل التنزيلات في الخلفية"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "التنزيلات",
"series": "مسلسلات",
"movies": "أفلام",
"queue": "قائمة الانتظار",
"other_media": "وسائط أخرى",
"queue_hint": "ستفقد قائمة الانتظار والتنزيلات عند إعادة تشغيل التطبيق",
"no_items_in_queue": "لا توجد عناصر في قائمة الانتظار",
"no_downloaded_items": "لا توجد عناصر تم تنزيلها",
"delete_all_movies_button": "حذف جميع الأفلام",
"delete_all_series_button": "حذف جميع المسلسلات",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "فشل حذف جميع المسلسلات",
"deleted_media_successfully": "تم حذف الوسائط الأخرى بنجاح!",
"failed_to_delete_media": "فشل حذف الوسائط الأخرى",
"download_deleted": "تم حذف التنزيل",
"download_cancelled": "تم إلغاء التنزيل",
"could_not_delete_download": "تعذر حذف التنزيل",
"download_paused": "تم إيقاف التنزيل مؤقتًا",
"could_not_pause_download": "تعذر إيقاف التنزيل مؤقتًا",
"download_resumed": "تم استئناف التنزيل",
"could_not_resume_download": "تعذر استئناف التنزيل",
"download_completed": "اكتمل التنزيل",
"download_failed": "فشل التنزيل",
"download_failed_for_item": "فشل تنزيل {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} قيد التنزيل بالفعل",
"all_files_deleted": "تم حذف جميع التنزيلات بنجاح",
"files_deleted_by_type": "تم حذف {{count}} {{type}}",
"all_files_folders_and_jobs_deleted_successfully": "تم حذف جميع الملفات والمجلدات والمهام بنجاح",
"failed_to_clean_cache_directory": "فشل تنظيف مجلد ذاكرة التخزين المؤقت",
"could_not_get_download_url_for_item": "تعذر الحصول على عنوان URL للتنزيل لـ{{itemName}}",
"go_to_downloads": "الذهاب إلى التنزيلات",
"file_deleted": "تم حذف {{item}}"
}
}
@@ -495,17 +583,16 @@
"none": "لا شيء",
"track": "أغنية",
"cancel": "إلغاء",
"stop": "Stop",
"delete": "حذف",
"ok": "حسناً",
"remove": "إزالة",
"next": "التالي",
"back": "رجوع",
"continue": "متابعة",
"verifying": "جارٍ التحقق...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "بحث...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "تعذر إنشاء بث لـChromecast",
"message_from_server": "رسالة من الخادم: {{message}}",
"next_episode": "الحلقة التالية",
"refresh_tracks": "تحديث المسارات",
"audio_tracks": "مسارات الصوت:",
"playback_state": "حالة التشغيل:",
"index": "الفِهْرِس:",
"continue_watching": "متابعة المشاهدة",
"go_back": "رجوع",
"downloaded_file_title": "تم تنزيل هذا الملف",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "عرض المزيد",
"show_less": "عرض أقل",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "التالي",
@@ -798,9 +888,13 @@
"playlists": "قوائم التشغيل",
"tracks": "الأغاني"
},
"filters": {
"all": "الكل"
},
"recently_added": "أضيف مؤخرًا",
"recently_played": "تم تشغيله مؤخرًا",
"frequently_played": "الأكثر تشغيلاً",
"explore": "اكتشف",
"top_tracks": "أفضل الأغاني",
"play": "تشغيل",
"shuffle": "ترتيب عشوائي",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Cap",
"OnlyForced": "Només els forçats"
},
"text_color": "Text Color",
"background_color": "Background Color",
"outline_color": "Outline Color",
"outline_thickness": "Outline Thickness",
"background_opacity": "Background Opacity",
"outline_opacity": "Outline Opacity",
"bold_text": "Bold Text",
"colors": {
"Black": "Black",
"Gray": "Gray",
"Silver": "Silver",
"White": "White",
"Maroon": "Maroon",
"Red": "Red",
"Fuchsia": "Fuchsia",
"Yellow": "Yellow",
"Olive": "Olive",
"Green": "Green",
"Teal": "Teal",
"Lime": "Lime",
"Purple": "Purple",
"Navy": "Navy",
"Blue": "Blue",
"Aqua": "Aqua"
},
"thickness": {
"None": "Cap",
"Thin": "Thin",
"Normal": "Normal",
"Thick": "Thick"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Altres",
"video_orientation": "Orientació del vídeo",
@@ -295,6 +351,11 @@
"UNKNOWN": "Desconeguda"
},
"safe_area_in_controls": "Àrea segura als controls",
"video_player": "Reproductor de vídeo",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "Mostrar enllaços del menú personalitzats",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "Oculta biblioteques",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Nombre màxim d'episodis de reproducció automàtica",
"disabled": "Desactivat"
},
"downloads": {
"downloads_title": "Descàrregues"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Connectors",
"jellyseerr": {
"jellyseerr_warning": "Aquesta integració es troba en una versió primerenca. Espereu que les coses canviïn.",
"server_url": "URL del servidor",
"server_url_hint": "Exemple: http(s)://el-vostre-domini.url\n(afegiu el port si és necessari)",
"server_url_placeholder": "URL de Jellyseerr...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Mostra més sobre Marlin.",
"save_button": "Desa",
"toasts": {
"saved": "Desat"
}
"saved": "Desat",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Suprimeix tots els fitxers descarregats",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Sistema"
},
"toasts": {
"error_deleting_files": "Error en suprimir fitxers"
"error_deleting_files": "Error en suprimir fitxers",
"background_downloads_enabled": "Descàrregues en segon pla activades",
"background_downloads_disabled": "Descàrregues en segon pla desactivades"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Descàrregues",
"series": "Sèries",
"movies": "Pel·lícules",
"queue": "Cua",
"other_media": "Other media",
"queue_hint": "La cua i les descàrregues es perdran en reiniciar l'aplicació",
"no_items_in_queue": "No hi ha elements a la cua",
"no_downloaded_items": "No hi ha elements descarregats",
"delete_all_movies_button": "Suprimeix totes les pel·lícules",
"delete_all_series_button": "Suprimeix totes les sèries",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "No s'han pogut suprimir totes les sèries",
"deleted_media_successfully": "Deleted other media Successfully!",
"failed_to_delete_media": "Failed to Delete other media",
"download_deleted": "Download Deleted",
"download_cancelled": "Descàrrega cancel·lada",
"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": "Descàrrega completada",
"download_failed": "Download Failed",
"download_failed_for_item": "Ha fallat la descàrrega per a {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "Tots els fitxers, carpetes i treballs s'han suprimit correctament",
"failed_to_clean_cache_directory": "Failed to clean cache directory",
"could_not_get_download_url_for_item": "Could not get download URL for {{itemName}}",
"go_to_downloads": "Ves a les descàrregues",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Cerca...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "No s'ha pogut crear un flux per a Chromecast",
"message_from_server": "Missatge del servidor: {{message}}",
"next_episode": "Episodi següent",
"refresh_tracks": "Actualitzar pistes",
"audio_tracks": "Pistes d'àudio:",
"playback_state": "Estat de reproducció:",
"index": "Índex:",
"continue_watching": "Continuar veient",
"go_back": "Enrere",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Mostra més",
"show_less": "Mostra menys",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Següent",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Nic",
"OnlyForced": "Pouze vynucené"
},
"text_color": "Barva textu",
"background_color": "Barva pozadí",
"outline_color": "Barva obrysu",
"outline_thickness": "Obrys tloušťky",
"background_opacity": "Průhlednost pozadí",
"outline_opacity": "Průhlednost obrysu",
"bold_text": "Bold Text",
"colors": {
"Black": "Černý",
"Gray": "Šedá",
"Silver": "Stříbro",
"White": "Bílý",
"Maroon": "Maroon",
"Red": "Červená",
"Fuchsia": "Fuchsia",
"Yellow": "Žlutá",
"Olive": "Olivy",
"Green": "Zelená",
"Teal": "Modrozelený",
"Lime": "Světle zelená",
"Purple": "Fialová",
"Navy": "Námořní loď",
"Blue": "Modrá",
"Aqua": "Aqua"
},
"thickness": {
"None": "Nic",
"Thin": "Tenké",
"Normal": "Normální",
"Thick": "Tlustá"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Ostatní",
"video_orientation": "Orientace videa",
@@ -295,6 +351,11 @@
"UNKNOWN": "Neznámý"
},
"safe_area_in_controls": "Bezpečná oblast v ovládání",
"video_player": "Video přehrávač",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (experimentální + PiP)"
},
"show_custom_menu_links": "Zobrazit vlastní Menu odkazy",
"show_large_home_carousel": "Zobrazit velký přehled (beta)",
"hide_libraries": "Skrýt knihovny",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Maximální počet automatických přehrávání epizod",
"disabled": "Zakázáno"
},
"downloads": {
"downloads_title": "Stahování"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Pluginy",
"jellyseerr": {
"jellyseerr_warning": "Tato integrace je v raných fázích. Očekávejte změnu situace.",
"server_url": "URL serveru",
"server_url_hint": "Příklad: http(s)://your-host.url\n(přidat port, pokud je vyžadováno)",
"server_url_placeholder": "Seerr URL",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Přečtěte si více o Marlinu.",
"save_button": "Uložit",
"toasts": {
"saved": "Uloženo"
}
"saved": "Uloženo",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Odstranit všechny stažené soubory",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Systém"
},
"toasts": {
"error_deleting_files": "Chyba při mazání souborů"
"error_deleting_files": "Chyba při mazání souborů",
"background_downloads_enabled": "Stahování na pozadí povoleno",
"background_downloads_disabled": "Stahování na pozadí zakázáno"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Stahování",
"series": "Televizní série",
"movies": "Filmy",
"queue": "Fronta",
"other_media": "Ostatní média",
"queue_hint": "Fronta a stahování budou ztraceny při restartu aplikace",
"no_items_in_queue": "Žádné položky ve frontě",
"no_downloaded_items": "Žádné stažené položky",
"delete_all_movies_button": "Odstranit všechny filmy",
"delete_all_series_button": "Odstranit všechny TV-série",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Nepodařilo se odstranit všechny TV-série",
"deleted_media_successfully": "Ostatní média úspěšně smazána!",
"failed_to_delete_media": "Nepodařilo se odstranit ostatní média",
"download_deleted": "Stahování smazáno",
"download_cancelled": "Download Cancelled",
"could_not_delete_download": "Stahování nelze odstranit",
"download_paused": "Stahování pozastaveno",
"could_not_pause_download": "Nelze pozastavit stahování",
"download_resumed": "Stahování obnoveno",
"could_not_resume_download": "Nelze pokračovat v stahování",
"download_completed": "Stahování dokončeno",
"download_failed": "Download Failed",
"download_failed_for_item": "Stahování se nezdařilo pro {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "Všechny soubory, složky a úlohy byly úspěšně odstraněny",
"failed_to_clean_cache_directory": "Nepodařilo se vyčistit adresář mezipaměti",
"could_not_get_download_url_for_item": "Nelze získat URL pro stažení {{itemName}}",
"go_to_downloads": "Přejít na stahování",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Hledat...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Nelze vytvořit stream pro Chromecast",
"message_from_server": "Zpráva od serveru: {{message}}",
"next_episode": "Další epizoda",
"refresh_tracks": "Obnovit skladby",
"audio_tracks": "Zvukové stopy:",
"playback_state": "Stav přehrávání:",
"index": "Index:",
"continue_watching": "Pokračovat ve sledování",
"go_back": "Zpět",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Zobrazit více",
"show_less": "Zobrazit méně",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Další",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Ingen",
"OnlyForced": "Kun tvungne undertekster"
},
"text_color": "Tekst Farve",
"background_color": "Baggrunds Farve",
"outline_color": "Omrids Farve",
"outline_thickness": "Omrids Tykkelse",
"background_opacity": "Baggrunds Gennemsigtighed",
"outline_opacity": "Omrids Gennemsigtighed",
"bold_text": "Bold Text",
"colors": {
"Black": "Sort",
"Gray": "Grå",
"Silver": "Sølv",
"White": "Hvid",
"Maroon": "Maroon",
"Red": "Rød",
"Fuchsia": "Fuchsia",
"Yellow": "Gul",
"Olive": "Oliven",
"Green": "Grøn",
"Teal": "Grønblåt",
"Lime": "Limegrøn",
"Purple": "Lilla",
"Navy": "Flåden",
"Blue": "Blå",
"Aqua": "Aqua"
},
"thickness": {
"None": "Ingen",
"Thin": "Tynd",
"Normal": "Normal",
"Thick": "Tyk"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Andet",
"video_orientation": "Videoorientering",
@@ -295,6 +351,11 @@
"UNKNOWN": "Ukendt"
},
"safe_area_in_controls": "Sikkert område i kontroller",
"video_player": "Videospiller",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Eksperimentel + PiP)"
},
"show_custom_menu_links": "Vis tilpassede menulinks",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "Skjul biblioteker",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Maks. Auto Afspil Episode Antal",
"disabled": "Deaktiveret"
},
"downloads": {
"downloads_title": "Downloads"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Plugins",
"jellyseerr": {
"jellyseerr_warning": "Denne integration er i en tidlig fase. Forvent, at tingene ændres.",
"server_url": "Server URL",
"server_url_hint": "Eksempel: http(s)://din-host.url\n(tilføj port hvis nødvendigt)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Læs mere om Marlin.",
"save_button": "Gem",
"toasts": {
"saved": "Gemt"
}
"saved": "Gemt",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Slet alle downloadede filer",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "System"
},
"toasts": {
"error_deleting_files": "Fejl ved sletning af filer"
"error_deleting_files": "Fejl ved sletning af filer",
"background_downloads_enabled": "Baggrundsdownloads aktiveret",
"background_downloads_disabled": "Baggrundsdownloads deaktiveret"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Downloads",
"series": "TV-serier",
"movies": "Film",
"queue": "Kø",
"other_media": "Andre medier",
"queue_hint": "Kø og downloads vil gå tabt ved genstart af appen",
"no_items_in_queue": "Ingen elementer i køen",
"no_downloaded_items": "Ingen downloadede elementer",
"delete_all_movies_button": "Slet alle film",
"delete_all_series_button": "Slet alle TV-serier",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Kunne ikke slette alle TV-serier",
"deleted_media_successfully": "Slettede andre medier med succes!",
"failed_to_delete_media": "Kunne ikke slette andre medier",
"download_deleted": "Download Slettet",
"download_cancelled": "Download afbrudt",
"could_not_delete_download": "Kunne Ikke Slette Download",
"download_paused": "Download Pauset",
"could_not_pause_download": "Kunne Ikke Pause Download",
"download_resumed": "Download Genoprettet",
"could_not_resume_download": "Kunne Ikke Genoptage Download",
"download_completed": "Download fuldført",
"download_failed": "Download Failed",
"download_failed_for_item": "Download mislykkedes for {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "Alle filer, mapper og jobs blev slettet med succes",
"failed_to_clean_cache_directory": "Kunne ikke rense cache-mappe",
"could_not_get_download_url_for_item": "Kunne ikke hente download URL til {{itemName}}",
"go_to_downloads": "Gå til downloads",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Søg...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Kunne ikke oprette en stream til Chromecast",
"message_from_server": "Besked fra server: {{message}}",
"next_episode": "Næste episode",
"refresh_tracks": "Opdater spor",
"audio_tracks": "Lydspor:",
"playback_state": "Afspilningstilstand:",
"index": "Indeks:",
"continue_watching": "Fortsæt med at se",
"go_back": "Gå Tilbage",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Vis mere",
"show_less": "Vis mindre",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Næste",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -4,8 +4,8 @@
"error_title": "Fehler",
"login_title": "Anmelden",
"login_to_title": "Anmelden bei",
"select_user": "Benutzer zum Anmelden auswählen",
"add_user_to_login": "Zum Anmelden einen Benutzer hinzufügen",
"select_user": "Select a user to log in",
"add_user_to_login": "Add a user to log in",
"add_user": "Add User",
"username_placeholder": "Benutzername",
"password_placeholder": "Passwort",
@@ -47,9 +47,9 @@
"add_account": "Konto hinzufügen",
"remove_account_description": "Hiermit werden die gespeicherten Zugangsdaten für {{username}} entfernt.",
"remove_server": "Remove Server",
"remove_server_description": "Dies wird {{server}} und alle gespeicherten Konten aus Ihrer Liste entfernen.",
"remove_server_description": "This will remove {{server}} and all saved accounts from your list.",
"select_your_server": "Select Your Server",
"add_server_to_get_started": "Füge einen Server hinzu, um loszulegen",
"add_server_to_get_started": "Add a server to get started",
"add_server": "Add Server",
"change_server": "Change Server"
},
@@ -95,7 +95,7 @@
"oops": "Ups!",
"error_message": "Etwas ist schiefgelaufen.\nBitte melde dich ab und wieder an.",
"continue_watching": "Weiterschauen",
"continue": "Weiter",
"continue": "Continue",
"next_up": "Als nächstes",
"continue_and_next_up": "\"Weiterschauen\" und \"Als Nächstes\"",
"recently_added_in": "Kürzlich hinzugefügt in {{libraryName}}",
@@ -121,9 +121,9 @@
"log_out_button": "Abmelden",
"switch_user": {
"title": "Switch User",
"account": "Benutzerkonto",
"account": "Account",
"switch_user": "Switch User on This Server",
"current": "aktuell"
"current": "current"
},
"categories": {
"title": "Kategorien"
@@ -143,9 +143,9 @@
"show_series_poster_on_episode": "Show Series Poster on Episodes",
"theme_music": "Theme Music",
"display_size": "Display Size",
"display_size_small": "Klein",
"display_size_default": "Standard",
"display_size_large": "Groß",
"display_size_small": "Small",
"display_size_default": "Default",
"display_size_large": "Large",
"display_size_extra_large": "Extra Large"
},
"network": {
@@ -203,8 +203,8 @@
"title": "Buffer Settings",
"cache_mode": "Cache Mode",
"cache_auto": "Auto",
"cache_yes": "Aktiviert",
"cache_no": "Deaktiviert",
"cache_yes": "Enabled",
"cache_no": "Disabled",
"buffer_duration": "Buffer Duration",
"max_cache_size": "Max Cache Size",
"max_backward_cache": "Max Backward Cache"
@@ -212,7 +212,7 @@
"vo_driver": {
"title": "Video Output",
"vo_mode": "VO Driver",
"gpu_next": "gpu-next (empfohlen)",
"gpu_next": "gpu-next (Recommended)",
"gpu": "gpu"
},
"gesture_controls": {
@@ -261,23 +261,79 @@
"None": "Keine",
"OnlyForced": "Nur erzwungene"
},
"text_color": "Textfarbe",
"background_color": "Hintergrundfarbe",
"outline_color": "Konturfarbe",
"outline_thickness": "Konturdicke",
"background_opacity": "Hintergrundtransparenz",
"outline_opacity": "Konturtransparenz",
"bold_text": "Fettgedruckter Text",
"colors": {
"Black": "Schwarz",
"Gray": "Grau",
"Silver": "Silber",
"White": "Weiß",
"Maroon": "Rotbraun",
"Red": "Rot",
"Fuchsia": "Magenta",
"Yellow": "Gelb",
"Olive": "Olivgrün",
"Green": "Grün",
"Teal": "Türkis",
"Lime": "Hellgrün",
"Purple": "Lila",
"Navy": "Marineblau",
"Blue": "Blau",
"Aqua": "Himmelblau"
},
"thickness": {
"None": "Keine",
"Thin": "Dünn",
"Normal": "Normal",
"Thick": "Dick"
},
"subtitle_color": "Untertitelfarbe",
"subtitle_background_color": "Hintergrundfarbe",
"subtitle_font": "Untertitel-Schriftart",
"ksplayer_title": "KSPlayer Einstellungen",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Hardwarebeschleunigung für Video Decoding verwenden. Deaktivieren wenn Wiedergabeprobleme auftreten.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Geben Sie Ihren OpenSubtitles API-Schlüssel ein, um die Client-seitige Untertitelsuche als Fallback zu aktivieren, wenn Ihr Jellyfin-Server keinen Untertitelanbieter konfiguriert hat.",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
"opensubtitles_api_key_placeholder": "API-Schüssel eingeben ...",
"opensubtitles_get_key": "Holen Sie sich Ihren kostenlosen API-Schlüssel unter opensubtitles.com/de/consumers",
"opensubtitles_api_key_placeholder": "Enter API key...",
"opensubtitles_get_key": "Get your free API key at opensubtitles.com/en/consumers",
"mpv_subtitle_scale": "Subtitle Scale",
"mpv_subtitle_margin_y": "Vertical Margin",
"mpv_subtitle_align_x": "Horizontal Align",
"mpv_subtitle_align_y": "Vertical Align",
"align": {
"left": "Links",
"center": "Mittig",
"right": "Rechts",
"top": "Oben",
"bottom": "Unten"
"left": "Left",
"center": "Center",
"right": "Right",
"top": "Top",
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Untertitel-Einstellungen",
"hint": "Anpassen des Untertitel-Erscheinungsbildes für VLC. Änderungen werden bei der nächsten Wiedergabe übernommen.",
"text_color": "Schriftfarbe",
"background_color": "Hintergrundfarbe",
"background_opacity": "Hintergrundtransparenz",
"outline_color": "Konturfarbe",
"outline_opacity": "Konturtransparenz",
"outline_thickness": "Konturdicke",
"bold": "Fettgedruckter Text",
"margin": "Unterer Abstand"
},
"video_player": {
"title": "Videoplayer",
"video_player": "Videoplayer",
"video_player_description": "Videoplayer auf iOS auswählen.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Sonstiges",
"video_orientation": "Videoausrichtung",
@@ -295,6 +351,11 @@
"UNKNOWN": "Unbekannt"
},
"safe_area_in_controls": "Sicherer Bereich in den Steuerungen",
"video_player": "Videoplayer",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimentell + PiP)"
},
"show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen",
"show_large_home_carousel": "Zeige große Startseiten-Übersicht (Beta)",
"hide_libraries": "Bibliotheken ausblenden",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Maximale automatisch abzuspielende Episodenanzahl",
"disabled": "Deaktiviert"
},
"downloads": {
"downloads_title": "Downloads"
},
"music": {
"title": "Musik",
"playback_title": "Wiedergabe",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Plugins",
"jellyseerr": {
"jellyseerr_warning": "Diese Integration ist in einer frühen Entwicklungsphase und kann jederzeit geändert werden.",
"server_url": "Server URL",
"server_url_hint": "Beispiel: http(s)://your-host.url\n(Port hinzufügen, falls erforderlich)",
"server_url_placeholder": "Seerr URL",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Erfahre mehr über Marlin.",
"save_button": "Speichern",
"toasts": {
"saved": "Gespeichert"
}
"saved": "Gespeichert",
"refreshed": "Einstellungen vom Server aktualisiert"
},
"refresh_from_server": "Einstellungen vom Server aktualisieren"
},
"streamystats": {
"enable_streamystats": "Streamystats aktivieren",
"disable_streamystats": "Streamystats deaktivieren",
"enable_search": "Zum Suchen verwenden",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"streamystats_search_hint": "URL für den Streamystats-Server eingeben.",
"read_more_about_streamystats": "Mehr über Streamystats erfahren.",
"save_button": "Speichern",
"save": "Gespeichert",
"features_title": "Features",
"home_sections_title": "Startseitenbereiche",
"enable_movie_recommendations": "Filmempfehlungen",
"enable_series_recommendations": "Serienempfehlungen",
"enable_promoted_watchlists": "Empfohlene Merklisten",
@@ -375,7 +445,8 @@
"refresh_from_server": "Einstellungen vom Server aktualisieren"
},
"kefinTweaks": {
"watchlist_enabler": "Merklisten-Integration aktivieren"
"watchlist_enabler": "Merklisten-Integration aktivieren",
"watchlist_button": "Merklisten-Integration umschalten"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Alle heruntergeladenen Dateien löschen",
"music_cache_title": "Musik-Cache",
"music_cache_description": "Beim Anhören Titel automatisch in den Cache laden um bessere Wiedergabe und Offline-Wiedergabe zu ermöglichen",
"enable_music_cache": "Musik-Cache aktivieren",
"clear_music_cache": "Musik-Cache leeren",
"music_cache_size": "{{size}} gechached",
"music_cache_cleared": "Musik-Cache geleert",
@@ -394,8 +466,10 @@
"downloaded_songs_deleted": "Heruntergeladene Titel gelöscht",
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Sind Sie sicher, dass Sie alle zwischengespeicherten Daten löschen möchten? Dadurch werden alle zwischengespeicherten Bilder, Musikdateien, Untertitel und Abfrage-Caches gelöscht. Ihre Einstellungen und Login-Sitzung werden beibehalten.",
"clear_all_cache_error_desc": "Beim Löschen des Caches ist ein Fehler aufgetreten."
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
"title": "Einführung",
@@ -416,20 +490,23 @@
"system": "System"
},
"toasts": {
"error_deleting_files": "Fehler beim Löschen von Dateien"
"error_deleting_files": "Fehler beim Löschen von Dateien",
"background_downloads_enabled": "Hintergrunddownloads aktiviert",
"background_downloads_disabled": "Hintergrunddownloads deaktiviert"
},
"security": {
"title": "Sicherheit",
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"disabled": "Deaktiviert",
"1_minute": "1 Minute",
"5_minutes": "5 Minuten",
"15_minutes": "15 Minuten",
"30_minutes": "30 Minuten",
"1_hour": "1 Stunde",
"4_hours": "4 Stunden",
"24_hours": "24 Stunden"
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
"15_minutes": "15 minutes",
"30_minutes": "30 minutes",
"1_hour": "1 hour",
"4_hours": "4 hours",
"24_hours": "24 hours"
}
}
},
@@ -441,7 +518,10 @@
"downloads_title": "Downloads",
"series": "Serien",
"movies": "Filme",
"queue": "Warteschlange",
"other_media": "Andere Medien",
"queue_hint": "Warteschlange und aktive Downloads gehen verloren wenn die App neu gestartet wird",
"no_items_in_queue": "Keine Elemente in der Warteschlange",
"no_downloaded_items": "Keine heruntergeladenen Elemente",
"delete_all_movies_button": "Alle Filme löschen",
"delete_all_series_button": "Alle Serien löschen",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Fehler beim Löschen aller Serien",
"deleted_media_successfully": "Andere Medien erfolgreich gelöscht!",
"failed_to_delete_media": "Fehler beim Löschen anderer Medien",
"download_deleted": "Download gelöscht",
"download_cancelled": "Download abgebrochen",
"could_not_delete_download": "Download konnte nicht gelöscht werden",
"download_paused": "Download pausiert",
"could_not_pause_download": "Download konnte nicht angehalten werden",
"download_resumed": "Download fortgesetzt",
"could_not_resume_download": "Download konnte nicht fortgesetzt werden",
"download_completed": "Download abgeschlossen",
"download_failed": "Download fehlgeschlagen",
"download_failed_for_item": "Download für {{item}} fehlgeschlagen - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} Lädt",
"all_files_deleted": "Alle Downloads gelöscht",
"files_deleted_by_type": "{{count}} {{type}} 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",
"could_not_get_download_url_for_item": "Download-URL für {{itemName}} konnte nicht geladen werden",
"go_to_downloads": "Zu Downloads gehen",
"file_deleted": "{{item}} gelöscht"
}
}
@@ -495,17 +583,16 @@
"none": "Keine",
"track": "Spur",
"cancel": "Abbrechen",
"stop": "Stop",
"delete": "Löschen",
"ok": "OK",
"remove": "Entfernen",
"next": "Weiter",
"back": "Zurück",
"continue": "Fortsetzen",
"verifying": "Verifiziere...",
"login": "Anmelden",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"login": "Login",
"refresh": "Refresh"
},
"search": {
"search": "Suchen...",
@@ -554,7 +641,7 @@
"movies": "Filme",
"series": "Serien",
"boxsets": "Boxsets",
"playlists": "Wiedergabelisten",
"playlists": "Playlists",
"items": "Elemente"
},
"options": {
@@ -566,7 +653,7 @@
"cover": "Cover",
"show_titles": "Titel anzeigen",
"show_stats": "Statistiken anzeigen",
"options_title": "Optionen"
"options_title": "Options"
},
"filters": {
"genres": "Genres",
@@ -575,10 +662,10 @@
"filter_by": "Filtern nach",
"sort_order": "Sortierreihenfolge",
"tags": "Tags",
"all": "Alle",
"reset": "Zurücksetzen",
"asc": "Aufsteigend",
"desc": "Absteigend"
"all": "All",
"reset": "Reset",
"asc": "Ascending",
"desc": "Descending"
}
},
"favorites": {
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Konnte keinen Stream für Chromecast erstellen",
"message_from_server": "Nachricht vom Server: {{message}}",
"next_episode": "Nächste Episode",
"refresh_tracks": "Spuren aktualisieren",
"audio_tracks": "Audiospuren:",
"playback_state": "Wiedergabestatus:",
"index": "Index:",
"continue_watching": "Fortsetzen",
"go_back": "Zurück",
"downloaded_file_title": "Diese Datei wurde bereits heruntergeladen",
@@ -611,35 +702,34 @@
"downloaded_file_yes": "Ja",
"downloaded_file_no": "Nein",
"downloaded_file_cancel": "Abbrechen",
"swipe_down_settings": "Für Einstellungen nach unten wischen",
"swipe_down_settings": "Swipe down for settings",
"ends_at": "Endet um {{time}}",
"search_subtitles": "Search Subtitles",
"subtitle_tracks": "Titel",
"subtitle_tracks": "Tracks",
"subtitle_search": "Search & Download",
"download": "Herunterladen",
"subtitle_download_hint": "Heruntergeladene Untertitel werden in Ihrer Bibliothek gespeichert",
"download": "Download",
"subtitle_download_hint": "Downloaded subtitles will be saved to your library",
"using_jellyfin_server": "Using Jellyfin Server",
"language": "Sprache",
"results": "Ergebnisse",
"searching": "Suche ...",
"search_failed": "Suche fehlgeschlagen",
"no_subtitle_provider": "Kein Untertitelanbieter auf dem Server konfiguriert",
"no_subtitles_found": "Keine Untertitel gefunden",
"add_opensubtitles_key_hint": "OpenSubtitles API-Schlüssel in den Einstellungen für Client-seitigen Fallback hinzufügen",
"settings": "Einstellungen",
"language": "Language",
"results": "Results",
"searching": "Searching...",
"search_failed": "Search failed",
"no_subtitle_provider": "No subtitle provider configured on server",
"no_subtitles_found": "No subtitles found",
"add_opensubtitles_key_hint": "Add OpenSubtitles API key in settings for client-side fallback",
"settings": "Settings",
"skip_intro": "Skip Intro",
"skip_credits": "Skip Credits",
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Wiedergabe von \"{{title}}\" beenden?",
"stopPlayingConfirm": "Bist du sicher, dass du die Wiedergabe beenden möchtest?",
"downloaded": "Heruntergeladen",
"missing_parameters": "Missing playback parameters"
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded"
},
"chapters": {
"title": "Kapitel",
"chapter_number": "Kapitel {{number}}",
"open": "Kapitel öffnen",
"close": "Kapitel schließen"
"title": "Chapters",
"chapter_number": "Chapter {{number}}",
"open": "Open chapters",
"close": "Close chapters"
},
"item_card": {
"next_up": "Als Nächstes",
@@ -664,19 +754,20 @@
"quality": "Qualität",
"audio": "Audio",
"subtitles": {
"label": "Untertitel",
"none": "Keine",
"tracks": "Titel"
"label": "Subtitle",
"none": "None",
"tracks": "Tracks"
},
"show_more": "Mehr anzeigen",
"show_less": "Weniger anzeigen",
"left": "übrig",
"director": "Regisseur*in",
"cast": "Besetzung",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
"appeared_in": "Erschien in",
"movies": "Filme",
"shows": "Serien",
"movies": "Movies",
"shows": "Shows",
"could_not_load_item": "Konnte Element nicht laden",
"none": "Keine",
"download": {
@@ -691,10 +782,9 @@
"mark_played": "Mark as Watched",
"mark_unplayed": "Mark as Unwatched",
"resume_playback": "Resume Playback",
"resume_playback_description": "Möchtest du dort fortfahren, wo du aufgehört hast oder von Anfang anfangen?",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Weiter ab {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Nächste",
@@ -706,16 +796,16 @@
"sports": "Sport",
"for_kids": "Für Kinder",
"news": "Nachrichten",
"page_of": "Seite {{current}} von {{total}}",
"no_programs": "Keine Programme verfügbar",
"no_channels": "Keine Kanäle verfügbar",
"page_of": "Page {{current}} of {{total}}",
"no_programs": "No programs available",
"no_channels": "No channels available",
"tabs": {
"programs": "Programme",
"guide": "Führer",
"channels": "Kanäle",
"recordings": "Aufzeichnungen",
"schedule": "Planung",
"series": "Serien"
"programs": "Programs",
"guide": "Guide",
"channels": "Channels",
"recordings": "Recordings",
"schedule": "Schedule",
"series": "Series"
}
},
"jellyseerr": {
@@ -761,12 +851,12 @@
"decline": "Ablehnen",
"requested_by": "Angefragt von {{user}}",
"unknown_user": "Unbekannter Nutzer",
"select": "Auswählen",
"select": "Select",
"request_all": "Request All",
"request_seasons": "Request Seasons",
"select_seasons": "Select Seasons",
"request_selected": "Request Selected",
"n_selected": "{{count}} ausgewählt",
"n_selected": "{{count}} selected",
"toasts": {
"jellyseer_does_not_meet_requirements": "Seerr-Server erfüllt nicht die minimalen Versionsanforderungen. Bitte den Seerr-Server auf mindestens 2.0.0 aktualisieren.",
"jellyseerr_test_failed": "Seerr-Test fehlgeschlagen. Bitte erneut versuchen.",
@@ -787,7 +877,7 @@
"library": "Bibliothek",
"custom_links": "Links",
"favorites": "Favoriten",
"settings": "Einstellungen"
"settings": "Settings"
},
"music": {
"title": "Musik",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "Titel"
},
"filters": {
"all": "Alle"
},
"recently_added": "Kürzlich hinzugefügt",
"recently_played": "Vor kurzem gehört",
"frequently_played": "Oft gehört",
"explore": "Entdecken",
"top_tracks": "Top-Titel",
"play": "Abspielen",
"shuffle": "Shuffle",
@@ -910,33 +1004,34 @@
}
},
"companion_login": {
"title": "Mit TV koppeln",
"align_qr": "Den QR-Code innerhalb des Rahmens ausrichten",
"enter_code_manually": "Code manuell eingeben",
"pairing_enter_credentials": "Anmeldedaten für TV eingeben",
"pairing_code_label": "Kopplungscode",
"title": "Pair with TV",
"align_qr": "Align the QR code within the frame",
"enter_code_manually": "Enter code manually",
"pairing_enter_credentials": "Enter credentials for TV",
"pairing_code_label": "Pairing code",
"server": "Server",
"authorize_button": "Autorisieren",
"authorizing": "Autorisieren...",
"authorize_button": "Authorize",
"authorizing": "Authorizing...",
"scan_again": "Scan Again",
"done": "Fertig",
"done": "Done",
"success_title": "Authorization Sent",
"pairing_tv_connecting": "Der Fernseher verbindet sich mit Ihrem Konto",
"pairing_tv_connecting": "The TV is connecting to your account",
"error_title": "Authorization Failed",
"error_invalid_qr": "Ungültiger QR-Code. Bitte scannen Sie den TV-Kopplungscode.",
"error_generic": "Etwas ist schiefgelaufen. Bitte versuche es erneut.",
"error_permission_denied": "Kameraberechtigung erforderlich zum Scannen von QR-Codes.",
"login_as": "Als {{username}} anmelden?",
"on_server": "auf {{server}}",
"use_different_user": "Verwende einen anderen Benutzer",
"open_settings": "Einstellungen öffnen"
"error_invalid_qr": "Invalid QR code. Please scan the TV pairing code.",
"error_generic": "Something went wrong. Please try again.",
"error_permission_denied": "Camera permission is required to scan QR codes.",
"login_as": "Log in as {{username}}?",
"on_server": "on {{server}}",
"use_different_user": "Use a different user",
"open_settings": "Open Settings"
},
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"waiting_for_phone": "Warte auf Telefon...",
"scan_with_phone": "Scanne mit der Streamyfin-App auf deinem Handy",
"logging_in": "Anmeldung...",
"logging_in_description": "Verbinde mit deinem Server"
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",
"logging_in_description": "Connecting to your server"
}
}

View File

@@ -261,6 +261,43 @@
"None": "Κανένα",
"OnlyForced": "Μόνο"
},
"text_color": "Χρώμα Κειμένου",
"background_color": "Χρώμα Φόντου",
"outline_color": "Χρώμα Περιγράμματος",
"outline_thickness": "Πάχος Περιγράμματος",
"background_opacity": "Αδιαφάνεια Φόντου",
"outline_opacity": "Αδιαφάνεια Περιγράμματος",
"bold_text": "Bold Text",
"colors": {
"Black": "Μαύρο",
"Gray": "Γκρι",
"Silver": "Ασημένιο",
"White": "Λευκό",
"Maroon": "Μαρώ",
"Red": "Κόκκινο",
"Fuchsia": "Fuchsia",
"Yellow": "Κίτρινο",
"Olive": "Ελιές",
"Green": "Πράσινο",
"Teal": "Τιρκουάζ",
"Lime": "Άσβεστος",
"Purple": "Μωβ",
"Navy": "Ναυτικό",
"Blue": "Μπλε",
"Aqua": "Νερό"
},
"thickness": {
"None": "Κανένα",
"Thin": "Λεπτό",
"Normal": "Κανονικό",
"Thick": "Παχύ"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Άλλο",
"video_orientation": "Προσανατολισμός Βίντεο",
@@ -295,6 +351,11 @@
"UNKNOWN": "Άγνωστο"
},
"safe_area_in_controls": "Ασφαλής περιοχή σε χειριστήρια",
"video_player": "Αναπαραγωγέας Βίντεο",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Πειραματική + PiP)"
},
"show_custom_menu_links": "Εμφάνιση Προσαρμοσμένων Συνδέσμων Μενού",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "Απόκρυψη Βιβλιοθηκών",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Μέγιστο Πλήθος Επεισόδιο Αυτόματου Παιχνιδιού",
"disabled": "Απενεργοποιημένο"
},
"downloads": {
"downloads_title": "Λήψεις"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Πρόσθετα",
"jellyseerr": {
"jellyseerr_warning": "Αυτή η ενσωμάτωση βρίσκεται στα αρχικά της στάδια.",
"server_url": "Url Διακομιστή",
"server_url_hint": "Παράδειγμα: http(s)://your-host.url\n(προσθέστε θύρα εφόσον απαιτείται)",
"server_url_placeholder": "Seerr URL",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Διαβάστε Περισσότερα Σχετικά Με Marlin.",
"save_button": "Αποθήκευση",
"toasts": {
"saved": "Αποθηκεύτηκε"
}
"saved": "Αποθηκεύτηκε",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Διαγραφή Όλων Των Ληφθέντων Αρχείων",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Σύστημα"
},
"toasts": {
"error_deleting_files": "Σφάλμα Διαγραφής Αρχείων"
"error_deleting_files": "Σφάλμα Διαγραφής Αρχείων",
"background_downloads_enabled": "Οι λήψεις στο παρασκήνιο ενεργοποιήθηκαν",
"background_downloads_disabled": "Οι λήψεις παρασκηνίου απενεργοποιήθηκαν"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Λήψεις",
"series": "Τηλεόραση-Σειρά",
"movies": "Ταινίες",
"queue": "Ουρά",
"other_media": "Άλλα μέσα",
"queue_hint": "Ουρά και λήψεις θα χαθούν κατά την επανεκκίνηση της εφαρμογής",
"no_items_in_queue": "Δεν υπάρχουν αντικείμενα στην ουρά",
"no_downloaded_items": "Δεν Έχουν Ληφθεί Αντικείμενα",
"delete_all_movies_button": "Διαγραφή Όλων Των Ταινιών",
"delete_all_series_button": "Διαγραφή Όλων Των Τηλεοπτικών Σειρών",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Αποτυχία διαγραφής Όλων των TV-Series",
"deleted_media_successfully": "Διαγράφηκε άλλο μέσο επιτυχώς!",
"failed_to_delete_media": "Αποτυχία διαγραφής άλλων πολυμέσων",
"download_deleted": "Η Λήψη Διαγράφηκε",
"download_cancelled": "Download Cancelled",
"could_not_delete_download": "Αδυναμία Διαγραφής Λήψης",
"download_paused": "Λήψη Σε Παύση",
"could_not_pause_download": "Αδυναμία Παύσης Λήψης",
"download_resumed": "Συνέχιση Λήψης",
"could_not_resume_download": "Αδυναμία Συνέχισης Λήψης",
"download_completed": "Η Λήψη Ολοκληρώθηκε",
"download_failed": "Download Failed",
"download_failed_for_item": "Η λήψη απέτυχε για το {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "Όλα τα αρχεία, οι φάκελοι και οι εργασίες διαγράφηκαν με επιτυχία",
"failed_to_clean_cache_directory": "Αποτυχία καθαρισμού φακέλου προσωρινής μνήμης",
"could_not_get_download_url_for_item": "Αδυναμία λήψης του URL λήψης για το {{itemName}}",
"go_to_downloads": "Μετάβαση στις λήψεις",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Αναζήτηση...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Αδυναμία δημιουργίας ροής για το Chromecast",
"message_from_server": "Μήνυμα από το διακομιστή: {{message}}",
"next_episode": "Επόμενο Επεισόδιο",
"refresh_tracks": "Ανανέωση Κομματιών",
"audio_tracks": "Κομμάτια Ήχου:",
"playback_state": "Κατάσταση Αναπαραγωγής:",
"index": "Δείκτης:",
"continue_watching": "Συνέχεια Παρακολούθησης",
"go_back": "Μετάβαση Πίσω",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Εμφάνιση Περισσότερων",
"show_less": "Εμφάνιση Λιγότερων",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Επόμενο",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -12,18 +12,21 @@
"login_button": "Log in",
"quick_connect": "Quick Connect",
"enter_code_to_login": "Enter code {{code}} to log in",
"quick_connect_instructions": "Enter this code on a signed-in device — you'll be logged in automatically.",
"tap_code_to_copy": "Tap the code to copy it",
"code_copied": "Code copied",
"failed_to_initiate_quick_connect": "Failed to initiate Quick Connect",
"got_it": "Got it",
"connection_failed": "Connection failed",
"could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.",
"an_unexpected_error_occured": "An unexpected error occurred",
"an_unexpected_error_occurred": "An unexpected error occurred",
"change_server": "Change server",
"invalid_username_or_password": "Invalid username or password",
"user_does_not_have_permission_to_log_in": "User does not have permission to log in",
"server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later",
"server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.",
"there_is_a_server_error": "There is a server error",
"an_unexpected_error_occured_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
"too_old_server_text": "Unsupported Jellyfin server discovered",
"too_old_server_description": "Please update Jellyfin to the latest version"
},
@@ -33,6 +36,7 @@
"connect_button": "Connect",
"previous_servers": "Previous servers",
"clear_button": "Clear all",
"server_url": "Server URL",
"swipe_to_remove": "Swipe to remove",
"search_for_local_servers": "Search for local servers",
"searching": "Searching...",
@@ -188,10 +192,11 @@
"authorize_button": "Authorize Quick Connect",
"enter_the_quick_connect_code": "Enter the Quick Connect code...",
"success": "Success",
"quick_connect_autorized": "Quick Connect authorized",
"quick_connect_authorized": "Quick Connect authorized",
"error": "Error",
"invalid_code": "Invalid code",
"authorize": "Authorize"
"authorize": "Authorize",
"paste_code": "Paste code"
},
"media_controls": {
"media_controls_title": "Media controls",
@@ -270,6 +275,10 @@
"mpv_subtitle_margin_y": "Vertical margin",
"mpv_subtitle_align_x": "Horizontal align",
"mpv_subtitle_align_y": "Vertical align",
"mpv_settings_title": "MPV Subtitle Settings",
"mpv_settings_description": "Advanced subtitle customization for MPV player",
"opaque_background": "Opaque Background",
"background_opacity": "Background Opacity",
"align": {
"left": "Left",
"center": "Center",
@@ -298,7 +307,7 @@
"show_custom_menu_links": "Show custom menu links",
"show_large_home_carousel": "Show large home carousel (beta)",
"hide_libraries": "Hide libraries",
"select_liraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
"select_libraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
"disable_haptic_feedback": "Disable haptic feedback",
"default_quality": "Default quality",
"default_playback_speed": "Default playback speed",
@@ -384,6 +393,8 @@
"device_usage": "Device {{availableSpace}}%",
"size_used": "{{used}} of {{total}} used",
"delete_all_downloaded_files": "Delete all downloaded files",
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
"music_cache_title": "Music cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"clear_music_cache": "Clear music cache",
@@ -435,10 +446,13 @@
},
"sessions": {
"title": "Sessions",
"no_active_sessions": "No active sessions"
"no_active_sessions": "No active sessions",
"select_session": "Select Session",
"now_playing": "Now playing:"
},
"downloads": {
"downloads_title": "Downloads",
"transcoding": "Transcoding",
"series": "Series",
"movies": "Movies",
"other_media": "Other media",
@@ -495,6 +509,8 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"open_menu": "Open Menu",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
@@ -596,10 +612,34 @@
},
"player": {
"live": "LIVE",
"menu": {
"quality": "Quality",
"subtitles": "Subtitles",
"subtitle_scale": "Subtitle Scale",
"audio": "Audio",
"speed": "Speed",
"playback_options": "Playback Options",
"show_technical_info": "Show Technical Info",
"hide_technical_info": "Hide Technical Info"
},
"technical_info": {
"video": "Video:",
"audio": "Audio:",
"subtitle": "Subtitle:",
"bitrate": "Bitrate:",
"buffer_seconds": "Buffer: {{seconds}}s",
"vo": "VO:",
"dropped_frames": "Dropped: {{count}} frames",
"loading": "Loading..."
},
"mpv_player_title": "MPV player",
"aspect_ratio": "Aspect Ratio",
"aspect_ratio_original": "Original",
"hash_match": "Hash Match",
"still_watching": "Are you still watching?",
"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.",
"an_error_occurred_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}}",
@@ -697,6 +737,7 @@
"no_data_available": "No data available"
},
"live_tv": {
"title": "Live TV",
"next": "Next",
"previous": "Previous",
"coming_soon": "Coming soon",
@@ -768,7 +809,7 @@
"request_selected": "Request selected",
"n_selected": "{{count}} selected",
"toasts": {
"jellyseer_does_not_meet_requirements": "Seerr server does not meet minimum version requirements! Please update to at least 2.0.0",
"jellyseerr_does_not_meet_requirements": "Seerr server does not meet minimum version requirements! Please update to at least 2.0.0",
"jellyseerr_test_failed": "Seerr test failed. Please try again.",
"failed_to_test_jellyseerr_server_url": "Failed to test Seerr server url",
"issue_submitted": "Issue submitted!",
@@ -781,6 +822,16 @@
"failed_to_decline_request": "Failed to decline request"
}
},
"accessibility": {
"play_button": "Play button",
"play_hint": "Tap to play the media",
"toggle_orientation": "Toggle screen orientation",
"toggle_orientation_hint": "Toggles the screen orientation between portrait and landscape"
},
"not_found": {
"title": "This screen doesn't exist.",
"go_home": "Go to home screen!"
},
"tabs": {
"home": "Home",
"search": "Search",
@@ -791,6 +842,12 @@
},
"music": {
"title": "Music",
"no_track_playing": "No track playing",
"queue_empty": "Queue is empty",
"playing_from_queue": "Playing from queue",
"up_next": "Up next",
"now_playing": "Now Playing",
"missing_library_id": "Missing music library id.",
"tabs": {
"suggestions": "Suggestions",
"albums": "Albums",

View File

@@ -261,6 +261,43 @@
"None": "Nada",
"OnlyForced": "Solo forzados"
},
"text_color": "Color del texto",
"background_color": "Color de fondo",
"outline_color": "Color de salida",
"outline_thickness": "Grosor exterior",
"background_opacity": "Opacidad de fondo",
"outline_opacity": "Opacidad exterior",
"bold_text": "Texto en negrita",
"colors": {
"Black": "Negro",
"Gray": "Gris",
"Silver": "Plata",
"White": "Blanco",
"Maroon": "Granate",
"Red": "Rojo",
"Fuchsia": "Fucsia",
"Yellow": "Amarillo",
"Olive": "Oliva",
"Green": "Verde",
"Teal": "Cereal",
"Lime": "Lima",
"Purple": "Morado",
"Navy": "Naval",
"Blue": "Azul",
"Aqua": "Agua"
},
"thickness": {
"None": "Ninguno",
"Thin": "Ligero",
"Normal": "Normal",
"Thick": "Grosor"
},
"subtitle_color": "Color de los Subtítulos",
"subtitle_background_color": "Color del fondo",
"subtitle_font": "Fuente de los subtítulos",
"ksplayer_title": "Ajustes de KSPlayer",
"hardware_decode": "Decodificación de hardware",
"hardware_decode_description": "Utilizar la aceleración de hardware para la decodificación de vídeo. Deshabilite si experimenta problemas de reproducción.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "Configuración de subtítulos VLC",
"hint": "Personalizar la apariencia de los subtítulos para el reproductor VLC. Los cambios tendrán efecto en la próxima reproducción.",
"text_color": "Color del texto",
"background_color": "Color del fondo",
"background_opacity": "Opacidad del fondo",
"outline_color": "Color del contorno",
"outline_opacity": "Opacidad del contorno",
"outline_thickness": "Grosor del contorno",
"bold": "Texto en negrita",
"margin": "Margen inferior"
},
"video_player": {
"title": "Reproductor de vídeo",
"video_player": "Reproductor de vídeo",
"video_player_description": "Elige qué reproductor de vídeo en iOS",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Otros",
"video_orientation": "Orientación de vídeo",
@@ -295,6 +351,11 @@
"UNKNOWN": "Desconocida"
},
"safe_area_in_controls": "Área segura en controles",
"video_player": "Reproductor de vídeo",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "Mostrar enlaces de menú personalizados",
"show_large_home_carousel": "Mostrar carrusel del menú principal grande (beta)",
"hide_libraries": "Ocultar bibliotecas",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Máximo número de episodios de Auto Play",
"disabled": "Deshabilitado"
},
"downloads": {
"downloads_title": "Descargas"
},
"music": {
"title": "Música",
"playback_title": "Reproducir",
@@ -314,12 +378,13 @@
"caching_title": "Almacenando en caché",
"caching_description": "Cachear automáticamente las próximas canciones para una reproducción más suave.",
"lookahead_enabled": "Activar el look-Ahead Cache",
"lookahead_count": "Songs to pre-cache",
"lookahead_count": "",
"max_cache_size": "Tamaño máximo del caché"
},
"plugins": {
"plugins_title": "Plugins",
"jellyseerr": {
"jellyseerr_warning": "Esta integración está en sus primeras etapas. Cuenta con posibles cambios.",
"server_url": "URL del servidor",
"server_url_hint": "Ejemplo: http(s)://tu-dominio.url\n(añade el puerto si es necesario)",
"server_url_placeholder": "URL de Jellyseerr...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Leer más sobre Marlin.",
"save_button": "Guardar",
"toasts": {
"saved": "Guardado"
}
"saved": "Guardado",
"refreshed": "Ajustes del servidor actualizados"
},
"refresh_from_server": "Actualizar ajustes del servidor"
},
"streamystats": {
"enable_streamystats": "Habilitar Streamystats",
"disable_streamystats": "Deshabilitar Streamystats",
"enable_search": "Usar para la búsqueda",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.ejemplo.com",
"streamystats_search_hint": "Introduzca la URL para su servidor Streamystats. La URL debe incluir http o https y opcionalmente el puerto.",
"read_more_about_streamystats": "Leer más sobre Streamystats.",
"save_button": "Guardar",
"save": "Guardar",
"features_title": "Características",
"home_sections_title": "Secciones de inicio",
"enable_movie_recommendations": "Recomendaciones de películas",
"enable_series_recommendations": "Recomendaciones de series",
"enable_promoted_watchlists": "Listas promocionadas",
@@ -375,7 +445,8 @@
"refresh_from_server": "Actualizar ajustes desde el servidor"
},
"kefinTweaks": {
"watchlist_enabler": "Habilitar la integración de la lista de seguimiento"
"watchlist_enabler": "Habilitar la integración de la lista de seguimiento",
"watchlist_button": "Activar o desactivar la integración de la lista de seguimiento"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Eliminar todos los archivos descargados",
"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",
"enable_music_cache": "Activar Caché de Música",
"clear_music_cache": "Borrar Caché de Música",
"music_cache_size": "Caché {{Tamaño}}",
"music_cache_cleared": "Caché de música eliminado",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Sistema"
},
"toasts": {
"error_deleting_files": "Error al eliminar archivos"
"error_deleting_files": "Error al eliminar archivos",
"background_downloads_enabled": "Descargas en segundo plano habilitadas",
"background_downloads_disabled": "Descargas en segundo plano deshabilitadas"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Descargas",
"series": "Series",
"movies": "Películas",
"queue": "Cola",
"other_media": "Otros medios",
"queue_hint": "La cola de series y películas se perderá al reiniciar la app",
"no_items_in_queue": "No hay ítems en la cola",
"no_downloaded_items": "No hay ítems descargados",
"delete_all_movies_button": "Eliminar todas las películas",
"delete_all_series_button": "Eliminar todas las series",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Error al eliminar todas las series",
"deleted_media_successfully": "¡Otros medios eliminados con éxito!",
"failed_to_delete_media": "Error al eliminar otros medios",
"download_deleted": "Descarga eliminada",
"download_cancelled": "Descarga cancelada",
"could_not_delete_download": "No se pudo eliminar la descarga",
"download_paused": "Descarga pausada",
"could_not_pause_download": "No se pudo pausar la descarga",
"download_resumed": "Descarga rebatida",
"could_not_resume_download": "No se pudo reiniciar la descarga",
"download_completed": "Descarga completada",
"download_failed": "Descarga fallida",
"download_failed_for_item": "Descarga fallida para {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} ya está descargando",
"all_files_deleted": "Todas las descargas eliminadas correctamente",
"files_deleted_by_type": "{{count}} {{type}} eliminado",
"all_files_folders_and_jobs_deleted_successfully": "Todos los archivos, carpetas y trabajos eliminados con éxito",
"failed_to_clean_cache_directory": "Error al limpiar el directorio de caché",
"could_not_get_download_url_for_item": "No se pudo obtener la URL de descarga para {{itemName}}",
"go_to_downloads": "Ir a descargas",
"file_deleted": "{{item}} eliminado"
}
}
@@ -495,17 +583,16 @@
"none": "Nada",
"track": "Pista",
"cancel": "Cancelar",
"stop": "Stop",
"delete": "Borrar",
"ok": "Aceptar",
"remove": "Eliminar",
"next": "Siguiente",
"back": "Atrás",
"continue": "Continuar",
"verifying": "Verificando...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Buscar...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "No se pudo crear el Steam para Chromecast",
"message_from_server": "Mensaje del servidor: {{message}}",
"next_episode": "Siguiente episodio",
"refresh_tracks": "Refrescar pistas",
"audio_tracks": "Pistas de audio:",
"playback_state": "Estado de la reproducción:",
"index": "Índice:",
"continue_watching": "Continuar viendo",
"go_back": "Volver",
"downloaded_file_title": "Ya tienes este archivo descargado",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Mostrar más",
"show_less": "Mostrar menos",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Siguiente",
@@ -798,9 +888,13 @@
"playlists": "Listas de reproducción",
"tracks": "Canciones"
},
"filters": {
"all": "Todas"
},
"recently_added": "Recientemente añadido",
"recently_played": "Reproducidos Recientemente",
"frequently_played": "Reproducido con frecuencia",
"explore": "Explorar",
"top_tracks": "Canciones Populares",
"play": "Reproducir",
"shuffle": "Aleatorio",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Ei mitään",
"OnlyForced": "Vain pakotettu"
},
"text_color": "Tekstin väri",
"background_color": "Taustaväri",
"outline_color": "Ääriviivan väri",
"outline_thickness": "Ääriviivan paksuus",
"background_opacity": "Taustan läpinäkyvyys",
"outline_opacity": "Ääriviivan Läpinäkyvyys",
"bold_text": "Lihavoi teksti",
"colors": {
"Black": "Musta",
"Gray": "Harmaa",
"Silver": "Hopea",
"White": "Valkoinen",
"Maroon": "Maroon",
"Red": "Punainen",
"Fuchsia": "Fuchsia",
"Yellow": "Keltainen",
"Olive": "Oliivit",
"Green": "Vihreä",
"Teal": "Sinappi",
"Lime": "Limea",
"Purple": "Violetti",
"Navy": "Laiva",
"Blue": "Sininen",
"Aqua": "Vesi"
},
"thickness": {
"None": "Ei mitään",
"Thin": "Ohut",
"Normal": "Normaali",
"Thick": "Paksu"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Muut",
"video_orientation": "Videon suunta",
@@ -295,6 +351,11 @@
"UNKNOWN": "Tuntematon"
},
"safe_area_in_controls": "Turvallinen alue ohjaimissa",
"video_player": "Videosoitin",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Kokeellinen + PiP)"
},
"show_custom_menu_links": "Näytä mukautetut valikkolinkit",
"show_large_home_carousel": "Näytä suuri kotikaruselli (beta)",
"hide_libraries": "Piilota kirjastot",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Automaattisten Toistojaksojen Maksimimäärä",
"disabled": "Pois Käytöstä"
},
"downloads": {
"downloads_title": "Lataukset"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Liitännäiset",
"jellyseerr": {
"jellyseerr_warning": "Tämä integraatio on alkuvaiheessa. Odota muutoksia.",
"server_url": "Palvelimen URL",
"server_url_hint": "Esimerkki: http(s)://verkkotunnus.url\n(lisää portti tarvittaessa)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Lue lisää Marlinista.",
"save_button": "Tallenna",
"toasts": {
"saved": "Tallennettu"
}
"saved": "Tallennettu",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Poista kaikki ladatut tiedostot",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Järjestelmä"
},
"toasts": {
"error_deleting_files": "Virhe tiedostojen poistamisessa"
"error_deleting_files": "Virhe tiedostojen poistamisessa",
"background_downloads_enabled": "Taustalataukset käytössä",
"background_downloads_disabled": "Taustalataukset pois käytöstä"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Lataukset",
"series": "TV-sarjat",
"movies": "Elokuvat",
"queue": "Jonot",
"other_media": "Muu media",
"queue_hint": "Jonot ja lataukset menetetään sovelluksen uudelleenkäynnistyksen yhteydessä",
"no_items_in_queue": "Ei kohteita jonossa",
"no_downloaded_items": "Ei ladattuja kohteita",
"delete_all_movies_button": "Poista kaikki elokuvat",
"delete_all_series_button": "Poista kaikki TV-sarjat",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Kaikkien TV-sarjojen poistaminen epäonnistui",
"deleted_media_successfully": "Muu media poistettu onnistuneesti!",
"failed_to_delete_media": "Muiden medioiden poistaminen epäonnistui",
"download_deleted": "Lataus Poistettu",
"download_cancelled": "Lataus peruutettu",
"could_not_delete_download": "Latausta Ei Voitu Poistaa",
"download_paused": "Lataus Keskeytetty",
"could_not_pause_download": "Latausta Ei Voitu Keskeyttää",
"download_resumed": "Lataus Jatketaan",
"could_not_resume_download": "Latausta Ei Voitu Jatkaa.",
"download_completed": "Lataus valmis",
"download_failed": "Lataus epäonnistui",
"download_failed_for_item": "Lataus epäonnistui kohteelle {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "Kaikki lataukset poistettu onnistuneesti",
"files_deleted_by_type": "{{count}} {{type}} poistettu",
"all_files_folders_and_jobs_deleted_successfully": "Kaikki tiedostot, kansiot ja tehtävät poistettu onnistuneesti",
"failed_to_clean_cache_directory": "Välimuistin hakemiston puhdistus epäonnistui",
"could_not_get_download_url_for_item": "Latauksen URL-osoitetta ei voitu ladata {{itemName}}",
"go_to_downloads": "Siirry latauksiin",
"file_deleted": "{{item}} poistettu"
}
}
@@ -495,17 +583,16 @@
"none": "Ei mitään",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Haku...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Suoratoistoa ei voitu luoda Chromecastia varten",
"message_from_server": "Viesti palvelimelta: {{message}}",
"next_episode": "Seuraava Jakso",
"refresh_tracks": "Päivitä Kappaleet",
"audio_tracks": "Ääni Kappaleet:",
"playback_state": "Toiston Tila:",
"index": "Indeksi:",
"continue_watching": "Jatka katsomista",
"go_back": "Siirry Takaisin",
"downloaded_file_title": "Tämä tiedosto on ladattuna",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Näytä Lisää",
"show_less": "Näytä Vähemmän",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Seuraava",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Aucun",
"OnlyForced": "Forcés seulement"
},
"text_color": "Couleur du texte",
"background_color": "Couleur d'arrière-plan",
"outline_color": "Couleur du contour",
"outline_thickness": "Épaisseur du contour",
"background_opacity": "Opacité de l'arrière-plan",
"outline_opacity": "Opacité du contour",
"bold_text": "Texte en gras",
"colors": {
"Black": "Noir",
"Gray": "Gris",
"Silver": "Argent",
"White": "Blanc",
"Maroon": "Marron",
"Red": "Rouge",
"Fuchsia": "Fuchsia",
"Yellow": "Jaune",
"Olive": "Olive",
"Green": "Vert",
"Teal": "Bleu canard",
"Lime": "Citron vert",
"Purple": "Violet",
"Navy": "Bleu marine",
"Blue": "Bleu",
"Aqua": "Bleu turquoise"
},
"thickness": {
"None": "Aucun",
"Thin": "Maigre",
"Normal": "Normale",
"Thick": "Épais"
},
"subtitle_color": "Couleur des sous-titres",
"subtitle_background_color": "Couleur d'arrière-plan",
"subtitle_font": "Police des sous-titres",
"ksplayer_title": "Paramètres de KSPlayer",
"hardware_decode": "Décodage matériel",
"hardware_decode_description": "Utilisez laccélération matérielle pour le décodage vidéo. Désactivez si vous rencontrez des problèmes de lecture.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "Paramètres des sous-titres VLC",
"hint": "Personnaliser l'apparence des sous-titres pour le lecteur VLC. Les changements prennent effet lors de la lecture suivante.",
"text_color": "Couleur du texte",
"background_color": "Couleur d'arrière-plan",
"background_opacity": "Opacité de l'arrière-plan",
"outline_color": "Couleur du contour",
"outline_opacity": "Opacité du contour",
"outline_thickness": "Épaisseur du contour",
"bold": "Texte en gras",
"margin": "Marge inférieure"
},
"video_player": {
"title": "Lecteur vidéo",
"video_player": "Lecteur vidéo",
"video_player_description": "Choisissez le lecteur vidéo à utiliser sur iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Autres",
"video_orientation": "Orientation vidéo",
@@ -295,6 +351,11 @@
"UNKNOWN": "Inconnu"
},
"safe_area_in_controls": "Zone de sécurité dans les contrôles",
"video_player": "Lecteur vidéo",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Expérimental + PiP)"
},
"show_custom_menu_links": "Afficher les liens personnalisés",
"show_large_home_carousel": "Afficher le grand carrousel daccueil (bêta)",
"hide_libraries": "Cacher des bibliothèques",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Nombre d'épisodes en lecture automatique max",
"disabled": "Désactivé"
},
"downloads": {
"downloads_title": "Téléchargements"
},
"music": {
"title": "Musique",
"playback_title": "Lecture",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Plugins",
"jellyseerr": {
"jellyseerr_warning": "Cette intégration est dans ses débuts. Attendez-vous à ce que des choses changent.",
"server_url": "URL du serveur",
"server_url_hint": "Exemple : http(s)://votre-domaine.url\n(ajouter le port si nécessaire)",
"server_url_placeholder": "URL de Seerr...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "En savoir plus sur Marlin.",
"save_button": "Enregistrer",
"toasts": {
"saved": "Enregistré"
}
"saved": "Enregistré",
"refreshed": "Paramètres actualisés depuis le serveur"
},
"refresh_from_server": "Rafraîchir les paramètres depuis le serveur"
},
"streamystats": {
"enable_streamystats": "Activer Streamystats",
"disable_streamystats": "Désactiver Streamystats",
"enable_search": "Utiliser pour la recherche",
"url": "URL",
"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.",
"read_more_about_streamystats": "En savoir plus sur Streamystats.",
"save_button": "Enregistrer",
"save": "Enregistrer",
"features_title": "Fonctionnalités",
"home_sections_title": "Sections de la page d´accueil",
"enable_movie_recommendations": "Recommandations de films",
"enable_series_recommendations": "Recommandations de séries",
"enable_promoted_watchlists": "Listes de lecture promues",
@@ -375,7 +445,8 @@
"refresh_from_server": "Rafraîchir les paramètres depuis le serveur"
},
"kefinTweaks": {
"watchlist_enabler": "Activer l'intégration de notre liste de lecture"
"watchlist_enabler": "Activer l'intégration de notre liste de lecture",
"watchlist_button": "Activer l'intégration de notre liste de lecture"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Supprimer tous les fichiers téléchargés",
"music_cache_title": "Mise en cache de la musique",
"music_cache_description": "Mettez automatiquement en cache les chansons au fur et à mesure que vous écoutez pour une lecture plus fluide et une prise en charge hors ligne",
"enable_music_cache": "Activer le cache sur la musique",
"clear_music_cache": "Vider le cache de la musique",
"music_cache_size": "{{size}} mis en cache",
"music_cache_cleared": "Cache de musique effacé",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Système"
},
"toasts": {
"error_deleting_files": "Erreur lors de la suppression des fichiers"
"error_deleting_files": "Erreur lors de la suppression des fichiers",
"background_downloads_enabled": "Téléchargements en arrière-plan activés",
"background_downloads_disabled": "Téléchargements en arrière-plan désactivés"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Téléchargements",
"series": "Séries",
"movies": "Films",
"queue": "File d'attente",
"other_media": "Autres médias",
"queue_hint": "La file d'attente et les téléchargements seront perdus au redémarrage de l'application",
"no_items_in_queue": "Aucun téléchargement de média dans la file d'attente",
"no_downloaded_items": "Aucun média téléchargé",
"delete_all_movies_button": "Supprimer tous les films",
"delete_all_series_button": "Supprimer toutes les séries",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Échec de la suppression de toutes les séries",
"deleted_media_successfully": "Les autres médias ont été supprimés avec succès !",
"failed_to_delete_media": "Échec de la suppression d'un autre média",
"download_deleted": "Téléchargement supprimé",
"download_cancelled": "Téléchargement annulé",
"could_not_delete_download": "Impossible de supprimer le téléchargement",
"download_paused": "Téléchargement en pause",
"could_not_pause_download": "Impossible de mettre en pause le téléchargement",
"download_resumed": "Reprise du téléchargement",
"could_not_resume_download": "Impossible de reprendre le téléchargement",
"download_completed": "Téléchargement terminé",
"download_failed": "Échec du téléchargement",
"download_failed_for_item": "Échec du téléchargement pour {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} est déjà en cours de téléchargement",
"all_files_deleted": "Tous les téléchargements supprimés avec succès",
"files_deleted_by_type": "{{count}} {{type}} supprimé",
"all_files_folders_and_jobs_deleted_successfully": "Tous les fichiers, dossiers et tâches ont été supprimés avec succès",
"failed_to_clean_cache_directory": "Échec du nettoyage du répertoire de cache",
"could_not_get_download_url_for_item": "Échec d'obtention de l'URL de téléchargement pour {{itemName}}",
"go_to_downloads": "Aller aux téléchargements",
"file_deleted": "{{item}} supprimé"
}
}
@@ -495,17 +583,16 @@
"none": "Aucun",
"track": "Suivre",
"cancel": "Annuler",
"stop": "Stop",
"delete": "Supprimer",
"ok": "Ok",
"remove": "Retirer",
"next": "Suivant",
"back": "Précédent",
"continue": "Continuer",
"verifying": "Vérification...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Rechercher...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Impossible de créer un flux sur la Chromecast",
"message_from_server": "Message du serveur : {{message}}",
"next_episode": "Épisode suivant",
"refresh_tracks": "Rafraîchir les pistes",
"audio_tracks": "Pistes audio :",
"playback_state": "État de lecture :",
"index": "Index :",
"continue_watching": "Continuer à regarder",
"go_back": "Retour",
"downloaded_file_title": "Ce fichier est téléchargé",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Afficher plus",
"show_less": "Afficher moins",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Suivant",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "morceaux"
},
"filters": {
"all": "Toutes"
},
"recently_added": "Ajoutés récemment",
"recently_played": "Récemment joué",
"frequently_played": "Fréquemment joué",
"explore": "Explorez",
"top_tracks": "Top chansons",
"play": "Lecture",
"shuffle": "Aléatoire",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "ללא",
"OnlyForced": "רק כפוי"
},
"text_color": "צבע הטקסט",
"background_color": "צבע רקע",
"outline_color": "צבע קו מתאר",
"outline_thickness": "עובי קו מתאר",
"background_opacity": "שקיפות רקע",
"outline_opacity": "אטימות קו מתאר",
"bold_text": "טקסט בולט",
"colors": {
"Black": "שחור",
"Gray": "אפור",
"Silver": "כסף",
"White": "לבן",
"Maroon": "חום ערמוני",
"Red": "אדום",
"Fuchsia": "פוקסיה",
"Yellow": "צהוב",
"Olive": "זית",
"Green": "ירוק",
"Teal": "תכלת",
"Lime": "ירוק ליים",
"Purple": "סגול",
"Navy": "כחול כהה",
"Blue": "כחול",
"Aqua": "כחול בהיר"
},
"thickness": {
"None": "ללא",
"Thin": "דק",
"Normal": "רגיל",
"Thick": "עבה"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "נגן וידאו",
"video_player": "נגן וידאו",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "אחר",
"video_orientation": "כיוון וידיאו",
@@ -295,6 +351,11 @@
"UNKNOWN": "לא ידוע"
},
"safe_area_in_controls": "איזור בטוח בפקדים",
"video_player": "נגן וידאו",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (ניסיוני + נגן בתוך נגן)"
},
"show_custom_menu_links": "הצג קישורים לתפריטים מותאמים אישית",
"show_large_home_carousel": "הצג קרוסלה גדולה במסך הבית (בטא)",
"hide_libraries": "הסתר ספריות",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "כמות פרקים מקסימלית לניגון אוטומטי",
"disabled": "כבוי"
},
"downloads": {
"downloads_title": "הורדות"
},
"music": {
"title": "מוזיקה",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "תוספים",
"jellyseerr": {
"jellyseerr_warning": "חלק זה נמצא עדיין בשלבים מוקדמים. צפו שדברים ישתנו.",
"server_url": "כתובת ה-URL של השרת",
"server_url_hint": "לדוגמא: http(s)://your-host.url\n(הוסף פורט במידת הצורך)",
"server_url_placeholder": "כתובת ה-URL של Seerr",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "קרא עוד על Marlin.",
"save_button": "שמור",
"toasts": {
"saved": "נשמר"
}
"saved": "נשמר",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "מחק את כל הקבצים שהורדו",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "מערכת"
},
"toasts": {
"error_deleting_files": "שגיאה במחיקת קבצים"
"error_deleting_files": "שגיאה במחיקת קבצים",
"background_downloads_enabled": "הורדה ברקע מופעלת",
"background_downloads_disabled": "הורדה ברקע כבויה"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "הורדות",
"series": "סדרות",
"movies": "סרטים",
"queue": "תוֹר",
"other_media": "תוכן אחר",
"queue_hint": "התור וההורדות יאבדו בפתיחה מחדש של האפליקציה",
"no_items_in_queue": "אין פרטים בתור",
"no_downloaded_items": "אין פריטים שהורדו",
"delete_all_movies_button": "מחק את כל הסרטים",
"delete_all_series_button": "מחק את כל הסדרות",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "נכשל במחיקת כל הסדרות",
"deleted_media_successfully": "כל שאר התוכן נמחק בהצלחה!",
"failed_to_delete_media": "נכשל במחיקת שאר התוכן",
"download_deleted": "ההורדה נמחקה",
"download_cancelled": "ההורדה בוטלה",
"could_not_delete_download": "לא היה ניתן למחוק את ההורדה",
"download_paused": "ההורדה נעצרה",
"could_not_pause_download": "לא היה ניתן לעצור את ההורדה",
"download_resumed": "ההורדה חודשה",
"could_not_resume_download": "לא היה ניתן לחדש את ההורדה",
"download_completed": "ההורדה הושלמה",
"download_failed": "ההורדה נכשלה",
"download_failed_for_item": "ההורדה נכשלה עבור {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} כבר נמצא בהורדה",
"all_files_deleted": "כל ההורדות נמחקו בהצלחה",
"files_deleted_by_type": "{{count}} {{type}} נמחקו",
"all_files_folders_and_jobs_deleted_successfully": "כל הקבצים, התיקיות והעבודות נמחקו בהצלחה",
"failed_to_clean_cache_directory": "נכשל בניסיון למחוק את תיקיית המטמון",
"could_not_get_download_url_for_item": "לא היה ניתן להשיג את קישור ההורדה של {{itemName}}",
"go_to_downloads": "עבור להורדות",
"file_deleted": "{{item}} נמחק"
}
}
@@ -495,17 +583,16 @@
"none": "ללא",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "חפש...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "נכשל ביצירת זרם עבור Chromecast",
"message_from_server": "הודעה מהשרת: {{message}}",
"next_episode": "הפרק הבא",
"refresh_tracks": "רענן רצועות",
"audio_tracks": "רצועות שמע:",
"playback_state": "מצב ניגון:",
"index": "מיקום:",
"continue_watching": "המשך לצפות",
"go_back": "חזור",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "הצג עוד",
"show_less": "הצג פחות",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "הבא",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Nincs",
"OnlyForced": "Csak Kényszerített"
},
"text_color": "Szövegszín",
"background_color": "Háttérszín",
"outline_color": "Körvonal színe",
"outline_thickness": "Körvonal Vastagsága",
"background_opacity": "Háttér Áttetszőség",
"outline_opacity": "Körvonal Áttetszőség",
"bold_text": "Félkövér Szöveg",
"colors": {
"Black": "Fekete",
"Gray": "Szürke",
"Silver": "Ezüst",
"White": "Fehér",
"Maroon": "Sötétvörös",
"Red": "Piros",
"Fuchsia": "Fukszia",
"Yellow": "Sárga",
"Olive": "Oliva",
"Green": "Zöld",
"Teal": "Türkiz",
"Lime": "Lime",
"Purple": "Lila",
"Navy": "Sötétkék",
"Blue": "Kék",
"Aqua": "Türkizkék"
},
"thickness": {
"None": "Nincs",
"Thin": "Vékony",
"Normal": "Normál",
"Thick": "Vastag"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Egyéb",
"video_orientation": "Videó Tájolás",
@@ -295,6 +351,11 @@
"UNKNOWN": "Ismeretlen"
},
"safe_area_in_controls": "Biztonsági Sáv a Vezérlőkben",
"video_player": "Videólejátszó",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Kísérleti + PiP)"
},
"show_custom_menu_links": "Egyéni Menülinkek Megjelenítése",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "Könyvtárak Elrejtése",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Max. Auto. Epizódlejátszás",
"disabled": "Letiltva"
},
"downloads": {
"downloads_title": "Letöltések"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Bővítmények",
"jellyseerr": {
"jellyseerr_warning": "Ez az integráció még korai stádiumban van. Számíts a változásokra.",
"server_url": "Szerver URL",
"server_url_hint": "Példa: http(s)://a-te-szolgáltatód.url\n(adj meg portot, ha szükséges)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Tudj Meg Többet a Marlinról",
"save_button": "Mentés",
"toasts": {
"saved": "Mentve"
}
"saved": "Mentve",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Minden Letöltött Fájl Törlése",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Rendszer"
},
"toasts": {
"error_deleting_files": "Hiba a Fájlok Törlésekor"
"error_deleting_files": "Hiba a Fájlok Törlésekor",
"background_downloads_enabled": "Background downloads enabled",
"background_downloads_disabled": "Background downloads disabled"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Letöltések",
"series": "Sorozatok",
"movies": "Filmek",
"queue": "Sor",
"other_media": "Other media",
"queue_hint": "A sor és a letöltések az alkalmazás újraindításakor elvesznek",
"no_items_in_queue": "Nincs Elem a Sorban",
"no_downloaded_items": "Nincsenek Letöltött Elemek",
"delete_all_movies_button": "Összes Film Törlése",
"delete_all_series_button": "Összes Sorozat Törlése",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Nem Sikerült Törölni Az Összes Sorozatot",
"deleted_media_successfully": "Deleted other media Successfully!",
"failed_to_delete_media": "Failed to Delete other media",
"download_deleted": "Letöltés Törölve",
"download_cancelled": "Download Cancelled",
"could_not_delete_download": "Nem Sikerült Törölni a Letöltést",
"download_paused": "Letöltés Szüneteltetve",
"could_not_pause_download": "Nem Sikerült Szüneteltetni a Letöltést",
"download_resumed": "Letöltés Folytatva",
"could_not_resume_download": "Nem Sikerült Folytatni a Letöltést",
"download_completed": "Letöltés Befejezve",
"download_failed": "Download Failed",
"download_failed_for_item": "A(z) {{item}} letöltése sikertelen - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "Minden fájl, mappa és feladat sikeresen törölve",
"failed_to_clean_cache_directory": "Failed to clean cache directory",
"could_not_get_download_url_for_item": "Could not get download URL for {{itemName}}",
"go_to_downloads": "Ugrás a Letöltésekhez",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Keresés...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "A Chromecast stream létrehozása sikertelen volt",
"message_from_server": "Üzenet a szervertől: {{message}}",
"next_episode": "Következő Epizód",
"refresh_tracks": "Sávok Frissítése",
"audio_tracks": "Hangsávok:",
"playback_state": "Lejátszás Állapota:",
"index": "Index:",
"continue_watching": "Folytatás",
"go_back": "Vissza",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Több Megjelenítése",
"show_less": "Kevesebb Megjelenítése",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Következő",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -4,8 +4,8 @@
"error_title": "Errore",
"login_title": "Accesso",
"login_to_title": "Accedi a",
"select_user": "Seleziona un utente per accedere",
"add_user_to_login": "Aggiungi un utente per accedere",
"select_user": "Select a user to log in",
"add_user_to_login": "Add a user to log in",
"add_user": "Add User",
"username_placeholder": "Nome utente",
"password_placeholder": "Password",
@@ -33,7 +33,7 @@
"connect_button": "Connetti",
"previous_servers": "server precedente",
"clear_button": "Cancella",
"swipe_to_remove": "Scorri per rimuovere",
"swipe_to_remove": "Swipe to remove",
"search_for_local_servers": "Ricerca dei server locali",
"searching": "Cercando...",
"servers": "Server",
@@ -41,46 +41,46 @@
"session_expired": "Session Expired",
"please_login_again": "La tua sessione è scaduta. Si prega di eseguire nuovamente l'accesso.",
"remove_saved_login": "Remove Saved Login",
"remove_saved_login_description": "Questo rimuoverà le tue credenziali salvate per questo server. Dovrai inserire nuovamente il tuo nome utente e la password la prossima volta.",
"accounts_count": "Account {{count}}",
"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",
"select_account": "Select Account",
"add_account": "Add Account",
"remove_account_description": "Questo rimuoverà le credenziali salvate per {{username}}.",
"remove_account_description": "This will remove the saved credentials for {{username}}.",
"remove_server": "Remove Server",
"remove_server_description": "Questo rimuove {{server}} e tutti gli account salvati dall'elenco.",
"remove_server_description": "This will remove {{server}} and all saved accounts from your list.",
"select_your_server": "Select Your Server",
"add_server_to_get_started": "Aggiungi un server per iniziare",
"add_server_to_get_started": "Add a server to get started",
"add_server": "Add Server",
"change_server": "Change Server"
},
"save_account": {
"title": "Save Account",
"save_for_later": "Salva questo account",
"save_for_later": "Save this account",
"security_option": "Security Option",
"no_protection": "Nessuna Protezione",
"no_protection_desc": "Accesso rapido senza autenticazione",
"pin_code": "Codice PIN",
"pin_code_desc": "PIN di 4 cifre richiesto quando si cambia utente",
"password": "Inserisci nuovamente la password",
"password_desc": "Password richiesta quando si cambia",
"save_button": "Salva",
"cancel_button": "Annulla"
"no_protection": "No protection",
"no_protection_desc": "Quick login without authentication",
"pin_code": "PIN code",
"pin_code_desc": "4-digit PIN required when switching",
"password": "Re-enter password",
"password_desc": "Password required when switching",
"save_button": "Save",
"cancel_button": "Cancel"
},
"pin": {
"enter_pin": "Inserisci il PIN",
"enter_pin_for": "Inserisci PIN per {{username}}",
"enter_4_digits": "Inserisci 4 cifre",
"invalid_pin": "PIN non valido",
"enter_pin": "Enter PIN",
"enter_pin_for": "Enter PIN for {{username}}",
"enter_4_digits": "Enter 4 digits",
"invalid_pin": "Invalid PIN",
"setup_pin": "Set Up PIN",
"confirm_pin": "Conferma PIN",
"pins_dont_match": "I PIN non corrispondono",
"forgot_pin": "Hai dimenticato il PIN?",
"forgot_pin_desc": "Le credenziali salvate verranno rimosse"
"confirm_pin": "Confirm PIN",
"pins_dont_match": "PINs don't match",
"forgot_pin": "Forgot PIN?",
"forgot_pin_desc": "Your saved credentials will be removed"
},
"password": {
"enter_password": "Enter Password",
"enter_password_for": "Inserire la password per {{username}}",
"invalid_password": "Password errata"
"enter_password_for": "Enter password for {{username}}",
"invalid_password": "Invalid password"
},
"home": {
"checking_server_connection": "Controllo connessione server...",
@@ -95,7 +95,7 @@
"oops": "Ops!",
"error_message": "Qualcosa è andato storto. \nEffetturare il logout e riaccedere.",
"continue_watching": "Continua a guardare",
"continue": "Continua",
"continue": "Continue",
"next_up": "Prossimo",
"continue_and_next_up": "Continue & Next Up",
"recently_added_in": "Aggiunti di recente a {{libraryName}}",
@@ -123,7 +123,7 @@
"title": "Switch User",
"account": "Account",
"switch_user": "Switch User on This Server",
"current": "attuale"
"current": "current"
},
"categories": {
"title": "Categorie"
@@ -143,37 +143,37 @@
"show_series_poster_on_episode": "Show Series Poster on Episodes",
"theme_music": "Theme Music",
"display_size": "Display Size",
"display_size_small": "Piccolo",
"display_size_default": "Predefinito",
"display_size_large": "Grande",
"display_size_small": "Small",
"display_size_default": "Default",
"display_size_large": "Large",
"display_size_extra_large": "Extra Large"
},
"network": {
"title": "Rete",
"local_network": "Rete locale",
"auto_switch_enabled": "Cambia automaticamente quando sei in casa",
"title": "Network",
"local_network": "",
"auto_switch_enabled": "Auto-switch when at home",
"auto_switch_description": "Automatically switch to local URL when connected to home WiFi",
"local_url": "URL locale",
"local_url": "Local URL",
"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",
"home_wifi_networks": "Home WiFi Networks",
"add_current_network": "Aggiungi \"{{ssid}}\"",
"add_current_network": "Add \"{{ssid}}\"",
"not_connected_to_wifi": "Not connected to WiFi",
"no_networks_configured": "Nessuna rete configurata",
"no_networks_configured": "No networks configured",
"add_network_hint": "Add your home WiFi network to enable auto-switching",
"current_wifi": "WiFi Attuale",
"using_url": "Sta utilizzando",
"local": "URL locale",
"remote": "URL remoto",
"not_connected": "Non connesso",
"local": "Local URL",
"remote": "Remote URL",
"not_connected": "Not connected",
"current_server": "Current Server",
"remote_url": "URL remoto",
"active_url": "URL Attivo",
"not_configured": "Non configurato",
"network_added": "Rete aggiunta",
"network_already_added": "Rete già inserita",
"remote_url": "Remote URL",
"active_url": "Active URL",
"not_configured": "Not configured",
"network_added": "Network added",
"network_already_added": "Network already added",
"no_wifi_connected": "Not connected to WiFi",
"permission_denied": "Autorizzazione alla posizione negata",
"permission_denied": "Location permission denied",
"permission_denied_explanation": "Location permission is required to detect WiFi network for auto-switching. Please enable it in Settings."
},
"user_info": {
@@ -202,9 +202,9 @@
"buffer": {
"title": "Buffer Settings",
"cache_mode": "Cache Mode",
"cache_auto": "Automatico",
"cache_yes": "Abilitato",
"cache_no": "Disabilitato",
"cache_auto": "Auto",
"cache_yes": "Enabled",
"cache_no": "Disabled",
"buffer_duration": "Buffer Duration",
"max_cache_size": "Max Cache Size",
"max_backward_cache": "Max Backward Cache"
@@ -212,7 +212,7 @@
"vo_driver": {
"title": "Video Output",
"vo_mode": "VO Driver",
"gpu_next": "gpu-next (Consigliato)",
"gpu_next": "gpu-next (Recommended)",
"gpu": "gpu"
},
"gesture_controls": {
@@ -224,7 +224,7 @@
"right_side_volume": "Controllo Volume Laterale Destro",
"right_side_volume_description": "Scorri verso l'alto/verso il basso per regolare il volume",
"hide_volume_slider": "Hide Volume Slider",
"hide_volume_slider_description": "Nascondi il cursore del volume nel lettore video",
"hide_volume_slider_description": "Hide the volume slider in the video player",
"hide_brightness_slider": "Hide Brightness Slider",
"hide_brightness_slider_description": "Hide the brightness slider in the video player"
},
@@ -261,6 +261,43 @@
"None": "Nessuno",
"OnlyForced": "Solo forzati"
},
"text_color": "Colore Del Testo",
"background_color": "Colore Di Sfondo",
"outline_color": "Colore Contorno",
"outline_thickness": "Spessore Contorno",
"background_opacity": "Opacità Dello Sfondo",
"outline_opacity": "Opacità Contorno",
"bold_text": "Bold Text",
"colors": {
"Black": "Nero",
"Gray": "Grigio",
"Silver": "Argento",
"White": "Bianco",
"Maroon": "Maroon",
"Red": "Rosso",
"Fuchsia": "Fuchsia",
"Yellow": "Giallo",
"Olive": "Olive",
"Green": "Verde",
"Teal": "Teal",
"Lime": "Lime",
"Purple": "Viola",
"Navy": "Marina",
"Blue": "Blu",
"Aqua": "Aqua"
},
"thickness": {
"None": "Nessuno",
"Thin": "Sottile",
"Normal": "Normale",
"Thick": "Spessa"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Altro",
"video_orientation": "Orientamento del video",
@@ -295,6 +351,11 @@
"UNKNOWN": "Sconosciuto"
},
"safe_area_in_controls": "Area sicura per i controlli",
"video_player": "Video player",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Sperimentale + PiP)"
},
"show_custom_menu_links": "Mostra i link del menu personalizzato",
"show_large_home_carousel": "Mostra Carosello Grande nella Home (beta)",
"hide_libraries": "Nascondi Librerie",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Numero Massimo Di Episodi Riproduzione Automatica",
"disabled": "Disabilitato"
},
"downloads": {
"downloads_title": "Scaricamento"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Plugin",
"jellyseerr": {
"jellyseerr_warning": "Questa integrazione è in fase iniziale. Aspettarsi cambiamenti.",
"server_url": "URL del Server",
"server_url_hint": "Esempio: http(s)://tuo-host.url\n(aggiungere la porta se richiesto)",
"server_url_placeholder": "URL di Jellyseerr...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Leggi di più su Marlin.",
"save_button": "Salva",
"toasts": {
"saved": "Salvato"
}
"saved": "Salvato",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Cancella Tutti i File Scaricati",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Sistema"
},
"toasts": {
"error_deleting_files": "Errore nella cancellazione dei file"
"error_deleting_files": "Errore nella cancellazione dei file",
"background_downloads_enabled": "Scaricamento in background abilitato",
"background_downloads_disabled": "Scaricamento in background disabilitato"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Scaricati",
"series": "Serie TV",
"movies": "Film",
"queue": "Coda",
"other_media": "Altri supporti",
"queue_hint": "La coda e gli elementi scaricati saranno persi con il riavvio dell'app",
"no_items_in_queue": "Nessun elemento in coda",
"no_downloaded_items": "Nessun elemento scaricato",
"delete_all_movies_button": "Cancella tutti i film",
"delete_all_series_button": "Cancella tutte le serie TV",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Impossibile eliminare tutte le serie TV",
"deleted_media_successfully": "Eliminato altri supporti con successo!",
"failed_to_delete_media": "Impossibile eliminare altri media",
"download_deleted": "Download Eliminato",
"download_cancelled": "Scaricamento annullato",
"could_not_delete_download": "Impossibile Eliminare Il Download",
"download_paused": "Download In Pausa",
"could_not_pause_download": "Impossibile Sbloccare Il Download",
"download_resumed": "Download Ripreso",
"could_not_resume_download": "Impossibile Riprendere Il Download",
"download_completed": "Scaricamento completato",
"download_failed": "Scaricamento non riuscito",
"download_failed_for_item": "Scaricamento fallito per {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} è già in download",
"all_files_deleted": "Tutti i Download Eliminati con Successo",
"files_deleted_by_type": "{{count}} {{type}} cancellati",
"all_files_folders_and_jobs_deleted_successfully": "Tutti i file, le cartelle e i processi sono stati eliminati con successo.",
"failed_to_clean_cache_directory": "Pulizia della directory della cache non riuscita",
"could_not_get_download_url_for_item": "Impossibile ottenere l'URL di download per {{itemName}}",
"go_to_downloads": "Vai agli elementi scaricati",
"file_deleted": "{{item}} cancellato"
}
}
@@ -495,17 +583,16 @@
"none": "Nulla",
"track": "Traccia",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Cerca...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Impossibile creare uno stream per Chromecast",
"message_from_server": "Messaggio dal server",
"next_episode": "Prossimo Episodio",
"refresh_tracks": "Aggiorna tracce",
"audio_tracks": "Tracce audio:",
"playback_state": "Stato della riproduzione:",
"index": "Indice:",
"continue_watching": "Continua a guardare",
"go_back": "Indietro",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Mostra di più",
"show_less": "Mostra di meno",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Prossimo",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "なし",
"OnlyForced": "強制のみ"
},
"text_color": "テキストの色",
"background_color": "背景色",
"outline_color": "アウトラインの色",
"outline_thickness": "概要 厚さ",
"background_opacity": "背景の透明度",
"outline_opacity": "アウトラインの透明度",
"bold_text": "Bold Text",
"colors": {
"Black": "ブラック",
"Gray": "グレー",
"Silver": "シルバー",
"White": "白",
"Maroon": "Maroon",
"Red": "赤",
"Fuchsia": "Fuchsia",
"Yellow": "黄色",
"Olive": "オリーブ",
"Green": "緑",
"Teal": "ティール",
"Lime": "黄緑",
"Purple": "パープル",
"Navy": "海軍format@@0",
"Blue": "青",
"Aqua": "Aqua"
},
"thickness": {
"None": "なし",
"Thin": "細いです",
"Normal": "標準",
"Thick": "濃厚な"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "その他",
"video_orientation": "動画の向き",
@@ -295,6 +351,11 @@
"UNKNOWN": "不明"
},
"safe_area_in_controls": "コントロールの安全エリア",
"video_player": "Video player",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "カスタムメニューのリンクを表示",
"show_large_home_carousel": "大きなヒーローBeta",
"hide_libraries": "ライブラリを非表示",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "自動再生エピソードの最大数",
"disabled": "無効"
},
"downloads": {
"downloads_title": "ダウンロード"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "プラグイン",
"jellyseerr": {
"jellyseerr_warning": "この統合はまだ初期段階です。状況が変化する可能性があります。",
"server_url": "サーバーURL",
"server_url_hint": "例: http(s)://your-host.url\n(必要に応じてポートを追加)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Marlinについて詳しく読む。",
"save_button": "保存",
"toasts": {
"saved": "保存しました"
}
"saved": "保存しました",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "すべてのダウンロードファイルを削除",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "システム"
},
"toasts": {
"error_deleting_files": "ファイルの削除エラー"
"error_deleting_files": "ファイルの削除エラー",
"background_downloads_enabled": "バックグラウンドでのダウンロードは有効です",
"background_downloads_disabled": "バックグラウンドでのダウンロードは無効です"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "ダウンロード",
"series": "TVシリーズ",
"movies": "映画",
"queue": "キュー",
"other_media": "その他のメディア",
"queue_hint": "アプリを再起動するとキューとダウンロードは失われます",
"no_items_in_queue": "キューにアイテムがありません",
"no_downloaded_items": "ダウンロードしたアイテムはありません",
"delete_all_movies_button": "すべての映画を削除",
"delete_all_series_button": "すべてのシリーズを削除",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "すべてのシリーズを削除できませんでした",
"deleted_media_successfully": "他のメディアを削除しました!",
"failed_to_delete_media": "他のメディアの削除に失敗しました",
"download_deleted": "ダウンロードが削除されました",
"download_cancelled": "ダウンロードをキャンセルしました",
"could_not_delete_download": "ダウンロードを削除できませんでした",
"download_paused": "ダウンロードを一時停止しました",
"could_not_pause_download": "ダウンロードを一時停止できませんでした",
"download_resumed": "ダウンロード再開",
"could_not_resume_download": "ダウンロードを再開できませんでした",
"download_completed": "ダウンロードが完了しました",
"download_failed": "ダウンロードに失敗しました",
"download_failed_for_item": "{{item}}のダウンロードに失敗しました - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "すべてのファイル、フォルダ、ジョブが正常に削除されました",
"failed_to_clean_cache_directory": "キャッシュディレクトリのクリーンアップに失敗しました",
"could_not_get_download_url_for_item": "{{itemName}} のダウンロードURLを取得できませんでした",
"go_to_downloads": "ダウンロードに移動",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "検索...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Chromecastのストリームを作成できませんでした",
"message_from_server": "サーバーからのメッセージ",
"next_episode": "次のエピソード",
"refresh_tracks": "トラックを更新",
"audio_tracks": "音声トラック:",
"playback_state": "再生状態:",
"index": "インデックス:",
"continue_watching": "視聴を続ける",
"go_back": "戻る",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "もっと見る",
"show_less": "少なく表示",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "次",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "None",
"OnlyForced": "OnlyForced"
},
"text_color": "Text Color",
"background_color": "Background Color",
"outline_color": "Outline Color",
"outline_thickness": "Outline Thickness",
"background_opacity": "Background Opacity",
"outline_opacity": "Outline Opacity",
"bold_text": "Bold Text",
"colors": {
"Black": "검정색",
"Gray": "회색",
"Silver": "은색",
"White": "흰색",
"Maroon": "밤색",
"Red": "빨간색",
"Fuchsia": "분홍색",
"Yellow": "노란색",
"Olive": "올리브 색",
"Green": "녹색",
"Teal": "청록색",
"Lime": "라임색",
"Purple": "보라색",
"Navy": "남색",
"Blue": "파란색",
"Aqua": "아쿠아색"
},
"thickness": {
"None": "없음",
"Thin": "얇게",
"Normal": "보통",
"Thick": "굵게"
},
"subtitle_color": "자막 색상",
"subtitle_background_color": "배경 색상",
"subtitle_font": "자막 폰트",
"ksplayer_title": "KSPlayer 설정",
"hardware_decode": "하드웨어 디코딩",
"hardware_decode_description": "비디오 디코딩에 하드웨어 가속을 사용하십시오. 재생 문제가 발생하는 경우 비활성화하십시오.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC 자막 설정",
"hint": "VLC 플레이어의 자막 표시 방식을 설정하세요. 변경 사항은 다음 재생 시 적용됩니다.",
"text_color": "글자색",
"background_color": "배경 색상",
"background_opacity": "배경 투명도",
"outline_color": "외곽선 색상",
"outline_opacity": "외곽선 투명도",
"outline_thickness": "외곽선 굵기",
"bold": "굵은 글씨",
"margin": "아래쪽 여백"
},
"video_player": {
"title": "비디오 플레이어",
"video_player": "비디오 플레이어",
"video_player_description": "iOS 사용자는 비디오 플레이어를 선택하세요.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Other",
"video_orientation": "Video Orientation",
@@ -295,6 +351,11 @@
"UNKNOWN": "Unknown"
},
"safe_area_in_controls": "컨트롤 안전 영역",
"video_player": "Video Player",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "사용자 지정 메뉴 링크 표시",
"show_large_home_carousel": "대형 홈 슬라이드 배너 표시 (베타)",
"hide_libraries": "라이브러리 숨기기",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Max Auto Play Episode Count",
"disabled": "Disabled"
},
"downloads": {
"downloads_title": "Downloads"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Plugins",
"jellyseerr": {
"jellyseerr_warning": "This integration is in its early stages. Expect things to change.",
"server_url": "Server URL",
"server_url_hint": "Example: http(s)://your-host.url\n(add port if required)",
"server_url_placeholder": "Seerr URL",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Read More About Marlin.",
"save_button": "Save",
"toasts": {
"saved": "Saved"
}
"saved": "Saved",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "시리즈 추천",
"enable_promoted_watchlists": "추천 관심 목록",
@@ -375,7 +445,8 @@
"refresh_from_server": "서버에서 설정 새로고침"
},
"kefinTweaks": {
"watchlist_enabler": "관심 목록 통합 기능 활성화"
"watchlist_enabler": "관심 목록 통합 기능 활성화",
"watchlist_button": "관심 목록 연동 켜기/끄기"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Delete All Downloaded Files",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "음악 캐시가 삭제되었습니다",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "System"
},
"toasts": {
"error_deleting_files": "Error Deleting Files"
"error_deleting_files": "Error Deleting Files",
"background_downloads_enabled": "Background downloads enabled",
"background_downloads_disabled": "Background downloads disabled"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Downloads",
"series": "TV-Series",
"movies": "Movies",
"queue": "Queue",
"other_media": "Other media",
"queue_hint": "Queue and downloads will be lost on app restart",
"no_items_in_queue": "No Items in Queue",
"no_downloaded_items": "No Downloaded Items",
"delete_all_movies_button": "Delete All Movies",
"delete_all_series_button": "Delete All TV-Series",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Failed to Delete All TV-Series",
"deleted_media_successfully": "Deleted other media Successfully!",
"failed_to_delete_media": "Failed to Delete other 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_for_item": "Download failed for {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"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": "Could not get download URL for {{itemName}}",
"go_to_downloads": "Go to Downloads",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Search...",
@@ -604,6 +691,10 @@
"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",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Show More",
"show_less": "Show Less",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Next",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Geen",
"OnlyForced": "Alleen Geforceerd"
},
"text_color": "Tekst kleur",
"background_color": "Achtergrond Kleur",
"outline_color": "Kleur omlijning",
"outline_thickness": "Dikte omlijning",
"background_opacity": "Transparantie achtergrond",
"outline_opacity": "Doorzichtigheid omlijning",
"bold_text": "Bold Text",
"colors": {
"Black": "Zwart",
"Gray": "Grijs",
"Silver": "Zilver",
"White": "Wit",
"Maroon": "Kastanjebruin",
"Red": "Rood",
"Fuchsia": "Fuchsia",
"Yellow": "Geel",
"Olive": "Olijf",
"Green": "Groen",
"Teal": "Groenblauw",
"Lime": "Lichtgroen",
"Purple": "Paars",
"Navy": "Marine",
"Blue": "Blauw",
"Aqua": "Aqua"
},
"thickness": {
"None": "Geen",
"Thin": "Dun",
"Normal": "normaal",
"Thick": "Dikke"
},
"subtitle_color": "Kleur ondertiteling",
"subtitle_background_color": "Achtergrondkleur",
"subtitle_font": "Lettertype ondertitels",
"ksplayer_title": "KSPlayer Instellingen",
"hardware_decode": "Hardware Acceleratie",
"hardware_decode_description": "Gebruik hardware acceleratie voor video-decodering. Uitschakelen als u problemen met afspelen ondervindt.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC ondertitel instellingen",
"hint": "Aanpassen van ondertiteling voor VLC-speler. Wijzigingen worden toegepast bij het afspelen.",
"text_color": "Tekstkleur",
"background_color": "Achtergrondkleur",
"background_opacity": "Doorzichtigheid achtergrond",
"outline_color": "Kleur omlijning",
"outline_opacity": "Omtrek opaciteit",
"outline_thickness": "Omtrek dikte",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Videospeler",
"video_player": "Videospeler",
"video_player_description": "Kies welke videospeler gebruikt moet worden op iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Andere",
"video_orientation": "Video oriëntatie",
@@ -295,6 +351,11 @@
"UNKNOWN": "Onbekend"
},
"safe_area_in_controls": "Veilig gebied in bedieningen",
"video_player": "Video player",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimentele + PiP)"
},
"show_custom_menu_links": "Aangepaste menulinks tonen",
"show_large_home_carousel": "Toon grote carrousel op startpagina (bèta)",
"hide_libraries": "Verberg Bibliotheken",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Max Automatisch Aflevering Aantal",
"disabled": "Uitgeschakeld"
},
"downloads": {
"downloads_title": "Downloads"
},
"music": {
"title": "Muziek",
"playback_title": "Afspelen",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Uitbreidingen",
"jellyseerr": {
"jellyseerr_warning": "Deze integratie is nog in een vroeg stadium. Verwacht dat zaken nog veranderen.",
"server_url": "Server-URL",
"server_url_hint": "Voorbeeld: http(s)://je-host.url\n(indien nodig: voeg de poort toe)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Lees meer over Marlin.",
"save_button": "Opslaan",
"toasts": {
"saved": "Opgeslagen"
}
"saved": "Opgeslagen",
"refreshed": "Instellingen zijn vernieuwd vanaf server"
},
"refresh_from_server": "Ververs Instellingen van Server"
},
"streamystats": {
"enable_streamystats": "Streamystats inschakelen",
"disable_streamystats": "Streamystats Uitschakelen",
"enable_search": "Gebruik voor Zoeken",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"streamystats_search_hint": "Vul de URL van de Streamystats server in. De URL moet http of https bevatten en optioneel de poort.",
"read_more_about_streamystats": "Lees Meer over Streamystats.",
"save_button": "Opslaan",
"save": "Opslaan",
"features_title": "Functies",
"home_sections_title": "Thuis Secties",
"enable_movie_recommendations": "Film Aanbevelingen",
"enable_series_recommendations": "Series Aanbevelingen",
"enable_promoted_watchlists": "Gepromote Kijklijst",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Verwijder alle gedownloade bestanden",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} gecached",
"music_cache_cleared": "Muziek cache gewist",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Systeem"
},
"toasts": {
"error_deleting_files": "Fout bij het verwijderen van bestanden"
"error_deleting_files": "Fout bij het verwijderen van bestanden",
"background_downloads_enabled": "Downloads op de achtergrond ingeschakeld",
"background_downloads_disabled": "Downloads op de achtergrond uitgeschakeld"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Downloads",
"series": "Series",
"movies": "Films",
"queue": "Wachtrij",
"other_media": "Andere media",
"queue_hint": "Wachtrij en downloads verdwijnen bij een herstart van de app",
"no_items_in_queue": "Geen items in wachtrij",
"no_downloaded_items": "Geen gedownloade items",
"delete_all_movies_button": "Verwijder alle films",
"delete_all_series_button": "Verwijder alle Series",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Alle series zijn niet verwijderd",
"deleted_media_successfully": "Andere media succesvol verwijderd!",
"failed_to_delete_media": "Verwijderen van andere media mislukt",
"download_deleted": "Download verwijderd",
"download_cancelled": "Download geannuleerd",
"could_not_delete_download": "Kon download niet verwijderen",
"download_paused": "Download gepauzeerd",
"could_not_pause_download": "Kan niet pauzeren download",
"download_resumed": "Download hervat",
"could_not_resume_download": "Kon de download niet hervatten",
"download_completed": "Download afgerond",
"download_failed": "Download Mislukt",
"download_failed_for_item": "Download gefaald voor {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} wordt al gedownload",
"all_files_deleted": "Alle Bestanden Succesvol Gedownload",
"files_deleted_by_type": "{{count}} {{type}} verwijderd",
"all_files_folders_and_jobs_deleted_successfully": "Alle bestanden, mappen en taken succesvol verwijderd",
"failed_to_clean_cache_directory": "Opschonen cachemap mislukt",
"could_not_get_download_url_for_item": "Kan download-URL voor {{itemName}} niet ophalen",
"go_to_downloads": "Ga naar downloads",
"file_deleted": "{{item}} verwijderd"
}
}
@@ -495,17 +583,16 @@
"none": "Geen",
"track": "Spoor",
"cancel": "Annuleren",
"stop": "Stop",
"delete": "Verwijderen",
"ok": "Oké",
"remove": "Verwijderen",
"next": "Volgende",
"back": "Terug",
"continue": "Doorgaan",
"verifying": "Verifiëren...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Zoek...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Kon geen stream maken voor Chromecast",
"message_from_server": "Bericht van de server",
"next_episode": "Volgende Aflevering",
"refresh_tracks": "Tracks verversen",
"audio_tracks": "Audio Tracks:",
"playback_state": "Afspeelstatus:",
"index": "Index:",
"continue_watching": "Verder kijken",
"go_back": "Terug",
"downloaded_file_title": "Je hebt dit bestand gedownload",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Toon meer",
"show_less": "Toon minder",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Volgende ",
@@ -798,9 +888,13 @@
"playlists": "Afspeellijsten",
"tracks": "Nummers"
},
"filters": {
"all": "Alle"
},
"recently_added": "Recent toegevoegd",
"recently_played": "Onlangs afgespeeld",
"frequently_played": "Vaak afgespeeld",
"explore": "Ontdek",
"top_tracks": "Top Tracks",
"play": "Afspelen",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -123,7 +123,7 @@
"title": "Switch User",
"account": "Account",
"switch_user": "Switch User on This Server",
"current": "nåværende"
"current": "current"
},
"categories": {
"title": "Categories"
@@ -261,6 +261,43 @@
"None": "Ingen",
"OnlyForced": "Enkelt"
},
"text_color": "Tekst farge",
"background_color": "Bakgrunnsfarge",
"outline_color": "Omrissets farge",
"outline_thickness": "Omriss Tykkelse",
"background_opacity": "Bakgrunns gjennomsiktighet",
"outline_opacity": "Omrissets gjennomsiktighet",
"bold_text": "Bold Text",
"colors": {
"Black": "Svart",
"Gray": "Grå",
"Silver": "Sølv",
"White": "Hvit",
"Maroon": "Rødbrun",
"Red": "Rød",
"Fuchsia": "Fuchsia",
"Yellow": "Gul",
"Olive": "Olivengrønn",
"Green": "Grønn",
"Teal": "Blågrønn",
"Lime": "Limegrønn",
"Purple": "Lilla",
"Navy": "Marineblå",
"Blue": "Blå",
"Aqua": "Vann"
},
"thickness": {
"None": "Ingen",
"Thin": "Tynn",
"Normal": "Vanlig",
"Thick": "Tykk"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Annet",
"video_orientation": "Video Retning",
@@ -295,6 +351,11 @@
"UNKNOWN": "Ukjent"
},
"safe_area_in_controls": "Sikker sone i kontroller",
"video_player": "Video Spiller",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (eksperimentell + PiP)"
},
"show_custom_menu_links": "Vis tilpassede menylenker",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "Skjul biblioteker",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Maks automatisk avspilling Episode Telling",
"disabled": "Deaktivert"
},
"downloads": {
"downloads_title": "Nedlastinger"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Utvidelser",
"jellyseerr": {
"jellyseerr_warning": "Denne integreringen er i tidlige faser. Forvent ting å forandre.",
"server_url": "URL til server",
"server_url_hint": "Eksempel: http(s)://your-host.url\n(legg til port hvis nødvendig)",
"server_url_placeholder": "Seerr URL",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Les mer om Marlin.",
"save_button": "Lagre",
"toasts": {
"saved": "Lagret"
}
"saved": "Lagret",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Slett alle nedlastede filer",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Systemadministrasjon"
},
"toasts": {
"error_deleting_files": "Feil ved sletting av filer"
"error_deleting_files": "Feil ved sletting av filer",
"background_downloads_enabled": "Nedlastinger av bakgrunn aktivert",
"background_downloads_disabled": "Bakgrunnsnedlastinger deaktivert"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Nedlastinger",
"series": "TV-Serier",
"movies": "Filmer",
"queue": "Kø",
"other_media": "Andre medier",
"queue_hint": "Kø og nedlastinger vil gå tapt når appen startes på nytt",
"no_items_in_queue": "Ingen elementer i køen",
"no_downloaded_items": "Ingen nedlastede elementer",
"delete_all_movies_button": "Slett alle filmer",
"delete_all_series_button": "Slett alle TV-Serier",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Kunne ikke slette alle TV-Serier",
"deleted_media_successfully": "Slettet andre media vellykket!",
"failed_to_delete_media": "Kunne ikke slette andre medier",
"download_deleted": "Nedlasting slettet",
"download_cancelled": "Download Cancelled",
"could_not_delete_download": "Kunne ikke slette nedlasting",
"download_paused": "Last ned Pauset",
"could_not_pause_download": "Kunne ikke pause nedlasting",
"download_resumed": "Nedlastingen er gjenopptatt",
"could_not_resume_download": "Kunne ikke fortsette nedlasting",
"download_completed": "Nedlasting fullført",
"download_failed": "Download Failed",
"download_failed_for_item": "Nedlasting feilet for {{item}} {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "Alle filer, mapper og jobber slettet",
"failed_to_clean_cache_directory": "Klarte ikke å tømme mellomlagermappen",
"could_not_get_download_url_for_item": "Kunne ikke hente nedlastings-URL for {{itemName}}",
"go_to_downloads": "Gå til nedlastinger",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Søk...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Kan ikke opprette en strøm for Chromecast",
"message_from_server": "Melding fra tjener: {{message}}",
"next_episode": "Neste Episode",
"refresh_tracks": "Oppdater sporing",
"audio_tracks": "Lyd Tracks:",
"playback_state": "Avspillingsstatus:",
"index": "Indeks:",
"continue_watching": "Fortsett å se",
"go_back": "Gå tilbake",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Vis mer",
"show_less": "Vis mindre",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Neste",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Brak",
"OnlyForced": "Tylko wymuszone"
},
"text_color": "Kolor tekstu",
"background_color": "Kolor tła",
"outline_color": "Kolor konturu",
"outline_thickness": "Grubość konturu",
"background_opacity": "Przezroczystość tła",
"outline_opacity": "Przezroczystość konturu",
"bold_text": "Tekst pogrubiony",
"colors": {
"Black": "Czarny",
"Gray": "Szary",
"Silver": "Srebro",
"White": "Biały",
"Maroon": "Bordowy",
"Red": "Czerwony",
"Fuchsia": "Fuksja",
"Yellow": "Żółty",
"Olive": "Oliwki",
"Green": "Zielony",
"Teal": "Turkusowy",
"Lime": "Limonkowy",
"Purple": "Fioletowy",
"Navy": "Granatowy",
"Blue": "Niebieski",
"Aqua": "Aqua"
},
"thickness": {
"None": "Brak",
"Thin": "Cienka",
"Normal": "Normalny",
"Thick": "Gruba"
},
"subtitle_color": "Kolor napisów",
"subtitle_background_color": "Kolor tła",
"subtitle_font": "Czcionka napisów",
"ksplayer_title": "Ustawienia KSPlayer",
"hardware_decode": "Dekodowanie sprzętowe",
"hardware_decode_description": "Używaj akceleracji sprzętowej dla dekodowania wideo. Wyłącz, jeśli doświadczasz problemów z odtwarzaniem.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "Ustawienia napisów VLC",
"hint": "Personalizuj wygląd napisów dla odtwarzacza VLC. Zmiany zajdą przy następnym odtwarzaniu.",
"text_color": "Kolor tekstu",
"background_color": "Kolor tła",
"background_opacity": "Przezroczystość tła",
"outline_color": "Kolor obrysu",
"outline_opacity": "Przezroczystość obrysu",
"outline_thickness": "Grubość obrysu",
"bold": "Pogrubiony tekst",
"margin": "Dolny margines"
},
"video_player": {
"title": "Odtwarzacz wideo",
"video_player": "Odtwarzacz wideo",
"video_player_description": "Wybierz którego odtwarzacza wideo używać w iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Inne",
"video_orientation": "Orientacja wideo",
@@ -295,6 +351,11 @@
"UNKNOWN": "Nieznana"
},
"safe_area_in_controls": "Bezpieczny obszar w kontrolkach",
"video_player": "Odtwarzacz wideo",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Eksperymentalny + PiP)"
},
"show_custom_menu_links": "Pokaż niestandardowe odnośniki w menu",
"show_large_home_carousel": "Wyświetl Dużą Karuzelę na ekranie głównym (beta)",
"hide_libraries": "Ukryj biblioteki",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Maksymalna liczba odcinków automatycznego odtwarzania",
"disabled": "Wyłączone"
},
"downloads": {
"downloads_title": "Pobieranie"
},
"music": {
"title": "Muzyka",
"playback_title": "Odtwarzanie",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Wtyczki",
"jellyseerr": {
"jellyseerr_warning": "Ta integracja jest na wczesnym etapie. Należy oczekiwać zmian.",
"server_url": "URL serwera",
"server_url_hint": "Przykład: http(s)://twoja-nazwa.url\n(dodaj port, jeśli jest wymagany)",
"server_url_placeholder": "Adres URL Seerr",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Dowiedz się więcej o Marlin.",
"save_button": "Zapisz",
"toasts": {
"saved": "Zapisano"
}
"saved": "Zapisano",
"refreshed": "Ustawienia odświeżone z serwera"
},
"refresh_from_server": "Odśwież ustawienia z serwera"
},
"streamystats": {
"enable_streamystats": "Włącz Streamystats",
"disable_streamystats": "Wyłącz Streamystats",
"enable_search": "Używaj do wyszukiwania",
"url": "Adres URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"streamystats_search_hint": "Wprowadź adres URL dla twojego serwera Streamystats. URL powinien zawierać http lub https i opcjonalnie port.",
"read_more_about_streamystats": "Dowiedz się więcej o Streamystats.",
"save_button": "Zapisz",
"save": "Zapisz",
"features_title": "Funkcje",
"home_sections_title": "Sekcja główna",
"enable_movie_recommendations": "Rekomendacje filmów",
"enable_series_recommendations": "Rekomendację seriali",
"enable_promoted_watchlists": "Promowane listy oglądania",
@@ -375,7 +445,8 @@
"refresh_from_server": "Odśwież ustawienia z serwera"
},
"kefinTweaks": {
"watchlist_enabler": "Aktywuj naszą integrację Listy Oglądania"
"watchlist_enabler": "Aktywuj naszą integrację Listy Oglądania",
"watchlist_button": "Przelącz integrację Listy Oglądania"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Usuń wszystkie pobrane pliki",
"music_cache_title": "Bufor muzyki",
"music_cache_description": "Automatycznie buforuj piosenki w trakcie słuchania dla płynniejszego odtwarzania i wsparcia offline",
"enable_music_cache": "Włącz bufor muzyki",
"clear_music_cache": "Wyczyść bufor muzyki",
"music_cache_size": "Zbuforowano {{size}}",
"music_cache_cleared": "Wyczyszczono bufor muzyki",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "System"
},
"toasts": {
"error_deleting_files": "Błąd podczas usuwania plików"
"error_deleting_files": "Błąd podczas usuwania plików",
"background_downloads_enabled": "Pobieranie w tle włączone",
"background_downloads_disabled": "Pobieranie w tle wyłączone"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Pobrane",
"series": "Seriale",
"movies": "Filmy",
"queue": "Kolejka",
"other_media": "Inne media",
"queue_hint": "Kolejka i pobierania zostaną utracone po ponownym uruchomieniu aplikacji",
"no_items_in_queue": "Brak elementów w kolejce",
"no_downloaded_items": "Brak pobranych elementów",
"delete_all_movies_button": "Usuń wszystkie filmy",
"delete_all_series_button": "Usuń wszystkie seriale",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Nie udało się usunąć wszystkich seriali",
"deleted_media_successfully": "Pomyślnie usunięto inne media!",
"failed_to_delete_media": "Nie udało się usunąć innych mediów",
"download_deleted": "Pobieranie usunięte",
"download_cancelled": "Pobieranie anulowane",
"could_not_delete_download": "Nie można usunąć pobrania",
"download_paused": "Pobieranie wstrzymane",
"could_not_pause_download": "Nie można wstrzymać pobierania",
"download_resumed": "Pobieranie wznowione",
"could_not_resume_download": "Nie można wznowić pobierania",
"download_completed": "Pobieranie zakończone",
"download_failed": "Pobieranie nie powiodło się",
"download_failed_for_item": "Pobieranie nie powiodło się dla {{item}} {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} jest w trakcie pobierania",
"all_files_deleted": "Pomyślnie usunięto wszystkie pobrane",
"files_deleted_by_type": "{{count}} {{type}} usunięto",
"all_files_folders_and_jobs_deleted_successfully": "Wszystkie pliki, foldery i zadania zostały pomyślnie usunięte",
"failed_to_clean_cache_directory": "Nie udało się wyczyścić katalogu pamięci podręcznej",
"could_not_get_download_url_for_item": "Nie można pobrać adresu URL dla {{itemName}}",
"go_to_downloads": "Przejdź do pobranych",
"file_deleted": "Usunięto {{item}}"
}
}
@@ -495,17 +583,16 @@
"none": "Nic",
"track": "Utwór",
"cancel": "Anuluj",
"stop": "Stop",
"delete": "Usuń",
"ok": "OK",
"remove": "Usuń",
"next": "Następne",
"back": "Poprzednie",
"continue": "Kontynuuj",
"verifying": "Weryfikacja...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Szukaj...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Nie udało się utworzyć strumienia dla Chromecasta",
"message_from_server": "Wiadomość z serwera: {{message}}",
"next_episode": "Następny odcinek",
"refresh_tracks": "Odśwież ścieżki",
"audio_tracks": "Ścieżki audio:",
"playback_state": "Stan odtwarzania:",
"index": "Indeks:",
"continue_watching": "Kontynuuj oglądanie",
"go_back": "Wstecz",
"downloaded_file_title": "Ten plik masz już pobrany",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Pokaż więcej",
"show_less": "Pokaż mniej",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Następny",
@@ -798,9 +888,13 @@
"playlists": "Playlisty",
"tracks": "utwory"
},
"filters": {
"all": "Wszystkie"
},
"recently_added": "Ostatnio dodano",
"recently_played": "Ostatnio odtwarzano",
"frequently_played": "Często odtwarzane",
"explore": "Odkrywaj",
"top_tracks": "Popularne utwory",
"play": "Odtwórz",
"shuffle": "Losuj",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Nenhuma",
"OnlyForced": "Somente Forçado"
},
"text_color": "Cor do texto",
"background_color": "Cor de fundo",
"outline_color": "Cor do contorno",
"outline_thickness": "Espessura do Contorno",
"background_opacity": "Opacidade de fundo",
"outline_opacity": "Opacidade do Contorno",
"bold_text": "Texto em negrito",
"colors": {
"Black": "Preto",
"Gray": "Cinzento",
"Silver": "Prata",
"White": "Branco",
"Maroon": "Castanho",
"Red": "Vermelho",
"Fuchsia": "Fuchsia",
"Yellow": "Amarelo",
"Olive": "Verde-oliva",
"Green": "Verde",
"Teal": "Verde-azulado",
"Lime": "Verde-limão",
"Purple": "Roxo",
"Navy": "Azul-marinho",
"Blue": "Azul",
"Aqua": "Água"
},
"thickness": {
"None": "Nenhuma",
"Thin": "Magro",
"Normal": "Normal",
"Thick": "Grosso"
},
"subtitle_color": "Cor da legenda",
"subtitle_background_color": "Cor de fundo",
"subtitle_font": "Fonte da legenda",
"ksplayer_title": "Configurações do KSPlayer",
"hardware_decode": "Decodificação por hardware",
"hardware_decode_description": "Use aceleração de hardware para decodificação de vídeo. Desative se você tiver problemas de reprodução.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Reprodutor de Vídeo",
"video_player": "Reprodutor de Vídeo",
"video_player_description": "Escolha qual player de vídeo usar no iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Outros",
"video_orientation": "Orientação do Vídeo",
@@ -295,6 +351,11 @@
"UNKNOWN": "Desconhecido"
},
"safe_area_in_controls": "Área segura nos controles",
"video_player": "Reprodutor de Vídeo",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "Mostrar Links de Menu Personalizado",
"show_large_home_carousel": "Mostrar Carrossel Grande (beta)",
"hide_libraries": "Ocultar bibliotecas",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Contagem máxima de episódios de reprodução automática",
"disabled": "Desabilitado"
},
"downloads": {
"downloads_title": "Downloads"
},
"music": {
"title": "Música",
"playback_title": "Reproduzir",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Complementos",
"jellyseerr": {
"jellyseerr_warning": "Essa integração está em suas fases iniciais. Espere que as coisas mudem.",
"server_url": "URL do servidor",
"server_url_hint": "Exemplo: http(s)://seu-host.url\n(adicionar porta se necessário)",
"server_url_placeholder": "URL do Seerr",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Leia mais sobre Marlin.",
"save_button": "Salvar",
"toasts": {
"saved": "Salvo"
}
"saved": "Salvo",
"refreshed": "Configurações atualizadas do servidor"
},
"refresh_from_server": "Atualizar as configurações do servidor"
},
"streamystats": {
"enable_streamystats": "Ativar Streamystats",
"disable_streamystats": "Desativar streamystats",
"enable_search": "Usar para Pesquisa",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"streamystats_search_hint": "Digite a URL para seu servidor de StreamyStats. A URL deve incluir http ou https e, opcionalmente, a porta.",
"read_more_about_streamystats": "Leia mais sobre Streamystats.",
"save_button": "Salvar",
"save": "Salvar",
"features_title": "Funcionalidades",
"home_sections_title": "Seções da Página Inicial",
"enable_movie_recommendations": "Recomendações de filmes",
"enable_series_recommendations": "Recomendações de Séries",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Atualizar Configurações do Servidor"
},
"kefinTweaks": {
"watchlist_enabler": "Ative nossa integração de Lista de Interesses"
"watchlist_enabler": "Ative nossa integração de Lista de Interesses",
"watchlist_button": "Ativar/desativar Lista de Interesses"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Excluir todos os arquivos baixados",
"music_cache_title": "Cache de Música",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Habilitar Cache de Música",
"clear_music_cache": "Limpar Cache de Música",
"music_cache_size": "{{size}} em cache",
"music_cache_cleared": "Cache de música limpo",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Sistema"
},
"toasts": {
"error_deleting_files": "Erro ao excluir arquivos"
"error_deleting_files": "Erro ao excluir arquivos",
"background_downloads_enabled": "Downloads em segundo plano ativados",
"background_downloads_disabled": "Downloads em segundo plano desativados"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Downloads",
"series": "TV-Séries",
"movies": "Filmes",
"queue": "Fila",
"other_media": "Outras mídias",
"queue_hint": "A fila e os downloads serão perdidos ao reiniciar o aplicativo",
"no_items_in_queue": "Nenhum item na fila",
"no_downloaded_items": "Nenhum item baixado",
"delete_all_movies_button": "Excluir todos os filmes",
"delete_all_series_button": "Excluir todas as séries",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Falha ao excluir todas as séries",
"deleted_media_successfully": "Outras mídias excluídas com sucesso!",
"failed_to_delete_media": "Falha ao excluir outras mídias",
"download_deleted": "Download Excluído",
"download_cancelled": "Download Cancelado",
"could_not_delete_download": "Não foi possível excluir o download",
"download_paused": "Download Pausado",
"could_not_pause_download": "Não foi possível Pausar o Download",
"download_resumed": "Download Retomado",
"could_not_resume_download": "Não foi possível retomar o download",
"download_completed": "Download concluído",
"download_failed": "Download Falhou",
"download_failed_for_item": "Download Falhou para {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} já está sendo baixado",
"all_files_deleted": "Todos os Downloads Excluídos com Sucesso",
"files_deleted_by_type": "{{count}} {{type}} excluído",
"all_files_folders_and_jobs_deleted_successfully": "Todos os arquivos, pastas e trabalhos excluídos com sucesso",
"failed_to_clean_cache_directory": "Falha ao limpar o diretório de cache",
"could_not_get_download_url_for_item": "Não foi possível obter o URL de download para {{itemName}}",
"go_to_downloads": "Ir para Downloads",
"file_deleted": "{{item}} deletado"
}
}
@@ -495,17 +583,16 @@
"none": "Nenhum",
"track": "Faixa",
"cancel": "Cancelar",
"stop": "Stop",
"delete": "Apagar",
"ok": "OK",
"remove": "Remover",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Buscar...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Não foi possível criar um fluxo para o Chromecast",
"message_from_server": "Mensagem do Servidor: {{message}}",
"next_episode": "Próximo Episódio",
"refresh_tracks": "Atualizar Faixas",
"audio_tracks": "Faixas de Áudio:",
"playback_state": "Estado de Reprodução:",
"index": "Índice",
"continue_watching": "Continuar assistindo",
"go_back": "Voltar atrás",
"downloaded_file_title": "Você já fez o download deste arquivo",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Mostrar mais",
"show_less": "Mostrar menos",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Próximo",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "faixas"
},
"filters": {
"all": "Tudo"
},
"recently_added": "Adicionado recentemente",
"recently_played": "Reproduzido Recentemente",
"frequently_played": "Reproduzidos com frequência",
"explore": "Explorar",
"top_tracks": "Músicas populares",
"play": "Reproduzir",
"shuffle": "Alteatório",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Niciuna",
"OnlyForced": "OnlyForced"
},
"text_color": "Culoare text",
"background_color": "Culoare fundal",
"outline_color": "Culoare contur",
"outline_thickness": "Grosime contur",
"background_opacity": "Opacitatea fundalului",
"outline_opacity": "Opacitatea conturului",
"bold_text": "Bold Text",
"colors": {
"Black": "Negru",
"Gray": "Gri",
"Silver": "Argint",
"White": "Alb",
"Maroon": "Maro",
"Red": "Roșu",
"Fuchsia": "Fuchsia",
"Yellow": "Galben",
"Olive": "Oliv",
"Green": "Verde",
"Teal": "Turcoaz",
"Lime": "Verde-Deschis",
"Purple": "Violet",
"Navy": "Marină",
"Blue": "Albastru",
"Aqua": "Aqua"
},
"thickness": {
"None": "Nimic",
"Thin": "Subțire",
"Normal": "Normală",
"Thick": "Grozav"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Altele",
"video_orientation": "Orientarea video",
@@ -295,6 +351,11 @@
"UNKNOWN": "Necunoscut"
},
"safe_area_in_controls": "Zona sigură pentru controale",
"video_player": "Player video",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "Afișează link-uri personalizate în meniu",
"show_large_home_carousel": "Arată Caruselul Media Mare (beta)",
"hide_libraries": "Ascunde bibliotecile",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Maxim episoade redare automată",
"disabled": "Dezactivat"
},
"downloads": {
"downloads_title": "Descărcări"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Plugin-uri",
"jellyseerr": {
"jellyseerr_warning": "Această integrare este în stadii incipiente. Așteptați-vă ca lucrurile să se schimbe.",
"server_url": "URL Server",
"server_url_hint": "Exemplu: http(s)://your-host.url\n(adăugați portul dacă este necesar)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Citește mai multe despre Marlin.",
"save_button": "Salvează",
"toasts": {
"saved": "Salvat"
}
"saved": "Salvat",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Ștergeți toate fișierele descărcate",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Sistem"
},
"toasts": {
"error_deleting_files": "Eroare la ștergerea fișierelor"
"error_deleting_files": "Eroare la ștergerea fișierelor",
"background_downloads_enabled": "Descărcări în fundal activate",
"background_downloads_disabled": "Descărcări în fundal dezactivate"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Descărcări",
"series": "Seriale",
"movies": "Filme",
"queue": "Coadă",
"other_media": "Alte suporturi",
"queue_hint": "Descărcările se vor pierde la repornirea aplicației",
"no_items_in_queue": "Niciun articol în coadă",
"no_downloaded_items": "Niciun element descărcat",
"delete_all_movies_button": "Șterge toate filmele",
"delete_all_series_button": "Șterge toate serialele",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Nu s-au putut șterge toate serialele",
"deleted_media_successfully": "Alte fișiere șterse cu succes!",
"failed_to_delete_media": "Ștergerea altor fișiere media a eșuat",
"download_deleted": "Descărcare ştearsă",
"download_cancelled": "Descărcare anulată",
"could_not_delete_download": "Nu s-a putut șterge descărcarea",
"download_paused": "Descărcare întreruptă",
"could_not_pause_download": "Nu s-a putut întrerupe descărcarea",
"download_resumed": "Descărcare din nou",
"could_not_resume_download": "Nu s-a putut relua descărcarea",
"download_completed": "Descărcare completă",
"download_failed": "Descărcare eșuată",
"download_failed_for_item": "Descărcarea a eșuat {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} se descarcă deja",
"all_files_deleted": "Toate descărcările au fost șterse cu succes",
"files_deleted_by_type": "{{count}} {{type}} au fost șterse",
"all_files_folders_and_jobs_deleted_successfully": "Toate fișierele, folderele și lucrările au fost șterse cu succes",
"failed_to_clean_cache_directory": "Curățarea directorului cache a eșuat",
"could_not_get_download_url_for_item": "Nu s-a putut obține URL-ul de descărcare pentru {{itemName}}",
"go_to_downloads": "Accesați descărcările",
"file_deleted": "{{item}} șters"
}
}
@@ -495,17 +583,16 @@
"none": "Nimic",
"track": "Limbă audio",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Caută...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Nu s-a putut crea un flux pentru Chromecast",
"message_from_server": "Mesaj de la server: {{message}}",
"next_episode": "Episodul următor",
"refresh_tracks": "Reîmprospătare piese",
"audio_tracks": "Audio:",
"playback_state": "Stare de redare:",
"index": "Indice:",
"continue_watching": "Continuă să vizionezi",
"go_back": "Înapoi",
"downloaded_file_title": "Aveţi acest fişier descărcat",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Arată mai mult",
"show_less": "Arată mai puțin",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Următorul",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Отсутствует",
"OnlyForced": "Только принудительные"
},
"text_color": "Цвет текста",
"background_color": "Цвет фона",
"outline_color": "Цвет контура",
"outline_thickness": "Толщина контура",
"background_opacity": "Прозрачность фона",
"outline_opacity": "Прозрачность контура",
"bold_text": "Жирный",
"colors": {
"Black": "Черный",
"Gray": "Серый",
"Silver": "Серебристый",
"White": "Белый",
"Maroon": "Бордовый",
"Red": "Красный",
"Fuchsia": "Пурпурный",
"Yellow": "Жёлтый",
"Olive": "Оливковый",
"Green": "Зелёный",
"Teal": "Бирюзовый",
"Lime": "Лаймовый",
"Purple": "Фиолетовый",
"Navy": "Тёмно-синий",
"Blue": "Синий",
"Aqua": "Голубой"
},
"thickness": {
"None": "Отсутствует",
"Thin": "Тонкий",
"Normal": "Обычный",
"Thick": "Толстый"
},
"subtitle_color": "Цвет субтитров",
"subtitle_background_color": "Цвет фона",
"subtitle_font": "Шрифт субтитров",
"ksplayer_title": "Настройки KSPlayer",
"hardware_decode": "Аппаратное декодирование",
"hardware_decode_description": "Использовать аппаратное ускорение для декодирования видео. Выключите, если наблюдаете проблемы с воспроизведением.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "Настройки субтитров в VLC",
"hint": "Настройте внешний вид субтитров в VLC плеере. Изменения применятся при следующем воспроизведении.",
"text_color": "Цвет текста",
"background_color": "Цвет фона",
"background_opacity": "Прозрачность фона",
"outline_color": "Цвет контура",
"outline_opacity": "Прозрачность контура",
"outline_thickness": "Толщина контура",
"bold": "Жирный",
"margin": "Отступ снизу"
},
"video_player": {
"title": "Видео плеер",
"video_player": "Видео плеер",
"video_player_description": "Выберите видео плеер в iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Другое",
"video_orientation": "Ориентация видео",
@@ -295,6 +351,11 @@
"UNKNOWN": "Неизвестное"
},
"safe_area_in_controls": "Безопасная зона в элементах управления",
"video_player": "Видео плеер",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Экспериментальный + PiP)"
},
"show_custom_menu_links": "Показать ссылки пользовательского меню",
"show_large_home_carousel": "Показывать большую карусель (beta)",
"hide_libraries": "Скрыть библиотеки",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Максимальное количество авто воспроизводимых эпизодов",
"disabled": "Отключено"
},
"downloads": {
"downloads_title": "Загрузки"
},
"music": {
"title": "Музыка",
"playback_title": "Воспроизведение",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Плагины",
"jellyseerr": {
"jellyseerr_warning": "Эта интеграция находится на ранней стадии. Ожидайте изменений.",
"server_url": "URL сервера",
"server_url_hint": "Пример: http(s)://your-host.url\n(добавьте порт если необходимо)",
"server_url_placeholder": "Seerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Узнать больше о Marlin.",
"save_button": "Сохранить",
"toasts": {
"saved": "Сохранено"
}
"saved": "Сохранено",
"refreshed": "Настройки обновлены с сервера"
},
"refresh_from_server": "Обновить настройки с сервера"
},
"streamystats": {
"enable_streamystats": "Включить Streamystats",
"disable_streamystats": "Выключить Streamystats",
"enable_search": "Использовать в поиске",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"streamystats_search_hint": "Введите URL вашего сервера Streamystats. URL должен включать http/https и порт при необходимости.",
"read_more_about_streamystats": "Узнать больше про Streamystats.",
"save_button": "Сохранить",
"save": "Сохранить",
"features_title": "Функции",
"home_sections_title": "Показывать на главной",
"enable_movie_recommendations": "Рекомендации фильмов",
"enable_series_recommendations": "Рекомендации сериалов",
"enable_promoted_watchlists": "Продвигаемые списки просмотра",
@@ -375,7 +445,8 @@
"refresh_from_server": "Обновить настройки с сервера"
},
"kefinTweaks": {
"watchlist_enabler": "Включить интеграцию со списками просмотра"
"watchlist_enabler": "Включить интеграцию со списками просмотра",
"watchlist_button": "Изменить интеграцию со списками просмотра"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Удалить все загруженные файлы",
"music_cache_title": "Кеш музыки",
"music_cache_description": "Автоматически кешировать песни по мере прослушивания для плавного воспроизведения и поддержки отсутствия интернета",
"enable_music_cache": "Кешировать музыку",
"clear_music_cache": "Очистить кеш музыки",
"music_cache_size": "Кешировано: {{size}}",
"music_cache_cleared": "Кеш музыки очищен",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Системный"
},
"toasts": {
"error_deleting_files": "Ошибка при удалении файлов"
"error_deleting_files": "Ошибка при удалении файлов",
"background_downloads_enabled": "Фоновая загрузка включена",
"background_downloads_disabled": "Фоновая загрузка отключена"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Загрузки",
"series": "Сериалы",
"movies": "Фильмы",
"queue": "Очередь",
"other_media": "Прочие файлы",
"queue_hint": "Очередь очистится после перезапуска",
"no_items_in_queue": "Нет элементов в очереди",
"no_downloaded_items": "Нет загруженных файлов",
"delete_all_movies_button": "Удалить все фильмы",
"delete_all_series_button": "Удалить все сериалы",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Возникла ошибка при удалении всех сериалов",
"deleted_media_successfully": "Остальные медиафайлы успешно удалены!",
"failed_to_delete_media": "Не удалось удалить остальные медиафайлы",
"download_deleted": "Загруженный контент удалён",
"download_cancelled": "Загрузка отменена",
"could_not_delete_download": "Не удалось удалить загрузку",
"download_paused": "На паузе",
"could_not_pause_download": "Не удалось приостановить загрузку",
"download_resumed": "Продолжено",
"could_not_resume_download": "Не удалось возобновить загрузку",
"download_completed": "Завершено",
"download_failed": "Не удалось загрузить",
"download_failed_for_item": "Загрузка {{item}} провалилась с ошибкой: {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} уже загружается",
"all_files_deleted": "Все загрузки удалены",
"files_deleted_by_type": "Удалено: {{count}} {{type}}",
"all_files_folders_and_jobs_deleted_successfully": "Все файлы, папки, и задачи были успешно удалены",
"failed_to_clean_cache_directory": "Не удалось очистить директорию кэша",
"could_not_get_download_url_for_item": "Не удалось получить URL для загрузки {{itemName}}",
"go_to_downloads": "В загрузки",
"file_deleted": "Удалено: {{item}}"
}
}
@@ -495,17 +583,16 @@
"none": "Отсутствует",
"track": "Трек",
"cancel": "Отмена",
"stop": "Stop",
"delete": "Удалить",
"ok": "ОК",
"remove": "Удалить",
"next": "Вперед",
"back": "Назад",
"continue": "Продолжить",
"verifying": "Проверка...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Поиск...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Не удалось создать поток для Chromecast",
"message_from_server": "Сообщение от сервера: {{message}}",
"next_episode": "Следующая серия",
"refresh_tracks": "Обновить дорожки",
"audio_tracks": "Аудио дорожки:",
"playback_state": "Состояние воспроизведения:",
"index": "Индекс:",
"continue_watching": "Продолжить просмотр",
"go_back": "Назад",
"downloaded_file_title": "Этот файл уже скачан",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Показать больше",
"show_less": "Показать меньше",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Далее",
@@ -798,9 +888,13 @@
"playlists": "Плейлисты",
"tracks": "треки"
},
"filters": {
"all": "Все"
},
"recently_added": "Недавно добавлено",
"recently_played": "Недавно воспроизведено",
"frequently_played": "Часто играет",
"explore": "Найти новое",
"top_tracks": "Топ",
"play": "Воспроизвести",
"shuffle": "Перемешать",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Inga",
"OnlyForced": "Bara Tvingande"
},
"text_color": "Textfärg",
"background_color": "Bakgrundsfärg",
"outline_color": "Konturfärg",
"outline_thickness": "Konturtjocklek",
"background_opacity": "Bakgrundsgenomskinlighet",
"outline_opacity": "Kontursgenomskinlighet",
"bold_text": "FetStil",
"colors": {
"Black": "Svart",
"Gray": "Grå",
"Silver": "Silver",
"White": "Vit",
"Maroon": "Rödbrun",
"Red": "Röd",
"Fuchsia": "Purpur",
"Yellow": "Gul",
"Olive": "Olivgrön",
"Green": "Grön",
"Teal": "Turkos",
"Lime": "Limegrön",
"Purple": "Lila",
"Navy": "Marinblå",
"Blue": "Blå",
"Aqua": "Aqua"
},
"thickness": {
"None": "Inget",
"Thin": "Tunn",
"Normal": "Normal",
"Thick": "Tjock"
},
"subtitle_color": "Undertextfärg",
"subtitle_background_color": "Bakgrundsfärg",
"subtitle_font": "Typsnitt för undertexter",
"ksplayer_title": "KSPlayer-inställningar",
"hardware_decode": "Hårdvaruavkodning",
"hardware_decode_description": "Använd hårdvaruacceleration för videoavkodning. Inaktivera om du upplever uppspelningsproblem.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Ange din OpenSubtitles API-nyckel för att aktivera klientbaserad undertextsökning som reserv när din Jellyfin-server inte har en undertextleverantör konfigurerad.",
"opensubtitles_api_key": "API-nyckel",
@@ -278,6 +315,25 @@
"bottom": "Botten"
}
},
"vlc_subtitles": {
"title": "VLC undertextsinställningar",
"hint": "Anpassa undertextens utseende för VLC-spelare. Förändringar träder i kraft vid nästa uppspelning.",
"text_color": "Textfärg",
"background_color": "Bakgrundsfärg",
"background_opacity": "Bakgrundsgenomskinlighet",
"outline_color": "Konturfärg",
"outline_opacity": "Kontursgenomskinlighet",
"outline_thickness": "Konturtjocklek",
"bold": "FetStil",
"margin": "Nedre marginal"
},
"video_player": {
"title": "Videospelare",
"video_player": "Videospelare",
"video_player_description": "Välj vilken videospelare som ska användas på iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Övrigt",
"video_orientation": "Videoriktning",
@@ -295,6 +351,11 @@
"UNKNOWN": "Okänt"
},
"safe_area_in_controls": "Säkert område i kontrollerna",
"video_player": "Videospelare",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimentell + PiP)"
},
"show_custom_menu_links": "Visa anpassade menylänkar",
"show_large_home_carousel": "Visa toppbanner (beta)",
"hide_libraries": "Dölj bibliotek",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Antal Avsnitt för Automatisk Uppspelning",
"disabled": "Inaktiverad"
},
"downloads": {
"downloads_title": "Nedladdningar"
},
"music": {
"title": "Musik",
"playback_title": "Uppspelning",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Tillägg",
"jellyseerr": {
"jellyseerr_warning": "Denna integration är i ett tidigt skede. Räkna med att saker och ting förändras.",
"server_url": "Serveradress",
"server_url_hint": "Exempel: http(s)://your-host.url\n(lägg till port vid behov)",
"server_url_placeholder": "Seerr URL",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Läs mer om Marlin.",
"save_button": "Spara",
"toasts": {
"saved": "Sparade"
}
"saved": "Sparade",
"refreshed": "Inställningarna uppdateras från servern"
},
"refresh_from_server": "Uppdatera inställningar från server"
},
"streamystats": {
"enable_streamystats": "Aktivera Streamystats",
"disable_streamystats": "Inaktivera Streamystats",
"enable_search": "Använd för sökning",
"url": "Webbadress",
"server_url_placeholder": "http(s)://streamystats.exempel.se",
"streamystats_search_hint": "Ange URL för Marlin-servern. URL bör innehålla http eller https och vid behov port.",
"read_more_about_streamystats": "Läs mer om Streamystats.",
"save_button": "Spara",
"save": "Spara",
"features_title": "Funktioner",
"home_sections_title": "Hemsektioner",
"enable_movie_recommendations": "Filmrekommendationer",
"enable_series_recommendations": "serierekommendationer",
"enable_promoted_watchlists": "rekommenderade listor att titta på",
@@ -375,7 +445,8 @@
"refresh_from_server": "Uppdatera inställningar från server"
},
"kefinTweaks": {
"watchlist_enabler": "Aktivera vår bevakningslista integration"
"watchlist_enabler": "Aktivera vår bevakningslista integration",
"watchlist_button": "sätt på/av bevakningslisteintegrationen"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Ta bort alla nerladdade filer",
"music_cache_title": "Musikcache",
"music_cache_description": "Cacha automatiskt låtar när du lyssnar för smidigare uppspelning och offline-stöd",
"enable_music_cache": "Aktivera musikcache",
"clear_music_cache": "Rensa musikcache",
"music_cache_size": "{{size}} cachad",
"music_cache_cleared": "Musikcache rensad",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "System"
},
"toasts": {
"error_deleting_files": "Fel Vid Borttagning Av Filer"
"error_deleting_files": "Fel Vid Borttagning Av Filer",
"background_downloads_enabled": "Bakgrundsnedladdningar aktiverade",
"background_downloads_disabled": "Bakgrundsnedladdningar inaktiverade"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Nedladdningar",
"series": "TV-Serier",
"movies": "Filmer",
"queue": "Kö",
"other_media": "Annan media",
"queue_hint": "Kö och nedladdningar kommer försvinna vid omstart av appen",
"no_items_in_queue": "Inga objekt i Kön",
"no_downloaded_items": "Inga Nedladdade Objekt",
"delete_all_movies_button": "Ta Bort Alla Filmer",
"delete_all_series_button": "Ta Bort Alla TV-Serier",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Det Gick Inte Att Ta Bort Alla TV-Serier",
"deleted_media_successfully": "Andra Medier Har Tagits Bort!",
"failed_to_delete_media": "Kunde Inte Ta Bort Andra Medier",
"download_deleted": "Nedladdning Borttagen",
"download_cancelled": "Nerladdningen Avbruten",
"could_not_delete_download": "Kunde Inte Ta Bort Nedladdning",
"download_paused": "Nedladdning Pausad",
"could_not_pause_download": "Kunde Inte Pausa Nedladdning",
"download_resumed": "Nedladdning Återupptagen",
"could_not_resume_download": "Kunde Inte Återuppta Nedladdning",
"download_completed": "Nedladdning Slutförd",
"download_failed": "Nerladdningen misslyckades",
"download_failed_for_item": "Nedladdning misslyckades för {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} Laddas redan ner",
"all_files_deleted": "Alla nedladdningar raderades",
"files_deleted_by_type": "{{count}} {{type}} Raderad",
"all_files_folders_and_jobs_deleted_successfully": "Alla filer, mappar och jobb har tagits bort",
"failed_to_clean_cache_directory": "Det gick inte att rensa cachemappen",
"could_not_get_download_url_for_item": "Kunde inte hämta nedladdnings-URL för {{itemName}}",
"go_to_downloads": "Gå till nedladdningar",
"file_deleted": "{{item}} Raderad"
}
}
@@ -495,17 +583,16 @@
"none": "Ingen",
"track": "Spår",
"cancel": "Avbryt",
"stop": "Stoppa",
"delete": "Ta bort",
"ok": "OK",
"remove": "Radera",
"next": "Nästa",
"back": "Tillbaka",
"continue": "Fortsätt",
"verifying": "Verifierar...",
"login": "Logga in",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Uppdatera"
},
"search": {
"search": "Sök...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Kunde inte skapa stream för Chromecast",
"message_from_server": "Meddelande från servern: {{message}}",
"next_episode": "Nästa avsnitt",
"refresh_tracks": "Uppdatera spår",
"audio_tracks": "Ljudspår:",
"playback_state": "Uppspelningsstatus:",
"index": "Index:",
"continue_watching": "Fortsätt titta",
"go_back": "Tillbaka",
"downloaded_file_title": "Du har denna fil nedladdad",
@@ -632,8 +723,7 @@
"stopPlayback": "Stoppa uppspelning",
"stopPlayingTitle": "Sluta spela \"{{title}}\"?",
"stopPlayingConfirm": "Är du säker på att du vill stoppa uppspelningen?",
"downloaded": "Nedladdad",
"missing_parameters": "Missing playback parameters"
"downloaded": "Nedladdad"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Visa Mer",
"show_less": "Visa Mindre",
"left": "kvar",
"more_info": "Mer info",
"director": "Regissör",
"cast": "Skådespelare",
"technical_details": "Tekniska detaljer",
@@ -693,8 +784,7 @@
"resume_playback": "Återuppta uppspelning",
"resume_playback_description": "Vill du fortsätta där du slutade eller börja om från början?",
"play_from_start": "Spela från början",
"continue_from": "Fortsätt från {{time}}",
"no_data_available": "No data available"
"continue_from": "Fortsätt från {{time}}"
},
"live_tv": {
"next": "Nästa",
@@ -798,9 +888,13 @@
"playlists": "Spellistor",
"tracks": "spår"
},
"filters": {
"all": "Alla"
},
"recently_added": "Nyligen tillagt",
"recently_played": "Nyligen spelat",
"frequently_played": "Spelas ofta",
"explore": "Utforska",
"top_tracks": "Toppspår",
"play": "Spela",
"shuffle": "Blanda spår",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "None",
"OnlyForced": "OnlyForced"
},
"text_color": "Text Color",
"background_color": "Background Color",
"outline_color": "Outline Color",
"outline_thickness": "Outline Thickness",
"background_opacity": "Background Opacity",
"outline_opacity": "Outline Opacity",
"bold_text": "Bold Text",
"colors": {
"Black": "Black",
"Gray": "Gray",
"Silver": "Silver",
"White": "White",
"Maroon": "Maroon",
"Red": "Red",
"Fuchsia": "Fuchsia",
"Yellow": "Yellow",
"Olive": "Olive",
"Green": "Green",
"Teal": "Teal",
"Lime": "Lime",
"Purple": "Purple",
"Navy": "Navy",
"Blue": "สีน้ำเงิน",
"Aqua": "Aqua"
},
"thickness": {
"None": "None",
"Thin": "Thin",
"Normal": "Normal",
"Thick": "Thick"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Other",
"video_orientation": "Video Orientation",
@@ -290,11 +346,16 @@
"PORTRAIT_DOWN": "Portrait Down",
"LANDSCAPE": "Landscape",
"LANDSCAPE_LEFT": "Landscape Left",
"LANDSCAPE_RIGHT": "Landscape right",
"LANDSCAPE_RIGHT": "",
"OTHER": "Other",
"UNKNOWN": "Unknown"
},
"safe_area_in_controls": "Safe Area in Controls",
"video_player": "Video Player",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "Show Custom Menu Links",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "Hide Libraries",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Max Auto Play Episode Count",
"disabled": "Disabled"
},
"downloads": {
"downloads_title": "Downloads"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Plugins",
"jellyseerr": {
"jellyseerr_warning": "This integration is in its early stages. Expect things to change.",
"server_url": "Server URL",
"server_url_hint": "Example: http(s)://your-host.url\n(add port if required)",
"server_url_placeholder": "Seerr URL",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Read More About Marlin.",
"save_button": "Save",
"toasts": {
"saved": "Saved"
}
"saved": "Saved",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Delete All Downloaded Files",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "System"
},
"toasts": {
"error_deleting_files": "Error Deleting Files"
"error_deleting_files": "Error Deleting Files",
"background_downloads_enabled": "Background downloads enabled",
"background_downloads_disabled": "Background downloads disabled"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Downloads",
"series": "TV-Series",
"movies": "Movies",
"queue": "Queue",
"other_media": "Other media",
"queue_hint": "Queue and downloads will be lost on app restart",
"no_items_in_queue": "No Items in Queue",
"no_downloaded_items": "No Downloaded Items",
"delete_all_movies_button": "Delete All Movies",
"delete_all_series_button": "Delete All TV-Series",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Failed to Delete All TV-Series",
"deleted_media_successfully": "Deleted other media Successfully!",
"failed_to_delete_media": "Failed to Delete other 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_for_item": "Download failed for {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"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": "Could not get download URL for {{itemName}}",
"go_to_downloads": "Go to Downloads",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Search...",
@@ -604,6 +691,10 @@
"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",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Show More",
"show_less": "Show Less",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Next",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "pagh",
"OnlyForced": "Dun je'"
},
"text_color": "GhItlh rIt",
"background_color": "Tlhagh rIt",
"outline_color": "Outline Color",
"outline_thickness": "Outline Thickness",
"background_opacity": "Background Opacity",
"outline_opacity": "Outline Opacity",
"bold_text": "Bold Text",
"colors": {
"Black": "Black",
"Gray": "Gray",
"Silver": "Silver",
"White": "White",
"Maroon": "Maroon",
"Red": "Red",
"Fuchsia": "Fuchsia",
"Yellow": "Yellow",
"Olive": "Olive",
"Green": "Green",
"Teal": "Teal",
"Lime": "Lime",
"Purple": "Purple",
"Navy": "Navy",
"Blue": "Blue",
"Aqua": "Aqua"
},
"thickness": {
"None": "pagh",
"Thin": "Thin",
"Normal": "Normal",
"Thick": "Thick"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "patlh",
"video_orientation": "mu'tlhegh pegh",
@@ -295,6 +351,11 @@
"UNKNOWN": "Sovbe'"
},
"safe_area_in_controls": "SeHlawDaq yot QIH",
"video_player": "mu'tlhegh tlholwI'",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (PiP mIwHa')"
},
"show_custom_menu_links": "menuDaq ret teqlu' yInej",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "De'wI' bom yIQIj",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Max Auto Play Episode Count",
"disabled": "Disabled"
},
"downloads": {
"downloads_title": "Qaw' Doch"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "mIwHom",
"jellyseerr": {
"jellyseerr_warning": "mIwHomvam chu'. ghoSlaH.",
"server_url": "Ho'Do' veS URL",
"server_url_hint": "ghu': http(s)://HoDo-veS.url\n(pord yIbel)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Marlin latlh yIlaD",
"save_button": "yIqIp",
"toasts": {
"saved": "qIp"
}
"saved": "qIp",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Hoch Qaw' Doch yIQaw'",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "mIw'a'"
},
"toasts": {
"error_deleting_files": "Qaw' ghIq"
"error_deleting_files": "Qaw' ghIq",
"background_downloads_enabled": "tlhegh Qaw' chu'",
"background_downloads_disabled": "tlhegh Qaw' QIj"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Qaw' Doch",
"series": "TV Hem",
"movies": "DIS",
"queue": "ghom",
"other_media": "Other media",
"queue_hint": "ghun ghImDI' ghom Qaw'laH.",
"no_items_in_queue": "ghom Doch pagh",
"no_downloaded_items": "Qaw' Doch pagh",
"delete_all_movies_button": "Hoch DIS yIQaw'",
"delete_all_series_button": "Hoch TV Hem yIQaw'",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Hoch TV Hem Qaw'laHbe'",
"deleted_media_successfully": "Deleted other media Successfully!",
"failed_to_delete_media": "Failed to Delete other media",
"download_deleted": "Download Deleted",
"download_cancelled": "Qaw' ghIm",
"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": "Qaw' Qapla'",
"download_failed": "Download Failed",
"download_failed_for_item": "{{item}} Qaw'laHbe' - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "Hoch De', ram 'ej vum Qaw' Qapla'",
"failed_to_clean_cache_directory": "Failed to clean cache directory",
"could_not_get_download_url_for_item": "Could not get download URL for {{itemName}}",
"go_to_downloads": "Qaw' Doch yIghoS",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "yISam...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Chromecast tlhol ret qonlaHbe'",
"message_from_server": "Ho'Do' veS jach: {{message}}",
"next_episode": "wej HemHom",
"refresh_tracks": "ret yIchu'qa'",
"audio_tracks": "QoQ ret:",
"playback_state": "tlhol mIw:",
"index": "nem:",
"continue_watching": "tlhol yIHaDqa'",
"go_back": "Go Back",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "latlh yIHoch",
"show_less": "Hom yIHoch",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "wej",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Yok",
"OnlyForced": "Sadece Zorunlu"
},
"text_color": "Metin Rengi",
"background_color": "Arkaplan Rengi",
"outline_color": "Kenarlık Rengi",
"outline_thickness": "Kenarlık kalınlığı",
"background_opacity": "Arkaplan Opaklığı",
"outline_opacity": "Kenarlık Opaklığı",
"bold_text": "Kalın Metin",
"colors": {
"Black": "Siyah",
"Gray": "Gri",
"Silver": "Gümüş",
"White": "Beyaz",
"Maroon": "Kestane",
"Red": "Kırmızı",
"Fuchsia": "Fuşya",
"Yellow": "Sarı",
"Olive": "Zeytin yeşili",
"Green": "Yeşil",
"Teal": "Deniz mavisi",
"Lime": "Limon",
"Purple": "Mor",
"Navy": "Lacivert",
"Blue": "Mavi",
"Aqua": "Açık Mavi"
},
"thickness": {
"None": "Hiçbiri",
"Thin": "İnce",
"Normal": "Normal",
"Thick": "Kalın"
},
"subtitle_color": "Altyazı Rengi",
"subtitle_background_color": "Arkaplan Rengi",
"subtitle_font": "Altyazı Yazı Tipi",
"ksplayer_title": "KSPlayer Ayarları",
"hardware_decode": "Donanımsal Kod Çözme",
"hardware_decode_description": "Video kod çözme için donanımsal hızlandırma kullan. Oynatma sorunları yaşıyorsanız devre dışı bırakın.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Altyazı Ayarları",
"hint": "VLC oynatıcı için altyazı görünümünü değiştirin. Değişiklikler bir sonraki oynatmada etkili olacak.",
"text_color": "Metin Rengi",
"background_color": "Arkaplan Rengi",
"background_opacity": "Arkaplan Opaklığı",
"outline_color": "Kenarlık Rengi",
"outline_opacity": "Kenarlık Opaklığı",
"outline_thickness": "Kenarlık Kalınlığı",
"bold": "Kalın Metin",
"margin": "Alt Kenar Boşluğu"
},
"video_player": {
"title": "Video oynatıcısı",
"video_player": "Video oynatıcısı",
"video_player_description": "iOS'da hangi video oynatıcının kullanılacağını seçin.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Diğer",
"video_orientation": "Video Yönü",
@@ -295,6 +351,11 @@
"UNKNOWN": "Bilinmeyen"
},
"safe_area_in_controls": "Kontrollerde Güvenli Alan",
"video_player": "Video player",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Deneysel + PiP)"
},
"show_custom_menu_links": "Özel Menü Bağlantılarını Göster",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "Kütüphaneleri Gizle",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "En Fazla Otomatik Oynatılacak Bölüm Sayısı",
"disabled": "Devre dışı"
},
"downloads": {
"downloads_title": "İndirmeler"
},
"music": {
"title": "Müzik",
"playback_title": "Oynatma",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Eklentiler",
"jellyseerr": {
"jellyseerr_warning": "Bu entegrasyon erken aşamalardadır. Değişiklikler olabilir.",
"server_url": "Sunucu URL'si",
"server_url_hint": "Örnek: http(s)://your-host.url\n(port gerekiyorsa ekleyin)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Marlin hakkında daha fazla oku.",
"save_button": "Kaydet",
"toasts": {
"saved": "Kaydedildi"
}
"saved": "Kaydedildi",
"refreshed": "Ayarlar sunucudan yeniden alındı"
},
"refresh_from_server": "Ayarları Sunucudan Yeniden Al"
},
"streamystats": {
"enable_streamystats": "Streamystats'ı Etkinleştir",
"disable_streamystats": "Streamystats'ı Devre Dışı Bırak",
"enable_search": "Arama için kullan",
"url": "URL Adresi",
"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.",
"read_more_about_streamystats": "Streamystats hakkında daha fazla bilgi.",
"save_button": "Kaydet",
"save": "Kaydet",
"features_title": "Özellikler",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Film Önerileri",
"enable_series_recommendations": "Dizi Önerileri",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Ayarları Sunucudan Yeniden Al"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Tüm indirilen dosyaları sil",
"music_cache_title": "Müzik Ön Belleği",
"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",
"clear_music_cache": "Müzik Ön Belleğini Temizle",
"music_cache_size": "{{size}} ön belleklendi",
"music_cache_cleared": "Müzik ön belleği temizlendi",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Sistem"
},
"toasts": {
"error_deleting_files": "Dosyalar silinirken hata oluştu"
"error_deleting_files": "Dosyalar silinirken hata oluştu",
"background_downloads_enabled": "Arka plan indirmeleri etkinleştirildi",
"background_downloads_disabled": "Arka plan indirmeleri devre dışı bırakıldı"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "İndirilenler",
"series": "Diziler",
"movies": "Filmler",
"queue": "Sıra",
"other_media": "Diğer medya",
"queue_hint": "Sıra ve indirmeler uygulama yeniden başlatıldığında kaybolacaktır",
"no_items_in_queue": "Sırada öğe yok",
"no_downloaded_items": "İndirilen öğe yok",
"delete_all_movies_button": "Tüm Filmleri Sil",
"delete_all_series_button": "Tüm Dizileri Sil",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Diziler silinemedi",
"deleted_media_successfully": "Diğer medya başarıyla silindi!",
"failed_to_delete_media": "Failed to Delete other media",
"download_deleted": "İndirme silindi",
"download_cancelled": "İndirme iptal edildi",
"could_not_delete_download": "İndirme Silinemedi",
"download_paused": "İndirme Duraklatıldı",
"could_not_pause_download": "İndirme Duraklatılamadı",
"download_resumed": "İndirme Devam Ediyor",
"could_not_resume_download": "İndirme Devam Ettirilemedi",
"download_completed": "İndirme tamamlandı",
"download_failed": "İndirme başarısız oldu",
"download_failed_for_item": "{{item}} için indirme başarısız oldu - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} zaten indiriliyor",
"all_files_deleted": "Bütün indirilenler başarıyla silindi",
"files_deleted_by_type": "{{count}} {{type}} 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",
"could_not_get_download_url_for_item": "{{itemName}} için indirme URL'si alınamadı",
"go_to_downloads": "İndirmelere git",
"file_deleted": "{{item}} silindi"
}
}
@@ -495,17 +583,16 @@
"none": "Hiçbiri",
"track": "Parça",
"cancel": "Vazgeç",
"stop": "Stop",
"delete": "Sil",
"ok": "Tamam",
"remove": "Kaldır",
"next": "Sonraki",
"back": "Geri",
"continue": "Devam",
"verifying": "Doğrulanıyor...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Ara...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Chromecast için yayın oluşturulamadı",
"message_from_server": "Sunucudan mesaj: {{message}}",
"next_episode": "Sonraki bölüm",
"refresh_tracks": "Parçaları yenile",
"audio_tracks": "Ses Parçaları:",
"playback_state": "Oynatma Durumu:",
"index": "İndeks:",
"continue_watching": "İzlemeye devam et",
"go_back": "Geri",
"downloaded_file_title": "Bu dosya indirilmiş",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Daha fazla göster",
"show_less": "Daha az göster",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Sonraki",
@@ -798,9 +888,13 @@
"playlists": "Çalma listeleri",
"tracks": "parçalar"
},
"filters": {
"all": "Tümü"
},
"recently_added": "Son Eklenenler",
"recently_played": "Son Oynatılanlar",
"frequently_played": "Sık Oynatılanlar",
"explore": "Keşfet",
"top_tracks": "En Popülar Parçalar",
"play": "Oynat",
"shuffle": "Karıştır",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

View File

@@ -261,6 +261,43 @@
"None": "Някий",
"OnlyForced": "Виключно Форсовані"
},
"text_color": "Text Color",
"background_color": "Background Color",
"outline_color": "Outline Color",
"outline_thickness": "Outline Thickness",
"background_opacity": "Background Opacity",
"outline_opacity": "Outline Opacity",
"bold_text": "Bold Text",
"colors": {
"Black": "Black",
"Gray": "Gray",
"Silver": "Silver",
"White": "White",
"Maroon": "Maroon",
"Red": "Red",
"Fuchsia": "Fuchsia",
"Yellow": "Yellow",
"Olive": "Olive",
"Green": "Green",
"Teal": "Teal",
"Lime": "Lime",
"Purple": "Purple",
"Navy": "Navy",
"Blue": "Blue",
"Aqua": "Aqua"
},
"thickness": {
"None": "None",
"Thin": "Thin",
"Normal": "Normal",
"Thick": "Thick"
},
"subtitle_color": "Subtitle Color",
"subtitle_background_color": "Background Color",
"subtitle_font": "Subtitle Font",
"ksplayer_title": "KSPlayer Settings",
"hardware_decode": "Hardware Decoding",
"hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues.",
"opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key",
@@ -278,6 +315,25 @@
"bottom": "Bottom"
}
},
"vlc_subtitles": {
"title": "VLC Subtitle Settings",
"hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.",
"text_color": "Text Color",
"background_color": "Background Color",
"background_opacity": "Background Opacity",
"outline_color": "Outline Color",
"outline_opacity": "Outline Opacity",
"outline_thickness": "Outline Thickness",
"bold": "Bold Text",
"margin": "Bottom Margin"
},
"video_player": {
"title": "Video Player",
"video_player": "Video Player",
"video_player_description": "Choose which video player to use on iOS.",
"ksplayer": "KSPlayer",
"vlc": "VLC"
},
"other": {
"other_title": "Інші",
"video_orientation": "Орієнтація відео",
@@ -295,6 +351,11 @@
"UNKNOWN": "Невідомо"
},
"safe_area_in_controls": "Безпечна зона в елементах керування",
"video_player": "Відео плеєр",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "Показати користувацькі посилання меню",
"show_large_home_carousel": "Show Large Home Carousel (beta)",
"hide_libraries": "Сховати медіатеки",
@@ -306,6 +367,9 @@
"max_auto_play_episode_count": "Max Auto Play Episode Count",
"disabled": "Вимкнено"
},
"downloads": {
"downloads_title": "Завантаження"
},
"music": {
"title": "Music",
"playback_title": "Playback",
@@ -320,6 +384,7 @@
"plugins": {
"plugins_title": "Плагіни",
"jellyseerr": {
"jellyseerr_warning": "Ця інтеграція перебуває на початковій стадії. Очікуйте, що все зміниться.",
"server_url": "URL Сервера",
"server_url_hint": "Наприклад: http(s)://your-host.url\n(додайте порт якщо необхідно)",
"server_url_placeholder": "Jellyseerr URL...",
@@ -348,18 +413,23 @@
"read_more_about_marlin": "Дізнайтеся більше про Marlin.",
"save_button": "Зберегти",
"toasts": {
"saved": "Збережено"
}
"saved": "Збережено",
"refreshed": "Settings refreshed from server"
},
"refresh_from_server": "Refresh Settings from Server"
},
"streamystats": {
"enable_streamystats": "Enable Streamystats",
"disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search",
"url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com",
"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": "Read More About Streamystats.",
"save_button": "Save",
"save": "Save",
"features_title": "Features",
"home_sections_title": "Home Sections",
"enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists",
@@ -375,7 +445,8 @@
"refresh_from_server": "Refresh Settings from Server"
},
"kefinTweaks": {
"watchlist_enabler": "Enable our Watchlist integration"
"watchlist_enabler": "Enable our Watchlist integration",
"watchlist_button": "Toggle Watchlist integration"
}
},
"storage": {
@@ -386,6 +457,7 @@
"delete_all_downloaded_files": "Видалити усі завантаженні файли",
"music_cache_title": "Music Cache",
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"enable_music_cache": "Enable Music Cache",
"clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} cached",
"music_cache_cleared": "Music cache cleared",
@@ -395,6 +467,8 @@
"clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_success": "Cache Cleared",
"clear_all_cache_success_desc": "All cache has been cleared successfully.",
"clear_all_cache_error_desc": "An error occurred while clearing the cache."
},
"intro": {
@@ -416,12 +490,15 @@
"system": "Системна"
},
"toasts": {
"error_deleting_files": "Помилка при видалені файлів"
"error_deleting_files": "Помилка при видалені файлів",
"background_downloads_enabled": "Завантаження в фоні увімкнене",
"background_downloads_disabled": "Завантаження в фоні вимкнене"
},
"security": {
"title": "Security",
"inactivity_timeout": {
"title": "Inactivity Timeout",
"description": "Auto logout after inactivity",
"disabled": "Disabled",
"1_minute": "1 minute",
"5_minutes": "5 minutes",
@@ -441,7 +518,10 @@
"downloads_title": "Завантаження",
"series": "ТБ-Серіали",
"movies": "Фільми",
"queue": "Черга",
"other_media": "Other media",
"queue_hint": "Черга і завантаження буде втрачене при перезапуску застосунку",
"no_items_in_queue": "Нема елементів в черзі",
"no_downloaded_items": "Нема завантажених елементів",
"delete_all_movies_button": "Видалити всі Фільми",
"delete_all_series_button": "Видалити всі ТБ-Серіали",
@@ -466,8 +546,13 @@
"failed_to_delete_all_series": "Не вдалося видалити всі телесеріали",
"deleted_media_successfully": "Deleted other media Successfully!",
"failed_to_delete_media": "Failed to Delete other media",
"download_deleted": "Download Deleted",
"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_failed": "Download Failed",
"download_failed_for_item": "Не вдалося завантажити {{item}} - {{error}}",
@@ -477,7 +562,10 @@
"item_already_downloading": "{{item}} is already downloading",
"all_files_deleted": "All Downloads Deleted Successfully",
"files_deleted_by_type": "{{count}} {{type}} deleted",
"all_files_folders_and_jobs_deleted_successfully": "Усі файли, папки та завдання успішно видалено",
"failed_to_clean_cache_directory": "Failed to clean cache directory",
"could_not_get_download_url_for_item": "Could not get download URL for {{itemName}}",
"go_to_downloads": "Перейти до завантаження",
"file_deleted": "{{item}} deleted"
}
}
@@ -495,17 +583,16 @@
"none": "None",
"track": "Track",
"cancel": "Cancel",
"stop": "Stop",
"delete": "Delete",
"ok": "OK",
"remove": "Remove",
"next": "Next",
"back": "Back",
"continue": "Continue",
"verifying": "Verifying...",
"login": "Login",
"episodes": "Episodes",
"movies": "Movies",
"loading": "Loading…",
"seeAll": "See all"
"refresh": "Refresh"
},
"search": {
"search": "Шукати...",
@@ -604,6 +691,10 @@
"could_not_create_stream_for_chromecast": "Не вдалося створити потік для Chromecast",
"message_from_server": "Повідомлення від серверу: {{message}}",
"next_episode": "Наступний Епізод",
"refresh_tracks": "Оновити доріжки",
"audio_tracks": "Аудіо-доріжки:",
"playback_state": "Стан відтворення:",
"index": "Індекс:",
"continue_watching": "Продовжити перегляд",
"go_back": "Назад",
"downloaded_file_title": "You have this file downloaded",
@@ -632,8 +723,7 @@
"stopPlayback": "Stop Playback",
"stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Downloaded",
"missing_parameters": "Missing playback parameters"
"downloaded": "Downloaded"
},
"chapters": {
"title": "Chapters",
@@ -671,6 +761,7 @@
"show_more": "Показати більше",
"show_less": "Показати менше",
"left": "left",
"more_info": "More Info",
"director": "Director",
"cast": "Cast",
"technical_details": "Technical Details",
@@ -693,8 +784,7 @@
"resume_playback": "Resume Playback",
"resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start",
"continue_from": "Continue from {{time}}",
"no_data_available": "No data available"
"continue_from": "Continue from {{time}}"
},
"live_tv": {
"next": "Наступний",
@@ -798,9 +888,13 @@
"playlists": "Playlists",
"tracks": "tracks"
},
"filters": {
"all": "All"
},
"recently_added": "Recently Added",
"recently_played": "Recently Played",
"frequently_played": "Frequently Played",
"explore": "Explore",
"top_tracks": "Top Tracks",
"play": "Play",
"shuffle": "Shuffle",
@@ -934,6 +1028,7 @@
"pairing": {
"pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV",
"pair_with_phone_description": "Scan the QR code displayed on your TV to log in",
"waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Logging in...",

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