mirror of
https://github.com/iv-org/invidious.git
synced 2025-05-23 16:41:26 -04:00
Compare commits
407 commits
v2.2024082
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4b37d47ebb | ||
![]() |
2c857b5ab6 | ||
![]() |
00299ca4a0 | ||
![]() |
9d18c8699f | ||
![]() |
475bf7448a | ||
![]() |
50e0a4361b | ||
![]() |
6bfb61e9b4 | ||
![]() |
ef07c542dc | ||
![]() |
a9180aa6c1 | ||
![]() |
4b2f9ffffc | ||
![]() |
64ad97f308 | ||
![]() |
d5cb653fd1 | ||
![]() |
0b23dd12e1 | ||
![]() |
23d66338cd | ||
![]() |
df41cb9588 | ||
![]() |
49ada0aae9 | ||
![]() |
f6a41ce90d | ||
![]() |
f7aefd5fb1 | ||
![]() |
6376fd55db | ||
![]() |
9e172d8371 | ||
![]() |
8d0834005f | ||
![]() |
9f192d4f74 | ||
![]() |
ee7b8b6c61 | ||
![]() |
b9097d0a3b | ||
![]() |
be469304de | ||
![]() |
b6b245586a | ||
![]() |
88195113bf | ||
![]() |
4d381aca60 | ||
![]() |
a5904ecce2 | ||
![]() |
42125dfadd | ||
![]() |
5953f7286f | ||
![]() |
31556d0f88 | ||
![]() |
7bd1abecde | ||
![]() |
583195ccbd | ||
![]() |
f96e476ed9 | ||
![]() |
9186020f94 | ||
![]() |
546a799f0b | ||
![]() |
2d8326c63d | ||
![]() |
9c9a8592e0 | ||
![]() |
435106b7de | ||
![]() |
6a9ed48d5d | ||
![]() |
a5b97a5850 | ||
![]() |
f8f6eb74f5 | ||
![]() |
1e73f4e382 | ||
![]() |
476bc51b0e | ||
![]() |
96b226b130 | ||
![]() |
3b87bf2675 | ||
![]() |
0dff773a07 | ||
![]() |
03f89be929 | ||
![]() |
d4eb2a9741 | ||
![]() |
6fe21a7523 | ||
![]() |
aab6ff4bb6 | ||
![]() |
20cf913a4e | ||
![]() |
1492453c60 | ||
![]() |
401bc110d6 | ||
![]() |
30ae222bf2 | ||
![]() |
81ca831439 | ||
![]() |
8feea29607 | ||
![]() |
c4944ee061 | ||
![]() |
406277b16f | ||
![]() |
7259c63648 | ||
![]() |
73f524fccd | ||
![]() |
03e06b239b | ||
![]() |
c304ea6db3 | ||
![]() |
b120abdcc5 | ||
![]() |
9e3c0dfd85 | ||
![]() |
25eade589f | ||
![]() |
35896d086b | ||
![]() |
d1bc15b8bf | ||
![]() |
1f028fee0f | ||
![]() |
2c1400c41e | ||
![]() |
8fd0b82c38 | ||
![]() |
7579adc3a3 | ||
![]() |
d567c6be6e | ||
![]() |
05b99df49a | ||
![]() |
6c063436d4 | ||
![]() |
0c07e9d27a | ||
![]() |
23ff6135bb | ||
![]() |
7b27585454 | ||
![]() |
f7810ba007 | ||
![]() |
c288005bfd | ||
![]() |
aae5ba01c2 | ||
![]() |
dd16f15aae | ||
![]() |
180d77276b | ||
![]() |
0e0a95430a | ||
![]() |
9de69c0052 | ||
![]() |
dbeee71457 | ||
![]() |
94cb80ea81 | ||
![]() |
409d12a81e | ||
![]() |
70ff463cc6 | ||
![]() |
e23d0d13be | ||
![]() |
5c8b4eb379 | ||
![]() |
dd2e999402 | ||
![]() |
adcdb8cb92 | ||
![]() |
fe4fa0480a | ||
![]() |
dbbcacc955 | ||
![]() |
58ad848d56 | ||
![]() |
f9b9e85ee4 | ||
![]() |
6ac74f4362 | ||
![]() |
9fbe3944b0 | ||
![]() |
c5e9447f41 | ||
![]() |
3e329410d1 | ||
![]() |
74dfda150e | ||
![]() |
e60f53154e | ||
![]() |
3d77635a5c | ||
![]() |
d0433c8386 | ||
![]() |
4ea4878d1a | ||
![]() |
1f0a89fb5f | ||
![]() |
f95f87e448 | ||
![]() |
49afbf2a14 | ||
![]() |
d853b9f6dc | ||
![]() |
d70681538a | ||
![]() |
05c5448bc1 | ||
![]() |
e2df12b7d6 | ||
![]() |
29219c46a1 | ||
![]() |
a77f083a0a | ||
![]() |
eaf47385c5 | ||
![]() |
1fb8d3f583 | ||
![]() |
26b15d6e35 | ||
![]() |
786e3e0550 | ||
![]() |
104553fdc4 | ||
![]() |
ae670d5b2d | ||
![]() |
b2c14f1a2a | ||
![]() |
b899bc959e | ||
![]() |
74dc6795cd | ||
![]() |
5404b67bef | ||
![]() |
7b59ccf645 | ||
![]() |
cc6c39d0e6 | ||
![]() |
37f3c285d7 | ||
![]() |
106086c766 | ||
![]() |
0980867d42 | ||
![]() |
3abc377d56 | ||
![]() |
4a0a6f7ed5 | ||
![]() |
3056e1767e | ||
![]() |
0846faa6f6 | ||
![]() |
943c42e47b | ||
![]() |
fc7b5120db | ||
![]() |
d4d6a4b172 | ||
![]() |
e0cb54f7e0 | ||
![]() |
844e1bdf43 | ||
![]() |
aacfbb09da | ||
![]() |
f57b4b5e4f | ||
![]() |
b1422b7434 | ||
![]() |
f56e4012fe | ||
![]() |
7d5b2ec7b6 | ||
![]() |
cad64e420c | ||
![]() |
f181ae3cb0 | ||
![]() |
0fd480bae2 | ||
![]() |
afb0aad7d3 | ||
![]() |
6816ded0fa | ||
![]() |
0546a73bfa | ||
![]() |
164d764d55 | ||
![]() |
4a31da4000 | ||
![]() |
831017f403 | ||
![]() |
52daafe047 | ||
![]() |
dca130ca6f | ||
![]() |
086c6209ab | ||
![]() |
0d398c9d1a | ||
![]() |
dc38bcdf17 | ||
![]() |
d5442d45bc | ||
![]() |
d4f0560e80 | ||
![]() |
eae3c42dab | ||
![]() |
c0131d8646 | ||
![]() |
21fd717701 | ||
![]() |
8ee73aa0c1 | ||
![]() |
6e3ec10d76 | ||
![]() |
d95ae7e6a5 | ||
![]() |
d36f372bd1 | ||
![]() |
58c65e921f | ||
![]() |
5d9ed95ffd | ||
![]() |
033e42a981 | ||
![]() |
bfa6da2474 | ||
![]() |
097b4f0433 | ||
![]() |
e1378702af | ||
![]() |
b13f77b5af | ||
![]() |
b4a6193642 | ||
![]() |
525dea1e2a | ||
![]() |
f9885cca8e | ||
![]() |
047ead8080 | ||
![]() |
275318dae2 | ||
![]() |
48d2250024 | ||
![]() |
5f8130fd03 | ||
![]() |
b4e930f3bc | ||
![]() |
d7f5cdc2f9 | ||
![]() |
04b0742293 | ||
![]() |
1838ac4c99 | ||
![]() |
8729f01075 | ||
![]() |
6dd89bd401 | ||
![]() |
bba1769f4b | ||
![]() |
6b0e4e6817 | ||
![]() |
6abee5de99 | ||
![]() |
9892604758 | ||
![]() |
5d2dd40bc3 | ||
![]() |
699d53ad41 | ||
![]() |
3ac8978e96 | ||
![]() |
e7a93fcc18 | ||
![]() |
aa33d9b7ec | ||
![]() |
2150264d84 | ||
![]() |
d42561d74a | ||
![]() |
7092bb8855 | ||
![]() |
d7c35e6e3d | ||
![]() |
bc86fb8a82 | ||
![]() |
ec82c2f539 | ||
![]() |
4b363e32fa | ||
![]() |
7a15318fbc | ||
![]() |
5fa87cc27c | ||
![]() |
d2123b4682 | ||
![]() |
0f8f32bca8 | ||
![]() |
f3e93ca83d | ||
![]() |
82b1506ccc | ||
![]() |
b9ad9bd723 | ||
![]() |
8bf7e02978 | ||
![]() |
1a49e798c8 | ||
![]() |
9d54cf903e | ||
![]() |
1333fed26c | ||
![]() |
b173d4acf2 | ||
![]() |
43d5efd9da | ||
![]() |
1480e0089f | ||
![]() |
a5fb78bba5 | ||
![]() |
09f5485889 | ||
![]() |
a760b69cb6 | ||
![]() |
4f7a18a630 | ||
![]() |
42da2547e3 | ||
![]() |
09ccea1d31 | ||
![]() |
2a19dbb1fe | ||
![]() |
6dd662a5b8 | ||
![]() |
301aeffa78 | ||
![]() |
d27a5e7fae | ||
![]() |
afc5b27d83 | ||
![]() |
1a5047aad9 | ||
![]() |
ce910b5269 | ||
![]() |
78f18b257c | ||
![]() |
3196182d4d | ||
![]() |
82248fad02 | ||
![]() |
cbc546f032 | ||
![]() |
792d0d5f6d | ||
![]() |
ac6e796c73 | ||
![]() |
75c5881c55 | ||
![]() |
6da18ddc41 | ||
![]() |
cdf93b29e6 | ||
![]() |
eed14d08a8 | ||
![]() |
c243d08afb | ||
![]() |
2e3a7ad044 | ||
![]() |
c427c184e2 | ||
![]() |
59acf23c0c | ||
![]() |
2eeb6a731d | ||
![]() |
0fb67cc090 | ||
![]() |
9957da28dc | ||
![]() |
f326bcf8db | ||
![]() |
b0c7dd9771 | ||
![]() |
dbdf2ad23a | ||
![]() |
dbd96c77e4 | ||
![]() |
e453a2a682 | ||
![]() |
7e4b3b182a | ||
![]() |
711d52d47f | ||
![]() |
ee72809282 | ||
![]() |
d8b893e9ad | ||
![]() |
70e4eb7f5d | ||
![]() |
0d03818700 | ||
![]() |
e6f52eaf00 | ||
![]() |
90544e07b6 | ||
![]() |
952b3625a0 | ||
![]() |
f51a3b8d2b | ||
![]() |
84e4746265 | ||
![]() |
d2edd4b63f | ||
![]() |
a88a723de3 | ||
![]() |
d5f5490aee | ||
![]() |
82d797b74e | ||
![]() |
97895a491a | ||
![]() |
0ac9367322 | ||
![]() |
d3830f7870 | ||
![]() |
3cfcc16403 | ||
![]() |
171c0a0814 | ||
![]() |
82ac9a8609 | ||
![]() |
7c79ee7cc2 | ||
![]() |
f6e09250cd | ||
![]() |
0fecde6917 | ||
![]() |
66f5b12ecd | ||
![]() |
77f57714ea | ||
![]() |
d9afe38504 | ||
![]() |
3af11d800c | ||
![]() |
d72531d843 | ||
![]() |
ecfcad8d1c | ||
![]() |
d63b15dc1c | ||
![]() |
edb69d601e | ||
![]() |
51562f4b24 | ||
![]() |
76f045b8d7 | ||
![]() |
46eaa0f9b8 | ||
![]() |
56bccaba77 | ||
![]() |
4e8d03221b | ||
![]() |
5d46eba6f2 | ||
![]() |
d3eedab545 | ||
![]() |
cd43997bba | ||
![]() |
fead7603e6 | ||
![]() |
486b5b363c | ||
![]() |
2b3619e489 | ||
![]() |
7a95cb43ef | ||
![]() |
e09a7de5c7 | ||
![]() |
79d1aaff1a | ||
![]() |
d7a5ca8fff | ||
![]() |
542d4fe553 | ||
![]() |
33df8249f1 | ||
![]() |
4e7fd7ac3b | ||
![]() |
8912e2448d | ||
![]() |
98f1e4170b | ||
![]() |
b384133dc9 | ||
![]() |
1961fc3b11 | ||
![]() |
2e649363d2 | ||
![]() |
53e8a5d62d | ||
![]() |
17b525f2a6 | ||
![]() |
b2a83991d1 | ||
![]() |
d77afdcf00 | ||
![]() |
f8ec312328 | ||
![]() |
a021b93063 | ||
![]() |
d9df90b5e3 | ||
![]() |
cec3cfba77 | ||
![]() |
de918b9234 | ||
![]() |
5e899d73a9 | ||
![]() |
f247b2f862 | ||
![]() |
bd34659ff6 | ||
![]() |
f1baeef4bc | ||
![]() |
157c4c3e98 | ||
![]() |
3850739d7f | ||
![]() |
9d91ac3b88 | ||
![]() |
5d0149844f | ||
![]() |
75b68618ab | ||
![]() |
003c6f81dc | ||
![]() |
4bc77b81bf | ||
![]() |
06e1a508e8 | ||
![]() |
52bc9aa328 | ||
![]() |
480e073fa9 | ||
![]() |
6e39b9b303 | ||
![]() |
46c58bd84c | ||
![]() |
7521902e88 | ||
![]() |
bd48af825c | ||
![]() |
ee89db49ba | ||
![]() |
3af6681869 | ||
![]() |
1124dd645d | ||
![]() |
b2133c6b2c | ||
![]() |
b526f48120 | ||
![]() |
e8cd631b2d | ||
![]() |
69ff6def5f | ||
![]() |
26dc9dc99c | ||
![]() |
2d6b46c926 | ||
![]() |
cab02d4959 | ||
![]() |
5f590dda80 | ||
![]() |
b2f5b1eb68 | ||
![]() |
7693f61e44 | ||
![]() |
7214fdaff4 | ||
![]() |
c24ed85110 | ||
![]() |
288e1dccda | ||
![]() |
9980c0e00f | ||
![]() |
aa96cf3453 | ||
![]() |
41c978d350 | ||
![]() |
cff25a7b25 | ||
![]() |
6b7e730100 | ||
![]() |
ccb2a6c58e | ||
![]() |
3b471ae964 | ||
![]() |
eb8fcc9e88 | ||
![]() |
5b11ca22d0 | ||
![]() |
6db4a46c5f | ||
![]() |
7b7197cde8 | ||
![]() |
3c6019edd0 | ||
![]() |
6861148290 | ||
![]() |
03f9962a47 | ||
![]() |
d098e5ae9b | ||
![]() |
4c486634e2 | ||
![]() |
3bced4e12b | ||
![]() |
0d22af6564 | ||
![]() |
2a6a32e667 | ||
![]() |
50da6cf3e7 | ||
![]() |
7388e4ca72 | ||
![]() |
be216fff94 | ||
![]() |
019807256f | ||
![]() |
a0d24190b8 | ||
![]() |
2b2d67fcfa | ||
![]() |
76369eb599 | ||
![]() |
6236cea33e | ||
![]() |
e8c2388589 | ||
![]() |
995df2d296 | ||
![]() |
c0d75bc52f | ||
![]() |
e307fcc9a1 | ||
![]() |
bae8bab3ff | ||
![]() |
fa59f41f7b | ||
![]() |
20ca1ebcc0 | ||
![]() |
b0b4f09b3a | ||
![]() |
48af0af9d5 | ||
![]() |
f9460e31bc | ||
![]() |
b7a252b096 | ||
![]() |
6b929da0e1 | ||
![]() |
21122db3a7 | ||
![]() |
c9a843c7fe | ||
![]() |
275501aad3 | ||
![]() |
5cdbc184c7 | ||
![]() |
9996d00cb1 | ||
![]() |
9a617ae087 | ||
![]() |
c257882a1f | ||
![]() |
58bad6180f | ||
![]() |
509bace7d1 | ||
![]() |
824cc1a5aa | ||
![]() |
07c52cba3d | ||
![]() |
04ba7b0d58 | ||
![]() |
4788a3b4a9 | ||
![]() |
7fe2af735d | ||
![]() |
905582db66 | ||
![]() |
bf470704a5 | ||
![]() |
78773d7326 |
142 changed files with 3591 additions and 1444 deletions
.ameba.yml
.github
CHANGELOG.mdMakefileREADME.mdassets
css
js
config
docker
locales
ar.jsonbg.jsonca.jsoncs.jsoncy.jsonde.jsonel.jsonen-US.jsones.jsonfa.jsonfi.jsonfr.jsonhr.jsonia.jsonis.jsonit.jsonja.jsonko.jsonlv.jsonnb-NO.jsonnl.jsonpl.jsonpt-BR.jsonpt-PT.jsonpt.jsonru.jsonsl.jsonsq.jsonsr.jsonsr_Cyrl.jsonsv-SE.jsonta.jsontok.jsontr.jsonuk.jsonvi.jsonzh-CN.jsonzh-TW.json
mocksscripts
shard.lockshard.ymlspec/invidious
src
ext
invidious.crinvidious
|
@ -38,6 +38,9 @@ Style/RedundantBegin:
|
||||||
Style/RedundantReturn:
|
Style/RedundantReturn:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
Style/RedundantNext:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Style/ParenthesesAroundCondition:
|
Style/ParenthesesAroundCondition:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -6,7 +6,7 @@ docker/ @unixfox
|
||||||
kubernetes/ @unixfox
|
kubernetes/ @unixfox
|
||||||
|
|
||||||
README.md @thefrenchghosty
|
README.md @thefrenchghosty
|
||||||
config/config.example.yml @thefrenchghosty @SamantazFox @unixfox
|
config/config.example.yml @SamantazFox @unixfox
|
||||||
|
|
||||||
scripts/ @syeopite
|
scripts/ @syeopite
|
||||||
shards.lock @syeopite
|
shards.lock @syeopite
|
||||||
|
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -10,8 +10,10 @@ assignees: ''
|
||||||
<!--
|
<!--
|
||||||
BEFORE TRYING TO REPORT A BUG:
|
BEFORE TRYING TO REPORT A BUG:
|
||||||
|
|
||||||
* Read the FAQ!
|
* Read the FAQ: https://docs.invidious.io/faq/!
|
||||||
* Use the search function to check if there is already an issue open for your problem!
|
* Use the search function to check if there is already an issue open for your problem: https://github.com/search?q=repo%3Aiv-org%2Finvidious+replace+me+with+your+bug&type=issues!
|
||||||
|
|
||||||
|
MAKE SURE TO FOLLOW THE TWO STEPS ABOVE BEFORE REPORTING A BUG. A BUG THAT ALREADY EXIST WILL IMMEDIATELY CLOSED.
|
||||||
|
|
||||||
If you want to suggest a new feature please use "Feature request" instead
|
If you want to suggest a new feature please use "Feature request" instead
|
||||||
If you want to suggest an enhancement to an existing feature please use "Enhancement" instead
|
If you want to suggest an enhancement to an existing feature please use "Enhancement" instead
|
||||||
|
|
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/docker"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
17
.github/workflows/build-nightly-container.yml
vendored
17
.github/workflows/build-nightly-container.yml
vendored
|
@ -23,19 +23,6 @@ jobs:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Crystal
|
|
||||||
uses: crystal-lang/install-crystal@v1.8.2
|
|
||||||
with:
|
|
||||||
crystal: 1.12.2
|
|
||||||
|
|
||||||
- name: Run lint
|
|
||||||
run: |
|
|
||||||
if ! crystal tool format --check; then
|
|
||||||
crystal tool format
|
|
||||||
git diff
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
|
@ -63,7 +50,7 @@ jobs:
|
||||||
quay.expires-after=12w
|
quay.expires-after=12w
|
||||||
|
|
||||||
- name: Build and push Docker AMD64 image for Push Event
|
- name: Build and push Docker AMD64 image for Push Event
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
|
@ -88,7 +75,7 @@ jobs:
|
||||||
quay.expires-after=12w
|
quay.expires-after=12w
|
||||||
|
|
||||||
- name: Build and push Docker ARM64 image for Push Event
|
- name: Build and push Docker ARM64 image for Push Event
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile.arm64
|
file: docker/Dockerfile.arm64
|
||||||
|
|
17
.github/workflows/build-stable-container.yml
vendored
17
.github/workflows/build-stable-container.yml
vendored
|
@ -14,19 +14,6 @@ jobs:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Crystal
|
|
||||||
uses: crystal-lang/install-crystal@v1.8.2
|
|
||||||
with:
|
|
||||||
crystal: 1.12.2
|
|
||||||
|
|
||||||
- name: Run lint
|
|
||||||
run: |
|
|
||||||
if ! crystal tool format --check; then
|
|
||||||
crystal tool format
|
|
||||||
git diff
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
|
@ -56,7 +43,7 @@ jobs:
|
||||||
quay.expires-after=12w
|
quay.expires-after=12w
|
||||||
|
|
||||||
- name: Build and push Docker AMD64 image for Push Event
|
- name: Build and push Docker AMD64 image for Push Event
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
|
@ -82,7 +69,7 @@ jobs:
|
||||||
quay.expires-after=12w
|
quay.expires-after=12w
|
||||||
|
|
||||||
- name: Build and push Docker ARM64 image for Push Event
|
- name: Build and push Docker ARM64 image for Push Event
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile.arm64
|
file: docker/Dockerfile.arm64
|
||||||
|
|
58
.github/workflows/ci.yml
vendored
58
.github/workflows/ci.yml
vendored
|
@ -38,10 +38,11 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
stable: [true]
|
stable: [true]
|
||||||
crystal:
|
crystal:
|
||||||
- 1.9.2
|
- 1.12.2
|
||||||
- 1.10.1
|
- 1.13.3
|
||||||
- 1.11.2
|
- 1.14.1
|
||||||
- 1.12.1
|
- 1.15.1
|
||||||
|
- 1.16.3
|
||||||
include:
|
include:
|
||||||
- crystal: nightly
|
- crystal: nightly
|
||||||
stable: false
|
stable: false
|
||||||
|
@ -51,15 +52,22 @@ jobs:
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
- name: Install required APT packages
|
||||||
|
run: |
|
||||||
|
sudo apt install -y libsqlite3-dev
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Install Crystal
|
- name: Install Crystal
|
||||||
uses: crystal-lang/install-crystal@v1.8.0
|
uses: crystal-lang/install-crystal@v1.8.2
|
||||||
with:
|
with:
|
||||||
crystal: ${{ matrix.crystal }}
|
crystal: ${{ matrix.crystal }}
|
||||||
|
|
||||||
- name: Cache Shards
|
- name: Cache Shards
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ./lib
|
path: |
|
||||||
|
./lib
|
||||||
|
./bin
|
||||||
key: shards-${{ hashFiles('shard.lock') }}
|
key: shards-${{ hashFiles('shard.lock') }}
|
||||||
|
|
||||||
- name: Install Shards
|
- name: Install Shards
|
||||||
|
@ -71,14 +79,6 @@ jobs:
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: crystal spec
|
run: crystal spec
|
||||||
|
|
||||||
- name: Run lint
|
|
||||||
run: |
|
|
||||||
if ! crystal tool format --check; then
|
|
||||||
crystal tool format
|
|
||||||
git diff
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: crystal build --warnings all --error-on-warnings --error-trace src/invidious.cr
|
run: crystal build --warnings all --error-on-warnings --error-trace src/invidious.cr
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ jobs:
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Build Docker ARM64 image
|
- name: Build Docker ARM64 image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile.arm64
|
file: docker/Dockerfile.arm64
|
||||||
|
@ -124,28 +124,44 @@ jobs:
|
||||||
- name: Test Docker
|
- name: Test Docker
|
||||||
run: while curl -Isf http://localhost:3000; do sleep 1; done
|
run: while curl -Isf http://localhost:3000; do sleep 1; done
|
||||||
|
|
||||||
ameba_lint:
|
lint:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Install Crystal
|
- name: Install Crystal
|
||||||
uses: crystal-lang/install-crystal@v1.8.0
|
id: lint_step_install_crystal
|
||||||
|
uses: crystal-lang/install-crystal@v1.8.2
|
||||||
with:
|
with:
|
||||||
crystal: latest
|
crystal: latest
|
||||||
|
|
||||||
- name: Cache Shards
|
- name: Cache Shards
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
./lib
|
./lib
|
||||||
./bin
|
./bin
|
||||||
key: shards-${{ hashFiles('shard.lock') }}
|
key: shards-${{ hashFiles('shard.lock') }}-${{ steps.lint_step_install_crystal.outputs.crystal }}
|
||||||
|
|
||||||
- name: Install Shards
|
- name: Install Shards
|
||||||
run: shards install
|
run: |
|
||||||
|
if ! shards check; then
|
||||||
|
shards install
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check Crystal formatter compliance
|
||||||
|
run: |
|
||||||
|
if ! crystal tool format --check; then
|
||||||
|
crystal tool format
|
||||||
|
git diff
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run Ameba linter
|
- name: Run Ameba linter
|
||||||
run: bin/ameba
|
run: bin/ameba
|
||||||
|
|
15
.github/workflows/stale.yml
vendored
15
.github/workflows/stale.yml
vendored
|
@ -10,17 +10,14 @@ jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 365
|
days-before-stale: 730
|
||||||
days-before-pr-stale: 90
|
days-before-pr-stale: -1
|
||||||
days-before-close: 30
|
days-before-close: 60
|
||||||
exempt-pr-labels: blocked,exempt-stale
|
|
||||||
stale-issue-message: 'This issue has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely outdated. If you think this issue is still relevant and applicable, you just have to post a comment and it will be unmarked.'
|
stale-issue-message: 'This issue has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely outdated. If you think this issue is still relevant and applicable, you just have to post a comment and it will be unmarked.'
|
||||||
stale-pr-message: 'This pull request has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely abandoned or outdated. If you think this pull request is still relevant and applicable, you just have to post a comment and it will be unmarked.'
|
|
||||||
stale-issue-label: "stale"
|
stale-issue-label: "stale"
|
||||||
stale-pr-label: "stale"
|
|
||||||
ascending: true
|
ascending: true
|
||||||
# Never mark feature requests/enhancements as stale
|
# Exempt the following types of issues from being staled
|
||||||
exempt-issue-labels: "feature-request,enhancement,exempt-stale"
|
exempt-issue-labels: "feature-request,enhancement,discussion,exempt-stale"
|
||||||
|
|
249
CHANGELOG.md
249
CHANGELOG.md
|
@ -1,5 +1,254 @@
|
||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## vX.Y.0 (future)
|
||||||
|
|
||||||
|
## v2.20250517.0
|
||||||
|
|
||||||
|
Inverse fallback for the YouTube client from TVHTML then MWEB. Fixes https://github.com/iv-org/invidious/issues/5273
|
||||||
|
|
||||||
|
## v2.20250504.0
|
||||||
|
|
||||||
|
Small release with quick workaround fix for issue #4251 (Nil assertion failed).
|
||||||
|
|
||||||
|
PR: https://github.com/iv-org/invidious/issues/5262
|
||||||
|
|
||||||
|
## v2.20250314.0
|
||||||
|
|
||||||
|
### Wrap-up
|
||||||
|
|
||||||
|
This release brings the long awaited feature of supporting multiple audio tracks in a video, some bug fixes and UX improvements, and many other things primarily oriented to self-hosting instances, and developers using the API.
|
||||||
|
|
||||||
|
The `Community` channel tab has been replaced by `Posts` in light of YouTube changes, but the URL remains the same.
|
||||||
|
|
||||||
|
Tamil is now available as an interface language
|
||||||
|
|
||||||
|
Automatic instance redirects will no longer have the chance to annoyingly redirect to the same instance you're on.
|
||||||
|
|
||||||
|
Due to their requirements for video playback, Invidious will log warning messages when either inv-sig-helper, `po_token` or `visitor_data` is not configured
|
||||||
|
|
||||||
|
Invidious is now able to listen through a UNIX socket
|
||||||
|
|
||||||
|
User notifications are now batched for each channel
|
||||||
|
|
||||||
|
**The minimum Crystal version supported by Invidious now `1.12.0`**
|
||||||
|
|
||||||
|
### New features & important changes
|
||||||
|
|
||||||
|
#### For users
|
||||||
|
|
||||||
|
* Invidious now supports videos with multiple audio tracks allowing you to select which one you want to hear with!
|
||||||
|
* Channel pages now have a proper previous page button
|
||||||
|
* RSS feeds for channels will no longer contain the channel's profile picture
|
||||||
|
* Support for channel `courses` page has been added
|
||||||
|
* `Community` tabs has been replaced with `Posts` to comply with YouTube changes
|
||||||
|
* Tamil is now an available interface language.
|
||||||
|
|
||||||
|
#### For instance owners
|
||||||
|
* Invidious is now able to listen on a UNIX socket
|
||||||
|
* User notifications are now batched by channels, significantly reducing database load.
|
||||||
|
* **`1.12.0` is now the oldest Crystal version that Invidious supports**
|
||||||
|
* The example config will no longer force an http proxy to be configured
|
||||||
|
* Invidious will now warn when any top-level config option must be set to a custom value, instead of just `HMAC_KEY`
|
||||||
|
* Due to their requirements for video playback, Invidious will log warning messages when either inv-sig-helper, `po_token` or `visitor_data` is not configured
|
||||||
|
|
||||||
|
#### For developers
|
||||||
|
* Invidious is now compliant to Crystal 1.15 formatting rules, which are incompatible with earlier versions.
|
||||||
|
* `/api/v1/transcripts/{id}` has been added to the API to allow for fetching the transcripts for a video. The arguments are the same as the captions endpoint.
|
||||||
|
* `author_thumbnail` field has been added to videos in the various paged api endpoints
|
||||||
|
* `published` field has been added to the API response for a video's related videos.
|
||||||
|
* Docker builds now uses the Crystal compiler cache, reducing build times on repeated builds significantly.
|
||||||
|
* Invidious ajax action handlers has undergone a clean up and may face compatibility issues with code that depends on these endpoints.
|
||||||
|
* The versions of Crystal that we test in CI/CD are now: `1.12.1`, `1.13.2`, `1.14.0`, `1.15.0`
|
||||||
|
|
||||||
|
### Bugs fixed
|
||||||
|
|
||||||
|
#### User-side
|
||||||
|
* Local video listen mode is now preserved when clicking on a video in the sidebar playlist widget
|
||||||
|
* Automatic instance redirects will no longer redirect to the same instance the user is on
|
||||||
|
* Fix some thumbnails responses returning 404
|
||||||
|
* Videos: Fix missing host parameter on playback URLs when `local=true`
|
||||||
|
* Fix HLS being used for non-livestream videos
|
||||||
|
* Fix timeupdate event errors when required elements are missing
|
||||||
|
* User: Ensure IO is properly closed when importing NewPipe subscriptions
|
||||||
|
|
||||||
|
#### For instance owners
|
||||||
|
* Fix http proxy configuration being forced by the standard example config
|
||||||
|
|
||||||
|
#### API
|
||||||
|
* `/api/v1/videos/{id}` will no longer return an occasional empty JSON response
|
||||||
|
|
||||||
|
### Full list of pull requests merged since the last release (newest first)
|
||||||
|
* Make Invidious compliant to Crystal 1.15 formatting rules (https://github.com/iv-org/invidious/pull/5014, by @syeopite)
|
||||||
|
* Remove formatter check on container workflows (https://github.com/iv-org/invidious/pull/5153, by @syeopite)
|
||||||
|
* Videos: Fix missing host parameter on playback URLs when `local=true` (https://github.com/iv-org/invidious/pull/4992, by @SamantazFox)
|
||||||
|
* Remove stdlib override for proxy initialization (https://github.com/iv-org/invidious/pull/5065, by @syeopite)
|
||||||
|
* Add support for author thumbnails in search api for videos (https://github.com/iv-org/invidious/pull/5072, thanks @ChunkyProgrammer)
|
||||||
|
* Skip route if resp got closed by before handlers (https://github.com/iv-org/invidious/pull/5073, by @syeopite)
|
||||||
|
* Fix video thumbnails in mixes (https://github.com/iv-org/invidious/pull/5116, thanks @iBicha)
|
||||||
|
* CI: Drop support for versions prior to 1.12 and add 1.15.0 (https://github.com/iv-org/invidious/pull/5148, by @syeopite)
|
||||||
|
* [Continuing #5094] Set language info for dash audio streams and sort (https://github.com/iv-org/invidious/pull/5149, thanks @giuliano-macedo)
|
||||||
|
* Warn when any top-level config is "CHANGE_ME!!" (https://github.com/iv-org/invidious/pull/5150, by @syeopite)
|
||||||
|
* Comment out http_proxy in example config (https://github.com/iv-org/invidious/pull/5151, by @syeopite)
|
||||||
|
* API: Add a 'published' video parameter for related videos (https://github.com/iv-org/invidious/pull/4149, thanks @RadoslavL)
|
||||||
|
* Ensure IO is properly closed when importing NewPipe subscriptions (https://github.com/iv-org/invidious/pull/4346, thanks @ChunkyProgrammer)
|
||||||
|
* Carry over audio-only mode in playlist links (https://github.com/iv-org/invidious/pull/4784, thanks @krystof1119)
|
||||||
|
* Routes: Clean ajax actions handlers (https://github.com/iv-org/invidious/pull/5036, by @SamantazFox)
|
||||||
|
* Frontend: Add a first page and previous page buttons for channel navigation (https://github.com/iv-org/invidious/pull/4123, thanks @RadoslavL)
|
||||||
|
* RSS: Channel + Playlist improvements (https://github.com/iv-org/invidious/pull/4298, thanks @ChunkyProgrammer)
|
||||||
|
* Batch user notifications together (https://github.com/iv-org/invidious/pull/4486, thanks @999eagle)
|
||||||
|
* JS: Update timeupdate event making it more defensive to prevent errors (https://github.com/iv-org/invidious/pull/4782, thanks @PMK)
|
||||||
|
* Add API endpoint for fetching transcripts from YouTube by (https://github.com/iv-org/invidious/pull/4788, by @syeopite)
|
||||||
|
* Translations update from Hosted Weblate by (https://github.com/iv-org/invidious/pull/4989, thanks to our many translators)
|
||||||
|
* Add the ability to listen on UNIX sockets (https://github.com/iv-org/invidious/pull/5112, thanks @Caian)
|
||||||
|
* Pick a different instance upon redirect (https://github.com/iv-org/invidious/pull/5154, thanks @epicsam123)
|
||||||
|
* Add Courses to channel page and channel API (https://github.com/iv-org/invidious/pull/5158, thanks @ChunkyProgrammer)
|
||||||
|
* fix /api/v1/videos/:id returns 200 with no content (https://github.com/iv-org/invidious/pull/5162, thanks @Drikanis)
|
||||||
|
* Use Crystal compiler cache in docker builds (https://github.com/iv-org/invidious/pull/5163, by @syeopite)
|
||||||
|
* Channels: Fix community tab by (https://github.com/iv-org/invidious/pull/5183, thanks @Fijxu)
|
||||||
|
* Fix typo in `src/invidious/routes/images.cr` (https://github.com/iv-org/invidious/pull/5184, by @syeopite)
|
||||||
|
* Fix an issue with the HLS manifest check for livestream videos (https://github.com/iv-org/invidious/pull/5189, thanks @alexmaras)
|
||||||
|
* Warn when `po_token`, `visitor_data` and/or `inv-sig-helper` is not configured (https://github.com/iv-org/invidious/pull/5202, by @syeopite)
|
||||||
|
## v2.20241110.0
|
||||||
|
|
||||||
|
### Wrap-up
|
||||||
|
|
||||||
|
This release is most importantly here to fix to the annoying "Youtube API returned error 400"
|
||||||
|
error that prevented all channel pages from loading.
|
||||||
|
|
||||||
|
If you're updating from the previous release, it provides no improvements on the ability to play
|
||||||
|
videos. If updating from a commit in-between release, it removes the "Please sign in" error caused
|
||||||
|
by a previous attempt at restoring video playback on large instances.
|
||||||
|
|
||||||
|
In the preferences, a new option allows for control of video preload. When enabled, this option
|
||||||
|
tells the browser to load the video as soon as the page is loaded (this used to be the default).
|
||||||
|
When disabled, the video starts loading only when the "play" button is pressed.
|
||||||
|
|
||||||
|
New interface languages available: Bulgarian, Welsh and Lombard
|
||||||
|
|
||||||
|
New dependency required: `tzdata`.
|
||||||
|
|
||||||
|
An HTTP proxy can be configured directly in Invidious, if needed. \
|
||||||
|
**NOTE:** In that case, it is recommended to comment out `force_resolve`.
|
||||||
|
|
||||||
|
|
||||||
|
### New features & important changes
|
||||||
|
|
||||||
|
#### For users
|
||||||
|
|
||||||
|
* Channels: Fix "Youtube API returned error 400" error preventing channel pages from loading
|
||||||
|
* Channels: Shorts can now be sorted by "newest", "oldest" and "popular"
|
||||||
|
* Preferences: Addition of the new "preload" option
|
||||||
|
* New interface languages available: Bulgarian, Welsh and Lombard
|
||||||
|
* Added "Filipino (auto-generated)" to the list of caption languages available
|
||||||
|
* Lots of new translations from Weblate
|
||||||
|
|
||||||
|
#### For instance owners
|
||||||
|
|
||||||
|
* Allow the configuration of an HTTP proxy to talk to Youtube
|
||||||
|
* Invidious tries to reconnect to `inv_sig_helper` if the socket is closed
|
||||||
|
* The instance list is downloaded in the background to improve redirection speed
|
||||||
|
* New `colorize_logs` option makes each log level a different color
|
||||||
|
|
||||||
|
#### For developpers
|
||||||
|
|
||||||
|
* `/api/v1/channels/{id}/shorts` now supports the `sort-by` parameter with the following values:
|
||||||
|
`newest`, `oldest` and `popular`
|
||||||
|
* Older `/api/v1/channels/xyz/{id}` (tab name before UCID) were removed
|
||||||
|
* API/Search: New video metadata available: `isNew`, `is4k`, `is8k`, `isVr180`, `isVr360`,
|
||||||
|
`is3d` and `hasCaptions`
|
||||||
|
|
||||||
|
### Bugs fixed
|
||||||
|
|
||||||
|
#### User-side
|
||||||
|
|
||||||
|
* Channels: The second page of shorts now loads as expected
|
||||||
|
* Channels: Fixed intermittent empty "playlists" tab
|
||||||
|
* Search: Fixed `youtu.be` URLs not being properly redirected to the watch page
|
||||||
|
* Fixed `DB::MappingException` error on the subscriptions feed (due to missing `tzdata` in docker)
|
||||||
|
* Switching to another instance is much faster
|
||||||
|
* Fixed an "invalid byte sequence" error when subscribing to a playlist
|
||||||
|
* Videos: Playback URLs were sometimes broken when cached and `inv_sig_helper` was used
|
||||||
|
|
||||||
|
#### For instance owners
|
||||||
|
|
||||||
|
* Fix `force_resolve` being ignored in some cases
|
||||||
|
|
||||||
|
#### API
|
||||||
|
|
||||||
|
* API/Videos: Fixed `live_now` and `premiere_timestamp` sometimes not having the right values
|
||||||
|
|
||||||
|
|
||||||
|
### Full list of pull requests merged since the last release (newest first)
|
||||||
|
|
||||||
|
* API: Add "sort_by" parameter to channels/shorts endpoint ([#5071], thanks @iBicha)
|
||||||
|
* Docker: Install tzdata in Dockerfile ([#5070], by @SamantazFox)
|
||||||
|
* Videos: Stop using TVHTML5_SIMPLY_EMBEDDED_PLAYER ([#5063], thanks @unixfox)
|
||||||
|
* Routing: Deprecate old channel API routes ([#5045], by @SamantazFox)
|
||||||
|
* Videos: use WEB client instead of WEB CREATOR ([#4984], thanks @unixfox)
|
||||||
|
* Parsers: Fix parsing live_now and premiere_timestamp ([#4934], thanks @absidue)
|
||||||
|
* Stale bot updates ([#5060], thanks @syeopite)
|
||||||
|
* Channels: Fix "Youtube API returned error 400" ([#5059], by @SamantazFox)
|
||||||
|
* Channels: Fix for live videos ([#5027], thanks @iBicha)
|
||||||
|
* Locales: Add Bulgarian, Welsh and Lombard to the list ([#5046], by @SamantazFox)
|
||||||
|
* Shards: Update database dependencies ([#5034], by @SamantazFox)
|
||||||
|
* Logger: Add color support for different log levels ([#4931], thanks @Fijxu)
|
||||||
|
* Fix named arg syntax when passing force_resolve ([#4754], thanks @syeopite)
|
||||||
|
* Use make_client instead of calling HTTP::Client ([#4709], thanks @syeopite)
|
||||||
|
* Add "Filipino (auto-generated)" to the list of caption languages ([#4995], by @SamantazFox)
|
||||||
|
* Makefile: Add MT option to enable the 'preview_mt' flag ([#4993], by @SamantazFox)
|
||||||
|
* SigHelper: Reconnect to signature helper ([#4991], thanks @Fijxu)
|
||||||
|
* Fix player menus hiding onHover ready ([#4750], thanks @giacomocerquone)
|
||||||
|
* Use connection pools when requesting images from YouTube ([#4326], thanks @syeopite)
|
||||||
|
* Add support for using Invidious through a HTTP Proxy ([#4270], thanks @syeopite)
|
||||||
|
* Search: Fix 'youtu.be' URLs in sanitizer ([#4894], by @SamantazFox)
|
||||||
|
* Ameba: Disable Style/RedundantNext rule ([#4888], thanks @syeopite)
|
||||||
|
* Playlists: Fix 'invalid byte sequence' error when subscribing ([#4887], thanks @DmitrySandalov)
|
||||||
|
* Parse more metadata badges for SearchVideos ([#4863], thanks @ChunkyProgrammer)
|
||||||
|
* Translations update from Hosted Weblate ([#4862], thanks to our many translators)
|
||||||
|
* Videos: Convert URL before putting result into cache ([#4850], by @SamantazFox)
|
||||||
|
* HTML: Add error message to "search issues on GitHub" link ([#4652], thanks @tracedgod)
|
||||||
|
* Preferences: Add option to control preloading of video data ([#4122], thanks @Nerdmind)
|
||||||
|
* Performance: Improve speed of automatic instance redirection ([#4193], thanks @syeopite)
|
||||||
|
* Remove myself from CODEOWNERS on the config file ([#4942], by @TheFrenchGhosty)
|
||||||
|
* Update latest version WEB_CREATOR + fix comment web embed ([#4930], thanks @unixfox)
|
||||||
|
* use WEB_CREATOR when po_token with WEB_EMBED as a fallback ([#4928], thanks @unixfox)
|
||||||
|
* Revert "use web screen embed for fixing potoken functionality"
|
||||||
|
* use web screen embed for fixing potoken functionality ([#4923], thanks @unixfox)
|
||||||
|
|
||||||
|
[#4122]: https://github.com/iv-org/invidious/pull/4122
|
||||||
|
[#4193]: https://github.com/iv-org/invidious/pull/4193
|
||||||
|
[#4270]: https://github.com/iv-org/invidious/pull/4270
|
||||||
|
[#4326]: https://github.com/iv-org/invidious/pull/4326
|
||||||
|
[#4652]: https://github.com/iv-org/invidious/pull/4652
|
||||||
|
[#4709]: https://github.com/iv-org/invidious/pull/4709
|
||||||
|
[#4750]: https://github.com/iv-org/invidious/pull/4750
|
||||||
|
[#4754]: https://github.com/iv-org/invidious/pull/4754
|
||||||
|
[#4850]: https://github.com/iv-org/invidious/pull/4850
|
||||||
|
[#4862]: https://github.com/iv-org/invidious/pull/4862
|
||||||
|
[#4863]: https://github.com/iv-org/invidious/pull/4863
|
||||||
|
[#4887]: https://github.com/iv-org/invidious/pull/4887
|
||||||
|
[#4888]: https://github.com/iv-org/invidious/pull/4888
|
||||||
|
[#4894]: https://github.com/iv-org/invidious/pull/4894
|
||||||
|
[#4923]: https://github.com/iv-org/invidious/pull/4923
|
||||||
|
[#4928]: https://github.com/iv-org/invidious/pull/4928
|
||||||
|
[#4930]: https://github.com/iv-org/invidious/pull/4930
|
||||||
|
[#4931]: https://github.com/iv-org/invidious/pull/4931
|
||||||
|
[#4934]: https://github.com/iv-org/invidious/pull/4934
|
||||||
|
[#4942]: https://github.com/iv-org/invidious/pull/4942
|
||||||
|
[#4984]: https://github.com/iv-org/invidious/pull/4984
|
||||||
|
[#4991]: https://github.com/iv-org/invidious/pull/4991
|
||||||
|
[#4993]: https://github.com/iv-org/invidious/pull/4993
|
||||||
|
[#4995]: https://github.com/iv-org/invidious/pull/4995
|
||||||
|
[#5027]: https://github.com/iv-org/invidious/pull/5027
|
||||||
|
[#5034]: https://github.com/iv-org/invidious/pull/5034
|
||||||
|
[#5045]: https://github.com/iv-org/invidious/pull/5045
|
||||||
|
[#5046]: https://github.com/iv-org/invidious/pull/5046
|
||||||
|
[#5059]: https://github.com/iv-org/invidious/pull/5059
|
||||||
|
[#5060]: https://github.com/iv-org/invidious/pull/5060
|
||||||
|
[#5063]: https://github.com/iv-org/invidious/pull/5063
|
||||||
|
[#5070]: https://github.com/iv-org/invidious/pull/5070
|
||||||
|
[#5071]: https://github.com/iv-org/invidious/pull/5071
|
||||||
|
|
||||||
|
|
||||||
## v2.20240825.2 (2024-08-26)
|
## v2.20240825.2 (2024-08-26)
|
||||||
|
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -7,6 +7,11 @@ STATIC := 0
|
||||||
|
|
||||||
NO_DBG_SYMBOLS := 0
|
NO_DBG_SYMBOLS := 0
|
||||||
|
|
||||||
|
# Enable multi-threading.
|
||||||
|
# Warning: Experimental feature!!
|
||||||
|
# invidious is not stable when MT is enabled.
|
||||||
|
MT := 0
|
||||||
|
|
||||||
|
|
||||||
FLAGS ?=
|
FLAGS ?=
|
||||||
|
|
||||||
|
@ -19,6 +24,10 @@ ifeq ($(STATIC), 1)
|
||||||
FLAGS += --static
|
FLAGS += --static
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(MT), 1)
|
||||||
|
FLAGS += -Dpreview_mt
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
ifeq ($(NO_DBG_SYMBOLS), 1)
|
ifeq ($(NO_DBG_SYMBOLS), 1)
|
||||||
FLAGS += --no-debug
|
FLAGS += --no-debug
|
||||||
|
|
14
README.md
14
README.md
|
@ -81,9 +81,9 @@
|
||||||
- [Available in many languages](locales/), thanks to [our translators](#contribute)
|
- [Available in many languages](locales/), thanks to [our translators](#contribute)
|
||||||
|
|
||||||
**Data import/export**
|
**Data import/export**
|
||||||
- Import subscriptions from YouTube, NewPipe and Freetube
|
- Import subscriptions from YouTube, NewPipe and FreeTube
|
||||||
- Import watch history from YouTube and NewPipe
|
- Import watch history from YouTube and NewPipe
|
||||||
- Export subscriptions to NewPipe and Freetube
|
- Export subscriptions to NewPipe and FreeTube
|
||||||
- Import/Export Invidious user data
|
- Import/Export Invidious user data
|
||||||
|
|
||||||
**Technical features**
|
**Technical features**
|
||||||
|
@ -95,11 +95,11 @@
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
**Using invidious:**
|
**Using Invidious:**
|
||||||
|
|
||||||
- [Select a public instance from the list](https://instances.invidious.io) and start watching videos right now!
|
- [Select a public instance from the list](https://instances.invidious.io) and start watching videos right now!
|
||||||
|
|
||||||
**Hosting invidious:**
|
**Hosting Invidious:**
|
||||||
|
|
||||||
- [Follow the installation instructions](https://docs.invidious.io/installation/)
|
- [Follow the installation instructions](https://docs.invidious.io/installation/)
|
||||||
|
|
||||||
|
@ -114,8 +114,8 @@ https://github.com/iv-org/documentation
|
||||||
### Extensions
|
### Extensions
|
||||||
|
|
||||||
We highly recommend the use of [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect#get),
|
We highly recommend the use of [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect#get),
|
||||||
a browser extension that automatically redirects Youtube URLs to any Invidious instance and replaces
|
a browser extension that automatically redirects YouTube URLs to any Invidious instance and replaces
|
||||||
embedded youtube videos on other websites with invidious.
|
embedded YouTube videos on other websites with Invidious.
|
||||||
|
|
||||||
The documentation contains a list of browser extensions that we recommended to use along with Invidious.
|
The documentation contains a list of browser extensions that we recommended to use along with Invidious.
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ We use [Weblate](https://weblate.org) to manage Invidious translations.
|
||||||
You can suggest new translations and/or correction here: https://hosted.weblate.org/engage/invidious/.
|
You can suggest new translations and/or correction here: https://hosted.weblate.org/engage/invidious/.
|
||||||
|
|
||||||
Creating an account is not required, but recommended, especially if you want to contribute regularly.
|
Creating an account is not required, but recommended, especially if you want to contribute regularly.
|
||||||
Weblate also allows you to log-in with major SSO providers like Github, Gitlab, BitBucket, Google, ...
|
Weblate also allows you to log-in with major SSO providers like GitHub, GitLab, BitBucket, Google, ...
|
||||||
|
|
||||||
|
|
||||||
## Projects using Invidious
|
## Projects using Invidious
|
||||||
|
|
|
@ -550,6 +550,10 @@ span > select {
|
||||||
color: #565d64;
|
color: #565d64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.light-theme .error-card {
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
.no-theme a:hover,
|
.no-theme a:hover,
|
||||||
.no-theme a:active,
|
.no-theme a:active,
|
||||||
|
@ -596,6 +600,10 @@ span > select {
|
||||||
.light-theme .pure-menu-heading {
|
.light-theme .pure-menu-heading {
|
||||||
color: #565d64;
|
color: #565d64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-theme .error-card {
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -658,6 +666,10 @@ body.dark-theme {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark-theme .error-card {
|
||||||
|
border: 1px solid #5e5e5e;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.no-theme a:hover,
|
.no-theme a:hover,
|
||||||
.no-theme a:active,
|
.no-theme a:active,
|
||||||
|
@ -719,6 +731,10 @@ body.dark-theme {
|
||||||
.no-theme footer a {
|
.no-theme footer a {
|
||||||
color: #adadad !important;
|
color: #adadad !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-theme .error-card {
|
||||||
|
border: 1px solid #5e5e5e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -816,3 +832,57 @@ h1, h2, h3, h4, h5, p,
|
||||||
#download_widget {
|
#download_widget {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 25px;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-card > .explanation {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content 1fr;
|
||||||
|
grid-template-rows: 1fr max-content;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 10px;
|
||||||
|
row-gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-card > .explanation > i {
|
||||||
|
color: #f44;
|
||||||
|
font-size: 24px;
|
||||||
|
grid-area: 1 / 1 / 2 / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-card > .explanation > h4 {
|
||||||
|
grid-area: 1 / 2 / 2 / 3;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-card > .explanation > p {
|
||||||
|
grid-area: 2 / 2 / 3 / 3;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-card details {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-card summary {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-card pre {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-issue-template {
|
||||||
|
padding: 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.12345);
|
||||||
|
}
|
|
@ -68,6 +68,7 @@
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-menu-button-popup .vjs-menu {
|
.video-js.player-style-youtube .vjs-menu-button-popup .vjs-menu {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
|
padding-top: 2em
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {height: 5px;
|
.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {height: 5px;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
summary {
|
#filters-collapse summary {
|
||||||
/* This should hide the marker */
|
/* This should hide the marker */
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
@ -8,10 +8,10 @@ summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
summary::-webkit-details-marker,
|
#filters-collapse summary::-webkit-details-marker,
|
||||||
summary::marker { display: none; }
|
#filters-collapse summary::marker { display: none; }
|
||||||
|
|
||||||
summary:before {
|
#filters-collapse summary:before {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
content: "[ + ]";
|
content: "[ + ]";
|
||||||
margin: -2px 10px 0 10px;
|
margin: -2px 10px 0 10px;
|
||||||
|
@ -20,7 +20,7 @@ summary:before {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
details[open] > summary:before { content: "[ − ]"; }
|
#filters-collapse details[open] > summary:before { content: "[ − ]"; }
|
||||||
|
|
||||||
|
|
||||||
#filters-box {
|
#filters-box {
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
var count = document.getElementById('count');
|
var count = document.getElementById('count');
|
||||||
count.textContent--;
|
count.textContent--;
|
||||||
|
|
||||||
var url = '/token_ajax?action_revoke_token=1&redirect=false' +
|
var url = '/token_ajax?action=revoke_token&redirect=false' +
|
||||||
'&referer=' + encodeURIComponent(location.href) +
|
'&referer=' + encodeURIComponent(location.href) +
|
||||||
'&session=' + target.getAttribute('data-session');
|
'&session=' + target.getAttribute('data-session');
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
var count = document.getElementById('count');
|
var count = document.getElementById('count');
|
||||||
count.textContent--;
|
count.textContent--;
|
||||||
|
|
||||||
var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
|
var url = '/subscription_ajax?action=remove_subscriptions&redirect=false' +
|
||||||
'&referer=' + encodeURIComponent(location.href) +
|
'&referer=' + encodeURIComponent(location.href) +
|
||||||
'&c=' + target.getAttribute('data-ucid');
|
'&c=' + target.getAttribute('data-ucid');
|
||||||
|
|
||||||
|
|
93
assets/js/pagination.js
Normal file
93
assets/js/pagination.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const CURRENT_CONTINUATION = (new URL(document.location)).searchParams.get("continuation");
|
||||||
|
const CONT_CACHE_KEY = `continuation_cache_${encodeURIComponent(window.location.pathname)}`;
|
||||||
|
|
||||||
|
function get_data(){
|
||||||
|
return JSON.parse(sessionStorage.getItem(CONT_CACHE_KEY)) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_data(){
|
||||||
|
const prev_data = get_data();
|
||||||
|
prev_data.push(CURRENT_CONTINUATION);
|
||||||
|
|
||||||
|
sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function button_press(){
|
||||||
|
let prev_data = get_data();
|
||||||
|
if (!prev_data.length) return null;
|
||||||
|
|
||||||
|
// Sanity check. Nowhere should the current continuation token exist in the cache
|
||||||
|
// but it can happen when using the browser's back feature. As such we'd need to travel
|
||||||
|
// back to the point where the current continuation token first appears in order to
|
||||||
|
// account for the rewind.
|
||||||
|
const conflict_at = prev_data.indexOf(CURRENT_CONTINUATION);
|
||||||
|
if (conflict_at != -1) {
|
||||||
|
prev_data.length = conflict_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prev_ctoken = prev_data.pop();
|
||||||
|
|
||||||
|
// On the first page, the stored continuation token is null.
|
||||||
|
if (prev_ctoken === null) {
|
||||||
|
sessionStorage.removeItem(CONT_CACHE_KEY);
|
||||||
|
let url = set_continuation();
|
||||||
|
window.location.href = url;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data));
|
||||||
|
let url = set_continuation(prev_ctoken);
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Method to set the current page's continuation token
|
||||||
|
// Removes the continuation parameter when a continuation token is not given
|
||||||
|
function set_continuation(prev_ctoken = null){
|
||||||
|
let url = window.location.href.split('?')[0];
|
||||||
|
let params = window.location.href.split('?')[1];
|
||||||
|
let url_params = new URLSearchParams(params);
|
||||||
|
|
||||||
|
if (prev_ctoken) {
|
||||||
|
url_params.set("continuation", prev_ctoken);
|
||||||
|
} else {
|
||||||
|
url_params.delete('continuation');
|
||||||
|
};
|
||||||
|
|
||||||
|
if(Array.from(url_params).length > 0){
|
||||||
|
return `${url}?${url_params.toString()}`;
|
||||||
|
} else {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener('DOMContentLoaded', function(){
|
||||||
|
const pagination_data = JSON.parse(document.getElementById('pagination-data').textContent);
|
||||||
|
const next_page_containers = document.getElementsByClassName("page-next-container");
|
||||||
|
|
||||||
|
for (let container of next_page_containers){
|
||||||
|
const next_page_button = container.getElementsByClassName("pure-button")
|
||||||
|
|
||||||
|
// exists?
|
||||||
|
if (next_page_button.length > 0){
|
||||||
|
next_page_button[0].addEventListener("click", save_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add previous page buttons when not on the first page
|
||||||
|
if (CURRENT_CONTINUATION) {
|
||||||
|
const prev_page_containers = document.getElementsByClassName("page-prev-container")
|
||||||
|
|
||||||
|
for (let container of prev_page_containers) {
|
||||||
|
if (pagination_data.is_rtl) {
|
||||||
|
container.innerHTML = `<button class="pure-button pure-button-secondary">${pagination_data.prev_page} <i class="icon ion-ios-arrow-forward"></i></button>`
|
||||||
|
} else {
|
||||||
|
container.innerHTML = `<button class="pure-button pure-button-secondary"><i class="icon ion-ios-arrow-back"></i> ${pagination_data.prev_page}</button>`
|
||||||
|
}
|
||||||
|
container.getElementsByClassName("pure-button")[0].addEventListener("click", button_press);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -3,7 +3,6 @@ var player_data = JSON.parse(document.getElementById('player_data').textContent)
|
||||||
var video_data = JSON.parse(document.getElementById('video_data').textContent);
|
var video_data = JSON.parse(document.getElementById('video_data').textContent);
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
preload: 'auto',
|
|
||||||
liveui: true,
|
liveui: true,
|
||||||
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
|
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
|
||||||
controlBar: {
|
controlBar: {
|
||||||
|
@ -135,26 +134,32 @@ player.on('timeupdate', function () {
|
||||||
// YouTube links
|
// YouTube links
|
||||||
|
|
||||||
let elem_yt_watch = document.getElementById('link-yt-watch');
|
let elem_yt_watch = document.getElementById('link-yt-watch');
|
||||||
|
if (elem_yt_watch) {
|
||||||
|
let base_url_yt_watch = elem_yt_watch.getAttribute('data-base-url');
|
||||||
|
elem_yt_watch.href = addCurrentTimeToURL(base_url_yt_watch);
|
||||||
|
}
|
||||||
|
|
||||||
let elem_yt_embed = document.getElementById('link-yt-embed');
|
let elem_yt_embed = document.getElementById('link-yt-embed');
|
||||||
|
if (elem_yt_embed) {
|
||||||
let base_url_yt_watch = elem_yt_watch.getAttribute('data-base-url');
|
let base_url_yt_embed = elem_yt_embed.getAttribute('data-base-url');
|
||||||
let base_url_yt_embed = elem_yt_embed.getAttribute('data-base-url');
|
elem_yt_embed.href = addCurrentTimeToURL(base_url_yt_embed);
|
||||||
|
}
|
||||||
elem_yt_watch.href = addCurrentTimeToURL(base_url_yt_watch);
|
|
||||||
elem_yt_embed.href = addCurrentTimeToURL(base_url_yt_embed);
|
|
||||||
|
|
||||||
// Invidious links
|
// Invidious links
|
||||||
|
|
||||||
let domain = window.location.origin;
|
let domain = window.location.origin;
|
||||||
|
|
||||||
let elem_iv_embed = document.getElementById('link-iv-embed');
|
let elem_iv_embed = document.getElementById('link-iv-embed');
|
||||||
|
if (elem_iv_embed) {
|
||||||
|
let base_url_iv_embed = elem_iv_embed.getAttribute('data-base-url');
|
||||||
|
elem_iv_embed.href = addCurrentTimeToURL(base_url_iv_embed, domain);
|
||||||
|
}
|
||||||
|
|
||||||
let elem_iv_other = document.getElementById('link-iv-other');
|
let elem_iv_other = document.getElementById('link-iv-other');
|
||||||
|
if (elem_iv_other) {
|
||||||
let base_url_iv_embed = elem_iv_embed.getAttribute('data-base-url');
|
let base_url_iv_other = elem_iv_other.getAttribute('data-base-url');
|
||||||
let base_url_iv_other = elem_iv_other.getAttribute('data-base-url');
|
elem_iv_other.href = addCurrentTimeToURL(base_url_iv_other, domain);
|
||||||
|
}
|
||||||
elem_iv_embed.href = addCurrentTimeToURL(base_url_iv_embed, domain);
|
|
||||||
elem_iv_other.href = addCurrentTimeToURL(base_url_iv_other, domain);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ function add_playlist_video(target) {
|
||||||
var select = target.parentNode.children[0].children[1];
|
var select = target.parentNode.children[0].children[1];
|
||||||
var option = select.children[select.selectedIndex];
|
var option = select.children[select.selectedIndex];
|
||||||
|
|
||||||
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
|
var url = '/playlist_ajax?action=add_video&redirect=false' +
|
||||||
'&video_id=' + target.getAttribute('data-id') +
|
'&video_id=' + target.getAttribute('data-id') +
|
||||||
'&playlist_id=' + option.getAttribute('data-plid');
|
'&playlist_id=' + option.getAttribute('data-plid');
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ function add_playlist_item(target) {
|
||||||
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
tile.style.display = 'none';
|
tile.style.display = 'none';
|
||||||
|
|
||||||
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
|
var url = '/playlist_ajax?action=add_video&redirect=false' +
|
||||||
'&video_id=' + target.getAttribute('data-id') +
|
'&video_id=' + target.getAttribute('data-id') +
|
||||||
'&playlist_id=' + target.getAttribute('data-plid');
|
'&playlist_id=' + target.getAttribute('data-plid');
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ function remove_playlist_item(target) {
|
||||||
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
tile.style.display = 'none';
|
tile.style.display = 'none';
|
||||||
|
|
||||||
var url = '/playlist_ajax?action_remove_video=1&redirect=false' +
|
var url = '/playlist_ajax?action=remove_video&redirect=false' +
|
||||||
'&set_video_id=' + target.getAttribute('data-index') +
|
'&set_video_id=' + target.getAttribute('data-index') +
|
||||||
'&playlist_id=' + target.getAttribute('data-plid');
|
'&playlist_id=' + target.getAttribute('data-plid');
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ function subscribe() {
|
||||||
subscribe_button.onclick = unsubscribe;
|
subscribe_button.onclick = unsubscribe;
|
||||||
subscribe_button.innerHTML = '<b>' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
|
subscribe_button.innerHTML = '<b>' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
|
||||||
|
|
||||||
var url = '/subscription_ajax?action_create_subscription_to_channel=1&redirect=false' +
|
var url = '/subscription_ajax?action=create_subscription_to_channel&redirect=false' +
|
||||||
'&c=' + subscribe_data.ucid;
|
'&c=' + subscribe_data.ucid;
|
||||||
|
|
||||||
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'subscribe request'}, {
|
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'subscribe request'}, {
|
||||||
|
@ -32,7 +32,7 @@ function unsubscribe() {
|
||||||
subscribe_button.onclick = subscribe;
|
subscribe_button.onclick = subscribe;
|
||||||
subscribe_button.innerHTML = '<b>' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
|
subscribe_button.innerHTML = '<b>' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
|
||||||
|
|
||||||
var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
|
var url = '/subscription_ajax?action=remove_subscriptions&redirect=false' +
|
||||||
'&c=' + subscribe_data.ucid;
|
'&c=' + subscribe_data.ucid;
|
||||||
|
|
||||||
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'unsubscribe request'}, {
|
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'unsubscribe request'}, {
|
||||||
|
|
|
@ -67,6 +67,10 @@ function get_playlist(plid) {
|
||||||
'&format=html&hl=' + video_data.preferences.locale;
|
'&format=html&hl=' + video_data.preferences.locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (video_data.params.listen) {
|
||||||
|
plid_url += '&listen=1'
|
||||||
|
}
|
||||||
|
|
||||||
helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, {
|
helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, {
|
||||||
on200: function (response) {
|
on200: function (response) {
|
||||||
playlist.innerHTML = response.playlistHtml;
|
playlist.innerHTML = response.playlistHtml;
|
||||||
|
|
|
@ -6,7 +6,7 @@ function mark_watched(target) {
|
||||||
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
tile.style.display = 'none';
|
tile.style.display = 'none';
|
||||||
|
|
||||||
var url = '/watch_ajax?action_mark_watched=1&redirect=false' +
|
var url = '/watch_ajax?action=mark_watched&redirect=false' +
|
||||||
'&id=' + target.getAttribute('data-id');
|
'&id=' + target.getAttribute('data-id');
|
||||||
|
|
||||||
helpers.xhr('POST', url, {payload: payload}, {
|
helpers.xhr('POST', url, {payload: payload}, {
|
||||||
|
@ -22,7 +22,7 @@ function mark_unwatched(target) {
|
||||||
var count = document.getElementById('count');
|
var count = document.getElementById('count');
|
||||||
count.textContent--;
|
count.textContent--;
|
||||||
|
|
||||||
var url = '/watch_ajax?action_mark_unwatched=1&redirect=false' +
|
var url = '/watch_ajax?action=mark_unwatched&redirect=false' +
|
||||||
'&id=' + target.getAttribute('data-id');
|
'&id=' + target.getAttribute('data-id');
|
||||||
|
|
||||||
helpers.xhr('POST', url, {payload: payload}, {
|
helpers.xhr('POST', url, {payload: payload}, {
|
||||||
|
|
|
@ -54,6 +54,53 @@ db:
|
||||||
##
|
##
|
||||||
#signature_server:
|
#signature_server:
|
||||||
|
|
||||||
|
##
|
||||||
|
## Invidious companion is an external program
|
||||||
|
## for loading the video streams from YouTube servers.
|
||||||
|
##
|
||||||
|
## When this setting is commented out, Invidious companion is not used.
|
||||||
|
## Otherwise, Invidious will proxy the requests to Invidious companion.
|
||||||
|
##
|
||||||
|
## Note: multiple URL can be configured. In this case, invidious will
|
||||||
|
## randomly pick one every time video data needs to be retrieved. This
|
||||||
|
## URL is then kept in the video metadata cache to allow video playback
|
||||||
|
## to work. Once said cache has expired, requesting that video's data
|
||||||
|
## again will cause a new companion URL to be picked.
|
||||||
|
##
|
||||||
|
## The parameter private_url needs to be configured for the internal
|
||||||
|
## communication between the companion and Invidious.
|
||||||
|
## And public_url is the public URL from which companion is listening
|
||||||
|
## to the requests from the user(s).
|
||||||
|
##
|
||||||
|
## If you are using a reverse proxy then you will probably need to
|
||||||
|
## configure the public_url to be the same as the domain used for Invidious.
|
||||||
|
## Also apply when used from an external IP address (without a domain).
|
||||||
|
## Examples: https://MYINVIDIOUSDOMAIN or http://192.168.1.100:8282
|
||||||
|
##
|
||||||
|
## Both parameter can have identical URL when Invidious is hosted in
|
||||||
|
## an internal network or at home or locally (localhost).
|
||||||
|
##
|
||||||
|
## Accepted values: "http(s)://<IP-HOSTNAME>:<Port>"
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#invidious_companion:
|
||||||
|
# - private_url: "http://localhost:8282"
|
||||||
|
# public_url: "http://localhost:8282"
|
||||||
|
|
||||||
|
##
|
||||||
|
## API key for Invidious companion, used for securing the communication
|
||||||
|
## between Invidious and Invidious companion.
|
||||||
|
## The key needs to be exactly 16 characters long.
|
||||||
|
##
|
||||||
|
## Note: This parameter is mandatory when Invidious companion is enabled
|
||||||
|
## and should be a random string.
|
||||||
|
## Such random string can be generated on linux with the following
|
||||||
|
## command: `pwgen 16 1`
|
||||||
|
##
|
||||||
|
## Accepted values: a string (of length 16)
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#invidious_companion_key: "CHANGE_ME!!"
|
||||||
|
|
||||||
#########################################
|
#########################################
|
||||||
#
|
#
|
||||||
|
@ -130,6 +177,20 @@ https_only: false
|
||||||
##
|
##
|
||||||
#hsts: true
|
#hsts: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## Path and permissions of a UNIX socket to listen on for incoming connections.
|
||||||
|
##
|
||||||
|
## Note: Enabling socket will make invidious stop listening on the address
|
||||||
|
## specified by 'host_binding' and 'port'.
|
||||||
|
##
|
||||||
|
## Accepted values: Any path to a new file (that doesn't exist yet) and its
|
||||||
|
## permissions following the UNIX octal convention.
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#socket_binding:
|
||||||
|
# path: /tmp/invidious.sock
|
||||||
|
# permissions: 777
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# Network (outbound)
|
# Network (outbound)
|
||||||
|
@ -173,6 +234,17 @@ https_only: false
|
||||||
##
|
##
|
||||||
#force_resolve:
|
#force_resolve:
|
||||||
|
|
||||||
|
##
|
||||||
|
## Configuration for using a HTTP proxy
|
||||||
|
##
|
||||||
|
## If unset, then no HTTP proxy will be used.
|
||||||
|
##
|
||||||
|
#http_proxy:
|
||||||
|
# user:
|
||||||
|
# password:
|
||||||
|
# host:
|
||||||
|
# port:
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## Use Innertube's transcripts API instead of timedtext for closed captions
|
## Use Innertube's transcripts API instead of timedtext for closed captions
|
||||||
|
@ -222,6 +294,17 @@ https_only: false
|
||||||
##
|
##
|
||||||
#log_level: Info
|
#log_level: Info
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enables colors in logs. Useful for debugging purposes
|
||||||
|
## This is overridden if "-k" or "--colorize"
|
||||||
|
## are passed on the command line.
|
||||||
|
## Colors are also disabled if the environment variable
|
||||||
|
## NO_COLOR is present and has any value
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#colorize_logs: false
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# Features
|
# Features
|
||||||
|
@ -707,6 +790,22 @@ default_user_preferences:
|
||||||
# Video player behavior
|
# Video player behavior
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## This option controls the value of the HTML5 <video> element's
|
||||||
|
## "preload" attribute.
|
||||||
|
##
|
||||||
|
## If set to 'false', no video data will be loaded until the user
|
||||||
|
## explicitly starts the video by clicking the "Play" button.
|
||||||
|
## If set to 'true', the web browser will buffer some video data
|
||||||
|
## while the page is loading.
|
||||||
|
##
|
||||||
|
## See: https://www.w3schools.com/tags/att_video_preload.asp
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#preload: true
|
||||||
|
|
||||||
##
|
##
|
||||||
## Automatically play videos on page load.
|
## Automatically play videos on page load.
|
||||||
##
|
##
|
||||||
|
@ -759,9 +858,9 @@ default_user_preferences:
|
||||||
## Default video quality.
|
## Default video quality.
|
||||||
##
|
##
|
||||||
## Accepted values: dash, hd720, medium, small
|
## Accepted values: dash, hd720, medium, small
|
||||||
## Default: hd720
|
## Default: dash
|
||||||
##
|
##
|
||||||
#quality: hd720
|
#quality: dash
|
||||||
|
|
||||||
##
|
##
|
||||||
## Default dash video quality.
|
## Default dash video quality.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM crystallang/crystal:1.12.1-alpine AS builder
|
FROM crystallang/crystal:1.16.3-alpine AS builder
|
||||||
|
|
||||||
RUN apk add --no-cache sqlite-static yaml-static
|
RUN apk add --no-cache sqlite-static yaml-static
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
|
||||||
|
|
||||||
RUN crystal spec --warnings all \
|
RUN crystal spec --warnings all \
|
||||||
--link-flags "-lxml2 -llzma"
|
--link-flags "-lxml2 -llzma"
|
||||||
RUN if [[ "${release}" == 1 ]] ; then \
|
RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \
|
||||||
crystal build ./src/invidious.cr \
|
crystal build ./src/invidious.cr \
|
||||||
--release \
|
--release \
|
||||||
--static --warnings all \
|
--static --warnings all \
|
||||||
|
@ -32,8 +32,8 @@ RUN if [[ "${release}" == 1 ]] ; then \
|
||||||
--link-flags "-lxml2 -llzma"; \
|
--link-flags "-lxml2 -llzma"; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
FROM alpine:3.18
|
FROM alpine:3.21
|
||||||
RUN apk add --no-cache rsvg-convert ttf-opensans tini
|
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
|
||||||
WORKDIR /invidious
|
WORKDIR /invidious
|
||||||
RUN addgroup -g 1000 -S invidious && \
|
RUN addgroup -g 1000 -S invidious && \
|
||||||
adduser -u 1000 -S invidious -G invidious
|
adduser -u 1000 -S invidious -G invidious
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
FROM alpine:3.19 AS builder
|
FROM alpine:3.21 AS builder
|
||||||
RUN apk add --no-cache 'crystal=1.10.1-r0' shards sqlite-static yaml-static yaml-dev libxml2-static zlib-static openssl-libs-static openssl-dev musl-dev xz-static
|
RUN apk add --no-cache 'crystal=1.14.0-r0' shards sqlite-static yaml-static yaml-dev libxml2-static \
|
||||||
|
zlib-static openssl-libs-static openssl-dev musl-dev xz-static
|
||||||
|
|
||||||
ARG release
|
ARG release
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
|
||||||
RUN crystal spec --warnings all \
|
RUN crystal spec --warnings all \
|
||||||
--link-flags "-lxml2 -llzma"
|
--link-flags "-lxml2 -llzma"
|
||||||
|
|
||||||
RUN if [[ "${release}" == 1 ]] ; then \
|
RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \
|
||||||
crystal build ./src/invidious.cr \
|
crystal build ./src/invidious.cr \
|
||||||
--release \
|
--release \
|
||||||
--static --warnings all \
|
--static --warnings all \
|
||||||
|
@ -32,8 +33,8 @@ RUN if [[ "${release}" == 1 ]] ; then \
|
||||||
--link-flags "-lxml2 -llzma"; \
|
--link-flags "-lxml2 -llzma"; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
FROM alpine:3.18
|
FROM alpine:3.21
|
||||||
RUN apk add --no-cache rsvg-convert ttf-opensans tini
|
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
|
||||||
WORKDIR /invidious
|
WORKDIR /invidious
|
||||||
RUN addgroup -g 1000 -S invidious && \
|
RUN addgroup -g 1000 -S invidious && \
|
||||||
adduser -u 1000 -S invidious -G invidious
|
adduser -u 1000 -S invidious -G invidious
|
||||||
|
|
|
@ -154,8 +154,8 @@
|
||||||
"View YouTube comments": "عرض تعليقات اليوتيوب",
|
"View YouTube comments": "عرض تعليقات اليوتيوب",
|
||||||
"View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع ريديت",
|
"View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع ريديت",
|
||||||
"View `x` comments": {
|
"View `x` comments": {
|
||||||
"([^.,0-9]|^)1([^.,0-9]|$)": "عرض `x` تعليقات",
|
"([^.,0-9]|^)1([^.,0-9]|$)": "عرض `x` تعليق",
|
||||||
"": "عرض `x` تعليقات."
|
"": "عرض `x` تعليقات"
|
||||||
},
|
},
|
||||||
"View Reddit comments": "عرض تعليقات ريديت",
|
"View Reddit comments": "عرض تعليقات ريديت",
|
||||||
"Hide replies": "إخفاء الردود",
|
"Hide replies": "إخفاء الردود",
|
||||||
|
@ -483,7 +483,7 @@
|
||||||
"comments_view_x_replies_3": "عرض رد {{count}}",
|
"comments_view_x_replies_3": "عرض رد {{count}}",
|
||||||
"comments_view_x_replies_4": "عرض الردود {{count}}",
|
"comments_view_x_replies_4": "عرض الردود {{count}}",
|
||||||
"comments_view_x_replies_5": "عرض رد {{count}}",
|
"comments_view_x_replies_5": "عرض رد {{count}}",
|
||||||
"search_message_use_another_instance": " يمكنك أيضًا البحث عن <a href=\"`x`\"> في مثيل آخر </a>.",
|
"search_message_use_another_instance": "يمكنك أيضًا البحث عن <a href=\"`x`\"> في مثيل آخر </a>.",
|
||||||
"comments_points_count_0": "{{count}} نقطة",
|
"comments_points_count_0": "{{count}} نقطة",
|
||||||
"comments_points_count_1": "نقطة واحدة",
|
"comments_points_count_1": "نقطة واحدة",
|
||||||
"comments_points_count_2": "نقطتان",
|
"comments_points_count_2": "نقطتان",
|
||||||
|
@ -559,10 +559,15 @@
|
||||||
"toggle_theme": "تبديل الموضوع",
|
"toggle_theme": "تبديل الموضوع",
|
||||||
"Add to playlist": "أضف إلى قائمة التشغيل",
|
"Add to playlist": "أضف إلى قائمة التشغيل",
|
||||||
"Add to playlist: ": "أضف إلى قائمة التشغيل: ",
|
"Add to playlist: ": "أضف إلى قائمة التشغيل: ",
|
||||||
"Answer": "الرد",
|
"Answer": "اجابة",
|
||||||
"Search for videos": "ابحث عن مقاطع الفيديو",
|
"Search for videos": "ابحث عن مقاطع الفيديو",
|
||||||
"The Popular feed has been disabled by the administrator.": "تم تعطيل الخلاصة الشائعة من قبل المسؤول.",
|
"The Popular feed has been disabled by the administrator.": "تم تعطيل الخلاصة الشائعة من قبل المسؤول.",
|
||||||
"carousel_slide": "الشريحة {{current}} من {{total}}",
|
"carousel_slide": "الشريحة {{current}} من {{total}}",
|
||||||
"carousel_skip": "تخطي الكاروسيل",
|
"carousel_skip": "تخطي الكاروسيل",
|
||||||
"carousel_go_to": "انتقل إلى الشريحة `x`"
|
"carousel_go_to": "انتقل إلى الشريحة `x`",
|
||||||
|
"preferences_preload_label": "التحميل المسبق لبيانات الفيديو: ",
|
||||||
|
"Filipino (auto-generated)": "الفلبينية (المولدة تلقائيًا)",
|
||||||
|
"channel_tab_courses_label": "الدورات",
|
||||||
|
"channel_tab_posts_label": "المنشورات",
|
||||||
|
"First page": "الصفحة الأولى"
|
||||||
}
|
}
|
||||||
|
|
|
@ -403,7 +403,7 @@
|
||||||
"comments_view_x_replies": "Виж {{count}} отговор",
|
"comments_view_x_replies": "Виж {{count}} отговор",
|
||||||
"comments_view_x_replies_plural": "Виж {{count}} отговора",
|
"comments_view_x_replies_plural": "Виж {{count}} отговора",
|
||||||
"footer_original_source_code": "Оригинален изходен код",
|
"footer_original_source_code": "Оригинален изходен код",
|
||||||
"Import YouTube subscriptions": "Импортиране на YouTube/OPML абонаменти",
|
"Import YouTube subscriptions": "Импортиране на YouTube-CSV/OPML абонаменти",
|
||||||
"Lithuanian": "Литовски",
|
"Lithuanian": "Литовски",
|
||||||
"Nyanja": "Нянджа",
|
"Nyanja": "Нянджа",
|
||||||
"Updated `x` ago": "Актуализирано преди `x`",
|
"Updated `x` ago": "Актуализирано преди `x`",
|
||||||
|
@ -493,5 +493,8 @@
|
||||||
"Add to playlist: ": "Добави към плейлист: ",
|
"Add to playlist: ": "Добави към плейлист: ",
|
||||||
"Answer": "Отговор",
|
"Answer": "Отговор",
|
||||||
"Search for videos": "Търсене на видеа",
|
"Search for videos": "Търсене на видеа",
|
||||||
"The Popular feed has been disabled by the administrator.": "Популярната страница е деактивирана от администратора."
|
"The Popular feed has been disabled by the administrator.": "Популярната страница е деактивирана от администратора.",
|
||||||
|
"Filipino (auto-generated)": "Филипински (автоматично генериран)",
|
||||||
|
"preferences_preload_label": "Предварително заредете видео данни: ",
|
||||||
|
"First page": "Първа страница"
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,7 +204,7 @@
|
||||||
"View JavaScript license information.": "Consulta la informació de la llicència de JavaScript.",
|
"View JavaScript license information.": "Consulta la informació de la llicència de JavaScript.",
|
||||||
"Playlist privacy": "Privacitat de la llista de reproducció",
|
"Playlist privacy": "Privacitat de la llista de reproducció",
|
||||||
"search_message_no_results": "No s'han trobat resultats.",
|
"search_message_no_results": "No s'han trobat resultats.",
|
||||||
"search_message_use_another_instance": " També es pot <a href=\"`x`\">buscar en una altra instància</a>.",
|
"search_message_use_another_instance": "També es pot <a href=\"`x`\">cercar en una altra instància</a>.",
|
||||||
"Genre: ": "Gènere: ",
|
"Genre: ": "Gènere: ",
|
||||||
"Hidden field \"challenge\" is a required field": "El camp ocult \"repte\" és un camp obligatori",
|
"Hidden field \"challenge\" is a required field": "El camp ocult \"repte\" és un camp obligatori",
|
||||||
"Burmese": "Birmà",
|
"Burmese": "Birmà",
|
||||||
|
@ -489,5 +489,16 @@
|
||||||
"generic_button_delete": "Suprimeix",
|
"generic_button_delete": "Suprimeix",
|
||||||
"Import YouTube watch history (.json)": "Importa l'historial de visualitzacions de YouTube (.json)",
|
"Import YouTube watch history (.json)": "Importa l'historial de visualitzacions de YouTube (.json)",
|
||||||
"Answer": "Resposta",
|
"Answer": "Resposta",
|
||||||
"toggle_theme": "Commuta el tema"
|
"toggle_theme": "Commuta el tema",
|
||||||
|
"Add to playlist": "Afegeix a la llista de reproducció",
|
||||||
|
"Add to playlist: ": "Afegeix a la llista de reproducció: ",
|
||||||
|
"Search for videos": "Cercar vídeos",
|
||||||
|
"carousel_slide": "Diapositiva {{current}} de {{total}}",
|
||||||
|
"preferences_preload_label": "Precarregar dades del vídeo: ",
|
||||||
|
"carousel_go_to": "Anar a la diapositiva `x`",
|
||||||
|
"First page": "Primera pàgina",
|
||||||
|
"Filipino (auto-generated)": "Filipí (generat automàticament)",
|
||||||
|
"channel_tab_courses_label": "Cursos",
|
||||||
|
"channel_tab_posts_label": "Missatges",
|
||||||
|
"carousel_skip": "Saltar l'exhibició"
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@
|
||||||
"Family friendly? ": "Vhodné pro rodiny? ",
|
"Family friendly? ": "Vhodné pro rodiny? ",
|
||||||
"Engagement: ": "Zapojení: ",
|
"Engagement: ": "Zapojení: ",
|
||||||
"English": "Angličtina",
|
"English": "Angličtina",
|
||||||
"English (auto-generated)": "Angličtina (automaticky generováno)",
|
"English (auto-generated)": "Angličtina (vytvořeno automaticky)",
|
||||||
"Afrikaans": "Afrikánština",
|
"Afrikaans": "Afrikánština",
|
||||||
"Albanian": "Albánština",
|
"Albanian": "Albánština",
|
||||||
"Amharic": "Amharština",
|
"Amharic": "Amharština",
|
||||||
|
@ -294,8 +294,8 @@
|
||||||
"Chinese (China)": "Čínština (Čína)",
|
"Chinese (China)": "Čínština (Čína)",
|
||||||
"Chinese (Hong Kong)": "Čínština (Hong Kong)",
|
"Chinese (Hong Kong)": "Čínština (Hong Kong)",
|
||||||
"Chinese (Taiwan)": "Čínština (Taiwan)",
|
"Chinese (Taiwan)": "Čínština (Taiwan)",
|
||||||
"Portuguese (auto-generated)": "Portugalština (automaticky generováno)",
|
"Portuguese (auto-generated)": "Portugalština (vytvořeno automaticky)",
|
||||||
"Spanish (auto-generated)": "Španělština (automaticky generováno)",
|
"Spanish (auto-generated)": "Španělština (vytvořeno automaticky)",
|
||||||
"Spanish (Mexico)": "Španělština (Mexiko)",
|
"Spanish (Mexico)": "Španělština (Mexiko)",
|
||||||
"Spanish (Spain)": "Španělština (Španělsko)",
|
"Spanish (Spain)": "Španělština (Španělsko)",
|
||||||
"generic_count_years_0": "{{count}} rokem",
|
"generic_count_years_0": "{{count}} rokem",
|
||||||
|
@ -352,13 +352,13 @@
|
||||||
"comments_points_count_0": "{{count}} bod",
|
"comments_points_count_0": "{{count}} bod",
|
||||||
"comments_points_count_1": "{{count}} body",
|
"comments_points_count_1": "{{count}} body",
|
||||||
"comments_points_count_2": "{{count}} bodů",
|
"comments_points_count_2": "{{count}} bodů",
|
||||||
"German (auto-generated)": "Němčina (automaticky generováno)",
|
"German (auto-generated)": "Němčina (vytvořeno automaticky)",
|
||||||
"Indonesian (auto-generated)": "Indonéština (automaticky generováno)",
|
"Indonesian (auto-generated)": "Indonéština (vytvořeno automaticky)",
|
||||||
"Interlingue": "Interlingue",
|
"Interlingue": "Interlingue",
|
||||||
"Italian (auto-generated)": "Italština (automaticky generováno)",
|
"Italian (auto-generated)": "Italština (vytvořeno automaticky)",
|
||||||
"Japanese (auto-generated)": "Japonština (automaticky generováno)",
|
"Japanese (auto-generated)": "Japonština (vytvořeno automaticky)",
|
||||||
"Korean (auto-generated)": "Korejština (automaticky generováno)",
|
"Korean (auto-generated)": "Korejština (vytvořeno automaticky)",
|
||||||
"Russian (auto-generated)": "Ruština (automaticky generováno)",
|
"Russian (auto-generated)": "Ruština (vytvořeno automaticky)",
|
||||||
"generic_count_months_0": "{{count}} měsícem",
|
"generic_count_months_0": "{{count}} měsícem",
|
||||||
"generic_count_months_1": "{{count}} měsíci",
|
"generic_count_months_1": "{{count}} měsíci",
|
||||||
"generic_count_months_2": "{{count}} měsíci",
|
"generic_count_months_2": "{{count}} měsíci",
|
||||||
|
@ -371,7 +371,7 @@
|
||||||
"footer_documentation": "Dokumentace",
|
"footer_documentation": "Dokumentace",
|
||||||
"next_steps_error_message_refresh": "Obnovit stránku",
|
"next_steps_error_message_refresh": "Obnovit stránku",
|
||||||
"Chinese": "Čínština",
|
"Chinese": "Čínština",
|
||||||
"Dutch (auto-generated)": "Nizozemština (automaticky generováno)",
|
"Dutch (auto-generated)": "Nizozemština (vytvořeno automaticky)",
|
||||||
"Erroneous token": "Chybný token",
|
"Erroneous token": "Chybný token",
|
||||||
"tokens_count_0": "{{count}} token",
|
"tokens_count_0": "{{count}} token",
|
||||||
"tokens_count_1": "{{count}} tokeny",
|
"tokens_count_1": "{{count}} tokeny",
|
||||||
|
@ -380,9 +380,9 @@
|
||||||
"Token is expired, please try again": "Token vypršel, zkuste to prosím znovu",
|
"Token is expired, please try again": "Token vypršel, zkuste to prosím znovu",
|
||||||
"English (United States)": "Angličtina (Spojené státy)",
|
"English (United States)": "Angličtina (Spojené státy)",
|
||||||
"Cantonese (Hong Kong)": "Kantonština (Hong Kong)",
|
"Cantonese (Hong Kong)": "Kantonština (Hong Kong)",
|
||||||
"French (auto-generated)": "Francouzština (automaticky generováno)",
|
"French (auto-generated)": "Francouzština (vytvořeno automaticky)",
|
||||||
"Turkish (auto-generated)": "Turečtina (automaticky generováno)",
|
"Turkish (auto-generated)": "Turečtina (vytvořeno automaticky)",
|
||||||
"Vietnamese (auto-generated)": "Vietnamština (automaticky generováno)",
|
"Vietnamese (auto-generated)": "Vietnamština (vytvořeno automaticky)",
|
||||||
"Current version: ": "Aktuální verze: ",
|
"Current version: ": "Aktuální verze: ",
|
||||||
"next_steps_error_message": "Měli byste zkusit: ",
|
"next_steps_error_message": "Měli byste zkusit: ",
|
||||||
"footer_donate_page": "Přispět",
|
"footer_donate_page": "Přispět",
|
||||||
|
@ -471,7 +471,7 @@
|
||||||
"search_filters_title": "Filtry",
|
"search_filters_title": "Filtry",
|
||||||
"search_filters_duration_option_medium": "Střední (4 - 20 minut)",
|
"search_filters_duration_option_medium": "Střední (4 - 20 minut)",
|
||||||
"search_filters_duration_option_long": "Dlouhá (> 20 minut)",
|
"search_filters_duration_option_long": "Dlouhá (> 20 minut)",
|
||||||
"search_message_use_another_instance": " Můžete také <a href=\"`x`\">hledat na jiné instanci</a>.",
|
"search_message_use_another_instance": "Můžete také <a href=\"`x`\">hledat na jiné instanci</a>.",
|
||||||
"search_filters_features_label": "Vlastnosti",
|
"search_filters_features_label": "Vlastnosti",
|
||||||
"search_filters_features_option_three_sixty": "360°",
|
"search_filters_features_option_three_sixty": "360°",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"The Popular feed has been disabled by the administrator.": "Kategorie Populární byla zakázána administrátorem.",
|
"The Popular feed has been disabled by the administrator.": "Kategorie Populární byla zakázána administrátorem.",
|
||||||
"carousel_slide": "Snímek {{current}} z {{total}}",
|
"carousel_slide": "Snímek {{current}} z {{total}}",
|
||||||
"carousel_skip": "Přeskočit galerii",
|
"carousel_skip": "Přeskočit galerii",
|
||||||
"carousel_go_to": "Přejít na snímek `x`"
|
"carousel_go_to": "Přejít na snímek `x`",
|
||||||
|
"preferences_preload_label": "Předem načíst data videa: ",
|
||||||
|
"Filipino (auto-generated)": "Filipínština (vytvořeno automaticky)",
|
||||||
|
"First page": "První stránka",
|
||||||
|
"channel_tab_courses_label": "Kurzy",
|
||||||
|
"channel_tab_posts_label": "Příspěvky"
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@
|
||||||
"An alternative front-end to YouTube": "Pen blaen amgen i YouTube",
|
"An alternative front-end to YouTube": "Pen blaen amgen i YouTube",
|
||||||
"source": "ffynhonnell",
|
"source": "ffynhonnell",
|
||||||
"Log in": "Mewngofnodi",
|
"Log in": "Mewngofnodi",
|
||||||
"Log in/register": "Mewngofnodi/Cofrestru",
|
"Log in/register": "Mewngofnodi/cofrestru",
|
||||||
"User ID": "Enw defnyddiwr",
|
"User ID": "Enw defnyddiwr",
|
||||||
"preferences_quality_option_dash": "DASH (ansawdd addasol)",
|
"preferences_quality_option_dash": "DASH (ansawdd addasol)",
|
||||||
"Sign In": "Mewngofnodi",
|
"Sign In": "Mewngofnodi",
|
||||||
|
@ -381,5 +381,32 @@
|
||||||
"channel_tab_channels_label": "Sianeli",
|
"channel_tab_channels_label": "Sianeli",
|
||||||
"channel_tab_community_label": "Cymuned",
|
"channel_tab_community_label": "Cymuned",
|
||||||
"channel_tab_shorts_label": "Fideos byrion",
|
"channel_tab_shorts_label": "Fideos byrion",
|
||||||
"channel_tab_videos_label": "Fideos"
|
"channel_tab_videos_label": "Fideos",
|
||||||
|
"generic_playlists_count_0": "{{count}} rhestr chwarae",
|
||||||
|
"generic_playlists_count_1": "{{count}} rhestr chwarae",
|
||||||
|
"generic_playlists_count_2": "{{count}} rhestri chwarae",
|
||||||
|
"generic_playlists_count_3": "{{count}} rhestri chwarae",
|
||||||
|
"generic_playlists_count_4": "{{count}} rhestri chwarae",
|
||||||
|
"generic_playlists_count_5": "{{count}} rhestri chwarae",
|
||||||
|
"New passwords must match": "Rhaid i'r cyfrineiriau newydd cyfateb â'i gilydd",
|
||||||
|
"last": "diwethaf",
|
||||||
|
"First page": "Tudalen gyntaf",
|
||||||
|
"preferences_preload_label": "Cynlwytho data fideo: ",
|
||||||
|
"preferences_extend_desc_label": "Ymestyn disgrifiad fideo'n awtomatig: ",
|
||||||
|
"preferences_vr_mode_label": "Fideos rhyngweithiol 360 gradd (angen WebGL): ",
|
||||||
|
"preferences_video_loop_label": "Doleniwch bob amser: ",
|
||||||
|
"Top enabled: ": "Tudalen fideos brig wedi'i alluogi: ",
|
||||||
|
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Allforio tanysgrifiadau ar fformat OPML (i NewPipe a FreeTube)",
|
||||||
|
"Export subscriptions as OPML": "Allforio tanysgrifiadau ar fformat OPML",
|
||||||
|
"preferences_annotations_subscribed_label": "Ddangos nodiadau sianeli tanysgrifiwyd fel rhagosodiad? ",
|
||||||
|
"Redirect homepage to feed: ": "Ailgyfeirio tudalen gartref i'r borthiant: ",
|
||||||
|
"preferences_feed_menu_label": "Dewislen porthiant: ",
|
||||||
|
"Login enabled: ": "Mewngofnodi wedi'i alluogi: ",
|
||||||
|
"tokens_count_0": "",
|
||||||
|
"tokens_count_1": "tocyn",
|
||||||
|
"tokens_count_2": "",
|
||||||
|
"tokens_count_3": "",
|
||||||
|
"tokens_count_4": "tocynnau",
|
||||||
|
"tokens_count_5": "",
|
||||||
|
"Source available here.": "Tarddle ar gael yma."
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"last": "neueste",
|
"last": "neueste",
|
||||||
"Next page": "Nächste Seite",
|
"Next page": "Nächste Seite",
|
||||||
"Previous page": "Vorherige Seite",
|
"Previous page": "Vorherige Seite",
|
||||||
|
"First page": "Erste Seite",
|
||||||
"Clear watch history?": "Verlauf löschen?",
|
"Clear watch history?": "Verlauf löschen?",
|
||||||
"New password": "Neues Passwort",
|
"New password": "Neues Passwort",
|
||||||
"New passwords must match": "Neue Passwörter müssen übereinstimmen",
|
"New passwords must match": "Neue Passwörter müssen übereinstimmen",
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
"Preferences": "Einstellungen",
|
"Preferences": "Einstellungen",
|
||||||
"preferences_category_player": "Wiedergabeeinstellungen",
|
"preferences_category_player": "Wiedergabeeinstellungen",
|
||||||
"preferences_video_loop_label": "Immer wiederholen: ",
|
"preferences_video_loop_label": "Immer wiederholen: ",
|
||||||
|
"preferences_preload_label": "Videodaten vorladen: ",
|
||||||
"preferences_autoplay_label": "Automatisch abspielen: ",
|
"preferences_autoplay_label": "Automatisch abspielen: ",
|
||||||
"preferences_continue_label": "Immer automatisch nächstes Video abspielen: ",
|
"preferences_continue_label": "Immer automatisch nächstes Video abspielen: ",
|
||||||
"preferences_continue_autoplay_label": "Nächstes Video automatisch abspielen: ",
|
"preferences_continue_autoplay_label": "Nächstes Video automatisch abspielen: ",
|
||||||
|
@ -322,7 +324,7 @@
|
||||||
"channel_tab_community_label": "Gemeinschaft",
|
"channel_tab_community_label": "Gemeinschaft",
|
||||||
"search_filters_sort_option_relevance": "Relevanz",
|
"search_filters_sort_option_relevance": "Relevanz",
|
||||||
"search_filters_sort_option_rating": "Bewertung",
|
"search_filters_sort_option_rating": "Bewertung",
|
||||||
"search_filters_sort_option_date": "Datum",
|
"search_filters_sort_option_date": "Hochladedatum",
|
||||||
"search_filters_sort_option_views": "Aufrufe",
|
"search_filters_sort_option_views": "Aufrufe",
|
||||||
"search_filters_type_label": "Inhaltstyp",
|
"search_filters_type_label": "Inhaltstyp",
|
||||||
"search_filters_duration_label": "Dauer",
|
"search_filters_duration_label": "Dauer",
|
||||||
|
@ -454,7 +456,7 @@
|
||||||
"Portuguese (auto-generated)": "Portugiesisch (automatisch generiert)",
|
"Portuguese (auto-generated)": "Portugiesisch (automatisch generiert)",
|
||||||
"search_filters_title": "Filtern",
|
"search_filters_title": "Filtern",
|
||||||
"search_message_change_filters_or_query": "Versuchen Sie, Ihre Suchanfrage zu erweitern und/oder die Filter zu ändern.",
|
"search_message_change_filters_or_query": "Versuchen Sie, Ihre Suchanfrage zu erweitern und/oder die Filter zu ändern.",
|
||||||
"search_message_use_another_instance": " Sie können auch <a href=\"`x`\">auf einer anderen Instanz suchen</a>.",
|
"search_message_use_another_instance": "Sie können auch <a href=\"`x`\">auf einer anderen Instanz suchen</a>.",
|
||||||
"Popular enabled: ": "„Beliebt“-Seite aktiviert: ",
|
"Popular enabled: ": "„Beliebt“-Seite aktiviert: ",
|
||||||
"search_message_no_results": "Keine Ergebnisse gefunden.",
|
"search_message_no_results": "Keine Ergebnisse gefunden.",
|
||||||
"search_filters_duration_option_medium": "Mittel (4 - 20 Minuten)",
|
"search_filters_duration_option_medium": "Mittel (4 - 20 Minuten)",
|
||||||
|
@ -489,9 +491,15 @@
|
||||||
"generic_channels_count_plural": "{{count}} Kanäle",
|
"generic_channels_count_plural": "{{count}} Kanäle",
|
||||||
"Import YouTube watch history (.json)": "YouTube Wiedergabeverlauf importieren (.json)",
|
"Import YouTube watch history (.json)": "YouTube Wiedergabeverlauf importieren (.json)",
|
||||||
"Answer": "Antwort",
|
"Answer": "Antwort",
|
||||||
"The Popular feed has been disabled by the administrator.": "Der Angesagt-Feed wurde vom Administrator deaktiviert.",
|
"The Popular feed has been disabled by the administrator.": "Der Feed für beliebte Inhalte wurde vom Administrator deaktiviert.",
|
||||||
"Add to playlist": "Einer Wiedergabeliste hinzufügen",
|
"Add to playlist": "Einer Wiedergabeliste hinzufügen",
|
||||||
"Search for videos": "Nach Videos suchen",
|
"Search for videos": "Nach Videos suchen",
|
||||||
"toggle_theme": "Thema wechseln",
|
"toggle_theme": "Thema wechseln",
|
||||||
"Add to playlist: ": "Einer Wiedergabeliste hinzufügen: "
|
"Add to playlist: ": "Einer Wiedergabeliste hinzufügen: ",
|
||||||
|
"carousel_go_to": "Zu Element `x` springen",
|
||||||
|
"carousel_slide": "Seite {{current}} von {{total}}",
|
||||||
|
"carousel_skip": "Galerie überspringen",
|
||||||
|
"Filipino (auto-generated)": "Philippinisch (automatisch generiert)",
|
||||||
|
"channel_tab_courses_label": "Kurse",
|
||||||
|
"channel_tab_posts_label": "Beiträge"
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"Import and Export Data": "Εισαγωγή και Εξαγωγή Δεδομένων",
|
"Import and Export Data": "Εισαγωγή και Εξαγωγή Δεδομένων",
|
||||||
"Import": "Εισαγωγή",
|
"Import": "Εισαγωγή",
|
||||||
"Import Invidious data": "Εsαγωγή δεδομένων Invidious JSON",
|
"Import Invidious data": "Εsαγωγή δεδομένων Invidious JSON",
|
||||||
"Import YouTube subscriptions": "Εισαγωγή συνδρομών YouTube/OPML",
|
"Import YouTube subscriptions": "Εισαγωγή συνδρομών YouTube απο CVS/OPML",
|
||||||
"Import FreeTube subscriptions (.db)": "Εισαγωγή συνδρομών FreeTube (.db)",
|
"Import FreeTube subscriptions (.db)": "Εισαγωγή συνδρομών FreeTube (.db)",
|
||||||
"Import NewPipe subscriptions (.json)": "Εισαγωγή συνδρομών NewPipe (.json)",
|
"Import NewPipe subscriptions (.json)": "Εισαγωγή συνδρομών NewPipe (.json)",
|
||||||
"Import NewPipe data (.zip)": "Εισαγωγή δεδομένων NewPipe (.zip)",
|
"Import NewPipe data (.zip)": "Εισαγωγή δεδομένων NewPipe (.zip)",
|
||||||
|
@ -455,7 +455,7 @@
|
||||||
"channel_tab_streams_label": "Ζωντανή μετάδοση",
|
"channel_tab_streams_label": "Ζωντανή μετάδοση",
|
||||||
"playlist_button_add_items": "Προσθήκη βίντεο",
|
"playlist_button_add_items": "Προσθήκη βίντεο",
|
||||||
"Artist: ": "Καλλιτέχνης: ",
|
"Artist: ": "Καλλιτέχνης: ",
|
||||||
"search_message_use_another_instance": " Μπορείτε επίσης <a href=\"`x`\">να αναζητήσετε σε άλλο instance</a>.",
|
"search_message_use_another_instance": "Μπορείτε επίσης <a href=\"`x`\">να αναζητήσετε σε άλλο instance</a>.",
|
||||||
"generic_button_save": "Αποθήκευση",
|
"generic_button_save": "Αποθήκευση",
|
||||||
"generic_button_cancel": "Ακύρωση",
|
"generic_button_cancel": "Ακύρωση",
|
||||||
"subscriptions_unseen_notifs_count": "{{count}} μη αναγνωσμένη ειδοποίηση",
|
"subscriptions_unseen_notifs_count": "{{count}} μη αναγνωσμένη ειδοποίηση",
|
||||||
|
@ -489,5 +489,17 @@
|
||||||
"search_filters_date_label": "Ημερομηνία αναφόρτωσης",
|
"search_filters_date_label": "Ημερομηνία αναφόρτωσης",
|
||||||
"Search for videos": "Αναζήτηση βίντεο",
|
"Search for videos": "Αναζήτηση βίντεο",
|
||||||
"The Popular feed has been disabled by the administrator.": "Η δημοφιλής ροή έχει απενεργοποιηθεί από τον διαχειριστή.",
|
"The Popular feed has been disabled by the administrator.": "Η δημοφιλής ροή έχει απενεργοποιηθεί από τον διαχειριστή.",
|
||||||
"Answer": "Απάντηση"
|
"Answer": "Απάντηση",
|
||||||
|
"Add to playlist": "Προσθήκη στην λίστα αναπαραγωγής",
|
||||||
|
"Add to playlist: ": "Προσθήκη στην λίστα αναπαραγωγής : ",
|
||||||
|
"carousel_slide": "Εικόνα {{current}}απο {{total}}",
|
||||||
|
"carousel_go_to": "Πήγαινε στην εικόνα`x`",
|
||||||
|
"toggle_theme": "Αλλαγή θέματος",
|
||||||
|
"Import YouTube watch history (.json)": "Εισαγωγή ιστορικού προβολής YouTube (.json)",
|
||||||
|
"Filipino (auto-generated)": "Φιλιππινέζικα (αυτόματη παραγωγή)",
|
||||||
|
"preferences_preload_label": "Προφόρτιση δεδομένων βίντεο: ",
|
||||||
|
"carousel_skip": "Αποφυγή εμφάνισης εικόνων",
|
||||||
|
"First page": "Πρώτη σελίδα",
|
||||||
|
"channel_tab_courses_label": "Μαθήματα",
|
||||||
|
"channel_tab_posts_label": "Δημοσιεύσεις"
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"last": "last",
|
"last": "last",
|
||||||
"Next page": "Next page",
|
"Next page": "Next page",
|
||||||
"Previous page": "Previous page",
|
"Previous page": "Previous page",
|
||||||
|
"First page": "First page",
|
||||||
"Clear watch history?": "Clear watch history?",
|
"Clear watch history?": "Clear watch history?",
|
||||||
"New password": "New password",
|
"New password": "New password",
|
||||||
"New passwords must match": "New passwords must match",
|
"New passwords must match": "New passwords must match",
|
||||||
|
@ -63,14 +64,13 @@
|
||||||
"User ID": "User ID",
|
"User ID": "User ID",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Time (h:mm:ss):": "Time (h:mm:ss):",
|
"Time (h:mm:ss):": "Time (h:mm:ss):",
|
||||||
"Text CAPTCHA": "Text CAPTCHA",
|
|
||||||
"Image CAPTCHA": "Image CAPTCHA",
|
|
||||||
"Sign In": "Sign In",
|
"Sign In": "Sign In",
|
||||||
"Register": "Register",
|
"Register": "Register",
|
||||||
"E-mail": "E-mail",
|
"E-mail": "E-mail",
|
||||||
"Preferences": "Preferences",
|
"Preferences": "Preferences",
|
||||||
"preferences_category_player": "Player preferences",
|
"preferences_category_player": "Player preferences",
|
||||||
"preferences_video_loop_label": "Always loop: ",
|
"preferences_video_loop_label": "Always loop: ",
|
||||||
|
"preferences_preload_label": "Preload video data: ",
|
||||||
"preferences_autoplay_label": "Autoplay: ",
|
"preferences_autoplay_label": "Autoplay: ",
|
||||||
"preferences_continue_label": "Play next by default: ",
|
"preferences_continue_label": "Play next by default: ",
|
||||||
"preferences_continue_autoplay_label": "Autoplay next video: ",
|
"preferences_continue_autoplay_label": "Autoplay next video: ",
|
||||||
|
@ -190,7 +190,7 @@
|
||||||
"Switch Invidious Instance": "Switch Invidious Instance",
|
"Switch Invidious Instance": "Switch Invidious Instance",
|
||||||
"search_message_no_results": "No results found.",
|
"search_message_no_results": "No results found.",
|
||||||
"search_message_change_filters_or_query": "Try widening your search query and/or changing the filters.",
|
"search_message_change_filters_or_query": "Try widening your search query and/or changing the filters.",
|
||||||
"search_message_use_another_instance": " You can also <a href=\"`x`\">search on another instance</a>.",
|
"search_message_use_another_instance": "You can also <a href=\"`x`\">search on another instance</a>.",
|
||||||
"Hide annotations": "Hide annotations",
|
"Hide annotations": "Hide annotations",
|
||||||
"Show annotations": "Show annotations",
|
"Show annotations": "Show annotations",
|
||||||
"Genre: ": "Genre: ",
|
"Genre: ": "Genre: ",
|
||||||
|
@ -285,6 +285,7 @@
|
||||||
"Esperanto": "Esperanto",
|
"Esperanto": "Esperanto",
|
||||||
"Estonian": "Estonian",
|
"Estonian": "Estonian",
|
||||||
"Filipino": "Filipino",
|
"Filipino": "Filipino",
|
||||||
|
"Filipino (auto-generated)": "Filipino (auto-generated)",
|
||||||
"Finnish": "Finnish",
|
"Finnish": "Finnish",
|
||||||
"French": "French",
|
"French": "French",
|
||||||
"French (auto-generated)": "French (auto-generated)",
|
"French (auto-generated)": "French (auto-generated)",
|
||||||
|
@ -422,7 +423,7 @@
|
||||||
"search_filters_title": "Filters",
|
"search_filters_title": "Filters",
|
||||||
"search_filters_date_label": "Upload date",
|
"search_filters_date_label": "Upload date",
|
||||||
"search_filters_date_option_none": "Any date",
|
"search_filters_date_option_none": "Any date",
|
||||||
"search_filters_date_option_hour": "Last Hour",
|
"search_filters_date_option_hour": "Last hour",
|
||||||
"search_filters_date_option_today": "Today",
|
"search_filters_date_option_today": "Today",
|
||||||
"search_filters_date_option_week": "This week",
|
"search_filters_date_option_week": "This week",
|
||||||
"search_filters_date_option_month": "This month",
|
"search_filters_date_option_month": "This month",
|
||||||
|
@ -454,7 +455,7 @@
|
||||||
"search_filters_sort_label": "Sort By",
|
"search_filters_sort_label": "Sort By",
|
||||||
"search_filters_sort_option_relevance": "Relevance",
|
"search_filters_sort_option_relevance": "Relevance",
|
||||||
"search_filters_sort_option_rating": "Rating",
|
"search_filters_sort_option_rating": "Rating",
|
||||||
"search_filters_sort_option_date": "Upload Date",
|
"search_filters_sort_option_date": "Upload date",
|
||||||
"search_filters_sort_option_views": "View count",
|
"search_filters_sort_option_views": "View count",
|
||||||
"search_filters_apply_button": "Apply selected filters",
|
"search_filters_apply_button": "Apply selected filters",
|
||||||
"Current version: ": "Current version: ",
|
"Current version: ": "Current version: ",
|
||||||
|
@ -490,11 +491,16 @@
|
||||||
"channel_tab_streams_label": "Livestreams",
|
"channel_tab_streams_label": "Livestreams",
|
||||||
"channel_tab_podcasts_label": "Podcasts",
|
"channel_tab_podcasts_label": "Podcasts",
|
||||||
"channel_tab_releases_label": "Releases",
|
"channel_tab_releases_label": "Releases",
|
||||||
|
"channel_tab_courses_label": "Courses",
|
||||||
"channel_tab_playlists_label": "Playlists",
|
"channel_tab_playlists_label": "Playlists",
|
||||||
"channel_tab_community_label": "Community",
|
"channel_tab_community_label": "Community",
|
||||||
|
"channel_tab_posts_label": "Posts",
|
||||||
"channel_tab_channels_label": "Channels",
|
"channel_tab_channels_label": "Channels",
|
||||||
"toggle_theme": "Toggle Theme",
|
"toggle_theme": "Toggle Theme",
|
||||||
"carousel_slide": "Slide {{current}} of {{total}}",
|
"carousel_slide": "Slide {{current}} of {{total}}",
|
||||||
"carousel_skip": "Skip the Carousel",
|
"carousel_skip": "Skip the Carousel",
|
||||||
"carousel_go_to": "Go to slide `x`"
|
"carousel_go_to": "Go to slide `x`",
|
||||||
|
"timeline_parse_error_placeholder_heading": "Unable to parse item",
|
||||||
|
"timeline_parse_error_placeholder_message": "Invidious encountered an error while trying to parse this item. For more information see below:",
|
||||||
|
"timeline_parse_error_show_technical_details": "Show technical details"
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,10 +187,10 @@
|
||||||
"Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio",
|
"Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio",
|
||||||
"Erroneous challenge": "Desafío no válido",
|
"Erroneous challenge": "Desafío no válido",
|
||||||
"Erroneous token": "Símbolo no válido",
|
"Erroneous token": "Símbolo no válido",
|
||||||
"No such user": "Usuario no existe",
|
"No such user": "El usuario no existe",
|
||||||
"Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo",
|
"Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo",
|
||||||
"English": "Inglés",
|
"English": "Inglés",
|
||||||
"English (auto-generated)": "Inglés (generado automáticamente)",
|
"English (auto-generated)": "Inglés (generados automáticamente)",
|
||||||
"Afrikaans": "Afrikáans",
|
"Afrikaans": "Afrikáans",
|
||||||
"Albanian": "Albanés",
|
"Albanian": "Albanés",
|
||||||
"Amharic": "Amárico",
|
"Amharic": "Amárico",
|
||||||
|
@ -276,7 +276,7 @@
|
||||||
"Somali": "Somalí",
|
"Somali": "Somalí",
|
||||||
"Southern Sotho": "Sesoto",
|
"Southern Sotho": "Sesoto",
|
||||||
"Spanish": "Español",
|
"Spanish": "Español",
|
||||||
"Spanish (Latin America)": "Español (Hispanoamérica)",
|
"Spanish (Latin America)": "Español (Latinoamérica)",
|
||||||
"Sundanese": "Sondanés",
|
"Sundanese": "Sondanés",
|
||||||
"Swahili": "Suajili",
|
"Swahili": "Suajili",
|
||||||
"Swedish": "Sueco",
|
"Swedish": "Sueco",
|
||||||
|
@ -412,8 +412,8 @@
|
||||||
"generic_count_weeks_1": "{{count}} semanas",
|
"generic_count_weeks_1": "{{count}} semanas",
|
||||||
"generic_count_weeks_2": "{{count}} semanas",
|
"generic_count_weeks_2": "{{count}} semanas",
|
||||||
"generic_playlists_count_0": "{{count}} lista de reproducción",
|
"generic_playlists_count_0": "{{count}} lista de reproducción",
|
||||||
"generic_playlists_count_1": "{{count}} listas de reproducciones",
|
"generic_playlists_count_1": "{{count}} listas de reproducción",
|
||||||
"generic_playlists_count_2": "{{count}} listas de reproducciones",
|
"generic_playlists_count_2": "{{count}} listas de reproducción",
|
||||||
"generic_videos_count_0": "{{count}} video",
|
"generic_videos_count_0": "{{count}} video",
|
||||||
"generic_videos_count_1": "{{count}} videos",
|
"generic_videos_count_1": "{{count}} videos",
|
||||||
"generic_videos_count_2": "{{count}} videos",
|
"generic_videos_count_2": "{{count}} videos",
|
||||||
|
@ -463,7 +463,7 @@
|
||||||
"Chinese (Hong Kong)": "Chino (Hong Kong)",
|
"Chinese (Hong Kong)": "Chino (Hong Kong)",
|
||||||
"Chinese (China)": "Chino (China)",
|
"Chinese (China)": "Chino (China)",
|
||||||
"Korean (auto-generated)": "Coreano (generados automáticamente)",
|
"Korean (auto-generated)": "Coreano (generados automáticamente)",
|
||||||
"Spanish (Mexico)": "Español (Méjico)",
|
"Spanish (Mexico)": "Español (México)",
|
||||||
"Spanish (auto-generated)": "Español (generados automáticamente)",
|
"Spanish (auto-generated)": "Español (generados automáticamente)",
|
||||||
"preferences_watch_history_label": "Habilitar historial de reproducciones: ",
|
"preferences_watch_history_label": "Habilitar historial de reproducciones: ",
|
||||||
"search_message_no_results": "No se han encontrado resultados.",
|
"search_message_no_results": "No se han encontrado resultados.",
|
||||||
|
@ -478,7 +478,7 @@
|
||||||
"tokens_count_0": "{{count}} token",
|
"tokens_count_0": "{{count}} token",
|
||||||
"tokens_count_1": "{{count}} tokens",
|
"tokens_count_1": "{{count}} tokens",
|
||||||
"tokens_count_2": "{{count}} tokens",
|
"tokens_count_2": "{{count}} tokens",
|
||||||
"search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.",
|
"search_message_use_another_instance": "También puedes <a href=\"`x`\">buscar en otra instancia</a>.",
|
||||||
"Popular enabled: ": "¿Habilitar la sección popular? ",
|
"Popular enabled: ": "¿Habilitar la sección popular? ",
|
||||||
"error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. <a href=\"`x`\">Haz clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
|
"error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. <a href=\"`x`\">Haz clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
|
||||||
"channel_tab_streams_label": "Directos",
|
"channel_tab_streams_label": "Directos",
|
||||||
|
@ -500,7 +500,7 @@
|
||||||
"generic_button_cancel": "Cancelar",
|
"generic_button_cancel": "Cancelar",
|
||||||
"generic_button_rss": "RSS",
|
"generic_button_rss": "RSS",
|
||||||
"channel_tab_podcasts_label": "Podcasts",
|
"channel_tab_podcasts_label": "Podcasts",
|
||||||
"channel_tab_releases_label": "Publicaciones",
|
"channel_tab_releases_label": "Lanzamientos",
|
||||||
"generic_channels_count_0": "{{count}} canal",
|
"generic_channels_count_0": "{{count}} canal",
|
||||||
"generic_channels_count_1": "{{count}} canales",
|
"generic_channels_count_1": "{{count}} canales",
|
||||||
"generic_channels_count_2": "{{count}} canales",
|
"generic_channels_count_2": "{{count}} canales",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"The Popular feed has been disabled by the administrator.": "El feed Popular ha sido desactivado por el administrador.",
|
"The Popular feed has been disabled by the administrator.": "El feed Popular ha sido desactivado por el administrador.",
|
||||||
"carousel_slide": "Diapositiva {{current}} de {{total}}",
|
"carousel_slide": "Diapositiva {{current}} de {{total}}",
|
||||||
"carousel_skip": "Saltar el carrusel",
|
"carousel_skip": "Saltar el carrusel",
|
||||||
"carousel_go_to": "Ir a la diapositiva `x`"
|
"carousel_go_to": "Ir a la diapositiva `x`",
|
||||||
|
"preferences_preload_label": "Precargar datos del vídeo: ",
|
||||||
|
"Filipino (auto-generated)": "Filipino (generados automáticamente)",
|
||||||
|
"channel_tab_posts_label": "Publicaciones",
|
||||||
|
"First page": "Primera página",
|
||||||
|
"channel_tab_courses_label": "Cursos"
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,7 +360,7 @@
|
||||||
"search_filters_duration_label": "مدت",
|
"search_filters_duration_label": "مدت",
|
||||||
"search_filters_features_label": "ویژگیها",
|
"search_filters_features_label": "ویژگیها",
|
||||||
"search_filters_sort_label": "به ترتیب",
|
"search_filters_sort_label": "به ترتیب",
|
||||||
"search_filters_date_option_hour": "یک ساعت گذشته",
|
"search_filters_date_option_hour": "ساعت گذشته",
|
||||||
"search_filters_date_option_today": "امروز",
|
"search_filters_date_option_today": "امروز",
|
||||||
"search_filters_date_option_week": "این هفته",
|
"search_filters_date_option_week": "این هفته",
|
||||||
"search_filters_date_option_month": "این ماه",
|
"search_filters_date_option_month": "این ماه",
|
||||||
|
@ -461,7 +461,7 @@
|
||||||
"Song: ": "آهنگ: ",
|
"Song: ": "آهنگ: ",
|
||||||
"Channel Sponsor": "اسپانسر کانال",
|
"Channel Sponsor": "اسپانسر کانال",
|
||||||
"Standard YouTube license": "پروانه استاندارد YouTube",
|
"Standard YouTube license": "پروانه استاندارد YouTube",
|
||||||
"search_message_use_another_instance": " شما همچنین میتوانید <a href=\"`x`\">در نمونه دیگر هم جستجو کنید</a>.",
|
"search_message_use_another_instance": "همچنین میتوانید <a href=\"`x`\">در نمونهای دیگر هم جستوجو کنید</a>.",
|
||||||
"Download is disabled": "دریافت غیرفعال است",
|
"Download is disabled": "دریافت غیرفعال است",
|
||||||
"crash_page_before_reporting": "پیش از گزارش ایراد، مطمئنید شوید که:",
|
"crash_page_before_reporting": "پیش از گزارش ایراد، مطمئنید شوید که:",
|
||||||
"playlist_button_add_items": "افزودن ویدیو",
|
"playlist_button_add_items": "افزودن ویدیو",
|
||||||
|
@ -496,5 +496,6 @@
|
||||||
"crash_page_search_issue": "دنبال <a href=\"`x`\"> گشتیم بین مشکلات در گیت هاب </a>",
|
"crash_page_search_issue": "دنبال <a href=\"`x`\"> گشتیم بین مشکلات در گیت هاب </a>",
|
||||||
"crash_page_report_issue": "اگر هیچ یک از روش های بالا کمکی نکردند لطفا <a href=\"`x`\"> (ترجیحا به انگلیسی) یک سوال جدید در گیت هاب بپرسید و </a> طوری که سوالتون شامل متن زیر باشه:",
|
"crash_page_report_issue": "اگر هیچ یک از روش های بالا کمکی نکردند لطفا <a href=\"`x`\"> (ترجیحا به انگلیسی) یک سوال جدید در گیت هاب بپرسید و </a> طوری که سوالتون شامل متن زیر باشه:",
|
||||||
"channel_tab_releases_label": "آثار",
|
"channel_tab_releases_label": "آثار",
|
||||||
"toggle_theme": "تغییر وضعیت تم"
|
"toggle_theme": "تغییر وضعیت تم",
|
||||||
|
"preferences_preload_label": "پیش بار کردن دادههای ویدیو: "
|
||||||
}
|
}
|
||||||
|
|
|
@ -460,7 +460,7 @@
|
||||||
"search_filters_apply_button": "Ota valitut suodattimet käyttöön",
|
"search_filters_apply_button": "Ota valitut suodattimet käyttöön",
|
||||||
"search_filters_date_label": "Latausaika",
|
"search_filters_date_label": "Latausaika",
|
||||||
"search_filters_duration_option_medium": "Keskipituinen (4 - 20 minuuttia)",
|
"search_filters_duration_option_medium": "Keskipituinen (4 - 20 minuuttia)",
|
||||||
"search_message_use_another_instance": " Voit myös <a href=\"`x`\">hakea toisella instanssilla</a>.",
|
"search_message_use_another_instance": "Voit myös <a href=\"`x`\">hakea toisella instanssilla</a>.",
|
||||||
"search_filters_date_option_none": "Milloin tahansa",
|
"search_filters_date_option_none": "Milloin tahansa",
|
||||||
"search_filters_type_option_all": "Mikä tahansa tyyppi",
|
"search_filters_type_option_all": "Mikä tahansa tyyppi",
|
||||||
"Popular enabled: ": "Suosittu käytössä: ",
|
"Popular enabled: ": "Suosittu käytössä: ",
|
||||||
|
@ -496,5 +496,6 @@
|
||||||
"generic_channels_count_plural": "{{count}} kanavaa",
|
"generic_channels_count_plural": "{{count}} kanavaa",
|
||||||
"The Popular feed has been disabled by the administrator.": "Järjestelmänvalvoja on poistanut Suositut-syötteen.",
|
"The Popular feed has been disabled by the administrator.": "Järjestelmänvalvoja on poistanut Suositut-syötteen.",
|
||||||
"Import YouTube watch history (.json)": "Tuo Youtube-katseluhistoria (.json)",
|
"Import YouTube watch history (.json)": "Tuo Youtube-katseluhistoria (.json)",
|
||||||
"toggle_theme": "Vaihda teemaa"
|
"toggle_theme": "Vaihda teemaa",
|
||||||
|
"preferences_preload_label": "Esilataa video data. "
|
||||||
}
|
}
|
||||||
|
|
|
@ -484,7 +484,7 @@
|
||||||
"search_filters_duration_option_medium": "Moyenne (de 4 à 20 minutes)",
|
"search_filters_duration_option_medium": "Moyenne (de 4 à 20 minutes)",
|
||||||
"search_filters_apply_button": "Appliquer les filtres",
|
"search_filters_apply_button": "Appliquer les filtres",
|
||||||
"search_message_no_results": "Aucun résultat.",
|
"search_message_no_results": "Aucun résultat.",
|
||||||
"search_message_use_another_instance": " Vous pouvez également <a href=\"`x`\">effectuer votre recherche sur une autre instance</a>.",
|
"search_message_use_another_instance": "Vous pouvez également <a href=\"`x`\">effectuer votre recherche sur une autre instance</a>.",
|
||||||
"search_filters_type_option_all": "Tous les types",
|
"search_filters_type_option_all": "Tous les types",
|
||||||
"search_filters_date_label": "Date d'ajout",
|
"search_filters_date_label": "Date d'ajout",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
|
@ -505,7 +505,7 @@
|
||||||
"channel_tab_releases_label": "Parutions",
|
"channel_tab_releases_label": "Parutions",
|
||||||
"channel_tab_podcasts_label": "Émissions audio",
|
"channel_tab_podcasts_label": "Émissions audio",
|
||||||
"Import YouTube watch history (.json)": "Importer l'historique de visionnement YouTube (.json)",
|
"Import YouTube watch history (.json)": "Importer l'historique de visionnement YouTube (.json)",
|
||||||
"Add to playlist: ": "Ajouter à la playlist : ",
|
"Add to playlist: ": "Ajouter à la playlist : ",
|
||||||
"Add to playlist": "Ajouter à la playlist",
|
"Add to playlist": "Ajouter à la playlist",
|
||||||
"Answer": "Répondre",
|
"Answer": "Répondre",
|
||||||
"Search for videos": "Rechercher des vidéos",
|
"Search for videos": "Rechercher des vidéos",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"carousel_skip": "Passez le carrousel",
|
"carousel_skip": "Passez le carrousel",
|
||||||
"carousel_slide": "Diapositive {{current}} sur {{total}}",
|
"carousel_slide": "Diapositive {{current}} sur {{total}}",
|
||||||
"carousel_go_to": "Aller à la diapositive `x`",
|
"carousel_go_to": "Aller à la diapositive `x`",
|
||||||
"toggle_theme": "Changer le Thème"
|
"toggle_theme": "Changer le Thème",
|
||||||
|
"Filipino (auto-generated)": "Philippines (automatiquement générer)",
|
||||||
|
"preferences_preload_label": "Précharger les données de la vidéo : ",
|
||||||
|
"First page": "Première page",
|
||||||
|
"channel_tab_courses_label": "Cours",
|
||||||
|
"channel_tab_posts_label": "Messages"
|
||||||
}
|
}
|
||||||
|
|
|
@ -449,30 +449,30 @@
|
||||||
"Cantonese (Hong Kong)": "Kantonski (Hong Kong)",
|
"Cantonese (Hong Kong)": "Kantonski (Hong Kong)",
|
||||||
"Chinese": "Kineski",
|
"Chinese": "Kineski",
|
||||||
"Chinese (Taiwan)": "Kineski (Tajvan)",
|
"Chinese (Taiwan)": "Kineski (Tajvan)",
|
||||||
"Dutch (auto-generated)": "Nizozemski (automatski generiran)",
|
"Dutch (auto-generated)": "Nizozemski (automatski generirano)",
|
||||||
"French (auto-generated)": "Francuski (automatski generiran)",
|
"French (auto-generated)": "Francuski (automatski generirano)",
|
||||||
"Indonesian (auto-generated)": "Indonezijski (automatski generiran)",
|
"Indonesian (auto-generated)": "Indonezijski (automatski generirano)",
|
||||||
"Interlingue": "Interlingua",
|
"Interlingue": "Interlingua",
|
||||||
"Japanese (auto-generated)": "Japanski (automatski generiran)",
|
"Japanese (auto-generated)": "Japanski (automatski generirano)",
|
||||||
"Russian (auto-generated)": "Ruski (automatski generiran)",
|
"Russian (auto-generated)": "Ruski (automatski generirano)",
|
||||||
"Turkish (auto-generated)": "Turski (automatski generiran)",
|
"Turkish (auto-generated)": "Turski (automatski generirano)",
|
||||||
"Vietnamese (auto-generated)": "Vijetnamski (automatski generiran)",
|
"Vietnamese (auto-generated)": "Vijetnamski (automatski generirano)",
|
||||||
"Spanish (Spain)": "Španjolski (Španjolska)",
|
"Spanish (Spain)": "Španjolski (Španjolska)",
|
||||||
"Italian (auto-generated)": "Talijanski (automatski generiran)",
|
"Italian (auto-generated)": "Talijanski (automatski generirano)",
|
||||||
"Portuguese (Brazil)": "Portugalski (Brazil)",
|
"Portuguese (Brazil)": "Portugalski (Brazil)",
|
||||||
"Spanish (Mexico)": "Španjolski (Meksiko)",
|
"Spanish (Mexico)": "Španjolski (Meksiko)",
|
||||||
"German (auto-generated)": "Njemački (automatski generiran)",
|
"German (auto-generated)": "Njemački (automatski generirano)",
|
||||||
"Chinese (China)": "Kineski (Kina)",
|
"Chinese (China)": "Kineski (Kina)",
|
||||||
"Chinese (Hong Kong)": "Kineski (Hong Kong)",
|
"Chinese (Hong Kong)": "Kineski (Hong Kong)",
|
||||||
"Korean (auto-generated)": "Korejski (automatski generiran)",
|
"Korean (auto-generated)": "Korejski (automatski generirano)",
|
||||||
"Portuguese (auto-generated)": "Portugalski (automatski generiran)",
|
"Portuguese (auto-generated)": "Portugalski (automatski generirano)",
|
||||||
"Spanish (auto-generated)": "Španjolski (automatski generiran)",
|
"Spanish (auto-generated)": "Španjolski (automatski generirano)",
|
||||||
"preferences_watch_history_label": "Aktiviraj povijest gledanja: ",
|
"preferences_watch_history_label": "Aktiviraj povijest gledanja: ",
|
||||||
"search_filters_title": "Filtri",
|
"search_filters_title": "Filtri",
|
||||||
"search_filters_date_option_none": "Bilo koji datum",
|
"search_filters_date_option_none": "Bilo koji datum",
|
||||||
"search_filters_date_label": "Datum prijenosa",
|
"search_filters_date_label": "Datum prijenosa",
|
||||||
"search_message_no_results": "Nema rezultata.",
|
"search_message_no_results": "Nema rezultata.",
|
||||||
"search_message_use_another_instance": " Također možeš <a href=\"`x`\">tražiti na jednoj drugoj instanci</a>.",
|
"search_message_use_another_instance": "Također možeš <a href=\"`x`\">tražiti na jednoj drugoj instanci</a>.",
|
||||||
"search_message_change_filters_or_query": "Pokušaj proširiti upit za pretragu i/ili promijeni filtre.",
|
"search_message_change_filters_or_query": "Pokušaj proširiti upit za pretragu i/ili promijeni filtre.",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_filters_duration_option_none": "Bilo koje duljine",
|
"search_filters_duration_option_none": "Bilo koje duljine",
|
||||||
|
@ -513,5 +513,7 @@
|
||||||
"toggle_theme": "Uklj./Isklj. temu",
|
"toggle_theme": "Uklj./Isklj. temu",
|
||||||
"carousel_slide": "Kadar {{current}} od {{total}}",
|
"carousel_slide": "Kadar {{current}} od {{total}}",
|
||||||
"carousel_go_to": "Idi na kadar `x`",
|
"carousel_go_to": "Idi na kadar `x`",
|
||||||
"carousel_skip": "Preskoči vrtuljak"
|
"carousel_skip": "Preskoči vrtuljak",
|
||||||
|
"Filipino (auto-generated)": "Filipinski (automatski generirano)",
|
||||||
|
"preferences_preload_label": "Unaprijed učitaj podatke videa: "
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"invidious": "Invidious",
|
"invidious": "Invidious",
|
||||||
"Image CAPTCHA": "Imagine CAPTCHA",
|
"Image CAPTCHA": "Imagine CAPTCHA",
|
||||||
"newest": "plus nove",
|
"newest": "plus nove",
|
||||||
"generic_button_save": "Salvar",
|
"generic_button_save": "Salveguardar",
|
||||||
"Dark mode: ": "Modo obscur: ",
|
"Dark mode: ": "Modo obscur: ",
|
||||||
"preferences_dark_mode_label": "Thema: ",
|
"preferences_dark_mode_label": "Thema: ",
|
||||||
"preferences_category_subscription": "Preferentias de subscription",
|
"preferences_category_subscription": "Preferentias de subscription",
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
"light": "clar",
|
"light": "clar",
|
||||||
"No": "Non",
|
"No": "Non",
|
||||||
"youtube": "YouTube",
|
"youtube": "YouTube",
|
||||||
"LIVE": "IN DIRECTE",
|
"LIVE": "IN DIRECTO",
|
||||||
"reddit": "Reddit",
|
"reddit": "Reddit",
|
||||||
"preferences_category_player": "Preferentias de reproductor",
|
"preferences_category_player": "Preferentias de reproductor",
|
||||||
"Preferences": "Preferentias",
|
"Preferences": "Preferentias",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"LIVE": "BEINT",
|
"LIVE": "BEINT",
|
||||||
"Shared `x` ago": "Deilt fyrir `x` síðan",
|
"Shared `x` ago": "Deilt fyrir `x` síðan",
|
||||||
"Unsubscribe": "Afskrá",
|
"Unsubscribe": "Afskrá",
|
||||||
"Subscribe": "Áskrifa",
|
"Subscribe": "Setja í áskrift",
|
||||||
"View channel on YouTube": "Skoða rás á YouTube",
|
"View channel on YouTube": "Skoða rás á YouTube",
|
||||||
"View playlist on YouTube": "Skoða spilunarlista á YouTube",
|
"View playlist on YouTube": "Skoða spilunarlista á YouTube",
|
||||||
"newest": "nýjasta",
|
"newest": "nýjasta",
|
||||||
|
@ -14,8 +14,8 @@
|
||||||
"Clear watch history?": "Hreinsa áhorfsferil?",
|
"Clear watch history?": "Hreinsa áhorfsferil?",
|
||||||
"New password": "Nýtt lykilorð",
|
"New password": "Nýtt lykilorð",
|
||||||
"New passwords must match": "Nýtt lykilorð verður að passa",
|
"New passwords must match": "Nýtt lykilorð verður að passa",
|
||||||
"Authorize token?": "Leyfa teikn?",
|
"Authorize token?": "Auðkenna teikn?",
|
||||||
"Authorize token for `x`?": "Leyfa teikn fyrir `x`?",
|
"Authorize token for `x`?": "Auðkenna teikn fyrir `x`?",
|
||||||
"Yes": "Já",
|
"Yes": "Já",
|
||||||
"No": "Nei",
|
"No": "Nei",
|
||||||
"Import and Export Data": "Inn- og útflutningur gagna",
|
"Import and Export Data": "Inn- og útflutningur gagna",
|
||||||
|
@ -36,17 +36,17 @@
|
||||||
"source": "uppruni",
|
"source": "uppruni",
|
||||||
"Log in": "Skrá inn",
|
"Log in": "Skrá inn",
|
||||||
"Log in/register": "Innskráning/nýskráning",
|
"Log in/register": "Innskráning/nýskráning",
|
||||||
"User ID": "Notandakenni",
|
"User ID": "Auðkenni notanda",
|
||||||
"Password": "Lykilorð",
|
"Password": "Lykilorð",
|
||||||
"Time (h:mm:ss):": "Tími (h:mm: ss):",
|
"Time (h:mm:ss):": "Tími (h:mm: ss):",
|
||||||
"Text CAPTCHA": "Texta CAPTCHA",
|
"Text CAPTCHA": "CAPTCHA-texti",
|
||||||
"Image CAPTCHA": "Mynd CAPTCHA",
|
"Image CAPTCHA": "CAPTCHA-mynd",
|
||||||
"Sign In": "Skrá inn",
|
"Sign In": "Skrá inn",
|
||||||
"Register": "Nýskrá",
|
"Register": "Nýskrá",
|
||||||
"E-mail": "Tölvupóstur",
|
"E-mail": "Tölvupóstur",
|
||||||
"Preferences": "Kjörstillingar",
|
"Preferences": "Kjörstillingar",
|
||||||
"preferences_category_player": "Kjörstillingar spilara",
|
"preferences_category_player": "Kjörstillingar spilara",
|
||||||
"preferences_video_loop_label": "Alltaf lykkja: ",
|
"preferences_video_loop_label": "Alltaf endurtaka: ",
|
||||||
"preferences_autoplay_label": "Sjálfvirk spilun: ",
|
"preferences_autoplay_label": "Sjálfvirk spilun: ",
|
||||||
"preferences_continue_label": "Spila næst sjálfgefið: ",
|
"preferences_continue_label": "Spila næst sjálfgefið: ",
|
||||||
"preferences_continue_autoplay_label": "Spila næsta myndskeið sjálfkrafa: ",
|
"preferences_continue_autoplay_label": "Spila næsta myndskeið sjálfkrafa: ",
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
"preferences_unseen_only_label": "Sýna aðeins óséð: ",
|
"preferences_unseen_only_label": "Sýna aðeins óséð: ",
|
||||||
"preferences_notifications_only_label": "Sýna aðeins tilkynningar (ef einhverjar eru): ",
|
"preferences_notifications_only_label": "Sýna aðeins tilkynningar (ef einhverjar eru): ",
|
||||||
"Enable web notifications": "Virkja veftilkynningar",
|
"Enable web notifications": "Virkja veftilkynningar",
|
||||||
"`x` uploaded a video": "`x` hlóð upp myndband",
|
"`x` uploaded a video": "`x` sendi inn myndskeið",
|
||||||
"`x` is live": "`x` er í beinni",
|
"`x` is live": "`x` er í beinni",
|
||||||
"preferences_category_data": "Gagnastillingar",
|
"preferences_category_data": "Gagnastillingar",
|
||||||
"Clear watch history": "Hreinsa áhorfsferil",
|
"Clear watch history": "Hreinsa áhorfsferil",
|
||||||
|
@ -104,8 +104,8 @@
|
||||||
"Registration enabled: ": "Nýskráning virkjuð? ",
|
"Registration enabled: ": "Nýskráning virkjuð? ",
|
||||||
"Report statistics: ": "Skrá tölfræði? ",
|
"Report statistics: ": "Skrá tölfræði? ",
|
||||||
"Save preferences": "Vista stillingar",
|
"Save preferences": "Vista stillingar",
|
||||||
"Subscription manager": "Áskriftarstjóri",
|
"Subscription manager": "Áskriftastýring",
|
||||||
"Token manager": "Teiknastjórnun",
|
"Token manager": "Teiknastýring",
|
||||||
"Token": "Teikn",
|
"Token": "Teikn",
|
||||||
"Import/export": "Flytja inn/út",
|
"Import/export": "Flytja inn/út",
|
||||||
"unsubscribe": "afskrá",
|
"unsubscribe": "afskrá",
|
||||||
|
@ -233,7 +233,7 @@
|
||||||
"Korean": "Kóreska",
|
"Korean": "Kóreska",
|
||||||
"Kurdish": "Kúrdíska",
|
"Kurdish": "Kúrdíska",
|
||||||
"Kyrgyz": "Kirgisíska",
|
"Kyrgyz": "Kirgisíska",
|
||||||
"Lao": "Laó",
|
"Lao": "Laóska",
|
||||||
"Latin": "Latína",
|
"Latin": "Latína",
|
||||||
"Latvian": "Lettneska",
|
"Latvian": "Lettneska",
|
||||||
"Lithuanian": "Litháíska",
|
"Lithuanian": "Litháíska",
|
||||||
|
@ -295,18 +295,18 @@
|
||||||
"View as playlist": "Skoða sem spilunarlista",
|
"View as playlist": "Skoða sem spilunarlista",
|
||||||
"Default": "Sjálfgefið",
|
"Default": "Sjálfgefið",
|
||||||
"Music": "Tónlist",
|
"Music": "Tónlist",
|
||||||
"Gaming": "Tólvuleikja",
|
"Gaming": "Spilun leikja",
|
||||||
"News": "Fréttir",
|
"News": "Fréttir",
|
||||||
"Movies": "Kvikmyndir",
|
"Movies": "Kvikmyndir",
|
||||||
"Download": "Niðurhal",
|
"Download": "Niðurhal",
|
||||||
"Download as: ": "Niðurhala sem: ",
|
"Download as: ": "Sækja sem: ",
|
||||||
"%A %B %-d, %Y": "%A %B %-d, %Y",
|
"%A %B %-d, %Y": "%A %B %-d, %Y",
|
||||||
"(edited)": "(breytt)",
|
"(edited)": "(breytt)",
|
||||||
"YouTube comment permalink": "YouTube ummæli varanlegur tengill",
|
"YouTube comment permalink": "Varanlegur tengill á YouTube-ummæli",
|
||||||
"permalink": "Varanlegur tengill",
|
"permalink": "Varanlegur tengill",
|
||||||
"`x` marked it with a ❤": "`x` merkti það með ❤",
|
"`x` marked it with a ❤": "`x` merkti það með ❤",
|
||||||
"Audio mode": "Hljóð ham",
|
"Audio mode": "Hljóðhamur",
|
||||||
"Video mode": "Myndband ham",
|
"Video mode": "Myndhamur",
|
||||||
"channel_tab_videos_label": "Myndskeið",
|
"channel_tab_videos_label": "Myndskeið",
|
||||||
"Playlists": "Spilunarlistar",
|
"Playlists": "Spilunarlistar",
|
||||||
"channel_tab_community_label": "Samfélag",
|
"channel_tab_community_label": "Samfélag",
|
||||||
|
@ -388,7 +388,7 @@
|
||||||
"crash_page_before_reporting": "Áður en þú tilkynnir villu, gakktu úr skugga um að þú hafir:",
|
"crash_page_before_reporting": "Áður en þú tilkynnir villu, gakktu úr skugga um að þú hafir:",
|
||||||
"crash_page_switch_instance": "reynt að <a href=\"`x`\">nota annað tilvik</a>",
|
"crash_page_switch_instance": "reynt að <a href=\"`x`\">nota annað tilvik</a>",
|
||||||
"crash_page_report_issue": "Ef ekkert af ofantöldu hjálpaði, ættirðu að <a href=\"`x`\">opna nýja verkbeiðni (issue) á GitHub</a> (helst á ensku) og láta fylgja eftirfarandi texta í skilaboðunum þínum (alls EKKI þýða þennan texta):",
|
"crash_page_report_issue": "Ef ekkert af ofantöldu hjálpaði, ættirðu að <a href=\"`x`\">opna nýja verkbeiðni (issue) á GitHub</a> (helst á ensku) og láta fylgja eftirfarandi texta í skilaboðunum þínum (alls EKKI þýða þennan texta):",
|
||||||
"channel_tab_shorts_label": "Stuttmyndir",
|
"channel_tab_shorts_label": "Símamyndir",
|
||||||
"carousel_slide": "Skyggna {{current}} af {{total}}",
|
"carousel_slide": "Skyggna {{current}} af {{total}}",
|
||||||
"carousel_go_to": "Fara á skyggnu `x`",
|
"carousel_go_to": "Fara á skyggnu `x`",
|
||||||
"channel_tab_streams_label": "Bein streymi",
|
"channel_tab_streams_label": "Bein streymi",
|
||||||
|
@ -396,13 +396,13 @@
|
||||||
"toggle_theme": "Víxla þema",
|
"toggle_theme": "Víxla þema",
|
||||||
"carousel_skip": "Sleppa hringekjunni",
|
"carousel_skip": "Sleppa hringekjunni",
|
||||||
"preferences_quality_option_medium": "Miðlungs",
|
"preferences_quality_option_medium": "Miðlungs",
|
||||||
"search_message_use_another_instance": " Þú getur líka <a href=\"`x`\">leitað á öðrum netþjóni</a>.",
|
"search_message_use_another_instance": "Þú getur líka <a href=\"`x`\">leitað á öðrum netþjóni</a>.",
|
||||||
"footer_source_code": "Grunnkóði",
|
"footer_source_code": "Grunnkóði",
|
||||||
"English (United Kingdom)": "Enska (Bretland)",
|
"English (United Kingdom)": "Enska (Bretland)",
|
||||||
"English (United States)": "Enska (Bandarísk)",
|
"English (United States)": "Enska (Bandarísk)",
|
||||||
"Vietnamese (auto-generated)": "Víetnamska (sjálfvirkt útbúið)",
|
"Vietnamese (auto-generated)": "Víetnamska (sjálfvirkt útbúið)",
|
||||||
"generic_count_months": "{{count}} mánuður",
|
"generic_count_months": "{{count}} mánuði",
|
||||||
"generic_count_months_plural": "{{count}} mánuðir",
|
"generic_count_months_plural": "{{count}} mánuðum",
|
||||||
"search_filters_sort_option_rating": "Einkunn",
|
"search_filters_sort_option_rating": "Einkunn",
|
||||||
"videoinfo_youTube_embed_link": "Ívefja",
|
"videoinfo_youTube_embed_link": "Ívefja",
|
||||||
"error_video_not_in_playlist": "Umbeðið myndskeið fyrirfinnst ekki í þessum spilunarlista. <a href=\"`x`\">Smelltu hér til að fara á heimasíðu spilunarlistans.</a>",
|
"error_video_not_in_playlist": "Umbeðið myndskeið fyrirfinnst ekki í þessum spilunarlista. <a href=\"`x`\">Smelltu hér til að fara á heimasíðu spilunarlistans.</a>",
|
||||||
|
@ -429,11 +429,11 @@
|
||||||
"Spanish (auto-generated)": "Spænska (sjálfvirkt útbúið)",
|
"Spanish (auto-generated)": "Spænska (sjálfvirkt útbúið)",
|
||||||
"Spanish (Mexico)": "Spænska (Mexíkó)",
|
"Spanish (Mexico)": "Spænska (Mexíkó)",
|
||||||
"generic_count_hours": "{{count}} klukkustund",
|
"generic_count_hours": "{{count}} klukkustund",
|
||||||
"generic_count_hours_plural": "{{count}} klukkustundir",
|
"generic_count_hours_plural": "{{count}} klukkustundum",
|
||||||
"generic_count_years": "{{count}} ár",
|
"generic_count_years": "{{count}} ári",
|
||||||
"generic_count_years_plural": "{{count}} ár",
|
"generic_count_years_plural": "{{count}} árum",
|
||||||
"generic_count_weeks": "{{count}} vika",
|
"generic_count_weeks": "{{count}} viku",
|
||||||
"generic_count_weeks_plural": "{{count}} vikur",
|
"generic_count_weeks_plural": "{{count}} vikum",
|
||||||
"search_filters_date_option_none": "Hvaða dagsetning sem er",
|
"search_filters_date_option_none": "Hvaða dagsetning sem er",
|
||||||
"Channel Sponsor": "Styrktaraðili rásar",
|
"Channel Sponsor": "Styrktaraðili rásar",
|
||||||
"search_filters_date_option_week": "Í þessari viku",
|
"search_filters_date_option_week": "Í þessari viku",
|
||||||
|
@ -476,8 +476,8 @@
|
||||||
"preferences_quality_dash_option_144p": "144p",
|
"preferences_quality_dash_option_144p": "144p",
|
||||||
"invidious": "Invidious",
|
"invidious": "Invidious",
|
||||||
"Korean (auto-generated)": "Kóreska (sjálfvirkt útbúið)",
|
"Korean (auto-generated)": "Kóreska (sjálfvirkt útbúið)",
|
||||||
"generic_count_days": "{{count}} dagur",
|
"generic_count_days": "{{count}} degi",
|
||||||
"generic_count_days_plural": "{{count}} dagar",
|
"generic_count_days_plural": "{{count}} dögum",
|
||||||
"search_filters_date_option_today": "Í dag",
|
"search_filters_date_option_today": "Í dag",
|
||||||
"search_filters_type_label": "Tegund",
|
"search_filters_type_label": "Tegund",
|
||||||
"search_filters_type_option_all": "Hvaða tegund sem er",
|
"search_filters_type_option_all": "Hvaða tegund sem er",
|
||||||
|
@ -496,5 +496,10 @@
|
||||||
"footer_documentation": "Leiðbeiningar",
|
"footer_documentation": "Leiðbeiningar",
|
||||||
"channel_tab_channels_label": "Rásir",
|
"channel_tab_channels_label": "Rásir",
|
||||||
"Import YouTube playlist (.csv)": "Flytja inn YouTube spilunarlista (.csv)",
|
"Import YouTube playlist (.csv)": "Flytja inn YouTube spilunarlista (.csv)",
|
||||||
"preferences_quality_option_dash": "DASH (aðlaganleg gæði)"
|
"preferences_quality_option_dash": "DASH (aðlaganleg gæði)",
|
||||||
|
"preferences_preload_label": "Forhlaða gögnum myndskeiðs: ",
|
||||||
|
"Filipino (auto-generated)": "Filippínska (sjálfvirkt útbúin)",
|
||||||
|
"channel_tab_posts_label": "Færslur",
|
||||||
|
"First page": "Fyrsta síða",
|
||||||
|
"channel_tab_courses_label": "Kennsluefni"
|
||||||
}
|
}
|
||||||
|
|
|
@ -449,7 +449,7 @@
|
||||||
"Portuguese (Brazil)": "Portoghese (Brasile)",
|
"Portuguese (Brazil)": "Portoghese (Brasile)",
|
||||||
"preferences_watch_history_label": "Attiva cronologia di riproduzione: ",
|
"preferences_watch_history_label": "Attiva cronologia di riproduzione: ",
|
||||||
"French (auto-generated)": "Francese (generati automaticamente)",
|
"French (auto-generated)": "Francese (generati automaticamente)",
|
||||||
"search_message_use_another_instance": " Puoi anche <a href=\"`x`\">cercare in un'altra istanza</a>.",
|
"search_message_use_another_instance": "Puoi anche <a href=\"`x`\">cercare in un'altra istanza</a>.",
|
||||||
"search_message_no_results": "Nessun risultato trovato.",
|
"search_message_no_results": "Nessun risultato trovato.",
|
||||||
"search_message_change_filters_or_query": "Prova ad ampliare la ricerca e/o modificare i filtri.",
|
"search_message_change_filters_or_query": "Prova ad ampliare la ricerca e/o modificare i filtri.",
|
||||||
"English (United States)": "Inglese (Stati Uniti)",
|
"English (United States)": "Inglese (Stati Uniti)",
|
||||||
|
@ -469,8 +469,8 @@
|
||||||
"Spanish (auto-generated)": "Spagnolo (generati automaticamente)",
|
"Spanish (auto-generated)": "Spagnolo (generati automaticamente)",
|
||||||
"Spanish (Mexico)": "Spagnolo (Messico)",
|
"Spanish (Mexico)": "Spagnolo (Messico)",
|
||||||
"Spanish (Spain)": "Spagnolo (Spagna)",
|
"Spanish (Spain)": "Spagnolo (Spagna)",
|
||||||
"Turkish (auto-generated)": "Turco (auto-generato)",
|
"Turkish (auto-generated)": "Turco (generati automaticamente)",
|
||||||
"Vietnamese (auto-generated)": "Vietnamita (auto-generato)",
|
"Vietnamese (auto-generated)": "Vietnamita (generati automaticamente)",
|
||||||
"search_filters_date_label": "Data caricamento",
|
"search_filters_date_label": "Data caricamento",
|
||||||
"search_filters_date_option_none": "Qualunque data",
|
"search_filters_date_option_none": "Qualunque data",
|
||||||
"search_filters_type_option_all": "Qualunque tipo",
|
"search_filters_type_option_all": "Qualunque tipo",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"The Popular feed has been disabled by the administrator.": "La sezione dei contenuti popolari è stata disabilitata dall'amministratore.",
|
"The Popular feed has been disabled by the administrator.": "La sezione dei contenuti popolari è stata disabilitata dall'amministratore.",
|
||||||
"carousel_slide": "Fotogramma {{current}} di {{total}}",
|
"carousel_slide": "Fotogramma {{current}} di {{total}}",
|
||||||
"carousel_skip": "Salta la galleria",
|
"carousel_skip": "Salta la galleria",
|
||||||
"carousel_go_to": "Vai al fotogramma `x`"
|
"carousel_go_to": "Vai al fotogramma `x`",
|
||||||
|
"preferences_preload_label": "Precarica dati video: ",
|
||||||
|
"Filipino (auto-generated)": "Filippino (generati automaticamente)",
|
||||||
|
"First page": "Prima pagina",
|
||||||
|
"channel_tab_courses_label": "Corsi",
|
||||||
|
"channel_tab_posts_label": "Post"
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"No": "いいえ",
|
"No": "いいえ",
|
||||||
"Import and Export Data": "データのインポートとエクスポート",
|
"Import and Export Data": "データのインポートとエクスポート",
|
||||||
"Import": "インポート",
|
"Import": "インポート",
|
||||||
"Import Invidious data": "Invidious JSONデータをインポート",
|
"Import Invidious data": "Invidious JSON データをインポート",
|
||||||
"Import YouTube subscriptions": "YouTube/OPML 登録チャンネルをインポート",
|
"Import YouTube subscriptions": "YouTube/OPML 登録チャンネルをインポート",
|
||||||
"Import FreeTube subscriptions (.db)": "FreeTube 登録チャンネルをインポート (.db)",
|
"Import FreeTube subscriptions (.db)": "FreeTube 登録チャンネルをインポート (.db)",
|
||||||
"Import NewPipe subscriptions (.json)": "NewPipe 登録チャンネルをインポート (.json)",
|
"Import NewPipe subscriptions (.json)": "NewPipe 登録チャンネルをインポート (.json)",
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
"preferences_related_videos_label": "関連動画を表示: ",
|
"preferences_related_videos_label": "関連動画を表示: ",
|
||||||
"preferences_annotations_label": "最初からアノテーションを表示: ",
|
"preferences_annotations_label": "最初からアノテーションを表示: ",
|
||||||
"preferences_extend_desc_label": "動画の説明文を自動的に拡張: ",
|
"preferences_extend_desc_label": "動画の説明文を自動的に拡張: ",
|
||||||
"preferences_vr_mode_label": "対話的な360°動画 (WebGLが必要): ",
|
"preferences_vr_mode_label": "対話的な 360° 動画 (WebGL が必要): ",
|
||||||
"preferences_category_visual": "外観設定",
|
"preferences_category_visual": "外観設定",
|
||||||
"preferences_player_style_label": "プレイヤーのスタイル: ",
|
"preferences_player_style_label": "プレイヤーのスタイル: ",
|
||||||
"Dark mode: ": "ダークモード: ",
|
"Dark mode: ": "ダークモード: ",
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
"light": "ライト",
|
"light": "ライト",
|
||||||
"preferences_thin_mode_label": "最小モード: ",
|
"preferences_thin_mode_label": "最小モード: ",
|
||||||
"preferences_category_misc": "ほかの設定",
|
"preferences_category_misc": "ほかの設定",
|
||||||
"preferences_automatic_instance_redirect_label": "インスタンスの自動転送 (redirect.invidious.ioにフォールバック): ",
|
"preferences_automatic_instance_redirect_label": "インスタンスの自動転送 (redirect.invidious.io にフォールバック): ",
|
||||||
"preferences_category_subscription": "登録チャンネル設定",
|
"preferences_category_subscription": "登録チャンネル設定",
|
||||||
"preferences_annotations_subscribed_label": "最初から登録チャンネルのアノテーションを表示 ",
|
"preferences_annotations_subscribed_label": "最初から登録チャンネルのアノテーションを表示 ",
|
||||||
"Redirect homepage to feed: ": "ホームからフィードにリダイレクト: ",
|
"Redirect homepage to feed: ": "ホームからフィードにリダイレクト: ",
|
||||||
|
@ -125,7 +125,7 @@
|
||||||
"subscriptions_unseen_notifs_count_0": "{{count}}件の未読通知",
|
"subscriptions_unseen_notifs_count_0": "{{count}}件の未読通知",
|
||||||
"search": "検索",
|
"search": "検索",
|
||||||
"Log out": "ログアウト",
|
"Log out": "ログアウト",
|
||||||
"Released under the AGPLv3 on Github.": "GitHub上でAGPLv3の元で公開",
|
"Released under the AGPLv3 on Github.": "GitHub 上で AGPLv3 の元で公開",
|
||||||
"Source available here.": "ソースはここで閲覧可能です。",
|
"Source available here.": "ソースはここで閲覧可能です。",
|
||||||
"View JavaScript license information.": "JavaScriptライセンス情報",
|
"View JavaScript license information.": "JavaScriptライセンス情報",
|
||||||
"View privacy policy.": "個人情報保護方針",
|
"View privacy policy.": "個人情報保護方針",
|
||||||
|
@ -143,8 +143,8 @@
|
||||||
"Editing playlist `x`": "再生リスト `x` を編集中",
|
"Editing playlist `x`": "再生リスト `x` を編集中",
|
||||||
"Show more": "もっと見る",
|
"Show more": "もっと見る",
|
||||||
"Show less": "表示を少なく",
|
"Show less": "表示を少なく",
|
||||||
"Watch on YouTube": "YouTubeで視聴",
|
"Watch on YouTube": "YouTube で視聴",
|
||||||
"Switch Invidious Instance": "Invidiousインスタンスの変更",
|
"Switch Invidious Instance": "Invidious インスタンスの変更",
|
||||||
"Hide annotations": "アノテーションを隠す",
|
"Hide annotations": "アノテーションを隠す",
|
||||||
"Show annotations": "アノテーションを表示",
|
"Show annotations": "アノテーションを表示",
|
||||||
"Genre: ": "ジャンル: ",
|
"Genre: ": "ジャンル: ",
|
||||||
|
@ -330,7 +330,7 @@
|
||||||
"(edited)": "(編集済み)",
|
"(edited)": "(編集済み)",
|
||||||
"YouTube comment permalink": "YouTube コメントのパーマリンク",
|
"YouTube comment permalink": "YouTube コメントのパーマリンク",
|
||||||
"permalink": "パーマリンク",
|
"permalink": "パーマリンク",
|
||||||
"`x` marked it with a ❤": "`x` が❤を送りました",
|
"`x` marked it with a ❤": "`x` が ❤ を送りました",
|
||||||
"Audio mode": "音声モード",
|
"Audio mode": "音声モード",
|
||||||
"Video mode": "動画モード",
|
"Video mode": "動画モード",
|
||||||
"channel_tab_videos_label": "動画",
|
"channel_tab_videos_label": "動画",
|
||||||
|
@ -343,7 +343,7 @@
|
||||||
"search_filters_type_label": "種類",
|
"search_filters_type_label": "種類",
|
||||||
"search_filters_duration_label": "再生時間",
|
"search_filters_duration_label": "再生時間",
|
||||||
"search_filters_features_label": "特徴",
|
"search_filters_features_label": "特徴",
|
||||||
"search_filters_sort_label": "順番",
|
"search_filters_sort_label": "並べ替え",
|
||||||
"search_filters_date_option_hour": "1時間以内",
|
"search_filters_date_option_hour": "1時間以内",
|
||||||
"search_filters_date_option_today": "今日",
|
"search_filters_date_option_today": "今日",
|
||||||
"search_filters_date_option_week": "今週",
|
"search_filters_date_option_week": "今週",
|
||||||
|
@ -363,15 +363,15 @@
|
||||||
"search_filters_features_option_location": "場所",
|
"search_filters_features_option_location": "場所",
|
||||||
"search_filters_features_option_hdr": "HDR",
|
"search_filters_features_option_hdr": "HDR",
|
||||||
"Current version: ": "現在のバージョン: ",
|
"Current version: ": "現在のバージョン: ",
|
||||||
"next_steps_error_message": "以下をお試してください: ",
|
"next_steps_error_message": "以下をお試しください: ",
|
||||||
"next_steps_error_message_refresh": "再読み込み",
|
"next_steps_error_message_refresh": "再読み込み",
|
||||||
"next_steps_error_message_go_to_youtube": "YouTubeを開く",
|
"next_steps_error_message_go_to_youtube": "YouTube を開く",
|
||||||
"search_filters_duration_option_short": "4分未満",
|
"search_filters_duration_option_short": "4分未満",
|
||||||
"footer_documentation": "説明書",
|
"footer_documentation": "説明書",
|
||||||
"footer_source_code": "ソースコード",
|
"footer_source_code": "ソースコード",
|
||||||
"footer_original_source_code": "元のソースコード",
|
"footer_original_source_code": "元のソースコード",
|
||||||
"footer_modfied_source_code": "改変して使用",
|
"footer_modfied_source_code": "改変し使用中",
|
||||||
"adminprefs_modified_source_code_url_label": "改変されたソースコードのレポジトリのURL",
|
"adminprefs_modified_source_code_url_label": "改変されたソースコードのレポジトリの URL",
|
||||||
"search_filters_duration_option_long": "20分以上",
|
"search_filters_duration_option_long": "20分以上",
|
||||||
"preferences_region_label": "地域: ",
|
"preferences_region_label": "地域: ",
|
||||||
"footer_donate_page": "寄付する",
|
"footer_donate_page": "寄付する",
|
||||||
|
@ -396,10 +396,10 @@
|
||||||
"download_subtitles": "字幕 - `x` (.vtt)",
|
"download_subtitles": "字幕 - `x` (.vtt)",
|
||||||
"search_filters_features_option_purchased": "購入済み",
|
"search_filters_features_option_purchased": "購入済み",
|
||||||
"preferences_quality_option_dash": "DASH (適応的画質)",
|
"preferences_quality_option_dash": "DASH (適応的画質)",
|
||||||
"preferences_quality_dash_option_worst": "最悪",
|
"preferences_quality_dash_option_worst": "最低",
|
||||||
"preferences_quality_dash_option_best": "最高",
|
"preferences_quality_dash_option_best": "最高",
|
||||||
"videoinfo_started_streaming_x_ago": "`x`前に配信を開始",
|
"videoinfo_started_streaming_x_ago": "`x`前に配信を開始",
|
||||||
"videoinfo_watch_on_youTube": "YouTubeで視聴",
|
"videoinfo_watch_on_youTube": "YouTube で視聴",
|
||||||
"user_created_playlists": "`x`個の作成した再生リスト",
|
"user_created_playlists": "`x`個の作成した再生リスト",
|
||||||
"Video unavailable": "動画は利用できません",
|
"Video unavailable": "動画は利用できません",
|
||||||
"Chinese": "中国語",
|
"Chinese": "中国語",
|
||||||
|
@ -434,7 +434,7 @@
|
||||||
"crash_page_switch_instance": "<a href=\"`x`\">別のインスタンスを使用</a>を試す",
|
"crash_page_switch_instance": "<a href=\"`x`\">別のインスタンスを使用</a>を試す",
|
||||||
"crash_page_read_the_faq": "<a href=\"`x`\">よくある質問 (FAQ)</a> を読む",
|
"crash_page_read_the_faq": "<a href=\"`x`\">よくある質問 (FAQ)</a> を読む",
|
||||||
"Popular enabled: ": "人気動画を有効化 ",
|
"Popular enabled: ": "人気動画を有効化 ",
|
||||||
"search_message_use_another_instance": " <a href=\"`x`\">別のインスタンス上での検索</a>も可能です。",
|
"search_message_use_another_instance": "<a href=\"`x`\">別のインスタンス上での検索</a>も可能です。",
|
||||||
"search_filters_apply_button": "選択したフィルターを適用",
|
"search_filters_apply_button": "選択したフィルターを適用",
|
||||||
"user_saved_playlists": "`x`個の保存済みの再生リスト",
|
"user_saved_playlists": "`x`個の保存済みの再生リスト",
|
||||||
"crash_page_you_found_a_bug": "Invidious のバグのようです!",
|
"crash_page_you_found_a_bug": "Invidious のバグのようです!",
|
||||||
|
@ -446,7 +446,7 @@
|
||||||
"search_filters_duration_option_medium": "4 ~ 20分",
|
"search_filters_duration_option_medium": "4 ~ 20分",
|
||||||
"preferences_save_player_pos_label": "再生位置を保存: ",
|
"preferences_save_player_pos_label": "再生位置を保存: ",
|
||||||
"crash_page_before_reporting": "バグを報告する前に、次のことを確認してください。",
|
"crash_page_before_reporting": "バグを報告する前に、次のことを確認してください。",
|
||||||
"crash_page_report_issue": "上記が助けにならないなら、<a href=\"`x`\">GitHub</a> に新しい issue を作成し(英語が好ましい)、メッセージに次のテキストを含めてください(テキストは翻訳しない)。",
|
"crash_page_report_issue": "上記が助けにならない場合、<a href=\"`x`\">GitHub</a> に新しい issue を作成し (できれば英語で) 、メッセージに次のテキストを含めてください (テキストは翻訳しない) 。",
|
||||||
"crash_page_search_issue": "<a href=\"`x`\">GitHub の既存の問題 (issue)</a> を検索",
|
"crash_page_search_issue": "<a href=\"`x`\">GitHub の既存の問題 (issue)</a> を検索",
|
||||||
"channel_tab_streams_label": "ライブ",
|
"channel_tab_streams_label": "ライブ",
|
||||||
"channel_tab_playlists_label": "再生リスト",
|
"channel_tab_playlists_label": "再生リスト",
|
||||||
|
@ -479,5 +479,10 @@
|
||||||
"carousel_go_to": "スライド`x`を表示",
|
"carousel_go_to": "スライド`x`を表示",
|
||||||
"carousel_slide": "スライド{{current}} / 全{{total}}個中",
|
"carousel_slide": "スライド{{current}} / 全{{total}}個中",
|
||||||
"carousel_skip": "画像のスライド表示をスキップ",
|
"carousel_skip": "画像のスライド表示をスキップ",
|
||||||
"toggle_theme": "テーマの切り替え"
|
"toggle_theme": "テーマの切り替え",
|
||||||
|
"preferences_preload_label": "動画データを事前に読み込む: ",
|
||||||
|
"Filipino (auto-generated)": "フィリピノ語 (自動生成)",
|
||||||
|
"First page": "最初のページ",
|
||||||
|
"channel_tab_posts_label": "投稿",
|
||||||
|
"channel_tab_courses_label": "コース"
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
"preferences_related_videos_label": "관련 동영상 보기: ",
|
"preferences_related_videos_label": "관련 동영상 보기: ",
|
||||||
"Fallback captions: ": "대체 자막: ",
|
"Fallback captions: ": "대체 자막: ",
|
||||||
"preferences_captions_label": "기본 자막: ",
|
"preferences_captions_label": "기본 자막: ",
|
||||||
"reddit": "Reddit",
|
"reddit": "레딧",
|
||||||
"youtube": "YouTube",
|
"youtube": "유튜브",
|
||||||
"preferences_comments_label": "기본 댓글: ",
|
"preferences_comments_label": "기본 댓글: ",
|
||||||
"preferences_volume_label": "플레이어 볼륨: ",
|
"preferences_volume_label": "플레이어 볼륨: ",
|
||||||
"preferences_quality_label": "선호하는 비디오 품질: ",
|
"preferences_quality_label": "선호하는 비디오 품질: ",
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
"An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
|
"An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
|
||||||
"History": "시청 기록",
|
"History": "시청 기록",
|
||||||
"Delete account?": "계정을 삭제 하시겠습니까?",
|
"Delete account?": "계정을 삭제 하시겠습니까?",
|
||||||
"Export data as JSON": "JSON으로 데이터 내보내기",
|
"Export data as JSON": "인비디어스 데이터 내보내기 (.json)",
|
||||||
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML로 구독 내보내기 (뉴파이프 및 프리튜브)",
|
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML로 구독 내보내기 (뉴파이프 및 프리튜브)",
|
||||||
"Export subscriptions as OPML": "OPML로 구독 내보내기",
|
"Export subscriptions as OPML": "OPML로 구독 내보내기",
|
||||||
"Export": "내보내기",
|
"Export": "내보내기",
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
"Next page": "다음 페이지",
|
"Next page": "다음 페이지",
|
||||||
"last": "마지막",
|
"last": "마지막",
|
||||||
"Shared `x` ago": "`x` 전",
|
"Shared `x` ago": "`x` 전",
|
||||||
"popular": "인기",
|
"popular": "인기순",
|
||||||
"oldest": "과거순",
|
"oldest": "과거순",
|
||||||
"newest": "최신순",
|
"newest": "최신순",
|
||||||
"View playlist on YouTube": "유튜브에서 재생목록 보기",
|
"View playlist on YouTube": "유튜브에서 재생목록 보기",
|
||||||
|
@ -78,10 +78,10 @@
|
||||||
"Subscribe": "구독",
|
"Subscribe": "구독",
|
||||||
"Unsubscribe": "구독 취소",
|
"Unsubscribe": "구독 취소",
|
||||||
"LIVE": "실시간",
|
"LIVE": "실시간",
|
||||||
"generic_views_count_0": "조회수 {{count}}회",
|
"generic_views_count_0": "{{count}} 조회수",
|
||||||
"generic_videos_count_0": "동영상 {{count}}개",
|
"generic_videos_count_0": "{{count}} 동영상",
|
||||||
"generic_playlists_count_0": "재생목록 {{count}}개",
|
"generic_playlists_count_0": "{{count}} 재생목록",
|
||||||
"generic_subscribers_count_0": "구독자 {{count}}명",
|
"generic_subscribers_count_0": "{{count}} 구독자",
|
||||||
"generic_subscriptions_count_0": "{{count}} 구독",
|
"generic_subscriptions_count_0": "{{count}} 구독",
|
||||||
"search_filters_type_option_playlist": "재생목록",
|
"search_filters_type_option_playlist": "재생목록",
|
||||||
"Korean": "한국어",
|
"Korean": "한국어",
|
||||||
|
@ -109,14 +109,14 @@
|
||||||
"This channel does not exist.": "이 채널은 존재하지 않습니다.",
|
"This channel does not exist.": "이 채널은 존재하지 않습니다.",
|
||||||
"Deleted or invalid channel": "삭제되었거나 더 이상 존재하지 않는 채널",
|
"Deleted or invalid channel": "삭제되었거나 더 이상 존재하지 않는 채널",
|
||||||
"channel:`x`": "채널:`x`",
|
"channel:`x`": "채널:`x`",
|
||||||
"Show replies": "댓글 보이기",
|
"Show replies": "댓글 보기",
|
||||||
"Hide replies": "댓글 숨기기",
|
"Hide replies": "댓글 숨기기",
|
||||||
"Incorrect password": "잘못된 비밀번호",
|
"Incorrect password": "잘못된 비밀번호",
|
||||||
"License: ": "라이선스: ",
|
"License: ": "라이선스: ",
|
||||||
"Genre: ": "장르: ",
|
"Genre: ": "장르: ",
|
||||||
"Editing playlist `x`": "재생목록 `x` 수정하기",
|
"Editing playlist `x`": "재생목록 `x` 수정하기",
|
||||||
"Playlist privacy": "재생목록 공개 범위",
|
"Playlist privacy": "재생목록 공개 범위",
|
||||||
"Watch on YouTube": "YouTube에서 보기",
|
"Watch on YouTube": "유튜브에서 보기",
|
||||||
"Show less": "간략히",
|
"Show less": "간략히",
|
||||||
"Show more": "더보기",
|
"Show more": "더보기",
|
||||||
"Title": "제목",
|
"Title": "제목",
|
||||||
|
@ -125,7 +125,7 @@
|
||||||
"Delete playlist": "재생목록 삭제",
|
"Delete playlist": "재생목록 삭제",
|
||||||
"Delete playlist `x`?": "재생목록 `x` 를 삭제하시겠습니까?",
|
"Delete playlist `x`?": "재생목록 `x` 를 삭제하시겠습니까?",
|
||||||
"Updated `x` ago": "`x` 전에 업데이트됨",
|
"Updated `x` ago": "`x` 전에 업데이트됨",
|
||||||
"Released under the AGPLv3 on Github.": "GitHub에 AGPLv3 으로 배포됩니다.",
|
"Released under the AGPLv3 on Github.": "깃허브에 AGPLv3 으로 배포됩니다.",
|
||||||
"View all playlists": "모든 재생목록 보기",
|
"View all playlists": "모든 재생목록 보기",
|
||||||
"Private": "비공개",
|
"Private": "비공개",
|
||||||
"Unlisted": "목록에 없음",
|
"Unlisted": "목록에 없음",
|
||||||
|
@ -135,12 +135,12 @@
|
||||||
"Source available here.": "소스는 여기에서 사용할 수 있습니다.",
|
"Source available here.": "소스는 여기에서 사용할 수 있습니다.",
|
||||||
"Log out": "로그아웃",
|
"Log out": "로그아웃",
|
||||||
"search": "검색",
|
"search": "검색",
|
||||||
"subscriptions_unseen_notifs_count_0": "읽지 않은 알림 {{count}}개",
|
"subscriptions_unseen_notifs_count_0": "{{count}} 읽지 않은 알림",
|
||||||
"Subscriptions": "구독",
|
"Subscriptions": "구독",
|
||||||
"revoke": "철회",
|
"revoke": "철회",
|
||||||
"unsubscribe": "구독 취소",
|
"unsubscribe": "구독 취소",
|
||||||
"Import/export": "가져오기/내보내기",
|
"Import/export": "가져오기/내보내기",
|
||||||
"tokens_count_0": "토큰 {{count}}개",
|
"tokens_count_0": "{{count}} 토큰",
|
||||||
"Token": "토큰",
|
"Token": "토큰",
|
||||||
"Token manager": "토큰 관리자",
|
"Token manager": "토큰 관리자",
|
||||||
"Subscription manager": "구독 관리자",
|
"Subscription manager": "구독 관리자",
|
||||||
|
@ -163,7 +163,7 @@
|
||||||
"Clear watch history": "시청 기록 지우기",
|
"Clear watch history": "시청 기록 지우기",
|
||||||
"preferences_category_data": "데이터 설정",
|
"preferences_category_data": "데이터 설정",
|
||||||
"`x` is live": "`x` 이(가) 라이브 중입니다",
|
"`x` is live": "`x` 이(가) 라이브 중입니다",
|
||||||
"`x` uploaded a video": "`x` 이(가) 동영상을 게시했습니다",
|
"`x` uploaded a video": "`x` 동영상 게시됨",
|
||||||
"Enable web notifications": "웹 알림 활성화",
|
"Enable web notifications": "웹 알림 활성화",
|
||||||
"preferences_notifications_only_label": "알림만 표시 (있는 경우): ",
|
"preferences_notifications_only_label": "알림만 표시 (있는 경우): ",
|
||||||
"preferences_unseen_only_label": "시청하지 않은 것만 표시: ",
|
"preferences_unseen_only_label": "시청하지 않은 것만 표시: ",
|
||||||
|
@ -241,7 +241,7 @@
|
||||||
"Could not create mix.": "믹스를 생성할 수 없습니다.",
|
"Could not create mix.": "믹스를 생성할 수 없습니다.",
|
||||||
"`x` ago": "`x` 전",
|
"`x` ago": "`x` 전",
|
||||||
"comments_view_x_replies_0": "답글 {{count}}개 보기",
|
"comments_view_x_replies_0": "답글 {{count}}개 보기",
|
||||||
"View Reddit comments": "Reddit 댓글 보기",
|
"View Reddit comments": "레딧 댓글 보기",
|
||||||
"Engagement: ": "약속: ",
|
"Engagement: ": "약속: ",
|
||||||
"Wilson score: ": "Wilson Score: ",
|
"Wilson score: ": "Wilson Score: ",
|
||||||
"Family friendly? ": "전연령 영상입니까? ",
|
"Family friendly? ": "전연령 영상입니까? ",
|
||||||
|
@ -267,8 +267,8 @@
|
||||||
"Bulgarian": "불가리아어",
|
"Bulgarian": "불가리아어",
|
||||||
"Bosnian": "보스니아어",
|
"Bosnian": "보스니아어",
|
||||||
"Belarusian": "벨라루스어",
|
"Belarusian": "벨라루스어",
|
||||||
"View more comments on Reddit": "Reddit에서 댓글 더 보기",
|
"View more comments on Reddit": "레딧에서 댓글 더 보기",
|
||||||
"View YouTube comments": "YouTube 댓글 보기",
|
"View YouTube comments": "유튜브 댓글 보기",
|
||||||
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "자바스크립트가 꺼져 있는 것 같습니다! 댓글을 보려면 여기를 클릭하세요. 댓글을 로드하는 데 시간이 조금 더 걸릴 수 있습니다.",
|
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "자바스크립트가 꺼져 있는 것 같습니다! 댓글을 보려면 여기를 클릭하세요. 댓글을 로드하는 데 시간이 조금 더 걸릴 수 있습니다.",
|
||||||
"Shared `x`": "`x` 업로드",
|
"Shared `x`": "`x` 업로드",
|
||||||
"Whitelisted regions: ": "차단되지 않은 지역: ",
|
"Whitelisted regions: ": "차단되지 않은 지역: ",
|
||||||
|
@ -289,7 +289,7 @@
|
||||||
"Empty playlist": "재생목록 비어 있음",
|
"Empty playlist": "재생목록 비어 있음",
|
||||||
"Show annotations": "주석 보이기",
|
"Show annotations": "주석 보이기",
|
||||||
"Hide annotations": "주석 숨기기",
|
"Hide annotations": "주석 숨기기",
|
||||||
"Switch Invidious Instance": "Invidious 인스턴스 변경",
|
"Switch Invidious Instance": "인비디어스 인스턴스 변경",
|
||||||
"Spanish": "스페인어",
|
"Spanish": "스페인어",
|
||||||
"Southern Sotho": "소토어",
|
"Southern Sotho": "소토어",
|
||||||
"Somali": "소말리어",
|
"Somali": "소말리어",
|
||||||
|
@ -329,7 +329,7 @@
|
||||||
"Swedish": "스웨덴어",
|
"Swedish": "스웨덴어",
|
||||||
"Spanish (Latin America)": "스페인어 (라틴 아메리카)",
|
"Spanish (Latin America)": "스페인어 (라틴 아메리카)",
|
||||||
"comments_points_count_0": "{{count}} 포인트",
|
"comments_points_count_0": "{{count}} 포인트",
|
||||||
"Invidious Private Feed for `x`": "`x` 에 대한 Invidious 비공개 피드",
|
"Invidious Private Feed for `x`": "`x` 에 대한 인비디어스 비공개 피드",
|
||||||
"Premieres `x`": "최초 공개 `x`",
|
"Premieres `x`": "최초 공개 `x`",
|
||||||
"Premieres in `x`": "`x` 후 최초 공개",
|
"Premieres in `x`": "`x` 후 최초 공개",
|
||||||
"next_steps_error_message": "다음 방법을 시도해 보세요: ",
|
"next_steps_error_message": "다음 방법을 시도해 보세요: ",
|
||||||
|
@ -408,7 +408,7 @@
|
||||||
"preferences_quality_dash_option_1080p": "1080p",
|
"preferences_quality_dash_option_1080p": "1080p",
|
||||||
"preferences_quality_dash_option_worst": "최저",
|
"preferences_quality_dash_option_worst": "최저",
|
||||||
"preferences_watch_history_label": "시청 기록 저장: ",
|
"preferences_watch_history_label": "시청 기록 저장: ",
|
||||||
"invidious": "Invidious",
|
"invidious": "인비디어스",
|
||||||
"preferences_quality_option_small": "낮음",
|
"preferences_quality_option_small": "낮음",
|
||||||
"preferences_quality_dash_option_auto": "자동",
|
"preferences_quality_dash_option_auto": "자동",
|
||||||
"preferences_quality_dash_option_480p": "480p",
|
"preferences_quality_dash_option_480p": "480p",
|
||||||
|
@ -419,7 +419,7 @@
|
||||||
"Portuguese (Brazil)": "포르투갈어 (브라질)",
|
"Portuguese (Brazil)": "포르투갈어 (브라질)",
|
||||||
"search_message_no_results": "결과가 없습니다.",
|
"search_message_no_results": "결과가 없습니다.",
|
||||||
"search_message_change_filters_or_query": "필터를 변경하시거나 검색어를 넓게 시도해보세요.",
|
"search_message_change_filters_or_query": "필터를 변경하시거나 검색어를 넓게 시도해보세요.",
|
||||||
"search_message_use_another_instance": " <a href=\"`x`\">다른 인스턴스에서 검색</a>할 수도 있습니다.",
|
"search_message_use_another_instance": "<a href=\"`x`\">다른 인스턴스에서 검색</a>할 수도 있습니다.",
|
||||||
"English (United States)": "영어 (미국)",
|
"English (United States)": "영어 (미국)",
|
||||||
"Chinese": "중국어",
|
"Chinese": "중국어",
|
||||||
"Chinese (China)": "중국어 (중국)",
|
"Chinese (China)": "중국어 (중국)",
|
||||||
|
@ -453,7 +453,7 @@
|
||||||
"channel_tab_streams_label": "실시간 스트리밍",
|
"channel_tab_streams_label": "실시간 스트리밍",
|
||||||
"channel_tab_channels_label": "채널",
|
"channel_tab_channels_label": "채널",
|
||||||
"channel_tab_playlists_label": "재생목록",
|
"channel_tab_playlists_label": "재생목록",
|
||||||
"Standard YouTube license": "표준 YouTube 라이선스",
|
"Standard YouTube license": "표준 유튜브 라이선스",
|
||||||
"Song: ": "제목: ",
|
"Song: ": "제목: ",
|
||||||
"Channel Sponsor": "채널 스폰서",
|
"Channel Sponsor": "채널 스폰서",
|
||||||
"Album: ": "앨범: ",
|
"Album: ": "앨범: ",
|
||||||
|
@ -479,5 +479,10 @@
|
||||||
"carousel_go_to": "`x` 슬라이드로 이동",
|
"carousel_go_to": "`x` 슬라이드로 이동",
|
||||||
"Search for videos": "비디오 검색",
|
"Search for videos": "비디오 검색",
|
||||||
"toggle_theme": "테마 전환",
|
"toggle_theme": "테마 전환",
|
||||||
"carousel_slide": "{{total}}의 슬라이드 {{current}}"
|
"carousel_slide": "{{total}}의 슬라이드 {{current}}",
|
||||||
|
"preferences_preload_label": "비디오 데이터 사전 로드: ",
|
||||||
|
"First page": "첫 페이지",
|
||||||
|
"Filipino (auto-generated)": "Filipino (auto-generated)",
|
||||||
|
"channel_tab_posts_label": "게시글",
|
||||||
|
"channel_tab_courses_label": "코스"
|
||||||
}
|
}
|
||||||
|
|
69
locales/lv.json
Normal file
69
locales/lv.json
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
"generic_channels_count_0": "{{count}} kanāli",
|
||||||
|
"generic_channels_count_1": "{{count}} kanāls",
|
||||||
|
"generic_channels_count_2": "{{count}} kanāli",
|
||||||
|
"Add to playlist": "Pievienot atskaņošanas sarakstam",
|
||||||
|
"Answer": "Atbildēt",
|
||||||
|
"generic_subscribers_count_0": "{{count}} abonenti",
|
||||||
|
"generic_subscribers_count_1": "{{count}} abonents",
|
||||||
|
"generic_subscribers_count_2": "{{count}} abonenti",
|
||||||
|
"generic_button_delete": "Dzēst",
|
||||||
|
"generic_button_edit": "Rediģēt",
|
||||||
|
"generic_button_save": "Saglabāt",
|
||||||
|
"generic_button_cancel": "Atcelt",
|
||||||
|
"generic_button_rss": "RSS",
|
||||||
|
"Unsubscribe": "Pārtraukt abonementu",
|
||||||
|
"View playlist on YouTube": "Skatīt atskaņošanas sarakstu YouTube vietnē",
|
||||||
|
"New password": "Jaunā parole",
|
||||||
|
"Yes": "Jā",
|
||||||
|
"No": "Nē",
|
||||||
|
"Import and Export Data": "Ievietot un izgūt datus",
|
||||||
|
"Import": "Ievietot",
|
||||||
|
"Import Invidious data": "Ievietot Invidious JSON datus",
|
||||||
|
"Delete account?": "Vai dzēst kontu?",
|
||||||
|
"History": "Vēsture",
|
||||||
|
"User ID": "Lietotāja ID",
|
||||||
|
"Password": "Parole",
|
||||||
|
"Import YouTube subscriptions": "Ievietot YouTube CSV vai OPML abonementus",
|
||||||
|
"E-mail": "E-pasts",
|
||||||
|
"Preferences": "Iestatījumi",
|
||||||
|
"preferences_category_player": "Atskaņotāja iestatījumi",
|
||||||
|
"preferences_quality_option_hd720": "HD - 720p",
|
||||||
|
"preferences_quality_option_medium": "Vidēja",
|
||||||
|
"preferences_quality_dash_option_worst": "Vissliktākā",
|
||||||
|
"preferences_quality_dash_option_2160p": "2160p (4K)",
|
||||||
|
"preferences_quality_dash_option_1080p": "1080p (Full HD)",
|
||||||
|
"preferences_quality_dash_option_720p": "720p (HD)",
|
||||||
|
"preferences_quality_dash_option_1440p": "1440p (2.5K, QHD)",
|
||||||
|
"preferences_quality_dash_option_480p": "480p (SD)",
|
||||||
|
"preferences_quality_dash_option_360p": "360p",
|
||||||
|
"preferences_quality_dash_option_240p": "240p",
|
||||||
|
"preferences_quality_dash_option_144p": "144p",
|
||||||
|
"preferences_volume_label": "Atskaņošanas skaļums: ",
|
||||||
|
"reddit": "Reddit",
|
||||||
|
"invidious": "Invidious",
|
||||||
|
"Bangla": "Bengāļu",
|
||||||
|
"Basque": "Basku",
|
||||||
|
"Cebuano": "Sebuāņu",
|
||||||
|
"Chinese (Traditional)": "Ķīniešu (tradicionālā)",
|
||||||
|
"Corsican": "Korsikāņu",
|
||||||
|
"Croatian": "Horvātu",
|
||||||
|
"Galician": "Galisiešu",
|
||||||
|
"Georgian": "Gruzīnu",
|
||||||
|
"Gujarati": "Gudžaratu",
|
||||||
|
"German": "Vācu",
|
||||||
|
"Greek": "Grieķu",
|
||||||
|
"Haitian Creole": "Haitiešu",
|
||||||
|
"Hausa": "Hausu",
|
||||||
|
"Hawaiian": "Havajiešu",
|
||||||
|
"Export data as JSON": "Izgūt Invidious datus JSON formātā",
|
||||||
|
"preferences_quality_dash_option_4320p": "4320p (8K)",
|
||||||
|
"Time (h:mm:ss):": "Laiks (h:mm:ss):",
|
||||||
|
"Chinese (Simplified)": "Ķīniešu (vienkāršotā)",
|
||||||
|
"preferences_quality_dash_option_best": "Vislabākā",
|
||||||
|
"preferences_quality_option_small": "Zema",
|
||||||
|
"youtube": "YouTube",
|
||||||
|
"Add to playlist: ": "Pievienot atskaņošanas sarakstam: ",
|
||||||
|
"Subscribe": "Abonēt",
|
||||||
|
"View channel on YouTube": "Skatīt kanālu YouTube vietnē"
|
||||||
|
}
|
|
@ -322,13 +322,13 @@
|
||||||
"channel_tab_community_label": "Gemenskap",
|
"channel_tab_community_label": "Gemenskap",
|
||||||
"search_filters_sort_option_relevance": "relevans",
|
"search_filters_sort_option_relevance": "relevans",
|
||||||
"search_filters_sort_option_rating": "vurdering",
|
"search_filters_sort_option_rating": "vurdering",
|
||||||
"search_filters_sort_option_date": "dato",
|
"search_filters_sort_option_date": "Opplastingsdato",
|
||||||
"search_filters_sort_option_views": "visninger",
|
"search_filters_sort_option_views": "visninger",
|
||||||
"search_filters_type_label": "innholdstype",
|
"search_filters_type_label": "innholdstype",
|
||||||
"search_filters_duration_label": "varighet",
|
"search_filters_duration_label": "varighet",
|
||||||
"search_filters_features_label": "funksjoner",
|
"search_filters_features_label": "funksjoner",
|
||||||
"search_filters_sort_label": "sorter",
|
"search_filters_sort_label": "sorter",
|
||||||
"search_filters_date_option_hour": "time",
|
"search_filters_date_option_hour": "Siste time",
|
||||||
"search_filters_date_option_today": "i dag",
|
"search_filters_date_option_today": "i dag",
|
||||||
"search_filters_date_option_week": "uke",
|
"search_filters_date_option_week": "uke",
|
||||||
"search_filters_date_option_month": "måned",
|
"search_filters_date_option_month": "måned",
|
||||||
|
@ -459,7 +459,7 @@
|
||||||
"search_message_no_results": "Resultatløst.",
|
"search_message_no_results": "Resultatløst.",
|
||||||
"search_filters_type_option_all": "Alle typer",
|
"search_filters_type_option_all": "Alle typer",
|
||||||
"search_filters_duration_option_none": "Enhver varighet",
|
"search_filters_duration_option_none": "Enhver varighet",
|
||||||
"search_message_use_another_instance": " Du kan også <a href=\"`x`\">søke på en annen instans</a>.",
|
"search_message_use_another_instance": "Du kan også <a href=\"`x`\">søke på en annen instans</a>.",
|
||||||
"search_filters_date_label": "Opplastningsdato",
|
"search_filters_date_label": "Opplastningsdato",
|
||||||
"search_filters_apply_button": "Bruk valgte filtre",
|
"search_filters_apply_button": "Bruk valgte filtre",
|
||||||
"search_filters_date_option_none": "Siden begynnelsen",
|
"search_filters_date_option_none": "Siden begynnelsen",
|
||||||
|
@ -494,5 +494,8 @@
|
||||||
"carousel_slide": "Lysark {{current}} av {{total}}",
|
"carousel_slide": "Lysark {{current}} av {{total}}",
|
||||||
"carousel_skip": "Hopp over karusellen",
|
"carousel_skip": "Hopp over karusellen",
|
||||||
"Add to playlist": "Legg til i spilleliste",
|
"Add to playlist": "Legg til i spilleliste",
|
||||||
"Add to playlist: ": "Legg til i spilleliste: "
|
"Add to playlist: ": "Legg til i spilleliste: ",
|
||||||
|
"The Popular feed has been disabled by the administrator.": "Populært-kilden er koblet ut av administratoren.",
|
||||||
|
"toggle_theme": "Endre utseende",
|
||||||
|
"preferences_preload_label": "Last videodata på forhånd: "
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,13 +317,13 @@
|
||||||
"channel_tab_community_label": "Gemeenschap",
|
"channel_tab_community_label": "Gemeenschap",
|
||||||
"search_filters_sort_option_relevance": "relevantie",
|
"search_filters_sort_option_relevance": "relevantie",
|
||||||
"search_filters_sort_option_rating": "beoordeling",
|
"search_filters_sort_option_rating": "beoordeling",
|
||||||
"search_filters_sort_option_date": "datum",
|
"search_filters_sort_option_date": "Upload datum",
|
||||||
"search_filters_sort_option_views": "keren bekeken",
|
"search_filters_sort_option_views": "keren bekeken",
|
||||||
"search_filters_type_label": "Type inhoud",
|
"search_filters_type_label": "Type inhoud",
|
||||||
"search_filters_duration_label": "duur",
|
"search_filters_duration_label": "duur",
|
||||||
"search_filters_features_label": "eigenschappen",
|
"search_filters_features_label": "eigenschappen",
|
||||||
"search_filters_sort_label": "sorteren",
|
"search_filters_sort_label": "sorteren",
|
||||||
"search_filters_date_option_hour": "uur",
|
"search_filters_date_option_hour": "Laatste uur",
|
||||||
"search_filters_date_option_today": "vandaag",
|
"search_filters_date_option_today": "vandaag",
|
||||||
"search_filters_date_option_week": "week",
|
"search_filters_date_option_week": "week",
|
||||||
"search_filters_date_option_month": "maand",
|
"search_filters_date_option_month": "maand",
|
||||||
|
@ -357,7 +357,7 @@
|
||||||
"footer_original_source_code": "Originele bron-code",
|
"footer_original_source_code": "Originele bron-code",
|
||||||
"footer_modfied_source_code": "Gewijzigde bron-code",
|
"footer_modfied_source_code": "Gewijzigde bron-code",
|
||||||
"adminprefs_modified_source_code_url_label": "URL naar gewijzigde bron-code-opslagplaats",
|
"adminprefs_modified_source_code_url_label": "URL naar gewijzigde bron-code-opslagplaats",
|
||||||
"next_steps_error_message": "Daarna moet u proberen om: ",
|
"next_steps_error_message": "Waarna u zou kunnen proberen om: ",
|
||||||
"footer_source_code": "Bron-code",
|
"footer_source_code": "Bron-code",
|
||||||
"search_filters_duration_option_long": "Lang (> 20 minuten)",
|
"search_filters_duration_option_long": "Lang (> 20 minuten)",
|
||||||
"preferences_quality_option_dash": "DASH (adaptieve kwaliteit)",
|
"preferences_quality_option_dash": "DASH (adaptieve kwaliteit)",
|
||||||
|
@ -450,7 +450,7 @@
|
||||||
"Chinese (Hong Kong)": "Chinees (Hongkong)",
|
"Chinese (Hong Kong)": "Chinees (Hongkong)",
|
||||||
"Korean (auto-generated)": "Koreaans (automatisch gegenereerd)",
|
"Korean (auto-generated)": "Koreaans (automatisch gegenereerd)",
|
||||||
"search_filters_apply_button": "Geselecteerde filters toepassen",
|
"search_filters_apply_button": "Geselecteerde filters toepassen",
|
||||||
"search_message_use_another_instance": " Je kan ook <a href=\"`x`\">zoeken op een andere instantie</a>.",
|
"search_message_use_another_instance": "Je kan ook <a href=\"`x`\">zoeken op een andere instantie</a>.",
|
||||||
"Cantonese (Hong Kong)": "Kantonees (Hongkong)",
|
"Cantonese (Hong Kong)": "Kantonees (Hongkong)",
|
||||||
"Chinese (China)": "Chinees (China)",
|
"Chinese (China)": "Chinees (China)",
|
||||||
"crash_page_read_the_faq": "de <a href=\"`x`\">veelgestelde vragen (FAQ)</a> gelezen hebt",
|
"crash_page_read_the_faq": "de <a href=\"`x`\">veelgestelde vragen (FAQ)</a> gelezen hebt",
|
||||||
|
@ -477,7 +477,7 @@
|
||||||
"Song: ": "Lied: ",
|
"Song: ": "Lied: ",
|
||||||
"generic_channels_count": "{{count}} kanaal",
|
"generic_channels_count": "{{count}} kanaal",
|
||||||
"generic_channels_count_plural": "{{count}} kanalen",
|
"generic_channels_count_plural": "{{count}} kanalen",
|
||||||
"Popular enabled: ": "Populair geactiveerd: ",
|
"Popular enabled: ": "Populair ingeschakeld: ",
|
||||||
"channel_tab_playlists_label": "Afspeellijsten",
|
"channel_tab_playlists_label": "Afspeellijsten",
|
||||||
"generic_button_edit": "Bewerken",
|
"generic_button_edit": "Bewerken",
|
||||||
"Music in this video": "Muziek in deze video",
|
"Music in this video": "Muziek in deze video",
|
||||||
|
@ -496,5 +496,10 @@
|
||||||
"Answer": "Antwoorden",
|
"Answer": "Antwoorden",
|
||||||
"Search for videos": "Naar video's zoeken",
|
"Search for videos": "Naar video's zoeken",
|
||||||
"carousel_skip": "Carousel overslaan",
|
"carousel_skip": "Carousel overslaan",
|
||||||
"toggle_theme": "Thema omschakelen"
|
"toggle_theme": "Thema omschakelen",
|
||||||
|
"preferences_preload_label": "Videogegevens vooraf laden: ",
|
||||||
|
"Filipino (auto-generated)": "Filipijns (automatisch gegenereerd)",
|
||||||
|
"channel_tab_courses_label": "Cursussen",
|
||||||
|
"First page": "Eerste pagina",
|
||||||
|
"channel_tab_posts_label": "Gepost"
|
||||||
}
|
}
|
||||||
|
|
|
@ -478,7 +478,7 @@
|
||||||
"search_filters_date_label": "Data przesłania",
|
"search_filters_date_label": "Data przesłania",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_filters_date_option_none": "Dowolna data",
|
"search_filters_date_option_none": "Dowolna data",
|
||||||
"search_message_use_another_instance": " Możesz także <a href=\"`x`\">wyszukać w innej instancji</a>.",
|
"search_message_use_another_instance": "Możesz także <a href=\"`x`\">wyszukać w innej instancji</a>.",
|
||||||
"search_filters_type_option_all": "Dowolny typ",
|
"search_filters_type_option_all": "Dowolny typ",
|
||||||
"search_filters_duration_option_none": "Dowolna długość",
|
"search_filters_duration_option_none": "Dowolna długość",
|
||||||
"search_filters_duration_option_medium": "Średnia (4-20 minut)",
|
"search_filters_duration_option_medium": "Średnia (4-20 minut)",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"Add to playlist: ": "Dodaj do playlisty: ",
|
"Add to playlist: ": "Dodaj do playlisty: ",
|
||||||
"carousel_slide": "Slajd {{current}} z {{total}}",
|
"carousel_slide": "Slajd {{current}} z {{total}}",
|
||||||
"carousel_skip": "Pomiń karuzelę",
|
"carousel_skip": "Pomiń karuzelę",
|
||||||
"carousel_go_to": "Przejdź do slajdu `x`"
|
"carousel_go_to": "Przejdź do slajdu `x`",
|
||||||
|
"preferences_preload_label": "Wstępne ładowanie danych wideo: ",
|
||||||
|
"Filipino (auto-generated)": "filipiński (wygenerowany automatycznie)",
|
||||||
|
"First page": "Pierwsza strona",
|
||||||
|
"channel_tab_posts_label": "Posty",
|
||||||
|
"channel_tab_courses_label": "Kursy"
|
||||||
}
|
}
|
||||||
|
|
|
@ -474,7 +474,7 @@
|
||||||
"Spanish (auto-generated)": "Espanhol (gerado automaticamente)",
|
"Spanish (auto-generated)": "Espanhol (gerado automaticamente)",
|
||||||
"Spanish (Mexico)": "Espanhol (México)",
|
"Spanish (Mexico)": "Espanhol (México)",
|
||||||
"search_filters_duration_option_none": "Qualquer duração",
|
"search_filters_duration_option_none": "Qualquer duração",
|
||||||
"search_message_use_another_instance": " Você também pode <a href=\"`x`\">pesquisar em outra instância</a>.",
|
"search_message_use_another_instance": "Você também pode <a href=\"`x`\">pesquisar em outra instância</a>.",
|
||||||
"Spanish (Spain)": "Espanhol (Espanha)",
|
"Spanish (Spain)": "Espanhol (Espanha)",
|
||||||
"Turkish (auto-generated)": "Turco (gerado automaticamente)",
|
"Turkish (auto-generated)": "Turco (gerado automaticamente)",
|
||||||
"search_filters_duration_option_medium": "Médio (4 - 20 minutos)",
|
"search_filters_duration_option_medium": "Médio (4 - 20 minutos)",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"Answer": "Resposta",
|
"Answer": "Resposta",
|
||||||
"carousel_slide": "Slide {{current}} de {{total}}",
|
"carousel_slide": "Slide {{current}} de {{total}}",
|
||||||
"carousel_skip": "Ignorar carrossel",
|
"carousel_skip": "Ignorar carrossel",
|
||||||
"carousel_go_to": "Ir ao slide `x`"
|
"carousel_go_to": "Ir ao slide `x`",
|
||||||
|
"preferences_preload_label": "Pré-carregar dados do vídeo: ",
|
||||||
|
"Filipino (auto-generated)": "Filipino (gerado automaticamente)",
|
||||||
|
"channel_tab_posts_label": "Postagens",
|
||||||
|
"First page": "Primeira página",
|
||||||
|
"channel_tab_courses_label": "Cursos"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
{
|
{
|
||||||
"LIVE": "Em direto",
|
"LIVE": "Direto",
|
||||||
"Shared `x` ago": "Partilhado `x` atrás",
|
"Shared `x` ago": "Partilhado `x` atrás",
|
||||||
"Unsubscribe": "Anular subscrição",
|
"Unsubscribe": "Anular subscrição",
|
||||||
"Subscribe": "Subscrever",
|
"Subscribe": "Subscrever",
|
||||||
"View channel on YouTube": "Ver canal no YouTube",
|
"View channel on YouTube": "Ver canal no YouTube",
|
||||||
"View playlist on YouTube": "Ver lista de reprodução no YouTube",
|
"View playlist on YouTube": "Ver lista de reprodução no YouTube",
|
||||||
"newest": "mais recentes",
|
"newest": "recentes",
|
||||||
"oldest": "mais antigos",
|
"oldest": "antigos",
|
||||||
"popular": "popular",
|
"popular": "populares",
|
||||||
"last": "últimos",
|
"last": "últimos",
|
||||||
"Next page": "Próxima página",
|
"Next page": "Página seguinte",
|
||||||
"Previous page": "Página anterior",
|
"Previous page": "Página anterior",
|
||||||
"Clear watch history?": "Limpar histórico de reprodução?",
|
"Clear watch history?": "Limpar histórico de reprodução?",
|
||||||
"New password": "Nova palavra-chave",
|
"New password": "Nova palavra-passe",
|
||||||
"New passwords must match": "As novas palavra-chaves devem corresponder",
|
"New passwords must match": "As novas palavras-passe devem ser iguais",
|
||||||
"Authorize token?": "Autorizar token?",
|
"Authorize token?": "Autorizar 'token'?",
|
||||||
"Authorize token for `x`?": "Autorizar token para `x`?",
|
"Authorize token for `x`?": "Autorizar 'token' para `x`?",
|
||||||
"Yes": "Sim",
|
"Yes": "Sim",
|
||||||
"No": "Não",
|
"No": "Não",
|
||||||
"Import and Export Data": "Importar e exportar dados",
|
"Import and Export Data": "Importar e exportar dados",
|
||||||
"Import": "Importar",
|
"Import": "Importar",
|
||||||
"Import Invidious data": "Importar dados JSON do Invidious",
|
"Import Invidious data": "Importar dados JSON do Invidious",
|
||||||
"Import YouTube subscriptions": "Importar subscrições do YouTube/OPML",
|
"Import YouTube subscriptions": "Importar via YouTube csv ou subscrição OPML",
|
||||||
"Import FreeTube subscriptions (.db)": "Importar subscrições do FreeTube (.db)",
|
"Import FreeTube subscriptions (.db)": "Importar subscrições do FreeTube (.db)",
|
||||||
"Import NewPipe subscriptions (.json)": "Importar subscrições do NewPipe (.json)",
|
"Import NewPipe subscriptions (.json)": "Importar subscrições do NewPipe (.json)",
|
||||||
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
|
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
|
||||||
|
@ -32,38 +32,38 @@
|
||||||
"Delete account?": "Eliminar conta?",
|
"Delete account?": "Eliminar conta?",
|
||||||
"History": "Histórico",
|
"History": "Histórico",
|
||||||
"An alternative front-end to YouTube": "Uma interface alternativa ao YouTube",
|
"An alternative front-end to YouTube": "Uma interface alternativa ao YouTube",
|
||||||
"JavaScript license information": "Informação de licença do JavaScript",
|
"JavaScript license information": "Informação da licença JavaScript",
|
||||||
"source": "código-fonte",
|
"source": "fonte",
|
||||||
"Log in": "Iniciar sessão",
|
"Log in": "Iniciar sessão",
|
||||||
"Log in/register": "Iniciar sessão/registar",
|
"Log in/register": "Iniciar sessão/registar",
|
||||||
"User ID": "Utilizador",
|
"User ID": "Utilizador",
|
||||||
"Password": "Palavra-chave",
|
"Password": "Palavra-passe",
|
||||||
"Time (h:mm:ss):": "Tempo (h:mm:ss):",
|
"Time (h:mm:ss):": "Tempo (h:mm:ss):",
|
||||||
"Text CAPTCHA": "Texto CAPTCHA",
|
"Text CAPTCHA": "Texto CAPTCHA",
|
||||||
"Image CAPTCHA": "Imagem CAPTCHA",
|
"Image CAPTCHA": "Imagem CAPTCHA",
|
||||||
"Sign In": "Iniciar sessão",
|
"Sign In": "Entrar",
|
||||||
"Register": "Registar",
|
"Register": "Registar",
|
||||||
"E-mail": "E-mail",
|
"E-mail": "E-mail",
|
||||||
"Preferences": "Preferências",
|
"Preferences": "Preferências",
|
||||||
"preferences_category_player": "Preferências do reprodutor",
|
"preferences_category_player": "Preferências do reprodutor",
|
||||||
"preferences_video_loop_label": "Repetir sempre: ",
|
"preferences_video_loop_label": "Repetir sempre: ",
|
||||||
"preferences_autoplay_label": "Reprodução automática: ",
|
"preferences_autoplay_label": "Reprodução automática: ",
|
||||||
"preferences_continue_label": "Reproduzir sempre o próximo: ",
|
"preferences_continue_label": "Reproduzir sempre o seguinte: ",
|
||||||
"preferences_continue_autoplay_label": "Reproduzir próximo vídeo automaticamente: ",
|
"preferences_continue_autoplay_label": "Reproduzir próximo vídeo automaticamente: ",
|
||||||
"preferences_listen_label": "Apenas áudio: ",
|
"preferences_listen_label": "Apenas áudio: ",
|
||||||
"preferences_local_label": "Usar proxy nos vídeos: ",
|
"preferences_local_label": "Usar proxy nos vídeos: ",
|
||||||
"preferences_speed_label": "Velocidade preferida: ",
|
"preferences_speed_label": "Velocidade preferida: ",
|
||||||
"preferences_quality_label": "Qualidade de vídeo preferida: ",
|
"preferences_quality_label": "Qualidade de vídeo preferida: ",
|
||||||
"preferences_volume_label": "Volume da reprodução: ",
|
"preferences_volume_label": "Volume de reprodução: ",
|
||||||
"preferences_comments_label": "Preferência dos comentários: ",
|
"preferences_comments_label": "Comentários padrão: ",
|
||||||
"youtube": "YouTube",
|
"youtube": "YouTube",
|
||||||
"reddit": "Reddit",
|
"reddit": "Reddit",
|
||||||
"preferences_captions_label": "Legendas predefinidas: ",
|
"preferences_captions_label": "Legendas padrão: ",
|
||||||
"Fallback captions: ": "Legendas alternativas: ",
|
"Fallback captions: ": "Legendas alternativas: ",
|
||||||
"preferences_related_videos_label": "Mostrar vídeos relacionados: ",
|
"preferences_related_videos_label": "Mostrar vídeos relacionados: ",
|
||||||
"preferences_annotations_label": "Mostrar anotações sempre: ",
|
"preferences_annotations_label": "Mostrar anotações sempre: ",
|
||||||
"preferences_extend_desc_label": "Estender automaticamente a descrição do vídeo: ",
|
"preferences_extend_desc_label": "Expandir automaticamente a descrição do vídeo: ",
|
||||||
"preferences_vr_mode_label": "Vídeos interativos de 360 graus (necessita de WebGL): ",
|
"preferences_vr_mode_label": "Vídeos interativos de 360 graus (requer WebGL): ",
|
||||||
"preferences_category_visual": "Preferências visuais",
|
"preferences_category_visual": "Preferências visuais",
|
||||||
"preferences_player_style_label": "Estilo do reprodutor: ",
|
"preferences_player_style_label": "Estilo do reprodutor: ",
|
||||||
"Dark mode: ": "Modo escuro: ",
|
"Dark mode: ": "Modo escuro: ",
|
||||||
|
@ -74,9 +74,9 @@
|
||||||
"preferences_category_misc": "Preferências diversas",
|
"preferences_category_misc": "Preferências diversas",
|
||||||
"preferences_automatic_instance_redirect_label": "Redirecionamento de instância automática (solução de último recurso para redirect.invidious.io): ",
|
"preferences_automatic_instance_redirect_label": "Redirecionamento de instância automática (solução de último recurso para redirect.invidious.io): ",
|
||||||
"preferences_category_subscription": "Preferências de subscrições",
|
"preferences_category_subscription": "Preferências de subscrições",
|
||||||
"preferences_annotations_subscribed_label": "Mostrar sempre anotações aos canais subscritos: ",
|
"preferences_annotations_subscribed_label": "Mostrar sempre anotações nos canais subscritos: ",
|
||||||
"Redirect homepage to feed: ": "Redirecionar página inicial para subscrições: ",
|
"Redirect homepage to feed: ": "Redirecionar página inicial para subscrições: ",
|
||||||
"preferences_max_results_label": "Quantidade de vídeos nas subscrições: ",
|
"preferences_max_results_label": "Número de vídeos nas subscrições: ",
|
||||||
"preferences_sort_label": "Ordenar vídeos por: ",
|
"preferences_sort_label": "Ordenar vídeos por: ",
|
||||||
"published": "publicado",
|
"published": "publicado",
|
||||||
"published - reverse": "publicado - inverso",
|
"published - reverse": "publicado - inverso",
|
||||||
|
@ -88,19 +88,19 @@
|
||||||
"Only show latest unwatched video from channel: ": "Mostrar apenas vídeos mais recentes não visualizados do canal: ",
|
"Only show latest unwatched video from channel: ": "Mostrar apenas vídeos mais recentes não visualizados do canal: ",
|
||||||
"preferences_unseen_only_label": "Mostrar apenas vídeos não visualizados: ",
|
"preferences_unseen_only_label": "Mostrar apenas vídeos não visualizados: ",
|
||||||
"preferences_notifications_only_label": "Mostrar apenas notificações (se existirem): ",
|
"preferences_notifications_only_label": "Mostrar apenas notificações (se existirem): ",
|
||||||
"Enable web notifications": "Ativar notificações pela web",
|
"Enable web notifications": "Ativar notificações web",
|
||||||
"`x` uploaded a video": "`x` publicou um novo vídeo",
|
"`x` uploaded a video": "`x` publicou um vídeo",
|
||||||
"`x` is live": "`x` está em direto",
|
"`x` is live": "`x` está em direto",
|
||||||
"preferences_category_data": "Preferências de dados",
|
"preferences_category_data": "Preferências de dados",
|
||||||
"Clear watch history": "Limpar histórico de reprodução",
|
"Clear watch history": "Limpar histórico de reprodução",
|
||||||
"Import/export data": "Importar / exportar dados",
|
"Import/export data": "Importar/exportar dados",
|
||||||
"Change password": "Alterar palavra-chave",
|
"Change password": "Alterar palavra-passe",
|
||||||
"Manage subscriptions": "Gerir as subscrições",
|
"Manage subscriptions": "Gerir subscrições",
|
||||||
"Manage tokens": "Gerir tokens",
|
"Manage tokens": "Gerir tokens",
|
||||||
"Watch history": "Histórico de reprodução",
|
"Watch history": "Histórico de reprodução",
|
||||||
"Delete account": "Eliminar conta",
|
"Delete account": "Eliminar conta",
|
||||||
"preferences_category_admin": "Preferências de administrador",
|
"preferences_category_admin": "Preferências de administrador",
|
||||||
"preferences_default_home_label": "Página inicial predefinida: ",
|
"preferences_default_home_label": "Página inicial padrão: ",
|
||||||
"preferences_feed_menu_label": "Menu de subscrições: ",
|
"preferences_feed_menu_label": "Menu de subscrições: ",
|
||||||
"preferences_show_nick_label": "Mostrar nome de utilizador em cima: ",
|
"preferences_show_nick_label": "Mostrar nome de utilizador em cima: ",
|
||||||
"Top enabled: ": "Destaques ativados: ",
|
"Top enabled: ": "Destaques ativados: ",
|
||||||
|
@ -109,28 +109,29 @@
|
||||||
"Registration enabled: ": "Registar ativado: ",
|
"Registration enabled: ": "Registar ativado: ",
|
||||||
"Report statistics: ": "Relatório de estatísticas: ",
|
"Report statistics: ": "Relatório de estatísticas: ",
|
||||||
"Save preferences": "Guardar preferências",
|
"Save preferences": "Guardar preferências",
|
||||||
"Subscription manager": "Gerir subscrições",
|
"Subscription manager": "Gestor de subscrições",
|
||||||
"Token manager": "Gerir tokens",
|
"Token manager": "Gestor de tokens",
|
||||||
"Token": "Token",
|
"Token": "Token",
|
||||||
"tokens_count": "{{count}} token",
|
"tokens_count_0": "{{count}} token",
|
||||||
"tokens_count_plural": "{{count}} tokens",
|
"tokens_count_1": "{{count}} tokens",
|
||||||
"Import/export": "Importar / exportar",
|
"tokens_count_2": "{{count}} tokens",
|
||||||
|
"Import/export": "Importar/exportar",
|
||||||
"unsubscribe": "anular subscrição",
|
"unsubscribe": "anular subscrição",
|
||||||
"revoke": "revogar",
|
"revoke": "revogar",
|
||||||
"Subscriptions": "Subscrições",
|
"Subscriptions": "Subscrições",
|
||||||
"search": "pesquisar",
|
"search": "pesquisar",
|
||||||
"Log out": "Terminar sessão",
|
"Log out": "Terminar sessão",
|
||||||
"Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no GitHub.",
|
"Released under the AGPLv3 on Github.": "Disponibilizada sob a AGPLv3 no GitHub.",
|
||||||
"Source available here.": "Código-fonte disponível aqui.",
|
"Source available here.": "Código-fonte disponível aqui.",
|
||||||
"View JavaScript license information.": "Ver informações da licença do JavaScript.",
|
"View JavaScript license information.": "Ver informações da licença JavaScript.",
|
||||||
"View privacy policy.": "Ver a política de privacidade.",
|
"View privacy policy.": "Ver política de privacidade.",
|
||||||
"Trending": "Tendências",
|
"Trending": "Tendências",
|
||||||
"Public": "Público",
|
"Public": "Público",
|
||||||
"Unlisted": "Não listado",
|
"Unlisted": "Não listado",
|
||||||
"Private": "Privado",
|
"Private": "Privado",
|
||||||
"View all playlists": "Ver todas as listas de reprodução",
|
"View all playlists": "Ver todas as listas de reprodução",
|
||||||
"Updated `x` ago": "Atualizado `x` atrás",
|
"Updated `x` ago": "Atualizado há `x`",
|
||||||
"Delete playlist `x`?": "Eliminar a lista de reprodução `x`?",
|
"Delete playlist `x`?": "Eliminar lista de reprodução `x`?",
|
||||||
"Delete playlist": "Eliminar lista de reprodução",
|
"Delete playlist": "Eliminar lista de reprodução",
|
||||||
"Create playlist": "Criar lista de reprodução",
|
"Create playlist": "Criar lista de reprodução",
|
||||||
"Title": "Título",
|
"Title": "Título",
|
||||||
|
@ -139,7 +140,7 @@
|
||||||
"Show more": "Mostrar mais",
|
"Show more": "Mostrar mais",
|
||||||
"Show less": "Mostrar menos",
|
"Show less": "Mostrar menos",
|
||||||
"Watch on YouTube": "Ver no YouTube",
|
"Watch on YouTube": "Ver no YouTube",
|
||||||
"Switch Invidious Instance": "Mudar a instância do Invidious",
|
"Switch Invidious Instance": "Alterar instância Invidious",
|
||||||
"Hide annotations": "Ocultar anotações",
|
"Hide annotations": "Ocultar anotações",
|
||||||
"Show annotations": "Mostrar anotações",
|
"Show annotations": "Mostrar anotações",
|
||||||
"Genre: ": "Género: ",
|
"Genre: ": "Género: ",
|
||||||
|
@ -150,27 +151,27 @@
|
||||||
"Whitelisted regions: ": "Regiões permitidas: ",
|
"Whitelisted regions: ": "Regiões permitidas: ",
|
||||||
"Blacklisted regions: ": "Regiões bloqueadas: ",
|
"Blacklisted regions: ": "Regiões bloqueadas: ",
|
||||||
"Shared `x`": "Partilhado `x`",
|
"Shared `x`": "Partilhado `x`",
|
||||||
"Premieres in `x`": "Estreias em `x`",
|
"Premieres in `x`": "Estreia a `x`",
|
||||||
"Premieres `x`": "Estreias `x`",
|
"Premieres `x`": "Estreia `x`",
|
||||||
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Olá! Parece que o JavaScript está desativado. Clique aqui para ver os comentários, entretanto eles podem levar mais tempo para carregar.",
|
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Olá! Parece que o JavaScript está desativado. Clique aqui para ver os comentários, mas tenha e conta que podem levar mais tempo para carregar.",
|
||||||
"View YouTube comments": "Ver comentários do YouTube",
|
"View YouTube comments": "Ver comentários do YouTube",
|
||||||
"View more comments on Reddit": "Ver mais comentários no Reddit",
|
"View more comments on Reddit": "Ver mais comentários no Reddit",
|
||||||
"View `x` comments": {
|
"View `x` comments": {
|
||||||
"([^.,0-9]|^)1([^.,0-9]|$)": "Ver `x` comentários",
|
"([^.,0-9]|^)1([^.,0-9]|$)": "Ver `x` comentário",
|
||||||
"": "Ver `x` comentários"
|
"": "Ver `x` comentários"
|
||||||
},
|
},
|
||||||
"View Reddit comments": "Ver comentários do Reddit",
|
"View Reddit comments": "Ver comentários do Reddit",
|
||||||
"Hide replies": "Ocultar respostas",
|
"Hide replies": "Ocultar respostas",
|
||||||
"Show replies": "Mostrar respostas",
|
"Show replies": "Mostrar respostas",
|
||||||
"Incorrect password": "Palavra-chave incorreta",
|
"Incorrect password": "Palavra-passe incorreta",
|
||||||
"Wrong answer": "Resposta errada",
|
"Wrong answer": "Resposta errada",
|
||||||
"Erroneous CAPTCHA": "CAPTCHA inválido",
|
"Erroneous CAPTCHA": "CAPTCHA inválido",
|
||||||
"CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
|
"CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
|
||||||
"User ID is a required field": "O nome de utilizador é um campo obrigatório",
|
"User ID is a required field": "O nome de utilizador é um campo obrigatório",
|
||||||
"Password is a required field": "Palavra-chave é um campo obrigatório",
|
"Password is a required field": "Palavra-passe é um campo obrigatório",
|
||||||
"Wrong username or password": "Nome de utilizador ou palavra-chave incorreto",
|
"Wrong username or password": "Nome de utilizador ou palavra-passe incorreta",
|
||||||
"Password cannot be empty": "A palavra-chave não pode estar vazia",
|
"Password cannot be empty": "A palavra-passe não pode estar vazia",
|
||||||
"Password cannot be longer than 55 characters": "A palavra-chave não pode ser superior a 55 caracteres",
|
"Password cannot be longer than 55 characters": "A palavra-passe não pode ter mais do que 55 caracteres",
|
||||||
"Please log in": "Por favor, inicie sessão",
|
"Please log in": "Por favor, inicie sessão",
|
||||||
"Invidious Private Feed for `x`": "Feed Privado do Invidious para `x`",
|
"Invidious Private Feed for `x`": "Feed Privado do Invidious para `x`",
|
||||||
"channel:`x`": "canal:`x`",
|
"channel:`x`": "canal:`x`",
|
||||||
|
@ -180,20 +181,20 @@
|
||||||
"Could not fetch comments": "Não foi possível obter os comentários",
|
"Could not fetch comments": "Não foi possível obter os comentários",
|
||||||
"`x` ago": "`x` atrás",
|
"`x` ago": "`x` atrás",
|
||||||
"Load more": "Carregar mais",
|
"Load more": "Carregar mais",
|
||||||
"Could not create mix.": "Não foi possível criar a mistura.",
|
"Could not create mix.": "Não foi possível criar o mix.",
|
||||||
"Empty playlist": "Lista de reprodução vazia",
|
"Empty playlist": "Lista de reprodução vazia",
|
||||||
"Not a playlist.": "Não é uma lista de reprodução.",
|
"Not a playlist.": "Não é uma lista de reprodução.",
|
||||||
"Playlist does not exist.": "A lista de reprodução não existe.",
|
"Playlist does not exist.": "A lista de reprodução não existe.",
|
||||||
"Could not pull trending pages.": "Não foi possível obter as páginas de tendências.",
|
"Could not pull trending pages.": "Não foi possível obter a página de tendências.",
|
||||||
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
|
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
|
||||||
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é um campo obrigatório",
|
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é um campo obrigatório",
|
||||||
"Erroneous challenge": "Desafio inválido",
|
"Erroneous challenge": "Desafio inválido",
|
||||||
"Erroneous token": "Token inválido",
|
"Erroneous token": "Token inválido",
|
||||||
"No such user": "Utilizador inválido",
|
"No such user": "Utilizador inválido",
|
||||||
"Token is expired, please try again": "Token expirou, tente novamente",
|
"Token is expired, please try again": "Token caducado, tente novamente",
|
||||||
"English": "Inglês",
|
"English": "Inglês",
|
||||||
"English (auto-generated)": "Inglês (auto-gerado)",
|
"English (auto-generated)": "Inglês (auto-gerado)",
|
||||||
"Afrikaans": "Africano",
|
"Afrikaans": "Africânder",
|
||||||
"Albanian": "Albanês",
|
"Albanian": "Albanês",
|
||||||
"Amharic": "Amárico",
|
"Amharic": "Amárico",
|
||||||
"Arabic": "Árabe",
|
"Arabic": "Árabe",
|
||||||
|
@ -209,7 +210,7 @@
|
||||||
"Cebuano": "Cebuano",
|
"Cebuano": "Cebuano",
|
||||||
"Chinese (Simplified)": "Chinês (simplificado)",
|
"Chinese (Simplified)": "Chinês (simplificado)",
|
||||||
"Chinese (Traditional)": "Chinês (tradicional)",
|
"Chinese (Traditional)": "Chinês (tradicional)",
|
||||||
"Corsican": "Corso",
|
"Corsican": "Córsego",
|
||||||
"Croatian": "Croata",
|
"Croatian": "Croata",
|
||||||
"Czech": "Checo",
|
"Czech": "Checo",
|
||||||
"Danish": "Dinamarquês",
|
"Danish": "Dinamarquês",
|
||||||
|
@ -252,7 +253,7 @@
|
||||||
"Macedonian": "Macedónio",
|
"Macedonian": "Macedónio",
|
||||||
"Malagasy": "Malgaxe",
|
"Malagasy": "Malgaxe",
|
||||||
"Malay": "Malaio",
|
"Malay": "Malaio",
|
||||||
"Malayalam": "Malaiala",
|
"Malayalam": "Malaialaio",
|
||||||
"Maltese": "Maltês",
|
"Maltese": "Maltês",
|
||||||
"Maori": "Maori",
|
"Maori": "Maori",
|
||||||
"Marathi": "Marathi",
|
"Marathi": "Marathi",
|
||||||
|
@ -297,30 +298,37 @@
|
||||||
"Yiddish": "Iídiche",
|
"Yiddish": "Iídiche",
|
||||||
"Yoruba": "Ioruba",
|
"Yoruba": "Ioruba",
|
||||||
"Zulu": "Zulu",
|
"Zulu": "Zulu",
|
||||||
"generic_count_years": "{{count}} ano",
|
"generic_count_years_0": "{{count}} ano",
|
||||||
"generic_count_years_plural": "{{count}} anos",
|
"generic_count_years_1": "{{count}} anos",
|
||||||
"generic_count_months": "{{count}} mês",
|
"generic_count_years_2": "{{count}} anos",
|
||||||
"generic_count_months_plural": "{{count}} meses",
|
"generic_count_months_0": "{{count}} mês",
|
||||||
"generic_count_weeks": "{{count}} seman",
|
"generic_count_months_1": "{{count}} meses",
|
||||||
"generic_count_weeks_plural": "{{count}} semanas",
|
"generic_count_months_2": "{{count}} meses",
|
||||||
"generic_count_days": "{{count}} dia",
|
"generic_count_weeks_0": "{{count}} semana",
|
||||||
"generic_count_days_plural": "{{count}} dias",
|
"generic_count_weeks_1": "{{count}} semanas",
|
||||||
"generic_count_hours": "{{count}} hora",
|
"generic_count_weeks_2": "{{count}} semanas",
|
||||||
"generic_count_hours_plural": "{{count}} horas",
|
"generic_count_days_0": "{{count}} dia",
|
||||||
"generic_count_minutes": "{{count}} minuto",
|
"generic_count_days_1": "{{count}} dias",
|
||||||
"generic_count_minutes_plural": "{{count}} minutos",
|
"generic_count_days_2": "{{count}} dias",
|
||||||
"generic_count_seconds": "{{count}} segundo",
|
"generic_count_hours_0": "{{count}} hora",
|
||||||
"generic_count_seconds_plural": "{{count}} segundos",
|
"generic_count_hours_1": "{{count}} horas",
|
||||||
"Fallback comments: ": "Comentários alternativos: ",
|
"generic_count_hours_2": "{{count}} horas",
|
||||||
|
"generic_count_minutes_0": "{{count}} minuto",
|
||||||
|
"generic_count_minutes_1": "{{count}} minutos",
|
||||||
|
"generic_count_minutes_2": "{{count}} minutos",
|
||||||
|
"generic_count_seconds_0": "{{count}} segundo",
|
||||||
|
"generic_count_seconds_1": "{{count}} segundos",
|
||||||
|
"generic_count_seconds_2": "{{count}} segundos",
|
||||||
|
"Fallback comments: ": "Alternativa para comentários: ",
|
||||||
"Popular": "Popular",
|
"Popular": "Popular",
|
||||||
"Search": "Pesquisar",
|
"Search": "Pesquisar",
|
||||||
"Top": "Destaques",
|
"Top": "Destaques",
|
||||||
"About": "Sobre",
|
"About": "Acerca",
|
||||||
"Rating: ": "Avaliação: ",
|
"Rating: ": "Avaliação: ",
|
||||||
"preferences_locale_label": "Idioma: ",
|
"preferences_locale_label": "Idioma: ",
|
||||||
"View as playlist": "Ver como lista de reprodução",
|
"View as playlist": "Ver como lista de reprodução",
|
||||||
"Default": "Predefinido",
|
"Default": "Padrão",
|
||||||
"Music": "Música",
|
"Music": "Músicas",
|
||||||
"Gaming": "Jogos",
|
"Gaming": "Jogos",
|
||||||
"News": "Notícias",
|
"News": "Notícias",
|
||||||
"Movies": "Filmes",
|
"Movies": "Filmes",
|
||||||
|
@ -328,9 +336,9 @@
|
||||||
"Download as: ": "Descarregar como: ",
|
"Download as: ": "Descarregar como: ",
|
||||||
"%A %B %-d, %Y": "%A %B %-d, %Y",
|
"%A %B %-d, %Y": "%A %B %-d, %Y",
|
||||||
"(edited)": "(editado)",
|
"(edited)": "(editado)",
|
||||||
"YouTube comment permalink": "Hiperligação permanente do comentário no YouTube",
|
"YouTube comment permalink": "Ligação permanente do comentário no YouTube",
|
||||||
"permalink": "hiperligação permanente",
|
"permalink": "ligação permanente",
|
||||||
"`x` marked it with a ❤": "`x` foi marcado como ❤",
|
"`x` marked it with a ❤": "`x` foi marcado com um ❤",
|
||||||
"Audio mode": "Modo de áudio",
|
"Audio mode": "Modo de áudio",
|
||||||
"Video mode": "Modo de vídeo",
|
"Video mode": "Modo de vídeo",
|
||||||
"channel_tab_videos_label": "Vídeos",
|
"channel_tab_videos_label": "Vídeos",
|
||||||
|
@ -338,7 +346,7 @@
|
||||||
"channel_tab_community_label": "Comunidade",
|
"channel_tab_community_label": "Comunidade",
|
||||||
"search_filters_sort_option_relevance": "Relevância",
|
"search_filters_sort_option_relevance": "Relevância",
|
||||||
"search_filters_sort_option_rating": "Avaliação",
|
"search_filters_sort_option_rating": "Avaliação",
|
||||||
"search_filters_sort_option_date": "Data de envio",
|
"search_filters_sort_option_date": "Data de carregamento",
|
||||||
"search_filters_sort_option_views": "Visualizações",
|
"search_filters_sort_option_views": "Visualizações",
|
||||||
"search_filters_type_label": "Tipo",
|
"search_filters_type_label": "Tipo",
|
||||||
"search_filters_duration_label": "Duração",
|
"search_filters_duration_label": "Duração",
|
||||||
|
@ -353,38 +361,44 @@
|
||||||
"search_filters_type_option_channel": "Canal",
|
"search_filters_type_option_channel": "Canal",
|
||||||
"search_filters_type_option_playlist": "Lista de reprodução",
|
"search_filters_type_option_playlist": "Lista de reprodução",
|
||||||
"search_filters_type_option_movie": "Filme",
|
"search_filters_type_option_movie": "Filme",
|
||||||
"search_filters_type_option_show": "Espetáculo",
|
"search_filters_type_option_show": "Séries",
|
||||||
"search_filters_features_option_hd": "HD",
|
"search_filters_features_option_hd": "HD",
|
||||||
"search_filters_features_option_subtitles": "Legendas",
|
"search_filters_features_option_subtitles": "Legendas",
|
||||||
"search_filters_features_option_c_commons": "Creative Commons",
|
"search_filters_features_option_c_commons": "Creative Commons",
|
||||||
"search_filters_features_option_three_d": "3D",
|
"search_filters_features_option_three_d": "3D",
|
||||||
"search_filters_features_option_live": "Em direto",
|
"search_filters_features_option_live": "Direto",
|
||||||
"search_filters_features_option_four_k": "4K",
|
"search_filters_features_option_four_k": "4K",
|
||||||
"search_filters_features_option_location": "Localização",
|
"search_filters_features_option_location": "Localização",
|
||||||
"search_filters_features_option_hdr": "HDR",
|
"search_filters_features_option_hdr": "HDR",
|
||||||
"Current version: ": "Versão atual: ",
|
"Current version: ": "Versão atual: ",
|
||||||
"next_steps_error_message": "Pode tentar as seguintes opções: ",
|
"next_steps_error_message": "Pode tentar as seguintes opções: ",
|
||||||
"next_steps_error_message_refresh": "Atualizar",
|
"next_steps_error_message_refresh": "Recarregar",
|
||||||
"next_steps_error_message_go_to_youtube": "Ir ao YouTube",
|
"next_steps_error_message_go_to_youtube": "Ir para o YouTube",
|
||||||
"search_filters_title": "Filtro",
|
"search_filters_title": "Filtro",
|
||||||
"generic_videos_count": "{{count}} vídeo",
|
"generic_videos_count_0": "{{count}} vídeo",
|
||||||
"generic_videos_count_plural": "{{count}} vídeos",
|
"generic_videos_count_1": "{{count}} vídeos",
|
||||||
"generic_playlists_count": "{{count}} lista de reprodução",
|
"generic_videos_count_2": "{{count}} vídeos",
|
||||||
"generic_playlists_count_plural": "{{count}} listas de reprodução",
|
"generic_playlists_count_0": "{{count}} lista de reprodução",
|
||||||
"generic_subscriptions_count": "{{count}} inscrição",
|
"generic_playlists_count_1": "{{count}} listas de reprodução",
|
||||||
"generic_subscriptions_count_plural": "{{count}} inscrições",
|
"generic_playlists_count_2": "{{count}} listas de reprodução",
|
||||||
"generic_views_count": "{{count}} visualização",
|
"generic_subscriptions_count_0": "{{count}} subscrição",
|
||||||
"generic_views_count_plural": "{{count}} visualizações",
|
"generic_subscriptions_count_1": "{{count}} subscrições",
|
||||||
"generic_subscribers_count": "{{count}} inscrito",
|
"generic_subscriptions_count_2": "{{count}} subscrições",
|
||||||
"generic_subscribers_count_plural": "{{count}} inscritos",
|
"generic_views_count_0": "{{count}} visualização",
|
||||||
|
"generic_views_count_1": "{{count}} visualizações",
|
||||||
|
"generic_views_count_2": "{{count}} visualizações",
|
||||||
|
"generic_subscribers_count_0": "{{count}} subscritor",
|
||||||
|
"generic_subscribers_count_1": "{{count}} subscritores",
|
||||||
|
"generic_subscribers_count_2": "{{count}} subscritores",
|
||||||
"preferences_quality_dash_option_4320p": "4320p",
|
"preferences_quality_dash_option_4320p": "4320p",
|
||||||
"preferences_quality_dash_label": "Qualidade de vídeo DASH preferida: ",
|
"preferences_quality_dash_label": "Qualidade de vídeo DASH preferida: ",
|
||||||
"preferences_quality_dash_option_2160p": "2160p",
|
"preferences_quality_dash_option_2160p": "2160p",
|
||||||
"subscriptions_unseen_notifs_count": "{{count}} notificação não vista",
|
"subscriptions_unseen_notifs_count_0": "{{count}} notificação não vista",
|
||||||
"subscriptions_unseen_notifs_count_plural": "{{count}} notificações não vistas",
|
"subscriptions_unseen_notifs_count_1": "{{count}} notificações não vistas",
|
||||||
|
"subscriptions_unseen_notifs_count_2": "{{count}} notificações não vistas",
|
||||||
"Popular enabled: ": "Página \"popular\" ativada: ",
|
"Popular enabled: ": "Página \"popular\" ativada: ",
|
||||||
"search_message_no_results": "Nenhum resultado encontrado.",
|
"search_message_no_results": "Nenhum resultado encontrado.",
|
||||||
"preferences_quality_dash_option_auto": "Automático",
|
"preferences_quality_dash_option_auto": "Automática",
|
||||||
"preferences_region_label": "País do conteúdo: ",
|
"preferences_region_label": "País do conteúdo: ",
|
||||||
"preferences_quality_dash_option_1440p": "1440p",
|
"preferences_quality_dash_option_1440p": "1440p",
|
||||||
"preferences_quality_dash_option_720p": "720p",
|
"preferences_quality_dash_option_720p": "720p",
|
||||||
|
@ -403,10 +417,12 @@
|
||||||
"preferences_quality_dash_option_240p": "240p",
|
"preferences_quality_dash_option_240p": "240p",
|
||||||
"Video unavailable": "Vídeo não disponível",
|
"Video unavailable": "Vídeo não disponível",
|
||||||
"Russian (auto-generated)": "Russo (gerado automaticamente)",
|
"Russian (auto-generated)": "Russo (gerado automaticamente)",
|
||||||
"comments_view_x_replies": "Ver {{count}} resposta",
|
"comments_view_x_replies_0": "Ver {{count}} resposta",
|
||||||
"comments_view_x_replies_plural": "Ver {{count}} respostas",
|
"comments_view_x_replies_1": "Ver {{count}} respostas",
|
||||||
"comments_points_count": "{{count}} ponto",
|
"comments_view_x_replies_2": "Ver {{count}} respostas",
|
||||||
"comments_points_count_plural": "{{count}} pontos",
|
"comments_points_count_0": "{{count}} ponto",
|
||||||
|
"comments_points_count_1": "{{count}} pontos",
|
||||||
|
"comments_points_count_2": "{{count}} pontos",
|
||||||
"English (United Kingdom)": "Inglês (Reino Unido)",
|
"English (United Kingdom)": "Inglês (Reino Unido)",
|
||||||
"Chinese (Hong Kong)": "Chinês (Hong Kong)",
|
"Chinese (Hong Kong)": "Chinês (Hong Kong)",
|
||||||
"Chinese (Taiwan)": "Chinês (Taiwan)",
|
"Chinese (Taiwan)": "Chinês (Taiwan)",
|
||||||
|
@ -432,13 +448,13 @@
|
||||||
"videoinfo_watch_on_youTube": "Ver no YouTube",
|
"videoinfo_watch_on_youTube": "Ver no YouTube",
|
||||||
"videoinfo_youTube_embed_link": "Incorporar",
|
"videoinfo_youTube_embed_link": "Incorporar",
|
||||||
"adminprefs_modified_source_code_url_label": "URL do repositório do código-fonte alterado",
|
"adminprefs_modified_source_code_url_label": "URL do repositório do código-fonte alterado",
|
||||||
"videoinfo_invidious_embed_link": "Incorporar hiperligação",
|
"videoinfo_invidious_embed_link": "Incorporar ligação",
|
||||||
"none": "nenhum",
|
"none": "nenhum",
|
||||||
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão há `x`",
|
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão há `x`",
|
||||||
"download_subtitles": "Legendas - `x` (.vtt)",
|
"download_subtitles": "Legendas - `x` (.vtt)",
|
||||||
"user_created_playlists": "`x` listas de reprodução criadas",
|
"user_created_playlists": "`x` listas de reprodução criadas",
|
||||||
"user_saved_playlists": "`x` listas de reprodução guardadas",
|
"user_saved_playlists": "`x` listas de reprodução guardadas",
|
||||||
"preferences_save_player_pos_label": "Guardar a posição de reprodução atual do vídeo: ",
|
"preferences_save_player_pos_label": "Guardar posição de reprodução: ",
|
||||||
"Turkish (auto-generated)": "Turco (gerado automaticamente)",
|
"Turkish (auto-generated)": "Turco (gerado automaticamente)",
|
||||||
"Cantonese (Hong Kong)": "Cantonês (Hong Kong)",
|
"Cantonese (Hong Kong)": "Cantonês (Hong Kong)",
|
||||||
"Chinese (China)": "Chinês (China)",
|
"Chinese (China)": "Chinês (China)",
|
||||||
|
@ -455,21 +471,52 @@
|
||||||
"search_filters_date_option_none": "Qualquer data",
|
"search_filters_date_option_none": "Qualquer data",
|
||||||
"search_filters_features_option_three_sixty": "360°",
|
"search_filters_features_option_three_sixty": "360°",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_message_use_another_instance": " Também pode <a href=\"`x`\">pesquisar noutra instância</a>.",
|
"search_message_use_another_instance": "Também pode <a href=\"`x`\">pesquisar noutra instância</a>.",
|
||||||
"crash_page_you_found_a_bug": "Parece que encontrou um erro no Invidious!",
|
"crash_page_you_found_a_bug": "Parece que encontrou um erro no Invidious!",
|
||||||
"crash_page_before_reporting": "Antes de reportar um erro, verifique se:",
|
"crash_page_before_reporting": "Antes de reportar um erro, verifique se:",
|
||||||
"crash_page_read_the_faq": "leia as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>",
|
"crash_page_read_the_faq": "leu as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>",
|
||||||
"crash_page_search_issue": "procurou se <a href=\"`x`\">o erro já foi reportado no GitHub</a>",
|
"crash_page_search_issue": "procurou se <a href=\"`x`\">o erro já foi reportado no GitHub</a>",
|
||||||
"crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto tal qual (NÃO o traduza):",
|
"crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto (NÃO o traduza):",
|
||||||
"search_message_change_filters_or_query": "Tente alargar os termos genéricos da pesquisa e/ou alterar os filtros.",
|
"search_message_change_filters_or_query": "Tente alargar os termos genéricos da pesquisa e/ou alterar os filtros.",
|
||||||
"crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>",
|
"crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>",
|
||||||
"crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>",
|
"crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>",
|
||||||
"error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para a página inicial da lista de reprodução.</a>",
|
"error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para voltar à página inicial da lista de reprodução.</a>",
|
||||||
"Artist: ": "Artista: ",
|
"Artist: ": "Artista: ",
|
||||||
"Album: ": "Álbum: ",
|
"Album: ": "Álbum: ",
|
||||||
"channel_tab_streams_label": "Diretos",
|
"channel_tab_streams_label": "Emissões em direto",
|
||||||
"channel_tab_playlists_label": "Listas de reprodução",
|
"channel_tab_playlists_label": "Listas de reprodução",
|
||||||
"channel_tab_channels_label": "Canais",
|
"channel_tab_channels_label": "Canais",
|
||||||
"Music in this video": "Música neste vídeo",
|
"Music in this video": "Música neste vídeo",
|
||||||
"channel_tab_shorts_label": "Curtos"
|
"channel_tab_shorts_label": "Curtos",
|
||||||
|
"generic_button_delete": "Eliminar",
|
||||||
|
"generic_button_edit": "Editar",
|
||||||
|
"generic_button_save": "Guardar",
|
||||||
|
"generic_button_cancel": "Cancelar",
|
||||||
|
"Import YouTube playlist (.csv)": "Importar lista de reprodução do YouTube (.csv)",
|
||||||
|
"Song: ": "Canção: ",
|
||||||
|
"Answer": "Responder",
|
||||||
|
"The Popular feed has been disabled by the administrator.": "O feed Popular foi desativado por um administrador.",
|
||||||
|
"Channel Sponsor": "Patrocinador do canal",
|
||||||
|
"Download is disabled": "A descarga está desativada",
|
||||||
|
"Add to playlist": "Adicionar à lista de reprodução",
|
||||||
|
"Add to playlist: ": "Adicionar à lista de reprodução: ",
|
||||||
|
"Search for videos": "Procurar vídeos",
|
||||||
|
"generic_channels_count_0": "{{count}} canal",
|
||||||
|
"generic_channels_count_1": "{{count}} canais",
|
||||||
|
"generic_channels_count_2": "{{count}} canais",
|
||||||
|
"generic_button_rss": "RSS",
|
||||||
|
"Import YouTube watch history (.json)": "Importar histórico de reprodução do YouTube (.json)",
|
||||||
|
"preferences_preload_label": "Pré-carregamento dos dados: ",
|
||||||
|
"playlist_button_add_items": "Adicionar vídeos",
|
||||||
|
"channel_tab_podcasts_label": "Podcasts",
|
||||||
|
"channel_tab_releases_label": "Lançamentos",
|
||||||
|
"carousel_slide": "Diapositivo {{current}} de{{total}}",
|
||||||
|
"carousel_skip": "Ignorar carrossel",
|
||||||
|
"carousel_go_to": "Ir para o diapositivo`x`",
|
||||||
|
"First page": "Primeira página",
|
||||||
|
"Standard YouTube license": "Licença padrão do YouTube",
|
||||||
|
"Filipino (auto-generated)": "Filipino (gerado automaticamente)",
|
||||||
|
"channel_tab_courses_label": "Cursos",
|
||||||
|
"channel_tab_posts_label": "Publicações",
|
||||||
|
"toggle_theme": "Trocar tema"
|
||||||
}
|
}
|
||||||
|
|
|
@ -448,7 +448,7 @@
|
||||||
"Chinese (Taiwan)": "Chinês (Taiwan)",
|
"Chinese (Taiwan)": "Chinês (Taiwan)",
|
||||||
"search_message_no_results": "Nenhum resultado encontrado.",
|
"search_message_no_results": "Nenhum resultado encontrado.",
|
||||||
"search_message_change_filters_or_query": "Tente alargar os termos genéricos da pesquisa e/ou alterar os filtros.",
|
"search_message_change_filters_or_query": "Tente alargar os termos genéricos da pesquisa e/ou alterar os filtros.",
|
||||||
"search_message_use_another_instance": " Também pode <a href=\"`x`\">pesquisar noutra instância</a>.",
|
"search_message_use_another_instance": "Também pode <a href=\"`x`\">pesquisar noutra instância</a>.",
|
||||||
"English (United Kingdom)": "Inglês (Reino Unido)",
|
"English (United Kingdom)": "Inglês (Reino Unido)",
|
||||||
"English (United States)": "Inglês (Estados Unidos)",
|
"English (United States)": "Inglês (Estados Unidos)",
|
||||||
"Cantonese (Hong Kong)": "Cantonês (Hong Kong)",
|
"Cantonese (Hong Kong)": "Cantonês (Hong Kong)",
|
||||||
|
@ -508,10 +508,15 @@
|
||||||
"toggle_theme": "Trocar tema",
|
"toggle_theme": "Trocar tema",
|
||||||
"Add to playlist": "Adicionar à lista de reprodução",
|
"Add to playlist": "Adicionar à lista de reprodução",
|
||||||
"Add to playlist: ": "Adicionar à lista de reprodução: ",
|
"Add to playlist: ": "Adicionar à lista de reprodução: ",
|
||||||
"Answer": "Resposta",
|
"Answer": "Responder",
|
||||||
"Search for videos": "Procurar vídeos",
|
"Search for videos": "Procurar vídeos",
|
||||||
"carousel_slide": "Diapositivo {{current}} de{{total}}",
|
"carousel_slide": "Diapositivo {{current}} de{{total}}",
|
||||||
"carousel_skip": "Ignorar carrossel",
|
"carousel_skip": "Ignorar carrossel",
|
||||||
"carousel_go_to": "Ir para o diapositivo`x`",
|
"carousel_go_to": "Ir para o diapositivo`x`",
|
||||||
"The Popular feed has been disabled by the administrator.": "O feed Popular foi desativado por um administrador."
|
"The Popular feed has been disabled by the administrator.": "O feed Popular foi desativado por um administrador.",
|
||||||
|
"preferences_preload_label": "Pré-carregamento dos dados: ",
|
||||||
|
"Filipino (auto-generated)": "Filipino (gerado automaticamente)",
|
||||||
|
"First page": "Primeira página",
|
||||||
|
"channel_tab_courses_label": "Cursos",
|
||||||
|
"channel_tab_posts_label": "Publicações"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"last": "последние",
|
"last": "последние",
|
||||||
"Next page": "Следующая страница",
|
"Next page": "Следующая страница",
|
||||||
"Previous page": "Предыдущая страница",
|
"Previous page": "Предыдущая страница",
|
||||||
|
"First page": "Первая страница",
|
||||||
"Clear watch history?": "Очистить историю просмотров?",
|
"Clear watch history?": "Очистить историю просмотров?",
|
||||||
"New password": "Новый пароль",
|
"New password": "Новый пароль",
|
||||||
"New passwords must match": "Новые пароли не совпадают",
|
"New passwords must match": "Новые пароли не совпадают",
|
||||||
|
@ -48,8 +49,8 @@
|
||||||
"preferences_category_player": "Настройки проигрывателя",
|
"preferences_category_player": "Настройки проигрывателя",
|
||||||
"preferences_video_loop_label": "Всегда повторять: ",
|
"preferences_video_loop_label": "Всегда повторять: ",
|
||||||
"preferences_autoplay_label": "Автовоспроизведение: ",
|
"preferences_autoplay_label": "Автовоспроизведение: ",
|
||||||
"preferences_continue_label": "Переходить к следующему видео? ",
|
"preferences_continue_label": "Воспроизводить следующее видео: ",
|
||||||
"preferences_continue_autoplay_label": "Автопроигрывание следующего видео: ",
|
"preferences_continue_autoplay_label": "Автовоспроизведение следующего видео: ",
|
||||||
"preferences_listen_label": "Режим «только аудио» по умолчанию: ",
|
"preferences_listen_label": "Режим «только аудио» по умолчанию: ",
|
||||||
"preferences_local_label": "Проигрывать видео через прокси? ",
|
"preferences_local_label": "Проигрывать видео через прокси? ",
|
||||||
"preferences_speed_label": "Скорость видео по умолчанию: ",
|
"preferences_speed_label": "Скорость видео по умолчанию: ",
|
||||||
|
@ -474,7 +475,7 @@
|
||||||
"search_filters_date_option_none": "Любая дата",
|
"search_filters_date_option_none": "Любая дата",
|
||||||
"search_filters_date_label": "Дата загрузки",
|
"search_filters_date_label": "Дата загрузки",
|
||||||
"search_message_no_results": "Ничего не найдено.",
|
"search_message_no_results": "Ничего не найдено.",
|
||||||
"search_message_use_another_instance": " Дополнительно вы можете <a href=\"`x`\">поискать на других зеркалах</a>.",
|
"search_message_use_another_instance": "Дополнительно вы можете <a href=\"`x`\">поискать на других зеркалах</a>.",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_message_change_filters_or_query": "Попробуйте расширить поисковый запрос и/или изменить фильтры.",
|
"search_message_change_filters_or_query": "Попробуйте расширить поисковый запрос и/или изменить фильтры.",
|
||||||
"search_filters_duration_option_medium": "Средние (4 - 20 минут)",
|
"search_filters_duration_option_medium": "Средние (4 - 20 минут)",
|
||||||
|
@ -509,6 +510,12 @@
|
||||||
"Add to playlist: ": "Добавить в плейлист: ",
|
"Add to playlist: ": "Добавить в плейлист: ",
|
||||||
"Answer": "Ответить",
|
"Answer": "Ответить",
|
||||||
"Search for videos": "Поиск видео",
|
"Search for videos": "Поиск видео",
|
||||||
"The Popular feed has been disabled by the administrator.": "Популярная лента была отключена администратором.",
|
"The Popular feed has been disabled by the administrator.": "Лента популярного была отключена администратором.",
|
||||||
"toggle_theme": "Переключатель тем"
|
"toggle_theme": "Переключатель тем",
|
||||||
|
"carousel_slide": "Пролистано {{current}} из {{total}}",
|
||||||
|
"carousel_skip": "Пропустить всё",
|
||||||
|
"carousel_go_to": "Перейти к странице `x`",
|
||||||
|
"preferences_preload_label": "Предзагрузка видеоданных: ",
|
||||||
|
"channel_tab_courses_label": "Курсы",
|
||||||
|
"channel_tab_posts_label": "Записи"
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"Import and Export Data": "Uvoz in izvoz podatkov",
|
"Import and Export Data": "Uvoz in izvoz podatkov",
|
||||||
"Import": "Uvozi",
|
"Import": "Uvozi",
|
||||||
"Import Invidious data": "Uvozi Invidious JSON podatke",
|
"Import Invidious data": "Uvozi Invidious JSON podatke",
|
||||||
"Import YouTube subscriptions": "Uvozi YouTube/OPML naročnine",
|
"Import YouTube subscriptions": "Uvozi YouTube CSV ali OPML naročnine",
|
||||||
"Import FreeTube subscriptions (.db)": "Uvozi FreeTube (.db) naročnine",
|
"Import FreeTube subscriptions (.db)": "Uvozi FreeTube (.db) naročnine",
|
||||||
"Import NewPipe data (.zip)": "Uvozi NewPipe (.zip) podatke",
|
"Import NewPipe data (.zip)": "Uvozi NewPipe (.zip) podatke",
|
||||||
"Export": "Izvozi",
|
"Export": "Izvozi",
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
"Show more": "Pokaži več",
|
"Show more": "Pokaži več",
|
||||||
"Switch Invidious Instance": "Preklopi Invidious instanco",
|
"Switch Invidious Instance": "Preklopi Invidious instanco",
|
||||||
"search_message_change_filters_or_query": "Poskusi razširiti iskalno poizvedbo in/ali spremeniti filtre.",
|
"search_message_change_filters_or_query": "Poskusi razširiti iskalno poizvedbo in/ali spremeniti filtre.",
|
||||||
"search_message_use_another_instance": " Lahko tudi <a href=\"`x`\">iščeš v drugi istanci</a>.",
|
"search_message_use_another_instance": "Lahko tudi <a href=\"`x`\">iščeš v drugi istanci</a>.",
|
||||||
"Wilson score: ": "Wilsonov rezultat: ",
|
"Wilson score: ": "Wilsonov rezultat: ",
|
||||||
"Engagement: ": "Sodelovanje: ",
|
"Engagement: ": "Sodelovanje: ",
|
||||||
"Blacklisted regions: ": "Regije na seznamu nedovoljenih: ",
|
"Blacklisted regions: ": "Regije na seznamu nedovoljenih: ",
|
||||||
|
@ -462,7 +462,7 @@
|
||||||
"search_filters_features_option_four_k": "4K",
|
"search_filters_features_option_four_k": "4K",
|
||||||
"search_filters_features_option_hdr": "HDR",
|
"search_filters_features_option_hdr": "HDR",
|
||||||
"next_steps_error_message_refresh": "Osveži",
|
"next_steps_error_message_refresh": "Osveži",
|
||||||
"search_filters_date_option_hour": "Zadnja ura",
|
"search_filters_date_option_hour": "V zadnji uri",
|
||||||
"search_filters_features_option_purchased": "Kupljeno",
|
"search_filters_features_option_purchased": "Kupljeno",
|
||||||
"search_filters_sort_label": "Razvrsti po",
|
"search_filters_sort_label": "Razvrsti po",
|
||||||
"search_filters_sort_option_views": "številu ogledov",
|
"search_filters_sort_option_views": "številu ogledov",
|
||||||
|
@ -521,5 +521,16 @@
|
||||||
"generic_channels_count_1": "{{count}} kanala",
|
"generic_channels_count_1": "{{count}} kanala",
|
||||||
"generic_channels_count_2": "{{count}} kanali",
|
"generic_channels_count_2": "{{count}} kanali",
|
||||||
"generic_channels_count_3": "{{count}} kanalov",
|
"generic_channels_count_3": "{{count}} kanalov",
|
||||||
"Import YouTube watch history (.json)": "Uvozi zgodovino gledanja YouTube (.json)"
|
"Import YouTube watch history (.json)": "Uvozi zgodovino gledanja YouTube (.json)",
|
||||||
|
"Add to playlist": "Dodaj na seznam predvajanja",
|
||||||
|
"Add to playlist: ": "Dodaj na seznam predvajanja: ",
|
||||||
|
"Search for videos": "Iskanje videoposnetkov",
|
||||||
|
"The Popular feed has been disabled by the administrator.": "Administrator je onemogočil priljubljeni vir.",
|
||||||
|
"Answer": "Odgovor",
|
||||||
|
"Filipino (auto-generated)": "filipinščina (samodejno ustvarjeno)",
|
||||||
|
"toggle_theme": "Preklopi temo",
|
||||||
|
"carousel_slide": "Diapozitiv {{current}} od {{total}}",
|
||||||
|
"carousel_skip": "Preskoči galerijo",
|
||||||
|
"carousel_go_to": "Pojdi na diapozitiv `x`",
|
||||||
|
"preferences_preload_label": "Predhodno naloži video podatke: "
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,13 +257,13 @@
|
||||||
"Video mode": "Mënyrë video",
|
"Video mode": "Mënyrë video",
|
||||||
"channel_tab_videos_label": "Video",
|
"channel_tab_videos_label": "Video",
|
||||||
"search_filters_sort_option_rating": "Vlerësim",
|
"search_filters_sort_option_rating": "Vlerësim",
|
||||||
"search_filters_sort_option_date": "Datë Ngarkimi",
|
"search_filters_sort_option_date": "Datë ngarkimi",
|
||||||
"search_filters_sort_option_views": "Numër parjesh",
|
"search_filters_sort_option_views": "Numër parjesh",
|
||||||
"search_filters_type_label": "Lloj",
|
"search_filters_type_label": "Lloj",
|
||||||
"search_filters_duration_label": "Kohëzgjatje",
|
"search_filters_duration_label": "Kohëzgjatje",
|
||||||
"search_filters_features_label": "Veçori",
|
"search_filters_features_label": "Veçori",
|
||||||
"search_filters_sort_label": "Renditi Sipas",
|
"search_filters_sort_label": "Renditi Sipas",
|
||||||
"search_filters_date_option_hour": "Orën e Fundit",
|
"search_filters_date_option_hour": "Orën e fundit",
|
||||||
"search_filters_date_option_today": "Sot",
|
"search_filters_date_option_today": "Sot",
|
||||||
"search_filters_duration_option_long": "E gjatë (> 20 minuta)",
|
"search_filters_duration_option_long": "E gjatë (> 20 minuta)",
|
||||||
"search_filters_features_option_hd": "HD",
|
"search_filters_features_option_hd": "HD",
|
||||||
|
@ -435,14 +435,14 @@
|
||||||
"tokens_count_plural": "{{count}} tokenë",
|
"tokens_count_plural": "{{count}} tokenë",
|
||||||
"preferences_save_player_pos_label": "Mba mend pozicionin e luajtjes: ",
|
"preferences_save_player_pos_label": "Mba mend pozicionin e luajtjes: ",
|
||||||
"Import Invidious data": "Importoni të dhëna JSON Invidious",
|
"Import Invidious data": "Importoni të dhëna JSON Invidious",
|
||||||
"Import YouTube subscriptions": "Importoni pajtime YouTube/OPML",
|
"Import YouTube subscriptions": "Importoni pajtime YouTube CSV ose OPML",
|
||||||
"Export data as JSON": "Eksportoji të dhënat Invidious si JSON",
|
"Export data as JSON": "Eksportoji të dhënat Invidious si JSON",
|
||||||
"preferences_vr_mode_label": "Video me ndërveprim 360 gradë (lyp WebGL): ",
|
"preferences_vr_mode_label": "Video me ndërveprim 360 gradë (lyp WebGL): ",
|
||||||
"Shared `x`": "Ndarë me të tjerë më `x`",
|
"Shared `x`": "Ndarë me të tjerë më `x`",
|
||||||
"search_filters_title": "Filtra",
|
"search_filters_title": "Filtra",
|
||||||
"Popular enabled: ": "Me populloret të aktivizuara: ",
|
"Popular enabled: ": "Me populloret të aktivizuara: ",
|
||||||
"error_video_not_in_playlist": "Videoja e kërkuar s’ekziston në këtë luajlistë. <a href=\"`x`\">Klikoni këtu për faqen hyrëse të luajlistës.</a>",
|
"error_video_not_in_playlist": "Videoja e kërkuar s’ekziston në këtë luajlistë. <a href=\"`x`\">Klikoni këtu për faqen hyrëse të luajlistës.</a>",
|
||||||
"search_message_use_another_instance": " Mundeni edhe të <a href=\"`x`\">kërkoni në një instancë tjetër</a>.",
|
"search_message_use_another_instance": "Mundeni edhe të <a href=\"`x`\">kërkoni në një instancë tjetër</a>.",
|
||||||
"search_filters_date_label": "Datë ngarkimi",
|
"search_filters_date_label": "Datë ngarkimi",
|
||||||
"preferences_watch_history_label": "Aktivizo historik parjesh: ",
|
"preferences_watch_history_label": "Aktivizo historik parjesh: ",
|
||||||
"Top enabled: ": "Me kryesueset të aktivizuara: ",
|
"Top enabled: ": "Me kryesueset të aktivizuara: ",
|
||||||
|
@ -484,5 +484,19 @@
|
||||||
"Import YouTube watch history (.json)": "Importo historik parjesh YouTube (.json)",
|
"Import YouTube watch history (.json)": "Importo historik parjesh YouTube (.json)",
|
||||||
"preferences_local_label": "Video përmes ndërmjetësi: ",
|
"preferences_local_label": "Video përmes ndërmjetësi: ",
|
||||||
"Fallback captions: ": "Titra nga halli: ",
|
"Fallback captions: ": "Titra nga halli: ",
|
||||||
"Erroneous challenge": "Zgjidhje e gabuar"
|
"Erroneous challenge": "Zgjidhje e gabuar",
|
||||||
|
"Add to playlist: ": "Shtoje te luajlistë: ",
|
||||||
|
"Add to playlist": "Shtoje te luajlistë",
|
||||||
|
"Answer": "Përgjigje",
|
||||||
|
"Search for videos": "Kërko për video",
|
||||||
|
"The Popular feed has been disabled by the administrator.": "Prurja Popullore është çaktivizuar nga përgjegjësi.",
|
||||||
|
"carousel_skip": "Anashkaloje Rrotullamen",
|
||||||
|
"carousel_slide": "Diapozitiv {{current}} nga {{total}}",
|
||||||
|
"carousel_go_to": "Kalo te diapozitivi `x`",
|
||||||
|
"Filipino (auto-generated)": "Filipineze (të prodhuara automatikisht)",
|
||||||
|
"preferences_preload_label": "Parangarko të dhëna videoje: ",
|
||||||
|
"toggle_theme": "Ndërroni Temë",
|
||||||
|
"channel_tab_courses_label": "Kurse",
|
||||||
|
"channel_tab_posts_label": "Postime",
|
||||||
|
"First page": "Faqja e parë"
|
||||||
}
|
}
|
||||||
|
|
|
@ -404,7 +404,7 @@
|
||||||
"generic_count_months_0": "{{count}} mesec",
|
"generic_count_months_0": "{{count}} mesec",
|
||||||
"generic_count_months_1": "{{count}} meseca",
|
"generic_count_months_1": "{{count}} meseca",
|
||||||
"generic_count_months_2": "{{count}} meseci",
|
"generic_count_months_2": "{{count}} meseci",
|
||||||
"search_message_use_another_instance": " Takođe, možete <a href=\"`x`\">pretraživati na drugoj instanci</a>.",
|
"search_message_use_another_instance": "Takođe, možete <a href=\"`x`\">pretraživati na drugoj instanci</a>.",
|
||||||
"generic_subscribers_count_0": "{{count}} pratilac",
|
"generic_subscribers_count_0": "{{count}} pratilac",
|
||||||
"generic_subscribers_count_1": "{{count}} pratioca",
|
"generic_subscribers_count_1": "{{count}} pratioca",
|
||||||
"generic_subscribers_count_2": "{{count}} pratilaca",
|
"generic_subscribers_count_2": "{{count}} pratilaca",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"Answer": "Odgovor",
|
"Answer": "Odgovor",
|
||||||
"Search for videos": "Pretražite video snimke",
|
"Search for videos": "Pretražite video snimke",
|
||||||
"carousel_skip": "Preskoči karusel",
|
"carousel_skip": "Preskoči karusel",
|
||||||
"toggle_theme": "Подеси тему"
|
"toggle_theme": "Podesi temu",
|
||||||
|
"preferences_preload_label": "Unapred učitaj podatke o video snimku: ",
|
||||||
|
"Filipino (auto-generated)": "Filipinski (automatski generisano)",
|
||||||
|
"channel_tab_posts_label": "Objave",
|
||||||
|
"First page": "Prva stranica",
|
||||||
|
"channel_tab_courses_label": "Kursevi"
|
||||||
}
|
}
|
||||||
|
|
|
@ -404,7 +404,7 @@
|
||||||
"generic_count_months_0": "{{count}} месец",
|
"generic_count_months_0": "{{count}} месец",
|
||||||
"generic_count_months_1": "{{count}} месеца",
|
"generic_count_months_1": "{{count}} месеца",
|
||||||
"generic_count_months_2": "{{count}} месеци",
|
"generic_count_months_2": "{{count}} месеци",
|
||||||
"search_message_use_another_instance": " Такође, можете <a href=\"`x`\">претраживати на другој инстанци</a>.",
|
"search_message_use_another_instance": "Такође, можете <a href=\"`x`\">претраживати на другој инстанци</a>.",
|
||||||
"generic_subscribers_count_0": "{{count}} пратилац",
|
"generic_subscribers_count_0": "{{count}} пратилац",
|
||||||
"generic_subscribers_count_1": "{{count}} пратиоца",
|
"generic_subscribers_count_1": "{{count}} пратиоца",
|
||||||
"generic_subscribers_count_2": "{{count}} пратилаца",
|
"generic_subscribers_count_2": "{{count}} пратилаца",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"Add to playlist: ": "Додајте на плејлисту: ",
|
"Add to playlist: ": "Додајте на плејлисту: ",
|
||||||
"carousel_skip": "Прескочи карусел",
|
"carousel_skip": "Прескочи карусел",
|
||||||
"The Popular feed has been disabled by the administrator.": "Администратор је онемогућио фид „Популарно“.",
|
"The Popular feed has been disabled by the administrator.": "Администратор је онемогућио фид „Популарно“.",
|
||||||
"carousel_slide": "Слајд {{current}} од {{total}}"
|
"carousel_slide": "Слајд {{current}} од {{total}}",
|
||||||
|
"preferences_preload_label": "Унапред учитај податке о видео снимку: ",
|
||||||
|
"Filipino (auto-generated)": "Филипински (аутоматски генерисано)",
|
||||||
|
"channel_tab_courses_label": "Курсеви",
|
||||||
|
"First page": "Прва страница",
|
||||||
|
"channel_tab_posts_label": "Објаве"
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,13 +320,13 @@
|
||||||
"channel_tab_community_label": "Gemenskap",
|
"channel_tab_community_label": "Gemenskap",
|
||||||
"search_filters_sort_option_relevance": "Relevans",
|
"search_filters_sort_option_relevance": "Relevans",
|
||||||
"search_filters_sort_option_rating": "Rankning",
|
"search_filters_sort_option_rating": "Rankning",
|
||||||
"search_filters_sort_option_date": "Uppladdnings Datum",
|
"search_filters_sort_option_date": "Uppladdnings datum",
|
||||||
"search_filters_sort_option_views": "Visningar",
|
"search_filters_sort_option_views": "Visningar",
|
||||||
"search_filters_type_label": "Typ",
|
"search_filters_type_label": "Typ",
|
||||||
"search_filters_duration_label": "Varaktighet",
|
"search_filters_duration_label": "Varaktighet",
|
||||||
"search_filters_features_label": "Funktioner",
|
"search_filters_features_label": "Funktioner",
|
||||||
"search_filters_sort_label": "Sortera efter",
|
"search_filters_sort_label": "Sortera efter",
|
||||||
"search_filters_date_option_hour": "Senaste Timmen",
|
"search_filters_date_option_hour": "Senaste timmen",
|
||||||
"search_filters_date_option_today": "Idag",
|
"search_filters_date_option_today": "Idag",
|
||||||
"search_filters_date_option_week": "Denna vecka",
|
"search_filters_date_option_week": "Denna vecka",
|
||||||
"search_filters_date_option_month": "Denna månad",
|
"search_filters_date_option_month": "Denna månad",
|
||||||
|
@ -393,7 +393,7 @@
|
||||||
"Artist: ": "Artist: ",
|
"Artist: ": "Artist: ",
|
||||||
"generic_count_months": "{{count}}månad",
|
"generic_count_months": "{{count}}månad",
|
||||||
"generic_count_months_plural": "{{count}}månader",
|
"generic_count_months_plural": "{{count}}månader",
|
||||||
"search_message_use_another_instance": " Du kan också <a href=\"`x`\">söka på en annan instans</a>.",
|
"search_message_use_another_instance": "Du kan också <a href=\"`x`\">söka på en annan instans</a>.",
|
||||||
"generic_subscribers_count": "{{count}} prenumerant",
|
"generic_subscribers_count": "{{count}} prenumerant",
|
||||||
"generic_subscribers_count_plural": "{{count}} prenumeranter",
|
"generic_subscribers_count_plural": "{{count}} prenumeranter",
|
||||||
"download_subtitles": "Undertexter - `x` (.vtt)",
|
"download_subtitles": "Undertexter - `x` (.vtt)",
|
||||||
|
@ -496,5 +496,10 @@
|
||||||
"The Popular feed has been disabled by the administrator.": "Det populära flödet har inaktiverats av administratören.",
|
"The Popular feed has been disabled by the administrator.": "Det populära flödet har inaktiverats av administratören.",
|
||||||
"carousel_slide": "Bildspel {{current}} av {{total}}",
|
"carousel_slide": "Bildspel {{current}} av {{total}}",
|
||||||
"carousel_skip": "Hoppa över karusellen",
|
"carousel_skip": "Hoppa över karusellen",
|
||||||
"carousel_go_to": "Gå till bildspel `x`"
|
"carousel_go_to": "Gå till bildspel `x`",
|
||||||
|
"preferences_preload_label": "Förladda video data: ",
|
||||||
|
"Filipino (auto-generated)": "Filippinska (auto-genererad)",
|
||||||
|
"First page": "Första sidan",
|
||||||
|
"channel_tab_courses_label": "Kurser",
|
||||||
|
"channel_tab_posts_label": "Inlägg"
|
||||||
}
|
}
|
||||||
|
|
502
locales/ta.json
Normal file
502
locales/ta.json
Normal file
|
@ -0,0 +1,502 @@
|
||||||
|
{
|
||||||
|
"Add to playlist": "பிளேலிச்ட்டில் சேர்க்கவும்",
|
||||||
|
"generic_channels_count": "{{count}} சேனல்",
|
||||||
|
"generic_channels_count_plural": "{{count}} சேனல்கள்",
|
||||||
|
"generic_views_count": "{{count}} பார்வை",
|
||||||
|
"generic_views_count_plural": "{{count}} காட்சிகள்",
|
||||||
|
"generic_videos_count": "{{count}} வீடியோ",
|
||||||
|
"generic_videos_count_plural": "{{count}} வீடியோக்கள்",
|
||||||
|
"generic_playlists_count": "{{count}} பிளேலிச்ட்",
|
||||||
|
"generic_playlists_count_plural": "{{count}} பிளேலிச்ட்கள்",
|
||||||
|
"generic_subscribers_count": "{{count}} சந்தாதாரர்",
|
||||||
|
"generic_subscribers_count_plural": "{{count}} சந்தாதாரர்கள்",
|
||||||
|
"generic_button_delete": "நீக்கு",
|
||||||
|
"generic_button_rss": "ஆர்.எச்.எச்",
|
||||||
|
"LIVE": "வாழ",
|
||||||
|
"Shared `x` ago": "`X` முன்பு பகிரப்பட்டது",
|
||||||
|
"Unsubscribe": "குழுவிலகவும்",
|
||||||
|
"View playlist on YouTube": "யூடியூப்பில் பிளேலிச்ட்டைக் காண்க",
|
||||||
|
"newest": "புதியது",
|
||||||
|
"oldest": "பழமையானது",
|
||||||
|
"popular": "மக்கள்",
|
||||||
|
"last": "கடைசி",
|
||||||
|
"Next page": "அடுத்த பக்கம்",
|
||||||
|
"Previous page": "முந்தைய பக்கம்",
|
||||||
|
"Clear watch history?": "தெளிவான கண்காணிப்பு வரலாறு?",
|
||||||
|
"New password": "புதிய கடவுச்சொல்",
|
||||||
|
"New passwords must match": "புதிய கடவுச்சொற்கள் பொருந்த வேண்டும்",
|
||||||
|
"Authorize token?": "கிள்ளாக்கை அங்கீகரிக்கவா?",
|
||||||
|
"Yes": "ஆம்",
|
||||||
|
"Import YouTube playlist (.csv)": "யூடியூப் பிளேலிச்ட்டை இறக்குமதி செய்க (.csv)",
|
||||||
|
"Import YouTube watch history (.json)": "YouTube வாட்ச் வரலாற்றை இறக்குமதி செய்க (.json)",
|
||||||
|
"Import Invidious data": "வன்கவர்வு சாதொபொகு தரவை இறக்குமதி செய்க",
|
||||||
|
"Import YouTube subscriptions": "YouTube காபிம அல்லது OPML சந்தாக்களை இறக்குமதி செய்க",
|
||||||
|
"Import FreeTube subscriptions (.db)": "ஃப்ரீட்யூப் சந்தாக்களை இறக்குமதி செய்க (.db)",
|
||||||
|
"Import NewPipe data (.zip)": "நியூபைப் தரவை இறக்குமதி செய்க (.zip)",
|
||||||
|
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML ஆக சந்தாக்களை ஏற்றுமதி செய்யுங்கள் (நியூபைப் & ஃப்ரீட்யூப்பிற்கு)",
|
||||||
|
"Export subscriptions as OPML": "OPML ஆக சந்தாக்களை ஏற்றுமதி செய்யுங்கள்",
|
||||||
|
"Export data as JSON": "சாதொபொகு ஆக வன்கவர்வு தரவை ஏற்றுமதி செய்யுங்கள்",
|
||||||
|
"Delete account?": "கணக்கை நீக்கவா?",
|
||||||
|
"History": "வரலாறு",
|
||||||
|
"JavaScript license information": "சாவாச்கிரிப்ட் உரிம செய்தி",
|
||||||
|
"source": "மூலம்",
|
||||||
|
"An alternative front-end to YouTube": "YouTube க்கு ஒரு மாற்று முன் இறுதியில்",
|
||||||
|
"Log in": "புகுபதிகை",
|
||||||
|
"Log in/register": "உள்நுழைக/பதிவு செய்யுங்கள்",
|
||||||
|
"User ID": "பயனர் ஐடி",
|
||||||
|
"Password": "கடவுச்சொல்",
|
||||||
|
"Time (h:mm:ss):": "நேரம் (h: மிமீ: எச்எச்):",
|
||||||
|
"Sign In": "விடுபதிகை",
|
||||||
|
"Register": "பதிவு செய்யுங்கள்",
|
||||||
|
"E-mail": "மின்னஞ்சல்",
|
||||||
|
"Preferences": "விருப்பத்தேர்வுகள்",
|
||||||
|
"preferences_preload_label": "வீடியோ தரவை முன்பே ஏற்றவும்: ",
|
||||||
|
"preferences_autoplay_label": "தன்னியக்க: ",
|
||||||
|
"preferences_continue_label": "இயல்பாக அடுத்து விளையாடுங்கள்: ",
|
||||||
|
"preferences_local_label": "பதிலாள் வீடியோக்கள்: ",
|
||||||
|
"preferences_watch_history_label": "கண்காணிப்பு வரலாற்றை இயக்கு: ",
|
||||||
|
"preferences_speed_label": "இயல்புநிலை வேகம்: ",
|
||||||
|
"preferences_quality_label": "விருப்பமான வீடியோ தரம்: ",
|
||||||
|
"preferences_quality_dash_label": "விருப்பமான கோடு வீடியோ தரம்: ",
|
||||||
|
"preferences_quality_dash_option_auto": "தானி",
|
||||||
|
"preferences_quality_dash_option_best": "சிறந்த",
|
||||||
|
"preferences_quality_dash_option_worst": "மோசமான",
|
||||||
|
"preferences_quality_dash_option_4320p": "4320 ப",
|
||||||
|
"preferences_quality_dash_option_1080p": "1080 ப",
|
||||||
|
"preferences_quality_dash_option_720p": "720 ஆ",
|
||||||
|
"preferences_quality_dash_option_480p": "480 ப",
|
||||||
|
"preferences_quality_dash_option_360p": "360 ப",
|
||||||
|
"preferences_quality_dash_option_144p": "144 ப",
|
||||||
|
"preferences_volume_label": "பிளேயர் தொகுதி: ",
|
||||||
|
"preferences_comments_label": "இயல்புநிலை கருத்துகள்: ",
|
||||||
|
"Fallback captions: ": "குறைவடையும் தலைப்புகள்: ",
|
||||||
|
"preferences_captions_label": "இயல்புநிலை தலைப்புகள்: ",
|
||||||
|
"preferences_related_videos_label": "தொடர்புடைய வீடியோக்களைக் காட்டு: ",
|
||||||
|
"preferences_annotations_label": "முன்னிருப்பாக சிறுகுறிப்புகளைக் காட்டு: ",
|
||||||
|
"preferences_vr_mode_label": "ஊடாடும் 360 டிகிரி வீடியோக்கள் (வெப்சிஎல் தேவை): ",
|
||||||
|
"preferences_category_visual": "காட்சி விருப்பத்தேர்வுகள்",
|
||||||
|
"light": "ஒளி",
|
||||||
|
"preferences_thin_mode_label": "மெல்லிய பயன்முறை: ",
|
||||||
|
"preferences_category_misc": "இதர விருப்பத்தேர்வுகள்",
|
||||||
|
"preferences_category_subscription": "சந்தா விருப்பத்தேர்வுகள்",
|
||||||
|
"preferences_annotations_subscribed_label": "சந்தா சேனல்களுக்கு முன்னிருப்பாக சிறுகுறிப்புகளைக் காட்டவா? ",
|
||||||
|
"Redirect homepage to feed: ": "உணவளிக்க முகப்புப்பக்கத்தை திருப்பி விடுங்கள்: ",
|
||||||
|
"preferences_sort_label": "வீடியோக்களை வரிசைப்படுத்துங்கள்: ",
|
||||||
|
"published": "வெளியிடப்பட்டது",
|
||||||
|
"published - reverse": "வெளியிடப்பட்டது - தலைகீழ்",
|
||||||
|
"alphabetically": "அகரவரிசை",
|
||||||
|
"preferences_unseen_only_label": "கவனக்குறைவாக மட்டுமே காட்டுங்கள்: ",
|
||||||
|
"preferences_notifications_only_label": "அறிவிப்புகளைக் காட்டுங்கள் (ஏதேனும் இருந்தால்): ",
|
||||||
|
"Enable web notifications": "வலை அறிவிப்புகளை இயக்கவும்",
|
||||||
|
"`x` is live": "`x` நேரலையில்",
|
||||||
|
"preferences_category_data": "தரவு விருப்பத்தேர்வுகள்",
|
||||||
|
"Manage subscriptions": "சந்தாக்களை நிர்வகிக்கவும்",
|
||||||
|
"Watch history": "வரலாற்றைப் பாருங்கள்",
|
||||||
|
"Delete account": "கணக்கை நீக்கு",
|
||||||
|
"preferences_category_admin": "நிர்வாகி விருப்பத்தேர்வுகள்",
|
||||||
|
"preferences_default_home_label": "இயல்புநிலை முகப்புப்பக்கம்: ",
|
||||||
|
"preferences_feed_menu_label": "ஊட்ட மெனு: ",
|
||||||
|
"preferences_show_nick_label": "மேலே புனைப்பெயரைக் காட்டு: ",
|
||||||
|
"Top enabled: ": "மேலே இயக்கப்பட்டது: ",
|
||||||
|
"CAPTCHA enabled: ": "கேப்ட்சா இயக்கப்பட்டது: ",
|
||||||
|
"Login enabled: ": "உள்நுழைவு இயக்கப்பட்டது: ",
|
||||||
|
"Registration enabled: ": "பதிவு இயக்கப்பட்டது: ",
|
||||||
|
"Report statistics: ": "அறிக்கை புள்ளிவிவரங்கள்: ",
|
||||||
|
"Save preferences": "விருப்பங்களை சேமிக்கவும்",
|
||||||
|
"Subscription manager": "சந்தா மேலாளர்",
|
||||||
|
"Token manager": "கிள்ளாக்கு மேலாளர்",
|
||||||
|
"Token": "கிள்ளாக்கு",
|
||||||
|
"search": "தேடல்",
|
||||||
|
"Released under the AGPLv3 on Github.": "கிட்அப்பில் AgPlv3 இன் கீழ் வெளியிடப்பட்டது.",
|
||||||
|
"View JavaScript license information.": "சாவாச்கிரிப்ட் உரிமத் தகவலைக் காண்க.",
|
||||||
|
"View privacy policy.": "தனியுரிமைக் கொள்கையைக் காண்க.",
|
||||||
|
"Trending": "டிரெண்டிங்",
|
||||||
|
"Public": "பொது",
|
||||||
|
"Unlisted": "பட்டியலிடப்படாதது",
|
||||||
|
"Private": "தனிப்பட்ட",
|
||||||
|
"View all playlists": "அனைத்து பிளேலிச்ட்களையும் காண்க",
|
||||||
|
"Updated `x` ago": "`X` முன்பு புதுப்பிக்கப்பட்டது",
|
||||||
|
"Delete playlist `x`?": "பிளேலிச்ட்டை நீக்கவா?",
|
||||||
|
"Playlist privacy": "பிளேலிச்ட் தனியுரிமை",
|
||||||
|
"Watch on YouTube": "YouTube இல் பாருங்கள்",
|
||||||
|
"Hide annotations": "சிறுகுறிப்புகளை மறைக்கவும்",
|
||||||
|
"Show replies": "பதில்களைக் காட்டு",
|
||||||
|
"Incorrect password": "தவறான கடவுச்சொல்",
|
||||||
|
"Wrong answer": "தவறான பதில்",
|
||||||
|
"Erroneous CAPTCHA": "தவறான கேப்ட்சா",
|
||||||
|
"CAPTCHA is a required field": "கேப்ட்சா ஒரு தேவையான புலம்",
|
||||||
|
"User ID is a required field": "பயனர் ஐடி தேவையான புலம்",
|
||||||
|
"Password is a required field": "கடவுச்சொல் தேவையான புலம்",
|
||||||
|
"Password cannot be empty": "கடவுச்சொல் காலியாக இருக்க முடியாது",
|
||||||
|
"Please log in": "தயவுசெய்து உள்நுழைக",
|
||||||
|
"This channel does not exist.": "இந்த சேனல் இல்லை.",
|
||||||
|
"Could not get channel info.": "சேனல் தகவலைப் பெற முடியவில்லை.",
|
||||||
|
"Could not fetch comments": "கருத்துகளைப் பெற முடியவில்லை",
|
||||||
|
"comments_points_count": "{{count}} புள்ளி",
|
||||||
|
"comments_points_count_plural": "{{count}} புள்ளிகள்",
|
||||||
|
"Could not create mix.": "கலவையை உருவாக்க முடியவில்லை.",
|
||||||
|
"Empty playlist": "வெற்று பிளேலிச்ட்",
|
||||||
|
"Not a playlist.": "ஒரு பிளேலிச்ட் அல்ல.",
|
||||||
|
"Playlist does not exist.": "பிளேலிச்ட் இல்லை.",
|
||||||
|
"Could not pull trending pages.": "பிரபலமான பக்கங்களை இழுக்க முடியவில்லை.",
|
||||||
|
"Erroneous challenge": "தவறான அறைகூவல்",
|
||||||
|
"Erroneous token": "தவறான கிள்ளாக்கு",
|
||||||
|
"No such user": "அத்தகைய பயனர் இல்லை",
|
||||||
|
"Token is expired, please try again": "கிள்ளாக்கு காலாவதியானது, தயவுசெய்து மீண்டும் முயற்சிக்கவும்",
|
||||||
|
"English": "ஆங்கிலம்",
|
||||||
|
"English (United States)": "ஆங்கிலம் (ஐக்கிய அமெரிக்க)",
|
||||||
|
"English (United Kingdom)": "ஆங்கிலம் (ஐக்கிய முடியரசு)",
|
||||||
|
"English (auto-generated)": "ஆங்கிலம் (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Afrikaans": "ஆப்பிரிக்கா",
|
||||||
|
"Albanian": "அல்பேனிய",
|
||||||
|
"Amharic": "அம்ஆரிக்",
|
||||||
|
"Arabic": "அரபு",
|
||||||
|
"Armenian": "ஆர்மீனியன்",
|
||||||
|
"Azerbaijani": "அசர்பைசானி",
|
||||||
|
"Bangla": "பாங்லா",
|
||||||
|
"Basque": "பாச்க்",
|
||||||
|
"Belarusian": "பெலாருசியன்",
|
||||||
|
"Bosnian": "போச்னிய",
|
||||||
|
"Bulgarian": "பல்கேரியன்",
|
||||||
|
"Burmese": "பர்மீச்",
|
||||||
|
"Cantonese (Hong Kong)": "கான்டோனீச் (ஆங்காங்)",
|
||||||
|
"Catalan": "கற்றலான்",
|
||||||
|
"Cebuano": "செபுவானோ",
|
||||||
|
"Chinese": "சீன",
|
||||||
|
"Chinese (China)": "சீன (சீனா)",
|
||||||
|
"Chinese (Hong Kong)": "சீன (ஆங்காங்)",
|
||||||
|
"Chinese (Simplified)": "சீன (எளிமைப்படுத்தப்பட்ட)",
|
||||||
|
"Chinese (Taiwan)": "சீன (தைவான்)",
|
||||||
|
"Chinese (Traditional)": "சீன (பாரம்பரிய)",
|
||||||
|
"Dutch": "டச்சு",
|
||||||
|
"Finnish": "பின்னிச்",
|
||||||
|
"French": "பிரஞ்சு",
|
||||||
|
"German (auto-generated)": "செர்மன் (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Greek": "கிரேக்கம்",
|
||||||
|
"Gujarati": "குசராத்தி",
|
||||||
|
"Haitian Creole": "ஐட்டிய கிரியோல்",
|
||||||
|
"Hungarian": "அங்கேரியன்",
|
||||||
|
"Icelandic": "ஐச்லாந்திய",
|
||||||
|
"Igbo": "இக்போ",
|
||||||
|
"Korean (auto-generated)": "கொரிய (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Macedonian": "மாசிடோனியன்",
|
||||||
|
"Malagasy": "மலகாசி",
|
||||||
|
"Maltese": "மால்டிச்",
|
||||||
|
"Maori": "மௌரி",
|
||||||
|
"Malayalam": "மலையாளம்",
|
||||||
|
"Marathi": "மராத்தி",
|
||||||
|
"Mongolian": "மங்கோலியன்",
|
||||||
|
"Nepali": "நேபாளி",
|
||||||
|
"Norwegian Bokmål": "நார்வேசியன் பொக்மால்",
|
||||||
|
"Nyanja": "நயன்சா",
|
||||||
|
"Russian": "ரச்ய",
|
||||||
|
"Russian (auto-generated)": "ரச்ய (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Samoan": "சமோவான்",
|
||||||
|
"Scottish Gaelic": "ச்கோட்டிச் கயாலிக்",
|
||||||
|
"Serbian": "செர்பிய",
|
||||||
|
"Shona": "சோனா",
|
||||||
|
"Sindhi": "சிந்தி",
|
||||||
|
"Somali": "சோமாலி",
|
||||||
|
"Southern Sotho": "தெற்கத்திய சோதோ",
|
||||||
|
"Spanish": "ச்பானிச்",
|
||||||
|
"Spanish (auto-generated)": "ச்பானிச் (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Sundanese": "சுந்தானியர்கள்",
|
||||||
|
"Swahili": "ச்வாஇலி",
|
||||||
|
"Swedish": "ச்வீடிச்",
|
||||||
|
"Tajik": "தசிக்",
|
||||||
|
"Tamil": "தமிழ்",
|
||||||
|
"Thai": "தாய்",
|
||||||
|
"Turkish": "துருக்கிய",
|
||||||
|
"Vietnamese": "வியட்நாமிய",
|
||||||
|
"Welsh": "வேல்ச்",
|
||||||
|
"Xhosa": "ஓசா",
|
||||||
|
"Yiddish": "யெட்டிச்",
|
||||||
|
"Yoruba": "யோருபா",
|
||||||
|
"Top": "மேலே",
|
||||||
|
"About": "பற்றி",
|
||||||
|
"View as playlist": "பிளேலிச்ட்டாக காண்க",
|
||||||
|
"Gaming": "கேமிங்",
|
||||||
|
"News": "செய்தி",
|
||||||
|
"Movies": "திரைப்படங்கள்",
|
||||||
|
"Download as: ": "என பதிவிறக்கவும்: ",
|
||||||
|
"Download is disabled": "பதிவிறக்கம் முடக்கப்பட்டுள்ளது",
|
||||||
|
"(edited)": "(திருத்தப்பட்டது)",
|
||||||
|
"YouTube comment permalink": "YouTube கருத்து பெர்மாலின்க்",
|
||||||
|
"`x` marked it with a ❤": "`x` அதை a உடன் குறித்தது",
|
||||||
|
"Video mode": "வீடியோ பயன்முறை",
|
||||||
|
"Playlists": "பிளேலிச்ட்கள்",
|
||||||
|
"search_filters_date_option_today": "இன்று",
|
||||||
|
"search_filters_date_option_week": "இந்த வாரம்",
|
||||||
|
"search_filters_date_option_month": "இந்த மாதம்",
|
||||||
|
"search_filters_type_option_channel": "வாய்க்கால்",
|
||||||
|
"search_filters_type_option_playlist": "பிளேலிச்ட்",
|
||||||
|
"search_filters_duration_label": "காலம்",
|
||||||
|
"search_filters_duration_option_none": "எந்த காலமும்",
|
||||||
|
"search_filters_duration_option_medium": "நடுத்தர (4 - 20 நிமிடங்கள்)",
|
||||||
|
"search_filters_duration_option_long": "நீண்ட (> 20 நிமிடங்கள்)",
|
||||||
|
"search_filters_features_label": "நற்பொருத்தங்கள்",
|
||||||
|
"search_filters_features_option_four_k": "எச்.சி.",
|
||||||
|
"search_filters_features_option_live": "நேரடி",
|
||||||
|
"search_filters_features_option_hd": "எச்டி",
|
||||||
|
"search_filters_features_option_subtitles": "வசன வரிகள்/சிசி",
|
||||||
|
"search_filters_features_option_c_commons": "கிரியேட்டிவ் காமன்ச்",
|
||||||
|
"search_filters_features_option_three_sixty": "360 °",
|
||||||
|
"search_filters_features_option_three_d": "ZD",
|
||||||
|
"search_filters_features_option_hdr": "எச்.டி.ஆர்",
|
||||||
|
"search_filters_features_option_location": "இடம்",
|
||||||
|
"search_filters_sort_option_relevance": "பொருத்தமானது",
|
||||||
|
"search_filters_sort_option_rating": "செயல்வரம்பு",
|
||||||
|
"Current version: ": "தற்போதைய பதிப்பு: ",
|
||||||
|
"next_steps_error_message": "அதன் பிறகு நீங்கள் முயற்சி செய்ய வேண்டும்: ",
|
||||||
|
"next_steps_error_message_refresh": "புதுப்பிப்பு",
|
||||||
|
"next_steps_error_message_go_to_youtube": "YouTube க்குச் செல்லுங்கள்",
|
||||||
|
"footer_donate_page": "நன்கொடை",
|
||||||
|
"footer_modfied_source_code": "மாற்றியமைக்கப்பட்ட மூலக் குறியீடு",
|
||||||
|
"adminprefs_modified_source_code_url_label": "மாற்றியமைக்கப்பட்ட மூலக் குறியீடு களஞ்சியத்திற்கு முகவரி",
|
||||||
|
"videoinfo_started_streaming_x_ago": "`X` முன்பு ச்ட்ரீமிங் செய்யத் தொடங்கியது",
|
||||||
|
"videoinfo_watch_on_youTube": "YouTube இல் பாருங்கள்",
|
||||||
|
"download_subtitles": "வசன வரிகள் - `x` (.vtt)",
|
||||||
|
"user_created_playlists": "`x` உருவாக்கியது பிளேலிச்ட்கள்",
|
||||||
|
"user_saved_playlists": "`x` சேமித்த பிளேலிச்ட்கள்",
|
||||||
|
"crash_page_before_reporting": "ஒரு பிழையைப் புகாரளிப்பதற்கு முன், உங்களிடம் இருப்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்:",
|
||||||
|
"crash_page_switch_instance": "<a href = \"` x` \"> மற்றொரு நிகழ்வைப் பயன்படுத்த முயற்சித்தேன் </a>",
|
||||||
|
"crash_page_search_issue": "அறிவிலிமையத்தில் உள்ள <a href=\"`x`\"> தற்போதைய சிக்கல்களைத் தேடியது</a>",
|
||||||
|
"channel_tab_shorts_label": "குறுக்குகள்",
|
||||||
|
"channel_tab_streams_label": "லைவ்ச்ட்ரீம்கள்",
|
||||||
|
"carousel_go_to": "`X` ச்லைடு செல்லவும்",
|
||||||
|
"Popular": "புகழ்பெற்ற",
|
||||||
|
"Subscribe": "குழுசேர்",
|
||||||
|
"View channel on YouTube": "YouTube இல் சேனலைக் காண்க",
|
||||||
|
"Authorize token for `x`?": "`X` க்கு கிள்ளாக்கை அங்கீகரிக்கவா?",
|
||||||
|
"No": "இல்லை",
|
||||||
|
"Add to playlist: ": "பிளேலிச்ட்டில் சேர்க்கவும்: ",
|
||||||
|
"Answer": "பதில்",
|
||||||
|
"Search for videos": "வீடியோக்களைத் தேடுங்கள்",
|
||||||
|
"The Popular feed has been disabled by the administrator.": "பிரபலமான ஊட்டத்தை நிர்வாகியால் முடக்கப்பட்டுள்ளது.",
|
||||||
|
"generic_subscriptions_count": "{{count}} சந்தா",
|
||||||
|
"generic_subscriptions_count_plural": "{{count}} சந்தாக்கள்",
|
||||||
|
"generic_button_edit": "தொகு",
|
||||||
|
"generic_button_save": "சேமி",
|
||||||
|
"generic_button_cancel": "ரத்துசெய்",
|
||||||
|
"Import and Export Data": "தரவை இறக்குமதி செய்து ஏற்றுமதி செய்யுங்கள்",
|
||||||
|
"Import": "இறக்குமதி",
|
||||||
|
"Import NewPipe subscriptions (.json)": "நியூபிப்பிப் சந்தாக்களை இறக்குமதி செய்யுங்கள் (.json)",
|
||||||
|
"Export": "ஏற்றுமதி",
|
||||||
|
"Text CAPTCHA": "உரை கேப்ட்சா",
|
||||||
|
"Image CAPTCHA": "பட கேப்ட்சா",
|
||||||
|
"preferences_category_player": "பிளேயர் விருப்பத்தேர்வுகள்",
|
||||||
|
"preferences_video_loop_label": "எப்போதும் லூப்: ",
|
||||||
|
"preferences_continue_autoplay_label": "தன்னியக்க அடுத்த வீடியோ: ",
|
||||||
|
"preferences_listen_label": "இயல்பாக கேளுங்கள்: ",
|
||||||
|
"preferences_quality_option_dash": "கோடு (தகவமைப்பு தரம்)",
|
||||||
|
"preferences_quality_option_hd720": "HD720",
|
||||||
|
"preferences_quality_option_medium": "சராசரி",
|
||||||
|
"preferences_quality_option_small": "சிறிய",
|
||||||
|
"preferences_quality_dash_option_2160p": "2160 ப",
|
||||||
|
"preferences_quality_dash_option_1440p": "1440 ப",
|
||||||
|
"preferences_quality_dash_option_240p": "240 ப",
|
||||||
|
"youtube": "YouTube",
|
||||||
|
"reddit": "ரெடிட்",
|
||||||
|
"invidious": "வெகுவாக",
|
||||||
|
"preferences_extend_desc_label": "வீடியோ விளக்கத்தை தானாக நீட்டிக்கவும்: ",
|
||||||
|
"preferences_region_label": "உள்ளடக்க நாடு: ",
|
||||||
|
"preferences_player_style_label": "பிளேயர் ச்டைல்: ",
|
||||||
|
"Dark mode: ": "இருண்ட முறை: ",
|
||||||
|
"preferences_dark_mode_label": "தீம்: ",
|
||||||
|
"dark": "இருண்ட",
|
||||||
|
"preferences_automatic_instance_redirect_label": "தானியங்கி நிகழ்வு திசைதிருப்பல் (redirect.invidious.io க்கு குறைவடையும்): ",
|
||||||
|
"preferences_max_results_label": "ஊட்டத்தில் காட்டப்பட்டுள்ள வீடியோக்களின் எண்ணிக்கை: ",
|
||||||
|
"alphabetically - reverse": "அகரவரிசை - தலைகீழ்",
|
||||||
|
"channel name": "சேனல் பெயர்",
|
||||||
|
"channel name - reverse": "சேனல் பெயர் - தலைகீழ்",
|
||||||
|
"Only show latest video from channel: ": "சேனலில் இருந்து அண்மைக் கால வீடியோவைக் காட்டுங்கள்: ",
|
||||||
|
"Only show latest unwatched video from channel: ": "சேனலில் இருந்து அண்மைக் கால கவனிக்கப்படாத வீடியோவைக் காட்டுங்கள்: ",
|
||||||
|
"`x` uploaded a video": "`x` ஒரு வீடியோவைப் பதிவேற்றியது",
|
||||||
|
"Clear watch history": "தெளிவான கண்காணிப்பு வரலாறு",
|
||||||
|
"Log out": "விடுபதிகை",
|
||||||
|
"Source available here.": "சான்று இங்கே கிடைக்கிறது.",
|
||||||
|
"Delete playlist": "பிளேலிச்ட்டை நீக்கு",
|
||||||
|
"Create playlist": "பிளேலிச்ட்டை உருவாக்கவும்",
|
||||||
|
"Title": "தலைப்பு",
|
||||||
|
"Import/export data": "தரவு இறக்குமதி/ஏற்றுமதி",
|
||||||
|
"Change password": "கடவுச்சொல்லை மாற்றவும்",
|
||||||
|
"Manage tokens": "டோக்கன்களை நிர்வகிக்கவும்",
|
||||||
|
"Popular enabled: ": "பிரபலமான இயக்கப்பட்டது: ",
|
||||||
|
"tokens_count": "{{count}} கிள்ளாக்கு",
|
||||||
|
"tokens_count_plural": "{{count}} டோக்கன்கள்",
|
||||||
|
"Import/export": "இறக்குமதி/ஏற்றுமதி",
|
||||||
|
"unsubscribe": "குழுவிலகவும்",
|
||||||
|
"revoke": "ரத்து செய்யுங்கள்",
|
||||||
|
"Subscriptions": "சந்தாக்கள்",
|
||||||
|
"subscriptions_unseen_notifs_count": "{{count}} காணப்படாத அறிவிப்பு",
|
||||||
|
"subscriptions_unseen_notifs_count_plural": "{{count}} காணப்படாத அறிவிப்புகள்",
|
||||||
|
"Editing playlist `x`": "பிளேலிச்ட்டைத் திருத்துதல் `x`",
|
||||||
|
"playlist_button_add_items": "வீடியோக்களைச் சேர்க்கவும்",
|
||||||
|
"Show more": "மேலும் காட்டு",
|
||||||
|
"Show less": "குறைவாகக் காட்டு",
|
||||||
|
"Switch Invidious Instance": "அக்யோர்ட் உதாரணத்தை மாற்றவும்",
|
||||||
|
"search_message_no_results": "முடிவுகள் எதுவும் கிடைக்கவில்லை.",
|
||||||
|
"search_message_change_filters_or_query": "உங்கள் தேடல் வினவலை அகலப்படுத்த முயற்சிக்கவும்/அல்லது வடிப்பான்களை மாற்றவும்.",
|
||||||
|
"search_message_use_another_instance": "நீங்கள் <a href = \"` x` \"> மற்றொரு நிகழ்வில் தேடலாம் </a>.",
|
||||||
|
"Show annotations": "சிறுகுறிப்புகளைக் காட்டு",
|
||||||
|
"Genre: ": "வகை: ",
|
||||||
|
"License: ": "உரிமம்: ",
|
||||||
|
"Standard YouTube license": "நிலையான YouTube உரிமம்",
|
||||||
|
"Family friendly? ": "குடும்ப நட்பு? ",
|
||||||
|
"Wilson score: ": "வில்சன் மதிப்பெண்: ",
|
||||||
|
"Engagement: ": "நிச்சயதார்த்தம்: ",
|
||||||
|
"Whitelisted regions: ": "அனுமதிப்பட்டிய பகுதிகள்: ",
|
||||||
|
"Blacklisted regions: ": "தடுப்புப்பட்டியாக்கப்பட்ட பகுதிகள்: ",
|
||||||
|
"Music in this video": "இந்த வீடியோவில் இசை",
|
||||||
|
"Artist: ": "கலைஞர்: ",
|
||||||
|
"Song: ": "பாடல்: ",
|
||||||
|
"Album: ": "ஆல்பம்: ",
|
||||||
|
"Shared `x`": "பகிரப்பட்டது `x`",
|
||||||
|
"Premieres in `x`": "`X` இல் பிரீமியர்ச்",
|
||||||
|
"Premieres `x`": "பிரீமியர்ச் `x`",
|
||||||
|
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "ஆய்! நீங்கள் சாவாச்கிரிப்ட் முடக்கப்பட்டிருப்பது போல் தெரிகிறது. கருத்துகளைக் காண இங்கே சொடுக்கு செய்க, அவர்கள் ஏற்றுவதற்கு சிறிது நேரம் ஆகலாம் என்பதை நினைவில் கொள்ளுங்கள்.",
|
||||||
|
"View YouTube comments": "YouTube கருத்துகளைக் காண்க",
|
||||||
|
"View more comments on Reddit": "ரெடிட் குறித்த கூடுதல் கருத்துகளைக் காண்க",
|
||||||
|
"View `x` comments": {
|
||||||
|
"([^.,0-9]|^)1([^.,0-9]|$)": "`X` கருத்தைக் காண்க",
|
||||||
|
"": "`X` கருத்துகளைக் காண்க"
|
||||||
|
},
|
||||||
|
"View Reddit comments": "ரெடிட் கருத்துகளைக் காண்க",
|
||||||
|
"Hide replies": "பதில்களை மறைக்கவும்",
|
||||||
|
"Wrong username or password": "தவறான பயனர்பெயர் அல்லது கடவுச்சொல்",
|
||||||
|
"Password cannot be longer than 55 characters": "கடவுச்சொல் 55 எழுத்துகளை விட நீளமாக இருக்க முடியாது",
|
||||||
|
"Invidious Private Feed for `x`": "`X` க்கான மோசமான தனியார் ஊட்டம்",
|
||||||
|
"channel:`x`": "சேனல்: `x`",
|
||||||
|
"Deleted or invalid channel": "நீக்கப்பட்ட அல்லது தவறான சேனல்",
|
||||||
|
"comments_view_x_replies": "{{count}} பதிலைக் காண்க",
|
||||||
|
"comments_view_x_replies_plural": "{{count}} பதில்களைக் காண்க",
|
||||||
|
"`x` ago": "`x` முன்பு",
|
||||||
|
"Load more": "மேலும் ஏற்றவும்",
|
||||||
|
"Hidden field \"challenge\" is a required field": "மறைக்கப்பட்ட புலம் \"அறைகூவல்\" என்பது தேவையான புலம்",
|
||||||
|
"Hidden field \"token\" is a required field": "மறைக்கப்பட்ட புலம் \"கிள்ளாக்கு\" என்பது தேவையான புலம்",
|
||||||
|
"Corsican": "கார்சிகன்",
|
||||||
|
"Croatian": "குரோசியன்",
|
||||||
|
"Czech": "செக்",
|
||||||
|
"Danish": "டேனிச்",
|
||||||
|
"Dutch (auto-generated)": "டச்சு (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Esperanto": "எச்பெராண்டோ",
|
||||||
|
"Estonian": "எச்டோனிய",
|
||||||
|
"Filipino": "ஃபிலிபினோ",
|
||||||
|
"Filipino (auto-generated)": "பிலிப்பைன்ச் (தானாக உருவாக்கிய)",
|
||||||
|
"French (auto-generated)": "பிரஞ்சு (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Galician": "காலிசியன்",
|
||||||
|
"Georgian": "சார்சியன்",
|
||||||
|
"German": "செர்மன்",
|
||||||
|
"Hausa": "ஔசா",
|
||||||
|
"Lao": "லாவோ",
|
||||||
|
"Latin": "லத்தீன்",
|
||||||
|
"Latvian": "லாட்வியன்",
|
||||||
|
"Hawaiian": "அவாயியன்",
|
||||||
|
"Hebrew": "எபிரேய",
|
||||||
|
"Lithuanian": "லிதுவேனியன்",
|
||||||
|
"Hindi": "இந்தி",
|
||||||
|
"Hmong": "அமோங்",
|
||||||
|
"Indonesian": "இந்தோனேசிய",
|
||||||
|
"Indonesian (auto-generated)": "இந்தோனேசிய (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Interlingue": "இன்டர்லின்குய்",
|
||||||
|
"Irish": "ஐரிச்",
|
||||||
|
"Italian": "இத்தாலிய",
|
||||||
|
"Italian (auto-generated)": "இத்தாலியன் (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Japanese": "சப்பானியர்கள்",
|
||||||
|
"Japanese (auto-generated)": "சப்பானிய (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Javanese": "சாவானீச்",
|
||||||
|
"Kannada": "கன்னடா",
|
||||||
|
"Kazakh": "கசாக்",
|
||||||
|
"Khmer": "கெமர்",
|
||||||
|
"Korean": "கொரிய",
|
||||||
|
"Kurdish": "குர்திச்",
|
||||||
|
"Kyrgyz": "கிர்கிச்",
|
||||||
|
"Luxembourgish": "லக்சம்போர்கிச்",
|
||||||
|
"Malay": "மலாய்",
|
||||||
|
"Pashto": "பச்தோ",
|
||||||
|
"Persian": "பெர்சியன்",
|
||||||
|
"Polish": "போலீச்",
|
||||||
|
"Portuguese": "போர்த்துகீசியம்",
|
||||||
|
"Portuguese (auto-generated)": "போர்த்துகீசியம் (தானாக உருவாக்கிய)",
|
||||||
|
"generic_count_minutes": "{{count}} மணித்துளி",
|
||||||
|
"generic_count_minutes_plural": "{{count}} நிமிடங்கள்",
|
||||||
|
"generic_count_seconds": "{{count}} இரண்டாவது",
|
||||||
|
"generic_count_seconds_plural": "{{count}} வினாடிகள்",
|
||||||
|
"Fallback comments: ": "குறைவடையும் கருத்துரைகள்: ",
|
||||||
|
"Portuguese (Brazil)": "போர்த்துகீசியம் (பிரேசில்)",
|
||||||
|
"Punjabi": "பஞ்சாபி",
|
||||||
|
"Romanian": "ருமேனிய",
|
||||||
|
"Sinhala": "சிங்களம்",
|
||||||
|
"Slovak": "ச்லோவாக்",
|
||||||
|
"Slovenian": "ச்லோவேனியன்",
|
||||||
|
"Spanish (Latin America)": "ச்பானிச் (லத்தீன் அமெரிக்கா)",
|
||||||
|
"Spanish (Mexico)": "ச்பானிச் (மெக்சிகோ)",
|
||||||
|
"Spanish (Spain)": "ச்பானிச் (ச்பெயின்)",
|
||||||
|
"Telugu": "தெலுங்கு",
|
||||||
|
"Turkish (auto-generated)": "துருக்கிய (தானாக உருவாக்கிய)",
|
||||||
|
"Ukrainian": "உக்ரேனிய",
|
||||||
|
"Urdu": "உருது",
|
||||||
|
"Uzbek": "உச்பெக்",
|
||||||
|
"Vietnamese (auto-generated)": "வியட்நாமிய (தானாக உருவாக்கப்பட்ட)",
|
||||||
|
"Western Frisian": "மேற்கு ஃபிரிசியன்",
|
||||||
|
"Zulu": "சுலு",
|
||||||
|
"generic_count_years": "{{count}}} ஆண்டு",
|
||||||
|
"generic_count_years_plural": "{{count}} ஆண்டுகள்",
|
||||||
|
"generic_count_months": "{{count}} மாதம்",
|
||||||
|
"generic_count_months_plural": "{{count}} மாதங்கள்",
|
||||||
|
"generic_count_weeks": "{{count}}} வாரம்",
|
||||||
|
"generic_count_weeks_plural": "{{count}} வாரங்கள்",
|
||||||
|
"generic_count_days": "{{count}}} நாள்",
|
||||||
|
"generic_count_days_plural": "{{count}} நாட்கள்",
|
||||||
|
"generic_count_hours": "{{count}} மணிநேரம்",
|
||||||
|
"generic_count_hours_plural": "{{count}} மணிநேரம்",
|
||||||
|
"Search": "தேடல்",
|
||||||
|
"Rating: ": "மதிப்பீடு: ",
|
||||||
|
"preferences_locale_label": "மொழி: ",
|
||||||
|
"Default": "இயல்புநிலை",
|
||||||
|
"Music": "இசை",
|
||||||
|
"Download": "பதிவிறக்கம்",
|
||||||
|
"%A %B %-d, %Y": "%A %b %-d, %y",
|
||||||
|
"permalink": "பெர்மாலின்க்",
|
||||||
|
"Channel Sponsor": "சேனல் ஒப்புரவாளர்",
|
||||||
|
"Audio mode": "ஆடியோ பயன்முறை",
|
||||||
|
"search_filters_duration_option_short": "குறுகிய (<4 நிமிடங்கள்)",
|
||||||
|
"search_filters_title": "வடிப்பான்கள்",
|
||||||
|
"search_filters_date_label": "தேதி பதிவேற்றும் தேதி",
|
||||||
|
"search_filters_date_option_none": "எந்த தேதி",
|
||||||
|
"search_filters_date_option_hour": "கடைசி மணி",
|
||||||
|
"search_filters_date_option_year": "இந்த ஆண்டு",
|
||||||
|
"search_filters_type_label": "வகை",
|
||||||
|
"search_filters_type_option_all": "எந்த வகை",
|
||||||
|
"search_filters_type_option_video": "ஒளிதோற்றம்",
|
||||||
|
"search_filters_type_option_movie": "படம்",
|
||||||
|
"search_filters_type_option_show": "காட்டு",
|
||||||
|
"search_filters_features_option_vr180": "VR180",
|
||||||
|
"search_filters_features_option_purchased": "வாங்கப்பட்டது",
|
||||||
|
"search_filters_sort_label": "வரிசைப்படுத்தவும்",
|
||||||
|
"search_filters_sort_option_date": "பதிவேற்ற தேதி",
|
||||||
|
"search_filters_sort_option_views": "எண்ணிக்கை காண்க",
|
||||||
|
"search_filters_apply_button": "தேர்ந்தெடுக்கப்பட்ட வடிப்பான்களைப் பயன்படுத்துங்கள்",
|
||||||
|
"footer_documentation": "ஆவணப்படுத்துதல்",
|
||||||
|
"footer_source_code": "மூலக் குறியீடு",
|
||||||
|
"footer_original_source_code": "அசல் மூலக் குறியீடு",
|
||||||
|
"none": "எதுவுமில்லை",
|
||||||
|
"videoinfo_youTube_embed_link": "உட்பொதிக்கப்பட்டது",
|
||||||
|
"videoinfo_invidious_embed_link": "உட்பொதிப்பு இணைப்பு",
|
||||||
|
"Video unavailable": "வீடியோ கிடைக்கவில்லை",
|
||||||
|
"preferences_save_player_pos_label": "பிளேபேக் நிலையை சேமிக்கவும்: ",
|
||||||
|
"crash_page_you_found_a_bug": "நீங்கள் ஒரு பிழையை கண்டுபிடித்ததாகத் தெரிகிறது!",
|
||||||
|
"crash_page_refresh": "<a href = \"` x` \"> பக்கத்தை புதுப்பிக்க முயற்சித்தேன் </a>",
|
||||||
|
"crash_page_read_the_faq": "<a href = \"` x` \"> அடிக்கடி கேட்கப்படும் கேள்விகள் (கேள்விகள்) </a> ஐப் படியுங்கள்",
|
||||||
|
"crash_page_report_issue": "மேலே எதுவும் உதவவில்லை என்றால், தயவுசெய்து <a href = \"` x` \"> அறிவிலிமையம் </a> (முன்னுரிமை ஆங்கிலத்தில்) ஒரு புதிய சிக்கலைத் திறந்து உங்கள் செய்தியில் பின்வரும் உரையைச் சேர்க்கவும் (அந்த உரையை மொழிபெயர்க்க வேண்டாம்):",
|
||||||
|
"error_video_not_in_playlist": "கோரப்பட்ட வீடியோ இந்த பிளேலிச்ட்டில் இல்லை. <a href = \"` x` \"> பிளேலிச்ட் முகப்பு பக்கத்திற்கு இங்கே சொடுக்கு செய்க. </a>",
|
||||||
|
"channel_tab_videos_label": "வீடியோக்கள்",
|
||||||
|
"channel_tab_podcasts_label": "பாட்காச்ட்கள்",
|
||||||
|
"channel_tab_releases_label": "வெளியீடுகள்",
|
||||||
|
"channel_tab_playlists_label": "பிளேலிச்ட்கள்",
|
||||||
|
"channel_tab_community_label": "சமூகம்",
|
||||||
|
"channel_tab_channels_label": "சேனல்கள்",
|
||||||
|
"toggle_theme": "கருப்பொருளை மாற்றவும்",
|
||||||
|
"carousel_slide": "{{total}} இன் ச்லைடு {{current}}",
|
||||||
|
"carousel_skip": "கொணர்வி தவிர்க்கவும்"
|
||||||
|
}
|
1
locales/tok.json
Normal file
1
locales/tok.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -322,13 +322,13 @@
|
||||||
"channel_tab_community_label": "Topluluk",
|
"channel_tab_community_label": "Topluluk",
|
||||||
"search_filters_sort_option_relevance": "İlgi",
|
"search_filters_sort_option_relevance": "İlgi",
|
||||||
"search_filters_sort_option_rating": "Değerlendirme",
|
"search_filters_sort_option_rating": "Değerlendirme",
|
||||||
"search_filters_sort_option_date": "Yükleme Tarihi",
|
"search_filters_sort_option_date": "Yükleme tarihi",
|
||||||
"search_filters_sort_option_views": "Görüntüleme Sayısı",
|
"search_filters_sort_option_views": "Görüntüleme Sayısı",
|
||||||
"search_filters_type_label": "Tür",
|
"search_filters_type_label": "Tür",
|
||||||
"search_filters_duration_label": "Süre",
|
"search_filters_duration_label": "Süre",
|
||||||
"search_filters_features_label": "Özellikler",
|
"search_filters_features_label": "Özellikler",
|
||||||
"search_filters_sort_label": "Sıralama Ölçütü",
|
"search_filters_sort_label": "Sıralama Ölçütü",
|
||||||
"search_filters_date_option_hour": "Son Saat",
|
"search_filters_date_option_hour": "Son saat",
|
||||||
"search_filters_date_option_today": "Bugün",
|
"search_filters_date_option_today": "Bugün",
|
||||||
"search_filters_date_option_week": "Bu Hafta",
|
"search_filters_date_option_week": "Bu Hafta",
|
||||||
"search_filters_date_option_month": "Bu Ay",
|
"search_filters_date_option_month": "Bu Ay",
|
||||||
|
@ -452,7 +452,7 @@
|
||||||
"Spanish (Spain)": "İspanyolca (İspanya)",
|
"Spanish (Spain)": "İspanyolca (İspanya)",
|
||||||
"Vietnamese (auto-generated)": "Vietnamca (Otomatik Oluşturuldu)",
|
"Vietnamese (auto-generated)": "Vietnamca (Otomatik Oluşturuldu)",
|
||||||
"preferences_watch_history_label": "İzleme Geçmişini Etkinleştir: ",
|
"preferences_watch_history_label": "İzleme Geçmişini Etkinleştir: ",
|
||||||
"search_message_use_another_instance": " Ayrıca <a href=\"`x`\">başka bir örnekte arayabilirsiniz</a>.",
|
"search_message_use_another_instance": "Ayrıca <a href=\"`x`\">başka bir örnekte arayabilirsiniz</a>.",
|
||||||
"search_filters_type_option_all": "Herhangi Bir Tür",
|
"search_filters_type_option_all": "Herhangi Bir Tür",
|
||||||
"search_filters_duration_option_none": "Herhangi Bir Süre",
|
"search_filters_duration_option_none": "Herhangi Bir Süre",
|
||||||
"search_message_no_results": "Sonuç bulunamadı.",
|
"search_message_no_results": "Sonuç bulunamadı.",
|
||||||
|
@ -496,5 +496,10 @@
|
||||||
"carousel_slide": "Sunum {{current}} / {{total}}",
|
"carousel_slide": "Sunum {{current}} / {{total}}",
|
||||||
"carousel_skip": "Kayar menüyü atla",
|
"carousel_skip": "Kayar menüyü atla",
|
||||||
"carousel_go_to": "`x` sunumuna git",
|
"carousel_go_to": "`x` sunumuna git",
|
||||||
"The Popular feed has been disabled by the administrator.": "Popüler akışı yönetici tarafından devre dışı bırakıldı."
|
"The Popular feed has been disabled by the administrator.": "Popüler akışı yönetici tarafından devre dışı bırakıldı.",
|
||||||
|
"preferences_preload_label": "Video verilerini önceden yükle: ",
|
||||||
|
"First page": "İlk sayfa",
|
||||||
|
"Filipino (auto-generated)": "Filipince (oto-oluşturuldu)",
|
||||||
|
"channel_tab_courses_label": "Kurslar",
|
||||||
|
"channel_tab_posts_label": "Yazılar"
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,7 +455,7 @@
|
||||||
"search_filters_date_option_week": "Цей тиждень",
|
"search_filters_date_option_week": "Цей тиждень",
|
||||||
"search_filters_type_label": "Тип",
|
"search_filters_type_label": "Тип",
|
||||||
"search_filters_type_option_channel": "Канал",
|
"search_filters_type_option_channel": "Канал",
|
||||||
"search_message_use_another_instance": " Можете також <a href=\"`x`\">пошукати іншим сервером</a>.",
|
"search_message_use_another_instance": "Можете також <a href=\"`x`\">пошукати на іншому сервері</a>.",
|
||||||
"search_filters_title": "Фільтри",
|
"search_filters_title": "Фільтри",
|
||||||
"search_filters_date_option_hour": "Остання година",
|
"search_filters_date_option_hour": "Остання година",
|
||||||
"search_filters_date_option_month": "Цей місяць",
|
"search_filters_date_option_month": "Цей місяць",
|
||||||
|
@ -472,7 +472,7 @@
|
||||||
"search_filters_features_option_three_sixty": "360°",
|
"search_filters_features_option_three_sixty": "360°",
|
||||||
"search_filters_features_option_hdr": "HDR",
|
"search_filters_features_option_hdr": "HDR",
|
||||||
"search_filters_sort_label": "Спершу",
|
"search_filters_sort_label": "Спершу",
|
||||||
"search_filters_sort_option_date": "Нещодавні",
|
"search_filters_sort_option_date": "Дата вивантаження",
|
||||||
"search_filters_apply_button": "Застосувати фільтри",
|
"search_filters_apply_button": "Застосувати фільтри",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_filters_features_option_purchased": "Придбано",
|
"search_filters_features_option_purchased": "Придбано",
|
||||||
|
@ -513,5 +513,10 @@
|
||||||
"The Popular feed has been disabled by the administrator.": "Стрічка Популярні вимкнена адміністратором.",
|
"The Popular feed has been disabled by the administrator.": "Стрічка Популярні вимкнена адміністратором.",
|
||||||
"carousel_slide": "Слайд {{current}} з {{total}}",
|
"carousel_slide": "Слайд {{current}} з {{total}}",
|
||||||
"carousel_skip": "Пропустити карусель",
|
"carousel_skip": "Пропустити карусель",
|
||||||
"carousel_go_to": "Перейти до слайда `x`"
|
"carousel_go_to": "Перейти до слайда `x`",
|
||||||
|
"preferences_preload_label": "Попереднє завантаження відеоданих: ",
|
||||||
|
"Filipino (auto-generated)": "Філіппінська (згенеровано автоматично)",
|
||||||
|
"First page": "Перша сторінка",
|
||||||
|
"channel_tab_courses_label": "Курси",
|
||||||
|
"channel_tab_posts_label": "Дописи"
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,11 +314,11 @@
|
||||||
"search_filters_duration_label": "Thời lượng",
|
"search_filters_duration_label": "Thời lượng",
|
||||||
"search_filters_features_label": "Đặc điểm",
|
"search_filters_features_label": "Đặc điểm",
|
||||||
"search_filters_sort_label": "Sắp xếp theo",
|
"search_filters_sort_label": "Sắp xếp theo",
|
||||||
"search_filters_date_option_hour": "Một giờ qua",
|
"search_filters_date_option_hour": "Một giờ trước",
|
||||||
"search_filters_date_option_today": "Hôm nay",
|
"search_filters_date_option_today": "Hôm nay",
|
||||||
"search_filters_date_option_week": "Tuần này",
|
"search_filters_date_option_week": "Tuần này",
|
||||||
"search_filters_date_option_month": "Tháng này",
|
"search_filters_date_option_month": "Tháng này",
|
||||||
"search_filters_date_option_year": "Năm này",
|
"search_filters_date_option_year": "Năm nay",
|
||||||
"search_filters_type_option_video": "video",
|
"search_filters_type_option_video": "video",
|
||||||
"search_filters_type_option_channel": "Kênh",
|
"search_filters_type_option_channel": "Kênh",
|
||||||
"search_filters_type_option_playlist": "Danh sách phát",
|
"search_filters_type_option_playlist": "Danh sách phát",
|
||||||
|
@ -479,5 +479,8 @@
|
||||||
"carousel_skip": "Bỏ qua Carousel",
|
"carousel_skip": "Bỏ qua Carousel",
|
||||||
"carousel_go_to": "Đi tới trang `x`",
|
"carousel_go_to": "Đi tới trang `x`",
|
||||||
"Search for videos": "Tìm kiếm video",
|
"Search for videos": "Tìm kiếm video",
|
||||||
"The Popular feed has been disabled by the administrator.": "Bảng tin phổ biến đã bị tắt bởi ban quản lý."
|
"The Popular feed has been disabled by the administrator.": "Bảng tin phổ biến đã bị tắt bởi ban quản lý.",
|
||||||
|
"preferences_preload_label": "Tải trước dữ liệu video: ",
|
||||||
|
"Filipino (auto-generated)": "Tiếng Philippines (tự động tạo)",
|
||||||
|
"First page": "Trang đầu"
|
||||||
}
|
}
|
||||||
|
|
|
@ -420,7 +420,7 @@
|
||||||
"Chinese": "中文",
|
"Chinese": "中文",
|
||||||
"Chinese (China)": "中文 (中国)",
|
"Chinese (China)": "中文 (中国)",
|
||||||
"Chinese (Hong Kong)": "中文 (中国香港)",
|
"Chinese (Hong Kong)": "中文 (中国香港)",
|
||||||
"Chinese (Taiwan)": "中文 (中国台湾)",
|
"Chinese (Taiwan)": "中文 (台湾)",
|
||||||
"German (auto-generated)": "德语 (自动生成)",
|
"German (auto-generated)": "德语 (自动生成)",
|
||||||
"Indonesian (auto-generated)": "印尼语 (自动生成)",
|
"Indonesian (auto-generated)": "印尼语 (自动生成)",
|
||||||
"Interlingue": "国际语",
|
"Interlingue": "国际语",
|
||||||
|
@ -436,7 +436,7 @@
|
||||||
"Turkish (auto-generated)": "土耳其语 (自动生成)",
|
"Turkish (auto-generated)": "土耳其语 (自动生成)",
|
||||||
"Spanish (Spain)": "西班牙语 (西班牙)",
|
"Spanish (Spain)": "西班牙语 (西班牙)",
|
||||||
"preferences_watch_history_label": "启用观看历史: ",
|
"preferences_watch_history_label": "启用观看历史: ",
|
||||||
"search_message_use_another_instance": " 你也可以 <a href=\"`x`\">在另一实例上搜索</a>。",
|
"search_message_use_another_instance": "你也可以 <a href=\"`x`\">在另一实例上搜索</a>。",
|
||||||
"search_filters_title": "过滤器",
|
"search_filters_title": "过滤器",
|
||||||
"search_filters_date_label": "上传日期",
|
"search_filters_date_label": "上传日期",
|
||||||
"search_filters_apply_button": "应用所选过滤器",
|
"search_filters_apply_button": "应用所选过滤器",
|
||||||
|
@ -479,5 +479,10 @@
|
||||||
"The Popular feed has been disabled by the administrator.": "“流行”源已被管理员禁用。",
|
"The Popular feed has been disabled by the administrator.": "“流行”源已被管理员禁用。",
|
||||||
"carousel_slide": "当前为第 {{current}} 张图,共 {{total}} 张图",
|
"carousel_slide": "当前为第 {{current}} 张图,共 {{total}} 张图",
|
||||||
"carousel_skip": "跳过图集",
|
"carousel_skip": "跳过图集",
|
||||||
"carousel_go_to": "转到图 `x`"
|
"carousel_go_to": "转到图 `x`",
|
||||||
|
"preferences_preload_label": "预加载视频数据: ",
|
||||||
|
"Filipino (auto-generated)": "菲律宾语 (自动生成)",
|
||||||
|
"channel_tab_posts_label": "帖子",
|
||||||
|
"First page": "第一页",
|
||||||
|
"channel_tab_courses_label": "课程"
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,13 +338,13 @@
|
||||||
"channel_tab_community_label": "社群",
|
"channel_tab_community_label": "社群",
|
||||||
"search_filters_sort_option_relevance": "關聯",
|
"search_filters_sort_option_relevance": "關聯",
|
||||||
"search_filters_sort_option_rating": "評分",
|
"search_filters_sort_option_rating": "評分",
|
||||||
"search_filters_sort_option_date": "日期",
|
"search_filters_sort_option_date": "上傳日期",
|
||||||
"search_filters_sort_option_views": "檢視",
|
"search_filters_sort_option_views": "檢視",
|
||||||
"search_filters_type_label": "內容類型",
|
"search_filters_type_label": "內容類型",
|
||||||
"search_filters_duration_label": "時長",
|
"search_filters_duration_label": "時長",
|
||||||
"search_filters_features_label": "特色",
|
"search_filters_features_label": "特色",
|
||||||
"search_filters_sort_label": "排序",
|
"search_filters_sort_label": "排序",
|
||||||
"search_filters_date_option_hour": "小時",
|
"search_filters_date_option_hour": "最後一小時",
|
||||||
"search_filters_date_option_today": "今天",
|
"search_filters_date_option_today": "今天",
|
||||||
"search_filters_date_option_week": "週",
|
"search_filters_date_option_week": "週",
|
||||||
"search_filters_date_option_month": "月",
|
"search_filters_date_option_month": "月",
|
||||||
|
@ -442,7 +442,7 @@
|
||||||
"search_filters_duration_option_none": "任何時長",
|
"search_filters_duration_option_none": "任何時長",
|
||||||
"search_filters_duration_option_medium": "中等(4到20分鐘)",
|
"search_filters_duration_option_medium": "中等(4到20分鐘)",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_message_use_another_instance": " 您也可以<a href=\"`x`\">在其他站台上搜尋</a>。",
|
"search_message_use_another_instance": "您也可以<a href=\"`x`\">在其他站台上搜尋</a>。",
|
||||||
"search_filters_title": "過濾條件",
|
"search_filters_title": "過濾條件",
|
||||||
"search_filters_date_label": "上傳日期",
|
"search_filters_date_label": "上傳日期",
|
||||||
"search_filters_type_option_all": "任何類型",
|
"search_filters_type_option_all": "任何類型",
|
||||||
|
@ -479,5 +479,10 @@
|
||||||
"carousel_slide": "第 {{current}} 張投影片,共 {{total}} 張",
|
"carousel_slide": "第 {{current}} 張投影片,共 {{total}} 張",
|
||||||
"carousel_skip": "略過輪播",
|
"carousel_skip": "略過輪播",
|
||||||
"carousel_go_to": "跳到投影片 `x`",
|
"carousel_go_to": "跳到投影片 `x`",
|
||||||
"The Popular feed has been disabled by the administrator.": "熱門 feed 已被管理員停用。"
|
"The Popular feed has been disabled by the administrator.": "熱門 feed 已被管理員停用。",
|
||||||
|
"preferences_preload_label": "預先載入影片資訊 ",
|
||||||
|
"Filipino (auto-generated)": "菲律賓語(自動產生)",
|
||||||
|
"channel_tab_courses_label": "課程",
|
||||||
|
"First page": "第一頁",
|
||||||
|
"channel_tab_posts_label": "貼文"
|
||||||
}
|
}
|
||||||
|
|
2
mocks
2
mocks
|
@ -1 +1 @@
|
||||||
Subproject commit 11ec372f72747c09d48ffef04843f72be67d5b54
|
Subproject commit b55d58dea94f7144ff0205857dfa70ec14eaa872
|
56
scripts/generate_js_licenses.cr
Normal file
56
scripts/generate_js_licenses.cr
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# This file automatically generates Crystal strings of rows within an HTML Javascript licenses table
|
||||||
|
#
|
||||||
|
# These strings will then be placed within a `<%= %>` statement in licenses.ecr at compile time which
|
||||||
|
# will be interpolated at run-time. This interpolation is only for the translation of the "source" string
|
||||||
|
# so maybe we can just switch to a non-translated string to simplify the logic here.
|
||||||
|
#
|
||||||
|
# The Javascript Web Labels table defined at https://www.gnu.org/software/librejs/free-your-javascript.html#step3
|
||||||
|
# for example just reiterates the name of the source file rather than use a "source" string.
|
||||||
|
all_javascript_files = Dir.glob("assets/**/*.js")
|
||||||
|
|
||||||
|
videojs_js = [] of String
|
||||||
|
invidious_js = [] of String
|
||||||
|
|
||||||
|
all_javascript_files.each do |js_path|
|
||||||
|
if js_path.starts_with?("assets/videojs/")
|
||||||
|
videojs_js << js_path[7..]
|
||||||
|
else
|
||||||
|
invidious_js << js_path[7..]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_licence_tr(path, file_name, licence_name, licence_link, source_location)
|
||||||
|
tr = <<-HTML
|
||||||
|
"<tr>
|
||||||
|
<td><a href=\\"/#{path}\\">#{file_name}</a></td>
|
||||||
|
<td><a href=\\"#{licence_link}\\">#{licence_name}</a></td>
|
||||||
|
<td><a href=\\"#{source_location}\\">\#{translate(locale, "source")}</a></td>
|
||||||
|
</tr>"
|
||||||
|
HTML
|
||||||
|
|
||||||
|
# New lines are removed as to allow for using String.join and StringLiteral.split
|
||||||
|
# to get a clean list of each table row.
|
||||||
|
tr.gsub('\n', "")
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO Use videojs-dependencies.yml to generate license info for videojs javascript
|
||||||
|
jslicence_table_rows = [] of String
|
||||||
|
|
||||||
|
invidious_js.each do |path|
|
||||||
|
file_name = path.split('/')[-1]
|
||||||
|
|
||||||
|
# A couple non Invidious JS files are also shipped alongside Invidious due to various reasons
|
||||||
|
next if {
|
||||||
|
"sse.js", "silvermine-videojs-quality-selector.min.js", "videojs-youtube-annotations.min.js",
|
||||||
|
}.includes?(file_name)
|
||||||
|
|
||||||
|
jslicence_table_rows << create_licence_tr(
|
||||||
|
path: path,
|
||||||
|
file_name: file_name,
|
||||||
|
licence_name: "AGPL-3.0",
|
||||||
|
licence_link: "https://www.gnu.org/licenses/agpl-3.0.html",
|
||||||
|
source_location: path
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
puts jslicence_table_rows.join("\n")
|
22
shard.lock
22
shard.lock
|
@ -10,27 +10,27 @@ shards:
|
||||||
|
|
||||||
backtracer:
|
backtracer:
|
||||||
git: https://github.com/sija/backtracer.cr.git
|
git: https://github.com/sija/backtracer.cr.git
|
||||||
version: 1.2.1
|
version: 1.2.2
|
||||||
|
|
||||||
db:
|
db:
|
||||||
git: https://github.com/crystal-lang/crystal-db.git
|
git: https://github.com/crystal-lang/crystal-db.git
|
||||||
version: 0.10.1
|
version: 0.13.1
|
||||||
|
|
||||||
exception_page:
|
exception_page:
|
||||||
git: https://github.com/crystal-loot/exception_page.git
|
git: https://github.com/crystal-loot/exception_page.git
|
||||||
version: 0.2.2
|
version: 0.4.1
|
||||||
|
|
||||||
|
http_proxy:
|
||||||
|
git: https://github.com/mamantoha/http_proxy.git
|
||||||
|
version: 0.10.3
|
||||||
|
|
||||||
kemal:
|
kemal:
|
||||||
git: https://github.com/kemalcr/kemal.git
|
git: https://github.com/kemalcr/kemal.git
|
||||||
version: 1.1.2
|
version: 1.6.0
|
||||||
|
|
||||||
kilt:
|
|
||||||
git: https://github.com/jeromegn/kilt.git
|
|
||||||
version: 0.6.1
|
|
||||||
|
|
||||||
pg:
|
pg:
|
||||||
git: https://github.com/will/crystal-pg.git
|
git: https://github.com/will/crystal-pg.git
|
||||||
version: 0.24.0
|
version: 0.28.0
|
||||||
|
|
||||||
protodec:
|
protodec:
|
||||||
git: https://github.com/iv-org/protodec.git
|
git: https://github.com/iv-org/protodec.git
|
||||||
|
@ -42,9 +42,9 @@ shards:
|
||||||
|
|
||||||
spectator:
|
spectator:
|
||||||
git: https://github.com/icy-arctic-fox/spectator.git
|
git: https://github.com/icy-arctic-fox/spectator.git
|
||||||
version: 0.10.4
|
version: 0.10.6
|
||||||
|
|
||||||
sqlite3:
|
sqlite3:
|
||||||
git: https://github.com/crystal-lang/crystal-sqlite3.git
|
git: https://github.com/crystal-lang/crystal-sqlite3.git
|
||||||
version: 0.18.0
|
version: 0.21.0
|
||||||
|
|
||||||
|
|
29
shard.yml
29
shard.yml
|
@ -1,33 +1,32 @@
|
||||||
name: invidious
|
name: invidious
|
||||||
version: 0.20.1
|
version: 2.20250517.0-dev
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- Omar Roth <omarroth@protonmail.com>
|
- Invidious team <contact@invidious.io>
|
||||||
- Invidious team
|
- Contributors!
|
||||||
|
|
||||||
targets:
|
description: |
|
||||||
invidious:
|
Invidious is an alternative front-end to YouTube
|
||||||
main: src/invidious.cr
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pg:
|
pg:
|
||||||
github: will/crystal-pg
|
github: will/crystal-pg
|
||||||
version: ~> 0.24.0
|
version: ~> 0.28.0
|
||||||
sqlite3:
|
sqlite3:
|
||||||
github: crystal-lang/crystal-sqlite3
|
github: crystal-lang/crystal-sqlite3
|
||||||
version: ~> 0.18.0
|
version: ~> 0.21.0
|
||||||
kemal:
|
kemal:
|
||||||
github: kemalcr/kemal
|
github: kemalcr/kemal
|
||||||
version: ~> 1.1.2
|
version: ~> 1.6.0
|
||||||
kilt:
|
|
||||||
github: jeromegn/kilt
|
|
||||||
version: ~> 0.6.1
|
|
||||||
protodec:
|
protodec:
|
||||||
github: iv-org/protodec
|
github: iv-org/protodec
|
||||||
version: ~> 0.1.5
|
version: ~> 0.1.5
|
||||||
athena-negotiation:
|
athena-negotiation:
|
||||||
github: athena-framework/negotiation
|
github: athena-framework/negotiation
|
||||||
version: ~> 0.1.1
|
version: ~> 0.1.1
|
||||||
|
http_proxy:
|
||||||
|
github: mamantoha/http_proxy
|
||||||
|
version: ~> 0.10.3
|
||||||
|
|
||||||
development_dependencies:
|
development_dependencies:
|
||||||
spectator:
|
spectator:
|
||||||
|
@ -37,6 +36,10 @@ development_dependencies:
|
||||||
github: crystal-ameba/ameba
|
github: crystal-ameba/ameba
|
||||||
version: ~> 1.6.1
|
version: ~> 1.6.1
|
||||||
|
|
||||||
crystal: ">= 1.0.0, < 2.0.0"
|
crystal: ">= 1.10.0, < 2.0.0"
|
||||||
|
|
||||||
license: AGPLv3
|
license: AGPLv3
|
||||||
|
|
||||||
|
repository: https://github.com/iv-org/invidious
|
||||||
|
homepage: https://invidious.io
|
||||||
|
documentation: https://docs.invidious.io
|
||||||
|
|
|
@ -27,8 +27,8 @@ Spectator.describe Invidious::Hashtag do
|
||||||
expect(video_11.length_seconds).to eq((56.minutes + 41.seconds).total_seconds.to_i32)
|
expect(video_11.length_seconds).to eq((56.minutes + 41.seconds).total_seconds.to_i32)
|
||||||
expect(video_11.views).to eq(40_504_893)
|
expect(video_11.views).to eq(40_504_893)
|
||||||
|
|
||||||
expect(video_11.live_now).to be_false
|
expect(video_11.badges.live_now?).to be_false
|
||||||
expect(video_11.premium).to be_false
|
expect(video_11.badges.premium?).to be_false
|
||||||
expect(video_11.premiere_timestamp).to be_nil
|
expect(video_11.premiere_timestamp).to be_nil
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -49,8 +49,8 @@ Spectator.describe Invidious::Hashtag do
|
||||||
expect(video_35.length_seconds).to eq((3.minutes + 14.seconds).total_seconds.to_i32)
|
expect(video_35.length_seconds).to eq((3.minutes + 14.seconds).total_seconds.to_i32)
|
||||||
expect(video_35.views).to eq(30_790_049)
|
expect(video_35.views).to eq(30_790_049)
|
||||||
|
|
||||||
expect(video_35.live_now).to be_false
|
expect(video_35.badges.live_now?).to be_false
|
||||||
expect(video_35.premium).to be_false
|
expect(video_35.badges.premium?).to be_false
|
||||||
expect(video_35.premiere_timestamp).to be_nil
|
expect(video_35.premiere_timestamp).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,8 +80,8 @@ Spectator.describe Invidious::Hashtag do
|
||||||
expect(video_41.length_seconds).to eq((1.hour).total_seconds.to_i32)
|
expect(video_41.length_seconds).to eq((1.hour).total_seconds.to_i32)
|
||||||
expect(video_41.views).to eq(63_240)
|
expect(video_41.views).to eq(63_240)
|
||||||
|
|
||||||
expect(video_41.live_now).to be_false
|
expect(video_41.badges.live_now?).to be_false
|
||||||
expect(video_41.premium).to be_false
|
expect(video_41.badges.premium?).to be_false
|
||||||
expect(video_41.premiere_timestamp).to be_nil
|
expect(video_41.premiere_timestamp).to be_nil
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -102,8 +102,8 @@ Spectator.describe Invidious::Hashtag do
|
||||||
expect(video_48.length_seconds).to eq((35.minutes + 46.seconds).total_seconds.to_i32)
|
expect(video_48.length_seconds).to eq((35.minutes + 46.seconds).total_seconds.to_i32)
|
||||||
expect(video_48.views).to eq(68_704)
|
expect(video_48.views).to eq(68_704)
|
||||||
|
|
||||||
expect(video_48.live_now).to be_false
|
expect(video_48.badges.live_now?).to be_false
|
||||||
expect(video_48.premium).to be_false
|
expect(video_48.badges.premium?).to be_false
|
||||||
expect(video_48.premiere_timestamp).to be_nil
|
expect(video_48.premiere_timestamp).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,8 +17,8 @@ Spectator.describe "parse_video_info" do
|
||||||
# Basic video infos
|
# Basic video infos
|
||||||
|
|
||||||
expect(info["title"].as_s).to eq("I Gave My 100,000,000th Subscriber An Island")
|
expect(info["title"].as_s).to eq("I Gave My 100,000,000th Subscriber An Island")
|
||||||
expect(info["views"].as_i).to eq(126_573_823)
|
expect(info["views"].as_i).to eq(220_226_287)
|
||||||
expect(info["likes"].as_i).to eq(5_157_654)
|
expect(info["likes"].as_i).to eq(6_870_691)
|
||||||
|
|
||||||
# For some reason the video length from VideoDetails and the
|
# For some reason the video length from VideoDetails and the
|
||||||
# one from microformat differs by 1s...
|
# one from microformat differs by 1s...
|
||||||
|
@ -48,12 +48,12 @@ Spectator.describe "parse_video_info" do
|
||||||
|
|
||||||
expect(info["relatedVideos"].as_a.size).to eq(20)
|
expect(info["relatedVideos"].as_a.size).to eq(20)
|
||||||
|
|
||||||
expect(info["relatedVideos"][0]["id"]).to eq("Hwybp38GnZw")
|
expect(info["relatedVideos"][0]["id"]).to eq("krsBRQbOPQ4")
|
||||||
expect(info["relatedVideos"][0]["title"]).to eq("I Built Willy Wonka's Chocolate Factory!")
|
expect(info["relatedVideos"][0]["title"]).to eq("$1 vs $250,000,000 Private Island!")
|
||||||
expect(info["relatedVideos"][0]["author"]).to eq("MrBeast")
|
expect(info["relatedVideos"][0]["author"]).to eq("MrBeast")
|
||||||
expect(info["relatedVideos"][0]["ucid"]).to eq("UCX6OQ3DkcsbYNE6H8uQQuVA")
|
expect(info["relatedVideos"][0]["ucid"]).to eq("UCX6OQ3DkcsbYNE6H8uQQuVA")
|
||||||
expect(info["relatedVideos"][0]["view_count"]).to eq("179877630")
|
expect(info["relatedVideos"][0]["view_count"]).to eq("230617484")
|
||||||
expect(info["relatedVideos"][0]["short_view_count"]).to eq("179M")
|
expect(info["relatedVideos"][0]["short_view_count"]).to eq("230M")
|
||||||
expect(info["relatedVideos"][0]["author_verified"]).to eq("true")
|
expect(info["relatedVideos"][0]["author_verified"]).to eq("true")
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
@ -76,11 +76,11 @@ Spectator.describe "parse_video_info" do
|
||||||
expect(info["ucid"].as_s).to eq("UCX6OQ3DkcsbYNE6H8uQQuVA")
|
expect(info["ucid"].as_s).to eq("UCX6OQ3DkcsbYNE6H8uQQuVA")
|
||||||
|
|
||||||
expect(info["authorThumbnail"].as_s).to eq(
|
expect(info["authorThumbnail"].as_s).to eq(
|
||||||
"https://yt3.ggpht.com/ytc/AL5GRJVuqw82ERvHzsmBxL7avr1dpBtsVIXcEzBPZaloFg=s48-c-k-c0x00ffffff-no-rj"
|
"https://yt3.ggpht.com/fxGKYucJAVme-Yz4fsdCroCFCrANWqw0ql4GYuvx8Uq4l_euNJHgE-w9MTkLQA805vWCi-kE0g=s48-c-k-c0x00ffffff-no-rj"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(info["authorVerified"].as_bool).to be_true
|
expect(info["authorVerified"].as_bool).to be_true
|
||||||
expect(info["subCountText"].as_s).to eq("143M")
|
expect(info["subCountText"].as_s).to eq("320M")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "parses a regular video with no descrition/comments" do
|
it "parses a regular video with no descrition/comments" do
|
||||||
|
@ -99,8 +99,8 @@ Spectator.describe "parse_video_info" do
|
||||||
# Basic video infos
|
# Basic video infos
|
||||||
|
|
||||||
expect(info["title"].as_s).to eq("Chris Rea - Auberge")
|
expect(info["title"].as_s).to eq("Chris Rea - Auberge")
|
||||||
expect(info["views"].as_i).to eq(10_943_126)
|
expect(info["views"].as_i).to eq(14_324_584)
|
||||||
expect(info["likes"].as_i).to eq(0)
|
expect(info["likes"].as_i).to eq(35_870)
|
||||||
expect(info["lengthSeconds"].as_i).to eq(283_i64)
|
expect(info["lengthSeconds"].as_i).to eq(283_i64)
|
||||||
expect(info["published"].as_s).to eq("2012-05-21T00:00:00Z")
|
expect(info["published"].as_s).to eq("2012-05-21T00:00:00Z")
|
||||||
|
|
||||||
|
@ -132,14 +132,14 @@ Spectator.describe "parse_video_info" do
|
||||||
|
|
||||||
# Related videos
|
# Related videos
|
||||||
|
|
||||||
expect(info["relatedVideos"].as_a.size).to eq(19)
|
expect(info["relatedVideos"].as_a.size).to eq(20)
|
||||||
|
|
||||||
expect(info["relatedVideos"][0]["id"]).to eq("Ww3KeZ2_Yv4")
|
expect(info["relatedVideos"][0]["id"]).to eq("gUUdQfnshJ4")
|
||||||
expect(info["relatedVideos"][0]["title"]).to eq("Chris Rea")
|
expect(info["relatedVideos"][0]["title"]).to eq("Chris Rea - The Road To Hell 1989 Full Version")
|
||||||
expect(info["relatedVideos"][0]["author"]).to eq("PanMusic")
|
expect(info["relatedVideos"][0]["author"]).to eq("NEA ZIXNH")
|
||||||
expect(info["relatedVideos"][0]["ucid"]).to eq("UCsKAPSuh1iNbLWUga_igPyA")
|
expect(info["relatedVideos"][0]["ucid"]).to eq("UCYMEOGcvav3gCgImK2J07CQ")
|
||||||
expect(info["relatedVideos"][0]["view_count"]).to eq("31581")
|
expect(info["relatedVideos"][0]["view_count"]).to eq("53298661")
|
||||||
expect(info["relatedVideos"][0]["short_view_count"]).to eq("31K")
|
expect(info["relatedVideos"][0]["short_view_count"]).to eq("53M")
|
||||||
expect(info["relatedVideos"][0]["author_verified"]).to eq("false")
|
expect(info["relatedVideos"][0]["author_verified"]).to eq("false")
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
@ -156,11 +156,13 @@ Spectator.describe "parse_video_info" do
|
||||||
|
|
||||||
# Author infos
|
# Author infos
|
||||||
|
|
||||||
expect(info["author"].as_s).to eq("ChrisReaOfficial")
|
expect(info["author"].as_s).to eq("ChrisReaVideos")
|
||||||
expect(info["ucid"].as_s).to eq("UC_5q6nWPbD30-y6oiWF_oNA")
|
expect(info["ucid"].as_s).to eq("UC_5q6nWPbD30-y6oiWF_oNA")
|
||||||
|
|
||||||
expect(info["authorThumbnail"].as_s).to be_empty
|
expect(info["authorThumbnail"].as_s).to eq(
|
||||||
|
"https://yt3.ggpht.com/ytc/AIdro_n71nsegpKfjeRKwn1JJmK5IVMh_7j5m_h3_1KnUUg=s48-c-k-c0x00ffffff-no-rj"
|
||||||
|
)
|
||||||
expect(info["authorVerified"].as_bool).to be_false
|
expect(info["authorVerified"].as_bool).to be_false
|
||||||
expect(info["subCountText"].as_s).to eq("-")
|
expect(info["subCountText"].as_s).to eq("3.11K")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Overrides for Kemal's `content_for` macro in order to keep using
|
|
||||||
# kilt as it was before Kemal v1.1.1 (Kemal PR #618).
|
|
||||||
|
|
||||||
require "kemal"
|
|
||||||
require "kilt"
|
|
||||||
|
|
||||||
macro content_for(key, file = __FILE__)
|
|
||||||
%proc = ->() {
|
|
||||||
__kilt_io__ = IO::Memory.new
|
|
||||||
{{ yield }}
|
|
||||||
__kilt_io__.to_s
|
|
||||||
}
|
|
||||||
|
|
||||||
CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, %proc
|
|
||||||
nil
|
|
||||||
end
|
|
|
@ -71,7 +71,7 @@ def send_file(env : HTTP::Server::Context, file_path : String, data : Slice(UInt
|
||||||
filesize = data.bytesize
|
filesize = data.bytesize
|
||||||
attachment(env, filename, disposition)
|
attachment(env, filename, disposition)
|
||||||
|
|
||||||
Kemal.config.static_headers.try(&.call(env.response, file_path, filestat))
|
Kemal.config.static_headers.try(&.call(env, file_path, filestat))
|
||||||
|
|
||||||
file = IO::Memory.new(data)
|
file = IO::Memory.new(data)
|
||||||
if env.request.method == "GET" && env.request.headers.has_key?("Range")
|
if env.request.method == "GET" && env.request.headers.has_key?("Range")
|
||||||
|
|
|
@ -17,12 +17,11 @@
|
||||||
require "digest/md5"
|
require "digest/md5"
|
||||||
require "file_utils"
|
require "file_utils"
|
||||||
|
|
||||||
# Require kemal, kilt, then our own overrides
|
# Require kemal, then our own overrides
|
||||||
require "kemal"
|
require "kemal"
|
||||||
require "kilt"
|
|
||||||
require "./ext/kemal_content_for.cr"
|
|
||||||
require "./ext/kemal_static_file_handler.cr"
|
require "./ext/kemal_static_file_handler.cr"
|
||||||
|
|
||||||
|
require "http_proxy"
|
||||||
require "athena-negotiation"
|
require "athena-negotiation"
|
||||||
require "openssl/hmac"
|
require "openssl/hmac"
|
||||||
require "option_parser"
|
require "option_parser"
|
||||||
|
@ -48,7 +47,8 @@ require "./invidious/channels/*"
|
||||||
require "./invidious/user/*"
|
require "./invidious/user/*"
|
||||||
require "./invidious/search/*"
|
require "./invidious/search/*"
|
||||||
require "./invidious/routes/**"
|
require "./invidious/routes/**"
|
||||||
require "./invidious/jobs/**"
|
require "./invidious/jobs/base_job"
|
||||||
|
require "./invidious/jobs/*"
|
||||||
|
|
||||||
# Declare the base namespace for invidious
|
# Declare the base namespace for invidious
|
||||||
module Invidious
|
module Invidious
|
||||||
|
@ -92,6 +92,14 @@ SOFTWARE = {
|
||||||
|
|
||||||
YT_POOL = YoutubeConnectionPool.new(YT_URL, capacity: CONFIG.pool_size)
|
YT_POOL = YoutubeConnectionPool.new(YT_URL, capacity: CONFIG.pool_size)
|
||||||
|
|
||||||
|
# Image request pool
|
||||||
|
|
||||||
|
GGPHT_POOL = YoutubeConnectionPool.new(URI.parse("https://yt3.ggpht.com"), capacity: CONFIG.pool_size)
|
||||||
|
|
||||||
|
COMPANION_POOL = CompanionConnectionPool.new(
|
||||||
|
capacity: CONFIG.pool_size
|
||||||
|
)
|
||||||
|
|
||||||
# CLI
|
# CLI
|
||||||
Kemal.config.extra_options do |parser|
|
Kemal.config.extra_options do |parser|
|
||||||
parser.banner = "Usage: invidious [arguments]"
|
parser.banner = "Usage: invidious [arguments]"
|
||||||
|
@ -117,6 +125,9 @@ Kemal.config.extra_options do |parser|
|
||||||
parser.on("-l LEVEL", "--log-level=LEVEL", "Log level, one of #{LogLevel.values} (default: #{CONFIG.log_level})") do |log_level|
|
parser.on("-l LEVEL", "--log-level=LEVEL", "Log level, one of #{LogLevel.values} (default: #{CONFIG.log_level})") do |log_level|
|
||||||
CONFIG.log_level = LogLevel.parse(log_level)
|
CONFIG.log_level = LogLevel.parse(log_level)
|
||||||
end
|
end
|
||||||
|
parser.on("-k", "--colorize", "Colorize logs") do
|
||||||
|
CONFIG.colorize_logs = true
|
||||||
|
end
|
||||||
parser.on("-v", "--version", "Print version") do
|
parser.on("-v", "--version", "Print version") do
|
||||||
puts SOFTWARE.to_pretty_json
|
puts SOFTWARE.to_pretty_json
|
||||||
exit
|
exit
|
||||||
|
@ -133,7 +144,7 @@ if CONFIG.output.upcase != "STDOUT"
|
||||||
FileUtils.mkdir_p(File.dirname(CONFIG.output))
|
FileUtils.mkdir_p(File.dirname(CONFIG.output))
|
||||||
end
|
end
|
||||||
OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mode: "a")
|
OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mode: "a")
|
||||||
LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level)
|
LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level, CONFIG.colorize_logs)
|
||||||
|
|
||||||
# Check table integrity
|
# Check table integrity
|
||||||
Invidious::Database.check_integrity(CONFIG)
|
Invidious::Database.check_integrity(CONFIG)
|
||||||
|
@ -184,11 +195,14 @@ if CONFIG.popular_enabled
|
||||||
Invidious::Jobs.register Invidious::Jobs::PullPopularVideosJob.new(PG_DB)
|
Invidious::Jobs.register Invidious::Jobs::PullPopularVideosJob.new(PG_DB)
|
||||||
end
|
end
|
||||||
|
|
||||||
CONNECTION_CHANNEL = ::Channel({Bool, ::Channel(PQ::Notification)}).new(32)
|
NOTIFICATION_CHANNEL = ::Channel(VideoNotification).new(32)
|
||||||
Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(CONNECTION_CHANNEL, CONFIG.database_url)
|
CONNECTION_CHANNEL = ::Channel({Bool, ::Channel(PQ::Notification)}).new(32)
|
||||||
|
Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(NOTIFICATION_CHANNEL, CONNECTION_CHANNEL, CONFIG.database_url)
|
||||||
|
|
||||||
Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
|
Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
|
||||||
|
|
||||||
|
Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new
|
||||||
|
|
||||||
Invidious::Jobs.start_all
|
Invidious::Jobs.start_all
|
||||||
|
|
||||||
def popular_videos
|
def popular_videos
|
||||||
|
@ -211,8 +225,8 @@ error 500 do |env, ex|
|
||||||
error_template(500, ex)
|
error_template(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
static_headers do |response|
|
static_headers do |env|
|
||||||
response.headers.add("Cache-Control", "max-age=2629800")
|
env.response.headers.add("Cache-Control", "max-age=2629800")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Init Kemal
|
# Init Kemal
|
||||||
|
@ -229,8 +243,6 @@ add_context_storage_type(Preferences)
|
||||||
add_context_storage_type(Invidious::User)
|
add_context_storage_type(Invidious::User)
|
||||||
|
|
||||||
Kemal.config.logger = LOGGER
|
Kemal.config.logger = LOGGER
|
||||||
Kemal.config.host_binding = Kemal.config.host_binding != "0.0.0.0" ? Kemal.config.host_binding : CONFIG.host_binding
|
|
||||||
Kemal.config.port = Kemal.config.port != 3000 ? Kemal.config.port : CONFIG.port
|
|
||||||
Kemal.config.app_name = "Invidious"
|
Kemal.config.app_name = "Invidious"
|
||||||
|
|
||||||
# Use in kemal's production mode.
|
# Use in kemal's production mode.
|
||||||
|
@ -239,4 +251,16 @@ Kemal.config.app_name = "Invidious"
|
||||||
Kemal.config.env = "production" if !ENV.has_key?("KEMAL_ENV")
|
Kemal.config.env = "production" if !ENV.has_key?("KEMAL_ENV")
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
Kemal.run
|
Kemal.run do |config|
|
||||||
|
if socket_binding = CONFIG.socket_binding
|
||||||
|
File.delete?(socket_binding.path)
|
||||||
|
# Create a socket and set its desired permissions
|
||||||
|
server = UNIXServer.new(socket_binding.path)
|
||||||
|
perms = socket_binding.permissions.to_i(base: 8)
|
||||||
|
File.chmod(socket_binding.path, perms)
|
||||||
|
config.server.not_nil!.bind server
|
||||||
|
else
|
||||||
|
Kemal.config.host_binding = Kemal.config.host_binding != "0.0.0.0" ? Kemal.config.host_binding : CONFIG.host_binding
|
||||||
|
Kemal.config.port = Kemal.config.port != 3000 ? Kemal.config.port : CONFIG.port
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -223,7 +223,7 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
||||||
length_seconds = channel_video.try &.length_seconds
|
length_seconds = channel_video.try &.length_seconds
|
||||||
length_seconds ||= 0
|
length_seconds ||= 0
|
||||||
|
|
||||||
live_now = channel_video.try &.live_now
|
live_now = channel_video.try &.badges.live_now?
|
||||||
live_now ||= false
|
live_now ||= false
|
||||||
|
|
||||||
premiere_timestamp = channel_video.try &.premiere_timestamp
|
premiere_timestamp = channel_video.try &.premiere_timestamp
|
||||||
|
@ -249,11 +249,7 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
||||||
|
|
||||||
if was_insert
|
if was_insert
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Inserted, updating subscriptions")
|
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Inserted, updating subscriptions")
|
||||||
if CONFIG.enable_user_notifications
|
NOTIFICATION_CHANNEL.send(VideoNotification.from_video(video))
|
||||||
Invidious::Database::Users.add_notification(video)
|
|
||||||
else
|
|
||||||
Invidious::Database::Users.feed_needs_update(video)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Updated")
|
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Updated")
|
||||||
end
|
end
|
||||||
|
@ -275,7 +271,7 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
||||||
ucid: video.ucid,
|
ucid: video.ucid,
|
||||||
author: video.author,
|
author: video.author,
|
||||||
length_seconds: video.length_seconds,
|
length_seconds: video.length_seconds,
|
||||||
live_now: video.live_now,
|
live_now: video.badges.live_now?,
|
||||||
premiere_timestamp: video.premiere_timestamp,
|
premiere_timestamp: video.premiere_timestamp,
|
||||||
views: video.views,
|
views: video.views,
|
||||||
})
|
})
|
||||||
|
@ -285,11 +281,7 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
||||||
if Time.utc - video.published > 1.minute
|
if Time.utc - video.published > 1.minute
|
||||||
was_insert = Invidious::Database::ChannelVideos.insert(video)
|
was_insert = Invidious::Database::ChannelVideos.insert(video)
|
||||||
if was_insert
|
if was_insert
|
||||||
if CONFIG.enable_user_notifications
|
NOTIFICATION_CHANNEL.send(VideoNotification.from_video(video))
|
||||||
Invidious::Database::Users.add_notification(video)
|
|
||||||
else
|
|
||||||
Invidious::Database::Users.feed_needs_update(video)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -44,3 +44,12 @@ def fetch_channel_releases(ucid, author, continuation)
|
||||||
end
|
end
|
||||||
return extract_items(initial_data, author, ucid)
|
return extract_items(initial_data, author, ucid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_channel_courses(ucid, author, continuation)
|
||||||
|
if continuation
|
||||||
|
initial_data = YoutubeAPI.browse(continuation)
|
||||||
|
else
|
||||||
|
initial_data = YoutubeAPI.browse(ucid, params: "Egdjb3Vyc2Vz8gYFCgPCAQA%3D")
|
||||||
|
end
|
||||||
|
return extract_items(initial_data, author, ucid)
|
||||||
|
end
|
||||||
|
|
|
@ -1,78 +1,3 @@
|
||||||
def produce_channel_content_continuation(ucid, content_type, page = 1, auto_generated = nil, sort_by = "newest", v2 = false)
|
|
||||||
object_inner_2 = {
|
|
||||||
"2:0:embedded" => {
|
|
||||||
"1:0:varint" => 0_i64,
|
|
||||||
},
|
|
||||||
"5:varint" => 50_i64,
|
|
||||||
"6:varint" => 1_i64,
|
|
||||||
"7:varint" => (page * 30).to_i64,
|
|
||||||
"9:varint" => 1_i64,
|
|
||||||
"10:varint" => 0_i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
object_inner_2_encoded = object_inner_2
|
|
||||||
.try { |i| Protodec::Any.cast_json(i) }
|
|
||||||
.try { |i| Protodec::Any.from_json(i) }
|
|
||||||
.try { |i| Base64.urlsafe_encode(i) }
|
|
||||||
.try { |i| URI.encode_www_form(i) }
|
|
||||||
|
|
||||||
content_type_numerical =
|
|
||||||
case content_type
|
|
||||||
when "videos" then 15
|
|
||||||
when "livestreams" then 14
|
|
||||||
else 15 # Fallback to "videos"
|
|
||||||
end
|
|
||||||
|
|
||||||
sort_by_numerical =
|
|
||||||
case sort_by
|
|
||||||
when "newest" then 1_i64
|
|
||||||
when "popular" then 2_i64
|
|
||||||
when "oldest" then 4_i64
|
|
||||||
else 1_i64 # Fallback to "newest"
|
|
||||||
end
|
|
||||||
|
|
||||||
object_inner_1 = {
|
|
||||||
"110:embedded" => {
|
|
||||||
"3:embedded" => {
|
|
||||||
"#{content_type_numerical}:embedded" => {
|
|
||||||
"1:embedded" => {
|
|
||||||
"1:string" => object_inner_2_encoded,
|
|
||||||
},
|
|
||||||
"2:embedded" => {
|
|
||||||
"1:string" => "00000000-0000-0000-0000-000000000000",
|
|
||||||
},
|
|
||||||
"3:varint" => sort_by_numerical,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
object_inner_1_encoded = object_inner_1
|
|
||||||
.try { |i| Protodec::Any.cast_json(i) }
|
|
||||||
.try { |i| Protodec::Any.from_json(i) }
|
|
||||||
.try { |i| Base64.urlsafe_encode(i) }
|
|
||||||
.try { |i| URI.encode_www_form(i) }
|
|
||||||
|
|
||||||
object = {
|
|
||||||
"80226972:embedded" => {
|
|
||||||
"2:string" => ucid,
|
|
||||||
"3:string" => object_inner_1_encoded,
|
|
||||||
"35:string" => "browse-feed#{ucid}videos102",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
continuation = object.try { |i| Protodec::Any.cast_json(i) }
|
|
||||||
.try { |i| Protodec::Any.from_json(i) }
|
|
||||||
.try { |i| Base64.urlsafe_encode(i) }
|
|
||||||
.try { |i| URI.encode_www_form(i) }
|
|
||||||
|
|
||||||
return continuation
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_initial_content_ctoken(ucid, content_type, sort_by) : String
|
|
||||||
return produce_channel_content_continuation(ucid, content_type, sort_by: sort_by)
|
|
||||||
end
|
|
||||||
|
|
||||||
module Invidious::Channel::Tabs
|
module Invidious::Channel::Tabs
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
|
@ -101,7 +26,7 @@ module Invidious::Channel::Tabs
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_videos(author : String, ucid : String, *, continuation : String? = nil, sort_by = "newest")
|
def get_videos(author : String, ucid : String, *, continuation : String? = nil, sort_by = "newest")
|
||||||
continuation ||= make_initial_content_ctoken(ucid, "videos", sort_by)
|
continuation ||= make_initial_videos_ctoken(ucid, sort_by)
|
||||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
|
||||||
return extract_items(initial_data, author, ucid)
|
return extract_items(initial_data, author, ucid)
|
||||||
|
@ -130,14 +55,10 @@ module Invidious::Channel::Tabs
|
||||||
# Shorts
|
# Shorts
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
def get_shorts(channel : AboutChannel, continuation : String? = nil)
|
def get_shorts(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||||
if continuation.nil?
|
continuation ||= make_initial_shorts_ctoken(channel.ucid, sort_by)
|
||||||
# EgZzaG9ydHPyBgUKA5oBAA%3D%3D is the protobuf object to load "shorts"
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
# TODO: try to extract the continuation tokens that allows other sorting options
|
|
||||||
initial_data = YoutubeAPI.browse(channel.ucid, params: "EgZzaG9ydHPyBgUKA5oBAA%3D%3D")
|
|
||||||
else
|
|
||||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
|
||||||
end
|
|
||||||
return extract_items(initial_data, channel.author, channel.ucid)
|
return extract_items(initial_data, channel.author, channel.ucid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -145,9 +66,8 @@ module Invidious::Channel::Tabs
|
||||||
# Livestreams
|
# Livestreams
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
def get_livestreams(channel : AboutChannel, continuation : String? = nil, sort_by = "newest")
|
def get_livestreams(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||||
continuation ||= make_initial_content_ctoken(channel.ucid, "livestreams", sort_by)
|
continuation ||= make_initial_livestreams_ctoken(channel.ucid, sort_by)
|
||||||
|
|
||||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
|
||||||
return extract_items(initial_data, channel.author, channel.ucid)
|
return extract_items(initial_data, channel.author, channel.ucid)
|
||||||
|
@ -171,4 +91,102 @@ module Invidious::Channel::Tabs
|
||||||
|
|
||||||
return items, next_continuation
|
return items, next_continuation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# C-tokens
|
||||||
|
# -------------------
|
||||||
|
|
||||||
|
private def sort_options_videos_short(sort_by : String)
|
||||||
|
case sort_by
|
||||||
|
when "newest" then return 4_i64
|
||||||
|
when "popular" then return 2_i64
|
||||||
|
when "oldest" then return 5_i64
|
||||||
|
else return 4_i64 # Fallback to "newest"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate the initial "continuation token" to get the first page of the
|
||||||
|
# "videos" tab. The following page requires the ctoken provided in that
|
||||||
|
# first page, and so on.
|
||||||
|
private def make_initial_videos_ctoken(ucid : String, sort_by = "newest")
|
||||||
|
object = {
|
||||||
|
"15:embedded" => {
|
||||||
|
"2:embedded" => {
|
||||||
|
"1:string" => "00000000-0000-0000-0000-000000000000",
|
||||||
|
},
|
||||||
|
"4:varint" => sort_options_videos_short(sort_by),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel_ctoken_wrap(ucid, object)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate the initial "continuation token" to get the first page of the
|
||||||
|
# "shorts" tab. The following page requires the ctoken provided in that
|
||||||
|
# first page, and so on.
|
||||||
|
private def make_initial_shorts_ctoken(ucid : String, sort_by = "newest")
|
||||||
|
object = {
|
||||||
|
"10:embedded" => {
|
||||||
|
"2:embedded" => {
|
||||||
|
"1:string" => "00000000-0000-0000-0000-000000000000",
|
||||||
|
},
|
||||||
|
"4:varint" => sort_options_videos_short(sort_by),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel_ctoken_wrap(ucid, object)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate the initial "continuation token" to get the first page of the
|
||||||
|
# "livestreams" tab. The following page requires the ctoken provided in that
|
||||||
|
# first page, and so on.
|
||||||
|
private def make_initial_livestreams_ctoken(ucid : String, sort_by = "newest")
|
||||||
|
sort_by_numerical =
|
||||||
|
case sort_by
|
||||||
|
when "newest" then 12_i64
|
||||||
|
when "popular" then 14_i64
|
||||||
|
when "oldest" then 13_i64
|
||||||
|
else 12_i64 # Fallback to "newest"
|
||||||
|
end
|
||||||
|
|
||||||
|
object = {
|
||||||
|
"14:embedded" => {
|
||||||
|
"2:embedded" => {
|
||||||
|
"1:string" => "00000000-0000-0000-0000-000000000000",
|
||||||
|
},
|
||||||
|
"5:varint" => sort_by_numerical,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel_ctoken_wrap(ucid, object)
|
||||||
|
end
|
||||||
|
|
||||||
|
# The protobuf structure common between videos/shorts/livestreams
|
||||||
|
private def channel_ctoken_wrap(ucid : String, object)
|
||||||
|
object_inner = {
|
||||||
|
"110:embedded" => {
|
||||||
|
"3:embedded" => object,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
object_inner_encoded = object_inner
|
||||||
|
.try { |i| Protodec::Any.cast_json(i) }
|
||||||
|
.try { |i| Protodec::Any.from_json(i) }
|
||||||
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
|
object = {
|
||||||
|
"80226972:embedded" => {
|
||||||
|
"2:string" => ucid,
|
||||||
|
"3:string" => object_inner_encoded,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
continuation = object.try { |i| Protodec::Any.cast_json(i) }
|
||||||
|
.try { |i| Protodec::Any.from_json(i) }
|
||||||
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
|
return continuation
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,11 +8,19 @@ struct DBConfig
|
||||||
property dbname : String
|
property dbname : String
|
||||||
end
|
end
|
||||||
|
|
||||||
|
struct SocketBindingConfig
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
property path : String
|
||||||
|
property permissions : String
|
||||||
|
end
|
||||||
|
|
||||||
struct ConfigPreferences
|
struct ConfigPreferences
|
||||||
include YAML::Serializable
|
include YAML::Serializable
|
||||||
|
|
||||||
property annotations : Bool = false
|
property annotations : Bool = false
|
||||||
property annotations_subscribed : Bool = false
|
property annotations_subscribed : Bool = false
|
||||||
|
property preload : Bool = true
|
||||||
property autoplay : Bool = false
|
property autoplay : Bool = false
|
||||||
property captions : Array(String) = ["", "", ""]
|
property captions : Array(String) = ["", "", ""]
|
||||||
property comments : Array(String) = ["youtube", ""]
|
property comments : Array(String) = ["youtube", ""]
|
||||||
|
@ -27,7 +35,7 @@ struct ConfigPreferences
|
||||||
property max_results : Int32 = 40
|
property max_results : Int32 = 40
|
||||||
property notifications_only : Bool = false
|
property notifications_only : Bool = false
|
||||||
property player_style : String = "invidious"
|
property player_style : String = "invidious"
|
||||||
property quality : String = "hd720"
|
property quality : String = "dash"
|
||||||
property quality_dash : String = "auto"
|
property quality_dash : String = "auto"
|
||||||
property default_home : String? = "Popular"
|
property default_home : String? = "Popular"
|
||||||
property feed_menu : Array(String) = ["Popular", "Trending", "Subscriptions", "Playlists"]
|
property feed_menu : Array(String) = ["Popular", "Trending", "Subscriptions", "Playlists"]
|
||||||
|
@ -54,9 +62,28 @@ struct ConfigPreferences
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
struct HTTPProxyConfig
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
property user : String
|
||||||
|
property password : String
|
||||||
|
property host : String
|
||||||
|
property port : Int32
|
||||||
|
end
|
||||||
|
|
||||||
class Config
|
class Config
|
||||||
include YAML::Serializable
|
include YAML::Serializable
|
||||||
|
|
||||||
|
class CompanionConfig
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
@[YAML::Field(converter: Preferences::URIConverter)]
|
||||||
|
property private_url : URI = URI.parse("")
|
||||||
|
|
||||||
|
@[YAML::Field(converter: Preferences::URIConverter)]
|
||||||
|
property public_url : URI = URI.parse("")
|
||||||
|
end
|
||||||
|
|
||||||
# Number of threads to use for crawling videos from channels (for updating subscriptions)
|
# Number of threads to use for crawling videos from channels (for updating subscriptions)
|
||||||
property channel_threads : Int32 = 1
|
property channel_threads : Int32 = 1
|
||||||
# Time interval between two executions of the job that crawls channel videos (subscriptions update).
|
# Time interval between two executions of the job that crawls channel videos (subscriptions update).
|
||||||
|
@ -68,6 +95,8 @@ class Config
|
||||||
property output : String = "STDOUT"
|
property output : String = "STDOUT"
|
||||||
# Default log level, valid YAML values are ints and strings, see src/invidious/helpers/logger.cr
|
# Default log level, valid YAML values are ints and strings, see src/invidious/helpers/logger.cr
|
||||||
property log_level : LogLevel = LogLevel::Info
|
property log_level : LogLevel = LogLevel::Info
|
||||||
|
# Enables colors in logs. Useful for debugging purposes
|
||||||
|
property colorize_logs : Bool = false
|
||||||
# Database configuration with separate parameters (username, hostname, etc)
|
# Database configuration with separate parameters (username, hostname, etc)
|
||||||
property db : DBConfig? = nil
|
property db : DBConfig? = nil
|
||||||
|
|
||||||
|
@ -126,8 +155,12 @@ class Config
|
||||||
property port : Int32 = 3000
|
property port : Int32 = 3000
|
||||||
# Host to bind (overridden by command line argument)
|
# Host to bind (overridden by command line argument)
|
||||||
property host_binding : String = "0.0.0.0"
|
property host_binding : String = "0.0.0.0"
|
||||||
|
# Path and permissions to make Invidious listen on a UNIX socket instead of a TCP port
|
||||||
|
property socket_binding : SocketBindingConfig? = nil
|
||||||
# Pool size for HTTP requests to youtube.com and ytimg.com (each domain has a separate pool of `pool_size`)
|
# Pool size for HTTP requests to youtube.com and ytimg.com (each domain has a separate pool of `pool_size`)
|
||||||
property pool_size : Int32 = 100
|
property pool_size : Int32 = 100
|
||||||
|
# HTTP Proxy configuration
|
||||||
|
property http_proxy : HTTPProxyConfig? = nil
|
||||||
|
|
||||||
# Use Innertube's transcripts API instead of timedtext for closed captions
|
# Use Innertube's transcripts API instead of timedtext for closed captions
|
||||||
property use_innertube_for_captions : Bool = false
|
property use_innertube_for_captions : Bool = false
|
||||||
|
@ -137,6 +170,12 @@ class Config
|
||||||
# poToken for passing bot attestation
|
# poToken for passing bot attestation
|
||||||
property po_token : String? = nil
|
property po_token : String? = nil
|
||||||
|
|
||||||
|
# Invidious companion
|
||||||
|
property invidious_companion : Array(CompanionConfig) = [] of CompanionConfig
|
||||||
|
|
||||||
|
# Invidious companion API key
|
||||||
|
property invidious_companion_key : String = ""
|
||||||
|
|
||||||
# Saved cookies in "name1=value1; name2=value2..." format
|
# Saved cookies in "name1=value1; name2=value2..." format
|
||||||
@[YAML::Field(converter: Preferences::StringToCookies)]
|
@[YAML::Field(converter: Preferences::StringToCookies)]
|
||||||
property cookies : HTTP::Cookies = HTTP::Cookies.new
|
property cookies : HTTP::Cookies = HTTP::Cookies.new
|
||||||
|
@ -170,6 +209,9 @@ class Config
|
||||||
config = Config.from_yaml(config_yaml)
|
config = Config.from_yaml(config_yaml)
|
||||||
|
|
||||||
# Update config from env vars (upcased and prefixed with "INVIDIOUS_")
|
# Update config from env vars (upcased and prefixed with "INVIDIOUS_")
|
||||||
|
#
|
||||||
|
# Also checks if any top-level config options are set to "CHANGE_ME!!"
|
||||||
|
# TODO: Support non-top-level config options such as the ones in DBConfig
|
||||||
{% for ivar in Config.instance_vars %}
|
{% for ivar in Config.instance_vars %}
|
||||||
{% env_id = "INVIDIOUS_#{ivar.id.upcase}" %}
|
{% env_id = "INVIDIOUS_#{ivar.id.upcase}" %}
|
||||||
|
|
||||||
|
@ -206,16 +248,40 @@ class Config
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Warn when any config attribute is set to "CHANGE_ME!!"
|
||||||
|
if config.{{ivar.id}} == "CHANGE_ME!!"
|
||||||
|
puts "Config: The value of '#{ {{ivar.stringify}} }' needs to be changed!!"
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
|
if config.invidious_companion.present?
|
||||||
|
# invidious_companion and signature_server can't work together
|
||||||
|
if config.signature_server
|
||||||
|
puts "Config: You can not run inv_sig_helper and invidious_companion at the same time."
|
||||||
|
exit(1)
|
||||||
|
elsif config.invidious_companion_key.empty?
|
||||||
|
puts "Config: Please configure a key if you are using invidious companion."
|
||||||
|
exit(1)
|
||||||
|
elsif config.invidious_companion_key == "CHANGE_ME!!"
|
||||||
|
puts "Config: The value of 'invidious_companion_key' needs to be changed!!"
|
||||||
|
exit(1)
|
||||||
|
elsif config.invidious_companion_key.size != 16
|
||||||
|
puts "Config: The value of 'invidious_companion_key' needs to be a size of 16 characters."
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
elsif config.signature_server
|
||||||
|
puts("WARNING: inv-sig-helper is deprecated. Please switch to Invidious companion: https://docs.invidious.io/companion-installation/")
|
||||||
|
else
|
||||||
|
puts("WARNING: Invidious companion is required to view and playback videos. For more information see https://docs.invidious.io/companion-installation/")
|
||||||
|
end
|
||||||
|
|
||||||
# HMAC_key is mandatory
|
# HMAC_key is mandatory
|
||||||
# See: https://github.com/iv-org/invidious/issues/3854
|
# See: https://github.com/iv-org/invidious/issues/3854
|
||||||
if config.hmac_key.empty?
|
if config.hmac_key.empty?
|
||||||
puts "Config: 'hmac_key' is required/can't be empty"
|
puts "Config: 'hmac_key' is required/can't be empty"
|
||||||
exit(1)
|
exit(1)
|
||||||
elsif config.hmac_key == "CHANGE_ME!!"
|
|
||||||
puts "Config: The value of 'hmac_key' needs to be changed!!"
|
|
||||||
exit(1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Build database_url from db.* if it's not set directly
|
# Build database_url from db.* if it's not set directly
|
||||||
|
@ -235,6 +301,24 @@ class Config
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check if the socket configuration is valid
|
||||||
|
if sb = config.socket_binding
|
||||||
|
if sb.path.ends_with?("/") || File.directory?(sb.path)
|
||||||
|
puts "Config: The socket path " + sb.path + " must not be a directory!"
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
d = File.dirname(sb.path)
|
||||||
|
if !File.directory?(d)
|
||||||
|
puts "Config: Socket directory " + sb.path + " does not exist or is not a directory!"
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
p = sb.permissions.to_i?(base: 8)
|
||||||
|
if !p || p < 0 || p > 0o777
|
||||||
|
puts "Config: Socket permissions must be an octal between 0 and 777!"
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return config
|
return config
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -91,7 +91,7 @@ module Invidious::Database::Playlists
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
# Salect
|
# Select
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
def select(*, id : String) : InvidiousPlaylist?
|
def select(*, id : String) : InvidiousPlaylist?
|
||||||
|
@ -113,7 +113,7 @@ module Invidious::Database::Playlists
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
# Salect (filtered)
|
# Select (filtered)
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
def select_like_iv(email : String) : Array(InvidiousPlaylist)
|
def select_like_iv(email : String) : Array(InvidiousPlaylist)
|
||||||
|
@ -213,7 +213,7 @@ module Invidious::Database::PlaylistVideos
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
# Salect
|
# Select
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
def select(plid : String, index : VideoIndex, offset, limit = 100) : Array(PlaylistVideo)
|
def select(plid : String, index : VideoIndex, offset, limit = 100) : Array(PlaylistVideo)
|
||||||
|
|
|
@ -119,15 +119,15 @@ module Invidious::Database::Users
|
||||||
# Update (notifs)
|
# Update (notifs)
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
def add_notification(video : ChannelVideo)
|
def add_multiple_notifications(channel_id : String, video_ids : Array(String))
|
||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET notifications = array_append(notifications, $1),
|
SET notifications = array_cat(notifications, $1),
|
||||||
feed_needs_update = true
|
feed_needs_update = true
|
||||||
WHERE $2 = ANY(subscriptions)
|
WHERE $2 = ANY(subscriptions)
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, video.id, video.ucid)
|
PG_DB.exec(request, video_ids, channel_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_notification(user : User, vid : String)
|
def remove_notification(user : User, vid : String)
|
||||||
|
@ -154,14 +154,14 @@ module Invidious::Database::Users
|
||||||
# Update (misc)
|
# Update (misc)
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
def feed_needs_update(video : ChannelVideo)
|
def feed_needs_update(channel_id : String)
|
||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET feed_needs_update = true
|
SET feed_needs_update = true
|
||||||
WHERE $1 = ANY(subscriptions)
|
WHERE $1 = ANY(subscriptions)
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, video.ucid)
|
PG_DB.exec(request, channel_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_preferences(user : User)
|
def update_preferences(user : User)
|
||||||
|
|
|
@ -7,8 +7,9 @@ module Invidious::Frontend::ChannelPage
|
||||||
Streams
|
Streams
|
||||||
Podcasts
|
Podcasts
|
||||||
Releases
|
Releases
|
||||||
|
Courses
|
||||||
Playlists
|
Playlists
|
||||||
Community
|
Posts
|
||||||
Channels
|
Channels
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,24 @@ require "uri"
|
||||||
module Invidious::Frontend::Pagination
|
module Invidious::Frontend::Pagination
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
|
private def first_page(str : String::Builder, locale : String?, url : String)
|
||||||
|
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
||||||
|
|
||||||
|
if locale_is_rtl?(locale)
|
||||||
|
# Inverted arrow ("first" points to the right)
|
||||||
|
str << translate(locale, "First page")
|
||||||
|
str << " "
|
||||||
|
str << %(<i class="icon ion-ios-arrow-forward"></i>)
|
||||||
|
else
|
||||||
|
# Regular arrow ("first" points to the left)
|
||||||
|
str << %(<i class="icon ion-ios-arrow-back"></i>)
|
||||||
|
str << " "
|
||||||
|
str << translate(locale, "First page")
|
||||||
|
end
|
||||||
|
|
||||||
|
str << "</a>"
|
||||||
|
end
|
||||||
|
|
||||||
private def previous_page(str : String::Builder, locale : String?, url : String)
|
private def previous_page(str : String::Builder, locale : String?, url : String)
|
||||||
# Link
|
# Link
|
||||||
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
||||||
|
@ -72,18 +90,24 @@ module Invidious::Frontend::Pagination
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?)
|
def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?, first_page : Bool, params : URI::Params)
|
||||||
return String.build do |str|
|
return String.build do |str|
|
||||||
str << %(<div class="h-box">\n)
|
str << %(<div class="h-box">\n)
|
||||||
str << %(<div class="page-nav-container flexible">\n)
|
str << %(<div class="page-nav-container flexible">\n)
|
||||||
|
|
||||||
str << %(<div class="page-prev-container flex-left"></div>\n)
|
str << %(<div class="page-prev-container flex-left">)
|
||||||
|
|
||||||
|
if !first_page
|
||||||
|
self.first_page(str, locale, base_url.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
str << %(</div>\n)
|
||||||
|
|
||||||
str << %(<div class="page-next-container flex-right">)
|
str << %(<div class="page-next-container flex-right">)
|
||||||
|
|
||||||
if !ctoken.nil?
|
if !ctoken.nil?
|
||||||
params_next = URI::Params{"continuation" => ctoken}
|
params["continuation"] = ctoken
|
||||||
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
|
url_next = HttpServer::Utils.add_params_to_url(base_url, params)
|
||||||
|
|
||||||
self.next_page(str, locale, url_next.to_s)
|
self.next_page(str, locale, url_next.to_s)
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Invidious::Frontend::WatchPage
|
||||||
@full_videos,
|
@full_videos,
|
||||||
@video_streams,
|
@video_streams,
|
||||||
@audio_streams,
|
@audio_streams,
|
||||||
@captions
|
@captions,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -23,10 +23,16 @@ module Invidious::Frontend::WatchPage
|
||||||
return "<p id=\"download\">#{translate(locale, "Download is disabled")}</p>"
|
return "<p id=\"download\">#{translate(locale, "Download is disabled")}</p>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
url = "/download"
|
||||||
|
if (CONFIG.invidious_companion.present?)
|
||||||
|
invidious_companion = CONFIG.invidious_companion.sample
|
||||||
|
url = "#{invidious_companion.public_url}/download?check=#{invidious_companion_encrypt(video.id)}"
|
||||||
|
end
|
||||||
|
|
||||||
return String.build(4000) do |str|
|
return String.build(4000) do |str|
|
||||||
str << "<form"
|
str << "<form"
|
||||||
str << " class=\"pure-form pure-form-stacked\""
|
str << " class=\"pure-form pure-form-stacked\""
|
||||||
str << " action='/download'"
|
str << " action='#{url}'"
|
||||||
str << " method='post'"
|
str << " method='post'"
|
||||||
str << " rel='noopener'"
|
str << " rel='noopener'"
|
||||||
str << " target='_blank'>"
|
str << " target='_blank'>"
|
||||||
|
|
|
@ -18,16 +18,7 @@ def github_details(summary : String, content : String)
|
||||||
return HTML.escape(details)
|
return HTML.escape(details)
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exception : Exception)
|
def get_issue_template(env : HTTP::Server::Context, exception : Exception) : Tuple(String, String)
|
||||||
if exception.is_a?(InfoException)
|
|
||||||
return error_template_helper(env, status_code, exception.message || "")
|
|
||||||
end
|
|
||||||
|
|
||||||
locale = env.get("preferences").as(Preferences).locale
|
|
||||||
|
|
||||||
env.response.content_type = "text/html"
|
|
||||||
env.response.status_code = status_code
|
|
||||||
|
|
||||||
issue_title = "#{exception.message} (#{exception.class})"
|
issue_title = "#{exception.message} (#{exception.class})"
|
||||||
|
|
||||||
issue_template = <<-TEXT
|
issue_template = <<-TEXT
|
||||||
|
@ -40,9 +31,29 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
|
||||||
|
|
||||||
issue_template += github_details("Backtrace", exception.inspect_with_backtrace)
|
issue_template += github_details("Backtrace", exception.inspect_with_backtrace)
|
||||||
|
|
||||||
|
return issue_title, issue_template
|
||||||
|
end
|
||||||
|
|
||||||
|
def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exception : Exception)
|
||||||
|
if exception.is_a?(InfoException)
|
||||||
|
return error_template_helper(env, status_code, exception.message || "")
|
||||||
|
end
|
||||||
|
|
||||||
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
|
||||||
|
env.response.content_type = "text/html"
|
||||||
|
env.response.status_code = status_code
|
||||||
|
|
||||||
|
# Unpacking into issue_title, issue_template directly causes a compiler error
|
||||||
|
# I have no idea why.
|
||||||
|
issue_template_components = get_issue_template(env, exception)
|
||||||
|
issue_title, issue_template = issue_template_components
|
||||||
|
|
||||||
# URLs for the error message below
|
# URLs for the error message below
|
||||||
url_faq = "https://github.com/iv-org/documentation/blob/master/docs/faq.md"
|
url_faq = "https://github.com/iv-org/documentation/blob/master/docs/faq.md"
|
||||||
url_search_issues = "https://github.com/iv-org/invidious/issues"
|
url_search_issues = "https://github.com/iv-org/invidious/issues"
|
||||||
|
url_search_issues += "?q=is:issue+is:open+"
|
||||||
|
url_search_issues += URI.encode_www_form("[Bug] #{issue_title}")
|
||||||
|
|
||||||
url_switch = "https://redirect.invidious.io" + env.request.resource
|
url_switch = "https://redirect.invidious.io" + env.request.resource
|
||||||
|
|
||||||
|
@ -67,7 +78,7 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
|
||||||
<p>#{translate(locale, "crash_page_report_issue", url_new_issue)}</p>
|
<p>#{translate(locale, "crash_page_report_issue", url_new_issue)}</p>
|
||||||
|
|
||||||
<!-- TODO: Add a "copy to clipboard" button -->
|
<!-- TODO: Add a "copy to clipboard" button -->
|
||||||
<pre style="padding: 20px; background: rgba(0, 0, 0, 0.12345);">#{issue_template}</pre>
|
<pre class="error-issue-template">#{issue_template}</pre>
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
END_HTML
|
||||||
|
|
||||||
|
@ -128,7 +139,7 @@ def error_json_helper(
|
||||||
env : HTTP::Server::Context,
|
env : HTTP::Server::Context,
|
||||||
status_code : Int32,
|
status_code : Int32,
|
||||||
exception : Exception,
|
exception : Exception,
|
||||||
additional_fields : Hash(String, Object) | Nil = nil
|
additional_fields : Hash(String, Object) | Nil = nil,
|
||||||
)
|
)
|
||||||
if exception.is_a?(InfoException)
|
if exception.is_a?(InfoException)
|
||||||
return error_json_helper(env, status_code, exception.message || "", additional_fields)
|
return error_json_helper(env, status_code, exception.message || "", additional_fields)
|
||||||
|
@ -150,7 +161,7 @@ def error_json_helper(
|
||||||
env : HTTP::Server::Context,
|
env : HTTP::Server::Context,
|
||||||
status_code : Int32,
|
status_code : Int32,
|
||||||
message : String,
|
message : String,
|
||||||
additional_fields : Hash(String, Object) | Nil = nil
|
additional_fields : Hash(String, Object) | Nil = nil,
|
||||||
)
|
)
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
env.response.status_code = status_code
|
env.response.status_code = status_code
|
||||||
|
|
|
@ -27,6 +27,7 @@ class Kemal::RouteHandler
|
||||||
# Processes the route if it's a match. Otherwise renders 404.
|
# Processes the route if it's a match. Otherwise renders 404.
|
||||||
private def process_request(context)
|
private def process_request(context)
|
||||||
raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_found?
|
raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_found?
|
||||||
|
return if context.response.closed?
|
||||||
content = context.route.handler.call(context)
|
content = context.route.handler.call(context)
|
||||||
|
|
||||||
if !Kemal.config.error_handlers.empty? && Kemal.config.error_handlers.has_key?(context.response.status_code) && exclude_match?(context)
|
if !Kemal.config.error_handlers.empty? && Kemal.config.error_handlers.has_key?(context.response.status_code) && exclude_match?(context)
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
|
# Languages requiring a better level of translation (at least 20%)
|
||||||
|
# to be added to the list below:
|
||||||
|
#
|
||||||
|
# "af" => "", # Afrikaans
|
||||||
|
# "az" => "", # Azerbaijani
|
||||||
|
# "be" => "", # Belarusian
|
||||||
|
# "bn_BD" => "", # Bengali (Bangladesh)
|
||||||
|
# "ia" => "", # Interlingua
|
||||||
|
# "or" => "", # Odia
|
||||||
|
# "tk" => "", # Turkmen
|
||||||
|
# "tok => "", # Toki Pona
|
||||||
|
#
|
||||||
LOCALES_LIST = {
|
LOCALES_LIST = {
|
||||||
"ar" => "العربية", # Arabic
|
"ar" => "العربية", # Arabic
|
||||||
|
"bg" => "български", # Bulgarian
|
||||||
"bn" => "বাংলা", # Bengali
|
"bn" => "বাংলা", # Bengali
|
||||||
"ca" => "Català", # Catalan
|
"ca" => "Català", # Catalan
|
||||||
"cs" => "Čeština", # Czech
|
"cs" => "Čeština", # Czech
|
||||||
|
"cy" => "Cymraeg", # Welsh
|
||||||
"da" => "Dansk", # Danish
|
"da" => "Dansk", # Danish
|
||||||
"de" => "Deutsch", # German
|
"de" => "Deutsch", # German
|
||||||
"el" => "Ελληνικά", # Greek
|
"el" => "Ελληνικά", # Greek
|
||||||
|
@ -23,6 +37,7 @@ LOCALES_LIST = {
|
||||||
"it" => "Italiano", # Italian
|
"it" => "Italiano", # Italian
|
||||||
"ja" => "日本語", # Japanese
|
"ja" => "日本語", # Japanese
|
||||||
"ko" => "한국어", # Korean
|
"ko" => "한국어", # Korean
|
||||||
|
"lmo" => "Lombard", # Lombard
|
||||||
"lt" => "Lietuvių", # Lithuanian
|
"lt" => "Lietuvių", # Lithuanian
|
||||||
"nb-NO" => "Norsk bokmål", # Norwegian Bokmål
|
"nb-NO" => "Norsk bokmål", # Norwegian Bokmål
|
||||||
"nl" => "Nederlands", # Dutch
|
"nl" => "Nederlands", # Dutch
|
||||||
|
@ -39,6 +54,7 @@ LOCALES_LIST = {
|
||||||
"sr" => "Srpski (latinica)", # Serbian (Latin)
|
"sr" => "Srpski (latinica)", # Serbian (Latin)
|
||||||
"sr_Cyrl" => "Српски (ћирилица)", # Serbian (Cyrillic)
|
"sr_Cyrl" => "Српски (ћирилица)", # Serbian (Cyrillic)
|
||||||
"sv-SE" => "Svenska", # Swedish
|
"sv-SE" => "Svenska", # Swedish
|
||||||
|
"ta" => "தமிழ்", # Tamil
|
||||||
"tr" => "Türkçe", # Turkish
|
"tr" => "Türkçe", # Turkish
|
||||||
"uk" => "Українська", # Ukrainian
|
"uk" => "Українська", # Ukrainian
|
||||||
"vi" => "Tiếng Việt", # Vietnamese
|
"vi" => "Tiếng Việt", # Vietnamese
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require "colorize"
|
||||||
|
|
||||||
enum LogLevel
|
enum LogLevel
|
||||||
All = 0
|
All = 0
|
||||||
Trace = 1
|
Trace = 1
|
||||||
|
@ -10,7 +12,9 @@ enum LogLevel
|
||||||
end
|
end
|
||||||
|
|
||||||
class Invidious::LogHandler < Kemal::BaseLogHandler
|
class Invidious::LogHandler < Kemal::BaseLogHandler
|
||||||
def initialize(@io : IO = STDOUT, @level = LogLevel::Debug)
|
def initialize(@io : IO = STDOUT, @level = LogLevel::Debug, use_color : Bool = true)
|
||||||
|
Colorize.enabled = use_color
|
||||||
|
Colorize.on_tty_only!
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(context : HTTP::Server::Context)
|
def call(context : HTTP::Server::Context)
|
||||||
|
@ -39,10 +43,22 @@ class Invidious::LogHandler < Kemal::BaseLogHandler
|
||||||
@io.flush
|
@io.flush
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def color(level)
|
||||||
|
case level
|
||||||
|
when LogLevel::Trace then :cyan
|
||||||
|
when LogLevel::Debug then :green
|
||||||
|
when LogLevel::Info then :white
|
||||||
|
when LogLevel::Warn then :yellow
|
||||||
|
when LogLevel::Error then :red
|
||||||
|
when LogLevel::Fatal then :magenta
|
||||||
|
else :default
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
{% for level in %w(trace debug info warn error fatal) %}
|
{% for level in %w(trace debug info warn error fatal) %}
|
||||||
def {{level.id}}(message : String)
|
def {{level.id}}(message : String)
|
||||||
if LogLevel::{{level.id.capitalize}} >= @level
|
if LogLevel::{{level.id.capitalize}} >= @level
|
||||||
puts("#{Time.utc} [{{level.id}}] #{message}")
|
puts("#{Time.utc} [{{level.id}}] #{message}".colorize(color(LogLevel::{{level.id.capitalize}})))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
|
@ -55,12 +55,11 @@ macro templated(_filename, template = "template", navbar_search = true)
|
||||||
{{ layout = "src/invidious/views/" + template + ".ecr" }}
|
{{ layout = "src/invidious/views/" + template + ".ecr" }}
|
||||||
|
|
||||||
__content_filename__ = {{filename}}
|
__content_filename__ = {{filename}}
|
||||||
content = Kilt.render({{filename}})
|
render {{filename}}, {{layout}}
|
||||||
Kilt.render({{layout}})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
macro rendered(filename)
|
macro rendered(filename)
|
||||||
Kilt.render("src/invidious/views/#{{{filename}}}.ecr")
|
render("src/invidious/views/#{{{filename}}}.ecr")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Similar to Kemals halt method but works in a
|
# Similar to Kemals halt method but works in a
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
@[Flags]
|
||||||
|
enum VideoBadges
|
||||||
|
LiveNow
|
||||||
|
Premium
|
||||||
|
ThreeD
|
||||||
|
FourK
|
||||||
|
New
|
||||||
|
EightK
|
||||||
|
VR180
|
||||||
|
VR360
|
||||||
|
ClosedCaptions
|
||||||
|
end
|
||||||
|
|
||||||
struct SearchVideo
|
struct SearchVideo
|
||||||
include DB::Serializable
|
include DB::Serializable
|
||||||
|
|
||||||
|
@ -9,10 +22,10 @@ struct SearchVideo
|
||||||
property views : Int64
|
property views : Int64
|
||||||
property description_html : String
|
property description_html : String
|
||||||
property length_seconds : Int32
|
property length_seconds : Int32
|
||||||
property live_now : Bool
|
|
||||||
property premium : Bool
|
|
||||||
property premiere_timestamp : Time?
|
property premiere_timestamp : Time?
|
||||||
property author_verified : Bool
|
property author_verified : Bool
|
||||||
|
property author_thumbnail : String?
|
||||||
|
property badges : VideoBadges
|
||||||
|
|
||||||
def to_xml(auto_generated, query_params, xml : XML::Builder)
|
def to_xml(auto_generated, query_params, xml : XML::Builder)
|
||||||
query_params["v"] = self.id
|
query_params["v"] = self.id
|
||||||
|
@ -76,6 +89,24 @@ struct SearchVideo
|
||||||
json.field "authorUrl", "/channel/#{self.ucid}"
|
json.field "authorUrl", "/channel/#{self.ucid}"
|
||||||
json.field "authorVerified", self.author_verified
|
json.field "authorVerified", self.author_verified
|
||||||
|
|
||||||
|
author_thumbnail = self.author_thumbnail
|
||||||
|
|
||||||
|
if author_thumbnail
|
||||||
|
json.field "authorThumbnails" do
|
||||||
|
json.array do
|
||||||
|
qualities = {32, 48, 76, 100, 176, 512}
|
||||||
|
|
||||||
|
qualities.each do |quality|
|
||||||
|
json.object do
|
||||||
|
json.field "url", author_thumbnail.gsub(/=s\d+/, "=s#{quality}")
|
||||||
|
json.field "width", quality
|
||||||
|
json.field "height", quality
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
json.field "videoThumbnails" do
|
json.field "videoThumbnails" do
|
||||||
Invidious::JSONify::APIv1.thumbnails(json, self.id)
|
Invidious::JSONify::APIv1.thumbnails(json, self.id)
|
||||||
end
|
end
|
||||||
|
@ -88,13 +119,20 @@ struct SearchVideo
|
||||||
json.field "published", self.published.to_unix
|
json.field "published", self.published.to_unix
|
||||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
|
||||||
json.field "lengthSeconds", self.length_seconds
|
json.field "lengthSeconds", self.length_seconds
|
||||||
json.field "liveNow", self.live_now
|
json.field "liveNow", self.badges.live_now?
|
||||||
json.field "premium", self.premium
|
json.field "premium", self.badges.premium?
|
||||||
json.field "isUpcoming", self.upcoming?
|
json.field "isUpcoming", self.upcoming?
|
||||||
|
|
||||||
if self.premiere_timestamp
|
if self.premiere_timestamp
|
||||||
json.field "premiereTimestamp", self.premiere_timestamp.try &.to_unix
|
json.field "premiereTimestamp", self.premiere_timestamp.try &.to_unix
|
||||||
end
|
end
|
||||||
|
json.field "isNew", self.badges.new?
|
||||||
|
json.field "is4k", self.badges.four_k?
|
||||||
|
json.field "is8k", self.badges.eight_k?
|
||||||
|
json.field "isVr180", self.badges.vr180?
|
||||||
|
json.field "isVr360", self.badges.vr360?
|
||||||
|
json.field "is3d", self.badges.three_d?
|
||||||
|
json.field "hasCaptions", self.badges.closed_captions?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -204,7 +242,7 @@ struct SearchChannel
|
||||||
|
|
||||||
qualities.each do |quality|
|
qualities.each do |quality|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "url", self.author_thumbnail.gsub(/=\d+/, "=s#{quality}")
|
json.field "url", self.author_thumbnail.gsub(/=s\d+/, "=s#{quality}")
|
||||||
json.field "width", quality
|
json.field "width", quality
|
||||||
json.field "height", quality
|
json.field "height", quality
|
||||||
end
|
end
|
||||||
|
@ -253,6 +291,55 @@ struct SearchHashtag
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A `ProblematicTimelineItem` is a `SearchItem` created by Invidious that
|
||||||
|
# represents an item that caused an exception during parsing.
|
||||||
|
#
|
||||||
|
# This is not a parsed object from YouTube but rather an Invidious-only type
|
||||||
|
# created to gracefully communicate parse errors without throwing away
|
||||||
|
# the rest of the (hopefully) successfully parsed item on a page.
|
||||||
|
struct ProblematicTimelineItem
|
||||||
|
property parse_exception : Exception
|
||||||
|
property id : String
|
||||||
|
|
||||||
|
def initialize(@parse_exception)
|
||||||
|
@id = Random.new.hex(8)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_json(locale : String?, json : JSON::Builder)
|
||||||
|
json.object do
|
||||||
|
json.field "type", "parse-error"
|
||||||
|
json.field "errorMessage", @parse_exception.message
|
||||||
|
json.field "errorBacktrace", @parse_exception.inspect_with_backtrace
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Provides compatibility with PlaylistVideo
|
||||||
|
def to_json(json : JSON::Builder, *args, **kwargs)
|
||||||
|
return to_json("", json)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_xml(env, locale, xml : XML::Builder)
|
||||||
|
xml.element("entry") do
|
||||||
|
xml.element("id") { xml.text "iv-err-#{@id}" }
|
||||||
|
xml.element("title") { xml.text "Parse Error: This item has failed to parse" }
|
||||||
|
xml.element("updated") { xml.text Time.utc.to_rfc3339 }
|
||||||
|
|
||||||
|
xml.element("content", type: "xhtml") do
|
||||||
|
xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do
|
||||||
|
xml.element("div") do
|
||||||
|
xml.element("h4") { translate(locale, "timeline_parse_error_placeholder_heading") }
|
||||||
|
xml.element("p") { translate(locale, "timeline_parse_error_placeholder_message") }
|
||||||
|
end
|
||||||
|
|
||||||
|
xml.element("pre") do
|
||||||
|
get_issue_template(env, @parse_exception)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Category
|
class Category
|
||||||
include DB::Serializable
|
include DB::Serializable
|
||||||
|
|
||||||
|
@ -295,4 +382,4 @@ struct Continuation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
alias SearchItem = SearchVideo | SearchChannel | SearchPlaylist | SearchHashtag | Category
|
alias SearchItem = SearchVideo | SearchChannel | SearchPlaylist | SearchHashtag | Category | ProblematicTimelineItem
|
||||||
|
|
|
@ -175,8 +175,9 @@ module Invidious::SigHelper
|
||||||
@queue = {} of TransactionID => Transaction
|
@queue = {} of TransactionID => Transaction
|
||||||
|
|
||||||
@conn : Connection
|
@conn : Connection
|
||||||
|
@uri_or_path : String
|
||||||
|
|
||||||
def initialize(uri_or_path)
|
def initialize(@uri_or_path)
|
||||||
@conn = Connection.new(uri_or_path)
|
@conn = Connection.new(uri_or_path)
|
||||||
listen
|
listen
|
||||||
end
|
end
|
||||||
|
@ -186,10 +187,26 @@ module Invidious::SigHelper
|
||||||
|
|
||||||
LOGGER.debug("SigHelper: Multiplexor listening")
|
LOGGER.debug("SigHelper: Multiplexor listening")
|
||||||
|
|
||||||
# TODO: reopen socket if unexpectedly closed
|
|
||||||
spawn do
|
spawn do
|
||||||
loop do
|
loop do
|
||||||
receive_data
|
begin
|
||||||
|
receive_data
|
||||||
|
rescue ex
|
||||||
|
LOGGER.info("SigHelper: Connection to helper died with '#{ex.message}' trying to reconnect...")
|
||||||
|
# We close the socket because for some reason is not closed.
|
||||||
|
@conn.close
|
||||||
|
loop do
|
||||||
|
begin
|
||||||
|
@conn = Connection.new(@uri_or_path)
|
||||||
|
LOGGER.info("SigHelper: Reconnected to SigHelper!")
|
||||||
|
rescue ex
|
||||||
|
LOGGER.debug("SigHelper: Reconnection to helper unsuccessful with error '#{ex.message}'. Retrying")
|
||||||
|
sleep 500.milliseconds
|
||||||
|
next
|
||||||
|
end
|
||||||
|
break if !@conn.closed?
|
||||||
|
end
|
||||||
|
end
|
||||||
Fiber.yield
|
Fiber.yield
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -262,7 +262,7 @@ def get_referer(env, fallback = "/", unroll = true)
|
||||||
end
|
end
|
||||||
|
|
||||||
referer = referer.request_target
|
referer = referer.request_target
|
||||||
referer = "/" + referer.gsub(/[^\/?@&%=\-_.:,*0-9a-zA-Z]/, "").lstrip("/\\")
|
referer = "/" + referer.gsub(/[^\/?@&%=\-_.:,*0-9a-zA-Z+]/, "").lstrip("/\\")
|
||||||
|
|
||||||
if referer == env.request.path
|
if referer == env.request.path
|
||||||
referer = fallback
|
referer = fallback
|
||||||
|
@ -323,68 +323,6 @@ def parse_range(range)
|
||||||
return 0_i64, nil
|
return 0_i64, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_random_instance
|
|
||||||
begin
|
|
||||||
instance_api_client = make_client(URI.parse("https://api.invidious.io"))
|
|
||||||
|
|
||||||
# Timeouts
|
|
||||||
instance_api_client.connect_timeout = 10.seconds
|
|
||||||
instance_api_client.dns_timeout = 10.seconds
|
|
||||||
|
|
||||||
instance_list = JSON.parse(instance_api_client.get("/instances.json").body).as_a
|
|
||||||
instance_api_client.close
|
|
||||||
rescue Socket::ConnectError | IO::TimeoutError | JSON::ParseException
|
|
||||||
instance_list = [] of JSON::Any
|
|
||||||
end
|
|
||||||
|
|
||||||
filtered_instance_list = [] of String
|
|
||||||
|
|
||||||
instance_list.each do |data|
|
|
||||||
# TODO Check if current URL is onion instance and use .onion types if so.
|
|
||||||
if data[1]["type"] == "https"
|
|
||||||
# Instances can have statistics disabled, which is an requirement of version validation.
|
|
||||||
# as_nil? doesn't exist. Thus we'll have to handle the error raised if as_nil fails.
|
|
||||||
begin
|
|
||||||
data[1]["stats"].as_nil
|
|
||||||
next
|
|
||||||
rescue TypeCastError
|
|
||||||
end
|
|
||||||
|
|
||||||
# stats endpoint could also lack the software dict.
|
|
||||||
next if data[1]["stats"]["software"]?.nil?
|
|
||||||
|
|
||||||
# Makes sure the instance isn't too outdated.
|
|
||||||
if remote_version = data[1]["stats"]?.try &.["software"]?.try &.["version"]
|
|
||||||
remote_commit_date = remote_version.as_s.match(/\d{4}\.\d{2}\.\d{2}/)
|
|
||||||
next if !remote_commit_date
|
|
||||||
|
|
||||||
remote_commit_date = Time.parse(remote_commit_date[0], "%Y.%m.%d", Time::Location::UTC)
|
|
||||||
local_commit_date = Time.parse(CURRENT_VERSION, "%Y.%m.%d", Time::Location::UTC)
|
|
||||||
|
|
||||||
next if (remote_commit_date - local_commit_date).abs.days > 30
|
|
||||||
|
|
||||||
begin
|
|
||||||
data[1]["monitor"].as_nil
|
|
||||||
health = data[1]["monitor"].as_h["dailyRatios"][0].as_h["ratio"]
|
|
||||||
filtered_instance_list << data[0].as_s if health.to_s.to_f > 90
|
|
||||||
rescue TypeCastError
|
|
||||||
# We can't check the health if the monitoring is broken. Thus we'll just add it to the list
|
|
||||||
# and move on. Ideally we'll ignore any instance that has broken health monitoring but due to the fact that
|
|
||||||
# it's an error that often occurs with all the instances at the same time, we have to just skip the check.
|
|
||||||
filtered_instance_list << data[0].as_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# If for some reason no instances managed to get fetched successfully then we'll just redirect to redirect.invidious.io
|
|
||||||
if filtered_instance_list.size == 0
|
|
||||||
return "redirect.invidious.io"
|
|
||||||
end
|
|
||||||
|
|
||||||
return filtered_instance_list.sample(1)[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = "…") : String
|
def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = "…") : String
|
||||||
str = uri.to_s.sub(/^https?:\/\//, "")
|
str = uri.to_s.sub(/^https?:\/\//, "")
|
||||||
if str.size > max_length
|
if str.size > max_length
|
||||||
|
@ -445,3 +383,22 @@ def parse_link_endpoint(endpoint : JSON::Any, text : String, video_id : String)
|
||||||
end
|
end
|
||||||
return text
|
return text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def encrypt_ecb_without_salt(data, key)
|
||||||
|
cipher = OpenSSL::Cipher.new("aes-128-ecb")
|
||||||
|
cipher.encrypt
|
||||||
|
cipher.key = key
|
||||||
|
|
||||||
|
io = IO::Memory.new
|
||||||
|
io.write(cipher.update(data))
|
||||||
|
io.write(cipher.final)
|
||||||
|
io.rewind
|
||||||
|
|
||||||
|
return io
|
||||||
|
end
|
||||||
|
|
||||||
|
def invidious_companion_encrypt(data)
|
||||||
|
timestamp = Time.utc.to_unix
|
||||||
|
encrypted_data = encrypt_ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key)
|
||||||
|
return Base64.urlsafe_encode(encrypted_data)
|
||||||
|
end
|
||||||
|
|
97
src/invidious/jobs/instance_refresh_job.cr
Normal file
97
src/invidious/jobs/instance_refresh_job.cr
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
|
||||||
|
# We update the internals of a constant as so it can be accessed from anywhere
|
||||||
|
# within the codebase
|
||||||
|
#
|
||||||
|
# "INSTANCES" => Array(Tuple(String, String)) # region, instance
|
||||||
|
|
||||||
|
INSTANCES = {"INSTANCES" => [] of Tuple(String, String)}
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
end
|
||||||
|
|
||||||
|
def begin
|
||||||
|
loop do
|
||||||
|
refresh_instances
|
||||||
|
LOGGER.info("InstanceListRefreshJob: Done, sleeping for 30 minutes")
|
||||||
|
sleep 30.minute
|
||||||
|
Fiber.yield
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Refreshes the list of instances used for redirects.
|
||||||
|
#
|
||||||
|
# Does the following three checks for each instance
|
||||||
|
# - Is it a clear-net instance?
|
||||||
|
# - Is it an instance with a good uptime?
|
||||||
|
# - Is it an updated instance?
|
||||||
|
private def refresh_instances
|
||||||
|
raw_instance_list = self.fetch_instances
|
||||||
|
filtered_instance_list = [] of Tuple(String, String)
|
||||||
|
|
||||||
|
raw_instance_list.each do |instance_data|
|
||||||
|
# TODO allow Tor hidden service instances when the current instance
|
||||||
|
# is also a hidden service. Same for i2p and any other non-clearnet instances.
|
||||||
|
begin
|
||||||
|
domain = instance_data[0]
|
||||||
|
info = instance_data[1]
|
||||||
|
stats = info["stats"]
|
||||||
|
|
||||||
|
next unless info["type"] == "https"
|
||||||
|
next if bad_uptime?(info["monitor"])
|
||||||
|
next if outdated?(stats["software"]["version"])
|
||||||
|
|
||||||
|
filtered_instance_list << {info["region"].as_s, domain.as_s}
|
||||||
|
rescue ex
|
||||||
|
if domain
|
||||||
|
LOGGER.info("InstanceListRefreshJob: failed to parse information from '#{domain}' because \"#{ex}\"\n\"#{ex.backtrace.join('\n')}\" ")
|
||||||
|
else
|
||||||
|
LOGGER.info("InstanceListRefreshJob: failed to parse information from an instance because \"#{ex}\"\n\"#{ex.backtrace.join('\n')}\" ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !filtered_instance_list.empty?
|
||||||
|
INSTANCES["INSTANCES"] = filtered_instance_list
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fetches information regarding instances from api.invidious.io or an otherwise configured URL
|
||||||
|
private def fetch_instances : Array(JSON::Any)
|
||||||
|
begin
|
||||||
|
# We directly call the stdlib HTTP::Client here as it allows us to negate the effects
|
||||||
|
# of the force_resolve config option. This is needed as api.invidious.io does not support ipv6
|
||||||
|
# and as such the following request raises if we were to use force_resolve with the ipv6 value.
|
||||||
|
instance_api_client = HTTP::Client.new(URI.parse("https://api.invidious.io"))
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
instance_api_client.connect_timeout = 10.seconds
|
||||||
|
instance_api_client.dns_timeout = 10.seconds
|
||||||
|
|
||||||
|
raw_instance_list = JSON.parse(instance_api_client.get("/instances.json").body).as_a
|
||||||
|
instance_api_client.close
|
||||||
|
rescue ex : Socket::ConnectError | IO::TimeoutError | JSON::ParseException
|
||||||
|
raw_instance_list = [] of JSON::Any
|
||||||
|
end
|
||||||
|
|
||||||
|
return raw_instance_list
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if the given target instance is outdated
|
||||||
|
private def outdated?(target_instance_version) : Bool
|
||||||
|
remote_commit_date = target_instance_version.as_s.match(/\d{4}\.\d{2}\.\d{2}/)
|
||||||
|
return false if !remote_commit_date
|
||||||
|
|
||||||
|
remote_commit_date = Time.parse(remote_commit_date[0], "%Y.%m.%d", Time::Location::UTC)
|
||||||
|
local_commit_date = Time.parse(CURRENT_VERSION, "%Y.%m.%d", Time::Location::UTC)
|
||||||
|
|
||||||
|
return (remote_commit_date - local_commit_date).abs.days > 30
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if the uptime of the target instance is greater than 90% over a 30 day period
|
||||||
|
private def bad_uptime?(target_instance_health_monitor) : Bool
|
||||||
|
return true if !target_instance_health_monitor["down"].as_bool == false
|
||||||
|
return true if target_instance_health_monitor["uptime"].as_f < 90
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,8 +1,32 @@
|
||||||
|
struct VideoNotification
|
||||||
|
getter video_id : String
|
||||||
|
getter channel_id : String
|
||||||
|
getter published : Time
|
||||||
|
|
||||||
|
def_hash @channel_id, @video_id
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
video_id == other.video_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_video(video : ChannelVideo) : self
|
||||||
|
VideoNotification.new(video.id, video.ucid, video.published)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(@video_id, @channel_id, @published)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clone : VideoNotification
|
||||||
|
VideoNotification.new(video_id.clone, channel_id.clone, published.clone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Invidious::Jobs::NotificationJob < Invidious::Jobs::BaseJob
|
class Invidious::Jobs::NotificationJob < Invidious::Jobs::BaseJob
|
||||||
|
private getter notification_channel : ::Channel(VideoNotification)
|
||||||
private getter connection_channel : ::Channel({Bool, ::Channel(PQ::Notification)})
|
private getter connection_channel : ::Channel({Bool, ::Channel(PQ::Notification)})
|
||||||
private getter pg_url : URI
|
private getter pg_url : URI
|
||||||
|
|
||||||
def initialize(@connection_channel, @pg_url)
|
def initialize(@notification_channel, @connection_channel, @pg_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def begin
|
def begin
|
||||||
|
@ -10,6 +34,70 @@ class Invidious::Jobs::NotificationJob < Invidious::Jobs::BaseJob
|
||||||
|
|
||||||
PG.connect_listen(pg_url, "notifications") { |event| connections.each(&.send(event)) }
|
PG.connect_listen(pg_url, "notifications") { |event| connections.each(&.send(event)) }
|
||||||
|
|
||||||
|
# hash of channels to their videos (id+published) that need notifying
|
||||||
|
to_notify = Hash(String, Set(VideoNotification)).new(
|
||||||
|
->(hash : Hash(String, Set(VideoNotification)), key : String) {
|
||||||
|
hash[key] = Set(VideoNotification).new
|
||||||
|
}
|
||||||
|
)
|
||||||
|
notify_mutex = Mutex.new
|
||||||
|
|
||||||
|
# fiber to locally cache all incoming notifications (from pubsub webhooks and refresh channels job)
|
||||||
|
spawn do
|
||||||
|
begin
|
||||||
|
loop do
|
||||||
|
notification = notification_channel.receive
|
||||||
|
notify_mutex.synchronize do
|
||||||
|
to_notify[notification.channel_id] << notification
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# fiber to regularly persist all cached notifications
|
||||||
|
spawn do
|
||||||
|
loop do
|
||||||
|
begin
|
||||||
|
LOGGER.debug("NotificationJob: waking up")
|
||||||
|
cloned = {} of String => Set(VideoNotification)
|
||||||
|
notify_mutex.synchronize do
|
||||||
|
cloned = to_notify.clone
|
||||||
|
to_notify.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
cloned.each do |channel_id, notifications|
|
||||||
|
if notifications.empty?
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
LOGGER.info("NotificationJob: updating channel #{channel_id} with #{notifications.size} notifications")
|
||||||
|
if CONFIG.enable_user_notifications
|
||||||
|
video_ids = notifications.map(&.video_id)
|
||||||
|
Invidious::Database::Users.add_multiple_notifications(channel_id, video_ids)
|
||||||
|
PG_DB.using_connection do |conn|
|
||||||
|
notifications.each do |n|
|
||||||
|
# Deliver notifications to `/api/v1/auth/notifications`
|
||||||
|
payload = {
|
||||||
|
"topic" => n.channel_id,
|
||||||
|
"videoId" => n.video_id,
|
||||||
|
"published" => n.published.to_unix,
|
||||||
|
}.to_json
|
||||||
|
conn.exec("NOTIFY notifications, E'#{payload}'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Invidious::Database::Users.feed_needs_update(channel_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
LOGGER.trace("NotificationJob: Done, sleeping")
|
||||||
|
rescue ex
|
||||||
|
LOGGER.error("NotificationJob: #{ex.message}")
|
||||||
|
end
|
||||||
|
sleep 1.minute
|
||||||
|
Fiber.yield
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
action, connection = connection_channel.receive
|
action, connection = connection_channel.receive
|
||||||
|
|
||||||
|
|
|
@ -267,6 +267,12 @@ module Invidious::JSONify::APIv1
|
||||||
json.field "lengthSeconds", rv["length_seconds"]?.try &.to_i
|
json.field "lengthSeconds", rv["length_seconds"]?.try &.to_i
|
||||||
json.field "viewCountText", rv["short_view_count"]?
|
json.field "viewCountText", rv["short_view_count"]?
|
||||||
json.field "viewCount", rv["view_count"]?.try &.empty? ? nil : rv["view_count"].to_i64
|
json.field "viewCount", rv["view_count"]?.try &.empty? ? nil : rv["view_count"].to_i64
|
||||||
|
json.field "published", rv["published"]?
|
||||||
|
if rv["published"]?.try &.presence
|
||||||
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(Time.parse_rfc3339(rv["published"].to_s), locale))
|
||||||
|
else
|
||||||
|
json.field "publishedText", ""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -81,7 +81,7 @@ def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_mix(mix)
|
def template_mix(mix, listen)
|
||||||
html = <<-END_HTML
|
html = <<-END_HTML
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/mix?list=#{mix["mixId"]}">
|
<a href="/mix?list=#{mix["mixId"]}">
|
||||||
|
@ -95,7 +95,7 @@ def template_mix(mix)
|
||||||
mix["videos"].as_a.each do |video|
|
mix["videos"].as_a.each do |video|
|
||||||
html += <<-END_HTML
|
html += <<-END_HTML
|
||||||
<li class="pure-menu-item">
|
<li class="pure-menu-item">
|
||||||
<a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}">
|
<a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}#{listen ? "&listen=1" : ""}">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<img loading="lazy" class="thumbnail" src="/vi/#{video["videoId"]}/mqdefault.jpg" alt="" />
|
<img loading="lazy" class="thumbnail" src="/vi/#{video["videoId"]}/mqdefault.jpg" alt="" />
|
||||||
<p class="length">#{recode_length_seconds(video["lengthSeconds"].as_i)}</p>
|
<p class="length">#{recode_length_seconds(video["lengthSeconds"].as_i)}</p>
|
||||||
|
|
|
@ -270,7 +270,7 @@ end
|
||||||
|
|
||||||
def subscribe_playlist(user, playlist)
|
def subscribe_playlist(user, playlist)
|
||||||
playlist = InvidiousPlaylist.new({
|
playlist = InvidiousPlaylist.new({
|
||||||
title: playlist.title.byte_slice(0, 150),
|
title: playlist.title[..150],
|
||||||
id: playlist.id,
|
id: playlist.id,
|
||||||
author: user.email,
|
author: user.email,
|
||||||
description: "", # Max 5000 characters
|
description: "", # Max 5000 characters
|
||||||
|
@ -432,7 +432,7 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32,
|
||||||
offset = initial_data.dig?("contents", "twoColumnWatchNextResults", "playlist", "playlist", "currentIndex").try &.as_i || offset
|
offset = initial_data.dig?("contents", "twoColumnWatchNextResults", "playlist", "playlist", "currentIndex").try &.as_i || offset
|
||||||
end
|
end
|
||||||
|
|
||||||
videos = [] of PlaylistVideo
|
videos = [] of PlaylistVideo | ProblematicTimelineItem
|
||||||
|
|
||||||
until videos.size >= 200 || videos.size == playlist.video_count || offset >= playlist.video_count
|
until videos.size >= 200 || videos.size == playlist.video_count || offset >= playlist.video_count
|
||||||
# 100 videos per request
|
# 100 videos per request
|
||||||
|
@ -448,7 +448,7 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32,
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
||||||
videos = [] of PlaylistVideo
|
videos = [] of PlaylistVideo | ProblematicTimelineItem
|
||||||
|
|
||||||
if initial_data["contents"]?
|
if initial_data["contents"]?
|
||||||
tabs = initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"]
|
tabs = initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"]
|
||||||
|
@ -500,12 +500,14 @@ def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
||||||
index: index,
|
index: index,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
rescue ex
|
||||||
|
videos << ProblematicTimelineItem.new(parse_exception: ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
return videos
|
return videos
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_playlist(playlist)
|
def template_playlist(playlist, listen)
|
||||||
html = <<-END_HTML
|
html = <<-END_HTML
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/playlist?list=#{playlist["playlistId"]}">
|
<a href="/playlist?list=#{playlist["playlistId"]}">
|
||||||
|
@ -519,7 +521,7 @@ def template_playlist(playlist)
|
||||||
playlist["videos"].as_a.each do |video|
|
playlist["videos"].as_a.each do |video|
|
||||||
html += <<-END_HTML
|
html += <<-END_HTML
|
||||||
<li class="pure-menu-item" id="#{video["videoId"]}">
|
<li class="pure-menu-item" id="#{video["videoId"]}">
|
||||||
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}&index=#{video["index"]}">
|
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}&index=#{video["index"]}#{listen ? "&listen=1" : ""}">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<img loading="lazy" class="thumbnail" src="/vi/#{video["videoId"]}/mqdefault.jpg" alt="" />
|
<img loading="lazy" class="thumbnail" src="/vi/#{video["videoId"]}/mqdefault.jpg" alt="" />
|
||||||
<p class="length">#{recode_length_seconds(video["lengthSeconds"].as_i)}</p>
|
<p class="length">#{recode_length_seconds(video["lengthSeconds"].as_i)}</p>
|
||||||
|
|
|
@ -328,17 +328,9 @@ module Invidious::Routes::Account
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if env.params.query["action_revoke_token"]?
|
case action = env.params.query["action"]?
|
||||||
action = "action_revoke_token"
|
when "revoke_token"
|
||||||
else
|
session = env.params.query["session"]
|
||||||
return env.redirect referer
|
|
||||||
end
|
|
||||||
|
|
||||||
session = env.params.query["session"]?
|
|
||||||
session ||= ""
|
|
||||||
|
|
||||||
case action
|
|
||||||
when .starts_with? "action_revoke_token"
|
|
||||||
Invidious::Database::SessionIDs.delete(sid: session, email: user.email)
|
Invidious::Database::SessionIDs.delete(sid: session, email: user.email)
|
||||||
else
|
else
|
||||||
return error_json(400, "Unsupported action #{action}")
|
return error_json(400, "Unsupported action #{action}")
|
||||||
|
|
|
@ -8,6 +8,11 @@ module Invidious::Routes::API::Manifest
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
region = env.params.query["region"]?
|
region = env.params.query["region"]?
|
||||||
|
|
||||||
|
if CONFIG.invidious_companion.present?
|
||||||
|
invidious_companion = CONFIG.invidious_companion.sample
|
||||||
|
return env.redirect "#{invidious_companion.public_url}/api/manifest/dash/id/#{id}?#{env.params.query}"
|
||||||
|
end
|
||||||
|
|
||||||
# Since some implementations create playlists based on resolution regardless of different codecs,
|
# Since some implementations create playlists based on resolution regardless of different codecs,
|
||||||
# we can opt to only add a source to a representation if it has a unique height within that representation
|
# we can opt to only add a source to a representation if it has a unique height within that representation
|
||||||
unique_res = env.params.query["unique_res"]?.try { |q| (q == "true" || q == "1").to_unsafe }
|
unique_res = env.params.query["unique_res"]?.try { |q| (q == "true" || q == "1").to_unsafe }
|
||||||
|
@ -27,28 +32,21 @@ module Invidious::Routes::API::Manifest
|
||||||
haltf env, status_code: response.status_code
|
haltf env, status_code: response.status_code
|
||||||
end
|
end
|
||||||
|
|
||||||
manifest = response.body
|
# Proxy URLs for video playback on invidious.
|
||||||
|
# Other API clients can get the original URLs by omiting `local=true`.
|
||||||
manifest = manifest.gsub(/<BaseURL>[^<]+<\/BaseURL>/) do |baseurl|
|
manifest = response.body.gsub(/<BaseURL>[^<]+<\/BaseURL>/) do |baseurl|
|
||||||
url = baseurl.lchop("<BaseURL>")
|
url = baseurl.lchop("<BaseURL>").rchop("</BaseURL>")
|
||||||
url = url.rchop("</BaseURL>")
|
url = HttpServer::Utils.proxy_video_url(url, absolute: true) if local
|
||||||
|
|
||||||
if local
|
|
||||||
uri = URI.parse(url)
|
|
||||||
url = "#{HOST_URL}#{uri.request_target}host/#{uri.host}/"
|
|
||||||
end
|
|
||||||
|
|
||||||
"<BaseURL>#{url}</BaseURL>"
|
"<BaseURL>#{url}</BaseURL>"
|
||||||
end
|
end
|
||||||
|
|
||||||
return manifest
|
return manifest
|
||||||
end
|
end
|
||||||
|
|
||||||
adaptive_fmts = video.adaptive_fmts
|
# Ditto, only proxify URLs if `local=true` is used
|
||||||
|
|
||||||
if local
|
if local
|
||||||
adaptive_fmts.each do |fmt|
|
video.adaptive_fmts.each do |fmt|
|
||||||
fmt["url"] = JSON::Any.new("#{HOST_URL}#{URI.parse(fmt["url"].as_s).request_target}")
|
fmt["url"] = JSON::Any.new(HttpServer::Utils.proxy_video_url(fmt["url"].as_s, absolute: true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -70,17 +68,23 @@ module Invidious::Routes::API::Manifest
|
||||||
# OTF streams aren't supported yet (See https://github.com/TeamNewPipe/NewPipe/issues/2415)
|
# OTF streams aren't supported yet (See https://github.com/TeamNewPipe/NewPipe/issues/2415)
|
||||||
next if !(fmt.has_key?("indexRange") && fmt.has_key?("initRange"))
|
next if !(fmt.has_key?("indexRange") && fmt.has_key?("initRange"))
|
||||||
|
|
||||||
|
audio_track = fmt["audioTrack"]?.try &.as_h? || {} of String => JSON::Any
|
||||||
|
lang = audio_track["id"]?.try &.as_s.split('.')[0] || "und"
|
||||||
|
is_default = audio_track.has_key?("audioIsDefault") ? audio_track["audioIsDefault"].as_bool : i == 0
|
||||||
|
displayname = audio_track["displayName"]?.try &.as_s || "Unknown"
|
||||||
|
bitrate = fmt["bitrate"]
|
||||||
|
|
||||||
# Different representations of the same audio should be groupped into one AdaptationSet.
|
# Different representations of the same audio should be groupped into one AdaptationSet.
|
||||||
# However, most players don't support auto quality switching, so we have to trick them
|
# However, most players don't support auto quality switching, so we have to trick them
|
||||||
# into providing a quality selector.
|
# into providing a quality selector.
|
||||||
# See https://github.com/iv-org/invidious/issues/3074 for more details.
|
# See https://github.com/iv-org/invidious/issues/3074 for more details.
|
||||||
xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true, label: fmt["bitrate"].to_s + "k") do
|
xml.element("AdaptationSet", id: i, mimeType: mime_type, startWithSAP: 1, subsegmentAlignment: true, label: "#{displayname} [#{bitrate}k]", lang: lang) do
|
||||||
codecs = fmt["mimeType"].as_s.split("codecs=")[1].strip('"')
|
codecs = fmt["mimeType"].as_s.split("codecs=")[1].strip('"')
|
||||||
bandwidth = fmt["bitrate"].as_i
|
bandwidth = fmt["bitrate"].as_i
|
||||||
itag = fmt["itag"].as_i
|
itag = fmt["itag"].as_i
|
||||||
url = fmt["url"].as_s
|
url = fmt["url"].as_s
|
||||||
|
|
||||||
xml.element("Role", schemeIdUri: "urn:mpeg:dash:role:2011", value: i == 0 ? "main" : "alternate")
|
xml.element("Role", schemeIdUri: "urn:mpeg:dash:role:2011", value: is_default ? "main" : "alternate")
|
||||||
|
|
||||||
xml.element("Representation", id: fmt["itag"], codecs: codecs, bandwidth: bandwidth) do
|
xml.element("Representation", id: fmt["itag"], codecs: codecs, bandwidth: bandwidth) do
|
||||||
xml.element("AudioChannelConfiguration", schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
|
xml.element("AudioChannelConfiguration", schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
|
||||||
|
@ -177,8 +181,9 @@ module Invidious::Routes::API::Manifest
|
||||||
manifest = response.body
|
manifest = response.body
|
||||||
|
|
||||||
if local
|
if local
|
||||||
manifest = manifest.gsub(/^https:\/\/\w+---.{11}\.c\.youtube\.com[^\n]*/m) do |match|
|
manifest = manifest.gsub(/https:\/\/[^\n"]*/m) do |match|
|
||||||
path = URI.parse(match).path
|
uri = URI.parse(match)
|
||||||
|
path = uri.path
|
||||||
|
|
||||||
path = path.lchop("/videoplayback/")
|
path = path.lchop("/videoplayback/")
|
||||||
path = path.rchop("/")
|
path = path.rchop("/")
|
||||||
|
@ -207,7 +212,7 @@ module Invidious::Routes::API::Manifest
|
||||||
raw_params["fvip"] = fvip["fvip"]
|
raw_params["fvip"] = fvip["fvip"]
|
||||||
end
|
end
|
||||||
|
|
||||||
raw_params["local"] = "true"
|
raw_params["host"] = uri.host.not_nil!
|
||||||
|
|
||||||
"#{HOST_URL}/videoplayback?#{raw_params}"
|
"#{HOST_URL}/videoplayback?#{raw_params}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -197,6 +197,7 @@ module Invidious::Routes::API::V1::Channels
|
||||||
get_channel()
|
get_channel()
|
||||||
|
|
||||||
# Retrieve continuation from URL parameters
|
# Retrieve continuation from URL parameters
|
||||||
|
sort_by = env.params.query["sort_by"]?.try &.downcase || "newest"
|
||||||
continuation = env.params.query["continuation"]?
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
if channel.is_age_gated
|
if channel.is_age_gated
|
||||||
|
@ -211,7 +212,7 @@ module Invidious::Routes::API::V1::Channels
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
videos, next_continuation = Channel::Tabs.get_shorts(
|
videos, next_continuation = Channel::Tabs.get_shorts(
|
||||||
channel, continuation: continuation
|
channel, continuation: continuation, sort_by: sort_by
|
||||||
)
|
)
|
||||||
rescue ex
|
rescue ex
|
||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
|
@ -367,6 +368,35 @@ module Invidious::Routes::API::V1::Channels
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.courses(env)
|
||||||
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
|
ucid = env.params.url["ucid"]
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
|
# Use the macro defined above
|
||||||
|
channel = nil # Make the compiler happy
|
||||||
|
get_channel()
|
||||||
|
|
||||||
|
items, next_continuation = fetch_channel_courses(channel.ucid, channel.author, continuation)
|
||||||
|
|
||||||
|
JSON.build do |json|
|
||||||
|
json.object do
|
||||||
|
json.field "playlists" do
|
||||||
|
json.array do
|
||||||
|
items.each do |item|
|
||||||
|
item.to_json(locale, json) if item.is_a?(SearchPlaylist)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json.field "continuation", next_continuation if next_continuation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.community(env)
|
def self.community(env)
|
||||||
locale = env.get("preferences").as(Preferences).locale
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,9 @@ module Invidious::Routes::API::V1::Misc
|
||||||
format = env.params.query["format"]?
|
format = env.params.query["format"]?
|
||||||
format ||= "json"
|
format ||= "json"
|
||||||
|
|
||||||
|
listen_param = env.params.query["listen"]?
|
||||||
|
listen = (listen_param == "true" || listen_param == "1")
|
||||||
|
|
||||||
if plid.starts_with? "RD"
|
if plid.starts_with? "RD"
|
||||||
return env.redirect "/api/v1/mixes/#{plid}"
|
return env.redirect "/api/v1/mixes/#{plid}"
|
||||||
end
|
end
|
||||||
|
@ -85,7 +88,7 @@ module Invidious::Routes::API::V1::Misc
|
||||||
end
|
end
|
||||||
|
|
||||||
if format == "html"
|
if format == "html"
|
||||||
playlist_html = template_playlist(json_response)
|
playlist_html = template_playlist(json_response, listen)
|
||||||
index, next_video = json_response["videos"].as_a.skip(1 + lookback).select { |video| !video["author"].as_s.empty? }[0]?.try { |v| {v["index"], v["videoId"]} } || {nil, nil}
|
index, next_video = json_response["videos"].as_a.skip(1 + lookback).select { |video| !video["author"].as_s.empty? }[0]?.try { |v| {v["index"], v["videoId"]} } || {nil, nil}
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
|
@ -111,6 +114,9 @@ module Invidious::Routes::API::V1::Misc
|
||||||
format = env.params.query["format"]?
|
format = env.params.query["format"]?
|
||||||
format ||= "json"
|
format ||= "json"
|
||||||
|
|
||||||
|
listen_param = env.params.query["listen"]?
|
||||||
|
listen = (listen_param == "true" || listen_param == "1")
|
||||||
|
|
||||||
begin
|
begin
|
||||||
mix = fetch_mix(rdid, continuation, locale: locale)
|
mix = fetch_mix(rdid, continuation, locale: locale)
|
||||||
|
|
||||||
|
@ -141,9 +147,7 @@ module Invidious::Routes::API::V1::Misc
|
||||||
json.field "authorUrl", "/channel/#{video.ucid}"
|
json.field "authorUrl", "/channel/#{video.ucid}"
|
||||||
|
|
||||||
json.field "videoThumbnails" do
|
json.field "videoThumbnails" do
|
||||||
json.array do
|
Invidious::JSONify::APIv1.thumbnails(json, video.id)
|
||||||
Invidious::JSONify::APIv1.thumbnails(json, video.id)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
json.field "index", video.index
|
json.field "index", video.index
|
||||||
|
@ -157,7 +161,7 @@ module Invidious::Routes::API::V1::Misc
|
||||||
|
|
||||||
if format == "html"
|
if format == "html"
|
||||||
response = JSON.parse(response)
|
response = JSON.parse(response)
|
||||||
playlist_html = template_mix(response)
|
playlist_html = template_mix(response, listen)
|
||||||
next_video = response["videos"].as_a.select { |video| !video["author"].as_s.empty? }[0]?.try &.["videoId"]
|
next_video = response["videos"].as_a.select { |video| !video["author"].as_s.empty? }[0]?.try &.["videoId"]
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
|
|
|
@ -31,9 +31,7 @@ module Invidious::Routes::API::V1::Search
|
||||||
query = env.params.query["q"]? || ""
|
query = env.params.query["q"]? || ""
|
||||||
|
|
||||||
begin
|
begin
|
||||||
client = HTTP::Client.new("suggestqueries-clients6.youtube.com")
|
client = make_client(URI.parse("https://suggestqueries-clients6.youtube.com"), force_youtube_headers: true)
|
||||||
client.before_request { |r| add_yt_headers(r) }
|
|
||||||
|
|
||||||
url = "/complete/search?client=youtube&hl=en&gl=#{region}&q=#{URI.encode_www_form(query)}&gs_ri=youtube&ds=yt"
|
url = "/complete/search?client=youtube&hl=en&gl=#{region}&q=#{URI.encode_www_form(query)}&gs_ri=youtube&ds=yt"
|
||||||
|
|
||||||
response = client.get(url).body
|
response = client.get(url).body
|
||||||
|
|
|
@ -429,4 +429,90 @@ module Invidious::Routes::API::V1::Videos
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Fetches transcripts from YouTube
|
||||||
|
#
|
||||||
|
# Use the `lang` and `autogen` query parameter to select which transcript to fetch
|
||||||
|
# Request without any URL parameters to see all the available transcripts.
|
||||||
|
def self.transcripts(env)
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
|
id = env.params.url["id"]
|
||||||
|
lang = env.params.query["lang"]?
|
||||||
|
label = env.params.query["label"]?
|
||||||
|
auto_generated = env.params.query["autogen"]? ? true : false
|
||||||
|
|
||||||
|
# Return all available transcript options when none is given
|
||||||
|
if !label && !lang
|
||||||
|
begin
|
||||||
|
video = get_video(id)
|
||||||
|
rescue ex : NotFoundException
|
||||||
|
return error_json(404, ex)
|
||||||
|
rescue ex
|
||||||
|
return error_json(500, ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
response = JSON.build do |json|
|
||||||
|
# The amount of transcripts available to fetch is the
|
||||||
|
# same as the amount of captions available.
|
||||||
|
available_transcripts = video.captions
|
||||||
|
|
||||||
|
json.object do
|
||||||
|
json.field "transcripts" do
|
||||||
|
json.array do
|
||||||
|
available_transcripts.each do |transcript|
|
||||||
|
json.object do
|
||||||
|
json.field "label", transcript.name
|
||||||
|
json.field "languageCode", transcript.language_code
|
||||||
|
json.field "autoGenerated", transcript.auto_generated
|
||||||
|
|
||||||
|
if transcript.auto_generated
|
||||||
|
json.field "url", "/api/v1/transcripts/#{id}?lang=#{URI.encode_www_form(transcript.language_code)}&autogen"
|
||||||
|
else
|
||||||
|
json.field "url", "/api/v1/transcripts/#{id}?lang=#{URI.encode_www_form(transcript.language_code)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
|
||||||
|
# If lang is not given then we attempt to fetch
|
||||||
|
# the transcript through the given label
|
||||||
|
if lang.nil?
|
||||||
|
begin
|
||||||
|
video = get_video(id)
|
||||||
|
rescue ex : NotFoundException
|
||||||
|
return error_json(404, ex)
|
||||||
|
rescue ex
|
||||||
|
return error_json(500, ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
target_transcript = video.captions.select(&.name.== label)
|
||||||
|
if target_transcript.empty?
|
||||||
|
return error_json(404, NotFoundException.new("Requested transcript does not exist"))
|
||||||
|
else
|
||||||
|
target_transcript = target_transcript[0]
|
||||||
|
lang, auto_generated = target_transcript.language_code, target_transcript.auto_generated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
params = Invidious::Videos::Transcript.generate_param(id, lang, auto_generated)
|
||||||
|
|
||||||
|
begin
|
||||||
|
transcript = Invidious::Videos::Transcript.from_raw(
|
||||||
|
YoutubeAPI.get_transcript(params), lang, auto_generated
|
||||||
|
)
|
||||||
|
rescue ex : NotFoundException
|
||||||
|
return error_json(404, ex)
|
||||||
|
rescue ex
|
||||||
|
return error_json(500, ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
return transcript.to_json
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,14 +20,6 @@ module Invidious::Routes::BeforeAll
|
||||||
env.response.headers["X-XSS-Protection"] = "1; mode=block"
|
env.response.headers["X-XSS-Protection"] = "1; mode=block"
|
||||||
env.response.headers["X-Content-Type-Options"] = "nosniff"
|
env.response.headers["X-Content-Type-Options"] = "nosniff"
|
||||||
|
|
||||||
# Allow media resources to be loaded from google servers
|
|
||||||
# TODO: check if *.youtube.com can be removed
|
|
||||||
if CONFIG.disabled?("local") || !preferences.local
|
|
||||||
extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443"
|
|
||||||
else
|
|
||||||
extra_media_csp = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
# Only allow the pages at /embed/* to be embedded
|
# Only allow the pages at /embed/* to be embedded
|
||||||
if env.request.resource.starts_with?("/embed")
|
if env.request.resource.starts_with?("/embed")
|
||||||
frame_ancestors = "'self' file: http: https:"
|
frame_ancestors = "'self' file: http: https:"
|
||||||
|
@ -45,7 +37,7 @@ module Invidious::Routes::BeforeAll
|
||||||
"font-src 'self' data:",
|
"font-src 'self' data:",
|
||||||
"connect-src 'self'",
|
"connect-src 'self'",
|
||||||
"manifest-src 'self'",
|
"manifest-src 'self'",
|
||||||
"media-src 'self' blob:" + extra_media_csp,
|
"media-src 'self' blob:",
|
||||||
"child-src 'self' blob:",
|
"child-src 'self' blob:",
|
||||||
"frame-src 'self'",
|
"frame-src 'self'",
|
||||||
"frame-ancestors " + frame_ancestors,
|
"frame-ancestors " + frame_ancestors,
|
||||||
|
@ -110,6 +102,21 @@ module Invidious::Routes::BeforeAll
|
||||||
preferences.locale = locale
|
preferences.locale = locale
|
||||||
env.set "preferences", preferences
|
env.set "preferences", preferences
|
||||||
|
|
||||||
|
# Allow media resources to be loaded from google servers
|
||||||
|
# TODO: check if *.youtube.com can be removed
|
||||||
|
#
|
||||||
|
# `!preferences.local` has to be checked after setting and
|
||||||
|
# reading `preferences` from the "PREFS" cookie and
|
||||||
|
# saved user preferences from the database, otherwise
|
||||||
|
# `https://*.googlevideo.com:443 https://*.youtube.com:443`
|
||||||
|
# will not be set in the CSP header if
|
||||||
|
# `default_user_preferences.local` is set to true on the
|
||||||
|
# configuration file, causing preference “Proxy Videos”
|
||||||
|
# not to work while having it disabled and using medium quality.
|
||||||
|
if CONFIG.disabled?("local") || !preferences.local
|
||||||
|
env.response.headers["Content-Security-Policy"] = env.response.headers["Content-Security-Policy"].gsub("media-src", "media-src https://*.googlevideo.com:443 https://*.youtube.com:443")
|
||||||
|
end
|
||||||
|
|
||||||
current_page = env.request.path
|
current_page = env.request.path
|
||||||
if env.request.query
|
if env.request.query
|
||||||
query = HTTP::Params.parse(env.request.query.not_nil!)
|
query = HTTP::Params.parse(env.request.query.not_nil!)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue