Compare commits

...

32 Commits

Author SHA1 Message Date
renovate[bot]
6e23876854 chore(deps): update dependency react-test-renderer to v19.1.0 2025-06-04 12:03:34 +00:00
Gauvino
d6c7246cd1 fix: put @main instead of v8 to fix cache problem 2025-06-04 13:31:06 +02:00
Gauvain
973d226c49 feat: update bun version (#745) 2025-06-04 12:25:01 +02:00
Gauvain
dd849b532b refactor: fix the ios-build action (#742) 2025-06-04 11:54:01 +02:00
Fredrik Burmester
1a58df27d2 chore: version 2025-06-03 08:26:23 +02:00
Fredrik Burmester
68b5fe3599 chore 2025-06-03 08:20:43 +02:00
Fredrik Burmester
67f73bfa39 fix: format 2025-06-03 08:20:35 +02:00
Fredrik Burmester
5de7cab285 Merge branch 'master' into develop 2025-06-03 08:20:32 +02:00
Fredrik Burmester
67d39c39ea fix: android bug 2025-06-03 08:06:49 +02:00
Gauvain
9d8e227609 fix: remove description of pr in message (#737) 2025-06-02 16:28:28 +02:00
renovate[bot]
962323a75c chore(deps): update github/codeql-action action to v3.28.18 (#727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 16:18:42 +02:00
Gauvain
fc23201b4f fix: biome check, remove spell-check (#731) 2025-06-02 16:17:34 +02:00
lance chant
f0519ea88d fix: tv home screen navigation (#732) 2025-06-02 15:15:31 +02:00
renovate[bot]
f9f21606ff chore(deps): pin dependencies (#722)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 14:18:04 +02:00
Gauvain
c4d026f4d8 fix: remove cache bun (#730) 2025-06-02 14:15:46 +02:00
Gauvain
577827303e fix: correct name of dictonnary and use correct version in action (#728) 2025-06-02 14:13:44 +02:00
Gauvain
3e0a1af9fa fix: fix error on pr title for renovate bot (#729) 2025-06-02 14:13:36 +02:00
lance chant
63bc806a06 fix: made tv os compile (#721) 2025-06-02 14:13:13 +02:00
Jaakko Rantamäki
f05496a458 feat: Adaptive icons for iOS 18 and Android (#606) 2025-06-02 14:04:41 +02:00
Gauvain
d3660b45b1 fix: rename merge conflict label (#725) 2025-06-02 13:52:12 +02:00
Sim
1b812ebed5 fix: Recently Added isn't updating correctly. (#686) 2025-06-02 13:21:50 +02:00
Chris
6703299da9 docs: fix typo in README.md (#719) 2025-06-02 13:17:37 +02:00
Kamil Kosek
80d63c0219 feat: remotecontrol (#705) 2025-06-02 13:16:15 +02:00
Nyanmisaka
c2f8145e74 fix: Fixed container name mp4 in transcoding profiles (#696) 2025-06-02 13:14:31 +02:00
Gauvain
ce00aeb5f1 feat(ci/cd): Add Android builds and quality checks (#694) 2025-06-02 13:14:20 +02:00
Fredrik Burmester
5899cc8625 chore: version code 2025-05-30 21:00:02 +02:00
sarendsen
90217bb495 fix: error race conditions 2025-05-29 16:39:46 +02:00
lostb1t
16e88cca8c fix: error race conditions 2025-05-29 16:04:22 +02:00
lostb1t
e8e62061ae fix: remove unwanted detail calls on search (#707) 2025-05-28 14:24:22 +02:00
Fredrik Burmester
205715ae29 chore 2025-03-25 13:13:46 +01:00
Fredrik Burmester
ffbaaa81a8 Merge branch 'develop' 2025-03-19 11:33:51 +01:00
Chris
7201be6f02 Update README.md (#605) 2025-03-14 14:47:09 +01:00
44 changed files with 2953 additions and 1795 deletions

79
.github/workflows/build-android.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: 🤖 Android APK Build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
pull_request:
branches: [develop, master]
push:
branches: [develop, master]
jobs:
build:
runs-on: ubuntu-24.04
name: 🏗️ Build Android APK
permissions:
contents: read
steps:
- name: 📥 Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
show-progress: false
submodules: recursive
fetch-depth: 0
- name: 🍞 Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: '1.2.15'
- name: ☕ Setup JDK
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: 'zulu'
java-version: '17'
- name: 💾 Cache Bun dependencies
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-cache-
- name: 📦 Install dependencies
run: |
bun install --frozen-lockfile
bun run submodule-reload
- name: 💾 Cache Android dependencies
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: |
android/.gradle
key: ${{ runner.os }}-android-deps-${{ hashFiles('android/**/build.gradle') }}
restore-keys: |
${{ runner.os }}-android-deps-
- name: 🛠️ Generate project files
run: bun run prebuild
- name: 🚀 Build APK via Bun
run: bun run build:android:local
- name: 📅 Set date tag
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
- name: 📤 Upload APK artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: streamyfin-apk-${{ github.sha }}-${{ env.DATE_TAG }}
path: |
android/app/build/outputs/apk/release/*.apk
android/app/build/outputs/bundle/release/*.aab
retention-days: 7

View File

@@ -1,49 +0,0 @@
name: Automatic Build and Deploy
on:
workflow_dispatch:
push:
branches:
- main
jobs:
build:
runs-on: macos-15
name: Build IOS
steps:
- uses: actions/checkout@v2
name: Check out repository
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: |
bun i && bun run submodule-reload
npx expo prebuild
- uses: sparkfabrik/ios-build-action@v2.3.0
with:
upload-to-testflight: false
increment-build-number: false
build-pods: true
pods-path: "ios/Podfile"
configuration: Release
# Change later to app-store if wanted
export-method: appstore
#export-method: ad-hoc
workspace-path: "ios/Streamyfin.xcodeproj/project.xcworkspace/"
project-path: "ios/Streamyfin.xcodeproj"
scheme: Streamyfin
apple-key-id: ${{ secrets.APPLE_KEY_ID }}
apple-key-issuer-id: ${{ secrets.APPLE_KEY_ISSUER_ID }}
apple-key-content: ${{ secrets.APPLE_KEY_CONTENT }}
team-id: ${{ secrets.TEAM_ID }}
team-name: ${{ secrets.TEAM_NAME }}
#match-password: ${{ secrets.MATCH_PASSWORD }}
#match-git-url: ${{ secrets.MATCH_GIT_URL }}
#match-git-basic-authorization: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
#match-build-type: "appstore"
#browserstack-upload: true
#browserstack-username: ${{ secrets.BROWSERSTACK_USERNAME }}
#browserstack-access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
#fastlane-env: stage
ios-app-id: com.stetsed.teststreamyfin
output-path: build-${{ github.sha }}.ipa

70
.github/workflows/build-ios.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: 🤖 iOS IPA Build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
pull_request:
branches: [develop, master]
push:
branches: [develop, master]
jobs:
build:
runs-on: macos-15
name: 🏗️ Build iOS IPA
permissions:
contents: read
steps:
- name: 📥 Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
show-progress: false
submodules: recursive
fetch-depth: 0
- name: 🍞 Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: '1.2.15'
- name: 💾 Cache Bun dependencies
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-cache-
- name: 📦 Install & Prepare
run: |
bun install --frozen-lockfile
bun run submodule-reload
- name: 🛠️ Generate project files
run: bun run prebuild
- name: 🏗 Setup EAS
uses: expo/expo-github-action@main
with:
eas-version: 16.7.1
token: ${{ secrets.EXPO_TOKEN }}
- name: 🏗️ Build iOS app
run: |
eas build -p ios --local --non-interactive
- name: 📅 Set date tag
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
- name: 📤 Upload IPA artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: streamyfin-ipa-${{ github.sha }}-${{ env.DATE_TAG }}
path: |
build-*.ipa
retention-days: 7

46
.github/workflows/check-lockfile.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: 🔒 Lockfile Consistency Check
on:
pull_request:
branches: [develop, master]
push:
branches: [develop, master]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check-lockfile:
name: 🔍 Check bun.lock and package.json consistency
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: 📥 Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
show-progress: false
submodules: recursive
fetch-depth: 0
- name: 🍞 Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: '1.2.15'
- name: 💾 Cache Bun dependencies
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
- name: 🛡️ Verify lockfile consistency
run: |
set -euxo pipefail
echo "➡️ Checking for discrepancies between bun.lock and package.json..."
bun install --frozen-lockfile --dry-run --ignore-scripts
echo "✅ Lockfile is consistent with package.json!"

43
.github/workflows/ci-codeql.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: 🛡️ CodeQL Analysis
on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]
schedule:
- cron: '24 2 * * *'
jobs:
analyze:
name: 🔎 Analyze with CodeQL
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript-typescript' ]
steps:
- name: 📥 Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
show-progress: false
fetch-depth: 0
- name: 🏁 Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with:
languages: ${{ matrix.language }}
queries: +security-extended,security-and-quality
- name: 🛠️ Autobuild
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
- name: 🧪 Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18

24
.github/workflows/conflict.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: 🏷🔀Merge Conflict Labeler
on:
push:
branches: [develop]
pull_request_target:
branches: [develop]
types: [synchronize]
jobs:
label:
name: 🏷️ Labeling Merge Conflicts
runs-on: ubuntu-24.04
if: ${{ github.repository == 'streamyfin/streamyfin' }}
permissions:
contents: read
pull-requests: write
steps:
- name: 🚩 Apply merge conflict label
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
with:
dirtyLabel: 'merge-conflict'
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
repoToken: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -1,41 +0,0 @@
name: "Lint PR"
on:
pull_request_target:
types:
- opened
- edited
- synchronize
- reopened
permissions:
pull-requests: write
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: marocchino/sticky-pull-request-comment@v2
if: always() && (steps.lint_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
message: |
Hey there and thank you for opening this pull request! 👋🏼
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
Details:
```
${{ steps.lint_pr_title.outputs.error_message }}
```
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
delete: true

View File

@@ -1,28 +0,0 @@
name: Lint
on:
pull_request:
branches: [ develop, master ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Run linting checks
run: bun run check

95
.github/workflows/linting.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: 🚦 Security & Quality Gate
on:
pull_request_target:
types: [opened, edited, synchronize, reopened]
branches: [develop, master]
workflow_dispatch:
permissions:
contents: read
jobs:
validate_pr_title:
name: "📝 Validate PR Title"
runs-on: ubuntu-24.04
permissions:
pull-requests: write
contents: read
steps:
- uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db # v2.9.2
if: always() && (steps.lint_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
message: |
Hey there and thank you for opening this pull request! 👋🏼
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/).
**Error details:**
```
${{ steps.lint_pr_title.outputs.error_message }}
```
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db # v2.9.2
with:
header: pr-title-lint-error
delete: true
dependency-review:
name: 🔍 Vulnerable Dependencies
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Checkout Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Dependency Review
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
with:
fail-on-severity: high
deny-licenses: GPL-3.0, AGPL-3.0
base-ref: ${{ github.event.pull_request.base.sha || 'develop' }}
head-ref: ${{ github.event.pull_request.head.sha || github.ref }}
code_quality:
name: "🔍 Lint & Test (${{ matrix.command }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command:
- "lint"
- "check"
steps:
- name: "📥 Checkout PR code"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
submodules: recursive
fetch-depth: 0
- name: "🟢 Setup Node.js"
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20.x'
- name: "🍞 Setup Bun"
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: '1.2.15'
- name: "📦 Install dependencies"
run: bun install --frozen-lockfile
- name: "🚨 Run ${{ matrix.command }}"
run: bun run ${{ matrix.command }}

View File

@@ -1,39 +0,0 @@
name: Handle Stale Issues
on:
schedule:
- cron: "30 1 * * *" # Runs at 1:30 UTC every day
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
# Issue specific settings
days-before-issue-stale: 90
days-before-issue-close: 7
stale-issue-label: "stale"
stale-issue-message: |
This issue has been automatically marked as stale because it has had no activity in the last 30 days.
If this issue is still relevant, please leave a comment to keep it open.
Otherwise, it will be closed in 7 days if no further activity occurs.
Thank you for your contributions!
close-issue-message: |
This issue has been automatically closed because it has been inactive for 7 days since being marked as stale.
If you believe this issue is still relevant, please feel free to reopen it and add a comment explaining the current status.
# Pull request settings (disabled)
days-before-pr-stale: -1
days-before-pr-close: -1
# Other settings
repo-token: ${{ secrets.GITHUB_TOKEN }}
operations-per-run: 100
exempt-issue-labels: "Roadmap v1,help needed,enhancement"

View File

@@ -1,18 +0,0 @@
name: Discord Pull Request Notification
on:
pull_request:
types: [opened, reopened]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- uses: joelwmale/webhook-action@master
with:
url: ${{ secrets.DISCORD_WEBHOOK_URL }}
body: |
{
"content": "New Pull Request: ${{ github.event.pull_request.title }}\nBy: ${{ github.event.pull_request.user.login }}\n\n${{ github.event.pull_request.html_url }}",
"avatar_url": "https://avatars.githubusercontent.com/u/193271640"
}

23
.github/workflows/notification.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: 🛎️ Discord Pull Request Notification
on:
pull_request:
types: [opened, reopened]
branches: [develop]
jobs:
notify:
runs-on: ubuntu-24.04
steps:
- name: 🛎️ Notify Discord
uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9 # 0.3.2
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }}
DISCORD_AVATAR: https://avatars.githubusercontent.com/u/193271640
with:
args: |
📢 New Pull Request in **${{ github.repository }}**
**Title:** ${{ github.event.pull_request.title }}
**By:** ${{ github.event.pull_request.user.login }}
**Branch:** ${{ github.event.pull_request.head.ref }}
🔗 ${{ github.event.pull_request.html_url }}

49
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: 🕒 Handle Stale Issues
on:
schedule:
# Runs daily at 1:30 AM UTC (3:30 AM CEST - France time)
- cron: "30 1 * * *"
jobs:
stale-issues:
name: 🗑️ Cleanup Stale Issues
runs-on: ubuntu-24.04
permissions:
issues: write
pull-requests: write
steps:
- name: 🔄 Mark/Close Stale Issues
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with:
# Global settings
repo-token: ${{ secrets.GITHUB_TOKEN }}
operations-per-run: 500 # Increase if you have >1000 issues
log-level: debug
# Issue configuration
days-before-issue-stale: 90
days-before-issue-close: 7
stale-issue-label: "stale"
exempt-issue-labels: "Roadmap v1,help needed,enhancement"
# Notifications messages
stale-issue-message: |
⏳ This issue has been automatically marked as **stale** because it has had no activity for 90 days.
**Next steps:**
- If this is still relevant, add a comment to keep it open
- Otherwise, it will be closed in 7 days
Thank you for your contributions! 🙌
close-issue-message: |
🚮 This issue has been automatically closed due to inactivity (7 days since being marked stale).
**Need to reopen?**
Click "Reopen" and add a comment explaining why this should stay open.
# Disable PR handling
days-before-pr-stale: -1
days-before-pr-close: -1

6
.gitignore vendored
View File

@@ -11,7 +11,6 @@ npm-debug.*
web-build/
modules/vlc-player/android/build
modules/vlc-player/android/.gradle
bun.lockb
# macOS
.DS_Store
@@ -20,9 +19,7 @@ expo-env.d.ts
Streamyfin.app
build-*
*.mp4
build-*
Streamyfin.app
package-lock.json
@@ -47,4 +44,5 @@ credentials.json
modules/hls-downloader/android/build
streamyfin-4fec1-firebase-adminsdk.json
.env
.env.local
.env.local
*.aab

View File

@@ -35,12 +35,12 @@ Chromecast support is still in development, and we're working on improving it. C
### Streamyfin Plugin
The Jellyfin Plugin for Streamyfin is a plugin you install into Jellyfin that hold all settings for the client Streamyfin. This allows you to syncronize settings accross all your users, like:
The Jellyfin Plugin for Streamyfin is a plugin you install into Jellyfin that holds all settings for the client Streamyfin. This allows you to synchronize settings across all your users, like for example:
- Auto log in to Jellyseerr without the user having to do anythin
- Auto log in to Jellyseerr without the user having to do anything
- Choose the default languages
- Set download method and search provider
- Customize homescreen
- Customize home screen
- And more...
[Streamyfin Plugin](https://github.com/streamyfin/jellyfin-plugin-streamyfin)
@@ -66,7 +66,7 @@ Or download the APKs [here on GitHub](https://github.com/streamyfin/streamyfin/r
### Beta testing
To access the Streamyfin beta, you need to subscribe to the Member tier (or higher) on [Patreon](https://www.patreon.com/streamyfin). This will give you immediate access to the ⁠🧪-public-beta channel on Discord and i'll know that you have subscribed. This is where I post APKs and IPAs. This won't give automatic access to the TestFlight, however, so you need to send me a DM with the email you use for Apple so that i can manually add you.
To access the Streamyfin beta, you need to subscribe to the Member tier (or higher) on [Patreon](https://www.patreon.com/streamyfin). This will give you immediate access to the ⁠🧪-public-beta channel on Discord and I'll know that you have subscribed. This is where I post APKs and IPAs. This won't give automatic access to the TestFlight, however, so you need to send me a DM with the email you use for Apple so that I can manually add you.
**Note**: Everyone who is actively contributing to the source code of Streamyfin will have automatic access to the betas.

View File

@@ -27,13 +27,19 @@
"usesNonExemptEncryption": false
},
"supportsTablet": true,
"bundleIdentifier": "com.fredrikburmester.streamyfin"
"bundleIdentifier": "com.fredrikburmester.streamyfin",
"icon": {
"dark": "./assets/images/icon-plain.png",
"light": "./assets/images/icon-ios-light.png",
"tinted": "./assets/images/icon-ios-tinted.png"
}
},
"android": {
"jsEngine": "hermes",
"versionCode": 54,
"versionCode": 56,
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive_icon.png",
"foregroundImage": "./assets/images/icon-plain.png",
"monochromeImage": "./assets/images/icon-mono.png",
"backgroundColor": "#464646"
},
"package": "com.fredrikburmester.streamyfin",
@@ -127,6 +133,12 @@
"icon": "./assets/images/notification.png",
"color": "#9333EA"
}
],
[
"react-native-google-cast",
{
"useDefaultExpandedMediaControls": true
}
]
],
"experiments": {

View File

@@ -18,12 +18,18 @@ import {
HardwareAccelerationType,
type SessionInfoDto,
} from "@jellyfin/sdk/lib/generated-client";
import {
GeneralCommandType,
PlaystateCommand,
} from "@jellyfin/sdk/lib/generated-client/models";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
import { FlashList } from "@shopify/flash-list";
import { useQuery } from "@tanstack/react-query";
import { useAtomValue } from "jotai";
import { get } from "lodash";
import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { TouchableOpacity, View } from "react-native";
export default function page() {
const { sessions, isLoading } = useSessions({} as useSessionsProps);
@@ -110,6 +116,77 @@ const SessionCard = ({ session }: SessionCardProps) => {
},
});
// Handle session controls
const [isControlLoading, setIsControlLoading] = useState<
Record<string, boolean>
>({});
const handleSystemCommand = async (command: GeneralCommandType) => {
if (!api || !session.Id) return false;
setIsControlLoading({ ...isControlLoading, [command]: true });
try {
getSessionApi(api).sendSystemCommand({
sessionId: session.Id,
command,
});
return true;
} catch (error) {
console.error(`Error sending ${command} command:`, error);
return false;
} finally {
setIsControlLoading({ ...isControlLoading, [command]: false });
}
};
const handlePlaystateCommand = async (command: PlaystateCommand) => {
if (!api || !session.Id) return false;
setIsControlLoading({ ...isControlLoading, [command]: true });
try {
getSessionApi(api).sendPlaystateCommand({
sessionId: session.Id,
command,
});
return true;
} catch (error) {
console.error(`Error sending playstate ${command} command:`, error);
return false;
} finally {
setIsControlLoading({ ...isControlLoading, [command]: false });
}
};
const handlePlayPause = async () => {
console.log("handlePlayPause");
await handlePlaystateCommand(PlaystateCommand.PlayPause);
};
const handleStop = async () => {
await handlePlaystateCommand(PlaystateCommand.Stop);
};
const handlePrevious = async () => {
await handlePlaystateCommand(PlaystateCommand.PreviousTrack);
};
const handleNext = async () => {
await handlePlaystateCommand(PlaystateCommand.NextTrack);
};
const handleToggleMute = async () => {
await handleSystemCommand(GeneralCommandType.ToggleMute);
};
const handleVolumeUp = async () => {
await handleSystemCommand(GeneralCommandType.VolumeUp);
};
const handleVolumeDown = async () => {
await handleSystemCommand(GeneralCommandType.VolumeDown);
};
useInterval(tick, 1000);
return (
@@ -181,6 +258,107 @@ const SessionCard = ({ session }: SessionCardProps) => {
}}
/>
</View>
{/* Session controls */}
<View className='flex flex-row mt-2 space-x-4 justify-center'>
<TouchableOpacity
onPress={handlePrevious}
disabled={isControlLoading[PlaystateCommand.PreviousTrack]}
style={{
opacity: isControlLoading[PlaystateCommand.PreviousTrack]
? 0.5
: 1,
}}
>
<MaterialCommunityIcons
name='skip-previous'
size={24}
color='white'
/>
</TouchableOpacity>
<TouchableOpacity
onPress={handlePlayPause}
disabled={isControlLoading[PlaystateCommand.PlayPause]}
style={{
opacity: isControlLoading[PlaystateCommand.PlayPause]
? 0.5
: 1,
}}
>
{session.PlayState?.IsPaused ? (
<Ionicons name='play' size={24} color='white' />
) : (
<Ionicons name='pause' size={24} color='white' />
)}
</TouchableOpacity>
<TouchableOpacity
onPress={handleStop}
disabled={isControlLoading[PlaystateCommand.Stop]}
style={{
opacity: isControlLoading[PlaystateCommand.Stop] ? 0.5 : 1,
}}
>
<Ionicons name='stop' size={24} color='white' />
</TouchableOpacity>
<TouchableOpacity
onPress={handleNext}
disabled={isControlLoading[PlaystateCommand.NextTrack]}
style={{
opacity: isControlLoading[PlaystateCommand.NextTrack]
? 0.5
: 1,
}}
>
<MaterialCommunityIcons
name='skip-next'
size={24}
color='white'
/>
</TouchableOpacity>
<TouchableOpacity
onPress={handleVolumeDown}
disabled={isControlLoading[GeneralCommandType.VolumeDown]}
style={{
opacity: isControlLoading[GeneralCommandType.VolumeDown]
? 0.5
: 1,
}}
>
<Ionicons name='volume-low' size={24} color='white' />
</TouchableOpacity>
<TouchableOpacity
onPress={handleToggleMute}
disabled={isControlLoading[GeneralCommandType.ToggleMute]}
style={{
opacity: isControlLoading[GeneralCommandType.ToggleMute]
? 0.5
: 1,
}}
>
<Ionicons
name='volume-mute'
size={24}
color={session.PlayState?.IsMuted ? "red" : "white"}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={handleVolumeUp}
disabled={isControlLoading[GeneralCommandType.VolumeUp]}
style={{
opacity: isControlLoading[GeneralCommandType.VolumeUp]
? 0.5
: 1,
}}
>
<Ionicons name='volume-high' size={24} color='white' />
</TouchableOpacity>
</View>
</View>
</View>
</View>

View File

@@ -367,7 +367,15 @@ const Page = () => {
className='mr-1'
id={libraryId}
queryKey='sortBy'
queryFn={async () => sortOptions.map((s) => s.key)}
queryFn={async () =>
sortOptions
.filter(
(s) =>
library?.CollectionType !== "movies" ||
s.key !== SortByOption.DateLastContentAdded,
)
.map((s) => s.key)
}
set={setSortBy}
values={sortBy}
title={t("library.filters.sort_by")}

View File

@@ -331,7 +331,7 @@ export default function search() {
<View className={l1 || l2 ? "opacity-0" : "opacity-100"}>
<SearchItemWrapper
header={t("search.movies")}
ids={movies?.map((m) => m.Id!)}
items={movies}
renderItem={(item: BaseItemDto) => (
<TouchableItemRouter
key={item.Id}
@@ -349,7 +349,7 @@ export default function search() {
)}
/>
<SearchItemWrapper
ids={series?.map((m) => m.Id!)}
items={series}
header={t("search.series")}
renderItem={(item: BaseItemDto) => (
<TouchableItemRouter
@@ -368,7 +368,7 @@ export default function search() {
)}
/>
<SearchItemWrapper
ids={episodes?.map((m) => m.Id!)}
items={episodes}
header={t("search.episodes")}
renderItem={(item: BaseItemDto) => (
<TouchableItemRouter
@@ -382,7 +382,7 @@ export default function search() {
)}
/>
<SearchItemWrapper
ids={collections?.map((m) => m.Id!)}
items={collections}
header={t("search.collections")}
renderItem={(item: BaseItemDto) => (
<TouchableItemRouter
@@ -398,7 +398,7 @@ export default function search() {
)}
/>
<SearchItemWrapper
ids={actors?.map((m) => m.Id!)}
items={actors}
header={t("search.actors")}
renderItem={(item: BaseItemDto) => (
<TouchableItemRouter

View File

@@ -60,6 +60,7 @@ export default function page() {
const [showControls, _setShowControls] = useState(true);
const [ignoreSafeAreas, setIgnoreSafeAreas] = useState(false);
const [isPlaying, setIsPlaying] = useState(false);
const [isMuted, setIsMuted] = useState(false);
const [isBuffering, setIsBuffering] = useState(true);
const [isVideoLoaded, setIsVideoLoaded] = useState(false);
const [isPipStarted, setIsPipStarted] = useState(false);
@@ -67,6 +68,10 @@ export default function page() {
const progress = useSharedValue(0);
const isSeeking = useSharedValue(false);
const cacheProgress = useSharedValue(0);
const VolumeManager = Platform.isTV
? null
: require("react-native-volume-manager");
let getDownloadedItem = null;
if (!Platform.isTV) {
getDownloadedItem = downloadProvider.useDownload();
@@ -132,11 +137,10 @@ export default function page() {
fetchedItem = res.data;
}
setItem(fetchedItem);
setItemStatus({ isLoading: false, isError: false });
} catch (error) {
console.error("Failed to fetch item:", error);
setItemStatus({ isLoading: false, isError: true });
} finally {
setItemStatus({ isLoading: false, isError: false });
}
};
@@ -159,6 +163,7 @@ export default function page() {
useEffect(() => {
const fetchStreamData = async () => {
setStreamStatus({ isLoading: true, isError: false });
const native = await generateDeviceProfile();
try {
let result: Stream | null = null;
@@ -193,11 +198,10 @@ export default function page() {
result = { mediaSource, sessionId, url };
}
setStream(result);
setStreamStatus({ isLoading: false, isError: false });
} catch (error) {
console.error("Failed to fetch stream:", error);
setStreamStatus({ isLoading: false, isError: true });
} finally {
setStreamStatus({ isLoading: false, isError: false });
}
};
fetchStreamData();
@@ -220,7 +224,7 @@ export default function page() {
setIsPlaying(!isPlaying);
if (isPlaying) {
await videoRef.current?.pause();
reportPlaybackStopped();
reportPlaybackProgress();
} else {
videoRef.current?.play();
await getPlaystateApi(api!).reportPlaybackStart({
@@ -240,7 +244,15 @@ export default function page() {
});
revalidateProgressCache();
}, [api, item, mediaSourceId, stream]);
}, [
api,
item,
mediaSourceId,
stream,
progress,
offline,
revalidateProgressCache,
]);
const stop = useCallback(() => {
reportPlaybackStopped();
@@ -266,7 +278,7 @@ export default function page() {
isPaused: !isPlaying,
playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
playSessionId: stream.sessionId,
isMuted: false,
isMuted: isMuted,
canSeek: true,
repeatMode: RepeatMode.RepeatNone,
playbackOrder: PlaybackOrder.Default,
@@ -330,13 +342,84 @@ export default function page() {
return item?.UserData?.PlaybackPositionTicks
? ticksToSeconds(item.UserData.PlaybackPositionTicks)
: 0;
}, [item]);
}, [item, offline]);
const volumeUpCb = useCallback(async () => {
if (Platform.isTV) return;
try {
const { volume: currentVolume } = await VolumeManager.getVolume();
const newVolume = Math.min(currentVolume + 0.1, 1.0);
await VolumeManager.setVolume(newVolume);
} catch (error) {
console.error("Error adjusting volume:", error);
}
}, []);
const [previousVolume, setPreviousVolume] = useState<number | null>(null);
const toggleMuteCb = useCallback(async () => {
if (Platform.isTV) return;
try {
const { volume: currentVolume } = await VolumeManager.getVolume();
const currentVolumePercent = currentVolume * 100;
if (currentVolumePercent > 0) {
// Currently not muted, so mute
setPreviousVolume(currentVolumePercent);
await VolumeManager.setVolume(0);
setIsMuted(true);
} else {
// Currently muted, so restore previous volume
const volumeToRestore = previousVolume || 50; // Default to 50% if no previous volume
await VolumeManager.setVolume(volumeToRestore / 100);
setPreviousVolume(null);
setIsMuted(false);
}
} catch (error) {
console.error("Error toggling mute:", error);
}
}, [previousVolume]);
const volumeDownCb = useCallback(async () => {
if (Platform.isTV) return;
try {
const { volume: currentVolume } = await VolumeManager.getVolume();
const newVolume = Math.max(currentVolume - 0.1, 0); // Decrease by 10%
console.log(
"Volume Down",
Math.round(currentVolume * 100),
"→",
Math.round(newVolume * 100),
);
await VolumeManager.setVolume(newVolume);
} catch (error) {
console.error("Error adjusting volume:", error);
}
}, []);
const setVolumeCb = useCallback(async (newVolume: number) => {
if (Platform.isTV) return;
try {
const clampedVolume = Math.max(0, Math.min(newVolume, 100));
console.log("Setting volume to", clampedVolume);
await VolumeManager.setVolume(clampedVolume / 100);
} catch (error) {
console.error("Error setting volume:", error);
}
}, []);
useWebSocket({
isPlaying: isPlaying,
togglePlay: togglePlay,
stopPlayback: stop,
offline,
toggleMute: toggleMuteCb,
volumeUp: volumeUpCb,
volumeDown: volumeDownCb,
setVolume: setVolumeCb,
});
const onPlaybackStateChanged = useCallback(
@@ -423,7 +506,7 @@ export default function page() {
);
}
if (!item || !stream || itemStatus.isError || streamStatus.isError)
if (itemStatus.isError || streamStatus.isError)
return (
<View className='w-screen h-screen flex flex-col items-center justify-center bg-black'>
<Text className='text-white'>{t("player.error")}</Text>

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
assets/images/icon-mono.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

View File

@@ -6,7 +6,7 @@
"dependencies": {
"@bottom-tabs/react-navigation": "0.8.6",
"@expo/config-plugins": "~9.0.15",
"@expo/react-native-action-sheet": "^4.1.0",
"@expo/react-native-action-sheet": "^4.1.1",
"@expo/vector-icons": "^14.0.4",
"@futurejj/react-native-visibility-sensor": "^1.3.10",
"@gorhom/bottom-sheet": "^5.1.0",
@@ -57,7 +57,7 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-i18next": "^15.4.0",
"react-native": "npm:react-native-tvos@~0.77.0-0",
"react-native": "npm:react-native-tvos@~0.77.2-0",
"react-native-awesome-slider": "^2.9.0",
"react-native-bottom-tabs": "0.8.6",
"react-native-circular-progress": "^1.4.1",
@@ -107,10 +107,11 @@
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^19.0.0",
"@types/uuid": "^10.0.0",
"cross-env": "^7.0.3",
"husky": "^9.1.7",
"lint-staged": "^15.5.0",
"postinstall-postinstall": "^2.1.0",
"react-test-renderer": "19.0.0",
"react-test-renderer": "19.1.0",
"typescript": "~5.7.3",
},
},
@@ -436,7 +437,7 @@
"@expo/prebuild-config": ["@expo/prebuild-config@8.0.28", "", { "dependencies": { "@expo/config": "~10.0.10", "@expo/config-plugins": "~9.0.15", "@expo/config-types": "^52.0.4", "@expo/image-utils": "^0.6.5", "@expo/json-file": "^9.0.2", "@react-native/normalize-colors": "0.76.7", "debug": "^4.3.1", "fs-extra": "^9.0.0", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" } }, "sha512-SDDgCKKS1wFNNm3de2vBP8Q5bnxcabuPDE9Mnk9p7Gb4qBavhwMbAtrLcAyZB+WRb4QM+yan3z3K95vvCfI/+A=="],
"@expo/react-native-action-sheet": ["@expo/react-native-action-sheet@4.1.0", "", { "dependencies": { "@types/hoist-non-react-statics": "^3.3.1", "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "react": ">=18.0.0" } }, "sha512-RILoWhREgjMdr1NUSmZa/cHg8onV2YPDAMOy0iIP1c3H7nT9QQZf5dQNHK8ehcLM82sarVxriBJyYSSHAx7j6w=="],
"@expo/react-native-action-sheet": ["@expo/react-native-action-sheet@4.1.1", "", { "dependencies": { "@types/hoist-non-react-statics": "^3.3.1", "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "react": ">=18.0.0" } }, "sha512-4KRaba2vhqDRR7ObBj6nrD5uJw8ePoNHdIOMETTpgGTX7StUbrF4j/sfrP1YUyaPEa1P8FXdwG6pB+2WtrJd1A=="],
"@expo/rudder-sdk-node": ["@expo/rudder-sdk-node@1.1.1", "", { "dependencies": { "@expo/bunyan": "^4.0.0", "@segment/loosely-validate-event": "^2.0.0", "fetch-retry": "^4.1.1", "md5": "^2.2.1", "node-fetch": "^2.6.1", "remove-trailing-slash": "^0.1.0", "uuid": "^8.3.2" } }, "sha512-uy/hS/awclDJ1S88w9UGpc6Nm9XnNUjzOAAib1A3PVAnGQIwebg8DpFqOthFBTlZxeuV/BKbZ5jmTbtNZkp1WQ=="],
@@ -624,27 +625,27 @@
"@react-native-tvos/config-tv": ["@react-native-tvos/config-tv@0.1.1", "", { "dependencies": { "getenv": "^1.0.0" }, "peerDependencies": { "expo": "^52" } }, "sha512-Le/5wGElcNarDcoafCbvk/HMxcG3s0/468xXMWqAsOtBhGAdGtyXtjWEgp/uEr4GgZJlEIdM3ZqiuB8P7p8sjw=="],
"@react-native-tvos/virtualized-lists": ["@react-native-tvos/virtualized-lists@0.77.0-0", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^18.2.6", "react": "*", "react-native-tvos": "*" }, "optionalPeers": ["@types/react"] }, "sha512-em0PMjOD8XQvlygbFoNT4R76rSIRxekZ9TL6EbTIC/kJUDrSPB3W9RafA6n6p4OLoWgEF7MIJ9W+zfibdiVXbw=="],
"@react-native-tvos/virtualized-lists": ["@react-native-tvos/virtualized-lists@0.77.2-0", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^18.2.6", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-9l51YsjgrUv6f3Q8bmQPIPRuID6gLfc29CjLLQ3+RIeHFF1xzT/xwOp0+s7JMhDdZOZ5mcn9RiN7BbmcPej08A=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.77.0", "", {}, "sha512-Ms4tYYAMScgINAXIhE4riCFJPPL/yltughHS950l0VP5sm5glbimn9n7RFn9Tc8cipX74/ddbk19+ydK2iDMmA=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.77.2", "", {}, "sha512-AcEhFjndzBWVVhaHaASk36vhA83iDVkQbFYb0D0vATzjuJ67vhhHVLae0+JtHl5jhghotUFDg4Vj/1QbZNDyyQ=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.76.7", "", { "dependencies": { "@react-native/codegen": "0.76.7" } }, "sha512-+8H4DXJREM4l/pwLF/wSVMRzVhzhGDix5jLezNrMD9J1U1AMfV2aSkWA1XuqR7pjPs/Vqf6TaPL7vJMZ4LU05Q=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.76.7", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.76.7", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-/c5DYZ6y8tyg+g8tgXKndDT7mWnGmkZ9F+T3qNDfoE3Qh7ucrNeC2XWvU9h5pk8eRtj9l4SzF4aO1phzwoibyg=="],
"@react-native/codegen": ["@react-native/codegen@0.77.0", "", { "dependencies": { "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "jscodeshift": "^17.0.0", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/preset-env": "^7.1.6" } }, "sha512-rE9lXx41ZjvE8cG7e62y/yGqzUpxnSvJ6me6axiX+aDewmI4ZrddvRGYyxCnawxy5dIBHSnrpZse3P87/4Lm7w=="],
"@react-native/codegen": ["@react-native/codegen@0.77.2", "", { "dependencies": { "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "jscodeshift": "^17.0.0", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/preset-env": "^7.1.6" } }, "sha512-uJSGm9Sp9K5XAhb17cty6iOc2lZpORQKMpS61/B3gYwe9LNz9TJpcfq1L2+3Mv6lppqsulOH9+fslapo0OTfSQ=="],
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.77.0", "", { "dependencies": { "@react-native/dev-middleware": "0.77.0", "@react-native/metro-babel-transformer": "0.77.0", "chalk": "^4.0.0", "debug": "^2.2.0", "invariant": "^2.2.4", "metro": "^0.81.0", "metro-config": "^0.81.0", "metro-core": "^0.81.0", "readline": "^1.3.0", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli-server-api": "*" }, "optionalPeers": ["@react-native-community/cli-server-api"] }, "sha512-GRshwhCHhtupa3yyCbel14SlQligV8ffNYN5L1f8HCo2SeGPsBDNjhj2U+JTrMPnoqpwowPGvkCwyqwqYff4MQ=="],
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.77.2", "", { "dependencies": { "@react-native/dev-middleware": "0.77.2", "@react-native/metro-babel-transformer": "0.77.2", "chalk": "^4.0.0", "debug": "^2.2.0", "invariant": "^2.2.4", "metro": "^0.81.3", "metro-config": "^0.81.3", "metro-core": "^0.81.3", "readline": "^1.3.0", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*" }, "optionalPeers": ["@react-native-community/cli"] }, "sha512-Dc93eXHhzhnRy+vF3wOdM8C4dplLpT7ItpUpYrDeA1ffHUImwWpcupB6vpX9+l3UaaJ1cPfdxTjB2d1ACVKOaA=="],
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.76.7", "", {}, "sha512-89ZtZXt7ZxE94i7T94qzZMhp4Gfcpr/QVpGqEaejAxZD+gvDCH21cYSF+/Rz2ttBazm0rk5MZ0mFqb0Iqp1jmw=="],
"@react-native/dev-middleware": ["@react-native/dev-middleware@0.76.7", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.76.7", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^2.2.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "selfsigned": "^2.4.1", "serve-static": "^1.13.1", "ws": "^6.2.3" } }, "sha512-Jsw8g9DyLPnR9yHEGuT09yHZ7M88/GL9CtU9WmyChlBwdXSeE3AmRqLegsV3XcgULQ1fqdemokaOZ/MwLYkjdA=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.77.0", "", {}, "sha512-rmfh93jzbndSq7kihYHUQ/EGHTP8CCd3GDCmg5SbxSOHAaAYx2HZ28ZG7AVcGUsWeXp+e/90zGIyfOzDRx0Zaw=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.77.2", "", {}, "sha512-M3kU6xnn/06CGdezd31wn64v/BuKdw19K3GjOcRe1L+zKYEeezRovEVgzCNsXLcNtXUfJvmrIN4uYnqmgrJGfg=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.77.0", "", {}, "sha512-kHFcMJVkGb3ptj3yg1soUsMHATqal4dh0QTGAbYihngJ6zy+TnP65J3GJq4UlwqFE9K1RZkeCmTwlmyPFHOGvA=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.77.2", "", {}, "sha512-qwKeYqRANL8CKzeVWOdhRZJ7LBqqoiXR+cb5yGwVKQxqesrx5Y7gYyq6GP1zRMnhv9iQAY7Rwub8TvDxi2YP6Q=="],
"@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.77.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.77.0", "hermes-parser": "0.25.1", "nullthrows": "^1.1.1" } }, "sha512-19GfvhBRKCU3UDWwCnDR4QjIzz3B2ZuwhnxMRwfAgPxz7QY9uKour9RGmBAVUk1Wxi/SP7dLEvWnmnuBO39e2A=="],
"@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.77.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.77.2", "hermes-parser": "0.25.1", "nullthrows": "^1.1.1" } }, "sha512-vSG1/d5peUo50aqaBbNnVGE5QxQTSY3j0OWmixfJqiX11wwO3tR2niKxH8OjB3WuSsROgJzosMe9kMsQJQ3ONA=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.76.7", "", {}, "sha512-ST1xxBuYVIXPdD81dR6+tzIgso7m3pa9+6rOBXTh5Xm7KEEFik7tnQX+GydXYMp3wr1gagJjragdXkPnxK6WNg=="],
@@ -984,6 +985,8 @@
"cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="],
"cross-env": ["cross-env@7.0.3", "", { "dependencies": { "cross-spawn": "^7.0.1" }, "bin": { "cross-env": "src/bin/cross-env.js", "cross-env-shell": "src/bin/cross-env-shell.js" } }, "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw=="],
"cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
@@ -1574,33 +1577,33 @@
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"metro": ["metro@0.81.1", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^2.2.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.25.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.6.3", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.81.1", "metro-cache": "0.81.1", "metro-cache-key": "0.81.1", "metro-config": "0.81.1", "metro-core": "0.81.1", "metro-file-map": "0.81.1", "metro-resolver": "0.81.1", "metro-runtime": "0.81.1", "metro-source-map": "0.81.1", "metro-symbolicate": "0.81.1", "metro-transform-plugins": "0.81.1", "metro-transform-worker": "0.81.1", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-fqRu4fg8ONW7VfqWFMGgKAcOuMzyoQah2azv9Y3VyFXAmG+AoTU6YIFWqAADESCGVWuWEIvxTJhMf3jxU6jwjA=="],
"metro": ["metro@0.81.5", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^2.2.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.25.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.81.5", "metro-cache": "0.81.5", "metro-cache-key": "0.81.5", "metro-config": "0.81.5", "metro-core": "0.81.5", "metro-file-map": "0.81.5", "metro-resolver": "0.81.5", "metro-runtime": "0.81.5", "metro-source-map": "0.81.5", "metro-symbolicate": "0.81.5", "metro-transform-plugins": "0.81.5", "metro-transform-worker": "0.81.5", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-YpFF0DDDpDVygeca2mAn7K0+us+XKmiGk4rIYMz/CRdjFoCGqAei/IQSpV0UrGfQbToSugpMQeQJveaWSH88Hg=="],
"metro-babel-transformer": ["metro-babel-transformer@0.81.1", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.25.1", "nullthrows": "^1.1.1" } }, "sha512-JECKDrQaUnDmj0x/Q/c8c5YwsatVx38Lu+BfCwX9fR8bWipAzkvJocBpq5rOAJRDXRgDcPv2VO4Q4nFYrpYNQg=="],
"metro-babel-transformer": ["metro-babel-transformer@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.25.1", "nullthrows": "^1.1.1" } }, "sha512-oKCQuajU5srm+ZdDcFg86pG/U8hkSjBlkyFjz380SZ4TTIiI5F+OQB830i53D8hmqmcosa4wR/pnKv8y4Q3dLw=="],
"metro-cache": ["metro-cache@0.81.1", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "metro-core": "0.81.1" } }, "sha512-Uqcmn6sZ+Y0VJHM88VrG5xCvSeU7RnuvmjPmSOpEcyJJBe02QkfHL05MX2ZyGDTyZdbKCzaX0IijrTe4hN3F0Q=="],
"metro-cache": ["metro-cache@0.81.5", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "metro-core": "0.81.5" } }, "sha512-wOsXuEgmZMZ5DMPoz1pEDerjJ11AuMy9JifH4yNW7NmWS0ghCRqvDxk13LsElzLshey8C+my/tmXauXZ3OqZgg=="],
"metro-cache-key": ["metro-cache-key@0.81.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-5fDaHR1yTvpaQuwMAeEoZGsVyvjrkw9IFAS7WixSPvaNY5YfleqoJICPc6hbXFJjvwCCpwmIYFkjqzR/qJ6yqA=="],
"metro-cache-key": ["metro-cache-key@0.81.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-lGWnGVm1UwO8faRZ+LXQUesZSmP1LOg14OVR+KNPBip8kbMECbQJ8c10nGesw28uQT7AE0lwQThZPXlxDyCLKQ=="],
"metro-config": ["metro-config@0.81.1", "", { "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.6.3", "metro": "0.81.1", "metro-cache": "0.81.1", "metro-core": "0.81.1", "metro-runtime": "0.81.1" } }, "sha512-VAAJmxsKIZ+Fz5/z1LVgxa32gE6+2TvrDSSx45g85WoX4EtLmdBGP3DSlpQW3DqFUfNHJCGwMLGXpJnxifd08g=="],
"metro-config": ["metro-config@0.81.5", "", { "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.81.5", "metro-cache": "0.81.5", "metro-core": "0.81.5", "metro-runtime": "0.81.5" } }, "sha512-oDRAzUvj6RNRxratFdcVAqtAsg+T3qcKrGdqGZFUdwzlFJdHGR9Z413sW583uD2ynsuOjA2QB6US8FdwiBdNKg=="],
"metro-core": ["metro-core@0.81.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.81.1" } }, "sha512-4d2/+02IYqOwJs4dmM0dC8hIZqTzgnx2nzN4GTCaXb3Dhtmi/SJ3v6744zZRnithhN4lxf8TTJSHnQV75M7SSA=="],
"metro-core": ["metro-core@0.81.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.81.5" } }, "sha512-+2R0c8ByfV2N7CH5wpdIajCWa8escUFd8TukfoXyBq/vb6yTCsznoA25FhNXJ+MC/cz1L447Zj3vdUfCXIZBwg=="],
"metro-file-map": ["metro-file-map@0.81.1", "", { "dependencies": { "debug": "^2.2.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.6.3", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-aY72H2ujmRfFxcsbyh83JgqFF+uQ4HFN1VhV2FmcfQG4s1bGKf2Vbkk+vtZ1+EswcBwDZFbkpvAjN49oqwGzAA=="],
"metro-file-map": ["metro-file-map@0.81.5", "", { "dependencies": { "debug": "^2.2.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-mW1PKyiO3qZvjeeVjj1brhkmIotObA3/9jdbY1fQQYvEWM6Ml7bN/oJCRDGn2+bJRlG+J8pwyJ+DgdrM4BsKyg=="],
"metro-minify-terser": ["metro-minify-terser@0.81.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-p/Qz3NNh1nebSqMlxlUALAnESo6heQrnvgHtAuxufRPtKvghnVDq9hGGex8H7z7YYLsqe42PWdt4JxTA3mgkvg=="],
"metro-minify-terser": ["metro-minify-terser@0.81.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-/mn4AxjANnsSS3/Bb+zA1G5yIS5xygbbz/OuPaJYs0CPcZCaWt66D+65j4Ft/nJkffUxcwE9mk4ubpkl3rjgtw=="],
"metro-resolver": ["metro-resolver@0.81.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-E61t6fxRoYRkl6Zo3iUfCKW4DYfum/bLjcejXBMt1y3I7LFkK84TCR/Rs9OAwsMCY/7GOPB4+CREYZOtCC7CNA=="],
"metro-resolver": ["metro-resolver@0.81.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-6BX8Nq3g3go3FxcyXkVbWe7IgctjDTk6D9flq+P201DfHHQ28J+DWFpVelFcrNTn4tIfbP/Bw7u/0g2BGmeXfQ=="],
"metro-runtime": ["metro-runtime@0.81.1", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-pqu5j5d01rjF85V/K8SDDJ0NR3dRp6bE3z5bKVVb5O2Rx0nbR9KreUxYALQCRCcQHaYySqCg5fYbGKBHC295YQ=="],
"metro-runtime": ["metro-runtime@0.81.5", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-M/Gf71ictUKP9+77dV/y8XlAWg7xl76uhU7ggYFUwEdOHHWPG6gLBr1iiK0BmTjPFH8yRo/xyqMli4s3oGorPQ=="],
"metro-source-map": ["metro-source-map@0.81.1", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.81.1", "nullthrows": "^1.1.1", "ob1": "0.81.1", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-1i8ROpNNiga43F0ZixAXoFE/SS3RqcRDCCslpynb+ytym0VI7pkTH1woAN2HI9pczYtPrp3Nq0AjRpsuY35ieA=="],
"metro-source-map": ["metro-source-map@0.81.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.81.5", "nullthrows": "^1.1.1", "ob1": "0.81.5", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-Jz+CjvCKLNbJZYJTBeN3Kq9kIJf6b61MoLBdaOQZJ5Ajhw6Pf95Nn21XwA8BwfUYgajsi6IXsp/dTZsYJbN00Q=="],
"metro-symbolicate": ["metro-symbolicate@0.81.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.81.1", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-Lgk0qjEigtFtsM7C0miXITbcV47E1ZYIfB+m/hCraihiwRWkNUQEPCWvqZmwXKSwVE5mXA0EzQtghAvQSjZDxw=="],
"metro-symbolicate": ["metro-symbolicate@0.81.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.81.5", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-X3HV3n3D6FuTE11UWFICqHbFMdTavfO48nXsSpnNGFkUZBexffu0Xd+fYKp+DJLNaQr3S+lAs8q9CgtDTlRRuA=="],
"metro-transform-plugins": ["metro-transform-plugins@0.81.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-7L1lI44/CyjIoBaORhY9fVkoNe8hrzgxjSCQ/lQlcfrV31cZb7u0RGOQrKmUX7Bw4FpejrB70ArQ7Mse9mk7+Q=="],
"metro-transform-plugins": ["metro-transform-plugins@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-MmHhVx/1dJC94FN7m3oHgv5uOjKH8EX8pBeu1pnPMxbJrx6ZuIejO0k84zTSaQTZ8RxX1wqwzWBpXAWPjEX8mA=="],
"metro-transform-worker": ["metro-transform-worker@0.81.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.81.1", "metro-babel-transformer": "0.81.1", "metro-cache": "0.81.1", "metro-cache-key": "0.81.1", "metro-minify-terser": "0.81.1", "metro-source-map": "0.81.1", "metro-transform-plugins": "0.81.1", "nullthrows": "^1.1.1" } }, "sha512-M+2hVT3rEy5K7PBmGDgQNq3Zx53TjScOcO/CieyLnCRFtBGWZiSJ2+bLAXXOKyKa/y3bI3i0owxtyxuPGDwbZg=="],
"metro-transform-worker": ["metro-transform-worker@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.81.5", "metro-babel-transformer": "0.81.5", "metro-cache": "0.81.5", "metro-cache-key": "0.81.5", "metro-minify-terser": "0.81.5", "metro-source-map": "0.81.5", "metro-transform-plugins": "0.81.5", "nullthrows": "^1.1.1" } }, "sha512-lUFyWVHa7lZFRSLJEv+m4jH8WrR5gU7VIjUlg4XmxQfV8ngY4V10ARKynLhMYPeQGl7Qvf+Ayg0eCZ272YZ4Mg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
@@ -1676,7 +1679,7 @@
"nullthrows": ["nullthrows@1.1.1", "", {}, "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="],
"ob1": ["ob1@0.81.1", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-1PEbvI+AFvOcgdNcO79FtDI1TUO8S3lhiKOyAiyWQF3sFDDKS+aw2/BZvGlArFnSmqckwOOB9chQuIX0/OahoQ=="],
"ob1": ["ob1@0.81.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-iNpbeXPLmaiT9I5g16gFFFjsF3sGxLpYG2EGP3dfFB4z+l9X60mp/yRzStHhMtuNt8qmf7Ww80nOPQHngHhnIQ=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@@ -1846,9 +1849,9 @@
"react-i18next": ["react-i18next@15.4.1", "", { "dependencies": { "@babel/runtime": "^7.25.0", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 23.2.3", "react": ">= 16.8.0" } }, "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw=="],
"react-is": ["react-is@19.0.0", "", {}, "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g=="],
"react-is": ["react-is@19.1.0", "", {}, "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg=="],
"react-native": ["react-native-tvos@0.77.0-0", "", { "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native-tvos/virtualized-lists": "0.77.0-0", "@react-native/assets-registry": "0.77.0", "@react-native/codegen": "0.77.0", "@react-native/community-cli-plugin": "0.77.0", "@react-native/gradle-plugin": "0.77.0", "@react-native/js-polyfills": "0.77.0", "@react-native/normalize-colors": "0.77.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.6.3", "jsc-android": "^250231.0.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.81.0", "metro-source-map": "^0.81.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.0.1", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.24.0-canary-efb381bbf-20230505", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^18.2.6", "react": "^18.2.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-edIOqGrPadpXHmt5R/LuhekHHLx/0DyrfY5A9odS2AlS+03S0ada7H5oDvusOUVcyq1vc3isrwZpUSQzudoR1g=="],
"react-native": ["react-native-tvos@0.77.2-0", "", { "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native-tvos/virtualized-lists": "0.77.2-0", "@react-native/assets-registry": "0.77.2", "@react-native/codegen": "0.77.2", "@react-native/community-cli-plugin": "0.77.2", "@react-native/gradle-plugin": "0.77.2", "@react-native/js-polyfills": "0.77.2", "@react-native/normalize-colors": "0.77.2", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.6.3", "jsc-android": "^250231.0.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.81.3", "metro-source-map": "^0.81.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.0.1", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.24.0-canary-efb381bbf-20230505", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^18.2.6", "react": "^18.2.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-Ys0tka4VRxClE8oGV4itR0CaeQwtI7jQ51uO7DedmUpt3m8I5uUUFQANgH8IhdEeTtvyPFbnCUffbpcFm59jKg=="],
"react-native-awesome-slider": ["react-native-awesome-slider@2.9.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=3.0.0" } }, "sha512-sc5qgX4YtM6IxjtosjgQLdsal120MvU+YWs0F2MdgQWijps22AXLDCUoBnZZ8vxVhVyJ2WnnIPrmtVBvVJjSuQ=="],
@@ -1900,8 +1903,6 @@
"react-native-tab-view": ["react-native-tab-view@4.0.5", "", { "dependencies": { "use-latest-callback": "^0.2.1" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0" } }, "sha512-Xn3TpYo4yvKRC/f4+cOcvsXlitdnSaYkacshckrEI3JiDmFKNFIRVNxtZFggm4MwbJafq2RzuzR6xrgKoxgkTw=="],
"react-native-tvos": ["react-native-tvos@0.77.0-0", "", { "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native-tvos/virtualized-lists": "0.77.0-0", "@react-native/assets-registry": "0.77.0", "@react-native/codegen": "0.77.0", "@react-native/community-cli-plugin": "0.77.0", "@react-native/gradle-plugin": "0.77.0", "@react-native/js-polyfills": "0.77.0", "@react-native/normalize-colors": "0.77.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.6.3", "jsc-android": "^250231.0.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.81.0", "metro-source-map": "^0.81.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.0.1", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.24.0-canary-efb381bbf-20230505", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^18.2.6", "react": "^18.2.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-edIOqGrPadpXHmt5R/LuhekHHLx/0DyrfY5A9odS2AlS+03S0ada7H5oDvusOUVcyq1vc3isrwZpUSQzudoR1g=="],
"react-native-udp": ["react-native-udp@4.1.7", "", { "dependencies": { "buffer": "^5.6.0", "events": "^3.1.0" } }, "sha512-NUE3zewu61NCdSsLlj+l0ad6qojcVEZPT4hVG/x6DU9U4iCzwtfZSASh9vm7teAcVzLkdD+cO3411LHshAi/wA=="],
"react-native-uitextview": ["react-native-uitextview@1.4.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-itm/frzkn/ma3+lwmKn2CkBOXPNo4bL8iVwQwjlzix5gVO59T2+axdfoj/Wi+Ra6F76KzNKxSah+7Y8dYmCHbQ=="],
@@ -1926,7 +1927,7 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"react-test-renderer": ["react-test-renderer@19.0.0", "", { "dependencies": { "react-is": "^19.0.0", "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-oX5u9rOQlHzqrE/64CNr0HB0uWxkCQmZNSfozlYvwE71TLVgeZxVf0IjouGEr1v7r1kcDifdAJBeOhdhxsG/DA=="],
"react-test-renderer": ["react-test-renderer@19.1.0", "", { "dependencies": { "react-is": "^19.1.0", "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-jXkSl3CpvPYEF+p/eGDLB4sPoDX8pKkYvRl9+rR8HxLY0X04vW7hCm1/0zHoUSjPZ3bDa+wXWNTDVIw/R8aDVw=="],
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
@@ -1992,7 +1993,7 @@
"sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
"scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"schema-utils": ["schema-utils@4.3.0", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g=="],
@@ -2422,7 +2423,7 @@
"@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware": ["@react-native/dev-middleware@0.77.0", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.77.0", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^2.2.0", "nullthrows": "^1.1.1", "open": "^7.0.3", "selfsigned": "^2.4.1", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-DAlEYujm43O+Dq98KP2XfLSX5c/TEGtt+JBDEIOQewk374uYY52HzRb1+Gj6tNaEj/b33no4GibtdxbO5zmPhg=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware": ["@react-native/dev-middleware@0.77.2", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.77.2", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^2.2.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "selfsigned": "^2.4.1", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-LBK0kY4XxE4vHVHJ3TwBGXmjl2ad9dsbbwnVgXwYNL/mkkWb2MHlmgHj6xlCMe1gtLtem2TpEF17TKg50ykPJw=="],
"@react-native/community-cli-plugin/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@@ -2432,7 +2433,7 @@
"@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
"@react-native/metro-babel-transformer/@react-native/babel-preset": ["@react-native/babel-preset@0.77.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.77.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-Z4yxE66OvPyQ/iAlaETI1ptRLcDm7Tk6ZLqtCPuUX3AMg+JNgIA86979T4RSk486/JrBUBH5WZe2xjj7eEHXsA=="],
"@react-native/metro-babel-transformer/@react-native/babel-preset": ["@react-native/babel-preset@0.77.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.77.2", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-If6X4I0z6W5aVzqZS4JOrN7sh08w1QzEL8Q66i3g0wI8K8ZK+V+/ARlEmboy14VtcOYlmmjXEqSCv+Z2o9cuKg=="],
"@react-navigation/core/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
@@ -2590,7 +2591,7 @@
"react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"react-native/@react-native/normalize-colors": ["@react-native/normalize-colors@0.77.0", "", {}, "sha512-qjmxW3xRZe4T0ZBEaXZNHtuUbRgyfybWijf1yUuQwjBt24tSapmIslwhCjpKidA0p93ssPcepquhY0ykH25mew=="],
"react-native/@react-native/normalize-colors": ["@react-native/normalize-colors@0.77.2", "", {}, "sha512-knKStQKX4KM8GkieeayotcSTO7I7PIZxwI71nhK/zBeRPqhDTJMNJQh5TnZJ63fO1Y+EZclWkRIKEj+aFRsssw=="],
"react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
@@ -2600,16 +2601,6 @@
"react-native/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"react-native-tvos/@react-native/normalize-colors": ["@react-native/normalize-colors@0.77.0", "", {}, "sha512-qjmxW3xRZe4T0ZBEaXZNHtuUbRgyfybWijf1yUuQwjBt24tSapmIslwhCjpKidA0p93ssPcepquhY0ykH25mew=="],
"react-native-tvos/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
"react-native-tvos/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"react-native-tvos/scheduler": ["scheduler@0.24.0-canary-efb381bbf-20230505", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA=="],
"react-native-tvos/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
"react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
@@ -2756,7 +2747,7 @@
"@react-native/codegen/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.77.0", "", {}, "sha512-glOvSEjCbVXw+KtfiOAmrq21FuLE1VsmBsyT7qud4KWbXP43aUEhzn70mWyFuiIdxnzVPKe2u8iWTQTdJksR1w=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.77.2", "", {}, "sha512-MRLjQLJr9C0M/TggoycEgYR7lUEZph4cg5PhUwBoNyRquV7lGHqMKNkfMBYBT09cuwKn9O+cFvQOmMNVqsPLxw=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
@@ -2764,7 +2755,7 @@
"@react-native/dev-middleware/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"@react-native/metro-babel-transformer/@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.77.0", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.77.0" } }, "sha512-5TYPn1k+jdDOZJU4EVb1kZ0p9TCVICXK3uplRev5Gul57oWesAaiWGZOzfRS3lonWeuR4ij8v8PFfIHOaq0vmA=="],
"@react-native/metro-babel-transformer/@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.77.2", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.77.2" } }, "sha512-2PShbsfsa4NZS+Zt0y2tl1AoWza5podKFmPE5qcYjJoN915VoH3BRkiTVlSpYNKmdvs31o1aQuXAMQDTh7DZ/g=="],
"ansi-fragments/slice-ansi/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
@@ -2848,8 +2839,6 @@
"pkg-dir/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
"react-native-tvos/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"react-native/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"readable-web-to-node-stream/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
@@ -2942,8 +2931,6 @@
"pkg-dir/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
"react-native-tvos/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"react-native/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],

View File

@@ -17,6 +17,7 @@ import { useImageColors } from "@/hooks/useImageColors";
import { useOrientation } from "@/hooks/useOrientation";
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
import { apiAtom } from "@/providers/JellyfinProvider";
import { userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
import type {
@@ -34,6 +35,7 @@ import { ItemHeader } from "./ItemHeader";
import { ItemTechnicalDetails } from "./ItemTechnicalDetails";
import { MediaSourceSelector } from "./MediaSourceSelector";
import { MoreMoviesWithActor } from "./MoreMoviesWithActor";
import { PlayInRemoteSessionButton } from "./PlayInRemoteSession";
const Chromecast = !Platform.isTV ? require("./Chromecast") : null;
export type SelectedOptions = {
@@ -50,6 +52,8 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
const { orientation } = useOrientation();
const navigation = useNavigation();
const insets = useSafeAreaInsets();
const [user] = useAtom(userAtom);
useImageColors({ item });
const [loadingLogo, setLoadingLogo] = useState(true);
@@ -97,6 +101,10 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
{!Platform.isTV && (
<DownloadSingleItem item={item} size='large' />
)}
{user?.Policy?.IsAdministrator && (
<PlayInRemoteSessionButton item={item} size='large' />
)}
<PlayedStatus items={[item]} size='large' />
<AddToFavorites item={item} />
</View>

View File

@@ -0,0 +1,194 @@
import { useAllSessions, type useSessionsProps } from "@/hooks/useSessions";
import { apiAtom } from "@/providers/JellyfinProvider";
import { Ionicons } from "@expo/vector-icons";
import {
type BaseItemDto,
PlayCommand,
} from "@jellyfin/sdk/lib/generated-client/models";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
import { useAtomValue } from "jotai";
import React, { useState } from "react";
import {
FlatList,
Modal,
StyleSheet,
TouchableOpacity,
View,
} from "react-native";
import { Loader } from "./Loader";
import { RoundButton } from "./RoundButton";
import { Text } from "./common/Text";
interface Props extends React.ComponentProps<typeof View> {
item: BaseItemDto;
size?: "default" | "large";
}
export const PlayInRemoteSessionButton: React.FC<Props> = ({
item,
...props
}) => {
const [modalVisible, setModalVisible] = useState(false);
const api = useAtomValue(apiAtom);
const { sessions, isLoading } = useAllSessions({} as useSessionsProps);
const handlePlayInSession = async (sessionId: string) => {
if (!api || !item.Id) return;
try {
console.log(`Playing ${item.Name} in session ${sessionId}`);
getSessionApi(api).play({
sessionId,
itemIds: [item.Id],
playCommand: PlayCommand.PlayNow,
});
setModalVisible(false);
} catch (error) {
console.error("Error playing in remote session:", error);
}
};
return (
<View {...props}>
<RoundButton
icon='play-circle-outline'
onPress={() => setModalVisible(true)}
size={props.size}
/>
<Modal
animationType='slide'
transparent={true}
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>Select Session</Text>
<TouchableOpacity onPress={() => setModalVisible(false)}>
<Ionicons name='close' size={24} color='white' />
</TouchableOpacity>
</View>
<View style={styles.modalContent}>
{isLoading ? (
<View style={styles.loadingContainer}>
<Loader />
</View>
) : !sessions || sessions.length === 0 ? (
<Text style={styles.noSessionsText}>
No active sessions found
</Text>
) : (
<FlatList
data={sessions}
keyExtractor={(session) => session.Id || "unknown"}
renderItem={({ item: session }) => (
<TouchableOpacity
style={styles.sessionItem}
onPress={() => handlePlayInSession(session.Id || "")}
>
<View style={styles.sessionInfo}>
<Text style={styles.sessionName}>
{session.DeviceName}
</Text>
<Text style={styles.sessionDetails}>
{session.UserName} {session.Client}
</Text>
{session.NowPlayingItem && (
<Text style={styles.nowPlaying} numberOfLines={1}>
Now playing:{" "}
{session.NowPlayingItem.SeriesName
? `${session.NowPlayingItem.SeriesName} :`
: ""}
{session.NowPlayingItem.Name}
</Text>
)}
</View>
<Ionicons name='play-sharp' size={20} color='#888' />
</TouchableOpacity>
)}
contentContainerStyle={styles.listContent}
/>
)}
</View>
</View>
</View>
</Modal>
</View>
);
};
const styles = StyleSheet.create({
centeredView: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.7)",
},
modalView: {
width: "90%",
maxHeight: "80%",
backgroundColor: "#1c1c1c",
borderRadius: 20,
overflow: "hidden",
display: "flex",
flexDirection: "column",
},
modalHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: "#333",
},
modalContent: {
flex: 1,
},
modalTitle: {
fontSize: 18,
fontWeight: "600",
},
loadingContainer: {
padding: 40,
alignItems: "center",
},
noSessionsText: {
padding: 40,
textAlign: "center",
color: "#888",
},
listContent: {
paddingVertical: 8,
},
sessionItem: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingVertical: 12,
paddingHorizontal: 16,
borderBottomWidth: 1,
borderBottomColor: "#333",
},
sessionInfo: {
flex: 1,
},
sessionName: {
fontSize: 16,
fontWeight: "500",
marginBottom: 4,
},
sessionDetails: {
fontSize: 13,
opacity: 0.7,
marginBottom: 2,
},
nowPlaying: {
fontSize: 12,
opacity: 0.5,
fontStyle: "italic",
},
});

View File

@@ -53,6 +53,7 @@ const SeriesPoster: React.FC<MoviePosterProps> = ({ item }) => {
width: "100%",
}}
/>
{<WatchedIndicator item={item} />}
</View>
);
};

View File

@@ -9,7 +9,6 @@ import type { PropsWithChildren } from "react";
import { Text } from "../common/Text";
type SearchItemWrapperProps<T> = {
ids?: string[] | null;
items?: T[];
renderItem: (item: any) => React.ReactNode;
header?: string;
@@ -17,7 +16,6 @@ type SearchItemWrapperProps<T> = {
};
export const SearchItemWrapper = <T,>({
ids,
items,
renderItem,
header,
@@ -26,33 +24,7 @@ export const SearchItemWrapper = <T,>({
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const { data, isLoading: l1 } = useQuery({
queryKey: ["items", ids],
queryFn: async () => {
if (!user?.Id || !api || !ids || ids.length === 0) {
return [];
}
const itemPromises = ids.map((id) =>
getUserItemData({
api,
userId: user.Id,
itemId: id,
}),
);
const results = await Promise.all(itemPromises);
// Filter out null items
return results.filter(
(item) => item !== null,
) as unknown as BaseItemDto[];
},
enabled: !!ids && ids.length > 0 && !!api && !!user?.Id,
staleTime: Number.POSITIVE_INFINITY,
});
if (!data && (!items || items.length === 0)) return null;
if (!items || items.length === 0) return null;
return (
<>
@@ -67,7 +39,7 @@ export const SearchItemWrapper = <T,>({
keyExtractor={(_, index) => index.toString()}
estimatedItemSize={250}
/*@ts-ignore */
data={data || items}
data={items}
onEndReachedThreshold={1}
onEndReached={onEndReached}
//@ts-ignore

View File

@@ -36,6 +36,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
ActivityIndicator,
Platform,
RefreshControl,
ScrollView,
TouchableOpacity,
@@ -87,6 +88,12 @@ export const HomeIndex = () => {
const { downloadedFiles, cleanCacheDirectory } = useDownload();
useEffect(() => {
if (Platform.isTV) {
navigation.setOptions({
headerLeft: () => null,
});
return;
}
const hasDownloads = downloadedFiles && downloadedFiles.length > 0;
navigation.setOptions({
headerLeft: () => (
@@ -206,19 +213,43 @@ export const HomeIndex = () => {
queryKey,
queryFn: async () => {
if (!api) return [];
return (
(
await getUserLibraryApi(api).getLatestMedia({
userId: user?.Id,
limit: 20,
fields: ["PrimaryImageAspectRatio", "Path"],
imageTypeLimit: 1,
enableImageTypes: ["Primary", "Backdrop", "Thumb"],
includeItemTypes,
parentId,
})
).data || []
);
const response = await getItemsApi(api).getItems({
userId: user?.Id,
limit: 40,
recursive: true,
includeItemTypes,
sortBy: ["DateCreated"],
sortOrder: ["Descending"],
fields: ["PrimaryImageAspectRatio", "Path"],
parentId,
enableImageTypes: ["Primary", "Backdrop", "Thumb"],
});
let items = response.data.Items || [];
if (includeItemTypes.includes("Episode")) {
// Removes individual episodes from the list if they are part of a series
// and only keeps the series item
// Note: The 'Latest' API endpoint does not work well with combining batch episode imports
// and will either only show the series or the episodes, not both.
// This is a workaround to filter out the episodes from the list
const seriesIds = new Set(
items.filter((i) => i.Type === "Series").map((i) => i.Id),
);
items = items.filter(
(i) =>
i.Type === "Series" ||
(i.Type === "Episode" && !seriesIds.has(i.SeriesId!)),
);
}
if (items.length > 20) {
items = items.slice(0, 20);
}
return items;
},
type: "ScrollingCollectionList",
}),
@@ -232,7 +263,7 @@ export const HomeIndex = () => {
const latestMediaViews = collections.map((c) => {
const includeItemTypes: BaseItemKind[] =
c.CollectionType === "tvshows" ? ["Series"] : ["Movie"];
c.CollectionType === "tvshows" ? ["Episode", "Series"] : ["Movie"];
const title = t("home.recently_added_in", { libraryName: c.Name });
const queryKey = [
"home",
@@ -358,10 +389,10 @@ export const HomeIndex = () => {
const response = await getTvShowsApi(api).getNextUp({
userId: user?.Id,
fields: ["MediaSourceCount"],
limit: section.items?.limit || 25,
limit: section.nextUp?.limit || 25,
enableImageTypes: ["Primary", "Backdrop", "Thumb"],
enableResumable: section.items?.enableResumable,
enableRewatching: section.items?.enableRewatching,
enableResumable: section.nextUp?.enableResumable,
enableRewatching: section.nextUp?.enableRewatching,
});
return response.data.Items || [];
}

View File

@@ -44,3 +44,27 @@ export const useSessions = ({
return { sessions: data, isLoading };
};
export const useAllSessions = ({
refetchInterval = 5 * 1000,
activeWithinSeconds = 360,
}: useSessionsProps) => {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const { data, isLoading } = useQuery({
queryKey: ["allSessions"],
queryFn: async () => {
if (!api || !user || !user.Policy?.IsAdministrator) {
return [];
}
const response = await getSessionApi(api).getSessions({
activeWithinSeconds: activeWithinSeconds,
});
return response.data;
},
refetchInterval: refetchInterval,
});
return { sessions: data, isLoading };
};

View File

@@ -9,6 +9,38 @@ interface UseWebSocketProps {
togglePlay: () => void;
stopPlayback: () => void;
offline: boolean;
nextTrack?: () => void;
previousTrack?: () => void;
rewindPlayback?: () => void;
fastForwardPlayback?: () => void;
seekPlayback?: (positionTicks: number) => void;
volumeUp?: () => void;
volumeDown?: () => void;
toggleMute?: () => void;
toggleOsd?: () => void;
toggleFullscreen?: () => void;
goHome?: () => void;
goToSettings?: () => void;
setAudioStreamIndex?: (index: number) => void;
setSubtitleStreamIndex?: (index: number) => void;
moveUp?: () => void;
moveDown?: () => void;
moveLeft?: () => void;
moveRight?: () => void;
select?: () => void;
pageUp?: () => void;
pageDown?: () => void;
setVolume?: (volume: number) => void;
setRepeatMode?: (mode: string) => void;
setShuffleMode?: (mode: string) => void;
togglePictureInPicture?: () => void;
takeScreenshot?: () => void;
sendString?: (text: string) => void;
sendKey?: (key: string) => void;
playMediaSource?: (itemIds: string[], startPositionTicks?: number) => void;
playTrailers?: (itemId: string) => void;
}
export const useWebSocket = ({
@@ -16,38 +48,270 @@ export const useWebSocket = ({
togglePlay,
stopPlayback,
offline,
nextTrack,
previousTrack,
rewindPlayback,
fastForwardPlayback,
seekPlayback,
volumeUp,
volumeDown,
toggleMute,
toggleOsd,
toggleFullscreen,
goHome,
goToSettings,
setAudioStreamIndex,
setSubtitleStreamIndex,
moveUp,
moveDown,
moveLeft,
moveRight,
select,
pageUp,
pageDown,
setVolume,
setRepeatMode,
setShuffleMode,
togglePictureInPicture,
takeScreenshot,
sendString,
sendKey,
playMediaSource,
playTrailers,
}: UseWebSocketProps) => {
const router = useRouter();
const { ws } = useWebSocketContext();
const { lastMessage } = useWebSocketContext();
const { t } = useTranslation();
const { clearLastMessage } = useWebSocketContext();
useEffect(() => {
if (!ws) return;
if (!lastMessage) return;
if (offline) return;
ws.onmessage = (e) => {
const json = JSON.parse(e.data);
const command = json?.Data?.Command;
const messageType = lastMessage.MessageType;
const command: string | undefined =
lastMessage?.Data?.Command || lastMessage?.Data?.Name;
console.log("[WS] ~ ", json);
const args = lastMessage?.Data?.Arguments as
| Record<string, string>
| undefined; // Arguments are Dictionary<string, string>
if (command === "PlayPause") {
console.log("Command ~ PlayPause");
console.log("[WS] ~ ", lastMessage);
if (command === "PlayPause") {
console.log("Command ~ PlayPause");
togglePlay();
} else if (command === "Stop") {
console.log("Command ~ Stop");
stopPlayback();
router.canGoBack() && router.back();
} else if (command === "Pause") {
console.log("Command ~ Pause");
if (isPlaying) {
togglePlay();
} else if (command === "Stop") {
console.log("Command ~ Stop");
stopPlayback();
router.canGoBack() && router.back();
} else if (json?.Data?.Name === "DisplayMessage") {
console.log("Command ~ DisplayMessage");
const title = json?.Data?.Arguments?.Header;
const body = json?.Data?.Arguments?.Text;
Alert.alert(t("player.message_from_server", { message: title }), body);
}
};
} else if (command === "Unpause") {
console.log("Command ~ Unpause");
if (!isPlaying) {
togglePlay();
}
} else if (command === "NextTrack") {
console.log("Command ~ NextTrack");
nextTrack?.();
} else if (command === "PreviousTrack") {
console.log("Command ~ PreviousTrack");
previousTrack?.();
} else if (command === "Rewind") {
console.log("Command ~ Rewind");
rewindPlayback?.();
} else if (command === "FastForward") {
console.log("Command ~ FastForward");
fastForwardPlayback?.();
} else if (command === "Seek") {
const positionStr = args?.SeekPositionTicks;
console.log("Command ~ Seek", { positionStr });
if (positionStr) {
const position = Number.parseInt(positionStr, 10);
if (!Number.isNaN(position)) {
seekPlayback?.(position);
}
}
} else if (command === "Back") {
console.log("Command ~ Back");
if (router.canGoBack()) {
router.back();
}
} else if (command === "GoHome") {
console.log("Command ~ GoHome");
goHome ? goHome() : router.push("/");
} else if (command === "GoToSettings") {
console.log("Command ~ GoToSettings");
goToSettings ? goToSettings() : router.push("/settings");
} else if (command === "VolumeUp") {
console.log("Command ~ VolumeUp");
volumeUp?.();
} else if (command === "VolumeDown") {
console.log("Command ~ VolumeDown");
volumeDown?.();
} else if (command === "ToggleMute") {
console.log("Command ~ ToggleMute");
return () => {
ws.onmessage = null;
};
}, [ws, stopPlayback, togglePlay, isPlaying, router]);
toggleMute?.();
} else if (command === "ToggleOsd") {
console.log("Command ~ ToggleOsd");
toggleOsd?.();
} else if (command === "ToggleFullscreen") {
console.log("Command ~ ToggleFullscreen");
toggleFullscreen?.();
} else if (command === "SetAudioStreamIndex") {
const indexStr = args?.Index;
console.log("Command ~ SetAudioStreamIndex", { indexStr });
if (indexStr) {
const index = Number.parseInt(indexStr, 10);
if (!Number.isNaN(index)) {
setAudioStreamIndex?.(index);
}
}
} else if (command === "SetSubtitleStreamIndex") {
const indexStr = args?.Index;
console.log("Command ~ SetSubtitleStreamIndex", { indexStr });
if (indexStr) {
const index = Number.parseInt(indexStr, 10);
if (!Number.isNaN(index)) {
setSubtitleStreamIndex?.(index);
}
}
}
// Neue Befehle hier implementieren
else if (command === "MoveUp") {
console.log("Command ~ MoveUp");
moveUp?.();
} else if (command === "MoveDown") {
console.log("Command ~ MoveDown");
moveDown?.();
} else if (command === "MoveLeft") {
console.log("Command ~ MoveLeft");
moveLeft?.();
} else if (command === "MoveRight") {
console.log("Command ~ MoveRight");
moveRight?.();
} else if (command === "Select") {
console.log("Command ~ Select");
select?.();
} else if (command === "PageUp") {
console.log("Command ~ PageUp");
pageUp?.();
} else if (command === "PageDown") {
console.log("Command ~ PageDown");
pageDown?.();
} else if (command === "SetVolume") {
const volumeStr = args?.Volume;
console.log("Command ~ SetVolume", { volumeStr });
if (volumeStr) {
const volumeValue = Number.parseInt(volumeStr, 10);
if (!Number.isNaN(volumeValue)) {
setVolume?.(volumeValue);
}
}
} else if (command === "SetRepeatMode") {
const mode = args?.Mode;
console.log("Command ~ SetRepeatMode", { mode });
if (mode) {
setRepeatMode?.(mode);
}
} else if (command === "SetShuffleMode") {
const mode = args?.Mode;
console.log("Command ~ SetShuffleMode", { mode });
if (mode) {
setShuffleMode?.(mode);
}
} else if (command === "TogglePictureInPicture") {
console.log("Command ~ TogglePictureInPicture");
togglePictureInPicture?.();
} else if (command === "TakeScreenshot") {
console.log("Command ~ TakeScreenshot");
takeScreenshot?.();
} else if (command === "SendString") {
const text = args?.Text;
console.log("Command ~ SendString", { text });
if (text) {
sendString?.(text);
}
} else if (command === "SendKey") {
const key = args?.Key;
console.log("Command ~ SendKey", { key });
if (key) {
sendKey?.(key);
}
} else if (command === "PlayMediaSource") {
const itemIdsStr = args?.ItemIds;
const startPositionTicksStr = args?.StartPositionTicks;
console.log("Command ~ PlayMediaSource", {
itemIdsStr,
startPositionTicksStr,
});
if (itemIdsStr) {
const itemIds = itemIdsStr.split(",");
let startPositionTicks: number | undefined = undefined;
if (startPositionTicksStr) {
const parsedTicks = Number.parseInt(startPositionTicksStr, 10);
if (!Number.isNaN(parsedTicks)) {
startPositionTicks = parsedTicks;
}
}
playMediaSource?.(itemIds, startPositionTicks);
}
} else if (command === "PlayTrailers") {
const itemId = args?.ItemId;
console.log("Command ~ PlayTrailers", { itemId });
if (itemId) {
playTrailers?.(itemId);
}
} else if (command === "DisplayMessage") {
console.log("Command ~ DisplayMessage");
const title = args?.Header;
const body = args?.Text;
Alert.alert(t("player.message_from_server", { message: title }), body);
}
clearLastMessage();
}, [
lastMessage,
offline,
isPlaying,
togglePlay,
stopPlayback,
router,
nextTrack,
previousTrack,
rewindPlayback,
fastForwardPlayback,
seekPlayback,
volumeUp,
volumeDown,
toggleMute,
toggleOsd,
toggleFullscreen,
goHome,
goToSettings,
setAudioStreamIndex,
setSubtitleStreamIndex,
moveUp,
moveDown,
moveLeft,
moveRight,
select,
pageUp,
pageDown,
setVolume,
setRepeatMode,
setShuffleMode,
togglePictureInPicture,
takeScreenshot,
sendString,
sendKey,
playMediaSource,
playTrailers,
t,
clearLastMessage,
]);
};

View File

@@ -4,8 +4,8 @@ import { initReactI18next } from "react-i18next";
import { getLocales } from "expo-localization";
import de from "./translations/de.json";
import en from "./translations/en.json";
import es from "./translations/es.json";
import eo from "./translations/eo.json";
import es from "./translations/es.json";
import fr from "./translations/fr.json";
import it from "./translations/it.json";
import ja from "./translations/ja.json";
@@ -14,8 +14,8 @@ import pl from "./translations/pl.json";
import ptBR from "./translations/pt-BR.json";
import ru from "./translations/ru.json";
import sv from "./translations/sv.json";
import tr from "./translations/tr.json";
import tlh from "./translations/tlh.json";
import tr from "./translations/tr.json";
import uk from "./translations/uk.json";
import zhCN from "./translations/zh-CN.json";
import zhTW from "./translations/zh-TW.json";

View File

@@ -6,12 +6,13 @@
"submodule-reload": "git submodule update --init --remote --recursive",
"clean": "echo y | expo prebuild --clean",
"start": "bun run submodule-reload && expo start",
"ios": "EXPO_TV=0 expo run:ios",
"ios:tv": "EXPO_TV=1 expo run:ios",
"android": "EXPO_TV=0 expo run:android",
"android:tv": "EXPO_TV=1 expo run:android",
"prebuild": "EXPO_TV=0 bun run clean",
"prebuild:tv": "EXPO_TV=1 bun run clean",
"ios": "cross-env EXPO_TV=0 expo run:ios",
"ios:tv": "cross-env EXPO_TV=1 expo run:ios",
"android": "cross-env EXPO_TV=0 expo run:android",
"android:tv": "cross-env EXPO_TV=1 expo run:android",
"prebuild": "cross-env EXPO_TV=0 bun run clean",
"prebuild:tv": "cross-env EXPO_TV=1 bun run clean",
"build:android:local": "cd android && cross-env NODE_ENV=production ./gradlew assembleRelease",
"prepare": "husky",
"check": "biome check .",
"lint": "biome check --write --unsafe"
@@ -19,7 +20,7 @@
"dependencies": {
"@bottom-tabs/react-navigation": "0.8.6",
"@expo/config-plugins": "~9.0.15",
"@expo/react-native-action-sheet": "^4.1.0",
"@expo/react-native-action-sheet": "^4.1.1",
"@expo/vector-icons": "^14.0.4",
"@futurejj/react-native-visibility-sensor": "^1.3.10",
"@gorhom/bottom-sheet": "^5.1.0",
@@ -120,10 +121,11 @@
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^19.0.0",
"@types/uuid": "^10.0.0",
"cross-env": "^7.0.3",
"husky": "^9.1.7",
"lint-staged": "^15.5.0",
"postinstall-postinstall": "^2.1.0",
"react-test-renderer": "19.0.0",
"react-test-renderer": "19.1.0",
"typescript": "~5.7.3"
},
"private": true,
@@ -134,6 +136,6 @@
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["biome check --write --unsafe"],
"*.{json,md}": ["biome format --write"]
"*.{json}": ["biome format --write"]
}
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -842,12 +842,39 @@ export function DownloadProvider({ children }: { children: React.ReactNode }) {
}
export function useDownload() {
if (Platform.isTV) {
// Since tv doesn't do downloads, just return no-op functions for everything
return {
processes: [],
startBackgroundDownload: useCallback(
async (
_url: string,
_item: BaseItemDto,
_mediaSource: MediaSourceInfo,
_maxBitrate?: Bitrate,
) => {},
[],
),
downloadedFiles: [],
deleteAllFiles: async (): Promise<void> => {},
deleteFile: async (id: string): Promise<void> => {},
deleteItems: async (items: BaseItemDto[]) => {},
saveDownloadedItemInfo: (item: BaseItemDto, size?: number) => {},
removeProcess: (id: string) => {},
setProcesses: () => {},
startDownload: async (_process: JobStatus): Promise<void> => {},
getDownloadedItem: (itemId: string) => {},
deleteFileByType: async (_type: BaseItemDto["Type"]) => {},
appSizeUsage: async () => 0,
getDownloadedItemSize: (itemId: string) => {},
APP_CACHE_DOWNLOAD_DIRECTORY: "",
cleanCacheDirectory: async (): Promise<void> => {},
};
}
const context = useContext(DownloadContext);
if (context === null) {
throw new Error("useDownload must be used within a DownloadProvider");
}
if (Platform.isTV) {
throw new Error("useDownload is not supported on TVOS");
}
return context;
}

View File

@@ -1,5 +1,6 @@
import { apiAtom, getOrSetDeviceId } from "@/providers/JellyfinProvider";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api";
import { useRouter } from "expo-router";
import { useAtomValue } from "jotai";
import React, {
createContext,
@@ -12,6 +13,12 @@ import React, {
} from "react";
import { AppState, type AppStateStatus } from "react-native";
interface WebSocketMessage {
MessageType: string;
Data: any;
// Add other fields as needed
}
interface WebSocketProviderProps {
children: ReactNode;
}
@@ -19,6 +26,9 @@ interface WebSocketProviderProps {
interface WebSocketContextType {
ws: WebSocket | null;
isConnected: boolean;
lastMessage: WebSocketMessage | null;
sendMessage: (message: any) => void;
clearLastMessage: () => void;
}
const WebSocketContext = createContext<WebSocketContextType | null>(null);
@@ -27,7 +37,8 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
const api = useAtomValue(apiAtom);
const [ws, setWs] = useState<WebSocket | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [lastMessage, setLastMessage] = useState<WebSocketMessage | null>(null);
const router = useRouter();
const deviceId = useMemo(() => {
return getOrSetDeviceId();
}, []);
@@ -48,6 +59,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
let keepAliveInterval: number | null = null;
newWebSocket.onopen = () => {
console.log("WebSocket connection opened");
setIsConnected(true);
keepAliveInterval = setInterval(() => {
if (newWebSocket.readyState === WebSocket.OPEN) {
@@ -56,9 +68,23 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
}, 30000);
};
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 10000;
newWebSocket.onerror = (e) => {
console.error("WebSocket error:", e);
setIsConnected(false);
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
setTimeout(() => {
console.log(`WebSocket reconnect attempt ${reconnectAttempts}`);
connectWebSocket();
}, reconnectDelay);
} else {
console.warn("Max WebSocket reconnect attempts reached.");
}
};
newWebSocket.onclose = () => {
@@ -67,7 +93,15 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
}
setIsConnected(false);
};
newWebSocket.onmessage = (e) => {
try {
const message = JSON.parse(e.data);
console.log("[WS] Received message:", message);
setLastMessage(message); // Store the last message in context
} catch (error) {
console.error("Error parsing WebSocket message:", error);
}
};
setWs(newWebSocket);
return () => {
@@ -78,6 +112,41 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
};
}, [api, deviceId]);
useEffect(() => {
if (!lastMessage) {
return;
}
if (lastMessage.MessageType === "Play") {
handlePlayCommand(lastMessage.Data);
}
}, [lastMessage, router]);
const handlePlayCommand = useCallback(
(data: any) => {
if (!data || !data.ItemIds || !data.ItemIds.length) {
console.warn("[WS] Received Play command with no items");
return;
}
const itemId = data.ItemIds[0];
console.log(`[WS] Handling Play command for item: ${itemId}`);
router.push({
pathname: "/(auth)/player/direct-player",
params: {
itemId: itemId,
playCommand: data.PlayCommand || "PlayNow",
audioIndex: data.AudioStreamIndex?.toString(),
subtitleIndex: data.SubtitleStreamIndex?.toString(),
mediaSourceId: data.MediaSourceId || "",
bitrateValue: "",
offline: "false",
},
});
},
[router],
);
useEffect(() => {
const cleanup = connectWebSocket();
return cleanup;
@@ -126,9 +195,23 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
ws?.close();
};
}, [ws, connectWebSocket]);
const sendMessage = useCallback(
(message: any) => {
if (ws && isConnected) {
ws.send(JSON.stringify(message));
} else {
console.warn("Cannot send message: WebSocket is not connected");
}
},
[ws, isConnected],
);
const clearLastMessage = useCallback(() => {
setLastMessage(null);
}, []);
return (
<WebSocketContext.Provider value={{ ws, isConnected }}>
<WebSocketContext.Provider
value={{ ws, isConnected, lastMessage, sendMessage, clearLastMessage }}
>
{children}
</WebSocketContext.Provider>
);

48
renovate.json Normal file
View File

@@ -0,0 +1,48 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"description": "Default Renovate preset for Streamyfin repositories",
"extends": [
"config:base",
":disableDependencyDashboard",
":enableVulnerabilityAlertsWithLabel(security)",
":semanticCommits",
":timezone(Etc/UTC)",
"docker:enableMajor",
"group:testNonMajor",
"group:monorepos",
"helpers:pinGitHubActionDigests"
],
"addLabels": ["dependencies"],
"rebaseWhen": "conflicted",
"ignorePaths": ["**/node_modules/**", "**/bower_components/**"],
"lockFileMaintenance": {
"enabled": true,
"groupName": "lockfiles",
"schedule": ["every month"]
},
"packageRules": [
{
"description": "Add 'ci' and 'github-actions' labels to GitHub Action update PRs",
"matchManagers": ["github-actions"],
"addLabels": ["ci", "github-actions"]
},
{
"description": "Group minor and patch GitHub Action updates into a single PR",
"matchManagers": ["github-actions"],
"groupName": "CI dependencies",
"groupSlug": "ci-deps",
"matchUpdateTypes": ["minor", "patch"]
},
{
"description": "Group lock file maintenance updates",
"matchUpdateTypes": ["lockFileMaintenance"],
"groupName": "lockfiles",
"dependencyDashboardApproval": true
},
{
"description": "Add specific labels for Expo and React Native dependencies",
"matchPackagePatterns": ["expo", "react-native"],
"addLabels": ["expo", "react-native"]
}
]
}

View File

@@ -1,480 +1,480 @@
{
"login": {
"username_required": "Uzantnomo estas deviga",
"error_title": "Eraro",
"login_title": "Ensaluti",
"login_to_title": "Ensaluti al",
"username_placeholder": "Uzantnomo",
"password_placeholder": "Pasvorto",
"login_button": "Ensaluti",
"quick_connect": "Rapida Konekto",
"enter_code_to_login": "Enigu kodon {{code}} por ensaluti",
"failed_to_initiate_quick_connect": "Malsukcesis iniciati Rapidan Konekton",
"got_it": "Komprenita",
"connection_failed": "Konekto malsukcesis",
"could_not_connect_to_server": "Ne povis konekti al la servilo. Bonvolu kontroli la URL-on kaj vian retan konekton.",
"an_unexpected_error_occured": "Neatendita eraro okazis",
"change_server": "Ŝanĝi servilon",
"invalid_username_or_password": "Nevalida uzantnomo aŭ pasvorto",
"user_does_not_have_permission_to_log_in": "Uzanto ne havas permeson ensaluti",
"server_is_taking_too_long_to_respond_try_again_later": "Servilo respondas tro malrapide, provu denove poste",
"server_received_too_many_requests_try_again_later": "Servilo ricevis tro multajn petojn, provu denove poste.",
"there_is_a_server_error": "Estas servila eraro",
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Neatendita eraro okazis. Ĉu vi enigis la ĝustan servilan URL-on?"
},
"server": {
"enter_url_to_jellyfin_server": "Enigu la URL-on al via Jellyfin-servilo",
"server_url_placeholder": "http(s)://via-servilo.com",
"connect_button": "Konekti",
"previous_servers": "antaŭaj serviloj",
"clear_button": "Forviŝi",
"search_for_local_servers": "Serĉi lokajn servilojn",
"searching": "Serĉante...",
"servers": "Serviloj"
},
"home": {
"no_internet": "Neniu Interreto",
"no_items": "Neniuj eroj",
"no_internet_message": "Ne zorgu, vi ankoraŭ povas spekti\nelsŝutitan enhavon.",
"go_to_downloads": "Iri al elŝutoj",
"oops": "Ho ve!",
"error_message": "Io misfunkciis.\nBonvolu elsaluti kaj reensaluti.",
"continue_watching": "Daŭrigi Spektadon",
"next_up": "Sekva",
"recently_added_in": "Ĵus Aldonita en {{libraryName}}",
"suggested_movies": "Sugestitaj Filmoj",
"suggested_episodes": "Sugestitaj Epizodoj",
"intro": {
"welcome_to_streamyfin": "Bonvenon al Streamyfin",
"a_free_and_open_source_client_for_jellyfin": "Senpaga kaj malfermfonta kliento por Jellyfin.",
"features_title": "Trajtoj",
"features_description": "Streamyfin havas multajn trajtojn kaj integriĝas kun vasta gamo de programaroj, kiujn vi povas trovi en la agorda menuo, tiuj inkluzivas:",
"jellyseerr_feature_description": "Konekti al via Jellyseerr-instanco kaj peti filmojn rekte en la aplikaĵo.",
"downloads_feature_title": "Elŝutoj",
"downloads_feature_description": "Elŝutu filmojn kaj televidajn seriojn por vidi senkonekte. Uzu aŭ la defaŭltan metodon aŭ instalu la optimumigan servilon por elŝuti dosierojn en la fono.",
"chromecast_feature_description": "Ĵetu filmojn kaj televidajn seriojn al viaj Chromecast-aparatoj.",
"centralised_settings_plugin_title": "Centralizita Agorda Kromprogramo",
"centralised_settings_plugin_description": "Agordu agordojn de centralizita loko sur via Jellyfin-servilo. Ĉiuj klientaj agordoj por ĉiuj uzantoj estos sinkronigitaj aŭtomate.",
"done_button": "Farite",
"go_to_settings_button": "Iri al agordoj",
"read_more": "Legu pli"
},
"settings": {
"settings_title": "Agordoj",
"log_out_button": "Elsaluti",
"user_info": {
"user_info_title": "Uzantaj Informoj",
"user": "Uzanto",
"server": "Servilo",
"token": "Ĵetono",
"app_version": "Aplikaĵa Versio"
},
"quick_connect": {
"quick_connect_title": "Rapida Konekto",
"authorize_button": "Aŭtorizi Rapidan Konekton",
"enter_the_quick_connect_code": "Enigu la rapidan konektan kodon...",
"success": "Sukceso",
"quick_connect_autorized": "Rapida Konekto aŭtorizita",
"error": "Eraro",
"invalid_code": "Nevalida kodo",
"authorize": "Aŭtorizi"
},
"media_controls": {
"media_controls_title": "Mediaj Kontroloj",
"forward_skip_length": "Antaŭensalta longeco",
"rewind_length": "Rebobena longeco",
"seconds_unit": "s"
},
"audio": {
"audio_title": "Audio",
"set_audio_track": "Agordi Aŭdian Trakon De Antaŭa Ero",
"audio_language": "Aŭdia lingvo",
"audio_hint": "Elektu defaŭltan aŭdian lingvon.",
"none": "Neniu",
"language": "Lingvo"
},
"subtitles": {
"subtitle_title": "Subtekstoj",
"subtitle_language": "Subteksta lingvo",
"subtitle_mode": "Subteksta Reĝimo",
"set_subtitle_track": "Agordi Subtekstan Trakon De Antaŭa Ero",
"subtitle_size": "Subteksta Grandeco",
"subtitle_hint": "Agordu subtekstan preferon.",
"none": "Neniu",
"language": "Lingvo",
"loading": "Ŝarĝante",
"modes": {
"Default": "Defaŭlta",
"Smart": "Inteligenta",
"Always": "Ĉiam",
"None": "Neniu",
"OnlyForced": "NurDevigita"
}
},
"other": {
"other_title": "Alia",
"follow_device_orientation": "Aŭtomata rotacio",
"video_orientation": "Video-orientiĝo",
"orientation": "Orientiĝo",
"orientations": {
"DEFAULT": "Defaŭlta",
"ALL": "Ĉiuj",
"PORTRAIT": "Portreta",
"PORTRAIT_UP": "Portreta Supren",
"PORTRAIT_DOWN": "Portreta Malsupren",
"LANDSCAPE": "Pejzaĝa",
"LANDSCAPE_LEFT": "Pejzaĝa Maldekstren",
"LANDSCAPE_RIGHT": "Pejzaĝa Dekstren",
"OTHER": "Alia",
"UNKNOWN": "Nekonata"
},
"safe_area_in_controls": "Sekura areo en kontroloj",
"video_player": "Video-ludilo",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Eksperimenta + PiP)"
},
"show_custom_menu_links": "Montri Proprajn Menuajn Ligilojn",
"hide_libraries": "Kaŝi Bibliotekojn",
"select_liraries_you_want_to_hide": "Elektu la bibliotekojn, kiujn vi volas kaŝi de la Biblioteka langeto kaj hejmpaĝaj sekcioj.",
"disable_haptic_feedback": "Malŝalti Haptan Rimarkon",
"default_quality": "Defaŭlta kvalito"
},
"downloads": {
"downloads_title": "Elŝutoj",
"download_method": "Elŝuta metodo",
"remux_max_download": "Remux maksimuma elŝuto",
"auto_download": "Aŭtomata elŝuto",
"optimized_versions_server": "Optimumigitaj versioj servilo",
"save_button": "Konservi",
"optimized_server": "Optimumigita Servilo",
"optimized": "Optimumigita",
"default": "Defaŭlta",
"optimized_version_hint": "Enigu la URL-on por la optimumiga servilo. La URL devus inkluzivi http aŭ https kaj laŭvole la pordon.",
"read_more_about_optimized_server": "Legu pli pri la optimumiga servilo.",
"url": "URL",
"server_url_placeholder": "http(s)://domajno.org:pordo"
},
"plugins": {
"plugins_title": "Kromprogramoj",
"jellyseerr": {
"jellyseerr_warning": "Ĉi tiu integriĝo estas en siaj fruaj stadioj. Atendu ŝanĝojn.",
"server_url": "Servila URL",
"server_url_hint": "Ekzemplo: http(s)://via-gastiganto.url\n(aldonu pordon se necese)",
"server_url_placeholder": "Jellyseerr URL...",
"password": "Pasvorto",
"password_placeholder": "Enigu pasvorton por Jellyfin-uzanto {{username}}",
"save_button": "Konservi",
"clear_button": "Forviŝi",
"login_button": "Ensaluti",
"total_media_requests": "Totalaj mediaj petoj",
"movie_quota_limit": "Filma kvota limo",
"movie_quota_days": "Filmaj kvotaj tagoj",
"tv_quota_limit": "Televida kvota limo",
"tv_quota_days": "Televidaj kvotaj tagoj",
"reset_jellyseerr_config_button": "Restarigi Jellyseerr-agordon",
"unlimited": "Senlima",
"plus_n_more": "+{{n}} pli",
"order_by": {
"DEFAULT": "Defaŭlta",
"VOTE_COUNT_AND_AVERAGE": "Voĉdonkalkulo kaj mezumo",
"POPULARITY": "Populareco"
}
},
"marlin_search": {
"enable_marlin_search": "Ebligi Marlin Serĉon ",
"url": "URL",
"server_url_placeholder": "http(s)://domajno.org:pordo",
"marlin_search_hint": "Enigu la URL-on por la Marlin-servilo. La URL devus inkluzivi http aŭ https kaj laŭvole la pordon.",
"read_more_about_marlin": "Legu pli pri Marlin.",
"save_button": "Konservi",
"toasts": {
"saved": "Konservita"
}
}
},
"storage": {
"storage_title": "Stokado",
"app_usage": "Aplikaĵo {{usedSpace}}%",
"device_usage": "Aparato {{availableSpace}}%",
"size_used": "{{used}} el {{total}} uzata",
"delete_all_downloaded_files": "Forigi Ĉiujn Elŝutitajn Dosierojn"
},
"intro": {
"show_intro": "Montri enkondukon",
"reset_intro": "Restarigi enkondukon"
},
"logs": {
"logs_title": "Protokoloj",
"export_logs": "Eksporti protokolojn",
"click_for_more_info": "Klaku por pli da informoj",
"level": "Nivelo",
"no_logs_available": "Neniuj protokoloj disponeblaj",
"delete_all_logs": "Forigi ĉiujn protokolojn"
},
"languages": {
"title": "Lingvoj",
"app_language": "Aplikaĵa lingvo",
"app_language_description": "Elektu la lingvon por la aplikaĵo.",
"system": "Sistemo"
},
"toasts": {
"error_deleting_files": "Eraro forigante dosierojn",
"background_downloads_enabled": "Fonaj elŝutoj ebligitaj",
"background_downloads_disabled": "Fonaj elŝutoj malŝaltitaj",
"connected": "Konektita",
"could_not_connect": "Ne povis konekti",
"invalid_url": "Nevalida URL"
}
},
"sessions": {
"title": "Sesioj",
"no_active_sessions": "Neniuj aktivaj sesioj"
},
"downloads": {
"downloads_title": "Elŝutoj",
"tvseries": "Televidaj serioj",
"movies": "Filmoj",
"queue": "Vico",
"queue_hint": "Vico kaj elŝutoj perdiĝos ĉe aplikaĵa rekomenco",
"no_items_in_queue": "Neniuj eroj en vico",
"no_downloaded_items": "Neniuj elŝutitaj eroj",
"delete_all_movies_button": "Forigi ĉiujn Filmojn",
"delete_all_tvseries_button": "Forigi ĉiujn Televidajn Seriojn",
"delete_all_button": "Forigi ĉion",
"active_download": "Aktiva elŝuto",
"no_active_downloads": "Neniuj aktivaj elŝutoj",
"active_downloads": "Aktivaj elŝutoj",
"new_app_version_requires_re_download": "Nova aplikaĵa versio postulas re-elŝuton",
"new_app_version_requires_re_download_description": "La nova ĝisdatigo postulas, ke enhavo estu elŝutita denove. Bonvolu forigi ĉian elŝutitan enhavon kaj provi denove.",
"back": "Reen",
"delete": "Forigi",
"something_went_wrong": "Io misfunkciis",
"could_not_get_stream_url_from_jellyfin": "Ne povis akiri la fluan URL-on de Jellyfin",
"eta": "ETA {{eta}}",
"methods": "Metodoj",
"toasts": {
"you_are_not_allowed_to_download_files": "Vi ne rajtas elŝuti dosierojn.",
"deleted_all_movies_successfully": "Sukcese forigis ĉiujn filmojn!",
"failed_to_delete_all_movies": "Malsukcesis forigi ĉiujn filmojn",
"deleted_all_tvseries_successfully": "Sukcese forigis ĉiujn Televidajn Seriojn!",
"failed_to_delete_all_tvseries": "Malsukcesis forigi ĉiujn Televidajn Seriojn",
"download_cancelled": "Elŝuto nuligita",
"could_not_cancel_download": "Ne povis nuligi elŝuton",
"download_completed": "Elŝuto finita",
"download_started_for": "Elŝuto komenciĝis por {{item}}",
"item_is_ready_to_be_downloaded": "{{item}} estas preta por esti elŝutita",
"download_stated_for_item": "Elŝuto komenciĝis por {{item}}",
"download_failed_for_item": "Elŝuto malsukcesis por {{item}} - {{error}}",
"download_completed_for_item": "Elŝuto finita por {{item}}",
"queued_item_for_optimization": "Envicigis {{item}} por optimumigo",
"failed_to_start_download_for_item": "Malsukcesis komenci elŝutadon por {{item}}: {{message}}",
"server_responded_with_status_code": "Servilo respondis kun statuskodo {{statusCode}}",
"no_response_received_from_server": "Neniu respondo ricevita de la servilo",
"error_setting_up_the_request": "Eraro starigante la peton",
"failed_to_start_download_for_item_unexpected_error": "Malsukcesis komenci elŝutadon por {{item}}: Neatendita eraro",
"all_files_folders_and_jobs_deleted_successfully": "Ĉiuj dosieroj, dosierujoj kaj taskoj sukcese forigitaj",
"an_error_occured_while_deleting_files_and_jobs": "Eraro okazis dum forigo de dosieroj kaj taskoj",
"go_to_downloads": "Iri al elŝutoj"
}
}
},
"search": {
"search_here": "Serĉu ĉi tie...",
"search": "Serĉi...",
"x_items": "{{count}} eroj",
"library": "Biblioteko",
"discover": "Malkovri",
"no_results": "Neniuj rezultoj",
"no_results_found_for": "Neniuj rezultoj trovitaj por",
"movies": "Filmoj",
"series": "Serioj",
"episodes": "Epizodoj",
"collections": "Kolektoj",
"actors": "Aktoroj",
"request_movies": "Peti Filmojn",
"request_series": "Peti Seriojn",
"recently_added": "Ĵus Aldonita",
"recent_requests": "Lastatempaj Petoj",
"plex_watchlist": "Plex Spektolisto",
"trending": "Tendencaj",
"popular_movies": "Popularaj Filmoj",
"movie_genres": "Filmaj Ĝenroj",
"upcoming_movies": "Venontaj Filmoj",
"studios": "Studioj",
"popular_tv": "Populara Televido",
"tv_genres": "Televidaj Ĝenroj",
"upcoming_tv": "Venonta Televido",
"networks": "Retoj",
"tmdb_movie_keyword": "TMDB Filma Ŝlosilvorto",
"tmdb_movie_genre": "TMDB Filma Ĝenro",
"tmdb_tv_keyword": "TMDB Televida Ŝlosilvorto",
"tmdb_tv_genre": "TMDB Televida Ĝenro",
"tmdb_search": "TMDB Serĉo",
"tmdb_studio": "TMDB Studio",
"tmdb_network": "TMDB Reto",
"tmdb_movie_streaming_services": "TMDB Filmaj Fluservoj",
"tmdb_tv_streaming_services": "TMDB Televidaj Fluservoj"
},
"library": {
"no_items_found": "Neniuj eroj trovitaj",
"no_results": "Neniuj rezultoj",
"no_libraries_found": "Neniuj bibliotekoj trovitaj",
"item_types": {
"movies": "filmoj",
"series": "serioj",
"boxsets": "skatolaj aroj",
"items": "eroj"
},
"options": {
"display": "Vidigi",
"row": "Vico",
"list": "Listo",
"image_style": "Bildostilo",
"poster": "Afiŝo",
"cover": "Kovrilo",
"show_titles": "Montri titolojn",
"show_stats": "Montri statistikojn"
},
"filters": {
"genres": "Ĝenroj",
"years": "Jaroj",
"sort_by": "Ordigi laŭ",
"sort_order": "Orda ordo",
"asc": "Supreniranta",
"desc": "Malsupreniranta",
"tags": "Etikedoj"
}
},
"favorites": {
"series": "Serioj",
"movies": "Filmoj",
"episodes": "Epizodoj",
"videos": "Videoj",
"boxsets": "Skatolaj aroj",
"playlists": "Ludlistoj",
"noDataTitle": "Ankoraŭ neniuj favoratoj",
"noData": "Marku erojn kiel favoratojn por vidi ilin aperi ĉi tie por rapida aliro."
},
"custom_links": {
"no_links": "Neniuj ligiloj"
},
"player": {
"error": "Eraro",
"failed_to_get_stream_url": "Malsukcesis akiri la fluan URL-on",
"an_error_occured_while_playing_the_video": "Eraro okazis dum ludado de la video. Kontrolu protokolojn en agordoj.",
"client_error": "Klienta eraro",
"could_not_create_stream_for_chromecast": "Ne povis krei fluon por Chromecast",
"message_from_server": "Mesaĝo de servilo: {{message}}",
"video_has_finished_playing": "Video finis ludi!",
"no_video_source": "Neniu video-fonto...",
"next_episode": "Sekva Epizodo",
"refresh_tracks": "Refreŝigi Trakojn",
"subtitle_tracks": "Subtekstaj Trakoj:",
"audio_tracks": "Aŭdiaj Trakoj:",
"playback_state": "Ludada Stato:",
"no_data_available": "Neniuj datumoj disponeblaj",
"index": "Indekso:"
},
"item_card": {
"next_up": "Sekva",
"no_items_to_display": "Neniuj eroj por montri",
"cast_and_crew": "Rolantaro & Skiparo",
"series": "Serioj",
"seasons": "Sezonoj",
"season": "Sezono",
"no_episodes_for_this_season": "Neniuj epizodoj por ĉi tiu sezono",
"overview": "Superrigardo",
"more_with": "Pli kun {{name}}",
"similar_items": "Similaj eroj",
"no_similar_items_found": "Neniuj similaj eroj trovitaj",
"video": "Video",
"more_details": "Pli da detaloj",
"quality": "Kvalito",
"audio": "Audio",
"subtitles": "Subteksto",
"show_more": "Montri pli",
"show_less": "Montri malpli",
"appeared_in": "Aperis en",
"could_not_load_item": "Ne povis ŝarĝi eron",
"none": "Neniu",
"download": {
"download_season": "Elŝuti Sezonon",
"download_series": "Elŝuti Serion",
"download_episode": "Elŝuti Epizodon",
"download_movie": "Elŝuti Filmon",
"download_x_item": "Elŝuti {{item_count}} erojn",
"download_button": "Elŝuti",
"using_optimized_server": "Uzante optimumigitan servilon",
"using_default_method": "Uzante defaŭltan metodon"
}
},
"live_tv": {
"next": "Sekva",
"previous": "Antaŭa",
"live_tv": "Viva Televido",
"coming_soon": "Baldaŭ",
"on_now": "Nun",
"shows": "Spektakloj",
"movies": "Filmoj",
"sports": "Sportoj",
"for_kids": "Por Infanoj",
"news": "Novaĵoj"
},
"jellyseerr": {
"confirm": "Konfirmi",
"cancel": "Nuligi",
"yes": "Jes",
"whats_wrong": "Kio estas malĝusta?",
"issue_type": "Problema tipo",
"select_an_issue": "Elektu problemon",
"types": "Tipoj",
"describe_the_issue": "(laŭvola) Priskribu la problemon...",
"submit_button": "Sendi",
"report_issue_button": "Raporti problemon",
"request_button": "Peti",
"are_you_sure_you_want_to_request_all_seasons": "Ĉu vi certas, ke vi volas peti ĉiujn sezonojn?",
"failed_to_login": "Malsukcesis ensaluti",
"cast": "Rolantaro",
"details": "Detaloj",
"status": "Stato",
"original_title": "Originala Titolo",
"series_type": "Seria Tipo",
"release_dates": "Eldondatoj",
"first_air_date": "Unua Elsendo-dato",
"next_air_date": "Sekva Elsendo-dato",
"revenue": "Enspezo",
"budget": "Buĝeto",
"original_language": "Originala Lingvo",
"production_country": "Produktada Lando",
"studios": "Studioj",
"network": "Reto",
"currently_streaming_on": "Nuntempe Flusanta ĉe",
"advanced": "Altnivela",
"request_as": "Peti Kiel",
"tags": "Etikedoj",
"quality_profile": "Kvalita Profilo",
"root_folder": "Radika Dosierujo",
"season_all": "Sezono (ĉiuj)",
"season_number": "Sezono {{season_number}}",
"number_episodes": "{{episode_number}} Epizodoj",
"born": "Naskiĝis",
"appearances": "Aperoj",
"toasts": {
"jellyseer_does_not_meet_requirements": "Jellyseerr-servilo ne plenumas minimumajn versiajn postulojn! Bonvolu ĝisdatigi al almenaŭ 2.0.0",
"jellyseerr_test_failed": "Jellyseerr-testo malsukcesis. Bonvolu provi denove.",
"failed_to_test_jellyseerr_server_url": "Malsukcesis testi jellyseerr-servilan url-on",
"issue_submitted": "Problemo sendita!",
"requested_item": "Petis {{item}}!",
"you_dont_have_permission_to_request": "Vi ne havas permeson peti!",
"something_went_wrong_requesting_media": "Io misfunkciis petante medion!"
}
},
"tabs": {
"home": "Hejmo",
"search": "Serĉi",
"library": "Biblioteko",
"custom_links": "Propraj Ligiloj",
"favorites": "Favoratoj"
}
}
{
"login": {
"username_required": "Uzantnomo estas deviga",
"error_title": "Eraro",
"login_title": "Ensaluti",
"login_to_title": "Ensaluti al",
"username_placeholder": "Uzantnomo",
"password_placeholder": "Pasvorto",
"login_button": "Ensaluti",
"quick_connect": "Rapida Konekto",
"enter_code_to_login": "Enigu kodon {{code}} por ensaluti",
"failed_to_initiate_quick_connect": "Malsukcesis iniciati Rapidan Konekton",
"got_it": "Komprenita",
"connection_failed": "Konekto malsukcesis",
"could_not_connect_to_server": "Ne povis konekti al la servilo. Bonvolu kontroli la URL-on kaj vian retan konekton.",
"an_unexpected_error_occured": "Neatendita eraro okazis",
"change_server": "Ŝanĝi servilon",
"invalid_username_or_password": "Nevalida uzantnomo aŭ pasvorto",
"user_does_not_have_permission_to_log_in": "Uzanto ne havas permeson ensaluti",
"server_is_taking_too_long_to_respond_try_again_later": "Servilo respondas tro malrapide, provu denove poste",
"server_received_too_many_requests_try_again_later": "Servilo ricevis tro multajn petojn, provu denove poste.",
"there_is_a_server_error": "Estas servila eraro",
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Neatendita eraro okazis. Ĉu vi enigis la ĝustan servilan URL-on?"
},
"server": {
"enter_url_to_jellyfin_server": "Enigu la URL-on al via Jellyfin-servilo",
"server_url_placeholder": "http(s)://via-servilo.com",
"connect_button": "Konekti",
"previous_servers": "antaŭaj serviloj",
"clear_button": "Forviŝi",
"search_for_local_servers": "Serĉi lokajn servilojn",
"searching": "Serĉante...",
"servers": "Serviloj"
},
"home": {
"no_internet": "Neniu Interreto",
"no_items": "Neniuj eroj",
"no_internet_message": "Ne zorgu, vi ankoraŭ povas spekti\nelsŝutitan enhavon.",
"go_to_downloads": "Iri al elŝutoj",
"oops": "Ho ve!",
"error_message": "Io misfunkciis.\nBonvolu elsaluti kaj reensaluti.",
"continue_watching": "Daŭrigi Spektadon",
"next_up": "Sekva",
"recently_added_in": "Ĵus Aldonita en {{libraryName}}",
"suggested_movies": "Sugestitaj Filmoj",
"suggested_episodes": "Sugestitaj Epizodoj",
"intro": {
"welcome_to_streamyfin": "Bonvenon al Streamyfin",
"a_free_and_open_source_client_for_jellyfin": "Senpaga kaj malfermfonta kliento por Jellyfin.",
"features_title": "Trajtoj",
"features_description": "Streamyfin havas multajn trajtojn kaj integriĝas kun vasta gamo de programaroj, kiujn vi povas trovi en la agorda menuo, tiuj inkluzivas:",
"jellyseerr_feature_description": "Konekti al via Jellyseerr-instanco kaj peti filmojn rekte en la aplikaĵo.",
"downloads_feature_title": "Elŝutoj",
"downloads_feature_description": "Elŝutu filmojn kaj televidajn seriojn por vidi senkonekte. Uzu aŭ la defaŭltan metodon aŭ instalu la optimumigan servilon por elŝuti dosierojn en la fono.",
"chromecast_feature_description": "Ĵetu filmojn kaj televidajn seriojn al viaj Chromecast-aparatoj.",
"centralised_settings_plugin_title": "Centralizita Agorda Kromprogramo",
"centralised_settings_plugin_description": "Agordu agordojn de centralizita loko sur via Jellyfin-servilo. Ĉiuj klientaj agordoj por ĉiuj uzantoj estos sinkronigitaj aŭtomate.",
"done_button": "Farite",
"go_to_settings_button": "Iri al agordoj",
"read_more": "Legu pli"
},
"settings": {
"settings_title": "Agordoj",
"log_out_button": "Elsaluti",
"user_info": {
"user_info_title": "Uzantaj Informoj",
"user": "Uzanto",
"server": "Servilo",
"token": "Ĵetono",
"app_version": "Aplikaĵa Versio"
},
"quick_connect": {
"quick_connect_title": "Rapida Konekto",
"authorize_button": "Aŭtorizi Rapidan Konekton",
"enter_the_quick_connect_code": "Enigu la rapidan konektan kodon...",
"success": "Sukceso",
"quick_connect_autorized": "Rapida Konekto aŭtorizita",
"error": "Eraro",
"invalid_code": "Nevalida kodo",
"authorize": "Aŭtorizi"
},
"media_controls": {
"media_controls_title": "Mediaj Kontroloj",
"forward_skip_length": "Antaŭensalta longeco",
"rewind_length": "Rebobena longeco",
"seconds_unit": "s"
},
"audio": {
"audio_title": "Audio",
"set_audio_track": "Agordi Aŭdian Trakon De Antaŭa Ero",
"audio_language": "Aŭdia lingvo",
"audio_hint": "Elektu defaŭltan aŭdian lingvon.",
"none": "Neniu",
"language": "Lingvo"
},
"subtitles": {
"subtitle_title": "Subtekstoj",
"subtitle_language": "Subteksta lingvo",
"subtitle_mode": "Subteksta Reĝimo",
"set_subtitle_track": "Agordi Subtekstan Trakon De Antaŭa Ero",
"subtitle_size": "Subteksta Grandeco",
"subtitle_hint": "Agordu subtekstan preferon.",
"none": "Neniu",
"language": "Lingvo",
"loading": "Ŝarĝante",
"modes": {
"Default": "Defaŭlta",
"Smart": "Inteligenta",
"Always": "Ĉiam",
"None": "Neniu",
"OnlyForced": "NurDevigita"
}
},
"other": {
"other_title": "Alia",
"follow_device_orientation": "Aŭtomata rotacio",
"video_orientation": "Video-orientiĝo",
"orientation": "Orientiĝo",
"orientations": {
"DEFAULT": "Defaŭlta",
"ALL": "Ĉiuj",
"PORTRAIT": "Portreta",
"PORTRAIT_UP": "Portreta Supren",
"PORTRAIT_DOWN": "Portreta Malsupren",
"LANDSCAPE": "Pejzaĝa",
"LANDSCAPE_LEFT": "Pejzaĝa Maldekstren",
"LANDSCAPE_RIGHT": "Pejzaĝa Dekstren",
"OTHER": "Alia",
"UNKNOWN": "Nekonata"
},
"safe_area_in_controls": "Sekura areo en kontroloj",
"video_player": "Video-ludilo",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Eksperimenta + PiP)"
},
"show_custom_menu_links": "Montri Proprajn Menuajn Ligilojn",
"hide_libraries": "Kaŝi Bibliotekojn",
"select_liraries_you_want_to_hide": "Elektu la bibliotekojn, kiujn vi volas kaŝi de la Biblioteka langeto kaj hejmpaĝaj sekcioj.",
"disable_haptic_feedback": "Malŝalti Haptan Rimarkon",
"default_quality": "Defaŭlta kvalito"
},
"downloads": {
"downloads_title": "Elŝutoj",
"download_method": "Elŝuta metodo",
"remux_max_download": "Remux maksimuma elŝuto",
"auto_download": "Aŭtomata elŝuto",
"optimized_versions_server": "Optimumigitaj versioj servilo",
"save_button": "Konservi",
"optimized_server": "Optimumigita Servilo",
"optimized": "Optimumigita",
"default": "Defaŭlta",
"optimized_version_hint": "Enigu la URL-on por la optimumiga servilo. La URL devus inkluzivi http aŭ https kaj laŭvole la pordon.",
"read_more_about_optimized_server": "Legu pli pri la optimumiga servilo.",
"url": "URL",
"server_url_placeholder": "http(s)://domajno.org:pordo"
},
"plugins": {
"plugins_title": "Kromprogramoj",
"jellyseerr": {
"jellyseerr_warning": "Ĉi tiu integriĝo estas en siaj fruaj stadioj. Atendu ŝanĝojn.",
"server_url": "Servila URL",
"server_url_hint": "Ekzemplo: http(s)://via-gastiganto.url\n(aldonu pordon se necese)",
"server_url_placeholder": "Jellyseerr URL...",
"password": "Pasvorto",
"password_placeholder": "Enigu pasvorton por Jellyfin-uzanto {{username}}",
"save_button": "Konservi",
"clear_button": "Forviŝi",
"login_button": "Ensaluti",
"total_media_requests": "Totalaj mediaj petoj",
"movie_quota_limit": "Filma kvota limo",
"movie_quota_days": "Filmaj kvotaj tagoj",
"tv_quota_limit": "Televida kvota limo",
"tv_quota_days": "Televidaj kvotaj tagoj",
"reset_jellyseerr_config_button": "Restarigi Jellyseerr-agordon",
"unlimited": "Senlima",
"plus_n_more": "+{{n}} pli",
"order_by": {
"DEFAULT": "Defaŭlta",
"VOTE_COUNT_AND_AVERAGE": "Voĉdonkalkulo kaj mezumo",
"POPULARITY": "Populareco"
}
},
"marlin_search": {
"enable_marlin_search": "Ebligi Marlin Serĉon ",
"url": "URL",
"server_url_placeholder": "http(s)://domajno.org:pordo",
"marlin_search_hint": "Enigu la URL-on por la Marlin-servilo. La URL devus inkluzivi http aŭ https kaj laŭvole la pordon.",
"read_more_about_marlin": "Legu pli pri Marlin.",
"save_button": "Konservi",
"toasts": {
"saved": "Konservita"
}
}
},
"storage": {
"storage_title": "Stokado",
"app_usage": "Aplikaĵo {{usedSpace}}%",
"device_usage": "Aparato {{availableSpace}}%",
"size_used": "{{used}} el {{total}} uzata",
"delete_all_downloaded_files": "Forigi Ĉiujn Elŝutitajn Dosierojn"
},
"intro": {
"show_intro": "Montri enkondukon",
"reset_intro": "Restarigi enkondukon"
},
"logs": {
"logs_title": "Protokoloj",
"export_logs": "Eksporti protokolojn",
"click_for_more_info": "Klaku por pli da informoj",
"level": "Nivelo",
"no_logs_available": "Neniuj protokoloj disponeblaj",
"delete_all_logs": "Forigi ĉiujn protokolojn"
},
"languages": {
"title": "Lingvoj",
"app_language": "Aplikaĵa lingvo",
"app_language_description": "Elektu la lingvon por la aplikaĵo.",
"system": "Sistemo"
},
"toasts": {
"error_deleting_files": "Eraro forigante dosierojn",
"background_downloads_enabled": "Fonaj elŝutoj ebligitaj",
"background_downloads_disabled": "Fonaj elŝutoj malŝaltitaj",
"connected": "Konektita",
"could_not_connect": "Ne povis konekti",
"invalid_url": "Nevalida URL"
}
},
"sessions": {
"title": "Sesioj",
"no_active_sessions": "Neniuj aktivaj sesioj"
},
"downloads": {
"downloads_title": "Elŝutoj",
"tvseries": "Televidaj serioj",
"movies": "Filmoj",
"queue": "Vico",
"queue_hint": "Vico kaj elŝutoj perdiĝos ĉe aplikaĵa rekomenco",
"no_items_in_queue": "Neniuj eroj en vico",
"no_downloaded_items": "Neniuj elŝutitaj eroj",
"delete_all_movies_button": "Forigi ĉiujn Filmojn",
"delete_all_tvseries_button": "Forigi ĉiujn Televidajn Seriojn",
"delete_all_button": "Forigi ĉion",
"active_download": "Aktiva elŝuto",
"no_active_downloads": "Neniuj aktivaj elŝutoj",
"active_downloads": "Aktivaj elŝutoj",
"new_app_version_requires_re_download": "Nova aplikaĵa versio postulas re-elŝuton",
"new_app_version_requires_re_download_description": "La nova ĝisdatigo postulas, ke enhavo estu elŝutita denove. Bonvolu forigi ĉian elŝutitan enhavon kaj provi denove.",
"back": "Reen",
"delete": "Forigi",
"something_went_wrong": "Io misfunkciis",
"could_not_get_stream_url_from_jellyfin": "Ne povis akiri la fluan URL-on de Jellyfin",
"eta": "ETA {{eta}}",
"methods": "Metodoj",
"toasts": {
"you_are_not_allowed_to_download_files": "Vi ne rajtas elŝuti dosierojn.",
"deleted_all_movies_successfully": "Sukcese forigis ĉiujn filmojn!",
"failed_to_delete_all_movies": "Malsukcesis forigi ĉiujn filmojn",
"deleted_all_tvseries_successfully": "Sukcese forigis ĉiujn Televidajn Seriojn!",
"failed_to_delete_all_tvseries": "Malsukcesis forigi ĉiujn Televidajn Seriojn",
"download_cancelled": "Elŝuto nuligita",
"could_not_cancel_download": "Ne povis nuligi elŝuton",
"download_completed": "Elŝuto finita",
"download_started_for": "Elŝuto komenciĝis por {{item}}",
"item_is_ready_to_be_downloaded": "{{item}} estas preta por esti elŝutita",
"download_stated_for_item": "Elŝuto komenciĝis por {{item}}",
"download_failed_for_item": "Elŝuto malsukcesis por {{item}} - {{error}}",
"download_completed_for_item": "Elŝuto finita por {{item}}",
"queued_item_for_optimization": "Envicigis {{item}} por optimumigo",
"failed_to_start_download_for_item": "Malsukcesis komenci elŝutadon por {{item}}: {{message}}",
"server_responded_with_status_code": "Servilo respondis kun statuskodo {{statusCode}}",
"no_response_received_from_server": "Neniu respondo ricevita de la servilo",
"error_setting_up_the_request": "Eraro starigante la peton",
"failed_to_start_download_for_item_unexpected_error": "Malsukcesis komenci elŝutadon por {{item}}: Neatendita eraro",
"all_files_folders_and_jobs_deleted_successfully": "Ĉiuj dosieroj, dosierujoj kaj taskoj sukcese forigitaj",
"an_error_occured_while_deleting_files_and_jobs": "Eraro okazis dum forigo de dosieroj kaj taskoj",
"go_to_downloads": "Iri al elŝutoj"
}
}
},
"search": {
"search_here": "Serĉu ĉi tie...",
"search": "Serĉi...",
"x_items": "{{count}} eroj",
"library": "Biblioteko",
"discover": "Malkovri",
"no_results": "Neniuj rezultoj",
"no_results_found_for": "Neniuj rezultoj trovitaj por",
"movies": "Filmoj",
"series": "Serioj",
"episodes": "Epizodoj",
"collections": "Kolektoj",
"actors": "Aktoroj",
"request_movies": "Peti Filmojn",
"request_series": "Peti Seriojn",
"recently_added": "Ĵus Aldonita",
"recent_requests": "Lastatempaj Petoj",
"plex_watchlist": "Plex Spektolisto",
"trending": "Tendencaj",
"popular_movies": "Popularaj Filmoj",
"movie_genres": "Filmaj Ĝenroj",
"upcoming_movies": "Venontaj Filmoj",
"studios": "Studioj",
"popular_tv": "Populara Televido",
"tv_genres": "Televidaj Ĝenroj",
"upcoming_tv": "Venonta Televido",
"networks": "Retoj",
"tmdb_movie_keyword": "TMDB Filma Ŝlosilvorto",
"tmdb_movie_genre": "TMDB Filma Ĝenro",
"tmdb_tv_keyword": "TMDB Televida Ŝlosilvorto",
"tmdb_tv_genre": "TMDB Televida Ĝenro",
"tmdb_search": "TMDB Serĉo",
"tmdb_studio": "TMDB Studio",
"tmdb_network": "TMDB Reto",
"tmdb_movie_streaming_services": "TMDB Filmaj Fluservoj",
"tmdb_tv_streaming_services": "TMDB Televidaj Fluservoj"
},
"library": {
"no_items_found": "Neniuj eroj trovitaj",
"no_results": "Neniuj rezultoj",
"no_libraries_found": "Neniuj bibliotekoj trovitaj",
"item_types": {
"movies": "filmoj",
"series": "serioj",
"boxsets": "skatolaj aroj",
"items": "eroj"
},
"options": {
"display": "Vidigi",
"row": "Vico",
"list": "Listo",
"image_style": "Bildostilo",
"poster": "Afiŝo",
"cover": "Kovrilo",
"show_titles": "Montri titolojn",
"show_stats": "Montri statistikojn"
},
"filters": {
"genres": "Ĝenroj",
"years": "Jaroj",
"sort_by": "Ordigi laŭ",
"sort_order": "Orda ordo",
"asc": "Supreniranta",
"desc": "Malsupreniranta",
"tags": "Etikedoj"
}
},
"favorites": {
"series": "Serioj",
"movies": "Filmoj",
"episodes": "Epizodoj",
"videos": "Videoj",
"boxsets": "Skatolaj aroj",
"playlists": "Ludlistoj",
"noDataTitle": "Ankoraŭ neniuj favoratoj",
"noData": "Marku erojn kiel favoratojn por vidi ilin aperi ĉi tie por rapida aliro."
},
"custom_links": {
"no_links": "Neniuj ligiloj"
},
"player": {
"error": "Eraro",
"failed_to_get_stream_url": "Malsukcesis akiri la fluan URL-on",
"an_error_occured_while_playing_the_video": "Eraro okazis dum ludado de la video. Kontrolu protokolojn en agordoj.",
"client_error": "Klienta eraro",
"could_not_create_stream_for_chromecast": "Ne povis krei fluon por Chromecast",
"message_from_server": "Mesaĝo de servilo: {{message}}",
"video_has_finished_playing": "Video finis ludi!",
"no_video_source": "Neniu video-fonto...",
"next_episode": "Sekva Epizodo",
"refresh_tracks": "Refreŝigi Trakojn",
"subtitle_tracks": "Subtekstaj Trakoj:",
"audio_tracks": "Aŭdiaj Trakoj:",
"playback_state": "Ludada Stato:",
"no_data_available": "Neniuj datumoj disponeblaj",
"index": "Indekso:"
},
"item_card": {
"next_up": "Sekva",
"no_items_to_display": "Neniuj eroj por montri",
"cast_and_crew": "Rolantaro & Skiparo",
"series": "Serioj",
"seasons": "Sezonoj",
"season": "Sezono",
"no_episodes_for_this_season": "Neniuj epizodoj por ĉi tiu sezono",
"overview": "Superrigardo",
"more_with": "Pli kun {{name}}",
"similar_items": "Similaj eroj",
"no_similar_items_found": "Neniuj similaj eroj trovitaj",
"video": "Video",
"more_details": "Pli da detaloj",
"quality": "Kvalito",
"audio": "Audio",
"subtitles": "Subteksto",
"show_more": "Montri pli",
"show_less": "Montri malpli",
"appeared_in": "Aperis en",
"could_not_load_item": "Ne povis ŝarĝi eron",
"none": "Neniu",
"download": {
"download_season": "Elŝuti Sezonon",
"download_series": "Elŝuti Serion",
"download_episode": "Elŝuti Epizodon",
"download_movie": "Elŝuti Filmon",
"download_x_item": "Elŝuti {{item_count}} erojn",
"download_button": "Elŝuti",
"using_optimized_server": "Uzante optimumigitan servilon",
"using_default_method": "Uzante defaŭltan metodon"
}
},
"live_tv": {
"next": "Sekva",
"previous": "Antaŭa",
"live_tv": "Viva Televido",
"coming_soon": "Baldaŭ",
"on_now": "Nun",
"shows": "Spektakloj",
"movies": "Filmoj",
"sports": "Sportoj",
"for_kids": "Por Infanoj",
"news": "Novaĵoj"
},
"jellyseerr": {
"confirm": "Konfirmi",
"cancel": "Nuligi",
"yes": "Jes",
"whats_wrong": "Kio estas malĝusta?",
"issue_type": "Problema tipo",
"select_an_issue": "Elektu problemon",
"types": "Tipoj",
"describe_the_issue": "(laŭvola) Priskribu la problemon...",
"submit_button": "Sendi",
"report_issue_button": "Raporti problemon",
"request_button": "Peti",
"are_you_sure_you_want_to_request_all_seasons": "Ĉu vi certas, ke vi volas peti ĉiujn sezonojn?",
"failed_to_login": "Malsukcesis ensaluti",
"cast": "Rolantaro",
"details": "Detaloj",
"status": "Stato",
"original_title": "Originala Titolo",
"series_type": "Seria Tipo",
"release_dates": "Eldondatoj",
"first_air_date": "Unua Elsendo-dato",
"next_air_date": "Sekva Elsendo-dato",
"revenue": "Enspezo",
"budget": "Buĝeto",
"original_language": "Originala Lingvo",
"production_country": "Produktada Lando",
"studios": "Studioj",
"network": "Reto",
"currently_streaming_on": "Nuntempe Flusanta ĉe",
"advanced": "Altnivela",
"request_as": "Peti Kiel",
"tags": "Etikedoj",
"quality_profile": "Kvalita Profilo",
"root_folder": "Radika Dosierujo",
"season_all": "Sezono (ĉiuj)",
"season_number": "Sezono {{season_number}}",
"number_episodes": "{{episode_number}} Epizodoj",
"born": "Naskiĝis",
"appearances": "Aperoj",
"toasts": {
"jellyseer_does_not_meet_requirements": "Jellyseerr-servilo ne plenumas minimumajn versiajn postulojn! Bonvolu ĝisdatigi al almenaŭ 2.0.0",
"jellyseerr_test_failed": "Jellyseerr-testo malsukcesis. Bonvolu provi denove.",
"failed_to_test_jellyseerr_server_url": "Malsukcesis testi jellyseerr-servilan url-on",
"issue_submitted": "Problemo sendita!",
"requested_item": "Petis {{item}}!",
"you_dont_have_permission_to_request": "Vi ne havas permeson peti!",
"something_went_wrong_requesting_media": "Io misfunkciis petante medion!"
}
},
"tabs": {
"home": "Hejmo",
"search": "Serĉi",
"library": "Biblioteko",
"custom_links": "Propraj Ligiloj",
"favorites": "Favoratoj"
}
}

View File

@@ -1,480 +1,480 @@
{
"login": {
"username_required": "tlhIngan DaneH",
"error_title": "ghIq",
"login_title": "lut 'el",
"login_to_title": "lut 'el",
"username_placeholder": "tlhIngan",
"password_placeholder": "ngoq De'",
"login_button": "yI'el!",
"quick_connect": "parmaq ngoQ",
"enter_code_to_login": "yI'elDI' De' {{code}} yIlaD",
"failed_to_initiate_quick_connect": "parmaq ngoQ yIchu'laHbe'",
"got_it": "jIyaj",
"connection_failed": "ngoQlaHbe'",
"could_not_connect_to_server": "SeHlaw veS Ho'Do'laHbe'. URL 'ej ret ghun mej.",
"an_unexpected_error_occured": "num ghIq Doch",
"change_server": "Ho'Do' veS yIghoS",
"invalid_username_or_password": "tlhIngan pagh ngoq De' law'be'",
"user_does_not_have_permission_to_log_in": "tlhIngan lut 'el je'laHbe'",
"server_is_taking_too_long_to_respond_try_again_later": "Ho'Do' veS jachrup. pItlh yIHaD.",
"server_received_too_many_requests_try_again_later": "Ho'Do' veS lutlh ngeb petlh law'. pItlh yIHaD.",
"there_is_a_server_error": "Ho'Do' veS ghIq maS",
"an_unexpected_error_occured_did_you_enter_the_correct_url": "num ghIq Doch. URL mej Danej'a'?"
},
"server": {
"enter_url_to_jellyfin_server": "Jellyfin Ho'Do' veS URL yI'el",
"server_url_placeholder": "http(s)://HoDo-veS.com",
"connect_button": "yIngoq!",
"previous_servers": "namen Ho'Do' veS",
"clear_button": "yIQaw'",
"search_for_local_servers": "val Ho'Do' veS yISam",
"searching": "Sam...",
"servers": "Ho'Do' veS"
},
"home": {
"no_internet": "ret pagh",
"no_items": "Doch pagh",
"no_internet_message": "QublaHbe'.\nDoch Qaw'laHnIS SoH.",
"go_to_downloads": "Qaw' Doch yIghoS",
"oops": "QI'ya!",
"error_message": "Doch rurbe'.\nyIQo' 'ej yI'elqa'.",
"continue_watching": "tlhol yIHaDqa'",
"next_up": "wej",
"recently_added_in": "num tu'lu' {{libraryName}}",
"suggested_movies": "rutlh DIS",
"suggested_episodes": "rutlh Hem",
"intro": {
"welcome_to_streamyfin": "Streamyfin yI'el!",
"a_free_and_open_source_client_for_jellyfin": "Jellyfin lut 'el je'be' 'ej wang.",
"features_title": "mIw",
"features_description": "Streamyfin mIw law' tu'. men menuDaq yISam:",
"jellyseerr_feature_description": "Jellyseerr yIngoq 'ej DIS pe'vIl yISov.",
"downloads_feature_title": "Qaw' Doch",
"downloads_feature_description": "DIS 'ej Hem Qaw'laH. Qaw' mIw tu'lu'.",
"chromecast_feature_description": "DIS 'ej Hem Chromecast vI' ghoS.",
"centralised_settings_plugin_title": "wa'DIch men mIw",
"centralised_settings_plugin_description": "Jellyfin Ho'Do' veSDaq men yISeH. tlhIngan chIch.",
"done_button": "Qapla'",
"go_to_settings_button": "men yIghoS",
"read_more": "yIlaDqa'"
},
"settings": {
"settings_title": "men",
"log_out_button": "yIQo'",
"user_info": {
"user_info_title": "tlhIngan De'",
"user": "tlhIngan",
"server": "Ho'Do' veS",
"token": "per De'",
"app_version": "ghun wej",
},
"quick_connect": {
"quick_connect_title": "parmaq ngoQ",
"authorize_button": "parmaq ngoQ yIje'",
"enter_the_quick_connect_code": "parmaq ngoQ De' yI'el...",
"success": "Qapla'",
"quick_connect_autorized": "parmaq ngoQ je'laH",
"error": "ghIq",
"invalid_code": "De' law'be'",
"authorize": "yIje'"
},
"media_controls": {
"media_controls_title": "tlhol SeHlaw",
"forward_skip_length": "Du'Hom vum",
"rewind_length": "bavHom vum",
"seconds_unit": "tera' rep"
},
"audio": {
"audio_title": "QoQ",
"set_audio_track": "namen Doch QoQ ret yISeH",
"audio_language": "QoQ Hol",
"audio_hint": "QoQ Hol wa' yIwIv.",
"none": "pagh",
"language": "Hol"
},
"subtitles": {
"subtitle_title": "De' chu'",
"subtitle_language": "De' chu' Hol",
"subtitle_mode": "De' chu' mIw",
"set_subtitle_track": "namen Doch De' chu' ret yISeH",
"subtitle_size": "De' chu' qIt",
"subtitle_hint": "De' chu' wIvlaw' yISeH.",
"none": "pagh",
"language": "Hol",
"loading": "tlha'... ",
"modes": {
"Default": "wa'",
"Smart": "SonchIy",
"Always": "reH",
"None": "pagh",
"OnlyForced": "Dun je'"
}
},
"other": {
"other_title": "patlh",
"follow_device_orientation": "naDevvo' pegh",
"video_orientation": "mu'tlhegh pegh",
"orientation": "pegh",
"orientations": {
"DEFAULT": "wa'",
"ALL": "Hoch",
"PORTRAIT": "leng ret",
"PORTRAIT_UP": "leng ret Dung",
"PORTRAIT_DOWN": "leng ret nuq",
"LANDSCAPE": "leng yot",
"LANDSCAPE_LEFT": "leng yot poS",
"LANDSCAPE_RIGHT": "leng yot nIH",
"OTHER": "patlh",
"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",
"hide_libraries": "De'wI' bom yIQIj",
"select_liraries_you_want_to_hide": "De'wI' bom Danej QIj yIwIv.",
"disable_haptic_feedback": "Qub quvHa' yIQIj",
"default_quality": "wa' luj"
},
"downloads": {
"downloads_title": "Qaw' Doch",
"download_method": "Qaw' mIw",
"remux_max_download": "Remux Qaw' Dun",
"auto_download": "chIch Qaw'",
"optimized_versions_server": "luj wej Ho'Do' veS",
"save_button": "yIqIp",
"optimized_server": "luj Ho'Do' veS",
"optimized": "luj",
"default": "wa'",
"optimized_version_hint": "luj Ho'Do' veS URL yI'el.",
"read_more_about_optimized_server": "luj Ho'Do' veS latlh yIlaD",
"url": "URL",
"server_url_placeholder": "http(s)://domajn.org:pord"
},
"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...",
"password": "ngoq De'",
"password_placeholder": "tlhIngan {{username}} ngoq De' yI'el",
"save_button": "yIqIp",
"clear_button": "yIQaw'",
"login_button": "yI'el!",
"total_media_requests": "Hoch tlhol petlh",
"movie_quota_limit": "DIS petlh Dun",
"movie_quota_days": "DIS petlh jaj",
"tv_quota_limit": "TV petlh Dun",
"tv_quota_days": "TV petlh jaj",
"reset_jellyseerr_config_button": "Jellyseerr men yIQaw'qa'",
"unlimited": "Dun pagh",
"plus_n_more": "+{{n}} latlh",
"order_by": {
"DEFAULT": "wa'",
"VOTE_COUNT_AND_AVERAGE": "nem chIm 'ej mev",
"POPULARITY": "ruch"
}
},
"marlin_search": {
"enable_marlin_search": "Marlin Sam yIchu'",
"url": "URL",
"server_url_placeholder": "http(s)://domajn.org:pord",
"marlin_search_hint": "Marlin Ho'Do' veS URL yI'el.",
"read_more_about_marlin": "Marlin latlh yIlaD",
"save_button": "yIqIp",
"toasts": {
"saved": "qIp"
}
}
},
"storage": {
"storage_title": "ram",
"app_usage": "ghun {{usedSpace}}%",
"device_usage": "naDev {{availableSpace}}%",
"size_used": "{{used}} / {{total}} ram",
"delete_all_downloaded_files": "Hoch Qaw' Doch yIQaw'",
},
"intro": {
"show_intro": "chu' Doch yIHoch",
"reset_intro": "chu' Doch yIQaw'qa'"
},
"logs": {
"logs_title": "De' qon",
"export_logs": "De' qon yISamqa'",
"click_for_more_info": "latlh De' yIchIch",
"level": "quv",
"no_logs_available": "De' qon pagh",
"delete_all_logs": "Hoch De' qon yIQaw'"
},
"languages": {
"title": "Holmey",
"app_language": "ghun Hol",
"app_language_description": "ghun Hol yIwIv.",
"system": "mIw'a'"
},
"toasts": {
"error_deleting_files": "Qaw' ghIq",
"background_downloads_enabled": "tlhegh Qaw' chu'",
"background_downloads_disabled": "tlhegh Qaw' QIj",
"connected": "ngoQ",
"could_not_connect": "ngoQlaHbe'",
"invalid_url": "URL law'be'"
}
},
"sessions": {
"title": "tlholrap",
"no_active_sessions": "tlholrap pagh chu'"
},
"downloads": {
"downloads_title": "Qaw' Doch",
"tvseries": "TV Hem",
"movies": "DIS",
"queue": "ghom",
"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_tvseries_button": "Hoch TV Hem yIQaw'",
"delete_all_button": "Hoch yIQaw'",
"active_download": "chu' Qaw'",
"no_active_downloads": "chu' Qaw' pagh",
"active_downloads": "chu' Qaw'",
"new_app_version_requires_re_download": "ghun wej chu' Qaw'qa' DaneH",
"new_app_version_requires_re_download_description": "wej chu' Doch Qaw'qa' DaneH. Hoch Qaw' Doch yIQaw' 'ej yIHaDqa'.",
"back": "yIbav",
"delete": "yIQaw'",
"something_went_wrong": "Doch rurbe'",
"could_not_get_stream_url_from_jellyfin": "Jellyfin tlhol ret URL tu'laHbe'",
"eta": "ETA {{eta}}",
"methods": "mIw",
"toasts": {
"you_are_not_allowed_to_download_files": "Doch Qaw' je'laHbe'.",
"deleted_all_movies_successfully": "Hoch DIS Qaw' Qapla'!",
"failed_to_delete_all_movies": "Hoch DIS Qaw'laHbe'",
"deleted_all_tvseries_successfully": "Hoch TV Hem Qaw' Qapla'!",
"failed_to_delete_all_tvseries": "Hoch TV Hem Qaw'laHbe'",
"download_cancelled": "Qaw' ghIm",
"could_not_cancel_download": "Qaw' ghImlaHbe'",
"download_completed": "Qaw' Qapla'",
"download_started_for": "{{item}} Qaw' vIlchu'",
"item_is_ready_to_be_downloaded": "{{item}} Qaw'laHnIS",
"download_stated_for_item": "{{item}} Qaw' vIlchu'",
"download_failed_for_item": "{{item}} Qaw'laHbe' - {{error}}",
"download_completed_for_item": "{{item}} Qaw' Qapla'",
"queued_item_for_optimization": "{{item}} luj ghom",
"failed_to_start_download_for_item": "{{item}} Qaw' vIlchu'laHbe': {{message}}",
"server_responded_with_status_code": "Ho'Do' veS jachrup {{statusCode}}",
"no_response_received_from_server": "Ho'Do' veS jachbe'",
"error_setting_up_the_request": "petlh SeH ghIq",
"failed_to_start_download_for_item_unexpected_error": "{{item}} Qaw' vIlchu'laHbe': num ghIq",
"all_files_folders_and_jobs_deleted_successfully": "Hoch De', ram 'ej vum Qaw' Qapla'",
"an_error_occured_while_deleting_files_and_jobs": "De', ram 'ej vum Qaw'DI' ghIq",
"go_to_downloads": "Qaw' Doch yIghoS"
}
}
},
"search": {
"search_here": "DaH yISam...",
"search": "yISam...",
"x_items": "{{count}} Doch",
"library": "De'wI' bom",
"discover": "yISamqa'",
"no_results": "Doch pagh tu'",
"no_results_found_for": "Doch pagh tu' <...>",
"movies": "DIS",
"series": "Hem",
"episodes": "HemHom",
"collections": "ghom",
"actors": "tlholwI'",
"request_movies": "DIS yIpetlh",
"request_series": "Hem yIpetlh",
"recently_added": "num tu'",
"recent_requests": "num petlh",
"plex_watchlist": "Plex tlhol ghom",
"trending": "chu' ruch",
"popular_movies": "ruch DIS",
"movie_genres": "DIS qorDu'",
"upcoming_movies": "DIS wej",
"studios": "DIS qonwI'",
"popular_tv": "ruch TV",
"tv_genres": "TV qorDu'",
"upcoming_tv": "TV wej",
"networks": "ret",
"tmdb_movie_keyword": "TMDB DIS De'",
"tmdb_movie_genre": "TMDB DIS qorDu'",
"tmdb_tv_keyword": "TMDB TV De'",
"tmdb_tv_genre": "TMDB TV qorDu'",
"tmdb_search": "TMDB Sam",
"tmdb_studio": "TMDB qonwI'",
"tmdb_network": "TMDB ret",
"tmdb_movie_streaming_services": "TMDB DIS tlhol mIw",
"tmdb_tv_streaming_services": "TMDB TV tlhol mIw"
},
"library": {
"no_items_found": "Doch pagh tu'",
"no_results": "Doch pagh tu'",
"no_libraries_found": "De'wI' bom pagh tu'",
"item_types": {
"movies": "DIS",
"series": "Hem",
"boxsets": "Hem ghom",
"items": "Doch"
},
"options": {
"display": "yIHoch",
"row": "ret",
"list": "ghom",
"image_style": "nagh bep",
"poster": "nagh",
"cover": "nagh chop",
"show_titles": "pab HoS yIHoch",
"show_stats": "chIm De' yIHoch"
},
"filters": {
"genres": "qorDu'",
"years": "DIS",
"sort_by": "yIwIv",
"sort_order": "wIv mIw",
"asc": "Dung",
"desc": "nuq",
"tags": "De'Hom"
}
},
"favorites": {
"series": "Hem",
"movies": "DIS",
"episodes": "HemHom",
"videos": "mu'tlhegh",
"boxsets": "Hem ghom",
"playlists": "bom ghom",
"noDataTitle": "wIv Doch pagh",
"noData": "Doch wIv DaneH. DaH tu'laH."
},
"custom_links": {
"no_links": "ret pagh"
},
"player": {
"error": "ghIq",
"failed_to_get_stream_url": "tlhol ret URL tu'laHbe'",
"an_error_occured_while_playing_the_video": "mu'tlhegh tlholDI' ghIq. menDaq De' qon mej.",
"client_error": "lut 'el ghIq",
"could_not_create_stream_for_chromecast": "Chromecast tlhol ret qonlaHbe'",
"message_from_server": "Ho'Do' veS jach: {{message}}",
"video_has_finished_playing": "mu'tlhegh tlhol Qapla'!",
"no_video_source": "mu'tlhegh wang pagh",
"next_episode": "wej HemHom",
"refresh_tracks": "ret yIchu'qa'",
"subtitle_tracks": "De' chu' ret:",
"audio_tracks": "QoQ ret:",
"playback_state": "tlhol mIw:",
"no_data_available": "De' pagh tu'",
"index": "nem:"
},
"item_card": {
"next_up": "wej",
"no_items_to_display": "Doch pagh HochlaH",
"cast_and_crew": "tlholwI' 'ej qonwI'",
"series": "Hem",
"seasons": "muv",
"season": "muv",
"no_episodes_for_this_season": "muvvam HemHom pagh",
"overview": "Hoch Sov",
"more_with": "{{name}} latlh",
"similar_items": "Doch rur",
"no_similar_items_found": "Doch rur pagh tu'",
"video": "mu'tlhegh",
"more_details": "latlh De'",
"quality": "luj",
"audio": "QoQ",
"subtitles": "De' chu'",
"show_more": "latlh yIHoch",
"show_less": "Hom yIHoch",
"appeared_in": "tlholvam",
"could_not_load_item": "Doch tlha'laHbe'",
"none": "pagh",
"download": {
"download_season": "muv yIQaw'",
"download_series": "Hem yIQaw'",
"download_episode": "HemHom yIQaw'",
"download_movie": "DIS yIQaw'",
"download_x_item": "{{item_count}} Doch yIQaw'",
"download_button": "yIQaw'",
"using_optimized_server": "luj Ho'Do' veS tu'lu'",
"using_default_method": "wa' mIw tu'lu'"
}
},
"live_tv": {
"next": "wej",
"previous": "namen",
"live_tv": "chu' TV",
"coming_soon": "wej lup",
"on_now": "DaH",
"shows": "tlhol",
"movies": "DIS",
"sports": "QI'",
"for_kids": "puqbeq",
"news": "De'"
},
"jellyseerr": {
"confirm": "yInej",
"cancel": "yIQo'",
"yes": "HIja'",
"whats_wrong": "Doch rurbe' 'Iv?",
"issue_type": "ghIq bep",
"select_an_issue": "ghIq yIwIv",
"types": "bep",
"describe_the_issue": "(num) ghIq yIqon...",
"submit_button": "yInejqa'",
"report_issue_button": "ghIq yIqon",
"request_button": "yIpetlh",
"are_you_sure_you_want_to_request_all_seasons": "Hoch muv Danej petlh'a'?",
"failed_to_login": "'ellaHbe'",
"cast": "tlholwI'",
"details": "De'",
"status": "mIw",
"original_title": "wa'DIch pab HoS",
"series_type": "Hem bep",
"release_dates": "Qaw' jaj",
"first_air_date": "wa'DIch tlhol jaj",
"next_air_date": "wej tlhol jaj",
"revenue": "boj De'",
"budget": "boj nem",
"original_language": "wa'DIch Hol",
"production_country": "qonwI' qo'",
"studios": "qonwI'",
"network": "ret",
"currently_streaming_on": "DaH tlhol <...>",
"advanced": "SonchIy",
"request_as": "yIpetlh <...>",
"tags": "De'Hom",
"quality_profile": "luj wIvlaw'",
"root_folder": "wa'DIch ram",
"season_all": "muv (Hoch)",
"season_number": "muv {{season_number}}",
"number_episodes": "{{episode_number}} HemHom",
"born": "poS",
"appearances": "tlholvam",
"toasts": {
"jellyseer_does_not_meet_requirements": "Jellyseerr Ho'Do' veS wej law'be'! 2.0.0 yIchu'!",
"jellyseerr_test_failed": "Jellyseerr nejlaHbe'. yIHaDqa'.",
"failed_to_test_jellyseerr_server_url": "Jellyseerr Ho'Do' veS URL nejlaHbe'",
"issue_submitted": "ghIq nejqa'!",
"requested_item": "{{item}} petlh!",
"you_dont_have_permission_to_request": "petlh je'laHbe'!",
"something_went_wrong_requesting_media": "tlhol petlhDI' Doch rurbe'!"
}
},
"tabs": {
"home": "juH",
"search": "Sam",
"library": "De'wI' bom",
"custom_links": "teqlu' ret",
"favorites": "wIv Doch"
}
}
{
"login": {
"username_required": "tlhIngan DaneH",
"error_title": "ghIq",
"login_title": "lut 'el",
"login_to_title": "lut 'el",
"username_placeholder": "tlhIngan",
"password_placeholder": "ngoq De'",
"login_button": "yI'el!",
"quick_connect": "parmaq ngoQ",
"enter_code_to_login": "yI'elDI' De' {{code}} yIlaD",
"failed_to_initiate_quick_connect": "parmaq ngoQ yIchu'laHbe'",
"got_it": "jIyaj",
"connection_failed": "ngoQlaHbe'",
"could_not_connect_to_server": "SeHlaw veS Ho'Do'laHbe'. URL 'ej ret ghun mej.",
"an_unexpected_error_occured": "num ghIq Doch",
"change_server": "Ho'Do' veS yIghoS",
"invalid_username_or_password": "tlhIngan pagh ngoq De' law'be'",
"user_does_not_have_permission_to_log_in": "tlhIngan lut 'el je'laHbe'",
"server_is_taking_too_long_to_respond_try_again_later": "Ho'Do' veS jachrup. pItlh yIHaD.",
"server_received_too_many_requests_try_again_later": "Ho'Do' veS lutlh ngeb petlh law'. pItlh yIHaD.",
"there_is_a_server_error": "Ho'Do' veS ghIq maS",
"an_unexpected_error_occured_did_you_enter_the_correct_url": "num ghIq Doch. URL mej Danej'a'?"
},
"server": {
"enter_url_to_jellyfin_server": "Jellyfin Ho'Do' veS URL yI'el",
"server_url_placeholder": "http(s)://HoDo-veS.com",
"connect_button": "yIngoq!",
"previous_servers": "namen Ho'Do' veS",
"clear_button": "yIQaw'",
"search_for_local_servers": "val Ho'Do' veS yISam",
"searching": "Sam...",
"servers": "Ho'Do' veS"
},
"home": {
"no_internet": "ret pagh",
"no_items": "Doch pagh",
"no_internet_message": "QublaHbe'.\nDoch Qaw'laHnIS SoH.",
"go_to_downloads": "Qaw' Doch yIghoS",
"oops": "QI'ya!",
"error_message": "Doch rurbe'.\nyIQo' 'ej yI'elqa'.",
"continue_watching": "tlhol yIHaDqa'",
"next_up": "wej",
"recently_added_in": "num tu'lu' {{libraryName}}",
"suggested_movies": "rutlh DIS",
"suggested_episodes": "rutlh Hem",
"intro": {
"welcome_to_streamyfin": "Streamyfin yI'el!",
"a_free_and_open_source_client_for_jellyfin": "Jellyfin lut 'el je'be' 'ej wang.",
"features_title": "mIw",
"features_description": "Streamyfin mIw law' tu'. men menuDaq yISam:",
"jellyseerr_feature_description": "Jellyseerr yIngoq 'ej DIS pe'vIl yISov.",
"downloads_feature_title": "Qaw' Doch",
"downloads_feature_description": "DIS 'ej Hem Qaw'laH. Qaw' mIw tu'lu'.",
"chromecast_feature_description": "DIS 'ej Hem Chromecast vI' ghoS.",
"centralised_settings_plugin_title": "wa'DIch men mIw",
"centralised_settings_plugin_description": "Jellyfin Ho'Do' veSDaq men yISeH. tlhIngan chIch.",
"done_button": "Qapla'",
"go_to_settings_button": "men yIghoS",
"read_more": "yIlaDqa'"
},
"settings": {
"settings_title": "men",
"log_out_button": "yIQo'",
"user_info": {
"user_info_title": "tlhIngan De'",
"user": "tlhIngan",
"server": "Ho'Do' veS",
"token": "per De'",
"app_version": "ghun wej"
},
"quick_connect": {
"quick_connect_title": "parmaq ngoQ",
"authorize_button": "parmaq ngoQ yIje'",
"enter_the_quick_connect_code": "parmaq ngoQ De' yI'el...",
"success": "Qapla'",
"quick_connect_autorized": "parmaq ngoQ je'laH",
"error": "ghIq",
"invalid_code": "De' law'be'",
"authorize": "yIje'"
},
"media_controls": {
"media_controls_title": "tlhol SeHlaw",
"forward_skip_length": "Du'Hom vum",
"rewind_length": "bavHom vum",
"seconds_unit": "tera' rep"
},
"audio": {
"audio_title": "QoQ",
"set_audio_track": "namen Doch QoQ ret yISeH",
"audio_language": "QoQ Hol",
"audio_hint": "QoQ Hol wa' yIwIv.",
"none": "pagh",
"language": "Hol"
},
"subtitles": {
"subtitle_title": "De' chu'",
"subtitle_language": "De' chu' Hol",
"subtitle_mode": "De' chu' mIw",
"set_subtitle_track": "namen Doch De' chu' ret yISeH",
"subtitle_size": "De' chu' qIt",
"subtitle_hint": "De' chu' wIvlaw' yISeH.",
"none": "pagh",
"language": "Hol",
"loading": "tlha'... ",
"modes": {
"Default": "wa'",
"Smart": "SonchIy",
"Always": "reH",
"None": "pagh",
"OnlyForced": "Dun je'"
}
},
"other": {
"other_title": "patlh",
"follow_device_orientation": "naDevvo' pegh",
"video_orientation": "mu'tlhegh pegh",
"orientation": "pegh",
"orientations": {
"DEFAULT": "wa'",
"ALL": "Hoch",
"PORTRAIT": "leng ret",
"PORTRAIT_UP": "leng ret Dung",
"PORTRAIT_DOWN": "leng ret nuq",
"LANDSCAPE": "leng yot",
"LANDSCAPE_LEFT": "leng yot poS",
"LANDSCAPE_RIGHT": "leng yot nIH",
"OTHER": "patlh",
"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",
"hide_libraries": "De'wI' bom yIQIj",
"select_liraries_you_want_to_hide": "De'wI' bom Danej QIj yIwIv.",
"disable_haptic_feedback": "Qub quvHa' yIQIj",
"default_quality": "wa' luj"
},
"downloads": {
"downloads_title": "Qaw' Doch",
"download_method": "Qaw' mIw",
"remux_max_download": "Remux Qaw' Dun",
"auto_download": "chIch Qaw'",
"optimized_versions_server": "luj wej Ho'Do' veS",
"save_button": "yIqIp",
"optimized_server": "luj Ho'Do' veS",
"optimized": "luj",
"default": "wa'",
"optimized_version_hint": "luj Ho'Do' veS URL yI'el.",
"read_more_about_optimized_server": "luj Ho'Do' veS latlh yIlaD",
"url": "URL",
"server_url_placeholder": "http(s)://domajn.org:pord"
},
"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...",
"password": "ngoq De'",
"password_placeholder": "tlhIngan {{username}} ngoq De' yI'el",
"save_button": "yIqIp",
"clear_button": "yIQaw'",
"login_button": "yI'el!",
"total_media_requests": "Hoch tlhol petlh",
"movie_quota_limit": "DIS petlh Dun",
"movie_quota_days": "DIS petlh jaj",
"tv_quota_limit": "TV petlh Dun",
"tv_quota_days": "TV petlh jaj",
"reset_jellyseerr_config_button": "Jellyseerr men yIQaw'qa'",
"unlimited": "Dun pagh",
"plus_n_more": "+{{n}} latlh",
"order_by": {
"DEFAULT": "wa'",
"VOTE_COUNT_AND_AVERAGE": "nem chIm 'ej mev",
"POPULARITY": "ruch"
}
},
"marlin_search": {
"enable_marlin_search": "Marlin Sam yIchu'",
"url": "URL",
"server_url_placeholder": "http(s)://domajn.org:pord",
"marlin_search_hint": "Marlin Ho'Do' veS URL yI'el.",
"read_more_about_marlin": "Marlin latlh yIlaD",
"save_button": "yIqIp",
"toasts": {
"saved": "qIp"
}
}
},
"storage": {
"storage_title": "ram",
"app_usage": "ghun {{usedSpace}}%",
"device_usage": "naDev {{availableSpace}}%",
"size_used": "{{used}} / {{total}} ram",
"delete_all_downloaded_files": "Hoch Qaw' Doch yIQaw'"
},
"intro": {
"show_intro": "chu' Doch yIHoch",
"reset_intro": "chu' Doch yIQaw'qa'"
},
"logs": {
"logs_title": "De' qon",
"export_logs": "De' qon yISamqa'",
"click_for_more_info": "latlh De' yIchIch",
"level": "quv",
"no_logs_available": "De' qon pagh",
"delete_all_logs": "Hoch De' qon yIQaw'"
},
"languages": {
"title": "Holmey",
"app_language": "ghun Hol",
"app_language_description": "ghun Hol yIwIv.",
"system": "mIw'a'"
},
"toasts": {
"error_deleting_files": "Qaw' ghIq",
"background_downloads_enabled": "tlhegh Qaw' chu'",
"background_downloads_disabled": "tlhegh Qaw' QIj",
"connected": "ngoQ",
"could_not_connect": "ngoQlaHbe'",
"invalid_url": "URL law'be'"
}
},
"sessions": {
"title": "tlholrap",
"no_active_sessions": "tlholrap pagh chu'"
},
"downloads": {
"downloads_title": "Qaw' Doch",
"tvseries": "TV Hem",
"movies": "DIS",
"queue": "ghom",
"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_tvseries_button": "Hoch TV Hem yIQaw'",
"delete_all_button": "Hoch yIQaw'",
"active_download": "chu' Qaw'",
"no_active_downloads": "chu' Qaw' pagh",
"active_downloads": "chu' Qaw'",
"new_app_version_requires_re_download": "ghun wej chu' Qaw'qa' DaneH",
"new_app_version_requires_re_download_description": "wej chu' Doch Qaw'qa' DaneH. Hoch Qaw' Doch yIQaw' 'ej yIHaDqa'.",
"back": "yIbav",
"delete": "yIQaw'",
"something_went_wrong": "Doch rurbe'",
"could_not_get_stream_url_from_jellyfin": "Jellyfin tlhol ret URL tu'laHbe'",
"eta": "ETA {{eta}}",
"methods": "mIw",
"toasts": {
"you_are_not_allowed_to_download_files": "Doch Qaw' je'laHbe'.",
"deleted_all_movies_successfully": "Hoch DIS Qaw' Qapla'!",
"failed_to_delete_all_movies": "Hoch DIS Qaw'laHbe'",
"deleted_all_tvseries_successfully": "Hoch TV Hem Qaw' Qapla'!",
"failed_to_delete_all_tvseries": "Hoch TV Hem Qaw'laHbe'",
"download_cancelled": "Qaw' ghIm",
"could_not_cancel_download": "Qaw' ghImlaHbe'",
"download_completed": "Qaw' Qapla'",
"download_started_for": "{{item}} Qaw' vIlchu'",
"item_is_ready_to_be_downloaded": "{{item}} Qaw'laHnIS",
"download_stated_for_item": "{{item}} Qaw' vIlchu'",
"download_failed_for_item": "{{item}} Qaw'laHbe' - {{error}}",
"download_completed_for_item": "{{item}} Qaw' Qapla'",
"queued_item_for_optimization": "{{item}} luj ghom",
"failed_to_start_download_for_item": "{{item}} Qaw' vIlchu'laHbe': {{message}}",
"server_responded_with_status_code": "Ho'Do' veS jachrup {{statusCode}}",
"no_response_received_from_server": "Ho'Do' veS jachbe'",
"error_setting_up_the_request": "petlh SeH ghIq",
"failed_to_start_download_for_item_unexpected_error": "{{item}} Qaw' vIlchu'laHbe': num ghIq",
"all_files_folders_and_jobs_deleted_successfully": "Hoch De', ram 'ej vum Qaw' Qapla'",
"an_error_occured_while_deleting_files_and_jobs": "De', ram 'ej vum Qaw'DI' ghIq",
"go_to_downloads": "Qaw' Doch yIghoS"
}
}
},
"search": {
"search_here": "DaH yISam...",
"search": "yISam...",
"x_items": "{{count}} Doch",
"library": "De'wI' bom",
"discover": "yISamqa'",
"no_results": "Doch pagh tu'",
"no_results_found_for": "Doch pagh tu' <...>",
"movies": "DIS",
"series": "Hem",
"episodes": "HemHom",
"collections": "ghom",
"actors": "tlholwI'",
"request_movies": "DIS yIpetlh",
"request_series": "Hem yIpetlh",
"recently_added": "num tu'",
"recent_requests": "num petlh",
"plex_watchlist": "Plex tlhol ghom",
"trending": "chu' ruch",
"popular_movies": "ruch DIS",
"movie_genres": "DIS qorDu'",
"upcoming_movies": "DIS wej",
"studios": "DIS qonwI'",
"popular_tv": "ruch TV",
"tv_genres": "TV qorDu'",
"upcoming_tv": "TV wej",
"networks": "ret",
"tmdb_movie_keyword": "TMDB DIS De'",
"tmdb_movie_genre": "TMDB DIS qorDu'",
"tmdb_tv_keyword": "TMDB TV De'",
"tmdb_tv_genre": "TMDB TV qorDu'",
"tmdb_search": "TMDB Sam",
"tmdb_studio": "TMDB qonwI'",
"tmdb_network": "TMDB ret",
"tmdb_movie_streaming_services": "TMDB DIS tlhol mIw",
"tmdb_tv_streaming_services": "TMDB TV tlhol mIw"
},
"library": {
"no_items_found": "Doch pagh tu'",
"no_results": "Doch pagh tu'",
"no_libraries_found": "De'wI' bom pagh tu'",
"item_types": {
"movies": "DIS",
"series": "Hem",
"boxsets": "Hem ghom",
"items": "Doch"
},
"options": {
"display": "yIHoch",
"row": "ret",
"list": "ghom",
"image_style": "nagh bep",
"poster": "nagh",
"cover": "nagh chop",
"show_titles": "pab HoS yIHoch",
"show_stats": "chIm De' yIHoch"
},
"filters": {
"genres": "qorDu'",
"years": "DIS",
"sort_by": "yIwIv",
"sort_order": "wIv mIw",
"asc": "Dung",
"desc": "nuq",
"tags": "De'Hom"
}
},
"favorites": {
"series": "Hem",
"movies": "DIS",
"episodes": "HemHom",
"videos": "mu'tlhegh",
"boxsets": "Hem ghom",
"playlists": "bom ghom",
"noDataTitle": "wIv Doch pagh",
"noData": "Doch wIv DaneH. DaH tu'laH."
},
"custom_links": {
"no_links": "ret pagh"
},
"player": {
"error": "ghIq",
"failed_to_get_stream_url": "tlhol ret URL tu'laHbe'",
"an_error_occured_while_playing_the_video": "mu'tlhegh tlholDI' ghIq. menDaq De' qon mej.",
"client_error": "lut 'el ghIq",
"could_not_create_stream_for_chromecast": "Chromecast tlhol ret qonlaHbe'",
"message_from_server": "Ho'Do' veS jach: {{message}}",
"video_has_finished_playing": "mu'tlhegh tlhol Qapla'!",
"no_video_source": "mu'tlhegh wang pagh",
"next_episode": "wej HemHom",
"refresh_tracks": "ret yIchu'qa'",
"subtitle_tracks": "De' chu' ret:",
"audio_tracks": "QoQ ret:",
"playback_state": "tlhol mIw:",
"no_data_available": "De' pagh tu'",
"index": "nem:"
},
"item_card": {
"next_up": "wej",
"no_items_to_display": "Doch pagh HochlaH",
"cast_and_crew": "tlholwI' 'ej qonwI'",
"series": "Hem",
"seasons": "muv",
"season": "muv",
"no_episodes_for_this_season": "muvvam HemHom pagh",
"overview": "Hoch Sov",
"more_with": "{{name}} latlh",
"similar_items": "Doch rur",
"no_similar_items_found": "Doch rur pagh tu'",
"video": "mu'tlhegh",
"more_details": "latlh De'",
"quality": "luj",
"audio": "QoQ",
"subtitles": "De' chu'",
"show_more": "latlh yIHoch",
"show_less": "Hom yIHoch",
"appeared_in": "tlholvam",
"could_not_load_item": "Doch tlha'laHbe'",
"none": "pagh",
"download": {
"download_season": "muv yIQaw'",
"download_series": "Hem yIQaw'",
"download_episode": "HemHom yIQaw'",
"download_movie": "DIS yIQaw'",
"download_x_item": "{{item_count}} Doch yIQaw'",
"download_button": "yIQaw'",
"using_optimized_server": "luj Ho'Do' veS tu'lu'",
"using_default_method": "wa' mIw tu'lu'"
}
},
"live_tv": {
"next": "wej",
"previous": "namen",
"live_tv": "chu' TV",
"coming_soon": "wej lup",
"on_now": "DaH",
"shows": "tlhol",
"movies": "DIS",
"sports": "QI'",
"for_kids": "puqbeq",
"news": "De'"
},
"jellyseerr": {
"confirm": "yInej",
"cancel": "yIQo'",
"yes": "HIja'",
"whats_wrong": "Doch rurbe' 'Iv?",
"issue_type": "ghIq bep",
"select_an_issue": "ghIq yIwIv",
"types": "bep",
"describe_the_issue": "(num) ghIq yIqon...",
"submit_button": "yInejqa'",
"report_issue_button": "ghIq yIqon",
"request_button": "yIpetlh",
"are_you_sure_you_want_to_request_all_seasons": "Hoch muv Danej petlh'a'?",
"failed_to_login": "'ellaHbe'",
"cast": "tlholwI'",
"details": "De'",
"status": "mIw",
"original_title": "wa'DIch pab HoS",
"series_type": "Hem bep",
"release_dates": "Qaw' jaj",
"first_air_date": "wa'DIch tlhol jaj",
"next_air_date": "wej tlhol jaj",
"revenue": "boj De'",
"budget": "boj nem",
"original_language": "wa'DIch Hol",
"production_country": "qonwI' qo'",
"studios": "qonwI'",
"network": "ret",
"currently_streaming_on": "DaH tlhol <...>",
"advanced": "SonchIy",
"request_as": "yIpetlh <...>",
"tags": "De'Hom",
"quality_profile": "luj wIvlaw'",
"root_folder": "wa'DIch ram",
"season_all": "muv (Hoch)",
"season_number": "muv {{season_number}}",
"number_episodes": "{{episode_number}} HemHom",
"born": "poS",
"appearances": "tlholvam",
"toasts": {
"jellyseer_does_not_meet_requirements": "Jellyseerr Ho'Do' veS wej law'be'! 2.0.0 yIchu'!",
"jellyseerr_test_failed": "Jellyseerr nejlaHbe'. yIHaDqa'.",
"failed_to_test_jellyseerr_server_url": "Jellyseerr Ho'Do' veS URL nejlaHbe'",
"issue_submitted": "ghIq nejqa'!",
"requested_item": "{{item}} petlh!",
"you_dont_have_permission_to_request": "petlh je'laHbe'!",
"something_went_wrong_requesting_media": "tlhol petlhDI' Doch rurbe'!"
}
},
"tabs": {
"home": "juH",
"search": "Sam",
"library": "De'wI' bom",
"custom_links": "teqlu' ret",
"favorites": "wIv Doch"
}
}

View File

@@ -1,483 +1,483 @@
{
"login": {
"username_required": "Імʼя користувача необхідне",
"error_title": "Помилка",
"login_title": "Вхід",
"login_to_title": "Увійти в",
"username_placeholder": "Імʼя користувача",
"password_placeholder": "Пароль",
"login_button": "Вхід",
"quick_connect": "Швидке Зʼєднання",
"enter_code_to_login": "Введіть код {{code}} для входу",
"failed_to_initiate_quick_connect": "Не вдалося ініціалізувати Швидке Зʼєднання",
"got_it": "Готово",
"connection_failed": "Помилка зʼєднання",
"could_not_connect_to_server": "Неможливо підʼєднатися до серверу. Будь ласка перевірте URL і ваше зʼєднання з мережею",
"an_unexpected_error_occured": "Сталася несподівана помилка",
"change_server": "Змінити сервер",
"invalid_username_or_password": "Неправильні імʼя користувача або пароль",
"user_does_not_have_permission_to_log_in": "Користувач не маю дозволу на вхід",
"server_is_taking_too_long_to_respond_try_again_later": "Сервер відповідає занадто довго, будь-ласка спробуйте пізніше",
"server_received_too_many_requests_try_again_later": "Сервер отримав забагато запитів, будь ласка спробуйте пізніше.",
"there_is_a_server_error": "Відбулася помилка на стороні сервера",
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Відбулася несподівана помилка. Чи введений URL сервера правильний?"
"login": {
"username_required": "Імʼя користувача необхідне",
"error_title": "Помилка",
"login_title": "Вхід",
"login_to_title": "Увійти в",
"username_placeholder": "Імʼя користувача",
"password_placeholder": "Пароль",
"login_button": "Вхід",
"quick_connect": "Швидке Зʼєднання",
"enter_code_to_login": "Введіть код {{code}} для входу",
"failed_to_initiate_quick_connect": "Не вдалося ініціалізувати Швидке Зʼєднання",
"got_it": "Готово",
"connection_failed": "Помилка зʼєднання",
"could_not_connect_to_server": "Неможливо підʼєднатися до серверу. Будь ласка перевірте URL і ваше зʼєднання з мережею",
"an_unexpected_error_occured": "Сталася несподівана помилка",
"change_server": "Змінити сервер",
"invalid_username_or_password": "Неправильні імʼя користувача або пароль",
"user_does_not_have_permission_to_log_in": "Користувач не маю дозволу на вхід",
"server_is_taking_too_long_to_respond_try_again_later": "Сервер відповідає занадто довго, будь-ласка спробуйте пізніше",
"server_received_too_many_requests_try_again_later": "Сервер отримав забагато запитів, будь ласка спробуйте пізніше.",
"there_is_a_server_error": "Відбулася помилка на стороні сервера",
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Відбулася несподівана помилка. Чи введений URL сервера правильний?"
},
"server": {
"enter_url_to_jellyfin_server": "Введіть URL вашого Jellyfin сервера",
"server_url_placeholder": "http(s)://your-server.com",
"connect_button": "Підʼєднатися",
"previous_servers": "попередні сервери",
"clear_button": "Очистити",
"search_for_local_servers": "Пошук локальних серверів",
"searching": "Пошук...",
"servers": "Сервери"
},
"home": {
"no_internet": "Інтернет відсутній",
"no_items": "Пусто",
"no_internet_message": "Не хвилюйтеся, ви все ще можете переглядати\nзавантажений контент.",
"go_to_downloads": "Перейти в завантаження",
"oops": "Упс!",
"error_message": "Щось пішло не так.\nБудь ласка вийдіть і увійдіть знов.",
"continue_watching": "Продовжити перегляд",
"next_up": "Далі",
"recently_added_in": "Нещодавно додане до \"{{libraryName}}\"",
"suggested_movies": "Рекомендовані Фільми",
"suggested_episodes": "Рекомендовані Епізоди",
"intro": {
"welcome_to_streamyfin": "Вітаємо у Streamyfin",
"a_free_and_open_source_client_for_jellyfin": "Вільний і open-source клієнт для Jellyfin.",
"features_title": "Функції",
"features_description": "Streamyfin має безліч функцій та інтегрується з широким спектром програмного забезпечення, яке ви можете знайти в меню налаштувань, зокрема:",
"jellyseerr_feature_description": "Підключіться до вашого екземпляру Jellyseerr і запитуватуйте фільми безпосередньо в застосунку.",
"downloads_feature_title": "Завантаження",
"downloads_feature_description": "Завантажуйте фільми і серіали для перегляду офлайн. Використовуйте або метод за замовчуванням або встановіть оптимізований сервер для завантаження файлів у фоні.",
"chromecast_feature_description": "Транслюйте фільми і серіали на ваші Chromecast прилади.",
"centralised_settings_plugin_title": "Centralised Settings Plugin",
"centralised_settings_plugin_description": "Налаштуйте параметри з централізованої локації на вашому сервері Jellyfin. Всі налаштування клієнтів для всіх користувачів будуть синхронізовані автоматично.",
"done_button": "Готово",
"go_to_settings_button": "Перейти до параметрів",
"read_more": "Прочитати більше"
},
"server": {
"enter_url_to_jellyfin_server": "Введіть URL вашого Jellyfin сервера",
"server_url_placeholder": "http(s)://your-server.com",
"connect_button": "Підʼєднатися",
"previous_servers": "попередні сервери",
"clear_button": "Очистити",
"search_for_local_servers": "Пошук локальних серверів",
"searching": "Пошук...",
"servers": "Сервери"
},
"home": {
"no_internet": "Інтернет відсутній",
"no_items": "Пусто",
"no_internet_message": "Не хвилюйтеся, ви все ще можете переглядати\nзавантажений контент.",
"go_to_downloads": "Перейти в завантаження",
"oops": "Упс!",
"error_message": "Щось пішло не так.\nБудь ласка вийдіть і увійдіть знов.",
"continue_watching": "Продовжити перегляд",
"next_up": "Далі",
"recently_added_in": "Нещодавно додане до \"{{libraryName}}\"",
"suggested_movies": "Рекомендовані Фільми",
"suggested_episodes": "Рекомендовані Епізоди",
"intro": {
"welcome_to_streamyfin": "Вітаємо у Streamyfin",
"a_free_and_open_source_client_for_jellyfin": "Вільний і open-source клієнт для Jellyfin.",
"features_title": "Функції",
"features_description": "Streamyfin має безліч функцій та інтегрується з широким спектром програмного забезпечення, яке ви можете знайти в меню налаштувань, зокрема:",
"jellyseerr_feature_description": "Підключіться до вашого екземпляру Jellyseerr і запитуватуйте фільми безпосередньо в застосунку.",
"downloads_feature_title": "Завантаження",
"downloads_feature_description": "Завантажуйте фільми і серіали для перегляду офлайн. Використовуйте або метод за замовчуванням або встановіть оптимізований сервер для завантаження файлів у фоні.",
"chromecast_feature_description": "Транслюйте фільми і серіали на ваші Chromecast прилади.",
"centralised_settings_plugin_title": "Centralised Settings Plugin",
"centralised_settings_plugin_description": "Налаштуйте параметри з централізованої локації на вашому сервері Jellyfin. Всі налаштування клієнтів для всіх користувачів будуть синхронізовані автоматично.",
"done_button": "Готово",
"go_to_settings_button": "Перейти до параметрів",
"read_more": "Прочитати більше"
},
"settings": {
"settings_title": "Параметри",
"log_out_button": "Вихід",
"user_info": {
"user_info_title": "Інформація користувача",
"user": "Користувач",
"server": "Сервер",
"token": "Токен",
"app_version": "Версія Застосунку"
},
"quick_connect": {
"quick_connect_title": "Швидке Зʼєднання",
"authorize_button": "Авторизуйте Швидке Зʼєднання",
"enter_the_quick_connect_code": "Введіть код для швидкого зʼєднання...",
"success": "Успіх",
"quick_connect_autorized": "Швидке Зʼєднання авторизовано",
"error": "Помилка",
"invalid_code": "Не правильний код",
"authorize": "Авторизувати"
},
"media_controls": {
"media_controls_title": "Керування Медіа",
"forward_skip_length": "Довжина перемотування вперед",
"rewind_length": "Довжина перемотування назад",
"seconds_unit": "с"
},
"audio": {
"audio_title": "Аудіо",
"set_audio_track": "Аудіо доріжка як в попередньому епізоді",
"audio_language": "Мова аудіо",
"audio_hint": "Вибрати мову аудіо за замовчуванням.",
"none": "Ніяка",
"language": "Мова"
},
"subtitles": {
"subtitle_title": "Субтитри",
"subtitle_language": "Мова субтитрів",
"subtitle_mode": "Режим субтитрів",
"set_subtitle_track": "Виставити доріжку субтитрів як в попередньому епізоду",
"subtitle_size": "Розмір субтитрів",
"subtitle_hint": "Налаштуйте параметри субтитрів.",
"none": "Ніякі",
"language": "Мова",
"loading": "Завантаження",
"modes": {
"Default": "За замовчування",
"Smart": "Smart",
"Always": "Завжди",
"None": "Някий",
"OnlyForced": "Виключно Форсовані"
}
},
"other": {
"other_title": "Інші",
"follow_device_orientation": "Дотримуйтесь орієнтації пристрою",
"video_orientation": "Орієнтація відео",
"orientation": "Orientation",
"orientations": {
"DEFAULT": "За змовчуванням",
"ALL": "Всі",
"PORTRAIT": "Портретна",
"PORTRAIT_UP": "Портретна Догори",
"PORTRAIT_DOWN": "Портретна Донизу",
"LANDSCAPE": "Альбомна",
"LANDSCAPE_LEFT": "Альбомна Ліва",
"LANDSCAPE_RIGHT": "Альбомна Права",
"OTHER": "Інше",
"UNKNOWN": "Невідомо"
},
"safe_area_in_controls": "Безпечна зона в елементах керування",
"video_player": "Відео плеєр",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "Показати користувацькі посилання меню",
"hide_libraries": "Сховати медіатеки",
"select_liraries_you_want_to_hide": "Виберіть медіатеки, що бажаєте приховати з вкладки Медіатека і з секції на головній сторінці.",
"disable_haptic_feedback": "Вимкнути тактильний зворотний зв'язок",
"default_quality": "Якість за замовченням",
"disabled": "Вимкнено"
},
"downloads": {
"downloads_title": "Завантаження",
"download_method": "Метод завантаження",
"remux_max_download": "Remux max download",
"auto_download": "Авто-завантаження",
"optimized_versions_server": "Сервер оптимізованих версій",
"save_button": "Зберегти",
"optimized_server": "Оптимізований Сервер",
"optimized": "Оптимізований",
"default": "За замовченням",
"optimized_version_hint": "Введіть URL-адресу сервера для оптимізації. URL-адреса має містити http або https і, за бажанням, порт.",
"read_more_about_optimized_server": "Дізнайтеся більше про сервер для оптимізації.",
"url": "URL",
"server_url_placeholder": "http(s)://domain.org:port"
},
"plugins": {
"plugins_title": "Плагіни",
"jellyseerr": {
"jellyseerr_warning": "Ця інтеграція перебуває на початковій стадії. Очікуйте, що все зміниться.",
"server_url": "URL Сервера",
"server_url_hint": "Наприклад: http(s)://your-host.url\n(додайте порт якщо необхідно)",
"server_url_placeholder": "Jellyseerr URL...",
"password": "Пароль",
"password_placeholder": "Введіть Jellyfin пароль для користувача {{username}}",
"save_button": "Зберегти",
"clear_button": "Очистити",
"login_button": "Вхід",
"total_media_requests": "Загальна кількість медіа запитів",
"movie_quota_limit": "Дні квоти на фільми",
"movie_quota_days": "Дні квоти на фільми",
"tv_quota_limit": "Дні квоти на серіали",
"tv_quota_days": "Дні квоти на серіали",
"reset_jellyseerr_config_button": "Скинути конфігурацію Jellyseerr",
"unlimited": "Необмежене",
"plus_n_more": "+{{n}} ще",
"order_by": {
"DEFAULT": "За замовченням",
"VOTE_COUNT_AND_AVERAGE": "Кількість голосів і середнє",
"POPULARITY": "Популярність"
}
},
"marlin_search": {
"enable_marlin_search": "Увімкнути Marlin Search ",
"url": "URL",
"server_url_placeholder": "http(s)://domain.org:port",
"marlin_search_hint": "Введіть URL-адресу сервера Marlin. Адреса повинна містити http або https і, за бажанням, порт.",
"read_more_about_marlin": "Дізнайтеся більше про Marlin.",
"save_button": "Зберегти",
"toasts": {
"saved": "Збережено"
}
}
},
"storage": {
"storage_title": "Сховище",
"app_usage": "Застосунок {{usedSpace}}%",
"device_usage": "Гаджет {{availableSpace}}%",
"size_used": "{{used}} з {{total}} використано",
"delete_all_downloaded_files": "Видалити усі завантаженні файли"
},
"intro": {
"show_intro": "Показати інтро",
"reset_intro": "Скинути інтро"
},
"logs": {
"logs_title": "Журнал",
"export_logs": "Export logs",
"click_for_more_info": "Click for more info",
"level": "Level",
"no_logs_available": "Нема доступних журналів",
"delete_all_logs": "Видалити усі журнали"
},
"languages": {
"title": "Мова",
"app_language": "Мова застосунку",
"app_language_description": "Виберіть мову застосунку.",
"system": "Системна"
},
"toasts": {
"error_deleting_files": "Помилка при видалені файлів",
"background_downloads_enabled": "Завантаження в фоні увімкнене",
"background_downloads_disabled": "Завантаження в фоні вимкнене",
"connected": "Зʼєднано",
"could_not_connect": "Неможливо зʼєднатися",
"invalid_url": "Неправльий URL"
}
},
"sessions": {
"title": "Сесії",
"no_active_sessions": "Нема активних сесій"
},
"downloads": {
"downloads_title": "Завантаження",
"tvseries": "ТБ-Серіали",
"movies": "Фільми",
"queue": "Черга",
"queue_hint": "Черга і завантаження буде втрачене при перезапуску застосунку",
"no_items_in_queue": "Нема елементів в черзі",
"no_downloaded_items": "Нема завантажених елементів",
"delete_all_movies_button": "Видалити всі Фільми",
"delete_all_tvseries_button": "Видалити всі ТБ-Серіали",
"delete_all_button": "Видалити Все",
"active_download": "Активне завантаження",
"no_active_downloads": "Нема активних завантажень",
"active_downloads": "Активні завантаження",
"new_app_version_requires_re_download": "Нова версія застосунку вимагає завантажити заново",
"new_app_version_requires_re_download_description": "Нове оновлення вимагає повторного завантаження вмісту. Будь ласка, видаліть весь завантажений вміст і повторіть спробу.",
"back": "Назад",
"delete": "Видалити",
"something_went_wrong": "Щось пішло не так",
"could_not_get_stream_url_from_jellyfin": "Не вдалося отримати URL-адресу потоку від Jellyfin",
"eta": "ETA {{eta}}",
"methods": "Методи",
"toasts": {
"you_are_not_allowed_to_download_files": "Вам не дозволено завантажувати файли.",
"deleted_all_movies_successfully": "Видалення всіх фільмів було успішне!",
"failed_to_delete_all_movies": "Не вдалося видалити усі фільми",
"deleted_all_tvseries_successfully": "Успішно видалено всі серіали!",
"failed_to_delete_all_tvseries": "Не вдалося видалити всі телесеріали",
"download_cancelled": "Завантаження скасоване",
"could_not_cancel_download": "Неможливо скасувати завантаження",
"download_completed": "Завантаження завершено",
"download_started_for": "Почалося завантаження {{item}}",
"item_is_ready_to_be_downloaded": "{{item}} вже завантажено",
"download_stated_for_item": "Почалося завантаження {{item}}",
"download_failed_for_item": "Не вдалося завантажити {{item}} - {{error}}",
"download_completed_for_item": "Завантаження завершено {{item}}",
"queued_item_for_optimization": "{{item}} в черзі на оптимізацію",
"failed_to_start_download_for_item": "Не вдалося почати завантаження {{item}}: {{message}}",
"server_responded_with_status_code": "Сервер відповів зі статусом {{statusCode}}",
"no_response_received_from_server": "Не отримано відповіді від сервера",
"error_setting_up_the_request": "Помилка налаштування запиту",
"failed_to_start_download_for_item_unexpected_error": "Не вдалося почати завантаження {{item}}: Несподівана помилка",
"all_files_folders_and_jobs_deleted_successfully": "Усі файли, папки та завдання успішно видалено",
"an_error_occured_while_deleting_files_and_jobs": "Виникла помилка під час видалення файлів і завдань",
"go_to_downloads": "Перейти до завантаження"
}
}
},
"search": {
"search_here": "Шукати тут...",
"search": "Шукати...",
"x_items": "{{count}} елементів",
"library": "Медіатека",
"discover": "Відкрийте для себе",
"no_results": "Без результатів",
"no_results_found_for": "Жодних результатів не знайдено для",
"movies": "Фільми",
"series": "Серіали",
"episodes": "Епізоди",
"collections": "Колекції",
"actors": "Актори",
"request_movies": "Запитати Фільми",
"request_series": "Запитати Серіали",
"recently_added": "Нещодавно Додане",
"recent_requests": "Нещодавні Запити",
"plex_watchlist": "Список перегляду Plex",
"trending": "У Тренді",
"popular_movies": "Популярні Фільми",
"movie_genres": "Жанри Кіно",
"upcoming_movies": "Майбутні Фільми",
"studios": "Студії",
"popular_tv": "Популярні Серіали",
"tv_genres": "Жанри Серіалів",
"upcoming_tv": "Майбутні Серіали",
"networks": "ТБ Канали",
"tmdb_movie_keyword": "TMDB Ключові слова Фільмів",
"tmdb_movie_genre": "TMDB Жанри Кіно",
"tmdb_tv_keyword": "TMDB ТБ Ключові слова",
"tmdb_tv_genre": "TMDB ТБ Жанри",
"tmdb_search": "TMDB Пошук",
"tmdb_studio": "TMDB Студії",
"tmdb_network": "TMDB ТБ Канали",
"tmdb_movie_streaming_services": "TMDB Стрімінгові Сервіси Фільмів",
"tmdb_tv_streaming_services": "TMDB Стрімінгові Сервіси Серіалів"
},
"library": {
"no_items_found": "Елементів не знайдено",
"no_results": "Без результатів",
"no_libraries_found": "Не знайдено медіатек",
"item_types": {
"movies": "фільми",
"series": "серіали",
"boxsets": "бокс-сети",
"items": "елементи"
},
"options": {
"display": "Показати",
"row": "Ряд",
"list": "Список",
"image_style": "Стиль зображення",
"poster": "Постер",
"cover": "Обкладинка",
"show_titles": "Показати заголовки",
"show_stats": "Показати статистику"
},
"filters": {
"genres": "Жанри",
"years": "Роки",
"sort_by": "Відсортувати за",
"sort_order": "Порядок сортування",
"asc": "За зростанням",
"desc": "За спаданням",
"tags": "Теги"
}
},
"favorites": {
"series": "Серіали",
"movies": "Фільми",
"episodes": "Епізоди",
"videos": "Відео",
"boxsets": "Бокс-сети",
"playlists": "Плейлісти",
"noDataTitle": "Поки що нема обраного",
"noData": "Відмітьте як улюблене що би побачити це тут в швидкому доступі."
},
"custom_links": {
"no_links": "Немає посилань"
},
"player": {
"settings": {
"settings_title": "Параметри",
"log_out_button": "Вихід",
"user_info": {
"user_info_title": "Інформація користувача",
"user": "Користувач",
"server": "Сервер",
"token": "Токен",
"app_version": "Версія Застосунку"
},
"quick_connect": {
"quick_connect_title": "Швидке Зʼєднання",
"authorize_button": "Авторизуйте Швидке Зʼєднання",
"enter_the_quick_connect_code": "Введіть код для швидкого зʼєднання...",
"success": "Успіх",
"quick_connect_autorized": "Швидке Зʼєднання авторизовано",
"error": "Помилка",
"failed_to_get_stream_url": "Не вдалося отримати URL-адресу потоку",
"an_error_occured_while_playing_the_video": "Під час відтворення відео сталася помилка. Перевірте журнал в налаштуваннях.",
"client_error": "Помилка клієнту",
"could_not_create_stream_for_chromecast": "Не вдалося створити потік для Chromecast",
"message_from_server": "Повідомлення від серверу: {{message}}",
"video_has_finished_playing": "Відтворення відео завершено!",
"no_video_source": "Немає джерела відео...",
"next_episode": "Наступний Епізод",
"refresh_tracks": "Оновити доріжки",
"subtitle_tracks": "Доріжки Субтитрів:",
"audio_tracks": "Аудіо-доріжки:",
"playback_state": "Стан відтворення:",
"no_data_available": "Дані відсутні",
"index": "Індекс:",
"continue_watching": "Продовжити перегляд",
"go_back": "Назад"
},
"item_card": {
"next_up": "Далі",
"no_items_to_display": "Немає елементів для відображення",
"cast_and_crew": "Акторський склад та команда",
"series": "Серіали",
"seasons": "Сезони",
"season": "Сезон",
"no_episodes_for_this_season": "У цьому сезоні немає епізодів",
"overview": "Огляд",
"more_with": "Більше з {{name}}",
"similar_items": "Схожі елементи",
"no_similar_items_found": "Не знайдено схожих елементів",
"video": "Відео",
"more_details": "Більше деталей",
"quality": "Якість",
"audio": "Аудіо",
"subtitles": "Субтитри",
"show_more": "Показати більше",
"show_less": "Показати менше",
"appeared_in": "Зʼявлявся у",
"could_not_load_item": "Неможливо завантажити елемент",
"none": "Нічого",
"download": {
"download_season": "Завантажити Сезон",
"download_series": "Завантажити Серіал",
"download_episode": "Завантажити Епізод",
"download_movie": "Завантажити Фільм",
"download_x_item": "Завантажено {{item_count}} елементів",
"download_button": "Завантажити",
"using_optimized_server": "Використовуючи сервер оптимізації",
"using_default_method": "Використовуючи метод за замовченням"
"invalid_code": "Не правильний код",
"authorize": "Авторизувати"
},
"media_controls": {
"media_controls_title": "Керування Медіа",
"forward_skip_length": "Довжина перемотування вперед",
"rewind_length": "Довжина перемотування назад",
"seconds_unit": "с"
},
"audio": {
"audio_title": "Аудіо",
"set_audio_track": "Аудіо доріжка як в попередньому епізоді",
"audio_language": "Мова аудіо",
"audio_hint": "Вибрати мову аудіо за замовчуванням.",
"none": "Ніяка",
"language": "Мова"
},
"subtitles": {
"subtitle_title": "Субтитри",
"subtitle_language": "Мова субтитрів",
"subtitle_mode": "Режим субтитрів",
"set_subtitle_track": "Виставити доріжку субтитрів як в попередньому епізоду",
"subtitle_size": "Розмір субтитрів",
"subtitle_hint": "Налаштуйте параметри субтитрів.",
"none": "Ніякі",
"language": "Мова",
"loading": "Завантаження",
"modes": {
"Default": "За замовчування",
"Smart": "Smart",
"Always": "Завжди",
"None": "Някий",
"OnlyForced": "Виключно Форсовані"
}
},
"live_tv": {
"next": "Наступний",
"previous": "Попередній",
"live_tv": "Live TV",
"coming_soon": "Скоро",
"on_now": "Просто зараз",
"shows": "Серіали",
"movies": "Фільми",
"sports": "Спорт",
"for_kids": "Для дітей",
"news": "Новини"
},
"jellyseerr": {
"confirm": "Підтвердити",
"cancel": "Скасувати",
"yes": "Так",
"whats_wrong": "Щось сталося?",
"issue_type": "Тип проблеми",
"select_an_issue": "Виберіть проблему",
"types": "Типи",
"describe_the_issue": "(опціонально) Опишіть проблему...",
"submit_button": "Надіслати",
"report_issue_button": "Звіт про проблему",
"request_button": "Запити",
"are_you_sure_you_want_to_request_all_seasons": "Ви впевнені, що хочете запросити всі сезони?",
"failed_to_login": "Не вдалося увійти",
"cast": "Акторський склад",
"details": "Деталі",
"status": "Статус",
"original_title": "Оригінальна Назва",
"series_type": "Тип Серіалу",
"release_dates": "Дата Виходу",
"first_air_date": "Дата першого етеру",
"next_air_date": "Дата наступного етеру",
"revenue": "Збори",
"budget": "Бюджет",
"original_language": "Мова Оригіналу",
"production_country": "Країна Виробництва",
"studios": "Студії",
"network": "ТБ Канали",
"currently_streaming_on": "Наразі транслюється на",
"advanced": "Просунуте",
"request_as": "Запит Як",
"tags": "Теги",
"quality_profile": "Профіль якості",
"root_folder": "Корнева Тека",
"season_all": "Сезон (всі)",
"season_number": "Сезон {{season_number}}",
"number_episodes": "{{episode_number}} Епізодів",
"born": "Дата народження",
"appearances": "Зовнішній вигляд",
"toasts": {
"jellyseer_does_not_meet_requirements": "Версія Jellyseerr не відповідає мінімальним вимогам! Будь ласка, оновіться принаймні до 2.0.0",
"jellyseerr_test_failed": "Тест Jellyseerr завершився невдало. Спробуйте ще раз.",
"failed_to_test_jellyseerr_server_url": "Не вдалося перевірити URL-адресу сервера jellyseerr",
"issue_submitted": "Звіт про проблему відправлено",
"requested_item": "Запитано {{item}}!",
"you_dont_have_permission_to_request": "Ви не маєте дозволу на запит медіа!",
"something_went_wrong_requesting_media": "Щось пішло не так під час запиту медіа!"
},
"other": {
"other_title": "Інші",
"follow_device_orientation": "Дотримуйтесь орієнтації пристрою",
"video_orientation": "Орієнтація відео",
"orientation": "Orientation",
"orientations": {
"DEFAULT": "За змовчуванням",
"ALL": "Всі",
"PORTRAIT": "Портретна",
"PORTRAIT_UP": "Портретна Догори",
"PORTRAIT_DOWN": "Портретна Донизу",
"LANDSCAPE": "Альбомна",
"LANDSCAPE_LEFT": "Альбомна Ліва",
"LANDSCAPE_RIGHT": "Альбомна Права",
"OTHER": "Інше",
"UNKNOWN": "Невідомо"
},
"safe_area_in_controls": "Безпечна зона в елементах керування",
"video_player": "Відео плеєр",
"video_players": {
"VLC_3": "VLC 3",
"VLC_4": "VLC 4 (Experimental + PiP)"
},
"show_custom_menu_links": "Показати користувацькі посилання меню",
"hide_libraries": "Сховати медіатеки",
"select_liraries_you_want_to_hide": "Виберіть медіатеки, що бажаєте приховати з вкладки Медіатека і з секції на головній сторінці.",
"disable_haptic_feedback": "Вимкнути тактильний зворотний зв'язок",
"default_quality": "Якість за замовченням",
"disabled": "Вимкнено"
},
"downloads": {
"downloads_title": "Завантаження",
"download_method": "Метод завантаження",
"remux_max_download": "Remux max download",
"auto_download": "Авто-завантаження",
"optimized_versions_server": "Сервер оптимізованих версій",
"save_button": "Зберегти",
"optimized_server": "Оптимізований Сервер",
"optimized": "Оптимізований",
"default": "За замовченням",
"optimized_version_hint": "Введіть URL-адресу сервера для оптимізації. URL-адреса має містити http або https і, за бажанням, порт.",
"read_more_about_optimized_server": "Дізнайтеся більше про сервер для оптимізації.",
"url": "URL",
"server_url_placeholder": "http(s)://domain.org:port"
},
"plugins": {
"plugins_title": "Плагіни",
"jellyseerr": {
"jellyseerr_warning": "Ця інтеграція перебуває на початковій стадії. Очікуйте, що все зміниться.",
"server_url": "URL Сервера",
"server_url_hint": "Наприклад: http(s)://your-host.url\n(додайте порт якщо необхідно)",
"server_url_placeholder": "Jellyseerr URL...",
"password": "Пароль",
"password_placeholder": "Введіть Jellyfin пароль для користувача {{username}}",
"save_button": "Зберегти",
"clear_button": "Очистити",
"login_button": "Вхід",
"total_media_requests": "Загальна кількість медіа запитів",
"movie_quota_limit": "Дні квоти на фільми",
"movie_quota_days": "Дні квоти на фільми",
"tv_quota_limit": "Дні квоти на серіали",
"tv_quota_days": "Дні квоти на серіали",
"reset_jellyseerr_config_button": "Скинути конфігурацію Jellyseerr",
"unlimited": "Необмежене",
"plus_n_more": "+{{n}} ще",
"order_by": {
"DEFAULT": "За замовченням",
"VOTE_COUNT_AND_AVERAGE": "Кількість голосів і середнє",
"POPULARITY": "Популярність"
}
},
"marlin_search": {
"enable_marlin_search": "Увімкнути Marlin Search ",
"url": "URL",
"server_url_placeholder": "http(s)://domain.org:port",
"marlin_search_hint": "Введіть URL-адресу сервера Marlin. Адреса повинна містити http або https і, за бажанням, порт.",
"read_more_about_marlin": "Дізнайтеся більше про Marlin.",
"save_button": "Зберегти",
"toasts": {
"saved": "Збережено"
}
}
},
"storage": {
"storage_title": "Сховище",
"app_usage": "Застосунок {{usedSpace}}%",
"device_usage": "Гаджет {{availableSpace}}%",
"size_used": "{{used}} з {{total}} використано",
"delete_all_downloaded_files": "Видалити усі завантаженні файли"
},
"intro": {
"show_intro": "Показати інтро",
"reset_intro": "Скинути інтро"
},
"logs": {
"logs_title": "Журнал",
"export_logs": "Export logs",
"click_for_more_info": "Click for more info",
"level": "Level",
"no_logs_available": "Нема доступних журналів",
"delete_all_logs": "Видалити усі журнали"
},
"languages": {
"title": "Мова",
"app_language": "Мова застосунку",
"app_language_description": "Виберіть мову застосунку.",
"system": "Системна"
},
"toasts": {
"error_deleting_files": "Помилка при видалені файлів",
"background_downloads_enabled": "Завантаження в фоні увімкнене",
"background_downloads_disabled": "Завантаження в фоні вимкнене",
"connected": "Зʼєднано",
"could_not_connect": "Неможливо зʼєднатися",
"invalid_url": "Неправльий URL"
}
},
"tabs": {
"home": "Головна",
"search": "Пошук",
"library": "Медіатека",
"custom_links": "Ваші Посилання",
"favorites": "Улюблене"
"sessions": {
"title": "Сесії",
"no_active_sessions": "Нема активних сесій"
},
"downloads": {
"downloads_title": "Завантаження",
"tvseries": "ТБ-Серіали",
"movies": "Фільми",
"queue": "Черга",
"queue_hint": "Черга і завантаження буде втрачене при перезапуску застосунку",
"no_items_in_queue": "Нема елементів в черзі",
"no_downloaded_items": "Нема завантажених елементів",
"delete_all_movies_button": "Видалити всі Фільми",
"delete_all_tvseries_button": "Видалити всі ТБ-Серіали",
"delete_all_button": "Видалити Все",
"active_download": "Активне завантаження",
"no_active_downloads": "Нема активних завантажень",
"active_downloads": "Активні завантаження",
"new_app_version_requires_re_download": "Нова версія застосунку вимагає завантажити заново",
"new_app_version_requires_re_download_description": "Нове оновлення вимагає повторного завантаження вмісту. Будь ласка, видаліть весь завантажений вміст і повторіть спробу.",
"back": "Назад",
"delete": "Видалити",
"something_went_wrong": "Щось пішло не так",
"could_not_get_stream_url_from_jellyfin": "Не вдалося отримати URL-адресу потоку від Jellyfin",
"eta": "ETA {{eta}}",
"methods": "Методи",
"toasts": {
"you_are_not_allowed_to_download_files": "Вам не дозволено завантажувати файли.",
"deleted_all_movies_successfully": "Видалення всіх фільмів було успішне!",
"failed_to_delete_all_movies": "Не вдалося видалити усі фільми",
"deleted_all_tvseries_successfully": "Успішно видалено всі серіали!",
"failed_to_delete_all_tvseries": "Не вдалося видалити всі телесеріали",
"download_cancelled": "Завантаження скасоване",
"could_not_cancel_download": "Неможливо скасувати завантаження",
"download_completed": "Завантаження завершено",
"download_started_for": "Почалося завантаження {{item}}",
"item_is_ready_to_be_downloaded": "{{item}} вже завантажено",
"download_stated_for_item": "Почалося завантаження {{item}}",
"download_failed_for_item": "Не вдалося завантажити {{item}} - {{error}}",
"download_completed_for_item": "Завантаження завершено {{item}}",
"queued_item_for_optimization": "{{item}} в черзі на оптимізацію",
"failed_to_start_download_for_item": "Не вдалося почати завантаження {{item}}: {{message}}",
"server_responded_with_status_code": "Сервер відповів зі статусом {{statusCode}}",
"no_response_received_from_server": "Не отримано відповіді від сервера",
"error_setting_up_the_request": "Помилка налаштування запиту",
"failed_to_start_download_for_item_unexpected_error": "Не вдалося почати завантаження {{item}}: Несподівана помилка",
"all_files_folders_and_jobs_deleted_successfully": "Усі файли, папки та завдання успішно видалено",
"an_error_occured_while_deleting_files_and_jobs": "Виникла помилка під час видалення файлів і завдань",
"go_to_downloads": "Перейти до завантаження"
}
}
}
},
"search": {
"search_here": "Шукати тут...",
"search": "Шукати...",
"x_items": "{{count}} елементів",
"library": "Медіатека",
"discover": "Відкрийте для себе",
"no_results": "Без результатів",
"no_results_found_for": "Жодних результатів не знайдено для",
"movies": "Фільми",
"series": "Серіали",
"episodes": "Епізоди",
"collections": "Колекції",
"actors": "Актори",
"request_movies": "Запитати Фільми",
"request_series": "Запитати Серіали",
"recently_added": "Нещодавно Додане",
"recent_requests": "Нещодавні Запити",
"plex_watchlist": "Список перегляду Plex",
"trending": "У Тренді",
"popular_movies": "Популярні Фільми",
"movie_genres": "Жанри Кіно",
"upcoming_movies": "Майбутні Фільми",
"studios": "Студії",
"popular_tv": "Популярні Серіали",
"tv_genres": "Жанри Серіалів",
"upcoming_tv": "Майбутні Серіали",
"networks": "ТБ Канали",
"tmdb_movie_keyword": "TMDB Ключові слова Фільмів",
"tmdb_movie_genre": "TMDB Жанри Кіно",
"tmdb_tv_keyword": "TMDB ТБ Ключові слова",
"tmdb_tv_genre": "TMDB ТБ Жанри",
"tmdb_search": "TMDB Пошук",
"tmdb_studio": "TMDB Студії",
"tmdb_network": "TMDB ТБ Канали",
"tmdb_movie_streaming_services": "TMDB Стрімінгові Сервіси Фільмів",
"tmdb_tv_streaming_services": "TMDB Стрімінгові Сервіси Серіалів"
},
"library": {
"no_items_found": "Елементів не знайдено",
"no_results": "Без результатів",
"no_libraries_found": "Не знайдено медіатек",
"item_types": {
"movies": "фільми",
"series": "серіали",
"boxsets": "бокс-сети",
"items": "елементи"
},
"options": {
"display": "Показати",
"row": "Ряд",
"list": "Список",
"image_style": "Стиль зображення",
"poster": "Постер",
"cover": "Обкладинка",
"show_titles": "Показати заголовки",
"show_stats": "Показати статистику"
},
"filters": {
"genres": "Жанри",
"years": "Роки",
"sort_by": "Відсортувати за",
"sort_order": "Порядок сортування",
"asc": "За зростанням",
"desc": "За спаданням",
"tags": "Теги"
}
},
"favorites": {
"series": "Серіали",
"movies": "Фільми",
"episodes": "Епізоди",
"videos": "Відео",
"boxsets": "Бокс-сети",
"playlists": "Плейлісти",
"noDataTitle": "Поки що нема обраного",
"noData": "Відмітьте як улюблене що би побачити це тут в швидкому доступі."
},
"custom_links": {
"no_links": "Немає посилань"
},
"player": {
"error": "Помилка",
"failed_to_get_stream_url": "Не вдалося отримати URL-адресу потоку",
"an_error_occured_while_playing_the_video": "Під час відтворення відео сталася помилка. Перевірте журнал в налаштуваннях.",
"client_error": "Помилка клієнту",
"could_not_create_stream_for_chromecast": "Не вдалося створити потік для Chromecast",
"message_from_server": "Повідомлення від серверу: {{message}}",
"video_has_finished_playing": "Відтворення відео завершено!",
"no_video_source": "Немає джерела відео...",
"next_episode": "Наступний Епізод",
"refresh_tracks": "Оновити доріжки",
"subtitle_tracks": "Доріжки Субтитрів:",
"audio_tracks": "Аудіо-доріжки:",
"playback_state": "Стан відтворення:",
"no_data_available": "Дані відсутні",
"index": "Індекс:",
"continue_watching": "Продовжити перегляд",
"go_back": "Назад"
},
"item_card": {
"next_up": "Далі",
"no_items_to_display": "Немає елементів для відображення",
"cast_and_crew": "Акторський склад та команда",
"series": "Серіали",
"seasons": "Сезони",
"season": "Сезон",
"no_episodes_for_this_season": "У цьому сезоні немає епізодів",
"overview": "Огляд",
"more_with": "Більше з {{name}}",
"similar_items": "Схожі елементи",
"no_similar_items_found": "Не знайдено схожих елементів",
"video": "Відео",
"more_details": "Більше деталей",
"quality": "Якість",
"audio": "Аудіо",
"subtitles": "Субтитри",
"show_more": "Показати більше",
"show_less": "Показати менше",
"appeared_in": "Зʼявлявся у",
"could_not_load_item": "Неможливо завантажити елемент",
"none": "Нічого",
"download": {
"download_season": "Завантажити Сезон",
"download_series": "Завантажити Серіал",
"download_episode": "Завантажити Епізод",
"download_movie": "Завантажити Фільм",
"download_x_item": "Завантажено {{item_count}} елементів",
"download_button": "Завантажити",
"using_optimized_server": "Використовуючи сервер оптимізації",
"using_default_method": "Використовуючи метод за замовченням"
}
},
"live_tv": {
"next": "Наступний",
"previous": "Попередній",
"live_tv": "Live TV",
"coming_soon": "Скоро",
"on_now": "Просто зараз",
"shows": "Серіали",
"movies": "Фільми",
"sports": "Спорт",
"for_kids": "Для дітей",
"news": "Новини"
},
"jellyseerr": {
"confirm": "Підтвердити",
"cancel": "Скасувати",
"yes": "Так",
"whats_wrong": "Щось сталося?",
"issue_type": "Тип проблеми",
"select_an_issue": "Виберіть проблему",
"types": "Типи",
"describe_the_issue": "(опціонально) Опишіть проблему...",
"submit_button": "Надіслати",
"report_issue_button": "Звіт про проблему",
"request_button": "Запити",
"are_you_sure_you_want_to_request_all_seasons": "Ви впевнені, що хочете запросити всі сезони?",
"failed_to_login": "Не вдалося увійти",
"cast": "Акторський склад",
"details": "Деталі",
"status": "Статус",
"original_title": "Оригінальна Назва",
"series_type": "Тип Серіалу",
"release_dates": "Дата Виходу",
"first_air_date": "Дата першого етеру",
"next_air_date": "Дата наступного етеру",
"revenue": "Збори",
"budget": "Бюджет",
"original_language": "Мова Оригіналу",
"production_country": "Країна Виробництва",
"studios": "Студії",
"network": "ТБ Канали",
"currently_streaming_on": "Наразі транслюється на",
"advanced": "Просунуте",
"request_as": "Запит Як",
"tags": "Теги",
"quality_profile": "Профіль якості",
"root_folder": "Корнева Тека",
"season_all": "Сезон (всі)",
"season_number": "Сезон {{season_number}}",
"number_episodes": "{{episode_number}} Епізодів",
"born": "Дата народження",
"appearances": "Зовнішній вигляд",
"toasts": {
"jellyseer_does_not_meet_requirements": "Версія Jellyseerr не відповідає мінімальним вимогам! Будь ласка, оновіться принаймні до 2.0.0",
"jellyseerr_test_failed": "Тест Jellyseerr завершився невдало. Спробуйте ще раз.",
"failed_to_test_jellyseerr_server_url": "Не вдалося перевірити URL-адресу сервера jellyseerr",
"issue_submitted": "Звіт про проблему відправлено",
"requested_item": "Запитано {{item}}!",
"you_dont_have_permission_to_request": "Ви не маєте дозволу на запит медіа!",
"something_went_wrong_requesting_media": "Щось пішло не так під час запиту медіа!"
}
},
"tabs": {
"home": "Головна",
"search": "Пошук",
"library": "Медіатека",
"custom_links": "Ваші Посилання",
"favorites": "Улюблене"
}
}

View File

@@ -8,6 +8,7 @@ export enum SortByOption {
CommunityRating = "CommunityRating",
CriticRating = "CriticRating",
DateCreated = "DateCreated",
DateLastContentAdded = "DateLastContentAdded",
DatePlayed = "DatePlayed",
PlayCount = "PlayCount",
ProductionYear = "ProductionYear",
@@ -37,6 +38,7 @@ export const sortOptions: {
{ key: SortByOption.CommunityRating, value: "Community Rating" },
{ key: SortByOption.CriticRating, value: "Critics Rating" },
{ key: SortByOption.DateCreated, value: "Date Added" },
{ key: SortByOption.DateLastContentAdded, value: "Date Episode Added" },
{ key: SortByOption.DatePlayed, value: "Date Played" },
{ key: SortByOption.PlayCount, value: "Play Count" },
{ key: SortByOption.ProductionYear, value: "Production Year" },

View File

@@ -79,7 +79,7 @@ export const generateDeviceProfile = async () => {
Type: MediaTypes.Video,
Context: "Streaming",
Protocol: "hls",
Container: "fmp4",
Container: "mp4",
VideoCodec: "h264, hevc",
AudioCodec: "aac,mp3,ac3,dts",
},