Compare commits

...

110 Commits

Author SHA1 Message Date
ce7ac8d6ca Add login restriction
https://github.com/iv-org/invidious/issues/446#issuecomment-1342296218
2023-10-27 16:00:34 +02:00
syeopite
8087e64dfe
Bump postgre version in dev compose (#4203)
Fix postgre ver diff between dev and prod compose
2023-10-26 00:22:59 +02:00
Samantaz Fox
2414e7db41
User: Add support for importing Youtube watch history (#4171) 2023-10-21 18:33:05 +02:00
Samantaz Fox
1a33012cad
CSS: Remove inline styling of the download widget (#4162) 2023-10-21 18:32:40 +02:00
Samantaz Fox
cf7c49deb0
Captions: Use 'fmt=vtt' instead of 'format=vtt' (#4152) 2023-10-21 18:32:28 +02:00
Samantaz Fox
d543a68a84
API: Add "authorVerified" to the video endpoint (#4150) 2023-10-21 18:32:01 +02:00
Samantaz Fox
2a65b5f52e
Frontend: Add video timestamp on external links (#4101) 2023-10-21 18:31:42 +02:00
Samantaz Fox
9072fa4355
CSS: Improve links contrast on dark theme (#4100) 2023-10-21 18:31:30 +02:00
Samantaz Fox
88cc62d45e
User: Fix importing FreeTube subscriptions with multiple profiles (#4011) 2023-10-21 18:31:18 +02:00
Samantaz Fox
40919c6a83
JS: Update external links exactly once per second 2023-10-21 13:45:15 +02:00
Ulysses Zhan
3b219a4c7f remove a debug statement 2023-10-20 13:45:16 -07:00
zlElo
b809e877a1
fix wrong link (#4183)
The link wasn't working, because there was a lost letter.
2023-10-20 15:40:04 +02:00
Émilien (perso)
0e4d3d89fc
update params for fetching stream data (#4156)
* update params for fetching stream data

* Remove link about special parameter
2023-10-20 08:48:56 +02:00
RadoslavL
cc703b0274 Removed commented lines 2023-10-17 10:02:08 +03:00
Ulysses Zhan
81a4f29c73 add 'Import YouTube watch history (.json)' entry to en-US.json 2023-10-16 21:46:41 -07:00
jt404
d7ea5609b2 move styles for download widget to default.css file 2023-10-17 01:00:14 +02:00
ChunkyProgrammer
0bd415158f Fix importing FreeTube subscriptions with multiple profiles
Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2023-10-16 00:32:24 -04:00
Ulysses Zhan
50977fb5d9 added translation importing watch history from youtube: zh-CN, zh-TW 2023-10-14 16:05:07 -07:00
Ulysses Zhan
a1a0e4c59f update readme about importing watch history 2023-10-14 15:56:04 -07:00
Ulysses Zhan
b9cbdce976 add: importing watch history from YouTube 2023-10-14 15:49:33 -07:00
jt404
8125ddca06 Replace inline styling for download widget with css file 2023-10-12 03:22:34 +02:00
xbdm
069e91d2a6
alpine v3.18 & Update Helm release postgresql (#4103)
Co-authored-by: Samantaz Fox <coding@samantaz.fr>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-10 19:12:21 +00:00
RadoslavL
8e45e05fba
Get "author_verified" using the instance method instead of using the info hash.
Co-authored-by: Samantaz Fox <coding@samantaz.fr>
2023-10-09 09:20:35 +03:00
Samantaz Fox
0aebac5f3e
Captions: Use 'fmt=vtt' instead of 'format=vtt' 2023-10-08 12:40:49 +02:00
Samantaz Fox
60fae015d8
Add support for community post page/comments (#4010) 2023-10-07 19:58:54 +02:00
Samantaz Fox
ce0e21400e
Search: Parse channel handle (#3994) 2023-10-07 19:56:07 +02:00
Samantaz Fox
7b40775427
Add more fields to PWA manifest (#4145) 2023-10-07 19:55:00 +02:00
Samantaz Fox
1caaf63c8a
Refactor WebVTT building logic into WebVTT::Builder (#4070) 2023-10-07 19:53:29 +02:00
Samantaz Fox
eddb54adb1
Routing: Add support for new routes (#4099) 2023-10-07 19:52:48 +02:00
Samantaz Fox
01491bf315
Translations update from Hosted Weblate (#4078) 2023-10-07 19:51:27 +02:00
Samantaz Fox
8ca884a5a3
i18Next: Add exceptions for mixed v3/v4 plural forms (#4147) 2023-10-07 19:50:56 +02:00
Hosted Weblate
3dc0574bb5
Update translation files
Updated by "Squash Git commits" hook in Weblate.

Translation: Invidious/Invidious Translations
Translate-URL: https://hosted.weblate.org/projects/invidious/translations/
2023-10-07 19:43:31 +02:00
Hosted Weblate
42b6c8032f
Update Norwegian Bokmål translation
Co-authored-by: Petter Reinholdtsen <pere-weblate@hungry.com>
2023-10-07 19:43:31 +02:00
Hosted Weblate
add6b3a602
Update Serbian (cyrillic) translation
Co-authored-by: NEXI <nexiphotographer@gmail.com>
2023-10-07 19:43:31 +02:00
Hosted Weblate
06e01f52cf
Update Chinese (Simplified) translation
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
2023-10-07 19:43:31 +02:00
Hosted Weblate
b73ea63e55
Update Chinese (Traditional) translation
Co-authored-by: Jeff Huang <s8321414@gmail.com>
2023-10-07 19:43:31 +02:00
Hosted Weblate
4723c1b3ee
Update Slovenian translation
Co-authored-by: Damjan Gerl <damjan@damjan.net>
2023-10-07 19:43:31 +02:00
Hosted Weblate
53905ac55f
Update Korean translation
Co-authored-by: xrfmkrh <rF3nMd7sRKezjF2vcEQo@protonmail.com>
2023-10-07 19:43:31 +02:00
Hosted Weblate
f6fbabc15d
Update Albanian translation
Co-authored-by: Besnik Bleta <besnik@programeshqip.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Radoslav Lelchev <rlelchev@abv.bg>
Translate-URL: https://hosted.weblate.org/projects/invidious/translations/
Translation: Invidious/Invidious Translations
2023-10-07 19:43:17 +02:00
Hosted Weblate
72aa4f6a6c
Update Serbian translation
Co-authored-by: NEXI <nexiphotographer@gmail.com>
2023-10-07 19:43:15 +02:00
Hosted Weblate
265bf2427c
Update French translation
Update French translation

Update French translation

Co-authored-by: Samantaz Fox <translator-weblate@samantaz.fr>
2023-10-07 19:43:15 +02:00
Hosted Weblate
0d055d4baa
Update Spanish translation
Update Spanish translation

Co-authored-by: Jorge Maldonado Ventura <jorgesumle@freakspot.net>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2023-10-07 19:43:10 +02:00
Hosted Weblate
9910939f43
Update Indonesian translation
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
2023-10-07 17:35:08 +00:00
Hosted Weblate
4917c5be4b
Update Arabic translation
Co-authored-by: Rex_sa <rex.sa@pm.me>
2023-10-07 17:35:08 +00:00
Hosted Weblate
2326330988
Update Italian translation
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
2023-10-07 17:35:08 +00:00
Hosted Weblate
dd21628792
Update Polish translation
Co-authored-by: Matthaiks <kitynska@gmail.com>
2023-10-07 17:35:07 +00:00
Hosted Weblate
2d6ab80622
Update Croatian translation
Co-authored-by: Milo Ivir <mail@milotype.de>
2023-10-07 17:35:07 +00:00
Hosted Weblate
0fe0524597
Update Czech translation
Co-authored-by: Fjuro <ifjuro@proton.me>
2023-10-07 17:35:06 +00:00
Hosted Weblate
fdf05eaa2b
Update Catalan translation
Co-authored-by: victor dargallo <victordargallo@disroot.org>
2023-10-07 17:35:06 +00:00
Hosted Weblate
6799c0b9b8
Update Japanese translation
Co-authored-by: maboroshin <maboroshin@users.noreply.hosted.weblate.org>
2023-10-07 17:35:05 +00:00
Hosted Weblate
4824a1f59a
Update Ukrainian translation
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
2023-10-07 17:35:05 +00:00
Hosted Weblate
71cbe97f1a
Update Bulgarian translation
Add Bulgarian translation

Co-authored-by: Radoslav Lelchev <rlelchev05@gmail.com>
Co-authored-by: Radoslav Lelchev <rlelchev@abv.bg>
2023-10-07 17:35:05 +00:00
Hosted Weblate
18549e8d27
Add Belarusian translation
Co-authored-by: Maksim <maxklezovich@gmail.com>
2023-10-07 17:35:04 +00:00
Hosted Weblate
9f695faf5d
Update Russian translation
Co-authored-by: Radoslav Lelchev <rlelchev@abv.bg>
2023-10-07 17:35:04 +00:00
Hosted Weblate
3cc2b34795
Update Esperanto translation
Co-authored-by: Jorge Maldonado Ventura <jorgesumle@freakspot.net>
2023-10-07 17:35:03 +00:00
Hosted Weblate
c0d6217cad
Update Greek translation
Co-authored-by: fresh <fresh190@protonmail.com>
2023-10-07 17:35:03 +00:00
Hosted Weblate
34b206899d
Update German translation
Update German translation

Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Radoslav Lelchev <rlelchev@abv.bg>
2023-10-07 17:35:02 +00:00
Hosted Weblate
b56dd5a010
Update Portuguese (Brazil) translation
Update Portuguese (Brazil) translation

Co-authored-by: Henrique <henrique.roberto97@gmail.com>
Co-authored-by: joaooliva <joaooliva@protonmail.com>
2023-10-07 17:35:02 +00:00
Hosted Weblate
68184e9d40
Update Turkish translation
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
2023-10-07 17:35:01 +00:00
RadoslavL
7dc9b3f088 Fixed formatting 2023-10-07 20:29:48 +03:00
Samantaz Fox
f26c995344
i18next: Revert some changes, as es/pt/pt-PT aren't mixed up (yet) 2023-10-07 19:12:17 +02:00
RadoslavL
ed2a44149e Added authorVerified to the video API v1 route 2023-10-07 17:55:42 +03:00
Jake Anto
572d9cf4a7
Remove shortcuts
As per PR comment https://github.com/iv-org/invidious/pull/4145#issuecomment-1750048450
2023-10-06 21:47:51 +05:30
Samantaz Fox
32310b7c9f
i18Next: Add exceptions for mixed v3/v4 plural forms 2023-10-06 08:19:27 +02:00
Jake Anto
6d177b5fa4
Add shortcuts 2023-10-05 15:26:18 +05:30
Jake Anto
877037e114
Add start_url 2023-10-05 15:24:44 +05:30
Jake Anto
2e6101e623
Add description 2023-10-05 15:23:00 +05:30
ChunkyProgrammer
f77e4378fe Add support for viewing comments without js
Improve stylings
2023-09-28 18:09:45 -04:00
ChunkyProgrammer
4f25069f55 remove unused variable
simplify resolve url

remove trailing spaces

Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2023-09-27 17:36:06 -04:00
RadoslavL
ea781ceeee Removed unnecessary lines 2023-09-24 10:08:16 +03:00
syeopite
be2feba17c
Lint 2023-09-23 09:57:26 -04:00
syeopite
a999438ae4
Consistency: rename #add_timestamp_component
Removes the add_ prefix for consistency with the other methods in
WebVTT::Builder
2023-09-23 09:41:43 -04:00
syeopite
e9d59a6dfd
Update src/invidious/helpers/webvtt.cr
Co-authored-by: Samantaz Fox <coding@samantaz.fr>
2023-09-23 09:41:43 -04:00
syeopite
4e97d8ad09
Update documentation for WebVTT.build 2023-09-23 09:41:43 -04:00
syeopite
d371eb50f2
WebVTT::Builder: rename #line to #cue 2023-09-23 09:41:41 -04:00
syeopite
0cb7d0b441
Refactor Invidious's VTT logic to use WebVtt.build 2023-09-23 09:40:04 -04:00
syeopite
54fa59cbb0
Add method to construct WebVTT files
Similar to JSON.Build
2023-09-23 09:20:20 -04:00
RadoslavL
8542c974c8
Merge branch 'iv-org:master' into dark-mode-contrast 2023-09-22 11:01:38 +03:00
RadoslavL
e8c9b85ef5 Increased footer contrast 2023-09-19 09:15:44 +03:00
ChunkyProgrammer
8781520b8a Search: Parse channel handle and hide video count when channel handle exists
Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2023-09-18 20:12:07 -04:00
ChunkyProgrammer
bb04bcc42c Apply suggestions from code review
add videoId to resolve_url function

Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2023-09-18 20:10:41 -04:00
ChunkyProgrammer
f55b96a53b Always craft Community Post params 2023-09-18 20:10:41 -04:00
ChunkyProgrammer
734f1b7764 Simplify resolveUrl api call
Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2023-09-18 20:10:41 -04:00
ChunkyProgrammer
e3c365f3d6 Add support for post page 2023-09-18 20:10:41 -04:00
Samantaz Fox
bb14f79496
Playlists: Use subtitle when author is missing (#4025) 2023-09-18 23:34:30 +02:00
Samantaz Fox
bf35200207
Bump stale timer for PRs (#4107) 2023-09-18 23:33:34 +02:00
Samantaz Fox
98ff03a926
CI: Update crystal version matrix (#4095) 2023-09-18 23:32:42 +02:00
Samantaz Fox
842e9fade5
Captions: Add ability to use Innertube's transcripts API (#4001) 2023-09-18 23:31:56 +02:00
RadoslavL
270d606ad8 Changed the default dark theme as well 2023-09-18 08:14:34 +03:00
syeopite
760bf4cfb3
Bump stale timer for PRs 2023-09-16 23:22:49 +00:00
Samantaz Fox
bbf067ed55
Bump crystal-install too 2023-09-16 11:55:45 +02:00
Samantaz Fox
33ce0ddf14
Update crystal version matrix in ci.yml 2023-09-16 11:55:42 +02:00
Émilien (perso)
cc03610325
Test crystal 1.8.2 2023-09-16 09:10:48 +00:00
Samantaz Fox
ebee973b24
Routes: Redirect unknown channel tabs to channel home page 2023-09-16 00:54:14 +02:00
RadoslavL
beec62cf0e Increased link contrast in dark mode 2023-09-14 20:37:35 +03:00
Samantaz Fox
2425c47882
Routing: Add support for the '/live/<id>' route 2023-09-13 23:41:31 +02:00
Samantaz Fox
49b9316b9f
Routing: Handle current and future routes more nicely 2023-09-13 23:40:20 +02:00
ChunkyProgrammer
afb04c3bda HTMLl.Escape the playlist subtitle 2023-09-11 22:35:58 -04:00
ChunkyProgrammer
d7696574f4 Playlist: Use subtitle when author is missing 2023-09-11 22:35:57 -04:00
syeopite
eabcea6f4a
Remove trailing whitespace in config documentation
Co-authored-by: Samantaz Fox <coding@samantaz.fr>
2023-08-29 06:18:35 +00:00
syeopite
3615bb0e62
Update src/invidious/videos/caption.cr
Co-authored-by: Samantaz Fox <coding@samantaz.fr>
2023-08-24 16:21:05 -07:00
syeopite
7d435f082b
Update src/invidious/videos/transcript.cr
Co-authored-by: Samantaz Fox <coding@samantaz.fr>
2023-08-24 23:20:20 +00:00
syeopite
1f7592e599
Refactor structure of caption.cr
Rename CaptionsMetadata to Metadata
Nest Metadata under Captions
Unnest LANGUAGES constant from Metadata to main Captions module
2023-08-24 16:00:02 -07:00
syeopite
3509752b79
Rename transcript() to get_transcript() in YT API 2023-07-23 16:52:47 -07:00
syeopite
e4942b188f
Integrate transcript captions into captions API 2023-07-23 14:40:09 -07:00
syeopite
caac7e2166
Add method to convert transcripts response to vtt 2023-07-23 14:40:08 -07:00
syeopite
4b3ac1a757
Add method to parse transcript JSON into structs 2023-07-23 14:40:08 -07:00
syeopite
8e18d445a7
Add method to generate params for transcripts api 2023-07-23 14:40:08 -07:00
syeopite
7e5935a9da
Rename Caption struct to CaptionMetadata
The Caption object does not actually store any text lines for the
subtitles. Instead it stores the metadata needed to display and fetch
the actual captions from the YT timedtext API.

Therefore it may be wiser to rename the struct to be more reflective of
its current usage as well as the future usage once the current caption
retrival system is replaced via InnerTube's transcript API
2023-07-23 14:40:08 -07:00
syeopite
2e67b90540
Add method to query /youtubei/v1/get_transcript 2023-07-23 14:40:02 -07:00
77 changed files with 2528 additions and 855 deletions

View File

@ -38,11 +38,10 @@ jobs:
matrix: matrix:
stable: [true] stable: [true]
crystal: crystal:
- 1.4.1
- 1.5.1
- 1.6.2 - 1.6.2
- 1.7.3 - 1.7.3
- 1.8.1 - 1.8.2
- 1.9.2
include: include:
- crystal: nightly - crystal: nightly
stable: false stable: false
@ -53,7 +52,7 @@ jobs:
submodules: true submodules: true
- name: Install Crystal - name: Install Crystal
uses: crystal-lang/install-crystal@v1.7.0 uses: crystal-lang/install-crystal@v1.8.0
with: with:
crystal: ${{ matrix.crystal }} crystal: ${{ matrix.crystal }}

View File

@ -25,9 +25,9 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Crystal - name: Install Crystal
uses: crystal-lang/install-crystal@v1.6.0 uses: crystal-lang/install-crystal@v1.8.0
with: with:
crystal: 1.5.0 crystal: 1.9.2
- name: Run lint - name: Run lint
run: | run: |
@ -77,4 +77,3 @@ jobs:
tags: quay.io/invidious/invidious:${{ github.sha }}-arm64,quay.io/invidious/invidious:latest-arm64 tags: quay.io/invidious/invidious:${{ github.sha }}-arm64,quay.io/invidious/invidious:latest-arm64
build-args: | build-args: |
"release=1" "release=1"

View File

@ -14,7 +14,7 @@ jobs:
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 365 days-before-stale: 365
days-before-pr-stale: 45 # PRs should be active. Anything that hasn't had activity in more than 45 days should be considered abandoned. days-before-pr-stale: 90
days-before-close: 30 days-before-close: 30
exempt-pr-labels: blocked exempt-pr-labels: blocked
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.'

View File

@ -82,7 +82,7 @@
**Data import/export** **Data import/export**
- Import subscriptions from YouTube, NewPipe and Freetube - Import subscriptions from YouTube, NewPipe and Freetube
- Import watch history from 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
@ -149,7 +149,7 @@ Weblate also allows you to log-in with major SSO providers like Github, Gitlab,
- [CloudTube](https://sr.ht/~cadence/tube/): A JavaScript-rich alternate YouTube player. - [CloudTube](https://sr.ht/~cadence/tube/): A JavaScript-rich alternate YouTube player.
- [PeerTubeify](https://gitlab.com/Cha_de_L/peertubeify): On YouTube, displays a link to the same video on PeerTube, if it exists. - [PeerTubeify](https://gitlab.com/Cha_de_L/peertubeify): On YouTube, displays a link to the same video on PeerTube, if it exists.
- [MusicPiped](https://github.com/deep-gaurav/MusicPiped): A material design music player that streams music from YouTube. - [MusicPiped](https://github.com/deep-gaurav/MusicPiped): A material design music player that streams music from YouTube.
- [HoloPlay](https://github.com/stephane-r/holoplay-wa): Progressive Web App connecting on Invidious API's with search, playlists and favorites. - [HoloPlay](https://github.com/stephane-r/holoplay-pwa): Progressive Web App connecting on Invidious API's with search, playlists and favorites.
- [WatchTube](https://github.com/WatchTubeTeam/WatchTube): Powerful YouTube client for Apple Watch. - [WatchTube](https://github.com/WatchTubeTeam/WatchTube): Powerful YouTube client for Apple Watch.
- [Yattee](https://github.com/yattee/yattee): Alternative YouTube frontend for iPhone, iPad, Mac and Apple TV. - [Yattee](https://github.com/yattee/yattee): Alternative YouTube frontend for iPhone, iPad, Mac and Apple TV.
- [TubiTui](https://codeberg.org/777/TubiTui): A lightweight, libre, TUI-based YouTube client. - [TubiTui](https://codeberg.org/777/TubiTui): A lightweight, libre, TUI-based YouTube client.

View File

@ -392,11 +392,19 @@ p.video-data { margin: 0; font-weight: bold; font-size: 80%; }
* Comments & community posts * Comments & community posts
*/ */
#comments { .comments {
max-width: 800px; max-width: 800px;
margin: auto; margin: auto;
} }
/*
* We don't want the top and bottom margin on the post page.
*/
.comments.post-comments {
margin-bottom: 0;
margin-top: 0;
}
.video-iframe-wrapper { .video-iframe-wrapper {
position: relative; position: relative;
height: 0; height: 0;
@ -433,16 +441,26 @@ p.video-data { margin: 0; font-weight: bold; font-size: 80%; }
*/ */
footer { footer {
color: #919191;
margin-top: auto; margin-top: auto;
padding: 1.5em 0; padding: 1.5em 0;
text-align: center; text-align: center;
max-height: 30vh; max-height: 30vh;
} }
footer a { .light-theme footer {
color: #919191 !important; color: #7c7c7c;
text-decoration: underline; }
.dark-theme footer {
color: #adadad;
}
.light-theme footer a {
color: #7c7c7c !important;
}
.dark-theme footer a {
color: #adadad !important;
} }
footer span { footer span {
@ -548,6 +566,14 @@ span > select {
color: #303030; color: #303030;
} }
.no-theme footer {
color: #7c7c7c;
}
.no-theme footer a {
color: #7c7c7c !important;
}
.light-theme .pure-menu-heading { .light-theme .pure-menu-heading {
color: #565d64; color: #565d64;
} }
@ -581,7 +607,7 @@ span > select {
} }
.dark-theme a { .dark-theme a {
color: #a0a0a0; color: #adadad;
text-decoration: none; text-decoration: none;
} }
@ -635,7 +661,7 @@ body.dark-theme {
} }
.no-theme a { .no-theme a {
color: #a0a0a0; color: #adadad;
text-decoration: none; text-decoration: none;
} }
@ -666,6 +692,14 @@ body.dark-theme {
background-color: inherit; background-color: inherit;
color: inherit; color: inherit;
} }
.no-theme footer {
color: #adadad;
}
.no-theme footer a {
color: #adadad !important;
}
} }
@ -759,3 +793,7 @@ h1, h2, h3, h4, h5, p,
.channel-emoji { .channel-emoji {
margin: 0 2px; margin: 0 2px;
} }
#download_widget {
width: 100%;
}

174
assets/js/comments.js Normal file
View File

@ -0,0 +1,174 @@
var video_data = JSON.parse(document.getElementById('video_data').textContent);
var spinnerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
var spinnerHTMLwithHR = spinnerHTML + '<hr>';
String.prototype.supplant = function (o) {
return this.replace(/{([^{}]*)}/g, function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
});
};
function toggle_comments(event) {
var target = event.target;
var body = target.parentNode.parentNode.parentNode.children[1];
if (body.style.display === 'none') {
target.textContent = '[ ]';
body.style.display = '';
} else {
target.textContent = '[ + ]';
body.style.display = 'none';
}
}
function hide_youtube_replies(event) {
var target = event.target;
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
var body = target.parentNode.parentNode.children[1];
body.style.display = 'none';
target.textContent = sub_text;
target.onclick = show_youtube_replies;
target.setAttribute('data-inner-text', inner_text);
target.setAttribute('data-sub-text', sub_text);
}
function show_youtube_replies(event) {
var target = event.target;
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
var body = target.parentNode.parentNode.children[1];
body.style.display = '';
target.textContent = sub_text;
target.onclick = hide_youtube_replies;
target.setAttribute('data-inner-text', inner_text);
target.setAttribute('data-sub-text', sub_text);
}
function get_youtube_comments() {
var comments = document.getElementById('comments');
var fallback = comments.innerHTML;
comments.innerHTML = spinnerHTML;
var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
var url = baseUrl +
'?format=html' +
'&hl=' + video_data.preferences.locale +
'&thin_mode=' + video_data.preferences.thin_mode;
if (video_data.ucid) {
url += '&ucid=' + video_data.ucid
}
var onNon200 = function (xhr) { comments.innerHTML = fallback; };
if (video_data.params.comments[1] === 'youtube')
onNon200 = function (xhr) {};
helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, {
on200: function (response) {
var commentInnerHtml = ' \
<div> \
<h3> \
<a href="javascript:void(0)">[ ]</a> \
{commentsText} \
</h3> \
<b> \
'
if (video_data.support_reddit) {
commentInnerHtml += ' <a href="javascript:void(0)" data-comments="reddit"> \
{redditComments} \
</a> \
'
}
commentInnerHtml += ' </b> \
</div> \
<div>{contentHtml}</div> \
<hr>'
commentInnerHtml = commentInnerHtml.supplant({
contentHtml: response.contentHtml,
redditComments: video_data.reddit_comments_text,
commentsText: video_data.comments_text.supplant({
// toLocaleString correctly splits number with local thousands separator. e.g.:
// '1,234,567.89' for user with English locale
// '1 234 567,89' for user with Russian locale
// '1.234.567,89' for user with Portuguese locale
commentCount: response.commentCount.toLocaleString()
})
});
comments.innerHTML = commentInnerHtml;
comments.children[0].children[0].children[0].onclick = toggle_comments;
if (video_data.support_reddit) {
comments.children[0].children[1].children[0].onclick = swap_comments;
}
},
onNon200: onNon200, // declared above
onError: function (xhr) {
comments.innerHTML = spinnerHTML;
},
onTimeout: function (xhr) {
comments.innerHTML = spinnerHTML;
}
});
}
function get_youtube_replies(target, load_more, load_replies) {
var continuation = target.getAttribute('data-continuation');
var body = target.parentNode.parentNode;
var fallback = body.innerHTML;
body.innerHTML = spinnerHTML;
var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
var url = baseUrl +
'?format=html' +
'&hl=' + video_data.preferences.locale +
'&thin_mode=' + video_data.preferences.thin_mode +
'&continuation=' + continuation;
if (video_data.ucid) {
url += '&ucid=' + video_data.ucid
}
if (load_replies) url += '&action=action_get_comment_replies';
helpers.xhr('GET', url, {}, {
on200: function (response) {
if (load_more) {
body = body.parentNode.parentNode;
body.removeChild(body.lastElementChild);
body.insertAdjacentHTML('beforeend', response.contentHtml);
} else {
body.removeChild(body.lastElementChild);
var p = document.createElement('p');
var a = document.createElement('a');
p.appendChild(a);
a.href = 'javascript:void(0)';
a.onclick = hide_youtube_replies;
a.setAttribute('data-sub-text', video_data.hide_replies_text);
a.setAttribute('data-inner-text', video_data.show_replies_text);
a.textContent = video_data.hide_replies_text;
var div = document.createElement('div');
div.innerHTML = response.contentHtml;
body.appendChild(p);
body.appendChild(div);
}
},
onNon200: function (xhr) {
body.innerHTML = fallback;
},
onTimeout: function (xhr) {
console.warn('Pulling comments failed');
body.innerHTML = fallback;
}
});
}

View File

@ -114,12 +114,23 @@ function addCurrentTimeToURL(url, base) {
return urlUsed; return urlUsed;
} }
/**
* Global variable to save the last timestamp (in full seconds) at which the external
* links were updated by the 'timeupdate' callback below.
*
* It is initialized to 5s so that the video will always restart from the beginning
* if the user hasn't really started watching before switching to the other website.
*/
var timeupdate_last_ts = 5;
/** /**
* Callback that updates the timestamp on all external links * Callback that updates the timestamp on all external links
*/ */
player.on('timeupdate', function () { player.on('timeupdate', function () {
// Only update once every 5 seconds // Only update once every second
if ((Math.ceil(player.currentTime()) % 5) != 0) return; let current_ts = Math.floor(player.currentTime());
if (current_ts > timeupdate_last_ts) timeupdate_last_ts = current_ts;
else return;
// YouTube links // YouTube links

3
assets/js/post.js Normal file
View File

@ -0,0 +1,3 @@
addEventListener('load', function (e) {
get_youtube_comments();
});

View File

@ -1,14 +1,4 @@
'use strict'; 'use strict';
var video_data = JSON.parse(document.getElementById('video_data').textContent);
var spinnerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
var spinnerHTMLwithHR = spinnerHTML + '<hr>';
String.prototype.supplant = function (o) {
return this.replace(/{([^{}]*)}/g, function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
});
};
function toggle_parent(target) { function toggle_parent(target) {
var body = target.parentNode.parentNode.children[1]; var body = target.parentNode.parentNode.children[1];
@ -21,18 +11,6 @@ function toggle_parent(target) {
} }
} }
function toggle_comments(event) {
var target = event.target;
var body = target.parentNode.parentNode.parentNode.children[1];
if (body.style.display === 'none') {
target.textContent = '[ ]';
body.style.display = '';
} else {
target.textContent = '[ + ]';
body.style.display = 'none';
}
}
function swap_comments(event) { function swap_comments(event) {
var source = event.target.getAttribute('data-comments'); var source = event.target.getAttribute('data-comments');
@ -43,36 +21,6 @@ function swap_comments(event) {
} }
} }
function hide_youtube_replies(event) {
var target = event.target;
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
var body = target.parentNode.parentNode.children[1];
body.style.display = 'none';
target.textContent = sub_text;
target.onclick = show_youtube_replies;
target.setAttribute('data-inner-text', inner_text);
target.setAttribute('data-sub-text', sub_text);
}
function show_youtube_replies(event) {
var target = event.target;
var sub_text = target.getAttribute('data-inner-text');
var inner_text = target.getAttribute('data-sub-text');
var body = target.parentNode.parentNode.children[1];
body.style.display = '';
target.textContent = sub_text;
target.onclick = hide_youtube_replies;
target.setAttribute('data-inner-text', inner_text);
target.setAttribute('data-sub-text', sub_text);
}
var continue_button = document.getElementById('continue'); var continue_button = document.getElementById('continue');
if (continue_button) { if (continue_button) {
continue_button.onclick = continue_autoplay; continue_button.onclick = continue_autoplay;
@ -208,111 +156,6 @@ function get_reddit_comments() {
}); });
} }
function get_youtube_comments() {
var comments = document.getElementById('comments');
var fallback = comments.innerHTML;
comments.innerHTML = spinnerHTML;
var url = '/api/v1/comments/' + video_data.id +
'?format=html' +
'&hl=' + video_data.preferences.locale +
'&thin_mode=' + video_data.preferences.thin_mode;
var onNon200 = function (xhr) { comments.innerHTML = fallback; };
if (video_data.params.comments[1] === 'youtube')
onNon200 = function (xhr) {};
helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, {
on200: function (response) {
comments.innerHTML = ' \
<div> \
<h3> \
<a href="javascript:void(0)">[ ]</a> \
{commentsText} \
</h3> \
<b> \
<a href="javascript:void(0)" data-comments="reddit"> \
{redditComments} \
</a> \
</b> \
</div> \
<div>{contentHtml}</div> \
<hr>'.supplant({
contentHtml: response.contentHtml,
redditComments: video_data.reddit_comments_text,
commentsText: video_data.comments_text.supplant({
// toLocaleString correctly splits number with local thousands separator. e.g.:
// '1,234,567.89' for user with English locale
// '1 234 567,89' for user with Russian locale
// '1.234.567,89' for user with Portuguese locale
commentCount: response.commentCount.toLocaleString()
})
});
comments.children[0].children[0].children[0].onclick = toggle_comments;
comments.children[0].children[1].children[0].onclick = swap_comments;
},
onNon200: onNon200, // declared above
onError: function (xhr) {
comments.innerHTML = spinnerHTML;
},
onTimeout: function (xhr) {
comments.innerHTML = spinnerHTML;
}
});
}
function get_youtube_replies(target, load_more, load_replies) {
var continuation = target.getAttribute('data-continuation');
var body = target.parentNode.parentNode;
var fallback = body.innerHTML;
body.innerHTML = spinnerHTML;
var url = '/api/v1/comments/' + video_data.id +
'?format=html' +
'&hl=' + video_data.preferences.locale +
'&thin_mode=' + video_data.preferences.thin_mode +
'&continuation=' + continuation;
if (load_replies) url += '&action=action_get_comment_replies';
helpers.xhr('GET', url, {}, {
on200: function (response) {
if (load_more) {
body = body.parentNode.parentNode;
body.removeChild(body.lastElementChild);
body.insertAdjacentHTML('beforeend', response.contentHtml);
} else {
body.removeChild(body.lastElementChild);
var p = document.createElement('p');
var a = document.createElement('a');
p.appendChild(a);
a.href = 'javascript:void(0)';
a.onclick = hide_youtube_replies;
a.setAttribute('data-sub-text', video_data.hide_replies_text);
a.setAttribute('data-inner-text', video_data.show_replies_text);
a.textContent = video_data.hide_replies_text;
var div = document.createElement('div');
div.innerHTML = response.contentHtml;
body.appendChild(p);
body.appendChild(div);
}
},
onNon200: function (xhr) {
body.innerHTML = fallback;
},
onTimeout: function (xhr) {
console.warn('Pulling comments failed');
body.innerHTML = fallback;
}
});
}
if (video_data.play_next) { if (video_data.play_next) {
player.on('ended', function () { player.on('ended', function () {
var url = new URL('https://example.com/watch?v=' + video_data.next_video); var url = new URL('https://example.com/watch?v=' + video_data.next_video);

View File

@ -15,5 +15,7 @@
], ],
"theme_color": "#575757", "theme_color": "#575757",
"background_color": "#575757", "background_color": "#575757",
"display": "standalone" "display": "standalone",
"description": "An alternative front-end to YouTube",
"start_url": "/"
} }

View File

@ -161,6 +161,19 @@ https_only: false
#force_resolve: #force_resolve:
##
## Use Innertube's transcripts API instead of timedtext for closed captions
##
## Useful for larger instances as InnerTube is **not ratelimited**. See https://github.com/iv-org/invidious/issues/2567
##
## Subtitle experience may differ slightly on Invidious.
##
## Accepted values: true, false
## Default: false
##
# use_innertube_for_captions: false
# ----------------------------- # -----------------------------
# Logging # Logging
# ----------------------------- # -----------------------------

View File

@ -40,7 +40,7 @@ services:
- invidious-db - invidious-db
invidious-db: invidious-db:
image: docker.io/library/postgres:13 image: docker.io/library/postgres:14
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- postgresdata:/var/lib/postgresql/data - postgresdata:/var/lib/postgresql/data

View File

@ -1,4 +1,5 @@
FROM crystallang/crystal:1.4.1-alpine AS builder FROM crystallang/crystal:1.8.2-alpine AS builder
RUN apk add --no-cache sqlite-static yaml-static RUN apk add --no-cache sqlite-static yaml-static
ARG release ARG release
@ -20,7 +21,6 @@ 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 if [[ "${release}" == 1 ]] ; then \
crystal build ./src/invidious.cr \ crystal build ./src/invidious.cr \
--release \ --release \
@ -32,8 +32,7 @@ RUN if [[ "${release}" == 1 ]] ; then \
--link-flags "-lxml2 -llzma"; \ --link-flags "-lxml2 -llzma"; \
fi fi
FROM alpine:3.18
FROM alpine:3.16
RUN apk add --no-cache librsvg ttf-opensans tini RUN apk add --no-cache librsvg ttf-opensans tini
WORKDIR /invidious WORKDIR /invidious
RUN addgroup -g 1000 -S invidious && \ RUN addgroup -g 1000 -S invidious && \

View File

@ -1,5 +1,5 @@
FROM alpine:3.16 AS builder FROM alpine:3.18 AS builder
RUN apk add --no-cache 'crystal=1.4.1-r0' shards sqlite-static yaml-static yaml-dev libxml2-dev zlib-static openssl-libs-static openssl-dev musl-dev RUN apk add --no-cache 'crystal=1.8.2-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
@ -32,7 +32,7 @@ RUN if [[ "${release}" == 1 ]] ; then \
--link-flags "-lxml2 -llzma"; \ --link-flags "-lxml2 -llzma"; \
fi fi
FROM alpine:3.16 FROM alpine:3.18
RUN apk add --no-cache librsvg ttf-opensans tini RUN apk add --no-cache librsvg ttf-opensans tini
WORKDIR /invidious WORKDIR /invidious
RUN addgroup -g 1000 -S invidious && \ RUN addgroup -g 1000 -S invidious && \

View File

@ -1,6 +1,6 @@
dependencies: dependencies:
- name: postgresql - name: postgresql
repository: https://charts.bitnami.com/bitnami/ repository: https://charts.bitnami.com/bitnami/
version: 12.1.9 version: 12.11.1
digest: sha256:71ff342a6c0a98bece3d7fe199983afb2113f8db65a3e3819de875af2c45add7 digest: sha256:3c10008175c4f5c1cec38782f5a7316154b89074c77ebbd9bcc4be4f5ff21122
generated: "2023-01-20T20:42:32.757707004Z" generated: "2023-09-14T22:40:43.171275362Z"

View File

@ -17,6 +17,6 @@ maintainers:
email: mail@leonklingele.de email: mail@leonklingele.de
dependencies: dependencies:
- name: postgresql - name: postgresql
version: ~12.1.6 version: ~12.11.0
repository: "https://charts.bitnami.com/bitnami/" repository: "https://charts.bitnami.com/bitnami/"
engine: gotpl engine: gotpl

View File

@ -548,5 +548,11 @@
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"channel_tab_releases_label": "الإصدارات", "channel_tab_releases_label": "الإصدارات",
"playlist_button_add_items": "إضافة مقاطع فيديو", "playlist_button_add_items": "إضافة مقاطع فيديو",
"channel_tab_podcasts_label": "البودكاست" "channel_tab_podcasts_label": "البودكاست",
"generic_channels_count_0": "{{count}} قناة",
"generic_channels_count_1": "{{count}} قناة",
"generic_channels_count_2": "{{count}} قناتان",
"generic_channels_count_3": "{{count}} قنوات",
"generic_channels_count_4": "{{count}} قنوات",
"generic_channels_count_5": "{{count}} قناة"
} }

490
locales/bg.json Normal file
View File

@ -0,0 +1,490 @@
{
"Korean (auto-generated)": "Корейски (автоматично генерирано)",
"search_filters_features_option_three_sixty": "360°",
"published - reverse": "публикувани - в обратен ред",
"preferences_quality_dash_option_worst": "Най-ниско качество",
"Password is a required field": "Парола е задължитело поле",
"channel_tab_podcasts_label": "Подкасти",
"Token is expired, please try again": "Токенът е изтекъл, моля опитайте отново",
"Turkish": "Турски",
"preferences_save_player_pos_label": "Запази позицията на плейъра: ",
"View Reddit comments": "Виж Reddit коментари",
"Export data as JSON": "Експортиране на Invidious информацията като JSON",
"About": "За сайта",
"Save preferences": "Запази промените",
"Load more": "Зареди още",
"Import/export": "Импортиране/експортиране",
"Albanian": "Албански",
"New password": "Нова парола",
"Southern Sotho": "Южен Сото",
"channel_tab_videos_label": "Видеа",
"Spanish (Mexico)": "Испански (Мексико)",
"preferences_player_style_label": "Стил на плейъра: ",
"preferences_region_label": "Държавата на съдържанието: ",
"Premieres in `x`": "Премиера в `x`",
"Watch history": "История на гледане",
"generic_subscriptions_count": "{{count}} абонамент",
"generic_subscriptions_count_plural": "{{count}} абонамента",
"preferences_continue_label": "Пускай следващото видео автоматично: ",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Здравей! Изглежда си изключил JavaScript. Натисни тук за да видиш коментарите, но обърни внимание, че може да отнеме повече време да заредят.",
"Polish": "Полски",
"Icelandic": "Исландски",
"preferences_local_label": "Пускане на видеа през прокси: ",
"Hebrew": "Иврит",
"Fallback captions: ": "Резервни надписи: ",
"search_filters_title": "Филтри",
"search_filters_apply_button": "Приложете избрани филтри",
"Download is disabled": "Изтеглянето е деактивирано",
"User ID is a required field": "Потребителско име е задължително поле",
"comments_points_count": "{{count}} точка",
"comments_points_count_plural": "{{count}} точки",
"next_steps_error_message_go_to_youtube": "Отидеш в YouTube",
"preferences_quality_dash_option_2160p": "2160p",
"search_filters_type_option_video": "Видео",
"Spanish (Latin America)": "Испански (Латинска Америка)",
"Download as: ": "Изтегли като: ",
"Default": "По подразбиране",
"search_filters_sort_option_views": "Гледания",
"search_filters_features_option_four_k": "4K",
"Igbo": "Игбо",
"Subscriptions": "Абонаменти",
"German (auto-generated)": "Немски (автоматично генерирано)",
"`x` is live": "`x` е на живо",
"Azerbaijani": "Азербайджански",
"Premieres `x`": "Премиера `x`",
"Japanese (auto-generated)": "Японски (автоматично генерирано)",
"preferences_quality_option_medium": "Средно",
"footer_donate_page": "Даряване",
"Show replies": "Покажи отговорите",
"Esperanto": "Есперанто",
"search_message_change_filters_or_query": "Опитай да разшириш търсенето си и/или да смениш филтрите.",
"CAPTCHA enabled: ": "Активиране на CAPTCHA: ",
"View playlist on YouTube": "Виж плейлиста в YouTube",
"crash_page_before_reporting": "Преди докладването на бъг, бъди сигурен, че си:",
"Top enabled: ": "Активиране на страница с топ видеа: ",
"preferences_quality_dash_option_best": "Най-високо",
"search_filters_duration_label": "Продължителност",
"Slovak": "Словашки",
"Channel Sponsor": "Канален спонсор",
"generic_videos_count": "{{count}} видео",
"generic_videos_count_plural": "{{count}} видеа",
"videoinfo_started_streaming_x_ago": "Започна да излъчва преди `x`",
"videoinfo_youTube_embed_link": "Вграждане",
"channel_tab_streams_label": "Стриймове",
"oldest": "най-стари",
"playlist_button_add_items": "Добавяне на видеа",
"Import NewPipe data (.zip)": "Импортиране на NewPipe информация (.zip)",
"Clear watch history": "Изчистване на историята на гледане",
"generic_count_minutes": "{{count}} минута",
"generic_count_minutes_plural": "{{count}} минути",
"published": "публикувани",
"Show annotations": "Покажи анотации",
"Login enabled: ": "Активиране на впизване: ",
"Somali": "Сомалийски",
"YouTube comment permalink": "Постоянна връзка на коментарите на YouTube",
"Kurdish": "Кюрдски",
"search_filters_date_option_hour": "Последния час",
"Lao": "Лаоски",
"Maltese": "Малтийски",
"Register": "Регистрация",
"View channel on YouTube": "Виж канала в YouTube",
"Playlist privacy": "Поверителен плейлист",
"preferences_unseen_only_label": "Показвай само негледаните: ",
"Gujarati": "Гуджарати",
"Please log in": "Моля влезте",
"search_filters_sort_option_rating": "Рейтинг",
"Manage subscriptions": "Управление на абонаментите",
"preferences_quality_dash_option_720p": "720p",
"preferences_watch_history_label": "Активирай историята на гледане: ",
"user_saved_playlists": "`x` запази плейлисти",
"preferences_extend_desc_label": "Автоматично разшири описанието на видеото ",
"preferences_max_results_label": "Брой видеа показани на началната страница: ",
"Spanish (Spain)": "Испански (Испания)",
"invidious": "Invidious",
"crash_page_refresh": "пробвал да <a href=\"`x`\">опресниш страницата</a>",
"Image CAPTCHA": "CAPTCHA с Изображение",
"search_filters_features_option_hd": "HD",
"Chinese (Hong Kong)": "Китайски (Хонг Конг)",
"Import Invidious data": "Импортиране на Invidious JSON информацията",
"Blacklisted regions: ": "Неразрешени региони: ",
"Only show latest video from channel: ": "Показвай само най-новите видеа в канала: ",
"Hmong": "Хмонг",
"French": "Френски",
"search_filters_type_option_channel": "Канал",
"Artist: ": "Артист: ",
"generic_count_months": "{{count}} месец",
"generic_count_months_plural": "{{count}} месеца",
"preferences_annotations_subscribed_label": "Показвай анотаций по подразбиране за абонирани канали? ",
"search_message_use_another_instance": " Можеш също да <a href=\"`x`\">търсиш на друга инстанция</a>.",
"Danish": "Датски",
"generic_subscribers_count": "{{count}} абонат",
"generic_subscribers_count_plural": "{{count}} абоната",
"Galician": "Галисий",
"newest": "най-нови",
"Empty playlist": "Плейлиста е празен",
"download_subtitles": "Субритри - `x` (.vtt)",
"preferences_category_misc": "Различни предпочитания",
"Uzbek": "Узбекски",
"View JavaScript license information.": "Виж Javascript лиценза.",
"Filipino": "Филипински",
"Malagasy": "Мадагаскарски",
"generic_button_save": "Запиши",
"Dark mode: ": "Тъмен режим: ",
"Public": "Публичен",
"Basque": "Баскски",
"channel:`x`": "Канал:`x`",
"Armenian": "Арменски",
"This channel does not exist.": "Този канал не съществува.",
"Luxembourgish": "Люксембургски",
"preferences_related_videos_label": "Покажи подобни видеа: ",
"English": "Английски",
"Delete account": "Изтриване на акаунт",
"Gaming": "Игри",
"Video mode": "Видео режим",
"preferences_dark_mode_label": "Тема: ",
"crash_page_search_issue": "потърсил за <a href=\"`x`\">съществуващи проблеми в GitHub</a>",
"preferences_category_subscription": "Предпочитания за абонаменти",
"last": "най-скорощни",
"Chinese (Simplified)": "Китайски (Опростен)",
"Could not create mix.": "Създаването на микс е неуспешно.",
"generic_button_cancel": "Отказ",
"search_filters_type_option_movie": "Филм",
"search_filters_date_option_year": "Тази година",
"Swedish": "Шведски",
"Previous page": "Предишна страница",
"none": "нищо",
"popular": "най-популярни",
"Unsubscribe": "Отписване",
"Slovenian": "Словенски",
"Nepali": "Непалски",
"Time (h:mm:ss):": "Време (h:mm:ss):",
"English (auto-generated)": "Английски (автоматично генерирано)",
"search_filters_sort_label": "Сортирай по",
"View more comments on Reddit": "Виж повече коментари в Reddit",
"Sinhala": "Синхалски",
"preferences_feed_menu_label": "Меню с препоръки: ",
"preferences_autoplay_label": "Автоматично пускане: ",
"Pashto": "Пущунски",
"English (United States)": "Английски (САЩ)",
"Sign In": "Вход",
"subscriptions_unseen_notifs_count": "{{count}} невидяно известие",
"subscriptions_unseen_notifs_count_plural": "{{count}} невидяни известия",
"Log in": "Вход",
"Engagement: ": "Участие: ",
"Album: ": "Албум: ",
"preferences_speed_label": "Скорост по подразбиране: ",
"Import FreeTube subscriptions (.db)": "Импортиране на FreeTube абонаменти (.db)",
"preferences_quality_option_dash": "DASH (адаптивно качество)",
"preferences_show_nick_label": "Показвай потребителското име отгоре: ",
"Private": "Частен",
"Samoan": "Самоански",
"preferences_notifications_only_label": "Показвай само известията (ако има такива): ",
"Create playlist": "Създаване на плейлист",
"next_steps_error_message_refresh": "Опресниш",
"Top": "Топ",
"preferences_quality_dash_option_1080p": "1080p",
"Malayalam": "Малаялам",
"Token": "Токен",
"preferences_comments_label": "Коментари по подразбиране: ",
"Movies": "Филми",
"light": "светла",
"Unlisted": "Скрит",
"preferences_category_admin": "Администраторни предпочитания",
"Erroneous token": "Невалиден токен",
"No": "Не",
"CAPTCHA is a required field": "CAPTCHA е задължително поле",
"Video unavailable": "Неналично видео",
"footer_source_code": "Изходен код",
"New passwords must match": "Новите пароли трябва да съвпадат",
"Playlist does not exist.": "Плейлиста не съществува.",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Експортиране на абонаментите като OPML (за NewPipe и FreeTube)",
"search_filters_duration_option_short": "Кратко (< 4 минути)",
"search_filters_duration_option_long": "Дълго (> 20 минути)",
"tokens_count": "{{count}} токен",
"tokens_count_plural": "{{count}} токена",
"Yes": "Да",
"Dutch": "Холандски",
"Arabic": "Арабски",
"An alternative front-end to YouTube": "Алтернативен преден план на YouTube",
"View `x` comments": {
"([^.,0-9]|^)1([^.,0-9]|$)": "Виж `x` коментар",
"": "Виж `x` коментари"
},
"Chinese (China)": "Китайски (Китай)",
"Italian (auto-generated)": "Италиански (автоматично генерирано)",
"alphabetically - reverse": "обратно на азбучния ред",
"channel_tab_shorts_label": "Shorts",
"`x` marked it with a ❤": "`x` го маркира със ❤",
"Current version: ": "Текуща версия: ",
"channel_tab_community_label": "Общност",
"preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_option_360p": "360p",
"`x` uploaded a video": "`x` качи видео",
"Welsh": "Уелски",
"search_message_no_results": "Няма намерени резултати.",
"channel_tab_releases_label": "Версии",
"Bangla": "Бенгалски",
"preferences_quality_dash_option_144p": "144p",
"Indonesian": "Индонезийски",
"`x` ago": "преди `x`",
"Invidious Private Feed for `x`": "Invidious персонални видеа за `x`",
"Finnish": "Финландски",
"Amharic": "Амхарски",
"Malay": "Малайски",
"Interlingue": "Интерлинг",
"search_filters_date_option_month": "Този месец",
"Georgian": "Грузински",
"Xhosa": "Кхоса",
"Marathi": "Маратхи",
"Yoruba": "Йоруба",
"Song: ": "Музика: ",
"Scottish Gaelic": "Шотландски гелски",
"search_filters_features_label": "Функции",
"preferences_quality_label": "Предпочитано качество на видеото: ",
"generic_channels_count": "{{count}} канал",
"generic_channels_count_plural": "{{count}} канала",
"Croatian": "Хърватски",
"Thai": "Тайски",
"Chinese (Taiwan)": "Китайски (Тайван)",
"youtube": "YouTube",
"Source available here.": "Източник наличен тук.",
"LIVE": "На живо",
"Ukrainian": "Украински",
"Russian": "Руски",
"Tajik": "Таджикски",
"Token manager": "Управляване на токени",
"preferences_quality_dash_label": "Предпочитано DASH качество на видеото: ",
"adminprefs_modified_source_code_url_label": "URL до хранилището на променения изходен код",
"Japanese": "Японски",
"Title": "Заглавие",
"Authorize token for `x`?": "Разреши токена за `x`?",
"reddit": "Reddit",
"permalink": "постоянна връзка",
"Trending": "На върха",
"Turkish (auto-generated)": "Турски (автоматично генерирано)",
"Bulgarian": "Български",
"Indonesian (auto-generated)": "Индонезийски (автоматично генерирано)",
"Enable web notifications": "Активирай уеб известия",
"Western Frisian": "Западен фризски",
"search_filters_date_option_week": "Тази седмица",
"Yiddish": "Идиш",
"preferences_category_player": "Предпочитания за плейъра",
"Shared `x` ago": "Споделено преди `x`",
"Swahili": "Суахили",
"Portuguese (auto-generated)": "Португалски (автоматично генерирано)",
"generic_count_years": "{{count}} година",
"generic_count_years_plural": "{{count}} години",
"Wilson score: ": "Wilson оценка: ",
"Genre: ": "Жанр: ",
"videoinfo_invidious_embed_link": "Вграждане на линк",
"Popular enabled: ": "Активиране на популярната страница: ",
"Wrong username or password": "Грешно потребителско име или парола",
"Vietnamese": "Виетнамски",
"alphabetically": "по азбучен ред",
"Afrikaans": "Африкаанс",
"Zulu": "Зулуски",
"(edited)": "(редактирано)",
"Whitelisted regions: ": "Разрешени региони: ",
"Spanish (auto-generated)": "Испански (автоматично генерирано)",
"Could not fetch comments": "Получаването на коментарите е неуспешно",
"Sindhi": "Синдхи",
"News": "Новини",
"preferences_video_loop_label": "Винаги повтаряй: ",
"%A %B %-d, %Y": "%-d %B %Y, %A",
"preferences_quality_option_small": "Ниско",
"English (United Kingdom)": "Английски (Великобритания)",
"Rating: ": "Рейтинг: ",
"channel_tab_playlists_label": "Плейлисти",
"generic_button_edit": "Редактирай",
"Report statistics: ": "Активиране на статистики за репортиране: ",
"Cebuano": "Себуано",
"Chinese (Traditional)": "Китайски (Традиционен)",
"generic_playlists_count": "{{count}} плейлист",
"generic_playlists_count_plural": "{{count}} плейлиста",
"Import NewPipe subscriptions (.json)": "Импортиране на NewPipe абонаменти (.json)",
"Preferences": "Предпочитания",
"Subscribe": "Абониране",
"Import and Export Data": "Импортиране и експортиране на информация",
"preferences_quality_option_hd720": "HD720",
"search_filters_type_option_playlist": "Плейлист",
"Serbian": "Сръбски",
"Kazakh": "Казахски",
"Telugu": "Телугу",
"search_filters_features_option_purchased": "Купено",
"revoke": "отмяна",
"search_filters_sort_option_date": "Дата на качване",
"preferences_category_data": "Предпочитания за информацията",
"search_filters_date_option_none": "Всякаква дата",
"Log out": "Излизане",
"Search": "Търсене",
"preferences_quality_dash_option_auto": "Автоматично",
"dark": "тъмна",
"Cantonese (Hong Kong)": "Кантонски (Хонг Конг)",
"crash_page_report_issue": "Ако никои от горепосочените не помогнаха, моля <a href=\"`x`\">отворете нов проблем в GitHub</a> (предпочитано на Английски) и добавете следния текст в съобщението (НЕ превеждайте този текст):",
"Czech": "Чешки",
"crash_page_switch_instance": "пробвал да <a href=\"`x`\">ползваш друга инстанция</a>",
"generic_count_weeks": "{{count}} седмица",
"generic_count_weeks_plural": "{{count}} седмици",
"search_filters_features_option_subtitles": "Субтитри",
"videoinfo_watch_on_youTube": "Виж в YouTube",
"Portuguese": "Португалски",
"Music in this video": "Музика в това видео",
"Hide replies": "Скрий отговорите",
"Password cannot be longer than 55 characters": "Паролата не може да бъде по-дълга от 55 символа",
"footer_modfied_source_code": "Променен изходен код",
"Bosnian": "Босненски",
"Deleted or invalid channel": "Изтрит или невалиден канал",
"Popular": "Популярно",
"search_filters_type_label": "Тип",
"preferences_locale_label": "Език: ",
"Playlists": "Плейлисти",
"generic_button_rss": "RSS",
"Export": "Експортиране",
"preferences_quality_dash_option_4320p": "4320p",
"Erroneous challenge": "Невалиден тест",
"History": "История",
"generic_count_hours": "{{count}} час",
"generic_count_hours_plural": "{{count}} часа",
"Registration enabled: ": "Активиране на регистрация: ",
"Music": "Музика",
"Incorrect password": "Грешна парола",
"Persian": "Перскийски",
"Import": "Импортиране",
"Import/export data": "Импортиране/Експортиране на информация",
"Shared `x`": "Споделено `x`",
"Javanese": "Явански",
"French (auto-generated)": "Френски (автоматично генерирано)",
"Norwegian Bokmål": "Норвежки",
"Catalan": "Каталунски",
"Hindi": "Хинди",
"Tamil": "Тамилски",
"search_filters_features_option_live": "На живо",
"crash_page_read_the_faq": "прочел <a href=\"`x`\">Често задавани въпроси (FAQ)</a>",
"preferences_default_home_label": "Начална страница по подразбиране: ",
"Download": "Изтегляне",
"Show less": "Покажи по-малко",
"Password": "Парола",
"User ID": "Потребителско име",
"Subscription manager": "Управляване на абонаменти",
"search": "търсене",
"No such user": "Няма такъв потребител",
"View privacy policy.": "Виж политиката за поверителност.",
"Only show latest unwatched video from channel: ": "Показвай само най-новите негледани видеа в канала: ",
"user_created_playlists": "`x` създаде плейлисти",
"Editing playlist `x`": "Редактиране на плейлист `x`",
"preferences_thin_mode_label": "Тънък режим: ",
"E-mail": "Имейл",
"Haitian Creole": "Хаитянски креол",
"Irish": "Ирландски",
"channel_tab_channels_label": "Канали",
"Delete account?": "Изтрий акаунта?",
"Redirect homepage to feed: ": "Препращане на началната страница до препоръки ",
"Urdu": "Урду",
"preferences_vr_mode_label": "Интерактивни 360 градусови видеа (изисква WebGL): ",
"Password cannot be empty": "Паролата не може да бъде празна",
"Mongolian": "Монголски",
"Authorize token?": "Разреши токена?",
"search_filters_type_option_all": "Всякакъв тип",
"Romanian": "Румънски",
"Belarusian": "Беларуски",
"channel name - reverse": "име на канал - в обратен ред",
"Erroneous CAPTCHA": "Невалидна CAPTCHA",
"Watch on YouTube": "Гледай в YouTube",
"search_filters_features_option_location": "Местоположение",
"Could not pull trending pages.": "Получаването на трендинг страниците е неуспешно.",
"German": "Немски",
"search_filters_features_option_c_commons": "Creative Commons",
"Family friendly? ": "За всяка възраст? ",
"Hidden field \"token\" is a required field": "Скритото поле \"токен\" е задължително поле",
"Russian (auto-generated)": "Руски (автоматично генерирано)",
"preferences_quality_dash_option_480p": "480p",
"Corsican": "Корсикански",
"Macedonian": "Македонски",
"comments_view_x_replies": "Виж {{count}} отговор",
"comments_view_x_replies_plural": "Виж {{count}} отговора",
"footer_original_source_code": "Оригинален изходен код",
"Import YouTube subscriptions": "Импортиране на YouTube/OPML абонаменти",
"Lithuanian": "Литовски",
"Nyanja": "Нянджа",
"Updated `x` ago": "Актуализирано преди `x`",
"JavaScript license information": "Информация за Javascript лиценза",
"Spanish": "Испански",
"Latin": "Латински",
"Shona": "Шона",
"Portuguese (Brazil)": "Португалски (Бразилия)",
"Show more": "Покажи още",
"Clear watch history?": "Изчисти историята на търсене?",
"Manage tokens": "Управление на токени",
"Hausa": "Хауса",
"search_filters_features_option_vr180": "VR180",
"preferences_category_visual": "Визуални предпочитания",
"Italian": "Италиански",
"preferences_volume_label": "Сила на звука на плейъра: ",
"error_video_not_in_playlist": "Заявеното видео не съществува в този плейлист. <a href=\"`x`\">Натиснете тук за началната страница на плейлиста.</a>",
"preferences_listen_label": "Само звук по подразбиране: ",
"Dutch (auto-generated)": "Холандски (автоматично генерирано)",
"preferences_captions_label": "Надписи по подразбиране: ",
"generic_count_days": "{{count}} ден",
"generic_count_days_plural": "{{count}} дни",
"Hawaiian": "Хавайски",
"Could not get channel info.": "Получаването на информация за канала е неуспешно.",
"View as playlist": "Виж като плейлист",
"Vietnamese (auto-generated)": "Виетнамски (автоматично генерирано)",
"search_filters_duration_option_none": "Всякаква продължителност",
"preferences_quality_dash_option_240p": "240p",
"Latvian": "Латвийски",
"search_filters_features_option_hdr": "HDR",
"preferences_sort_label": "Сортирай видеата по: ",
"Estonian": "Естонски",
"Hidden field \"challenge\" is a required field": "Скритото поле \"тест\" е задължително поле",
"footer_documentation": "Документация",
"Kyrgyz": "Киргизски",
"preferences_continue_autoplay_label": "Пускай следващотото видео автоматично: ",
"Chinese": "Китайски",
"search_filters_sort_option_relevance": "Уместност",
"source": "източник",
"Fallback comments: ": "Резервни коментари: ",
"preferences_automatic_instance_redirect_label": "Автоматично препращане на инстанция (чрез redirect.invidious.io): ",
"Maori": "Маори",
"generic_button_delete": "Изтрий",
"Import YouTube playlist (.csv)": "Импортиране на YouTube плейлист (.csv)",
"Switch Invidious Instance": "Смени Invidious инстанция",
"channel name": "име на канал",
"Audio mode": "Аудио режим",
"search_filters_type_option_show": "Сериал",
"search_filters_date_option_today": "Днес",
"search_filters_features_option_three_d": "3D",
"next_steps_error_message": "След което можеш да пробваш да: ",
"Hide annotations": "Скрий анотации",
"Standard YouTube license": "Стандартен YouTube лиценз",
"Text CAPTCHA": "Текст CAPTCHA",
"Log in/register": "Вход/регистрация",
"Punjabi": "Пенджаби",
"Change password": "Смяна на паролата",
"License: ": "Лиценз: ",
"search_filters_duration_option_medium": "Средно (4 - 20 минути)",
"Delete playlist": "Изтриване на плейлист",
"Delete playlist `x`?": "Изтрий плейлиста `x`?",
"Korean": "Корейски",
"Export subscriptions as OPML": "Експортиране на абонаментите като OPML",
"unsubscribe": "отписване",
"View YouTube comments": "Виж YouTube коментарите",
"Kannada": "Каннада",
"Not a playlist.": "Невалиден плейлист.",
"Wrong answer": "Грешен отговор",
"Released under the AGPLv3 on Github.": "Публикувано под AGPLv3 в GitHub.",
"Burmese": "Бирмански",
"Sundanese": "Сундански",
"Hungarian": "Унгарски",
"generic_count_seconds": "{{count}} секунда",
"generic_count_seconds_plural": "{{count}} секунди",
"search_filters_date_label": "Дата на качване",
"Greek": "Гръцки",
"crash_page_you_found_a_bug": "Изглежда намери бъг в Invidious!",
"View all playlists": "Виж всички плейлисти",
"Khmer": "Кхмерски",
"preferences_annotations_label": "Покажи анотаций по подразбиране: ",
"generic_views_count": "{{count}} гледане",
"generic_views_count_plural": "{{count}} гледания",
"Next page": "Следваща страница"
}

View File

@ -476,5 +476,15 @@
"Redirect homepage to feed: ": "Redirigeix la pàgina d'inici al feed: ", "Redirect homepage to feed: ": "Redirigeix la pàgina d'inici al feed: ",
"Standard YouTube license": "Llicència estàndard de YouTube", "Standard YouTube license": "Llicència estàndard de YouTube",
"Download is disabled": "Les baixades s'han inhabilitat", "Download is disabled": "Les baixades s'han inhabilitat",
"Import YouTube playlist (.csv)": "Importar llista de reproducció de YouTube (.csv)" "Import YouTube playlist (.csv)": "Importar llista de reproducció de YouTube (.csv)",
"channel_tab_podcasts_label": "Podcasts",
"playlist_button_add_items": "Afegeix vídeos",
"generic_button_save": "Desa",
"generic_button_cancel": "Cancel·la",
"channel_tab_releases_label": "Publicacions",
"generic_channels_count": "{{count}} canal",
"generic_channels_count_plural": "{{count}} canals",
"generic_button_edit": "Edita",
"generic_button_rss": "RSS",
"generic_button_delete": "Suprimeix"
} }

View File

@ -500,5 +500,8 @@
"channel_tab_releases_label": "Vydání", "channel_tab_releases_label": "Vydání",
"generic_button_edit": "Upravit", "generic_button_edit": "Upravit",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"playlist_button_add_items": "Přidat videa" "playlist_button_add_items": "Přidat videa",
"generic_channels_count_0": "{{count}} kanál",
"generic_channels_count_1": "{{count}} kanály",
"generic_channels_count_2": "{{count}} kanálů"
} }

View File

@ -97,7 +97,7 @@
"Change password": "Passwort ändern", "Change password": "Passwort ändern",
"Manage subscriptions": "Abonnements verwalten", "Manage subscriptions": "Abonnements verwalten",
"Manage tokens": "Tokens verwalten", "Manage tokens": "Tokens verwalten",
"Watch history": "Verlauf", "Watch history": "Wiedergabeverlauf",
"Delete account": "Account löschen", "Delete account": "Account löschen",
"preferences_category_admin": "Administrator-Einstellungen", "preferences_category_admin": "Administrator-Einstellungen",
"preferences_default_home_label": "Standard-Startseite: ", "preferences_default_home_label": "Standard-Startseite: ",
@ -476,11 +476,15 @@
"Standard YouTube license": "Standard YouTube-Lizenz", "Standard YouTube license": "Standard YouTube-Lizenz",
"Song: ": "Musik: ", "Song: ": "Musik: ",
"Download is disabled": "Herunterladen ist deaktiviert", "Download is disabled": "Herunterladen ist deaktiviert",
"Import YouTube playlist (.csv)": "YouTube Playlist Importieren (.csv)", "Import YouTube playlist (.csv)": "YouTube Wiedergabeliste importieren (.csv)",
"generic_button_delete": "Löschen", "generic_button_delete": "Löschen",
"generic_button_edit": "Bearbeiten", "generic_button_edit": "Bearbeiten",
"generic_button_save": "Speichern", "generic_button_save": "Speichern",
"generic_button_cancel": "Abbrechen", "generic_button_cancel": "Abbrechen",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"playlist_button_add_items": "Videos hinzufügen" "playlist_button_add_items": "Videos hinzufügen",
"channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Veröffentlichungen",
"generic_channels_count": "{{count}} Kanal",
"generic_channels_count_plural": "{{count}} Kanäle"
} }

View File

@ -41,7 +41,7 @@
"Time (h:mm:ss):": "Ώρα (ω:λλ:δδ):", "Time (h:mm:ss):": "Ώρα (ω:λλ:δδ):",
"Text CAPTCHA": "Κείμενο CAPTCHA", "Text CAPTCHA": "Κείμενο CAPTCHA",
"Image CAPTCHA": "Εικόνα CAPTCHA", "Image CAPTCHA": "Εικόνα CAPTCHA",
"Sign In": "Σύνδεση", "Sign In": "Εγγραφή",
"Register": "Εγγραφή", "Register": "Εγγραφή",
"E-mail": "Ηλεκτρονικό ταχυδρομείο", "E-mail": "Ηλεκτρονικό ταχυδρομείο",
"Preferences": "Προτιμήσεις", "Preferences": "Προτιμήσεις",
@ -145,7 +145,7 @@
"View YouTube comments": "Προβολή σχολίων από το YouTube", "View YouTube comments": "Προβολή σχολίων από το YouTube",
"View more comments on Reddit": "Προβολή περισσότερων σχολίων στο Reddit", "View more comments on Reddit": "Προβολή περισσότερων σχολίων στο Reddit",
"View `x` comments": { "View `x` comments": {
"([^.,0-9]|^)1([^.,0-9]|$)": "Προβολή `x` σχολίων", "([^.,0-9]|^)1([^.,0-9]|$)": "Προβολή `x` σχολίου",
"": "Προβολή `x` σχολίων" "": "Προβολή `x` σχολίων"
}, },
"View Reddit comments": "Προβολή σχολίων από το Reddit", "View Reddit comments": "Προβολή σχολίων από το Reddit",
@ -349,7 +349,7 @@
"crash_page_you_found_a_bug": "Φαίνεται ότι βρήκατε ένα σφάλμα στο Invidious!", "crash_page_you_found_a_bug": "Φαίνεται ότι βρήκατε ένα σφάλμα στο Invidious!",
"crash_page_before_reporting": "Πριν αναφέρετε ένα σφάλμα, βεβαιωθείτε ότι έχετε:", "crash_page_before_reporting": "Πριν αναφέρετε ένα σφάλμα, βεβαιωθείτε ότι έχετε:",
"crash_page_refresh": "προσπαθήσει να <a href=\"`x`\">ανανεώσετε τη σελίδα</a>", "crash_page_refresh": "προσπαθήσει να <a href=\"`x`\">ανανεώσετε τη σελίδα</a>",
"crash_page_read_the_faq": "διαβάσει τις <a href=\"`x`\">Συχνές Ερωτήσεις (ΣΕ)</a>", "crash_page_read_the_faq": "διαβάστε τις <a href=\"`x`\">Συχνές Ερωτήσεις (ΣΕ)</a>",
"crash_page_search_issue": "αναζητήσει για <a href=\"`x`\">υπάρχοντα θέματα στο GitHub</a>", "crash_page_search_issue": "αναζητήσει για <a href=\"`x`\">υπάρχοντα θέματα στο GitHub</a>",
"generic_views_count": "{{count}} προβολή", "generic_views_count": "{{count}} προβολή",
"generic_views_count_plural": "{{count}} προβολές", "generic_views_count_plural": "{{count}} προβολές",
@ -442,5 +442,49 @@
"search_filters_type_option_show": "Μπάρα προόδου διαβάσματος", "search_filters_type_option_show": "Μπάρα προόδου διαβάσματος",
"preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: ", "preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: ",
"search_filters_title": "Φίλτρο", "search_filters_title": "Φίλτρο",
"search_message_no_results": "Δε βρέθηκαν αποτελέσματα." "search_message_no_results": "Δε βρέθηκαν αποτελέσματα.",
"channel_tab_podcasts_label": "Podcast",
"preferences_save_player_pos_label": "Αποθήκευση σημείου αναπαραγωγής: ",
"search_filters_apply_button": "Εφαρμογή επιλεγμένων φίλτρων",
"Download is disabled": "Είναι απενεργοποιημένη η λήψη",
"comments_points_count": "{{count}} βαθμός",
"comments_points_count_plural": "{{count}} βαθμοί",
"search_filters_sort_option_views": "Προβολές",
"search_message_change_filters_or_query": "Προσπαθήστε να διευρύνετε το ερώτημα αναζήτησης ή/και να αλλάξετε τα φίλτρα.",
"Channel Sponsor": "Χορηγός Καναλιού",
"channel_tab_streams_label": "Ζωντανή μετάδοση",
"playlist_button_add_items": "Προσθήκη βίντεο",
"Artist: ": "Καλλιτέχνης: ",
"search_message_use_another_instance": " Μπορείτε επίσης <a href=\"`x`\">να αναζητήσετε σε άλλο instance</a>.",
"generic_button_save": "Αποθήκευση",
"generic_button_cancel": "Ακύρωση",
"subscriptions_unseen_notifs_count": "{{count}} μη αναγνωσμένη ειδοποίηση",
"subscriptions_unseen_notifs_count_plural": "{{count}} μη αναγνωσμένες ειδοποιήσεις",
"Album: ": "Δίσκος: ",
"tokens_count": "{{count}} σύμβολο",
"tokens_count_plural": "{{count}} σύμβολα",
"channel_tab_shorts_label": "Short",
"channel_tab_releases_label": "Κυκλοφορίες",
"Song: ": "Τραγούδι: ",
"generic_channels_count": "{{count}} κανάλι",
"generic_channels_count_plural": "{{count}} κανάλια",
"Popular enabled: ": "Ενεργοποιημένα Δημοφιλή: ",
"channel_tab_playlists_label": "Λίστες αναπαραγωγής",
"generic_button_edit": "Επεξεργασία",
"search_filters_date_option_none": "Οποιαδήποτε ημερομηνία",
"crash_page_switch_instance": "προσπάθεια <a href=\"`x`\">χρήσης άλλου instance</a>",
"Music in this video": "Μουσική σε αυτό το βίντεο",
"generic_button_rss": "RSS",
"channel_tab_channels_label": "Κανάλια",
"search_filters_type_option_all": "Οποιοσδήποτε τύπος",
"search_filters_features_option_vr180": "VR180",
"error_video_not_in_playlist": "Το αιτούμενο βίντεο δεν υπάρχει στη δεδομένη λίστα αναπαραγωγής. <a href=\"`x`\">Πατήστε εδώ για επιστροφή στη κεντρική σελίδα λιστών αναπαραγωγής.</a>",
"search_filters_duration_option_none": "Οποιαδήποτε διάρκεια",
"preferences_automatic_instance_redirect_label": "Αυτόματη ανακατεύθυνση instance (εναλλακτική σε redirect.invidious.io): ",
"generic_button_delete": "Διαγραφή",
"Import YouTube playlist (.csv)": "Εισαγωγή λίστας αναπαραγωγής YouTube (.csv)",
"Switch Invidious Instance": "Αλλαγή Instance Invidious",
"Standard YouTube license": "Τυπική άδεια YouTube",
"search_filters_duration_option_medium": "Μεσαία (4 - 20 λεπτά)",
"search_filters_date_label": "Ημερομηνία αναφόρτωσης"
} }

View File

@ -40,6 +40,7 @@
"Import Invidious data": "Import Invidious JSON data", "Import Invidious data": "Import Invidious JSON data",
"Import YouTube subscriptions": "Import YouTube/OPML subscriptions", "Import YouTube subscriptions": "Import YouTube/OPML subscriptions",
"Import YouTube playlist (.csv)": "Import YouTube playlist (.csv)", "Import YouTube playlist (.csv)": "Import YouTube playlist (.csv)",
"Import YouTube watch history (.json)": "Import YouTube watch history (.json)",
"Import FreeTube subscriptions (.db)": "Import FreeTube subscriptions (.db)", "Import FreeTube subscriptions (.db)": "Import FreeTube subscriptions (.db)",
"Import NewPipe subscriptions (.json)": "Import NewPipe subscriptions (.json)", "Import NewPipe subscriptions (.json)": "Import NewPipe subscriptions (.json)",
"Import NewPipe data (.zip)": "Import NewPipe data (.zip)", "Import NewPipe data (.zip)": "Import NewPipe data (.zip)",

View File

@ -484,5 +484,7 @@
"channel_tab_podcasts_label": "Podkastoj", "channel_tab_podcasts_label": "Podkastoj",
"generic_button_cancel": "Nuligi", "generic_button_cancel": "Nuligi",
"channel_tab_releases_label": "Eldonoj", "channel_tab_releases_label": "Eldonoj",
"generic_button_save": "Konservi" "generic_button_save": "Konservi",
"generic_channels_count": "{{count}} kanalo",
"generic_channels_count_plural": "{{count}} kanaloj"
} }

View File

@ -484,5 +484,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": "Publicaciones",
"generic_channels_count": "{{count}} canal",
"generic_channels_count_plural": "{{count}} canales"
} }

View File

@ -1,16 +1,22 @@
{ {
"generic_channels_count": "{{count}} chaîne", "generic_channels_count_0": "{{count}} chaîne",
"generic_channels_count_plural": "{{count}} chaînes", "generic_channels_count_1": "{{count}} de chaînes",
"generic_views_count": "{{count}} vue", "generic_channels_count_2": "{{count}} chaînes",
"generic_views_count_plural": "{{count}} vues", "generic_views_count_0": "{{count}} vue",
"generic_videos_count": "{{count}} vidéo", "generic_views_count_1": "{{count}} de vues",
"generic_videos_count_plural": "{{count}} vidéos", "generic_views_count_2": "{{count}} vues",
"generic_playlists_count": "{{count}} liste de lecture", "generic_videos_count_0": "{{count}} vidéo",
"generic_playlists_count_plural": "{{count}} listes de lecture", "generic_videos_count_1": "{{count}} de vidéos",
"generic_subscribers_count": "{{count}} abonné", "generic_videos_count_2": "{{count}} vidéos",
"generic_subscribers_count_plural": "{{count}} abonnés", "generic_playlists_count_0": "{{count}} liste de lecture",
"generic_subscriptions_count": "{{count}} abonnement", "generic_playlists_count_1": "{{count}} listes de lecture",
"generic_subscriptions_count_plural": "{{count}} abonnements", "generic_playlists_count_2": "{{count}} listes de lecture",
"generic_subscribers_count_0": "{{count}} abonné",
"generic_subscribers_count_1": "{{count}} d'abonnés",
"generic_subscribers_count_2": "{{count}} abonnés",
"generic_subscriptions_count_0": "{{count}} abonnement",
"generic_subscriptions_count_1": "{{count}} d'abonnements",
"generic_subscriptions_count_2": "{{count}} abonnements",
"generic_button_delete": "Supprimer", "generic_button_delete": "Supprimer",
"generic_button_edit": "Editer", "generic_button_edit": "Editer",
"generic_button_save": "Enregistrer", "generic_button_save": "Enregistrer",
@ -130,14 +136,16 @@
"Subscription manager": "Gestionnaire d'abonnement", "Subscription manager": "Gestionnaire d'abonnement",
"Token manager": "Gestionnaire de token", "Token manager": "Gestionnaire de token",
"Token": "Token", "Token": "Token",
"tokens_count": "{{count}} jeton", "tokens_count_0": "{{count}} jeton",
"tokens_count_plural": "{{count}} jetons", "tokens_count_1": "{{count}} de jetons",
"tokens_count_2": "{{count}} jetons",
"Import/export": "Importer/Exporter", "Import/export": "Importer/Exporter",
"unsubscribe": "se désabonner", "unsubscribe": "se désabonner",
"revoke": "révoquer", "revoke": "révoquer",
"Subscriptions": "Abonnements", "Subscriptions": "Abonnements",
"subscriptions_unseen_notifs_count": "{{count}} notification non vue", "subscriptions_unseen_notifs_count_0": "{{count}} notification non vue",
"subscriptions_unseen_notifs_count_plural": "{{count}} notifications non vues", "subscriptions_unseen_notifs_count_1": "{{count}} de notifications non vues",
"subscriptions_unseen_notifs_count_2": "{{count}} notifications non vues",
"search": "rechercher", "search": "rechercher",
"Log out": "Se déconnecter", "Log out": "Se déconnecter",
"Released under the AGPLv3 on Github.": "Publié sous licence AGPLv3 sur GitHub.", "Released under the AGPLv3 on Github.": "Publié sous licence AGPLv3 sur GitHub.",
@ -199,12 +207,14 @@
"This channel does not exist.": "Cette chaine n'existe pas.", "This channel does not exist.": "Cette chaine n'existe pas.",
"Could not get channel info.": "Impossible de charger les informations de cette chaîne.", "Could not get channel info.": "Impossible de charger les informations de cette chaîne.",
"Could not fetch comments": "Impossible de charger les commentaires", "Could not fetch comments": "Impossible de charger les commentaires",
"comments_view_x_replies": "Voir {{count}} réponse", "comments_view_x_replies_0": "Voir {{count}} réponse",
"comments_view_x_replies_plural": "Voir {{count}} réponses", "comments_view_x_replies_1": "Voir {{count}} de réponses",
"comments_view_x_replies_2": "Voir {{count}} réponses",
"`x` ago": "il y a `x`", "`x` ago": "il y a `x`",
"Load more": "Voir plus", "Load more": "Voir plus",
"comments_points_count": "{{count}} point", "comments_points_count_0": "{{count}} point",
"comments_points_count_plural": "{{count}} points", "comments_points_count_1": "{{count}} de points",
"comments_points_count_2": "{{count}} points",
"Could not create mix.": "Impossible de charger cette liste de lecture.", "Could not create mix.": "Impossible de charger cette liste de lecture.",
"Empty playlist": "La liste de lecture est vide", "Empty playlist": "La liste de lecture est vide",
"Not a playlist.": "La liste de lecture est invalide.", "Not a playlist.": "La liste de lecture est invalide.",
@ -322,20 +332,27 @@
"Yiddish": "Yiddish", "Yiddish": "Yiddish",
"Yoruba": "Yoruba", "Yoruba": "Yoruba",
"Zulu": "Zoulou", "Zulu": "Zoulou",
"generic_count_years": "{{count}} an", "generic_count_years_0": "{{count}} an",
"generic_count_years_plural": "{{count}} ans", "generic_count_years_1": "{{count}} ans",
"generic_count_months": "{{count}} mois", "generic_count_years_2": "{{count}} ans",
"generic_count_months_plural": "{{count}} mois", "generic_count_months_0": "{{count}} mois",
"generic_count_weeks": "{{count}} semaine", "generic_count_months_1": "{{count}} mois",
"generic_count_weeks_plural": "{{count}} semaines", "generic_count_months_2": "{{count}} mois",
"generic_count_days": "{{count}} jour", "generic_count_weeks_0": "{{count}} semaine",
"generic_count_days_plural": "{{count}} jours", "generic_count_weeks_1": "{{count}} semaines",
"generic_count_hours": "{{count}} heure", "generic_count_weeks_2": "{{count}} semaines",
"generic_count_hours_plural": "{{count}} heures", "generic_count_days_0": "{{count}} jour",
"generic_count_minutes": "{{count}} minute", "generic_count_days_1": "{{count}} jours",
"generic_count_minutes_plural": "{{count}} minutes", "generic_count_days_2": "{{count}} jours",
"generic_count_seconds": "{{count}} seconde", "generic_count_hours_0": "{{count}} heure",
"generic_count_seconds_plural": "{{count}} secondes", "generic_count_hours_1": "{{count}} heures",
"generic_count_hours_2": "{{count}} heures",
"generic_count_minutes_0": "{{count}} minute",
"generic_count_minutes_1": "{{count}} minutes",
"generic_count_minutes_2": "{{count}} minutes",
"generic_count_seconds_0": "{{count}} seconde",
"generic_count_seconds_1": "{{count}} secondes",
"generic_count_seconds_2": "{{count}} secondes",
"Fallback comments: ": "Commentaires alternatifs : ", "Fallback comments: ": "Commentaires alternatifs : ",
"Popular": "Populaire", "Popular": "Populaire",
"Search": "Rechercher", "Search": "Rechercher",

View File

@ -500,5 +500,8 @@
"generic_button_save": "Spremi", "generic_button_save": "Spremi",
"generic_button_cancel": "Odustani", "generic_button_cancel": "Odustani",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"channel_tab_releases_label": "Izdanja" "channel_tab_releases_label": "Izdanja",
"generic_channels_count_0": "{{count}} kanal",
"generic_channels_count_1": "{{count}} kanala",
"generic_channels_count_2": "{{count}} kanala"
} }

View File

@ -446,5 +446,28 @@
"crash_page_read_the_faq": "baca <a href=\"`x`\">Soal Sering Ditanya (SSD/FAQ)</a>", "crash_page_read_the_faq": "baca <a href=\"`x`\">Soal Sering Ditanya (SSD/FAQ)</a>",
"crash_page_search_issue": "mencari <a href=\"`x`\">isu yang ada di GitHub</a>", "crash_page_search_issue": "mencari <a href=\"`x`\">isu yang ada di GitHub</a>",
"crash_page_report_issue": "Jika yang di atas tidak membantu, <a href=\"`x`\">buka isu baru di GitHub</a> (sebaiknya dalam bahasa Inggris) dan sertakan teks berikut dalam pesan Anda (JANGAN terjemahkan teks tersebut):", "crash_page_report_issue": "Jika yang di atas tidak membantu, <a href=\"`x`\">buka isu baru di GitHub</a> (sebaiknya dalam bahasa Inggris) dan sertakan teks berikut dalam pesan Anda (JANGAN terjemahkan teks tersebut):",
"Popular enabled: ": "Populer diaktifkan: " "Popular enabled: ": "Populer diaktifkan: ",
"channel_tab_podcasts_label": "Podcast",
"Download is disabled": "Download dinonaktifkan",
"Channel Sponsor": "Saluran Sponsor",
"channel_tab_streams_label": "Streaming langsung",
"playlist_button_add_items": "Tambahkan video",
"Artist: ": "Artis: ",
"generic_button_save": "Simpan",
"generic_button_cancel": "Batal",
"Album: ": "Album: ",
"channel_tab_shorts_label": "Shorts",
"channel_tab_releases_label": "Terbit",
"Interlingue": "Interlingue",
"Song: ": "Lagu: ",
"generic_channels_count_0": "Saluran {{count}}",
"channel_tab_playlists_label": "Daftar putar",
"generic_button_edit": "Ubah",
"Music in this video": "Musik dalam video ini",
"generic_button_rss": "RSS",
"channel_tab_channels_label": "Saluran",
"error_video_not_in_playlist": "Video yang diminta tidak ada dalam daftar putar ini. <a href=\"`x`\">Klik di sini untuk halaman beranda daftar putar.</a>",
"generic_button_delete": "Hapus",
"Import YouTube playlist (.csv)": "Impor daftar putar YouTube (.csv)",
"Standard YouTube license": "Lisensi YouTube standar"
} }

View File

@ -1,10 +1,13 @@
{ {
"generic_subscribers_count": "{{count}} iscritto", "generic_subscribers_count_0": "{{count}} iscritto",
"generic_subscribers_count_plural": "{{count}} iscritti", "generic_subscribers_count_1": "{{count}} iscritti",
"generic_videos_count": "{{count}} video", "generic_subscribers_count_2": "{{count}} iscritti",
"generic_videos_count_plural": "{{count}} video", "generic_videos_count_0": "{{count}} video",
"generic_playlists_count": "{{count}} playlist", "generic_videos_count_1": "{{count}} video",
"generic_playlists_count_plural": "{{count}} playlist", "generic_videos_count_2": "{{count}} video",
"generic_playlists_count_0": "{{count}} playlist",
"generic_playlists_count_1": "{{count}} playlist",
"generic_playlists_count_2": "{{count}} playlist",
"LIVE": "IN DIRETTA", "LIVE": "IN DIRETTA",
"Shared `x` ago": "Condiviso `x` fa", "Shared `x` ago": "Condiviso `x` fa",
"Unsubscribe": "Disiscriviti", "Unsubscribe": "Disiscriviti",
@ -113,16 +116,19 @@
"Subscription manager": "Gestione delle iscrizioni", "Subscription manager": "Gestione delle iscrizioni",
"Token manager": "Gestione dei gettoni", "Token manager": "Gestione dei gettoni",
"Token": "Gettone", "Token": "Gettone",
"generic_subscriptions_count": "{{count}} iscrizione", "generic_subscriptions_count_0": "{{count}} iscrizione",
"generic_subscriptions_count_plural": "{{count}} iscrizioni", "generic_subscriptions_count_1": "{{count}} iscrizioni",
"tokens_count": "{{count}} gettone", "generic_subscriptions_count_2": "{{count}} iscrizioni",
"tokens_count_plural": "{{count}} gettoni", "tokens_count_0": "{{count}} gettone",
"tokens_count_1": "{{count}} gettoni",
"tokens_count_2": "{{count}} gettoni",
"Import/export": "Importa/esporta", "Import/export": "Importa/esporta",
"unsubscribe": "disiscriviti", "unsubscribe": "disiscriviti",
"revoke": "revoca", "revoke": "revoca",
"Subscriptions": "Iscrizioni", "Subscriptions": "Iscrizioni",
"subscriptions_unseen_notifs_count": "{{count}} notifica non visualizzata", "subscriptions_unseen_notifs_count_0": "{{count}} notifica non visualizzata",
"subscriptions_unseen_notifs_count_plural": "{{count}} notifiche non visualizzate", "subscriptions_unseen_notifs_count_1": "{{count}} notifiche non visualizzate",
"subscriptions_unseen_notifs_count_2": "{{count}} notifiche non visualizzate",
"search": "Cerca", "search": "Cerca",
"Log out": "Esci", "Log out": "Esci",
"Source available here.": "Codice sorgente.", "Source available here.": "Codice sorgente.",
@ -151,8 +157,9 @@
"Whitelisted regions: ": "Regioni in lista bianca: ", "Whitelisted regions: ": "Regioni in lista bianca: ",
"Blacklisted regions: ": "Regioni in lista nera: ", "Blacklisted regions: ": "Regioni in lista nera: ",
"Shared `x`": "Condiviso `x`", "Shared `x`": "Condiviso `x`",
"generic_views_count": "{{count}} visualizzazione", "generic_views_count_0": "{{count}} visualizzazione",
"generic_views_count_plural": "{{count}} visualizzazioni", "generic_views_count_1": "{{count}} visualizzazioni",
"generic_views_count_2": "{{count}} visualizzazioni",
"Premieres in `x`": "In anteprima in `x`", "Premieres in `x`": "In anteprima in `x`",
"Premieres `x`": "In anteprima `x`", "Premieres `x`": "In anteprima `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.": "Ciao, Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti, ma considera che il caricamento potrebbe richiedere più tempo.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Ciao, Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti, ma considera che il caricamento potrebbe richiedere più tempo.",
@ -300,20 +307,27 @@
"Yiddish": "Yiddish", "Yiddish": "Yiddish",
"Yoruba": "Yoruba", "Yoruba": "Yoruba",
"Zulu": "Zulu", "Zulu": "Zulu",
"generic_count_years": "{{count}} anno", "generic_count_years_0": "{{count}} anno",
"generic_count_years_plural": "{{count}} anni", "generic_count_years_1": "{{count}} anni",
"generic_count_months": "{{count}} mese", "generic_count_years_2": "{{count}} anni",
"generic_count_months_plural": "{{count}} mesi", "generic_count_months_0": "{{count}} mese",
"generic_count_weeks": "{{count}} settimana", "generic_count_months_1": "{{count}} mesi",
"generic_count_weeks_plural": "{{count}} settimane", "generic_count_months_2": "{{count}} mesi",
"generic_count_days": "{{count}} giorno", "generic_count_weeks_0": "{{count}} settimana",
"generic_count_days_plural": "{{count}} giorni", "generic_count_weeks_1": "{{count}} settimane",
"generic_count_hours": "{{count}} ora", "generic_count_weeks_2": "{{count}} settimane",
"generic_count_hours_plural": "{{count}} ore", "generic_count_days_0": "{{count}} giorno",
"generic_count_minutes": "{{count}} minuto", "generic_count_days_1": "{{count}} giorni",
"generic_count_minutes_plural": "{{count}} minuti", "generic_count_days_2": "{{count}} giorni",
"generic_count_seconds": "{{count}} secondo", "generic_count_hours_0": "{{count}} ora",
"generic_count_seconds_plural": "{{count}} secondi", "generic_count_hours_1": "{{count}} ore",
"generic_count_hours_2": "{{count}} ore",
"generic_count_minutes_0": "{{count}} minuto",
"generic_count_minutes_1": "{{count}} minuti",
"generic_count_minutes_2": "{{count}} minuti",
"generic_count_seconds_0": "{{count}} secondo",
"generic_count_seconds_1": "{{count}} secondi",
"generic_count_seconds_2": "{{count}} secondi",
"Fallback comments: ": "Commenti alternativi: ", "Fallback comments: ": "Commenti alternativi: ",
"Popular": "Popolare", "Popular": "Popolare",
"Search": "Cerca", "Search": "Cerca",
@ -417,10 +431,12 @@
"search_filters_duration_option_short": "Corto (< 4 minuti)", "search_filters_duration_option_short": "Corto (< 4 minuti)",
"search_filters_duration_option_long": "Lungo (> 20 minuti)", "search_filters_duration_option_long": "Lungo (> 20 minuti)",
"search_filters_features_option_purchased": "Acquistato", "search_filters_features_option_purchased": "Acquistato",
"comments_view_x_replies": "Vedi {{count}} risposta", "comments_view_x_replies_0": "Vedi {{count}} risposta",
"comments_view_x_replies_plural": "Vedi {{count}} risposte", "comments_view_x_replies_1": "Vedi {{count}} risposte",
"comments_points_count": "{{count}} punto", "comments_view_x_replies_2": "Vedi {{count}} risposte",
"comments_points_count_plural": "{{count}} punti", "comments_points_count_0": "{{count}} punto",
"comments_points_count_1": "{{count}} punti",
"comments_points_count_2": "{{count}} punti",
"Portuguese (auto-generated)": "Portoghese (generati automaticamente)", "Portuguese (auto-generated)": "Portoghese (generati automaticamente)",
"crash_page_you_found_a_bug": "Sembra che tu abbia trovato un bug in Invidious!", "crash_page_you_found_a_bug": "Sembra che tu abbia trovato un bug in Invidious!",
"crash_page_switch_instance": "provato a <a href=\"`x`\">usare un'altra istanza</a>", "crash_page_switch_instance": "provato a <a href=\"`x`\">usare un'altra istanza</a>",
@ -484,5 +500,8 @@
"generic_button_delete": "Elimina", "generic_button_delete": "Elimina",
"generic_button_save": "Salva", "generic_button_save": "Salva",
"playlist_button_add_items": "Aggiungi video", "playlist_button_add_items": "Aggiungi video",
"channel_tab_podcasts_label": "Podcast" "channel_tab_podcasts_label": "Podcast",
"generic_channels_count_0": "{{count}} canale",
"generic_channels_count_1": "{{count}} canali",
"generic_channels_count_2": "{{count}} canali"
} }

View File

@ -468,5 +468,6 @@
"generic_button_edit": "編集", "generic_button_edit": "編集",
"generic_button_save": "保存", "generic_button_save": "保存",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"playlist_button_add_items": "動画を追加" "playlist_button_add_items": "動画を追加",
"generic_channels_count_0": "{{count}}個のチャンネル"
} }

View File

@ -468,5 +468,6 @@
"generic_button_save": "저장", "generic_button_save": "저장",
"generic_button_cancel": "취소", "generic_button_cancel": "취소",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"channel_tab_releases_label": "출시" "channel_tab_releases_label": "출시",
"generic_channels_count_0": "{{count}} 채널"
} }

