Compare commits

..

87 Commits
4.0.2 ... 4.1.2

Author SHA1 Message Date
mrjvs
1e29ab3e3c Merge pull request #590 from movie-web/dev
Version 4.1.2
2023-12-25 22:15:12 +01:00
mrjvs
8619fe9780 Merge branch 'master' into dev 2023-12-25 22:12:18 +01:00
mrjvs
34f96cd533 Bump version 2023-12-25 21:45:58 +01:00
William Oldham
34b749abed Merge pull request #583 from movie-web/fix-touch-pause
More bugfixes
2023-12-24 15:55:48 +00:00
mrjvs
53dae57b75 Merge branch 'dev' into fix-touch-pause 2023-12-24 16:49:49 +01:00
mrjvs
ac7fa99c45 Fix noOutput parsing + better error modal 2023-12-24 16:37:00 +01:00
mrjvs
12f30bc42f Improve poster url quality 2023-12-24 16:22:09 +01:00
mrjvs
2e0a5910ca Fix missing timeout on touch controls hovering 2023-12-24 16:12:28 +01:00
mrjvs
51724987ca Fix popout bug 2023-12-24 15:51:19 +01:00
mrjvs
ba6fe26c26 Merge pull request #572 from gh-movie-web/weblate-movie-web-website
Translations update from movie-web weblate
2023-12-24 15:20:08 +01:00
5Litt
8cf581e92b Translated using Weblate (Czech)
Currently translated at 100.0% (247 of 247 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/cs/
Author: 5Litt <5Litt@users.noreply.weblate.movie-web.app>
2023-12-24 13:25:57 +00:00
Kipoddo
37d1bf9016 Translated using Weblate (Hebrew)
Currently translated at 100.0% (247 of 247 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/he/
Author: Kipoddo <guybusinessk@gmail.com>
2023-12-24 13:25:57 +00:00
teaishealthy
1eee338131 Translated using Weblate (German)
Currently translated at 100.0% (247 of 247 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/de/
Author: teaishealthy <teaishealthy@protonmail.com>
2023-12-24 13:25:56 +00:00
Samuel Bárany
3662eb92a4 Translated using Weblate (Czech)
Currently translated at 65.1% (161 of 247 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/cs/
Author: Samuel Bárany <barany.samuel.sam@gmail.com>
2023-12-24 13:25:56 +00:00
Weblate
ae8cbe1789 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/
2023-12-24 13:25:56 +00:00
atomic
467c9758a8 Translated using Weblate (French)
Currently translated at 100.0% (247 of 247 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/fr/
Author: atomic <atomiclikesmans@gmail.com>
2023-12-24 13:25:56 +00:00
Samuel Bárany
9ff44eaa9b Translated using Weblate (Czech)
Currently translated at 37.6% (93 of 247 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/cs/
Author: Samuel Bárany <barany.samuel.sam@gmail.com>
2023-12-24 13:25:56 +00:00
nuh uh
fac9384f62 Translated using Weblate (Czech)
Currently translated at 37.6% (93 of 247 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/cs/
Author: nuh uh <obastlik@gmail.com>
2023-12-24 13:25:56 +00:00
mrjvs
0045163630 Merge pull request #578 from rafcontreras/package-upgrade
Upgrade packages, bundling, performance
2023-12-24 14:25:52 +01:00
mrjvs
257b080e67 Fix hostname not being show correctly 2023-12-24 14:23:10 +01:00
mrjvs
287ea61e17 Chunk react-dom away from main package for faster load times + add rollup chunking analyzer + ignore vite config from tsconfig linting 2023-12-24 14:20:47 +01:00
Contreras, Raf
68538d2112 More bundling improvements 2023-12-24 16:14:34 +13:00
Contreras, Raf
bbf5645c2b PR review changes 2023-12-24 14:58:07 +13:00
Contreras, Raf
48b708d569 Upgrade packages, bundling, performance 2023-12-23 18:24:43 +13:00
William Oldham
040a054b13 Merge pull request #571 from movie-web/dev
Version v4.1.1
2023-12-21 20:33:23 +00:00
mrjvs
afe2b24c96 Merge branch 'master' into dev 2023-12-21 21:28:03 +01:00
mrjvs
7b3d3105c8 Remove unused import 2023-12-21 21:14:37 +01:00
William Oldham
c007a48c7d Merge pull request #570 from gh-movie-web/weblate-movie-web-website
Translations update from movie-web weblate
2023-12-21 20:13:33 +00:00
Aayush Shah
ac8558be7e Translated using Weblate (Nepali)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/ne/
Author: Aayush Shah <shahaayush999@gmail.com>
2023-12-21 20:08:22 +00:00
Raymond Nee
de8059b7a2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/zh_Hans/
Author: Raymond Nee <monstorix@outlook.com>
2023-12-21 20:08:22 +00:00
Mycroft Holmes
b42a3d3a14 Translated using Weblate (Arabic)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/ar/
Author: Mycroft Holmes <mycroft_47@users.noreply.weblate.movie-web.app>
2023-12-21 20:08:22 +00:00
atomic
cefa7141f4 Translated using Weblate (French)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/fr/
Author: atomic <atomiclikesmans@gmail.com>
2023-12-21 20:08:22 +00:00
William Oldham
dd56986081 Merge pull request #569 from movie-web/fix-some-bugs
Fix numerous bugs
2023-12-21 20:08:16 +00:00
mrjvs
d032d6e2f2 Bump version 2023-12-21 21:02:09 +01:00
mrjvs
0e830ee0a6 Fix settings toggles sometimes not being toggable
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
2023-12-21 21:00:36 +01:00
mrjvs
690b1c6e68 Fix subtitles not being able to be turned off while logged in
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
2023-12-21 21:00:17 +01:00
mrjvs
75af3b992d Made some more keys translatable 2023-12-21 20:47:38 +01:00
mrjvs
82d2516951 Report captcha solves 2023-12-21 20:39:53 +01:00
mrjvs
66001a16bc Merge pull request #565 from blackvid/fish-patch
Improved fish generation
2023-12-20 21:03:49 +01:00
blackvid
a59668f46c Important update 2023-12-20 15:48:06 -04:00
William Oldham
29657c0f4c Merge pull request #564 from movie-web/dev
Version 4.1.0
2023-12-20 16:30:16 +00:00
mrjvs
0ef33ccd44 Fix workers test page not using the actual proxy workers 2023-12-20 17:27:37 +01:00
mrjvs
81e70030c9 Merge branch 'master' into dev 2023-12-20 17:25:54 +01:00
William Oldham
ed05e40e32 Bump Version
Co-authored-by: mrjvs <jellevs@gmail.com>
2023-12-20 16:23:06 +00:00
mrjvs
c884e44556 Add arabic to language selector (RTL) 2023-12-20 16:47:45 +01:00
William Oldham
fc43b59205 Merge pull request #563 from gh-movie-web/weblate-movie-web-website
Translations update from movie-web weblate
2023-12-20 15:37:03 +00:00
Hank Dank
a9d368d795 Translated using Weblate (Turkish)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/tr/
Author: Hank Dank <hdank2657@gmail.com>
2023-12-20 15:33:36 +00:00
Aayush Shah
e7ddacea30 Translated using Weblate (Nepali)
Currently translated at 69.5% (171 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/ne/
Author: Aayush Shah <shahaayush999@gmail.com>
2023-12-20 15:33:36 +00:00
Raymond Nee
10393c499f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/zh_Hans/
Author: Raymond Nee <monstorix@outlook.com>
2023-12-20 15:33:36 +00:00
Mycroft Holmes
de8c852a14 Translated using Weblate (Arabic)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/ar/
Author: Mycroft Holmes <mycroft_47@users.noreply.weblate.movie-web.app>
2023-12-20 15:33:36 +00:00
William Oldham
025aaffc2b Merge pull request #561 from movie-web/add-providers-api
Add providers api integration
2023-12-20 15:33:31 +00:00
mrjvs
4372f30f0d Merge branch 'dev' into add-providers-api 2023-12-20 15:27:18 +01:00
mrjvs
d9fd16613a Implement turnstile on provider package simple proxy 2023-12-20 15:22:05 +01:00
mrjvs
4db6dcca48 Implement setting api token after response 2023-12-20 15:08:04 +01:00
mrjvs
d998dceb1e Fix proxiedFetch not using new turnstile integration 2023-12-20 15:02:05 +01:00
mrjvs
1acc81205a Add prefixes to tokens 2023-12-19 22:39:14 +01:00
mrjvs
673b353629 update languages array 2023-12-19 21:15:33 +01:00
mrjvs
e41453ddfb Merge pull request #543 from gh-movie-web/weblate-movie-web-website
Translations update from movie-web weblate
2023-12-19 21:13:16 +01:00
mrjvs
bc51076369 Remove turnstile cdn link 2023-12-19 21:00:25 +01:00
mrjvs
b5a11ef000 turnstile integration for provider api 2023-12-19 20:41:56 +01:00
mrjvs
4847980947 Improve error handling for providers api 2023-12-19 18:47:54 +01:00
mrjvs
ca2e20fdbc Add provider-api to source selection menu 2023-12-19 18:09:05 +01:00
mrjvs
15d97dda02 remove unused import 2023-12-19 00:14:42 +01:00
mrjvs
116501e0c1 Source list 2023-12-19 00:14:13 +01:00
mrjvs
2bf0b5b03c Metadata fetching 2023-12-19 00:10:46 +01:00
mrjvs
e48af381c5 its not finished 2023-12-18 22:53:59 +01:00
mrjvs
ed67c1e63b Remove console log 2023-12-18 22:30:46 +01:00
mrjvs
f55b39d0fa Properly map events and data to providers api 2023-12-18 22:24:34 +01:00
mrjvs
8af4256d95 I cant use commas 2023-12-18 21:50:30 +01:00
mrjvs
a52fac701a Parse provider API urls + use new provider api in runAll scrape 2023-12-18 21:47:19 +01:00
Jip Frijlink
d65fca6fff Translated using Weblate (Dutch)
Currently translated at 49.5% (122 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/nl/
Author: Jip Frijlink <jipfrijlink@gmail.com>
2023-12-17 20:25:30 +00:00
Aayush Shah
fca1bc0992 Translated using Weblate (Nepali)
Currently translated at 47.5% (117 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/ne/
Author: Aayush Shah <shahaayush999@gmail.com>
2023-12-17 19:48:06 +00:00
Nemo
75c8a25e0f Translated using Weblate (Thai)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/th/
Author: Nemo <cello_monsoon0g@icloud.com>
2023-12-17 19:48:06 +00:00
Hank Dank
97af64f568 Translated using Weblate (Turkish)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/tr/
Author: Hank Dank <hdank2657@gmail.com>
2023-12-17 19:48:06 +00:00
Raymond Nee
96f621f23e Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/zh_Hans/
Author: Raymond Nee <monstorix@outlook.com>
2023-12-17 19:48:06 +00:00
Mycroft Holmes
2f9fc94d28 Added translation using Weblate (Arabic)
Author: Mycroft Holmes <mycroft.forty.seven@gmail.com>
2023-12-17 19:48:06 +00:00
Me
dd796f71b1 Translated using Weblate (Latvian)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/lv/
Author: Me <Jurgis227@gmail.com>
2023-12-17 19:48:06 +00:00
Hank Dank
48bc4d0790 Translated using Weblate (Turkish)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/tr/
Author: Hank Dank <hdank2657@gmail.com>
2023-12-17 19:48:06 +00:00
Hank Dank
093b278a6e Translated using Weblate (Turkish)
Currently translated at 50.8% (125 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/tr/
Author: Hank Dank <hdank2657@gmail.com>
2023-12-17 19:48:06 +00:00
Aayush Shah
26cf1a65f1 Translated using Weblate (Nepali)
Currently translated at 18.6% (46 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/ne/
Author: Aayush Shah <shahaayush999@gmail.com>
2023-12-17 19:48:06 +00:00
Me
4defe7fc6c Translated using Weblate (Latvian)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/lv/
Author: Me <Jurgis227@gmail.com>
2023-12-17 19:48:06 +00:00
Kipoddo
c47f490d66 Translated using Weblate (Hebrew)
Currently translated at 100.0% (246 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/he/
Author: Kipoddo <guybusinessk@gmail.com>
2023-12-17 19:48:06 +00:00
Hank Dank
8d7f8ca209 Translated using Weblate (Turkish)
Currently translated at 50.4% (124 of 246 strings)

Translation: movie-web/website
Translate-URL: http://weblate.movie-web.app/projects/movie-web/website/tr/
Author: Hank Dank <hdank2657@gmail.com>
2023-12-17 19:48:06 +00:00
Aayush Shah
d24c3e7c9e Added translation using Weblate (Nepali)
Author: Aayush Shah <shahaayush999@gmail.com>
2023-12-17 19:48:06 +00:00
Me
477554ac18 Added translation using Weblate (Latvian)
Author: Me <Jurgis227@gmail.com>
2023-12-17 19:48:06 +00:00
Jip Frijlink
de30929dd6 Merge pull request #553 from zisra/patch-1
Fix RTL subtitles
2023-12-17 20:48:01 +01:00
zisra
43ed6edd99 Fix RTL subtitles 2023-12-17 13:37:28 -06:00
164 changed files with 6550 additions and 2351 deletions

View File

@@ -21,6 +21,7 @@ module.exports = {
"dist/*",
"/*.js",
"/*.ts",
"/*.mts",
"/plugins/*.ts",
"/plugins/*.mjs",
"/themes/**/*.ts"
@@ -61,7 +62,7 @@ module.exports = {
"no-nested-ternary": "off",
"prefer-destructuring": "off",
"no-param-reassign": "off",
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
"react/jsx-filename-extension": [
"error",
{ extensions: [".js", ".tsx", ".jsx"] }

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ node_modules
# production
/dist
dev-dist
/stats.html
# misc
.DS_Store

View File

@@ -20,7 +20,6 @@
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
<script src="/config.js"></script>
<script src="https://cdn.jsdelivr.net/gh/movie-web/6C6F6C7A@8b821f445b83d51ef1b8f42c99b7346f6b47dce5/out.js"></script>
<!-- prevent darkreader extension from messing with our already dark site -->
<meta name="darkreader-lock" />
@@ -59,4 +58,4 @@
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
</html>

View File

@@ -1,6 +1,6 @@
{
"name": "movie-web",
"version": "4.0.2",
"version": "4.1.2",
"private": true,
"homepage": "https://movie-web.app",
"scripts": {
@@ -26,94 +26,99 @@
]
},
"dependencies": {
"@formkit/auto-animate": "^0.7.0",
"@headlessui/react": "^1.5.0",
"@formkit/auto-animate": "^0.8.1",
"@headlessui/react": "^1.7.17",
"@movie-web/providers": "^1.1.5",
"@noble/hashes": "^1.3.2",
"@react-spring/web": "^9.7.1",
"@scure/bip39": "^1.2.1",
"@noble/hashes": "^1.3.3",
"@react-spring/web": "^9.7.3",
"@scure/bip39": "^1.2.2",
"@sozialhelden/ietf-language-tags": "^5.4.2",
"@types/node-forge": "^1.3.8",
"@types/node-forge": "^1.3.10",
"classnames": "^2.3.2",
"core-js": "^3.29.1",
"dompurify": "^3.0.1",
"flag-icons": "^6.11.1",
"core-js": "^3.34.0",
"dompurify": "^3.0.6",
"flag-icons": "^7.1.0",
"focus-trap-react": "^10.2.3",
"fscreen": "^1.2.0",
"fuse.js": "^6.4.6",
"hls.js": "^1.0.7",
"i18next": "^22.4.5",
"immer": "^10.0.2",
"fuse.js": "^7.0.0",
"hls.js": "^1.4.14",
"i18next": "^23.7.11",
"immer": "^10.0.3",
"iso-639-1": "^3.1.0",
"jwt-decode": "^4.0.0",
"lodash.isequal": "^4.5.0",
"million": "^2.6.4",
"nanoid": "^5.0.4",
"node-forge": "^1.3.1",
"ofetch": "^1.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-ga4": "^2.0.0",
"ofetch": "^1.3.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-ga4": "^2.1.0",
"react-google-recaptcha-v3": "^1.10.1",
"react-helmet-async": "^1.3.0",
"react-i18next": "^12.1.1",
"react-router-dom": "^5.2.0",
"react-helmet-async": "^2.0.4",
"react-i18next": "^14.0.0",
"react-lazy-with-preload": "^2.2.1",
"react-router-dom": "^6.21.1",
"react-sticky-el": "^2.1.0",
"react-use": "^17.4.0",
"react-turnstile": "^1.1.2",
"react-use": "^17.4.2",
"slugify": "^1.6.6",
"subsrt-ts": "^2.1.1",
"zustand": "^4.3.9"
"subsrt-ts": "^2.1.2",
"zustand": "^4.4.7"
},
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.21.0",
"@types/chromecast-caf-sender": "^1.0.5",
"@types/crypto-js": "^4.1.1",
"@types/dompurify": "^2.4.0",
"@types/fscreen": "^1.0.1",
"@babel/core": "^7.23.6",
"@babel/preset-env": "^7.23.6",
"@babel/preset-typescript": "^7.23.3",
"@types/chromecast-caf-sender": "^1.0.8",
"@types/crypto-js": "^4.2.1",
"@types/dompurify": "^3.0.5",
"@types/fscreen": "^1.0.4",
"@types/lodash.isequal": "^4.5.8",
"@types/lodash.throttle": "^4.1.7",
"@types/node": "^17.0.15",
"@types/pako": "^2.0.0",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"@types/react-helmet": "^6.1.6",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^20.10.5",
"@types/pako": "^2.0.3",
"@types/react": "^18.2.45",
"@types/react-dom": "^18.2.18",
"@types/react-helmet": "^6.1.11",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"@types/react-stickynode": "^4.0.0",
"@types/react-transition-group": "^4.4.5",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.13",
"@types/react-stickynode": "^4.0.3",
"@types/react-transition-group": "^4.4.10",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
"cross-env": "^7.0.3",
"eslint": "^8.10.0",
"eslint": "^8.56.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "^8.6.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "7.29.4",
"eslint-plugin-react-hooks": "4.3.0",
"glob": "^10.3.3",
"handlebars": "^4.7.7",
"jsdom": "^21.1.0",
"postcss": "^8.4.20",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "^5.1.1",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.0",
"glob": "^10.3.10",
"handlebars": "^4.7.8",
"jsdom": "^23.0.1",
"postcss": "^8.4.32",
"postcss-rtl": "^2.0.0",
"postcss-rtlcss": "^4.0.9",
"prettier": "^2.5.1",
"prettier-plugin-tailwindcss": "^0.1.7",
"tailwind-scrollbar": "^2.0.1",
"tailwindcss": "^3.2.4",
"tailwindcss-themer": "^3.1.0",
"type-fest": "^4.3.3",
"typescript": "^4.6.4",
"vite": "^4.4.12",
"vite-plugin-checker": "^0.5.6",
"vite-plugin-package-version": "^1.0.2",
"vite-plugin-pwa": "^0.16.5",
"vite-plugin-static-copy": "^0.16.0",
"vitest": "^0.28.5"
"prettier": "^3.1.1",
"prettier-plugin-tailwindcss": "^0.5.9",
"rollup-plugin-visualizer": "^5.11.0",
"tailwind-scrollbar": "^3.0.5",
"tailwindcss": "^3.4.0",
"tailwindcss-themer": "^4.0.0",
"type-fest": "^4.8.3",
"typescript": "^5.3.3",
"vite": "^5.0.10",
"vite-plugin-checker": "^0.6.2",
"vite-plugin-package-version": "^1.1.0",
"vite-plugin-pwa": "^0.17.4",
"vite-plugin-static-copy": "^1.0.0",
"vitest": "^1.1.0"
},
"pnpm": {
"overrides": {

3687
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,18 @@
import ar from "@/assets/locales/ar.json";
import cs from "@/assets/locales/cs.json";
import de from "@/assets/locales/de.json";
import en from "@/assets/locales/en.json";
import fr from "@/assets/locales/fr.json";
import he from "@/assets/locales/he.json";
import it from "@/assets/locales/it.json";
import lv from "@/assets/locales/lv.json";
import minion from "@/assets/locales/minion.json";
import ne from "@/assets/locales/ne.json";
import nl from "@/assets/locales/nl.json";
import pirate from "@/assets/locales/pirate.json";
import pl from "@/assets/locales/pl.json";
import sv from "@/assets/locales/sv.json";
import th from "@/assets/locales/th.json";
import tr from "@/assets/locales/tr.json";
import vi from "@/assets/locales/vi.json";
import zh from "@/assets/locales/zh.json";
@@ -28,7 +32,11 @@ export const locales = {
sv,
pirate,
minion,
lv,
th,
ne,
ar,
};
export type Locales = keyof typeof locales;
export const rtlLocales: Locales[] = ["he"];
export const rtlLocales: Locales[] = ["he", "ar"];

420
src/assets/locales/ar.json Normal file
View File

@@ -0,0 +1,420 @@
{
"about": {
"description": "movie-web هو تطبيق ويب يبحث في الإنترنت عن بثوث. يهدف الفريق إلى تبني نهج معظمه بسيط في استهلاك المحتوى.",
"faqTitle": "الأسئلة الشائعة",
"q1": {
"body": "لا يستضيف movie-web أي محتوى. عندما تنقر فوق شيء للمشاهدة، يتم البحث على الإنترنت عن الوسائط المحددة (يمكنك رؤية المصدر الذي تستخدمه على شاشة التحميل وفي علامة تبويب \"مصادر الفيديو\"). لا يتم رفع الوسائط أبدًا عن طريق movie-web، كل شيء يتم من خلال آلية البحث هته.",
"title": "من أين يأتي المحتوى؟"
},
"q2": {
"body": "لا يمكن طلب عرض أو فيلم، لأن movie-web لا يدير أي محتوى. يتم مشاهدة جميع المحتويات من خلال مصادر على الإنترنت.",
"title": "أين يمكنني طلب مسلسل أو فلم؟"
},
"q3": {
"body": "نتائج البحث لدينا مدعومة بقاعدة بيانات الأفلام (TMDB) وتظهر بغض النظر عما إذا كانت مصادرنا تتضمن فعليًا المحتوى أم لا.",
"title": "تعرِضُ نتائجُ البحثِ الخاصِّ بالعرض أو الفيلم اللذي أريد، لكن لماذا لا يمكنني تشغيله؟"
},
"title": "حول movie-web"
},
"actions": {
"copied": "تم النسخ",
"copy": "نسخ"
},
"auth": {
"createAccount": "ليس لديك حساب بعد؟ <0>أنشئ حسابًا.</0>",
"deviceNameLabel": "اسم الجهاز",
"deviceNamePlaceholder": "الهاتف الشخصي",
"generate": {
"description": "جملة مروركَ هي بمثابة اسم مستخدمٍ وكلمة مرورٍ. تأكد من حفظها بشكل آمن، ستحتاجها لتسجيل الدخول إلى حسابك",
"next": "لقد قمتُ بحفظ جملة مروري",
"title": "جملة مرورك"
},
"hasAccount": "لديك حساب بالفعل؟ <0>قم بتسجيل الدخول هنا.</0>",
"login": {
"description": "رجاءً جملة المرور لتسجيل الدخول إلى حسابك",
"deviceLengthError": "رجاءً أدخل اسم جهاز",
"passphraseLabel": "جملة مرور من 12 كلمة",
"passphrasePlaceholder": "جملة مرور",
"submit": "تسجيل الدخول",
"title": "تسجيل الدخول إلى حسابك",
"validationError": "جملة المرور خاطئة أو ناقصة"
},
"register": {
"information": {
"color1": "لون الملف الشخصي الأول",
"color2": "لون الملف الشخصي الثاني",
"header": "أدخل اسمًا لجهازك واختر ألوانًا وأيقونة مستخدم حسب اختيارك",
"icon": "أيقونة المستخدم",
"next": "التالي",
"title": "معلومات الحساب"
}
},
"trust": {
"failed": {
"text": "هل قمت بضبطه بشكل صحيح؟",
"title": "تعَذَّر الوصول إلى الخادم"
},
"host": "أنت على وشك الاتصال بـ <0>{{hostname}}</0> - يرجى تأكيد أنك تثق فيه قبل إنشاء حساب",
"no": "عودة",
"title": "هل تثِقُ في هذا الخادم؟",
"yes": "أثِقُ في هذا الخادم"
},
"verify": {
"description": "رجاءً أدخل جملة المرور السابقة لتأكيد أنك قد قمت بحفظها ولإنشاء حسابك",
"invalidData": "البيانات غير صالحة",
"noMatch": "جملة المرور غير متطابقة",
"passphraseLabel": "جملة مرورك المكونة من 12 كلمة",
"recaptchaFailed": "فشل التحقق من ReCaptcha",
"register": "إنشاء حساب",
"title": "قم بتأكيد جملة مرورك"
}
},
"errors": {
"badge": "تم كسره",
"details": "تفاصيل الخطأ",
"reloadPage": "أعد تحميل الصفحة",
"showError": "عرض تفاصيل الخطأ",
"title": "واجهنا خطأ!"
},
"footer": {
"legal": {
"disclaimer": "تنويه",
"disclaimerText": "لا يستضيف movie-web أي ملفات، بل يقوم بالربط مع خدمات طرف ثالث. يجب معالجة المشاكل القانونية مع مضيفي الملفات والمزودين. لا يتحمل movie-web مسؤولية أي ملفات يعرضها مزودو الفيديو."
},
"links": {
"discord": "ديسكورد",
"dmca": "DMCA",
"github": "غيت هاب"
},
"tagline": "شاهد عروضك وأفلامك المفضلة باستخدام تطبيق البث مفتوح المصدر هذا."
},
"global": {
"name": "movie-web",
"pages": {
"about": "حَول",
"dmca": "DMCA",
"login": "تسجيل الدخول",
"pagetitle": "\"{{title}} - movie-web\"",
"register": "إنشاء حساب",
"settings": "الإعدادات"
}
},
"home": {
"bookmarks": {
"sectionTitle": "علامات مرجعية"
},
"continueWatching": {
"sectionTitle": "متابعة المشاهدة"
},
"mediaList": {
"stopEditing": "إنهاء التعديل"
},
"search": {
"allResults": "هذا كل ما لدينا!",
"failed": "تعذر العثور على الوسائط، حاول مجددا!",
"loading": "جار التحميل...",
"noResults": "لم نتمكن من العثور على أي شيء!",
"placeholder": "ماذا تريد أن تشاهد؟",
"sectionTitle": "نتائج البحث"
},
"titles": {
"day": {
"default": "ماذا تريد أن تشاهد في هته الظهيرة؟"
},
"morning": {
"default": "ماذا تريد أن تشاهد في هذا الصباح؟",
"extra": [
"سمعت أن فلم \"Before Sunrise\" جيد"
]
},
"night": {
"default": "ماذا تريد أن تشاهد في هته الليلة؟",
"extra": [
"مُرهَق؟ سمعت أن فيلم \"The Exorcist\" جيد."
]
}
}
},
"media": {
"episodeDisplay": "\"S{{season}} E{{episode}}\"",
"types": {
"movie": "فِلم",
"show": "سِلسلة"
}
},
"navigation": {
"banner": {
"offline": "تحقق من اتصالك بالأنترنت"
},
"menu": {
"about": "عنا",
"donation": "تبرع",
"logout": "تسجيل الخروج",
"register": "مزامنة إلى االتخزين لسحابي",
"settings": "الإعدادات",
"support": "الدعم"
}
},
"notFound": {
"badge": "صفحة غير موجودة",
"goHome": "عودة",
"message": "بحثنا في كل مكان: في الخزانة وحتى تحت الصناديق، ومع ذلك لم نعثر عن الصفحة التي طلبتَها.",
"title": "تعذر العثور على هته الصفحة"
},
"overlays": {
"close": "إغلاق"
},
"player": {
"back": {
"default": "عودة",
"short": "عُدْ"
},
"casting": {
"enabled": "جارٍ العرض على الجهاز..."
},
"menus": {
"captions": {
"customChoice": "اختيار ترجمة من ملف",
"customizeLabel": "تخصيص",
"offChoice": "إيقاف",
"settings": {
"delay": "تأخير الترجمة",
"fixCapitals": "تصحيح التهجئة"
},
"title": "مقاطع الترجمة",
"unknownLanguage": "غير معروف"
},
"downloads": {
"disclaimer": "يتم أخذ التنزيلات مباشرةً من قبل مقدم الخدمة. movie-web لاسيطرة له على كيفية توفير تلك التنزيلات.",
"downloadCaption": "تنزيل مقاطع الترجمة الحالية",
"downloadVideo": "تنزيل الفيديو",
"hlsExplanation": "هذا الوسيط هو بثٌ مباشر من نوع HTTP، لذا لايمكن تنزيله من movie-web.",
"onAndroid": {
"1": "للتنزيل على أندرويد، أنقر زر التنزيل وفي الصفحة الموالية <bold>إضغط باستمرار</bold> على الفيديو، وبعدها اختر <bold>حفظ</bold>.",
"shortTitle": "تنزيل / Android",
"title": "تنزيل على Android"
},
"onIos": {
"1": "للتنزيل على نظام iOS، انقر على زر التنزيل ثم، على الصفحة الجديدة، انقر على <bold><ios_share /></bold>، ثم <bold>حفظ إلى الملفات <ios_files /></bold>.",
"shortTitle": "تنزيل / iOS",
"title": "تنزيل على iOS"
},
"onPc": {
"1": "على الحاسوب، انقر على زر التنزيل، ثم على الصفحة الجديدة، انقر بزر الفأرة الأيمن على الفيديو وحدد <bold>حفظ الفيديو باسم</bold>",
"shortTitle": "تنزيل / حاسوب",
"title": "تنزيل على الحاسوب"
},
"title": "تنزيل"
},
"episodes": {
"button": "الحلقات",
"emptyState": "لا توجد حلقات في هذا الموسم، يرجى التحقق لاحقًا!",
"episodeBadge": "E{{episode}}",
"loadingError": "خطأ في تحميل الموسم",
"loadingList": "تحميل...",
"loadingTitle": "تحميل..."
},
"playback": {
"speedLabel": "سرعة التشغيل",
"title": "إعدادات التشغيل"
},
"quality": {
"automaticLabel": "جودة تلقائية",
"hint": "يمكنك محاولة <0>تغيير المصدر</0> للحصول على خيارات جودة مختلفة.",
"iosNoQuality": "نظرًا للقيود المحددة من قبل Apple، خيارات الجودة غير متوفرة على iOS لهذا المصدر. يمكنك محاولة <0>التبديل إلى مصدر آخر</0> للحصول على خيارات جودة مختلفة.",
"title": "جودة"
},
"settings": {
"captionItem": "إعدادات الترجمة",
"downloadItem": "تنزيل",
"enableCaptions": "تفعيل مقاطع الترجمة",
"experienceSection": "تجربة المشاهدة",
"playbackItem": "إعدادات التشغيل",
"qualityItem": "جودة",
"sourceItem": "مصادر الفيديو",
"videoSection": "إعدادات الفيديو"
},
"sources": {
"failed": {
"text": "حدثت خطأ أثناء محاولة إيجاد عن أي فيديو، يرجى تجربة مصدر آخر.",
"title": "فشلت عملية الاستخراج"
},
"noEmbeds": {
"text": "تعذر العثور على أي تضمينات، يرجى تجربة مصدر آخر.",
"title": "لم يتم العثور على تضمينات"
},
"noStream": {
"text": "هذا المصدر ليس فيه بث لهذا الفلم أو المسلسل.",
"title": "لا بث"
},
"title": "مصادر",
"unknownOption": "مجهول"
}
},
"metadata": {
"failed": {
"badge": "فشل",
"homeButton": "عُدْ للصفحة الرئيسية",
"text": "تعذر تحميل البيانات الوصفية للوسائط من قاعدة البيانات TMDB. يرجى التحقق مما إذا كانت TMDB غير متاحة أو محظورة على اتصال الإنترنت الخاص بك.",
"title": "فشل في تحميل البيانات الوصفية"
},
"notFound": {
"badge": "غير موجود",
"homeButton": "عُدْ للصفحة الرئيسية",
"text": "لم نتمكن من العثور على الوسيط الذي طلبته.",
"title": "تعذر إيجاد هذا الوسيط."
}
},
"nextEpisode": {
"cancel": "إلغاء",
"next": "الحلقة الموالية"
},
"playbackError": {
"badge": "خطأ في التشغيل",
"errors": {
"errorAborted": "تم إلغاء جلب الوسائط بناءً على طلب المستخدم.",
"errorDecode": "رغم كونها قابلة للتشغيل سابقا، أحد الأخطاء على مستوى فك ترميز الوسائط، أدى إلى فشل عملية التشغيل.",
"errorGenericMedia": "حدث خطأ مجهول متصل بالوسائط.",
"errorNetwork": "أحد الأخطاء المتصلة بالشبكة، تسبب في تعذرِ جلبِ الوسائط، على الرغم من توفرها سابقا.",
"errorNotSupported": "هذا الوسيط أو مصدره غير مدعوم."
},
"homeButton": "عُدْ للصفحة الرئيسية",
"text": "حدث خطأٌ أثناء محاولة تشغيل الوسائط. رجاءً حاول مرة أخرى.",
"title": "فشِلَ تشغيلُ الفيديو!"
},
"scraping": {
"items": {
"failure": "حدث خطأ",
"notFound": "لا يحتوي على اي فيديو",
"pending": "جارٍ التحققُ من وجود فيديوهات..."
},
"notFound": {
"badge": "غير موجود",
"detailsButton": "عرض التفاصيل",
"homeButton": "عُدْ للصفحة الرئيسية",
"text": "بحثنا عن طريقِ مُزودينا ولم نعثر على ما كنتَ تبحثُ عنه! نحن لانستضيف الوسائط ولسنا المتحكمين فيما هو متاح. رجاءً انقر على 'عرض التفاصيل' أسفله لمزيد من المعلومات.",
"title": "لم نتمكن من العثور على ذلك"
}
},
"time": {
"regular": "{{timeWatched}} / {{duration}}",
"remaining": "باقٍ {{timeLeft}} • سينتهي مع {{timeFinished, datetime}}",
"shortRegular": "{{timeWatched}}",
"shortRemaining": "-{{timeLeft}}"
}
},
"screens": {
"dmca": {
"text": "مرحبًا بكم في صفحة movie-web الخاصة بالتواصل حول قانون الألفية للملكية الرقمية (DMCA) ! نحن نحترم حقوق الملكية الفكرية ونرغب في التعامل بسرعة مع أي مسألة تخص حقوق الطبع والنشر. إن كنت تعتقد أن أيا من أعمالك المحميو بحقوق الطبع والنشر قد تم استخدامها بشكل غير لائق على منصتنا، رجاءً أرسل إشعارا مفصلا إلى البريد الإلكتروني أدناه. يرجى تضمين وصف للمواد المحمية بحقوق الطبع والنشر، وكذا طريقةً للتواصل معك، إضافة إلى تصريح بمصداقية طلبك. نحن ملتزمون بحل هذه القضايا بسرعة ونقدر تعاونكم في الحفاظ على movie-web كمكان يحترم الإبداع وحقوق الطبع والنشر.",
"title": "DMCA"
},
"loadingApp": "جار تحميل التطبيق",
"loadingUser": "جارٍ تحميل ملفك الشخصي",
"loadingUserError": {
"logout": "تسجيل الخروج",
"reset": "إعادة تعيين الخادم المخصص",
"text": "فشل تحميل ملفك الشخصي",
"textWithReset": "فشل تحميل ملفك الشخصي من خادمك المخصص، هل ترغب في العودة إلى الخادم الافتراضي؟"
},
"migration": {
"failed": "فشلت عملية ترحيل بياناتك.",
"inProgress": "يرجى الانتظار، نقوم بترحيل بياناتك. لن يستغرق ذلك وقتًا طويلاً."
}
},
"settings": {
"account": {
"accountDetails": {
"deviceNameLabel": "اسم الجهاز",
"deviceNamePlaceholder": "الهاتف الشخصي",
"editProfile": "تعديل",
"logoutButton": "تسجيل الخروج"
},
"actions": {
"delete": {
"button": "حذف الحساب",
"confirmButton": "حذف الحساب",
"confirmDescription": "هل أنت متأكد أنك تريد حذف حسابك؟ ستفقد جميع بياناتك!",
"confirmTitle": "هل أنت متأكد؟",
"text": "هذا الإجراء لا يمكن التراجع عنه. سيتم حذف جميع البيانات ولا يمكن استعادتها.",
"title": "حذف الحساب"
},
"title": "الإجراءات"
},
"devices": {
"deviceNameLabel": "اسم الجهاز",
"failed": "فشل تحميل الجلسات",
"removeDevice": "إزالة",
"title": "الأجهزة"
},
"profile": {
"finish": "إنهاء التعديل",
"firstColor": "لون الملف الشخصي الأول",
"secondColor": "لون الملف الشخصي الثاني",
"title": "تعديل صورة الملف الشخصي",
"userIcon": "أيقونة المستخدم"
},
"register": {
"cta": "ابدأ",
"text": "شارك تقدم مشاهدتك بين الأجهزة وحافظ على تزامنها.",
"title": "مزامنة إلى االتخزين لسحابي"
},
"title": "حساب"
},
"appearance": {
"activeTheme": "نَشِط",
"themes": {
"blue": "أزرق",
"default": "افتراضي",
"gray": "رمادي",
"red": "أحمر",
"teal": "تركواز"
},
"title": "المظهر"
},
"captions": {
"backgroundLabel": "تعتيم الخلفية",
"colorLabel": "لون",
"previewQuote": "يجب ألا أخاف. الخوف هو قاتل العقل.",
"textSizeLabel": "حجم النص",
"title": "مقاطع الترجمة"
},
"connections": {
"server": {
"description": "إذا كنت ترغب في الاتصال بخادم خلفي مخصص لتخزين بياناتك، قم بتفعيل هذا ووفر الرابط.",
"label": "خادم مُخصص",
"urlLabel": "رابط الخادم المخصص"
},
"title": "الاتصالات",
"workers": {
"addButton": "إضافة وكيل جديد",
"description": "لتشغيل التطبيق، يتم توجيه كل الاتصالات عبر وكلاء البروكسي. قم بتمكين هذا إذا كنت ترغب في استخدام خوادم العمل الخاصة بك.",
"emptyState": "لا يوجد وكلاء حتى الآن، أضف واحدًا أدناه",
"label": "استخدام وكلاء مُخصصين",
"urlLabel": "روابط الوكلاء",
"urlPlaceholder": "https://"
}
},
"locale": {
"language": "لغة التطبيق",
"languageDescription": "اللغة المطبقة على كامل التطبيق.",
"title": "اللغة"
},
"reset": "إعادة تعيين",
"save": "حفظ",
"sidebar": {
"info": {
"appVersion": "إصدار التطبيق",
"backendUrl": "رابط النهاية الخلفية",
"backendVersion": "إصدار النهاية الخلفية",
"hostname": "اسم المضيف",
"insecure": "غير آمن",
"notLoggedIn": "أنت لم تسجل دخولك بعد",
"secure": "آمن",
"title": "معلومات التطبيق",
"unknownVersion": "غير معروف",
"userId": "معرِّف المستخدم"
}
},
"unsaved": "لديك تغييرات غير محفوظة"
}
}

View File

@@ -1,71 +1,421 @@
{
"global": {
"name": "movie-web"
"about": {
"description": "movie-web je webová aplikace, která vyhledává na internetu proudy médií. Cílem týmu je převážně minimalistický přístup ke konzumaci obsahu.",
"faqTitle": "Často kladené otázky",
"q1": {
"body": "movie-web nehostuje žádný obsah. Když kliknete na něco, co chcete sledovat, na internetu se vyhledá vybrané médium (Na obrazovce načítání a na kartě 'zdroje videa' můžete vidět, který zdroj používáte). Média se nikdy nenahrávají movie-webem, vše probíhá prostřednictvím tohoto vyhledávacího mechanismu.",
"title": "Kde bereme obsah?"
},
"home": {
"search": {
"allResults": "To je vše co máme!",
"sectionTitle": "Výsledky vyhledávání",
"noResults": "Nemohli jsme nic najít!",
"failed": "Nepodařilo se najít média, zkuste to znovu!",
"loading": "Načítání...",
"placeholder": "Co si přejete sledovat?"
},
"bookmarks": {
"sectionTitle": "Záložky"
},
"continueWatching": {
"sectionTitle": "Pokračujte ve sledování"
}
"q2": {
"body": "Není možné požádat o pořad nebo film, movie-web nespravuje žádný obsah. Veškerý obsah je prohlížen prostřednictvím zdrojů na internetu.",
"title": "Kde můžu požádat o pořad nebo film?"
},
"media": {
"types": {
"movie": "Film",
"show": "Seriál"
},
"episodeDisplay": "S{{season}} E{{episode}}"
"q3": {
"body": "Naše výsledky vyhledávání jsou založeny na The Movie Database (TMDB) a zobrazují se bez ohledu na to, zda naše zdroje skutečně obsah mají.",
"title": "Ve výsledcích vyhledávání se zobrazuje pořad nebo film, proč jej nemůžu přehrát?"
},
"player": {
"playbackError": {
"title": "Jejda, rozbilo se to!"
},
"metadata": {
"notFound": {
"badge": "Nenalezeno",
"homeButton": "Zpátky domů",
"title": "Nemohli jsme najít Vaše média.",
"text": "Nemohli jsme najít média o které jste požádali. Buďto jsme ho nemohli najít, nebo jste manipulovali s URL."
}
},
"menus": {
"captions": {
"customChoice": "Nahrát titulky",
"customizeLabel": "Upravit",
"title": "Titulky"
},
"sources": {
"title": "Zdroje"
},
"episodes": {
"button": "Epizody",
"loadingTitle": "Načítání...",
"loadingList": "Načítání..."
}
},
"back": {
"default": "Zpátky domů",
"short": "Zpět"
}
"title": "O movie-webu"
},
"actions": {
"copied": "Zkopírováno",
"copy": "Zkopírovat"
},
"auth": {
"createAccount": "Ještě nemáte účet? <0>Vytvořte si účet.</0>",
"deviceNameLabel": "Název zařízení",
"deviceNamePlaceholder": "Osobní telefon",
"generate": {
"description": "Vaše přístupová fráze se chová jako vaše přezdívka a heslo. Uchovejte jí v bezpečí, protože jí budete muset zadat, abyste se mohli přihlásit ke svému účtu",
"next": "Uložil jsem si moji přístupovou frázi",
"passphraseFrameLabel": "Přístupová fráze",
"title": "Vaše přístupová fráze"
},
"notFound": {
"badge": "Nenalezeno",
"goHome": "Zpátky domů",
"title": "Tuto stránku se nepodařilo najít",
"message": "Dívali jsme se všude: pod koši, ve skříni, za proxy, ale nakonec jsme nemohli najít stránku, kterou hledáte."
"hasAccount": "Již máte účet? <0> Přihlaste se zde.</0>",
"login": {
"description": "Pro přihlášení ke svému účtu zadejte svou přístupovou frázi",
"deviceLengthError": "Zadejte název zařízení",
"passphraseLabel": "12slovná přístupová fráze",
"passphrasePlaceholder": "Přístupová fráze",
"submit": "Přihlásit",
"title": "Přihlaste se ke svému účtu",
"validationError": "Nesprávná nebo neúplná přístupová fráze"
},
"navigation": {
"banner": {
"offline": "Zkontrolujte své internetové připojení"
}
"register": {
"information": {
"color1": "První barva profilu",
"color2": "Druhá barva profilu",
"header": "Zadejte název pro vaše zařízení a vyberte barvy a ikonu uživatele podle vašeho výběru",
"icon": "Ikona uživatele",
"next": "Další",
"title": "Informace o účtu"
}
},
"trust": {
"failed": {
"text": "Nastavili jste to správně?",
"title": "Selhalo připojení k serveru"
},
"host": "Připojujete se k <0>{{hostname}}</0> - potvrďte, že mu věříte před vytvořením účtu",
"no": "Zpět",
"title": "Věříte tomuto serveru?",
"yes": "Věřím tomuto serveru"
},
"verify": {
"description": "Zadejte prosím svou přístupovou frázi, abyste potvrdili, že jste si ji uložili, a vytvořte si účet",
"invalidData": "Data nejsou platná",
"noMatch": "Přístupová fráze neodpovídá",
"passphraseLabel": "Vaše 12slovná přístupová fráze",
"recaptchaFailed": "ReCaptcha ověření se nezdařilo",
"register": "Založit účet",
"title": "Potvrďte vaši přístupovou frázi"
}
},
"errors": {
"badge": "Rozbilo se to",
"details": "Detaily chyby",
"reloadPage": "Znovu načíst stránku",
"showError": "Ukázat detaily chyby",
"title": "Narazili jsme na chybu!"
},
"footer": {
"legal": {
"disclaimer": "Zřeknutí odpovědnosti",
"disclaimerText": "movie-web nehostuje žádné soubory, pouze odkazuje na služby třetích stran. Právní záležitosti by měly být řešeny s hostiteli souborů a poskytovateli. movie-web nenese odpovědnost za žádné mediální soubory zobrazené poskytovateli videa."
},
"links": {
"discord": "Discord",
"dmca": "DMCA",
"github": "GitHub"
},
"tagline": "Sledujte své oblíbené pořady a filmy s touto aplikací pro streamování s otevřeným zdrojovým kódem."
},
"global": {
"name": "movie-web",
"pages": {
"about": "O nás",
"dmca": "DMCA",
"login": "Přihlásit se",
"pagetitle": "{{title}} - movie-web",
"register": "Zaregistrovat se",
"settings": "Nastavení"
}
},
"home": {
"bookmarks": {
"sectionTitle": "Záložky"
},
"continueWatching": {
"sectionTitle": "Pokračujte ve sledování"
},
"mediaList": {
"stopEditing": "Přestat upravovat"
},
"search": {
"allResults": "To je vše co máme!",
"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?",
"sectionTitle": "Výsledky vyhledávání"
},
"titles": {
"day": {
"default": "Na co byste se chtěli dnes odpoledne dívat?"
},
"morning": {
"default": "Na co byste se chtěli dnes ráno dívat?",
"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."
]
}
}
},
"media": {
"episodeDisplay": "S{{season}} E{{episode}}",
"types": {
"movie": "Film",
"show": "Seriál"
}
},
"navigation": {
"banner": {
"offline": "Zkontrolujte své internetové připojení"
},
"menu": {
"about": "O nás",
"donation": "Přispět",
"logout": "Odhlásit se",
"register": "Synchronizovat do cloudu",
"settings": "Nastavení",
"support": "Podpořte nás"
}
},
"notFound": {
"badge": "Nenalezeno",
"goHome": "Zpátky domů",
"message": "Dívali jsme se všude: pod koši, ve skříni, za proxy, ale nakonec jsme nemohli najít stránku, kterou hledáte.",
"title": "Tuto stránku se nepodařilo najít"
},
"overlays": {
"close": "Zavřít"
},
"player": {
"back": {
"default": "Zpátky domů",
"short": "Zpět"
},
"casting": {
"enabled": "Odesílání do zařízení..."
},
"menus": {
"captions": {
"customChoice": "Nahrát titulky ze souboru",
"customizeLabel": "Přizpůsobit",
"offChoice": "Vypnuto",
"settings": {
"delay": "Posunutí titulků",
"fixCapitals": "Opravit velká písmena"
},
"title": "Titulky",
"unknownLanguage": "Neznámo"
},
"downloads": {
"disclaimer": "Stahování probíhá přímo u poskytovatele. movie-web nemá kontrolu nad tím, jak jsou stahování poskytovány.",
"downloadCaption": "Stáhnout titulky",
"downloadVideo": "Stáhnout video",
"hlsExplanation": "Toto médium je proud HLS, který nelze stáhnout na movie-web.",
"onAndroid": {
"1": "Na Androidu klikněte na tlačítko stahování, poté na nové stránce <bold>klepněte a podržte</bold> na videu a poté vyberte <bold>uložit</bold>.",
"shortTitle": "Stahování / Android",
"title": "Stahování na Androidu"
},
"onIos": {
"1": "Na iOS klikněte na tlačítko stahování a poté na nové stránce klikněte na <bold><ios_share /></bold> a poté na <bold>Uložit do souborů <ios_files /></bold>.",
"shortTitle": "Stahování / iOS",
"title": "Stahování na iOS"
},
"onPc": {
"1": "Na počítači klikněte na tlačítko stahování, poté na nové stránce klikněte pravým tlačítkem na video a vyberte <bold>Uložit video jako</bold>",
"shortTitle": "Stahování / počítač",
"title": "Stahování na počítači"
},
"title": "Stáhnout"
},
"episodes": {
"button": "Epizody",
"emptyState": "V této sezóně nejsou žádné epizody, vraťte se později!",
"episodeBadge": "E{{episode}}",
"loadingError": "Chyba při načítání sezóny",
"loadingList": "Načítání...",
"loadingTitle": "Načítání..."
},
"playback": {
"speedLabel": "Rychlost přehrávání",
"title": "Nastavení přehrávání"
},
"quality": {
"automaticLabel": "Automatická kvalita",
"hint": "Chcete-li získat jinou kvalitu, můžete zkusit <0>přepnout zdroj</0>.",
"iosNoQuality": "Kvůli omezením definovaným společností Apple není pro tento zdroj v iOS k dispozici výběr kvality. Chcete-li získat jinou kvalitu, můžete zkusit <0>přepnout zdroj</0>.",
"title": "Kvalita"
},
"settings": {
"captionItem": "Nastavení titulků",
"downloadItem": "Stáhnout",
"enableCaptions": "Povolit titulky",
"experienceSection": "Zážitek sledování",
"playbackItem": "Nastavení přehrávání",
"qualityItem": "Kvalita",
"sourceItem": "Zdroje videa",
"videoSection": "Nastavení videa"
},
"sources": {
"failed": {
"text": "Při pokusu o nalezení videí došlo k chybě. Zkuste prosím jiný zdroj.",
"title": "Nepodařilo se extrahovat data"
},
"noEmbeds": {
"text": "Nepodařilo se nám najít žádný vklad, zkuste prosím jiný zdroj.",
"title": "Žádné vklady"
},
"noStream": {
"text": "Tento zdroj nemá pro tento film nebo pořad žádné proudy média.",
"title": "Žádný proud média"
},
"title": "Zdroje",
"unknownOption": "Neznámý"
}
},
"metadata": {
"failed": {
"badge": "Neúspěšný",
"homeButton": "Jít domů",
"text": "Nelze načíst metadata média z TMDB. Zkontrolujte, zda není TMDB nefunkční nebo blokovaný na vašem internetovém připojení.",
"title": "Načtení metadat se nezdařilo"
},
"notFound": {
"badge": "Nenalezeno",
"homeButton": "Zpátky domů",
"text": "Nemohli jsme najít média o které jste požádali. Buď bylo odstraňeno, nebo jste manipulovali s URL.",
"title": "Nemohli jsme najít Vaše média."
}
},
"nextEpisode": {
"cancel": "Zrušit",
"next": "Další epizoda"
},
"playbackError": {
"badge": "Chyba přehrávání",
"errors": {
"errorAborted": "Načítání média bylo přerušeno uživatelem.",
"errorDecode": "Navzdory tomu, že bylo dříve určeno jako použitelné došlo při pokusu o dekódování média k chybě.",
"errorGenericMedia": "Nastala chyba neznámého média.",
"errorNetwork": "Nastala nějaká chyba síťě, která zabránila načtení média, přestože bylo předtím dostupné.",
"errorNotSupported": "Médium nebo poskytovatel média není podporovaný."
},
"homeButton": "Jít domů",
"text": "Nastala chyba při přehrávání média. Prosíme skuste to znovu.",
"title": "Video se nepodařilo přehrát!"
},
"scraping": {
"items": {
"failure": "Nastala chyba",
"notFound": "Nemá toto video",
"pending": "Ověřování videí..."
},
"notFound": {
"badge": "Nenalezeno",
"detailsButton": "Zobrazit podrobnosti",
"homeButton": "Jít domů",
"text": "Prohledali jsme naše poskytovatele a nenašli jsme média, která hledáte! Nehostujeme žádné média a nemáme žádnou kontrolu nad tím, co je k dispozici. Pro více podrobností klikněte níže na 'Zobrazit podrobnosti'.",
"title": "Nedokázali jsme to najít"
}
},
"time": {
"regular": "{{timeWatched}} / {{duration}}",
"remaining": "{{timeLeft}} zbývá • Dokončeno v {{timeFinished, datetime}}",
"shortRegular": "{{timeWatched}}",
"shortRemaining": "-{{timeLeft}}"
}
},
"screens": {
"dmca": {
"text": "Vítejte na DMCA kontaktní stránce movie-webu! Respektujeme práva duševního vlastnictví a chceme rychle řešit jakékoli problémy s autorským právem. Pokud se domníváte, že vaše dílo chráněné autorskými právy bylo na naší platformě neoprávněně použito, zašlete prosím podrobné oznámení DMCA na níže uvedený e-mail. Uveďte prosím popis materiálu chráněného autorským právem, své kontaktní údaje a prohlášení o dobré víře. Jsme odhodláni tyto záležitosti rychle vyřešit a oceňujeme vaši spolupráci při udržování movie-webu jako místa, které respektuje kreativitu a autorská práva.",
"title": "DMCA"
},
"loadingApp": "Načítání aplikace",
"loadingUser": "Načítání vášeho profilu",
"loadingUserError": {
"logout": "Odhlásit se",
"reset": "Resetovat vlastní server",
"text": "Nezdařilo se načíst váš profil",
"textWithReset": "Nezdařilo se načíst váš profil z vašeho serveru, chcete ho přepnout na výchozí server?"
},
"migration": {
"failed": "Migrace dat se nezdařila.",
"inProgress": "Počkejte prosím, migrujeme vaše data. Nemělo by to trvat dlouho."
}
},
"settings": {
"account": {
"accountDetails": {
"deviceNameLabel": "Název zařízení",
"deviceNamePlaceholder": "Osobní telefon",
"editProfile": "Upravit",
"logoutButton": "Odhlásit se"
},
"actions": {
"delete": {
"button": "Smazat účet",
"confirmButton": "Smazat účet",
"confirmDescription": "Jste si jisti, že chcete smazat váš účet? Všechny data budou ztracena!",
"confirmTitle": "Jste si jisti?",
"text": "Tato akce nejde vrátit. Všechny data budou smazána a nic nepůjde zachránit.",
"title": "Smazat účet"
},
"title": "Akce"
},
"devices": {
"deviceNameLabel": "Název zařízení",
"failed": "Načtení relací se nezdařilo",
"removeDevice": "Odstranit",
"title": "Zařízení"
},
"profile": {
"finish": "Dokončit",
"firstColor": "První barva profilu",
"secondColor": "Druhá barva profilu",
"title": "Upravit profilovou fotografii",
"userIcon": "Ikona uživatele"
},
"register": {
"cta": "Začněte",
"text": "Sdílejte průběh sledování mezi zařízeními a udržujte je synchronizovaná.",
"title": "Synchronizace do cloudu"
},
"title": "Účet"
},
"appearance": {
"activeTheme": "Aktivní",
"themes": {
"blue": "Modrá",
"default": "Výchozí",
"gray": "Šedá",
"red": "Červená",
"teal": "Modrozelená"
},
"title": "Vzhled"
},
"captions": {
"backgroundLabel": "Neprůhlednost pozadí",
"colorLabel": "Barva",
"previewQuote": "Nesmím se bát. Strach je zabiják mysli.",
"textSizeLabel": "Velikost písma",
"title": "Titulky"
},
"connections": {
"server": {
"description": "Pokud se chcete připojit k vlastnímu backendu pr ukládání dat, povolte toto a zadejte URL adresu.",
"label": "Vlastní server",
"urlLabel": "URL adresa vlastního serveru"
},
"title": "Spojení",
"workers": {
"addButton": "Přidat nového pracovníka",
"description": "Aby byla aplikace funkční, veškerá trafika prochází přes proxy. Povolte toto, pokud chcete používat svoje vlastní pracovníky.",
"emptyState": "Zatím žádní pracovníci, přidej jednoho dolů",
"label": "Použít vlastní proxy pracovníky",
"urlLabel": "URL adresy pracovníků",
"urlPlaceholder": "https://"
}
},
"locale": {
"language": "Jazyk aplikace",
"languageDescription": "Jazyk použitý na celou aplikaci.",
"title": "Lokální"
},
"reset": "Resetovat",
"save": "Uložit",
"sidebar": {
"info": {
"appVersion": "Verze aplikace",
"backendUrl": "URL backendu",
"backendVersion": "Verze backendu",
"hostname": "Název hostitele",
"insecure": "nebezpečný",
"notLoggedIn": "Nejste přihlášen",
"secure": "bezpečný",
"title": "Informace o aplikaci",
"unknownVersion": "Neznámo",
"userId": "Uživatelské ID"
}
},
"unsaved": "Máte neuložené změny"
}
}

View File

@@ -27,6 +27,7 @@
"generate": {
"description": "Deine Passphrase dient als dein Nutzername und Passwort. Speiche sie sicher ab, damit du dich in deinem Konto anmelden kannst",
"next": "Ich habe meine Passphrase gespeichert",
"passphraseFrameLabel": "Passphrase",
"title": "Deine Passphrase"
},
"hasAccount": "Du hast bereits einen Account? <0>Anmelden.</0>",

View File

@@ -25,6 +25,7 @@
},
"generate": {
"title": "Your passphrase",
"passphraseFrameLabel": "Passphrase",
"next": "I have saved my passphrase",
"description": "Your passphrase acts as your username and password. Make sure to keep it safe as you will need to enter it to login to your account"
},

View File

@@ -7,11 +7,11 @@
"title": "D'où vient le contenu ?"
},
"q2": {
"body": "Il n'est pas possible de demander une émission ou un film, movie-web ne gère aucun contenu. Tous les contenus sont consultés par l'intermédiaire de sources sur Internet.",
"title": "Où puis-je demander un show ou un film ?"
"body": "Il est impossible de solliciter une émission ou un film car movie-web ne gère aucun contenu. Les sources sur Internet sont utilisées pour consulter tous les contenus.",
"title": "Où puis-je demander une série ou un film ?"
},
"q3": {
"body": "Nos résultats de recherche sont fournis par The Movie Database (TMDB) et s'affichent indépendamment du fait que nos sources possèdent ou non le contenu.",
"body": "Nos résultats de recherche sont disponibles dans The Movie Database (TMDB), indépendamment du fait que nos sources possèdent ou non le contenu.",
"title": "Les résultats de la recherche affichent l'émission ou le film, pourquoi ne puis-je pas le lire ?"
},
"title": "A propos de movie-web"
@@ -21,17 +21,18 @@
"copy": "Copier"
},
"auth": {
"createAccount": "Vous n'avez pas encore de compte ? <0>Créer un compte.</0>",
"createAccount": "N'avez-vous pas encore de compte? <0>Créer un compte.</0>",
"deviceNameLabel": "Nom de l'appareil",
"deviceNamePlaceholder": "Téléphone personnel",
"generate": {
"description": "Votre passphrase fait office de nom d'utilisateur et de mot de passe. Conservez-la précieusement, car vous devrez la saisir pour vous connecter à votre compte",
"description": "Le nom d'utilisateur et le mot de passe sont obtenus à partir de votre passphrase. Vous devrez la saisir pour accéder à votre compte, alors gardez-la précieusement",
"next": "J'ai sauvegardé ma passphrase",
"passphraseFrameLabel": "Pass phrase",
"title": "Votre passphrase"
},
"hasAccount": "Vous avez déjà un compte ? <0>Connectez-vous ici.</0>",
"hasAccount": "Avez-vous déjà un compte? <0>Connectez-vous ici.</0>",
"login": {
"description": "Veuillez entrer votre passphrase pour vous connecter à votre compte",
"description": "Veuillez fournir votre passphrase pour accéder à votre compte",
"deviceLengthError": "Veuillez saisir un nom d'appareil",
"passphraseLabel": "Passphrase de 12 mots",
"passphrasePlaceholder": "Passphrase",
@@ -54,9 +55,9 @@
"text": "L'avez-vous configuré correctement ?",
"title": "Échec de la connexion au serveur"
},
"host": "Vous vous connectez à <0>{{hostname}}</0> - veuillez confirmer que vous lui faites confiance avant de créer un compte.",
"host": "Vous vous connectez à <0>{{hostname}}</0> - veuillez confirmer que vous lui faites confiance avant de créer un compte",
"no": "Retour",
"title": "Faites-vous confiance à ce serveur ?",
"title": "Est-ce que vous avez confiance à ce serveur?",
"yes": "Je fais confiance à ce serveur"
},
"verify": {
@@ -79,14 +80,14 @@
"footer": {
"legal": {
"disclaimer": "Avertissement",
"disclaimerText": "movie-web n'héberge aucun fichier, il se contente de proposer des liens vers des services tiers. Les questions juridiques doivent être réglées avec les hébergeurs et les fournisseurs de fichiers. movie-web n'est pas responsable des fichiers multimédias diffusés par les fournisseurs de vidéos."
"disclaimerText": "Le site movie-web ne stocke pas de fichiers, mais propose des liens vers des services externes. Les problèmes juridiques doivent être traités avec les fournisseurs et les hébergeurs de fichiers. Les fichiers multimédias diffusés par les fournisseurs de vidéos ne sont pas couverts par movie-web."
},
"links": {
"discord": "Discord",
"dmca": "DMCA",
"github": "GitHub"
},
"tagline": "Regardez vos émissions et films préférés avec cette application de streaming open source."
"tagline": "Cette application de streaming open source vous permet de regarder vos émissions et films préférés."
},
"global": {
"name": "movie-web",

View File

@@ -27,9 +27,10 @@
"generate": {
"description": "ביטוי הסיסמה שלך משמש כשם המשתמש והסיסמה שלך. אנא הקפד לשמור אותו בטוח מכיוון שתצטרך להזין אותו כדי להתחבר לחשבון שלך",
"next": "אני שמרתי את משפט הסיסמה שלי",
"passphraseFrameLabel": "ביטוי סיסמה",
"title": "משפט הסיסמה שלך"
},
"hasAccount": "Already have an account? <0>Login here.</0>",
"hasAccount": "כבר יש לך חשבון? <0>התחבר כאן.</0>",
"login": {
"description": "אנא הזן את ביטוי הסיסמה שלך כדי להתחבר לחשבונך",
"deviceLengthError": "אנא הזן שם מכשיר",
@@ -92,7 +93,7 @@
"name": "movie-web",
"pages": {
"about": "אודות",
"dmca": "DMCA",
"dmca": "זכויות יוצרים",
"login": "התחבר",
"pagetitle": "{{title}} - movie-web",
"register": "הירשם",
@@ -220,8 +221,8 @@
},
"quality": {
"automaticLabel": "איכות אוטומטית",
"hint": "You can try <0>switching source</0> to get different quality options.",
"iosNoQuality": "Due to Apple-defined limitations, quality selection is not available on iOS for this source. You can try <0>switching to another source</0> to get different quality options.",
"hint": "אתה יכול לנסות <0>להחליף מקור</0> כדי לקבל אפשרויות איכות שונות.",
"iosNoQuality": "בשל מגבלות שהוגדרו על ידי אפל, בחירת איכות אינה זמינה ב-iOS עבור מקור זה. אתה יכול לנסות <0>לעבור למקור אחר</0> כדי לקבל אפשרויות איכות שונות.",
"title": "איכות"
},
"settings": {
@@ -229,7 +230,7 @@
"downloadItem": "הורד",
"enableCaptions": "אפשר כתוביות",
"experienceSection": "חווית צפייה",
"playbackItem": "הגדרות השמעה",
"playbackItem": "הגדרות ניגון",
"qualityItem": "איכות",
"sourceItem": "מקורות וידאו",
"videoSection": "הגדרות וידאו"
@@ -306,7 +307,7 @@
"screens": {
"dmca": {
"text": "ברוכה הבאה לדף יצירת קשר DMCA של movie-web! אנו מכבדים את זכויות הקניין הרוחני ורוצים לטפל בכל חשש לזכויות יוצרים במהירות. אם אתה סבור שהעבודה שלך המוגנת בזכויות יוצרים נוצלה בצורה לא נכונה בפלטפורמה שלנו, אנא שלח הודעת DMCA מפורטת למייל למטה. אנא כלול תיאור של החומר המוגן בזכויות יוצרים, פרטי ההתקשרות שלך והצהרת תום לב. אנו מחויבים לפתור את העניינים הללו באופן מיידי ומעריכים את שיתוף הפעולה שלך בשמירה על movie-web מקום שמכבד יצירתיות וזכויות יוצרים.",
"title": "DMCA"
"title": "זכויות יוצרים"
},
"loadingApp": "טוען את האפליקציה",
"loadingUser": "טוען את הפרופיל שלך",

420
src/assets/locales/lv.json Normal file
View File

@@ -0,0 +1,420 @@
{
"about": {
"description": "movie-web ir tīmekļa lietojumprogramma, kas internetā meklē straumes. Komandas mērķis ir galvenokārt minimālistiska pieeja satura patērēšanai.",
"faqTitle": "Bieži jautājumi",
"q1": {
"body": "Movie-web neveic nekādu saturu. Noklikšķinot uz kāda skatāma satura, internetā tiek meklēts atlasītais multivides saturs (ielādes ekrānā un cilnē “video avoti” varat redzēt, kuru avotu izmantojat). Multivide nekad netiek augšupielādēta, izmantojot filmu tīmekli, viss notiek caur šo meklēšanas mehānismu.",
"title": "No kurienes nāk saturs?"
},
"q2": {
"body": "Nav iespējams pieprasīt pārraidi vai filmu, filmu tīmeklis nepārvalda saturu. Viss saturs tiek skatīts, izmantojot avotus internetā.",
"title": "Kur es varu pieprasīt filmu vai seriālu?"
},
"q3": {
"body": "Mūsu meklēšanas rezultātus nodrošina filmu datu bāze (TMDB), un tie tiek rādīti neatkarīgi no tā, vai mūsu avotos patiešām ir saturs.",
"title": "Meklēšanas rezultātos tiek parādīta seriāls vai filma. Kāpēc es nevaru to atskaņot?"
},
"title": "Par movie-web"
},
"actions": {
"copied": "Nokopēts",
"copy": "Kopēt"
},
"auth": {
"createAccount": "Nav vēl konts? <0>Taisīt kontu.</0>",
"deviceNameLabel": "Ierīces nosaukums",
"deviceNamePlaceholder": "Personiskais telefons",
"generate": {
"description": "Tava paroles frāze ir kā vārds un parole. Esi drošs ka turi to drošibā jo tev vajadzēs to izmantot lai ieietu kontā",
"next": "Esmu saglabājis paroles frāzi",
"title": "Tava paroles frāze"
},
"hasAccount": "Tev jau ir konts> <0>Ienāc šeit.</0>",
"login": {
"description": "Lūdzu ievadi paroles frāzi lai ieietu kontā",
"deviceLengthError": "Lūdzu ievadi ierīces nosaukumu",
"passphraseLabel": "12-Vārdu paroles frāze",
"passphrasePlaceholder": "Paroles frāze",
"submit": "Ieiet",
"title": "Reģistrējies savā kontā",
"validationError": "Nepareizs vai nepabeigts paroles frāze"
},
"register": {
"information": {
"color1": "Profila krāsa viens",
"color2": "Profila krāsa divi",
"header": "Ievadi ierīces vārdu un izvēlies krāsu un profila ikonu kādu gribi",
"icon": "Profila ikona",
"next": "Nakamais",
"title": "Konta informācija"
}
},
"trust": {
"failed": {
"text": "Vai tu konfigurēji to pareizi?",
"title": "Neizdevās savienot serveri"
},
"host": "Tu piesaisties <0>{{hostname}}</0> - apstiprini ka uzticies pirms taisi kontu",
"no": "Atpakaļ",
"title": "Vai tu uzticies šim serverim?",
"yes": "Es uzticos šim serverim"
},
"verify": {
"description": "Lūdzu ievadi paroles frāzi ko ieguvi iepriekš lai apstiprinātu ka saglabāji un uztaisītu kontu",
"invalidData": "Dati nav pieejami",
"noMatch": "paroles frāze nesakrīt",
"passphraseLabel": "Tava 12-vārdu paroles frāze",
"recaptchaFailed": "ReCaptcha apstiprināšana neizdevās",
"register": "Izveidot kontu",
"title": "Apstiprini paroles frāzi"
}
},
"errors": {
"badge": "Saplīsa",
"details": "Kļūdas deteļas",
"reloadPage": "Atjaunot lapu",
"showError": "Radīt kļūdas deteļas",
"title": "Mēs sastapāmies ar kļūdu!"
},
"footer": {
"legal": {
"disclaimer": "Atruna",
"disclaimerText": "movie-web nemitina nekādus failus, tas tikai veido saites uz trešās puses pakalpojumiem. Juridiskie jautājumi ir jārisina ar failu resursdatoriem un nodrošinātājiem. movie-web nav atbildīgs par video pakalpojumu sniedzēju parādītajiem multivides failiem."
},
"links": {
"discord": "Discord",
"dmca": "Autortiesību likums",
"github": "GitHub"
},
"tagline": "Skatieties savas iecienītākās pārraides un filmas, izmantojot šo atvērtā koda straumēšanas lietotni."
},
"global": {
"name": "Filmas-web",
"pages": {
"about": "Par",
"dmca": "Autortiesību likums",
"login": "Ieiet",
"pagetitle": "{{title}} - filmas-web",
"register": "Reģistrēties",
"settings": "Iestādijumi"
}
},
"home": {
"bookmarks": {
"sectionTitle": "Grāmatzīmes"
},
"continueWatching": {
"sectionTitle": "Turpini skatīties"
},
"mediaList": {
"stopEditing": "Pārtraukt rediģēšanu"
},
"search": {
"allResults": "Tass ir viss kas mums ir!",
"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?",
"sectionTitle": "Meklējuma rezultāti"
},
"titles": {
"day": {
"default": "Ko jūs vēlētos noskatīties šajā pēcpusdienā?"
},
"morning": {
"default": "Ko tu gribētu šorīt noskatīties?",
"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."
]
}
}
},
"media": {
"episodeDisplay": "Sezona{{season}} Episode{{episode}}",
"types": {
"movie": "Filma",
"show": "seriāls"
}
},
"navigation": {
"banner": {
"offline": "Pārbaudiet interneta savienojumu"
},
"menu": {
"about": "Par mums",
"donation": "Ziedot",
"logout": "Iziet",
"register": "Sinhronizēt ar mākoni",
"settings": "Iestādijumi",
"support": "Atbalsts"
}
},
"notFound": {
"badge": "Nav atrasts",
"goHome": "Atpakaļ uz majām",
"message": "Mēs apskatijāmies visur: zem miskastes, skapī, aiz proxija bet nevarejām atrast lapu ko tu meklēji.",
"title": "Nevarēja atrast lapu"
},
"overlays": {
"close": "Aizvērt"
},
"player": {
"back": {
"default": "Atpakaļ uz mājām",
"short": "Atpakaļ"
},
"casting": {
"enabled": "Atskaņo uz ierīci..."
},
"menus": {
"captions": {
"customChoice": "Atlasiet failā parakstu",
"customizeLabel": "Paraksti",
"offChoice": "Izslēgts",
"settings": {
"delay": "Parakstu aizkave",
"fixCapitals": "Labojiet lielo burtu lietojumu"
},
"title": "Paraksti",
"unknownLanguage": "Nezināms"
},
"downloads": {
"disclaimer": "Lejupielādes tiek ņemtas tieši no pakalpojumu sniedzēja. Movie-web nevar kontrolēt, kā tiek nodrošinātas lejupielādes.",
"downloadCaption": "Lejupielādēt pašreizējo parakstu",
"downloadVideo": "Lejupielādēt video",
"hlsExplanation": "Šī multivide ir HLS straume, kuru nevar lejupielādēt filmu tīmeklī.",
"onAndroid": {
"1": "Lai lejupielādētu operētājsistēmā Android, noklikšķiniet uz lejupielādes pogas, pēc tam jaunajā lapā <bold>pieskarieties videoklipam un turiet to</bold>, pēc tam atlasiet <bold>saglabāt</bold>.",
"shortTitle": "Lejupielādēt / Android",
"title": "Lejupielāde operētājsistēmā Android"
},
"onIos": {
"1": "Lai lejupielādētu operētājsistēmā iOS, noklikšķiniet uz lejupielādes pogas, pēc tam jaunajā lapā noklikšķiniet uz <bold><ios_share /></bold> un pēc tam uz <bold>Saglabāt failos <ios_files /></bold>.",
"shortTitle": "Lejupielādēt / iOS",
"title": "Lejupielāde operētājsistēmā iOS"
},
"onPc": {
"1": "Datorā noklikšķiniet uz lejupielādes pogas, pēc tam jaunajā lapā ar peles labo pogu noklikšķiniet uz videoklipa un atlasiet <bold>Saglabāt video kā</bold>",
"shortTitle": "Lejupielādēt / datorā",
"title": "Lejupielāde datorā"
},
"title": "Lejupladēt"
},
"episodes": {
"button": "Episodes",
"emptyState": "Šajā sezonā nav nevienas sērijas. Pārbaudiet vēlāk!",
"episodeBadge": "E{{episode}}",
"loadingError": "Kļūda ladējot sezonu",
"loadingList": "Lādejas...",
"loadingTitle": "Lādejas..."
},
"playback": {
"speedLabel": "Atskaņošana ātrums",
"title": "Atskaņošana iestādijumi"
},
"quality": {
"automaticLabel": "Automātiskā kvalitāte",
"hint": "Varat mēģināt <0>pārslēgt avotu</0>, lai iegūtu dažādas kvalitātes opcijas.",
"iosNoQuality": "Apple noteikto ierobežojumu dēļ šim avotam iOS nav pieejama kvalitātes izvēle. Varat mēģināt <0>pārslēgties uz citu avotu</0>, lai iegūtu dažādas kvalitātes opcijas.",
"title": "Kvalitāte"
},
"settings": {
"captionItem": "Parakstu iestatījumi",
"downloadItem": "Lejupladēt",
"enableCaptions": "Iespējot parakstus",
"experienceSection": "Skatīšanās pieredze",
"playbackItem": "Atskaņošana iestādijumi",
"qualityItem": "Kvalitāte",
"sourceItem": "Video avoti",
"videoSection": "Video iestādijumi"
},
"sources": {
"failed": {
"text": "Mēģinot atrast videoklipus, radās kļūda. Lūdzu, mēģiniet izmantot citu avotu.",
"title": "Neizdevās nokasīt"
},
"noEmbeds": {
"text": "Mēs nevarējām atrast nevienu iegulšanu. Lūdzu, mēģiniet izmantot citu avotu.",
"title": "Netika atrasta neviena iegulšana"
},
"noStream": {
"text": "Šim avotam nav šīs filmas vai pārraides straumju.",
"title": "Nav streama"
},
"title": "Avoti",
"unknownOption": "Nezināms"
}
},
"metadata": {
"failed": {
"badge": "Neizdevās",
"homeButton": "iet uz majām",
"text": "Nevarēja ielādēt multivides metadatus no TMDB. Lūdzu, pārbaudiet, vai TMDB nedarbojas vai nav bloķēts jūsu interneta savienojumā.",
"title": "Neizdevās ielādēt metadatus"
},
"notFound": {
"badge": "Nav atrasts",
"homeButton": "Atpakaļ uz mājām",
"text": "Mēs nevarējām atrast jūsu pieprasīto multividi. Vai nu tas ir noņemts, vai arī jūs esat mainījis URL.",
"title": "Nevarēja atrast mēdiju."
}
},
"nextEpisode": {
"cancel": "Atcelt",
"next": "Nakamā epizode"
},
"playbackError": {
"badge": "Atskaņošanas kļūda",
"errors": {
"errorAborted": "Multivides iegūšana tika pārtraukta pēc lietotāja pieprasījuma.",
"errorDecode": "Lai gan iepriekš tika noteikts, ka tas ir lietojams, mēģinot atšifrēt multivides resursu, radās kļūda, kā rezultātā radās kļūda.",
"errorGenericMedia": "Nezināma medijas kļūda paradijās.",
"errorNetwork": "Radās sava veida tīkla kļūda, kas neļāva veiksmīgi ielādēt multividi, lai gan tas iepriekš bija pieejams.",
"errorNotSupported": "Multivides vai multivides nodrošinātāja objekts netiek atbalstīts."
},
"homeButton": "iet majās",
"text": "Notika kļūda kamēr meiģināja atskaņot video. Lūdzu meiģini atkal.",
"title": "Neizdevās palaist video!"
},
"scraping": {
"items": {
"failure": "Parādijās kļūda",
"notFound": "Nav šī video",
"pending": "Pārbauda priekš video..."
},
"notFound": {
"badge": "Nav atrasts",
"detailsButton": "seriāls deteļas",
"homeButton": "Iet majās",
"text": "Mēs meklejām cour mūsu piedavatājiem un nevarējām atrast ko tu meklēji! Mēs nehostojam mediju un mums nav kontroles kas ir pieejams. Lūdzu uzpied 'šova deteļas' apakšā priekš vairāk deteļām.",
"title": "Mēs nevarējām atrast to"
}
},
"time": {
"regular": "{{timeWatched}} / {{duration}}",
"remaining": "{{timeLeft}} beidza • pabeidza {{timeFinished, datetime}}",
"shortRegular": "{{timeWatched}}",
"shortRemaining": "-{{timeLeft}}"
}
},
"screens": {
"dmca": {
"text": "Laipni lūdzam filmu-web Autortiesību kontaktu lapā! Mēs cienām intelektuālā īpašuma tiesības un vēlamies ātri atrisināt visas autortiesību problēmas. Ja uzskatāt, ka jūsu ar autortiesībām aizsargātais darbs ir nepareizi izmantots mūsu platformā, lūdzu, nosūtiet uz tālāk norādīto e-pasta ziņojumu detalizētu Digitālās tūkstošgades autortiesību likuma paziņojumu. Lūdzu, iekļaujiet ar autortiesībām aizsargātā materiāla aprakstu, savu kontaktinformāciju un labas ticības apliecinājumu. Mēs esam apņēmušies ātri atrisināt šīs problēmas un novērtējam jūsu sadarbību, lai saglabātu filmu tīmekli par vietu, kurā tiek ievērotas radošums un autortiesības.",
"title": "Autortiesību likums"
},
"loadingApp": "Notiek aplikācijas ielāde",
"loadingUser": "Notiek jūsu profila ielāde",
"loadingUserError": {
"logout": "iziet",
"reset": "Atiestatīt pielāgoto serveri",
"text": "Neizdevās ieladēt jūsu profilu",
"textWithReset": "Neizdevās ielādēt profilu no pielāgotā servera. Vai vēlaties atiestatīt atpakaļ uz noklusējuma serveri?"
},
"migration": {
"failed": "Neizdevās migrēt jūsu datus.",
"inProgress": "Lūdzu, uzgaidiet, mēs migrējam jūsu datus. Tam nevajadzētu ilgt ilgu laiku."
}
},
"settings": {
"account": {
"accountDetails": {
"deviceNameLabel": "Ierīces vārds",
"deviceNamePlaceholder": "Personiskais telefons",
"editProfile": "Reģistrēt",
"logoutButton": "Iziet"
},
"actions": {
"delete": {
"button": "Dzēst kontu",
"confirmButton": "Dzēst kontu",
"confirmDescription": "Vai tiešām vēlaties dzēst savu kontu? Visi jūsu dati tiks zaudēti!",
"confirmTitle": "Vai tu esi pārliecināts?",
"text": "Šī darbība ir neatgriezeniska. Visi dati tiks dzēsti, un neko nevarēs atgūt.",
"title": "Dzēst kontu"
},
"title": "Darbības"
},
"devices": {
"deviceNameLabel": "Ierīces vārds",
"failed": "Neizdevās ielādēt sesijas",
"removeDevice": "Nonēmt",
"title": "Ierīces"
},
"profile": {
"finish": "Pabeidziet rediģēšanu",
"firstColor": "Profila krāsa viens",
"secondColor": "Profila krāsa divi",
"title": "Mainīt profila bildi",
"userIcon": "Konta ikona"
},
"register": {
"cta": "Sākt",
"text": "Kopīgojiet pulksteņa progresu starp ierīcēm un sinhronizējiet tās.",
"title": "Sinhronizēt ar mākoni"
},
"title": "Konts"
},
"appearance": {
"activeTheme": "Aktīvs",
"themes": {
"blue": "Zils",
"default": "Parasts",
"gray": "Pelēks",
"red": "Sarkans",
"teal": "zilganzaļš"
},
"title": "Izskats"
},
"captions": {
"backgroundLabel": "Fona necaurredzamība",
"colorLabel": "Krāsa",
"previewQuote": "Es nedrīkstu baidīties. Bailes ir prāta slepkava.",
"textSizeLabel": "Teksta lielums",
"title": "Paraksti"
},
"connections": {
"server": {
"description": "Ja vēlaties izveidot savienojumu ar pielāgotu aizmugursistēmu, lai saglabātu savus datus, iespējojiet to un norādiet URL.",
"label": "Pielāgots serveris",
"urlLabel": "Pielāgota servera URL"
},
"title": "Savienojumi",
"workers": {
"addButton": "pievienot jaunu worker",
"description": "Lai lietojumprogramma darbotos, visa trafika tiek maršrutēta caur starpniekserveriem. Iespējojiet šo, ja vēlaties piesaistīt savus darbiniekus.",
"emptyState": "Vēl nav workers. Pievienojiet vienu zemāk",
"label": "Izmantojiet pielāgotus starpniekserverus",
"urlLabel": "Worker URLs",
"urlPlaceholder": "https://"
}
},
"locale": {
"language": "Lietojumprogrammas valoda",
"languageDescription": "Visai lietojumprogrammai lietotā valoda.",
"title": "Lokalizācija"
},
"reset": "Restartēt",
"save": "Saglabāt",
"sidebar": {
"info": {
"appVersion": "Aplikācijas versija",
"backendUrl": "Aizmugursistēmas URL",
"backendVersion": "Aizmugurējā versija",
"hostname": "Saimniekdatora nosaukums",
"insecure": "Nedrošs",
"notLoggedIn": "Jūs neesat pievienojies",
"secure": "Drošs",
"title": "Aplikācijas informācija",
"unknownVersion": "Nezināms",
"userId": "Lietotāja ID"
}
},
"unsaved": "Jums ir nesaglabātas izmaiņas"
}
}

420
src/assets/locales/ne.json Normal file
View File

@@ -0,0 +1,420 @@
{
"about": {
"description": "movie-web एउटा वेब एप हो जसले स्ट्रिमहरूको लागि इन्टरनेटमा खोज्छ। हाम्रा टोलीले सामग्री उपभोग गर्नको लागि प्रायः न्यूनतम दृष्टिकोणको लागि लक्ष्य राख्छ।",
"faqTitle": "सामान्य प्रश्नहरू",
"q1": {
"body": "movie-web ले कुनै पनि सामग्री होस्ट गर्दैन। जब तपाइँ हेर्नको लागि केहि क्लिक गर्नुहुन्छ, इन्टरनेटमा चयन गरिएको मिडियाको लागि खोजी गरिन्छ (लोडिङ स्क्रिनमा र 'भिडियो स्रोत' ट्याबमा तपाइँ कुन स्रोत प्रयोग गरिरहनु भएको छ भनेर देख्न सक्नुहुन्छ)। मिडिया कहिले पनि चलचित्र-वेब द्वारा अपलोड हुँदैन, सबै कुरा यो खोजी संयन्त्र मार्फत हुन्छ।",
"title": "सामग्री कहाँबाट आउँछ?"
},
"q2": {
"body": "कार्यक्रम वा चलचित्र अनुरोध गर्न सम्भव छैन, movie-webले कुनै पनि सामग्री व्यवस्थापन गर्दैन। सबै सामग्री इन्टरनेटमा स्रोतहरू मार्फत हेरिन्छ।",
"title": "म कहाँ कार्यक्रम वा चलचित्र अनुरोध गर्न सक्छु?"
},
"q3": {
"body": "हाम्रा खोज परिणामहरू चलचित्र डाटाबेस (TMDB) द्वारा संचालित हुन्छन् र हाम्रा स्रोतहरूमा साँच्चै सामग्री छ कि छैन भनी प्रदर्शन गरिन्छ।",
"title": "खोज परिणामहरूले कार्यक्रम वा चलचित्र प्रदर्शन गर्दछ, म यसलाई किन प्ले गर्न सक्दिन?"
},
"title": "movie-web बारेमा"
},
"actions": {
"copied": "कपी भयो",
"copy": "कपी"
},
"auth": {
"createAccount": "अझै खाता छैन?<0>खाता खोल्नुहोस्|.</0>",
"deviceNameLabel": "उपकरणको नाम",
"deviceNamePlaceholder": "निजी फोन",
"generate": {
"description": "तपाईंको पासफ्रेजले तपाईंको प्रयोगकर्ता नाम र पासवर्डको रूपमा कार्य गर्दछ। यसलाई सुरक्षित राख्नुहोस् किनकि तपाईंले आफ्नो खातामा लगइन गर्न आवश्यक हुनेछ",
"next": "मैले मेरो पासफ्रेज सुरक्षित गरेको छु",
"title": "तपाईको पासफ्रेज"
},
"hasAccount": "पहिले नै खाता छ? <0>यहाँ लग-इन गर्नुहोस्|</0>",
"login": {
"description": "कृपया आफ्नो खातामा लगइन गर्नको लागि आफ्नो पासफ्रेज हाल्नुहोस",
"deviceLengthError": "कृपया फोनको नाम हाल्नुहोस",
"passphraseLabel": "१२-शब्द पासफ्रेज",
"passphrasePlaceholder": "पासफ्रेज",
"submit": "लगइन",
"title": "आफ्नो खातामा लगइन गर्नुहोस्",
"validationError": "गलत वा अपूर्ण पासफ्रेज"
},
"register": {
"information": {
"color1": "प्रोफाइल रङ एक",
"color2": "प्रोफाइल रङ दुई",
"header": "आफ्नो फोनको लागि नाम लेख्नुहोस र रङ र चित्र छनौट गर्नुहोस",
"icon": "प्रयोगकर्ता चित्र",
"next": "अर्को",
"title": "खाता जानकारी"
}
},
"trust": {
"failed": {
"text": "के तपाईंले यसलाई सही रूपमा कन्फिगर गर्नुभयो?",
"title": "सर्भरमा पुग्न असफल भयो"
},
"host": "तपाइँ <0>{{hostname}}</0> मा कनेक्ट हुनुहुन्छ - कृपया खाता बनाउनु अघि तपाइँ यसलाई विश्वास गर्नुहुन्छ भनेर पुष्टि गर्नुहोस्",
"no": "पछाडी जाउ",
"title": "के तपाइँ यो सर्भरमा भरोसा गर्नुहुन्छ?",
"yes": "म यो सर्भरलाई भरोसा गर्छु"
},
"verify": {
"description": "तपाईंले यसलाई सुरक्षित गर्नुभएको छ भनी पुष्टि गर्न र आफ्नो खाता सिर्जना गर्नको लागि कृपया आफ्नो पासफ्रेज हालनुहोस्",
"invalidData": "डाटा मान्य छैन",
"noMatch": "पासफ्रेज मेल खाँदैन",
"passphraseLabel": "तपाईंको १२-शब्द पासफ्रेज",
"recaptchaFailed": "ReCaptcha प्रमाणीकरण असफल भयो",
"register": "खाता बनाउनुहोस्",
"title": "आफ्नो पासफ्रेज पुष्टि गर्नुहोस्"
}
},
"errors": {
"badge": "यो बिग्रियो",
"details": "त्रुटि विवरण",
"reloadPage": "पेज फेरी लोड गर्नुहोस्",
"showError": "त्रुटि विवरण देखाउनुहोस्",
"title": "हामीले एउटा त्रुटिको सामना गर्यौं!"
},
"footer": {
"legal": {
"disclaimer": "Disclaimer",
"disclaimerText": "movie-webले कुनै पनि फाइलहरू होस्ट गर्दैन, यसले केवल तेस्रो पक्ष सेवाहरूमा लिङ्क गर्दछ। कानुनी मुद्दाहरू फाइल होस्ट र प्रदायकहरूसँग लिनु पर्छ। चलचित्र-वेब भिडियो प्रदायकहरू द्वारा देखाइएका कुनै पनि मिडिया फाइलहरूको लागि जिम्मेवार छैन।"
},
"links": {
"discord": "Discord",
"dmca": "DMCA",
"github": "GitHub"
},
"tagline": "यो खुला स्रोत स्ट्रिमिङ एपको साथ आफ्नो मनपर्ने शो र चलचित्रहरू हेर्नुहोस्।"
},
"global": {
"name": "movie-web",
"pages": {
"about": "जानकारी",
"dmca": "DMCA",
"login": "लग - इन",
"pagetitle": "{{title}} - movie-web",
"register": "दर्ता",
"settings": "सेटिङ्स्"
}
},
"home": {
"bookmarks": {
"sectionTitle": "बुकमार्कहरू"
},
"continueWatching": {
"sectionTitle": "हेर्न जारी राख्नुहोस्"
},
"mediaList": {
"stopEditing": "सम्पादन रोक्नुहोस्"
},
"search": {
"allResults": "हामीसँग यति मात्र छ!",
"failed": "मिडिया फेला पार्न असफल भयो, फेरि प्रयास गर्नुहोस्!",
"loading": "लोड गर्दै...",
"noResults": "हामीले केहि फेला पार्न सकेनौं!",
"placeholder": "तपाईं के हेर्न चाहनुहुन्छ?",
"sectionTitle": "खोज परिणामहरू"
},
"titles": {
"day": {
"default": "तपाईं आज दिउँसो के हेर्न चाहनुहुन्छ?"
},
"morning": {
"default": "तपाई आज बिहान के हेर्न चाहनुहुन्छ?",
"extra": [
"Before Sunrise राम्रो छ भन्ने सुन्छु"
]
},
"night": {
"default": "तपाईं आज राती के हेर्न चाहनुहुन्छ?",
"extra": [
"थकित? मैले सुनेको छु The Exorcist राम्रो छ।"
]
}
}
},
"media": {
"episodeDisplay": "S{{season}} E{{episode}}",
"types": {
"movie": "चलचित्र",
"show": "कार्यक्रम"
}
},
"navigation": {
"banner": {
"offline": "आफ्नो इन्टरनेट जाँच गर्नुहोस्"
},
"menu": {
"about": "हाम्रो बारे जानकारी",
"donation": "दान गर्नुहोस्",
"logout": "बाहिर निस्कनु",
"register": "क्लाउडमा सिंक गर्नुहोस्",
"settings": "सेटिङ",
"support": "समर्थन गर्नुहोस्"
}
},
"notFound": {
"badge": "फेला परेन",
"goHome": "होम् फिर्ता जानुहोस्",
"message": "हामीले जताततै हेर्यौं: डिब्बा मुनि, कोठरीमा, प्रोक्सी पछाडि तर अन्ततः तपाईंले खोजिरहनु भएको पेज फेला पार्न सकेनौं।",
"title": "त्यो पेज फेला पार्न सकेन"
},
"overlays": {
"close": "बन्द गर्नुहोस्"
},
"player": {
"back": {
"default": "घर फिर्ता",
"short": "फिर्ता"
},
"casting": {
"enabled": "उपकरणमा कास्ट गर्दै..."
},
"menus": {
"captions": {
"customChoice": "फाइलबाट क्याप्शन चयन गर्नुहोस्",
"customizeLabel": "रुचिको अनुसार बनाउनु",
"offChoice": "बन्द",
"settings": {
"delay": "क्याप्सन ढिलाइ",
"fixCapitals": "पूंजीकरण ठीक गर्नुहोस्"
},
"title": "क्याप्शन",
"unknownLanguage": "अज्ञात"
},
"downloads": {
"disclaimer": "डाउनलोडहरू सीधा प्रदायकबाट लिइन्छ। movie-web ले डाउनलोडहरू कसरी प्रदान गरिन्छ भन्नेमा नियन्त्रण गर्दैन।",
"downloadCaption": "डाउनलोड चलिरहेको क्याप्शन",
"downloadVideo": "डाउनलोड भिडियो",
"hlsExplanation": "यो मिडिया HLS स्ट्रिम हो जुन movie-web मा डाउनलोड गर्न सकिँदैन।",
"onAndroid": {
"1": "एन्ड्रोइड मा, डाउनलोड बटन क्लिक गर्नुहोस् त्यसपछि, नयाँ पृष्ठमा, भिडियोमा <bold>ट्याप गर्नुहोस् र होल्ड गर्नुहोस्</bold>, त्यसपछि <bold>बचत</bold> चयन गर्नुहोस्।",
"shortTitle": "डाउनलोड / एन्ड्रोइड",
"title": "एन्ड्रोइडमा डाउनलोड हुदैछ"
},
"onIos": {
"1": "iOS मा डाउनलोड गर्न, डाउनलोड बटन क्लिक गर्नुहोस् त्यसपछि, नयाँ पृष्ठमा, <bold><ios_share /></bold> क्लिक गर्नुहोस्, त्यसपछि <bold>Save to Files <ios_files /></bold>।",
"shortTitle": "डाउनलोड / iOS",
"title": "iOS मा डाउनलोड हुदैछ"
},
"onPc": {
"1": "PC मा, डाउनलोड बटन क्लिक गर्नुहोस् त्यसपछि, नयाँ पृष्ठमा, भिडियोमा दायाँ क्लिक गर्नुहोस् र <bold>भिडियोलाई यस रूपमा सेव गर्नुहोस्</bold> चयन गर्नुहोस्",
"shortTitle": "डाउनलोड / कम्प्युटर",
"title": "कम्प्युटरमा डाउनलोड हुदैछ"
},
"title": "डाउनलोड"
},
"episodes": {
"button": "एपिसोडहरू",
"emptyState": "यस सिजनमा कुनै एपिसोडहरू छैनन्, पछि फेरि जाँच गर्नुहोस्!",
"episodeBadge": "E{{episode}}",
"loadingError": "सिजन लोड गर्दा त्रुटि भयो",
"loadingList": "लोड गर्दै...",
"loadingTitle": "लोड गर्दै..."
},
"playback": {
"speedLabel": "प्लेब्याकको गति",
"title": "प्लेब्याक सेटिङ"
},
"quality": {
"automaticLabel": "स्वचालित क्वालिटी",
"hint": "तपाईं विभिन्न क्वालिटी प्राप्त गर्न <0>स्रोत स्विच</0> प्रयास गर्न सक्नुहुन्छ।",
"iosNoQuality": "Apple-परिभाषित सीमितताहरूका कारण, गुणस्तर चयन यो स्रोतको लागि iOS मा उपलब्ध छैन। तपाईं विभिन्न गुणस्तर विकल्पहरू प्राप्त गर्न <0>अर्को स्रोतमा स्विच गर्न</0> प्रयास गर्न सक्नुहुन्छ।",
"title": "क्वालिटी"
},
"settings": {
"captionItem": "क्याप्शन सेत्तिन्ग्स",
"downloadItem": "डाउनलोड",
"enableCaptions": "क्याप्सन इनेबल गर्नुहोस्",
"experienceSection": "हेर्ने अनुभव",
"playbackItem": "प्लेब्याक सेटिङ",
"qualityItem": "क्वालिटी",
"sourceItem": "भिडियो स्रोतहरू",
"videoSection": "भिडियो सेत्तिन्ग्स"
},
"sources": {
"failed": {
"text": "कुनै पनि भिडियोहरू फेला पार्न प्रयास गर्दा त्रुटि भयो, कृपया फरक स्रोत प्रयास गर्नुहोस्।",
"title": "फेला पार्न असफल भयो"
},
"noEmbeds": {
"text": "हामीले कुनै पनि इम्बेडहरू फेला पार्न सकेनौं, कृपया अर्कै स्रोत प्रयास गर्नुहोस्।",
"title": "कुनै इम्बेडहरू फेला परेनन्"
},
"noStream": {
"text": "यो स्रोतमा यो चलचित्र वा कार्यक्रमको लागि कुनै स्ट्रिमहरू छैनन्।",
"title": "कुनै स्ट्रिम छैन"
},
"title": "स्रोतहरू",
"unknownOption": "अज्ञात"
}
},
"metadata": {
"failed": {
"badge": "असफल",
"homeButton": "होम् जाउँ",
"text": "TMDB बाट मिडियाको मेटाडेटा लोड गर्न सकिएन। कृपया जाँच गर्नुहोस् कि TMDB डाउन वा तपाईंको इन्टरनेटमा प्रतिबन्धित छ।",
"title": "मेटाडेटा लोड गर्न असफल भयो"
},
"notFound": {
"badge": "फेला परेन",
"homeButton": "घर फिर्ता",
"text": "हामीले तपाईंले अनुरोध गर्नुभएको मिडिया फेला पार्न सकेनौं। या त यसलाई हटाइयो वा तपाईंले लिङ्कमा छेडछाड गर्नुभयो।",
"title": "त्यो मिडिया फेला पार्न सकेन।"
}
},
"nextEpisode": {
"cancel": "रद्द गर्नुहोस्",
"next": "अर्को एपिसोड"
},
"playbackError": {
"badge": "प्लेब्याक त्रुटि",
"errors": {
"errorAborted": "प्रयोगकर्ताको अनुरोधमा मिडिया ल्याउने कार्य रद्द गरियो।",
"errorDecode": "पहिले प्रयोगयोग्य हुन निर्धारण गरिएको भए तापनि, मिडिया स्रोत डिकोड गर्ने प्रयास गर्दा त्रुटि भयो, परिणामस्वरूप त्रुटि भयो।",
"errorGenericMedia": "अज्ञात मिडिया त्रुटि भयो।",
"errorNetwork": "केहि प्रकारको सञ्जाल त्रुटि देखा पर्‍यो जसले मिडियालाई पहिले उपलब्ध भएता पनि सफलतापूर्वक ल्याउनबाट रोक्यो।",
"errorNotSupported": "मिडिया वा मिडिया प्रदायक वस्तु सपोर्ट छैन।"
},
"homeButton": "होम् जाउँ",
"text": "मिडिया प्ले गर्ने प्रयास गर्दा त्रुटि भयो। फेरि प्रयास गर्नुहोस।",
"title": "भिडियो प्ले गर्न असफल भयो!"
},
"scraping": {
"items": {
"failure": "त्रुटि भयो",
"notFound": "भिडियो छैन",
"pending": "भिडियोहरू खोज्दैछौं..."
},
"notFound": {
"badge": "फेला परेन",
"detailsButton": "विवरण देखाऊ",
"homeButton": "होम् जाउँ",
"text": "हामीले हाम्रा प्रदायकहरू मार्फत खोज्यौं र तपाईंले खोजिरहनुभएको मिडिया फेला पार्न सकेनौं! हामी मिडिया होस्ट गर्दैनौं र के उपलब्ध छ त्यसमा कुनै नियन्त्रण छैन। कृपया थप विवरणहरूको लागि तल 'विवरणहरू देखाउनुहोस्' क्लिक गर्नुहोस्।",
"title": "हामीले त्यो फेला पार्न सकेनौं"
}
},
"time": {
"regular": "{{timeWatched}} / {{duration}}",
"remaining": "{{timeLeft}} बाकी • {{timeFinished, datetime}} मा सक्किनेछ",
"shortRegular": "{{timeWatched}}",
"shortRemaining": "-{{timeLeft}}"
}
},
"screens": {
"dmca": {
"text": "movie-web को DMCA सम्पर्क पृष्ठमा स्वागत छ! हामी बौद्धिक सम्पत्ति अधिकारको सम्मान गर्छौं र कुनै पनि प्रतिलिपि अधिकार सरोकारलाई तुरुन्तै सम्बोधन गर्न चाहन्छौं। यदि तपाइँ तपाइँको प्रतिलिपि अधिकार कार्य हाम्रो प्लेटफर्ममा अनुचित रूपमा प्रयोग भएको विश्वास गर्नुहुन्छ भने, कृपया तलको इमेलमा विस्तृत DMCA सूचना पठाउनुहोस्। कृपया प्रतिलिपि अधिकार सामग्रीको विवरण, तपाईंको सम्पर्क विवरणहरू, र राम्रो विश्वासको कथन समावेश गर्नुहोस्। हामी यी मामिलाहरू तुरुन्तै समाधान गर्न प्रतिबद्ध छौं र चलचित्र-वेबलाई रचनात्मकता र प्रतिलिपि अधिकारको सम्मान गर्ने ठाउँ राख्नमा तपाईंको सहयोगको कदर गर्छौं।",
"title": "DMCA"
},
"loadingApp": "एप लोड हुदैछ",
"loadingUser": "तपाईंको प्रोफाइल लोड हुदैछ",
"loadingUserError": {
"logout": "बाहिर निस्कनु",
"reset": "अनुकूलन सर्भर रिसेट गर्नुहोस्",
"text": "तपाईंको प्रोफाइल लोड गर्न असफल भयो",
"textWithReset": "तपाईंको अनुकूलन सर्भरबाट तपाईंको प्रोफाइल लोड गर्न असफल भयो, पूर्वनिर्धारित सर्भरमा पुन: सेट गर्न चाहनुहुन्छ?"
},
"migration": {
"failed": "तपाईंको डाटा माइग्रेट गर्न असफल भयो।",
"inProgress": "कृपया होल्ड गर्नुहोस्, हामी तपाईंको डाटा माइग्रेट गर्दैछौं। यो धेरै समय लाग्दैन।"
}
},
"settings": {
"account": {
"accountDetails": {
"deviceNameLabel": "उपकरणको नाम",
"deviceNamePlaceholder": "व्यक्तिगत फोन",
"editProfile": "सम्पादन गर्नुहोस्",
"logoutButton": "बाहिर निस्कनु"
},
"actions": {
"delete": {
"button": "खाता डिलीट गर्नुहोस्",
"confirmButton": "खाता डिलीट गर्नुहोस्",
"confirmDescription": "के तपाइँ आफ्नो खाता डिलीट गर्न नश्चित हुनुहुन्छ? तपाईंको सबै डाटा हराउनेछ!",
"confirmTitle": "के तपाईँ निश्चित हुनुहुन्छ?",
"text": "यो कार्य अपरिवर्तनीय छ। सबै डाटा मेटाइनेछ र केहि पनि पुन: प्राप्त गर्न सकिँदैन।",
"title": "खाता डिलीट गर्नुहोस्"
},
"title": "कार्यहरू"
},
"devices": {
"deviceNameLabel": "उपकरणको नाम",
"failed": "सत्रहरू लोड गर्न असफल भयो",
"removeDevice": "हटाउनुहोस्",
"title": "उपकरणहरु"
},
"profile": {
"finish": "सम्पादन समाप्त गर्नुहोस्",
"firstColor": "प्रोफाइल रङ एक",
"secondColor": "प्रोफाइल रङ दुई",
"title": "प्रोफाइल तस्वीर सम्पादन गर्नुहोस्",
"userIcon": "प्रयोगकर्ता आइकन"
},
"register": {
"cta": "सुरु गर्नु",
"text": "उपकरणहरू बीच आफ्नो cप्रगति साझेदारी गर्नुहोस् र तिनीहरूलाई सिंक राख्नुहोस्।",
"title": "क्लाउडमा सिंक गर्नुहोस्"
},
"title": "खाता"
},
"appearance": {
"activeTheme": "सक्रिय",
"themes": {
"blue": "निलो",
"default": "साधारण",
"gray": "खैरो",
"red": "रातो",
"teal": "हरियो-नीलो"
},
"title": "रूप-रंग"
},
"captions": {
"backgroundLabel": "पृष्ठभूमि अस्पष्टता",
"colorLabel": "रङ",
"previewQuote": "म डराउनु हुँदैन। डर मनको हत्यारा हो।",
"textSizeLabel": "शब्दको आकार",
"title": "क्याप्शन"
},
"connections": {
"server": {
"description": "यदि तपाईं आफ्नो डेटा भण्डारण गर्न अनुकूलन ब्याकइन्डमा जडान गर्न चाहनुहुन्छ भने, यसलाई सक्षम गर्नुहोस् र URL प्रदान गर्नुहोस्।",
"label": "अनुकूलन सर्भर",
"urlLabel": "अनुकूलन सर्भर URL"
},
"title": "संबन्धहरु",
"workers": {
"addButton": "नया worker हरु हाल्नुहोस",
"description": "एप्लिकेसन प्रकार्य बनाउनको लागि, सबै ट्राफिक प्रोक्सीहरू मार्फत रूट गरिएको छ। यदि तपाईं आफ्नो कामदारहरू ल्याउन चाहनुहुन्छ भने यसलाई सक्षम गर्नुहोस्।",
"emptyState": "अहिलेसम्म worker हरु छैनन्, तल एउटा थप्नुहोस्",
"label": "आफ्नै proxy workers हरु चलाउनुहोस्",
"urlLabel": "Worker URL हरु",
"urlPlaceholder": "https://"
}
},
"locale": {
"language": "एपको भाषा",
"languageDescription": "सम्पूर्ण अनुप्रयोगमा भाषा लागू गरियो।",
"title": "भाषा"
},
"reset": "रिसेट गर्नुहोस्",
"save": "सेभ गर्नुहोस्",
"sidebar": {
"info": {
"appVersion": "एप संस्करण",
"backendUrl": "ब्याकइन्ड URL",
"backendVersion": "ब्याकएन्ड संस्करण",
"hostname": "होस्टको नाम",
"insecure": "असुरक्षित",
"notLoggedIn": "तपाईं लग्द इन हुनुहुन्न",
"secure": "सुरक्षित",
"title": "एप बारे जानकारी",
"unknownVersion": "अज्ञात",
"userId": "प्रयोगकर्ता ID"
}
},
"unsaved": "तपाईंसँग सुरक्षित नगरिएका परिवर्तनहरू छन्"
}
}

View File

@@ -1,11 +1,68 @@
{
"auth": {
"deviceNameLabel": "Toestel naam",
"deviceNamePlaceholder": "Huistelefoon",
"hasAccount": "Heb je al een account? <0>Log hier in.</0>"
"createAccount": "Heb je nog geen account? <0>Maak er dan een.</0>",
"deviceNameLabel": "Naam toestel",
"deviceNamePlaceholder": "Mijn telefoon",
"generate": {
"description": "Je passphrase werkt als je gebruikersnaam en wachtwoord. Sla je passphrase dus goed op, je hebt hem namelijk nodig om in te loggen",
"next": "Ik heb mijn passphrase opgeslagen",
"title": "Jouw passphrase"
},
"hasAccount": "Heb je al een account? <0>Log hier in.</0>",
"login": {
"description": "Vul je passphrase in",
"deviceLengthError": "Vul de naam van je apparaat in",
"passphraseLabel": "12-Woordelijke passphrase",
"passphrasePlaceholder": "Passphrase",
"submit": "Log in",
"title": "Log in bij je account",
"validationError": "Incorrecte of incompleet passphrase"
},
"register": {
"information": {
"color1": "Profielkleur 1",
"color2": "Profielkleur 2",
"header": "Vul hier het naam van je apparaat. Kies ook je de kleuren die je wil, en een icoontje",
"icon": "Icoontje",
"next": "Volgende",
"title": "Account informatie"
}
},
"trust": {
"failed": {
"text": "Heb je het goed ingesteld?",
"title": "Kon niet met de server verbinden"
},
"host": "Je gaat zo verbinden met <0>{{hostname}}</0>, check even of je deze link vertrouwt",
"no": "Vorige pagina",
"title": "Vertrouw je deze server?",
"yes": "Ik vertrouw deze server"
},
"verify": {
"description": "Vul je passphrase in zodat we weten dat je het opgeslagen hebt, dan kunnen we je account maken",
"invalidData": "Ongeldige data",
"noMatch": "Passphrase komt niet overeen",
"passphraseLabel": "Jouw passphrase",
"recaptchaFailed": "ReCatpcha validatie is mislukt",
"register": "Maak een account",
"title": "Bevestig je passphrase"
}
},
"errors": {
"badge": "Tis kapot",
"details": "Informatie over foutmelding",
"reloadPage": "Herlaad de pagina",
"showError": "Meer informatie over foutmelding",
"title": "Er is iets fout gegaan!"
},
"global": {
"name": "movie-web"
"name": "movie-web",
"pages": {
"about": "Over",
"login": "Login",
"register": "Registreren",
"settings": "Instellingen"
}
},
"home": {
"bookmarks": {
@@ -26,8 +83,8 @@
"media": {
"episodeDisplay": "S{{season}} A{{episode}}",
"types": {
"movie": "Films",
"show": "Series"
"movie": "Film",
"show": "Serie"
}
},
"navigation": {
@@ -46,22 +103,70 @@
"default": "Naar de home-pagina",
"short": "Terug"
},
"casting": {
"enabled": "Aan het casten..."
},
"menus": {
"captions": {
"customChoice": "Ondertiteling uploaden",
"customizeLabel": "Instellingen",
"title": "Ondertiteling"
"offChoice": "Geen ondertiteling",
"settings": {
"delay": "Tijdverschil ondertiteling",
"fixCapitals": "Hoofdletters corrigeren"
},
"title": "Ondertiteling",
"unknownLanguage": "Onbekend"
},
"downloads": {
"disclaimer": "Downloads worden direct bij de bron opgehaald. movie-web heeft geen controle over het bestand dat je ontvangt.",
"downloadCaption": "Ondertiteling downloaden",
"downloadVideo": "Download filmpje",
"hlsExplanation": "Dit filmpje is een HLS bestand, een type bestand dat we helaas niet kunnen downloaden.",
"title": "Download"
},
"episodes": {
"button": "Afleveringen",
"loadingList": "Aan het zoeken...",
"emptyState": "Er zijn in dit seizoen geen afleveringen, kijk over een paar jaar nog eens!",
"episodeBadge": "A{{episode}}",
"loadingError": "Er ging iets mis bij het laden van dit seizoen",
"loadingList": "Aan het laden...",
"loadingTitle": "Aan het zoeken..."
},
"settings": {
"captionItem": "Instellingen ondertiteling",
"downloadItem": "Download",
"enableCaptions": "Ondertiteling aanzetten",
"experienceSection": "Kijk-ervaring",
"playbackItem": "Afspeel instellingen",
"qualityItem": "Kwaliteit",
"sourceItem": "Video-bron",
"videoSection": "Video instellingen"
},
"sources": {
"title": "Bronnen"
"failed": {
"text": "Er ging iets mis bij het zoeken naar videos, probeer een andere bron.",
"title": "Het is niet gelukt dit op te halen"
},
"noEmbeds": {
"text": "We konden geen embeds vinden, probeer een andere bron.",
"title": "Geen embeds gevonden"
},
"noStream": {
"text": "Deze bron heeft geen links voor deze film of serie.",
"title": "Geen bron"
},
"title": "Bronnen",
"unknownOption": "Onbekend"
}
},
"metadata": {
"failed": {
"badge": "Mislukt",
"homeButton": "Ga naar de home-pagina",
"text": "We konden geen informatie over deze media ophalen bij TMDB. Kijk even na of TMDB te bereiken is op dit netwerk.",
"title": "Metadata ophalen mislukt"
},
"notFound": {
"badge": "Pagina niet gevonden",
"homeButton": "Naar de home-pagina",
@@ -69,8 +174,39 @@
"title": "We konden deze media niet vinden."
}
},
"nextEpisode": {
"cancel": "Annuleren",
"next": "Volgende aflevering"
},
"playbackError": {
"badge": "Afspeelfout",
"errors": {
"errorAborted": "Het laden van de media is stopgezet omdat de gebruiker daar om vroeg.",
"errorDecode": "Ondanks het feit dat we eerder dachten dat deze bron beschikbaar, was dat toch niet zo en is er een decode error ontstaan.",
"errorGenericMedia": "Onbekende media foutmelding.",
"errorNetwork": "Er ging iets mis waardoor de media niet langer beschikbaar is, al was hij dat eerst wel.",
"errorNotSupported": "Deze media or media provider wordt niet ondersteund."
},
"homeButton": "Naar de home-pagina",
"text": "Er ging iets mis bij het afspelen. Probeer het nog eens.",
"title": "Oeps, hier ging iets mis!"
},
"scraping": {
"items": {
"failure": "Daar ging iets mis",
"notFound": "Er is geen video gevonden",
"pending": "Naar videos aan het zoeken..."
},
"notFound": {
"badge": "Niet gevonden",
"detailsButton": "Meer informatie",
"homeButton": "Naar de home-pagina",
"text": "We hebben al onze providers lief aangekeken maar we hebben niets kunnen vinden. Wij beheersen de media niet, en bepalen dus niet wat er beschikbaar is. Klik op \"meer informatie over foutmelding\" voor meer informatie.",
"title": "Dat konden we niet vinden"
}
},
"time": {
"remaining": "Nog {{timeLeft}} • Klaar om {{timeFinished, datetime}}"
}
}
}

View File

@@ -1 +1,420 @@
{}
{
"about": {
"description": "Movie-web เป็นเว็บแอปพลิเคชันที่ค้นหาสตรีมทางอินเทอร์เน็ต ทีมงานตั้งเป้าไปที่แนวทางการบริโภคเนื้อหาแบบมินิมอลเป็นส่วนใหญ่",
"faqTitle": "คําถามทั่วไป",
"q1": {
"body": "Movie-web ไม่ได้เป็นเจ้าของเนื้อหาใด ๆ เมื่อคุณคลิกที่บางสิ่งเพื่อดู อินเทอร์เน็ตจะถูกค้นหาสื่อที่เลือก (บนหน้าจอโหลดและในแท็บ 'แหล่งวิดีโอ' คุณจะเห็นว่าคุณกําลังใช้แหล่งใด) สื่อไม่เคยถูกอัปโหลดโดย movie-web ทุกอย่างผ่านกลไกการค้นหานี้",
"title": "เนื้อหามาจากที่ไหน?"
},
"q2": {
"body": "ไม่สามารถขอซีรี่ย์หรือภาพยนตร์ได้ movie-web ไม่ได้จัดการเนื้อหาใด ๆ เนื้อหาทั้งหมดถูกดูผ่านแหล่งข้อมูลบนอินเทอร์เน็ต",
"title": "ฉันจะขอซีรี่ย์หรือภาพยนตร์ได้ที่ไหน?"
},
"q3": {
"body": "ผลการค้นหาของเราขับเคลื่อนโดย The Movie Database (TMDB) และจะแสดงไม่ว่าแหล่งข้อมูลของเราจะมีเนื้อหาจริงหรือไม่",
"title": "ผลการค้นหาซีรี่ย์หรือภาพยนตร์ ทําไมฉันถึงเล่นไม่ได้?"
},
"title": "เกี่ยวกับ movie-web"
},
"actions": {
"copied": "คัดลอกแล้ว",
"copy": "คัดลอก"
},
"auth": {
"createAccount": "ยังไม่มีบัญชีใช่หรือไม่?<0>สร้างบัญชี</0>",
"deviceNameLabel": "ชื่ออุปกรณ์",
"deviceNamePlaceholder": "โทรศัพท์ส่วนบุคคล",
"generate": {
"description": "รหัสผ่านของคุณถูกตั้งเช่นเดียวกับชื่อผู้ใช้และรหัสผ่าน โปรดตรวจสอบให้แน่ใจว่ารหัสผ่านของคุณถูกเก็บอย่างปลอดภัย คุณจำเป็นต้องใช้เพื่อเข้าสู่ระบบบัญชีของคุณ",
"next": "ฉันบันทึกรหัสผ่านของฉันแล้ว",
"title": "หรัสผ่านของคุณ"
},
"hasAccount": "คุณมีบัญชีแล้วหรือไม่? <0>เข้าสู่ระบบที่นี่.</0>",
"login": {
"description": "โปรดป้อนสหัสผ่านของคุณเพื่อเข้าสู่ระบบ",
"deviceLengthError": "โปรดป้อนชื่ออุปกรณ์ของคุณ",
"passphraseLabel": "หรัสผ่าน 12 ตัว",
"passphrasePlaceholder": "หรัสผ่าน",
"submit": "เข้าสู่ระบบ",
"title": "เข้าสู่ระบบบัญชีของคุณที่นี่",
"validationError": "รหัสผ่านไม่ถูกต้อง หรือ รหัสผ่านไม่สมบูรณ์"
},
"register": {
"information": {
"color1": "สีโปรไฟล์ 1",
"color2": "สีโปรไฟล์ 2",
"header": "โปรดใส่ชื่ออุปกรณ์ของคุณและเลือกสีจากนั้นเลือกสัญลักษณ์ผู้ใช้ที่คุณต้องการ",
"icon": "สัญลักษณ์ผู้ใช้",
"next": "ต่อไป",
"title": "ข้อมูลบัญชี"
}
},
"trust": {
"failed": {
"text": "คุณทำตามขั้นตอนอย่างถูกต้องหรือไม่?",
"title": "การเข้าสู่ระบบล้มเหลว"
},
"host": "คุณกำลังเชื่อมต่อกับ <0>{{hostname}}</0> - โปรดกดตกลงเชื่อถือเครือข่ายนี้ก่อนสร้างบัญชี",
"no": "ย้อนกลับ",
"title": "คุณเชื่อเครือข่ายนี้หรือไม่?",
"yes": "ฉันเชื่อเครือข่ายนี้"
},
"verify": {
"description": "โปรดป้อนรหัสผ่านก่อนหน้านี้ของคุณ เพื่อยืนยันว่าคุณได้บันทึกและเพื่อสร้างบัญชีของคุณ",
"invalidData": "ข้อไม่ถูกต้อง",
"noMatch": "รหัสผ่านไม่ตรงกัน",
"passphraseLabel": "รหัสผ่าน 12 ตัว ของคุณ",
"recaptchaFailed": "การตรวจสอบล้มเหลว",
"register": "สร้างบัญชี",
"title": "ยืนยันรหัสผ่านของคุณ"
}
},
"errors": {
"badge": "ไม่สามารถใช้งานได้",
"details": "ข้อมูลผิดพลาด",
"reloadPage": "โหลดหน้าใหม่",
"showError": "แสดงข้อมูลที่ผิดพลาด",
"title": "เราพบข้อผิดพลาด!"
},
"footer": {
"legal": {
"disclaimer": "ข้อจํากัดความรับผิดชอบ",
"disclaimerText": "Movie-web ไม่ได้เป็นเจ้าของไฟล์ใด ๆ เป็นเพียงลิงก์ไปยังบริการของบุคคลที่สาม ประเด็นทางกฎหมายควรดําเนินการกับเจ้าของไฟล์ และผู้ให้บริการ movie-web ไม่รับผิดชอบต่อไฟล์สื่อใด ๆ ที่แสดงโดยผู้ให้บริการวิดีโอ"
},
"links": {
"discord": "Discord",
"dmca": "DMCA",
"github": "GitHub"
},
"tagline": "ดูรายการซีรี่ย์และภาพยนตร์ที่คุณชื่นชอบด้วยแอปสตรีมนี้"
},
"global": {
"name": "movie-web",
"pages": {
"about": "เกี่ยวกับ",
"dmca": "DMCA",
"login": "เข้าสู่ระบบ",
"pagetitle": "{{title}} - movie-web",
"register": "ลงทะเบียน",
"settings": "ตั้งค่า"
}
},
"home": {
"bookmarks": {
"sectionTitle": "รายการของฉัน"
},
"continueWatching": {
"sectionTitle": "ดูต่อ"
},
"mediaList": {
"stopEditing": "หยุดแก้ไข"
},
"search": {
"allResults": "นั่นคือทั้งหมดที่เรามี!",
"failed": "ไม่พบสื่อนี้ ลองอีกครั้ง!",
"loading": "กำลังโหลด..",
"noResults": "เราไม่พบอะไรเลย!",
"placeholder": "คุณอยากดูอะไรคะ?",
"sectionTitle": "ผลการค้นหา"
},
"titles": {
"day": {
"default": "คุณอยากดูเรื่องอะไรในช่วงบ่ายนี้?"
},
"morning": {
"default": "คุณอยากดูอะไรเช้านี้?",
"extra": [
"ฉันได้ยินมาว่าเรื่อง Before Sunrise สนุก"
]
},
"night": {
"default": "คุณอยากดูเรื่องอะไรในช่วงค่ำ?",
"extra": [
"เหนื่อยมั้ย? ฉันได้ยินมาว่า The Exorcist นั้นดี"
]
}
}
},
"media": {
"episodeDisplay": "ภ{{season}} ต{{episode}}",
"types": {
"movie": "ภาพยนตร์",
"show": "ซีรี่ย์"
}
},
"navigation": {
"banner": {
"offline": "ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ"
},
"menu": {
"about": "เกี่ยวกับเรา",
"donation": "บริจาค",
"logout": "ออกจากระบบ",
"register": "เชื่อมต่อกับคลาวด์",
"settings": "ตั้งค่า",
"support": "ช่วยเหลือ"
}
},
"notFound": {
"badge": "ไม่พบ",
"goHome": "กลับไปที่หน้าหลัก",
"message": "เรามองหาทั่วทุกที่แล้ว : ใต้ถังขยะ ในตู้เสื้อผ้า และข้างหลังพร็อกซิ แต่เราไม่พบหน้าที่คุณตามหา",
"title": "ไม่พบหน้านั้น"
},
"overlays": {
"close": "ปิด"
},
"player": {
"back": {
"default": "กลับไปที่หน้าหลัก",
"short": "กลับ"
},
"casting": {
"enabled": "เชื่อมต่ออุปกรณ์.."
},
"menus": {
"captions": {
"customChoice": "เลือกคําบรรยายจากไฟล์",
"customizeLabel": "ปรับแต่ง",
"offChoice": "ปิด",
"settings": {
"delay": "คําบรรยายล่าช้า",
"fixCapitals": "แก้ไขตัวพิมพ์ใหญ่"
},
"title": "คําบรรยาย",
"unknownLanguage": "ไม่ทราบ"
},
"downloads": {
"disclaimer": "การดาวน์โหลดจะถูกนํามาจากผู้ให้บริการโดยตรง movie-web ไม่สามารถควบคุมวิธีการดาวน์โหลดได้",
"downloadCaption": "ดาวน์โหลดคำบรรยายปัจจุบัน",
"downloadVideo": "ดาวน์โหลดวิดีโอ",
"hlsExplanation": "สื่อนี้เป็นสตรีม HLS ซึ่งไม่สามารถดาวน์โหลดบนเว็บภาพยนตร์ได้",
"onAndroid": {
"1": "หากต้องการดาวน์โหลดบน Android ให้คลิกปุ่มดาวน์โหลด จากนั้นในหน้าใหม่ <bold>แตะ</bold> บนวิดีโอค้างไว้ จากนั้นเลือก <bold>บันทึก</bold>",
"shortTitle": "ดาวน์โหลด / Android",
"title": "กําลังดาวน์โหลดบน Android"
},
"onIos": {
"1": "หากต้องการดาวน์โหลดบน iOS ให้คลิกปุ่มดาวน์โหลด จากนั้นในหน้าใหม่ ให้คลิก <bold><ios_ส่งต่อ /></bold> จากนั้น <bold>บันทึกลงในไฟล์ <ios_ไฟล์/></bold>",
"shortTitle": "ดาวน์โหลด / iOS",
"title": "กําลังดาวน์โหลดบน iOS"
},
"onPc": {
"1": "คลิกปุ่มดาวน์โหลดบนคอมพิวเตอร์ จากนั้นในหน้าใหม่ คลิกขวาที่วิดีโอแล้วเลือก <bold>บันทึกวิดีโอเป็น</bold>",
"shortTitle": "ดาวน์โหลด / คอมพิวเตอร์",
"title": "กําลังดาวน์โหลดบนคอมพิวเตอร์"
},
"title": "ดาวน์โหลด"
},
"episodes": {
"button": "ตอน",
"emptyState": "ไม่มีตอนเหล่านี้ในภาคนี้ โปรดกลับมาดูทีหลัง!",
"episodeBadge": "ต{{episode}}",
"loadingError": "การโหลดภาคผิดพลาด",
"loadingList": "กำลังโหลด…",
"loadingTitle": "กำลังโหลด…"
},
"playback": {
"speedLabel": "ความเร็วในการเล่น",
"title": "การตั้งค่าการเล่น"
},
"quality": {
"automaticLabel": "คุณภาพอัตโนมัติ",
"hint": "คุณสามารถลอง <0>สลับแหล่งที่มา</0> เพื่อรับตัวเลือกคุณภาพที่แตกต่างกัน",
"iosNoQuality": "เนื่องจากข้อจํากัดที่กําหนดโดย Apple การเลือกคุณภาพจึงไม่พร้อมใช้งานบน iOS สําหรับแหล่งข้อมูลนี้ คุณสามารถลอง <0>สลับไปยังแหล่งอื่น</0> เพื่อรับตัวเลือกคุณภาพที่แตกต่างกัน",
"title": "คุณภาพ"
},
"settings": {
"captionItem": "การตั้งค่าคําบรรยาย",
"downloadItem": "ดาวน์โหลด",
"enableCaptions": "เปิดใช้งานคําบรรยาย",
"experienceSection": "ประสบการณ์รับชม",
"playbackItem": "ตั้งค่าการเล่น",
"qualityItem": "คุณภาพ",
"sourceItem": "แหล่งที่มาของวิดีโอ",
"videoSection": "ตั้งค่าวิดีโอ"
},
"sources": {
"failed": {
"text": "มีข้อผิดพลาดขณะพยายามค้นหาวิดีโอ โปรดลองใช้แหล่งอื่น",
"title": "ค้นหาไม่พบ"
},
"noEmbeds": {
"text": "เราไม่พบการฝังใด ๆ โปรดลองใช้แหล่งอื่น",
"title": "ไม่พบการฝัง"
},
"noStream": {
"text": "แหล่งที่มานี้ไม่มีสตรีมสำหรับภาพยนตร์หรือซีรี่ย์เรื่องนี้",
"title": "ไม่มีสตรีม"
},
"title": "แหล่งที่มา",
"unknownOption": "ไม่ทราบ"
}
},
"metadata": {
"failed": {
"badge": "ล้มเหลว",
"homeButton": "กลับหน้าหลัก",
"text": "ไม่สามารถโหลดข้อมูล meta ของสื่อจาก TMDB ได้ โปรดตรวจสอบว่า TMDB ล่มหรือถูกบล็อกในการเชื่อมต่ออินเทอร์เน็ตของคุณ",
"title": "โหลดข้อมูล meta ไม่สำเร็จ"
},
"notFound": {
"badge": "ไม่พบ",
"homeButton": "กลับไปที่หน้าหลัก",
"text": "เราไม่พบสื่อที่คุณร้องขอ ไม่ว่าจะถูกลบออก หรือคุณดัดแปลงกับ URL",
"title": "ไม่พบสื่อ"
}
},
"nextEpisode": {
"cancel": "ยกเลิก",
"next": "ตอนต่อไป"
},
"playbackError": {
"badge": "เกิดข้อผิดพลาดในการเล่น",
"errors": {
"errorAborted": "การดึงข้อมูลสื่อถูกยกเลิกโดยคําขอของผู้ใช้",
"errorDecode": "แม้จะได้รับการพิจารณาก่อนหน้านี้ว่าใช้งานได้ แต่เกิดข้อผิดพลาดขณะพยายามถอดรหัสทรัพยากรสื่อ จึงส่งผลให้เกิดข้อผิดพลาด",
"errorGenericMedia": "เกิดข้อผิดพลาดของสื่อที่ไม่รู้จัก",
"errorNetwork": "เกิดข้อผิดพลาดของเครือข่ายบางประเภทซึ่งทําให้ไม่สามารถดึงสื่อได้สําเร็จ แม้ว่าจะเคยใช้งานมาก่อนก็ตาม",
"errorNotSupported": "ไม่รองรับสื่อหรือวัตถุของผู้ให้บริการสื่อ"
},
"homeButton": "กลับหน้าหลัก",
"text": "เกิดข้อผิดพลาดในการเล่นวิดีโอ โปรดลองอีกครั้ง",
"title": "เกิดข้อผิดพลาดในการเล่นวิดีโอ!"
},
"scraping": {
"items": {
"failure": "เกิดข้อผิดพลาด",
"notFound": "ไม่มีวิดีโอ",
"pending": "กำลังตรวจสอบวิดีโอ…"
},
"notFound": {
"badge": "ไม่พบ",
"detailsButton": "แสดงรายละเอียด",
"homeButton": "กลับหน้าหลัก",
"text": "เราได้ค้นหาผ่านผู้ให้บริการของเราและไม่พบสื่อที่คุณกําลังมองหา! เราไม่ได้เป็นเจ้าของสื่อและไม่สามารถควบคุมสิ่งที่ไม่มีอยู่ได้ โปรดคลิก “แสดงรายละเอียด” ด้านล่างสําหรับรายละเอียดเพิ่มเติม",
"title": "เราไม่พบสิ่งนั้น"
}
},
"time": {
"regular": "{{timeWatched}} / {{duration}}",
"remaining": "{{timeLeft}} หมดเวลา • สิ้นสุดใน {{timeFinished, datetime}}",
"shortRegular": "{{timeWatched}}",
"shortRemaining": "-{{timeLeft}}"
}
},
"screens": {
"dmca": {
"text": "ยินดีต้อนรับสู่หน้าติดต่อ DMCA ของ movie-web! เราเคารพสิทธิ์ในทรัพย์สินทางปัญญาและต้องการแก้ไขข้อกังวลด้านลิขสิทธิ์อย่างรวดเร็ว หากคุณเชื่อว่างานที่มีลิขสิทธิ์ของคุณถูกใช้อย่างไม่เหมาะสมบนแพลตฟอร์มของเรา โปรดส่งประกาศ DMCA โดยละเอียดไปยังอีเมลด้านล่าง โปรดระบุคําอธิบายของเนื้อหาที่มีลิขสิทธิ์ รายละเอียดการติดต่อของคุณ และคําแถลงความเชื่อด้วยความสุจริต เรามุ่งมั่นที่จะแก้ไขเรื่องเหล่านี้อย่างทันท่วงทีและขอขอบคุณสําหรับความร่วมมือของคุณในการทําให้ movie-web เป็นสถานที่ที่เคารพความคิดสร้างสรรค์และลิขสิทธิ์",
"title": "DMCA"
},
"loadingApp": "กําลังโหลดแอปพลิเคชัน",
"loadingUser": "กําลังโหลดโปรไฟล์ของคุณ",
"loadingUserError": {
"logout": "ออกจากระบบ",
"reset": "รีเซ็ตเซิร์ฟเวอร์ที่กําหนดเอง",
"text": "โหลดโปรไฟล์ของคุณไม่สําเร็จ",
"textWithReset": "โหลดโปรไฟล์ของคุณจากเซิร์ฟเวอร์ที่กําหนดเองไม่สําเร็จ ต้องการย้อนกลับไปที่เซิร์ฟเวอร์เริ่มต้นหรือไม่?"
},
"migration": {
"failed": "ไม่สามารถโยกย้ายข้อมูลของคุณได้",
"inProgress": "โปรดรอสักครู่ เรากําลังย้ายข้อมูลของคุณ สิ่งนี้ไม่ควรใช้เวลานาน"
}
},
"settings": {
"account": {
"accountDetails": {
"deviceNameLabel": "ชื่ออุปกรณ์",
"deviceNamePlaceholder": "โทรศัพท์ส่วนบุคคล",
"editProfile": "แก้ไข",
"logoutButton": "ออกจากระบบ"
},
"actions": {
"delete": {
"button": "ลบบัญชี",
"confirmButton": "ลบบัญชี",
"confirmDescription": "คุณแน่ใจหรือไม่ว่าต้องการลบบัญชีของคุณ? ข้อมูลทั้งหมดของคุณจะหายไป!",
"confirmTitle": "คุณแน่ใจใช่หรือไม่?",
"text": "การกระทํานี้ไม่สามารถย้อนกลับได้ ข้อมูลทั้งหมดจะถูกลบและไม่มีอะไรสามารถกู้คืนได้",
"title": "ลบบัญชี"
},
"title": "พฤติกรรม"
},
"devices": {
"deviceNameLabel": "ชื่ออุปกรณ์",
"failed": "โหลดเซสชันไม่สำเร็จ",
"removeDevice": "ลบ",
"title": "อุปกรณ์"
},
"profile": {
"finish": "แก้ไขสำเสร็จ",
"firstColor": "สีของโปรไฟล์ 1",
"secondColor": "สีของโปรไฟล์ 2",
"title": "แก้ไขรูปโปรไฟล์",
"userIcon": "สัญลักษณ์ผู้ใช้"
},
"register": {
"cta": "เริ่มเลย",
"text": "แบ่งปันความคืบหน้าการรับชมระหว่างอุปกรณ์และเชื่อมต่อเข้าด้วยกัน",
"title": "เชื่อมต่อกับคลาวด์"
},
"title": "บัญชี"
},
"appearance": {
"activeTheme": "ใช้งาน",
"themes": {
"blue": "ฟ้า",
"default": "ค่าเริ่มต้น",
"gray": "เทา",
"red": "แดง",
"teal": "เขียวแกมน้ำเงิน"
},
"title": "ธีม"
},
"captions": {
"backgroundLabel": "ความทึบของพื้นหลัง",
"colorLabel": "สี",
"previewQuote": "ข้าต้องไม่กลัว ความกลัวจักพิฆาตจิตใจ",
"textSizeLabel": "ขนาดตัวหนังสือ",
"title": "คำบรรยาย"
},
"connections": {
"server": {
"description": "หากคุณต้องการเชื่อมต่อกับ backend ที่กําหนดเองเพื่อจัดเก็บข้อมูลของคุณ ให้เปิดใช้งานสิ่งนี้และระบุ URL",
"label": "กำหนดเซิร์ฟเวอร์เอง",
"urlLabel": "กําหนด URL เซิร์ฟเวอร์เอง"
},
"title": "การเชื่อมต่อ",
"workers": {
"addButton": "เพิ่มผู้ช่วยใหม่",
"description": "เพื่อให้แอปพลิเคชันทํางาน การรับส่งข้อมูลทั้งหมดจะถูกส่งผ่านพร็อกซี่ เปิดใช้งานสิ่งนี้หากคุณต้องการนำเข้างานของคุณเอง",
"emptyState": "ยังไม่มีลูกทีม เพิ่มหนึ่งคนด้านล่าง",
"label": "ใช้พร็อกซี่แบบกําหนดเอง",
"urlLabel": "ลูกทีม URLs",
"urlPlaceholder": "https://"
}
},
"locale": {
"language": "ภาษา",
"languageDescription": "ภาษาที่ใช้กับแอปพลิเคชันทั้งหมด",
"title": "ตำแหน่งที่ตั้ง"
},
"reset": "เริ่มใหม่",
"save": "บันทึก",
"sidebar": {
"info": {
"appVersion": "เวอร์ชันแอป",
"backendUrl": "ระบบจัดการเว็บไซต์ URL",
"backendVersion": "เวอร์ชันหลัก",
"hostname": "ชื่อผู้ใช้",
"insecure": "ไม่ปลอดภัย",
"notLoggedIn": "คุณไม่ได้เข้าสู่ระบบ",
"secure": "ความปลอดภัย",
"title": "ข้อมูลแอปพลิเคชัน",
"unknownVersion": "ไม่ทราบ",
"userId": "รหัสผู้ใช้"
}
},
"unsaved": "คุณได้บันทึกการเปลี่ยนแปลงแล้ว"
}
}

View File

@@ -1,23 +1,103 @@
{
"about": {
"description": "movie-web internette akışlar için tarama yapan bir web uygulamasıdır. Ekip, içerik tüketmeye karşı ekseriyetle minimalist bir yaklaşım sergilemektedir.",
"faqTitle": "Sıkça sorulan sorular",
"q1": {
"body": "movie-web herhangi bir içerik barındırmaz. İzlemek için bir şeye tıkladığınızda, seçilen medya için internette arama yapılır (Yükleme ekranında ve 'video kaynakları' sekmesinde hangi kaynağı kullandığınızı görebilirsiniz). Medya hiçbir zaman movie-web tarafından yüklenmez, her şey bu arama mekanizması aracılığıyla gerçekleşir.",
"title": "İçerikler nereden geliyor?"
},
"q2": {
"body": "Bir dizi veya film talep etmek mümkün değildir, movie-web içeriklerin hiçbirini yönetmez. Tüm içerikler internet üzerindeki kaynaklar aracılığıyla görüntülenir.",
"title": "Bir dizi veya filmi nereden talep edebilirim?"
},
"q3": {
"body": "Arama sonuçlarımız The Movie Database (TMDB) tarafından desteklenmektedir ve kaynaklarımızın içeriğe gerçekten sahip olup olmadığına bakılmaksızın görüntülenir.",
"title": "Arama sonuçları diziyi veya filmi gösteriyor, neden oynatamıyorum?"
},
"title": "movie-web hakkında"
},
"actions": {
"copied": "Kopyalandı",
"copy": "Kopyala"
},
"auth": {
"createAccount": "Henüz bir hesabınız yok mu? <0>Hesap oluşturun.</0>",
"deviceNameLabel": "Cihaz ismi",
"deviceNamePlaceholder": "Kişisel telefon",
"generate": {
"description": "Parolanız, kullanıcı adınız ve şifreniz olarak işlev görür. Hesabınıza giriş yapmak için bu parolayı girmeniz gerekeceğinden onu güvende tuttuğunuzdan emin olun",
"next": "Parolamı kaydettim",
"title": "Parolanız"
},
"hasAccount": "Zaten hesabınız var mı?<0>Giriş yapın.</0>",
"login": {
"title": "Hesabınıza giriş yapın"
"description": "Hesabınıza giriş yapmak için lütfen parolanızı girin",
"deviceLengthError": "Lütfen bir cihaz ismi girin",
"passphraseLabel": "12 kelimelik parola",
"passphrasePlaceholder": "Parola",
"submit": "Giriş yap",
"title": "Hesabınıza giriş yapın",
"validationError": "Yanlış veya eksik parola"
},
"register": {
"information": {
"color1": "Birinci hesap rengi",
"color2": "İkinci hesap rengi",
"header": "Cihazınız için bir isim girin, zevkinize göre renk ve kullanıcı simgesi seçin",
"icon": "Kullanıcı simgesi",
"next": "İleri",
"title": "Hesap bilgisi"
}
},
"trust": {
"failed": {
"text": "Doğru şekilde yapılandırdınız mı?",
"title": "Sunucuya ulaşılamadı"
},
"host": "<0>{{hostname}}</0> adlı sunucuya bağlanıyorsunuz - lütfen hesap oluşturmadan önce sunucuya güvendiğinizi onaylayın",
"no": "Geri git",
"title": "Bu sunucuya güveniyor musunuz?",
"yes": "Bu sunucuya güveniyorum"
},
"verify": {
"description": "Kaydettiğinizi doğrulamak ve hesabınızı oluşturmak için önceki aşamada gösterilen parolayı girin",
"invalidData": "Veri geçersiz",
"noMatch": "Parola eşleşmiyor",
"passphraseLabel": "12 kelimelik parolanız",
"recaptchaFailed": "ReCaptcha doğrulaması başarısız",
"register": "Hesap oluştur",
"title": "Parolanızı doğrulayın"
}
},
"errors": {
"badge": "Bir şeyler ters gitti",
"details": "Hata detayları",
"reloadPage": "Sayfayı yenile",
"showError": "Hata detaylarını göster",
"title": "Bir hatayla karşılaştık!"
},
"footer": {
"legal": {
"disclaimer": "Sorumluluk Reddi",
"disclaimerText": "movie-web herhangi bir dosya barındırmaz, yalnızca 3. parti hizmetlere bağlantı verir. Yasal meseleler, dosya barındırıcıları ve sağlayıcıları ile görüşülmelidir. movie-web, video sağlayıcıları tarafından gösterilen hiçbir medya dosyasından sorumlu değildir."
},
"links": {
"discord": "Discord",
"dmca": "DMCA",
"github": "GitHub"
},
"tagline": "Bu açık kaynaklı akış uygulamasıyla en sevdiğiniz dizileri ve filmleri izleyin."
},
"global": {
"name": "movie-web"
"name": "movie-web",
"pages": {
"about": "Hakkında",
"dmca": "DMCA",
"login": "Giriş yap",
"pagetitle": "{{title}} - movie-web",
"register": "Kayıt ol",
"settings": "Ayarlar"
}
},
"home": {
"bookmarks": {
@@ -26,6 +106,9 @@
"continueWatching": {
"sectionTitle": "İzlemeye devam edin"
},
"mediaList": {
"stopEditing": "Düzenlemeyi bırak"
},
"search": {
"allResults": "Bu kadarını bulabildik!",
"failed": "Medya bulunamadı, tekrar deneyin!",
@@ -33,6 +116,23 @@
"noResults": "Hiçbir şey bulamadık!",
"placeholder": "Ne izlemek istersiniz?",
"sectionTitle": "Arama sonuçları"
},
"titles": {
"day": {
"default": "Bu öğleden sonra ne izlemek istersiniz?"
},
"morning": {
"default": "Bu sabah ne izlemek istersiniz?",
"extra": [
"Before Sunrise'a iyi diyorlar"
]
},
"night": {
"default": "Bu akşam ne izlemek istersiniz?",
"extra": [
"Yoruldun mu? The Exorcist'e iyi diyorlar."
]
}
}
},
"media": {
@@ -45,11 +145,19 @@
"navigation": {
"banner": {
"offline": "İnternet bağlantınızı kontrol ediniz"
},
"menu": {
"about": "Hakkımızda",
"donation": "Bağış yap",
"logout": ıkış yap",
"register": "Buluta eşitle",
"settings": "Ayarlar",
"support": "Destekle"
}
},
"notFound": {
"badge": "Bulunamadı",
"goHome": "Geri",
"goHome": "Ana sayfaya dön",
"message": "Her yere baktık: bazanın altına, dolabın içine hatta ara sunucuya ama maalesef aradığınız sayfayı bulamadık.",
"title": "Sayfa bulunamadı"
},
@@ -61,31 +169,252 @@
"default": "Ana sayfaya dön",
"short": "Geri"
},
"casting": {
"enabled": "Cihaza yansıtılıyor..."
},
"menus": {
"captions": {
"customChoice": "Altyazı yükle",
"customizeLabel": "Kişiselleştirme",
"title": "Altyazılar"
"customizeLabel": "Seçenekler",
"offChoice": "Kapalı",
"settings": {
"delay": "Altyazı gecikmesi",
"fixCapitals": "Büyük harf kullanımını düzelt"
},
"title": "Altyazılar",
"unknownLanguage": "Bilinmeyen"
},
"downloads": {
"disclaimer": "İndirme bağlantıları doğrudan sağlayıcının kendisinden alınır. movie-web'in sağlanan indirme bağlantıları üzerinde hiçbir konrolü yoktur.",
"downloadCaption": "Geçerli altyazıyı indir",
"downloadVideo": "Videoyu indir",
"hlsExplanation": "Bu medya, movie-web üzerinden indirilemeyen bir HLS akışıdır.",
"onAndroid": {
"1": "Android'e indirmek için önce indir butonuna basın, sonra açılan yeni sayfada video üzerine <bold>basılı tutun</bold>, ardından <bold>Videoyu indir</bold> seçeneğini seçin.",
"shortTitle": "İndir / Android",
"title": "Android'e indirme"
},
"onIos": {
"1": "iOS'a indirmek için öncelikle indir butonuna basın, sonra açılan yeni sayfada <bold><ios_share /></bold>'e basın, hemen ardından <bold>Dosyalara Kaydet <ios_files /></bold>'e basın.",
"shortTitle": "İndir / iOS",
"title": "iOS'a indirme"
},
"onPc": {
"1": "Bilgisayarda önce indir butonuna tıklayın, sonra ise açılan yeni sayfada videoya sağ tıklayın ve <bold>Videoyu farklı kaydet </bold> seçeneğini seçin",
"shortTitle": "İndir / Bilgisayar",
"title": "Bilgisayara indirme"
},
"title": "İndir"
},
"episodes": {
"button": "Bölümler",
"emptyState": "Bu sezonda hiç bölüm yok, sonra tekrar deneyin!",
"episodeBadge": "B{{episode}}",
"loadingError": "Sezon yüklenirken hata oluştu",
"loadingList": "Yükleniyor...",
"loadingTitle": "Yükleniyor..."
},
"playback": {
"speedLabel": "Oynatma hızı",
"title": "Oynatma ayarları"
},
"quality": {
"automaticLabel": "Otomatik kalite",
"hint": "Farklı kalite seçenekleri elde etmek için <0>kaynak değiştirmeyi</0> deneyebilirsiniz.",
"iosNoQuality": "Apple tarafından tanımlanan sınırlamalar nedeniyle, iOS'ta bu kaynak için kalite seçimi mevcut değildir. Farklı kalite seçenekleri elde etmek için <0>başka bir kaynağa geçmeyi</0> deneyebilirsiniz.",
"title": "Kalite"
},
"settings": {
"captionItem": "Altyazı ayarları",
"downloadItem": "İndir",
"enableCaptions": "Altyazıları etkinleştir",
"experienceSection": "İzleme deneyimi",
"playbackItem": "Oynatma ayarları",
"qualityItem": "Kalite",
"sourceItem": "Video kaynakları",
"videoSection": "Video ayarları"
},
"sources": {
"title": "Kaynaklar"
"failed": {
"text": "Video bulunmaya çalışılırken sorun oluştu, lütfen farklı bir kaynak deneyin.",
"title": "Video alınırken sorun oluştu"
},
"noEmbeds": {
"text": "Herhangi bir video bulamadık, lütfen farklı bir kaynak deneyin.",
"title": "Video bulunamadı"
},
"noStream": {
"text": "Bu kaynakta bu film ya da dizi için akış bulunmamaktadır.",
"title": "Akış yok"
},
"title": "Kaynaklar",
"unknownOption": "Bilinmeyen"
}
},
"metadata": {
"failed": {
"badge": "Başarısız oldu",
"homeButton": "Ana sayfaya dön",
"text": "Medyanın üstverisi TMDB'den alınamadı. Lütfen TMDB'nin göküp çökmediğini veya internet bağlantınız üzerinden engellenmediğini kontrol edin.",
"title": "Üstveri yüklenemedi"
},
"notFound": {
"badge": "Bulunamadı",
"homeButton": "Geri",
"text": "İstediğiniz medyayı bulamadık. URL'i yanlış girdiniz ya da medya kaldırıldı.",
"homeButton": "Ana sayfaya dön",
"text": "İstediğiniz medyayı bulamadık. URL'yi yanlış girdiniz ya da medya kaldırıldı.",
"title": "Medya bulunamadı."
}
},
"nextEpisode": {
"cancel": "Vazgeç",
"next": "Sonraki bölüm"
},
"playbackError": {
"title": "Hay aksi, bozuldu!"
"badge": "Oynatma hatası",
"errors": {
"errorAborted": "Medyanın alınması kullanıcının isteği üzerine iptal edildi.",
"errorDecode": "Daha önce kullanılabilir olduğu belirlenmiş olmasına rağmen, medya kaynağının kodu çözülmeye çalışılırken bir hata oluştu ve bu da bir hataya neden oldu.",
"errorGenericMedia": "Bilinmeyen medya hatası oluştu.",
"errorNetwork": "Medya önceden mevcut olsa da başarıyla alınmasını engelleyen bir tür ağ hatası oluştu.",
"errorNotSupported": "Medya veya medya sağlayıcısı desteklenmiyor."
},
"homeButton": "Ana sayfaya git",
"text": "Medya oynatılmaya çalışılırken bir hata oluştu. Lütfen tekrar deneyin.",
"title": "Video oynatılamadı!"
},
"scraping": {
"items": {
"failure": "Hata oluştu",
"notFound": "Video mevcut değil",
"pending": "Videolar aranıyor..."
},
"notFound": {
"badge": "Bulunamadı",
"detailsButton": "Detayları göster",
"homeButton": "Ana sayfaya git",
"text": "Sağlayıcılarımız arasında arama yaptık ve aradığınız medyayı bulamadık! Medyaları barındırmıyoruz ve mevcut olanlar üzerinde hiçbir kontrolümüz yok. Daha fazla ayrıntı için lütfen aşağıdaki 'Ayrıntıları göster' seçeneğine tıklayın.",
"title": "Bunu bulamadık"
}
},
"time": {
"regular": "{{timeWatched}} / {{duration}}",
"remaining": "{{timeLeft}} kaldı • {{timeFinished, datetime}}'de bitiyor",
"shortRegular": "{{timeWatched}}",
"shortRemaining": "-{{timeLeft}}"
}
},
"screens": {
"dmca": {
"text": "Movie-web'in DMCA iletişim sayfasına hoş geldiniz! Fikri mülkiyet haklarına saygı duyuyoruz ve telif hakkıyla ilgili endişeleri hızlı bir şekilde ele almak istiyoruz. Telif hakkıyla korunan çalışmanızın platformumuzda uygunsuz şekilde kullanıldığını düşünüyorsanız lütfen aşağıdaki e-postaya ayrıntılı bir DMCA bildirimi gönderin. Lütfen telif hakkıyla korunan materyalin açıklamasını, iletişim bilgilerinizi ve iyi niyet beyanınızı ekleyin. Bu sorunları derhal çözmeye kararlıyız ve ayrıca movie-web'i yaratıcılığa ve telif haklarına saygılı bir yer olarak tutma konusundaki işbirliğiniz için teşekkür ederiz.",
"title": "DMCA"
},
"loadingApp": "Uygulama yükleniyor",
"loadingUser": "Profiliniz yükleniyor",
"loadingUserError": {
"logout": ıkış yap",
"reset": "Özel sunucuyu sıfırla",
"text": "Profiliniz yüklenemedi",
"textWithReset": "Profiliniz özel sunucunuzdan yüklenemedi, varsayılan sunucuya sıfırlamak ister misiniz?"
},
"migration": {
"failed": "Verileriniz taşınamadı.",
"inProgress": "Lütfen bekleyin, verilerinizi taşıyoruz. Bu çok uzun sürmez."
}
},
"settings": {
"account": {
"accountDetails": {
"deviceNameLabel": "Cihaz adı",
"deviceNamePlaceholder": "Kişisel telefon",
"editProfile": "Düzenle",
"logoutButton": ıkış yap"
},
"actions": {
"delete": {
"button": "Hesabı sil",
"confirmButton": "Hesabı sil",
"confirmDescription": "Hesabınızı silmek istediğinize emin misiniz? Tüm verileriniz kaybedilecek!",
"confirmTitle": "Emin misiniz?",
"text": "Bu işlem geri alınamaz. Tüm veriler silinir ve hiçbir şey kurtarılamaz.",
"title": "Hesabı sil"
},
"title": "Eylemler"
},
"devices": {
"deviceNameLabel": "Cihaz adı",
"failed": "Oturumlar yüklenemedi",
"removeDevice": "Sil",
"title": "Cihazlar"
},
"profile": {
"finish": "Düzenlemeyi bitir",
"firstColor": "Birinci profil rengi",
"secondColor": "İkinci profil rengi",
"title": "Profil resmini düzenle",
"userIcon": "Kullanıcı simgesi"
},
"register": {
"cta": "Hadi başlayalım",
"text": "İzleme ilerlemenizi cihazlar arasında paylaşın ve eşit tutun.",
"title": "Buluta eşitle"
},
"title": "Hesap"
},
"appearance": {
"activeTheme": "Aktif",
"themes": {
"blue": "Mavi",
"default": "Varsayılan",
"gray": "Gri",
"red": "Kırmızı",
"teal": "Camgöbeği"
},
"title": "Görünüm"
},
"captions": {
"backgroundLabel": "Arka plan opaklığı",
"colorLabel": "Renk",
"previewQuote": "Korkmamalıyım. Korku aklı öldürür.",
"textSizeLabel": "Yazı boyutu",
"title": "Altyazılar"
},
"connections": {
"server": {
"description": "Verilerinizi depolamak için özel bir arkayüze bağlanmak istiyorsanız, bunu etkinleştirin ve URL'yi sağlayın.",
"label": "Özel sunucu",
"urlLabel": "Özel sunucu URL'si"
},
"title": "Bağlantılar",
"workers": {
"addButton": "Yeni işleyici ekle",
"description": "Uygulamanın çalışması için tüm trafik vekil sunucular üzerinden yönlendirilir. Kendi işleyicilerinizi getirmek istiyorsanız bunu etkinleştirin.",
"emptyState": "Henüz işleyici yok, aşağıya bir tane ekleyin",
"label": "Özel vekil sunucu işleyici kullan",
"urlLabel": "İşleyici URL'leri",
"urlPlaceholder": "https://"
}
},
"locale": {
"language": "Uygulama dili",
"languageDescription": "Uygulamanın tamamına uygulanan dil.",
"title": "Yerelleştirme"
},
"reset": "Sıfırla",
"save": "Kaydet",
"sidebar": {
"info": {
"appVersion": "Uygulama sürümü",
"backendUrl": "Arkayüz URL'si",
"backendVersion": "Arkayüz sürümü",
"hostname": "Ana makine adı",
"insecure": "Güvensiz",
"notLoggedIn": "Giriş yapmadınız",
"secure": "Güvenli",
"title": "Uygulama bilgisi",
"unknownVersion": "Bilinmeyen",
"userId": "Kullanıcı Kimliği"
}
},
"unsaved": "Kaydedilmemiş değişiklikleriniz mevcut"
}
}

View File

@@ -1,71 +1,420 @@
{
"global": {
"name": "movie-web"
"about": {
"description": "movie-web 是一个在互联网上搜寻流媒体的 Web 应用程序。团队致力于让用户采取最简约的方式消费内容。",
"faqTitle": "常见问题",
"q1": {
"body": "movie-web 不托管任何内容。您点选观看内容时,系统均从互联网搜寻(在加载提示页和“视频源”选项卡中,您可以看到正在使用的源)。媒体从未在 movie-web 中上传,所有内容均通过搜索机制而得。",
"title": "内容来自哪里?"
},
"home": {
"search": {
"allResults": "以上是我们能找到的所有结果!",
"sectionTitle": "搜索结果",
"noResults": "我们找不到任何结果!",
"failed": "查找媒体失败,请重试!",
"loading": "载入中……",
"placeholder": "您想看些什么?"
},
"bookmarks": {
"sectionTitle": "书签"
},
"continueWatching": {
"sectionTitle": "继续观看"
}
"q2": {
"body": "无法请求影视剧或其他节目movie-web 不管理任何内容。所有内容均从互联网视频源获取并供您观看。",
"title": "我可以从哪里请求观看影视剧或其他节目?"
},
"media": {
"types": {
"movie": "电影",
"show": "连续剧"
},
"episodeDisplay": "第{{season}}季 第{{episode}}集"
"q3": {
"body": "我们的搜索结果由电影数据库TMDB驱动无论视频源是否有对应内容均会显示结果。",
"title": "搜索结果中已显示了影视剧或其他节目,为何我无法播放?"
},
"player": {
"playbackError": {
"title": "哎呀,出问题了!"
},
"metadata": {
"notFound": {
"badge": "未找到",
"homeButton": "返回首页",
"title": "无法找到媒体.",
"text": "我们无法找到您请求的媒体。它可能已被删除,或您篡改了 URL."
}
},
"menus": {
"captions": {
"customChoice": "上传字幕",
"customizeLabel": "自定义",
"title": "字幕"
},
"sources": {
"title": "视频源"
},
"episodes": {
"button": "分集",
"loadingTitle": "载入中……",
"loadingList": "载入中……"
}
},
"back": {
"default": "返回首页",
"short": "返回"
}
"title": "关于 movie-web"
},
"actions": {
"copied": "已复制",
"copy": "复制"
},
"auth": {
"createAccount": "还没有账户? <0>创建一个。</0>",
"deviceNameLabel": "设备名称",
"deviceNamePlaceholder": "如:个人电话",
"generate": {
"description": "您的密码短语相当于用户名与密码。由于您需要输入它来登录账户,请确保将其存放到安全位置",
"next": "我已保存密码短语",
"title": "您的密码短语"
},
"notFound": {
"badge": "未找到",
"goHome": "返回首页",
"title": "无法找到页面",
"message": "我们已经到处找过了:不管是垃圾桶下、橱柜里或是代理之后。但最终并没有发现您查找的页面。"
"hasAccount": "已经拥有账户?<0>点击此处登录。</0>",
"login": {
"description": "请输入密码短语以登录您的账户",
"deviceLengthError": "请输入设备名称",
"passphraseLabel": "12 词密码短语",
"passphrasePlaceholder": "密码短语",
"submit": "登录",
"title": "登录到您的账户",
"validationError": "密码短语不正确或不完整"
},
"navigation": {
"banner": {
"offline": "检查您的互联网连接"
}
"register": {
"information": {
"color1": "个人资料颜色 1",
"color2": "个人资料颜色 2",
"header": "为您的设备输入名称,并选取一组代表色和一个用户图标",
"icon": "用户图标",
"next": "下一步",
"title": "账户信息"
}
},
"trust": {
"failed": {
"text": "您是否正确配置了它?",
"title": "服务器连通失败"
},
"host": "您正在连接到 <0>{{hostname}}</0> - 在创建账户前,确保您信任它",
"no": "返回",
"title": "您是否信任这个服务器?",
"yes": "我信任这个服务器"
},
"verify": {
"description": "请输入早先的密码短语,以确认您已经保存它,并创建您的账户",
"invalidData": "数据无效",
"noMatch": "密码短语不匹配",
"passphraseLabel": "您的 12 词密码短语",
"recaptchaFailed": "ReCaptcha 验证失败",
"register": "创建账户",
"title": "确认您的密码短语"
}
},
"errors": {
"badge": "它损坏了",
"details": "错误细节",
"reloadPage": "刷新页面",
"showError": "显示错误细节",
"title": "我们遇到了错误!"
},
"footer": {
"legal": {
"disclaimer": "免责声明",
"disclaimerText": "movie-web 不托管任何文件,仅链接到第三方服务。 法律问题应由文件托管者和内容提供者解决。 movie-web 对视频提供者显示的任何媒体文件不承担任何责任。"
},
"links": {
"discord": "Discord",
"dmca": "DMCA",
"github": "GitHub"
},
"tagline": "在这个开源流媒体应用上观看你最喜爱的影视剧或其他节目。"
},
"global": {
"name": "movie-web",
"pages": {
"about": "关于",
"dmca": "DMCA",
"login": "登录",
"pagetitle": "{{title}} - movie-web",
"register": "注册",
"settings": "设置"
}
},
"home": {
"bookmarks": {
"sectionTitle": "书签"
},
"continueWatching": {
"sectionTitle": "继续观看"
},
"mediaList": {
"stopEditing": "停止编辑"
},
"search": {
"allResults": "以上是我们能找到的所有结果!",
"failed": "查找媒体失败,请重试!",
"loading": "载入中……",
"noResults": "我们找不到任何结果!",
"placeholder": "您想看些什么?",
"sectionTitle": "搜索结果"
},
"titles": {
"day": {
"default": "您今天下午想看什么?"
},
"morning": {
"default": "您今早想看什么?",
"extra": [
"我听说《爱在黎明破晓前》不错"
]
},
"night": {
"default": "您今晚想看什么?",
"extra": [
"累了?我听说《驱魔人》不错。"
]
}
}
},
"media": {
"episodeDisplay": "第{{season}}季 第{{episode}}集",
"types": {
"movie": "电影",
"show": "连续剧"
}
},
"navigation": {
"banner": {
"offline": "检查您的互联网连接"
},
"menu": {
"about": "关于我们",
"donation": "捐赠",
"logout": "登出",
"register": "同步到云端",
"settings": "设置",
"support": "支持"
}
},
"notFound": {
"badge": "未找到",
"goHome": "返回首页",
"message": "我们已经到处找过了:不管是垃圾桶下、橱柜里或是代理之后。但最终并没有发现您查找的页面。",
"title": "无法找到页面"
},
"overlays": {
"close": "关闭"
},
"player": {
"back": {
"default": "返回首页",
"short": "返回"
},
"casting": {
"enabled": "正在投放到设备…"
},
"menus": {
"captions": {
"customChoice": "上传字幕",
"customizeLabel": "自定义",
"offChoice": "关闭",
"settings": {
"delay": "字幕延迟",
"fixCapitals": "修复大小写"
},
"title": "字幕",
"unknownLanguage": "未知"
},
"downloads": {
"disclaimer": "下载内容是直接从内容提供者获取的。movie-web 无法控制下载内容如何被提供。",
"downloadCaption": "下载当前字幕",
"downloadVideo": "下载视频",
"hlsExplanation": "该媒体为 HLS 流,因此无法从 movie-web 下载。",
"onAndroid": {
"1": "要从 Android 下载,先点击下载按钮,之后在新的页面上, <bold>点击并按住</bold>视频,然后选择 <bold>保存</bold>。",
"shortTitle": "下载 / Android",
"title": "正在 Android 上下载"
},
"onIos": {
"1": "要从 iOS 下载,点击下载按钮,之后在新的页面上,点击 <bold><ios_share /></bold>,然后<bold>保存到文件 <ios_files /></bold>。",
"shortTitle": "下载 / iOS",
"title": "正在 iOS 上下载"
},
"onPc": {
"1": "在 PC 上,点击下载按钮,之后在新的页面上,右击视频并选择 <bold>另存视频为</bold>",
"shortTitle": "下载 / PC",
"title": "正在 PC 上下载"
},
"title": "下载"
},
"episodes": {
"button": "分集",
"emptyState": "该季暂无剧集,请稍后再来!",
"episodeBadge": "第{{episode}}集",
"loadingError": "加载分季时发生错误",
"loadingList": "载入中……",
"loadingTitle": "载入中……"
},
"playback": {
"speedLabel": "播放速度",
"title": "播放设置"
},
"quality": {
"automaticLabel": "自动质量",
"hint": "您可以尝试 <0>切换视频源</0>以获取不同的质量选项。",
"iosNoQuality": "由于苹果施加的限制,该视频源的质量选择在 iOS 上不可用。您可以尝试 <0>切换到其他视频源</0>以获取不同的质量选项。",
"title": "质量"
},
"settings": {
"captionItem": "字幕设置",
"downloadItem": "下载",
"enableCaptions": "启用字幕",
"experienceSection": "观看体验",
"playbackItem": "播放设置",
"qualityItem": "质量",
"sourceItem": "视频源",
"videoSection": "视频设置"
},
"sources": {
"failed": {
"text": "尝试获取任何视频时均发生错误,请尝试其他视频源。",
"title": "刮取失败"
},
"noEmbeds": {
"text": "我们无法找到任何嵌入内容,请尝试其他视频源。",
"title": "未找到嵌入内容"
},
"noStream": {
"text": "此源没有该影片或剧集的串流。",
"title": "没有流"
},
"title": "视频源",
"unknownOption": "未知"
}
},
"metadata": {
"failed": {
"badge": "失败",
"homeButton": "返回首页",
"text": "无法从 TMDB 载入媒体的元数据。请检查 TMDB 是否掉线或在您的网络连接中被屏蔽。",
"title": "载入元数据失败"
},
"notFound": {
"badge": "未找到",
"homeButton": "返回首页",
"text": "我们无法找到您请求的媒体。它可能已被删除,或您篡改了 URL.",
"title": "无法找到媒体。"
}
},
"nextEpisode": {
"cancel": "取消",
"next": "下一集"
},
"playbackError": {
"badge": "播放错误",
"errors": {
"errorAborted": "媒体获取已根据用户请求中止。",
"errorDecode": "尽管媒体资源此前已确定可用,但在尝试解码时产生问题,导致错误。",
"errorGenericMedia": "发生了未知的媒体错误。",
"errorNetwork": "发生了一些网络错误,导致媒体无法成功获取,尽管此前它可用。",
"errorNotSupported": "该媒体或媒体提供者对象不被支持。"
},
"homeButton": "返回首页",
"text": "尝试播放媒体时发生错误。请重试。",
"title": "视频播放失败!"
},
"scraping": {
"items": {
"failure": "发生了错误",
"notFound": "没有视频",
"pending": "正在检测视频…"
},
"notFound": {
"badge": "未找到",
"detailsButton": "显示细节",
"homeButton": "返回首页",
"text": "我们已在所有内容提供者中搜索,但无法发现您搜寻的媒体内容!我们并不托管媒体内容,也无法控制有什么内容可用。请点击下方的“显示细节”了解更多。",
"title": "我们无法找到它"
}
},
"time": {
"regular": "{{timeWatched}} / {{duration}}",
"remaining": "剩余 {{timeLeft}} • 完结于 {{timeFinished, datetime}}",
"shortRegular": "{{timeWatched}}",
"shortRemaining": "-{{timeLeft}}"
}
},
"screens": {
"dmca": {
"text": "欢迎来到 movie-web 的 DMCA 联系页面!我们尊重知识产权,并希望迅速解决任何版权问题。如果您认为您的受版权保护的作品在我们的平台上被不当使用,请将详细的 DMCA 通知发送至以下电子邮件地址。请附上受版权保护的材料的描述、您的联系方式以及善意信念声明。 我们致力于迅速解决这些问题,并感谢您的合作,让 movie -web 成为尊重创造力和版权的地方。",
"title": "DMCA"
},
"loadingApp": "正在载入应用程序",
"loadingUser": "正在载入您的个人资料",
"loadingUserError": {
"logout": "登出",
"reset": "重设自定义服务器",
"text": "载入您的个人资料失败",
"textWithReset": "从您的自定义服务器载入个人资料失败,您想重设到默认服务器吗?"
},
"migration": {
"failed": "迁移您的数据失败。",
"inProgress": "请稍候,我们正在迁移您的数据。这不会花很多时间。"
}
},
"settings": {
"account": {
"accountDetails": {
"deviceNameLabel": "设备名称",
"deviceNamePlaceholder": "如:个人电话",
"editProfile": "编辑",
"logoutButton": "登出"
},
"actions": {
"delete": {
"button": "删除账户",
"confirmButton": "删除账户",
"confirmDescription": "您确定要删除账户吗?所有数据将会丢失!",
"confirmTitle": "您确定吗?",
"text": "此操作不可逆。所有数据将被删除且无法恢复。",
"title": "删除账户"
},
"title": "操作"
},
"devices": {
"deviceNameLabel": "设备名称",
"failed": "载入会话失败",
"removeDevice": "移除",
"title": "设备"
},
"profile": {
"finish": "完成编辑",
"firstColor": "个人资料颜色 1",
"secondColor": "个人资料颜色 2",
"title": "编辑个人资料图像",
"userIcon": "用户图标"
},
"register": {
"cta": "开始使用",
"text": "在设备之间共享并持续同步您的观看进度。",
"title": "同步到云端"
},
"title": "账户"
},
"appearance": {
"activeTheme": "已激活",
"themes": {
"blue": "蓝色",
"default": "默认",
"gray": "灰色",
"red": "红色",
"teal": "青色"
},
"title": "外观"
},
"captions": {
"backgroundLabel": "背景透明度",
"colorLabel": "颜色",
"previewQuote": "我一定不会害怕。 恐惧是心灵杀手。",
"textSizeLabel": "字体大小",
"title": "字幕"
},
"connections": {
"server": {
"description": "若您想连接到自定义后端保存数据,请启用此选项并提供 URL。",
"label": "自定义服务器",
"urlLabel": "自定义服务器 URL"
},
"title": "连接",
"workers": {
"addButton": "添加新的 Worker",
"description": "要让应用程序正常运作,所有流量会通过代理路由。若您想使用自己的 Worker请启用该选项。",
"emptyState": "还没有 Worker在下方添加一个",
"label": "使用自定义代理 Worker",
"urlLabel": "Worker URL",
"urlPlaceholder": "https://"
}
},
"locale": {
"language": "应用程序语言",
"languageDescription": "当前已应用到整个应用程序的语言。",
"title": "本地化"
},
"reset": "重设",
"save": "保存",
"sidebar": {
"info": {
"appVersion": "应用版本",
"backendUrl": "后端 URL",
"backendVersion": "后端版本",
"hostname": "主机名",
"insecure": "不安全",
"notLoggedIn": "您尚未登录",
"secure": "安全",
"title": "应用信息",
"unknownVersion": "未知",
"userId": "用户 ID"
}
},
"unsaved": "您有未保存的更改"
}
}

View File

@@ -22,7 +22,7 @@ export function getAuthHeaders(token: string): Record<string, string> {
export async function accountLogin(
url: string,
id: string,
deviceName: string
deviceName: string,
): Promise<LoginResponse> {
return ofetch<LoginResponse>("/auth/login", {
method: "POST",

View File

@@ -19,7 +19,7 @@ export interface BookmarkInput {
export function bookmarkMediaToInput(
tmdbId: string,
item: BookmarkMediaItem
item: BookmarkMediaItem,
): BookmarkInput {
return {
meta: {
@@ -35,7 +35,7 @@ export function bookmarkMediaToInput(
export async function addBookmark(
url: string,
account: AccountWithToken,
input: BookmarkInput
input: BookmarkInput,
) {
return ofetch<BookmarkResponse>(
`/users/${account.userId}/bookmarks/${input.tmdbId}`,
@@ -44,14 +44,14 @@ export async function addBookmark(
headers: getAuthHeaders(account.token),
baseURL: url,
body: input,
}
},
);
}
export async function removeBookmark(
url: string,
account: AccountWithToken,
id: string
id: string,
) {
return ofetch<{ tmdbId: string }>(
`/users/${account.userId}/bookmarks/${id}`,
@@ -59,6 +59,6 @@ export async function removeBookmark(
method: "DELETE",
headers: getAuthHeaders(account.token),
baseURL: url,
}
},
);
}

View File

@@ -41,7 +41,7 @@ export function genMnemonic(): string {
export async function signCode(
code: string,
privateKey: Uint8Array
privateKey: Uint8Array,
): Promise<Uint8Array> {
return forge.pki.ed25519.sign({
encoding: "utf8",
@@ -91,7 +91,7 @@ export async function encryptData(data: string, secret: Uint8Array) {
const cipher = forge.cipher.createCipher(
"AES-GCM",
forge.util.createBuffer(secret)
forge.util.createBuffer(secret),
);
cipher.start({
iv,
@@ -104,7 +104,7 @@ export async function encryptData(data: string, secret: Uint8Array) {
const tag = cipher.mode.tag;
return `${forge.util.encode64(iv)}.${stringBufferToBase64(
encryptedData
encryptedData,
)}.${stringBufferToBase64(tag)}` as const;
}
@@ -115,7 +115,7 @@ export function decryptData(data: string, secret: Uint8Array) {
const decipher = forge.cipher.createDecipher(
"AES-GCM",
forge.util.createBuffer(secret)
forge.util.createBuffer(secret),
);
decipher.start({
iv: base64ToStringBuffer(iv),

View File

@@ -9,7 +9,7 @@ import { ProgressInput } from "./progress";
export function importProgress(
url: string,
account: AccountWithToken,
progressItems: ProgressInput[]
progressItems: ProgressInput[],
) {
return ofetch<void>(`/users/${account.userId}/progress/import`, {
method: "PUT",
@@ -22,7 +22,7 @@ export function importProgress(
export function importBookmarks(
url: string,
account: AccountWithToken,
bookmarks: BookmarkInput[]
bookmarks: BookmarkInput[],
) {
return ofetch<void>(`/users/${account.userId}/bookmarks`, {
method: "PUT",

View File

@@ -8,7 +8,7 @@ export interface ChallengeTokenResponse {
export async function getLoginChallengeToken(
url: string,
publicKey: string
publicKey: string,
): Promise<ChallengeTokenResponse> {
return ofetch<ChallengeTokenResponse>("/auth/login/start", {
method: "POST",
@@ -35,7 +35,7 @@ export interface LoginInput {
export async function loginAccount(
url: string,
data: LoginInput
data: LoginInput,
): Promise<LoginResponse> {
return ofetch<LoginResponse>("/auth/login/complete", {
method: "POST",

View File

@@ -23,7 +23,7 @@ export interface ProgressInput {
}
export function progressUpdateItemToInput(
item: ProgressUpdateItem
item: ProgressUpdateItem,
): ProgressInput {
return {
duration: item.progress?.duration ?? 0,
@@ -44,7 +44,7 @@ export function progressUpdateItemToInput(
export function progressMediaItemToInputs(
tmdbId: string,
item: ProgressMediaItem
item: ProgressMediaItem,
): ProgressInput[] {
if (item.type === "show") {
return Object.entries(item.episodes).flatMap(([_, episode]) => ({
@@ -83,7 +83,7 @@ export function progressMediaItemToInputs(
export async function setProgress(
url: string,
account: AccountWithToken,
input: ProgressInput
input: ProgressInput,
) {
return ofetch<ProgressResponse>(
`/users/${account.userId}/progress/${input.tmdbId}`,
@@ -92,7 +92,7 @@ export async function setProgress(
headers: getAuthHeaders(account.token),
baseURL: url,
body: input,
}
},
);
}
@@ -101,7 +101,7 @@ export async function removeProgress(
account: AccountWithToken,
id: string,
episodeId?: string,
seasonId?: string
seasonId?: string,
) {
await ofetch(`/users/${account.userId}/progress/${id}`, {
method: "DELETE",

View File

@@ -9,7 +9,7 @@ export interface ChallengeTokenResponse {
export async function getRegisterChallengeToken(
url: string,
captchaToken?: string
captchaToken?: string,
): Promise<ChallengeTokenResponse> {
return ofetch<ChallengeTokenResponse>("/auth/register/start", {
method: "POST",
@@ -42,7 +42,7 @@ export interface RegisterInput {
export async function registerAccount(
url: string,
data: RegisterInput
data: RegisterInput,
): Promise<RegisterResponse> {
return ofetch<RegisterResponse>("/auth/register/complete", {
method: "POST",

View File

@@ -26,7 +26,7 @@ export async function getSessions(url: string, account: AccountWithToken) {
export async function updateSession(
url: string,
account: AccountWithToken,
update: SessionUpdate
update: SessionUpdate,
) {
return ofetch<SessionResponse[]>(`/sessions/${account.sessionId}`, {
method: "PATCH",
@@ -39,7 +39,7 @@ export async function updateSession(
export async function removeSession(
url: string,
token: string,
sessionId: string
sessionId: string,
) {
return ofetch<SessionResponse[]>(`/sessions/${sessionId}`, {
method: "DELETE",

View File

@@ -18,7 +18,7 @@ export interface SettingsResponse {
export function updateSettings(
url: string,
account: AccountWithToken,
settings: SettingsInput
settings: SettingsInput,
) {
return ofetch<SettingsResponse>(`/users/${account.userId}/settings`, {
method: "PUT",

View File

@@ -119,21 +119,21 @@ export function progressResponsesToEntries(responses: ProgressResponse[]) {
export async function getUser(
url: string,
token: string
token: string,
): Promise<{ user: UserResponse; session: SessionResponse }> {
return ofetch<{ user: UserResponse; session: SessionResponse }>(
"/users/@me",
{
headers: getAuthHeaders(token),
baseURL: url,
}
},
);
}
export async function editUser(
url: string,
account: AccountWithToken,
object: UserEdit
object: UserEdit,
): Promise<{ user: UserResponse; session: SessionResponse }> {
return ofetch<{ user: UserResponse; session: SessionResponse }>(
`/users/${account.userId}`,
@@ -142,13 +142,13 @@ export async function editUser(
headers: getAuthHeaders(account.token),
body: object,
baseURL: url,
}
},
);
}
export async function deleteUser(
url: string,
account: AccountWithToken
account: AccountWithToken,
): Promise<UserResponse> {
return ofetch<UserResponse>(`/users/${account.userId}`, {
headers: getAuthHeaders(account.token),

View File

@@ -1,5 +1,6 @@
import { FetchOptions, FetchResponse, ofetch } from "ofetch";
import { ofetch } from "ofetch";
import { getApiToken, setApiToken } from "@/backend/helpers/providerApi";
import { getLoadbalancedProxyUrl } from "@/utils/providers";
type P<T> = Parameters<typeof ofetch<T, any>>;
@@ -21,7 +22,11 @@ export function mwFetch<T>(url: string, ops: P<T>[1] = {}): R<T> {
return baseFetch<T>(url, ops);
}
export function proxiedFetch<T>(url: string, ops: P<T>[1] = {}): R<T> {
export async function singularProxiedFetch<T>(
proxyUrl: string,
url: string,
ops: P<T>[1] = {},
): R<T> {
let combinedUrl = ops?.baseURL ?? "";
if (
combinedUrl.length > 0 &&
@@ -45,45 +50,30 @@ export function proxiedFetch<T>(url: string, ops: P<T>[1] = {}): R<T> {
parsedUrl.searchParams.set(k, v);
});
return baseFetch<T>(getLoadbalancedProxyUrl(), {
let headers = ops.headers ?? {};
const apiToken = await getApiToken();
if (apiToken)
headers = {
...headers,
"X-Token": apiToken,
};
return baseFetch<T>(proxyUrl, {
...ops,
baseURL: undefined,
params: {
destination: parsedUrl.toString(),
},
query: {},
});
}
export function rawProxiedFetch<T>(
url: string,
ops: FetchOptions = {}
): Promise<FetchResponse<T>> {
let combinedUrl = ops?.baseURL ?? "";
if (
combinedUrl.length > 0 &&
combinedUrl.endsWith("/") &&
url.startsWith("/")
)
combinedUrl += url.slice(1);
else if (
combinedUrl.length > 0 &&
!combinedUrl.endsWith("/") &&
!url.startsWith("/")
)
combinedUrl += `/${url}`;
else combinedUrl += url;
const parsedUrl = new URL(combinedUrl);
Object.entries(ops?.params ?? {}).forEach(([k, v]) => {
parsedUrl.searchParams.set(k, v);
});
return baseFetch.raw(getLoadbalancedProxyUrl(), {
...ops,
baseURL: undefined,
params: {
destination: parsedUrl.toString(),
headers,
onResponse(context) {
const tokenHeader = context.response.headers.get("X-Token");
if (tokenHeader) setApiToken(tokenHeader);
ops.onResponse?.(context);
},
});
}
export function proxiedFetch<T>(url: string, ops: P<T>[1] = {}): R<T> {
return singularProxiedFetch<T>(getLoadbalancedProxyUrl(), url, ops);
}

View File

@@ -0,0 +1,163 @@
import { MetaOutput, NotFoundError, ScrapeMedia } from "@movie-web/providers";
import { jwtDecode } from "jwt-decode";
import { mwFetch } from "@/backend/helpers/fetch";
import { getTurnstileToken, isTurnstileInitialized } from "@/stores/turnstile";
let metaDataCache: MetaOutput[] | null = null;
let token: null | string = null;
export function setCachedMetadata(data: MetaOutput[]) {
metaDataCache = data;
}
export function getCachedMetadata(): MetaOutput[] {
return metaDataCache ?? [];
}
export function setApiToken(newToken: string) {
token = newToken;
}
function getTokenIfValid(): null | string {
if (!token) return null;
try {
const body = jwtDecode(token);
if (!body.exp) return `jwt|${token}`;
if (Date.now() / 1000 < body.exp) return `jwt|${token}`;
} catch (err) {
// we dont care about parse errors
}
return null;
}
export async function fetchMetadata(base: string) {
if (metaDataCache) return;
const data = await mwFetch<MetaOutput[][]>(`${base}/metadata`);
metaDataCache = data.flat();
}
function scrapeMediaToQueryMedia(media: ScrapeMedia) {
let extra: Record<string, string> = {};
if (media.type === "show") {
extra = {
episodeNumber: media.episode.number.toString(),
episodeTmdbId: media.episode.tmdbId,
seasonNumber: media.season.number.toString(),
seasonTmdbId: media.season.tmdbId,
};
}
return {
type: media.type,
releaseYear: media.releaseYear.toString(),
imdbId: media.imdbId,
tmdbId: media.tmdbId,
title: media.title,
...extra,
};
}
function addQueryDataToUrl(url: URL, data: Record<string, string | undefined>) {
Object.entries(data).forEach((entry) => {
if (entry[1]) url.searchParams.set(entry[0], entry[1]);
});
}
export function makeProviderUrl(base: string) {
const makeUrl = (p: string) => new URL(`${base}${p}`);
return {
scrapeSource(sourceId: string, media: ScrapeMedia) {
const url = makeUrl("/scrape/source");
addQueryDataToUrl(url, scrapeMediaToQueryMedia(media));
addQueryDataToUrl(url, { id: sourceId });
return url.toString();
},
scrapeAll(media: ScrapeMedia) {
const url = makeUrl("/scrape");
addQueryDataToUrl(url, scrapeMediaToQueryMedia(media));
return url.toString();
},
scrapeEmbed(embedId: string, embedUrl: string) {
const url = makeUrl("/scrape/embed");
addQueryDataToUrl(url, { id: embedId, url: embedUrl });
return url.toString();
},
};
}
export async function getApiToken(): Promise<string | null> {
let apiToken = getTokenIfValid();
if (!apiToken && isTurnstileInitialized()) {
apiToken = `turnstile|${await getTurnstileToken()}`;
}
return apiToken;
}
function parseEventInput(inp: string): any {
if (inp.length === 0) return {};
return JSON.parse(inp);
}
export async function connectServerSideEvents<T>(
url: string,
endEvents: string[],
) {
const apiToken = await getApiToken();
// insert token, if its set
const parsedUrl = new URL(url);
if (apiToken) parsedUrl.searchParams.set("token", apiToken);
const eventSource = new EventSource(parsedUrl.toString());
let promReject: (reason?: any) => void;
let promResolve: (value: T) => void;
const promise = new Promise<T>((resolve, reject) => {
promResolve = resolve;
promReject = reject;
});
endEvents.forEach((evt) => {
eventSource.addEventListener(evt, (e) => {
eventSource.close();
promResolve(parseEventInput(e.data));
});
});
eventSource.addEventListener("token", (e) => {
setApiToken(parseEventInput(e.data));
});
eventSource.addEventListener("error", (err: MessageEvent<any>) => {
eventSource.close();
if (err.data) {
const data = JSON.parse(err.data);
let errObj = new Error("scrape error");
if (data.name === NotFoundError.name)
errObj = new NotFoundError("Notfound from server");
Object.assign(errObj, data);
promReject(errObj);
return;
}
console.error("Failed to connect to SSE", err);
promReject(err);
});
eventSource.addEventListener("message", (ev) => {
if (!ev) {
eventSource.close();
return;
}
setTimeout(() => {
promReject(new Error("SSE closed improperly"));
}, 1000);
});
return {
promise: () => promise,
on<Data>(event: string, cb: (data: Data) => void) {
eventSource.addEventListener(event, (e) => cb(JSON.parse(e.data)));
},
};
}

View File

@@ -9,6 +9,7 @@ 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 batchId = () => nanoid(32);
export type ProviderMetric = {
@@ -57,7 +58,7 @@ export function scrapeSourceOutputToProviderMetric(
providerId: string,
embedId: string | null,
status: ProviderMetric["status"],
err: unknown | null
err: unknown | null,
): ProviderMetric {
const episodeId = media.episode?.tmdbId;
const seasonId = media.season?.tmdbId;
@@ -81,7 +82,7 @@ export function scrapeSourceOutputToProviderMetric(
export function scrapeSegmentToProviderMetric(
media: ScrapeMedia,
providerId: string,
segment: ScrapingSegment
segment: ScrapingSegment,
): ProviderMetric | null {
const status = segmentStatusMap[segment.status];
if (!status) return null;
@@ -111,7 +112,7 @@ export function scrapeSegmentToProviderMetric(
export function scrapePartsToProviderMetric(
media: ScrapeMedia,
order: ScrapingItems[],
sources: Record<string, ScrapingSegment>
sources: Record<string, ScrapingSegment>,
): ProviderMetric[] {
const output: ProviderMetric[] = [];
@@ -136,8 +137,17 @@ export function scrapePartsToProviderMetric(
export function useReportProviders() {
const report = useCallback((items: ProviderMetric[]) => {
if (items.length === 0) return;
reportProviders(items);
reportProviders(items).catch(() => {});
}, []);
return { report };
}
export function reportCaptchaSolve(success: boolean) {
ofetch(captchaMetricsEndpoint, {
method: "POST",
body: {
success,
},
}).catch(() => {});
}

View File

@@ -14,7 +14,7 @@ const expirySeconds = 24 * 60 * 60;
* Always returns SRT
*/
export async function downloadCaption(
caption: CaptionListItem
caption: CaptionListItem,
): Promise<string> {
const cached = downloadCache.get(caption.url);
if (cached) return cached;

View File

@@ -34,7 +34,7 @@ export interface DetailedMeta {
export function formatTMDBMetaResult(
details: TMDBShowData | TMDBMovieData,
type: MWMediaType
type: MWMediaType,
): TMDBMediaResult {
if (type === MWMediaType.MOVIE) {
const movie = details as TMDBMovieData;
@@ -68,7 +68,7 @@ export function formatTMDBMetaResult(
export async function getMetaFromId(
type: MWMediaType,
id: string,
seasonId?: string
seasonId?: string,
): Promise<DetailedMeta | null> {
const details = await getMediaDetails(id, mediaTypeToTMDB(type));
@@ -89,7 +89,7 @@ export async function getMetaFromId(
if (selectedSeason) {
const episodes = await getEpisodes(
details.id.toString(),
selectedSeason.season_number
selectedSeason.season_number,
);
seasonData = {
@@ -116,7 +116,7 @@ export async function getMetaFromId(
export async function getLegacyMetaFromId(
type: MWMediaType,
id: string,
seasonId?: string
seasonId?: string,
): Promise<DetailedMeta | null> {
const queryType = mediaTypeToJW(type);
@@ -135,15 +135,13 @@ export async function getLegacyMetaFromId(
throw err;
}
let imdbId = data.external_ids.find(
(v) => v.provider === "imdb_latest"
)?.external_id;
let imdbId = data.external_ids.find((v) => v.provider === "imdb_latest")
?.external_id;
if (!imdbId)
imdbId = data.external_ids.find((v) => v.provider === "imdb")?.external_id;
let tmdbId = data.external_ids.find(
(v) => v.provider === "tmdb_latest"
)?.external_id;
let tmdbId = data.external_ids.find((v) => v.provider === "tmdb_latest")
?.external_id;
if (!tmdbId)
tmdbId = data.external_ids.find((v) => v.provider === "tmdb")?.external_id;
@@ -175,7 +173,7 @@ export function isLegacyMediaType(url: string): boolean {
}
export async function convertLegacyUrl(
url: string
url: string,
): Promise<string | undefined> {
if (!isLegacyUrl(url)) return undefined;
@@ -191,7 +189,7 @@ export async function convertLegacyUrl(
return `/media/${TMDBIdToUrlId(
MWMediaType.SERIES,
details.id.toString(),
details.name
details.name,
)}${suffix}`;
}

View File

@@ -20,7 +20,7 @@ export function JWMediaToMediaType(type: string): MWMediaType {
export function formatJWMeta(
media: JWMediaResult,
season?: JWSeasonMetaResult
season?: JWSeasonMetaResult,
): MWMediaMeta {
const type = JWMediaToMediaType(media.object_type);
let seasons: undefined | MWSeasonMeta[];
@@ -32,7 +32,7 @@ export function formatJWMeta(
id: v.id.toString(),
number: v.season_number,
title: v.title,
})
}),
);
}
@@ -67,7 +67,7 @@ export function JWMediaToId(media: MWMediaMeta): string {
}
export function decodeJWId(
paramId: string
paramId: string,
): { id: string; type: MWMediaType } | null {
const [prefix, type, id] = paramId.split("-", 3);
if (prefix !== "JW") return null;

View File

@@ -38,7 +38,7 @@ export function TMDBMediaToMediaType(type: TMDBContentTypes): MWMediaType {
}
export function TMDBMediaToMediaItemType(
type: TMDBContentTypes
type: TMDBContentTypes,
): MediaItem["type"] {
if (type === TMDBContentTypes.MOVIE) return "movie";
if (type === TMDBContentTypes.TV) return "show";
@@ -47,7 +47,7 @@ export function TMDBMediaToMediaItemType(
export function formatTMDBMeta(
media: TMDBMediaResult,
season?: TMDBSeasonMetaResult
season?: TMDBSeasonMetaResult,
): MWMediaMeta {
const type = TMDBMediaToMediaType(media.object_type);
let seasons: undefined | MWSeasonMeta[];
@@ -59,7 +59,7 @@ export function formatTMDBMeta(
title: v.title,
id: v.id.toString(),
number: v.season_number,
})
}),
);
}
@@ -102,7 +102,7 @@ export function formatTMDBMetaToMediaItem(media: TMDBMediaResult): MediaItem {
export function TMDBIdToUrlId(
type: MWMediaType,
tmdbId: string,
title: string
title: string,
) {
return [
"tmdb",
@@ -120,12 +120,12 @@ export function mediaItemToId(media: MediaItem): string {
return TMDBIdToUrlId(
mediaItemTypeToMediaType(media.type),
media.id,
media.title
media.title,
);
}
export function decodeTMDBId(
paramId: string
paramId: string,
): { id: string; type: MWMediaType } | null {
const [prefix, type, id] = paramId.split("-", 3);
if (prefix !== "tmdb") return null;
@@ -160,7 +160,7 @@ async function get<T>(url: string, params?: object): Promise<T> {
}
export async function multiSearch(
query: string
query: string,
): Promise<(TMDBMovieSearchResult | TMDBShowSearchResult)[]> {
const data = await get<TMDBSearchResult>("search/multi", {
query,
@@ -172,13 +172,13 @@ export async function multiSearch(
const results = data.results.filter(
(r) =>
r.media_type === TMDBContentTypes.MOVIE ||
r.media_type === TMDBContentTypes.TV
r.media_type === TMDBContentTypes.TV,
);
return results;
}
export async function generateQuickSearchMediaUrl(
query: string
query: string,
): Promise<string | undefined> {
const data = await multiSearch(query);
if (data.length === 0) return undefined;
@@ -189,7 +189,7 @@ export async function generateQuickSearchMediaUrl(
return `/media/${TMDBIdToUrlId(
TMDBMediaToMediaType(result.media_type),
result.id.toString(),
title
title,
)}`;
}
@@ -198,12 +198,12 @@ type MediaDetailReturn<T extends TMDBContentTypes> =
T extends TMDBContentTypes.MOVIE
? TMDBMovieData
: T extends TMDBContentTypes.TV
? TMDBShowData
: never;
? TMDBShowData
: never;
export function getMediaDetails<
T extends TMDBContentTypes,
TReturn = MediaDetailReturn<T>
TReturn = MediaDetailReturn<T>,
>(id: string, type: T): Promise<TReturn> {
if (type === TMDBContentTypes.MOVIE) {
return get<TReturn>(`/movie/${id}`, { append_to_response: "external_ids" });
@@ -215,12 +215,12 @@ export function getMediaDetails<
}
export function getMediaPoster(posterPath: string | null): string | undefined {
if (posterPath) return `https://image.tmdb.org/t/p/w185/${posterPath}`;
if (posterPath) return `https://image.tmdb.org/t/p/w342/${posterPath}`;
}
export async function getEpisodes(
id: string,
season: number
season: number,
): Promise<TMDBEpisodeShort[]> {
const data = await get<TMDBSeason>(`/tv/${id}/season/${season}`);
return data.episodes.map((e) => ({
@@ -231,7 +231,7 @@ export async function getEpisodes(
}
export async function getMovieFromExternalId(
imdbId: string
imdbId: string,
): Promise<string | undefined> {
const data = await get<ExternalIdMovieSearchResult>(`/find/${imdbId}`, {
external_source: "imdb_id",
@@ -245,7 +245,7 @@ export async function getMovieFromExternalId(
export function formatTMDBSearchResult(
result: TMDBMovieSearchResult | TMDBShowSearchResult,
mediatype: TMDBContentTypes
mediatype: TMDBContentTypes,
): TMDBMediaResult {
const type = TMDBMediaToMediaType(mediatype);
if (type === MWMediaType.SERIES) {

View File

@@ -20,7 +20,7 @@ export function Avatar(props: AvatarProps) {
<div
className={classNames(
props.sizeClass,
"rounded-full overflow-hidden flex items-center justify-center text-white"
"rounded-full overflow-hidden flex items-center justify-center text-white",
)}
style={{
background: `linear-gradient(to bottom right, ${props.profile.colorA}, ${props.profile.colorB})`,
@@ -53,7 +53,7 @@ export function UserAvatar(props: {
auth.account && auth.account.seed
? base64ToBuffer(auth.account.seed)
: null,
[auth]
[auth],
);
if (!auth.account || auth.account === null) return null;

View File

@@ -51,7 +51,7 @@ export function FlagIcon(props: FlagIconProps) {
<span
className={classNames(
"!w-8 h-6 rounded overflow-hidden bg-video-context-flagBg bg-cover bg-center block fi",
props.countryCode ? `fi-${countryCode}` : undefined
props.countryCode ? `fi-${countryCode}` : undefined,
)}
/>
);

View File

@@ -1,7 +1,7 @@
import classNames from "classnames";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto";
import { UserAvatar } from "@/components/Avatar";
@@ -21,11 +21,11 @@ function GoToLink(props: {
className?: string;
onClick?: () => void;
}) {
const history = useHistory();
const navigate = useNavigate();
const goTo = (href: string) => {
if (href.startsWith("http")) window.open(href, "_blank");
else history.push(href);
else navigate(href);
};
return (
@@ -61,7 +61,7 @@ function DropdownLink(props: {
props.highlight
? "text-dropdown-highlight hover:text-dropdown-highlightHover"
: "text-dropdown-text hover:text-white",
props.className
props.className,
)}
>
{props.icon ? <Icon icon={props.icon} className="text-xl" /> : null}
@@ -88,7 +88,7 @@ export function LinksDropdown(props: { children: React.ReactNode }) {
const seed = useAuthStore((s) => s.account?.seed);
const bufferSeed = useMemo(
() => (seed ? base64ToBuffer(seed) : null),
[seed]
[seed],
);
const { logout } = useAuth();
@@ -118,7 +118,7 @@ export function LinksDropdown(props: { children: React.ReactNode }) {
<Icon
className={classNames(
"text-xl transition-transform duration-100",
open ? "rotate-180" : ""
open ? "rotate-180" : "",
)}
icon={Icons.CHEVRON_DOWN}
/>

View File

@@ -1,6 +1,6 @@
import classNames from "classnames";
import { ReactNode, useCallback } from "react";
import { useHistory } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { Icon, Icons } from "@/components/Icon";
import { Spinner } from "@/components/layout/Spinner";
@@ -19,13 +19,13 @@ interface Props {
}
export function Button(props: Props) {
const history = useHistory();
const navigate = useNavigate();
const { onClick, href, loading } = props;
const cb = useCallback(() => {
if (loading) return;
if (href) history.push(href);
if (href) navigate(href);
else onClick?.();
}, [onClick, href, history, loading]);
}, [onClick, href, navigate, loading]);
let colorClasses = "bg-white hover:bg-gray-200 text-black";
if (props.theme === "purple")
@@ -41,7 +41,7 @@ export function Button(props: Props) {
props.padding ?? "px-4 py-3",
props.className,
colorClasses,
props.disabled ? "cursor-not-allowed bg-opacity-60 text-opacity-60" : null
props.disabled ? "cursor-not-allowed bg-opacity-60 text-opacity-60" : null,
);
if (props.disabled)
@@ -49,7 +49,7 @@ export function Button(props: Props) {
.split(" ")
.filter(
(className) =>
!className.startsWith("hover:") && !className.startsWith("active:")
!className.startsWith("hover:") && !className.startsWith("active:"),
)
.join(" ");
@@ -120,7 +120,7 @@ export function ButtonPlain(props: ButtonPlainProps) {
"cursor-pointer inline-flex items-center justify-center rounded-lg font-medium transition-[transform,background-color] duration-100 active:scale-105 md:px-8",
"px-4 py-3",
props.className,
colorClasses
colorClasses,
);
return (

View File

@@ -7,14 +7,14 @@ export function Toggle(props: { onClick: () => void; enabled?: boolean }) {
onClick={props.onClick}
className={classNames(
"w-11 h-6 p-1 rounded-full grid transition-colors duration-100 group/toggle tabbable",
props.enabled ? "bg-buttons-toggle" : "bg-buttons-toggleDisabled"
props.enabled ? "bg-buttons-toggle" : "bg-buttons-toggleDisabled",
)}
>
<div className="relative w-full h-full">
<div
className={classNames(
"scale-90 group-hover/toggle:scale-100 h-full aspect-square rounded-full bg-white absolute transition-all duration-100",
props.enabled ? "left-full transform -translate-x-full" : "left-0"
props.enabled ? "left-full transform -translate-x-full" : "left-0",
)}
/>
</div>

View File

@@ -24,7 +24,7 @@ export function ColorPicker(props: {
tabIndex={0}
className={classNames(
"w-full h-10 rounded flex justify-center items-center text-white pointer border-2 border-opacity-10 cursor-pointer",
props.value === color ? "border-white" : "border-transparent"
props.value === color ? "border-white" : "border-transparent",
)}
onClick={() => props.onInput(color)}
style={{ backgroundColor: color }}

View File

@@ -32,7 +32,7 @@ export function IconPicker(props: {
"w-full h-10 rounded flex justify-center items-center text-white pointer border-2 border-opacity-10 cursor-pointer",
props.value === icon
? "bg-buttons-purple border-white"
: "bg-authentication-inputBg border-transparent"
: "bg-authentication-inputBg border-transparent",
)}
onClick={() => props.onInput(icon)}
key={icon}

View File

@@ -25,7 +25,9 @@ export function PassphraseDisplay(props: { mnemonic: string }) {
return (
<div className="rounded-lg border border-authentication-border/50 ">
<div className="px-4 py-2 flex justify-between border-b border-authentication-border/50">
<p className="font-bold text-sm text-white">Passphrase</p>
<p className="font-bold text-sm text-white">
{t("auth.generate.passphraseFrameLabel")}
</p>
<button
type="button"
className="text-authentication-copyText hover:text-authentication-copyTextHover transition-colors flex gap-2 items-center cursor-pointer"

View File

@@ -60,5 +60,5 @@ export const SearchBarInput = forwardRef<HTMLInputElement, SearchBarProps>(
</Flare.Child>
</Flare.Base>
);
}
},
);

View File

@@ -17,7 +17,7 @@ export function BrandPill(props: {
props.backgroundClass ?? "bg-pill-background bg-opacity-50",
props.clickable
? "transition-[transform,background-color] hover:scale-105 hover:bg-pill-backgroundHover hover:text-type-logo active:scale-95"
: ""
: "",
)}
>
<Icon className="text-xl" icon={Icons.MOVIE_WEB} />

View File

@@ -1,6 +1,6 @@
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import type { RequireExactlyOne } from "type-fest";
import { Icon, Icons } from "@/components/Icon";
@@ -21,13 +21,13 @@ type FooterLinkProps = RequireExactlyOne<
>;
function FooterLink(props: FooterLinkProps) {
const history = useHistory();
const navigate = useNavigate();
const navigateTo = useCallback(() => {
if (!props.to) return;
history.push(props.to);
}, [history, props.to]);
navigate(props.to);
}, [navigate, props.to]);
return (
<a
@@ -99,7 +99,7 @@ export function FooterView(props: {
return (
<div
className={["flex min-h-screen flex-col", props.className || ""].join(
" "
" ",
)}
>
<div style={{ flex: "1 0 auto" }}>{props.children}</div>

View File

@@ -51,7 +51,7 @@ export function Navigation(props: NavigationProps) {
"fixed left-0 right-0 h-20 flex items-center",
props.doBackground
? "bg-background-main border-b border-utils-divider border-opacity-50"
: null
: null,
)}
>
{props.doBackground ? (

View File

@@ -10,7 +10,7 @@ export function SettingsCard(props: {
className={classNames(
"w-full rounded-lg bg-settings-card-background bg-opacity-[0.15] border border-settings-card-border",
props.paddingClass ?? "px-8 py-6",
props.className
props.className,
)}
>
{props.children}
@@ -28,7 +28,7 @@ export function SolidSettingsCard(props: {
className={classNames(
"w-full rounded-lg bg-settings-card-altBackground bg-opacity-50",
props.paddingClass ?? "px-8 py-6",
props.className
props.className,
)}
>
{props.children}

View File

@@ -31,13 +31,13 @@ export function SidebarLink(props: {
"tabbable w-full px-3 py-2 flex items-center space-x-3 cursor-pointer rounded my-2",
props.active
? "bg-settings-sidebar-activeLink text-settings-sidebar-type-activated"
: null
: null,
)}
>
<Icon
className={classNames(
"text-2xl text-settings-sidebar-type-icon",
props.active ? "text-settings-sidebar-type-iconActivated" : null
props.active ? "text-settings-sidebar-type-iconActivated" : null,
)}
icon={props.icon}
/>

View File

@@ -66,7 +66,7 @@ function MediaCardContent({
"relative mb-4 pb-[150%] w-full overflow-hidden rounded-xl bg-mediaCard-hoverBackground bg-cover bg-center transition-[border-radius] duration-100",
{
"group-hover:rounded-lg": !closable,
}
},
)}
style={{
backgroundImage: media.poster ? `url(${media.poster})` : undefined,
@@ -152,7 +152,7 @@ export function MediaCard(props: MediaCardProps) {
link += `/${encodeURIComponent(props.series.seasonId)}`;
} else {
link += `/${encodeURIComponent(
props.series.seasonId
props.series.seasonId,
)}/${encodeURIComponent(props.series.episodeId)}`;
}
}
@@ -164,7 +164,7 @@ export function MediaCard(props: MediaCardProps) {
tabIndex={-1}
className={classNames(
"tabbable",
props.closable ? "hover:cursor-default" : ""
props.closable ? "hover:cursor-default" : "",
)}
>
{content}

View File

@@ -14,5 +14,5 @@ export const MediaGrid = forwardRef<HTMLDivElement, MediaGridProps>(
{props.children}
</div>
);
}
},
);

View File

@@ -32,7 +32,7 @@ export function WatchedMediaCard(props: WatchedMediaCardProps) {
}, [progressItems, props.media]);
const itemToDisplay = useMemo(
() => (item ? shouldShowProgress(item) : null),
[item]
[item],
);
const percentage = itemToDisplay?.show
? (itemToDisplay.progress.watched / itemToDisplay.progress.duration) * 100

View File

@@ -50,11 +50,7 @@ export function OverlayPortal(props: {
{portalElement
? createPortal(
<Transition show={props.show} animation="none">
<FocusTrap
focusTrapOptions={{
onDeactivate: close,
}}
>
<FocusTrap>
<div className="popout-wrapper fixed overflow-hidden pointer-events-auto inset-0 z-[999] select-none">
<Transition animation="fade" isChild>
<div
@@ -80,7 +76,7 @@ export function OverlayPortal(props: {
</div>
</FocusTrap>
</Transition>,
portalElement
portalElement,
)
: null}
</div>

View File

@@ -21,7 +21,7 @@ function RouterBase(props: { id: string; children: ReactNode }) {
const router = useInternalOverlayRouter(props.id);
const routeMeta = useMemo(
() => routes[router.currentRoute ?? ""],
[routes, router]
[routes, router],
);
const [dimensions, api] = useSpring(
@@ -34,7 +34,7 @@ function RouterBase(props: { id: string; children: ReactNode }) {
easing: easings.linear,
},
}),
[]
[],
);
const currentState = useRef<null | string>(null);

View File

@@ -25,11 +25,11 @@ function useCalculatePositions() {
setLeft(
Math.min(
buttonCenter - card.width / 2,
window.innerWidth - card.width - 30
)
window.innerWidth - card.width - 30,
),
);
},
[]
[],
);
useEffect(() => {

View File

@@ -18,7 +18,7 @@ export function Chromecast(props: ChromecastProps) {
const isVisible = (tag.getAttribute("style") ?? "").includes("inline");
setHidden(!isVisible);
},
[setHidden]
[setHidden],
);
useEffect(() => {

View File

@@ -54,7 +54,7 @@ function SeasonsView({
const meta = usePlayerStore((s) => s.meta);
const [loadingState, seasons] = useSeasonData(
meta?.tmdbId ?? "",
selectedSeason
selectedSeason,
);
let content: ReactNode = null;
@@ -120,7 +120,7 @@ function EpisodesView({
// player already switches route after meta change
router.close(true);
},
[setPlayerMeta, loadingState, router, onChange]
[setPlayerMeta, loadingState, router, onChange],
);
if (!meta?.tmdbId) return null;
@@ -175,7 +175,7 @@ function EpisodesView({
"p-0.5 px-2 rounded inline bg-video-context-hoverColor",
ep.id === meta?.episode?.tmdbId
? "text-white bg-opacity-100"
: "bg-opacity-50"
: "bg-opacity-50",
)}
>
{t("player.menus.episodes.episodeBadge", {
@@ -226,7 +226,7 @@ function EpisodesOverlay({
setSelectedSeason(seasonId);
router.navigate("/episodes");
},
[router]
[router],
);
return (

View File

@@ -10,7 +10,7 @@ import { usePlayerStore } from "@/stores/player/store";
function shouldShowNextEpisodeButton(
time: number,
duration: number
duration: number,
): "always" | "hover" | "none" {
const percentage = time / duration;
const secondsFromEnd = duration - time;
@@ -28,7 +28,7 @@ function Button(props: {
<button
className={classNames(
"font-bold rounded h-10 w-40 scale-95 hover:scale-100 transition-all duration-200",
props.className
props.className,
)}
type="button"
onClick={props.onClick}
@@ -53,7 +53,7 @@ export function NextEpisodeButton(props: {
const showingState = shouldShowNextEpisodeButton(time, duration);
const status = usePlayerStore((s) => s.status);
const setShouldStartFromBeginning = usePlayerStore(
(s) => s.setShouldStartFromBeginning
(s) => s.setShouldStartFromBeginning,
);
let show = false;
@@ -69,7 +69,7 @@ export function NextEpisodeButton(props: {
: "bottom-[calc(3rem+env(safe-area-inset-bottom))]";
const nextEp = meta?.episodes?.find(
(v) => v.number === (meta?.episode?.number ?? 0) + 1
(v) => v.number === (meta?.episode?.number ?? 0) + 1,
);
const loadNextEpisode = useCallback(() => {

View File

@@ -58,7 +58,7 @@ function ThumbnailDisplay(props: { at: number; show: boolean }) {
<p className="text-center mt-1">
{formatSeconds(
Math.max(props.at, 0),
durationExceedsHour(props.at)
durationExceedsHour(props.at),
)}
</p>
</div>
@@ -79,7 +79,7 @@ function useMouseHoverPosition(barRef: RefObject<HTMLDivElement>) {
const pos = (e.pageX - rect.left) / barRef.current.offsetWidth;
setMousePos(pos * 100);
},
[setMousePos, barRef]
[setMousePos, barRef],
);
const mouseLeave = useCallback(() => {
@@ -97,10 +97,10 @@ export function ProgressBar() {
const { isSeeking } = usePlayerStore((s) => s.interface);
const commitTime = useCallback(
(percentage) => {
(percentage: number) => {
display?.setTime(percentage * duration);
},
[duration, display]
[duration, display],
);
const ref = useRef<HTMLDivElement>(null);
@@ -108,7 +108,7 @@ export function ProgressBar() {
const { dragging, dragPercentage, dragMouseDown } = useProgressBar(
ref,
commitTime
commitTime,
);
useEffect(() => {
setSeeking(dragging);
@@ -165,8 +165,8 @@ export function ProgressBar() {
0,
Math.min(
1,
dragging ? dragPercentage / 100 : time / duration
)
dragging ? dragPercentage / 100 : time / duration,
),
) * 100
}%`,
}}

View File

@@ -22,19 +22,19 @@ export function Time(props: { short?: boolean }) {
setTimeFormat(
timeFormat === VideoPlayerTimeFormat.REGULAR
? VideoPlayerTimeFormat.REMAINING
: VideoPlayerTimeFormat.REGULAR
: VideoPlayerTimeFormat.REGULAR,
);
}
const currentTime = Math.min(
Math.max(isSeeking ? draggingTime : time, 0),
timeDuration
timeDuration,
);
const secondsRemaining = Math.abs(currentTime - timeDuration);
const timeLeft = formatSeconds(
secondsRemaining,
durationExceedsHour(secondsRemaining)
durationExceedsHour(secondsRemaining),
);
const timeWatched = formatSeconds(currentTime, hasHours);
const timeFinished = new Date(Date.now() + secondsRemaining * 1e3);

View File

@@ -23,16 +23,16 @@ export function Volume(props: Props) {
const { setVolume, toggleMute } = useVolume();
const commitVolume = useCallback(
(percentage) => {
(percentage: number) => {
setVolume(percentage);
},
[setVolume]
[setVolume],
);
const { dragging, dragPercentage, dragMouseDown } = useProgressBar(
ref,
commitVolume,
true
true,
);
const handleClick = useCallback(() => {

View File

@@ -19,7 +19,7 @@ export function ColorOption(props: {
type="button"
className={classNames(
"tabbable p-1.5 bg-video-context-buttonFocus rounded transition-colors duration-100",
props.active ? "bg-opacity-100" : "bg-opacity-0 cursor-pointer"
props.active ? "bg-opacity-100" : "bg-opacity-0 cursor-pointer",
)}
onClick={props.onClick}
>
@@ -50,18 +50,18 @@ export function CaptionSetting(props: {
const currentPercentage = (props.value - props.min) / (props.max - props.min);
const commit = useCallback(
(percentage) => {
(percentage: number) => {
const range = props.max - props.min;
const newPercentage = Math.min(Math.max(percentage, 0), 1);
props.onChange?.(props.min + range * newPercentage);
},
[props]
[props],
);
const { dragging, dragPercentage, dragMouseDown } = useProgressBar(
ref,
commit,
true
true,
);
const [isFocused, setIsFocused] = useState(false);
@@ -112,8 +112,8 @@ export function CaptionSetting(props: {
0,
Math.min(
1,
dragging ? dragPercentage / 100 : currentPercentage
)
dragging ? dragPercentage / 100 : currentPercentage,
),
) * 100
}%`,
}}
@@ -141,7 +141,7 @@ export function CaptionSetting(props: {
const num = Number((e.target as HTMLInputElement).value);
if (!Number.isNaN(num))
props.onChange?.(
(props.decimalsAllowed ?? 0) === 0 ? Math.round(num) : num
(props.decimalsAllowed ?? 0) === 0 ? Math.round(num) : num,
);
}}
ref={inputRef}
@@ -163,13 +163,13 @@ export function CaptionSetting(props: {
<button
className={classNames(
inputClasses,
props.controlButtons ? "relative" : undefined
props.controlButtons ? "relative" : undefined,
)}
type="button"
tabIndex={0}
>
{textTransformer(
props.value.toFixed(props.decimalsAllowed ?? 0)
props.value.toFixed(props.decimalsAllowed ?? 0),
)}
</button>
{props.controlButtons ? (
@@ -180,7 +180,8 @@ export function CaptionSetting(props: {
onClick={
() =>
props.onChange?.(
props.value - 1 / 10 ** (props.decimalsAllowed ?? 0)
props.value -
1 / 10 ** (props.decimalsAllowed ?? 0),
) // Remove depending on the decimalsAllowed. If there's 1 decimal allowed, add 0.1. For 2, add 0.01, etc.
}
className={arrowButtonClasses}
@@ -194,7 +195,8 @@ export function CaptionSetting(props: {
onClick={
() =>
props.onChange?.(
props.value + 1 / 10 ** (props.decimalsAllowed ?? 0)
props.value +
1 / 10 ** (props.decimalsAllowed ?? 0),
) // Add depending on the decimalsAllowed. If there's 1 decimal allowed, add 0.1. For 2, add 0.01, etc.
}
className={arrowButtonClasses}

View File

@@ -127,7 +127,7 @@ export function CaptionsView({ id }: { id: string }) {
setCurrentlyDownloading(language);
return selectLanguage(language);
},
[selectLanguage, setCurrentlyDownloading]
[selectLanguage, setCurrentlyDownloading],
);
const content = subtitleList.map((v, i) => {
@@ -141,7 +141,7 @@ export function CaptionsView({ id }: { id: string }) {
loading={v.language === currentlyDownloading && downloadReq.loading}
error={
v.language === currentlyDownloading && downloadReq.error
? downloadReq.error
? downloadReq.error.toString()
: undefined
}
onClick={() => startDownload(v.language)}
@@ -182,3 +182,5 @@ export function CaptionsView({ id }: { id: string }) {
</>
);
}
export default CaptionsView;

View File

@@ -48,7 +48,7 @@ export function DownloadView({ id }: { id: string }) {
selectedCaption
? convertSubtitlesToSrtDataurl(selectedCaption?.srtData)
: null,
[selectedCaption]
[selectedCaption],
);
if (!downloadUrl) return null;

View File

@@ -21,7 +21,7 @@ function ButtonList(props: {
"w-full px-2 py-1 rounded-md tabbable",
props.selected === option
? "bg-video-context-buttons-active text-white"
: null
: null,
)}
onClick={() => props.onClick(option)}
key={option}
@@ -44,7 +44,7 @@ export function PlaybackSettingsView({ id }: { id: string }) {
(v: number) => {
display?.setPlaybackRate(v);
},
[display]
[display],
);
const options = [0.25, 0.5, 1, 1.5, 2];

View File

@@ -43,7 +43,7 @@ export function QualityView({ id }: { id: string }) {
const currentQuality = usePlayerStore((s) => s.currentQuality);
const switchQuality = usePlayerStore((s) => s.switchQuality);
const enableAutomaticQuality = usePlayerStore(
(s) => s.enableAutomaticQuality
(s) => s.enableAutomaticQuality,
);
const setAutomaticQuality = useQualityStore((s) => s.setAutomaticQuality);
const setLastChosenQuality = useQualityStore((s) => s.setLastChosenQuality);
@@ -56,7 +56,7 @@ export function QualityView({ id }: { id: string }) {
switchQuality(q);
router.close();
},
[router, switchQuality, setLastChosenQuality, setAutomaticQuality]
[router, switchQuality, setLastChosenQuality, setAutomaticQuality],
);
const changeAutomatic = useCallback(() => {

View File

@@ -1,6 +1,7 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { getCachedMetadata } from "@/backend/helpers/providerApi";
import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon";
import { useCaptions } from "@/components/player/hooks/useCaptions";
@@ -10,20 +11,22 @@ import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { usePlayerStore } from "@/stores/player/store";
import { qualityToString } from "@/stores/player/utils/qualities";
import { useSubtitleStore } from "@/stores/subtitles";
import { providers } from "@/utils/providers";
export function SettingsMenu({ id }: { id: string }) {
const { t } = useTranslation();
const router = useOverlayRouter(id);
const currentQuality = usePlayerStore((s) => s.currentQuality);
const selectedCaptionLanguage = usePlayerStore(
(s) => s.caption.selected?.language
(s) => s.caption.selected?.language,
);
const subtitlesEnabled = useSubtitleStore((s) => s.enabled);
const currentSourceId = usePlayerStore((s) => s.sourceId);
const sourceName = useMemo(() => {
if (!currentSourceId) return "...";
return providers.getMetadata(currentSourceId)?.name ?? "...";
const source = getCachedMetadata().find(
(src) => src.id === currentSourceId,
);
return source?.name ?? "...";
}, [currentSourceId]);
const { toggleLastUsed } = useCaptions();
@@ -56,7 +59,7 @@ export function SettingsMenu({ id }: { id: string }) {
clickable
onClick={() =>
router.navigate(
source?.type === "file" ? "/download" : "/download/unable"
source?.type === "file" ? "/download" : "/download/unable",
)
}
rightSide={<Icon className="text-xl" icon={Icons.DOWNLOAD} />}

View File

@@ -1,6 +1,7 @@
import { ReactNode, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { getCachedMetadata } from "@/backend/helpers/providerApi";
import { Loading } from "@/components/layout/Loading";
import {
useEmbedScraping,
@@ -10,7 +11,6 @@ import { Menu } from "@/components/player/internals/ContextMenu";
import { SelectableLink } from "@/components/player/internals/ContextMenu/Links";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { usePlayerStore } from "@/stores/player/store";
import { providers } from "@/utils/providers";
export interface SourceSelectionViewProps {
id: string;
@@ -33,7 +33,7 @@ export function EmbedOption(props: {
const embedName = useMemo(() => {
if (!props.embedId) return unknownEmbedName;
const sourceMeta = providers.getMetadata(props.embedId);
const sourceMeta = getCachedMetadata().find((s) => s.id === props.embedId);
return sourceMeta?.name ?? unknownEmbedName;
}, [props.embedId, unknownEmbedName]);
@@ -41,7 +41,7 @@ export function EmbedOption(props: {
props.routerId,
props.sourceId,
props.url,
props.embedId
props.embedId,
);
return (
@@ -61,7 +61,7 @@ export function EmbedSelectionView({ sourceId, id }: EmbedSelectionViewProps) {
const sourceName = useMemo(() => {
if (!sourceId) return "...";
const sourceMeta = providers.getMetadata(sourceId);
const sourceMeta = getCachedMetadata().find((s) => s.id === sourceId);
return sourceMeta?.name ?? "...";
}, [sourceId]);
@@ -137,8 +137,8 @@ export function SourceSelectionView({
const currentSourceId = usePlayerStore((s) => s.sourceId);
const sources = useMemo(() => {
if (!metaType) return [];
return providers
.listSources()
return getCachedMetadata()
.filter((v) => v.type === "source")
.filter((v) => v.mediaTypes?.includes(metaType));
}, [metaType]);

View File

@@ -1,17 +1,17 @@
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { Icon, Icons } from "@/components/Icon";
export function BackLink(props: { url: string }) {
const { t } = useTranslation();
const history = useHistory();
const navigate = useNavigate();
return (
<div className="flex items-center">
<button
type="button"
onClick={() => history.push(props.url)}
onClick={() => navigate(props.url)}
className="py-1 -my-1 px-2 -mx-2 tabbable rounded-lg flex items-center cursor-pointer text-type-secondary hover:text-white transition-colors duration-200 font-medium"
>
<Icon className="mr-2" icon={Icons.ARROW_LEFT} />

View File

@@ -8,7 +8,7 @@ export function BottomControls(props: {
children: React.ReactNode;
}) {
const setHoveringAnyControls = usePlayerStore(
(s) => s.setHoveringAnyControls
(s) => s.setHoveringAnyControls,
);
useEffect(() => {

View File

@@ -21,7 +21,7 @@ export interface PlayerProps {
function useHovering(containerEl: RefObject<HTMLDivElement>) {
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const updateInterfaceHovering = usePlayerStore(
(s) => s.updateInterfaceHovering
(s) => s.updateInterfaceHovering,
);
const hovering = usePlayerStore((s) => s.interface.hovering);

View File

@@ -8,7 +8,7 @@ export function LeftSideControls(props: {
className?: string;
}) {
const setHoveringLeftControls = usePlayerStore(
(s) => s.setHoveringLeftControls
(s) => s.setHoveringLeftControls,
);
const mouseLeave = useCallback(() => {

View File

@@ -63,7 +63,7 @@ export function CaptionCue({
dangerouslySetInnerHTML={{
__html: parsedHtml,
}}
dir="auto"
dir="ltr"
/>
</p>
);
@@ -79,15 +79,15 @@ export function SubtitleRenderer() {
const parsedCaptions = useMemo(
() => (srtData ? parseSubtitles(srtData, language) : []),
[srtData, language]
[srtData, language],
);
const visibileCaptions = useMemo(
() =>
parsedCaptions.filter(({ start, end }) =>
captionIsVisible(start, end, delay, videoTime)
captionIsVisible(start, end, delay, videoTime),
),
[parsedCaptions, videoTime, delay]
[parsedCaptions, videoTime, delay],
);
return (

View File

@@ -11,7 +11,7 @@ export function TopControls(props: {
}) {
const bannerSize = useBannerSize("player");
const setHoveringAnyControls = usePlayerStore(
(s) => s.setHoveringAnyControls
(s) => s.setHoveringAnyControls,
);
useEffect(() => {

View File

@@ -36,7 +36,7 @@ function hlsLevelToQuality(level: Level): SourceQuality | null {
function qualityToHlsLevel(quality: SourceQuality): number | null {
const found = Object.entries(levelConversionMap).find(
(entry) => entry[1] === quality
(entry) => entry[1] === quality,
);
return found ? +found[0] : null;
}
@@ -83,7 +83,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
});
if (availableQuality) {
const levelIndex = hls.levels.findIndex(
(v) => v.height === qualityToHlsLevel(availableQuality)
(v) => v.height === qualityToHlsLevel(availableQuality),
);
if (levelIndex !== -1) {
hls.currentLevel = levelIndex;
@@ -182,10 +182,10 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
videoElement.addEventListener("canplay", () => emit("loading", false));
videoElement.addEventListener("waiting", () => emit("loading", true));
videoElement.addEventListener("volumechange", () =>
emit("volumechange", videoElement?.muted ? 0 : videoElement?.volume ?? 0)
emit("volumechange", videoElement?.muted ? 0 : videoElement?.volume ?? 0),
);
videoElement.addEventListener("timeupdate", () =>
emit("time", videoElement?.currentTime ?? 0)
emit("time", videoElement?.currentTime ?? 0),
);
videoElement.addEventListener("loadedmetadata", () => {
if (
@@ -202,7 +202,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
if (videoElement)
emit(
"buffered",
handleBuffered(videoElement.currentTime, videoElement.buffered)
handleBuffered(videoElement.currentTime, videoElement.buffered),
);
});
videoElement.addEventListener("webkitendfullscreen", () => {
@@ -216,7 +216,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
if (e.availability === "available") {
emit("canairplay", true);
}
}
},
);
videoElement.addEventListener("ratechange", () => {
if (videoElement) emit("playbackrate", videoElement.playbackRate);
@@ -368,7 +368,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
webkitPlayer.webkitSetPresentationMode(
webkitPlayer.webkitPresentationMode === "picture-in-picture"
? "inline"
: "picture-in-picture"
: "picture-in-picture",
);
}
if (canPictureInPicture()) {

View File

@@ -28,7 +28,7 @@ export interface ChromeCastDisplayInterfaceOptions {
*/
export function makeChromecastDisplayInterface(
ops: ChromeCastDisplayInterfaceOptions
ops: ChromeCastDisplayInterfaceOptions,
): DisplayInterface {
const { emit, on, off } = makeEmitter<DisplayInterfaceEvents>();
let isPaused = false;
@@ -89,12 +89,12 @@ export function makeChromecastDisplayInterface(
};
ops.controller?.addEventListener(
cast.framework.RemotePlayerEventType.ANY_CHANGE,
listen
listen,
);
return () => {
ops.controller?.removeEventListener(
cast.framework.RemotePlayerEventType.ANY_CHANGE,
listen
listen,
);
};
}

View File

@@ -54,7 +54,7 @@ export interface DisplayInterface extends Listener<DisplayInterfaceEvents> {
load(ops: qualityChangeOptions): void;
changeQuality(
automaticQuality: boolean,
preferredQuality: SourceQuality | null
preferredQuality: SourceQuality | null,
): void;
processVideoElement(video: HTMLVideoElement): void;
processContainerElement(container: HTMLElement): void;

View File

@@ -8,7 +8,7 @@ export function useCaptions() {
const setLanguage = useSubtitleStore((s) => s.setLanguage);
const enabled = useSubtitleStore((s) => s.enabled);
const resetSubtitleSpecificSettings = useSubtitleStore(
(s) => s.resetSubtitleSpecificSettings
(s) => s.resetSubtitleSpecificSettings,
);
const setCaption = usePlayerStore((s) => s.setCaption);
const lastSelectedLanguage = useSubtitleStore((s) => s.lastSelectedLanguage);
@@ -27,7 +27,7 @@ export function useCaptions() {
resetSubtitleSpecificSettings();
setLanguage(language);
},
[setLanguage, captionList, setCaption, resetSubtitleSpecificSettings]
[setLanguage, captionList, setCaption, resetSubtitleSpecificSettings],
);
const disable = useCallback(async () => {

View File

@@ -22,7 +22,7 @@ export function useInitializeSource() {
const source = usePlayerStore((s) => s.source);
const sourceIdentifier = useMemo(
() => (source ? JSON.stringify(source) : null),
[source]
[source],
);
const { selectLastUsedLanguageIfEnabled } = useCaptions();

View File

@@ -16,7 +16,7 @@ export interface Source {
function getProgress(
items: Record<string, ProgressMediaItem>,
meta: PlayerMeta | null
meta: PlayerMeta | null,
): number {
const item = items[meta?.tmdbId ?? ""];
if (!item || !meta) return 0;
@@ -38,10 +38,10 @@ export function usePlayer() {
const setSourceId = usePlayerStore((s) => s.setSourceId);
const status = usePlayerStore((s) => s.status);
const shouldStartFromBeginning = usePlayerStore(
(s) => s.interface.shouldStartFromBeginning
(s) => s.interface.shouldStartFromBeginning,
);
const setShouldStartFromBeginning = usePlayerStore(
(s) => s.setShouldStartFromBeginning
(s) => s.setShouldStartFromBeginning,
);
const reset = usePlayerStore((s) => s.reset);
const meta = usePlayerStore((s) => s.meta);
@@ -61,7 +61,7 @@ export function usePlayer() {
source: SourceSliceSource,
captions: CaptionListItem[],
sourceId: string | null,
startAtOverride?: number
startAtOverride?: number,
) {
const start = startAtOverride ?? getProgress(progressStore.items, meta);
setCaption(null);

View File

@@ -13,14 +13,14 @@ export function usePlayerMeta() {
const { meta, setMeta } = usePlayer();
const scrapeMedia = useMemo(
() => (meta ? metaToScrapeMedia(meta) : null),
[meta]
[meta],
);
const setDirectMeta = useCallback(
(m: PlayerMeta) => {
setMeta(m, playerStatus.SCRAPING);
},
[setMeta]
[setMeta],
);
const setPlayerMeta = useCallback(
@@ -65,7 +65,7 @@ export function usePlayerMeta() {
setDirectMeta(playerMeta);
return playerMeta;
},
[setDirectMeta]
[setDirectMeta],
);
return {

View File

@@ -4,12 +4,12 @@ import { usePlayerStore } from "@/stores/player/store";
export function useShouldShowControls() {
const hovering = usePlayerStore((s) => s.interface.hovering);
const lastHoveringState = usePlayerStore(
(s) => s.interface.lastHoveringState
(s) => s.interface.lastHoveringState,
);
const isPaused = usePlayerStore((s) => s.mediaPlaying.isPaused);
const hasOpenOverlay = usePlayerStore((s) => s.interface.hasOpenOverlay);
const isHoveringControls = usePlayerStore(
(s) => s.interface.isHoveringControls
(s) => s.interface.isHoveringControls,
);
const isUsingTouch = lastHoveringState === PlayerHoverState.MOBILE_TAPPED;

View File

@@ -5,6 +5,10 @@ import {
} from "@movie-web/providers";
import { useAsyncFn } from "react-use";
import {
connectServerSideEvents,
makeProviderUrl,
} from "@/backend/helpers/providerApi";
import {
scrapeSourceOutputToProviderMetric,
useReportProviders,
@@ -14,13 +18,13 @@ import { convertRunoutputToSource } from "@/components/player/utils/convertRunou
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { metaToScrapeMedia } from "@/stores/player/slices/source";
import { usePlayerStore } from "@/stores/player/store";
import { providers } from "@/utils/providers";
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
export function useEmbedScraping(
routerId: string,
sourceId: string,
url: string,
embedId: string
embedId: string,
) {
const setSource = usePlayerStore((s) => s.setSource);
const setCaption = usePlayerStore((s) => s.setCaption);
@@ -31,13 +35,23 @@ export function useEmbedScraping(
const { report } = useReportProviders();
const [request, run] = useAsyncFn(async () => {
const providerApiUrl = getLoadbalancedProviderApiUrl();
let result: EmbedOutput | undefined;
if (!meta) return;
try {
result = await providers.runEmbedScraper({
id: embedId,
url,
});
if (providerApiUrl) {
const baseUrlMaker = makeProviderUrl(providerApiUrl);
const conn = await connectServerSideEvents<EmbedOutput>(
baseUrlMaker.scrapeEmbed(embedId, url),
["completed", "noOutput"],
);
result = await conn.promise();
} else {
result = await providers.runEmbedScraper({
id: embedId,
url,
});
}
} catch (err) {
console.error(`Failed to scrape ${embedId}`, err);
const notFound = err instanceof NotFoundError;
@@ -48,7 +62,7 @@ export function useEmbedScraping(
sourceId,
embedId,
status,
err
err,
),
]);
throw err;
@@ -61,7 +75,7 @@ export function useEmbedScraping(
setSource(
convertRunoutputToSource({ stream: result.stream }),
convertProviderCaption(result.stream.captions),
progress
progress,
);
router.close();
}, [embedId, sourceId, meta, router, report, setCaption]);
@@ -85,13 +99,23 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
const [request, run] = useAsyncFn(async () => {
if (!sourceId || !meta) return null;
const scrapeMedia = metaToScrapeMedia(meta);
const providerApiUrl = getLoadbalancedProviderApiUrl();
let result: SourcererOutput | undefined;
try {
result = await providers.runSourceScraper({
id: sourceId,
media: scrapeMedia,
});
if (providerApiUrl) {
const baseUrlMaker = makeProviderUrl(providerApiUrl);
const conn = await connectServerSideEvents<SourcererOutput>(
baseUrlMaker.scrapeSource(sourceId, scrapeMedia),
["completed", "noOutput"],
);
result = await conn.promise();
} else {
result = await providers.runSourceScraper({
id: sourceId,
media: scrapeMedia,
});
}
} catch (err) {
console.error(`Failed to scrape ${sourceId}`, err);
const notFound = err instanceof NotFoundError;
@@ -110,7 +134,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
setSource(
convertRunoutputToSource({ stream: result.stream }),
convertProviderCaption(result.stream.captions),
progress
progress,
);
setSourceId(sourceId);
router.close();
@@ -120,10 +144,22 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
let embedResult: EmbedOutput | undefined;
if (!meta) return;
try {
embedResult = await providers.runEmbedScraper({
id: result.embeds[0].embedId,
url: result.embeds[0].url,
});
if (providerApiUrl) {
const baseUrlMaker = makeProviderUrl(providerApiUrl);
const conn = await connectServerSideEvents<EmbedOutput>(
baseUrlMaker.scrapeEmbed(
result.embeds[0].embedId,
result.embeds[0].url,
),
["completed", "noOutput"],
);
embedResult = await conn.promise();
} else {
embedResult = await providers.runEmbedScraper({
id: result.embeds[0].embedId,
url: result.embeds[0].url,
});
}
} catch (err) {
console.error(`Failed to scrape ${result.embeds[0].embedId}`, err);
const notFound = err instanceof NotFoundError;
@@ -134,7 +170,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
sourceId,
result.embeds[0].embedId,
status,
err
err,
),
]);
throw err;
@@ -145,7 +181,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
sourceId,
result.embeds[0].embedId,
"success",
null
null,
),
]);
setSourceId(sourceId);
@@ -153,7 +189,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
setSource(
convertRunoutputToSource({ stream: embedResult.stream }),
convertProviderCaption(embedResult.stream.captions),
progress
progress,
);
router.close();
}

View File

@@ -102,13 +102,13 @@ export function CastingInternal() {
}
newControlller.addEventListener(
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
connectionChanged
connectionChanged,
);
return () => {
newControlller.removeEventListener(
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
connectionChanged
connectionChanged,
);
};
}, [available, setPlayer, setController, setInstance, setIsCasting]);

View File

@@ -9,7 +9,7 @@ export function SectionTitle(props: {
<h3
className={classNames(
"uppercase font-bold text-video-context-type-secondary text-xs pt-8 pl-1 pb-2.5 border-b border-video-context-border",
props.className
props.className,
)}
>
{props.children}
@@ -47,7 +47,7 @@ export function ScrollToActiveSection(props: {
scrollingContainer.current?.scrollTo(
0,
activeYPos - boxRect.height / 2 + activeLinkRect.height / 2
activeYPos - boxRect.height / 2 + activeLinkRect.height / 2,
);
}, [props.loaded]);

View File

@@ -75,11 +75,11 @@ export function KeyboardEvents() {
}
if (k === "ArrowUp")
dataRef.current.setVolume(
(dataRef.current.mediaPlaying?.volume || 0) + 0.15
(dataRef.current.mediaPlaying?.volume || 0) + 0.15,
);
if (k === "ArrowDown")
dataRef.current.setVolume(
(dataRef.current.mediaPlaying?.volume || 0) - 0.15
(dataRef.current.mediaPlaying?.volume || 0) - 0.15,
);
if (k === "m") dataRef.current.toggleMute();

View File

@@ -15,7 +15,7 @@ export interface StatusCircleLoading extends StatusCircle {
}
function statusIsLoading(
props: StatusCircle | StatusCircleLoading
props: StatusCircle | StatusCircleLoading,
): props is StatusCircleLoading {
return props.type === "loading";
}
@@ -25,7 +25,7 @@ export function StatusCircle(props: StatusCircle | StatusCircleLoading) {
() => ({
percentage: statusIsLoading(props) ? props.percentage : 0,
}),
[props]
[props],
);
return (

View File

@@ -95,7 +95,7 @@ class ThumnbnailWorker {
0,
0,
this.canvasEl.width,
this.canvasEl.height
this.canvasEl.height,
);
const imgUrl = this.canvasEl.toDataURL();

View File

@@ -1,5 +1,6 @@
import classNames from "classnames";
import { PointerEvent, useCallback } from "react";
import { useEffectOnce, useTimeoutFn } from "react-use";
import { useShouldShowVideoElement } from "@/components/player/internals/VideoContainer";
import { PlayerHoverState } from "@/stores/player/slices/interface";
@@ -10,9 +11,15 @@ export function VideoClickTarget(props: { showingControls: boolean }) {
const display = usePlayerStore((s) => s.display);
const isPaused = usePlayerStore((s) => s.mediaPlaying.isPaused);
const updateInterfaceHovering = usePlayerStore(
(s) => s.updateInterfaceHovering
(s) => s.updateInterfaceHovering,
);
const hovering = usePlayerStore((s) => s.interface.hovering);
const [_, cancel, reset] = useTimeoutFn(() => {
updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
}, 3000);
useEffectOnce(() => {
cancel();
});
const toggleFullscreen = useCallback(() => {
display?.toggleFullscreen();
@@ -29,11 +36,15 @@ export function VideoClickTarget(props: { showingControls: boolean }) {
}
// toggle on other types of clicks
if (hovering !== PlayerHoverState.MOBILE_TAPPED)
if (hovering !== PlayerHoverState.MOBILE_TAPPED) {
updateInterfaceHovering(PlayerHoverState.MOBILE_TAPPED);
else updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
reset();
} else {
updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
cancel();
}
},
[display, isPaused, hovering, updateInterfaceHovering]
[display, isPaused, hovering, updateInterfaceHovering, reset, cancel],
);
if (!show) return null;

View File

@@ -70,7 +70,7 @@ function VideoElement() {
const language = usePlayerStore((s) => s.caption.selected?.language);
const trackObjectUrl = useObjectUrl(
() => (srtData ? convertSubtitlesToObjectUrl(srtData) : null),
[srtData]
[srtData],
);
// report video element to display interface

View File

@@ -12,7 +12,7 @@ export function captionIsVisible(
start: number,
end: number,
delay: number,
currentTime: number
currentTime: number,
) {
const delayedStart = start / 1000 + delay;
const delayedEnd = end / 1000 + delay;
@@ -52,7 +52,7 @@ export function convertSubtitlesToSrt(text: string): string {
export function parseSubtitles(
text: string,
_language?: string
_language?: string,
): CaptionCueType[] {
const vtt = convertSubtitlesToVtt(text);
return parse(vtt).filter((cue) => cue.type === "caption") as CaptionCueType[];
@@ -64,7 +64,7 @@ function stringToBase64(input: string): string {
export function convertSubtitlesToSrtDataurl(text: string): string {
return `data:application/x-subrip;base64,${stringToBase64(
convertSubtitlesToSrt(text)
convertSubtitlesToSrt(text),
)}`;
}
@@ -72,12 +72,12 @@ export function convertSubtitlesToObjectUrl(text: string): string {
return URL.createObjectURL(
new Blob([convertSubtitlesToVtt(text)], {
type: "text/vtt",
})
}),
);
}
export function convertProviderCaption(
captions: RunOutput["stream"]["captions"]
captions: RunOutput["stream"]["captions"],
): CaptionListItem[] {
return captions.map((v) => ({
language: v.language,

View File

@@ -18,7 +18,7 @@ const mediaErrorMap: Record<number, { name: string; key: string }> = {
};
export function getMediaErrorDetails(
err: MediaError | null
err: MediaError | null,
): (typeof mediaErrorMap)[number] {
const item = mediaErrorMap[err?.code ?? -1];
if (!item) {

View File

@@ -36,7 +36,7 @@ export const TextInputControl = forwardRef<
onFocus,
passwordToggleable,
},
ref
ref,
) => {
let inputType = "text";
const [showPassword, setShowPassword] = useState(true);
@@ -81,5 +81,5 @@ export const TextInputControl = forwardRef<
}
return input;
}
},
);

View File

@@ -8,7 +8,7 @@ export function Paragraph(props: {
<p
className={classNames(
"text-errors-type-secondary",
props.marginClass ?? "mt-6"
props.marginClass ?? "mt-6",
)}
>
{props.children}

View File

@@ -8,7 +8,7 @@ export function Title(props: {
<h2
className={classNames(
"text-white text-3xl font-bold text-opacity-100 mt-6",
props.className
props.className,
)}
>
{props.children}

View File

@@ -5,7 +5,7 @@ export function Divider(props: { marginClass?: string }) {
<hr
className={classNames(
"w-full h-px border-0 bg-utils-divider bg-opacity-50",
props.marginClass ?? "my-8"
props.marginClass ?? "my-8",
)}
/>
);

View File

@@ -46,11 +46,11 @@ function Light(props: FlareProps) {
const halfSize = size / 2;
outerRef.current.style.setProperty(
"--bg-x",
`${(e.clientX - rect.left - halfSize).toFixed(0)}px`
`${(e.clientX - rect.left - halfSize).toFixed(0)}px`,
);
outerRef.current.style.setProperty(
"--bg-y",
`${(e.clientY - rect.top - halfSize).toFixed(0)}px`
`${(e.clientY - rect.top - halfSize).toFixed(0)}px`,
);
}
document.addEventListener("mousemove", mouseMove);
@@ -66,7 +66,7 @@ function Light(props: FlareProps) {
props.className,
{
"!opacity-100": props.enabled ?? false,
}
},
)}
style={{
backgroundImage: `radial-gradient(circle at center, rgba(var(${cssVar}), 1), rgba(var(${cssVar}), 0) 70%)`,
@@ -79,7 +79,7 @@ function Light(props: FlareProps) {
className={c(
"absolute inset-[1px] overflow-hidden",
props.className,
props.backgroundClass
props.backgroundClass,
)}
>
<div

View File

@@ -33,7 +33,7 @@ class Particle {
options: LightbarOptions = {
horizontalMotion: false,
sizeRange: [10, 10],
}
},
) {
if (options.imgSrc) {
this.image = new Image();
@@ -117,7 +117,7 @@ class Particle {
this.radius * 1.5,
this.direction,
0,
Math.PI * 2
Math.PI * 2,
);
ctx.fillStyle = "white";
ctx.fill();
@@ -160,7 +160,7 @@ function ParticlesCanvas() {
}
// Fish easter egg
const shouldShowFishie = Math.floor(Math.random() * 600) === 1;
const shouldShowFishie = Math.floor(Math.random() * 600) === 69;
if (shouldShowFishie) {
imageOverride = [
{

View File

@@ -8,7 +8,7 @@ export function Ol(props: { items: React.ReactNode[] }) {
<li
className={classNames(
"grid grid-cols-[auto,1fr] gap-6",
i !== props.items.length - 1 ? "pb-12" : undefined
i !== props.items.length - 1 ? "pb-12" : undefined,
)}
>
<div className="relative z-0">

View File

@@ -24,7 +24,7 @@ interface Props {
function getClasses(
animation: TransitionAnimations,
duration: string
duration: string,
): TransitionClasses {
if (animation === "slide-down") {
return {

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