mirror of
https://github.com/movie-web/movie-web.git
synced 2025-01-14 18:29:09 +01:00
commit
1acefd75fa
2
.github/CODE_OF_CONDUCT.md
vendored
2
.github/CODE_OF_CONDUCT.md
vendored
@ -60,7 +60,7 @@ representative at an online or offline event.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
codeofconduct@movie-web.app.
|
||||
our [Discord](https://movie-web.github.io/links/discord).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
|
7
.github/CONTRIBUTING.md
vendored
7
.github/CONTRIBUTING.md
vendored
@ -1,6 +1,6 @@
|
||||
# Contributing Guidelines for movie-web
|
||||
|
||||
Thank you for investing your time in contributing to our project! Your contribution will be reflected on [movie-web.app](https://movie-web.app).
|
||||
Thank you for investing your time in contributing to our project! Your contribution will be reflected on all of the community hosted instances that are on the latest version.
|
||||
|
||||
Please read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
|
||||
|
||||
@ -33,7 +33,7 @@ There are two places where to request features or report bugs:
|
||||
### Discord Server
|
||||
If you do not have a GitHub account or want to discuss a feature or bug with us before making an issue, you can join our Discord server.
|
||||
|
||||
<a href="https://discord.movie-web.app"><img src="https://discord.com/api/guilds/871713465100816424/widget.png?style=banner2" alt="Discord Server"></a>
|
||||
<a href="https://movie-web.github.io/links/discord"><img src="https://discord.com/api/guilds/871713465100816424/widget.png?style=banner2" alt="Discord Server"></a>
|
||||
|
||||
### GitHub Issues
|
||||
To make a GitHub issue for movie-web, please visit the [new issue page](https://github.com/movie-web/movie-web/issues/new/choose) where you can pick either the "Bug Report" or "Feature Request" template.
|
||||
@ -85,7 +85,8 @@ Here are some tips to make sure that your pull requests are :pinched_fingers: fi
|
||||
### Language Contributions
|
||||
Language contributions help movie-web massively, allowing people worldwide to use our app!
|
||||
|
||||
We use weblate for crowdsourcing our translations. [Click here to go to our translation tool.](https://weblate.movie-web.app/projects/movie-web/website/)
|
||||
We use Weblate for crowdsourcing our translations. [Click here to go to our translation tool.](https://movie-web.github.io/links/weblate)
|
||||
|
||||
|
||||
1. First make sure you make an account. (click the link above)
|
||||
2. Click the language you want to help translate, if it's not listed you can click the plus top left to add a new language.
|
||||
|
9
.github/SECURITY.md
vendored
9
.github/SECURITY.md
vendored
@ -2,12 +2,9 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The movie-web maintainers only support the latest version of movie-web published at https://movie-web.app.
|
||||
|
||||
Support is not provided for any forks or mirrors of movie-web.
|
||||
The latest version of movie-web is the only version that is supported, as it is the only version that is being actively developed.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
There are two ways you can contact the movie-web maintainers to report a vulnerability:
|
||||
- Email [security@movie-web.app](mailto:security@movie-web.app)
|
||||
- Report the vulnerability in the [movie-web Discord server](https://discord.movie-web.app)
|
||||
You can contact the movie-web maintainers to report a vulnerability:
|
||||
- Report the vulnerability in the [movie-web Discord server](https://movie-web.github.io/links/discord)
|
||||
|
@ -8,6 +8,10 @@ COPY package.json ./
|
||||
COPY pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
ARG PWA_ENABLED="false"
|
||||
|
||||
ENV VITE_PWA_ENABLED=${PWA_ENABLED}
|
||||
|
||||
COPY . ./
|
||||
RUN pnpm run build
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
<p align="center">
|
||||
<img src="https://skillicons.dev/icons?i=react,vite,ts" />
|
||||
<br/>
|
||||
<a href="https://discord.movie-web.app"><kbd>🔵 discord</kbd></a> <a href="https://movie-web.app"><kbd>🟢 website</kbd></a>
|
||||
<a href="https://movie-web.github.io/links/discord"><kbd>🔵 discord</kbd></a> <a href="https://movie-web.github.io/docs"><kbd>🟢 docs</kbd></a>
|
||||
</p>
|
||||
<br/><br/>
|
||||
|
||||
# ⚡What is movie-web?
|
||||
|
||||
movie-web is a web app for watching movies easily. Check it out at <a href="https://movie-web.app"><kbd>movie-web.app</kbd></a>.
|
||||
movie-web is a web app for watching movies easily.
|
||||
|
||||
This service works by displaying video files from third-party providers inside an intuitive and aesthetic user interface.
|
||||
|
||||
@ -57,7 +57,7 @@ pnpm build
|
||||
|
||||
A simple guide has been written to assist in hosting your own instance of movie-web. Check it out below
|
||||
|
||||
|[Selfhosting guide](https://docs.movie-web.app)|
|
||||
|[Selfhosting guide](https://movie-web.github.io/docs)|
|
||||
|---|
|
||||
|
||||
## 🤝 Thanks to all Contributors
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "movie-web",
|
||||
"version": "4.4.2",
|
||||
"version": "4.5.0",
|
||||
"private": true,
|
||||
"homepage": "https://movie-web.app",
|
||||
"homepage": "https://github.com/movie-web/movie-web",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
@ -123,7 +123,8 @@
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-plugin-pwa": "^0.17.4",
|
||||
"vite-plugin-static-copy": "^1.0.0",
|
||||
"vitest": "^1.1.0"
|
||||
"vitest": "^1.1.0",
|
||||
"workbox-window": "^7.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
|
53
pnpm-lock.yaml
generated
53
pnpm-lock.yaml
generated
@ -268,7 +268,7 @@ devDependencies:
|
||||
version: 0.5.9(prettier@3.1.1)
|
||||
rollup-plugin-visualizer:
|
||||
specifier: ^5.11.0
|
||||
version: 5.11.0(@rollup/wasm-node@4.10.0)
|
||||
version: 5.11.0(@rollup/wasm-node@4.12.0)
|
||||
tailwind-scrollbar:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5(tailwindcss@3.4.0)
|
||||
@ -302,6 +302,9 @@ devDependencies:
|
||||
vitest:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@types/node@20.10.5)(jsdom@23.0.1)
|
||||
workbox-window:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
|
||||
packages:
|
||||
|
||||
@ -2059,7 +2062,7 @@ packages:
|
||||
engines: {node: '>=14.0.0'}
|
||||
dev: false
|
||||
|
||||
/@rollup/plugin-babel@5.3.1(@babel/core@7.23.6)(@rollup/wasm-node@4.10.0):
|
||||
/@rollup/plugin-babel@5.3.1(@babel/core@7.23.6)(@rollup/wasm-node@4.12.0):
|
||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
peerDependencies:
|
||||
@ -2072,36 +2075,36 @@ packages:
|
||||
dependencies:
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-module-imports': 7.22.15
|
||||
'@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.10.0)
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
'@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.12.0)
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-node-resolve@11.2.1(@rollup/wasm-node@4.10.0):
|
||||
/@rollup/plugin-node-resolve@11.2.1(@rollup/wasm-node@4.12.0):
|
||||
resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
peerDependencies:
|
||||
rollup: npm:@rollup/wasm-node
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.10.0)
|
||||
'@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.12.0)
|
||||
'@types/resolve': 1.17.1
|
||||
builtin-modules: 3.3.0
|
||||
deepmerge: 4.3.1
|
||||
is-module: 1.0.0
|
||||
resolve: 1.22.4
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-replace@2.4.2(@rollup/wasm-node@4.10.0):
|
||||
/@rollup/plugin-replace@2.4.2(@rollup/wasm-node@4.12.0):
|
||||
resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==}
|
||||
peerDependencies:
|
||||
rollup: npm:@rollup/wasm-node
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.10.0)
|
||||
'@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.12.0)
|
||||
magic-string: 0.25.9
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@3.1.0(@rollup/wasm-node@4.10.0):
|
||||
/@rollup/pluginutils@3.1.0(@rollup/wasm-node@4.12.0):
|
||||
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
peerDependencies:
|
||||
@ -2110,11 +2113,11 @@ packages:
|
||||
'@types/estree': 0.0.39
|
||||
estree-walker: 1.0.1
|
||||
picomatch: 2.3.1
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
dev: true
|
||||
|
||||
/@rollup/wasm-node@4.10.0:
|
||||
resolution: {integrity: sha512-wH/ih4T/iP2PUyTrkyioZqDoFY/gmu63LPLTOM5Q21gSB/D3Ejw3UBpUOMLt86fIbN3mV+wL45MyA71XAj1ytg==}
|
||||
/@rollup/wasm-node@4.12.0:
|
||||
resolution: {integrity: sha512-sqy3+YvV/uWX6bPZOR5PlEdH6xyMPXoelllRQ/uZ13tzy9f4pXZTbajnoWN8IHHXwTNKPiLzsePLiDEVmkxMNw==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@ -5098,7 +5101,7 @@ packages:
|
||||
'@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.6)
|
||||
'@babel/types': 7.23.6
|
||||
kleur: 4.1.5
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
unplugin: 1.5.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@ -6026,7 +6029,7 @@ packages:
|
||||
glob: 7.2.3
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-terser@7.0.2(@rollup/wasm-node@4.10.0):
|
||||
/rollup-plugin-terser@7.0.2(@rollup/wasm-node@4.12.0):
|
||||
resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
|
||||
deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser
|
||||
peerDependencies:
|
||||
@ -6034,12 +6037,12 @@ packages:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.23.5
|
||||
jest-worker: 26.6.2
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
serialize-javascript: 4.0.0
|
||||
terser: 5.19.3
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-visualizer@5.11.0(@rollup/wasm-node@4.10.0):
|
||||
/rollup-plugin-visualizer@5.11.0(@rollup/wasm-node@4.12.0):
|
||||
resolution: {integrity: sha512-exM0Ms2SN3AgTzMeW7y46neZQcyLY7eKwWAop1ZoRTCZwyrIRdMMJ6JjToAJbML77X/9N8ZEpmXG4Z/Clb9k8g==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
@ -6051,7 +6054,7 @@ packages:
|
||||
dependencies:
|
||||
open: 8.4.2
|
||||
picomatch: 2.3.1
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
source-map: 0.7.4
|
||||
yargs: 17.7.2
|
||||
dev: true
|
||||
@ -7037,7 +7040,7 @@ packages:
|
||||
'@types/node': 20.10.5
|
||||
esbuild: 0.19.10
|
||||
postcss: 8.4.32
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
@ -7299,9 +7302,9 @@ packages:
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/preset-env': 7.23.6(@babel/core@7.23.6)
|
||||
'@babel/runtime': 7.23.6
|
||||
'@rollup/plugin-babel': 5.3.1(@babel/core@7.23.6)(@rollup/wasm-node@4.10.0)
|
||||
'@rollup/plugin-node-resolve': 11.2.1(@rollup/wasm-node@4.10.0)
|
||||
'@rollup/plugin-replace': 2.4.2(@rollup/wasm-node@4.10.0)
|
||||
'@rollup/plugin-babel': 5.3.1(@babel/core@7.23.6)(@rollup/wasm-node@4.12.0)
|
||||
'@rollup/plugin-node-resolve': 11.2.1(@rollup/wasm-node@4.12.0)
|
||||
'@rollup/plugin-replace': 2.4.2(@rollup/wasm-node@4.12.0)
|
||||
'@surma/rollup-plugin-off-main-thread': 2.2.3
|
||||
ajv: 8.12.0
|
||||
common-tags: 1.8.2
|
||||
@ -7310,8 +7313,8 @@ packages:
|
||||
glob: 7.2.3
|
||||
lodash: 4.17.21
|
||||
pretty-bytes: 5.6.0
|
||||
rollup: /@rollup/wasm-node@4.10.0
|
||||
rollup-plugin-terser: 7.0.2(@rollup/wasm-node@4.10.0)
|
||||
rollup: /@rollup/wasm-node@4.12.0
|
||||
rollup-plugin-terser: 7.0.2(@rollup/wasm-node@4.12.0)
|
||||
source-map: 0.8.0-beta.0
|
||||
stringify-object: 3.3.0
|
||||
strip-comments: 2.0.1
|
||||
|
@ -1,6 +1,7 @@
|
||||
window.__CONFIG__ = {
|
||||
// The URL for the CORS proxy, the URL must NOT end with a slash!
|
||||
VITE_CORS_PROXY_URL: "CHANGEME",
|
||||
// If not specified, the onboarding will not allow a "default setup". The user will have to use the extension or set up a proxy themselves
|
||||
VITE_CORS_PROXY_URL: "",
|
||||
|
||||
// The READ API key to access TMDB
|
||||
VITE_TMDB_READ_API_KEY: "CHANGEME",
|
||||
@ -11,7 +12,7 @@ window.__CONFIG__ = {
|
||||
// Whether to disable hash-based routing, leave this as false if you don't know what this is
|
||||
VITE_NORMAL_ROUTER: false,
|
||||
|
||||
// The backend URL to communicate with, defaults to the movie-web hosted one at backend.movie-web.app
|
||||
// The backend URL to communicate with
|
||||
VITE_BACKEND_URL: null,
|
||||
|
||||
// A comma separated list of disallowed IDs in the case of a DMCA claim - in the format "series-<id>" and "movie-<id>"
|
||||
|
@ -116,27 +116,24 @@
|
||||
"failed": "تعذر العثور على الوسائط، حاول مجددا!",
|
||||
"loading": "جار التحميل...",
|
||||
"noResults": "لم نتمكن من العثور على أي شيء!",
|
||||
"placeholder": "ماذا تريد أن تشاهد؟",
|
||||
"placeholder": {
|
||||
"default": "ماذا تريد أن تشاهد؟",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "نتائج البحث"
|
||||
},
|
||||
"titles": {
|
||||
"day": {
|
||||
"default": "ماذا تريد أن تشاهد في هذه الظهيرة؟",
|
||||
"extra": [
|
||||
"متشوق للمغامرة؟ قد يكون Jurassic Park خيارًا مثاليًا لك."
|
||||
]
|
||||
"extra": ["متشوق للمغامرة؟ قد يكون Jurassic Park خيارًا مثاليًا لك."]
|
||||
},
|
||||
"morning": {
|
||||
"default": "ماذا تريد أن تشاهد في هذا الصباح؟",
|
||||
"extra": [
|
||||
"سمعت أن فلم \"Before Sunrise\" جيد"
|
||||
]
|
||||
"extra": ["سمعت أن فلم \"Before Sunrise\" جيد"]
|
||||
},
|
||||
"night": {
|
||||
"default": "ماذا تريد أن تشاهد في هذه الليلة؟",
|
||||
"extra": [
|
||||
"مُرهَق؟ سمعت أن فيلم \"The Exorcist\" جيد."
|
||||
]
|
||||
"extra": ["مُرهَق؟ سمعت أن فيلم \"The Exorcist\" جيد."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "Неуспешно намиране на медия, опитайте отново!",
|
||||
"loading": "Зареждане...",
|
||||
"noResults": "Не успяхме да намерим нищо!",
|
||||
"placeholder": "Какво искате да гледате?",
|
||||
"placeholder": {
|
||||
"default": "Какво искате да гледате?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Резултати от търсенето"
|
||||
},
|
||||
"titles": {
|
||||
@ -127,15 +130,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Какво бихте искали да гледате тази сутрин?",
|
||||
"extra": [
|
||||
"Чух, че Before Sunrise е добър"
|
||||
]
|
||||
"extra": ["Чух, че Before Sunrise е добър"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Какво бихте искали да гледате тази вечер?",
|
||||
"extra": [
|
||||
"Изморен? Чух, че Екзорсистът е добър."
|
||||
]
|
||||
"extra": ["Изморен? Чух, че Екзорсистът е добър."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,27 +115,24 @@
|
||||
"failed": "মিডিয়া খুঁজে পেতে ব্যর্থ, আবার চেষ্টা করুন!",
|
||||
"loading": "লোড হচ্ছে..।",
|
||||
"noResults": "আমরা কিছুই খুঁজে পাইনি!",
|
||||
"placeholder": "আপনি কি দেখতে চান?",
|
||||
"placeholder": {
|
||||
"default": "আপনি কি দেখতে চান?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "অনুসন্ধান ফলাফল"
|
||||
},
|
||||
"titles": {
|
||||
"day": {
|
||||
"default": "আপনি এই বিকেলে কি দেখতে চান?",
|
||||
"extra": [
|
||||
"দুঃসাহসিক বোধ করছেন? জুরাসিক পার্ক নিখুঁত পছন্দ হতে পারে।"
|
||||
]
|
||||
"extra": ["দুঃসাহসিক বোধ করছেন? জুরাসিক পার্ক নিখুঁত পছন্দ হতে পারে।"]
|
||||
},
|
||||
"morning": {
|
||||
"default": "আপনি এই সকালে কি দেখতে চান?",
|
||||
"extra": [
|
||||
"শুনি সূর্যোদয়ের আগে ভালো"
|
||||
]
|
||||
"extra": ["শুনি সূর্যোদয়ের আগে ভালো"]
|
||||
},
|
||||
"night": {
|
||||
"default": "আপনি আজ রাতে কি দেখতে চান?",
|
||||
"extra": [
|
||||
"ক্লান্ত? আমি শুনেছি দ্য এক্সরসিস্ট ভাল।"
|
||||
]
|
||||
"extra": ["ক্লান্ত? আমি শুনেছি দ্য এক্সরসিস্ট ভাল।"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "No s'ha pogut trobar cap contingut, torneu-ho a provar!",
|
||||
"loading": "S'està carregant…",
|
||||
"noResults": "No hem pogut trobar res!",
|
||||
"placeholder": "Què voleu mirar?",
|
||||
"placeholder": {
|
||||
"default": "Què voleu mirar?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Resultats de la cerca"
|
||||
},
|
||||
"titles": {
|
||||
@ -127,15 +130,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Què us agradaria mirar aquest matí?",
|
||||
"extra": [
|
||||
"He sentit que «Abans de l'alba» és bona"
|
||||
]
|
||||
"extra": ["He sentit que «Abans de l'alba» és bona"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Què us agradaria mirar aquesta nit?",
|
||||
"extra": [
|
||||
"Esteu cansat? He sentit que «L'exorcista» és bona."
|
||||
]
|
||||
"extra": ["Esteu cansat? He sentit que «L'exorcista» és bona."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,27 +116,24 @@
|
||||
"failed": "Nepodařilo se najít média, zkuste to znovu!",
|
||||
"loading": "Načítání...",
|
||||
"noResults": "Nemohli jsme nic najít!",
|
||||
"placeholder": "Co si přejete sledovat?",
|
||||
"placeholder": {
|
||||
"default": "Co si přejete sledovat?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Výsledky vyhledávání"
|
||||
},
|
||||
"titles": {
|
||||
"day": {
|
||||
"default": "Na co byste se chtěli dnes odpoledne dívat?",
|
||||
"extra": [
|
||||
"Chceš zažít dobrodružství? Jurský Park je pro tebe."
|
||||
]
|
||||
"extra": ["Chceš zažít dobrodružství? Jurský Park je pro tebe."]
|
||||
},
|
||||
"morning": {
|
||||
"default": "Na co byste se chtěli dnes ráno dívat?",
|
||||
"extra": [
|
||||
"Slyšel jsem, že Před úsvitem je super."
|
||||
]
|
||||
"extra": ["Slyšel jsem, že Před úsvitem je super."]
|
||||
},
|
||||
"night": {
|
||||
"default": "Na co byste se chtěli dnes večer dívat?",
|
||||
"extra": [
|
||||
"Unaven? Slyšel jsem, že Vymítač ďábla je super."
|
||||
]
|
||||
"extra": ["Unaven? Slyšel jsem, že Vymítač ďábla je super."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Das Medium wurde nicht gefunden, bitte versuchen Sie es erneut!",
|
||||
"loading": "Wird geladen...",
|
||||
"noResults": "Wir haben nichts gefunden!",
|
||||
"placeholder": "Was möchtest du schauen?",
|
||||
"placeholder": {
|
||||
"default": "Was möchtest du schauen?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Suchergebnisse"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Was würdest du diesen Morgen gerne schauen?",
|
||||
"extra": [
|
||||
"Before Sunrise soll gut sein"
|
||||
]
|
||||
"extra": ["Before Sunrise soll gut sein"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Was möchtest du diesen Abend gerne schauen?",
|
||||
"extra": [
|
||||
"Müde? Ich hab gehört The Exorcist soll gut sein."
|
||||
]
|
||||
"extra": ["Müde? Ich hab gehört The Exorcist soll gut sein."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "Απέτυχε η εύρεση πολυμέσων, δοκιμάστε ξανά!",
|
||||
"loading": "Φόρτωση...",
|
||||
"noResults": "Δεν μπορέσαμε να βρούμε τίποτα!",
|
||||
"placeholder": "Τι θέλετε να παρακολουθήσετε;",
|
||||
"placeholder": {
|
||||
"default": "Τι θέλετε να παρακολουθήσετε;",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Αποτελέσματα αναζήτησης"
|
||||
},
|
||||
"titles": {
|
||||
@ -127,15 +130,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Τι θα θέλατε να παρακολουθήσετε σήμερα το πρωί;",
|
||||
"extra": [
|
||||
"Έχω ακούσει ότι το Before Sunrise είναι καλό"
|
||||
]
|
||||
"extra": ["Έχω ακούσει ότι το Before Sunrise είναι καλό"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Τι θα θέλατε να παρακολουθήσετε απόψε;",
|
||||
"extra": [
|
||||
"Κούραση; Έχω ακούσει ότι ο Εξορκιστής είναι καλός."
|
||||
]
|
||||
"extra": ["Κούραση; Έχω ακούσει ότι ο Εξορκιστής είναι καλός."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -55,6 +55,8 @@
|
||||
"text": "Did you configure it correctly?",
|
||||
"title": "Failed to reach server"
|
||||
},
|
||||
"noHostTitle": "Server not configured!",
|
||||
"noHost": "The server has not been configured, therefore you cannot create an account",
|
||||
"host": "You are connecting to <0>{{hostname}}</0> - please confirm you trust it before making an account",
|
||||
"no": "Go back",
|
||||
"title": "Do you trust this server?",
|
||||
@ -116,7 +118,15 @@
|
||||
"failed": "Failed to find media, try again!",
|
||||
"loading": "Loading...",
|
||||
"noResults": "We couldn't find anything!",
|
||||
"placeholder": "What do you want to watch?",
|
||||
"placeholder": {
|
||||
"default": "What do you want to watch?",
|
||||
"extra": [
|
||||
"What do you want to explore?",
|
||||
"What's on your watchlist?",
|
||||
"What's your favorite movie?",
|
||||
"What's your favorite series?"
|
||||
]
|
||||
},
|
||||
"sectionTitle": "Search results"
|
||||
},
|
||||
"titles": {
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "¡Error al encontrar contenido, inténtalo de nuevo!",
|
||||
"loading": "Cargando...",
|
||||
"noResults": "¡No pudimos encontrar nada!",
|
||||
"placeholder": "¿Qué te gustaría ver?",
|
||||
"placeholder": {
|
||||
"default": "¿Qué te gustaría ver?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Resultados de búsqueda"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "¿Qué te gustaría ver esta mañana?",
|
||||
"extra": [
|
||||
"Escuché que “Antes del amanecer” es buena"
|
||||
]
|
||||
"extra": ["Escuché que “Antes del amanecer” es buena"]
|
||||
},
|
||||
"night": {
|
||||
"default": "¿Qué te gustaría ver esta noche?",
|
||||
"extra": [
|
||||
"¿Cansado? Escuché que “El Exorcista” es buena."
|
||||
]
|
||||
"extra": ["¿Cansado? Escuché que “El Exorcista” es buena."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Meedia leidmine ebaõnnestus, proovige uuesti!",
|
||||
"loading": "Laadimine....",
|
||||
"noResults": "Me ei leidnud midagi!",
|
||||
"placeholder": "Mida tahate vaadata?",
|
||||
"placeholder": {
|
||||
"default": "Mida tahate vaadata?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Otsingutulemused"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Mida te soovite täna hommikul vaadata?",
|
||||
"extra": [
|
||||
"Ma kuulsin, et Before Sunrise on hea"
|
||||
]
|
||||
"extra": ["Ma kuulsin, et Before Sunrise on hea"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Mida te soovite täna õhtul vaadata?",
|
||||
"extra": [
|
||||
"Väsinud? Olen kuulnud, et The Exorcist on hea."
|
||||
]
|
||||
"extra": ["Väsinud? Olen kuulnud, et The Exorcist on hea."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "چیزی پیدا نشد، دوباره تلاش کنید!",
|
||||
"loading": "در حال بارگذاری...",
|
||||
"noResults": "چیزی پیدا نکردیم!",
|
||||
"placeholder": "چه میخواهید تماشا کنید؟",
|
||||
"placeholder": {
|
||||
"default": "چه میخواهید تماشا کنید؟",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "نتایج جستجو"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "دوست دارید امروز صبح چه چیزی تماشا کنید؟",
|
||||
"extra": [
|
||||
"شنیدم فیلم \"پیش از طلوع\" عالیه"
|
||||
]
|
||||
"extra": ["شنیدم فیلم \"پیش از طلوع\" عالیه"]
|
||||
},
|
||||
"night": {
|
||||
"default": "دوست دارید امشب چه چیزی تماشا کنید؟",
|
||||
"extra": [
|
||||
"خسته اید؟ شنیده ام که \"جن گیر\" فیلم خوبی است."
|
||||
]
|
||||
"extra": ["خسته اید؟ شنیده ام که \"جن گیر\" فیلم خوبی است."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Mediaa ei löytynyt, yritä uudelleen!",
|
||||
"loading": "Ladataan...",
|
||||
"noResults": "Emme löytäneet mitään!",
|
||||
"placeholder": "Mitä haluat katsoa?",
|
||||
"placeholder": {
|
||||
"default": "Mitä haluat katsoa?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Hakutulokset"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Mitä haluaisit katsoa tänä aamuna?",
|
||||
"extra": [
|
||||
"Kuulen, että Rakkautta ennen aamua (Before Sunrise) on hyvä"
|
||||
]
|
||||
"extra": ["Kuulen, että Rakkautta ennen aamua (Before Sunrise) on hyvä"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Mitä haluaisit katsoa tänä iltana?",
|
||||
"extra": [
|
||||
"Väsynyt? Kuulin, että Manaaja (The Exorcist) on hyvä."
|
||||
]
|
||||
"extra": ["Väsynyt? Kuulin, että Manaaja (The Exorcist) on hyvä."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Le média n'a pas été trouvé, veuillez réessayez !",
|
||||
"loading": "Chargement...",
|
||||
"noResults": "Nous n'avons rien trouvé !",
|
||||
"placeholder": "Que voulez-vous voir ?",
|
||||
"placeholder": {
|
||||
"default": "Que voulez-vous voir ?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Résultats de la recherche"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,9 +131,7 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Que voulez-vous regarder ce matin ?",
|
||||
"extra": [
|
||||
"Les films, c'est comme les voyages : ça nous ouvre l'esprit"
|
||||
]
|
||||
"extra": ["Les films, c'est comme les voyages : ça nous ouvre l'esprit"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Que voulez-vous regarder ce soir ?",
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Error ao encontrar contido... intentao de novo!",
|
||||
"loading": "Cargando...",
|
||||
"noResults": "Non atopamos nada!",
|
||||
"placeholder": "Que che gustaría ver?",
|
||||
"placeholder": {
|
||||
"default": "Que che gustaría ver?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Resultados da busca"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Que che gustaría ver esta mañá?",
|
||||
"extra": [
|
||||
"Escoitei que “Antes del amanecer” é boa"
|
||||
]
|
||||
"extra": ["Escoitei que “Antes del amanecer” é boa"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Que che gustaría ver esta noite?",
|
||||
"extra": [
|
||||
"Canso? Escoitei que “El Exorcista” é boa."
|
||||
]
|
||||
"extra": ["Canso? Escoitei que “El Exorcista” é boa."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,21 +116,20 @@
|
||||
"failed": "મીડિયા શોધવામાં નિષ્ફળ, ફરી પ્રયાસ કરો!",
|
||||
"loading": "લોડ થાય છે...",
|
||||
"noResults": "અમે કંઈપણ શોધી શક્યા નથી!",
|
||||
"placeholder": "તમે શું જોવા માંગો છો?",
|
||||
"placeholder": {
|
||||
"default": "તમે શું જોવા માંગો છો?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "શોધ પરિણામો"
|
||||
},
|
||||
"titles": {
|
||||
"day": {
|
||||
"default": "તમે આ બપોરે શું જોવા માંગો છો?",
|
||||
"extra": [
|
||||
"સાહસિક લાગે છે? જુરાસિક પાર્ક યોગ્ય પસંદગી હોઈ શકે છે."
|
||||
]
|
||||
"extra": ["સાહસિક લાગે છે? જુરાસિક પાર્ક યોગ્ય પસંદગી હોઈ શકે છે."]
|
||||
},
|
||||
"morning": {
|
||||
"default": "તમે આ સવારે શું જોવા માંગો છો?",
|
||||
"extra": [
|
||||
"હું સાંભળું છું કે Before Sunrise સારું છે"
|
||||
]
|
||||
"extra": ["હું સાંભળું છું કે Before Sunrise સારું છે"]
|
||||
},
|
||||
"night": {
|
||||
"default": "તમે આજે રાત્રે શું જોવા માંગો છો?",
|
||||
|
@ -116,27 +116,24 @@
|
||||
"failed": "לא הצלחנו למצוא מדיה, נסה שוב!",
|
||||
"loading": "טוען...",
|
||||
"noResults": "לא יכולנו למצוא כלום!",
|
||||
"placeholder": "במה תרצה לצפות?",
|
||||
"placeholder": {
|
||||
"default": "במה תרצה לצפות?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "תוצאות חיפוש"
|
||||
},
|
||||
"titles": {
|
||||
"day": {
|
||||
"default": "במה תרצה לצפות באחר צהריים זה?",
|
||||
"extra": [
|
||||
"מרגיש הרפתקני? פארק היורה עשוי להיות הבחירה המושלמת."
|
||||
]
|
||||
"extra": ["מרגיש הרפתקני? פארק היורה עשוי להיות הבחירה המושלמת."]
|
||||
},
|
||||
"morning": {
|
||||
"default": "במה תרצה לצפות הבוקר?",
|
||||
"extra": [
|
||||
"שמעתי שלפני הזריחה זה סרט טוב"
|
||||
]
|
||||
"extra": ["שמעתי שלפני הזריחה זה סרט טוב"]
|
||||
},
|
||||
"night": {
|
||||
"default": "במה תרצה לצפות הלילה?",
|
||||
"extra": [
|
||||
"רוצה לישון? הפיג'מות היא בחירה מצויינת."
|
||||
]
|
||||
"extra": ["רוצה לישון? הפיג'מות היא בחירה מצויינת."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "मीडिया ढूंढने में विफल, पुनः प्रयास करें!",
|
||||
"loading": "लोड हो रहा है..।",
|
||||
"noResults": "हमें कुछ नहीं मिला!",
|
||||
"placeholder": "क्या देखना चाहते हो?",
|
||||
"placeholder": {
|
||||
"default": "क्या देखना चाहते हो?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "खोज के परिणाम"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "आप आज सुबह को क्या देखना चाहेंगे?",
|
||||
"extra": [
|
||||
"मैंने सुना है सूर्योदय से पहले ठीक है"
|
||||
]
|
||||
"extra": ["मैंने सुना है सूर्योदय से पहले ठीक है"]
|
||||
},
|
||||
"night": {
|
||||
"default": "आप आज रात को क्या देखना चाहेंगे?",
|
||||
"extra": [
|
||||
"थके हुए हो? मैंने सुना एक्सोरसिस्ट अच्छी मूवी है।"
|
||||
]
|
||||
"extra": ["थके हुए हो? मैंने सुना एक्सोरसिस्ट अच्छी मूवी है।"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "Gagal menemukan media, coba lagi!",
|
||||
"loading": "Memuat...",
|
||||
"noResults": "Kami tidak dapat menemukan apapun!",
|
||||
"placeholder": "Apa yang ingin anda tonton?",
|
||||
"placeholder": {
|
||||
"default": "Apa yang ingin anda tonton?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Hasil pencarian"
|
||||
},
|
||||
"titles": {
|
||||
@ -127,15 +130,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Apa yang ingin anda tonton pagi ini?",
|
||||
"extra": [
|
||||
"Kayaknya film Before Sunrise bagus deh"
|
||||
]
|
||||
"extra": ["Kayaknya film Before Sunrise bagus deh"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Apa yang ingin anda tonton malam ini?",
|
||||
"extra": [
|
||||
"Capek? Katanya The Exocist rekomended."
|
||||
]
|
||||
"extra": ["Capek? Katanya The Exocist rekomended."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -108,7 +108,10 @@
|
||||
"failed": "Mostókst að finna miðil, reyndu aftur!",
|
||||
"loading": "Hlaðið...",
|
||||
"noResults": "Við gátum ekki fundið neitt!",
|
||||
"placeholder": "Hvað viltu horfa á?",
|
||||
"placeholder": {
|
||||
"default": "Hvað viltu horfa á?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Leitar niðurstöður"
|
||||
},
|
||||
"titles": {
|
||||
@ -120,15 +123,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Hvað myndirðu vilja horfa á þessum morgni?",
|
||||
"extra": [
|
||||
"Ég heyrði að Before Sunrise sé góð"
|
||||
]
|
||||
"extra": ["Ég heyrði að Before Sunrise sé góð"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Hvað myndirðu vilja horfa á í nótt?",
|
||||
"extra": [
|
||||
"Þreytt? Ég heyrði að The Exorcist sé góð."
|
||||
]
|
||||
"extra": ["Þreytt? Ég heyrði að The Exorcist sé góð."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Impossibile trovare i media, riprova!",
|
||||
"loading": "Caricamento...",
|
||||
"noResults": "Non abbiamo trovato nulla!",
|
||||
"placeholder": "Cosa vuoi guardare?",
|
||||
"placeholder": {
|
||||
"default": "Cosa vuoi guardare?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Risultati della ricerca"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Cosa vorresti guardare questa mattina?",
|
||||
"extra": [
|
||||
"Ho sentito che «Prima Dell'alba» è buono"
|
||||
]
|
||||
"extra": ["Ho sentito che «Prima Dell'alba» è buono"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Cosa vorresti guardare questa sera?",
|
||||
"extra": [
|
||||
"Stanco? Ho sentito che L'esorciccio è buono."
|
||||
]
|
||||
"extra": ["Stanco? Ho sentito che L'esorciccio è buono."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -76,7 +76,10 @@
|
||||
"allResults": "それがすべてです!",
|
||||
"loading": "読み込み中...",
|
||||
"noResults": "見つかりませんでした!",
|
||||
"placeholder": "どんな映画を見たい?",
|
||||
"placeholder": {
|
||||
"default": "どんな映画を見たい?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "検索結果"
|
||||
},
|
||||
"titles": {
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "미디어 검색에 실패하였습니다, 다시 시도해주세요!",
|
||||
"loading": "로딩...",
|
||||
"noResults": "검색결과가 없습니다!",
|
||||
"placeholder": "무엇을 보고 싶으신가요?",
|
||||
"placeholder": {
|
||||
"default": "무엇을 보고 싶으신가요?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "검색 결과"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "오늘 아침에 무엇을 보고 싶으신가요?",
|
||||
"extra": [
|
||||
"Before Sunrise가 좋다고 들었어요"
|
||||
]
|
||||
"extra": ["Before Sunrise가 좋다고 들었어요"]
|
||||
},
|
||||
"night": {
|
||||
"default": "오늘 밤에 무엇을 보고 싶으신가요?",
|
||||
"extra": [
|
||||
"피곤하신가요? The Exorcist가 좋다고 들었어요."
|
||||
]
|
||||
"extra": ["피곤하신가요? The Exorcist가 좋다고 들었어요."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -114,7 +114,10 @@
|
||||
"failed": "Neizdevās atrast multividi. Mēģiniet vēlreiz!",
|
||||
"loading": "Lādejas...",
|
||||
"noResults": "Mēs nevarējām neko atrast!",
|
||||
"placeholder": "Ko tu gribi skatīties?",
|
||||
"placeholder": {
|
||||
"default": "Ko tu gribi skatīties?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Meklējuma rezultāti"
|
||||
},
|
||||
"titles": {
|
||||
@ -123,15 +126,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Ko tu gribētu šorīt noskatīties?",
|
||||
"extra": [
|
||||
"Es dzirdu, ka Pirms saullēkta ir labs"
|
||||
]
|
||||
"extra": ["Es dzirdu, ka Pirms saullēkta ir labs"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Ko tu gribētu šovakar skatīties?",
|
||||
"extra": [
|
||||
"Noguris? Es dzirdu, ka Exorcist ir labs."
|
||||
]
|
||||
"extra": ["Noguris? Es dzirdu, ka Exorcist ir labs."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "Failed to banana banana, try again!",
|
||||
"loading": "Loading...",
|
||||
"noResults": "We couldn't banana anything!",
|
||||
"placeholder": "Banana do you want to banana?",
|
||||
"placeholder": {
|
||||
"default": "Banana do you want to banana?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Banana results"
|
||||
},
|
||||
"titles": {
|
||||
@ -127,15 +130,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "What would you like to banana this banana?",
|
||||
"extra": [
|
||||
"Banana! I hear Banana Sunrise is banana"
|
||||
]
|
||||
"extra": ["Banana! I hear Banana Sunrise is banana"]
|
||||
},
|
||||
"night": {
|
||||
"default": "What would you like to banana banana?",
|
||||
"extra": [
|
||||
"Banana? I hear The Banana is banana."
|
||||
]
|
||||
"extra": ["Banana? I hear The Banana is banana."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "मिडिया फेला पार्न असफल भयो, फेरि प्रयास गर्नुहोस्!",
|
||||
"loading": "लोड गर्दै...",
|
||||
"noResults": "हामीले केहि फेला पार्न सकेनौं!",
|
||||
"placeholder": "तपाईं के हेर्न चाहनुहुन्छ?",
|
||||
"placeholder": {
|
||||
"default": "तपाईं के हेर्न चाहनुहुन्छ?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "खोज परिणामहरू"
|
||||
},
|
||||
"titles": {
|
||||
@ -127,15 +130,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "तपाई आज बिहान के हेर्न चाहनुहुन्छ?",
|
||||
"extra": [
|
||||
"Before Sunrise राम्रो छ भन्ने सुन्छु"
|
||||
]
|
||||
"extra": ["Before Sunrise राम्रो छ भन्ने सुन्छु"]
|
||||
},
|
||||
"night": {
|
||||
"default": "तपाईं आज राती के हेर्न चाहनुहुन्छ?",
|
||||
"extra": [
|
||||
"थकित? मैले सुनेको छु The Exorcist राम्रो छ।"
|
||||
]
|
||||
"extra": ["थकित? मैले सुनेको छु The Exorcist राम्रो छ।"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Het is niet gelukt de media te laden, probeer het nog eens!",
|
||||
"loading": "Aan het zoeken...",
|
||||
"noResults": "We konden helaas niets vinden!",
|
||||
"placeholder": "Wat wil je graag kijken?",
|
||||
"placeholder": {
|
||||
"default": "Wat wil je graag kijken?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Zoekresultaten"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Waar wil je deze ochtend naar kijken?",
|
||||
"extra": [
|
||||
"Ik hoor dat Before Sunrise goed is"
|
||||
]
|
||||
"extra": ["Ik hoor dat Before Sunrise goed is"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Wat wil je vanavond bekijken?",
|
||||
"extra": [
|
||||
"Moe? Ik hoor dat The Exorcist goed is."
|
||||
]
|
||||
"extra": ["Moe? Ik hoor dat The Exorcist goed is."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "ਮੀਡੀਆ ਲੱਭਣ ਵਿੱਚ ਅਸਫਲ, ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ!",
|
||||
"loading": "ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ...",
|
||||
"noResults": "ਅਸੀਂ ਕੁਝ ਵੀ ਨਹੀਂ ਲੱਭ ਸਕੇ!",
|
||||
"placeholder": "ਤੁਸੀਂ ਕੀ ਦੇਖਣਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"placeholder": {
|
||||
"default": "ਤੁਸੀਂ ਕੀ ਦੇਖਣਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "ਖੋਜ ਨਤੀਜੇ"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "ਤੁਸੀਂ ਅੱਜ ਸਵੇਰੇ ਕੀ ਦੇਖਣਾ ਚਾਹੋਗੇ?",
|
||||
"extra": [
|
||||
"ਮੈਂ ਸੁਣਦਾ ਹਾਂ ਕਿ ਸੂਰਜ ਚੜ੍ਹਨ ਤੋਂ ਪਹਿਲਾਂ ਚੰਗਾ ਹੁੰਦਾ ਹੈ"
|
||||
]
|
||||
"extra": ["ਮੈਂ ਸੁਣਦਾ ਹਾਂ ਕਿ ਸੂਰਜ ਚੜ੍ਹਨ ਤੋਂ ਪਹਿਲਾਂ ਚੰਗਾ ਹੁੰਦਾ ਹੈ"]
|
||||
},
|
||||
"night": {
|
||||
"default": "ਤੁਸੀਂ ਅੱਜ ਰਾਤ ਕੀ ਦੇਖਣਾ ਚਾਹੋਗੇ?",
|
||||
"extra": [
|
||||
"ਥੱਕ ਗਏ? ਮੈਂ ਸੁਣਿਆ ਹੈ ਕਿ Exorcist ਚੰਗਾ ਹੈ."
|
||||
]
|
||||
"extra": ["ਥੱਕ ਗਏ? ਮੈਂ ਸੁਣਿਆ ਹੈ ਕਿ Exorcist ਚੰਗਾ ਹੈ."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Arrrr failed to find media, try again!",
|
||||
"loading": "Hold yer horses, me heartie!",
|
||||
"noResults": "We couldn't find anythin', arrr!",
|
||||
"placeholder": "What do ye want to watch?",
|
||||
"placeholder": {
|
||||
"default": "What do ye want to watch?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Searchin' results"
|
||||
},
|
||||
"titles": {
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Nie udało się znaleźć mediów, Spróbuj ponownie!",
|
||||
"loading": "Wczytywanie...",
|
||||
"noResults": "Nie mogliśmy niczego znaleźć!",
|
||||
"placeholder": "Co chciałbyś obejrzeć?",
|
||||
"placeholder": {
|
||||
"default": "Co chciałbyś obejrzeć?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Wyniki wyszukiwania"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Co chciałbyś obejrzeć dziś rano?",
|
||||
"extra": [
|
||||
"Słyszałem że „Przed wschodem słońca” jest dobre"
|
||||
]
|
||||
"extra": ["Słyszałem że „Przed wschodem słońca” jest dobre"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Co chciałbyś obejrzeć dziś wieczorem?",
|
||||
"extra": [
|
||||
"Zmęczony? Słyszałem że „Egzorcysta” jest dobry."
|
||||
]
|
||||
"extra": ["Zmęczony? Słyszałem że „Egzorcysta” jest dobry."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Falha ao encontrar mídia, tente novamente!",
|
||||
"loading": "Carregando...",
|
||||
"noResults": "Não conseguimos encontrar nada!",
|
||||
"placeholder": "O que você quer assistir?",
|
||||
"placeholder": {
|
||||
"default": "O que você quer assistir?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Resultados da pesquisa"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "O que você gostaria de assistir esta manhã?",
|
||||
"extra": [
|
||||
"Ouvi dizer que Antes do Amanhecer é bom"
|
||||
]
|
||||
"extra": ["Ouvi dizer que Antes do Amanhecer é bom"]
|
||||
},
|
||||
"night": {
|
||||
"default": "O que você gostaria de assistir esta noite?",
|
||||
"extra": [
|
||||
"Cansado? Ouvi dizer que O Exorcista é bom."
|
||||
]
|
||||
"extra": ["Cansado? Ouvi dizer que O Exorcista é bom."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "Falha ao encontrar mídia, tente novamente!",
|
||||
"loading": "A carregar...",
|
||||
"noResults": "Não conseguimos encontrar nada!",
|
||||
"placeholder": "O que deseja assistir?",
|
||||
"placeholder": {
|
||||
"default": "O que deseja assistir?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Resultados da pesquisa"
|
||||
},
|
||||
"titles": {
|
||||
@ -127,15 +130,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "O que gostaria de assistir esta manhã?",
|
||||
"extra": [
|
||||
"Dizem que Antes do Amanhecer é bom"
|
||||
]
|
||||
"extra": ["Dizem que Antes do Amanhecer é bom"]
|
||||
},
|
||||
"night": {
|
||||
"default": "O que gostaria de assistir esta noite?",
|
||||
"extra": [
|
||||
"Cansado? Dizem que O Exorcista é bom."
|
||||
]
|
||||
"extra": ["Cansado? Dizem que O Exorcista é bom."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -114,7 +114,10 @@
|
||||
"failed": "Găsire media eșuată, încearcă din nou!",
|
||||
"loading": "Se încarcă...",
|
||||
"noResults": "Nu am putut găsi nimic!",
|
||||
"placeholder": "La ce dorești să te uiți?",
|
||||
"placeholder": {
|
||||
"default": "La ce dorești să te uiți?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Rezultate de căutare"
|
||||
},
|
||||
"titles": {
|
||||
@ -126,15 +129,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "La ce dorești să te in uiți dimineață aceasta?",
|
||||
"extra": [
|
||||
"Aud că Before Sunrise este bun"
|
||||
]
|
||||
"extra": ["Aud că Before Sunrise este bun"]
|
||||
},
|
||||
"night": {
|
||||
"default": "La ce dorești să te uiți în astă seară?",
|
||||
"extra": [
|
||||
"Obosit? Aud că The Exorcist is good."
|
||||
]
|
||||
"extra": ["Obosit? Aud că The Exorcist is good."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Не удалось найти медиафайл, попробуйте снова!",
|
||||
"loading": "Загрузка...",
|
||||
"noResults": "Мы ничего не нашли!",
|
||||
"placeholder": "Что вы хотите посмотреть?",
|
||||
"placeholder": {
|
||||
"default": "Что вы хотите посмотреть?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Результаты поиска"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,9 +131,7 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Что бы вы хотели посмотреть этим утром?",
|
||||
"extra": [
|
||||
"Слышали, что «Перед рассветом» – отличный фильм"
|
||||
]
|
||||
"extra": ["Слышали, что «Перед рассветом» – отличный фильм"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Что бы вы хотели посмотреть этим вечером?",
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "Ni uspelo najti medija, prosim poskusite znova!",
|
||||
"loading": "Nalaganje...",
|
||||
"noResults": "Vsebin nismo našli!",
|
||||
"placeholder": "Kaj si želite gledati?",
|
||||
"placeholder": {
|
||||
"default": "Kaj si želite gledati?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Rezultati iskanja"
|
||||
},
|
||||
"titles": {
|
||||
@ -127,9 +130,7 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Kateri film ali serijo bi si želeli ogledati ob tem jutru?",
|
||||
"extra": [
|
||||
"Slišala sem, da je film \"Pred sončnim vzhodom\" odličen"
|
||||
]
|
||||
"extra": ["Slišala sem, da je film \"Pred sončnim vzhodom\" odličen"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Kateri film ali serijo bi si želeli ogledati nocoj?",
|
||||
|
@ -114,7 +114,10 @@
|
||||
"failed": "Misslyckades med att hitta media, försök igen!",
|
||||
"loading": "Laddar...",
|
||||
"noResults": "Vi kunde inte hitta någonting!",
|
||||
"placeholder": "Vad vill du titta på?",
|
||||
"placeholder": {
|
||||
"default": "Vad vill du titta på?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Sökresultat"
|
||||
},
|
||||
"titles": {
|
||||
@ -123,15 +126,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Vad vill du titta på den här morgonen?",
|
||||
"extra": [
|
||||
"Jag hör att Before Sunrise är bra"
|
||||
]
|
||||
"extra": ["Jag hör att Before Sunrise är bra"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Vad vill du titta på ikväll?",
|
||||
"extra": [
|
||||
"Trött? Jag hör att The Exorcist är bra."
|
||||
]
|
||||
"extra": ["Trött? Jag hör att The Exorcist är bra."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,10 @@
|
||||
"failed": "ไม่พบสื่อนี้ ลองอีกครั้ง!",
|
||||
"loading": "กำลังโหลด..",
|
||||
"noResults": "เราไม่พบอะไรเลย!",
|
||||
"placeholder": "คุณอยากดูอะไรคะ?",
|
||||
"placeholder": {
|
||||
"default": "คุณอยากดูอะไรคะ?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "ผลการค้นหา"
|
||||
},
|
||||
"titles": {
|
||||
@ -124,15 +127,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "คุณอยากดูอะไรเช้านี้?",
|
||||
"extra": [
|
||||
"ฉันได้ยินมาว่าเรื่อง Before Sunrise สนุก"
|
||||
]
|
||||
"extra": ["ฉันได้ยินมาว่าเรื่อง Before Sunrise สนุก"]
|
||||
},
|
||||
"night": {
|
||||
"default": "คุณอยากดูเรื่องอะไรในช่วงค่ำ?",
|
||||
"extra": [
|
||||
"เหนื่อยมั้ย? ฉันได้ยินมาว่า The Exorcist นั้นดี"
|
||||
]
|
||||
"extra": ["เหนื่อยมั้ย? ฉันได้ยินมาว่า The Exorcist นั้นดี"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -115,27 +115,24 @@
|
||||
"failed": "lukin li pakala a! o alasa sin",
|
||||
"loading": "alasa...",
|
||||
"noResults": "ijo li lon ala a!",
|
||||
"placeholder": "sina wile lukin e seme?",
|
||||
"placeholder": {
|
||||
"default": "sina wile lukin e seme?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "mi lukin e ni:"
|
||||
},
|
||||
"titles": {
|
||||
"day": {
|
||||
"default": "tenpo suno ni la sina wile lukin e seme?",
|
||||
"extra": [
|
||||
"sina pilin alasa la o lukin e sitelen Jurassic Park"
|
||||
]
|
||||
"extra": ["sina pilin alasa la o lukin e sitelen Jurassic Park"]
|
||||
},
|
||||
"morning": {
|
||||
"default": "tenpo sin ni la sina wile lukin e seme?",
|
||||
"extra": [
|
||||
"ken la sitelen Before Sunrise li pona"
|
||||
]
|
||||
"extra": ["ken la sitelen Before Sunrise li pona"]
|
||||
},
|
||||
"night": {
|
||||
"default": "tenpo pimeja ni la sina wile lukin e seme?",
|
||||
"extra": [
|
||||
"sina pilin lape anu seme? o alasa lukin e sitelen Exorcist"
|
||||
]
|
||||
"extra": ["sina pilin lape anu seme? o alasa lukin e sitelen Exorcist"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Medya bulunamadı, tekrar deneyin!",
|
||||
"loading": "Yükleniyor...",
|
||||
"noResults": "Hiçbir şey bulamadık!",
|
||||
"placeholder": "Ne izlemek istersiniz?",
|
||||
"placeholder": {
|
||||
"default": "Ne izlemek istersiniz?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Arama sonuçları"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Bu sabah ne izlemek istersiniz?",
|
||||
"extra": [
|
||||
"Before Sunrise'a iyi diyorlar"
|
||||
]
|
||||
"extra": ["Before Sunrise'a iyi diyorlar"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Bu akşam ne izlemek istersiniz?",
|
||||
"extra": [
|
||||
"Yoruldun mu? The Exorcist'e iyi diyorlar."
|
||||
]
|
||||
"extra": ["Yoruldun mu? The Exorcist'e iyi diyorlar."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Не вдалося знайти медіафайли, повторіть спробу!",
|
||||
"loading": "Завантаження...",
|
||||
"noResults": "Ми не змогли знайти нічого!",
|
||||
"placeholder": "Що ви хочете подивитися?",
|
||||
"placeholder": {
|
||||
"default": "Що ви хочете подивитися?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Результати пошуку"
|
||||
},
|
||||
"titles": {
|
||||
@ -128,15 +131,11 @@
|
||||
},
|
||||
"morning": {
|
||||
"default": "Що б ви хотіли подивитися сьогодні вранці?",
|
||||
"extra": [
|
||||
"Я чув, що \"Перед сходом сонця\" гарний"
|
||||
]
|
||||
"extra": ["Я чув, що \"Перед сходом сонця\" гарний"]
|
||||
},
|
||||
"night": {
|
||||
"default": "Що б ви хотіли подивитися сьогодні ввечері?",
|
||||
"extra": [
|
||||
"Втомився? Я чув, що \"Екзорцист\" хороший."
|
||||
]
|
||||
"extra": ["Втомився? Я чув, що \"Екзорцист\" хороший."]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,7 +116,10 @@
|
||||
"failed": "Không thể tìm thấy nội dung, hãy thử lại!",
|
||||
"loading": "Đang tải...",
|
||||
"noResults": "Chúng tôi không thể tìm thấy gì!",
|
||||
"placeholder": "Bạn muốn xem gì?",
|
||||
"placeholder": {
|
||||
"default": "Bạn muốn xem gì?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "Kết quả tìm kiếm"
|
||||
},
|
||||
"titles": {
|
||||
|
@ -116,27 +116,24 @@
|
||||
"failed": "未能找到媒體,請重試!",
|
||||
"loading": "載入中...",
|
||||
"noResults": "我们找不到任何结果!",
|
||||
"placeholder": "您想看什麼?",
|
||||
"placeholder": {
|
||||
"default": "您想看什麼?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "搜索結果"
|
||||
},
|
||||
"titles": {
|
||||
"day": {
|
||||
"default": "您今天下午想看什麼?",
|
||||
"extra": [
|
||||
"想要來場冒險嗎?《侏羅紀公園》可能是完美選擇。"
|
||||
]
|
||||
"extra": ["想要來場冒險嗎?《侏羅紀公園》可能是完美選擇。"]
|
||||
},
|
||||
"morning": {
|
||||
"default": "您今天早上想看什麼?",
|
||||
"extra": [
|
||||
"我聽說《情留半天》不錯"
|
||||
]
|
||||
"extra": ["我聽說《情留半天》不錯"]
|
||||
},
|
||||
"night": {
|
||||
"default": "您今晚想看什麼?",
|
||||
"extra": [
|
||||
"疲倦了嗎?我聽說《驅魔人》不錯。"
|
||||
]
|
||||
"extra": ["疲倦了嗎?我聽說《驅魔人》不錯。"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,27 +116,24 @@
|
||||
"failed": "查找媒体失败,请重试!",
|
||||
"loading": "载入中……",
|
||||
"noResults": "我们找不到任何结果!",
|
||||
"placeholder": "您想看些什么?",
|
||||
"placeholder": {
|
||||
"default": "您想看些什么?",
|
||||
"extra": []
|
||||
},
|
||||
"sectionTitle": "搜索结果"
|
||||
},
|
||||
"titles": {
|
||||
"day": {
|
||||
"default": "您今天下午想看什么?",
|
||||
"extra": [
|
||||
"想要来场冒险?《侏罗纪公园》可能是最佳选项。"
|
||||
]
|
||||
"extra": ["想要来场冒险?《侏罗纪公园》可能是最佳选项。"]
|
||||
},
|
||||
"morning": {
|
||||
"default": "您今早想看什么?",
|
||||
"extra": [
|
||||
"我听说《爱在黎明破晓前》不错"
|
||||
]
|
||||
"extra": ["我听说《爱在黎明破晓前》不错"]
|
||||
},
|
||||
"night": {
|
||||
"default": "您今晚想看什么?",
|
||||
"extra": [
|
||||
"累了?我听说《驱魔人》不错。"
|
||||
]
|
||||
"extra": ["累了?我听说《驱魔人》不错。"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5,13 +5,14 @@ import { useCallback } from "react";
|
||||
|
||||
import { isExtensionActiveCached } from "@/backend/extension/messaging";
|
||||
import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape";
|
||||
import { BACKEND_URL } from "@/setup/constants";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { PlayerMeta } from "@/stores/player/slices/source";
|
||||
|
||||
// for anybody who cares - these are anonymous metrics.
|
||||
// They are just used for figuring out if providers are broken or not
|
||||
const metricsEndpoint = "https://backend.movie-web.app/metrics/providers";
|
||||
const captchaMetricsEndpoint = "https://backend.movie-web.app/metrics/captcha";
|
||||
const metricsEndpoint = `${BACKEND_URL}/metrics/providers`;
|
||||
const captchaMetricsEndpoint = `${BACKEND_URL}/metrics/captcha`;
|
||||
const batchId = () => nanoid(32);
|
||||
|
||||
export type ProviderMetric = {
|
||||
@ -44,6 +45,7 @@ function getStackTrace(error: Error, lines: number) {
|
||||
}
|
||||
|
||||
export async function reportProviders(items: ProviderMetric[]): Promise<void> {
|
||||
if (!BACKEND_URL) return;
|
||||
return ofetch(metricsEndpoint, {
|
||||
method: "POST",
|
||||
body: {
|
||||
@ -156,6 +158,7 @@ export function useReportProviders() {
|
||||
}
|
||||
|
||||
export function reportCaptchaSolve(success: boolean) {
|
||||
if (!BACKEND_URL) return;
|
||||
ofetch(captchaMetricsEndpoint, {
|
||||
method: "POST",
|
||||
body: {
|
||||
|
@ -5,6 +5,11 @@ import { convertSubtitlesToSrt } from "@/components/player/utils/captions";
|
||||
import { CaptionListItem } from "@/stores/player/slices/source";
|
||||
import { SimpleCache } from "@/utils/cache";
|
||||
|
||||
import {
|
||||
isExtensionActiveCached,
|
||||
sendExtensionRequest,
|
||||
} from "../extension/messaging";
|
||||
|
||||
export const subtitleTypeList = list().map((type) => `.${type}`);
|
||||
const downloadCache = new SimpleCache<string, string>();
|
||||
downloadCache.setCompare((a, b) => a === b);
|
||||
@ -21,7 +26,22 @@ export async function downloadCaption(
|
||||
|
||||
let data: string | undefined;
|
||||
if (caption.needsProxy) {
|
||||
data = await proxiedFetch<string>(caption.url, { responseType: "text" });
|
||||
if (isExtensionActiveCached()) {
|
||||
const extensionResponse = await sendExtensionRequest({
|
||||
url: caption.url,
|
||||
method: "GET",
|
||||
});
|
||||
if (
|
||||
!extensionResponse?.success ||
|
||||
typeof extensionResponse.response.body !== "string"
|
||||
) {
|
||||
throw new Error("failed to get caption data from extension");
|
||||
}
|
||||
|
||||
data = extensionResponse.response.body;
|
||||
} else {
|
||||
data = await proxiedFetch<string>(caption.url, { responseType: "text" });
|
||||
}
|
||||
} else {
|
||||
data = await fetch(caption.url).then((v) => v.text());
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import classNames from "classnames";
|
||||
import { ReactNode, useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { Spinner } from "@/components/layout/Spinner";
|
||||
@ -21,7 +20,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export function Button(props: Props) {
|
||||
const navigate = useNavigate();
|
||||
const { onClick, href, loading } = props;
|
||||
const cb = useCallback(
|
||||
(
|
||||
@ -31,10 +29,12 @@ export function Button(props: Props) {
|
||||
>,
|
||||
) => {
|
||||
if (loading) return;
|
||||
if (href && !onClick) navigate(href);
|
||||
else onClick?.(event);
|
||||
if (href && !onClick) {
|
||||
event.preventDefault();
|
||||
window.open(href, "_blank", "noreferrer");
|
||||
} else onClick?.(event);
|
||||
},
|
||||
[onClick, href, navigate, loading],
|
||||
[onClick, href, loading],
|
||||
);
|
||||
|
||||
let colorClasses = "bg-white hover:bg-gray-200 text-black";
|
||||
|
@ -47,8 +47,22 @@ export function Volume(props: Props) {
|
||||
if (dragging) percentage = makePercentage(dragPercentage);
|
||||
const percentageString = makePercentageString(percentage);
|
||||
|
||||
const handleWheel = useCallback(
|
||||
(event: React.WheelEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
let newVolume = volume - event.deltaY / 1000;
|
||||
newVolume = Math.max(0, Math.min(newVolume, 1));
|
||||
setVolume(newVolume);
|
||||
},
|
||||
[volume, setVolume],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={props.className} onMouseEnter={handleMouseEnter}>
|
||||
<div
|
||||
className={props.className}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div className="pointer-events-auto flex cursor-pointer items-center py-0">
|
||||
<div className="px-4 text-2xl text-white" onClick={handleClick}>
|
||||
<Icon icon={percentage > 0 ? Icons.VOLUME : Icons.VOLUME_X} />
|
||||
|
@ -63,6 +63,7 @@ export function useAuth() {
|
||||
|
||||
const login = useCallback(
|
||||
async (loginData: LoginData) => {
|
||||
if (!backendUrl) return;
|
||||
const keys = await keysFromMnemonic(loginData.mnemonic);
|
||||
const publicKeyBase64Url = bytesToBase64Url(keys.publicKey);
|
||||
const { challenge } = await getLoginChallengeToken(
|
||||
@ -87,7 +88,7 @@ export function useAuth() {
|
||||
);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
if (!currentAccount) return;
|
||||
if (!currentAccount || !backendUrl) return;
|
||||
try {
|
||||
await removeSession(
|
||||
backendUrl,
|
||||
@ -102,6 +103,7 @@ export function useAuth() {
|
||||
|
||||
const register = useCallback(
|
||||
async (registerData: RegistrationData) => {
|
||||
if (!backendUrl) return;
|
||||
const { challenge } = await getRegisterChallengeToken(
|
||||
backendUrl,
|
||||
registerData.recaptchaToken,
|
||||
@ -134,6 +136,7 @@ export function useAuth() {
|
||||
progressItems: Record<string, ProgressMediaItem>,
|
||||
bookmarks: Record<string, BookmarkMediaItem>,
|
||||
) => {
|
||||
if (!backendUrl) return;
|
||||
if (
|
||||
Object.keys(progressItems).length === 0 &&
|
||||
Object.keys(bookmarks).length === 0
|
||||
@ -159,6 +162,7 @@ export function useAuth() {
|
||||
|
||||
const restore = useCallback(
|
||||
async (account: AccountWithToken) => {
|
||||
if (!backendUrl) return;
|
||||
let user: { user: UserResponse; session: SessionResponse };
|
||||
try {
|
||||
user = await getUser(backendUrl, account.token);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { conf } from "@/setup/config";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
export function useBackendUrl() {
|
||||
export function useBackendUrl(): string | undefined {
|
||||
const backendUrl = useAuthStore((s) => s.backendUrl);
|
||||
return backendUrl ?? conf().BACKEND_URL;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
} from "react";
|
||||
|
||||
import { SubtitleStyling } from "@/stores/subtitles";
|
||||
import { usePreviewThemeStore } from "@/stores/theme";
|
||||
|
||||
export function useDerived<T>(
|
||||
initial: T,
|
||||
@ -56,6 +57,11 @@ export function useSettingsState(
|
||||
const [backendUrlState, setBackendUrl, resetBackendUrl, backendUrlChanged] =
|
||||
useDerived(backendUrl);
|
||||
const [themeState, setTheme, resetTheme, themeChanged] = useDerived(theme);
|
||||
const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
|
||||
const resetPreviewTheme = useCallback(
|
||||
() => setPreviewTheme(theme),
|
||||
[setPreviewTheme, theme],
|
||||
);
|
||||
const [
|
||||
appLanguageState,
|
||||
setAppLanguage,
|
||||
@ -81,6 +87,7 @@ export function useSettingsState(
|
||||
|
||||
function reset() {
|
||||
resetTheme();
|
||||
resetPreviewTheme();
|
||||
resetAppLanguage();
|
||||
resetSubStyling();
|
||||
resetProxyUrls();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import classNames from "classnames";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAsyncFn } from "react-use";
|
||||
|
||||
@ -33,7 +33,7 @@ import { AccountWithToken, useAuthStore } from "@/stores/auth";
|
||||
import { useLanguageStore } from "@/stores/language";
|
||||
import { usePreferencesStore } from "@/stores/preferences";
|
||||
import { useSubtitleStore } from "@/stores/subtitles";
|
||||
import { useThemeStore } from "@/stores/theme";
|
||||
import { usePreviewThemeStore, useThemeStore } from "@/stores/theme";
|
||||
|
||||
import { SubPageLayout } from "./layouts/SubPageLayout";
|
||||
import { PreferencesPart } from "./parts/settings/PreferencesPart";
|
||||
@ -70,6 +70,7 @@ export function AccountSettings(props: {
|
||||
const url = useBackendUrl();
|
||||
const { account } = props;
|
||||
const [sessionsResult, execSessions] = useAsyncFn(() => {
|
||||
if (!url) return Promise.resolve([]);
|
||||
return getSessions(url, account);
|
||||
}, [account, url]);
|
||||
useEffect(() => {
|
||||
@ -103,6 +104,8 @@ export function SettingsPage() {
|
||||
const { t } = useTranslation();
|
||||
const activeTheme = useThemeStore((s) => s.theme);
|
||||
const setTheme = useThemeStore((s) => s.setTheme);
|
||||
const previewTheme = usePreviewThemeStore((s) => s.previewTheme);
|
||||
const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
|
||||
|
||||
const appLanguage = useLanguageStore((s) => s.language);
|
||||
const setAppLanguage = useLanguageStore((s) => s.setLanguage);
|
||||
@ -143,8 +146,27 @@ export function SettingsPage() {
|
||||
enableThumbnails,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setPreviewTheme(activeTheme ?? "default");
|
||||
}, [setPreviewTheme, activeTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
// Clear preview theme on unmount
|
||||
return () => {
|
||||
setPreviewTheme(null);
|
||||
};
|
||||
}, [setPreviewTheme]);
|
||||
|
||||
const setThemeWithPreview = useCallback(
|
||||
(theme: string) => {
|
||||
state.theme.set(theme === "default" ? null : theme);
|
||||
setPreviewTheme(theme);
|
||||
},
|
||||
[state.theme, setPreviewTheme],
|
||||
);
|
||||
|
||||
const saveChanges = useCallback(async () => {
|
||||
if (account) {
|
||||
if (account && backendUrl) {
|
||||
if (
|
||||
state.appLanguage.changed ||
|
||||
state.theme.changed ||
|
||||
@ -241,7 +263,11 @@ export function SettingsPage() {
|
||||
/>
|
||||
</div>
|
||||
<div id="settings-appearance" className="mt-48">
|
||||
<ThemePart active={state.theme.state} setTheme={state.theme.set} />
|
||||
<ThemePart
|
||||
active={previewTheme ?? "default"}
|
||||
inUse={activeTheme ?? "default"}
|
||||
setTheme={setThemeWithPreview}
|
||||
/>
|
||||
</div>
|
||||
<div id="settings-captions" className="mt-48">
|
||||
<CaptionsPart
|
||||
|
@ -10,13 +10,13 @@ export function BlurEllipsis(props: { positionClass?: string }) {
|
||||
<div
|
||||
className={classNames(
|
||||
props.positionClass ?? "fixed",
|
||||
"top-0 -right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentA blur-[100px] pointer-events-none opacity-25",
|
||||
"top-0 -right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentA blur-[100px] pointer-events-none opacity-25 transition-colors duration-75",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
props.positionClass ?? "fixed",
|
||||
"top-0 right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentB blur-[100px] pointer-events-none opacity-25",
|
||||
"top-0 right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentB blur-[100px] pointer-events-none opacity-25 transition-colors duration-75",
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
} from "@/pages/onboarding/onboardingHooks";
|
||||
import { Card, CardContent, Link } from "@/pages/onboarding/utils";
|
||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||
import { getProxyUrls } from "@/utils/proxyUrls";
|
||||
|
||||
function VerticalLine(props: { className?: string }) {
|
||||
return (
|
||||
@ -27,6 +28,7 @@ export function OnboardingPage() {
|
||||
const skipModal = useModal("skip");
|
||||
const { completeAndRedirect } = useRedirectBack();
|
||||
const { t } = useTranslation();
|
||||
const noProxies = getProxyUrls().length === 0;
|
||||
|
||||
return (
|
||||
<MinimalPageLayout>
|
||||
@ -85,32 +87,34 @@ export function OnboardingPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<p className="text-center hidden md:block mt-12">
|
||||
<Trans i18nKey="onboarding.start.options.default.text">
|
||||
<br />
|
||||
<a
|
||||
onClick={skipModal.show}
|
||||
type="button"
|
||||
className="text-onboarding-link hover:opacity-75 cursor-pointer"
|
||||
/>
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className=" max-w-[300px] mx-auto md:hidden mt-12 ">
|
||||
<Button
|
||||
className="!text-type-text !bg-opacity-50"
|
||||
theme="secondary"
|
||||
onClick={skipModal.show}
|
||||
>
|
||||
<span>
|
||||
{noProxies ? null : (
|
||||
<>
|
||||
<p className="text-center hidden md:block mt-12">
|
||||
<Trans i18nKey="onboarding.start.options.default.text">
|
||||
<span />
|
||||
<span />
|
||||
<br />
|
||||
<a
|
||||
onClick={skipModal.show}
|
||||
type="button"
|
||||
className="text-onboarding-link hover:opacity-75 cursor-pointer"
|
||||
/>
|
||||
</Trans>
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</p>
|
||||
<div className=" max-w-[300px] mx-auto md:hidden mt-12 ">
|
||||
<Button
|
||||
className="!text-type-text !bg-opacity-50"
|
||||
theme="secondary"
|
||||
onClick={skipModal.show}
|
||||
>
|
||||
<span>
|
||||
<Trans i18nKey="onboarding.start.options.default.text">
|
||||
<span />
|
||||
<span />
|
||||
</Trans>
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CenterContainer>
|
||||
</MinimalPageLayout>
|
||||
);
|
||||
|
@ -115,7 +115,7 @@ export function ExtensionStatus(props: {
|
||||
</div>
|
||||
</Card>
|
||||
{lastKnownStatus === "unknown" ? <RefreshBar /> : null}
|
||||
{props.showHelp ? (
|
||||
{props.showHelp && props.status !== "success" ? (
|
||||
<Card className="mt-4">
|
||||
<div className="flex items-center space-x-7">
|
||||
<Icon icon={Icons.WARNING} className="text-type-danger text-2xl" />
|
||||
|
@ -43,7 +43,7 @@ export function OnboardingProxyPage() {
|
||||
throw new Error("onboarding.proxy.input.errorNotProxy");
|
||||
setProxySet([url]);
|
||||
|
||||
if (account) {
|
||||
if (account && backendUrl) {
|
||||
await updateSettings(backendUrl, account, {
|
||||
proxyUrls: [url],
|
||||
});
|
||||
|
@ -32,13 +32,21 @@ export function BackendTestPart() {
|
||||
value: null,
|
||||
});
|
||||
|
||||
if (!backendUrl) {
|
||||
return setStatus({
|
||||
hasTested: true,
|
||||
success: false,
|
||||
errorText: "Backend URL is not set",
|
||||
value: null,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const backendData = await getBackendMeta(backendUrl);
|
||||
return setStatus({
|
||||
hasTested: true,
|
||||
success: true,
|
||||
errorText:
|
||||
"Failed to call backend, double check the URL key and your internet connection",
|
||||
errorText: "",
|
||||
value: backendData,
|
||||
});
|
||||
} catch (err) {
|
||||
@ -46,7 +54,7 @@ export function BackendTestPart() {
|
||||
hasTested: true,
|
||||
success: false,
|
||||
errorText:
|
||||
"Failed to call backend, double check the URL key and your internet connection",
|
||||
"Failed to call backend, double check the URL, your internet connection, and ensure CORS is properly configured on your backend.",
|
||||
value: null,
|
||||
});
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export function TMDBTestPart() {
|
||||
return setStatus({
|
||||
hasTested: true,
|
||||
success: false,
|
||||
errorText: "TMDB api key is not set",
|
||||
errorText: "TMDB API key is not set",
|
||||
});
|
||||
}
|
||||
const isJWT = tmdbApiKey.split(".").length > 2;
|
||||
@ -37,7 +37,7 @@ export function TMDBTestPart() {
|
||||
return setStatus({
|
||||
hasTested: true,
|
||||
success: false,
|
||||
errorText: "TMDB api key is not a read only key",
|
||||
errorText: "TMDB API key is not a read only key",
|
||||
});
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ export function TMDBTestPart() {
|
||||
hasTested: true,
|
||||
success: false,
|
||||
errorText:
|
||||
"Failed to call tmdb, double check api key and your internet connection",
|
||||
"Failed to call TMDB, double check API key and your internet connection",
|
||||
});
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ export function TMDBTestPart() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading2 className="mb-8 mt-12">TMDB tests</Heading2>
|
||||
<Heading2 className="mb-8 mt-12">TMDB test</Heading2>
|
||||
<Box>
|
||||
<div className="flex items-center">
|
||||
<div className="flex-1">
|
||||
|
@ -52,14 +52,18 @@ export function WorkerTestPart() {
|
||||
{ id: string; status: "error" | "success"; error?: Error }[]
|
||||
>([]);
|
||||
|
||||
const [buttonDisabled, setButtonDisabled] = useState(false);
|
||||
|
||||
const [testState, runTests] = useAsyncFn(async () => {
|
||||
setButtonDisabled(true);
|
||||
function updateWorker(id: string, data: (typeof workerState)[number]) {
|
||||
setWorkerState((s) => {
|
||||
return [...s.filter((v) => v.id !== id), data];
|
||||
});
|
||||
}
|
||||
setWorkerState([]);
|
||||
for (const worker of workerList) {
|
||||
|
||||
const workerPromises = workerList.map(async (worker) => {
|
||||
try {
|
||||
if (worker.url.endsWith("/")) {
|
||||
updateWorker(worker.id, {
|
||||
@ -67,7 +71,7 @@ export function WorkerTestPart() {
|
||||
status: "error",
|
||||
error: new Error("URL ends with slash"),
|
||||
});
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
await singularProxiedFetch(
|
||||
worker.url,
|
||||
@ -85,7 +89,10 @@ export function WorkerTestPart() {
|
||||
error: err as Error,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(workerPromises);
|
||||
setTimeout(() => setButtonDisabled(false), 5000);
|
||||
}, [workerList, setWorkerState]);
|
||||
|
||||
return (
|
||||
@ -112,7 +119,12 @@ export function WorkerTestPart() {
|
||||
})}
|
||||
<Divider />
|
||||
<div className="flex justify-end">
|
||||
<Button theme="purple" loading={testState.loading} onClick={runTests}>
|
||||
<Button
|
||||
theme="purple"
|
||||
loading={testState.loading}
|
||||
onClick={buttonDisabled ? undefined : runTests}
|
||||
disabled={buttonDisabled}
|
||||
>
|
||||
Test workers
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -52,6 +52,9 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!account)
|
||||
throw new Error(t("auth.login.validationError") ?? undefined);
|
||||
|
||||
await importData(account, progressItems, bookmarkItems);
|
||||
|
||||
await restore(account);
|
||||
|
@ -22,8 +22,12 @@ interface TrustBackendPartProps {
|
||||
export function TrustBackendPart(props: TrustBackendPartProps) {
|
||||
const navigate = useNavigate();
|
||||
const backendUrl = useBackendUrl();
|
||||
const hostname = useMemo(() => new URL(backendUrl).hostname, [backendUrl]);
|
||||
const hostname = useMemo(
|
||||
() => (backendUrl ? new URL(backendUrl).hostname : undefined),
|
||||
[backendUrl],
|
||||
);
|
||||
const result = useAsync(() => {
|
||||
if (!backendUrl) return Promise.resolve(null);
|
||||
return getBackendMeta(backendUrl);
|
||||
}, [backendUrl]);
|
||||
const { t } = useTranslation();
|
||||
@ -50,38 +54,52 @@ export function TrustBackendPart(props: TrustBackendPartProps) {
|
||||
return (
|
||||
<LargeCard>
|
||||
<LargeCardText
|
||||
title={t("auth.trust.title")}
|
||||
title={hostname ? t("auth.trust.title") : t("auth.trust.noHostTitle")}
|
||||
icon={<Icon icon={Icons.CIRCLE_EXCLAMATION} />}
|
||||
>
|
||||
<Trans
|
||||
i18nKey="auth.trust.host"
|
||||
values={{
|
||||
hostname,
|
||||
}}
|
||||
>
|
||||
<span className="text-white" />
|
||||
</Trans>
|
||||
{hostname ? (
|
||||
<Trans
|
||||
i18nKey="auth.trust.host"
|
||||
values={{
|
||||
hostname,
|
||||
}}
|
||||
>
|
||||
<span className="text-white" />
|
||||
</Trans>
|
||||
) : (
|
||||
<p>{t("auth.trust.noHost")}</p>
|
||||
)}
|
||||
</LargeCardText>
|
||||
|
||||
<div className="border border-authentication-border rounded-xl px-4 py-8 flex flex-col items-center space-y-2 my-8">
|
||||
{cardContent}
|
||||
</div>
|
||||
<LargeCardButtons>
|
||||
<Button theme="secondary" onClick={() => navigate("/")}>
|
||||
{t("auth.trust.no")}
|
||||
</Button>
|
||||
<Button
|
||||
theme="purple"
|
||||
onClick={() => result.value && props.onNext?.(result.value)}
|
||||
>
|
||||
{t("auth.trust.yes")}
|
||||
</Button>
|
||||
</LargeCardButtons>
|
||||
<p className="text-center mt-6">
|
||||
<Trans i18nKey="auth.hasAccount">
|
||||
<MwLink to="/login">.</MwLink>
|
||||
</Trans>
|
||||
</p>
|
||||
{hostname ? (
|
||||
<>
|
||||
<div className="border border-authentication-border rounded-xl px-4 py-8 flex flex-col items-center space-y-2 my-8">
|
||||
{cardContent}
|
||||
</div>
|
||||
<LargeCardButtons>
|
||||
<Button theme="secondary" onClick={() => navigate("/")}>
|
||||
{t("auth.trust.no")}
|
||||
</Button>
|
||||
<Button
|
||||
theme="purple"
|
||||
onClick={() => result.value && props.onNext?.(result.value)}
|
||||
>
|
||||
{t("auth.trust.yes")}
|
||||
</Button>
|
||||
</LargeCardButtons>
|
||||
<p className="text-center mt-6">
|
||||
<Trans i18nKey="auth.hasAccount">
|
||||
<MwLink to="/login">.</MwLink>
|
||||
</Trans>
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<LargeCardButtons>
|
||||
<Button theme="purple" onClick={() => navigate("/")}>
|
||||
{t("auth.trust.no")}
|
||||
</Button>
|
||||
</LargeCardButtons>
|
||||
)}
|
||||
</LargeCard>
|
||||
);
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
||||
|
||||
const [result, execute] = useAsyncFn(
|
||||
async (inputMnemonic: string) => {
|
||||
if (!backendUrl)
|
||||
throw new Error(t("auth.verify.noBackendUrl") ?? undefined);
|
||||
if (!props.mnemonic || !props.userData)
|
||||
throw new Error(t("auth.verify.invalidData") ?? undefined);
|
||||
|
||||
@ -68,6 +70,9 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
||||
recaptchaToken,
|
||||
});
|
||||
|
||||
if (!account)
|
||||
throw new Error(t("auth.verify.registrationFailed") ?? undefined);
|
||||
|
||||
await importData(account, progressItems, bookmarkItems);
|
||||
|
||||
await updateSettings(backendUrl, account, {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Sticky from "react-sticky-el";
|
||||
import { useWindowSize } from "react-use";
|
||||
|
||||
@ -26,7 +25,6 @@ function getTimeOfDay(date: Date): "night" | "morning" | "day" {
|
||||
|
||||
export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
|
||||
const { t: randomT } = useRandomTranslation();
|
||||
const { t } = useTranslation();
|
||||
const [search, setSearch, setSearchUnFocus] = searchParams;
|
||||
const [, setShowBg] = useState(false);
|
||||
const bannerSize = useBannerSize();
|
||||
@ -54,7 +52,7 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
|
||||
|
||||
const time = getTimeOfDay(new Date());
|
||||
const title = randomT(`home.titles.${time}`);
|
||||
|
||||
const placeholder = randomT(`home.search.placeholder`);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useSlashFocus(inputRef);
|
||||
|
||||
@ -77,7 +75,7 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
|
||||
onChange={setSearch}
|
||||
value={search}
|
||||
onUnFocus={setSearchUnFocus}
|
||||
placeholder={t("home.search.placeholder") ?? ""}
|
||||
placeholder={placeholder ?? ""}
|
||||
/>
|
||||
</Sticky>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ export function AccountActionsPart() {
|
||||
const deleteModal = useModal("account-delete");
|
||||
|
||||
const [deleteResult, deleteExec] = useAsyncFn(async () => {
|
||||
if (!account) return;
|
||||
if (!account || !url) return;
|
||||
await deleteUser(url, account);
|
||||
await logout();
|
||||
deleteModal.hide();
|
||||
|
@ -55,7 +55,7 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
|
||||
</p>
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.workers.description">
|
||||
<MwLink to="https://docs.movie-web.app/proxy/deploy">
|
||||
<MwLink to="https://movie-web.github.io/docs/proxy/deploy">
|
||||
Proxy documentation
|
||||
</MwLink>
|
||||
</Trans>
|
||||
@ -125,7 +125,7 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
||||
</p>
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.server.description">
|
||||
<MwLink to="https://docs.movie-web.app/backend/deploy">
|
||||
<MwLink to="https://movie-web.github.io/docs/backend/deploy">
|
||||
Backend documentation
|
||||
</MwLink>
|
||||
</Trans>
|
||||
|
@ -24,6 +24,7 @@ export function Device(props: {
|
||||
const token = useAuthStore((s) => s.account?.token);
|
||||
const [result, exec] = useAsyncFn(async () => {
|
||||
if (!token) throw new Error("No token present");
|
||||
if (!url) throw new Error("No backend set");
|
||||
await removeSession(url, token, props.id);
|
||||
props.onRemove?.();
|
||||
}, [url, token, props.id]);
|
||||
|
@ -14,9 +14,9 @@ import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
const rem = 16;
|
||||
|
||||
function SecureBadge(props: { url: string }) {
|
||||
function SecureBadge(props: { url: string | undefined }) {
|
||||
const { t } = useTranslation();
|
||||
const secure = props.url.startsWith("https://");
|
||||
const secure = props.url ? props.url.startsWith("https://") : false;
|
||||
return (
|
||||
<div className="flex items-center gap-1 -mx-1 ml-3 px-1 rounded bg-largeCard-background font-bold">
|
||||
<Icon icon={secure ? Icons.LOCK : Icons.UNLOCK} />
|
||||
@ -68,6 +68,7 @@ export function SidebarPart() {
|
||||
const backendUrl = useBackendUrl();
|
||||
|
||||
const backendMeta = useAsync(async () => {
|
||||
if (!backendUrl) return;
|
||||
return getBackendMeta(backendUrl);
|
||||
}, [backendUrl]);
|
||||
|
||||
@ -159,7 +160,7 @@ export function SidebarPart() {
|
||||
<SecureBadge url={backendUrl} />
|
||||
</div>
|
||||
<p className="text-white">
|
||||
{backendUrl.replace(/https?:\/\//, "")}
|
||||
{backendUrl?.replace(/https?:\/\//, "") ?? "—"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -5,20 +5,29 @@ import { Icon, Icons } from "@/components/Icon";
|
||||
import { Heading1 } from "@/components/utils/Text";
|
||||
|
||||
const availableThemes = [
|
||||
{
|
||||
id: "default",
|
||||
selector: "theme-default",
|
||||
key: "settings.appearance.themes.default",
|
||||
},
|
||||
{
|
||||
id: "blue",
|
||||
selector: "theme-blue",
|
||||
key: "settings.appearance.themes.blue",
|
||||
},
|
||||
{
|
||||
id: "teal",
|
||||
selector: "theme-teal",
|
||||
key: "settings.appearance.themes.teal",
|
||||
},
|
||||
{
|
||||
id: "red",
|
||||
selector: "theme-red",
|
||||
key: "settings.appearance.themes.red",
|
||||
},
|
||||
{
|
||||
id: "gray",
|
||||
selector: "theme-gray",
|
||||
key: "settings.appearance.themes.gray",
|
||||
},
|
||||
];
|
||||
@ -26,6 +35,7 @@ const availableThemes = [
|
||||
function ThemePreview(props: {
|
||||
selector?: string;
|
||||
active?: boolean;
|
||||
inUse?: boolean;
|
||||
name: string;
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
@ -105,7 +115,7 @@ function ThemePreview(props: {
|
||||
<span
|
||||
className={classNames(
|
||||
"inline-block px-3 py-1 leading-tight text-sm transition-opacity duration-150 rounded-full bg-pill-activeBackground text-white/85",
|
||||
props.active ? "opacity-100" : "opacity-0 pointer-events-none",
|
||||
props.inUse ? "opacity-100" : "opacity-0 pointer-events-none",
|
||||
)}
|
||||
>
|
||||
{t("settings.appearance.activeTheme")}
|
||||
@ -116,8 +126,9 @@ function ThemePreview(props: {
|
||||
}
|
||||
|
||||
export function ThemePart(props: {
|
||||
active: string | null;
|
||||
setTheme: (theme: string | null) => void;
|
||||
active: string;
|
||||
inUse: string;
|
||||
setTheme: (theme: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -125,17 +136,11 @@ export function ThemePart(props: {
|
||||
<div>
|
||||
<Heading1 border>{t("settings.appearance.title")}</Heading1>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-6 max-w-[700px]">
|
||||
{/* default theme */}
|
||||
<ThemePreview
|
||||
name={t("settings.appearance.themes.default")}
|
||||
selector="theme-default"
|
||||
active={props.active === null}
|
||||
onClick={() => props.setTheme(null)}
|
||||
/>
|
||||
{availableThemes.map((v) => (
|
||||
<ThemePreview
|
||||
selector={`theme-${v.id}`}
|
||||
selector={v.selector}
|
||||
active={props.active === v.id}
|
||||
inUse={props.inUse === v.id}
|
||||
name={t(v.key)}
|
||||
key={v.id}
|
||||
onClick={() => props.setTheme(v.id)}
|
||||
|
@ -1,6 +1,6 @@
|
||||
export const APP_VERSION = import.meta.env.PACKAGE_VERSION;
|
||||
export const DISCORD_LINK = "https://discord.movie-web.app";
|
||||
export const DISCORD_LINK = "https://movie-web.github.io/links/discord";
|
||||
export const GITHUB_LINK = "https://github.com/movie-web/movie-web";
|
||||
export const DONATION_LINK = "https://ko-fi.com/movieweb";
|
||||
export const GA_ID = "G-44YVXRL61C";
|
||||
export const BACKEND_URL = "https://backend.movie-web.app";
|
||||
export const GA_ID = import.meta.env.VITE_GA_ID;
|
||||
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL;
|
||||
|
@ -2,8 +2,10 @@ import ReactGA from "react-ga4";
|
||||
|
||||
import { GA_ID } from "@/setup/constants";
|
||||
|
||||
ReactGA.initialize([
|
||||
{
|
||||
trackingId: GA_ID,
|
||||
},
|
||||
]);
|
||||
if (GA_ID) {
|
||||
ReactGA.initialize([
|
||||
{
|
||||
trackingId: GA_ID,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ export function BookmarkSyncer() {
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
(async () => {
|
||||
if (!url) return;
|
||||
const state = useBookmarkStore.getState();
|
||||
const user = useAuthStore.getState();
|
||||
await syncBookmarks(
|
||||
|
@ -62,6 +62,7 @@ export function ProgressSyncer() {
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
(async () => {
|
||||
if (!url) return;
|
||||
const state = useProgressStore.getState();
|
||||
const user = useAuthStore.getState();
|
||||
await syncProgress(
|
||||
|
@ -16,6 +16,7 @@ export function SettingsSyncer() {
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
(async () => {
|
||||
if (!url) return;
|
||||
const state = useSubtitleStore.getState();
|
||||
const user = useAuthStore.getState();
|
||||
if (state.lastSync.lastSelectedLanguage === state.lastSelectedLanguage)
|
||||
|
@ -25,12 +25,31 @@ export const useThemeStore = create(
|
||||
),
|
||||
);
|
||||
|
||||
export interface PreviewThemeStore {
|
||||
previewTheme: string | null;
|
||||
setPreviewTheme(v: string | null): void;
|
||||
}
|
||||
|
||||
export const usePreviewThemeStore = create(
|
||||
immer<PreviewThemeStore>((set) => ({
|
||||
previewTheme: null,
|
||||
setPreviewTheme(v) {
|
||||
set((s) => {
|
||||
s.previewTheme = v;
|
||||
});
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
export function ThemeProvider(props: {
|
||||
children?: ReactNode;
|
||||
applyGlobal?: boolean;
|
||||
}) {
|
||||
const previewTheme = usePreviewThemeStore((s) => s.previewTheme);
|
||||
const theme = useThemeStore((s) => s.theme);
|
||||
const themeSelector = theme ? `theme-${theme}` : undefined;
|
||||
|
||||
const themeToDisplay = previewTheme ?? theme;
|
||||
const themeSelector = themeToDisplay ? `theme-${themeToDisplay}` : undefined;
|
||||
|
||||
return (
|
||||
<div className={themeSelector}>
|
||||
|
Loading…
x
Reference in New Issue
Block a user