View File

@ -484,5 +484,7 @@
"generic_button_save": "Lagre", "generic_button_save": "Lagre",
"generic_button_cancel": "Avbryt", "generic_button_cancel": "Avbryt",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"playlist_button_add_items": "Legg til videoer" "playlist_button_add_items": "Legg til videoer",
"generic_channels_count": "{{count}} kanal",
"generic_channels_count_plural": "{{count}} kanaler"
} }

View File

@ -500,5 +500,8 @@
"channel_tab_releases_label": "Wydania", "channel_tab_releases_label": "Wydania",
"generic_button_delete": "Usuń", "generic_button_delete": "Usuń",
"generic_button_save": "Zapisz", "generic_button_save": "Zapisz",
"playlist_button_add_items": "Dodaj filmy" "playlist_button_add_items": "Dodaj filmy",
"generic_channels_count_0": "{{count}} kanał",
"generic_channels_count_1": "{{count}} kanały",
"generic_channels_count_2": "{{count}} kanałów"
} }

View File

@ -112,8 +112,9 @@
"Subscription manager": "Gerenciador de inscrições", "Subscription manager": "Gerenciador de inscrições",
"Token manager": "Gerenciador de tokens", "Token manager": "Gerenciador 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",
"tokens_count_2": "{{count}} tokens",
"Import/export": "Importar/Exportar", "Import/export": "Importar/Exportar",
"unsubscribe": "cancelar inscrição", "unsubscribe": "cancelar inscrição",
"revoke": "revogar", "revoke": "revogar",
@ -297,20 +298,27 @@
"Yiddish": "Iídiche", "Yiddish": "Iídiche",
"Yoruba": "Iorubá", "Yoruba": "Iorubá",
"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}} semana", "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",
"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: ": "Comentários alternativos: ", "Fallback comments: ": "Comentários alternativos: ",
"Popular": "Populares", "Popular": "Populares",
"Search": "Procurar", "Search": "Procurar",
@ -377,20 +385,27 @@
"preferences_quality_dash_label": "Qualidade de vídeo do painel preferida: ", "preferences_quality_dash_label": "Qualidade de vídeo do painel preferida: ",
"preferences_region_label": "País do conteúdo: ", "preferences_region_label": "País do conteúdo: ",
"preferences_quality_dash_option_4320p": "4320p", "preferences_quality_dash_option_4320p": "4320p",
"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_subscribers_count": "{{count}} inscrito", "generic_playlists_count_1": "{{count}} listas de reprodução",
"generic_subscribers_count_plural": "{{count}} inscritos", "generic_playlists_count_2": "{{count}} listas de reprodução",
"generic_subscriptions_count": "{{count}} inscrição", "generic_subscribers_count_0": "{{count}} inscrito",
"generic_subscriptions_count_plural": "{{count}} inscrições", "generic_subscribers_count_1": "{{count}} inscritos",
"subscriptions_unseen_notifs_count": "{{count}} notificação não vista", "generic_subscribers_count_2": "{{count}} inscritos",
"subscriptions_unseen_notifs_count_plural": "{{count}} notificações não vistas", "generic_subscriptions_count_0": "{{count}} inscrição",
"comments_view_x_replies": "Ver {{count}} resposta", "generic_subscriptions_count_1": "{{count}} inscrições",
"comments_view_x_replies_plural": "Ver {{count}} respostas", "generic_subscriptions_count_2": "{{count}} inscrições",
"comments_points_count": "{{count}} ponto", "subscriptions_unseen_notifs_count_0": "{{count}} notificação não vista",
"comments_points_count_plural": "{{count}} pontos", "subscriptions_unseen_notifs_count_1": "{{count}} notificações não vistas",
"subscriptions_unseen_notifs_count_2": "{{count}} notificações não vistas",
"comments_view_x_replies_0": "Ver {{count}} resposta",
"comments_view_x_replies_1": "Ver {{count}} respostas",
"comments_view_x_replies_2": "Ver {{count}} respostas",
"comments_points_count_0": "{{count}} ponto",
"comments_points_count_1": "{{count}} pontos",
"comments_points_count_2": "{{count}} pontos",
"crash_page_you_found_a_bug": "Parece que você encontrou um erro no Invidious!", "crash_page_you_found_a_bug": "Parece que você encontrou um erro no Invidious!",
"crash_page_before_reporting": "Antes de reportar um erro, verifique se você:", "crash_page_before_reporting": "Antes de reportar um erro, verifique se você:",
"preferences_save_player_pos_label": "Salvar a posição de reprodução: ", "preferences_save_player_pos_label": "Salvar a posição de reprodução: ",
@ -400,8 +415,9 @@
"crash_page_search_issue": "procurou por um <a href=\"`x`\">erro existente no GitHub</a>", "crash_page_search_issue": "procurou por um <a href=\"`x`\">erro existente 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 (NÃ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 traduza):",
"crash_page_read_the_faq": "leia as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>", "crash_page_read_the_faq": "leia as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>",
"generic_views_count": "{{count}} visualização", "generic_views_count_0": "{{count}} visualização",
"generic_views_count_plural": "{{count}} visualizações", "generic_views_count_1": "{{count}} visualizações",
"generic_views_count_2": "{{count}} visualizações",
"preferences_quality_option_dash": "DASH (qualidade adaptável)", "preferences_quality_option_dash": "DASH (qualidade adaptável)",
"preferences_quality_option_hd720": "HD720", "preferences_quality_option_hd720": "HD720",
"preferences_quality_option_small": "Pequeno", "preferences_quality_option_small": "Pequeno",
@ -484,5 +500,8 @@
"channel_tab_releases_label": "Lançamentos", "channel_tab_releases_label": "Lançamentos",
"channel_tab_podcasts_label": "Podcasts", "channel_tab_podcasts_label": "Podcasts",
"generic_button_cancel": "Cancelar", "generic_button_cancel": "Cancelar",
"generic_button_rss": "RSS" "generic_button_rss": "RSS",
"generic_channels_count_0": "{{count}} canal",
"generic_channels_count_1": "{{count}} canais",
"generic_channels_count_2": "{{count}} canais"
} }

View File

@ -500,5 +500,8 @@
"generic_button_cancel": "Отменить", "generic_button_cancel": "Отменить",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"playlist_button_add_items": "Добавить видео", "playlist_button_add_items": "Добавить видео",
"channel_tab_podcasts_label": "Подкасты" "channel_tab_podcasts_label": "Подкасты",
"generic_channels_count_0": "{{count}} канал",
"generic_channels_count_1": "{{count}} канала",
"generic_channels_count_2": "{{count}} каналов"
} }

View File

@ -516,5 +516,9 @@
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"playlist_button_add_items": "Dodaj videoposnetke", "playlist_button_add_items": "Dodaj videoposnetke",
"channel_tab_podcasts_label": "Poddaje", "channel_tab_podcasts_label": "Poddaje",
"channel_tab_releases_label": "Izdaje" "channel_tab_releases_label": "Izdaje",
"generic_channels_count_0": "{{count}} kanal",
"generic_channels_count_1": "{{count}} kanala",
"generic_channels_count_2": "{{count}} kanali",
"generic_channels_count_3": "{{count}} kanalov"
} }

View File

@ -257,7 +257,7 @@
"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",
@ -345,7 +345,7 @@
"View YouTube comments": "Shihni komente Youtube", "View YouTube comments": "Shihni komente Youtube",
"View more comments on Reddit": "Shihni më tepër komente në Reddit", "View more comments on Reddit": "Shihni më tepër komente në Reddit",
"View `x` comments": { "View `x` comments": {
"([^.,0-9]|^)1([^.,0-9]|$)": "Shihni `x` komente", "([^.,0-9]|^)1([^.,0-9]|$)": "Shihni `x` koment",
"": "Shihni `x` komente" "": "Shihni `x` komente"
}, },
"View Reddit comments": "Shihni komente Reddit", "View Reddit comments": "Shihni komente Reddit",
@ -462,5 +462,20 @@
"channel_tab_channels_label": "Kanale", "channel_tab_channels_label": "Kanale",
"Music in this video": "Muzikë në këtë video", "Music in this video": "Muzikë në këtë video",
"channel_tab_shorts_label": "Të shkurtra", "channel_tab_shorts_label": "Të shkurtra",
"channel_tab_streams_label": "Transmetime të drejtpërdrejta" "channel_tab_streams_label": "Transmetime të drejtpërdrejta",
"generic_button_cancel": "Anuloje",
"generic_channels_count": "{{count}} kanal",
"generic_channels_count_plural": "{{count}} kanale",
"generic_button_rss": "RSS",
"generic_button_delete": "Fshije",
"generic_button_save": "Ruaje",
"generic_button_edit": "Përpunoni",
"playlist_button_add_items": "Shtoni video",
"Report statistics: ": "Statistika raportimesh: ",
"Download is disabled": "Shkarkimi është i çaktivizuar",
"Channel Sponsor": "Sponsor Kanali",
"channel_tab_releases_label": "Hedhje në qarkullim",
"Song: ": "Pjesë: ",
"Import YouTube playlist (.csv)": "Importoni luajlistë YouTube (.csv)",
"Standard YouTube license": "Licencë YouTube standarde"
} }

View File

@ -1,90 +1,90 @@
{ {
"LIVE": "UŽIVO", "LIVE": "UŽIVO",
"Shared `x` ago": "Podeljeno pre `x`", "Shared `x` ago": "Deljeno pre `x`",
"Unsubscribe": "Prekini praćenje", "Unsubscribe": "Prekini praćenje",
"Subscribe": "Prati", "Subscribe": "Zaprati",
"View channel on YouTube": "Pogledaj kanal na YouTube-u", "View channel on YouTube": "Pogledaj kanal na YouTube-u",
"View playlist on YouTube": "Pogledaj spisak izvođenja na YouTube-u", "View playlist on YouTube": "Pogledaj plejlistu na YouTube-u",
"newest": "najnovije", "newest": "najnovije",
"oldest": "najstarije", "oldest": "najstarije",
"popular": "popularno", "popular": "popularno",
"last": "poslednje", "last": "poslednje",
"Next page": "Sledeća stranica", "Next page": "Sledeća stranica",
"Previous page": "Prethodna stranica", "Previous page": "Prethodna stranica",
"Clear watch history?": "Izbrisati povest pregledanja?", "Clear watch history?": "Očistiti istoriju gledanja?",
"New password": "Nova lozinka", "New password": "Nova lozinka",
"New passwords must match": "Nove lozinke moraju biti istovetne", "New passwords must match": "Nove lozinke moraju da se podudaraju",
"Authorize token?": "Ovlasti žeton?", "Authorize token?": "Autorizovati token?",
"Authorize token for `x`?": "Ovlasti žeton za `x`?", "Authorize token for `x`?": "Autorizovati token za `x`?",
"Yes": "Da", "Yes": "Da",
"No": "Ne", "No": "Ne",
"Import and Export Data": "Uvoz i Izvoz Podataka", "Import and Export Data": "Uvoz i izvoz podataka",
"Import": "Uvezi", "Import": "Uvezi",
"Import Invidious data": "Uvezi podatke sa Invidious-a", "Import Invidious data": "Uvezi Invidious JSON podatke",
"Import YouTube subscriptions": "Uvezi praćenja sa YouTube-a", "Import YouTube subscriptions": "Uvezi YouTube/OPML praćenja",
"Import FreeTube subscriptions (.db)": "Uvezi praćenja sa FreeTube-a (.db)", "Import FreeTube subscriptions (.db)": "Uvezi FreeTube praćenja (.db)",
"Import NewPipe subscriptions (.json)": "Uvezi praćenja sa NewPipe-a (.json)", "Import NewPipe subscriptions (.json)": "Uvezi NewPipe praćenja (.json)",
"Import NewPipe data (.zip)": "Uvezi podatke sa NewPipe-a (.zip)", "Import NewPipe data (.zip)": "Uvezi NewPipe podatke (.zip)",
"Export": "Izvezi", "Export": "Izvezi",
"Export subscriptions as OPML": "Izvezi praćenja kao OPML datoteku", "Export subscriptions as OPML": "Izvezi praćenja kao OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Izvezi praćenja kao OPML datoteku (za NewPipe i FreeTube)", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Izvezi praćenja kao OPML (za NewPipe i FreeTube)",
"Export data as JSON": "Izvezi podatke kao JSON datoteku", "Export data as JSON": "Izvezi Invidious podatke kao JSON",
"Delete account?": "Izbrišite nalog?", "Delete account?": "Izbrisati nalog?",
"History": "Istorija", "History": "Istorija",
"An alternative front-end to YouTube": "Zamenski korisnički sloj za YouTube", "An alternative front-end to YouTube": "Alternativni front-end za YouTube",
"JavaScript license information": "Izveštaj o JavaScript odobrenju", "JavaScript license information": "Informacije o JavaScript licenci",
"source": "izvor", "source": "izvor",
"Log in": "Prijavi se", "Log in": "Prijava",
"Log in/register": "Prijavi se/Otvori nalog", "Log in/register": "Prijava/registracija",
"User ID": "Korisnički ID", "User ID": "ID korisnika",
"Password": "Lozinka", "Password": "Lozinka",
"Time (h:mm:ss):": "Vreme (č:mm:ss):", "Time (h:mm:ss):": "Vreme (č:mm:ss):",
"Text CAPTCHA": "Znakovni CAPTCHA", "Text CAPTCHA": "Tekst CAPTCHA",
"Image CAPTCHA": "Slikovni CAPTCHA", "Image CAPTCHA": "Slika CAPTCHA",
"Sign In": "Prijava", "Sign In": "Prijava",
"Register": "Otvori nalog", "Register": "Registracija",
"E-mail": "E-pošta", "E-mail": "Imejl",
"Preferences": "Podešavanja", "Preferences": "Podešavanja",
"preferences_category_player": "Podešavanja reproduktora", "preferences_category_player": "Podešavanja plejera",
"preferences_video_loop_label": "Uvek ponavljaj: ", "preferences_video_loop_label": "Uvek ponavljaj: ",
"preferences_autoplay_label": "Samopuštanje: ", "preferences_autoplay_label": "Automatski pusti: ",
"preferences_continue_label": "Uvek podrazumevano puštaj sledeće: ", "preferences_continue_label": "Podrazumevano pusti sledeće: ",
"preferences_continue_autoplay_label": "Samopuštanje sledećeg video zapisa: ", "preferences_continue_autoplay_label": "Automatski pusti sledeći video snimak: ",
"preferences_listen_label": "Uvek podrazumevano uključen samo zvuk: ", "preferences_listen_label": "Podrazumevano uključi samo zvuk: ",
"preferences_local_label": "Prikaz video zapisa preko posrednika: ", "preferences_local_label": "Proksi video snimci: ",
"Playlist privacy": "Podešavanja privatnosti plej liste", "Playlist privacy": "Privatnost plejliste",
"Editing playlist `x`": "Izmena plej liste `x`", "Editing playlist `x`": "Izmenjivanje plejliste `x`",
"Playlist does not exist.": "Nepostojeća plej lista.", "Playlist does not exist.": "Plejlista ne postoji.",
"Erroneous challenge": "Pogrešan izazov", "Erroneous challenge": "Pogrešan izazov",
"Maltese": "Malteški", "Maltese": "Malteški",
"Download": "Preuzmi", "Download": "Preuzmi",
"Download as: ": "Preuzmi kao: ", "Download as: ": "Preuzeti kao: ",
"Bangla": "Bangla/Bengalski", "Bangla": "Bengalski",
"preferences_quality_dash_label": "Preferirani kvalitet DASH video formata: ", "preferences_quality_dash_label": "Preferirani DASH kvalitet video snimka: ",
"Token manager": "Upravljanje žetonima", "Token manager": "Upravljanje tokenima",
"Token": "Žeton", "Token": "Token",
"Import/export": "Uvezi/Izvezi", "Import/export": "Uvoz/izvoz",
"revoke": "opozovi", "revoke": "opozovi",
"search": "pretraga", "search": "pretraga",
"Log out": "Odjava", "Log out": "Odjava",
"Source available here.": "Izvorna koda je ovde dostupna.", "Source available here.": "Izvorni kôd je dostupan ovde.",
"Trending": "U trendu", "Trending": "U trendu",
"Updated `x` ago": "Ažurirano pre `x`", "Updated `x` ago": "Ažurirano pre `x`",
"Delete playlist `x`?": "Obriši plej listu `x`?", "Delete playlist `x`?": "Izbrisati plejlistu `x`?",
"Create playlist": "Napravi plej listu", "Create playlist": "Napravi plejlistu",
"Show less": "Prikaži manje", "Show less": "Prikaži manje",
"Switch Invidious Instance": "Promeni Invidious instancu", "Switch Invidious Instance": "Promeni Invidious instancu",
"Hide annotations": "Sakrij napomene", "Hide annotations": "Sakrij napomene",
"User ID is a required field": "Korisnički ID je obavezno polje", "User ID is a required field": "ID korisnika je obavezno polje",
"Wrong username or password": "Pogrešno korisničko ime ili lozinka", "Wrong username or password": "Pogrešno korisničko ime ili lozinka",
"Please log in": "Molimo vas da se prijavite", "Please log in": "Molimo, prijavite se",
"channel:`x`": "kanal:`x`", "channel:`x`": "kanal:`x`",
"Could not fetch comments": "Uzimanje komentara nije uspelo", "Could not fetch comments": "Nije moguće prikupiti komentare",
"Could not create mix.": "Pravljenje miksa nije uspelo.", "Could not create mix.": "Nije moguće napraviti miks.",
"Empty playlist": "Prazna plej lista", "Empty playlist": "Prazna plejlista",
"Not a playlist.": "Nije plej lista.", "Not a playlist.": "Nije plejlista.",
"Could not pull trending pages.": "Učitavanje 'U toku' stranica nije uspelo.", "Could not pull trending pages.": "Nije moguće povući stranice „U trendu“.",
"Token is expired, please try again": "Žeton je istekao, molimo vas da pokušate ponovo", "Token is expired, please try again": "Token je istekao, pokušajte ponovo",
"English (auto-generated)": "Engleski (automatski generisano)", "English (auto-generated)": "Engleski (automatski generisano)",
"Afrikaans": "Afrikans", "Afrikaans": "Afrikans",
"Albanian": "Albanski", "Albanian": "Albanski",
@ -95,19 +95,19 @@
"Bulgarian": "Bugarski", "Bulgarian": "Bugarski",
"Burmese": "Burmanski", "Burmese": "Burmanski",
"Catalan": "Katalonski", "Catalan": "Katalonski",
"Cebuano": "Sebuano", "Cebuano": "Cebuanski",
"Chinese (Traditional)": "Kineski (Tradicionalni)", "Chinese (Traditional)": "Kineski (Tradicionalni)",
"Corsican": "Korzikanski", "Corsican": "Korzikanski",
"Danish": "Danski", "Danish": "Danski",
"Kannada": "Kanada (Jezik)", "Kannada": "Kanada",
"Kazakh": "Kazaški", "Kazakh": "Kazaški",
"Russian": "Ruski", "Russian": "Ruski",
"Scottish Gaelic": "Škotski Gelski", "Scottish Gaelic": "Škotski Gelski",
"Sinhala": "Sinhalki", "Sinhala": "Sinhalski",
"Slovak": "Slovački", "Slovak": "Slovački",
"Spanish": "Španski", "Spanish": "Španski",
"Spanish (Latin America)": "Španski (Južna Amerika)", "Spanish (Latin America)": "Španski (Latinska Amerika)",
"Sundanese": "Sundski", "Sundanese": "Sundanski",
"Swedish": "Švedski", "Swedish": "Švedski",
"Tajik": "Tadžički", "Tajik": "Tadžički",
"Telugu": "Telugu", "Telugu": "Telugu",
@ -116,77 +116,77 @@
"Urdu": "Urdu", "Urdu": "Urdu",
"Uzbek": "Uzbečki", "Uzbek": "Uzbečki",
"Vietnamese": "Vijetnamski", "Vietnamese": "Vijetnamski",
"Rating: ": "Ocena/e: ", "Rating: ": "Ocena: ",
"View as playlist": "Pogledaj kao plej listu", "View as playlist": "Pogledaj kao plejlistu",
"Default": "Podrazumevan/o", "Default": "Podrazumevano",
"Gaming": "Igrice", "Gaming": "Video igre",
"Movies": "Filmovi", "Movies": "Filmovi",
"%A %B %-d, %Y": "%A %B %-d, %Y", "%A %B %-d, %Y": "%A %B %-d, %Y",
"(edited)": "(izmenjeno)", "(edited)": "(izmenjeno)",
"YouTube comment permalink": "YouTube komentar trajna veza", "YouTube comment permalink": "Trajni link YouTube komentara",
"Audio mode": "Audio mod", "Audio mode": "Režim audio snimka",
"Playlists": "Plej liste", "Playlists": "Plejliste",
"search_filters_sort_option_relevance": "Relevantnost", "search_filters_sort_option_relevance": "Relevantnost",
"search_filters_sort_option_rating": "Ocene", "search_filters_sort_option_rating": "Ocena",
"search_filters_sort_option_date": "Datum otpremanja", "search_filters_sort_option_date": "Datum otpremanja",
"search_filters_sort_option_views": "Broj pregleda", "search_filters_sort_option_views": "Broj pregleda",
"`x` marked it with a ❤": "`x` je označio/la ovo sa ❤", "`x` marked it with a ❤": "`x` je označio/la sa ❤",
"search_filters_duration_label": "Trajanje", "search_filters_duration_label": "Trajanje",
"search_filters_features_label": "Karakteristike", "search_filters_features_label": "Karakteristike",
"search_filters_date_option_hour": "Poslednji sat", "search_filters_date_option_hour": "Poslednji sat",
"search_filters_date_option_week": "Ove sedmice", "search_filters_date_option_week": "Ove nedelje",
"search_filters_date_option_month": "Ovaj mesec", "search_filters_date_option_month": "Ovog meseca",
"search_filters_date_option_year": "Ove godine", "search_filters_date_option_year": "Ove godine",
"search_filters_type_option_video": "Video", "search_filters_type_option_video": "Video snimak",
"search_filters_type_option_playlist": "Plej lista", "search_filters_type_option_playlist": "Plejlista",
"search_filters_type_option_movie": "Film", "search_filters_type_option_movie": "Film",
"search_filters_duration_option_long": "Dugo (> 20 minuta)", "search_filters_duration_option_long": "Dugo (> 20 minuta)",
"search_filters_features_option_hd": "HD", "search_filters_features_option_hd": "HD",
"search_filters_features_option_c_commons": "Creative Commons (Licenca)", "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_hdr": "Video Visoke Rezolucije", "search_filters_features_option_hdr": "HDR",
"next_steps_error_message": "Nakon čega bi trebali probati: ", "next_steps_error_message": "Nakon toga treba da pokušate da: ",
"next_steps_error_message_go_to_youtube": "Idi na YouTube", "next_steps_error_message_go_to_youtube": "Odete na YouTube",
"footer_documentation": "Dokumentacija", "footer_documentation": "Dokumentacija",
"preferences_region_label": "Država porekla sadržaja: ", "preferences_region_label": "Država sadržaja: ",
"preferences_player_style_label": "Stil plejera: ", "preferences_player_style_label": "Stil plejera: ",
"preferences_dark_mode_label": "Izgled/Tema: ", "preferences_dark_mode_label": "Tema: ",
"light": "svetlo", "light": "svetla",
"preferences_thin_mode_label": "Kompaktni režim: ", "preferences_thin_mode_label": "Kompaktni režim: ",
"preferences_category_misc": "Ostala podešavanja", "preferences_category_misc": "Ostala podešavanja",
"preferences_automatic_instance_redirect_label": "Automatsko prebacivanje na drugu instancu u slučaju otkazivanja (preči će nazad na redirect.invidious.io): ", "preferences_automatic_instance_redirect_label": "Automatsko preusmeravanje instance (povratak na redirect.invidious.io): ",
"alphabetically - reverse": "po alfabetu - obrnuto", "alphabetically - reverse": "abecedno - obrnuto",
"Enable web notifications": "Omogući obaveštenja u veb pretraživaču", "Enable web notifications": "Omogući veb obaveštenja",
"`x` is live": "`x` prenosi uživo", "`x` is live": "`x` je uživo",
"Manage tokens": "Upravljaj žetonima", "Manage tokens": "Upravljaj tokenima",
"Watch history": "Istorija gledanja", "Watch history": "Istorija gledanja",
"preferences_feed_menu_label": "Dovodna stranica: ", "preferences_feed_menu_label": "Fid meni: ",
"preferences_show_nick_label": "Prikaži nadimke na vrhu: ", "preferences_show_nick_label": "Prikaži nadimke na vrhu: ",
"CAPTCHA enabled: ": "CAPTCHA omogućena: ", "CAPTCHA enabled: ": "CAPTCHA omogućena: ",
"Registration enabled: ": "Registracija omogućena: ", "Registration enabled: ": "Registracija omogućena: ",
"Subscription manager": "Upravljanje praćenjima", "Subscription manager": "Upravljanje praćenjima",
"Wilson score: ": "Wilsonova ocena: ", "Wilson score: ": "Vilsonova ocena: ",
"Engagement: ": "Angažovanje: ", "Engagement: ": "Angažovanje: ",
"Whitelisted regions: ": "Dozvoljene oblasti: ", "Whitelisted regions: ": "Dostupni regioni: ",
"Shared `x`": "Podeljeno `x`", "Shared `x`": "Deljeno `x`",
"Premieres in `x`": "Premera u `x`", "Premieres in `x`": "Premijera u `x`",
"Premieres `x`": "Premere u `x`", "Premieres `x`": "Premijera `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.": "Hej! Izgleda da ste onemogućili JavaScript. Kliknite ovde da vidite komentare, čuvajte na umu da ovo može da potraje duže dok se ne učitaju.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Izgleda da ste isključili JavaScript. Kliknite ovde da biste videli komentare, imajte na umu da će možda potrajati malo duže da se učitaju.",
"View `x` comments": { "View `x` comments": {
"([^.,0-9]|^)1([^.,0-9]|$)": "Prikaži `x` komentar", "([^.,0-9]|^)1([^.,0-9]|$)": "Pogledaj `x` komentar",
"": "Prikaži `x` komentara" "": "Pogledaj`x` komentare"
}, },
"View Reddit comments": "Prikaži Reddit komentare", "View Reddit comments": "Pogledaj Reddit komentare",
"CAPTCHA is a required field": "CAPTCHA je obavezno polje", "CAPTCHA is a required field": "CAPTCHA je obavezno polje",
"Croatian": "Hrvatski", "Croatian": "Hrvatski",
"Estonian": "Estonski", "Estonian": "Estonski",
"Filipino": "Filipino", "Filipino": "Filipinski",
"French": "Francuski", "French": "Francuski",
"Galician": "Galicijski", "Galician": "Galicijski",
"German": "Nemački", "German": "Nemački",
"Greek": "Grčki", "Greek": "Grčki",
"Hausa": "Hausa", "Hausa": "Hausa",
"Italian": "Talijanski", "Italian": "Italijanski",
"Khmer": "Kmerski", "Khmer": "Kmerski",
"Kurdish": "Kurdski", "Kurdish": "Kurdski",
"Kyrgyz": "Kirgiski", "Kyrgyz": "Kirgiski",
@ -195,68 +195,68 @@
"Macedonian": "Makedonski", "Macedonian": "Makedonski",
"Malagasy": "Malgaški", "Malagasy": "Malgaški",
"Malay": "Malajski", "Malay": "Malajski",
"Marathi": "Marathi", "Marathi": "Maratski",
"Mongolian": "Mongolski", "Mongolian": "Mongolski",
"Norwegian Bokmål": "Norveški Bokmal", "Norwegian Bokmål": "Norveški Bokmal",
"Nyanja": "Čeva", "Nyanja": "Nijandža",
"Pashto": "Paštunski", "Pashto": "Paštunski",
"Persian": "Persijski", "Persian": "Persijski",
"Punjabi": "Pundžabi", "Punjabi": "Pandžapski",
"Romanian": "Rumunski", "Romanian": "Rumunski",
"Welsh": "Velški", "Welsh": "Velški",
"Western Frisian": "Zapadnofrizijski", "Western Frisian": "Zapadnofrizijski",
"Fallback comments: ": "Komentari u slučaju otkazivanja: ", "Fallback comments: ": "Rezervni komentari: ",
"Popular": "Popularno", "Popular": "Popularno",
"Search": "Pretraga", "Search": "Pretraga",
"About": "O programu", "About": "O sajtu",
"footer_source_code": "Izvorna Koda", "footer_source_code": "Izvorni kôd",
"footer_original_source_code": "Originalna Izvorna Koda", "footer_original_source_code": "Originalni izvorni kôd",
"preferences_related_videos_label": "Prikaži slične video klipove: ", "preferences_related_videos_label": "Prikaži povezane video snimke: ",
"preferences_annotations_label": "Prikaži napomene podrazumevano: ", "preferences_annotations_label": "Podrazumevano prikaži napomene: ",
"preferences_extend_desc_label": "Automatski prikaži ceo opis videa: ", "preferences_extend_desc_label": "Automatski proširi opis video snimka: ",
"preferences_vr_mode_label": "Interaktivni video klipovi u 360 stepeni: ", "preferences_vr_mode_label": "Interaktivni video snimci od 360 stepeni (zahteva WebGl): ",
"preferences_category_visual": "Vizuelne preference", "preferences_category_visual": "Vizuelna podešavanja",
"preferences_captions_label": "Podrazumevani titl: ", "preferences_captions_label": "Podrazumevani titlovi: ",
"Music": "Muzika", "Music": "Muzika",
"search_filters_type_label": "Tip", "search_filters_type_label": "Vrsta",
"Tamil": "Tamilski", "Tamil": "Tamilski",
"Save preferences": "Sačuvaj podešavanja", "Save preferences": "Sačuvaj podešavanja",
"Only show latest unwatched video from channel: ": "Prikaži samo poslednje video klipove koji nisu pogledani sa kanala: ", "Only show latest unwatched video from channel: ": "Prikaži samo najnoviji neodgledani video snimak sa kanala: ",
"Xhosa": "Kosa (Jezik)", "Xhosa": "Kosa (Khosa)",
"search_filters_type_option_channel": "Kanal", "search_filters_type_option_channel": "Kanal",
"Hungarian": "Mađarski", "Hungarian": "Mađarski",
"Maori": "Maori (Jezik)", "Maori": "Maorski",
"Manage subscriptions": "Upravljaj zapisima", "Manage subscriptions": "Upravljaj praćenjima",
"Hindi": "Hindi", "Hindi": "Hindi",
"`x` ago": "pre `x`", "`x` ago": "pre `x`",
"Import/export data": "Uvezi/Izvezi podatke", "Import/export data": "Uvezi/Izvezi podatke",
"`x` uploaded a video": "`x` je otpremio/la video klip", "`x` uploaded a video": "`x` je otpremio/la video snimak",
"Delete account": "Obriši nalog", "Delete account": "Izbriši nalog",
"preferences_default_home_label": "Podrazumevana početna stranica: ", "preferences_default_home_label": "Podrazumevana početna stranica: ",
"Serbian": "Srpski", "Serbian": "Srpski",
"License: ": "Licenca: ", "License: ": "Licenca: ",
"search_filters_features_option_live": "Uživo", "search_filters_features_option_live": "Uživo",
"Report statistics: ": "Izveštavaj o statistici: ", "Report statistics: ": "Izveštavaj statistike: ",
"Only show latest video from channel: ": "Prikazuj poslednje video klipove samo sa kanala: ", "Only show latest video from channel: ": "Prikaži samo najnoviji video snimak sa kanala: ",
"channel name - reverse": "ime kanala - obrnuto", "channel name - reverse": "ime kanala - obrnuto",
"Could not get channel info.": "Uzimanje podataka o kanalu nije uspelo.", "Could not get channel info.": "Nije moguće prikupiti informacije o kanalu.",
"View privacy policy.": "Pogledaj izveštaj o privatnosti.", "View privacy policy.": "Pogledaj politiku privatnosti.",
"Change password": "Promeni lozinku", "Change password": "Promeni lozinku",
"Malayalam": "Malajalam", "Malayalam": "Malajalamski",
"View more comments on Reddit": "Prikaži više komentara na Reddit-u", "View more comments on Reddit": "Pogledaj više komentara na Reddit-u",
"Portuguese": "Portugalski", "Portuguese": "Portugalski",
"View YouTube comments": "Prikaži YouTube komentare", "View YouTube comments": "Pogledaj YouTube komentare",
"published - reverse": "objavljeno - obrnuto", "published - reverse": "objavljeno - obrnuto",
"Dutch": "Holandski", "Dutch": "Holandski",
"preferences_volume_label": "Jačina zvuka: ", "preferences_volume_label": "Jačina zvuka plejera: ",
"preferences_locale_label": "Jezik: ", "preferences_locale_label": "Jezik: ",
"adminprefs_modified_source_code_url_label": "URL veza do skladišta sa Izmenjenom Izvornom Kodom", "adminprefs_modified_source_code_url_label": "URL adresa do repozitorijuma izmenjenog izvornog koda",
"channel_tab_community_label": "Zajednica", "channel_tab_community_label": "Zajednica",
"Video mode": "Video mod", "Video mode": "Režim video snimka",
"Fallback captions: ": "Titl u slučaju da glavni nije dostupan: ", "Fallback captions: ": "Rezervni titlovi: ",
"Private": "Privatno", "Private": "Privatno",
"alphabetically": "po alfabetu", "alphabetically": "abecedno",
"No such user": "Nepostoji korisnik", "No such user": "Ne postoji korisnik",
"Subscriptions": "Praćenja", "Subscriptions": "Praćenja",
"search_filters_date_option_today": "Danas", "search_filters_date_option_today": "Danas",
"Finnish": "Finski", "Finnish": "Finski",
@ -265,30 +265,30 @@
"Shona": "Šona", "Shona": "Šona",
"search_filters_features_option_location": "Lokacija", "search_filters_features_option_location": "Lokacija",
"Load more": "Učitaj više", "Load more": "Učitaj više",
"Released under the AGPLv3 on Github.": "Izbačeno pod licencom AGPLv3 na GitHub-u.", "Released under the AGPLv3 on Github.": "Objavljeno pod licencom AGPLv3 na GitHub-u.",
"Slovenian": "Slovenački", "Slovenian": "Slovenački",
"View JavaScript license information.": "Pogledaj informacije licence vezane za JavaScript.", "View JavaScript license information.": "Pogledaj informacije o JavaScript licenci.",
"Chinese (Simplified)": "Kineski (Pojednostavljeni)", "Chinese (Simplified)": "Kineski (Pojednostavljeni)",
"preferences_comments_label": "Podrazumevani komentari: ", "preferences_comments_label": "Podrazumevani komentari: ",
"Incorrect password": "Netačna lozinka", "Incorrect password": "Netačna lozinka",
"Show replies": "Prikaži odgovore", "Show replies": "Prikaži odgovore",
"Invidious Private Feed for `x`": "Invidious Privatni Dovod za `x`", "Invidious Private Feed for `x`": "Invidious privatni fid za `x`",
"Watch on YouTube": "Gledaj na YouTube-u", "Watch on YouTube": "Gledaj na YouTube-u",
"Wrong answer": "Pogrešan odgovor", "Wrong answer": "Pogrešan odgovor",
"preferences_quality_label": "Preferirani video kvalitet: ", "preferences_quality_label": "Preferirani kvalitet video snimka: ",
"Hide replies": "Sakrij odgovore", "Hide replies": "Sakrij odgovore",
"Erroneous CAPTCHA": "Pogrešna CAPTCHA", "Erroneous CAPTCHA": "Pogrešna CAPTCHA",
"Erroneous token": "Pogrešan žeton", "Erroneous token": "Pogrešan token",
"Czech": "Češki", "Czech": "Češki",
"Latin": "Latinski", "Latin": "Latinski",
"channel_tab_videos_label": "Video klipovi", "channel_tab_videos_label": "Video snimci",
"search_filters_features_option_four_k": "4К", "search_filters_features_option_four_k": "4К",
"footer_donate_page": "Doniraj", "footer_donate_page": "Doniraj",
"English": "Engleski", "English": "Engleski",
"Arabic": "Arapski", "Arabic": "Arapski",
"Unlisted": "Nenavedeno", "Unlisted": "Po pozivu",
"Hidden field \"challenge\" is a required field": "Sakriveno \"challenge\" polje je obavezno", "Hidden field \"challenge\" is a required field": "Skriveno polje „izazov“ je obavezno polje",
"Hidden field \"token\" is a required field": "Sakriveno \"token\" polje je obavezno", "Hidden field \"token\" is a required field": "Skriveno polje „token“ je obavezno polje",
"Georgian": "Gruzijski", "Georgian": "Gruzijski",
"Hawaiian": "Havajski", "Hawaiian": "Havajski",
"Hebrew": "Hebrejski", "Hebrew": "Hebrejski",
@ -297,68 +297,211 @@
"Japanese": "Japanski", "Japanese": "Japanski",
"Javanese": "Javanski", "Javanese": "Javanski",
"Sindhi": "Sindi", "Sindhi": "Sindi",
"Swahili": "Svahili", "Swahili": "Suvali",
"Yiddish": "Jidiš", "Yiddish": "Jidiš",
"Zulu": "Zulu", "Zulu": "Zulu",
"search_filters_features_option_subtitles": "Titl/Prevod", "search_filters_features_option_subtitles": "Titlovi/Skriveni titlovi",
"Password cannot be longer than 55 characters": "Lozinka ne može biti duža od 55 karaktera", "Password cannot be longer than 55 characters": "Lozinka ne može biti duža od 55 znakova",
"This channel does not exist.": "Ovaj kanal ne postoji.", "This channel does not exist.": "Ovaj kanal ne postoji.",
"Belarusian": "Beloruski", "Belarusian": "Beloruski",
"Gujarati": "Gudžarati", "Gujarati": "Gudžarati",
"Haitian Creole": "Haićanski Kreolski", "Haitian Creole": "Haićanski Kreolski",
"Somali": "Somalijski", "Somali": "Somalijski",
"Top": "Vrh", "Top": "Top",
"footer_modfied_source_code": "Izmenjena Izvorna Koda", "footer_modfied_source_code": "Izmenjeni izvorni kôd",
"preferences_category_subscription": "Podešavanja praćenja", "preferences_category_subscription": "Podešavanja praćenja",
"preferences_annotations_subscribed_label": "Podrazumevano prikazati napomene za kanale koje pratite? ", "preferences_annotations_subscribed_label": "Podrazumevano prikazati napomene za kanale koje pratite? ",
"preferences_max_results_label": "Broj video klipova prikazanih u dovodnoj listi: ", "preferences_max_results_label": "Broj video snimaka prikazanih u fidu: ",
"preferences_sort_label": "Sortiraj video klipove po: ", "preferences_sort_label": "Sortiraj video snimke po: ",
"preferences_unseen_only_label": "Prikaži samo video klipove koji nisu pogledani: ", "preferences_unseen_only_label": "Prikaži samo neodgledano: ",
"preferences_notifications_only_label": "Prikaži samo obaveštenja (ako ih uopšte ima): ", "preferences_notifications_only_label": "Prikaži samo obaveštenja (ako ih ima): ",
"preferences_category_data": "Podešavanja podataka", "preferences_category_data": "Podešavanja podataka",
"Clear watch history": "Obriši istoriju gledanja", "Clear watch history": "Očisti istoriju gledanja",
"preferences_category_admin": "Administratorska podešavanja", "preferences_category_admin": "Podešavanja administratora",
"published": "objavljeno", "published": "objavljeno",
"search_filters_sort_label": "Poredaj prema", "search_filters_sort_label": "Sortiranje po",
"search_filters_type_option_show": "Emisija", "search_filters_type_option_show": "Emisija",
"search_filters_duration_option_short": "Kratko (< 4 minute)", "search_filters_duration_option_short": "Kratko (< 4 minuta)",
"Current version: ": "Trenutna verzija: ", "Current version: ": "Trenutna verzija: ",
"Top enabled: ": "Vrh omogućen: ", "Top enabled: ": "Top omogućeno: ",
"Public": "Javno", "Public": "Javno",
"Delete playlist": "Obriši plej listu", "Delete playlist": "Izbriši plejlistu",
"Title": "Naslov", "Title": "Naslov",
"Show annotations": "Prikaži napomene", "Show annotations": "Prikaži napomene",
"Password cannot be empty": "Lozinka ne može biti prazna", "Password cannot be empty": "Lozinka ne može biti prazna",
"Deleted or invalid channel": "Obrisan ili nepostojeći kanal", "Deleted or invalid channel": "Izbrisan ili nevažeći kanal",
"Esperanto": "Esperanto", "Esperanto": "Esperanto",
"Hmong": "Hmong", "Hmong": "Hmong",
"Luxembourgish": "Luksemburški", "Luxembourgish": "Luksemburški",
"Nepali": "Nepalski", "Nepali": "Nepalski",
"Samoan": "Samoanski", "Samoan": "Samoanski",
"News": "Vesti", "News": "Vesti",
"permalink": "trajna veza", "permalink": "trajni link",
"Password is a required field": "Lozinka je obavezno polje", "Password is a required field": "Lozinka je obavezno polje",
"Amharic": "Amharski", "Amharic": "Amharski",
"Indonesian": "Indonežanski", "Indonesian": "Indonezijski",
"Irish": "Irski", "Irish": "Irski",
"Korean": "Korejski", "Korean": "Korejski",
"Southern Sotho": "Južni Soto", "Southern Sotho": "Južni Soto",
"Thai": "Tajski", "Thai": "Tajski",
"preferences_speed_label": "Podrazumevana brzina: ", "preferences_speed_label": "Podrazumevana brzina: ",
"Dark mode: ": "Tamni režim: ", "Dark mode: ": "Tamni režim: ",
"dark": "tamno", "dark": "tamna",
"Redirect homepage to feed: ": "Prebaci sa početne stranice na dovodnu listu: ", "Redirect homepage to feed: ": "Preusmeri početnu stranicu na fid: ",
"channel name": "ime kanala", "channel name": "ime kanala",
"View all playlists": "Pregledaj sve plej liste", "View all playlists": "Pogledaj sve plejliste",
"Show more": "Prikaži više", "Show more": "Prikaži više",
"Genre: ": "Žanr: ", "Genre: ": "Žanr: ",
"Family friendly? ": "Pogodno za porodicu? ", "Family friendly? ": "Pogodno za porodicu? ",
"next_steps_error_message_refresh": "Osveži stranicu", "next_steps_error_message_refresh": "Osvežite",
"youtube": "YouTube", "youtube": "YouTube",
"reddit": "Reddit", "reddit": "Reddit",
"unsubscribe": "prekini sa praćenjem", "unsubscribe": "prekini praćenje",
"Blacklisted regions: ": "Zabranjene oblasti: ", "Blacklisted regions: ": "Nedostupni regioni: ",
"Polish": "Poljski", "Polish": "Poljski",
"Yoruba": "Joruba", "Yoruba": "Joruba",
"search_filters_title": "Filter" "search_filters_title": "Filteri",
"Korean (auto-generated)": "Korejski (automatski generisano)",
"search_filters_features_option_three_sixty": "360°",
"preferences_quality_dash_option_worst": "Najgore",
"channel_tab_podcasts_label": "Podkasti",
"preferences_save_player_pos_label": "Sačuvaj poziciju reprodukcije: ",
"Spanish (Mexico)": "Španski (Meksiko)",
"generic_subscriptions_count_0": "{{count}} praćenje",
"generic_subscriptions_count_1": "{{count}} praćenja",
"generic_subscriptions_count_2": "{{count}} praćenja",
"search_filters_apply_button": "Primeni izabrane filtere",
"Download is disabled": "Preuzimanje je onemogućeno",
"comments_points_count_0": "{{count}} poen",
"comments_points_count_1": "{{count}} poena",
"comments_points_count_2": "{{count}} poena",
"preferences_quality_dash_option_2160p": "2160p",
"German (auto-generated)": "Nemački (automatski generisano)",
"Japanese (auto-generated)": "Japanski (automatski generisano)",
"preferences_quality_option_medium": "Srednje",
"search_message_change_filters_or_query": "Pokušajte da proširite upit za pretragu i/ili promenite filtere.",
"crash_page_before_reporting": "Pre nego što prijavite grešku, uverite se da ste:",
"preferences_quality_dash_option_best": "Najbolje",
"Channel Sponsor": "Sponzor kanala",
"generic_videos_count_0": "{{count}} video snimak",
"generic_videos_count_1": "{{count}} video snimka",
"generic_videos_count_2": "{{count}} video snimaka",
"videoinfo_started_streaming_x_ago": "Započeto strimovanje pre `x`",
"videoinfo_youTube_embed_link": "Ugrađeno",
"channel_tab_streams_label": "Strimovi uživo",
"playlist_button_add_items": "Dodaj video snimke",
"generic_count_minutes_0": "{{count}} minut",
"generic_count_minutes_1": "{{count}} minuta",
"generic_count_minutes_2": "{{count}} minuta",
"preferences_quality_dash_option_720p": "720p",
"preferences_watch_history_label": "Omogući istoriju gledanja: ",
"user_saved_playlists": "Sačuvanih plejlista: `x`",
"Spanish (Spain)": "Španski (Španija)",
"invidious": "Invidious",
"crash_page_refresh": "pokušali da <a href=\"`x`\">osvežite stranicu</a>",
"Chinese (Hong Kong)": "Kineski (Hong Kong)",
"Artist: ": "Izvođač: ",
"generic_count_months_0": "{{count}} mesec",
"generic_count_months_1": "{{count}} meseca",
"generic_count_months_2": "{{count}} meseci",
"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_1": "{{count}} pratioca",
"generic_subscribers_count_2": "{{count}} pratilaca",
"download_subtitles": "Titlovi - `x` (.vtt)",
"generic_button_save": "Sačuvaj",
"crash_page_search_issue": "pretražili <a href=\"`x`\">postojeće izveštaje o problemima na GitHub-u</a>",
"generic_button_cancel": "Otkaži",
"none": "nijedno",
"English (United States)": "Engleski (Sjedinjene Američke Države)",
"subscriptions_unseen_notifs_count_0": "{{count}} neviđeno obaveštenje",
"subscriptions_unseen_notifs_count_1": "{{count}} neviđena obaveštenja",
"subscriptions_unseen_notifs_count_2": "{{count}} neviđenih obaveštenja",
"Album: ": "Album: ",
"preferences_quality_option_dash": "DASH (adaptivni kvalitet)",
"preferences_quality_dash_option_1080p": "1080p",
"Video unavailable": "Video snimak nedostupan",
"tokens_count_0": "{{count}} token",
"tokens_count_1": "{{count}} tokena",
"tokens_count_2": "{{count}} tokena",
"Chinese (China)": "Kineski (Kina)",
"Italian (auto-generated)": "Italijanski (automatski generisano)",
"channel_tab_shorts_label": "Shorts",
"preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_option_360p": "360p",
"search_message_no_results": "Nisu pronađeni rezultati.",
"channel_tab_releases_label": "Izdanja",
"preferences_quality_dash_option_144p": "144p",
"Interlingue": "Interlingva",
"Song: ": "Pesma: ",
"generic_channels_count_0": "{{count}} kanal",
"generic_channels_count_1": "{{count}} kanala",
"generic_channels_count_2": "{{count}} kanala",
"Chinese (Taiwan)": "Kineski (Tajvan)",
"Turkish (auto-generated)": "Turski (automatski generisano)",
"Indonesian (auto-generated)": "Indonezijski (automatski generisano)",
"Portuguese (auto-generated)": "Portugalski (automatski generisano)",
"generic_count_years_0": "{{count}} godina",
"generic_count_years_1": "{{count}} godine",
"generic_count_years_2": "{{count}} godina",
"videoinfo_invidious_embed_link": "Ugrađeni link",
"Popular enabled: ": "Popularno omogućeno: ",
"Spanish (auto-generated)": "Španski (automatski generisano)",
"preferences_quality_option_small": "Malo",
"English (United Kingdom)": "Engleski (Ujedinjeno Kraljevstvo)",
"channel_tab_playlists_label": "Plejliste",
"generic_button_edit": "Izmeni",
"generic_playlists_count_0": "{{count}} plejlista",
"generic_playlists_count_1": "{{count}} plejliste",
"generic_playlists_count_2": "{{count}} plejlista",
"preferences_quality_option_hd720": "HD720",
"search_filters_features_option_purchased": "Kupljeno",
"search_filters_date_option_none": "Bilo koji datum",
"preferences_quality_dash_option_auto": "Automatski",
"Cantonese (Hong Kong)": "Kantonski (Hong Kong)",
"crash_page_report_issue": "Ako ništa od gorenavedenog nije pomoglo, <a href=\"`x`\">otvorite novi izveštaj o problemu na GitHub-u</a> (po mogućnosti na engleskom) i uključite sledeći tekst u svoju poruku (NE prevodite taj tekst):",
"crash_page_switch_instance": "pokušali da <a href=\"`x`\">koristite drugu instancu</a>",
"generic_count_weeks_0": "{{count}} nedelja",
"generic_count_weeks_1": "{{count}} nedelje",
"generic_count_weeks_2": "{{count}} nedelja",
"videoinfo_watch_on_youTube": "Gledaj na YouTube-u",
"Music in this video": "Muzika u ovom video snimku",
"generic_button_rss": "RSS",
"preferences_quality_dash_option_4320p": "4320p",
"generic_count_hours_0": "{{count}} sat",
"generic_count_hours_1": "{{count}} sata",
"generic_count_hours_2": "{{count}} sati",
"French (auto-generated)": "Francuski (automatski generisano)",
"crash_page_read_the_faq": "pročitali <a href=\"`x`\">Često Postavljana Pitanja (ČPP)</a>",
"user_created_playlists": "Napravljenih plejlista: `x`",
"channel_tab_channels_label": "Kanali",
"search_filters_type_option_all": "Bilo koja vrsta",
"Russian (auto-generated)": "Ruski (automatski generisano)",
"preferences_quality_dash_option_480p": "480p",
"comments_view_x_replies_0": "Pogledaj {{count}} odgovor",
"comments_view_x_replies_1": "Pogledaj {{count}} odgovora",
"comments_view_x_replies_2": "Pogledaj {{count}} odgovora",
"Portuguese (Brazil)": "Portugalski (Brazil)",
"search_filters_features_option_vr180": "VR180",
"error_video_not_in_playlist": "Traženi video snimak ne postoji na ovoj plejlisti. <a href=\"`x`\">Kliknite ovde za početnu stranicu plejliste.</a>",
"Dutch (auto-generated)": "Holandski (automatski generisano)",
"generic_count_days_0": "{{count}} dan",
"generic_count_days_1": "{{count}} dana",
"generic_count_days_2": "{{count}} dana",
"Vietnamese (auto-generated)": "Vijetnamski (automatski generisano)",
"search_filters_duration_option_none": "Bilo koje trajanje",
"preferences_quality_dash_option_240p": "240p",
"Chinese": "Kineski",
"generic_button_delete": "Izbriši",
"Import YouTube playlist (.csv)": "Uvezi YouTube plejlistu (.csv)",
"Standard YouTube license": "Standardna YouTube licenca",
"search_filters_duration_option_medium": "Srednje (4 - 20 minuta)",
"generic_count_seconds_0": "{{count}} sekunda",
"generic_count_seconds_1": "{{count}} sekunde",
"generic_count_seconds_2": "{{count}} sekundi",
"search_filters_date_label": "Datum otpremanja",
"crash_page_you_found_a_bug": "Izgleda da ste pronašli grešku u Invidious-u!",
"generic_views_count_0": "{{count}} pregled",
"generic_views_count_1": "{{count}} pregleda",
"generic_views_count_2": "{{count}} pregleda"
} }

View File

@ -1,166 +1,166 @@
{ {
"LIVE": "УЖИВО", "LIVE": "УЖИВО",
"Shared `x` ago": "Подељено пре `x`", "Shared `x` ago": "Дељено пре `x`",
"Unsubscribe": "Прекини праћење", "Unsubscribe": "Прекини праћење",
"Subscribe": "Прати", "Subscribe": "Запрати",
"View channel on YouTube": "Погледај канал на YouTube-у", "View channel on YouTube": "Погледај канал на YouTube-у",
"View playlist on YouTube": "Погледај списак извођења на YоуТубе-у", "View playlist on YouTube": "Погледај плејлисту на YouTube-у",
"newest": "најновије", "newest": "најновије",
"oldest": "најстарије", "oldest": "најстарије",
"popular": "популарно", "popular": "популарно",
"last": "последње", "last": "последње",
"Next page": "Следећа страна", "Next page": "Следећа страница",
"Previous page": "Претходна страна", "Previous page": "Претходна страница",
"Clear watch history?": "Избрисати повест прегледања?", "Clear watch history?": "Очистити историју гледања?",
"New password": "Нова лозинка", "New password": "Нова лозинка",
"New passwords must match": "Нове лозинке морају бити истоветне", "New passwords must match": "Нове лозинке морају да се подударају",
"Authorize token?": "Овласти жетон?", "Authorize token?": "Ауторизовати токен?",
"Authorize token for `x`?": "Овласти жетон за `x`?", "Authorize token for `x`?": "Ауторизовати токен за `x`?",
"Yes": "Да", "Yes": "Да",
"No": "Не", "No": "Не",
"Import and Export Data": "Увоз и извоз података", "Import and Export Data": "Увоз и извоз података",
"Import": "Увези", "Import": "Увези",
"Import Invidious data": "Увези податке са Individious-а", "Import Invidious data": "Увези Invidious JSON податке",
"Import YouTube subscriptions": "Увези праћења са YouTube-а", "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)",
"Import NewPipe data (.zip)": "Увези податке са NewPipe-a (.zip)", "Import NewPipe data (.zip)": "Увези NewPipe податке (.zip)",
"Export": "Извези", "Export": "Извези",
"Export subscriptions as OPML": "Извези праћења као ОПМЛ датотеку", "Export subscriptions as OPML": "Извези праћења као OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Извези праћења као ОПМЛ датотеку (за NewPipe и FreeTube)", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извези праћења као OPML (за NewPipe и FreeTube)",
"Export data as JSON": "Извези податке као JSON датотеку", "Export data as JSON": "Извези Invidious податке као JSON",
"Delete account?": "Избришите налог?", "Delete account?": "Избрисати налог?",
"History": "Историја", "History": "Историја",
"An alternative front-end to YouTube": "Заменски кориснички слој за YouTube", "An alternative front-end to YouTube": "Алтернативни фронт-енд за YouTube",
"JavaScript license information": звештај о JavaScript одобрењу", "JavaScript license information": нформације о JavaScript лиценци",
"source": "извор", "source": "извор",
"Log in": "Пријави се", "Log in": "Пријава",
"Log in/register": "Пријави се/Отворите налог", "Log in/register": "Пријава/регистрација",
"User ID": "Кориснички ИД", "User ID": "ID корисника",
"Password": "Лозинка", "Password": "Лозинка",
"Time (h:mm:ss):": "Време (ч:мм:сс):", "Time (h:mm:ss):": "Време (ч:мм:сс):",
"Text CAPTCHA": "Знаковни ЦАПТЧА", "Text CAPTCHA": "Текст CAPTCHA",
"Image CAPTCHA": "Сликовни CAPTCHA", "Image CAPTCHA": "Слика CAPTCHA",
"Sign In": "Пријава", "Sign In": "Пријава",
"Register": "Отвори налог", "Register": "Регистрација",
"E-mail": "Е-пошта", "E-mail": "Имејл",
"Preferences": "Подешавања", "Preferences": "Подешавања",
"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": "Подразумевана брзина: ",
"preferences_quality_label": "Преферирани видео квалитет: ", "preferences_quality_label": "Преферирани квалитет видео снимка: ",
"preferences_volume_label": "Јачина звука: ", "preferences_volume_label": "Јачина звука плејера: ",
"preferences_comments_label": "Подразумевани коментари: ", "preferences_comments_label": "Подразумевани коментари: ",
"youtube": "YouTube", "youtube": "YouTube",
"reddit": "Reddit", "reddit": "Reddit",
"preferences_captions_label": "Подразумевани титл: ", "preferences_captions_label": "Подразумевани титлови: ",
"Fallback captions: ": "Титл у случају да главни није доступан: ", "Fallback captions: ": "Резервни титлови: ",
"preferences_related_videos_label": "Прикажи сличне видео клипове: ", "preferences_related_videos_label": "Прикажи повезане видео снимке: ",
"preferences_annotations_label": рикажи напомене подразумевано: ", "preferences_annotations_label": одразумевано прикажи напомене: ",
"preferences_category_visual": "Визуелне преференце", "preferences_category_visual": "Визуелна подешавања",
"preferences_player_style_label": "Стил плејера: ", "preferences_player_style_label": "Стил плејера: ",
"Dark mode: ": "Тамни режим: ", "Dark mode: ": "Тамни режим: ",
"preferences_dark_mode_label": "Изглед/Тема: ", "preferences_dark_mode_label": "Тема: ",
"dark": "тамно", "dark": "тамна",
"light": "светло", "light": "светла",
"preferences_thin_mode_label": "Компактни режим: ", "preferences_thin_mode_label": "Компактни режим: ",
"preferences_category_subscription": "Подешавања праћења", "preferences_category_subscription": "Подешавања праћења",
"preferences_annotations_subscribed_label": "Подразумевано приказати напомене за канале које пратите? ", "preferences_annotations_subscribed_label": "Подразумевано приказати напомене за канале које пратите? ",
"Redirect homepage to feed: ": "Пребаци са почетне странице на доводну листу: ", "Redirect homepage to feed: ": "Преусмери почетну страницу на фид: ",
"preferences_max_results_label": "Број видео клипова приказаних у доводној листи: ", "preferences_max_results_label": "Број видео снимака приказаних у фиду: ",
"preferences_sort_label": "Сортирај видео клипове по: ", "preferences_sort_label": "Сортирај видео снимке по: ",
"published": "објављено", "published": "објављено",
"published - reverse": "објављено - обрнуто", "published - reverse": "објављено - обрнуто",
"alphabetically": "по алфабету", "alphabetically": "абецедно",
"alphabetically - reverse": "по алфабету - обрнуто", "alphabetically - reverse": "абецедно - обрнуто",
"channel name": "име канала", "channel name": "име канала",
"channel name - reverse": "име канала - обрнуто", "channel name - reverse": "име канала - обрнуто",
"Only show latest video from channel: ": "Приказуј последње видео клипове само са канала: ", "Only show latest video from channel: ": "Прикажи само најновији видео снимак са канала: ",
"Only show latest unwatched video from channel: ": "Прикажи само последње видео клипове који нису погледани са канала: ", "Only show latest unwatched video from channel: ": "Прикажи само најновији неодгледани видео снимак са канала: ",
"preferences_unseen_only_label": "Прикажи само видео клипове који нису погледани: ", "preferences_unseen_only_label": "Прикажи само недогледано: ",
"preferences_notifications_only_label": "Прикажи само обавештења (ако их уопште има): ", "preferences_notifications_only_label": "Прикажи само обавештења (ако их има): ",
"Enable web notifications": "Омогући обавештења у веб претраживачу", "Enable web notifications": "Омогући веб обавештења",
"`x` uploaded a video": "`x` је отпремио/ла видео клип", "`x` uploaded a video": "`x` је отпремио/ла видео снимак",
"`x` is live": "`x` преноси уживо", "`x` is live": "`x` је уживо",
"preferences_category_data": "Подешавања података", "preferences_category_data": "Подешавања података",
"Clear watch history": "Обриши историју гледања", "Clear watch history": "Очисти историју гледања",
"Import/export data": "Увези/Извези податке", "Import/export data": "Увези/Извези податке",
"Change password": "Промени лозинку", "Change password": "Промени лозинку",
"Manage subscriptions": "Управљај записима", "Manage subscriptions": "Управљај праћењима",
"Manage tokens": "Управљај жетонима", "Manage tokens": "Управљај токенима",
"Watch history": "Историја гледања", "Watch history": "Историја гледања",
"Delete account": "Обриши налог", "Delete account": "Избриши налог",
"preferences_category_admin": "Администраторска подешавања", "preferences_category_admin": "Подешавања администратора",
"preferences_default_home_label": "Подразумевана почетна страница: ", "preferences_default_home_label": "Подразумевана почетна страница: ",
"preferences_feed_menu_label": "Доводна страница: ", "preferences_feed_menu_label": "Фид мени: ",
"CAPTCHA enabled: ": "CAPTCHA омогућена: ", "CAPTCHA enabled: ": "CAPTCHA омогућена: ",
"Login enabled: ": "Пријава омогућена: ", "Login enabled: ": "Пријава омогућена: ",
"Registration enabled: ": "Регистрација омогућена: ", "Registration enabled: ": "Регистрација омогућена: ",
"Save preferences": "Сачувај подешавања", "Save preferences": "Сачувај подешавања",
"Subscription manager": "Управљање праћењима", "Subscription manager": "Управљање праћењима",
"Token manager": "Управљање жетонима", "Token manager": "Управљање токенима",
"Token": "Жетон", "Token": "Токен",
"Import/export": "Увези/Извези", "Import/export": "Увоз/извоз",
"unsubscribe": "прекини са праћењем", "unsubscribe": "прекини праћење",
"revoke": "опозови", "revoke": "опозови",
"Subscriptions": "Праћења", "Subscriptions": "Праћења",
"search": "претрага", "search": "претрага",
"Log out": "Одјава", "Log out": "Одјава",
"Source available here.": "Изворна кода је овде доступна.", "Source available here.": "Изворни кôд је доступан овде.",
"View JavaScript license information.": "Погледај информације лиценце везане за JavaScript.", "View JavaScript license information.": "Погледај информације о JavaScript лиценци.",
"View privacy policy.": "Погледај извештај о приватности.", "View privacy policy.": "Погледај политику приватности.",
"Trending": "У тренду", "Trending": "У тренду",
"Public": "Јавно", "Public": "Јавно",
"Unlisted": "Ненаведено", "Unlisted": "По позиву",
"Private": "Приватно", "Private": "Приватно",
"View all playlists": регледај све плеј листе", "View all playlists": огледај све плејлисте",
"Updated `x` ago": "Ажурирано пре `x`", "Updated `x` ago": "Ажурирано пре `x`",
"Delete playlist `x`?": "Обриши плеј листу `x`?", "Delete playlist `x`?": "Избрисати плејлисту `x`?",
"Delete playlist": "Обриши плеј листу", "Delete playlist": "Избриши плејлисту",
"Create playlist": "Направи плеј листу", "Create playlist": "Направи плејлисту",
"Title": "Наслов", "Title": "Наслов",
"Playlist privacy": одешавања приватности плеј листе", "Playlist privacy": риватност плејлисте",
"Editing playlist `x`": "Измена плеј листе `x`", "Editing playlist `x`": "Измењивање плејлисте `x`",
"Watch on YouTube": "Гледај на YouTube-у", "Watch on YouTube": "Гледај на YouTube-у",
"Hide annotations": "Сакриј напомене", "Hide annotations": "Сакриј напомене",
"Show annotations": "Прикажи напомене", "Show annotations": "Прикажи напомене",
"Genre: ": "Жанр: ", "Genre: ": "Жанр: ",
"License: ": "Лиценца: ", "License: ": "Лиценца: ",
"Engagement: ": "Ангажовање: ", "Engagement: ": "Ангажовање: ",
"Whitelisted regions: ": "Дозвољене области: ", "Whitelisted regions: ": "Доступни региони: ",
"Blacklisted regions: ": "Забрањене области: ", "Blacklisted regions: ": "Недоступни региони: ",
"Premieres in `x`": "Премера у `x`", "Premieres in `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.": "Хеј! Изгледа да сте онемогућили JavaScript. Кликните овде да видите коментаре, чувајте на уму да ово може да потраје дуже док се не учитају.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Хеј! Изгледа да сте искључили JavaScript. Кликните овде да бисте видели коментаре, имајте на уму да ће можда потрајати мало дуже да се учитају.",
"View YouTube comments": рикажи YouTube коментаре", "View YouTube comments": огледај YouTube коментаре",
"View more comments on Reddit": рикажи више коментара на Reddit-у", "View more comments on Reddit": огледај више коментара на Reddit-у",
"View Reddit comments": рикажи Reddit коментаре", "View Reddit comments": огледај Reddit коментаре",
"Hide replies": "Сакриј одговоре", "Hide replies": "Сакриј одговоре",
"Show replies": "Прикажи одговоре", "Show replies": "Прикажи одговоре",
"Incorrect password": "Нетачна лозинка", "Incorrect password": "Нетачна лозинка",
"Current version: ": "Тренутна верзија: ", "Current version: ": "Тренутна верзија: ",
"Wilson score: ": "Wилсонова оцена: ", "Wilson score: ": "Вилсонова оцена: ",
"Burmese": "Бурмански", "Burmese": "Бурмански",
"preferences_quality_dash_label": "Преферирани квалитет DASH видео формата: ", "preferences_quality_dash_label": "Преферирани DASH квалитет видео снимка: ",
"Erroneous token": "Погрешан жетон", "Erroneous token": "Погрешан токен",
"CAPTCHA is a required field": "CAPTCHA је обавезно поље", "CAPTCHA is a required field": "CAPTCHA је обавезно поље",
"No such user": "Непостојећи корисник", "No such user": "Не постоји корисник",
"Chinese (Traditional)": "Кинески (Традиционални)", "Chinese (Traditional)": "Кинески (Традиционални)",
"adminprefs_modified_source_code_url_label": "УРЛ веза до складишта са Измењеном Изворном Кодом", "adminprefs_modified_source_code_url_label": "URL адреса до репозиторијума измењеног изворног кода",
"Lao": "Лаоски", "Lao": "Лаоски",
"Czech": "Чешки", "Czech": "Чешки",
"Kannada": "Канада (Језик)", "Kannada": "Канада",
"Polish": "Пољски", "Polish": "Пољски",
"Cebuano": "Себуано", "Cebuano": "Цебуански",
"preferences_show_nick_label": "Прикажи надимке на врху: ", "preferences_show_nick_label": "Прикажи надимке на врху: ",
"Report statistics: ": "Извештавај о статистици: ", "Report statistics: ": "Извештавај статистике: ",
"Show more": "Прикажи више", "Show more": "Прикажи више",
"Wrong answer": "Погрешан одговор", "Wrong answer": "Погрешан одговор",
"Hidden field \"token\" is a required field": "Сакривено \"token\" поље је обавезно", "Hidden field \"token\" is a required field": "Скривено поље „токен“ је обавезно поље",
"English": "Енглески", "English": "Енглески",
"Albanian": "Албански", "Albanian": "Албански",
"Amharic": "Амхарски", "Amharic": "Амхарски",
@ -176,38 +176,38 @@
"Georgian": "Грузијски", "Georgian": "Грузијски",
"Greek": "Грчки", "Greek": "Грчки",
"Hausa": "Хауса", "Hausa": "Хауса",
"search_filters_type_option_video": "Видео", "search_filters_type_option_video": "Видео снимак",
"search_filters_type_option_playlist": "Плеј листа", "search_filters_type_option_playlist": "Плејлиста",
"search_filters_type_option_movie": "Филм", "search_filters_type_option_movie": "Филм",
"search_filters_duration_option_long": "Дуго (> 20 минута)", "search_filters_duration_option_long": "Дуго (> 20 минута)",
"search_filters_features_option_c_commons": "Creative Commons (Лиценца)", "search_filters_features_option_c_commons": "Creative Commons",
"search_filters_features_option_live": "Уживо", "search_filters_features_option_live": "Уживо",
"search_filters_features_option_location": "Локација", "search_filters_features_option_location": "Локација",
"next_steps_error_message": "Након чега би требали пробати: ", "next_steps_error_message": "Након тога би требало да покушате да: ",
"footer_donate_page": "Донирај", "footer_donate_page": "Донирај",
"footer_documentation": "Документација", "footer_documentation": "Документација",
"footer_modfied_source_code": "Измењена Изворна Кода", "footer_modfied_source_code": "Измењени изворни кôд",
"preferences_region_label": "Држава порекла садржаја: ", "preferences_region_label": "Држава садржаја: ",
"preferences_category_misc": "Остала подешавања", "preferences_category_misc": "Остала подешавања",
"User ID is a required field": "Кориснички ИД је обавезно поље", "User ID is a required field": "ID корисника је обавезно поље",
"Password is a required field": "Лозинка је обавезно поље", "Password is a required field": "Лозинка је обавезно поље",
"Wrong username or password": "Погрешно корисничко име или лозинка", "Wrong username or password": "Погрешно корисничко име или лозинка",
"Password cannot be empty": "Лозинка не може бити празна", "Password cannot be empty": "Лозинка не може бити празна",
"Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 карактера", "Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 знакова",
"Invidious Private Feed for `x`": "Инвидиоус Приватни Довод за `x`", "Invidious Private Feed for `x`": "Invidious приватни фид за `x`",
"Deleted or invalid channel": "Обрисан или непостојећи канал", "Deleted or invalid channel": "Избрисан или неважећи канал",
"This channel does not exist.": "Овај канал не постоји.", "This channel does not exist.": "Овај канал не постоји.",
"Could not create mix.": "Прављење микса није успело.", "Could not create mix.": "Није могуће направити микс.",
"Empty playlist": "Празна плеј листа", "Empty playlist": "Празна плејлиста",
"Not a playlist.": "Није плеј листа.", "Not a playlist.": "Није плејлиста.",
"Playlist does not exist.": "Непостојећа плеј листа.", "Playlist does not exist.": "Плејлиста не постоји.",
"Could not pull trending pages.": "Учитавање 'У току' страница није успело.", "Could not pull trending pages.": "Није могуће повући странице „У тренду“.",
"Hidden field \"challenge\" is a required field": "Сакривено \"challenge\" поље је обавезно", "Hidden field \"challenge\" is a required field": "Скривено поље „изазов“ је обавезно поље",
"Telugu": "Телугу", "Telugu": "Телугу",
"Turkish": "Турски", "Turkish": "Турски",
"Urdu": "Урду", "Urdu": "Урду",
"Western Frisian": "Западнофрисијски", "Western Frisian": "Западнофризијски",
"Xhosa": "Коса (Језик)", "Xhosa": "Коса (Кхоса)",
"Yiddish": "Јидиш", "Yiddish": "Јидиш",
"Hawaiian": "Хавајски", "Hawaiian": "Хавајски",
"Hmong": "Хмонг", "Hmong": "Хмонг",
@ -217,58 +217,58 @@
"Khmer": "Кмерски", "Khmer": "Кмерски",
"Kyrgyz": "Киргиски", "Kyrgyz": "Киргиски",
"Macedonian": "Македонски", "Macedonian": "Македонски",
"Maori": "Маори (Језик)", "Maori": "Маорски",
"Marathi": "Маратхи", "Marathi": "Маратски",
"Nepali": "Непалски", "Nepali": "Непалски",
"Norwegian Bokmål": "Норвешки Бокмал", "Norwegian Bokmål": "Норвешки Бокмал",
"Nyanja": "Чева", "Nyanja": "Нијанџа",
"Russian": "Руски", "Russian": "Руски",
"Scottish Gaelic": "Шкотски Гелски", "Scottish Gaelic": "Шкотски Гелски",
"Shona": "Шона", "Shona": "Шона",
"Slovak": "Словачки", "Slovak": "Словачки",
"Spanish (Latin America)": "Шпански (Јужна Америка)", "Spanish (Latin America)": "Шпански (Латинска Америка)",
"Sundanese": "Сундски", "Sundanese": "Сундански",
"Swahili": "Свахили", "Swahili": "Сували",
"Tajik": "Таџички", "Tajik": "Таџички",
"Search": "Претрага", "Search": "Претрага",
"Rating: ": "Ocena/e: ", "Rating: ": "Оцена: ",
"Default": "Подразумеван/о", "Default": "Подразумевано",
"News": "Вести", "News": "Вести",
"Download": "Преузми", "Download": "Преузми",
"(edited)": "(измењено)", "(edited)": "(измењено)",
"`x` marked it with a ❤": "`x` је означио/ла ово са ❤", "`x` marked it with a ❤": "`x` је означио/ла са ❤",
"Audio mode": "Аудио мод", "Audio mode": "Режим аудио снимка",
"channel_tab_videos_label": "Видео клипови", "channel_tab_videos_label": "Видео снимци",
"search_filters_sort_option_views": "Број прегледа", "search_filters_sort_option_views": "Број прегледа",
"search_filters_features_label": "Карактеристике", "search_filters_features_label": "Карактеристике",
"search_filters_date_option_today": "Данас", "search_filters_date_option_today": "Данас",
"%A %B %-d, %Y": "%A %B %-d, %Y", "%A %B %-d, %Y": "%A %B %-d, %Y",
"preferences_locale_label": "Језик: ", "preferences_locale_label": "Језик: ",
"Persian": "Перзијски", "Persian": "Персијски",
"View `x` comments": { "View `x` comments": {
"": рикажи `x` коментара", "": огледај `x` коментаре",
"([^.,0-9]|^)1([^.,0-9]|$)": рикажи `x` коментар" "([^.,0-9]|^)1([^.,0-9]|$)": огледај `x` коментар"
}, },
"search_filters_type_option_channel": "Канал", "search_filters_type_option_channel": "Канал",
"Haitian Creole": "Хаићански Креолски", "Haitian Creole": "Хаићански Креолски",
"Armenian": "Јерменски", "Armenian": "Јерменски",
"next_steps_error_message_go_to_youtube": "Иди на YouTube", "next_steps_error_message_go_to_youtube": "Одете на YouTube",
"Indonesian": "Индонежански", "Indonesian": "Индонезијски",
"preferences_vr_mode_label": "Интерактивни видео клипови у 360 степени: ", "preferences_vr_mode_label": "Интерактивни видео снимци од 360 степени (захтева WebGL): ",
"Switch Invidious Instance": "Промени Invidious инстанцу", "Switch Invidious Instance": "Промени Invidious инстанцу",
"Portuguese": "Португалски", "Portuguese": "Португалски",
"search_filters_date_option_week": "Ове седмице", "search_filters_date_option_week": "Ове недеље",
"search_filters_type_option_show": "Емисија", "search_filters_type_option_show": "Емисија",
"Fallback comments: ": "Коментари у случају отказивања: ", "Fallback comments: ": "Резервни коментари: ",
"search_filters_features_option_hdr": "Видео Високе Резолуције", "search_filters_features_option_hdr": "HDR",
"About": "О програму", "About": "О сајту",
"Kazakh": "Казашки", "Kazakh": "Казашки",
"Shared `x`": "Подељено `x`", "Shared `x`": "Дељено `x`",
"Playlists": "Плеј листе", "Playlists": "Плејлисте",
"Yoruba": "Јоруба", "Yoruba": "Јоруба",
"Erroneous challenge": "Погрешан изазов", "Erroneous challenge": "Погрешан изазов",
"Danish": "Дански", "Danish": "Дански",
"Could not get channel info.": "Узимање података о каналу није успело.", "Could not get channel info.": "Није могуће прикупити информације о каналу.",
"search_filters_features_option_hd": "HD", "search_filters_features_option_hd": "HD",
"Slovenian": "Словеначки", "Slovenian": "Словеначки",
"Load more": "Учитај више", "Load more": "Учитај више",
@ -276,53 +276,53 @@
"Luxembourgish": "Луксембуршки", "Luxembourgish": "Луксембуршки",
"Mongolian": "Монголски", "Mongolian": "Монголски",
"Latvian": "Летонски", "Latvian": "Летонски",
"channel:`x`": "kanal:`x`", "channel:`x`": "канал:`x`",
"Southern Sotho": "Јужни Сото", "Southern Sotho": "Јужни Сото",
"Popular": "Популарно", "Popular": "Популарно",
"Gujarati": "Гуџарати", "Gujarati": "Гуџарати",
"search_filters_date_option_year": "Ове године", "search_filters_date_option_year": "Ове године",
"Irish": "Ирски", "Irish": "Ирски",
"YouTube comment permalink": "YouTube коментар трајна веза", "YouTube comment permalink": "Трајни линк YouTube коментара",
"Malagasy": "Малгашки", "Malagasy": "Малгашки",
"Token is expired, please try again": "Жетон је истекао, молимо вас да покушате поново", "Token is expired, please try again": "Токен је истекао, покушајте поново",
"search_filters_duration_option_short": "Кратко (< 4 минуте)", "search_filters_duration_option_short": "Кратко (< 4 минута)",
"Samoan": "Самоански", "Samoan": "Самоански",
"Tamil": "Тамилски", "Tamil": "Тамилски",
"Ukrainian": "Украјински", "Ukrainian": "Украјински",
"permalink": "трајна веза", "permalink": "трајни линк",
"Pashto": "Паштунски", "Pashto": "Паштунски",
"channel_tab_community_label": "Заједница", "channel_tab_community_label": "Заједница",
"Sindhi": "Синди", "Sindhi": "Синди",
"Could not fetch comments": "Узимање коментара није успело", "Could not fetch comments": "Није могуће прикупити коментаре",
"Bangla": англа/Бенгалски", "Bangla": енгалски",
"Uzbek": "Узбечки", "Uzbek": "Узбечки",
"Lithuanian": "Литвански", "Lithuanian": "Литвански",
"Icelandic": "Исландски", "Icelandic": "Исландски",
"Thai": "Тајски", "Thai": "Тајски",
"search_filters_date_option_month": "Овај месец", "search_filters_date_option_month": "Овог месеца",
"search_filters_type_label": "Тип", "search_filters_type_label": "Врста",
"search_filters_date_option_hour": "Последњи сат", "search_filters_date_option_hour": "Последњи сат",
"Spanish": "Шпански", "Spanish": "Шпански",
"search_filters_sort_option_date": "Датум отпремања", "search_filters_sort_option_date": "Датум отпремања",
"View as playlist": "Погледај као плеј листу", "View as playlist": "Погледај као плејлисту",
"search_filters_sort_option_relevance": "Релевантност", "search_filters_sort_option_relevance": "Релевантност",
"Estonian": "Естонски", "Estonian": "Естонски",
"Sinhala": "Синхалешки", "Sinhala": "Синхалски",
"Corsican": "Корзикански", "Corsican": "Корзикански",
"Filipino": "Филипино", "Filipino": "Филипински",
"Gaming": "Игрице", "Gaming": "Видео игре",
"Movies": "Филмови", "Movies": "Филмови",
"search_filters_sort_option_rating": "Оцене", "search_filters_sort_option_rating": "Оцена",
"Top enabled: ": "Врх омогућен: ", "Top enabled: ": "Топ омогућено: ",
"Released under the AGPLv3 on Github.": "Избачено под лиценцом AGPLv3 на GitHub-у.", "Released under the AGPLv3 on Github.": "Објављено под лиценцом AGPLv3 на GitHub-у.",
"Afrikaans": "Африканс", "Afrikaans": "Африканс",
"preferences_automatic_instance_redirect_label": "Аутоматско пребацивање на другу инстанцу у случају отказивања (пречи ће назад на редирецт.инвидиоус.ио): ", "preferences_automatic_instance_redirect_label": "Аутоматско преусмеравање инстанце (повратак на redirect.invidious.io): ",
"Please log in": "Молимо вас да се пријавите", "Please log in": "Молимо, пријавите се",
"English (auto-generated)": "Енглески (аутоматски генерисано)", "English (auto-generated)": "Енглески (аутоматски генерисано)",
"Hindi": "Хинди", "Hindi": "Хинди",
"Italian": "Талијански", "Italian": "Италијански",
"Malayalam": "Малајалам", "Malayalam": "Малајаламски",
"Punjabi": унџаби", "Punjabi": анџапски",
"Somali": "Сомалијски", "Somali": "Сомалијски",
"Vietnamese": "Вијетнамски", "Vietnamese": "Вијетнамски",
"Welsh": "Велшки", "Welsh": "Велшки",
@ -330,25 +330,25 @@
"Maltese": "Малтешки", "Maltese": "Малтешки",
"Swedish": "Шведски", "Swedish": "Шведски",
"Music": "Музика", "Music": "Музика",
"Download as: ": "Преузми као: ", "Download as: ": "Преузети као: ",
"search_filters_duration_label": "Трајање", "search_filters_duration_label": "Трајање",
"search_filters_sort_label": "Поредај према", "search_filters_sort_label": "Сортирање по",
"search_filters_features_option_subtitles": "Титл/Превод", "search_filters_features_option_subtitles": "Титлови/Скривени титлови",
"preferences_extend_desc_label": "Аутоматски прикажи цео опис видеа: ", "preferences_extend_desc_label": "Аутоматски прошири опис видео снимка: ",
"Show less": "Прикажи мање", "Show less": "Прикажи мање",
"Family friendly? ": "Погодно за породицу? ", "Family friendly? ": "Погодно за породицу? ",
"Premieres `x`": "Премерe у `x`", "Premieres `x`": "Премијера `x`",
"Bosnian": "Босански", "Bosnian": "Босански",
"Catalan": "Каталонски", "Catalan": "Каталонски",
"Japanese": "Јапански", "Japanese": "Јапански",
"Latin": "Латински", "Latin": "Латински",
"next_steps_error_message_refresh": "Освежи страницу", "next_steps_error_message_refresh": "Освежите",
"footer_original_source_code": "Оригинална Изворна Кода", "footer_original_source_code": "Оригинални изворни кôд",
"Romanian": "Румунски", "Romanian": "Румунски",
"Serbian": "Српски", "Serbian": "Српски",
"Top": "Врх", "Top": "Топ",
"Video mode": "Видео мод", "Video mode": "Режим видео снимка",
"footer_source_code": "Изворна Кода", "footer_source_code": "Изворни кôд",
"search_filters_features_option_three_d": "3D", "search_filters_features_option_three_d": "3D",
"search_filters_features_option_four_k": "4K", "search_filters_features_option_four_k": "4K",
"Erroneous CAPTCHA": "Погрешна CAPTCHA", "Erroneous CAPTCHA": "Погрешна CAPTCHA",
@ -360,5 +360,148 @@
"Korean": "Корејски", "Korean": "Корејски",
"Kurdish": "Курдски", "Kurdish": "Курдски",
"Malay": "Малајски", "Malay": "Малајски",
"search_filters_title": "Филтер" "search_filters_title": "Филтери",
"Korean (auto-generated)": "Корејски (аутоматски генерисано)",
"search_filters_features_option_three_sixty": "360°",
"preferences_quality_dash_option_worst": "Најгоре",
"channel_tab_podcasts_label": "Подкасти",
"preferences_save_player_pos_label": "Сачувај позицију репродукције: ",
"Spanish (Mexico)": "Шпански (Мексико)",
"generic_subscriptions_count_0": "{{count}} праћење",
"generic_subscriptions_count_1": "{{count}} праћења",
"generic_subscriptions_count_2": "{{count}} праћења",
"search_filters_apply_button": "Примени изабране филтере",
"Download is disabled": "Преузимање је онемогућено",
"comments_points_count_0": "{{count}} поен",
"comments_points_count_1": "{{count}} поена",
"comments_points_count_2": "{{count}} поена",
"preferences_quality_dash_option_2160p": "2160p",
"German (auto-generated)": "Немачки (аутоматски генерисано)",
"Japanese (auto-generated)": "Јапански (аутоматски генерисано)",
"preferences_quality_option_medium": "Средње",
"search_message_change_filters_or_query": "Покушајте да проширите упит за претрагу и/или промените филтере.",
"crash_page_before_reporting": "Пре него што пријавите грешку, уверите се да сте:",
"preferences_quality_dash_option_best": "Најбоље",
"Channel Sponsor": "Спонзор канала",
"generic_videos_count_0": "{{count}} видео снимак",
"generic_videos_count_1": "{{count}} видео снимка",
"generic_videos_count_2": "{{count}} видео снимака",
"videoinfo_started_streaming_x_ago": "Започето стримовање пре `x`",
"videoinfo_youTube_embed_link": "Уграђено",
"channel_tab_streams_label": "Стримови уживо",
"playlist_button_add_items": "Додај видео снимке",
"generic_count_minutes_0": "{{count}} минут",
"generic_count_minutes_1": "{{count}} минута",
"generic_count_minutes_2": "{{count}} минута",
"preferences_quality_dash_option_720p": "720p",
"preferences_watch_history_label": "Омогући историју гледања: ",
"user_saved_playlists": "Сачуваних плејлиста: `x`",
"Spanish (Spain)": "Шпански (Шпанија)",
"invidious": "Invidious",
"crash_page_refresh": "покушали да <a href=\"`x`\">освежите страницу</a>",
"Chinese (Hong Kong)": "Кинески (Хонг Конг)",
"Artist: ": "Извођач: ",
"generic_count_months_0": "{{count}} месец",
"generic_count_months_1": "{{count}} месеца",
"generic_count_months_2": "{{count}} месеци",
"search_message_use_another_instance": " Такође, можете <a href=\"`x`\">претраживати на другој инстанци</a>.",
"generic_subscribers_count_0": "{{count}} пратилац",
"generic_subscribers_count_1": "{{count}} пратиоца",
"generic_subscribers_count_2": "{{count}} пратилаца",
"download_subtitles": "Титлови - `x` (.vtt)",
"generic_button_save": "Сачувај",
"crash_page_search_issue": "претражили <a href=\"`x`\">постојеће извештаје о проблемима на GitHub-у</a>",
"generic_button_cancel": "Откажи",
"none": "ниједно",
"English (United States)": "Енглески (Сједињене Америчке Државе)",
"subscriptions_unseen_notifs_count_0": "{{count}} невиђено обавештење",
"subscriptions_unseen_notifs_count_1": "{{count}} невиђена обавештења",
"subscriptions_unseen_notifs_count_2": "{{count}} невиђених обавештења",
"Album: ": "Албум: ",
"preferences_quality_option_dash": "DASH (адаптивни квалитет)",
"preferences_quality_dash_option_1080p": "1080p",
"Video unavailable": "Видео снимак недоступан",
"tokens_count_0": "{{count}} токен",
"tokens_count_1": "{{count}} токена",
"tokens_count_2": "{{count}} токена",
"Chinese (China)": "Кинески (Кина)",
"Italian (auto-generated)": "Италијански (аутоматски генерисано)",
"channel_tab_shorts_label": "Shorts",
"preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_option_360p": "360p",
"search_message_no_results": "Нису пронађени резултати.",
"channel_tab_releases_label": "Издања",
"preferences_quality_dash_option_144p": "144p",
"Interlingue": "Интерлингва",
"Song: ": "Песма: ",
"generic_channels_count_0": "{{count}} канал",
"generic_channels_count_1": "{{count}} канала",
"generic_channels_count_2": "{{count}} канала",
"Chinese (Taiwan)": "Кинески (Тајван)",
"Turkish (auto-generated)": "Турски (аутоматски генерисано)",
"Indonesian (auto-generated)": "Индонезијски (аутоматски генерисано)",
"Portuguese (auto-generated)": "Португалски (аутоматски генерисано)",
"generic_count_years_0": "{{count}} година",
"generic_count_years_1": "{{count}} године",
"generic_count_years_2": "{{count}} година",
"videoinfo_invidious_embed_link": "Уграђени линк",
"Popular enabled: ": "Популарно омогућено: ",
"Spanish (auto-generated)": "Шпански (аутоматски генерисано)",
"preferences_quality_option_small": "Мало",
"English (United Kingdom)": "Енглески (Уједињено Краљевство)",
"channel_tab_playlists_label": "Плејлисте",
"generic_button_edit": "Измени",
"generic_playlists_count_0": "{{count}} плејлиста",
"generic_playlists_count_1": "{{count}} плејлисте",
"generic_playlists_count_2": "{{count}} плејлиста",
"preferences_quality_option_hd720": "HD720",
"search_filters_features_option_purchased": "Купљено",
"search_filters_date_option_none": "Било који датум",
"preferences_quality_dash_option_auto": "Аутоматски",
"Cantonese (Hong Kong)": "Кантонски (Хонг Конг)",
"crash_page_report_issue": "Ако ништа од горенаведеног није помогло, <a href=\"`x`\">отворите нови извештај о проблему на GitHub-у</a> (по могућности на енглеском) и укључите следећи текст у своју поруку (НЕ преводите тај текст):",
"crash_page_switch_instance": "покушали да <a href=\"`x`\">користите другу инстанцу</a>",
"generic_count_weeks_0": "{{count}} недеља",
"generic_count_weeks_1": "{{count}} недеље",
"generic_count_weeks_2": "{{count}} недеља",
"videoinfo_watch_on_youTube": "Гледај на YouTube-у",
"Music in this video": "Музика у овом видео снимку",
"generic_button_rss": "RSS",
"preferences_quality_dash_option_4320p": "4320p",
"generic_count_hours_0": "{{count}} сат",
"generic_count_hours_1": "{{count}} сата",
"generic_count_hours_2": "{{count}} сати",
"French (auto-generated)": "Француски (аутоматски генерисано)",
"crash_page_read_the_faq": "прочитали <a href=\"`x`\">Често Постављана Питања (ЧПП)</a>",
"user_created_playlists": "Направљених плејлиста: `x`",
"channel_tab_channels_label": "Канали",
"search_filters_type_option_all": "Било која врста",
"Russian (auto-generated)": "Руски (аутоматски генерисано)",
"preferences_quality_dash_option_480p": "480p",
"comments_view_x_replies_0": "Погледај {{count}} одговор",
"comments_view_x_replies_1": "Погледај {{count}} одговора",
"comments_view_x_replies_2": "Погледај {{count}} одговора",
"Portuguese (Brazil)": "Португалски (Бразил)",
"search_filters_features_option_vr180": "VR180",
"error_video_not_in_playlist": "Тражени видео снимак не постоји на овој плејлисти. <a href=\"`x`\">Кликните овде за почетну страницу плејлисте.</a>",
"Dutch (auto-generated)": "Холандски (аутоматски генерисано)",
"generic_count_days_0": "{{count}} дан",
"generic_count_days_1": "{{count}} дана",
"generic_count_days_2": "{{count}} дана",
"Vietnamese (auto-generated)": "Вијетнамски (аутоматски генерисано)",
"search_filters_duration_option_none": "Било које трајање",
"preferences_quality_dash_option_240p": "240p",
"Chinese": "Кинески",
"generic_button_delete": "Избриши",
"Import YouTube playlist (.csv)": "Увези YouTube плејлисту (.csv)",
"Standard YouTube license": "Стандардна YouTube лиценца",
"search_filters_duration_option_medium": "Средње (4 - 20 минута)",
"generic_count_seconds_0": "{{count}} секунда",
"generic_count_seconds_1": "{{count}} секунде",
"generic_count_seconds_2": "{{count}} секунди",
"search_filters_date_label": "Датум отпремања",
"crash_page_you_found_a_bug": "Изгледа да сте пронашли грешку у Invidious-у!",
"generic_views_count_0": "{{count}} преглед",
"generic_views_count_1": "{{count}} прегледа",
"generic_views_count_2": "{{count}} прегледа"
} }

View File

@ -484,5 +484,7 @@
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"channel_tab_releases_label": "Yayınlar", "channel_tab_releases_label": "Yayınlar",
"playlist_button_add_items": "Video ekle", "playlist_button_add_items": "Video ekle",
"channel_tab_podcasts_label": "Podcast'ler" "channel_tab_podcasts_label": "Podcast'ler",
"generic_channels_count": "{{count}} kanal",
"generic_channels_count_plural": "{{count}} kanal"
} }

View File

@ -500,5 +500,8 @@
"channel_tab_releases_label": "Випуски", "channel_tab_releases_label": "Випуски",
"generic_button_delete": "Видалити", "generic_button_delete": "Видалити",
"generic_button_edit": "Змінити", "generic_button_edit": "Змінити",
"generic_button_save": "Зберегти" "generic_button_save": "Зберегти",
"generic_channels_count_0": "{{count}} канал",
"generic_channels_count_1": "{{count}} канали",
"generic_channels_count_2": "{{count}} каналів"
} }

View File

@ -461,6 +461,7 @@
"Standard YouTube license": "标准 YouTube 许可证", "Standard YouTube license": "标准 YouTube 许可证",
"Download is disabled": "已禁用下载", "Download is disabled": "已禁用下载",
"Import YouTube playlist (.csv)": "导入 YouTube 播放列表(.csv", "Import YouTube playlist (.csv)": "导入 YouTube 播放列表(.csv",
"Import YouTube watch history (.json)": "导入 YouTube 观看历史(.json",
"generic_button_cancel": "取消", "generic_button_cancel": "取消",
"playlist_button_add_items": "添加视频", "playlist_button_add_items": "添加视频",
"generic_button_delete": "删除", "generic_button_delete": "删除",
@ -468,5 +469,6 @@
"generic_button_edit": "编辑", "generic_button_edit": "编辑",
"generic_button_save": "保存", "generic_button_save": "保存",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"channel_tab_releases_label": "公告" "channel_tab_releases_label": "公告",
"generic_channels_count_0": "{{count}} 个频道"
} }

View File

@ -461,6 +461,7 @@
"Standard YouTube license": "標準 YouTube 授權條款", "Standard YouTube license": "標準 YouTube 授權條款",
"Download is disabled": "已停用下載", "Download is disabled": "已停用下載",
"Import YouTube playlist (.csv)": "匯入 YouTube 播放清單 (.csv)", "Import YouTube playlist (.csv)": "匯入 YouTube 播放清單 (.csv)",
"Import YouTube watch history (.json)": "匯入 YouTube 觀看歷史 (.json)",
"generic_button_cancel": "取消", "generic_button_cancel": "取消",
"generic_button_edit": "編輯", "generic_button_edit": "編輯",
"generic_button_save": "儲存", "generic_button_save": "儲存",
@ -468,5 +469,6 @@
"generic_button_delete": "刪除", "generic_button_delete": "刪除",
"playlist_button_add_items": "新增影片", "playlist_button_add_items": "新增影片",
"channel_tab_podcasts_label": "Podcast", "channel_tab_podcasts_label": "Podcast",
"channel_tab_releases_label": "發布" "channel_tab_releases_label": "發布",
"generic_channels_count_0": "{{count}} 個頻道"
} }

View File

@ -0,0 +1,64 @@
require "../../spec_helper.cr"
MockLines = [
{
"start_time": Time::Span.new(seconds: 1),
"end_time": Time::Span.new(seconds: 2),
"text": "Line 1",
},
{
"start_time": Time::Span.new(seconds: 2),
"end_time": Time::Span.new(seconds: 3),
"text": "Line 2",
},
]
Spectator.describe "WebVTT::Builder" do
it "correctly builds a vtt file" do
result = WebVTT.build do |vtt|
MockLines.each do |line|
vtt.cue(line["start_time"], line["end_time"], line["text"])
end
end
expect(result).to eq([
"WEBVTT",
"",
"00:00:01.000 --> 00:00:02.000",
"Line 1",
"",
"00:00:02.000 --> 00:00:03.000",
"Line 2",
"",
"",
].join('\n'))
end
it "correctly builds a vtt file with setting fields" do
setting_fields = {
"Kind" => "captions",
"Language" => "en",
}
result = WebVTT.build(setting_fields) do |vtt|
MockLines.each do |line|
vtt.cue(line["start_time"], line["end_time"], line["text"])
end
end
expect(result).to eq([
"WEBVTT",
"Kind: captions",
"Language: en",
"",
"00:00:01.000 --> 00:00:02.000",
"Line 1",
"",
"00:00:02.000 --> 00:00:03.000",
"Line 2",
"",
"",
].join('\n'))
end
end

View File

@ -15,12 +15,15 @@ FORM_TESTS = {
"ar" => I18next::Plurals::PluralForms::Special_Arabic, "ar" => I18next::Plurals::PluralForms::Special_Arabic,
"be" => I18next::Plurals::PluralForms::Dual_Slavic, "be" => I18next::Plurals::PluralForms::Dual_Slavic,
"cy" => I18next::Plurals::PluralForms::Special_Welsh, "cy" => I18next::Plurals::PluralForms::Special_Welsh,
"fr" => I18next::Plurals::PluralForms::Special_French_Portuguese,
"en" => I18next::Plurals::PluralForms::Single_not_one, "en" => I18next::Plurals::PluralForms::Single_not_one,
"fr" => I18next::Plurals::PluralForms::Single_gt_one, "es" => I18next::Plurals::PluralForms::Single_not_one,
"ga" => I18next::Plurals::PluralForms::Special_Irish, "ga" => I18next::Plurals::PluralForms::Special_Irish,
"gd" => I18next::Plurals::PluralForms::Special_Scottish_Gaelic, "gd" => I18next::Plurals::PluralForms::Special_Scottish_Gaelic,
"he" => I18next::Plurals::PluralForms::Special_Hebrew, "he" => I18next::Plurals::PluralForms::Special_Hebrew,
"hr" => I18next::Plurals::PluralForms::Special_Hungarian_Serbian,
"is" => I18next::Plurals::PluralForms::Special_Icelandic, "is" => I18next::Plurals::PluralForms::Special_Icelandic,
"it" => I18next::Plurals::PluralForms::Special_Spanish_Italian,
"jv" => I18next::Plurals::PluralForms::Special_Javanese, "jv" => I18next::Plurals::PluralForms::Special_Javanese,
"kw" => I18next::Plurals::PluralForms::Special_Cornish, "kw" => I18next::Plurals::PluralForms::Special_Cornish,
"lt" => I18next::Plurals::PluralForms::Special_Lithuanian, "lt" => I18next::Plurals::PluralForms::Special_Lithuanian,
@ -31,12 +34,12 @@ FORM_TESTS = {
"or" => I18next::Plurals::PluralForms::Special_Odia, "or" => I18next::Plurals::PluralForms::Special_Odia,
"pl" => I18next::Plurals::PluralForms::Special_Polish_Kashubian, "pl" => I18next::Plurals::PluralForms::Special_Polish_Kashubian,
"pt" => I18next::Plurals::PluralForms::Single_gt_one, "pt" => I18next::Plurals::PluralForms::Single_gt_one,
"pt-PT" => I18next::Plurals::PluralForms::Single_not_one, "pt-BR" => I18next::Plurals::PluralForms::Special_French_Portuguese,
"pt-BR" => I18next::Plurals::PluralForms::Single_gt_one,
"ro" => I18next::Plurals::PluralForms::Special_Romanian, "ro" => I18next::Plurals::PluralForms::Special_Romanian,
"su" => I18next::Plurals::PluralForms::None,
"sk" => I18next::Plurals::PluralForms::Special_Czech_Slovak, "sk" => I18next::Plurals::PluralForms::Special_Czech_Slovak,
"sl" => I18next::Plurals::PluralForms::Special_Slovenian, "sl" => I18next::Plurals::PluralForms::Special_Slovenian,
"su" => I18next::Plurals::PluralForms::None,
"sr" => I18next::Plurals::PluralForms::Special_Hungarian_Serbian,
} }
SUFFIX_TESTS = { SUFFIX_TESTS = {
@ -73,10 +76,18 @@ SUFFIX_TESTS = {
{num: 1, suffix: ""}, {num: 1, suffix: ""},
{num: 10, suffix: "_plural"}, {num: 10, suffix: "_plural"},
], ],
"fr" => [ "es" => [
{num: 0, suffix: ""}, {num: 0, suffix: "_plural"},
{num: 1, suffix: ""}, {num: 1, suffix: ""},
{num: 10, suffix: "_plural"}, {num: 10, suffix: "_plural"},
{num: 6_000_000, suffix: "_plural"},
],
"fr" => [
{num: 0, suffix: "_0"},
{num: 1, suffix: "_0"},
{num: 10, suffix: "_2"},
{num: 4_000_000, suffix: "_1"},
{num: 6_260_000, suffix: "_2"},
], ],
"ga" => [ "ga" => [
{num: 1, suffix: "_0"}, {num: 1, suffix: "_0"},
@ -155,31 +166,24 @@ SUFFIX_TESTS = {
{num: 1, suffix: "_0"}, {num: 1, suffix: "_0"},
{num: 5, suffix: "_2"}, {num: 5, suffix: "_2"},
], ],
"pt" => [ "pt-BR" => [
{num: 0, suffix: ""}, {num: 0, suffix: "_0"},
{num: 1, suffix: ""}, {num: 1, suffix: "_0"},
{num: 10, suffix: "_plural"}, {num: 10, suffix: "_2"},
{num: 42, suffix: "_2"},
{num: 9_000_000, suffix: "_1"},
], ],
"pt-PT" => [ "pt-PT" => [
{num: 0, suffix: "_plural"},
{num: 1, suffix: ""},
{num: 10, suffix: "_plural"},
],
"pt-BR" => [
{num: 0, suffix: ""}, {num: 0, suffix: ""},
{num: 1, suffix: ""}, {num: 1, suffix: ""},
{num: 10, suffix: "_plural"}, {num: 10, suffix: "_plural"},
{num: 9_000_000, suffix: "_plural"},
], ],
"ro" => [ "ro" => [
{num: 0, suffix: "_1"}, {num: 0, suffix: "_1"},
{num: 1, suffix: "_0"}, {num: 1, suffix: "_0"},
{num: 20, suffix: "_2"}, {num: 20, suffix: "_2"},
], ],
"su" => [
{num: 0, suffix: "_0"},
{num: 1, suffix: "_0"},
{num: 10, suffix: "_0"},
],
"sk" => [ "sk" => [
{num: 0, suffix: "_2"}, {num: 0, suffix: "_2"},
{num: 1, suffix: "_0"}, {num: 1, suffix: "_0"},
@ -191,6 +195,18 @@ SUFFIX_TESTS = {
{num: 2, suffix: "_2"}, {num: 2, suffix: "_2"},
{num: 3, suffix: "_3"}, {num: 3, suffix: "_3"},
], ],
"su" => [
{num: 0, suffix: "_0"},
{num: 1, suffix: "_0"},
{num: 10, suffix: "_0"},
],
"sr" => [
{num: 1, suffix: "_0"},
{num: 51, suffix: "_0"},
{num: 32, suffix: "_1"},
{num: 100, suffix: "_2"},
{num: 100_000, suffix: "_2"},
],
} }
Spectator.describe "i18next_Plural_Resolver" do Spectator.describe "i18next_Plural_Resolver" do

View File

@ -24,7 +24,33 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode) return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
end end
def extract_channel_community(items, *, ucid, locale, format, thin_mode) def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
object = {
"2:string" => "community",
"25:embedded" => {
"22:string" => post_id.to_s,
},
"45:embedded" => {
"2:varint" => 1_i64,
"3:varint" => 1_i64,
},
}
params = 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) }
initial_data = YoutubeAPI.browse(ucid, params: params)
items = [] of JSON::Any
extract_items(initial_data) do |item|
items << item
end
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode, is_single_post: true)
end
def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_single_post : Bool = false)
if message = items[0]["messageRenderer"]? if message = items[0]["messageRenderer"]?
error_message = (message["text"]["simpleText"]? || error_message = (message["text"]["simpleText"]? ||
message["text"]["runs"]?.try &.[0]?.try &.["text"]?) message["text"]["runs"]?.try &.[0]?.try &.["text"]?)
@ -39,6 +65,9 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode)
response = JSON.build do |json| response = JSON.build do |json|
json.object do json.object do
json.field "authorId", ucid json.field "authorId", ucid
if is_single_post
json.field "singlePost", true
end
json.field "comments" do json.field "comments" do
json.array do json.array do
items.each do |post| items.each do |post|
@ -240,11 +269,13 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode)
end end
end end
end end
if !is_single_post
if cont = items.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token") if cont = items.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token")
json.field "continuation", extract_channel_community_cursor(cont.as_s) json.field "continuation", extract_channel_community_cursor(cont.as_s)
end end
end end
end end
end
if format == "html" if format == "html"
response = JSON.parse(response) response = JSON.parse(response)

View File

@ -13,6 +13,51 @@ module Invidious::Comments
client_config = YoutubeAPI::ClientConfig.new(region: region) client_config = YoutubeAPI::ClientConfig.new(region: region)
response = YoutubeAPI.next(continuation: ctoken, client_config: client_config) response = YoutubeAPI.next(continuation: ctoken, client_config: client_config)
return parse_youtube(id, response, format, locale, thin_mode, sort_by)
end
def fetch_community_post_comments(ucid, post_id)
object = {
"2:string" => "community",
"25:embedded" => {
"22:string" => post_id,
},
"45:embedded" => {
"2:varint" => 1_i64,
"3:varint" => 1_i64,
},
"53:embedded" => {
"4:embedded" => {
"6:varint" => 0_i64,
"27:varint" => 1_i64,
"29:string" => post_id,
"30:string" => ucid,
},
"8:string" => "comments-section",
},
}
object_parsed = object.try { |i| Protodec::Any.cast_json(i) }
.try { |i| Protodec::Any.from_json(i) }
.try { |i| Base64.urlsafe_encode(i) }
object2 = {
"80226972:embedded" => {
"2:string" => ucid,
"3:string" => object_parsed,
},
}
continuation = object2.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) }
initial_data = YoutubeAPI.browse(continuation: continuation)
return initial_data
end
def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", isPost = false)
contents = nil contents = nil
if on_response_received_endpoints = response["onResponseReceivedEndpoints"]? if on_response_received_endpoints = response["onResponseReceivedEndpoints"]?
@ -68,7 +113,11 @@ module Invidious::Comments
json.field "commentCount", comment_count json.field "commentCount", comment_count
end end
if isPost
json.field "postId", id
else
json.field "videoId", id json.field "videoId", id
end
json.field "comments" do json.field "comments" do
json.array do json.array do

View File

@ -84,6 +84,7 @@ class Config
# Used to tell Invidious it is behind a proxy, so links to resources should be https:// # Used to tell Invidious it is behind a proxy, so links to resources should be https://
property https_only : Bool? property https_only : Bool?
property login_only : Bool?
# HMAC signing key for CSRF tokens and verifying pubsub subscriptions # HMAC signing key for CSRF tokens and verifying pubsub subscriptions
property hmac_key : String = "" property hmac_key : String = ""
# Domain to be used for links to resources on the site where an absolute URL is required # Domain to be used for links to resources on the site where an absolute URL is required
@ -127,6 +128,9 @@ class Config
# 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
# Use Innertube's transcripts API instead of timedtext for closed captions
property use_innertube_for_captions : Bool = false
# 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

View File

@ -23,6 +23,24 @@ module Invidious::Frontend::Comments
</div> </div>
</div> </div>
END_HTML END_HTML
elsif comments["authorId"]? && !comments["singlePost"]?
# for posts we should display a link to the post
replies_count_text = translate_count(locale,
"comments_view_x_replies",
child["replyCount"].as_i64 || 0,
NumberFormatting::Separator
)
replies_html = <<-END_HTML
<div class="pure-g">
<div class="pure-u-1-24"></div>
<div class="pure-u-23-24">
<p>
<a href="/post/#{child["commentId"]}?ucid=#{comments["authorId"]}">#{replies_count_text}</a>
</p>
</div>
</div>
END_HTML
end end
if !thin_mode if !thin_mode

View File

@ -7,7 +7,7 @@ module Invidious::Frontend::WatchPage
getter full_videos : Array(Hash(String, JSON::Any)) getter full_videos : Array(Hash(String, JSON::Any))
getter video_streams : Array(Hash(String, JSON::Any)) getter video_streams : Array(Hash(String, JSON::Any))
getter audio_streams : Array(Hash(String, JSON::Any)) getter audio_streams : Array(Hash(String, JSON::Any))
getter captions : Array(Invidious::Videos::Caption) getter captions : Array(Invidious::Videos::Captions::Metadata)
def initialize( def initialize(
@full_videos, @full_videos,
@ -42,8 +42,7 @@ module Invidious::Frontend::WatchPage
str << translate(locale, "Download as: ") str << translate(locale, "Download as: ")
str << "</label>\n" str << "</label>\n"
# TODO: remove inline style str << "\t\t<select name='download_widget' id='download_widget'>\n"
str << "\t\t<select style=\"width:100%\" name='download_widget' id='download_widget'>\n"
# Non-DASH videos (audio+video) # Non-DASH videos (audio+video)

View File

@ -35,19 +35,27 @@ module I18next::Plurals
Special_Slovenian = 21 Special_Slovenian = 21
Special_Hebrew = 22 Special_Hebrew = 22
Special_Odia = 23 Special_Odia = 23
# Mixed v3/v4 rules in Weblate
# `es`, `pt` and `pt-PT` doesn't seem to have been refreshed
# by weblate yet, but I suspect it will happen one day.
# See: https://github.com/translate/translate/issues/4873
Special_French_Portuguese
Special_Hungarian_Serbian
Special_Spanish_Italian
end end
private PLURAL_SETS = { private PLURAL_SETS = {
PluralForms::Single_gt_one => [ PluralForms::Single_gt_one => [
"ach", "ak", "am", "arn", "br", "fil", "fr", "gun", "ln", "mfe", "mg", "ach", "ak", "am", "arn", "br", "fil", "gun", "ln", "mfe", "mg",
"mi", "oc", "pt", "pt-BR", "tg", "tl", "ti", "tr", "uz", "wa", "mi", "oc", "pt", "tg", "tl", "ti", "tr", "uz", "wa",
], ],
PluralForms::Single_not_one => [ PluralForms::Single_not_one => [
"af", "an", "ast", "az", "bg", "bn", "ca", "da", "de", "dev", "el", "en", "af", "an", "ast", "az", "bg", "bn", "ca", "da", "de", "dev", "el", "en",
"eo", "es", "et", "eu", "fi", "fo", "fur", "fy", "gl", "gu", "ha", "hi", "eo", "es", "et", "eu", "fi", "fo", "fur", "fy", "gl", "gu", "ha", "hi",
"hu", "hy", "ia", "it", "kk", "kn", "ku", "lb", "mai", "ml", "mn", "mr", "hu", "hy", "ia", "kk", "kn", "ku", "lb", "mai", "ml", "mn", "mr",
"nah", "nap", "nb", "ne", "nl", "nn", "no", "nso", "pa", "pap", "pms", "nah", "nap", "nb", "ne", "nl", "nn", "no", "nso", "pa", "pap", "pms",
"ps", "pt-PT", "rm", "sco", "se", "si", "so", "son", "sq", "sv", "sw", "ps", "rm", "sco", "se", "si", "so", "son", "sq", "sv", "sw",
"ta", "te", "tk", "ur", "yo", "ta", "te", "tk", "ur", "yo",
], ],
PluralForms::None => [ PluralForms::None => [
@ -55,7 +63,7 @@ module I18next::Plurals
"lo", "ms", "sah", "su", "th", "tt", "ug", "vi", "wo", "zh", "lo", "ms", "sah", "su", "th", "tt", "ug", "vi", "wo", "zh",
], ],
PluralForms::Dual_Slavic => [ PluralForms::Dual_Slavic => [
"be", "bs", "cnr", "dz", "hr", "ru", "sr", "uk", "be", "bs", "cnr", "dz", "ru", "uk",
], ],
} }
@ -81,6 +89,12 @@ module I18next::Plurals
"ro" => PluralForms::Special_Romanian, "ro" => PluralForms::Special_Romanian,
"sk" => PluralForms::Special_Czech_Slovak, "sk" => PluralForms::Special_Czech_Slovak,
"sl" => PluralForms::Special_Slovenian, "sl" => PluralForms::Special_Slovenian,
# Mixed v3/v4 rules
"fr" => PluralForms::Special_French_Portuguese,
"hr" => PluralForms::Special_Hungarian_Serbian,
"it" => PluralForms::Special_Spanish_Italian,
"pt-BR" => PluralForms::Special_French_Portuguese,
"sr" => PluralForms::Special_Hungarian_Serbian,
} }
# These are the v1 and v2 compatible suffixes. # These are the v1 and v2 compatible suffixes.
@ -150,9 +164,8 @@ module I18next::Plurals
end end
def get_plural_form(locale : String) : PluralForms def get_plural_form(locale : String) : PluralForms
# Extract the ISO 639-1 or 639-2 code from an RFC 5646 language code, # Extract the ISO 639-1 or 639-2 code from an RFC 5646 language code
# except for pt-BR and pt-PT which needs to be kept as-is. if !locale.matches?(/^pt-BR$/)
if !locale.matches?(/^pt-(BR|PT)$/)
locale = locale.split('-')[0] locale = locale.split('-')[0]
end end
@ -246,6 +259,10 @@ module I18next::Plurals
when .special_slovenian? then return special_slovenian(count) when .special_slovenian? then return special_slovenian(count)
when .special_hebrew? then return special_hebrew(count) when .special_hebrew? then return special_hebrew(count)
when .special_odia? then return special_odia(count) when .special_odia? then return special_odia(count)
# Mixed v3/v4 forms
when .special_spanish_italian? then return special_cldr_Spanish_Italian(count)
when .special_french_portuguese? then return special_cldr_French_Portuguese(count)
when .special_hungarian_serbian? then return special_cldr_Hungarian_Serbian(count)
else else
# default, if nothing matched above # default, if nothing matched above
return 0_u8 return 0_u8
@ -507,5 +524,42 @@ module I18next::Plurals
def self.special_odia(count : Int) : UInt8 def self.special_odia(count : Int) : UInt8
return (count == 1) ? 0_u8 : 1_u8 return (count == 1) ? 0_u8 : 1_u8
end end
# -------------------
# "v3.5" rules
# -------------------
# Plural form for Spanish & Italian languages
#
# This rule is mostly compliant to CLDR v42
#
def self.special_cldr_Spanish_Italian(count : Int) : UInt8
return 0_u8 if (count == 1) # one
return 1_u8 if (count != 0 && count % 1_000_000 == 0) # many
return 2_u8 # other
end
# Plural form for French and Portuguese
#
# This rule is mostly compliant to CLDR v42
#
def self.special_cldr_French_Portuguese(count : Int) : UInt8
return 0_u8 if (count == 0 || count == 1) # one
return 1_u8 if (count % 1_000_000 == 0) # many
return 2_u8 # other
end
# Plural form for Hungarian and Serbian
#
# This rule is mostly compliant to CLDR v42
#
def self.special_cldr_Hungarian_Serbian(count : Int) : UInt8
n_mod_10 = count % 10
n_mod_100 = count % 100
return 0_u8 if (n_mod_10 == 1 && n_mod_100 != 11) # one
return 1_u8 if (2 <= n_mod_10 <= 4 && (n_mod_100 < 12 || 14 < n_mod_100)) # few
return 2_u8 # other
end
end end
end end

View File

@ -186,6 +186,7 @@ struct SearchChannel
property author_thumbnail : String property author_thumbnail : String
property subscriber_count : Int32 property subscriber_count : Int32
property video_count : Int32 property video_count : Int32
property channel_handle : String?
property description_html : String property description_html : String
property auto_generated : Bool property auto_generated : Bool
property author_verified : Bool property author_verified : Bool
@ -214,6 +215,7 @@ struct SearchChannel
json.field "autoGenerated", self.auto_generated json.field "autoGenerated", self.auto_generated
json.field "subCount", self.subscriber_count json.field "subCount", self.subscriber_count
json.field "videoCount", self.video_count json.field "videoCount", self.video_count
json.field "channelHandle", self.channel_handle
json.field "description", html_to_content(self.description_html) json.field "description", html_to_content(self.description_html)
json.field "descriptionHtml", self.description_html json.field "descriptionHtml", self.description_html

View File

@ -0,0 +1,67 @@
# Namespace for logic relating to generating WebVTT files
#
# Probably not compliant to WebVTT's specs but it is enough for Invidious.
module WebVTT
# A WebVTT builder generates WebVTT files
private class Builder
def initialize(@io : IO)
end
# Writes an vtt cue with the specified time stamp and contents
def cue(start_time : Time::Span, end_time : Time::Span, text : String)
timestamp(start_time, end_time)
@io << text
@io << "\n\n"
end
private def timestamp(start_time : Time::Span, end_time : Time::Span)
timestamp_component(start_time)
@io << " --> "
timestamp_component(end_time)
@io << '\n'
end
private def timestamp_component(timestamp : Time::Span)
@io << timestamp.hours.to_s.rjust(2, '0')
@io << ':' << timestamp.minutes.to_s.rjust(2, '0')
@io << ':' << timestamp.seconds.to_s.rjust(2, '0')
@io << '.' << timestamp.milliseconds.to_s.rjust(3, '0')
end
def document(setting_fields : Hash(String, String)? = nil, &)
@io << "WEBVTT\n"
if setting_fields
setting_fields.each do |name, value|
@io << name << ": " << value << '\n'
end
end
@io << '\n'
yield
end
end
# Returns the resulting `String` of writing WebVTT to the yielded `WebVTT::Builder`
#
# ```
# string = WebVTT.build do |vtt|
# vtt.cue(Time::Span.new(seconds: 1), Time::Span.new(seconds: 2), "Line 1")
# vtt.cue(Time::Span.new(seconds: 2), Time::Span.new(seconds: 3), "Line 2")
# end
#
# string # => "WEBVTT\n\n00:00:01.000 --> 00:00:02.000\nLine 1\n\n00:00:02.000 --> 00:00:03.000\nLine 2\n\n"
# ```
#
# Accepts an optional settings fields hash to add settings attribute to the resulting vtt file.
def self.build(setting_fields : Hash(String, String)? = nil, &)
String.build do |str|
builder = Builder.new(str)
builder.document(setting_fields) do
yield builder
end
end
end
end

View File

@ -39,6 +39,7 @@ module Invidious::JSONify::APIv1
json.field "author", video.author json.field "author", video.author
json.field "authorId", video.ucid json.field "authorId", video.ucid
json.field "authorUrl", "/channel/#{video.ucid}" json.field "authorUrl", "/channel/#{video.ucid}"
json.field "authorVerified", video.author_verified
json.field "authorThumbnails" do json.field "authorThumbnails" do
json.array do json.array do

View File

@ -89,6 +89,7 @@ struct Playlist
property views : Int64 property views : Int64
property updated : Time property updated : Time
property thumbnail : String? property thumbnail : String?
property subtitle : String?
def to_json(offset, json : JSON::Builder, video_id : String? = nil) def to_json(offset, json : JSON::Builder, video_id : String? = nil)
json.object do json.object do
@ -100,6 +101,7 @@ struct Playlist
json.field "author", self.author json.field "author", self.author
json.field "authorId", self.ucid json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}" json.field "authorUrl", "/channel/#{self.ucid}"
json.field "subtitle", self.subtitle
json.field "authorThumbnails" do json.field "authorThumbnails" do
json.array do json.array do
@ -356,6 +358,8 @@ def fetch_playlist(plid : String)
updated = Time.utc updated = Time.utc
video_count = 0 video_count = 0
subtitle = extract_text(initial_data.dig?("header", "playlistHeaderRenderer", "subtitle"))
playlist_info["stats"]?.try &.as_a.each do |stat| playlist_info["stats"]?.try &.as_a.each do |stat|
text = stat["runs"]?.try &.as_a.map(&.["text"].as_s).join("") || stat["simpleText"]?.try &.as_s text = stat["runs"]?.try &.as_a.map(&.["text"].as_s).join("") || stat["simpleText"]?.try &.as_s
next if !text next if !text
@ -397,6 +401,7 @@ def fetch_playlist(plid : String)
views: views, views: views,
updated: updated, updated: updated,
thumbnail: thumbnail, thumbnail: thumbnail,
subtitle: subtitle,
}) })
end end

View File

@ -343,6 +343,59 @@ module Invidious::Routes::API::V1::Channels
end end
end end
def self.post(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
id = env.params.url["id"].to_s
ucid = env.params.query["ucid"]?
thin_mode = env.params.query["thin_mode"]?
thin_mode = thin_mode == "true"
format = env.params.query["format"]?
format ||= "json"
if ucid.nil?
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
return error_json(400, "Invalid post ID") if response["error"]?
ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s
else
ucid = ucid.to_s
end
begin
fetch_channel_community_post(ucid, id, locale, format, thin_mode)
rescue ex
return error_json(500, ex)
end
end
def self.post_comments(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
id = env.params.url["id"]
thin_mode = env.params.query["thin_mode"]?
thin_mode = thin_mode == "true"
format = env.params.query["format"]?
format ||= "json"
continuation = env.params.query["continuation"]?
case continuation
when nil, ""
ucid = env.params.query["ucid"]
comments = Comments.fetch_community_post_comments(ucid, id)
else
comments = YoutubeAPI.browse(continuation: continuation)
end
return Comments.parse_youtube(id, comments, format, locale, thin_mode, isPost: true)
end
def self.channels(env) def self.channels(env)
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
ucid = env.params.url["ucid"] ucid = env.params.url["ucid"]

View File

@ -162,17 +162,20 @@ module Invidious::Routes::API::V1::Misc
resolved_url = YoutubeAPI.resolve_url(url.as(String)) resolved_url = YoutubeAPI.resolve_url(url.as(String))
endpoint = resolved_url["endpoint"] endpoint = resolved_url["endpoint"]
pageType = endpoint.dig?("commandMetadata", "webCommandMetadata", "webPageType").try &.as_s || "" pageType = endpoint.dig?("commandMetadata", "webCommandMetadata", "webPageType").try &.as_s || ""
if resolved_ucid = endpoint.dig?("watchEndpoint", "videoId") if pageType == "WEB_PAGE_TYPE_UNKNOWN"
elsif resolved_ucid = endpoint.dig?("browseEndpoint", "browseId")
elsif pageType == "WEB_PAGE_TYPE_UNKNOWN"
return error_json(400, "Unknown url") return error_json(400, "Unknown url")
end end
sub_endpoint = endpoint["watchEndpoint"]? || endpoint["browseEndpoint"]? || endpoint
params = sub_endpoint.try &.dig?("params")
rescue ex rescue ex
return error_json(500, ex) return error_json(500, ex)
end end
JSON.build do |json| JSON.build do |json|
json.object do json.object do
json.field "ucid", resolved_ucid.try &.as_s || "" json.field "ucid", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]?
json.field "videoId", sub_endpoint["videoId"].as_s if sub_endpoint["videoId"]?
json.field "params", params.try &.as_s
json.field "pageType", pageType json.field "pageType", pageType
end end
end end

View File

@ -87,6 +87,13 @@ module Invidious::Routes::API::V1::Videos
caption = caption[0] caption = caption[0]
end end
if CONFIG.use_innertube_for_captions
params = Invidious::Videos::Transcript.generate_param(id, caption.language_code, caption.auto_generated)
initial_data = YoutubeAPI.get_transcript(params)
webvtt = Invidious::Videos::Transcript.convert_transcripts_to_vtt(initial_data, caption.language_code)
else
# Timedtext API handling
url = URI.parse("#{caption.base_url}&tlang=#{tlang}").request_target url = URI.parse("#{caption.base_url}&tlang=#{tlang}").request_target
# Auto-generated captions often have cues that aren't aligned properly with the video, # Auto-generated captions often have cues that aren't aligned properly with the video,
@ -94,20 +101,17 @@ module Invidious::Routes::API::V1::Videos
if caption.name.includes? "auto-generated" if caption.name.includes? "auto-generated"
caption_xml = YT_POOL.client &.get(url).body caption_xml = YT_POOL.client &.get(url).body
settings_field = {
"Kind" => "captions",
"Language" => "#{tlang || caption.language_code}",
}
if caption_xml.starts_with?("<?xml") if caption_xml.starts_with?("<?xml")
webvtt = caption.timedtext_to_vtt(caption_xml, tlang) webvtt = caption.timedtext_to_vtt(caption_xml, tlang)
else else
caption_xml = XML.parse(caption_xml) caption_xml = XML.parse(caption_xml)
webvtt = String.build do |str| webvtt = WebVTT.build(settings_field) do |webvtt|
str << <<-END_VTT
WEBVTT
Kind: captions
Language: #{tlang || caption.language_code}
END_VTT
caption_nodes = caption_xml.xpath_nodes("//transcript/text") caption_nodes = caption_xml.xpath_nodes("//transcript/text")
caption_nodes.each_with_index do |node, i| caption_nodes.each_with_index do |node, i|
start_time = node["start"].to_f.seconds start_time = node["start"].to_f.seconds
@ -120,9 +124,6 @@ module Invidious::Routes::API::V1::Videos
end_time = start_time + duration end_time = start_time + duration
end end
start_time = "#{start_time.hours.to_s.rjust(2, '0')}:#{start_time.minutes.to_s.rjust(2, '0')}:#{start_time.seconds.to_s.rjust(2, '0')}.#{start_time.milliseconds.to_s.rjust(3, '0')}"
end_time = "#{end_time.hours.to_s.rjust(2, '0')}:#{end_time.minutes.to_s.rjust(2, '0')}:#{end_time.seconds.to_s.rjust(2, '0')}.#{end_time.milliseconds.to_s.rjust(3, '0')}"
text = HTML.unescape(node.content) text = HTML.unescape(node.content)
text = text.gsub(/<font color="#[a-fA-F0-9]{6}">/, "") text = text.gsub(/<font color="#[a-fA-F0-9]{6}">/, "")
text = text.gsub(/<\/font>/, "") text = text.gsub(/<\/font>/, "")
@ -130,27 +131,23 @@ module Invidious::Routes::API::V1::Videos
text = "<v #{md["name"]}>#{md["text"]}</v>" text = "<v #{md["name"]}>#{md["text"]}</v>"
end end
str << <<-END_CUE webvtt.cue(start_time, end_time, text)
#{start_time} --> #{end_time} end
#{text} end
end
else
webvtt = YT_POOL.client &.get("#{url}&fmt=vtt").body
if webvtt.starts_with?("<?xml")
END_CUE webvtt = caption.timedtext_to_vtt(webvtt)
end
end
end
else else
# Some captions have "align:[start/end]" and "position:[num]%" # Some captions have "align:[start/end]" and "position:[num]%"
# attributes. Those are causing issues with VideoJS, which is unable # attributes. Those are causing issues with VideoJS, which is unable
# to properly align the captions on the video, so we remove them. # to properly align the captions on the video, so we remove them.
# #
# See: https://github.com/iv-org/invidious/issues/2391 # See: https://github.com/iv-org/invidious/issues/2391
webvtt = YT_POOL.client &.get("#{url}&format=vtt").body webvtt = webvtt.gsub(/([0-9:.]{12} --> [0-9:.]{12}).+/, "\\1")
if webvtt.starts_with?("<?xml") end
webvtt = caption.timedtext_to_vtt(webvtt)
else
webvtt = YT_POOL.client &.get("#{url}&format=vtt").body
.gsub(/([0-9:.]{12} --> [0-9:.]{12}).+/, "\\1")
end end
end end
@ -207,11 +204,7 @@ module Invidious::Routes::API::V1::Videos
storyboard = storyboard[0] storyboard = storyboard[0]
end end
String.build do |str| WebVTT.build do |vtt|
str << <<-END_VTT
WEBVTT
END_VTT
start_time = 0.milliseconds start_time = 0.milliseconds
end_time = storyboard[:interval].milliseconds end_time = storyboard[:interval].milliseconds
@ -223,12 +216,8 @@ module Invidious::Routes::API::V1::Videos
storyboard[:storyboard_height].times do |j| storyboard[:storyboard_height].times do |j|
storyboard[:storyboard_width].times do |k| storyboard[:storyboard_width].times do |k|
str << <<-END_CUE current_cue_url = "#{url}#xywh=#{storyboard[:width] * k},#{storyboard[:height] * j},#{storyboard[:width] - 2},#{storyboard[:height]}"
#{start_time}.000 --> #{end_time}.000 vtt.cue(start_time, end_time, current_cue_url)
#{url}#xywh=#{storyboard[:width] * k},#{storyboard[:height] * j},#{storyboard[:width] - 2},#{storyboard[:height]}
END_CUE
start_time += storyboard[:interval].milliseconds start_time += storyboard[:interval].milliseconds
end_time += storyboard[:interval].milliseconds end_time += storyboard[:interval].milliseconds

View File

@ -122,5 +122,11 @@ module Invidious::Routes::BeforeAll
end end
env.set "current_page", URI.encode_www_form(current_page) env.set "current_page", URI.encode_www_form(current_page)
unregistered_path_whitelist = {"/", "/login", "/licenses", "/privacy"}
if !env.get?("user") && !unregistered_path_whitelist.includes?(env.request.path) && CONFIG.login_only
env.response.headers["Location"] = "/login"
haltf env, status_code: 302
end
end end
end end

View File

@ -1,6 +1,12 @@
{% skip_file if flag?(:api_only) %} {% skip_file if flag?(:api_only) %}
module Invidious::Routes::Channels module Invidious::Routes::Channels
# Redirection for unsupported routes ("tabs")
def self.redirect_home(env)
ucid = env.params.url["ucid"]
return env.redirect "/channel/#{URI.encode_www_form(ucid)}"
end
def self.home(env) def self.home(env)
self.videos(env) self.videos(env)
end end
@ -159,6 +165,11 @@ module Invidious::Routes::Channels
end end
locale, user, subscriptions, continuation, ucid, channel = data locale, user, subscriptions, continuation, ucid, channel = data
# redirect to post page
if lb = env.params.query["lb"]?
env.redirect "/post/#{URI.encode_www_form(lb)}?ucid=#{URI.encode_www_form(ucid)}"
end
thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode
thin_mode = thin_mode == "true" thin_mode = thin_mode == "true"
@ -187,6 +198,44 @@ module Invidious::Routes::Channels
templated "community" templated "community"
end end
def self.post(env)
# /post/{postId}
id = env.params.url["id"]
ucid = env.params.query["ucid"]?
prefs = env.get("preferences").as(Preferences)
locale = prefs.locale
thin_mode = env.params.query["thin_mode"]? || prefs.thin_mode
thin_mode = thin_mode == "true"
nojs = env.params.query["nojs"]?
nojs ||= "0"
nojs = nojs == "1"
if !ucid.nil?
ucid = ucid.to_s
post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode)
else
# resolve the url to get the author's UCID
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
return error_template(400, "Invalid post ID") if response["error"]?
ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s
post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode)
end
post_response = JSON.parse(post_response)
if nojs
comments = Comments.fetch_community_post_comments(ucid, id)
comment_html = JSON.parse(Comments.parse_youtube(id, comments, "html", locale, thin_mode, isPost: true))["contentHtml"]
end
templated "post"
end
def self.channels(env) def self.channels(env)
data = self.fetch_basic_information(env) data = self.fetch_basic_information(env)
return data if !data.is_a?(Tuple) return data if !data.is_a?(Tuple)
@ -217,6 +266,11 @@ module Invidious::Routes::Channels
env.redirect "/channel/#{ucid}" env.redirect "/channel/#{ucid}"
end end
private KNOWN_TABS = {
"home", "videos", "shorts", "streams", "podcasts",
"releases", "playlists", "community", "channels", "about",
}
# Redirects brand url channels to a normal /channel/:ucid route # Redirects brand url channels to a normal /channel/:ucid route
def self.brand_redirect(env) def self.brand_redirect(env)
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
@ -227,7 +281,10 @@ module Invidious::Routes::Channels
yt_url_params = URI::Params.encode(env.params.query.to_h.select(["a", "u", "user"])) yt_url_params = URI::Params.encode(env.params.query.to_h.select(["a", "u", "user"]))
# Retrieves URL params that only Invidious uses # Retrieves URL params that only Invidious uses
invidious_url_params = URI::Params.encode(env.params.query.to_h.select!(["a", "u", "user"])) invidious_url_params = env.params.query.dup
invidious_url_params.delete_all("a")
invidious_url_params.delete_all("u")
invidious_url_params.delete_all("user")
begin begin
resolved_url = YoutubeAPI.resolve_url("https://youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}") resolved_url = YoutubeAPI.resolve_url("https://youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}")
@ -236,14 +293,17 @@ module Invidious::Routes::Channels
return error_template(404, translate(locale, "This channel does not exist.")) return error_template(404, translate(locale, "This channel does not exist."))
end end
selected_tab = env.request.path.split("/")[-1] selected_tab = env.params.url["tab"]?
if {"home", "videos", "shorts", "streams", "playlists", "community", "channels", "about"}.includes? selected_tab
if KNOWN_TABS.includes? selected_tab
url = "/channel/#{ucid}/#{selected_tab}" url = "/channel/#{ucid}/#{selected_tab}"
else else
url = "/channel/#{ucid}" url = "/channel/#{ucid}"
end end
env.redirect url url += "?#{invidious_url_params}" if !invidious_url_params.empty?
return env.redirect url
end end
# Handles redirects for the /profile endpoint # Handles redirects for the /profile endpoint

View File

@ -319,6 +319,15 @@ module Invidious::Routes::PreferencesRoute
response: error_template(415, "Invalid playlist file uploaded") response: error_template(415, "Invalid playlist file uploaded")
) )
end end
when "import_youtube_wh"
filename = part.filename || ""
success = Invidious::User::Import.from_youtube_wh(user, body, filename, type)
if !success
haltf(env, status_code: 415,
response: error_template(415, "Invalid watch history file uploaded")
)
end
when "import_freetube" when "import_freetube"
Invidious::User::Import.from_freetube(user, body) Invidious::User::Import.from_freetube(user, body)
when "import_newpipe_subscriptions" when "import_newpipe_subscriptions"

View File

@ -124,28 +124,42 @@ module Invidious::Routing
get "/channel/:ucid/community", Routes::Channels, :community get "/channel/:ucid/community", Routes::Channels, :community
get "/channel/:ucid/channels", Routes::Channels, :channels get "/channel/:ucid/channels", Routes::Channels, :channels
get "/channel/:ucid/about", Routes::Channels, :about get "/channel/:ucid/about", Routes::Channels, :about
get "/channel/:ucid/live", Routes::Channels, :live get "/channel/:ucid/live", Routes::Channels, :live
get "/user/:user/live", Routes::Channels, :live get "/user/:user/live", Routes::Channels, :live
get "/c/:user/live", Routes::Channels, :live get "/c/:user/live", Routes::Channels, :live
get "/post/:id", Routes::Channels, :post
# Channel catch-all, to redirect future routes to the channel's home
# NOTE: defined last in order to be processed after the other routes
get "/channel/:ucid/*", Routes::Channels, :redirect_home
{"", "/videos", "/shorts", "/streams", "/playlists", "/community", "/about"}.each do |path|
# /c/LinusTechTips # /c/LinusTechTips
get "/c/:user#{path}", Routes::Channels, :brand_redirect get "/c/:user", Routes::Channels, :brand_redirect
# /user/linustechtips | Not always the same as /c/ get "/c/:user/:tab", Routes::Channels, :brand_redirect
get "/user/:user#{path}", Routes::Channels, :brand_redirect
# /@LinusTechTips | Handle # /user/linustechtips (Not always the same as /c/)
get "/@:user#{path}", Routes::Channels, :brand_redirect get "/user/:user", Routes::Channels, :brand_redirect
get "/user/:user/:tab", Routes::Channels, :brand_redirect
# /@LinusTechTips (Handle)
get "/@:user", Routes::Channels, :brand_redirect
get "/@:user/:tab", Routes::Channels, :brand_redirect
# /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow # /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow
get "/attribution_link#{path}", Routes::Channels, :brand_redirect get "/attribution_link", Routes::Channels, :brand_redirect
get "/attribution_link/:tab", Routes::Channels, :brand_redirect
# /profile?user=linustechtips # /profile?user=linustechtips
get "/profile/#{path}", Routes::Channels, :profile get "/profile", Routes::Channels, :profile
end get "/profile/*", Routes::Channels, :profile
end end
def register_watch_routes def register_watch_routes
get "/watch", Routes::Watch, :handle get "/watch", Routes::Watch, :handle
post "/watch_ajax", Routes::Watch, :mark_watched post "/watch_ajax", Routes::Watch, :mark_watched
get "/watch/:id", Routes::Watch, :redirect get "/watch/:id", Routes::Watch, :redirect
get "/live/:id", Routes::Watch, :redirect
get "/shorts/:id", Routes::Watch, :redirect get "/shorts/:id", Routes::Watch, :redirect
get "/clip/:clip", Routes::Watch, :clip get "/clip/:clip", Routes::Watch, :clip
get "/w/:id", Routes::Watch, :redirect get "/w/:id", Routes::Watch, :redirect
@ -240,6 +254,10 @@ module Invidious::Routing
get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}} get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}}
{% end %} {% end %}
# Posts
get "/api/v1/post/:id", {{namespace}}::Channels, :post
get "/api/v1/post/:id/comments", {{namespace}}::Channels, :post_comments
# 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community # 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community
get "/api/v1/channels/comments/:ucid", {{namespace}}::Channels, :channel_comments_redirect get "/api/v1/channels/comments/:ucid", {{namespace}}::Channels, :channel_comments_redirect
get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect
@ -249,6 +267,7 @@ module Invidious::Routing
get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions
get "/api/v1/hashtag/:hashtag", {{namespace}}::Search, :hashtag get "/api/v1/hashtag/:hashtag", {{namespace}}::Search, :hashtag
# Authenticated # Authenticated
# The notification APIs cannot be extracted yet! They require the *local* notifications constant defined in invidious.cr # The notification APIs cannot be extracted yet! They require the *local* notifications constant defined in invidious.cr

View File

@ -218,6 +218,26 @@ struct Invidious::User
end end
end end
def from_youtube_wh(user : User, body : String, filename : String, type : String) : Bool
extension = filename.split(".").last
if extension == "json" || type == "application/json"
data = JSON.parse(body)
watched = data.as_a.compact_map do |item|
next unless url = item["titleUrl"]?
next unless match = url.as_s.match(/\?v=(?<video_id>[a-zA-Z0-9_-]+)$/)
match["video_id"]
end
watched.reverse! # YouTube have newest first
user.watched += watched
user.watched.uniq!
Invidious::Database::Users.update_watch_history(user)
return true
else
return false
end
end
# ------------------- # -------------------
# Freetube # Freetube
# ------------------- # -------------------
@ -228,8 +248,12 @@ struct Invidious::User
subs = matches.map(&.["channel_id"]) subs = matches.map(&.["channel_id"])
if subs.empty? if subs.empty?
data = JSON.parse(body)["subscriptions"] profiles = body.split('\n', remove_empty: true)
subs = data.as_a.map(&.["id"].as_s) profiles.each do |profile|
if data = JSON.parse(profile)["subscriptions"]?
subs += data.as_a.map(&.["id"].as_s)
end
end
end end
user.subscriptions += subs user.subscriptions += subs

View File

@ -24,7 +24,7 @@ struct Video
property updated : Time property updated : Time
@[DB::Field(ignore: true)] @[DB::Field(ignore: true)]
@captions = [] of Invidious::Videos::Caption @captions = [] of Invidious::Videos::Captions::Metadata
@[DB::Field(ignore: true)] @[DB::Field(ignore: true)]
property adaptive_fmts : Array(Hash(String, JSON::Any))? property adaptive_fmts : Array(Hash(String, JSON::Any))?
@ -215,9 +215,9 @@ struct Video
keywords.includes? "YouTube Red" keywords.includes? "YouTube Red"
end end
def captions : Array(Invidious::Videos::Caption) def captions : Array(Invidious::Videos::Captions::Metadata)
if @captions.empty? && @info.has_key?("captions") if @captions.empty? && @info.has_key?("captions")
@captions = Invidious::Videos::Caption.from_yt_json(info["captions"]) @captions = Invidious::Videos::Captions::Metadata.from_yt_json(info["captions"])
end end
return @captions return @captions

View File

@ -1,21 +1,24 @@
require "json" require "json"
module Invidious::Videos module Invidious::Videos
struct Caption module Captions
struct Metadata
property name : String property name : String
property language_code : String property language_code : String
property base_url : String property base_url : String
def initialize(@name, @language_code, @base_url) property auto_generated : Bool
def initialize(@name, @language_code, @base_url, @auto_generated)
end end
# Parse the JSON structure from Youtube # Parse the JSON structure from Youtube
def self.from_yt_json(container : JSON::Any) : Array(Caption) def self.from_yt_json(container : JSON::Any) : Array(Captions::Metadata)
caption_tracks = container caption_tracks = container
.dig?("playerCaptionsTracklistRenderer", "captionTracks") .dig?("playerCaptionsTracklistRenderer", "captionTracks")
.try &.as_a .try &.as_a
captions_list = [] of Caption captions_list = [] of Captions::Metadata
return captions_list if caption_tracks.nil? return captions_list if caption_tracks.nil?
caption_tracks.each do |caption| caption_tracks.each do |caption|
@ -25,7 +28,9 @@ module Invidious::Videos
language_code = caption["languageCode"].to_s language_code = caption["languageCode"].to_s
base_url = caption["baseUrl"].to_s base_url = caption["baseUrl"].to_s
captions_list << Caption.new(name, language_code, base_url) auto_generated = (caption["kind"]? == "asr")
captions_list << Captions::Metadata.new(name, language_code, base_url, auto_generated)
end end
return captions_list return captions_list
@ -47,17 +52,13 @@ module Invidious::Videos
break break
end end
end end
result = String.build do |result|
result << <<-END_VTT
WEBVTT
Kind: captions
Language: #{tlang || @language_code}
settings_field = {
"Kind" => "captions",
"Language" => "#{tlang || @language_code}",
}
END_VTT result = WebVTT.build(settings_field) do |vtt|
result << "\n\n"
cues.each_with_index do |node, i| cues.each_with_index do |node, i|
start_time = node["t"].to_f.milliseconds start_time = node["t"].to_f.milliseconds
@ -71,31 +72,19 @@ module Invidious::Videos
end_time = start_time + duration end_time = start_time + duration
end end
# start_time text = String.build do |io|
result << start_time.hours.to_s.rjust(2, '0')
result << ':' << start_time.minutes.to_s.rjust(2, '0')
result << ':' << start_time.seconds.to_s.rjust(2, '0')
result << '.' << start_time.milliseconds.to_s.rjust(3, '0')
result << " --> "
# end_time
result << end_time.hours.to_s.rjust(2, '0')
result << ':' << end_time.minutes.to_s.rjust(2, '0')
result << ':' << end_time.seconds.to_s.rjust(2, '0')
result << '.' << end_time.milliseconds.to_s.rjust(3, '0')
result << "\n"
node.children.each do |s| node.children.each do |s|
result << s.content io << s.content
end
result << "\n"
result << "\n"
end end
end end
vtt.cue(start_time, end_time, text)
end
end
return result return result
end end
end
# List of all caption languages available on Youtube. # List of all caption languages available on Youtube.
LANGUAGES = { LANGUAGES = {

View File

@ -137,9 +137,8 @@ end
def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)? def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
LOGGER.debug("try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client.") LOGGER.debug("try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client.")
# CgIQBg is a workaround for streaming URLs that returns a 403. # 2AMBCgIQBg is a workaround for streaming URLs that returns a 403.
# See https://github.com/iv-org/invidious/issues/4027#issuecomment-1666944520 response = YoutubeAPI.player(video_id: id, params: "2AMBCgIQBg", client_config: client_config)
response = YoutubeAPI.player(video_id: id, params: "CgIQBg", client_config: client_config)
playability_status = response["playabilityStatus"]["status"] playability_status = response["playabilityStatus"]["status"]
LOGGER.debug("try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}.") LOGGER.debug("try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}.")

View File

@ -0,0 +1,77 @@
module Invidious::Videos
# Namespace for methods primarily relating to Transcripts
module Transcript
record TranscriptLine, start_ms : Time::Span, end_ms : Time::Span, line : String
def self.generate_param(video_id : String, language_code : String, auto_generated : Bool) : String
kind = auto_generated ? "asr" : ""
object = {
"1:0:string" => video_id,
"2:base64" => {
"1:string" => kind,
"2:string" => language_code,
"3:string" => "",
},
"3:varint" => 1_i64,
"5:string" => "engagement-panel-searchable-transcript-search-panel",
"6:varint" => 1_i64,
"7:varint" => 1_i64,
"8:varint" => 1_i64,
}
params = 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 params
end
def self.convert_transcripts_to_vtt(initial_data : Hash(String, JSON::Any), target_language : String) : String
# Convert into array of TranscriptLine
lines = self.parse(initial_data)
settings_field = {
"Kind" => "captions",
"Language" => target_language,
}
# Taken from Invidious::Videos::Captions::Metadata.timedtext_to_vtt()
vtt = WebVTT.build(settings_field) do |vtt|
lines.each do |line|
vtt.cue(line.start_ms, line.end_ms, line.line)
end
end
return vtt
end
private def self.parse(initial_data : Hash(String, JSON::Any))
body = initial_data.dig("actions", 0, "updateEngagementPanelAction", "content", "transcriptRenderer",
"content", "transcriptSearchPanelRenderer", "body", "transcriptSegmentListRenderer",
"initialSegments").as_a
lines = [] of TranscriptLine
body.each do |line|
# Transcript section headers. They are not apart of the captions and as such we can safely skip them.
if line.as_h.has_key?("transcriptSectionHeaderRenderer")
next
end
line = line["transcriptSegmentRenderer"]
start_ms = line["startMs"].as_s.to_i.millisecond
end_ms = line["endMs"].as_s.to_i.millisecond
text = extract_text(line["snippet"]) || ""
lines << TranscriptLine.new(start_ms, end_ms, text)
end
return lines
end
end
end

View File

@ -26,7 +26,7 @@
<p><%= error_message %></p> <p><%= error_message %></p>
</div> </div>
<% else %> <% else %>
<div class="h-box pure-g" id="comments"> <div class="h-box pure-g comments" id="comments">
<%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %> <%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %>
</div> </div>
<% end %> <% end %>

View File

@ -26,8 +26,9 @@
</a></div> </a></div>
</div> </div>
<% if !item.channel_handle.nil? %><p class="channel-name" dir="auto"><%= item.channel_handle %></p><% end %>
<p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p> <p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p>
<% if !item.auto_generated %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %> <% if !item.auto_generated && item.channel_handle.nil? %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %>
<h5><%= item.description_html %></h5> <h5><%= item.description_html %></h5>
<% when SearchHashtag %> <% when SearchHashtag %>
<% if !thin_mode %> <% if !thin_mode %>

View File

@ -70,7 +70,12 @@
</b> </b>
<% else %> <% else %>
<b> <b>
<% if !author.empty? %>
<a href="/channel/<%= playlist.ucid %>"><%= author %></a> | <a href="/channel/<%= playlist.ucid %>"><%= author %></a> |
<% elsif !playlist.subtitle.nil? %>
<% subtitle = playlist.subtitle || "" %>
<span><%= HTML.escape(subtitle[0..subtitle.rindex(" • ") || subtitle.size]) %></span> |
<% end %>
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %> | <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> <%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %>
</b> </b>

View File

@ -0,0 +1,48 @@
<% content_for "header" do %>
<title>Invidious</title>
<% end %>
<div>
<div id="post" class="comments post-comments">
<%= IV::Frontend::Comments.template_youtube(post_response.not_nil!, locale, thin_mode) %>
</div>
<% if nojs %>
<hr>
<% end %>
<br />
<div id="comments" class="comments post-comments">
<% if nojs %>
<%= comment_html %>
<% else %>
<noscript>
<a href="/post/<%= id %>?ucid=<%= ucid %>&nojs=1">
<%= translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
</a>
</noscript>
<% end %>
</div>
</div>
<script id="video_data" type="application/json">
<%=
{
"id" => id,
"youtube_comments_text" => HTML.escape(translate(locale, "View YouTube comments")),
"reddit_comments_text" => "",
"reddit_permalink_text" => "",
"comments_text" => HTML.escape(translate(locale, "View `x` comments", "{commentCount}")),
"hide_replies_text" => HTML.escape(translate(locale, "Hide replies")),
"show_replies_text" => HTML.escape(translate(locale, "Show replies")),
"params" => {
"comments": ["youtube"]
},
"preferences" => prefs,
"base_url" => "/api/v1/post/#{URI.encode_www_form(id)}/comments",
"ucid" => ucid
}.to_pretty_json
%>
</script>
<script src="/js/comments.js?v=<%= ASSET_COMMIT %>"></script>
<script src="/js/post.js?v=<%= ASSET_COMMIT %>"></script>

View File

@ -26,6 +26,11 @@
<input type="file" id="import_youtube_pl" name="import_youtube_pl"> <input type="file" id="import_youtube_pl" name="import_youtube_pl">
</div> </div>
<div class="pure-control-group">
<label for="import_youtube_wh"><%= translate(locale, "Import YouTube watch history (.json)") %></label>
<input type="file" id="import_youtube_wh" name="import_youtube_wh">
</div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="import_freetube"><%= translate(locale, "Import FreeTube subscriptions (.db)") %></label> <label for="import_freetube"><%= translate(locale, "Import FreeTube subscriptions (.db)") %></label>
<input type="file" id="import_freetube" name="import_freetube"> <input type="file" id="import_freetube" name="import_freetube">

View File

@ -89,7 +89,7 @@
<label for="captions[0]"><%= translate(locale, "preferences_captions_label") %></label> <label for="captions[0]"><%= translate(locale, "preferences_captions_label") %></label>
<% preferences.captions.each_with_index do |caption, index| %> <% preferences.captions.each_with_index do |caption, index| %>
<select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]"> <select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]">
<% Invidious::Videos::Caption::LANGUAGES.each do |option| %> <% Invidious::Videos::Captions::LANGUAGES.each do |option| %>
<option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option> <option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
<% end %> <% end %>
</select> </select>

View File

@ -64,7 +64,8 @@ we're going to need to do it here in order to allow for translations.
"premiere_timestamp" => video.premiere_timestamp.try &.to_unix, "premiere_timestamp" => video.premiere_timestamp.try &.to_unix,
"vr" => video.is_vr, "vr" => video.is_vr,
"projection_type" => video.projection_type, "projection_type" => video.projection_type,
"local_disabled" => CONFIG.disabled?("local") "local_disabled" => CONFIG.disabled?("local"),
"support_reddit" => true
}.to_pretty_json }.to_pretty_json
%> %>
</script> </script>
@ -287,7 +288,7 @@ we're going to need to do it here in order to allow for translations.
<hr> <hr>
<% end %> <% end %>
<div id="comments"> <div id="comments" class="comments">
<% if nojs %> <% if nojs %>
<%= comment_html %> <%= comment_html %>
<% else %> <% else %>
@ -369,4 +370,5 @@ we're going to need to do it here in order to allow for translations.
</div> </div>
<% end %> <% end %>
</div> </div>
<script src="/js/comments.js?v=<%= ASSET_COMMIT %>"></script>
<script src="/js/watch.js?v=<%= ASSET_COMMIT %>"></script> <script src="/js/watch.js?v=<%= ASSET_COMMIT %>"></script>

View File

@ -175,17 +175,18 @@ private module Parsers
# Always simpleText # Always simpleText
# TODO change default value to nil # TODO change default value to nil
subscriber_count = item_contents.dig?("subscriberCountText", "simpleText") subscriber_count = item_contents.dig?("subscriberCountText", "simpleText").try &.as_s
channel_handle = subscriber_count if (subscriber_count.try &.starts_with? "@")
# Since youtube added channel handles, `VideoCountText` holds the number of # Since youtube added channel handles, `VideoCountText` holds the number of
# subscribers and `subscriberCountText` holds the handle, except when the # subscribers and `subscriberCountText` holds the handle, except when the
# channel doesn't have a handle (e.g: some topic music channels). # channel doesn't have a handle (e.g: some topic music channels).
# See https://github.com/iv-org/invidious/issues/3394#issuecomment-1321261688 # See https://github.com/iv-org/invidious/issues/3394#issuecomment-1321261688
if !subscriber_count || !subscriber_count.as_s.includes? " subscriber" if !subscriber_count || !subscriber_count.includes? " subscriber"
subscriber_count = item_contents.dig?("videoCountText", "simpleText") subscriber_count = item_contents.dig?("videoCountText", "simpleText").try &.as_s
end end
subscriber_count = subscriber_count subscriber_count = subscriber_count
.try { |s| short_text_to_number(s.as_s.split(" ")[0]).to_i32 } || 0 .try { |s| short_text_to_number(s.split(" ")[0]).to_i32 } || 0
# Auto-generated channels doesn't have videoCountText # Auto-generated channels doesn't have videoCountText
# Taken from: https://github.com/iv-org/invidious/pull/2228#discussion_r717620922 # Taken from: https://github.com/iv-org/invidious/pull/2228#discussion_r717620922
@ -200,6 +201,7 @@ private module Parsers
author_thumbnail: author_thumbnail, author_thumbnail: author_thumbnail,
subscriber_count: subscriber_count, subscriber_count: subscriber_count,
video_count: video_count, video_count: video_count,
channel_handle: channel_handle,
description_html: description_html, description_html: description_html,
auto_generated: auto_generated, auto_generated: auto_generated,
author_verified: author_verified, author_verified: author_verified,

View File

@ -557,6 +557,30 @@ module YoutubeAPI
return self._post_json("/youtubei/v1/search", data, client_config) return self._post_json("/youtubei/v1/search", data, client_config)
end end
####################################################################
# get_transcript(params, client_config?)
#
# Requests the youtubei/v1/get_transcript endpoint with the required headers
# and POST data in order to get a JSON reply.
#
# The requested data is a specially encoded protobuf string that denotes the specific language requested.
#
# An optional ClientConfig parameter can be passed, too (see
# `struct ClientConfig` above for more details).
#
def get_transcript(
params : String,
client_config : ClientConfig | Nil = nil
) : Hash(String, JSON::Any)
data = {
"context" => self.make_context(client_config),
"params" => params,
}
return self._post_json("/youtubei/v1/get_transcript", data, client_config)
end
#################################################################### ####################################################################
# _post_json(endpoint, data, client_config?) # _post_json(endpoint, data, client_config?)
# #