Compare commits

...

159 commits

Author SHA1 Message Date
syeopite
9980c0e00f
Update uptime logic to handle updown.io response 2024-05-22 13:28:15 -07:00
syeopite
aa96cf3453
Fix invalid logic for instance uptime comparison 2024-05-22 13:22:00 -07:00
syeopite
41c978d350
Use HTTP::Client directly in instance list job
The HTTP::Client created via `make_client` is affected by the
force_resolve configuration option. However, api.invidious.io
does not support ipv6 and as such any request with ipv6 to
api.invidious.io will instead raise.

Directly calling the HTTP::Client will ignore the force_resolve option
allowing requests to go through ipv4 when needed.
2024-05-22 13:22:00 -07:00
syeopite
cff25a7b25
Refactor instance fetching logic into separate job 2024-05-22 13:22:00 -07:00
Samantaz Fox
eda7444ca4
Update changelog 2024-04-27 00:17:01 +02:00
Samantaz Fox
79b342aee5
Rename legacy changelog file 2024-04-27 00:14:46 +02:00
Samantaz Fox
0ab6d9e748
Videos: Use android test suite client (#4650)
"Temporary" fix, until a better and more permanent solution is found.

Closes issue 4584
2024-04-26 23:54:24 +02:00
Samantaz Fox
6f889dc695
Trending: Un-nest category if this is the only one (#4600)
Trending music was returning less than 24 videos which was getting
filtered out. Since multiple categories only appears on the default
trending page, we don't need to do that filtering for Music, Gaming or movies.

Closes issue 4596
2024-04-26 23:52:44 +02:00
Samantaz Fox
8eaaeb1c74
API: Add bitrate to formatStreams too (#4590)
Before this PR, the bitrate was only returned for the 'adaptiveFormats'.
They are now also returned the 'formatStreams'.

No linked issue
2024-04-26 23:49:44 +02:00
Samantaz Fox
7c1d2714e0
Comments: Add support for new format (#4576)
The new comment format is similar to the description's commandRuns.

This should fix the issues with most comments but there are still
some more changes that would need to be made like adding support for
formatting (bold, italic, underline) and channel emojis.

Fixes issue 4566
2024-04-26 23:48:15 +02:00
Samantaz Fox
c94c6f4b83
Remove legacy proxy code (#4570)
Also fixes the build on nightly as the offending code was removed.

Related to
https://github.com/iv-org/invidious/pull/4270#issuecomment-1858876952
2024-04-26 23:44:47 +02:00
Samantaz Fox
f1fd197cbc
API: convey info "is post live" from Youtube response (#4569)
Returns the 'isPostLiveDvr' field in the videos API when the video
is a post-live DVR (= ended livestream that hasn't been reprocessed
into VOD yet).

Example taken 10 minutes after that livestream ended:
/api/v1/videos/euqnWk-uP6M

{
  ...
  "isPostLiveDvr": true,
  ...
}

Partially fixes 4421
2024-04-26 23:44:30 +02:00
Samantaz Fox
bd549f21e9
API: Add 'authorVerified' field on recommended videos (#4562)
Closes 3323
2024-04-26 23:36:08 +02:00
Samantaz Fox
48ad864572
Videos: Add support for new likes format (#4462)
YouTube added a new format for like data in December 2023.
This PR adds support for parsing saod new format.

Related: LuanRT/YouTube.js issue 557

Closes 4419
2024-04-26 23:30:30 +02:00
Samantaz Fox
bcb679e653
Proxy: Handle non-200 HTTP codes on DASH manifests (#4429)
Before this PR, Invidious assumed that fetching the DASH manifest from
YouTube will always be successful and didn't check the status code.

That meant that if YouTube gave a rate-limiting page, invidious would
return an HTTP 200 response with the 'application/dash+xml' Content-Type
header and the YouTube ratelimiting page as the body.

No associated issue
2024-04-26 23:25:54 +02:00
Samantaz Fox
b163afecb7
API: Parse channel's tags (#4294)
Example from an auto generated channel (Minecraft - Topic):
/api/v1/channels/UCQvWX73GQygcwXOTSf_VDVg

{
  ...
  "tags":[
    "Video game","Indie game","Xbox One","New Nintendo 3DS",
    "PS3","Nintendo Switch","Wii U","Fire OS",
    "Mac operating systems","Apple TV","Xbox 360",
    "PlayStation Vita","PS4","Windows","Linux","Nintendo 3DS",
    "iOS","Windows Phone","iPadOS","Java","Android","tvOS",
    "ChromeOS"
  ]
  ...
}

Example from another channel (Mr Beast):
/api/v1/channels/UCX6OQ3DkcsbYNE6H8uQQuVA

{
  ...
  "tags":["mrbeast6000","beast","mrbeast","Mr.Beast","mr"]
  ...
}

No associated issue
2024-04-26 23:20:30 +02:00
Samantaz Fox
5855e9d188
Translations update from Hosted Weblate (#4164) 2024-04-26 23:19:23 +02:00
Samantaz Fox
33f316c864
Videos: Remove AndroidScreenEmbed client 2024-04-26 23:15:34 +02:00
Samantaz Fox
be291e8f0f
Videos: Copy captions over between responses 2024-04-26 22:33:08 +02:00
Samantaz Fox
d49c762609
YtAPI: Add more client infos for Android test suite 2024-04-26 22:26:45 +02:00
Samantaz Fox
7f3ddad12e
Videos: Use android test suite client 2024-04-26 22:03:59 +02:00
Hosted Weblate
01e2a5e89d
Update Lombard translation
Update translation files

Updated by "Remove blank strings" hook in Weblate.

Update Lombard translation

Add Lombard translation

Co-authored-by: Federico <fv4@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invidious/translations/
Translation: Invidious/Invidious Translations
2024-04-25 18:35:09 +02:00
Hosted Weblate
bff0b5c85a
Update Serbian (cyrillic) translation
Update Serbian (cyrillic) translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
2024-04-25 18:35:09 +02:00
Hosted Weblate
2da63bf36d
Update Chinese (Simplified) translation
Update Chinese (Simplified) translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-04-25 18:35:09 +02:00
Hosted Weblate
7546cb511d
Update Chinese (Traditional) translation
Update Chinese (Traditional) translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
2024-04-25 18:35:09 +02:00
Hosted Weblate
200cfd7579
Update Portuguese (Portugal) translation
Co-authored-by: Samantaz Fox <translator-weblate@samantaz.fr>
2024-04-25 18:35:09 +02:00
Hosted Weblate
6ed872d72b
Update English (United States) translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Lime bar <limebarartist@gmail.com>
2024-04-25 18:35:09 +02:00
Hosted Weblate
58dc63671a
Update Korean translation
Update Korean translation

Update Korean translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: simmon <simmon@nplob.com>
Co-authored-by: xrfmkrh <rF3nMd7sRKezjF2vcEQo@protonmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
eba0699c48
Update Serbian translation
Update Serbian translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
e3018e00c4
Update Swedish translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
2024-04-25 18:35:08 +02:00
Hosted Weblate
b54d45504f
Update Spanish translation
Update Spanish translation

Update Spanish translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Samantaz Fox <translator-weblate@samantaz.fr>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2024-04-25 18:35:08 +02:00
Hosted Weblate
64eef948bd
Update Dutch translation
Co-authored-by: Gert-dev <Gert-dev@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-04-25 18:35:08 +02:00
Hosted Weblate
3f9c7b6c19
Update Interlingua translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
c60d2561d1
Update Arabic translation
Update Arabic translation

Update Arabic translation

Update Arabic translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Samantaz Fox <translator-weblate@samantaz.fr>
2024-04-25 18:35:08 +02:00
Hosted Weblate
0de3b0a96d
Update Italian translation
Update Italian translation

Co-authored-by: Federico <fv4@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-04-25 18:35:08 +02:00
Hosted Weblate
5551b613d3
Update Polish translation
Update Polish translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Matthaiks <kitynska@gmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
ef7f3f5bd4
Update Hindi translation
Update Hindi translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Scrambled777 <weblate.scrambled777@simplelogin.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
c8369f9dbb
Update Croatian translation
Update Croatian translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
2024-04-25 18:35:08 +02:00
Hosted Weblate
8d75d6431a
Update Vietnamese translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Knight Hat <knightchanelgaming@gmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
a6bcf0280c
Update Portuguese translation
Update Portuguese translation

Update Portuguese translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Samantaz Fox <translator-weblate@samantaz.fr>
Co-authored-by: Sergio Marques <so.boston.android@gmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
97c4263530
Update Czech translation
Update Czech translation

Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-04-25 18:35:08 +02:00
Hosted Weblate
dd01b0f5eb
Update Japanese translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: maboroshin <maboroshin@users.noreply.hosted.weblate.org>
2024-04-25 18:35:08 +02:00
Hosted Weblate
197b3972a9
Update Ukrainian translation
Update Ukrainian translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Samantaz Fox <translator-weblate@samantaz.fr>
2024-04-25 18:35:08 +02:00
Hosted Weblate
cbbaded209
Update Bengali translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Tauhid Alam Rifty <tauhidalamrifty@gmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
066b1c35cc
Update Romanian translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Wiktor Muzynski <wiktormuzynski@gmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
25cbfd0681
Update Basque translation
Co-authored-by: Samantaz Fox <translator-weblate@samantaz.fr>
2024-04-25 18:35:08 +02:00
Hosted Weblate
a2f9707b3f
Update Danish translation
Co-authored-by: Samantaz Fox <translator-weblate@samantaz.fr>
2024-04-25 18:35:08 +02:00
Hosted Weblate
89c008211d
Update German translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Lenny Angst <lenny@familie-angst.ch>
2024-04-25 18:35:08 +02:00
Hosted Weblate
e92d250a1c
Update Portuguese (Brazil) translation
Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Update Portuguese (Brazil) translation

Co-authored-by: André Marcelo Alvarenga <andrealvarenga@gmx.net>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jose Delvani <delvani.eletricista@gmail.com>
Co-authored-by: joaooliva <joaooliva@protonmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
668c130f01
Update Turkmen translation
Add Turkmen translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hydyr Sopyyew <sopyyewhydyr46@gmail.com>
2024-04-25 18:35:08 +02:00
Hosted Weblate
f7ae680c25
Update Turkish translation
Update Turkish translation

Update Turkish translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
2024-04-25 18:35:08 +02:00
ChunkyProgrammer
24de19d06f only ignore smaller trending categories on default trending tab 2024-04-23 23:51:22 -04:00
absidue
f313162fa1 Add bitrate to formatStreams in /api/v1/videos/{id} response 2024-04-21 12:53:31 +02:00
ChunkyProgrammer
2b6e71b553 Simplify cvm assignment logic + improve formatting
Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2024-04-20 10:25:51 -04:00
ChunkyProgrammer
d1eae10147 make authorVerified a bool value 2024-04-16 18:21:45 -04:00
ChunkyProgrammer
fbf07e18aa Parse links in the comments
Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2024-04-11 08:58:33 -04:00
ChunkyProgrammer
de2287963f fix loading replies to comments, remove unneeded code
Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2024-04-10 18:32:20 -04:00
ChunkyProgrammer
039212ed91 escape html, add todo comment 2024-04-10 18:21:53 -04:00
ChunkyProgrammer
a9f55aa310 fix lint, improve performance 2024-04-10 18:21:53 -04:00
ChunkyProgrammer
c27bb90e4d Add support for new comment format 2024-04-10 18:21:53 -04:00
Émilien (perso)
b673695aa2
Merge pull request #4561 from ChunkyProgrammer/use-trending-api-for-health-checks
Use Trending API for health checks
2024-04-10 20:21:17 +07:00
syeopite
990931ff67
Remove legacy proxy code 2024-04-07 11:08:12 -07:00
ChunkyProgrammer
bfd9c9876e Parse if video is post live dvr and include it in API 2024-04-07 10:26:33 -04:00
ChunkyProgrammer
2a029b4d8c Add field for authorVerified for recommended videos when using the API 2024-04-04 20:20:27 -04:00
ChunkyProgrammer
170eef58fd Use trending api for health checks 2024-04-04 19:10:27 -04:00
Samantaz Fox
08390acd0c
Update workaround used to fetch streaming URLs (#4552)
Thanks to LuanRT (From youtube.js) for the fix!

Closes issue 4498
2024-03-31 18:42:10 +02:00
Brahim Hadriche
1a2d408d38 Update shorts params 2024-03-31 11:37:13 -04:00
ChunkyProgrammer
0aaa3e6a08 API: Parse channel's tags 2024-03-11 13:31:14 -04:00
Émilien (perso)
99a5e9cbc4
Merge pull request #4473 from SamantazFox/bump-api-clients
YoutubeAPI: bump client versions
2024-03-08 11:23:03 +01:00
Samantaz Fox
619aa3ff05
YoutubeAPI: bump client versions 2024-03-06 21:36:15 +01:00
ChunkyProgrammer
5ceeefa236 add support for new likes format 2024-03-01 23:45:29 -05:00
Samantaz Fox
e8a36985af
API: Add APIHandler back (#4431)
This handler should no have been removed in 4276, as it adds the required CORS
header (Access-Control-Allow-Origin) for public acces to the API.

Thanks to iBicha for noticing this!
2024-02-19 00:16:17 +01:00
Samantaz Fox
962ce23cc2
WebVTT::Builder: Add logic to escape special chars (#4414)
Note: WebVTT does allow some tags in the cue payload in some circumstances
while this PR just blindly escapes everything:
https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API#cue_payload_text_tags
2024-02-19 00:16:17 +01:00
Samantaz Fox
e0ce59d3e8
Channels: Add support for multi-image community posts (#4412)
This PR adds a CSS-only image carousel for community posts with more than
one image attached.

Closes issue 3522
2024-02-19 00:16:17 +01:00
Samantaz Fox
c5a3112e49
CSS: expand #contents width on small screens (#4405)
The #contents div now takes the full width on small screens (< 1280px).
All page elements have a little more room, especially the video titles.
2024-02-19 00:16:17 +01:00
Samantaz Fox
d3703baba9
I18n: Add missing translation strings (#4424)
Closes issue 3120
2024-02-19 00:15:44 +01:00
Samantaz Fox
20203f4ec0
I18n: Fix a typo in Finnish localization (#4375) 2024-02-18 23:53:16 +01:00
Samantaz Fox
732553519e
Translations update from Hosted Weblate (#4164) 2024-02-18 23:51:53 +01:00
ChunkyProgrammer
a957b0fb7c remove trailing white spaces 2024-02-16 16:22:43 -05:00
ChunkyProgrammer
26429bee3f make it so interpolation text can be a hash
Co-Authored-By: Samantaz Fox <coding@samantaz.fr>
2024-02-15 21:45:22 -05:00
ChunkyProgrammer
ef6b766b29 Add support for multi image community posts 2024-02-15 21:45:21 -05:00
Émilien (perso)
1e6ec605e8
Remove usage of depends_on (#4383) 2024-02-15 22:59:00 +01:00
Samantaz Fox
60f6a345d9
Locales: Fix broken i18Next v3/v4 plurals
Languages impacted: es, fa, pt
2024-02-15 22:12:04 +01:00
Samantaz Fox
d1dddc1adc
Locales: Remove Cyrillic text from Serbian (Latin) 2024-02-15 21:37:17 +01:00
Hosted Weblate
00ef004029
Update Norwegian Bokmål translation
Co-authored-by: Deleted User <noreply+73135@weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-02-15 18:02:15 +01:00
Hosted Weblate
7ff11e4c44
Update Serbian (cyrillic) translation
Update Serbian (cyrillic) translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
2024-02-15 18:02:15 +01:00
Hosted Weblate
8db2e060d9
Update Chinese (Simplified) translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-02-15 18:02:15 +01:00
Hosted Weblate
8b0cbd2a29
Update Chinese (Traditional) translation
Co-authored-by: Jeff Huang <s8321414@gmail.com>
2024-02-15 18:02:15 +01:00
Hosted Weblate
d2ce519559
Update Slovenian translation
Co-authored-by: Damjan Gerl <damjan@damjan.net>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-02-15 18:02:15 +01:00
Hosted Weblate
219b587945
Update Korean translation
Update Korean translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: simmon <simmon@nplob.com>
Co-authored-by: xrfmkrh <rF3nMd7sRKezjF2vcEQo@protonmail.com>
2024-02-15 18:02:15 +01:00
Hosted Weblate
e8810509c1
Update Albanian translation
Update Albanian translation

Co-authored-by: Besnik Bleta <besnik@programeshqip.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-02-15 18:02:15 +01:00
Hosted Weblate
9688200caf
Update Serbian translation
Update Serbian translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
2024-02-15 18:02:15 +01:00
Hosted Weblate
26a50eb4e8
Update Persian translation
Update Persian translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kaambiz <kambizx@gmail.com>
2024-02-15 18:02:15 +01:00
Hosted Weblate
0ce945bfa8
Update Swedish translation
Update Swedish translation

Update Swedish translation

Co-authored-by: Deleted User <noreply+73135@weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Max Bengtzén <aura.kettles.0h@icloud.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
2024-02-15 18:02:15 +01:00
Hosted Weblate
aadf848ee6
Update French translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jean Mareilles <waged1266@tutanota.com>
2024-02-15 18:02:15 +01:00
Hosted Weblate
53ce2a1a9a
Update Spanish translation
Update Spanish translation

Update Spanish translation

Update Spanish translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jorge Maldonado Ventura <jorgesumle@freakspot.net>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2024-02-15 18:02:15 +01:00
Hosted Weblate
1d5100462b
Update Dutch translation
Update Dutch translation

Co-authored-by: Deleted User <noreply+73135@weblate.org>
Co-authored-by: Gert-dev <Gert-dev@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-02-15 18:02:15 +01:00
Hosted Weblate
986515dc5b
Update Indonesian translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
2024-02-15 18:02:15 +01:00
Hosted Weblate
1d906aeecc
Update Interlingua translation
Add Interlingua translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
2024-02-15 18:02:15 +01:00
Hosted Weblate
426b472a15
Update Arabic translation
Update Arabic translation

Update Arabic translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
2024-02-15 18:02:15 +01:00
Hosted Weblate
1493e6a086
Update Italian translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
2024-02-15 18:02:14 +01:00
Hosted Weblate
3767ab2eeb
Update Polish translation
Update Polish translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Matthaiks <kitynska@gmail.com>
2024-02-15 18:02:14 +01:00
Hosted Weblate
fea36fc639
Update Hindi translation
Update Hindi translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Saurmandal <saurmandal@protonmail.com>
Co-authored-by: Snwglb <wishitwasarchived@gmail.com>
2024-02-15 18:02:14 +01:00
Hosted Weblate
a16235d3b9
Update Croatian translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
2024-02-15 18:02:14 +01:00
Hosted Weblate
99a3bd4fff
Update Vietnamese translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Tran Viet Duc <deli50@protonmail.com>
2024-02-15 18:02:14 +01:00
Hosted Weblate
4aed0e1102
Update Portuguese translation
Update Portuguese translation

Update Portuguese translation

Update Portuguese translation

Co-authored-by: Filipe Martins <mvrtinsbeats@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jener Gomes <jenerg1@gmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: jamerLamer <akHarINlMYExpSmVPDRT@proton.me>
2024-02-15 18:02:14 +01:00
Hosted Weblate
833c711cba
Update Czech translation
Update Czech translation

Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-02-15 18:02:14 +01:00
Hosted Weblate
7e1deea15e
Update Catalan translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: victor dargallo <victordargallo@disroot.org>
2024-02-15 18:02:14 +01:00
Hosted Weblate
b9ae1a61da
Update Japanese translation
Update Japanese translation

Update Japanese translation

Update Japanese translation

Update Japanese translation

Update Japanese translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: maboroshin <maboroshin@users.noreply.hosted.weblate.org>
2024-02-15 18:02:14 +01:00
Hosted Weblate
f062c18b82
Update Ukrainian translation
Update Ukrainian translation

Update Ukrainian translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com>
2024-02-15 18:02:14 +01:00
Hosted Weblate
f21a532c0d
Update Bulgarian translation
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Radoslav Lelchev <rlelchev@abv.bg>
2024-02-15 18:02:14 +01:00
Hosted Weblate
8cec7ba004
Update Russian translation
Update Russian translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Noise Maker <vh0d@disroot.org>
Co-authored-by: hikiko4ern <25303622+hikiko4ern@users.noreply.github.com>
2024-02-15 18:02:14 +01:00
Hosted Weblate
8169cd8977
Update Danish translation
Co-authored-by: Grooty12 <weblate@grooty.site>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-02-15 18:02:14 +01:00
Hosted Weblate
8ffc569ebd
Update German translation
Update German translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Lenny Angst <lenny@familie-angst.ch>
Co-authored-by: Radoslav Lelchev <rlelchev@abv.bg>
2024-02-15 18:02:14 +01:00
Hosted Weblate
736f35332a
Update Portuguese (Brazil) translation
Update Portuguese (Brazil) translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: joaooliva <joaooliva@protonmail.com>
2024-02-15 18:02:14 +01:00
Hosted Weblate
c52c6d3c9a
Update Turkish translation
Update Turkish translation

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
2024-02-15 18:02:14 +01:00
Samantaz Fox
7b84bdb29b
API: Add APIHandler back
This handler should no have been removed in 4276, as it adds the required CORS
header (Access-Control-Allow-Origin) for public acces to the API.

Thanks to iBicha for noticing this!
2024-02-13 21:05:26 +01:00
Samantaz Fox
5c0b6d8afa
Stats: Fix two swapped function names (#4376)
The function names `count_users_active_6m` and `count_users_active_1m` were
swapped. As the names were swapped on both sides (declaration and use), this
had no functional impact.

No related isse was tracked.
2024-02-12 22:34:13 +01:00
Samantaz Fox
c85b908613
API: Fix missing wildcards after login redirect (#4348)
This PR fixes an issue where the `scopes` parameter would see its wildmark
characters (*) removed during the login page redirection, after that a call
to `/authorize_token` was made while the user was not logged in.

Closes issue 4200
2024-02-12 22:30:48 +01:00
Samantaz Fox
f32764c840
HTML: Preserve playlist in "Watch on YouTube" link (#4342)
It seems that at some point, Youtube changed the URL parameter from `plid`
to `list` and we didn't notice. This fixes that.

Closes #3929
2024-02-12 22:23:44 +01:00
Samantaz Fox
d30dae43fe
HTML: Add title to toggle theme icon (#4320) 2024-02-12 22:20:14 +01:00
Samantaz Fox
338d3d9f86
CSS: Fix thumbnails' aspect ratio to prevent CLS (#4278)
Force the thumbnails aspect ratio to 16/9 in order to prevent Cumulative Layout
Shifting (CLS) from hapenning during lazy loading.

It also fixes the problematic, taller thumbnails that Youtube returns for
playlists.

Closes issue 4002
2024-02-12 22:19:14 +01:00
Samantaz Fox
1f51255f2f
API: Remove the fields parameter (#4276)
Multiple users have reported that the fields parameter is slowing down API
response times significantly. As most API endpoints are already optimized to
make as few requests as possible to Youtube, there is no point in limiting the
output. Furthermore, the added processing might be part of the broader memory
leak problem (See 1438).

In addition, the small increase in data output is not much of an issue compared
to the huge video proxy that lies next to this API.

No related issue tracked
2024-02-12 22:10:45 +01:00
Samantaz Fox
dcbe52c9fb
Videos: Use start time and end time for clips (#4264)
This PR parses the start and end time for clips.

It also adds a new, dedicated API endpoint (`/api/v1/clips/{id}`) for
retrieving the start and end time of a clip.

Here is a sample response from that new endpoint (`video` is a video object,
as described in https://docs.invidious.io/api/common_types/#videoobject):

GET `/api/v1/clips/UgkxxPM3BRphCAPLP88YoUGuj79KXPfpNNO_?pretty=1`

Response:
```
{
  "startTime": 8842.645,
  "endTime": 8855.856,
  "clipTitle": "✂️ Kirby is pink!",
  "video": {}
}
```

Closes issue 3921
2024-02-12 22:10:16 +01:00
Samantaz Fox
bd5df3af5f
API: Unescape search suggestions (#4218)
Previously, the suggestion were HTML encoded. This PR fixes that.
2024-02-12 22:03:33 +01:00
Samantaz Fox
9bd2072e1d
API: Add playlist and start time to resolve_url
This adds `playlistId` and `startTimeSeconds` to /api/v1/resolveurl if these
informations were returned by Youtube's endpoint.
2024-02-12 22:01:08 +01:00
Samantaz Fox
3b4358dbd4
Extractors: Don't error if AuthorId does not exist (#3869)
Some playlist author's don't have a YouTube channel, so does movies.
This caused various extractors (related videos, search) to fail.

Closes the following issues:
2530, 3349, 3766, 3812, 4133
2024-02-12 21:54:17 +01:00
absidue
72bcd3cc72 Handle non-200 status codes for YouTube DASH manifests 2024-02-12 18:55:15 +01:00
Émilien (perso)
cf686202e0
Merge pull request #4423 from tleydxdy/xml-namespace
Fix pubsub feed parsing
2024-02-12 08:29:44 +01:00
shironeko
6b33820f1f Add missing translation strings
closes #3120
2024-02-08 19:01:19 -05:00
shironeko
98c421e9f5 Fix when video from pubsub is a scheduled event 2024-02-08 18:58:23 -05:00
shironeko
c864a63b6d Fix pubsub feed parsing
similar to what's done in #3793, this is causing an assert on my instance
2024-02-08 17:05:11 -05:00
syeopite
0ad2eff2a4
WebVTT::Builder: Add logic to escape special chars 2024-01-30 15:25:45 -08:00
ThetaDev
c005ada487
fix: prevent censoring of self-harm related search queries (#4403)
* fix: prevent censoring of self-harm related search queries

* fix: yt_filters_spec with new flag
2024-01-29 14:59:25 +01:00
toabr
4a339df5c4 CSS: expand #contents width on small screens 2024-01-27 00:38:47 +01:00
syeopite
1c0b4205d4
Add parameter to disable force_resolve in make_client (#4335)
* Add option to disable force_resolve in make_client

Some websites such as archive.org and textcaptcha.com
does not support IPv6 and as such requests fail when Invidious requests
with IPv6 to those services.

* Reenable force_resolve on pubsub subcribe request

* Make force_resolve false by default in make_client

* Remove missed explicit force_resolve=false
2024-01-10 23:01:00 +00:00
syeopite
b16f66ef00
Exempt issues with "exempt-stale" from staling (#4385)
The exempt-stale label was not actually set to exempt issues from staling...
2024-01-10 20:40:19 +00:00
vojkovic
7cca1285aa
Fix two swapped function names 2024-01-06 15:51:31 +08:00
pitkajuh
c059829035 Fix typo 2024-01-05 20:39:29 +01:00
ChunkyProgrammer
7da4a7f72b add null safety to clip parsing 2023-12-26 22:05:09 -05:00
nixos script
0917efd9cb fix issue where scope would be missing the * if the user was not logged in before calling the authorize endpoint
fix #4200
2023-12-21 13:52:19 +08:00
ChunkyProgrammer
090b470bfc fix potential memory leak 2023-12-19 23:07:18 -05:00
Luigi
97c4165f55
Improve depends_on docker-compose (#4249)
* Improve depends_on checking the service is up and healthy before start the service that might cause issue first boot

* Docker version Ubuntu 22.04 has a version which doesn't support restart
2023-12-18 22:18:05 +00:00
guidiasz
87a8207f37 fix: "Watch on YouTube" preserve current playlist 2023-12-18 13:23:55 -03:00
ChunkyProgrammer
fe8b1b4cc4 Add title to toggle theme icon 2023-12-07 11:43:56 -05:00
ChunkyProgrammer
f1edb1d6bf fix related video author when id is empty 2023-12-07 09:39:33 -05:00
Chunky programmer
b5f8b4542a Search: Don't error if AuthorId does not exist 2023-12-07 09:39:33 -05:00
ChunkyProgrammer
b344d98c25 Add API endpoint for Clips 2023-12-07 09:39:04 -05:00
ChunkyProgrammer
8c22e6a640 use start time and endtime for clips 2023-12-07 09:39:03 -05:00
ChunkyProgrammer
6488794218 Unescape search suggestions 2023-12-07 09:36:59 -05:00
Samantaz Fox
7b6930c16b
Remove the 'fields' parameter on the client side too 2023-11-23 18:30:42 +01:00
Samantaz Fox
9d5fa2bcc4
Helpers: remove JSONFilter logic 2023-11-23 18:30:42 +01:00
Samantaz Fox
9310d09f93
Kemal: remove APIHandler middleware 2023-11-23 18:30:37 +01:00
Corné Dorrestijn
16c79f1ef5
Fixed aspect ratio for thumnails to prevent CLS 2023-11-21 08:14:45 +01:00
Brahim Hadriche
b40cf6544a Revert "Make head request to resolve short urls"
This reverts commit 7e267da5be.
2023-11-19 16:06:29 -05:00
Brahim Hadriche
3881038a32 format 2023-10-26 17:51:38 -04:00
Brahim Hadriche
7e267da5be Make head request to resolve short urls 2023-10-26 17:48:58 -04:00
Brahim Hadriche
d7901c1e0d type fix 2023-10-26 17:35:52 -04:00
Brahim Hadriche
85a5bbd696 Add playlist and start time to the resolve url 2023-10-26 17:24:53 -04:00
91 changed files with 3088 additions and 2401 deletions

View file

@ -16,7 +16,7 @@ jobs:
days-before-stale: 365 days-before-stale: 365
days-before-pr-stale: 90 days-before-pr-stale: 90
days-before-close: 30 days-before-close: 30
exempt-pr-labels: blocked exempt-pr-labels: blocked,exempt-stale
stale-issue-message: 'This issue has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely outdated. If you think this issue is still relevant and applicable, you just have to post a comment and it will be unmarked.' stale-issue-message: 'This issue has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely outdated. If you think this issue is still relevant and applicable, you just have to post a comment and it will be unmarked.'
stale-pr-message: 'This pull request has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely abandoned or outdated. If you think this pull request is still relevant and applicable, you just have to post a comment and it will be unmarked.' stale-pr-message: 'This pull request has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely abandoned or outdated. If you think this pull request is still relevant and applicable, you just have to post a comment and it will be unmarked.'
stale-issue-label: "stale" stale-issue-label: "stale"

View file

@ -1,844 +1,20 @@
# Note: This is no longer updated and links to omarroths repo, which doesn't exist anymore. # CHANGELOG
# 0.20.0 (2019-011-06) ## 2024-04-26
# Version 0.20.0: Custom Playlists Major bug fixes:
* Videos: Use android test suite client (#4650, thanks @SamantazFox)
It's been quite a while since the last release! There've been [198 commits](https://github.com/omarroth/invidious/compare/0.19.0..0.20.0) from 27 contributors. * Trending: Un-nest category if this is the only one (#4600, thanks @ChunkyProgrammer)
* Comments: Add support for new format (#4576, thanks @ChunkyProgrammer)
A couple smaller features have since been added. Channel pages and playlists in particular have received a bit of a face-lift, with both now displaying their descriptions as expected, and playlists providing video count and published information. Channels will also now provide video descriptions in their RSS feed.
Minor bug fixes:
Turkish (tr), Chinese (zh-TW, in addition to zh-CN), and Japanese (jp) are all now supported languages. Thank you as always to the hard work done by translators that makes this possible. * API: Add bitrate to formatStreams too (#4590, thanks @absidue)
* API: Add 'authorVerified' field on recommended videos (#4562, thanks @ChunkyProgrammer)
The feed menu and default home page are both now configurable for registered and unregistered users, and is quite a bit of an improvement for users looking to reduce distractions for their daily use. * Videos: Add support for new likes format (#4462, thanks @ChunkyProgrammer)
* Proxy: Handle non-200 HTTP codes on DASH manifests (#4429, thanks @absidue)
## For Administrators
Other improvements:
`feed_menu` and `default_home` are now configurable by the user, and have therefore been moved into `default_user_preferences`: * Remove legacy proxy code (#4570, thanks @syeopite)
* API: convey info "is post live" from Youtube response (#4569, thanks @ChunkyProgrammer)
```yaml * API: Parse channel's tags (#4294, thanks @ChunkyProgrammer)
feed_menu: ["Popular", "Top"] * Translations update from Hosted Weblate (#4164, thanks to our many translators)
default_home: Top
# becomes:
default_user_preferences:
feed_menu: ["Popular", "Top"]
default_home: Top
```
Several new options have also been added, including the ability to set a support email for the instance using `admin_email: EMAIL`, and forcing the use of a specific connection in the case of rate-limiting using `force_resolve` (see below).
## For Developers
Authenticated endpoints are now [properly documented](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints), as well how to generate and use API tokens. My hope is that this makes some of the more [interesting](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authnotifications) endpoints more accessible for developers to use in their own applications.
API endpoints for interacting with custom playlists have also been added with documentation available [here](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authplaylists).
## Custom playlists
This is probably the feature that has been the longest in the pipe and that I'm quite pleased is now implemented. It is now possible to create custom playlists, which can be played and edited through Invidious. API endpoints have also been added (documentation [here](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authplaylists)).
Overall I'm quite pleased with how smoothly it has been rolled out and with the experience so far, and I'm exctited for how it can be extended and improved in future.
## [instances.invidio.us](https://instances.invidio.us)
It is now possible to view a list of public instances (as provided in the [wiki](https://github.com/omarroth/invidious/wiki/Invidious-Instances)) through an API or a pretty new interface [here](https://instances.invidio.us). It combines uptime information, statistics from each instance and basic information already provided in the wiki. I expect it should be much more user-friendly than compiling the information yourself, and is already used by [Invidition](https://codeberg.org/Booteille/Invidition) to provide a list of instances for users to choose from.
The site itself is licensed under the AGPLv3 and the source is available [here](https://github.com/omarroth/instances.invidio.us).
## Video unavailable [#811](https://github.com/omarroth/invidious/issues/811)
Many users have likely noticed this error message if using Invidious directly or through another service, such as FreeTube. This issue is caused by rate-limiting by Google, and is not a new issuee for projects like Invidious (notably [youtube-dl](https://github.com/ytdl-org/youtube-dl#http-error-429-too-many-requests-or-402-payment-required)) and appears to be affecting smaller, private instances as well.
There is not a permanent fix for administrators currently, however there is some information available [here](https://github.com/omarroth/invidious/issues/811#issuecomment-540017772) that may provide a temporary solution. Unfortanately, in most cases the best option is to wait for the instance to be unbanned or to move the instance to a different IP. A more informative error message is also now provided, which should help an administrator more quickly diagnose the problem.
For those interested, I would recommend following [#811](https://github.com/omarroth/invidious/issues/811) for any future progress on the issue.
## BAT verified publisher
I'm quite late to this announcement, however I'm pleased to mention that Invidious is now a BAT verified publisher! I would recommend looking [here](https://basicattentiontoken.org/about/) or [here](https://www.reddit.com/r/BATProject/comments/7cr7yc/new_to_bat_read_this_introduction_to_basic/) for learning more about what it is and how it works. Overall I think it makes an interesting substitute for services like Liberapay, and a (hopefully) much less-intrusive alternative to direct advertising.
BAT is combined under other cryptocurrencies below. Currently there's a fairly significant delay in payout, which is the reason for the large fluctuation in crypto donations between September and October (and also the reason for the late announcement).
## Release schedule
Currently I'm quite pleased with the current state of the project. There's plenty of things I'd still like to add, however at this point I expect the rate of most new additions will slow down a bit, with more focus on stabililty and any long-standing bugs.
Because of this, I'm planning on releasing a new version quarterly, with any necessary hotfixes being pushed as a new patch release as necessary. As always it will be possible to run Invidious directly from [master](https://github.com/omarroth/invidious/wiki/Updating) if you'd still like to have the lastest version.
I'll plan on providing finances each release, with a similar monthly breakdown as below.
## Finances for September 2019
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$64.37
- [Liberapay](https://liberapay.com/omarroth) : \$76.04
- Crypto : ~\$99.89 (converted from BAT, BCH, BTC)
- Total : \$240.30
### Expenses
- invidious-lb1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node11 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node12 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node13 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node14 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node15 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node16 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$135.00
## Finances for October 2019
- [Liberapay](https://liberapay.com/omarroth) : \$134.40
- Crypto : ~\$8.29 (converted from BAT, BCH, BTC)
- Total : \$142.69
### Expenses
- invidious-lb1 (nyc1) : \$5.00 (load balancer)
- invidious-lb2 (nyc1) : \$5.00 (load balancer)
- invidious-lb3 (nyc1) : \$5.00 (load balancer)
- invidious-lb4 (nyc1) : \$5.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node11 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node12 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node13 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node14 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node15 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node16 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node17 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node18 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$155.00
# 0.19.0 (2019-07-13)
# Version 0.19.0: Communities
Hello again everyone! Focus this month has mainly been on improving playback performance, along with a couple new features I'd like to announce. There have been [109 commits](https://github.com/omarroth/invidious/compare/0.18.0...0.19.0) this past month from 10 contributors.
This past month has seen the addition of Chinese (`zh-CN`) and Icelandic (`is`) translations. I would like to give a huge thanks to their respective translators, and again an enormous thanks to everyone who helps translate the site.
I'm delighted to mention that [FreeTube 0.6.0](https://github.com/FreeTubeApp/FreeTube) now supports 1080p thanks to the Invidious API. I would very much recommend reading the [relevant post](https://freetube.writeas.com/freetube-release-0-6-0-beta-1080p-and-a-lot-of-qol) for some more information on how it works, along with several other major improvements. Folks that are interested in adding similar functionality for their own projects should feel free to get in touch.
This past month there has been quite a bit of work on improving memory usage and improving download and playback speeds. As mentioned in the previous release, some extra hardware has been allocated which should also help with this. I'm still looking for ways to improve performance and feedback is always appreciated.
Along with performance, a couple quality of life improvements have been added, including author thumbnails and banners, clickable titles for embedded videos, and better styling for captions, among some other enhancements.
## Communities
Support for YouTube's [communities tab](https://creatoracademy.youtube.com/page/lesson/community-tab) has been added. It's a very interesting but surprisingly unknown feature. Essentially, providing comments for a channel, rather than a video, where an author can post updates for their subscribers.
It's commonly used to promote interesting links and foster discussion. I hope this feature helps people find more interesting content that otherwise would have been overlooked.
## For Developers
For accessing channel communities, an `/api/v1/channels/comments/:ucid` endpoint has been added, with similar behavior and schema to `/api/v1/comments/:id`, with an extra `attachment` field for top-level comments. More info on usage and available data can be found in the [wiki](https://github.com/omarroth/invidious/wiki/API#get-apiv1channelscommentsucid-apiv1channelsucidcomments).
An `/api/v1/auth/feeds` endpoint has been added for programmatically accessing a user's subscription feed, with options for displaying notifications and filtering an existing feed.
An `/api/v1/search/suggestions` endpoint has been added for retrieving suggestions for a given query.
## For Administrators
It is now possible to disable more resource intensive features, such as downloads and DASH functionality by adding `disable_proxy` to your config. See [#453](https://github.com/omarroth/invidious/issues/453) and the [Wiki](https://github.com/omarroth/invidious/wiki/Configuration) for more information and example usage. I expect this to be a big help for folks with limited bandwidth when hosting their own instances.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$38.39
- [Liberapay](https://liberapay.com/omarroth) : \$84.85
- Crypto : ~\$0.00 (converted from BCH, BTC)
- Total : \$123.24
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$105.00
The goal on Patreon has been updated to reflect the above expenses. As mentioned above, the main reason for more hardware is to improve playback and download speeds, although I'm still looking into improving performance without allocating more hardware.
As always I'm grateful for everyone's support and feedback. I'll see you all next month.
# 0.18.0 (2019-06-06)
# Version 0.18.0: Native Notifications and Optimizations
Hope everyone has been doing well. This past month there have been [97 commits](https://github.com/omarroth/invidious/compare/0.17.0...0.18.0) from 10 contributors. For the most part changes this month have been on optimizing various parts of the site, mainly subscription feeds and support for serving images and other assets.
I'm quite happy to mention that support for Greek (`el`) has been added, which I hope will continue to make the site accessible for more users.
Subscription feeds will now only update when necessary, rather than periodically. This greatly lightens the load on DB as well as making the feeds generally more responsive when changing subscriptions, importing data, and when receiving new uploads.
Caching for images and other assets should be greatly improved with [#456](https://github.com/omarroth/invidious/issues/456). JavaScript has been pulled out into separate files where possible to take advantage of this, which should result in lighter pages and faster load times.
This past month several people have encountered issues with downloads and watching high quality video through the site, see [#532](https://github.com/omarroth/invidious/issues/532) and [#562](https://github.com/omarroth/invidious/issues/562). For this coming month I've allocated some more hardware which should help with this, and I'm also looking into optimizing how videos are currently served.
## For Developers
`viewCount` is now available for `/api/v1/popular` and all videos returned from `/api/v1/auth/notifications`. Both also now provide `"type"` for indicating available information for each object.
An `/authorize_token` page is now available for more easily creating new tokens for use in applications, see [this comment](https://github.com/omarroth/invidious/issues/473#issuecomment-496230812) in [#473](https://github.com/omarroth/invidious/issues/473) for more details.
A POST `/api/v1/auth/notifications` endpoint is also now available for correctly returning notifications for 150+ channels.
## For Administrators
There are two new schema changes for administrators: `views` for adding view count to the popular page, and `feed_needs_update` for tracking feed changes.
As always the relevant migration scripts are provided which should run when following instructions for [updating](https://github.com/omarroth/invidious/wiki/Updating). Otherwise, adding `check_tables: true` to your config will automatically make the required changes.
## Native Notifications
[<img src="https://omar.yt/81c3ae1839831bd9300d75e273b6552a86dc2352/native_notification.png" height="160" width="472">](https://omar.yt/81c3ae1839831bd9300d75e273b6552a86dc2352/native_notification.png "Example of native notification, available in repository under screnshots/native_notification.png")
It is now possible to receive [Web notifications](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API) from subscribed channels.
You can enable notifications by clicking "Enable web notifications" in your preferences. Generally they appear within 20-60 seconds of a new video being uploaded, and I've found them to be an enormous quality of life improvement.
Although it has been fairly stable, please feel free to report any issues you find [here](https://github.com/omarroth/invidious/issues) or emailing me directly at omarroth@protonmail.com.
Important to note for administrators is that instances require [`use_pubsub_feeds`](https://github.com/omarroth/invidious/wiki/Configuration) and must be served over HTTPS in order to correctly send web notifications.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$49.73
- [Liberapay](https://liberapay.com/omarroth) : \$100.57
- Crypto : ~\$11.12 (converted from BCH, BTC)
- Total : \$161.42
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$85.00
See you all next month!
# 0.17.0 (2019-05-06)
# Version 0.17.0: Player and Authentication API
Hello everyone! This past month there have been [130 commits](https://github.com/omarroth/invidious/compare/0.16.0..0.17.0) from 11 contributors. Large focus has been on improving the player as well as adding API access for other projects to make use of Invidious.
There have also been significant changes in preparation of native notifications (see [#195](https://github.com/omarroth/invidious/issues/195), [#469](https://github.com/omarroth/invidious/issues/469), [#473](https://github.com/omarroth/invidious/issues/473), and [#502](https://github.com/omarroth/invidious/issues/502)), and playlists. I expect to see both of these to be added in the next release.
I'm quite happy to mention that new translations have been added for Esperanto (`eo`) and Ukranian (`uk`). Support for pluralization has also been added, so it should now be possible to make a more native experience for speakers in other languages. The system currently in place is a bit cumbersome, so for any help using this feature please get in touch!
## For Administrators
A `check_tables` option has been added to automatically migrate without the use of custom scripts. This method will likely prove to be much more robust, and is currently enabled for the official instance. To prevent any unintended changes to the DB, `check_tables` is disabled by default and will print commands before executing. Having this makes features that require schema changes much easier to implement, and also makes it easier to upgrade from older instances.
As part of [#303](https://github.com/omarroth/invidious/issues/303), a `cache_annotations` option has been added to speed up access from `/api/v1/annotations/:id`. This vastly improves the experience for videos with annotations. Currently, only videos that contain legacy annotations will be cached, which should help keep down the size of the cache. `cache_annotations` is disabled by default.
## For Developers
An authorization API has been added which allows other applications to read and modify user subscriptions and preferences (see [#473](https://github.com/omarroth/invidious/issues/473)). Support for accessing user feeds and notifications is also planned. I believe this feature is a large step forward in supporting syncing subscriptions and preferences with other services, and I'm excited to see what other developers do with this functionality.
Support for server-to-client push notifications is currently underway. This allows Invidious users, as well as applications using the Invidious API, to receive notifications about uploads in near real-time (see #469). An `/api/v1/auth/notifications` endpoint is currently available. I'm very excited for this to be integrated into the site, and to see how other developers use it in their own projects.
An `/api/v1/storyboards/:id` endpoint has been added for accessing storyboard URLs, which allows developers to add video previews to their players (see below).
## Player
Support for annotations has been merged into master with [#303](https://github.com/omarroth/invidious/issues/303), thanks @glmdgrielson! Annotations can be enabled by default or only for subscribed channels, and can also be toggled per video. I'm extremely proud of the progress made here, and I'm so thankful to everyone that has made this possible. I expect this to be the last update with regards to supporting annotations, but I do plan on continuing to improve the experience as much as possible.
The Invidious player now supports video previews and a corresponding API endpoint `/api/v1/storyboards/:id` has been added for developers looking to add similar functionality to their own players. Not much else to say here. Overall it's a very nice quality of life improvement and an attractive addition to the site.
It is now possible to select specific sources for videos provided using DASH (see [#34](https://github.com/omarroth/invidious/issues/34)). I would consider support largely feature complete, although there are still several issues to be fixed before I would consider it ready for larger rollout. You can watch videos in 1080p by setting `Default quality` to `dash` in your preferences, or by adding `&quality=dash` to the end of video URLs.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$49.73
- [Liberapay](https://liberapay.com/omarroth) : \$63.03
- Crypto : ~\$0.00 (converted from BCH, BTC)
- Total : \$112.76
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$80.00
That's all for now. Thanks!
# 0.16.0 (2019-04-06)
# Version 0.16.0: API Improvements and Annotations
Hello again! This past month has seen [116 commits](https://github.com/omarroth/invidious/compare/0.15.0..0.16.0) from 13 contributors and a couple important changes I'd like to announce.
A privacy policy is now available [here](https://invidio.us/privacy). I've done my best to explain things as clearly as possible without oversimplifying, and would very much recommend reading it if you're concerned about your privacy and want to learn more about how Invidious uses your data. Please let me know if there is anything that needs clarification.
I'm also very happy to announce that a Spanish translation has been added to the site. You can use it with `?hl=es` or by setting `es` as your default locale. As always I'm extremely grateful to translators for making the site accessible to more people.
## For Administrators
Invidious now supports server-to-server [push notifications](https://developers.google.com/youtube/v3/guides/push_notifications). This uses [PubSubHubbub](https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html) to automatically handle new videos sent to an instance, which is less resource intensive and generally faster. Note that it will not pull all videos from a subscribed channel, so recommended usage is in addition to `channel_threads`. Using PubSub requires a valid `domain` that updates can be sent to, and a random string that can be used to sign updates sent to the instance. You can enable it by adding `use_pubsub_feeds: true` to your `config.yml`. See [Configuration](https://github.com/omarroth/invidious/wiki/Configuration) for more info.
Unfortunately there are a couple necessary changes to the DB to support `liveNow` and `premiereTimestamp` in subscription feeds. Migration scripts have been provided that should be used automatically if following the instructions [here](https://github.com/omarroth/invidious/wiki/Updating).
You can now configure default user preferences for your instance. This allows you to set default locale, player preferences, and more. See [#415](https://github.com/omarroth/invidious/issues/415) for more details and example usage.
## For Developers
The [fields](https://developers.google.com/youtube/v3/getting-started#fields) API has been added with [#429](https://github.com/omarroth/invidious/pull/429) and is now supported on all JSON endpoints, thanks [**@afrmtbl**](https://github.com/afrmtbl)! Synax is straight-forward and can be used to reduce data transfer and create a simpler response for debugging. You can see an example [here](https://invidio.us/api/v1/videos/CvFH_6DNRCY?pretty=1&fields=title,recommendedVideos/title). I've been quite happy using it and hope it is similarly useful for others.
An `/api/v1/annotations/:id` endpoint has been added for pulling legacy annotation data from [this](https://archive.org/details/youtubeannotations) archive, see below for more details. You can also access annotation data available on YouTube using `?source=youtube`, although this will only return card data as legacy annotations were deleted on January 15th.
A couple minor changes to existing endpoints:
- A `premiereTimestamp` field has been added to `/api/v1/videos/:id`
- A `sort_by` param has been added to `/api/v1/comments/:id`, supports `new`, `top`.
More info is available in the [documentation](https://github.com/omarroth/invidious/wiki/API).
## Annotations
I'm pleased to announce that annotation data is finally available from the roughly 1.4 billion videos archived as part of [this](https://www.reddit.com/r/DataHoarder/comments/aa6czg/youtube_annotation_archive/) project. They are accessible from the Internet Archive [here](https://archive.org/details/youtubeannotations) or as a 355GB torrent, see [here](https://www.reddit.com/r/DataHoarder/comments/b7imx9/youtube_annotation_archive_annotation_data_from/) for more details. A corresponding `/api/v1/annotations/:id` endpoint has been added to Invidious which uses the collection from IA to provide legacy annotations.
Support for them in the player is possible thanks to [this](https://github.com/afrmtbl/videojs-youtube-annotations) plugin developed by [**@afrmtbl**](https://github.com/afrmtbl). A PR for adding support to the site is available as [#303](https://github.com/omarroth/invidious/pull/303). There's also an [extension](https://github.com/afrmtbl/AnnotationsRestored) for overlaying them on top of the YouTube player (again thanks to [**@afrmtbl**](https://github.com/afrmtbl)), and an [extension](https://tech234a.bitbucket.io/AnnotationsReloaded?src=invidious) for hooking into code still present in the YouTube player itself, developed by [**@tech234a**](https://github.com/tech234a).
I would recommend reading the [official announcement](https://www.reddit.com/r/DataHoarder/comments/b7imx9/youtube_annotation_archive_annotation_data_from/) for more details. I would like to again thank everyone that helped contribute to this project.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$42.42
- [Liberapay](https://liberapay.com/omarroth) : \$70.11
- Crypto : ~\$1.76 (converted from BCH, BTC, BSV)
- Total : \$114.29
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$80.00
This past month the site saw a couple abnormal peaks in traffic, so an additional webserver has been added to match the increased load. The goal on Patreon has been updated to match the above expenses.
Thanks everyone!
# 0.15.0 (2019-03-06)
## Version 0.15.0: Preferences and Channel Playlists
The project has seen quite a bit of activity this past month. Large focus has been on fixing bugs, but there's still quite a few new features I'm happy to announce. There have been [133 commits](https://github.com/omarroth/invidious/compare/0.14.0...0.15.0) from 15 contributors this past month.
As a couple miscellaneous changes, a couple [nice screenshots](https://github.com/omarroth/invidious#screenshots) have been added to the README, so folks can see more of what the site has to offer without creating an account.
The footer has also been cleaned up quite a bit, and now displays the current version, so it's easier to know what features are available from the current instance.
## For Administrators
This past month there has been a minor release - `0.14.1` - which fixes a breaking change made by YouTube for their polymer redesign.
There have been several new features that unfortunately require a database migration. There are migration scripts provided in `config/migrate-scripts`, and the [wiki](https://github.com/omarroth/invidious/wiki/Updating) has instructions for automatically applying them. I'll do my best to keep those changes to a minimum, and expect to see a corresponding script to automatically apply any new changes.
Administrator preferences have been added with [#312](https://github.com/omarroth/invidious/issues/312), which allows administrators to customize their instance. Administrators can change the order of feed menus, change the default homepage, disable open registration, and several other options. There's a short 'how-to' [here](https://github.com/omarroth/invidious/issues/312#issuecomment-468831842), and the new options are documented [here](https://github.com/omarroth/invidious/wiki/Configuration).
An `/api/v1/stats` endpoint has been added with [#356](https://github.com/omarroth/invidious/issues/356), which reports the instance version and number of active users. Statistics are disabled by default, and can be enabled in administator preferences. Statistics for the official instance are available [here](https://invidio.us/api/v1/stats?pretty=1).
## For Developers
`/api/v1/channels/:ucid` now provides an `autoGenerated` tag, which returns true for topic channels, and larger genre channels generated by YouTube. These channels don't have any videos of their own, so `latestVideos` will be empty. It is recommended instead to display a list of playlists generated by YouTube.
You can now pull a list of playlists from a channel with `/api/v1/channels/playlists/:ucid`. Supported options are documented in the [wiki](https://github.com/omarroth/invidious/wiki/API#get-apiv1channelsplaylistsucid-apiv1channelsucidplaylists). Pagination is handled with a `continuation` token, which is generated on each call. Of note is that auto-generated channels currently have one page of results, and subsequent calls will be empty.
For quickly pulling the latest 30 videos from a channel, there is now `/api/v1/channels/latest/:ucid`. It is much faster than a call to `/api/v1/channels/:ucid`. It will not convert an author name to a valid ucid automatically, and will not return any extra data about a channel.
## Preferences
In addition to administrator preferences mentioned above, you can now change your preferences without an account (see [#42](https://github.com/omarroth/invidious/pull/42)). I think this is quite an improvement to the usability of the site, and is much friendlier to privacy-conscious folks that don't want to make an account. Preferences will be automatically imported to a newly created account.
Several issues with sorting subscriptions have been fixed, and `/manage_subscriptions` has been sped up significantly. The subscription feed has also seen a bump in performance. Delayed notifications have unfortunately started becoming a problem now that there are more users on the site. Some new changes are currently being tested which should mostly resolve the issue, so expect to see more in the next release.
## Channel Playlists
You can now view available playlists from a channel, and [auto-generated channels](https://invidio.us/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ) are no longer empty. You can sort as you would on YouTube, and all the same functionality should be available. I'm quite pleased to finally have it implemented, since it's currently the only data available from the above mentioned auto-generated channels, and makes it much easier to consume music on the site.
There's also more discussion on improving Invidious for streaming music in [#304](https://github.com/omarroth/invidious/issues/304), and adding support for music.youtube.com. I would appreciate any thoughts on how to improve that experience, since it's a very large and useful part of YouTube.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$42.42
- [Liberapay](https://liberapay.com/omarroth) : \$30.97
- Crypto : ~\$0.00 (converted from BCH, BTC)
- Total : \$73.39
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$75.00
It's been very humbling to see how fast the project has grown, and I look forward to making the site even better. Thank you everyone.
# 0.14.0 (2019-02-06)
## Version 0.14.0: Community
This last month several contributors have made improvements specifically for the people using this project. New pages have been added to the wiki, and there is now a [Matrix Server](https://riot.im/app/#/room/#invidious:matrix.org) and IRC channel so it's easier and faster for people to ask questions or chat. There have been [101 commits](https://github.com/omarroth/invidious/compare/0.13.0...0.14.0) since the last major release from 8 contributors.
It has come to my attention in the past month how many people are self-hosting, and I would like to make it easier for them to do so.
With that in mind, expect future releases to have a section for For Administrators (if any relevant changes) and For Developers (if any relevant changes).
## For Administrators
This month the most notable change for administrators is releases. As always, there will be a major release each month. However, a new minor release will be made whenever there are any critical bugs that need to be fixed.
This past month is the first time there has been a minor release - `0.13.1` - which fixes a breaking change made by YouTube. Administrators using versioning for their instances will be able to rely on the latest version, and should have a system in place to upgrade their instance as soon as a new release is available.
Several new pages have been added to the [wiki](https://github.com/omarroth/invidious/wiki#for-administrators) (as mentioned below) that will help administrators better setup their own instances. Configuration, maintenance, and instructions for updating are of note, as well as several common issues that are encountered when first setting up.
## For Developers
There's now a `pretty=1` parameter for most endpoints so you can view data easily from the browser, which is convenient for debugging and casual use. You can see an example [here](https://invidio.us/api/v1/videos/CvFH_6DNRCY?pretty=1).
Unfortunately the `/api/v1/insights/:id` endpoint is no longer functional, as YouTube removed all publicly available analytics around a month ago. The YouTube endpoint now returns a 404, so it's unlikely it will be functional again.
## Wiki
There have been a sizable number of changes to the Wiki, including a [list of public Invidious instances](https://github.com/omarroth/invidious/wiki/Invidious-Instances), the [list of extensions](https://github.com/omarroth/invidious/wiki/Extensions), and documentation for administrators (as mentioned above) and developers.
The wiki is editable by anyone so feel free to add anything you think is useful.
## Matrix & IRC
Thee is now a [Matrix Server](https://riot.im/app/#/room/#invidious:matrix.org) for Invidious, so please feel free to hop on if you have any questions or want to chat. There is also a registered IRC channel: #invidious on Freenode which is bridged to Matrix.
## Features
Several new features have been added, including a download button, creator hearts and comment colors, and a French translation.
There have been fixes for Google logins, missing text in locales, invalid links to genre channels, and better error handling in the player, among others.
Several fixes and features are omitted for space, so I'd recommend taking a look at the [compare tab](https://github.com/omarroth/invidious/compare/0.13.0...0.14.0) for more information.
## Annotations Update
Annotations were removed January 15th, 2019 around15:00 UTC. Before they were deleted we were able to archive annotations from around 1.4 billion videos. I'd very much recommend taking a look [here](https://www.reddit.com/r/DataHoarder/comments/al7exa/youtube_annotation_archive_update_and_preview/) for more information and a list of acknowledgements. I'm extremely thankful to everyone who was able to contribute and I'm glad we were able to save such a large part of internet history.
There's been large strides in supporting them in the player as well, which you can follow in [#303](https://github.com/omarroth/invidious/pull/303). You can preview the functionality at https://dev.invidio.us . Before they are added to the main site expect to see an option to disable them, both site-wide and per video.
Organizing this project has unfortunately taken up quite a bit of my time, and I've been very grateful for everyone's patience.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$49.42
- [Liberapay](https://liberapay.com/omarroth) : \$27.89
- Crypto : ~\$0.00 (converted from BCH, BTC)
- Total : \$77.31
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$75.00
As always I'm grateful for everyone's contributions and support. I'll see you all in March.
# 0.13.1 (2019-01-19)
##
# 0.13.0 (2019-01-06)
## Version 0.13.0: Translations, Annotations, and Tor
I hope everyone had a happy New Year! There's been a couple new additions since last release, with [44 commits](https://github.com/omarroth/invidious/compare/0.12.0...0.13.0) from 9 contributors. It's been quite a year for the project, and I hope to continue improving the project into 2019! Starting off the new year:
## Translations
I'm happy to announce support for translations has been added with [`a160c64`](https://github.com/omarroth/invidious/a160c64). Currently, there is support for:
- Arabic (`ar`)
- Dutch (`nl`)
- English (`en-US`)
- German (`de`)
- Norwegian Bokmål (`nb_NO`)
- Polish (`pl`)
- Russian (`ru`)
Which you can change in your preferences under `Language`. You can also add `&hl=LANGUAGE` to the end of any request to translate it to your preferred language, for example https://invidio.us/?hl=ru. I'd like to say thank you again to everyone who has helped translate the site! I've mentioned this before, but I'm delighted that so many people find the project useful.
## Annotations
Recently, [YouTube announced that all annotations will be deleted on January 15th, 2019](https://support.google.com/youtube/answer/7342737). I believe that annotations have a very important place in YouTube's history, and [announced a project to archive them](https://www.reddit.com/r/DataHoarder/comments/aa6czg/youtube_annotation_archive/).
I expect annotations to be supported in the Invidious player once archiving is complete (see [#110](https://github.com/omarroth/invidious/issues/110) for details), and would also like to host them for other developers to use in their projects.
The code is available [here](https://github.com/omarroth/archive), and contains instructions for running a worker if you would like to contribute. There's much more information available in the announcement as well for anyone who is interested.
## Tor
I unfortunately missed the chance to mention this in the previous release, but I'm now happy to announce that you can now view Invidious through Tor at the following links:
kgg2m7yk5aybusll.onion
axqzx4s6s54s32yentfqojs3x5i7faxza6xo3ehd4bzzsg2ii4fv2iid.onion
Invidious is well suited to use through Tor, as it does not require any JS and is fairly lightweight. I'd recommend looking [here](https://diasp.org/posts/10965196) and [here](https://www.reddit.com/r/TOR/comments/a3c1ak/you_can_now_watch_youtube_videos_anonymously_with/) for more details on how to use the onion links, and would like to say thank you to [/u/whonix-os](https://www.reddit.com/user/whonix-os) for suggesting it and providing support setting setting them up.
## Popular and Trending
You can now easily view videos trending on YouTube with [`a16f967`](https://github.com/omarroth/invidious/a16f967). It also provides support for viewing YouTube's various categories categories, such as `News`, `Gaming`, and `Music`. You can also change the `region` parameter to view trending in different countries, which should be made easier to use in the coming weeks.
A link to `/feed/popular` has also been added, which provides a list of videos sorted using the algorithm described [here](https://github.com/omarroth/invidious/issues/217#issuecomment-436503761). I think it better reflects what users watch on the site, but I'd like to hear peoples' thoughts on this and on how it could be improved.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth): \$64.63
- [Liberapay](https://liberapay.com/omarroth) : \$30.05
- Crypto : ~\$28.74 (converted from BCH, BTC)
- Total : \$123.42
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$75.00
### What will happen with what's left over?
I believe this is the first month that all expenses have been fully paid for by donations. Thank you! I expect to allocate the current amount for hardware to improve performance and for hosting annotation data, as mentioned above.
Anything that is left over is kept to continue hosting the project for as long as possible. Thank you again everyone!
I think that's everything for 2018. There's lots still planned, and I'm very excited for the future of this project!
# 0.12.0 (2018-12-06)
## Version 0.12.0: Accessibility, Privacy, Transparency
Hello again, it's been a while! A lot has happened since the last release. Invidious has seen [134 commits](https://github.com/omarroth/invidious/compare/0.11.0...0.12.0) from 3 contributors, and I'm quite happy with the progress that has been made. I enjoyed this past month, and I believe having a monthly release schedule allows me to focus on more long-term improvements, and I hope people enjoy these more substantial updates as well.
## Accessability and Privacy
There have been quite a few improvements for user privacy, and improvements that improve accessibility for both people and software.
You can now view comments without JS with [`19516ea`](https://github.com/omarroth/invidious/19516ea). Currently, this functionality is limited to the first 20 comments, but expect this functionality to be improved to come as close to the JS version as possible. Folks can track progress in [#204](https://github.com/omarroth/invidious/issues/204).
Invidious is now compatible with [LibreJS](https://www.gnu.org/software/librejs/), and provides license information [here](https://invidio.us/licenses) with [`7f868ec`](https://github.com/omarroth/invidious/7f868ec). As expected, all libraries are compatible under the AGPLv3, and I'm happy to mention that no other changes were required to make Invidious compatible with LibreJS.
A DNT policy has also been added with [`9194f47`](https://github.com/omarroth/invidious/9194f47) for compatibility with [Privacy Badger](https://www.eff.org/privacybadger). I'm pleased to mention that here too no other changes had to be made in order for Invidious to be compatible with this extension. I expect a privacy policy to be added soon as well, so users can better understand how Invidious uses their data.
For users that are visually impaired, there is now a text CAPTCHA available so it's easier to register and login. Because of the simple front-end of the project, I expect screen readers and other software to be able to easily understand the site's interface. In combination with the ability to listen-only, I believe Invidious is much more accessible than YouTube. Folks can read [#244](https://github.com/omarroth/invidious/issues/244) for more details, and I would very much appreciate any feedback on how this can be improved.
## User Preferences
There have been a lot of improvements to preferences. Options for enabling audio-only by default and continuous playback (autoplay) have been added with [`e39dec9`](https://github.com/omarroth/invidious/e39dec9), with [`4b76b93`](https://github.com/omarroth/invidious/4b76b93), respectively. Users can also now mark videos as watched from their subscription feed and view watch history by going to https://invidio.us/feed/history. I expect to add more information to history so that it's easier to use. Folks can track progress with [#182](https://github.com/omarroth/invidious/issues/182). As with all data Invidious keeps, watch history can be exported [here](https://invidio.us/data_control).
Users can now delete their account with [`b9c29bf`](https://github.com/omarroth/invidious/b9c29bf). This will remove _all_ user data from Invidious, including session IDs, watch history, and subscriptions. As mentioned above, it's easy to export that data and import it to a local instance, or export subscriptions for use with other applications such as [FreeTube](https://github.com/FreeTubeApp/FreeTube) or [NewPipe](https://github.com/TeamNewPipe/NewPipe).
## Translation and Internationalis(z)ation
Invidious has been approved for hosting by Weblate, available [here](https://hosted.weblate.org/projects/invidious/translations/). At the time of writing, translations for Arabic, Dutch, German, Polish, and Russian are currently underway. I would like to say a very big thank you to everyone working on them, and I hope to fully support them within around 2 weeks. Folks can track progress with [#251](https://github.com/omarroth/invidious/issues/251).
## Transperency and Finances
For the sake of transparency, I plan on publishing each month's finances. This is currently already done on Liberapay and Patreon, but there is not a total amount currently provided anywhere, and I would also like to include expenses to provide a better explanation of how patrons' money is being spent.
### Donations
- [Patreon](https://www.patreon.com/omarroth): \$43.60 (Patreon takes roughly 9%)
- [Liberapay](https://liberapay.com/omarroth) : \$22.10
- Crypto : ~\$1.25 (converted from BCH, BTC)
- Total : \$66.95
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$75.00
I'd be happy to provide any explanation where needed. I would also like to thank everyone who donates, it really helps and I can't say how happy I am to see that so many people find it valuable.
That's all for this month. I wish everyone the best for the holidays, and I'll see you all again in January!
# 0.11.0 (2018-10-23)
## Week 11: FreeTube and Styling
This past Friday I'm been very excited to see that FreeTube version [0.4.0](https://github.com/FreeTubeApp/FreeTube/tree/0.4.0) has been released! I'd recommend taking a look at the official patch notes, but to spoil a little bit here: FreeTube now uses the Invidious API for _all_ requests previously sent to YouTube, and has also seen support for playlists, keyboard shortcuts, and more default settings (speed, autoplay, and subtitles). I'm happy to see that FreeTube has reached 500 stars on Github, and I think it's very much deserved. I'd recommend keeping an eye on the newly-launched [FreeTube blog](https://freetube.writeas.com/) for updates on the project.
Quite a few styling changes have been added this past week, including channel subscriber count to the subscribe and unsubscribe buttons. The changes sound small, but they've been a very big improvement and I'm quite satisfied with how they look. Also to note is that partial support for duration in thumbnails have been added with [#202](https://github.com/omarroth/invidious/issues/202). Overall, I think the site is becoming much more pleasing visually, and I hope to continue to improve it.
I've been very pleased to see Invidious in its current state, and I believe it's many times more mature compared to even a month ago. Changes have also started slowing down a bit as it's become more mature, and therefore I'd like to transition to a monthly update schedule in order to provide more comprehensive updates for everyone. I want to thank you all for helping me reach this point. I can't say how happy I am for Invidious to be where it is now.
Enjoy the rest of your week everyone, I'll see you in November!
# 0.10.0 (2018-10-16)
## Week 10: Subscriptions
This week I'm happy to announce that subscriptions have been drastically sped up with
35e63fa. As I mentioned last week, this essentially "caches" a user's feed, meaning that operations that previously took 20 seconds or timed out, now can load in under a second. I'd take a look at [#173](https://github.com/omarroth/invidious/issues/173) for a sample benchmark. Previously features that made Invidious's feed so useful, such as filtering by unseen and by author would take too long to load, and so instead would timeout. I'm very happy that this has been fixed, and folks can get back to using these features.
Among some smaller features that have been added this week include [#118](https://github.com/omarroth/invidious/issues/118), which adds, in my opinion, some very attractive subscribe and unsubscribe buttons. I think it's also a bit of a functional improvement as well, since it doesn't require a user to reload the page in order to subscribe or unsubscribe to a channel, and also gives the opportunity to put the channel's sub count on display.
An option to swap between Reddit and YouTube comments without a page reload has been added with
5eefab6, bringing it somewhat closer in functionality to the popular [AlienTube](https://github.com/xlexi/alientube) extension, on which it is based (although the extension unfortunately appears now to be fragmented).
As always, there are a couple smaller improvements this week, including some minor fixes for geo-bypass with
e46e618 and [`245d0b5`](https://github.com/omarroth/invidious/245d0b5), playlist preferences with [`81b4477`](https://github.com/omarroth/invidious/81b4477), and YouTube comments with [`02335f3`](https://github.com/omarroth/invidious/02335f3).
This coming week I'd also recommend keeping an eye on the excellent [FreeTube](https://github.com/FreeTubeApp/FreeTube), which is looking forward to a new release. I've been very lucky to work with [**@PrestonN**](https://github.com/PrestonN) for the past few weeks to improve the Invidious API, and I'm quite looking forward to the new release.
That's all for this week folks, thank you all again for your continued interest and support.
# 0.9.0 (2018-10-08)
## Week 9: Playlists
Not as much to announce this week, but I'm still quite happy to announce a couple things, namely:
Playback support for playlists has finally been added with [`88430a6`](https://github.com/omarroth/invidious/88430a6). You can now view playlists with the `&list=` query param, as you would on YouTube. You can also view mixes with the mentioned `&list=`, although they require some extra handling that I would like to add in the coming week, as well as adding playlist looping and shuffle. I think playback support has been a roadblock for more exciting features such as [#114](https://github.com/omarroth/invidious/issues/114), and I look forward to improving the experience.
Comments have had a bit of a cosmetic upgrade with [#132](https://github.com/omarroth/invidious/issues/132), which I think helps better distinguish between Reddit and YouTube comments, as it makes them appear similarly to their respective sites. You can also now switch between YouTube and Reddit comments with a push of a button, which I think is quite an improvement, especially for newer or less popular videos with fewer comments.
I've had a small breakthrough in speeding up users' subscription feeds with PostgreSQL's [materialized views](https://www.postgresql.org/docs/current/static/rules-materializedviews.html). Without going into too much detail, materialized views essentially cache the result of a query, making it possible to run resource-intensive queries once, rather than every time a user visits their feed. In the coming week I hope to push this out to users, and hopefully close [#173](https://github.com/omarroth/invidious/issues/173).
I haven't had as much time to work on the project this week, but I'm quite happy to have added some new features. Have a great week everyone.
# 0.8.0 (2018-10-02)
## Week 8: Mixes
Hello again!
Mixes have been added with [`20130db`](https://github.com/omarroth/invidious/20130db), which makes it easy to create a playlist of related content. See [#188](https://github.com/omarroth/invidious/issues/188) for more info on how they work. Currently, they return the first 50 videos rather than a continuous feed to avoid tracking by Google/YouTube, which I think is a good trade-off between usability and privacy, and I hope other folks agree. You can create mixes by adding `RD` to the beginning of a video ID, an example is provided [here](https://www.invidio.us/mix?list=RDYE7VzlLtp-4) based on Big Buck Bunny. I've been quite happy with the results returned for the mixes I've tried, and it is not limited to music, which I think is a big plus. To emulate a continuous feed provided many are used to, using the last video of each mix as a new 'seed' has worked well for me. In the coming week I'd like to to add playback support in the player to listen to these easily.
A very big thanks to [**@flourgaz**](https://github.com/flourgaz) for Docker support with [#186](https://github.com/omarroth/invidious/pull/186). This is an enormous improvement in portability for the project, and opens the door for Heroku support (see [#162](https://github.com/omarroth/invidious/issues/162)), and seamless support on Windows. For most users, it should be as easy as running `docker-compose up`.
I've spent quite a bit of time this past week improving support for geo-bypass (see [#92](https://github.com/omarroth/invidious/issues/92)), and am happy to note that Invidious has been able to proxy ~50% of the geo-restricted videos I've tried. In addition, you can now watch geo-restricted videos if you have `dash` enabled as your `preferred quality`, for more details see [#34](https://github.com/omarroth/invidious/issues/34) and [#185](https://github.com/omarroth/invidious/issues/185), or last week's update. For folks interested in replicating these results for themselves, I'd take a look [here](https://gist.github.com/omarroth/3ce0f276c43e0c4b13e7d9cd35524688) for the script used, and [here](https://gist.github.com/omarroth/beffc4a76a7b82a422e1b36a571878ef) for a list of videos restricted in the US.
1080p has seen a fairly smooth roll-out, although there have been a couple issues reported, mainly [#193](https://github.com/omarroth/invidious/issues/193), which is likely an issue in the player. I've also encountered a couple other issues myself that I would like to investigate. Although none are major, I'd like to keep 1080p opt-in for registered users another week to better address these issues.
Have an excellent week everyone.
# 0.7.0 (2018-09-25)
## Week 7: 1080p and Search Types
Hello again everyone! I've got quite a couple announcements this week:
Experimental 1080p support has been added with [`b3ca392`](https://github.com/omarroth/invidious/b3ca392), and can be enabled by going to preferences and changing `preferred video quality` to `dash`. You can find more details [here](https://github.com/omarroth/invidious/issues/34#issuecomment-424171888). Currently quality and speed controls have not yet been integrated into the player, but I'd still appreciate feedback, mainly on any issues with buffering or DASH playback. I hope to integrate 1080p support into the player and push support site-wide in the coming weeks.
You can now filter content types in search with the `type:TYPE` filter. Supported content types are `playlist`, `channel`, and `video`. More info is available [here](https://github.com/omarroth/invidious/issues/126#issuecomment-423823148). I think this is quite an improvement in usability and I hope others find the same.
A [CHANGELOG](https://github.com/omarroth/invidious/blob/master/CHANGELOG.md) has been added to the repository, so folks will now receive a copy of all these updates when cloning. I think this is an improvement in hosting the project, as it is no longer tied to the `/releases` tab on Github or the posts on Patreon.
Recently, users have been reporting 504s when attempting to access their subscriptions, which is tracked in [#173](https://github.com/omarroth/invidious/issues/173). This is most likely caused by an uptick in usage, which I am absolutely grateful for, but unfortunately has resulted in an increase in costs for hosting the site, which is why I will be bumping my goal on Patreon from $60 to $80. I would appreciate any feedback on how subscriptions could be improved.
Other minor improvements include:
- Additional regions added to bypass geo-block with [`9a78523`](https://github.com/omarroth/invidious/9a78523)
- Fix for playlists containing less than 100 videos (previously shown as empty) with [`35ac887`](https://github.com/omarroth/invidious/35ac887)
- Fix for `published` date for Reddit comments (previously showing negative seconds) with [`6e09202`](https://github.com/omarroth/invidious/6e09202)
Thank you everyone for your support!
# 0.6.0 (2018-09-18)
## Week 6: Filters and Thumbnails
Hello again! This week I'm happy to mention a couple new features to search as well as some miscellaneous usability improvements.
You can now constrain your search query to a specific channel with the `channel:CHANNEL` filter (see [#165](https://github.com/omarroth/invidious/issues/165) for more details). Unfortunately, other search filters combined with channel search are not yet supported. I hope to add support for them in the coming weeks.
You can also now search only your subscriptions by adding `subscriptions:true` to your query (see [#30](https://github.com/omarroth/invidious/issues/30) for more details). It's not quite ready for widespread use but I would appreciate feedback as the site updates to fully support it. Other search filters are not yet supported with `subscriptions:true`, but I hope to add more functionality to this as well.
With [#153](https://github.com/omarroth/invidious/issues/153) and [#168](https://github.com/omarroth/invidious/issues/168) all images on the site are now proxied through Invidious. In addition to offering the user more protection from Google's eyes, it also allows the site to automatically pick out the highest resolution thumbnail for videos. I think this is quite a large aesthetic improvement and I hope others will find the same.
As a smaller improvement to the site, you can also now view RSS feeds for playlists with [#113](https://github.com/omarroth/invidious/issues/113).
These updates are also now listed under Github's [releases](https://github.com/omarroth/invidious/releases). I'm also planning on adding them as a `CHANGELOG.md` in the repository itself so people can receive a copy with the project's source.
That's all for this week. Thank you everyone for your support!
# 0.5.0 (2018-09-11)
## Week 5: Privacy and Security
I hope everyone had a good weekend! This past week I've been fixing some issues that have been brought to my attention to help better protect users and help them keep their anonymity.
An issue with open referers has been fixed with [`29a2186`](https://github.com/omarroth/invidious/29a2186), which prevents potential redirects to external sites on actions such as login or modifying preferences.
Additionally, X-XSS-Protection, X-Content-Type-Options, and X-Frame-Options headers have been added with [`96234e5`](https://github.com/omarroth/invidious/96234e5), which should keep users safer while using the site.
A potential XSS vector has also been fixed in YouTube comments with [`8c45694`](https://github.com/omarroth/invidious/8c45694).
All the above vulnerabilities were brought to my attention by someone who wishes to remain anonymous, but I would like to say again here how thankful I am. If anyone else would like to get in touch please feel free to email me at omarroth@hotmail.com or omarroth@protonmail.com.
This week a couple changes have been made to better protect user's privacy as well.
All CSS and JS assets are now served locally with [`3ec684a`](https://github.com/omarroth/invidious/3ec684a), which means users no longer need to whitelist unpkg.com. Although I personally have encountered few issues, I understand that many folks would like to keep their browsing activity contained to as few parties as possible. In the coming week I also hope to proxy YouTube images, so that no user data is sent to Google.
YouTube links in comments now should redirect properly to the Invidious alternate with [`1c8bd67`](https://github.com/omarroth/invidious/1c8bd67) and [`cf63c82`](https://github.com/omarroth/invidious/cf63c82), so users can more easily evade Google tracking.
I'm also happy to mention a couple quality of life features this week:
Invidious now shows a video's "license" if provided, see [#159](https://github.com/omarroth/invidious/issues/159) for more details. You can also search for videos licensed under the creative commons with "QUERY features:creative_commons".
Videos with only one source will always display the cog for changing quality, so that users can see what quality is currently playing. See [#158](https://github.com/omarroth/invidious/issues/158) for more details.
Folks have also probably noticed that the gutters on either side of the screen have been shrunk down quite significantly, so that more of the screen is filled with content. Hopefully this can be improved even more in the coming weeks.
"Music", "Sports", and "Popular on YouTube" channels now properly display their videos. You can subscribe to these channels just as you would normally.
This coming week I'm planning on spending time with my family, so I unfortunately may not be as responsive. I do still hope to add some smaller features for next week however, and I hope to continue development soon.
Thank you everyone again for your support.
# 0.4.0 (2018-09-06)
## Week 4: Genre Channels
Hello! I hope everyone enjoyed their weekend. Without further ado:
Just today genre channels have been added with [#119](https://github.com/omarroth/invidious/issues/119). More information on genre channels is available [here](https://support.google.com/youtube/answer/2579942). You can subscribe to them as normally, and view them as RSS. I think they offer an interesting alternative way to find new content and I hope people find them useful.
This past week folks have started reporting 504s on their subscription page (see [#144](https://github.com/omarroth/invidious/issues/144) for more details). Upgrading the database server appeared to fix the issue, as well as providing a smoother experience across the site. Unfortunately, that means I will be increasing the goal from $50 to $60 in order to meet the increased hosting costs.
With [#134](https://github.com/omarroth/invidious/issues/134), comments are now formatted correctly, providing support for bold, italics, and links in comments. I think this improvement makes them much easier to read, and I hope others find the same. Also to note is that links in both comments and the video description now no longer contain any of Google's tracking with [#115](https://github.com/omarroth/invidious/issues/115).
One of the major use cases for Invidious is as a stripped-down version of YouTube. In line with that, I'm happy to announce that you can now hide related videos if you're logged in, for users that prefer an even more lightweight experience.
Finally, I'm pleased to announce that Invidious has hit 100 stars on GitHub. I am very happy that Invidious has proven to be useful to so many people, and I can't say how grateful I am to everyone for their continued support.
Enjoy the rest of your week everyone!
# 0.3.0 (2018-09-06)
## Week 3: Quality of Life
Hello everyone! This week I've been working on some smaller features that will hopefully make the site more functional.
Search filters have been added with [#126](https://github.com/omarroth/invidious/issues/126). You can now specify 'sort', 'date', 'duration', and 'features' within your query using the 'operator:value' syntax. I'd recommend taking a look [here](https://github.com/omarroth/invidious/blob/master/src/invidious/search.cr#L33-L114) for a list of supported options and at [#126](https://github.com/omarroth/invidious/issues/126) for some examples. This also opens the door for features such as [#30](https://github.com/omarroth/invidious/issues/30) which can be implemented as filters. I think advanced search is a major point in which Invidious can improve on YouTube and hope to add more features soon!
This week a more advanced system for viewing fallback comments has been added (see [#84](https://github.com/omarroth/invidious/issues/84) for more details). You can now specify a comment fallback in your preferences, which Invidious will use. If, for example, no Reddit comments are available for a given video, it can choose to fallback on YouTube comments. This also makes it possible to turn comments off completely for users that prefer a more streamlined experience.
With [#98](https://github.com/omarroth/invidious/issues/98), it is now possible for users to specify preferences without creating an account. You can now change speed, volume, subtitles, autoplay, loop, and quality using query parameters. See the issue above for more details and several examples.
I'd also like to announce that I've set up an account on [Liberapay](https://liberapay.com/omarroth), for patrons that prefer a privacy-friendly alternative to Patreon. Liberapay also does not take any percentage of donations, so I'd recommend donating some to the Liberapay for their hard work. Go check it out!
[Two weeks ago](https://github.com/omarroth/invidious/releases/tag/0.1.0) I mentioned adding 1080p support into the player. Currently, the only thing blocking is [#207](https://github.com/videojs/http-streaming/pull/207) in the excellent [http-streaming](https://github.com/videojs/http-streaming) library. I hope to work with the videojs team to merge it soon and finally implement 1080p support!
That's all for this week, thank you again everyone for your support!
# 0.2.0 (2018-09-06)
## Week 2: Toward Playlists
Sorry for the late update! Not as much to announce this week, but still a couple things of note:
I'm happy to announce that a playlists page and API endpoint has been added so you can now view playlists. Currently, you cannot watch playlists through the player, but I hope to add that in the coming week as well as adding functionality to add and modify playlists. There is a good conversation on [#114](https://github.com/omarroth/invidious/issues/114) about giving playlists even more functionality, which I think is interesting and would appreciate feedback on.
As an update to the Invidious API announcement last week, I've been working with [**@PrestonN**](https://github.com/PrestonN), the developer of [FreeTube](https://github.com/FreeTubeApp/FreeTube), to help migrate his project to the Invidious API. Because of it's increasing popularity, he has had trouble keeping under the quota set by YouTube's API. I hope to improve the API to meet his and others needs and I'd recommend folks to keep an eye on his excellent project! There is a good discussion with his thoughts [here](https://github.com/FreeTubeApp/FreeTube/issues/100).
A couple of miscellaneous features and bugfixes:
- You can now login to Invidious simultaneously from multiple devices - [#109](https://github.com/omarroth/invidious/issues/109)
- Added a note for scheduled livestreams - [#124](https://github.com/omarroth/invidious/issues/124)
- Changed YouTube comment header to "View x comments" - [#120](https://github.com/omarroth/invidious/issues/120)
Enjoy your week everyone!
# 0.1.0 (2018-09-06)
## Week 1: Invidious API and Geo-Bypass
Hello everyone! This past week there have been quite a few things worthy of mention:
I'm happy to announce the [Invidious Developer API](https://github.com/omarroth/invidious/wiki/API). The Invidious API does not use any of the official YouTube APIs, and instead crawls the site to provide a JSON interface for other developers to use. It's still under development but is already powering [CloudTube](https://github.com/cloudrac3r/cadencegq). The API currently does not have a quota (compared to YouTube) which I hope to continue thanks to continued support from my Patrons. Hopefully other developers find it useful, and I hope to continue to improve it so it can better serve the community.
Just today partial support for bypassing geo-restrictions has been added with [fada57a](https://github.com/omarroth/invidious/commit/fada57a307d66d696d9286fc943c579a3fd22de6). If a video is unblocked in one of: United States, Canada, Germany, France, Japan, Russia, or United Kingdom, then Invidious will be able to serve video info. Currently you will not yet be able to access the video files themselves, but in the coming week I hope to proxy videos so that users can enjoy content across borders.
Support for generating DASH manifests has been fixed, in the coming week I hope to integrate this functionality into the watch page, so users can view videos in 1080p and above.
Thank you everyone for your continued interest and support!

844
CHANGELOG_legacy.md Normal file
View file

@ -0,0 +1,844 @@
# Note: This is no longer updated and links to omarroths repo, which doesn't exist anymore.
# 0.20.0 (2019-011-06)
# Version 0.20.0: Custom Playlists
It's been quite a while since the last release! There've been [198 commits](https://github.com/omarroth/invidious/compare/0.19.0..0.20.0) from 27 contributors.
A couple smaller features have since been added. Channel pages and playlists in particular have received a bit of a face-lift, with both now displaying their descriptions as expected, and playlists providing video count and published information. Channels will also now provide video descriptions in their RSS feed.
Turkish (tr), Chinese (zh-TW, in addition to zh-CN), and Japanese (jp) are all now supported languages. Thank you as always to the hard work done by translators that makes this possible.
The feed menu and default home page are both now configurable for registered and unregistered users, and is quite a bit of an improvement for users looking to reduce distractions for their daily use.
## For Administrators
`feed_menu` and `default_home` are now configurable by the user, and have therefore been moved into `default_user_preferences`:
```yaml
feed_menu: ["Popular", "Top"]
default_home: Top
# becomes:
default_user_preferences:
feed_menu: ["Popular", "Top"]
default_home: Top
```
Several new options have also been added, including the ability to set a support email for the instance using `admin_email: EMAIL`, and forcing the use of a specific connection in the case of rate-limiting using `force_resolve` (see below).
## For Developers
Authenticated endpoints are now [properly documented](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints), as well how to generate and use API tokens. My hope is that this makes some of the more [interesting](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authnotifications) endpoints more accessible for developers to use in their own applications.
API endpoints for interacting with custom playlists have also been added with documentation available [here](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authplaylists).
## Custom playlists
This is probably the feature that has been the longest in the pipe and that I'm quite pleased is now implemented. It is now possible to create custom playlists, which can be played and edited through Invidious. API endpoints have also been added (documentation [here](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authplaylists)).
Overall I'm quite pleased with how smoothly it has been rolled out and with the experience so far, and I'm exctited for how it can be extended and improved in future.
## [instances.invidio.us](https://instances.invidio.us)
It is now possible to view a list of public instances (as provided in the [wiki](https://github.com/omarroth/invidious/wiki/Invidious-Instances)) through an API or a pretty new interface [here](https://instances.invidio.us). It combines uptime information, statistics from each instance and basic information already provided in the wiki. I expect it should be much more user-friendly than compiling the information yourself, and is already used by [Invidition](https://codeberg.org/Booteille/Invidition) to provide a list of instances for users to choose from.
The site itself is licensed under the AGPLv3 and the source is available [here](https://github.com/omarroth/instances.invidio.us).
## Video unavailable [#811](https://github.com/omarroth/invidious/issues/811)
Many users have likely noticed this error message if using Invidious directly or through another service, such as FreeTube. This issue is caused by rate-limiting by Google, and is not a new issuee for projects like Invidious (notably [youtube-dl](https://github.com/ytdl-org/youtube-dl#http-error-429-too-many-requests-or-402-payment-required)) and appears to be affecting smaller, private instances as well.
There is not a permanent fix for administrators currently, however there is some information available [here](https://github.com/omarroth/invidious/issues/811#issuecomment-540017772) that may provide a temporary solution. Unfortanately, in most cases the best option is to wait for the instance to be unbanned or to move the instance to a different IP. A more informative error message is also now provided, which should help an administrator more quickly diagnose the problem.
For those interested, I would recommend following [#811](https://github.com/omarroth/invidious/issues/811) for any future progress on the issue.
## BAT verified publisher
I'm quite late to this announcement, however I'm pleased to mention that Invidious is now a BAT verified publisher! I would recommend looking [here](https://basicattentiontoken.org/about/) or [here](https://www.reddit.com/r/BATProject/comments/7cr7yc/new_to_bat_read_this_introduction_to_basic/) for learning more about what it is and how it works. Overall I think it makes an interesting substitute for services like Liberapay, and a (hopefully) much less-intrusive alternative to direct advertising.
BAT is combined under other cryptocurrencies below. Currently there's a fairly significant delay in payout, which is the reason for the large fluctuation in crypto donations between September and October (and also the reason for the late announcement).
## Release schedule
Currently I'm quite pleased with the current state of the project. There's plenty of things I'd still like to add, however at this point I expect the rate of most new additions will slow down a bit, with more focus on stabililty and any long-standing bugs.
Because of this, I'm planning on releasing a new version quarterly, with any necessary hotfixes being pushed as a new patch release as necessary. As always it will be possible to run Invidious directly from [master](https://github.com/omarroth/invidious/wiki/Updating) if you'd still like to have the lastest version.
I'll plan on providing finances each release, with a similar monthly breakdown as below.
## Finances for September 2019
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$64.37
- [Liberapay](https://liberapay.com/omarroth) : \$76.04
- Crypto : ~\$99.89 (converted from BAT, BCH, BTC)
- Total : \$240.30
### Expenses
- invidious-lb1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node11 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node12 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node13 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node14 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node15 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node16 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$135.00
## Finances for October 2019
- [Liberapay](https://liberapay.com/omarroth) : \$134.40
- Crypto : ~\$8.29 (converted from BAT, BCH, BTC)
- Total : \$142.69
### Expenses
- invidious-lb1 (nyc1) : \$5.00 (load balancer)
- invidious-lb2 (nyc1) : \$5.00 (load balancer)
- invidious-lb3 (nyc1) : \$5.00 (load balancer)
- invidious-lb4 (nyc1) : \$5.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node11 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node12 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node13 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node14 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node15 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node16 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node17 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node18 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$155.00
# 0.19.0 (2019-07-13)
# Version 0.19.0: Communities
Hello again everyone! Focus this month has mainly been on improving playback performance, along with a couple new features I'd like to announce. There have been [109 commits](https://github.com/omarroth/invidious/compare/0.18.0...0.19.0) this past month from 10 contributors.
This past month has seen the addition of Chinese (`zh-CN`) and Icelandic (`is`) translations. I would like to give a huge thanks to their respective translators, and again an enormous thanks to everyone who helps translate the site.
I'm delighted to mention that [FreeTube 0.6.0](https://github.com/FreeTubeApp/FreeTube) now supports 1080p thanks to the Invidious API. I would very much recommend reading the [relevant post](https://freetube.writeas.com/freetube-release-0-6-0-beta-1080p-and-a-lot-of-qol) for some more information on how it works, along with several other major improvements. Folks that are interested in adding similar functionality for their own projects should feel free to get in touch.
This past month there has been quite a bit of work on improving memory usage and improving download and playback speeds. As mentioned in the previous release, some extra hardware has been allocated which should also help with this. I'm still looking for ways to improve performance and feedback is always appreciated.
Along with performance, a couple quality of life improvements have been added, including author thumbnails and banners, clickable titles for embedded videos, and better styling for captions, among some other enhancements.
## Communities
Support for YouTube's [communities tab](https://creatoracademy.youtube.com/page/lesson/community-tab) has been added. It's a very interesting but surprisingly unknown feature. Essentially, providing comments for a channel, rather than a video, where an author can post updates for their subscribers.
It's commonly used to promote interesting links and foster discussion. I hope this feature helps people find more interesting content that otherwise would have been overlooked.
## For Developers
For accessing channel communities, an `/api/v1/channels/comments/:ucid` endpoint has been added, with similar behavior and schema to `/api/v1/comments/:id`, with an extra `attachment` field for top-level comments. More info on usage and available data can be found in the [wiki](https://github.com/omarroth/invidious/wiki/API#get-apiv1channelscommentsucid-apiv1channelsucidcomments).
An `/api/v1/auth/feeds` endpoint has been added for programmatically accessing a user's subscription feed, with options for displaying notifications and filtering an existing feed.
An `/api/v1/search/suggestions` endpoint has been added for retrieving suggestions for a given query.
## For Administrators
It is now possible to disable more resource intensive features, such as downloads and DASH functionality by adding `disable_proxy` to your config. See [#453](https://github.com/omarroth/invidious/issues/453) and the [Wiki](https://github.com/omarroth/invidious/wiki/Configuration) for more information and example usage. I expect this to be a big help for folks with limited bandwidth when hosting their own instances.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$38.39
- [Liberapay](https://liberapay.com/omarroth) : \$84.85
- Crypto : ~\$0.00 (converted from BCH, BTC)
- Total : \$123.24
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$105.00
The goal on Patreon has been updated to reflect the above expenses. As mentioned above, the main reason for more hardware is to improve playback and download speeds, although I'm still looking into improving performance without allocating more hardware.
As always I'm grateful for everyone's support and feedback. I'll see you all next month.
# 0.18.0 (2019-06-06)
# Version 0.18.0: Native Notifications and Optimizations
Hope everyone has been doing well. This past month there have been [97 commits](https://github.com/omarroth/invidious/compare/0.17.0...0.18.0) from 10 contributors. For the most part changes this month have been on optimizing various parts of the site, mainly subscription feeds and support for serving images and other assets.
I'm quite happy to mention that support for Greek (`el`) has been added, which I hope will continue to make the site accessible for more users.
Subscription feeds will now only update when necessary, rather than periodically. This greatly lightens the load on DB as well as making the feeds generally more responsive when changing subscriptions, importing data, and when receiving new uploads.
Caching for images and other assets should be greatly improved with [#456](https://github.com/omarroth/invidious/issues/456). JavaScript has been pulled out into separate files where possible to take advantage of this, which should result in lighter pages and faster load times.
This past month several people have encountered issues with downloads and watching high quality video through the site, see [#532](https://github.com/omarroth/invidious/issues/532) and [#562](https://github.com/omarroth/invidious/issues/562). For this coming month I've allocated some more hardware which should help with this, and I'm also looking into optimizing how videos are currently served.
## For Developers
`viewCount` is now available for `/api/v1/popular` and all videos returned from `/api/v1/auth/notifications`. Both also now provide `"type"` for indicating available information for each object.
An `/authorize_token` page is now available for more easily creating new tokens for use in applications, see [this comment](https://github.com/omarroth/invidious/issues/473#issuecomment-496230812) in [#473](https://github.com/omarroth/invidious/issues/473) for more details.
A POST `/api/v1/auth/notifications` endpoint is also now available for correctly returning notifications for 150+ channels.
## For Administrators
There are two new schema changes for administrators: `views` for adding view count to the popular page, and `feed_needs_update` for tracking feed changes.
As always the relevant migration scripts are provided which should run when following instructions for [updating](https://github.com/omarroth/invidious/wiki/Updating). Otherwise, adding `check_tables: true` to your config will automatically make the required changes.
## Native Notifications
[<img src="https://omar.yt/81c3ae1839831bd9300d75e273b6552a86dc2352/native_notification.png" height="160" width="472">](https://omar.yt/81c3ae1839831bd9300d75e273b6552a86dc2352/native_notification.png "Example of native notification, available in repository under screnshots/native_notification.png")
It is now possible to receive [Web notifications](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API) from subscribed channels.
You can enable notifications by clicking "Enable web notifications" in your preferences. Generally they appear within 20-60 seconds of a new video being uploaded, and I've found them to be an enormous quality of life improvement.
Although it has been fairly stable, please feel free to report any issues you find [here](https://github.com/omarroth/invidious/issues) or emailing me directly at omarroth@protonmail.com.
Important to note for administrators is that instances require [`use_pubsub_feeds`](https://github.com/omarroth/invidious/wiki/Configuration) and must be served over HTTPS in order to correctly send web notifications.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$49.73
- [Liberapay](https://liberapay.com/omarroth) : \$100.57
- Crypto : ~\$11.12 (converted from BCH, BTC)
- Total : \$161.42
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$85.00
See you all next month!
# 0.17.0 (2019-05-06)
# Version 0.17.0: Player and Authentication API
Hello everyone! This past month there have been [130 commits](https://github.com/omarroth/invidious/compare/0.16.0..0.17.0) from 11 contributors. Large focus has been on improving the player as well as adding API access for other projects to make use of Invidious.
There have also been significant changes in preparation of native notifications (see [#195](https://github.com/omarroth/invidious/issues/195), [#469](https://github.com/omarroth/invidious/issues/469), [#473](https://github.com/omarroth/invidious/issues/473), and [#502](https://github.com/omarroth/invidious/issues/502)), and playlists. I expect to see both of these to be added in the next release.
I'm quite happy to mention that new translations have been added for Esperanto (`eo`) and Ukranian (`uk`). Support for pluralization has also been added, so it should now be possible to make a more native experience for speakers in other languages. The system currently in place is a bit cumbersome, so for any help using this feature please get in touch!
## For Administrators
A `check_tables` option has been added to automatically migrate without the use of custom scripts. This method will likely prove to be much more robust, and is currently enabled for the official instance. To prevent any unintended changes to the DB, `check_tables` is disabled by default and will print commands before executing. Having this makes features that require schema changes much easier to implement, and also makes it easier to upgrade from older instances.
As part of [#303](https://github.com/omarroth/invidious/issues/303), a `cache_annotations` option has been added to speed up access from `/api/v1/annotations/:id`. This vastly improves the experience for videos with annotations. Currently, only videos that contain legacy annotations will be cached, which should help keep down the size of the cache. `cache_annotations` is disabled by default.
## For Developers
An authorization API has been added which allows other applications to read and modify user subscriptions and preferences (see [#473](https://github.com/omarroth/invidious/issues/473)). Support for accessing user feeds and notifications is also planned. I believe this feature is a large step forward in supporting syncing subscriptions and preferences with other services, and I'm excited to see what other developers do with this functionality.
Support for server-to-client push notifications is currently underway. This allows Invidious users, as well as applications using the Invidious API, to receive notifications about uploads in near real-time (see #469). An `/api/v1/auth/notifications` endpoint is currently available. I'm very excited for this to be integrated into the site, and to see how other developers use it in their own projects.
An `/api/v1/storyboards/:id` endpoint has been added for accessing storyboard URLs, which allows developers to add video previews to their players (see below).
## Player
Support for annotations has been merged into master with [#303](https://github.com/omarroth/invidious/issues/303), thanks @glmdgrielson! Annotations can be enabled by default or only for subscribed channels, and can also be toggled per video. I'm extremely proud of the progress made here, and I'm so thankful to everyone that has made this possible. I expect this to be the last update with regards to supporting annotations, but I do plan on continuing to improve the experience as much as possible.
The Invidious player now supports video previews and a corresponding API endpoint `/api/v1/storyboards/:id` has been added for developers looking to add similar functionality to their own players. Not much else to say here. Overall it's a very nice quality of life improvement and an attractive addition to the site.
It is now possible to select specific sources for videos provided using DASH (see [#34](https://github.com/omarroth/invidious/issues/34)). I would consider support largely feature complete, although there are still several issues to be fixed before I would consider it ready for larger rollout. You can watch videos in 1080p by setting `Default quality` to `dash` in your preferences, or by adding `&quality=dash` to the end of video URLs.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$49.73
- [Liberapay](https://liberapay.com/omarroth) : \$63.03
- Crypto : ~\$0.00 (converted from BCH, BTC)
- Total : \$112.76
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$80.00
That's all for now. Thanks!
# 0.16.0 (2019-04-06)
# Version 0.16.0: API Improvements and Annotations
Hello again! This past month has seen [116 commits](https://github.com/omarroth/invidious/compare/0.15.0..0.16.0) from 13 contributors and a couple important changes I'd like to announce.
A privacy policy is now available [here](https://invidio.us/privacy). I've done my best to explain things as clearly as possible without oversimplifying, and would very much recommend reading it if you're concerned about your privacy and want to learn more about how Invidious uses your data. Please let me know if there is anything that needs clarification.
I'm also very happy to announce that a Spanish translation has been added to the site. You can use it with `?hl=es` or by setting `es` as your default locale. As always I'm extremely grateful to translators for making the site accessible to more people.
## For Administrators
Invidious now supports server-to-server [push notifications](https://developers.google.com/youtube/v3/guides/push_notifications). This uses [PubSubHubbub](https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html) to automatically handle new videos sent to an instance, which is less resource intensive and generally faster. Note that it will not pull all videos from a subscribed channel, so recommended usage is in addition to `channel_threads`. Using PubSub requires a valid `domain` that updates can be sent to, and a random string that can be used to sign updates sent to the instance. You can enable it by adding `use_pubsub_feeds: true` to your `config.yml`. See [Configuration](https://github.com/omarroth/invidious/wiki/Configuration) for more info.
Unfortunately there are a couple necessary changes to the DB to support `liveNow` and `premiereTimestamp` in subscription feeds. Migration scripts have been provided that should be used automatically if following the instructions [here](https://github.com/omarroth/invidious/wiki/Updating).
You can now configure default user preferences for your instance. This allows you to set default locale, player preferences, and more. See [#415](https://github.com/omarroth/invidious/issues/415) for more details and example usage.
## For Developers
The [fields](https://developers.google.com/youtube/v3/getting-started#fields) API has been added with [#429](https://github.com/omarroth/invidious/pull/429) and is now supported on all JSON endpoints, thanks [**@afrmtbl**](https://github.com/afrmtbl)! Synax is straight-forward and can be used to reduce data transfer and create a simpler response for debugging. You can see an example [here](https://invidio.us/api/v1/videos/CvFH_6DNRCY?pretty=1&fields=title,recommendedVideos/title). I've been quite happy using it and hope it is similarly useful for others.
An `/api/v1/annotations/:id` endpoint has been added for pulling legacy annotation data from [this](https://archive.org/details/youtubeannotations) archive, see below for more details. You can also access annotation data available on YouTube using `?source=youtube`, although this will only return card data as legacy annotations were deleted on January 15th.
A couple minor changes to existing endpoints:
- A `premiereTimestamp` field has been added to `/api/v1/videos/:id`
- A `sort_by` param has been added to `/api/v1/comments/:id`, supports `new`, `top`.
More info is available in the [documentation](https://github.com/omarroth/invidious/wiki/API).
## Annotations
I'm pleased to announce that annotation data is finally available from the roughly 1.4 billion videos archived as part of [this](https://www.reddit.com/r/DataHoarder/comments/aa6czg/youtube_annotation_archive/) project. They are accessible from the Internet Archive [here](https://archive.org/details/youtubeannotations) or as a 355GB torrent, see [here](https://www.reddit.com/r/DataHoarder/comments/b7imx9/youtube_annotation_archive_annotation_data_from/) for more details. A corresponding `/api/v1/annotations/:id` endpoint has been added to Invidious which uses the collection from IA to provide legacy annotations.
Support for them in the player is possible thanks to [this](https://github.com/afrmtbl/videojs-youtube-annotations) plugin developed by [**@afrmtbl**](https://github.com/afrmtbl). A PR for adding support to the site is available as [#303](https://github.com/omarroth/invidious/pull/303). There's also an [extension](https://github.com/afrmtbl/AnnotationsRestored) for overlaying them on top of the YouTube player (again thanks to [**@afrmtbl**](https://github.com/afrmtbl)), and an [extension](https://tech234a.bitbucket.io/AnnotationsReloaded?src=invidious) for hooking into code still present in the YouTube player itself, developed by [**@tech234a**](https://github.com/tech234a).
I would recommend reading the [official announcement](https://www.reddit.com/r/DataHoarder/comments/b7imx9/youtube_annotation_archive_annotation_data_from/) for more details. I would like to again thank everyone that helped contribute to this project.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$42.42
- [Liberapay](https://liberapay.com/omarroth) : \$70.11
- Crypto : ~\$1.76 (converted from BCH, BTC, BSV)
- Total : \$114.29
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$80.00
This past month the site saw a couple abnormal peaks in traffic, so an additional webserver has been added to match the increased load. The goal on Patreon has been updated to match the above expenses.
Thanks everyone!
# 0.15.0 (2019-03-06)
## Version 0.15.0: Preferences and Channel Playlists
The project has seen quite a bit of activity this past month. Large focus has been on fixing bugs, but there's still quite a few new features I'm happy to announce. There have been [133 commits](https://github.com/omarroth/invidious/compare/0.14.0...0.15.0) from 15 contributors this past month.
As a couple miscellaneous changes, a couple [nice screenshots](https://github.com/omarroth/invidious#screenshots) have been added to the README, so folks can see more of what the site has to offer without creating an account.
The footer has also been cleaned up quite a bit, and now displays the current version, so it's easier to know what features are available from the current instance.
## For Administrators
This past month there has been a minor release - `0.14.1` - which fixes a breaking change made by YouTube for their polymer redesign.
There have been several new features that unfortunately require a database migration. There are migration scripts provided in `config/migrate-scripts`, and the [wiki](https://github.com/omarroth/invidious/wiki/Updating) has instructions for automatically applying them. I'll do my best to keep those changes to a minimum, and expect to see a corresponding script to automatically apply any new changes.
Administrator preferences have been added with [#312](https://github.com/omarroth/invidious/issues/312), which allows administrators to customize their instance. Administrators can change the order of feed menus, change the default homepage, disable open registration, and several other options. There's a short 'how-to' [here](https://github.com/omarroth/invidious/issues/312#issuecomment-468831842), and the new options are documented [here](https://github.com/omarroth/invidious/wiki/Configuration).
An `/api/v1/stats` endpoint has been added with [#356](https://github.com/omarroth/invidious/issues/356), which reports the instance version and number of active users. Statistics are disabled by default, and can be enabled in administator preferences. Statistics for the official instance are available [here](https://invidio.us/api/v1/stats?pretty=1).
## For Developers
`/api/v1/channels/:ucid` now provides an `autoGenerated` tag, which returns true for topic channels, and larger genre channels generated by YouTube. These channels don't have any videos of their own, so `latestVideos` will be empty. It is recommended instead to display a list of playlists generated by YouTube.
You can now pull a list of playlists from a channel with `/api/v1/channels/playlists/:ucid`. Supported options are documented in the [wiki](https://github.com/omarroth/invidious/wiki/API#get-apiv1channelsplaylistsucid-apiv1channelsucidplaylists). Pagination is handled with a `continuation` token, which is generated on each call. Of note is that auto-generated channels currently have one page of results, and subsequent calls will be empty.
For quickly pulling the latest 30 videos from a channel, there is now `/api/v1/channels/latest/:ucid`. It is much faster than a call to `/api/v1/channels/:ucid`. It will not convert an author name to a valid ucid automatically, and will not return any extra data about a channel.
## Preferences
In addition to administrator preferences mentioned above, you can now change your preferences without an account (see [#42](https://github.com/omarroth/invidious/pull/42)). I think this is quite an improvement to the usability of the site, and is much friendlier to privacy-conscious folks that don't want to make an account. Preferences will be automatically imported to a newly created account.
Several issues with sorting subscriptions have been fixed, and `/manage_subscriptions` has been sped up significantly. The subscription feed has also seen a bump in performance. Delayed notifications have unfortunately started becoming a problem now that there are more users on the site. Some new changes are currently being tested which should mostly resolve the issue, so expect to see more in the next release.
## Channel Playlists
You can now view available playlists from a channel, and [auto-generated channels](https://invidio.us/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ) are no longer empty. You can sort as you would on YouTube, and all the same functionality should be available. I'm quite pleased to finally have it implemented, since it's currently the only data available from the above mentioned auto-generated channels, and makes it much easier to consume music on the site.
There's also more discussion on improving Invidious for streaming music in [#304](https://github.com/omarroth/invidious/issues/304), and adding support for music.youtube.com. I would appreciate any thoughts on how to improve that experience, since it's a very large and useful part of YouTube.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$42.42
- [Liberapay](https://liberapay.com/omarroth) : \$30.97
- Crypto : ~\$0.00 (converted from BCH, BTC)
- Total : \$73.39
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$75.00
It's been very humbling to see how fast the project has grown, and I look forward to making the site even better. Thank you everyone.
# 0.14.0 (2019-02-06)
## Version 0.14.0: Community
This last month several contributors have made improvements specifically for the people using this project. New pages have been added to the wiki, and there is now a [Matrix Server](https://riot.im/app/#/room/#invidious:matrix.org) and IRC channel so it's easier and faster for people to ask questions or chat. There have been [101 commits](https://github.com/omarroth/invidious/compare/0.13.0...0.14.0) since the last major release from 8 contributors.
It has come to my attention in the past month how many people are self-hosting, and I would like to make it easier for them to do so.
With that in mind, expect future releases to have a section for For Administrators (if any relevant changes) and For Developers (if any relevant changes).
## For Administrators
This month the most notable change for administrators is releases. As always, there will be a major release each month. However, a new minor release will be made whenever there are any critical bugs that need to be fixed.
This past month is the first time there has been a minor release - `0.13.1` - which fixes a breaking change made by YouTube. Administrators using versioning for their instances will be able to rely on the latest version, and should have a system in place to upgrade their instance as soon as a new release is available.
Several new pages have been added to the [wiki](https://github.com/omarroth/invidious/wiki#for-administrators) (as mentioned below) that will help administrators better setup their own instances. Configuration, maintenance, and instructions for updating are of note, as well as several common issues that are encountered when first setting up.
## For Developers
There's now a `pretty=1` parameter for most endpoints so you can view data easily from the browser, which is convenient for debugging and casual use. You can see an example [here](https://invidio.us/api/v1/videos/CvFH_6DNRCY?pretty=1).
Unfortunately the `/api/v1/insights/:id` endpoint is no longer functional, as YouTube removed all publicly available analytics around a month ago. The YouTube endpoint now returns a 404, so it's unlikely it will be functional again.
## Wiki
There have been a sizable number of changes to the Wiki, including a [list of public Invidious instances](https://github.com/omarroth/invidious/wiki/Invidious-Instances), the [list of extensions](https://github.com/omarroth/invidious/wiki/Extensions), and documentation for administrators (as mentioned above) and developers.
The wiki is editable by anyone so feel free to add anything you think is useful.
## Matrix & IRC
Thee is now a [Matrix Server](https://riot.im/app/#/room/#invidious:matrix.org) for Invidious, so please feel free to hop on if you have any questions or want to chat. There is also a registered IRC channel: #invidious on Freenode which is bridged to Matrix.
## Features
Several new features have been added, including a download button, creator hearts and comment colors, and a French translation.
There have been fixes for Google logins, missing text in locales, invalid links to genre channels, and better error handling in the player, among others.
Several fixes and features are omitted for space, so I'd recommend taking a look at the [compare tab](https://github.com/omarroth/invidious/compare/0.13.0...0.14.0) for more information.
## Annotations Update
Annotations were removed January 15th, 2019 around15:00 UTC. Before they were deleted we were able to archive annotations from around 1.4 billion videos. I'd very much recommend taking a look [here](https://www.reddit.com/r/DataHoarder/comments/al7exa/youtube_annotation_archive_update_and_preview/) for more information and a list of acknowledgements. I'm extremely thankful to everyone who was able to contribute and I'm glad we were able to save such a large part of internet history.
There's been large strides in supporting them in the player as well, which you can follow in [#303](https://github.com/omarroth/invidious/pull/303). You can preview the functionality at https://dev.invidio.us . Before they are added to the main site expect to see an option to disable them, both site-wide and per video.
Organizing this project has unfortunately taken up quite a bit of my time, and I've been very grateful for everyone's patience.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth) : \$49.42
- [Liberapay](https://liberapay.com/omarroth) : \$27.89
- Crypto : ~\$0.00 (converted from BCH, BTC)
- Total : \$77.31
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$75.00
As always I'm grateful for everyone's contributions and support. I'll see you all in March.
# 0.13.1 (2019-01-19)
##
# 0.13.0 (2019-01-06)
## Version 0.13.0: Translations, Annotations, and Tor
I hope everyone had a happy New Year! There's been a couple new additions since last release, with [44 commits](https://github.com/omarroth/invidious/compare/0.12.0...0.13.0) from 9 contributors. It's been quite a year for the project, and I hope to continue improving the project into 2019! Starting off the new year:
## Translations
I'm happy to announce support for translations has been added with [`a160c64`](https://github.com/omarroth/invidious/a160c64). Currently, there is support for:
- Arabic (`ar`)
- Dutch (`nl`)
- English (`en-US`)
- German (`de`)
- Norwegian Bokmål (`nb_NO`)
- Polish (`pl`)
- Russian (`ru`)
Which you can change in your preferences under `Language`. You can also add `&hl=LANGUAGE` to the end of any request to translate it to your preferred language, for example https://invidio.us/?hl=ru. I'd like to say thank you again to everyone who has helped translate the site! I've mentioned this before, but I'm delighted that so many people find the project useful.
## Annotations
Recently, [YouTube announced that all annotations will be deleted on January 15th, 2019](https://support.google.com/youtube/answer/7342737). I believe that annotations have a very important place in YouTube's history, and [announced a project to archive them](https://www.reddit.com/r/DataHoarder/comments/aa6czg/youtube_annotation_archive/).
I expect annotations to be supported in the Invidious player once archiving is complete (see [#110](https://github.com/omarroth/invidious/issues/110) for details), and would also like to host them for other developers to use in their projects.
The code is available [here](https://github.com/omarroth/archive), and contains instructions for running a worker if you would like to contribute. There's much more information available in the announcement as well for anyone who is interested.
## Tor
I unfortunately missed the chance to mention this in the previous release, but I'm now happy to announce that you can now view Invidious through Tor at the following links:
kgg2m7yk5aybusll.onion
axqzx4s6s54s32yentfqojs3x5i7faxza6xo3ehd4bzzsg2ii4fv2iid.onion
Invidious is well suited to use through Tor, as it does not require any JS and is fairly lightweight. I'd recommend looking [here](https://diasp.org/posts/10965196) and [here](https://www.reddit.com/r/TOR/comments/a3c1ak/you_can_now_watch_youtube_videos_anonymously_with/) for more details on how to use the onion links, and would like to say thank you to [/u/whonix-os](https://www.reddit.com/user/whonix-os) for suggesting it and providing support setting setting them up.
## Popular and Trending
You can now easily view videos trending on YouTube with [`a16f967`](https://github.com/omarroth/invidious/a16f967). It also provides support for viewing YouTube's various categories categories, such as `News`, `Gaming`, and `Music`. You can also change the `region` parameter to view trending in different countries, which should be made easier to use in the coming weeks.
A link to `/feed/popular` has also been added, which provides a list of videos sorted using the algorithm described [here](https://github.com/omarroth/invidious/issues/217#issuecomment-436503761). I think it better reflects what users watch on the site, but I'd like to hear peoples' thoughts on this and on how it could be improved.
## Finances
### Donations
- [Patreon](https://www.patreon.com/omarroth): \$64.63
- [Liberapay](https://liberapay.com/omarroth) : \$30.05
- Crypto : ~\$28.74 (converted from BCH, BTC)
- Total : \$123.42
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$75.00
### What will happen with what's left over?
I believe this is the first month that all expenses have been fully paid for by donations. Thank you! I expect to allocate the current amount for hardware to improve performance and for hosting annotation data, as mentioned above.
Anything that is left over is kept to continue hosting the project for as long as possible. Thank you again everyone!
I think that's everything for 2018. There's lots still planned, and I'm very excited for the future of this project!
# 0.12.0 (2018-12-06)
## Version 0.12.0: Accessibility, Privacy, Transparency
Hello again, it's been a while! A lot has happened since the last release. Invidious has seen [134 commits](https://github.com/omarroth/invidious/compare/0.11.0...0.12.0) from 3 contributors, and I'm quite happy with the progress that has been made. I enjoyed this past month, and I believe having a monthly release schedule allows me to focus on more long-term improvements, and I hope people enjoy these more substantial updates as well.
## Accessability and Privacy
There have been quite a few improvements for user privacy, and improvements that improve accessibility for both people and software.
You can now view comments without JS with [`19516ea`](https://github.com/omarroth/invidious/19516ea). Currently, this functionality is limited to the first 20 comments, but expect this functionality to be improved to come as close to the JS version as possible. Folks can track progress in [#204](https://github.com/omarroth/invidious/issues/204).
Invidious is now compatible with [LibreJS](https://www.gnu.org/software/librejs/), and provides license information [here](https://invidio.us/licenses) with [`7f868ec`](https://github.com/omarroth/invidious/7f868ec). As expected, all libraries are compatible under the AGPLv3, and I'm happy to mention that no other changes were required to make Invidious compatible with LibreJS.
A DNT policy has also been added with [`9194f47`](https://github.com/omarroth/invidious/9194f47) for compatibility with [Privacy Badger](https://www.eff.org/privacybadger). I'm pleased to mention that here too no other changes had to be made in order for Invidious to be compatible with this extension. I expect a privacy policy to be added soon as well, so users can better understand how Invidious uses their data.
For users that are visually impaired, there is now a text CAPTCHA available so it's easier to register and login. Because of the simple front-end of the project, I expect screen readers and other software to be able to easily understand the site's interface. In combination with the ability to listen-only, I believe Invidious is much more accessible than YouTube. Folks can read [#244](https://github.com/omarroth/invidious/issues/244) for more details, and I would very much appreciate any feedback on how this can be improved.
## User Preferences
There have been a lot of improvements to preferences. Options for enabling audio-only by default and continuous playback (autoplay) have been added with [`e39dec9`](https://github.com/omarroth/invidious/e39dec9), with [`4b76b93`](https://github.com/omarroth/invidious/4b76b93), respectively. Users can also now mark videos as watched from their subscription feed and view watch history by going to https://invidio.us/feed/history. I expect to add more information to history so that it's easier to use. Folks can track progress with [#182](https://github.com/omarroth/invidious/issues/182). As with all data Invidious keeps, watch history can be exported [here](https://invidio.us/data_control).
Users can now delete their account with [`b9c29bf`](https://github.com/omarroth/invidious/b9c29bf). This will remove _all_ user data from Invidious, including session IDs, watch history, and subscriptions. As mentioned above, it's easy to export that data and import it to a local instance, or export subscriptions for use with other applications such as [FreeTube](https://github.com/FreeTubeApp/FreeTube) or [NewPipe](https://github.com/TeamNewPipe/NewPipe).
## Translation and Internationalis(z)ation
Invidious has been approved for hosting by Weblate, available [here](https://hosted.weblate.org/projects/invidious/translations/). At the time of writing, translations for Arabic, Dutch, German, Polish, and Russian are currently underway. I would like to say a very big thank you to everyone working on them, and I hope to fully support them within around 2 weeks. Folks can track progress with [#251](https://github.com/omarroth/invidious/issues/251).
## Transperency and Finances
For the sake of transparency, I plan on publishing each month's finances. This is currently already done on Liberapay and Patreon, but there is not a total amount currently provided anywhere, and I would also like to include expenses to provide a better explanation of how patrons' money is being spent.
### Donations
- [Patreon](https://www.patreon.com/omarroth): \$43.60 (Patreon takes roughly 9%)
- [Liberapay](https://liberapay.com/omarroth) : \$22.10
- Crypto : ~\$1.25 (converted from BCH, BTC)
- Total : \$66.95
### Expenses
- invidious-load1 (nyc1) : \$10.00 (load balancer)
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
- Total : \$75.00
I'd be happy to provide any explanation where needed. I would also like to thank everyone who donates, it really helps and I can't say how happy I am to see that so many people find it valuable.
That's all for this month. I wish everyone the best for the holidays, and I'll see you all again in January!
# 0.11.0 (2018-10-23)
## Week 11: FreeTube and Styling
This past Friday I'm been very excited to see that FreeTube version [0.4.0](https://github.com/FreeTubeApp/FreeTube/tree/0.4.0) has been released! I'd recommend taking a look at the official patch notes, but to spoil a little bit here: FreeTube now uses the Invidious API for _all_ requests previously sent to YouTube, and has also seen support for playlists, keyboard shortcuts, and more default settings (speed, autoplay, and subtitles). I'm happy to see that FreeTube has reached 500 stars on Github, and I think it's very much deserved. I'd recommend keeping an eye on the newly-launched [FreeTube blog](https://freetube.writeas.com/) for updates on the project.
Quite a few styling changes have been added this past week, including channel subscriber count to the subscribe and unsubscribe buttons. The changes sound small, but they've been a very big improvement and I'm quite satisfied with how they look. Also to note is that partial support for duration in thumbnails have been added with [#202](https://github.com/omarroth/invidious/issues/202). Overall, I think the site is becoming much more pleasing visually, and I hope to continue to improve it.
I've been very pleased to see Invidious in its current state, and I believe it's many times more mature compared to even a month ago. Changes have also started slowing down a bit as it's become more mature, and therefore I'd like to transition to a monthly update schedule in order to provide more comprehensive updates for everyone. I want to thank you all for helping me reach this point. I can't say how happy I am for Invidious to be where it is now.
Enjoy the rest of your week everyone, I'll see you in November!
# 0.10.0 (2018-10-16)
## Week 10: Subscriptions
This week I'm happy to announce that subscriptions have been drastically sped up with
35e63fa. As I mentioned last week, this essentially "caches" a user's feed, meaning that operations that previously took 20 seconds or timed out, now can load in under a second. I'd take a look at [#173](https://github.com/omarroth/invidious/issues/173) for a sample benchmark. Previously features that made Invidious's feed so useful, such as filtering by unseen and by author would take too long to load, and so instead would timeout. I'm very happy that this has been fixed, and folks can get back to using these features.
Among some smaller features that have been added this week include [#118](https://github.com/omarroth/invidious/issues/118), which adds, in my opinion, some very attractive subscribe and unsubscribe buttons. I think it's also a bit of a functional improvement as well, since it doesn't require a user to reload the page in order to subscribe or unsubscribe to a channel, and also gives the opportunity to put the channel's sub count on display.
An option to swap between Reddit and YouTube comments without a page reload has been added with
5eefab6, bringing it somewhat closer in functionality to the popular [AlienTube](https://github.com/xlexi/alientube) extension, on which it is based (although the extension unfortunately appears now to be fragmented).
As always, there are a couple smaller improvements this week, including some minor fixes for geo-bypass with
e46e618 and [`245d0b5`](https://github.com/omarroth/invidious/245d0b5), playlist preferences with [`81b4477`](https://github.com/omarroth/invidious/81b4477), and YouTube comments with [`02335f3`](https://github.com/omarroth/invidious/02335f3).
This coming week I'd also recommend keeping an eye on the excellent [FreeTube](https://github.com/FreeTubeApp/FreeTube), which is looking forward to a new release. I've been very lucky to work with [**@PrestonN**](https://github.com/PrestonN) for the past few weeks to improve the Invidious API, and I'm quite looking forward to the new release.
That's all for this week folks, thank you all again for your continued interest and support.
# 0.9.0 (2018-10-08)
## Week 9: Playlists
Not as much to announce this week, but I'm still quite happy to announce a couple things, namely:
Playback support for playlists has finally been added with [`88430a6`](https://github.com/omarroth/invidious/88430a6). You can now view playlists with the `&list=` query param, as you would on YouTube. You can also view mixes with the mentioned `&list=`, although they require some extra handling that I would like to add in the coming week, as well as adding playlist looping and shuffle. I think playback support has been a roadblock for more exciting features such as [#114](https://github.com/omarroth/invidious/issues/114), and I look forward to improving the experience.
Comments have had a bit of a cosmetic upgrade with [#132](https://github.com/omarroth/invidious/issues/132), which I think helps better distinguish between Reddit and YouTube comments, as it makes them appear similarly to their respective sites. You can also now switch between YouTube and Reddit comments with a push of a button, which I think is quite an improvement, especially for newer or less popular videos with fewer comments.
I've had a small breakthrough in speeding up users' subscription feeds with PostgreSQL's [materialized views](https://www.postgresql.org/docs/current/static/rules-materializedviews.html). Without going into too much detail, materialized views essentially cache the result of a query, making it possible to run resource-intensive queries once, rather than every time a user visits their feed. In the coming week I hope to push this out to users, and hopefully close [#173](https://github.com/omarroth/invidious/issues/173).
I haven't had as much time to work on the project this week, but I'm quite happy to have added some new features. Have a great week everyone.
# 0.8.0 (2018-10-02)
## Week 8: Mixes
Hello again!
Mixes have been added with [`20130db`](https://github.com/omarroth/invidious/20130db), which makes it easy to create a playlist of related content. See [#188](https://github.com/omarroth/invidious/issues/188) for more info on how they work. Currently, they return the first 50 videos rather than a continuous feed to avoid tracking by Google/YouTube, which I think is a good trade-off between usability and privacy, and I hope other folks agree. You can create mixes by adding `RD` to the beginning of a video ID, an example is provided [here](https://www.invidio.us/mix?list=RDYE7VzlLtp-4) based on Big Buck Bunny. I've been quite happy with the results returned for the mixes I've tried, and it is not limited to music, which I think is a big plus. To emulate a continuous feed provided many are used to, using the last video of each mix as a new 'seed' has worked well for me. In the coming week I'd like to to add playback support in the player to listen to these easily.
A very big thanks to [**@flourgaz**](https://github.com/flourgaz) for Docker support with [#186](https://github.com/omarroth/invidious/pull/186). This is an enormous improvement in portability for the project, and opens the door for Heroku support (see [#162](https://github.com/omarroth/invidious/issues/162)), and seamless support on Windows. For most users, it should be as easy as running `docker-compose up`.
I've spent quite a bit of time this past week improving support for geo-bypass (see [#92](https://github.com/omarroth/invidious/issues/92)), and am happy to note that Invidious has been able to proxy ~50% of the geo-restricted videos I've tried. In addition, you can now watch geo-restricted videos if you have `dash` enabled as your `preferred quality`, for more details see [#34](https://github.com/omarroth/invidious/issues/34) and [#185](https://github.com/omarroth/invidious/issues/185), or last week's update. For folks interested in replicating these results for themselves, I'd take a look [here](https://gist.github.com/omarroth/3ce0f276c43e0c4b13e7d9cd35524688) for the script used, and [here](https://gist.github.com/omarroth/beffc4a76a7b82a422e1b36a571878ef) for a list of videos restricted in the US.
1080p has seen a fairly smooth roll-out, although there have been a couple issues reported, mainly [#193](https://github.com/omarroth/invidious/issues/193), which is likely an issue in the player. I've also encountered a couple other issues myself that I would like to investigate. Although none are major, I'd like to keep 1080p opt-in for registered users another week to better address these issues.
Have an excellent week everyone.
# 0.7.0 (2018-09-25)
## Week 7: 1080p and Search Types
Hello again everyone! I've got quite a couple announcements this week:
Experimental 1080p support has been added with [`b3ca392`](https://github.com/omarroth/invidious/b3ca392), and can be enabled by going to preferences and changing `preferred video quality` to `dash`. You can find more details [here](https://github.com/omarroth/invidious/issues/34#issuecomment-424171888). Currently quality and speed controls have not yet been integrated into the player, but I'd still appreciate feedback, mainly on any issues with buffering or DASH playback. I hope to integrate 1080p support into the player and push support site-wide in the coming weeks.
You can now filter content types in search with the `type:TYPE` filter. Supported content types are `playlist`, `channel`, and `video`. More info is available [here](https://github.com/omarroth/invidious/issues/126#issuecomment-423823148). I think this is quite an improvement in usability and I hope others find the same.
A [CHANGELOG](https://github.com/omarroth/invidious/blob/master/CHANGELOG.md) has been added to the repository, so folks will now receive a copy of all these updates when cloning. I think this is an improvement in hosting the project, as it is no longer tied to the `/releases` tab on Github or the posts on Patreon.
Recently, users have been reporting 504s when attempting to access their subscriptions, which is tracked in [#173](https://github.com/omarroth/invidious/issues/173). This is most likely caused by an uptick in usage, which I am absolutely grateful for, but unfortunately has resulted in an increase in costs for hosting the site, which is why I will be bumping my goal on Patreon from $60 to $80. I would appreciate any feedback on how subscriptions could be improved.
Other minor improvements include:
- Additional regions added to bypass geo-block with [`9a78523`](https://github.com/omarroth/invidious/9a78523)
- Fix for playlists containing less than 100 videos (previously shown as empty) with [`35ac887`](https://github.com/omarroth/invidious/35ac887)
- Fix for `published` date for Reddit comments (previously showing negative seconds) with [`6e09202`](https://github.com/omarroth/invidious/6e09202)
Thank you everyone for your support!
# 0.6.0 (2018-09-18)
## Week 6: Filters and Thumbnails
Hello again! This week I'm happy to mention a couple new features to search as well as some miscellaneous usability improvements.
You can now constrain your search query to a specific channel with the `channel:CHANNEL` filter (see [#165](https://github.com/omarroth/invidious/issues/165) for more details). Unfortunately, other search filters combined with channel search are not yet supported. I hope to add support for them in the coming weeks.
You can also now search only your subscriptions by adding `subscriptions:true` to your query (see [#30](https://github.com/omarroth/invidious/issues/30) for more details). It's not quite ready for widespread use but I would appreciate feedback as the site updates to fully support it. Other search filters are not yet supported with `subscriptions:true`, but I hope to add more functionality to this as well.
With [#153](https://github.com/omarroth/invidious/issues/153) and [#168](https://github.com/omarroth/invidious/issues/168) all images on the site are now proxied through Invidious. In addition to offering the user more protection from Google's eyes, it also allows the site to automatically pick out the highest resolution thumbnail for videos. I think this is quite a large aesthetic improvement and I hope others will find the same.
As a smaller improvement to the site, you can also now view RSS feeds for playlists with [#113](https://github.com/omarroth/invidious/issues/113).
These updates are also now listed under Github's [releases](https://github.com/omarroth/invidious/releases). I'm also planning on adding them as a `CHANGELOG.md` in the repository itself so people can receive a copy with the project's source.
That's all for this week. Thank you everyone for your support!
# 0.5.0 (2018-09-11)
## Week 5: Privacy and Security
I hope everyone had a good weekend! This past week I've been fixing some issues that have been brought to my attention to help better protect users and help them keep their anonymity.
An issue with open referers has been fixed with [`29a2186`](https://github.com/omarroth/invidious/29a2186), which prevents potential redirects to external sites on actions such as login or modifying preferences.
Additionally, X-XSS-Protection, X-Content-Type-Options, and X-Frame-Options headers have been added with [`96234e5`](https://github.com/omarroth/invidious/96234e5), which should keep users safer while using the site.
A potential XSS vector has also been fixed in YouTube comments with [`8c45694`](https://github.com/omarroth/invidious/8c45694).
All the above vulnerabilities were brought to my attention by someone who wishes to remain anonymous, but I would like to say again here how thankful I am. If anyone else would like to get in touch please feel free to email me at omarroth@hotmail.com or omarroth@protonmail.com.
This week a couple changes have been made to better protect user's privacy as well.
All CSS and JS assets are now served locally with [`3ec684a`](https://github.com/omarroth/invidious/3ec684a), which means users no longer need to whitelist unpkg.com. Although I personally have encountered few issues, I understand that many folks would like to keep their browsing activity contained to as few parties as possible. In the coming week I also hope to proxy YouTube images, so that no user data is sent to Google.
YouTube links in comments now should redirect properly to the Invidious alternate with [`1c8bd67`](https://github.com/omarroth/invidious/1c8bd67) and [`cf63c82`](https://github.com/omarroth/invidious/cf63c82), so users can more easily evade Google tracking.
I'm also happy to mention a couple quality of life features this week:
Invidious now shows a video's "license" if provided, see [#159](https://github.com/omarroth/invidious/issues/159) for more details. You can also search for videos licensed under the creative commons with "QUERY features:creative_commons".
Videos with only one source will always display the cog for changing quality, so that users can see what quality is currently playing. See [#158](https://github.com/omarroth/invidious/issues/158) for more details.
Folks have also probably noticed that the gutters on either side of the screen have been shrunk down quite significantly, so that more of the screen is filled with content. Hopefully this can be improved even more in the coming weeks.
"Music", "Sports", and "Popular on YouTube" channels now properly display their videos. You can subscribe to these channels just as you would normally.
This coming week I'm planning on spending time with my family, so I unfortunately may not be as responsive. I do still hope to add some smaller features for next week however, and I hope to continue development soon.
Thank you everyone again for your support.
# 0.4.0 (2018-09-06)
## Week 4: Genre Channels
Hello! I hope everyone enjoyed their weekend. Without further ado:
Just today genre channels have been added with [#119](https://github.com/omarroth/invidious/issues/119). More information on genre channels is available [here](https://support.google.com/youtube/answer/2579942). You can subscribe to them as normally, and view them as RSS. I think they offer an interesting alternative way to find new content and I hope people find them useful.
This past week folks have started reporting 504s on their subscription page (see [#144](https://github.com/omarroth/invidious/issues/144) for more details). Upgrading the database server appeared to fix the issue, as well as providing a smoother experience across the site. Unfortunately, that means I will be increasing the goal from $50 to $60 in order to meet the increased hosting costs.
With [#134](https://github.com/omarroth/invidious/issues/134), comments are now formatted correctly, providing support for bold, italics, and links in comments. I think this improvement makes them much easier to read, and I hope others find the same. Also to note is that links in both comments and the video description now no longer contain any of Google's tracking with [#115](https://github.com/omarroth/invidious/issues/115).
One of the major use cases for Invidious is as a stripped-down version of YouTube. In line with that, I'm happy to announce that you can now hide related videos if you're logged in, for users that prefer an even more lightweight experience.
Finally, I'm pleased to announce that Invidious has hit 100 stars on GitHub. I am very happy that Invidious has proven to be useful to so many people, and I can't say how grateful I am to everyone for their continued support.
Enjoy the rest of your week everyone!
# 0.3.0 (2018-09-06)
## Week 3: Quality of Life
Hello everyone! This week I've been working on some smaller features that will hopefully make the site more functional.
Search filters have been added with [#126](https://github.com/omarroth/invidious/issues/126). You can now specify 'sort', 'date', 'duration', and 'features' within your query using the 'operator:value' syntax. I'd recommend taking a look [here](https://github.com/omarroth/invidious/blob/master/src/invidious/search.cr#L33-L114) for a list of supported options and at [#126](https://github.com/omarroth/invidious/issues/126) for some examples. This also opens the door for features such as [#30](https://github.com/omarroth/invidious/issues/30) which can be implemented as filters. I think advanced search is a major point in which Invidious can improve on YouTube and hope to add more features soon!
This week a more advanced system for viewing fallback comments has been added (see [#84](https://github.com/omarroth/invidious/issues/84) for more details). You can now specify a comment fallback in your preferences, which Invidious will use. If, for example, no Reddit comments are available for a given video, it can choose to fallback on YouTube comments. This also makes it possible to turn comments off completely for users that prefer a more streamlined experience.
With [#98](https://github.com/omarroth/invidious/issues/98), it is now possible for users to specify preferences without creating an account. You can now change speed, volume, subtitles, autoplay, loop, and quality using query parameters. See the issue above for more details and several examples.
I'd also like to announce that I've set up an account on [Liberapay](https://liberapay.com/omarroth), for patrons that prefer a privacy-friendly alternative to Patreon. Liberapay also does not take any percentage of donations, so I'd recommend donating some to the Liberapay for their hard work. Go check it out!
[Two weeks ago](https://github.com/omarroth/invidious/releases/tag/0.1.0) I mentioned adding 1080p support into the player. Currently, the only thing blocking is [#207](https://github.com/videojs/http-streaming/pull/207) in the excellent [http-streaming](https://github.com/videojs/http-streaming) library. I hope to work with the videojs team to merge it soon and finally implement 1080p support!
That's all for this week, thank you again everyone for your support!
# 0.2.0 (2018-09-06)
## Week 2: Toward Playlists
Sorry for the late update! Not as much to announce this week, but still a couple things of note:
I'm happy to announce that a playlists page and API endpoint has been added so you can now view playlists. Currently, you cannot watch playlists through the player, but I hope to add that in the coming week as well as adding functionality to add and modify playlists. There is a good conversation on [#114](https://github.com/omarroth/invidious/issues/114) about giving playlists even more functionality, which I think is interesting and would appreciate feedback on.
As an update to the Invidious API announcement last week, I've been working with [**@PrestonN**](https://github.com/PrestonN), the developer of [FreeTube](https://github.com/FreeTubeApp/FreeTube), to help migrate his project to the Invidious API. Because of it's increasing popularity, he has had trouble keeping under the quota set by YouTube's API. I hope to improve the API to meet his and others needs and I'd recommend folks to keep an eye on his excellent project! There is a good discussion with his thoughts [here](https://github.com/FreeTubeApp/FreeTube/issues/100).
A couple of miscellaneous features and bugfixes:
- You can now login to Invidious simultaneously from multiple devices - [#109](https://github.com/omarroth/invidious/issues/109)
- Added a note for scheduled livestreams - [#124](https://github.com/omarroth/invidious/issues/124)
- Changed YouTube comment header to "View x comments" - [#120](https://github.com/omarroth/invidious/issues/120)
Enjoy your week everyone!
# 0.1.0 (2018-09-06)
## Week 1: Invidious API and Geo-Bypass
Hello everyone! This past week there have been quite a few things worthy of mention:
I'm happy to announce the [Invidious Developer API](https://github.com/omarroth/invidious/wiki/API). The Invidious API does not use any of the official YouTube APIs, and instead crawls the site to provide a JSON interface for other developers to use. It's still under development but is already powering [CloudTube](https://github.com/cloudrac3r/cadencegq). The API currently does not have a quota (compared to YouTube) which I hope to continue thanks to continued support from my Patrons. Hopefully other developers find it useful, and I hope to continue to improve it so it can better serve the community.
Just today partial support for bypassing geo-restrictions has been added with [fada57a](https://github.com/omarroth/invidious/commit/fada57a307d66d696d9286fc943c579a3fd22de6). If a video is unblocked in one of: United States, Canada, Germany, France, Japan, Russia, or United Kingdom, then Invidious will be able to serve video info. Currently you will not yet be able to access the video files themselves, but in the coming week I hope to proxy videos so that users can enjoy content across borders.
Support for generating DASH manifests has been fixed, in the coming week I hope to integrate this functionality into the watch page, so users can view videos in 1080p and above.
Thank you everyone for your continued interest and support!

119
assets/css/carousel.css Normal file
View file

@ -0,0 +1,119 @@
/*
Copyright (c) 2024 by Jennifer (https://codepen.io/jwjertzoch/pen/JjyGeRy)
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
.carousel {
margin: 0 auto;
overflow: hidden;
text-align: center;
}
.slides {
width: 100%;
display: flex;
overflow-x: scroll;
scrollbar-width: none;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
.slides::-webkit-scrollbar {
display: none;
}
.slides-item {
align-items: center;
border-radius: 10px;
display: flex;
flex-shrink: 0;
font-size: 100px;
height: 600px;
justify-content: center;
margin: 0 1rem;
position: relative;
scroll-snap-align: start;
transform: scale(1);
transform-origin: center center;
transition: transform .5s;
width: 100%;
}
.carousel__nav {
padding: 1.25rem .5rem;
}
.slider-nav {
align-items: center;
background-color: #ddd;
border-radius: 50%;
color: #000;
display: inline-flex;
height: 1.5rem;
justify-content: center;
padding: .5rem;
position: relative;
text-decoration: none;
width: 1.5rem;
}
.skip-link {
height: 1px;
overflow: hidden;
position: absolute;
top: auto;
width: 1px;
}
.skip-link:focus {
align-items: center;
background-color: #000;
color: #fff;
display: flex;
font-size: 30px;
height: 30px;
justify-content: center;
opacity: .8;
text-decoration: none;
width: 50%;
z-index: 1;
}
.light-theme .slider-nav {
background-color: #ddd;
}
.dark-theme .slider-nav {
background-color: #0005;
}
@media (prefers-color-scheme: light) {
.no-theme .slider-nav {
background-color: #ddd;
}
}
@media (prefers-color-scheme: dark) {
.no-theme .slider-nav {
background-color: #0005;
}
}

View file

@ -13,6 +13,7 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
margin: auto;
} }
.h-box { .h-box {
@ -197,6 +198,7 @@ img.thumbnail {
display: block; /* See: https://stackoverflow.com/a/11635197 */ display: block; /* See: https://stackoverflow.com/a/11635197 */
width: 100%; width: 100%;
object-fit: cover; object-fit: cover;
aspect-ratio: 16 / 9;
} }
.thumbnail-placeholder { .thumbnail-placeholder {

View file

@ -10,7 +10,7 @@ var notifications, delivered;
var notifications_mock = { close: function () { } }; var notifications_mock = { close: function () { } };
function get_subscriptions() { function get_subscriptions() {
helpers.xhr('GET', '/api/v1/auth/subscriptions?fields=authorId', { helpers.xhr('GET', '/api/v1/auth/subscriptions', {
retries: 5, retries: 5,
entity_name: 'subscriptions' entity_name: 'subscriptions'
}, { }, {
@ -22,7 +22,7 @@ function create_notification_stream(subscriptions) {
// sse.js can't be replaced to EventSource in place as it lack support of payload and headers // sse.js can't be replaced to EventSource in place as it lack support of payload and headers
// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource // see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
notifications = new SSE( notifications = new SSE(
'/api/v1/auth/notifications?fields=videoId,title,author,authorId,publishedText,published,authorThumbnails,liveNow', { '/api/v1/auth/notifications', {
withCredentials: true, withCredentials: true,
payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','), payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' } headers: { 'Content-Type': 'application/x-www-form-urlencoded' }

View file

@ -32,12 +32,10 @@ services:
# statistics_enabled: false # statistics_enabled: false
hmac_key: "CHANGE_ME!!" hmac_key: "CHANGE_ME!!"
healthcheck: healthcheck:
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/comments/jNQXAC9IVRw || exit 1 test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 2 retries: 2
depends_on:
- invidious-db
invidious-db: invidious-db:
image: docker.io/library/postgres:14 image: docker.io/library/postgres:14

View file

@ -15,13 +15,13 @@
"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": "استيراد بيانات JSON Invidious", "Import Invidious data": "استيراد بيانات JSON Invidious",
"Import YouTube subscriptions": "استيراد اشتراكات YouTube/OPML", "Import YouTube subscriptions": "استيراد الاشتراكات YouTube بتنسيق CSV أو OPML",
"Import FreeTube subscriptions (.db)": "استيراد اشتراكات فريتيوب (.db)", "Import FreeTube subscriptions (.db)": "استيراد اشتراكات فريتيوب (.db)",
"Import NewPipe subscriptions (.json)": "استيراد اشتراكات نيو بايب (.json)", "Import NewPipe subscriptions (.json)": "استيراد اشتراكات نيو بايب (.json)",
"Import NewPipe data (.zip)": "استيراد بيانات نيو بايب (.zip)", "Import NewPipe data (.zip)": "استيراد بيانات نيو بايب (.zip)",
@ -41,7 +41,7 @@
"Time (h:mm:ss):": "الوقت (h:mm:ss):", "Time (h:mm:ss):": "الوقت (h:mm:ss):",
"Text CAPTCHA": "نص الكابتشا", "Text CAPTCHA": "نص الكابتشا",
"Image CAPTCHA": "صورة الكابتشا", "Image CAPTCHA": "صورة الكابتشا",
"Sign In": "تسجيل الدخول", "Sign In": "إنشاء حساب",
"Register": "التسجيل", "Register": "التسجيل",
"E-mail": "البريد الإلكتروني", "E-mail": "البريد الإلكتروني",
"Preferences": "الإعدادات", "Preferences": "الإعدادات",
@ -170,7 +170,7 @@
"Password cannot be empty": "لا يمكن أن تكون كلمة السر فارغة", "Password cannot be empty": "لا يمكن أن تكون كلمة السر فارغة",
"Password cannot be longer than 55 characters": "يجب أن لا تتعدى كلمة السر 55 حرفًا", "Password cannot be longer than 55 characters": "يجب أن لا تتعدى كلمة السر 55 حرفًا",
"Please log in": "الرجاء تسجيل الدخول", "Please log in": "الرجاء تسجيل الدخول",
"Invidious Private Feed for `x`": "تغذية Invidious خاصة ل 'x'", "Invidious Private Feed for `x`": "تغذية Invidious خاصة ل `x`",
"channel:`x`": "قناة:`x`", "channel:`x`": "قناة:`x`",
"Deleted or invalid channel": "قناة ممسوحة او غير صالحة", "Deleted or invalid channel": "قناة ممسوحة او غير صالحة",
"This channel does not exist.": "هذه القناة غير موجودة.", "This channel does not exist.": "هذه القناة غير موجودة.",
@ -382,11 +382,11 @@
"videoinfo_watch_on_youTube": "مشاهدة على يوتيوب", "videoinfo_watch_on_youTube": "مشاهدة على يوتيوب",
"videoinfo_youTube_embed_link": "مضمن", "videoinfo_youTube_embed_link": "مضمن",
"videoinfo_invidious_embed_link": "رابط مضمن", "videoinfo_invidious_embed_link": "رابط مضمن",
"user_created_playlists": "'x' إنشاء قوائم التشغيل", "user_created_playlists": "`x` إنشاء قوائم التشغيل",
"user_saved_playlists": "قوائم التشغيل المحفوظة 'x'", "user_saved_playlists": "قوائم التشغيل المحفوظة `x`",
"Video unavailable": "الفيديو غير متوفر", "Video unavailable": "الفيديو غير متوفر",
"search_filters_features_option_three_sixty": "360°", "search_filters_features_option_three_sixty": "360°",
"download_subtitles": "ترجمات - 'x' (.vtt)", "download_subtitles": "ترجمات - `x` (.vtt)",
"invidious": "الخيالي", "invidious": "الخيالي",
"preferences_save_player_pos_label": "حفظ موضع التشغيل: ", "preferences_save_player_pos_label": "حفظ موضع التشغيل: ",
"crash_page_you_found_a_bug": "يبدو أنك قد وجدت خطأً برمجيًّا في Invidious!", "crash_page_you_found_a_bug": "يبدو أنك قد وجدت خطأً برمجيًّا في Invidious!",
@ -554,5 +554,15 @@
"generic_channels_count_2": "{{count}} قناتان", "generic_channels_count_2": "{{count}} قناتان",
"generic_channels_count_3": "{{count}} قنوات", "generic_channels_count_3": "{{count}} قنوات",
"generic_channels_count_4": "{{count}} قنوات", "generic_channels_count_4": "{{count}} قنوات",
"generic_channels_count_5": "{{count}} قناة" "generic_channels_count_5": "{{count}} قناة",
"Import YouTube watch history (.json)": "استيراد سجل مشاهدة YouTube بصيغة (.json)",
"toggle_theme": "تبديل الموضوع",
"Add to playlist": "أضف إلى قائمة التشغيل",
"Add to playlist: ": "أضف إلى قائمة التشغيل: ",
"Answer": "الرد",
"Search for videos": "ابحث عن مقاطع الفيديو",
"The Popular feed has been disabled by the administrator.": "تم تعطيل الخلاصة الشائعة من قبل المسؤول.",
"carousel_slide": "الشريحة {{current}} من {{total}}",
"carousel_skip": "تخطي الكاروسيل",
"carousel_go_to": "انتقل إلى الشريحة `x`"
} }

View file

@ -486,5 +486,6 @@
"preferences_annotations_label": "Покажи анотаций по подразбиране: ", "preferences_annotations_label": "Покажи анотаций по подразбиране: ",
"generic_views_count": "{{count}} гледане", "generic_views_count": "{{count}} гледане",
"generic_views_count_plural": "{{count}} гледания", "generic_views_count_plural": "{{count}} гледания",
"Next page": "Следваща страница" "Next page": "Следваща страница",
"Import YouTube watch history (.json)": "Импортиране на историята на гледане от YouTube (.json)"
} }

View file

@ -90,5 +90,7 @@
"preferences_quality_option_medium": "মধ্যম", "preferences_quality_option_medium": "মধ্যম",
"preferences_quality_option_small": "ছোট", "preferences_quality_option_small": "ছোট",
"preferences_quality_dash_option_1080p": "১০৮০পি", "preferences_quality_dash_option_1080p": "১০৮০পি",
"preferences_quality_dash_option_720p": "৭২০পি" "preferences_quality_dash_option_720p": "৭২০পি",
"Add to playlist": "প্লেলিস্টে যোগ করুন",
"Add to playlist: ": "প্লেলিস্টে যোগ করুন: "
} }

View file

@ -486,5 +486,6 @@
"generic_channels_count_plural": "{{count}} canals", "generic_channels_count_plural": "{{count}} canals",
"generic_button_edit": "Edita", "generic_button_edit": "Edita",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"generic_button_delete": "Suprimeix" "generic_button_delete": "Suprimeix",
"Import YouTube watch history (.json)": "Importa l'historial de visualitzacions de YouTube (.json)"
} }

View file

@ -21,7 +21,7 @@
"Import and Export Data": "Import a export dat", "Import and Export Data": "Import a export dat",
"Import": "Importovat", "Import": "Importovat",
"Import Invidious data": "Importovat JSON údaje Invidious", "Import Invidious data": "Importovat JSON údaje Invidious",
"Import YouTube subscriptions": "Importovat odběry z YouTube/OPML", "Import YouTube subscriptions": "Importovat odběry z YouTube CSV nebo OPML",
"Import FreeTube subscriptions (.db)": "Importovat odběry z FreeTube (.db)", "Import FreeTube subscriptions (.db)": "Importovat odběry z FreeTube (.db)",
"Import NewPipe subscriptions (.json)": "Importovat odběry z NewPipe (.json)", "Import NewPipe subscriptions (.json)": "Importovat odběry z NewPipe (.json)",
"Import NewPipe data (.zip)": "Importovat údeje z NewPipe (.zip)", "Import NewPipe data (.zip)": "Importovat údeje z NewPipe (.zip)",
@ -503,5 +503,15 @@
"playlist_button_add_items": "Přidat videa", "playlist_button_add_items": "Přidat videa",
"generic_channels_count_0": "{{count}} kanál", "generic_channels_count_0": "{{count}} kanál",
"generic_channels_count_1": "{{count}} kanály", "generic_channels_count_1": "{{count}} kanály",
"generic_channels_count_2": "{{count}} kanálů" "generic_channels_count_2": "{{count}} kanálů",
"Import YouTube watch history (.json)": "Importovat historii sledování z YouTube (.json)",
"toggle_theme": "Přepnout motiv",
"Add to playlist": "Přidat do playlistu",
"Add to playlist: ": "Přidat do playlistu: ",
"Answer": "Odpověď",
"Search for videos": "Hledat videa",
"The Popular feed has been disabled by the administrator.": "Kategorie Populární byla zakázána administrátorem.",
"carousel_slide": "Snímek {{current}} z {{total}}",
"carousel_skip": "Přeskočit galerii",
"carousel_go_to": "Přejít na snímek `x`"
} }

View file

@ -165,12 +165,12 @@
"Password cannot be empty": "Adgangskoden må ikke være tom", "Password cannot be empty": "Adgangskoden må ikke være tom",
"Password cannot be longer than 55 characters": "Adgangskoden må ikke være længere end 55 tegn", "Password cannot be longer than 55 characters": "Adgangskoden må ikke være længere end 55 tegn",
"Please log in": "Venligst log ind", "Please log in": "Venligst log ind",
"channel:`x`": "kanal: 'x'", "channel:`x`": "kanal: `x`",
"Deleted or invalid channel": "Slettet eller invalid kanal", "Deleted or invalid channel": "Slettet eller invalid kanal",
"This channel does not exist.": "Denne kanal eksisterer ikke.", "This channel does not exist.": "Denne kanal eksisterer ikke.",
"Could not get channel info.": "Kunne ikke hente kanal info.", "Could not get channel info.": "Kunne ikke hente kanal info.",
"Could not fetch comments": "Kunne ikke hente kommentarer", "Could not fetch comments": "Kunne ikke hente kommentarer",
"`x` ago": "'x' siden", "`x` ago": "`x` siden",
"Load more": "Hent flere", "Load more": "Hent flere",
"Could not create mix.": "Kunne ikke skabe blanding.", "Could not create mix.": "Kunne ikke skabe blanding.",
"Empty playlist": "Tom playliste", "Empty playlist": "Tom playliste",
@ -452,5 +452,40 @@
"crash_page_you_found_a_bug": "Det ser ud til, at du har fundet en fejl i Invidious!", "crash_page_you_found_a_bug": "Det ser ud til, at du har fundet en fejl i Invidious!",
"crash_page_read_the_faq": "læs <a href=\"`x`\">Ofte stillede spørgsmål (FAQ)</a>", "crash_page_read_the_faq": "læs <a href=\"`x`\">Ofte stillede spørgsmål (FAQ)</a>",
"crash_page_search_issue": "søgte efter <a href=\"`x`\">eksisterende problemer på GitHub</a>", "crash_page_search_issue": "søgte efter <a href=\"`x`\">eksisterende problemer på GitHub</a>",
"search_filters_title": "Filter" "search_filters_title": "Filter",
"playlist_button_add_items": "Tilføj videoer",
"search_message_no_results": "Ingen resultater fundet.",
"Import YouTube watch history (.json)": "Importer YouTube afspilningshistorik (.json)",
"search_message_change_filters_or_query": "Prøv at udvide din søgeforspørgsel og/eller ændre filtrene.",
"search_message_use_another_instance": " Du kan også <a href=\"`x`\">søge på en anden instans</a>.",
"Music in this video": "Musik i denne video",
"search_filters_date_option_none": "Enhver dato",
"search_filters_type_option_all": "Enhver type",
"search_filters_duration_option_none": "Enhver varighed",
"search_filters_duration_option_medium": "Medium (4 - 20 minutter)",
"search_filters_features_option_vr180": "VR180",
"generic_channels_count": "{{count}} kanal",
"generic_channels_count_plural": "{{count}} kanaler",
"Import YouTube playlist (.csv)": "Importer YouTube playliste (.csv)",
"Standard YouTube license": "Standard Youtube-licens",
"Album: ": "Album: ",
"Channel Sponsor": "Kanal-sponsor",
"Song: ": "Sang: ",
"channel_tab_playlists_label": "Playlister",
"channel_tab_channels_label": "Kanaler",
"Artist: ": "Kunstner: ",
"search_filters_date_label": "Uploaddato",
"generic_button_delete": "Slet",
"generic_button_edit": "Rediger",
"generic_button_save": "Gem",
"generic_button_cancel": "Afbryd",
"generic_button_rss": "RSS",
"Popular enabled: ": "Populær aktiveret: ",
"search_filters_apply_button": "Anvend udvalgte filtre",
"channel_tab_shorts_label": "Shorts",
"channel_tab_streams_label": "Livestreams",
"channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Udgivelser",
"Download is disabled": "Download er slået fra",
"error_video_not_in_playlist": "Den ønskede video findes ikke i denne playliste. <a href=\"`x`\">Klik her for playlistens startside.</a>"
} }

View file

@ -148,7 +148,7 @@
"Whitelisted regions: ": "Erlaubte Regionen: ", "Whitelisted regions: ": "Erlaubte Regionen: ",
"Blacklisted regions: ": "Unerlaubte Regionen: ", "Blacklisted regions: ": "Unerlaubte Regionen: ",
"Shared `x`": "Geteilt `x`", "Shared `x`": "Geteilt `x`",
"Premieres in `x`": "Zuerst gesehen in `x`", "Premieres in `x`": "Premiere in `x`",
"Premieres `x`": "Erster Start `x`", "Premieres `x`": "Erster Start `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.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.",
"View YouTube comments": "YouTube Kommentare anzeigen", "View YouTube comments": "YouTube Kommentare anzeigen",
@ -486,5 +486,12 @@
"channel_tab_podcasts_label": "Podcasts", "channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Veröffentlichungen", "channel_tab_releases_label": "Veröffentlichungen",
"generic_channels_count": "{{count}} Kanal", "generic_channels_count": "{{count}} Kanal",
"generic_channels_count_plural": "{{count}} Kanäle" "generic_channels_count_plural": "{{count}} Kanäle",
"Import YouTube watch history (.json)": "YouTube Wiedergabeverlauf importieren (.json)",
"Answer": "Antwort",
"The Popular feed has been disabled by the administrator.": "Der Angesagt-Feed wurde vom Administrator deaktiviert.",
"Add to playlist": "Einer Wiedergabeliste hinzufügen",
"Search for videos": "Nach Videos suchen",
"toggle_theme": "Thema wechseln",
"Add to playlist: ": "Einer Wiedergabeliste hinzufügen: "
} }

View file

@ -1,4 +1,9 @@
{ {
"Add to playlist": "Add to playlist",
"Add to playlist: ": "Add to playlist: ",
"Answer": "Answer",
"Search for videos": "Search for videos",
"The Popular feed has been disabled by the administrator.": "The Popular feed has been disabled by the administrator.",
"generic_channels_count": "{{count}} channel", "generic_channels_count": "{{count}} channel",
"generic_channels_count_plural": "{{count}} channels", "generic_channels_count_plural": "{{count}} channels",
"generic_views_count": "{{count}} view", "generic_views_count": "{{count}} view",
@ -38,7 +43,7 @@
"Import and Export Data": "Import and Export Data", "Import and Export Data": "Import and Export Data",
"Import": "Import", "Import": "Import",
"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 CSV or 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 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)",
@ -487,5 +492,9 @@
"channel_tab_releases_label": "Releases", "channel_tab_releases_label": "Releases",
"channel_tab_playlists_label": "Playlists", "channel_tab_playlists_label": "Playlists",
"channel_tab_community_label": "Community", "channel_tab_community_label": "Community",
"channel_tab_channels_label": "Channels" "channel_tab_channels_label": "Channels",
"toggle_theme": "Toggle Theme",
"carousel_slide": "Slide {{current}} of {{total}}",
"carousel_skip": "Skip the Carousel",
"carousel_go_to": "Go to slide `x`"
} }

View file

@ -21,7 +21,7 @@
"Import and Export Data": "Importación y exportación de datos", "Import and Export Data": "Importación y exportación de datos",
"Import": "Importar", "Import": "Importar",
"Import Invidious data": "Importar datos JSON de Invidious", "Import Invidious data": "Importar datos JSON de Invidious",
"Import YouTube subscriptions": "Importar suscripciones de YouTube/OPML", "Import YouTube subscriptions": "Importar suscripciones CSV u OPML de YouTube",
"Import FreeTube subscriptions (.db)": "Importar suscripciones de FreeTube (.db)", "Import FreeTube subscriptions (.db)": "Importar suscripciones de FreeTube (.db)",
"Import NewPipe subscriptions (.json)": "Importar suscripciones de NewPipe (.json)", "Import NewPipe subscriptions (.json)": "Importar suscripciones de NewPipe (.json)",
"Import NewPipe data (.zip)": "Importar datos de NewPipe (.zip)", "Import NewPipe data (.zip)": "Importar datos de NewPipe (.zip)",
@ -90,7 +90,7 @@
"preferences_notifications_only_label": "Mostrar solo notificaciones (si hay alguna): ", "preferences_notifications_only_label": "Mostrar solo notificaciones (si hay alguna): ",
"Enable web notifications": "Habilitar notificaciones web", "Enable web notifications": "Habilitar notificaciones web",
"`x` uploaded a video": "`x` subió un video", "`x` uploaded a video": "`x` subió un video",
"`x` is live": "`x` esta en vivo", "`x` is live": "`x` está en directo",
"preferences_category_data": "Preferencias de los datos", "preferences_category_data": "Preferencias de los datos",
"Clear watch history": "Borrar el historial de reproducción", "Clear watch history": "Borrar el historial de reproducción",
"Import/export data": "Importar/Exportar datos", "Import/export data": "Importar/Exportar datos",
@ -102,7 +102,7 @@
"preferences_category_admin": "Preferencias de administrador", "preferences_category_admin": "Preferencias de administrador",
"preferences_default_home_label": "Página de inicio por defecto: ", "preferences_default_home_label": "Página de inicio por defecto: ",
"preferences_feed_menu_label": "Menú de fuentes: ", "preferences_feed_menu_label": "Menú de fuentes: ",
"preferences_show_nick_label": "Mostrar nombre de usuario arriba: ", "preferences_show_nick_label": "Mostrar nombre de usuario encima: ",
"Top enabled: ": "¿Habilitar los destacados? ", "Top enabled: ": "¿Habilitar los destacados? ",
"CAPTCHA enabled: ": "¿Habilitar los CAPTCHA? ", "CAPTCHA enabled: ": "¿Habilitar los CAPTCHA? ",
"Login enabled: ": "¿Habilitar el inicio de sesión? ", "Login enabled: ": "¿Habilitar el inicio de sesión? ",
@ -133,7 +133,7 @@
"Create playlist": "Crear lista de reproducción", "Create playlist": "Crear lista de reproducción",
"Title": "Título", "Title": "Título",
"Playlist privacy": "Privacidad de la lista de reproducción", "Playlist privacy": "Privacidad de la lista de reproducción",
"Editing playlist `x`": "Editando la lista de reproducción 'x'", "Editing playlist `x`": "Editando la lista de reproducción `x`",
"Show more": "Mostrar más", "Show more": "Mostrar más",
"Show less": "Mostrar menos", "Show less": "Mostrar menos",
"Watch on YouTube": "Ver en YouTube", "Watch on YouTube": "Ver en YouTube",
@ -144,13 +144,13 @@
"License: ": "Licencia: ", "License: ": "Licencia: ",
"Family friendly? ": "¿Filtrar contenidos? ", "Family friendly? ": "¿Filtrar contenidos? ",
"Wilson score: ": "Puntuación Wilson: ", "Wilson score: ": "Puntuación Wilson: ",
"Engagement: ": "Compromiso: ", "Engagement: ": "Retención: ",
"Whitelisted regions: ": "Regiones permitidas: ", "Whitelisted regions: ": "Regiones permitidas: ",
"Blacklisted regions: ": "Regiones bloqueadas: ", "Blacklisted regions: ": "Regiones bloqueadas: ",
"Shared `x`": "Compartido `x`", "Shared `x`": "Compartido `x`",
"Premieres in `x`": "Se estrena en `x`", "Premieres in `x`": "Se estrena en `x`",
"Premieres `x`": "Estrenos `x`", "Premieres `x`": "Estrenos `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.": "¡Hola! Parece que tienes JavaScript desactivado. Haz clic aquí para ver los comentarios, pero tengas en cuenta que pueden tardar un poco más en cargarse.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "¡Hola! Parece que tienes JavaScript desactivado. Haz clic aquí para ver los comentarios, ten en cuenta que pueden tardar un poco más en cargar.",
"View YouTube comments": "Ver los comentarios de YouTube", "View YouTube comments": "Ver los comentarios de YouTube",
"View more comments on Reddit": "Ver más comentarios en Reddit", "View more comments on Reddit": "Ver más comentarios en Reddit",
"View `x` comments": { "View `x` comments": {
@ -312,7 +312,7 @@
"Download as: ": "Descargar como: ", "Download as: ": "Descargar como: ",
"%A %B %-d, %Y": "%A %B %-d, %Y", "%A %B %-d, %Y": "%A %B %-d, %Y",
"(edited)": "(editado)", "(edited)": "(editado)",
"YouTube comment permalink": "Enlace permanente de YouTube del comentario", "YouTube comment permalink": "Enlace permanente de comentario de YouTube",
"permalink": "enlace permanente", "permalink": "enlace permanente",
"`x` marked it with a ❤": "`x` lo ha marcado con un ❤", "`x` marked it with a ❤": "`x` lo ha marcado con un ❤",
"Audio mode": "Modo de audio", "Audio mode": "Modo de audio",
@ -324,10 +324,10 @@
"search_filters_sort_option_rating": "Valoración", "search_filters_sort_option_rating": "Valoración",
"search_filters_sort_option_date": "Fecha de subida", "search_filters_sort_option_date": "Fecha de subida",
"search_filters_sort_option_views": "Visualizaciones", "search_filters_sort_option_views": "Visualizaciones",
"search_filters_type_label": "tipo de contenido", "search_filters_type_label": "Tipo de contenido",
"search_filters_duration_label": "duración", "search_filters_duration_label": "Duración",
"search_filters_features_label": "funcionalidades", "search_filters_features_label": "Funcionalidades",
"search_filters_sort_label": "ordenar", "search_filters_sort_label": "Ordenar",
"search_filters_date_option_hour": "Última hora", "search_filters_date_option_hour": "Última hora",
"search_filters_date_option_today": "Hoy", "search_filters_date_option_today": "Hoy",
"search_filters_date_option_week": "Esta semana", "search_filters_date_option_week": "Esta semana",
@ -390,43 +390,58 @@
"search_filters_features_option_three_sixty": "360°", "search_filters_features_option_three_sixty": "360°",
"videoinfo_watch_on_youTube": "Ver en YouTube", "videoinfo_watch_on_youTube": "Ver en YouTube",
"preferences_save_player_pos_label": "Guardar posición de reproducción: ", "preferences_save_player_pos_label": "Guardar posición de reproducción: ",
"generic_views_count": "{{count}} visualización", "generic_views_count_0": "{{count}} visualización",
"generic_views_count_plural": "{{count}} visualizaciones", "generic_views_count_1": "{{count}} visualizaciones",
"generic_subscribers_count": "{{count}} suscriptor", "generic_views_count_2": "{{count}} visualizaciones",
"generic_subscribers_count_plural": "{{count}} suscriptores", "generic_subscribers_count_0": "{{count}} suscriptor",
"generic_subscriptions_count": "{{count}} suscripción", "generic_subscribers_count_1": "{{count}} suscriptores",
"generic_subscriptions_count_plural": "{{count}} suscripciones", "generic_subscribers_count_2": "{{count}} suscriptores",
"subscriptions_unseen_notifs_count": "{{count}} notificación no vista", "generic_subscriptions_count_0": "{{count}} suscripción",
"subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones no vistas", "generic_subscriptions_count_1": "{{count}} suscripciones",
"generic_count_days": "{{count}} día", "generic_subscriptions_count_2": "{{count}} suscripciones",
"generic_count_days_plural": "{{count}} días", "subscriptions_unseen_notifs_count_0": "{{count}} notificación sin ver",
"comments_view_x_replies": "Ver {{count}} respuesta", "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones sin ver",
"comments_view_x_replies_plural": "Ver {{count}} respuestas", "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones sin ver",
"generic_count_weeks": "{{count}} semana", "generic_count_days_0": "{{count}} día",
"generic_count_weeks_plural": "{{count}} semanas", "generic_count_days_1": "{{count}} días",
"generic_playlists_count": "{{count}} lista de reproducción", "generic_count_days_2": "{{count}} días",
"generic_playlists_count_plural": "{{count}} listas de reproducciones", "comments_view_x_replies_0": "Ver {{count}} respuesta",
"generic_videos_count": "{{count}} video", "comments_view_x_replies_1": "Ver {{count}} respuestas",
"generic_videos_count_plural": "{{count}} video", "comments_view_x_replies_2": "Ver {{count}} respuestas",
"generic_count_months": "{{count}} mes", "generic_count_weeks_0": "{{count}} semana",
"generic_count_months_plural": "{{count}} meses", "generic_count_weeks_1": "{{count}} semanas",
"comments_points_count": "{{count}} punto", "generic_count_weeks_2": "{{count}} semanas",
"comments_points_count_plural": "{{count}} puntos", "generic_playlists_count_0": "{{count}} lista de reproducción",
"generic_count_years": "{{count}} año", "generic_playlists_count_1": "{{count}} listas de reproducciones",
"generic_count_years_plural": "{{count}} años", "generic_playlists_count_2": "{{count}} listas de reproducciones",
"generic_count_hours": "{{count}} hora", "generic_videos_count_0": "{{count}} video",
"generic_count_hours_plural": "{{count}} horas", "generic_videos_count_1": "{{count}} videos",
"generic_count_minutes": "{{count}} minuto", "generic_videos_count_2": "{{count}} videos",
"generic_count_minutes_plural": "{{count}} minutos", "generic_count_months_0": "{{count}} mes",
"generic_count_seconds": "{{count}} segundo", "generic_count_months_1": "{{count}} meses",
"generic_count_seconds_plural": "{{count}} segundos", "generic_count_months_2": "{{count}} meses",
"comments_points_count_0": "{{count}} punto",
"comments_points_count_1": "{{count}} puntos",
"comments_points_count_2": "{{count}} puntos",
"generic_count_years_0": "{{count}} año",
"generic_count_years_1": "{{count}} años",
"generic_count_years_2": "{{count}} años",
"generic_count_hours_0": "{{count}} hora",
"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",
"crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:", "crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:",
"crash_page_switch_instance": "probado a <a href=\"`x`\">usar otra instancia</a>", "crash_page_switch_instance": "probado a <a href=\"`x`\">usar otra instancia</a>",
"crash_page_read_the_faq": "leído las <a href=\"`x`\">Preguntas Frecuentes</a>", "crash_page_read_the_faq": "leído las <a href=\"`x`\">Preguntas Frecuentes</a>",
"crash_page_search_issue": "buscado <a href=\"`x`\">problemas existentes en GitHub</a>", "crash_page_search_issue": "buscado <a href=\"`x`\">problemas existentes en GitHub</a>",
"crash_page_you_found_a_bug": "¡Parece que has encontrado un error en Invidious!", "crash_page_you_found_a_bug": "¡Parece que has encontrado un error en Invidious!",
"crash_page_refresh": "probado a <a href=\"`x`\">recargar la página</a>", "crash_page_refresh": "probado a <a href=\"`x`\">recargar la página</a>",
"crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye verbatim el siguiente texto en tu mensaje:", "crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye el siguiente texto en tu mensaje (NO traduzcas este texto):",
"English (United States)": "Inglés (Estados Unidos)", "English (United States)": "Inglés (Estados Unidos)",
"Cantonese (Hong Kong)": "Cantonés (Hong Kong)", "Cantonese (Hong Kong)": "Cantonés (Hong Kong)",
"Dutch (auto-generated)": "Neerlandés (generados automáticamente)", "Dutch (auto-generated)": "Neerlandés (generados automáticamente)",
@ -454,14 +469,15 @@
"search_message_no_results": "No se han encontrado resultados.", "search_message_no_results": "No se han encontrado resultados.",
"search_message_change_filters_or_query": "Pruebe ampliar la consulta de búsqueda y/o a cambiar los filtros.", "search_message_change_filters_or_query": "Pruebe ampliar la consulta de búsqueda y/o a cambiar los filtros.",
"search_filters_title": "Filtros", "search_filters_title": "Filtros",
"search_filters_date_label": "fecha de subida", "search_filters_date_label": "Fecha de subida",
"search_filters_date_option_none": "Cualquier fecha", "search_filters_date_option_none": "Cualquier fecha",
"search_filters_type_option_all": "Cualquier tipo", "search_filters_type_option_all": "Cualquier tipo",
"search_filters_duration_option_none": "Cualquier duración", "search_filters_duration_option_none": "Cualquier duración",
"search_filters_features_option_vr180": "VR180", "search_filters_features_option_vr180": "VR180",
"search_filters_apply_button": "Aplicar filtros", "search_filters_apply_button": "Aplicar filtros",
"tokens_count": "{{count}} token", "tokens_count_0": "{{count}} token",
"tokens_count_plural": "{{count}} tokens", "tokens_count_1": "{{count}} tokens",
"tokens_count_2": "{{count}} tokens",
"search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.", "search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.",
"Popular enabled: ": "¿Habilitar la sección popular? ", "Popular enabled: ": "¿Habilitar la sección popular? ",
"error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. <a href=\"`x`\">Haz clic aquí para acceder a la página de inicio de la lista de reproducción.</a>", "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. <a href=\"`x`\">Haz clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
@ -485,6 +501,17 @@
"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_0": "{{count}} canal",
"generic_channels_count_plural": "{{count}} canales" "generic_channels_count_1": "{{count}} canales",
"generic_channels_count_2": "{{count}} canales",
"Import YouTube watch history (.json)": "Importar el historial de las visualizaciones de YouTube (.json)",
"toggle_theme": "Alternar tema",
"Add to playlist: ": "Añadir a la lista de reproducción: ",
"Add to playlist": "Añadir a la lista de reproducción",
"Answer": "Respuesta",
"Search for videos": "Buscar por vídeos",
"The Popular feed has been disabled by the administrator.": "El feed Popular ha sido desactivado por el administrador.",
"carousel_slide": "Diapositiva {{current}} de {{total}}",
"carousel_skip": "Saltar el carrusel",
"carousel_go_to": "Ir a la diapositiva `x`"
} }

View file

@ -161,13 +161,13 @@
"Source available here.": "Iturburua hemen eskura.", "Source available here.": "Iturburua hemen eskura.",
"View JavaScript license information.": "JavaScriptaren lizentzi adierazpena ikusi.", "View JavaScript license information.": "JavaScriptaren lizentzi adierazpena ikusi.",
"Blacklisted regions: ": "zerrenda beltzaren zonaldeak: ", "Blacklisted regions: ": "zerrenda beltzaren zonaldeak: ",
"Premieres `x`": "'x' estrenaldiak", "Premieres `x`": "`x` estrenaldiak",
"Wrong answer": "Erantzun ez zuzena", "Wrong answer": "Erantzun ez zuzena",
"Password is a required field": "Pasahitza beharrezkoa da", "Password is a required field": "Pasahitza beharrezkoa da",
"Wrong username or password": "Pasahitza edo ezizena gaizki", "Wrong username or password": "Pasahitza edo ezizena gaizki",
"Password cannot be longer than 55 characters": "Pasahitza 55 karaktere baino luzeagoa ezin da izan", "Password cannot be longer than 55 characters": "Pasahitza 55 karaktere baino luzeagoa ezin da izan",
"This channel does not exist.": "Kanal hau ez dago.", "This channel does not exist.": "Kanal hau ez dago.",
"`x` ago": "duela 'x'", "`x` ago": "duela `x`",
"Czech": "Txekiera", "Czech": "Txekiera",
"preferences_region_label": "Herrialdeko edukiera: ", "preferences_region_label": "Herrialdeko edukiera: ",
"preferences_sort_label": "Bideoak ordenatu: ", "preferences_sort_label": "Bideoak ordenatu: ",
@ -207,24 +207,24 @@
"Public": "Orokorra", "Public": "Orokorra",
"Unlisted": "Ez zerrendatua", "Unlisted": "Ez zerrendatua",
"Subscription manager": "Harpidetzen kudeatzailea", "Subscription manager": "Harpidetzen kudeatzailea",
"Updated `x` ago": "Duela 'x' eguneratua", "Updated `x` ago": "Duela `x` eguneratua",
"Hide replies": "Erantzunak izkutatu", "Hide replies": "Erantzunak izkutatu",
"preferences_thin_mode_label": "Urri eran: ", "preferences_thin_mode_label": "Urri eran: ",
"Show replies": "Erantzunak erakutsi", "Show replies": "Erantzunak erakutsi",
"Watch on YouTube": "YouTuben ikusi", "Watch on YouTube": "YouTuben ikusi",
"Premieres in `x`": "'x'eko estrenaldiak", "Premieres in `x`": "`x`eko estrenaldiak",
"Delete playlist `x`?": "'x' zerrenda ezabatu nahi?", "Delete playlist `x`?": "`x` zerrenda ezabatu nahi?",
"Token is expired, please try again": "Token kadukatua, saiatu berriro", "Token is expired, please try again": "Token kadukatua, saiatu berriro",
"CAPTCHA enabled: ": "CAPTCHA gaitu: ", "CAPTCHA enabled: ": "CAPTCHA gaitu: ",
"Released under the AGPLv3 on Github.": "GitHubeko AGPLv3pean argitaratuta.", "Released under the AGPLv3 on Github.": "GitHubeko AGPLv3pean argitaratuta.",
"channel:`x`": "Kanal: 'x'", "channel:`x`": "Kanal: `x`",
"Georgian": "Georgiera", "Georgian": "Georgiera",
"Incorrect password": "Pasahitza gaizki", "Incorrect password": "Pasahitza gaizki",
"Playlist does not exist.": "Zerrenda ez da existitzen.", "Playlist does not exist.": "Zerrenda ez da existitzen.",
"preferences_category_misc": "Askotariko lehentasunak", "preferences_category_misc": "Askotariko lehentasunak",
"View `x` comments": { "View `x` comments": {
"([^.,0-9]|^)1([^.,0-9]|$)": "'x' iruzkina ikusi", "([^.,0-9]|^)1([^.,0-9]|$)": "`x` iruzkina ikusi",
"": "'x' iruzkinak ikusi" "": "`x` iruzkinak ikusi"
}, },
"Report statistics: ": "Estatistikak adierazi: ", "Report statistics: ": "Estatistikak adierazi: ",
"preferences_max_results_label": "Jotzeko bideo zerrendaren luzera: ", "preferences_max_results_label": "Jotzeko bideo zerrendaren luzera: ",
@ -237,7 +237,7 @@
"Hidden field \"challenge\" is a required field": "\"challenge\" eremu ezkutua beharrezkoa da", "Hidden field \"challenge\" is a required field": "\"challenge\" eremu ezkutua beharrezkoa da",
"German": "Alemaniarra", "German": "Alemaniarra",
"View YouTube comments": "YouTubeko iruzkinak ikusi", "View YouTube comments": "YouTubeko iruzkinak ikusi",
"`x` is live": "'x' bizirik darrai", "`x` is live": "`x` bizirik darrai",
"Password cannot be empty": "Pasahitza ezin da hutsik utzi", "Password cannot be empty": "Pasahitza ezin da hutsik utzi",
"preferences_video_loop_label": "Beti begiztatu: ", "preferences_video_loop_label": "Beti begiztatu: ",
"Only show latest unwatched video from channel: ": "kanalaren azken bideo ezikusia erakutsi soilik ", "Only show latest unwatched video from channel: ": "kanalaren azken bideo ezikusia erakutsi soilik ",
@ -261,9 +261,9 @@
"Hide annotations": "Oharrak izkutatu", "Hide annotations": "Oharrak izkutatu",
"Title": "Titulua", "Title": "Titulua",
"channel name": "Kanalaren izena", "channel name": "Kanalaren izena",
"Authorize token for `x`?": "Baimendu tokena 'x'tzako?", "Authorize token for `x`?": "Baimendu tokena `x`tzako?",
"Private": "Pribatua", "Private": "Pribatua",
"Editing playlist `x`": "'x' zerrenda editatu", "Editing playlist `x`": "`x` zerrenda editatu",
"Could not pull trending pages.": "Ezin ekarri orri arrakastatsuak.", "Could not pull trending pages.": "Ezin ekarri orri arrakastatsuak.",
"crash_page_read_the_faq": "Bide <a href=\"`x`\"> (FAQ) ohiko galderak</a>" "crash_page_read_the_faq": "Bide <a href=\"`x`\"> (FAQ) ohiko galderak</a>"
} }

View file

@ -1,9 +1,14 @@
{ {
"generic_views_count_0": "{{count}} بازدید", "generic_views_count": "{{count}} بازدید",
"generic_videos_count_0": "{{count}} ویدئو", "generic_views_count_plural": "{{count}} بازدید",
"generic_playlists_count_0": "{{count}} فهرست پخش", "generic_videos_count": "{{count}} ویدئو",
"generic_subscribers_count_0": "{{count}} دنبال کننده", "generic_videos_count_plural": "{{count}} ویدئو",
"generic_subscriptions_count_0": "{{count}} اشتراک ها", "generic_playlists_count": "{{count}} فهرست پخش",
"generic_playlists_count_plural": "{{count}} فهرست پخش",
"generic_subscribers_count": "{{count}} دنبال کننده",
"generic_subscribers_count_plural": "{{count}} دنبال کننده",
"generic_subscriptions_count": "{{count}} اشتراک",
"generic_subscriptions_count_plural": "{{count}} اشتراک",
"LIVE": "زنده", "LIVE": "زنده",
"Shared `x` ago": "`x` پیش به اشتراک گذاشته شده", "Shared `x` ago": "`x` پیش به اشتراک گذاشته شده",
"Unsubscribe": "لغو اشتراک", "Unsubscribe": "لغو اشتراک",
@ -117,13 +122,15 @@
"Subscription manager": "مدیریت اشتراک", "Subscription manager": "مدیریت اشتراک",
"Token manager": "مدیر توکن", "Token manager": "مدیر توکن",
"Token": "توکن", "Token": "توکن",
"tokens_count_0": "{{count}} توکن ها", "tokens_count": "{{count}} توکن",
"tokens_count_plural": "{{count}} توکن",
"Import/export": "وارد کردن/خارج کردن", "Import/export": "وارد کردن/خارج کردن",
"unsubscribe": "لغو اشتراک", "unsubscribe": "لغو اشتراک",
"revoke": "ابطال", "revoke": "ابطال",
"Subscriptions": "اشتراک ها", "Subscriptions": "اشتراک ها",
"subscriptions_unseen_notifs_count_0": "{{count}} اعلان نادیده", "subscriptions_unseen_notifs_count": "{{count}} اعلان نادیده",
"search": "جستجو", "subscriptions_unseen_notifs_count_plural": "{{count}} اعلان نادیده",
"search": "جست و جو",
"Log out": "خروج", "Log out": "خروج",
"Released under the AGPLv3 on Github.": "منتشر شده تحت پروانه AGPLv3 روی گیت‌هاب.", "Released under the AGPLv3 on Github.": "منتشر شده تحت پروانه AGPLv3 روی گیت‌هاب.",
"Source available here.": "منبع اینجا دردسترس است.", "Source available here.": "منبع اینجا دردسترس است.",
@ -183,10 +190,12 @@
"This channel does not exist.": "این کانال وجود ندارد.", "This channel does not exist.": "این کانال وجود ندارد.",
"Could not get channel info.": "نمیتوان اطلاعات کانال را دریافت کرد.", "Could not get channel info.": "نمیتوان اطلاعات کانال را دریافت کرد.",
"Could not fetch comments": "نمیتوان نظرات را دریافت کرد", "Could not fetch comments": "نمیتوان نظرات را دریافت کرد",
"comments_view_x_replies_0": "نمایش {{count}} پاسخ ها", "comments_view_x_replies": "نمایش {{count}} پاسخ",
"comments_view_x_replies_plural": "نمایش {{count}} پاسخ",
"`x` ago": "`x` پیش", "`x` ago": "`x` پیش",
"Load more": "بارگذاری بیشتر", "Load more": "بارگذاری بیشتر",
"comments_points_count_0": "{{count}} نقطه ها", "comments_points_count": "{{count}} نقطه",
"comments_points_count_plural": "{{count}} نقطه",
"Could not create mix.": "نمیتوان میکس ساخت.", "Could not create mix.": "نمیتوان میکس ساخت.",
"Empty playlist": "سیاههٔ پخش خالی", "Empty playlist": "سیاههٔ پخش خالی",
"Not a playlist.": "یک سیاههٔ پخش نیست.", "Not a playlist.": "یک سیاههٔ پخش نیست.",
@ -304,16 +313,23 @@
"Yiddish": "ییدیش", "Yiddish": "ییدیش",
"Yoruba": "یوروبایی", "Yoruba": "یوروبایی",
"Zulu": "زولو", "Zulu": "زولو",
"generic_count_years_0": "{{count}} سال", "generic_count_years": "{{count}} سال",
"generic_count_months_0": "{{count}} ماه", "generic_count_years_plural": "{{count}} سال",
"generic_count_weeks_0": "{{count}} هفته", "generic_count_months": "{{count}} ماه",
"generic_count_days_0": "{{count}} روز", "generic_count_months_plural": "{{count}} ماه",
"generic_count_hours_0": "{{count}} ساعت", "generic_count_weeks": "{{count}} هفته",
"generic_count_minutes_0": "{{count}} دقیقه", "generic_count_weeks_plural": "{{count}} هفته",
"generic_count_seconds_0": "{{count}} ثانیه", "generic_count_days": "{{count}} روز",
"generic_count_days_plural": "{{count}} روز",
"generic_count_hours": "{{count}} ساعت",
"generic_count_hours_plural": "{{count}} ساعت",
"generic_count_minutes": "{{count}} دقیقه",
"generic_count_minutes_plural": "{{count}} دقیقه",
"generic_count_seconds": "{{count}} ثانیه",
"generic_count_seconds_plural": "{{count}} ثانیه",
"Fallback comments: ": "نظرات عقب گرد: ", "Fallback comments: ": "نظرات عقب گرد: ",
"Popular": "محبوب", "Popular": "محبوب",
"Search": "جستجو", "Search": "جست و جو",
"Top": "بالا", "Top": "بالا",
"About": "درباره", "About": "درباره",
"Rating: ": "رتبه دهی: ", "Rating: ": "رتبه دهی: ",
@ -445,5 +461,28 @@
"Song: ": "آهنگ: ", "Song: ": "آهنگ: ",
"Channel Sponsor": "اسپانسر کانال", "Channel Sponsor": "اسپانسر کانال",
"Standard YouTube license": "پروانه استاندارد YouTube", "Standard YouTube license": "پروانه استاندارد YouTube",
"search_message_use_another_instance": " شما همچنین می‌توانید <a href=\"`x`\">در نمونه دیگر هم جستجو کنید</a>." "search_message_use_another_instance": " شما همچنین می‌توانید <a href=\"`x`\">در نمونه دیگر هم جستجو کنید</a>.",
"Download is disabled": "دریافت غیرفعال است",
"crash_page_before_reporting": "پیش از گزارش ایراد، مطمئنید شوید که:",
"playlist_button_add_items": "افزودن ویدیو",
"user_saved_playlists": "فهرست‌های پخش ذخیره شده",
"crash_page_refresh": "که صفحه را <a href=\"`x`\">بازنشانی</a> کرده‌اید",
"generic_button_save": "ذخیره",
"generic_button_cancel": "لغو",
"generic_channels_count": "{{count}} کانال",
"generic_channels_count_plural": "{{count}} کانال",
"generic_button_edit": "ویرایش",
"crash_page_switch_instance": "که تلاش کرده‌اید <a href=\"`x`\">از یک نمونهٔ دیگر</a> استفاده کنید",
"generic_button_rss": "خوراک RSS",
"crash_page_read_the_faq": "که <a href=\"`x`\">سوالات بیشتر پرسیده شده (FAQ)</a> را خوانده‌اید",
"generic_button_delete": "حذف",
"Import YouTube playlist (.csv)": "واردکردن فهرست‌پخش YouTube (.csv)",
"Import YouTube watch history (.json)": "وارد کردن فهرست پخش YouTube (.json)",
"crash_page_you_found_a_bug": "به نظر می‌رسد که ایرادی در Invidious پیدا کرده‌اید!",
"channel_tab_podcasts_label": "پادکست‌ها",
"channel_tab_streams_label": "پخش زنده‌ها",
"channel_tab_shorts_label": "Shortها",
"channel_tab_playlists_label": "فهرست‌های پخش",
"channel_tab_channels_label": "کانال‌ها",
"error_video_not_in_playlist": "ویدیوی درخواستی معلق به این فهرست پخش نیست. <a href=\"`x`\">کلیک کنید تا به صفحهٔ اصلی فهرست پخش بروید.</a>"
} }

View file

@ -14,7 +14,7 @@
"Clear watch history?": "Tyhjennä katseluhistoria?", "Clear watch history?": "Tyhjennä katseluhistoria?",
"New password": "Uusi salasana", "New password": "Uusi salasana",
"New passwords must match": "Uusien salasanojen täytyy täsmätä", "New passwords must match": "Uusien salasanojen täytyy täsmätä",
"Authorize token?": "Valuutetaanko tunnus?", "Authorize token?": "Valtuutetaanko tunnus?",
"Authorize token for `x`?": "Valtuutetaanko tunnus `x`:lle?", "Authorize token for `x`?": "Valtuutetaanko tunnus `x`:lle?",
"Yes": "Kyllä", "Yes": "Kyllä",
"No": "Ei", "No": "Ei",

View file

@ -503,5 +503,6 @@
"Download is disabled": "Le téléchargement est désactivé", "Download is disabled": "Le téléchargement est désactivé",
"Import YouTube playlist (.csv)": "Importer des listes de lecture de Youtube (.csv)", "Import YouTube playlist (.csv)": "Importer des listes de lecture de Youtube (.csv)",
"channel_tab_releases_label": "Parutions", "channel_tab_releases_label": "Parutions",
"channel_tab_podcasts_label": "Émissions audio" "channel_tab_podcasts_label": "Émissions audio",
"Import YouTube watch history (.json)": "Importer l'historique de visionnement YouTube (.json)"
} }

View file

@ -62,7 +62,7 @@
"Import and Export Data": "डेटा को आयात और निर्यात करें", "Import and Export Data": "डेटा को आयात और निर्यात करें",
"Import": "आयात करें", "Import": "आयात करें",
"Import Invidious data": "Invidious JSON डेटा आयात करें", "Import Invidious data": "Invidious JSON डेटा आयात करें",
"Import YouTube subscriptions": "YouTube/OPML सदस्यताएँ आयात करें", "Import YouTube subscriptions": "YouTube CSV या OPML सदस्यताएँ आयात करें",
"Import FreeTube subscriptions (.db)": "FreeTube सदस्यताएँ आयात करें (.db)", "Import FreeTube subscriptions (.db)": "FreeTube सदस्यताएँ आयात करें (.db)",
"Import NewPipe subscriptions (.json)": "NewPipe सदस्यताएँ आयात करें (.json)", "Import NewPipe subscriptions (.json)": "NewPipe सदस्यताएँ आयात करें (.json)",
"Import NewPipe data (.zip)": "NewPipe डेटा आयात करें (.zip)", "Import NewPipe data (.zip)": "NewPipe डेटा आयात करें (.zip)",
@ -476,7 +476,7 @@
"generic_button_cancel": "रद्द करें", "generic_button_cancel": "रद्द करें",
"generic_button_rss": "आरएसएस", "generic_button_rss": "आरएसएस",
"generic_button_edit": "संपादित करें", "generic_button_edit": "संपादित करें",
"generic_button_delete": "मिटाएं", "generic_button_delete": "टाएं",
"playlist_button_add_items": "वीडियो जोड़ें", "playlist_button_add_items": "वीडियो जोड़ें",
"Song: ": "गाना: ", "Song: ": "गाना: ",
"channel_tab_podcasts_label": "पाॅडकास्ट", "channel_tab_podcasts_label": "पाॅडकास्ट",
@ -484,5 +484,17 @@
"Import YouTube playlist (.csv)": "YouTube प्लेलिस्ट (.csv) आयात करें", "Import YouTube playlist (.csv)": "YouTube प्लेलिस्ट (.csv) आयात करें",
"Standard YouTube license": "मानक यूट्यूब लाइसेंस", "Standard YouTube license": "मानक यूट्यूब लाइसेंस",
"Channel Sponsor": "चैनल प्रायोजक", "Channel Sponsor": "चैनल प्रायोजक",
"Download is disabled": "डाउनलोड करना अक्षम है" "Download is disabled": "डाउनलोड करना अक्षम है",
"generic_channels_count": "{{count}} चैनल",
"generic_channels_count_plural": "{{count}} चैनल",
"Import YouTube watch history (.json)": "YouTube पर देखने का इतिहास आयात करें (.json)",
"Add to playlist": "प्लेलिस्ट में जोड़ें",
"Answer": "जवाब",
"The Popular feed has been disabled by the administrator.": "लोकप्रिय फ़ीड व्यवस्थापक द्वारा अक्षम कर दिया गया है।",
"toggle_theme": "थीम टॉगल करें",
"carousel_slide": "{{total}} में से स्लाइड {{current}}",
"carousel_skip": "कैरोसेल छोड़ें",
"Add to playlist: ": "प्लेलिस्ट में जोड़ें: ",
"Search for videos": "वीडियो खोजें",
"carousel_go_to": "स्लाइड `x` पर जाएँ"
} }

View file

@ -21,7 +21,7 @@
"Import and Export Data": "Uvezi i izvezi podatke", "Import and Export Data": "Uvezi i izvezi podatke",
"Import": "Uvezi", "Import": "Uvezi",
"Import Invidious data": "Uvezi Invidious JSON podatke", "Import Invidious data": "Uvezi Invidious JSON podatke",
"Import YouTube subscriptions": "Uvezi YouTube/OPML pretplate", "Import YouTube subscriptions": "Uvezi YouTube CSV ili OPML pretplate",
"Import FreeTube subscriptions (.db)": "Uvezi FreeTube pretplate (.db)", "Import FreeTube subscriptions (.db)": "Uvezi FreeTube pretplate (.db)",
"Import NewPipe subscriptions (.json)": "Uvezi NewPipe pretplate (.json)", "Import NewPipe subscriptions (.json)": "Uvezi NewPipe pretplate (.json)",
"Import NewPipe data (.zip)": "Uvezi NewPipe podatke (.zip)", "Import NewPipe data (.zip)": "Uvezi NewPipe podatke (.zip)",
@ -503,5 +503,15 @@
"channel_tab_releases_label": "Izdanja", "channel_tab_releases_label": "Izdanja",
"generic_channels_count_0": "{{count}} kanal", "generic_channels_count_0": "{{count}} kanal",
"generic_channels_count_1": "{{count}} kanala", "generic_channels_count_1": "{{count}} kanala",
"generic_channels_count_2": "{{count}} kanala" "generic_channels_count_2": "{{count}} kanala",
"Import YouTube watch history (.json)": "Uvezi YouTube povijest gledanja (.json)",
"Add to playlist": "Dodaj u zbirku",
"Add to playlist: ": "Dodaj u zbirku: ",
"Answer": "Odgovor",
"Search for videos": "Traži videa",
"The Popular feed has been disabled by the administrator.": "Popularni feed je administrator deaktivirao.",
"toggle_theme": "Uklj./Isklj. temu",
"carousel_slide": "Kadar {{current}} od {{total}}",
"carousel_go_to": "Idi na kadar `x`",
"carousel_skip": "Preskoči vrtuljak"
} }

45
locales/ia.json Normal file
View file

@ -0,0 +1,45 @@
{
"New password": "Nove contrasigno",
"preferences_player_style_label": "Stylo de reproductor: ",
"preferences_region_label": "Pais de contento: ",
"oldest": "plus ancian",
"published": "data de publication",
"invidious": "Invidious",
"Image CAPTCHA": "Imagine CAPTCHA",
"newest": "plus nove",
"generic_button_save": "Salvar",
"Dark mode: ": "Modo obscur: ",
"preferences_dark_mode_label": "Thema: ",
"preferences_category_subscription": "Preferentias de subscription",
"last": "ultime",
"generic_button_cancel": "Cancellar",
"popular": "popular",
"Time (h:mm:ss):": "Tempore (h:mm:ss):",
"preferences_autoplay_label": "Reproduction automatic: ",
"Sign In": "Aperir le session",
"Log in": "Initiar le session",
"preferences_speed_label": "Velocitate per predefinition: ",
"preferences_comments_label": "Commentos predefinite: ",
"light": "clar",
"No": "Non",
"youtube": "YouTube",
"LIVE": "IN DIRECTE",
"reddit": "Reddit",
"preferences_category_player": "Preferentias de reproductor",
"Preferences": "Preferentias",
"preferences_quality_dash_option_auto": "Automatic",
"dark": "obscur",
"generic_button_rss": "RSS",
"Export": "Exportar",
"History": "Chronologia",
"Password": "Contrasigno",
"User ID": "ID de usator",
"E-mail": "E-mail",
"Delete account?": "Deler conto?",
"preferences_volume_label": "Volumine del reproductor: ",
"preferences_sort_label": "Ordinar le videos per: ",
"Next page": "Pagina sequente",
"Previous page": "Pagina previe",
"Yes": "Si",
"Import": "Importar"
}

View file

@ -469,5 +469,6 @@
"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>", "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", "generic_button_delete": "Hapus",
"Import YouTube playlist (.csv)": "Impor daftar putar YouTube (.csv)", "Import YouTube playlist (.csv)": "Impor daftar putar YouTube (.csv)",
"Standard YouTube license": "Lisensi YouTube standar" "Standard YouTube license": "Lisensi YouTube standar",
"Import YouTube watch history (.json)": "Impor riwayat tontonan YouTube (.json)"
} }

View file

@ -503,5 +503,15 @@
"channel_tab_podcasts_label": "Podcast", "channel_tab_podcasts_label": "Podcast",
"generic_channels_count_0": "{{count}} canale", "generic_channels_count_0": "{{count}} canale",
"generic_channels_count_1": "{{count}} canali", "generic_channels_count_1": "{{count}} canali",
"generic_channels_count_2": "{{count}} canali" "generic_channels_count_2": "{{count}} canali",
"Import YouTube watch history (.json)": "Importa la cronologia delle visualizzazioni di YouTube (.json)",
"Answer": "Risposta",
"toggle_theme": "Cambia Tema",
"Add to playlist": "Aggiungi alla playlist",
"Add to playlist: ": "Aggiungi alla playlist ",
"Search for videos": "Cerca dei video",
"The Popular feed has been disabled by the administrator.": "La sezione dei contenuti popolari è stata disabilitata dall'amministratore.",
"carousel_slide": "Fotogramma {{current}} di {{total}}",
"carousel_skip": "Salta la galleria",
"carousel_go_to": "Vai al fotogramma `x`"
} }

View file

@ -53,7 +53,7 @@
"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": "動画視聴にプロキシを経由: ",
@ -68,7 +68,7 @@
"preferences_related_videos_label": "関連動画を表示: ", "preferences_related_videos_label": "関連動画を表示: ",
"preferences_annotations_label": "最初からアノテーションを表示: ", "preferences_annotations_label": "最初からアノテーションを表示: ",
"preferences_extend_desc_label": "動画の説明文を自動的に拡張: ", "preferences_extend_desc_label": "動画の説明文を自動的に拡張: ",
"preferences_vr_mode_label": "対話的な360°動画 (WebGL が必要): ", "preferences_vr_mode_label": "対話的な360°動画 (WebGLが必要): ",
"preferences_category_visual": "外観設定", "preferences_category_visual": "外観設定",
"preferences_player_style_label": "プレイヤーのスタイル: ", "preferences_player_style_label": "プレイヤーのスタイル: ",
"Dark mode: ": "ダークモード: ", "Dark mode: ": "ダークモード: ",
@ -125,9 +125,9 @@
"subscriptions_unseen_notifs_count_0": "{{count}}件の未読通知", "subscriptions_unseen_notifs_count_0": "{{count}}件の未読通知",
"search": "検索", "search": "検索",
"Log out": "ログアウト", "Log out": "ログアウト",
"Released under the AGPLv3 on Github.": "GitHub 上で AGPLv3 の元で公開", "Released under the AGPLv3 on Github.": "GitHub上でAGPLv3の元で公開",
"Source available here.": "ソースはここで閲覧可能です。", "Source available here.": "ソースはここで閲覧可能です。",
"View JavaScript license information.": "JavaScript ライセンス情報", "View JavaScript license information.": "JavaScriptライセンス情報",
"View privacy policy.": "個人情報保護方針", "View privacy policy.": "個人情報保護方針",
"Trending": "急上昇", "Trending": "急上昇",
"Public": "公開", "Public": "公開",
@ -144,7 +144,7 @@
"Show more": "もっと見る", "Show more": "もっと見る",
"Show less": "表示を少なく", "Show less": "表示を少なく",
"Watch on YouTube": "YouTubeで視聴", "Watch on YouTube": "YouTubeで視聴",
"Switch Invidious Instance": "Invidious インスタンスの変更", "Switch Invidious Instance": "Invidiousインスタンスの変更",
"Hide annotations": "アノテーションを隠す", "Hide annotations": "アノテーションを隠す",
"Show annotations": "アノテーションを表示", "Show annotations": "アノテーションを表示",
"Genre: ": "ジャンル: ", "Genre: ": "ジャンル: ",
@ -363,9 +363,9 @@
"search_filters_features_option_location": "場所", "search_filters_features_option_location": "場所",
"search_filters_features_option_hdr": "HDR", "search_filters_features_option_hdr": "HDR",
"Current version: ": "現在のバージョン: ", "Current version: ": "現在のバージョン: ",
"next_steps_error_message": "下記のものを試して下さい: ", "next_steps_error_message": "以下をお試してください: ",
"next_steps_error_message_refresh": "再読込", "next_steps_error_message_refresh": "再読",
"next_steps_error_message_go_to_youtube": "YouTube", "next_steps_error_message_go_to_youtube": "YouTubeを開く",
"search_filters_duration_option_short": "4分未満", "search_filters_duration_option_short": "4分未満",
"footer_documentation": "説明書", "footer_documentation": "説明書",
"footer_source_code": "ソースコード", "footer_source_code": "ソースコード",
@ -459,7 +459,7 @@
"Song: ": "曲: ", "Song: ": "曲: ",
"Channel Sponsor": "チャンネルのスポンサー", "Channel Sponsor": "チャンネルのスポンサー",
"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)",
"generic_button_delete": "削除", "generic_button_delete": "削除",
"generic_button_cancel": "キャンセル", "generic_button_cancel": "キャンセル",
@ -469,5 +469,15 @@
"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}}個のチャンネル" "generic_channels_count_0": "{{count}}個のチャンネル",
"Import YouTube watch history (.json)": "YouTube 視聴履歴をインポート (.json)",
"Add to playlist": "再生リストに追加",
"Add to playlist: ": "再生リストに追加: ",
"Answer": "回答",
"Search for videos": "動画を検索",
"The Popular feed has been disabled by the administrator.": "人気の動画のページは管理者によって無効にされています。",
"carousel_go_to": "スライド`x`を表示",
"carousel_slide": "スライド{{current}} / 全{{total}}個中",
"carousel_skip": "画像のスライド表示をスキップ",
"toggle_theme": "テーマの切り替え"
} }

View file

@ -46,7 +46,7 @@
"source": "출처", "source": "출처",
"JavaScript license information": "자바스크립트 라이선스 정보", "JavaScript license information": "자바스크립트 라이선스 정보",
"An alternative front-end to YouTube": "유튜브의 프론트엔드 대안", "An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
"History": "역사", "History": "시청 기록",
"Delete account?": "계정을 삭제 하시겠습니까?", "Delete account?": "계정을 삭제 하시겠습니까?",
"Export data as JSON": "JSON으로 데이터 내보내기", "Export data as JSON": "JSON으로 데이터 내보내기",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML로 구독 내보내기 (뉴파이프 및 프리튜브)", "Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML로 구독 내보내기 (뉴파이프 및 프리튜브)",
@ -351,7 +351,7 @@
"News": "뉴스", "News": "뉴스",
"Gaming": "게임", "Gaming": "게임",
"Music": "음악", "Music": "음악",
"Default": "디폴트", "Default": "전체",
"Rating: ": "평점: ", "Rating: ": "평점: ",
"About": "정보", "About": "정보",
"Top": "최고", "Top": "최고",
@ -460,7 +460,7 @@
"Music in this video": "동영상 속 음악", "Music in this video": "동영상 속 음악",
"Artist: ": "아티스트: ", "Artist: ": "아티스트: ",
"Download is disabled": "다운로드가 비활성화 되어있음", "Download is disabled": "다운로드가 비활성화 되어있음",
"Import YouTube playlist (.csv)": "유튜브 플레이리스트 가져오기 (.csv)", "Import YouTube playlist (.csv)": "유튜브 재생목록 가져오기 (.csv)",
"playlist_button_add_items": "동영상 추가", "playlist_button_add_items": "동영상 추가",
"channel_tab_podcasts_label": "팟캐스트", "channel_tab_podcasts_label": "팟캐스트",
"generic_button_delete": "삭제", "generic_button_delete": "삭제",
@ -468,6 +468,16 @@
"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}} 채널" "generic_channels_count_0": "{{count}} 채널",
"Import YouTube watch history (.json)": "유튜브 시청 기록 가져오기 (.json)",
"Add to playlist": "재생목록에 추가",
"Add to playlist: ": "재생목록에 추가: ",
"Answer": "답",
"The Popular feed has been disabled by the administrator.": "관리자가 인기 피드를 비활성화했습니다.",
"carousel_skip": "캐러셀 건너뛰기",
"carousel_go_to": "`x` 슬라이드로 이동",
"Search for videos": "비디오 검색",
"toggle_theme": "테마 전환",
"carousel_slide": "{{total}}의 슬라이드 {{current}}"
} }

232
locales/lmo.json Normal file
View file

@ -0,0 +1,232 @@
{
"Add to playlist": "Giont a la playlist",
"generic_button_edit": "Modifega",
"generic_button_save": "Salva",
"LIVE": "EN DÌRETT",
"Shared `x` ago": "Compartiss `x` fa",
"View channel on YouTube": "Varda el canal sul YouTube",
"newest": "plù nöeuf",
"oldest": "plù végh",
"Search for videos": "Càuta dei video",
"The Popular feed has been disabled by the administrator.": "la seziùn Pupular la è stada disabilidada par l'amministratòr.",
"generic_channels_count": "{{count}} canal",
"generic_channels_count_plural": "{{count}} canai",
"popular": "pupular",
"generic_views_count": "{{count}} visualisazión",
"generic_views_count_plural": "{{count}} visualisazióni",
"generic_videos_count": "{{count}} video",
"generic_videos_count_plural": "{{count}} video",
"generic_playlists_count": "{{count}} playlist",
"generic_playlists_count_plural": "{{count}} playlist",
"generic_subscriptions_count": "{{count}} inscrizion",
"generic_subscriptions_count_plural": "{{count}} inscrizioni",
"generic_button_cancel": "Cançéla",
"generic_button_delete": "Scassa via",
"Unsubscribe": "Disinscriviti",
"Next page": "Pagina siguènt",
"Previous page": "Pagina indrèe",
"Clear watch history?": "Cançélar la istoria dei video vardàa?",
"New password": "Nöeva password",
"Import and Export Data": "Importazion ed esportazion dei dat",
"Import": "Importa",
"Import Invidious data": "Importa i dat de l'Invidious en el formàt JSON",
"Import YouTube subscriptions": "Importa le inscrizioni dal YouTube/OPML",
"Import YouTube playlist (.csv)": "Importa le playlist dal YouTube (.csv)",
"Import YouTube watch history (.json)": "Importa la istoria de visualizazzion dal YouTube (.json)",
"Import FreeTube subscriptions (.db)": "Importa le inscrizioni dal FreeTube (.db)",
"Import NewPipe data (.zip)": "importa i dat del NewPipe (.zip)",
"Export": "Esporta",
"Export subscriptions as OPML": "Esporta inscrizioni com OPML",
"Export data as JSON": "Esporta i dat de l'Invidious com JSON",
"Delete account?": "Eliminà 'l profil?",
"History": "Istoria",
"An alternative front-end to YouTube": "Una interfacia alternatif al YouTube",
"JavaScript license information": "Informaziòn su la licensa JavaScript",
"source": "font",
"Log in": "Và dent",
"Text CAPTCHA": "Tèst del CAPTCHA",
"Image CAPTCHA": "Imàgen del CAPTCHA",
"Sign In": "Ven denter",
"Register": "Registres",
"E-mail": "E-mail",
"Preferences": "Priferenze",
"preferences_category_player": "Priferenze del riprodutòr",
"preferences_quality_option_dash": "DASH (qualità adatif)",
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_medium": "Media",
"preferences_quality_option_small": "Picinina",
"preferences_quality_dash_option_auto": "Auto",
"preferences_quality_dash_option_best": "Meglior",
"preferences_quality_dash_option_worst": "Peggior",
"preferences_quality_dash_option_4320p": "4320p",
"preferences_quality_dash_option_2160p": "2160p",
"preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_option_1080p": "1080p",
"preferences_quality_dash_option_720p": "720p",
"preferences_quality_dash_option_480p": "480p",
"preferences_quality_dash_option_360p": "360p",
"preferences_quality_dash_option_240p": "240p",
"preferences_quality_dash_option_144p": "144p",
"reddit": "Reddit",
"invidious": "Invidious",
"light": "ciar",
"dark": "scur",
"preferences_category_misc": "Priferenze varie",
"preferences_category_subscription": "Priferenze de le inscrizioni",
"published": "data de publicazion",
"published - reverse": "data de publicazion - invertì",
"alphabetically": "orden alfabetegh",
"channel name": "nòm del canal",
"channel name - reverse": "nòm del canal - invertì",
"Enable web notifications": "Empisa le notifeghe da la red",
"`x` uploaded a video": "`x` la ghàa cargà un video",
"`x` is live": "`x` l'è 'n dirétt adés",
"preferences_category_data": "Priferenze dei dat",
"Import/export data": "Importa/esporta i dat",
"Change password": "Cambia la parola ciav",
"Manage subscriptions": "Organisa le inscrizioni",
"Manage tokens": "Organisa i tokens",
"Watch history": "Istoria dei video vardà",
"Delete account": "Cançéla 'l profil",
"Save preferences": "Salva priferenze",
"Subscription manager": "Manegia le inscrizioni",
"Token": "Token",
"tokens_count": "{{count}} token",
"tokens_count_plural": "{{count}} token",
"Import/export": "Importa/esporta",
"unsubscribe": "disinscriviti",
"subscriptions_unseen_notifs_count": "{{count}} notifega mia visualisada",
"subscriptions_unseen_notifs_count_plural": "{{count}} notifeghe mia visualisade",
"Log out": "Sortiss",
"Released under the AGPLv3 on Github.": "Publicà en el GitHub suta licenza AGPLv3.",
"Source available here.": "Codegh de la font disponivel chì.",
"View privacy policy.": "Varda la pulitega de la privacy.",
"Trending": "De moda",
"Public": "Publico",
"Unlisted": "Non en lista",
"Private": "Privàt",
"View all playlists": "Varda tute le playlist",
"Updated `x` ago": "Giurnà `x` fa",
"Delete playlist `x`?": "Cançéla la playlist `x`?",
"Delete playlist": "Cançéla playlist",
"Create playlist": "Crea playlist",
"Title": "Titel",
"Playlist privacy": "Privacy de la playlist",
"Editing playlist `x`": "Modifega playlist `x`",
"playlist_button_add_items": "Gionta video",
"Show more": "Varda plù",
"Show less": "Varda mèn",
"Watch on YouTube": "Varda sul YouTube",
"Switch Invidious Instance": "Cambia la instanza del Invidious",
"search_message_no_results": "Non è stat truvà nigun resultat.",
"Cebuano": "Cebuano",
"Chinese (Traditional)": "Cines (Tradizional)",
"Corsican": "Còrso",
"Croatian": "Cruat",
"Georgian": "Georgian",
"Gujarati": "Gujarati",
"Hawaiian": "Hawaiian",
"Kurdish": "Curd",
"Latin": "Latin",
"Latvian": "Letton",
"Lithuanian": "Lituan",
"Malay": "Males",
"Maltese": "Maltes",
"Mongolian": "móngol",
"Persian": "Persian",
"Polish": "Polacch",
"Portuguese": "Portoghes",
"Romanian": "Romen",
"Scottish Gaelic": "Gaelich Scusses",
"Spanish (Latin America)": "Spagnöl (America do Sùd)",
"Thai": "Thai",
"Western Frisian": "Frisian do ponente",
"Basque": "Basco",
"Chinese (Simplified)": "Cines (Semplificà)",
"Haitian Creole": "Creolo de Haiti",
"Galician": "Galiçian",
"Hebrew": "Ebraich",
"Korean": "Corean",
"View playlist on YouTube": "Varda la playlist sul YouTube",
"Southern Sotho": "Sotho do Sùd",
"generic_button_rss": "RSS",
"Welsh": "Galés",
"Answer": "Resposta",
"New passwords must match": "Le nöeve password la deven esere uguai",
"Authorize token?": "Autorisà 'l token?",
"Authorize token for `x`?": "Autorisà 'l token par `x`?",
"Yes": "Sì",
"No": "No",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Esporta inscrizioni com OPML (par 'l NewPipe e 'l FreeTube)",
"Log in/register": "Va dent/Registres",
"User ID": "ID utent",
"Password": "Parola ciav",
"Time (h:mm:ss):": "Temp (h:mm:ss):",
"Import NewPipe subscriptions (.json)": "importa le inscrizioni dal NewPipe (.json)",
"youtube": "YouTube",
"alphabetically - reverse": "orden alfabetegh - invertì",
"preferences_category_visual": "Priferenze grafeghe",
"Clear watch history": "Scompartiss la istoria dei video vardà",
"preferences_category_admin": "Priferenze de l'amministratòr",
"Token manager": "Manegia i token",
"Subscriptions": "Inscrizioni",
"search": "cerca",
"View JavaScript license information.": "Varda le informazion su la licenza JavaScript.",
"search_message_change_filters_or_query": "Ti pödi pruà a slargà la reçerca e/or a cangià i filter.",
"generic_subscribers_count": "{{count}} inscritt",
"generic_subscribers_count_plural": "{{count}} inscriti",
"Subscribe": "Inscriviti",
"last": "ùltim",
"Add to playlist: ": "Giont a la playlist: ",
"preferences_autoplay_label": "Reproduzion automatega: ",
"preferences_continue_label": "Reproduzion seguént preimpostà: ",
"preferences_continue_autoplay_label": "Fa partì en automatico el video seguént: ",
"preferences_listen_label": "Modalità de sól audio preimpostà: ",
"preferences_local_label": "Proxy par i video: ",
"preferences_watch_history_label": "Ativà la istoria de reproduzion: ",
"preferences_speed_label": "Velocità preimpostà: ",
"preferences_volume_label": "Volume del reprodutòr: ",
"preferences_region_label": "Nazion del contenut: ",
"Dark mode: ": "Tema scur ",
"preferences_dark_mode_label": "Tema: ",
"preferences_thin_mode_label": "Modalità legera: ",
"preferences_automatic_instance_redirect_label": "Reindirizazzion automatega de la instansa (rivèrt a redirect.invidious.io): ",
"Hide annotations": "Piaca le notazioni",
"Show annotations": "Mostra le notazioni",
"Family friendly? ": "Adàt a tüti? ",
"Whitelisted regions: ": "Regioni en lista bianca: ",
"Blacklisted regions: ": "Regioni en lista negher ",
"Artist: ": "Artista: ",
"Song: ": "Cansòn ",
"Album: ": "Album: ",
"View YouTube comments": "Varda i comment dal YouTube",
"Password cannot be empty": "La parola ciav la no po miga esser voeut",
"channel:`x`": "Canal:`x`",
"Bangla": "Bengales",
"Hausa": "Hausa",
"Hindi": "Hindi",
"Hmong": "Hmong",
"Igbo": "Igbo",
"Javanese": "Javanese",
"Kannada": "Kannada",
"Kazakh": "Kazach",
"Khmer": "Khmer",
"Kyrgyz": "Kirghiz",
"Lao": "Lao",
"Luxembourgish": "Lussemburghes",
"Macedonian": "Macedon",
"Malagasy": "Malagascio",
"Malayalam": "Malayalam",
"Maori": "Maori",
"Marathi": "Marati",
"Nepali": "Nepales",
"Nyanja": "Nyanja",
"Pashto": "Pashtu",
"Punjabi": "Punjabi",
"Samoan": "Samoan",
"Standard YouTube license": "licensa predefinida de Youtube",
"License: ": "Licensa: ",
"Music in this video": "Musica en sto video",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Ué! Sembra che ti la g'hà desabilitàa el JavaScript. Schisa chì para vardà i comment, ma cunsidera che peul vörse 'n po plu de temp a cargà.",
"preferences_video_loop_label": "Reproduci sèmper: "
}

View file

@ -486,5 +486,6 @@
"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": "{{count}} kanal",
"generic_channels_count_plural": "{{count}} kanaler" "generic_channels_count_plural": "{{count}} kanaler",
"Import YouTube watch history (.json)": "Importere YouTube visningshistorikk (.json)"
} }

View file

@ -107,10 +107,10 @@
"Report statistics: ": "Statistieken bijhouden? ", "Report statistics: ": "Statistieken bijhouden? ",
"Save preferences": "Instellingen opslaan", "Save preferences": "Instellingen opslaan",
"Subscription manager": "Abonnementen beheren", "Subscription manager": "Abonnementen beheren",
"Token manager": "Toegangssleutels beheren", "Token manager": "Toegangssleutelbeheerder",
"Token": "Toegangssleutel", "Token": "Toegangssleutel",
"Import/export": "Importeren/Exporteren", "Import/export": "Importeren/Exporteren",
"unsubscribe": "Deabonneren", "unsubscribe": "deabonneren",
"revoke": "Intrekken", "revoke": "Intrekken",
"Subscriptions": "Abonnementen", "Subscriptions": "Abonnementen",
"search": "zoeken", "search": "zoeken",
@ -357,7 +357,7 @@
"footer_original_source_code": "Originele bron-code", "footer_original_source_code": "Originele bron-code",
"footer_modfied_source_code": "Gewijzigde bron-code", "footer_modfied_source_code": "Gewijzigde bron-code",
"adminprefs_modified_source_code_url_label": "URL naar gewijzigde bron-code-opslagplaats", "adminprefs_modified_source_code_url_label": "URL naar gewijzigde bron-code-opslagplaats",
"next_steps_error_message": "Waarna u moet proberen om: ", "next_steps_error_message": "Daarna moet u proberen om: ",
"footer_source_code": "Bron-code", "footer_source_code": "Bron-code",
"search_filters_duration_option_long": "Lang (> 20 minuten)", "search_filters_duration_option_long": "Lang (> 20 minuten)",
"preferences_quality_option_dash": "DASH (adaptieve kwaliteit)", "preferences_quality_option_dash": "DASH (adaptieve kwaliteit)",
@ -462,5 +462,39 @@
"Spanish (auto-generated)": "Spaans (automatisch gegenereerd)", "Spanish (auto-generated)": "Spaans (automatisch gegenereerd)",
"crash_page_you_found_a_bug": "Je lijkt een bug in Invidious tegengekomen te zijn!", "crash_page_you_found_a_bug": "Je lijkt een bug in Invidious tegengekomen te zijn!",
"search_filters_duration_option_medium": "Gemiddeld (4 - 20 minuten)", "search_filters_duration_option_medium": "Gemiddeld (4 - 20 minuten)",
"crash_page_report_issue": "Indien het bovenstaande niet hielp, gelieve dan <a href=\"`x`\">een nieuw ticket op GitHub</a> te openen (liefst in het Engels) en neem de volgende tekst op in je bericht (gelieve deze NIET te vertalen):" "crash_page_report_issue": "Indien het bovenstaande niet hielp, gelieve dan <a href=\"`x`\">een nieuw ticket op GitHub</a> te openen (liefst in het Engels) en neem de volgende tekst op in je bericht (gelieve deze NIET te vertalen):",
"channel_tab_podcasts_label": "Podcasts",
"Download is disabled": "Downloaden is uitgeschakeld",
"Channel Sponsor": "Kanaalsponsor",
"channel_tab_streams_label": "Livestreams",
"playlist_button_add_items": "Video's toevoegen",
"Artist: ": "Artiest: ",
"generic_button_save": "Opslaan",
"generic_button_cancel": "Annuleren",
"Album: ": "Album: ",
"channel_tab_shorts_label": "Shorts",
"channel_tab_releases_label": "Uitgaves",
"Song: ": "Lied: ",
"generic_channels_count": "{{count}} kanaal",
"generic_channels_count_plural": "{{count}} kanalen",
"Popular enabled: ": "Populair geactiveerd: ",
"channel_tab_playlists_label": "Afspeellijsten",
"generic_button_edit": "Bewerken",
"Music in this video": "Muziek in deze video",
"generic_button_rss": "RSS",
"channel_tab_channels_label": "Kanalen",
"error_video_not_in_playlist": "De gevraagde video bestaat niet in deze afspeellijst. <a href=\"`x`\">Klik hier voor de startpagina van de afspeellijst.</a>",
"generic_button_delete": "Verwijderen",
"Import YouTube playlist (.csv)": "YouTube-afspeellijst importeren (.csv)",
"Standard YouTube license": "Standaard YouTube-licentie",
"Import YouTube watch history (.json)": "YouTube-kijkgeschiedenis importeren (.json)",
"Add to playlist": "Aan afspeellijst toevoegen",
"The Popular feed has been disabled by the administrator.": "De Populaire feed werd uitgeschakeld door een beheerder.",
"carousel_slide": "Dia {{current}} van {{total}}",
"carousel_go_to": "Naar dia `x` gaan",
"Add to playlist: ": "Aan afspeellijst toevoegen: ",
"Answer": "Antwoorden",
"Search for videos": "Naar video's zoeken",
"carousel_skip": "Carousel overslaan",
"toggle_theme": "Thema omschakelen"
} }

View file

@ -21,13 +21,13 @@
"Import and Export Data": "Import i eksport danych", "Import and Export Data": "Import i eksport danych",
"Import": "Import", "Import": "Import",
"Import Invidious data": "Importuj dane JSON Invidious", "Import Invidious data": "Importuj dane JSON Invidious",
"Import YouTube subscriptions": "Importuj subskrybcje z YouTube/OPML", "Import YouTube subscriptions": "Importuj subskrypcje YouTube w formacie CSV lub OPML",
"Import FreeTube subscriptions (.db)": "Importuj subskrybcje z FreeTube (.db)", "Import FreeTube subscriptions (.db)": "Importuj subskrypcje FreeTube (.db)",
"Import NewPipe subscriptions (.json)": "Importuj subskrybcje z NewPipe (.json)", "Import NewPipe subscriptions (.json)": "Importuj subskrypcje NewPipe (.json)",
"Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)", "Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)",
"Export": "Eksport", "Export": "Eksport",
"Export subscriptions as OPML": "Eksportuj subskrybcje jako OPML", "Export subscriptions as OPML": "Eksportuj subskrypcje jako OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrybcje jako OPML (dla NewPipe i FreeTube)", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrypcje jako OPML (dla NewPipe i FreeTube)",
"Export data as JSON": "Eksportuj dane Invidious jako JSON", "Export data as JSON": "Eksportuj dane Invidious jako JSON",
"Delete account?": "Usunąć konto?", "Delete account?": "Usunąć konto?",
"History": "Historia", "History": "Historia",
@ -73,7 +73,7 @@
"preferences_thin_mode_label": "Tryb minimalny: ", "preferences_thin_mode_label": "Tryb minimalny: ",
"preferences_category_misc": "Różne preferencje", "preferences_category_misc": "Różne preferencje",
"preferences_automatic_instance_redirect_label": "Automatycznie przekierowanie instancji (powrót do redirect.invidious.io): ", "preferences_automatic_instance_redirect_label": "Automatycznie przekierowanie instancji (powrót do redirect.invidious.io): ",
"preferences_category_subscription": "Preferencje subskrybcji", "preferences_category_subscription": "Preferencje subskrypcji",
"preferences_annotations_subscribed_label": "Domyślnie wyświetlaj adnotacje dla subskrybowanych kanałów: ", "preferences_annotations_subscribed_label": "Domyślnie wyświetlaj adnotacje dla subskrybowanych kanałów: ",
"Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ", "Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ",
"preferences_max_results_label": "Liczba filmów widoczna na stronie subskrybcji: ", "preferences_max_results_label": "Liczba filmów widoczna na stronie subskrybcji: ",
@ -95,7 +95,7 @@
"Clear watch history": "Wyczyść historię", "Clear watch history": "Wyczyść historię",
"Import/export data": "Import/Eksport danych", "Import/export data": "Import/Eksport danych",
"Change password": "Zmień hasło", "Change password": "Zmień hasło",
"Manage subscriptions": "Organizuj subskrybcje", "Manage subscriptions": "Organizuj subskrypcje",
"Manage tokens": "Zarządzaj tokenami", "Manage tokens": "Zarządzaj tokenami",
"Watch history": "Historia", "Watch history": "Historia",
"Delete account": "Usuń konto", "Delete account": "Usuń konto",
@ -115,7 +115,7 @@
"Import/export": "Import/Eksport", "Import/export": "Import/Eksport",
"unsubscribe": "odsubskrybuj", "unsubscribe": "odsubskrybuj",
"revoke": "cofnij", "revoke": "cofnij",
"Subscriptions": "Subskrybcje", "Subscriptions": "Subskrypcje",
"search": "szukaj", "search": "szukaj",
"Log out": "Wyloguj", "Log out": "Wyloguj",
"Source available here.": "Kod źródłowy dostępny tutaj.", "Source available here.": "Kod źródłowy dostępny tutaj.",
@ -492,7 +492,7 @@
"Song: ": "Piosenka: ", "Song: ": "Piosenka: ",
"Channel Sponsor": "Sponsor kanału", "Channel Sponsor": "Sponsor kanału",
"Standard YouTube license": "Standardowa licencja YouTube", "Standard YouTube license": "Standardowa licencja YouTube",
"Import YouTube playlist (.csv)": "Importuj playlistę YouTube (.csv)", "Import YouTube playlist (.csv)": "Importuj playlistę z YouTube (.csv)",
"generic_button_edit": "Edytuj", "generic_button_edit": "Edytuj",
"generic_button_cancel": "Anuluj", "generic_button_cancel": "Anuluj",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
@ -503,5 +503,15 @@
"playlist_button_add_items": "Dodaj filmy", "playlist_button_add_items": "Dodaj filmy",
"generic_channels_count_0": "{{count}} kanał", "generic_channels_count_0": "{{count}} kanał",
"generic_channels_count_1": "{{count}} kanały", "generic_channels_count_1": "{{count}} kanały",
"generic_channels_count_2": "{{count}} kanałów" "generic_channels_count_2": "{{count}} kanałów",
"Import YouTube watch history (.json)": "Importuj historię oglądania z YouTube (.json)",
"toggle_theme": "Przełącz motyw",
"The Popular feed has been disabled by the administrator.": "Kanał Popularne został wyłączony przez administratora.",
"Answer": "Odpowiedź",
"Search for videos": "Wyszukaj filmy",
"Add to playlist": "Dodaj do playlisty",
"Add to playlist: ": "Dodaj do playlisty: ",
"carousel_slide": "Slajd {{current}} z {{total}}",
"carousel_skip": "Pomiń karuzelę",
"carousel_go_to": "Przejdź do slajdu `x`"
} }

View file

@ -1,27 +1,27 @@
{ {
"LIVE": "AO VIVO", "LIVE": "AO VIVO",
"Shared `x` ago": "Compartilhado `x` atrás", "Shared `x` ago": "Publicado há `x`",
"Unsubscribe": "Cancelar inscrição", "Unsubscribe": "Cancelar inscrição",
"Subscribe": "Inscrever-se", "Subscribe": "Inscrever-se",
"View channel on YouTube": "Ver canal no YouTube", "View channel on YouTube": "Ver canal no YouTube",
"View playlist on YouTube": "Ver lista de reprodução no YouTube", "View playlist on YouTube": "Ver playlist no YouTube",
"newest": "mais recentes", "newest": "mais recentes",
"oldest": "mais antigos", "oldest": "mais antigos",
"popular": "populares", "popular": "populares",
"last": "último", "last": "últimos",
"Next page": "Próxima página", "Next page": "Próxima página",
"Previous page": "Página anterior", "Previous page": "Página anterior",
"Clear watch history?": "Limpar histórico de reprodução?", "Clear watch history?": "Limpar histórico de exibição?",
"New password": "Nova senha", "New password": "Nova senha",
"New passwords must match": "Nova senha deve ser igual", "New passwords must match": "As senhas devem ser iguais",
"Authorize token?": "Autorizar o token?", "Authorize token?": "Autorizar token?",
"Authorize token for `x`?": "Autorizar o token para `x`?", "Authorize token for `x`?": "Autorizar token para `x`?",
"Yes": "Sim", "Yes": "Sim",
"No": "Não", "No": "Não",
"Import and Export Data": "Importar e Exportar Dados", "Import and Export Data": "Importar/exportar dados",
"Import": "Importar", "Import": "Importar",
"Import Invidious data": "Importar dados em JSON do Invidious", "Import Invidious data": "Importar dados JSON do Invidious",
"Import YouTube subscriptions": "Importar inscrições do YouTube/OPML", "Import YouTube subscriptions": "Importar inscrições no formato CSV ou OPML do YouTube",
"Import FreeTube subscriptions (.db)": "Importar inscrições do FreeTube (.db)", "Import FreeTube subscriptions (.db)": "Importar inscrições do FreeTube (.db)",
"Import NewPipe subscriptions (.json)": "Importar inscrições do NewPipe (.json)", "Import NewPipe subscriptions (.json)": "Importar inscrições do NewPipe (.json)",
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)", "Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
@ -32,49 +32,49 @@
"Delete account?": "Excluir conta?", "Delete account?": "Excluir conta?",
"History": "Histórico", "History": "Histórico",
"An alternative front-end to YouTube": "Uma interface alternativa para o YouTube", "An alternative front-end to YouTube": "Uma interface alternativa para o YouTube",
"JavaScript license information": "Informação de licença do JavaScript", "JavaScript license information": "Informações sobre a licença do JavaScript",
"source": "código-fonte", "source": "fonte",
"Log in": "Entrar", "Log in": "Fazer login",
"Log in/register": "Entrar/Registrar", "Log in/register": "Fazer login/criar conta",
"User ID": "Usuário", "User ID": "Usuário",
"Password": "Senha", "Password": "Senha",
"Time (h:mm:ss):": "Hora (h:mm:ss):", "Time (h:mm:ss):": "Hora (h:mm:ss):",
"Text CAPTCHA": "CAPTCHA em texto", "Text CAPTCHA": "Mudar para um desafio de texto",
"Image CAPTCHA": "CAPTCHA em imagem", "Image CAPTCHA": "Mudar para um desafio visual",
"Sign In": "Entrar", "Sign In": "Entrar",
"Register": "Registrar", "Register": "Criar conta",
"E-mail": "E-mail", "E-mail": "E-mail",
"Preferences": "Preferências", "Preferences": "Preferências",
"preferences_category_player": "Preferências do reprodutor", "preferences_category_player": "Preferências de reprodução",
"preferences_video_loop_label": "Repetir sempre: ", "preferences_video_loop_label": "Repetir sempre: ",
"preferences_autoplay_label": "Reprodução automática: ", "preferences_autoplay_label": "Reprodução automática: ",
"preferences_continue_label": "Sempre reproduzir próximo: ", "preferences_continue_label": "Reproduzir a seguir, por padrão: ",
"preferences_continue_autoplay_label": "Reproduzir próximo vídeo automaticamente: ", "preferences_continue_autoplay_label": "Reproduzir próximo vídeo automaticamente: ",
"preferences_listen_label": "Apenas áudio por padrão: ", "preferences_listen_label": "Apenas áudio por padrão: ",
"preferences_local_label": "Usar proxy nos vídeos: ", "preferences_local_label": "Usar proxy nos vídeos: ",
"preferences_speed_label": "Velocidade padrão: ", "preferences_speed_label": "Velocidade padrão: ",
"preferences_quality_label": "Qualidade de vídeo preferida: ", "preferences_quality_label": "Qualidade de vídeo preferida: ",
"preferences_volume_label": "Volume de reprodução: ", "preferences_volume_label": "Volume de reprodução: ",
"preferences_comments_label": "Preferência de comentários: ", "preferences_comments_label": "Comentários padrão: ",
"youtube": "YouTube", "youtube": "YouTube",
"reddit": "Reddit", "reddit": "Reddit",
"preferences_captions_label": "Preferência de legendas: ", "preferences_captions_label": "Legendas padrão: ",
"Fallback captions: ": "Legendas alternativas: ", "Fallback captions: ": "Legendas alternativas: ",
"preferences_related_videos_label": "Mostrar vídeos relacionados: ", "preferences_related_videos_label": "Mostrar vídeos relacionados: ",
"preferences_annotations_label": "Sempre mostrar anotações: ", "preferences_annotations_label": "Sempre mostrar anotações: ",
"preferences_extend_desc_label": "Estenda automaticamente a descrição do vídeo: ", "preferences_extend_desc_label": "Expandir automaticamente a descrição do vídeo: ",
"preferences_vr_mode_label": "Vídeos interativos de 360 graus (requer WebGL): ", "preferences_vr_mode_label": "Vídeos interativos de 360 graus (requer WebGL): ",
"preferences_category_visual": "Preferências visuais", "preferences_category_visual": "Preferências visuais",
"preferences_player_style_label": "Estilo do tocador: ", "preferences_player_style_label": "Estilo de reprodução: ",
"Dark mode: ": "Modo escuro: ", "Dark mode: ": "Modo escuro: ",
"preferences_dark_mode_label": "Tema: ", "preferences_dark_mode_label": "Tema: ",
"dark": "escuro", "dark": "escuro",
"light": "claro", "light": "claro",
"preferences_thin_mode_label": "Modo compacto: ", "preferences_thin_mode_label": "Modo compacto: ",
"preferences_category_misc": "Preferências diversas", "preferences_category_misc": "Preferências diversas",
"preferences_automatic_instance_redirect_label": "Redirecionamento de instância automática (fallback para redirect.invidious.io): ", "preferences_automatic_instance_redirect_label": "Redirecionamento automático de instâncias (alternativa para redirect.invidious.io): ",
"preferences_category_subscription": "Preferências de inscrições", "preferences_category_subscription": "Preferências de inscrições",
"preferences_annotations_subscribed_label": "Sempre mostrar anotações dos vídeos de canais inscritos: ", "preferences_annotations_subscribed_label": "Mostrar anotações por padrão para canais inscritos? ",
"Redirect homepage to feed: ": "Redirecionar página inicial para o feed: ", "Redirect homepage to feed: ": "Redirecionar página inicial para o feed: ",
"preferences_max_results_label": "Número de vídeos no feed: ", "preferences_max_results_label": "Número de vídeos no feed: ",
"preferences_sort_label": "Ordenar vídeos por: ", "preferences_sort_label": "Ordenar vídeos por: ",
@ -84,30 +84,30 @@
"alphabetically - reverse": "alfabética - ordem inversa", "alphabetically - reverse": "alfabética - ordem inversa",
"channel name": "nome do canal", "channel name": "nome do canal",
"channel name - reverse": "nome do canal - ordem inversa", "channel name - reverse": "nome do canal - ordem inversa",
"Only show latest video from channel: ": "Mostrar apenas o vídeo mais recente do canal: ", "Only show latest video from channel: ": "Mostrar apenas vídeos mais recentes do canal: ",
"Only show latest unwatched video from channel: ": "Mostrar apenas o vídeo mais recente não visualizado do canal: ", "Only show latest unwatched video from channel: ": "Mostrar apenas vídeos mais recentes não assistido do canal: ",
"preferences_unseen_only_label": "Mostrar apenas vídeos não visualizados: ", "preferences_unseen_only_label": "Mostrar apenas vídeos não assistido: ",
"preferences_notifications_only_label": "Mostrar apenas notificações (se existentes): ", "preferences_notifications_only_label": "Mostrar apenas notificações (se houver): ",
"Enable web notifications": "Ativar notificações pela web", "Enable web notifications": "Ativar notificações da Web",
"`x` uploaded a video": "`x` publicou um novo vídeo", "`x` uploaded a video": "`x` publicou um vídeo",
"`x` is live": "`x` está ao vivo", "`x` is live": "`x` está ao vivo",
"preferences_category_data": "Preferências de dados", "preferences_category_data": "Preferências de dados",
"Clear watch history": "Limpar histórico de reprodução", "Clear watch history": "Limpar histórico de exibição",
"Import/export data": "Importar/Exportar dados", "Import/export data": "Importar/exportar dados",
"Change password": "Alterar senha", "Change password": "Alterar senha",
"Manage subscriptions": "Gerenciar inscrições", "Manage subscriptions": "Gerenciar inscrições",
"Manage tokens": "Gerenciar tokens", "Manage tokens": "Gerenciar tokens",
"Watch history": "Histórico de reprodução", "Watch history": "Histórico de exibição",
"Delete account": "Apagar sua conta", "Delete account": "Excluir conta",
"preferences_category_admin": "Preferências de administrador", "preferences_category_admin": "Preferências de administrador",
"preferences_default_home_label": "Página de início padrão: ", "preferences_default_home_label": "Página inicial padrão: ",
"preferences_feed_menu_label": "Menu do feed: ", "preferences_feed_menu_label": "Guias de feed preferidos: ",
"preferences_show_nick_label": "Mostrar o nickname no topo: ", "preferences_show_nick_label": "Mostrar nome de usuário na parte superior: ",
"Top enabled: ": "Habilitar destaques: ", "Top enabled: ": "Destaques ativados: ",
"CAPTCHA enabled: ": "Habilitar CAPTCHA: ", "CAPTCHA enabled: ": "CAPTCHA ativado: ",
"Login enabled: ": "Habilitar login: ", "Login enabled: ": "Fazer login ativado: ",
"Registration enabled: ": "Habilitar registro: ", "Registration enabled: ": "Criar conta ativado: ",
"Report statistics: ": "Habilitar estatísticas: ", "Report statistics: ": "Relatório de estatísticas: ",
"Save preferences": "Salvar preferências", "Save preferences": "Salvar preferências",
"Subscription manager": "Gerenciador de inscrições", "Subscription manager": "Gerenciador de inscrições",
"Token manager": "Gerenciador de tokens", "Token manager": "Gerenciador de tokens",
@ -115,24 +115,24 @@
"tokens_count_0": "{{count}} token", "tokens_count_0": "{{count}} token",
"tokens_count_1": "{{count}} tokens", "tokens_count_1": "{{count}} tokens",
"tokens_count_2": "{{count}} tokens", "tokens_count_2": "{{count}} tokens",
"Import/export": "Importar/Exportar", "Import/export": "Importar/exportar",
"unsubscribe": "cancelar inscrição", "unsubscribe": "cancelar inscrição",
"revoke": "revogar", "revoke": "revogar",
"Subscriptions": "Inscrições", "Subscriptions": "Inscrições",
"search": "Pesquisar", "search": "pesquisar",
"Log out": "Sair", "Log out": "Sair",
"Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no GitHub.", "Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no GitHub.",
"Source available here.": "Código-fonte disponível aqui.", "Source available here.": "Código-fonte disponível aqui.",
"View JavaScript license information.": "Ver informações da licença do JavaScript.", "View JavaScript license information.": "Informações de licença JavaScript.",
"View privacy policy.": "Ver a política de privacidade.", "View privacy policy.": "Política de privacidade.",
"Trending": "Tendências", "Trending": "Em alta",
"Public": "Público", "Public": "Público",
"Unlisted": "Não listado", "Unlisted": "Não listado",
"Private": "Privado", "Private": "Privado",
"View all playlists": "Mostrar todas listas de reprodução", "View all playlists": "Ver todas as playlists",
"Updated `x` ago": "Atualizado `x` atrás", "Updated `x` ago": "Atualizado `x` atrás",
"Delete playlist `x`?": "Apagar a playlist `x`?", "Delete playlist `x`?": "Excluir playlist `x`?",
"Delete playlist": "Apagar playlist", "Delete playlist": "Excluir playlist",
"Create playlist": "Criar playlist", "Create playlist": "Criar playlist",
"Title": "Título", "Title": "Título",
"Playlist privacy": "Privacidade da playlist", "Playlist privacy": "Privacidade da playlist",
@ -140,24 +140,24 @@
"Show more": "Mostrar mais", "Show more": "Mostrar mais",
"Show less": "Mostrar menos", "Show less": "Mostrar menos",
"Watch on YouTube": "Assistir no YouTube", "Watch on YouTube": "Assistir no YouTube",
"Switch Invidious Instance": "Mudar a instância do Invidious", "Switch Invidious Instance": "Alterar instância Invidious",
"Hide annotations": "Ocultar anotações", "Hide annotations": "Ocultar anotações",
"Show annotations": "Mostrar anotações", "Show annotations": "Mostrar anotações",
"Genre: ": "Gênero: ", "Genre: ": "Gênero: ",
"License: ": "Licença: ", "License: ": "Licença: ",
"Family friendly? ": "Filtrar conteúdo impróprio: ", "Family friendly? ": "Filtrar conteúdo impróprio: ",
"Wilson score: ": "Pontuação de Wilson: ", "Wilson score: ": "Pontuação de Wilson: ",
"Engagement: ": "Empenho: ", "Engagement: ": "Engajamento: ",
"Whitelisted regions: ": "Regiões permitidas: ", "Whitelisted regions: ": "Regiões permitidas: ",
"Blacklisted regions: ": "Regiões bloqueadas: ", "Blacklisted regions: ": "Regiões bloqueadas: ",
"Shared `x`": "Compartilhado `x`", "Shared `x`": "Publicado em `x`",
"Premieres in `x`": "Estreia em `x`", "Premieres in `x`": "Estreia em `x`",
"Premieres `x`": "Estreia `x`", "Premieres `x`": "Estreia `x`",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Oi! Parece que seu JavaScript está desativado. Clique aqui para ver os comentários, entretanto eles podem levar um pouco mais de tempo para carregar.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Olá! Parece que você está com o JavaScript desativado. Clique aqui para ver os comentários, mas lembre-se de que eles podem demorar um pouco mais para carregar.",
"View YouTube comments": "Ver comentários no YouTube", "View YouTube comments": "Ver comentários no YouTube",
"View more comments on Reddit": "Ver mais comentários no Reddit", "View more comments on Reddit": "Ver mais comentários no Reddit",
"View `x` comments": { "View `x` comments": {
"([^.,0-9]|^)1([^.,0-9]|$)": "Ver `x` comentários", "([^.,0-9]|^)1([^.,0-9]|$)": "Ver `x` comentário",
"": "Ver `x` comentários" "": "Ver `x` comentários"
}, },
"View Reddit comments": "Ver comentários no Reddit", "View Reddit comments": "Ver comentários no Reddit",
@ -166,7 +166,7 @@
"Incorrect password": "Senha incorreta", "Incorrect password": "Senha incorreta",
"Wrong answer": "Resposta incorreta", "Wrong answer": "Resposta incorreta",
"Erroneous CAPTCHA": "CAPTCHA inválido", "Erroneous CAPTCHA": "CAPTCHA inválido",
"CAPTCHA is a required field": "O CAPTCHA é um campo obrigatório", "CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
"User ID is a required field": "O nome de usuário é um campo obrigatório", "User ID is a required field": "O nome de usuário é um campo obrigatório",
"Password is a required field": "A senha é um campo obrigatório", "Password is a required field": "A senha é um campo obrigatório",
"Wrong username or password": "Nome de usuário ou senha inválidos", "Wrong username or password": "Nome de usuário ou senha inválidos",
@ -175,17 +175,17 @@
"Please log in": "Por favor, inicie sua sessão", "Please log in": "Por favor, inicie sua sessão",
"Invidious Private Feed for `x`": "Feed Privado do Invidious para `x`", "Invidious Private Feed for `x`": "Feed Privado do Invidious para `x`",
"channel:`x`": "canal: `x`", "channel:`x`": "canal: `x`",
"Deleted or invalid channel": "Este canal foi apagado ou é inválido", "Deleted or invalid channel": "Canal excluído ou inválido",
"This channel does not exist.": "Este canal não existe.", "This channel does not exist.": "Este canal não existe.",
"Could not get channel info.": "Não foi possível obter as informações do canal.", "Could not get channel info.": "Não foi possível obter as informações do canal.",
"Could not fetch comments": "Não foi possível obter os comentários", "Could not fetch comments": "Não foi possível obter os comentários",
"`x` ago": "`x` atrás", "`x` ago": "`x` atrás",
"Load more": "Carregar mais", "Load more": "Carregar mais",
"Could not create mix.": "Não foi possível criar o mix.", "Could not create mix.": "Não foi possível criar o mix.",
"Empty playlist": "Lista de reprodução vazia", "Empty playlist": "Playlist vazia",
"Not a playlist.": "Não é uma lista de reprodução.", "Not a playlist.": "Não é uma playlist.",
"Playlist does not exist.": "A lista de reprodução não existe.", "Playlist does not exist.": "A playlist não existe.",
"Could not pull trending pages.": "Não foi possível obter as páginas dos vídeos em alta.", "Could not pull trending pages.": "Não foi possível obter as páginas de vídeos em alta.",
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório", "Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é obrigatório", "Hidden field \"token\" is a required field": "O campo oculto \"token\" é obrigatório",
"Erroneous challenge": "Desafio inválido", "Erroneous challenge": "Desafio inválido",
@ -319,87 +319,87 @@
"generic_count_seconds_0": "{{count}} segundo", "generic_count_seconds_0": "{{count}} segundo",
"generic_count_seconds_1": "{{count}} segundos", "generic_count_seconds_1": "{{count}} segundos",
"generic_count_seconds_2": "{{count}} segundos", "generic_count_seconds_2": "{{count}} segundos",
"Fallback comments: ": "Comentários alternativos: ", "Fallback comments: ": "Alternativa para comentários: ",
"Popular": "Populares", "Popular": "Populares",
"Search": "Procurar", "Search": "Pesquisar",
"Top": "No topo", "Top": "Destaques",
"About": "Sobre", "About": "Sobre",
"Rating: ": "Avaliação: ", "Rating: ": "Avaliação: ",
"preferences_locale_label": "Idioma: ", "preferences_locale_label": "Idioma: ",
"View as playlist": "Ver como lista de reprodução", "View as playlist": "Ver como playlist",
"Default": "Padrão", "Default": "Padrão",
"Music": "Músicas", "Music": "Músicas",
"Gaming": "Jogos", "Gaming": "Jogos",
"News": "Notícias", "News": "Notícias",
"Movies": "Filmes", "Movies": "Filmes",
"Download": "Baixar", "Download": "Download",
"Download as: ": "Baixar como: ", "Download as: ": "Baixar como: ",
"%A %B %-d, %Y": "%A %-d %B %Y", "%A %B %-d, %Y": "%A %-d %B %Y",
"(edited)": "(editado)", "(edited)": "(editado)",
"YouTube comment permalink": "Link permanente do comentário no YouTube", "YouTube comment permalink": "Link permanente do comentário no YouTube",
"permalink": "Link permanente", "permalink": "Link permanente",
"`x` marked it with a ❤": "`x` foi marcado como ❤", "`x` marked it with a ❤": "`x` foi marcado com um ❤",
"Audio mode": "Modo de áudio", "Audio mode": "Modo de áudio",
"Video mode": "Modo de vídeo", "Video mode": "Modo de vídeo",
"channel_tab_videos_label": "Vídeos", "channel_tab_videos_label": "Vídeos",
"Playlists": "Listas de reprodução", "Playlists": "Playlists",
"channel_tab_community_label": "Comunidade", "channel_tab_community_label": "Comunidade",
"search_filters_sort_option_relevance": "relevância", "search_filters_sort_option_relevance": "Relevância",
"search_filters_sort_option_rating": "avaliação", "search_filters_sort_option_rating": "Avaliação",
"search_filters_sort_option_date": "data", "search_filters_sort_option_date": "Data de publicação",
"search_filters_sort_option_views": "visualizações", "search_filters_sort_option_views": "Visualizações",
"search_filters_type_label": "content_type", "search_filters_type_label": "Tipo",
"search_filters_duration_label": "duração", "search_filters_duration_label": "Duração",
"search_filters_features_label": "recursos", "search_filters_features_label": "Características",
"search_filters_sort_label": "ordenar", "search_filters_sort_label": "Ordenar por",
"search_filters_date_option_hour": "hora", "search_filters_date_option_hour": "Últimas horas",
"search_filters_date_option_today": "hoje", "search_filters_date_option_today": "Hoje",
"search_filters_date_option_week": "semana", "search_filters_date_option_week": "Esta semana",
"search_filters_date_option_month": "mês", "search_filters_date_option_month": "Este mês",
"search_filters_date_option_year": "ano", "search_filters_date_option_year": "Este ano",
"search_filters_type_option_video": "vídeo", "search_filters_type_option_video": "Vídeo",
"search_filters_type_option_channel": "Canal", "search_filters_type_option_channel": "Canal",
"search_filters_type_option_playlist": "playlist", "search_filters_type_option_playlist": "Playlist",
"search_filters_type_option_movie": "filme", "search_filters_type_option_movie": "Filme",
"search_filters_type_option_show": "show", "search_filters_type_option_show": "Séries",
"search_filters_features_option_hd": "hd", "search_filters_features_option_hd": "HD",
"search_filters_features_option_subtitles": "legendas", "search_filters_features_option_subtitles": "Legendas",
"search_filters_features_option_c_commons": "creative_commons", "search_filters_features_option_c_commons": "Creative Commons",
"search_filters_features_option_three_d": "3d", "search_filters_features_option_three_d": "3D",
"search_filters_features_option_live": "ao vivo", "search_filters_features_option_live": "AO VIVO",
"search_filters_features_option_four_k": "4k", "search_filters_features_option_four_k": "4K",
"search_filters_features_option_location": "localização", "search_filters_features_option_location": "Localização",
"search_filters_features_option_hdr": "hdr", "search_filters_features_option_hdr": "HDR",
"Current version: ": "Versão atual: ", "Current version: ": "Versão atual: ",
"next_steps_error_message": "Depois disso, você deve tentar: ", "next_steps_error_message": "Depois disso, você deve tentar: ",
"next_steps_error_message_refresh": "Atualizar", "next_steps_error_message_refresh": "Recarregar",
"next_steps_error_message_go_to_youtube": "Ir para o YouTube", "next_steps_error_message_go_to_youtube": "Ir para o YouTube",
"footer_donate_page": "Doe", "footer_donate_page": "Doar",
"adminprefs_modified_source_code_url_label": "URL para repositório de código fonte modificado", "adminprefs_modified_source_code_url_label": "URL para o repositório do código-fonte modificado",
"search_filters_duration_option_long": "Longo (> 20 minutos)", "search_filters_duration_option_long": "Longo (> 20 minutos)",
"search_filters_duration_option_short": "Curto (< 4 minutos)", "search_filters_duration_option_short": "Curto (< 4 minutos)",
"footer_documentation": "Documentação", "footer_documentation": "Documentação",
"footer_source_code": "Código fonte", "footer_source_code": "Código-fonte",
"footer_original_source_code": "Código fonte original", "footer_original_source_code": "Código-fonte original",
"footer_modfied_source_code": "Código-fonte modificado", "footer_modfied_source_code": "Código-fonte modificado",
"preferences_quality_dash_label": "Qualidade de vídeo do painel preferida: ", "preferences_quality_dash_label": "Qualidade de vídeo DASH 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_0": "{{count}} vídeo", "generic_videos_count_0": "{{count}} vídeo",
"generic_videos_count_1": "{{count}} vídeos", "generic_videos_count_1": "{{count}} vídeos",
"generic_videos_count_2": "{{count}} vídeos", "generic_videos_count_2": "{{count}} vídeos",
"generic_playlists_count_0": "{{count}} lista de reprodução", "generic_playlists_count_0": "{{count}} playlist",
"generic_playlists_count_1": "{{count}} listas de reprodução", "generic_playlists_count_1": "{{count}} playlists",
"generic_playlists_count_2": "{{count}} listas de reprodução", "generic_playlists_count_2": "{{count}} playlists",
"generic_subscribers_count_0": "{{count}} inscrito", "generic_subscribers_count_0": "{{count}} inscrito",
"generic_subscribers_count_1": "{{count}} inscritos", "generic_subscribers_count_1": "{{count}} inscritos",
"generic_subscribers_count_2": "{{count}} inscritos", "generic_subscribers_count_2": "{{count}} inscritos",
"generic_subscriptions_count_0": "{{count}} inscrição", "generic_subscriptions_count_0": "{{count}} inscrição",
"generic_subscriptions_count_1": "{{count}} inscrições", "generic_subscriptions_count_1": "{{count}} inscrições",
"generic_subscriptions_count_2": "{{count}} inscrições", "generic_subscriptions_count_2": "{{count}} inscrições",
"subscriptions_unseen_notifs_count_0": "{{count}} notificação não vista", "subscriptions_unseen_notifs_count_0": "{{count}} notificação não visualizada",
"subscriptions_unseen_notifs_count_1": "{{count}} notificações não vistas", "subscriptions_unseen_notifs_count_1": "{{count}} notificações não visualizadas",
"subscriptions_unseen_notifs_count_2": "{{count}} notificações não vistas", "subscriptions_unseen_notifs_count_2": "{{count}} notificações não visualizadas",
"comments_view_x_replies_0": "Ver {{count}} resposta", "comments_view_x_replies_0": "Ver {{count}} resposta",
"comments_view_x_replies_1": "Ver {{count}} respostas", "comments_view_x_replies_1": "Ver {{count}} respostas",
"comments_view_x_replies_2": "Ver {{count}} respostas", "comments_view_x_replies_2": "Ver {{count}} respostas",
@ -407,14 +407,14 @@
"comments_points_count_1": "{{count}} pontos", "comments_points_count_1": "{{count}} pontos",
"comments_points_count_2": "{{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 informar um erro, verifique se você:",
"preferences_save_player_pos_label": "Salvar a posição de reprodução: ", "preferences_save_player_pos_label": "Salvar posição de reprodução: ",
"search_filters_features_option_purchased": "Comprado", "search_filters_features_option_purchased": "Comprado",
"crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>", "crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>",
"crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>", "crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>",
"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": "leu as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>",
"generic_views_count_0": "{{count}} visualização", "generic_views_count_0": "{{count}} visualização",
"generic_views_count_1": "{{count}} visualizações", "generic_views_count_1": "{{count}} visualizações",
"generic_views_count_2": "{{count}} visualizações", "generic_views_count_2": "{{count}} visualizações",
@ -422,8 +422,8 @@
"preferences_quality_option_hd720": "HD720", "preferences_quality_option_hd720": "HD720",
"preferences_quality_option_small": "Pequeno", "preferences_quality_option_small": "Pequeno",
"preferences_quality_dash_option_auto": "Auto", "preferences_quality_dash_option_auto": "Auto",
"preferences_quality_dash_option_best": "Melhor", "preferences_quality_dash_option_best": "Melhor qualidade",
"preferences_quality_dash_option_worst": "Pior", "preferences_quality_dash_option_worst": "Pior qualidade",
"preferences_quality_dash_option_2160p": "2160p", "preferences_quality_dash_option_2160p": "2160p",
"preferences_quality_dash_option_1440p": "1440p", "preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_option_1080p": "1080p", "preferences_quality_dash_option_1080p": "1080p",
@ -435,17 +435,17 @@
"invidious": "Invidious", "invidious": "Invidious",
"preferences_quality_option_medium": "Médio", "preferences_quality_option_medium": "Médio",
"search_filters_features_option_three_sixty": "360°", "search_filters_features_option_three_sixty": "360°",
"none": "none", "none": "nenhum",
"videoinfo_watch_on_youTube": "Assistir no YouTube", "videoinfo_watch_on_youTube": "Assistir no YouTube",
"videoinfo_youTube_embed_link": "Embutir", "videoinfo_youTube_embed_link": "Embed",
"videoinfo_invidious_embed_link": "Link Embutido", "videoinfo_invidious_embed_link": "Embed link",
"download_subtitles": "Legendas - `x` (.vtt)", "download_subtitles": "Legendas - `x` (.vtt)",
"user_created_playlists": "`x` listas de reprodução criadas", "user_created_playlists": "`x` playlists criadas",
"user_saved_playlists": "`x` listas de reprodução salvas", "user_saved_playlists": "`x` playlists salvas",
"Video unavailable": "Vídeo indisponível", "Video unavailable": "Vídeo indisponível",
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`", "videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`",
"search_filters_title": "Filtro", "search_filters_title": "Filtro",
"preferences_watch_history_label": "Ative o histórico de exibição: ", "preferences_watch_history_label": "Ativar histórico de exibição: ",
"search_message_no_results": "Nenhum resultado encontrado.", "search_message_no_results": "Nenhum resultado encontrado.",
"search_message_change_filters_or_query": "Tente ampliar sua consulta de pesquisa e/ou alterar os filtros.", "search_message_change_filters_or_query": "Tente ampliar sua consulta de pesquisa e/ou alterar os filtros.",
"English (United Kingdom)": "Inglês (Reino Unido)", "English (United Kingdom)": "Inglês (Reino Unido)",
@ -465,7 +465,7 @@
"Portuguese (Brazil)": "Português (Brasil)", "Portuguese (Brazil)": "Português (Brasil)",
"Russian (auto-generated)": "Russo (gerado automaticamente)", "Russian (auto-generated)": "Russo (gerado automaticamente)",
"Vietnamese (auto-generated)": "Vietnamita (gerado automaticamente)", "Vietnamese (auto-generated)": "Vietnamita (gerado automaticamente)",
"search_filters_date_label": "Data de upload", "search_filters_date_label": "Data de publicação",
"search_filters_date_option_none": "Qualquer data", "search_filters_date_option_none": "Qualquer data",
"Dutch (auto-generated)": "Holandês (gerado automaticamente)", "Dutch (auto-generated)": "Holandês (gerado automaticamente)",
"French (auto-generated)": "Francês (gerado automaticamente)", "French (auto-generated)": "Francês (gerado automaticamente)",
@ -479,21 +479,21 @@
"Turkish (auto-generated)": "Turco (gerado automaticamente)", "Turkish (auto-generated)": "Turco (gerado automaticamente)",
"search_filters_duration_option_medium": "Médio (4 - 20 minutos)", "search_filters_duration_option_medium": "Médio (4 - 20 minutos)",
"search_filters_features_option_vr180": "VR180", "search_filters_features_option_vr180": "VR180",
"Popular enabled: ": "Popular habilitado: ", "Popular enabled: ": "Página \"Populares\" ativada: ",
"error_video_not_in_playlist": "O vídeo solicitado não existe nesta playlist. <a href=\"`x`\">Clique aqui para acessar a página inicial da playlist.</a>", "error_video_not_in_playlist": "O vídeo solicitado não existe nesta playlist. <a href=\"`x`\">Clique aqui para acessar a página inicial da playlist.</a>",
"channel_tab_channels_label": "Canais", "channel_tab_channels_label": "Canais",
"channel_tab_playlists_label": "Listas de reprodução", "channel_tab_playlists_label": "Playlists",
"channel_tab_shorts_label": "Curtos", "channel_tab_shorts_label": "Shorts",
"channel_tab_streams_label": "Ao Vivo", "channel_tab_streams_label": "Transmissão ao vivo",
"Music in this video": "Música neste vídeo", "Music in this video": "Música neste vídeo",
"Artist: ": "Artista: ", "Artist: ": "Artista: ",
"Album: ": "Álbum: ", "Album: ": "Álbum: ",
"Standard YouTube license": "Licença padrão do YouTube", "Standard YouTube license": "Licença padrão do YouTube",
"Song: ": "Música: ", "Song: ": "Música: ",
"Channel Sponsor": "Patrocinador do Canal", "Channel Sponsor": "Patrocinador do canal",
"Download is disabled": "Download está desabilitado", "Download is disabled": "Download indisponível",
"Import YouTube playlist (.csv)": "Importar lista de reprodução do YouTube (.csv)", "Import YouTube playlist (.csv)": "Importar playlist do YouTube (.csv)",
"generic_button_delete": "Apagar", "generic_button_delete": "Excluir",
"generic_button_save": "Salvar", "generic_button_save": "Salvar",
"generic_button_edit": "Editar", "generic_button_edit": "Editar",
"playlist_button_add_items": "Adicionar vídeos", "playlist_button_add_items": "Adicionar vídeos",
@ -503,5 +503,15 @@
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"generic_channels_count_0": "{{count}} canal", "generic_channels_count_0": "{{count}} canal",
"generic_channels_count_1": "{{count}} canais", "generic_channels_count_1": "{{count}} canais",
"generic_channels_count_2": "{{count}} canais" "generic_channels_count_2": "{{count}} canais",
"Import YouTube watch history (.json)": "Importar histórico de exibição do YouTube (.json)",
"toggle_theme": "Alternar tema",
"Add to playlist": "Adicionar à playlist",
"Add to playlist: ": "Adicionar à playlist: ",
"Search for videos": "Pesquisar vídeos",
"The Popular feed has been disabled by the administrator.": "O feed \"Populares\" foi desativado pelo administrador.",
"Answer": "Resposta",
"carousel_slide": "Slide {{current}} de {{total}}",
"carousel_skip": "Ignorar carrossel",
"carousel_go_to": "Ir ao slide `x`"
} }

View file

@ -130,12 +130,12 @@
"Private": "Privado", "Private": "Privado",
"View all playlists": "Ver todas as listas de reprodução", "View all playlists": "Ver todas as listas de reprodução",
"Updated `x` ago": "Atualizado `x` atrás", "Updated `x` ago": "Atualizado `x` atrás",
"Delete playlist `x`?": "Eliminar a lista de reprodução 'x'?", "Delete playlist `x`?": "Eliminar a lista de reprodução `x`?",
"Delete playlist": "Eliminar lista de reprodução", "Delete playlist": "Eliminar lista de reprodução",
"Create playlist": "Criar lista de reprodução", "Create playlist": "Criar lista de reprodução",
"Title": "Título", "Title": "Título",
"Playlist privacy": "Privacidade da lista de reprodução", "Playlist privacy": "Privacidade da lista de reprodução",
"Editing playlist `x`": "A editar lista de reprodução 'x'", "Editing playlist `x`": "A editar lista de reprodução `x`",
"Show more": "Mostrar mais", "Show more": "Mostrar mais",
"Show less": "Mostrar menos", "Show less": "Mostrar menos",
"Watch on YouTube": "Ver no YouTube", "Watch on YouTube": "Ver no YouTube",
@ -150,8 +150,8 @@
"Whitelisted regions: ": "Regiões permitidas: ", "Whitelisted regions: ": "Regiões permitidas: ",
"Blacklisted regions: ": "Regiões bloqueadas: ", "Blacklisted regions: ": "Regiões bloqueadas: ",
"Shared `x`": "Partilhado `x`", "Shared `x`": "Partilhado `x`",
"Premieres in `x`": "Estreias em 'x'", "Premieres in `x`": "Estreias em `x`",
"Premieres `x`": "Estreias 'x'", "Premieres `x`": "Estreias `x`",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Olá! Parece que o JavaScript está desativado. Clique aqui para ver os comentários, entretanto eles podem levar mais tempo para carregar.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Olá! Parece que o JavaScript está desativado. Clique aqui para ver os comentários, entretanto eles podem levar mais tempo para carregar.",
"View YouTube comments": "Ver comentários do YouTube", "View YouTube comments": "Ver comentários do YouTube",
"View more comments on Reddit": "Ver mais comentários no Reddit", "View more comments on Reddit": "Ver mais comentários no Reddit",
@ -173,7 +173,7 @@
"Password cannot be longer than 55 characters": "A palavra-chave não pode ser superior a 55 caracteres", "Password cannot be longer than 55 characters": "A palavra-chave não pode ser superior a 55 caracteres",
"Please log in": "Por favor, inicie sessão", "Please log in": "Por favor, inicie sessão",
"Invidious Private Feed for `x`": "Feed Privado do Invidious para `x`", "Invidious Private Feed for `x`": "Feed Privado do Invidious para `x`",
"channel:`x`": "canal:'x'", "channel:`x`": "canal:`x`",
"Deleted or invalid channel": "Canal eliminado ou inválido", "Deleted or invalid channel": "Canal eliminado ou inválido",
"This channel does not exist.": "Este canal não existe.", "This channel does not exist.": "Este canal não existe.",
"Could not get channel info.": "Não foi possível obter as informações do canal.", "Could not get channel info.": "Não foi possível obter as informações do canal.",

View file

@ -1,25 +1,25 @@
{ {
"search_filters_type_option_show": "Espetáculo", "search_filters_type_option_show": "Séries",
"search_filters_sort_option_views": "Visualizações", "search_filters_sort_option_views": "Visualizações",
"search_filters_sort_option_date": "Data de envio", "search_filters_sort_option_date": "Data de carregamento",
"search_filters_sort_option_rating": "Avaliação", "search_filters_sort_option_rating": "Avaliação",
"search_filters_sort_option_relevance": "Relevância", "search_filters_sort_option_relevance": "Relevância",
"Switch Invidious Instance": "Mudar a instância do Invidious", "Switch Invidious Instance": "Alterar instância Invidious",
"Show less": "Mostrar menos", "Show less": "Mostrar menos",
"Show more": "Mostrar mais", "Show more": "Mostrar mais",
"Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no GitHub.", "Released under the AGPLv3 on Github.": "Disponibilizada sob a AGPLv3 no GitHub.",
"preferences_show_nick_label": "Mostrar nome de utilizador em cima: ", "preferences_show_nick_label": "Mostrar nome de utilizador em cima: ",
"preferences_automatic_instance_redirect_label": "Redirecionamento de instância automática (solução de último recurso para redirect.invidious.io): ", "preferences_automatic_instance_redirect_label": "Redirecionamento de instância automática (solução de último recurso para redirect.invidious.io): ",
"preferences_category_misc": "Preferências diversas", "preferences_category_misc": "Preferências diversas",
"preferences_vr_mode_label": "Vídeos interativos de 360 graus (necessita de WebGL): ", "preferences_vr_mode_label": "Vídeos interativos de 360 graus (requer WebGL): ",
"preferences_extend_desc_label": "Estender automaticamente a descrição do vídeo: ", "preferences_extend_desc_label": "Expandir automaticamente a descrição do vídeo: ",
"next_steps_error_message_go_to_youtube": "Ir ao YouTube", "next_steps_error_message_go_to_youtube": "Ir para o YouTube",
"next_steps_error_message": "Pode tentar as seguintes opções: ", "next_steps_error_message": "Pode tentar as seguintes opções: ",
"next_steps_error_message_refresh": "Atualizar", "next_steps_error_message_refresh": "Recarregar",
"search_filters_features_option_hdr": "HDR", "search_filters_features_option_hdr": "HDR",
"search_filters_features_option_location": "Localização", "search_filters_features_option_location": "Localização",
"search_filters_features_option_four_k": "4K", "search_filters_features_option_four_k": "4K",
"search_filters_features_option_live": "Ao Vivo", "search_filters_features_option_live": "Direto",
"search_filters_features_option_three_d": "3D", "search_filters_features_option_three_d": "3D",
"search_filters_features_option_c_commons": "Creative Commons", "search_filters_features_option_c_commons": "Creative Commons",
"search_filters_features_option_subtitles": "Legendas", "search_filters_features_option_subtitles": "Legendas",
@ -37,45 +37,52 @@
"search_filters_features_label": "Funcionalidades", "search_filters_features_label": "Funcionalidades",
"search_filters_duration_label": "Duração", "search_filters_duration_label": "Duração",
"search_filters_type_label": "Tipo", "search_filters_type_label": "Tipo",
"permalink": "hiperligação permanente", "permalink": "ligação permanente",
"YouTube comment permalink": "Hiperligação permanente do comentário no YouTube", "YouTube comment permalink": "Ligação permanente do comentário no YouTube",
"Download as: ": "Descarregar como: ", "Download as: ": "Descarregar como: ",
"Download": "Descarregar", "Download": "Descarregar",
"Default": "Predefinido", "Default": "Padrão",
"Top": "Destaques", "Top": "Destaques",
"Search": "Pesquisar", "Search": "Pesquisar",
"generic_count_years": "{{count}} segundo", "generic_count_years_0": "{{count}} ano",
"generic_count_years_plural": "{{count}} segundos", "generic_count_years_1": "{{count}} anos",
"generic_count_months": "{{count}} minuto", "generic_count_years_2": "{{count}} anos",
"generic_count_months_plural": "{{count}} minutos", "generic_count_months_0": "{{count}} mês",
"generic_count_weeks": "{{count}} hora", "generic_count_months_1": "{{count}} meses",
"generic_count_weeks_plural": "{{count}} horas", "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}} seman", "generic_count_weeks_2": "{{count}} semanas",
"generic_count_hours_plural": "{{count}} semanas", "generic_count_days_0": "{{count}} dia",
"generic_count_minutes": "{{count}} mês", "generic_count_days_1": "{{count}} dias",
"generic_count_minutes_plural": "{{count}} meses", "generic_count_days_2": "{{count}} dias",
"generic_count_seconds": "{{count}} ano", "generic_count_hours_0": "{{count}} hora",
"generic_count_seconds_plural": "{{count}} anos", "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",
"Chinese (Traditional)": "Chinês (tradicional)", "Chinese (Traditional)": "Chinês (tradicional)",
"Chinese (Simplified)": "Chinês (simplificado)", "Chinese (Simplified)": "Chinês (simplificado)",
"Could not pull trending pages.": "Não foi possível obter as páginas de tendências.", "Could not pull trending pages.": "Não foi possível obter a página de tendências.",
"Could not create mix.": "Não foi possível criar a mistura.", "Could not create mix.": "Não foi possível criar o mix.",
"Deleted or invalid channel": "Canal eliminado ou inválido", "Deleted or invalid channel": "Canal eliminado ou inválido",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Olá! Parece que o JavaScript está desativado. Clique aqui para ver os comentários, entretanto eles podem levar mais tempo para carregar.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Olá! Parece que o JavaScript está desativado. Clique aqui para ver os comentários, mas tenha e conta que podem levar mais tempo para carregar.",
"Delete playlist": "Eliminar lista de reprodução", "Delete playlist": "Eliminar lista de reprodução",
"Delete playlist `x`?": "Eliminar a lista de reprodução 'x'?", "Delete playlist `x`?": "Eliminar lista de reprodução `x`?",
"search": "pesquisar", "search": "pesquisar",
"unsubscribe": "anular subscrição", "unsubscribe": "anular subscrição",
"Import/export": "Importar / exportar", "Import/export": "Importar/exportar",
"Save preferences": "Guardar preferências", "Save preferences": "Guardar preferências",
"Top enabled: ": "Destaques ativados: ", "Top enabled: ": "Destaques ativados: ",
"Delete account": "Eliminar conta", "Delete account": "Eliminar conta",
"Import/export data": "Importar / exportar dados", "Import/export data": "Importar/exportar dados",
"preferences_annotations_label": "Mostrar anotações sempre: ", "preferences_annotations_label": "Mostrar anotações sempre: ",
"preferences_continue_label": "Reproduzir sempre o próximo: ", "preferences_continue_label": "Reproduzir sempre o seguinte: ",
"Sign In": "Iniciar sessão", "Sign In": "Entrar",
"Log in/register": "Iniciar sessão/registar", "Log in/register": "Iniciar sessão/registar",
"Delete account?": "Eliminar conta?", "Delete account?": "Eliminar conta?",
"Import and Export Data": "Importar e exportar dados", "Import and Export Data": "Importar e exportar dados",
@ -86,7 +93,7 @@
"Danish": "Dinamarquês", "Danish": "Dinamarquês",
"Czech": "Checo", "Czech": "Checo",
"Croatian": "Croata", "Croatian": "Croata",
"Corsican": "Corso", "Corsican": "Córsego",
"Cebuano": "Cebuano", "Cebuano": "Cebuano",
"Catalan": "Catalão", "Catalan": "Catalão",
"Burmese": "Birmanês", "Burmese": "Birmanês",
@ -100,10 +107,10 @@
"Arabic": "Árabe", "Arabic": "Árabe",
"Amharic": "Amárico", "Amharic": "Amárico",
"Albanian": "Albanês", "Albanian": "Albanês",
"Afrikaans": "Africano", "Afrikaans": "Africânder",
"English (auto-generated)": "Inglês (auto-gerado)", "English (auto-generated)": "Inglês (auto-gerado)",
"English": "Inglês", "English": "Inglês",
"Token is expired, please try again": "Token expirou, tente novamente", "Token is expired, please try again": "Token caducado, tente novamente",
"No such user": "Utilizador inválido", "No such user": "Utilizador inválido",
"Erroneous token": "Token inválido", "Erroneous token": "Token inválido",
"Erroneous challenge": "Desafio inválido", "Erroneous challenge": "Desafio inválido",
@ -117,29 +124,29 @@
"Could not fetch comments": "Não foi possível obter os comentários", "Could not fetch comments": "Não foi possível obter os comentários",
"Could not get channel info.": "Não foi possível obter as informações do canal.", "Could not get channel info.": "Não foi possível obter as informações do canal.",
"This channel does not exist.": "Este canal não existe.", "This channel does not exist.": "Este canal não existe.",
"channel:`x`": "canal:'x'", "channel:`x`": "canal:`x`",
"Invidious Private Feed for `x`": "Feed Privado do Invidious para `x`", "Invidious Private Feed for `x`": "Feed Privado do Invidious para `x`",
"Please log in": "Por favor, inicie sessão", "Please log in": "Por favor, inicie sessão",
"Password cannot be longer than 55 characters": "A palavra-chave não pode ser superior a 55 caracteres", "Password cannot be longer than 55 characters": "A palavra-passe não pode ter mais do que 55 caracteres",
"Password cannot be empty": "A palavra-chave não pode estar vazia", "Password cannot be empty": "A palavra-passe não pode estar vazia",
"Wrong username or password": "Nome de utilizador ou palavra-chave incorreto", "Wrong username or password": "Nome de utilizador ou palavra-passe incorreta",
"Password is a required field": "Palavra-chave é um campo obrigatório", "Password is a required field": "Palavra-passe é um campo obrigatório",
"User ID is a required field": "O nome de utilizador é um campo obrigatório", "User ID is a required field": "O nome de utilizador é um campo obrigatório",
"CAPTCHA is a required field": "CAPTCHA é um campo obrigatório", "CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
"Erroneous CAPTCHA": "CAPTCHA inválido", "Erroneous CAPTCHA": "CAPTCHA inválido",
"Wrong answer": "Resposta errada", "Wrong answer": "Resposta errada",
"Incorrect password": "Palavra-chave incorreta", "Incorrect password": "Palavra-passe incorreta",
"Show replies": "Mostrar respostas", "Show replies": "Mostrar respostas",
"Hide replies": "Ocultar respostas", "Hide replies": "Ocultar respostas",
"View Reddit comments": "Ver comentários do Reddit", "View Reddit comments": "Ver comentários do Reddit",
"View `x` comments": { "View `x` comments": {
"": "Ver `x` comentários", "": "Ver `x` comentários",
"([^.,0-9]|^)1([^.,0-9]|$)": "Ver `x` comentários" "([^.,0-9]|^)1([^.,0-9]|$)": "Ver `x` comentário"
}, },
"View more comments on Reddit": "Ver mais comentários no Reddit", "View more comments on Reddit": "Ver mais comentários no Reddit",
"View YouTube comments": "Ver comentários do YouTube", "View YouTube comments": "Ver comentários do YouTube",
"Premieres `x`": "Estreias 'x'", "Premieres `x`": "Estreia `x`",
"Premieres in `x`": "Estreias em 'x'", "Premieres in `x`": "Estreia a `x`",
"Shared `x`": "Partilhado `x`", "Shared `x`": "Partilhado `x`",
"Blacklisted regions: ": "Regiões bloqueadas: ", "Blacklisted regions: ": "Regiões bloqueadas: ",
"Whitelisted regions: ": "Regiões permitidas: ", "Whitelisted regions: ": "Regiões permitidas: ",
@ -151,43 +158,44 @@
"Show annotations": "Mostrar anotações", "Show annotations": "Mostrar anotações",
"Hide annotations": "Ocultar anotações", "Hide annotations": "Ocultar anotações",
"Watch on YouTube": "Ver no YouTube", "Watch on YouTube": "Ver no YouTube",
"Editing playlist `x`": "A editar lista de reprodução 'x'", "Editing playlist `x`": "A editar lista de reprodução `x`",
"Playlist privacy": "Privacidade da lista de reprodução", "Playlist privacy": "Privacidade da lista de reprodução",
"Title": "Título", "Title": "Título",
"Create playlist": "Criar lista de reprodução", "Create playlist": "Criar lista de reprodução",
"Updated `x` ago": "Atualizado `x` atrás", "Updated `x` ago": "Atualizado `x`",
"View all playlists": "Ver todas as listas de reprodução", "View all playlists": "Ver todas as listas de reprodução",
"Private": "Privado", "Private": "Privado",
"Unlisted": "Não listado", "Unlisted": "Não listado",
"Public": "Público", "Public": "Público",
"Trending": "Tendências", "Trending": "Tendências",
"View privacy policy.": "Ver a política de privacidade.", "View privacy policy.": "Ver política de privacidade.",
"View JavaScript license information.": "Ver informações da licença do JavaScript.", "View JavaScript license information.": "Ver informações da licença JavaScript.",
"Source available here.": "Código-fonte disponível aqui.", "Source available here.": "Código-fonte disponível aqui.",
"Log out": "Terminar sessão", "Log out": "Terminar sessão",
"Subscriptions": "Subscrições", "Subscriptions": "Subscrições",
"revoke": "revogar", "revoke": "revogar",
"tokens_count": "{{count}} token", "tokens_count_0": "{{count}} token",
"tokens_count_plural": "{{count}} tokens", "tokens_count_1": "{{count}} tokens",
"tokens_count_2": "{{count}} tokens",
"Token": "Token", "Token": "Token",
"Token manager": "Gerir tokens", "Token manager": "Gestor de tokens",
"Subscription manager": "Gerir subscrições", "Subscription manager": "Gestor de subscrições",
"Report statistics: ": "Relatório de estatísticas: ", "Report statistics: ": "Relatório de estatísticas: ",
"Registration enabled: ": "Registar ativado: ", "Registration enabled: ": "Registar ativado: ",
"Login enabled: ": "Iniciar sessão ativado: ", "Login enabled: ": "Iniciar sessão ativado: ",
"CAPTCHA enabled: ": "CAPTCHA ativado: ", "CAPTCHA enabled: ": "CAPTCHA ativado: ",
"preferences_feed_menu_label": "Menu de subscrições: ", "preferences_feed_menu_label": "Menu de subscrições: ",
"preferences_default_home_label": "Página inicial predefinida: ", "preferences_default_home_label": "Página inicial padrão: ",
"preferences_category_admin": "Preferências de administrador", "preferences_category_admin": "Preferências de administrador",
"Watch history": "Histórico de reprodução", "Watch history": "Histórico de reprodução",
"Manage tokens": "Gerir tokens", "Manage tokens": "Gerir tokens",
"Manage subscriptions": "Gerir as subscrições", "Manage subscriptions": "Gerir subscrições",
"Change password": "Alterar palavra-chave", "Change password": "Alterar palavra-passe",
"Clear watch history": "Limpar histórico de reprodução", "Clear watch history": "Limpar histórico de reprodução",
"preferences_category_data": "Preferências de dados", "preferences_category_data": "Preferências de dados",
"`x` is live": "`x` está em direto", "`x` is live": "`x` está em direto",
"`x` uploaded a video": "`x` publicou um novo vídeo", "`x` uploaded a video": "`x` publicou um vídeo",
"Enable web notifications": "Ativar notificações pela web", "Enable web notifications": "Ativar notificações web",
"preferences_notifications_only_label": "Mostrar apenas notificações (se existirem): ", "preferences_notifications_only_label": "Mostrar apenas notificações (se existirem): ",
"preferences_unseen_only_label": "Mostrar apenas vídeos não visualizados: ", "preferences_unseen_only_label": "Mostrar apenas vídeos não visualizados: ",
"Only show latest unwatched video from channel: ": "Mostrar apenas vídeos mais recentes não visualizados do canal: ", "Only show latest unwatched video from channel: ": "Mostrar apenas vídeos mais recentes não visualizados do canal: ",
@ -199,9 +207,9 @@
"published - reverse": "publicado - inverso", "published - reverse": "publicado - inverso",
"published": "publicado", "published": "publicado",
"preferences_sort_label": "Ordenar vídeos por: ", "preferences_sort_label": "Ordenar vídeos por: ",
"preferences_max_results_label": "Quantidade de vídeos nas subscrições: ", "preferences_max_results_label": "Número de vídeos nas subscrições: ",
"Redirect homepage to feed: ": "Redirecionar página inicial para subscrições: ", "Redirect homepage to feed: ": "Redirecionar página inicial para subscrições: ",
"preferences_annotations_subscribed_label": "Mostrar sempre anotações aos canais subscritos: ", "preferences_annotations_subscribed_label": "Mostrar sempre anotações nos canais subscritos: ",
"preferences_category_subscription": "Preferências de subscrições", "preferences_category_subscription": "Preferências de subscrições",
"preferences_thin_mode_label": "Modo compacto: ", "preferences_thin_mode_label": "Modo compacto: ",
"light": "claro", "light": "claro",
@ -212,11 +220,11 @@
"preferences_category_visual": "Preferências visuais", "preferences_category_visual": "Preferências visuais",
"preferences_related_videos_label": "Mostrar vídeos relacionados: ", "preferences_related_videos_label": "Mostrar vídeos relacionados: ",
"Fallback captions: ": "Legendas alternativas: ", "Fallback captions: ": "Legendas alternativas: ",
"preferences_captions_label": "Legendas predefinidas: ", "preferences_captions_label": "Legendas padrão: ",
"reddit": "Reddit", "reddit": "Reddit",
"youtube": "YouTube", "youtube": "YouTube",
"preferences_comments_label": "Preferência dos comentários: ", "preferences_comments_label": "Comentários padrão: ",
"preferences_volume_label": "Volume da reprodução: ", "preferences_volume_label": "Volume de reprodução: ",
"preferences_quality_label": "Qualidade de vídeo preferida: ", "preferences_quality_label": "Qualidade de vídeo preferida: ",
"preferences_speed_label": "Velocidade preferida: ", "preferences_speed_label": "Velocidade preferida: ",
"preferences_local_label": "Usar proxy nos vídeos: ", "preferences_local_label": "Usar proxy nos vídeos: ",
@ -231,11 +239,11 @@
"Image CAPTCHA": "Imagem CAPTCHA", "Image CAPTCHA": "Imagem CAPTCHA",
"Text CAPTCHA": "Texto CAPTCHA", "Text CAPTCHA": "Texto CAPTCHA",
"Time (h:mm:ss):": "Tempo (h:mm:ss):", "Time (h:mm:ss):": "Tempo (h:mm:ss):",
"Password": "Palavra-chave", "Password": "Palavra-passe",
"User ID": "Utilizador", "User ID": "Utilizador",
"Log in": "Iniciar sessão", "Log in": "Iniciar sessão",
"source": "código-fonte", "source": "fonte",
"JavaScript license information": "Informação de licença do JavaScript", "JavaScript license information": "Informação da licença JavaScript",
"An alternative front-end to YouTube": "Uma interface alternativa ao YouTube", "An alternative front-end to YouTube": "Uma interface alternativa ao YouTube",
"History": "Histórico", "History": "Histórico",
"Export data as JSON": "Exportar dados Invidious como JSON", "Export data as JSON": "Exportar dados Invidious como JSON",
@ -245,18 +253,18 @@
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)", "Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
"Import NewPipe subscriptions (.json)": "Importar subscrições do NewPipe (.json)", "Import NewPipe subscriptions (.json)": "Importar subscrições do NewPipe (.json)",
"Import FreeTube subscriptions (.db)": "Importar subscrições do FreeTube (.db)", "Import FreeTube subscriptions (.db)": "Importar subscrições do FreeTube (.db)",
"Import YouTube subscriptions": "Importar subscrições do YouTube/OPML", "Import YouTube subscriptions": "Importar subscrições via YouTube/OPML",
"Import Invidious data": "Importar dados JSON do Invidious", "Import Invidious data": "Importar dados JSON do Invidious",
"Import": "Importar", "Import": "Importar",
"No": "Não", "No": "Não",
"Yes": "Sim", "Yes": "Sim",
"Authorize token for `x`?": "Autorizar token para `x`?", "Authorize token for `x`?": "Autorizar 'token' para `x`?",
"Authorize token?": "Autorizar token?", "Authorize token?": "Autorizar 'token'?",
"New passwords must match": "As novas palavra-chaves devem corresponder", "New passwords must match": "As novas palavras-passe devem ser iguais",
"New password": "Nova palavra-chave", "New password": "Nova palavra-passe",
"Clear watch history?": "Limpar histórico de reprodução?", "Clear watch history?": "Limpar histórico de reprodução?",
"Previous page": "Página anterior", "Previous page": "Página anterior",
"Next page": "Próxima página", "Next page": "Página seguinte",
"last": "últimos", "last": "últimos",
"Current version: ": "Versão atual: ", "Current version: ": "Versão atual: ",
"channel_tab_community_label": "Comunidade", "channel_tab_community_label": "Comunidade",
@ -264,19 +272,19 @@
"channel_tab_videos_label": "Vídeos", "channel_tab_videos_label": "Vídeos",
"Video mode": "Modo de vídeo", "Video mode": "Modo de vídeo",
"Audio mode": "Modo de áudio", "Audio mode": "Modo de áudio",
"`x` marked it with a ❤": "`x` foi marcado como ❤", "`x` marked it with a ❤": "`x` foi marcado com um ❤",
"(edited)": "(editado)", "(edited)": "(editado)",
"%A %B %-d, %Y": "%A %B %-d, %Y", "%A %B %-d, %Y": "%A %B %-d, %Y",
"Movies": "Filmes", "Movies": "Filmes",
"News": "Notícias", "News": "Notícias",
"Gaming": "Jogos", "Gaming": "Jogos",
"Music": "Música", "Music": "Músicas",
"View as playlist": "Ver como lista de reprodução", "View as playlist": "Ver como lista de reprodução",
"preferences_locale_label": "Idioma: ", "preferences_locale_label": "Idioma: ",
"Rating: ": "Avaliação: ", "Rating: ": "Avaliação: ",
"About": "Sobre", "About": "Acerca",
"Popular": "Popular", "Popular": "Popular",
"Fallback comments: ": "Comentários alternativos: ", "Fallback comments: ": "Alternativa para comentários: ",
"Zulu": "Zulu", "Zulu": "Zulu",
"Yoruba": "Ioruba", "Yoruba": "Ioruba",
"Yiddish": "Iídiche", "Yiddish": "Iídiche",
@ -321,7 +329,7 @@
"Marathi": "Marathi", "Marathi": "Marathi",
"Maori": "Maori", "Maori": "Maori",
"Maltese": "Maltês", "Maltese": "Maltês",
"Malayalam": "Malaiala", "Malayalam": "Malaialaio",
"Malay": "Malaio", "Malay": "Malaio",
"Malagasy": "Malgaxe", "Malagasy": "Malgaxe",
"Macedonian": "Macedónio", "Macedonian": "Macedónio",
@ -357,15 +365,15 @@
"Galician": "Galego", "Galician": "Galego",
"French": "Francês", "French": "Francês",
"Finnish": "Finlandês", "Finnish": "Finlandês",
"popular": "popular", "popular": "populares",
"oldest": "mais antigos", "oldest": "antigos",
"newest": "mais recentes", "newest": "recentes",
"View playlist on YouTube": "Ver lista de reprodução no YouTube", "View playlist on YouTube": "Ver lista de reprodução no YouTube",
"View channel on YouTube": "Ver canal no YouTube", "View channel on YouTube": "Ver canal no YouTube",
"Subscribe": "Subscrever", "Subscribe": "Subscrever",
"Unsubscribe": "Anular subscrição", "Unsubscribe": "Anular subscrição",
"Shared `x` ago": "Partilhado `x` atrás", "Shared `x` ago": "Partilhado `x` atrás",
"LIVE": "AO VIVO", "LIVE": "Direto",
"search_filters_duration_option_short": "Curto (< 4 minutos)", "search_filters_duration_option_short": "Curto (< 4 minutos)",
"search_filters_duration_option_long": "Longo (> 20 minutos)", "search_filters_duration_option_long": "Longo (> 20 minutos)",
"footer_source_code": "Código-fonte", "footer_source_code": "Código-fonte",
@ -378,7 +386,7 @@
"preferences_quality_dash_label": "Qualidade de vídeo DASH preferida: ", "preferences_quality_dash_label": "Qualidade de vídeo DASH preferida: ",
"preferences_quality_option_small": "Baixa", "preferences_quality_option_small": "Baixa",
"preferences_quality_option_hd720": "HD720", "preferences_quality_option_hd720": "HD720",
"preferences_quality_dash_option_auto": "Automático", "preferences_quality_dash_option_auto": "Automática",
"preferences_quality_dash_option_best": "Melhor", "preferences_quality_dash_option_best": "Melhor",
"preferences_quality_dash_option_4320p": "4320p", "preferences_quality_dash_option_4320p": "4320p",
"preferences_quality_dash_option_2160p": "2160p", "preferences_quality_dash_option_2160p": "2160p",
@ -389,7 +397,7 @@
"preferences_quality_dash_option_144p": "144p", "preferences_quality_dash_option_144p": "144p",
"search_filters_features_option_purchased": "Comprado", "search_filters_features_option_purchased": "Comprado",
"search_filters_features_option_three_sixty": "360°", "search_filters_features_option_three_sixty": "360°",
"videoinfo_invidious_embed_link": "Incorporar hiperligação", "videoinfo_invidious_embed_link": "Incorporar ligação",
"Video unavailable": "Vídeo não disponível", "Video unavailable": "Vídeo não disponível",
"invidious": "Invidious", "invidious": "Invidious",
"preferences_quality_option_medium": "Média", "preferences_quality_option_medium": "Média",
@ -400,33 +408,41 @@
"preferences_quality_dash_option_worst": "Pior", "preferences_quality_dash_option_worst": "Pior",
"none": "nenhum", "none": "nenhum",
"videoinfo_youTube_embed_link": "Incorporar", "videoinfo_youTube_embed_link": "Incorporar",
"preferences_save_player_pos_label": "Guardar a posição de reprodução atual do vídeo: ", "preferences_save_player_pos_label": "Guardar posição de reprodução: ",
"download_subtitles": "Legendas - `x` (.vtt)", "download_subtitles": "Legendas - `x` (.vtt)",
"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",
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão há `x`", "videoinfo_started_streaming_x_ago": "Iniciou a transmissão há `x`",
"user_saved_playlists": "`x` listas de reprodução guardadas", "user_saved_playlists": "`x` listas de reprodução guardadas",
"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",
"subscriptions_unseen_notifs_count": "{{count}} notificação não vista", "generic_playlists_count_1": "{{count}} listas de reprodução",
"subscriptions_unseen_notifs_count_plural": "{{count}} notificações não vistas", "generic_playlists_count_2": "{{count}} listas de reprodução",
"comments_view_x_replies": "Ver {{count}} resposta", "subscriptions_unseen_notifs_count_0": "{{count}} notificação não vista",
"comments_view_x_replies_plural": "Ver {{count}} respostas", "subscriptions_unseen_notifs_count_1": "{{count}} notificações não vistas",
"generic_subscribers_count": "{{count}} inscrito", "subscriptions_unseen_notifs_count_2": "{{count}} notificações não vistas",
"generic_subscribers_count_plural": "{{count}} inscritos", "comments_view_x_replies_0": "Ver {{count}} resposta",
"generic_subscriptions_count": "{{count}} inscrição", "comments_view_x_replies_1": "Ver {{count}} respostas",
"generic_subscriptions_count_plural": "{{count}} inscrições", "comments_view_x_replies_2": "Ver {{count}} respostas",
"comments_points_count": "{{count}} ponto", "generic_subscribers_count_0": "{{count}} subscritor",
"comments_points_count_plural": "{{count}} pontos", "generic_subscribers_count_1": "{{count}} subscritores",
"generic_subscribers_count_2": "{{count}} subscritores",
"generic_subscriptions_count_0": "{{count}} subscrição",
"generic_subscriptions_count_1": "{{count}} subscrições",
"generic_subscriptions_count_2": "{{count}} subscrições",
"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 encontrou um erro no Invidious!", "crash_page_you_found_a_bug": "Parece que encontrou um erro no Invidious!",
"crash_page_before_reporting": "Antes de reportar um erro, verifique se:", "crash_page_before_reporting": "Antes de reportar um erro, verifique se:",
"crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>", "crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>",
"crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>", "crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>",
"crash_page_read_the_faq": "leia as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>", "crash_page_read_the_faq": "leu as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>",
"crash_page_search_issue": "procurou se <a href=\"`x`\">o erro já foi reportado no GitHub</a>", "crash_page_search_issue": "procurou se <a href=\"`x`\">o erro já foi reportado no GitHub</a>",
"crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto tal qual (NÃO o traduza):", "crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto (NÃO o traduza):",
"user_created_playlists": "`x` listas de reprodução criadas", "user_created_playlists": "`x` listas de reprodução criadas",
"search_filters_title": "Filtro", "search_filters_title": "Filtro",
"Chinese (Taiwan)": "Chinês (Taiwan)", "Chinese (Taiwan)": "Chinês (Taiwan)",
@ -464,11 +480,11 @@
"search_filters_type_option_all": "Qualquer tipo", "search_filters_type_option_all": "Qualquer tipo",
"search_filters_duration_option_none": "Qualquer duração", "search_filters_duration_option_none": "Qualquer duração",
"Popular enabled: ": "Página \"popular\" ativada: ", "Popular enabled: ": "Página \"popular\" ativada: ",
"error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para a página inicial da lista de reprodução.</a>", "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para voltar à página inicial da lista de reprodução.</a>",
"channel_tab_playlists_label": "Listas de reprodução", "channel_tab_playlists_label": "Listas de reprodução",
"channel_tab_channels_label": "Canais", "channel_tab_channels_label": "Canais",
"channel_tab_shorts_label": "Curtos", "channel_tab_shorts_label": "Curtos",
"channel_tab_streams_label": "Diretos", "channel_tab_streams_label": "Emissões em direto",
"Music in this video": "Música neste vídeo", "Music in this video": "Música neste vídeo",
"Artist: ": "Artista: ", "Artist: ": "Artista: ",
"Album: ": "Álbum: ", "Album: ": "Álbum: ",
@ -477,12 +493,25 @@
"Standard YouTube license": "Licença padrão do YouTube", "Standard YouTube license": "Licença padrão do YouTube",
"Download is disabled": "A descarga está desativada", "Download is disabled": "A descarga está desativada",
"Import YouTube playlist (.csv)": "Importar lista de reprodução do YouTube (.csv)", "Import YouTube playlist (.csv)": "Importar lista de reprodução do YouTube (.csv)",
"generic_button_delete": "Deletar", "generic_button_delete": "Eliminar",
"generic_button_edit": "Editar", "generic_button_edit": "Editar",
"generic_button_rss": "RSS", "generic_button_rss": "RSS",
"channel_tab_podcasts_label": "Podcasts", "channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Lançamentos", "channel_tab_releases_label": "Lançamentos",
"generic_button_save": "Salvar", "generic_button_save": "Guardar",
"generic_button_cancel": "Cancelar", "generic_button_cancel": "Cancelar",
"playlist_button_add_items": "Adicionar vídeos" "playlist_button_add_items": "Adicionar vídeos",
"generic_channels_count_0": "{{count}} canal",
"generic_channels_count_1": "{{count}} canais",
"generic_channels_count_2": "{{count}} canais",
"Import YouTube watch history (.json)": "Importar histórico de reprodução do YouTube (.json)",
"toggle_theme": "Trocar tema",
"Add to playlist": "Adicionar à lista de reprodução",
"Add to playlist: ": "Adicionar à lista de reprodução: ",
"Answer": "Resposta",
"Search for videos": "Procurar vídeos",
"carousel_slide": "Diapositivo {{current}} de{{total}}",
"carousel_skip": "Ignorar carrossel",
"carousel_go_to": "Ir para o diapositivo`x`",
"The Popular feed has been disabled by the administrator.": "O feed Popular foi desativado por um administrador."
} }

View file

@ -478,5 +478,6 @@
"search_filters_type_option_all": "orice tip", "search_filters_type_option_all": "orice tip",
"preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_240p": "240p",
"preferences_quality_dash_option_144p": "144p", "preferences_quality_dash_option_144p": "144p",
"Show less": "Afișați mai puțin" "Show less": "Afișați mai puțin",
"Add to playlist": "Adaugă la playlist"
} }

View file

@ -8,14 +8,14 @@
"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": "Импорт и экспорт данных",
@ -29,7 +29,7 @@
"Export subscriptions as OPML": "Экспортировать подписки в формате OPML", "Export subscriptions as OPML": "Экспортировать подписки в формате OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в формате OPML (для NewPipe и FreeTube)", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в формате OPML (для NewPipe и FreeTube)",
"Export data as JSON": "Экспортировать данные Invidious в формате 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",
@ -42,7 +42,7 @@
"Text CAPTCHA": "Текстовая капча (англ.)", "Text CAPTCHA": "Текстовая капча (англ.)",
"Image CAPTCHA": "Капча-картинка", "Image CAPTCHA": "Капча-картинка",
"Sign In": "Войти", "Sign In": "Войти",
"Register": "Зарегистрироваться", "Register": "Регистрация",
"E-mail": "Эл. почта", "E-mail": "Эл. почта",
"Preferences": "Настройки", "Preferences": "Настройки",
"preferences_category_player": "Настройки проигрывателя", "preferences_category_player": "Настройки проигрывателя",
@ -61,7 +61,7 @@
"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_extend_desc_label": "Автоматически раскрывать описание видео: ", "preferences_extend_desc_label": "Автоматически раскрывать описание видео: ",
"preferences_vr_mode_label": "Интерактивные 360-градусные видео (необходим WebGL): ", "preferences_vr_mode_label": "Интерактивные 360-градусные видео (необходим WebGL): ",
"preferences_category_visual": "Настройки сайта", "preferences_category_visual": "Настройки сайта",
@ -77,13 +77,13 @@
"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": "Показывать только непросмотренные видео: ",
@ -134,8 +134,8 @@
"Title": "Заголовок", "Title": "Заголовок",
"Playlist privacy": "Видимость плейлиста", "Playlist privacy": "Видимость плейлиста",
"Editing playlist `x`": "Редактирование плейлиста `x`", "Editing playlist `x`": "Редактирование плейлиста `x`",
"Show more": "Развернуть", "Show more": "Показать больше",
"Show less": "Свернуть", "Show less": "Показать меньше",
"Watch on YouTube": "Смотреть на YouTube", "Watch on YouTube": "Смотреть на YouTube",
"Switch Invidious Instance": "Сменить зеркало Invidious", "Switch Invidious Instance": "Сменить зеркало Invidious",
"Hide annotations": "Скрыть аннотации", "Hide annotations": "Скрыть аннотации",
@ -414,7 +414,7 @@
"generic_count_days_0": "{{count}} день", "generic_count_days_0": "{{count}} день",
"generic_count_days_1": "{{count}} дня", "generic_count_days_1": "{{count}} дня",
"generic_count_days_2": "{{count}} дней", "generic_count_days_2": "{{count}} дней",
"preferences_quality_dash_option_auto": "Автоматическое", "preferences_quality_dash_option_auto": "Авто",
"preferences_quality_dash_option_1080p": "1080p", "preferences_quality_dash_option_1080p": "1080p",
"preferences_quality_dash_option_720p": "720p", "preferences_quality_dash_option_720p": "720p",
"generic_subscriptions_count_0": "{{count}} подписка", "generic_subscriptions_count_0": "{{count}} подписка",
@ -466,7 +466,7 @@
"search_filters_features_option_three_sixty": "360°", "search_filters_features_option_three_sixty": "360°",
"Video unavailable": "Видео недоступно", "Video unavailable": "Видео недоступно",
"preferences_save_player_pos_label": "Запоминать позицию: ", "preferences_save_player_pos_label": "Запоминать позицию: ",
"preferences_region_label": "Страна: ", "preferences_region_label": "Страна источник ",
"preferences_watch_history_label": "Включить историю просмотров: ", "preferences_watch_history_label": "Включить историю просмотров: ",
"search_filters_title": "Фильтр", "search_filters_title": "Фильтр",
"search_filters_duration_option_none": "Любой длины", "search_filters_duration_option_none": "Любой длины",
@ -476,7 +476,7 @@
"search_message_no_results": "Ничего не найдено.", "search_message_no_results": "Ничего не найдено.",
"search_message_use_another_instance": " Дополнительно вы можете <a href=\"`x`\">поискать на других зеркалах</a>.", "search_message_use_another_instance": " Дополнительно вы можете <a href=\"`x`\">поискать на других зеркалах</a>.",
"search_filters_features_option_vr180": "VR180", "search_filters_features_option_vr180": "VR180",
"search_message_change_filters_or_query": "Попробуйте расширить поисковый запрос или изменить фильтры.", "search_message_change_filters_or_query": "Попробуйте расширить поисковый запрос или изменить фильтры.",
"search_filters_duration_option_medium": "Средние (4 - 20 минут)", "search_filters_duration_option_medium": "Средние (4 - 20 минут)",
"search_filters_apply_button": "Применить фильтры", "search_filters_apply_button": "Применить фильтры",
"Popular enabled: ": "Популярное включено: ", "Popular enabled: ": "Популярное включено: ",
@ -503,5 +503,6 @@
"channel_tab_podcasts_label": "Подкасты", "channel_tab_podcasts_label": "Подкасты",
"generic_channels_count_0": "{{count}} канал", "generic_channels_count_0": "{{count}} канал",
"generic_channels_count_1": "{{count}} канала", "generic_channels_count_1": "{{count}} канала",
"generic_channels_count_2": "{{count}} каналов" "generic_channels_count_2": "{{count}} каналов",
"Import YouTube watch history (.json)": "Импортировать историю просмотра из YouTube (.json)"
} }

View file

@ -520,5 +520,6 @@
"generic_channels_count_0": "{{count}} kanal", "generic_channels_count_0": "{{count}} kanal",
"generic_channels_count_1": "{{count}} kanala", "generic_channels_count_1": "{{count}} kanala",
"generic_channels_count_2": "{{count}} kanali", "generic_channels_count_2": "{{count}} kanali",
"generic_channels_count_3": "{{count}} kanalov" "generic_channels_count_3": "{{count}} kanalov",
"Import YouTube watch history (.json)": "Uvozi zgodovino gledanja YouTube (.json)"
} }

View file

@ -79,7 +79,7 @@
"invidious": "Invidious", "invidious": "Invidious",
"preferences_captions_label": "Titra parazgjedhje: ", "preferences_captions_label": "Titra parazgjedhje: ",
"preferences_extend_desc_label": "Zgjero automatikisht përshkrimin e videos: ", "preferences_extend_desc_label": "Zgjero automatikisht përshkrimin e videos: ",
"preferences_player_style_label": "Silt lojtësi: ", "preferences_player_style_label": "Stil lojtësi: ",
"Dark mode: ": "Mënyra e errët: ", "Dark mode: ": "Mënyra e errët: ",
"preferences_dark_mode_label": "Temë: ", "preferences_dark_mode_label": "Temë: ",
"dark": "e errët", "dark": "e errët",
@ -477,5 +477,12 @@
"channel_tab_releases_label": "Hedhje në qarkullim", "channel_tab_releases_label": "Hedhje në qarkullim",
"Song: ": "Pjesë: ", "Song: ": "Pjesë: ",
"Import YouTube playlist (.csv)": "Importoni luajlistë YouTube (.csv)", "Import YouTube playlist (.csv)": "Importoni luajlistë YouTube (.csv)",
"Standard YouTube license": "Licencë YouTube standarde" "Standard YouTube license": "Licencë YouTube standarde",
"published - reverse": "publikuar më - së prapthi",
"channel_tab_podcasts_label": "Podcast-e",
"channel name - reverse": "emër kanali - së prapthi",
"Import YouTube watch history (.json)": "Importo historik parjesh YouTube (.json)",
"preferences_local_label": "Video përmes ndërmjetësi: ",
"Fallback captions: ": "Titra nga halli: ",
"Erroneous challenge": "Zgjidhje e gabuar"
} }

View file

@ -21,7 +21,7 @@
"Import and Export Data": "Uvoz i izvoz podataka", "Import and Export Data": "Uvoz i izvoz podataka",
"Import": "Uvezi", "Import": "Uvezi",
"Import Invidious data": "Uvezi Invidious JSON podatke", "Import Invidious data": "Uvezi Invidious JSON podatke",
"Import YouTube subscriptions": "Uvezi YouTube/OPML praćenja", "Import YouTube subscriptions": "Uvezi YouTube CSV ili OPML praćenja",
"Import FreeTube subscriptions (.db)": "Uvezi FreeTube praćenja (.db)", "Import FreeTube subscriptions (.db)": "Uvezi FreeTube praćenja (.db)",
"Import NewPipe subscriptions (.json)": "Uvezi NewPipe praćenja (.json)", "Import NewPipe subscriptions (.json)": "Uvezi NewPipe praćenja (.json)",
"Import NewPipe data (.zip)": "Uvezi NewPipe podatke (.zip)", "Import NewPipe data (.zip)": "Uvezi NewPipe podatke (.zip)",
@ -503,5 +503,15 @@
"crash_page_you_found_a_bug": "Izgleda da ste pronašli grešku u Invidious-u!", "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_0": "{{count}} pregled",
"generic_views_count_1": "{{count}} pregleda", "generic_views_count_1": "{{count}} pregleda",
"generic_views_count_2": "{{count}} pregleda" "generic_views_count_2": "{{count}} pregleda",
"Import YouTube watch history (.json)": "Uvezi YouTube istoriju gledanja (.json)",
"The Popular feed has been disabled by the administrator.": "Administrator je onemogućio fid „Popularno“.",
"Add to playlist: ": "Dodajte na plejlistu: ",
"Add to playlist": "Dodaj na plejlistu",
"carousel_slide": "Slajd {{current}} od {{total}}",
"carousel_go_to": "Idi na slajd `x`",
"Answer": "Odgovor",
"Search for videos": "Pretražite video snimke",
"carousel_skip": "Preskoči karusel",
"toggle_theme": "Подеси тему"
} }

View file

@ -21,7 +21,7 @@
"Import and Export Data": "Увоз и извоз података", "Import and Export Data": "Увоз и извоз података",
"Import": "Увези", "Import": "Увези",
"Import Invidious data": "Увези Invidious JSON податке", "Import Invidious data": "Увези Invidious JSON податке",
"Import YouTube subscriptions": "Увези YouTube/OPML праћења", "Import YouTube subscriptions": "Увези YouTube CSV или OPML праћења",
"Import FreeTube subscriptions (.db)": "Увези FreeTube праћења (.db)", "Import FreeTube subscriptions (.db)": "Увези FreeTube праћења (.db)",
"Import NewPipe subscriptions (.json)": "Увези NewPipe праћења (.json)", "Import NewPipe subscriptions (.json)": "Увези NewPipe праћења (.json)",
"Import NewPipe data (.zip)": "Увези NewPipe податке (.zip)", "Import NewPipe data (.zip)": "Увези NewPipe податке (.zip)",
@ -503,5 +503,15 @@
"crash_page_you_found_a_bug": "Изгледа да сте пронашли грешку у Invidious-у!", "crash_page_you_found_a_bug": "Изгледа да сте пронашли грешку у Invidious-у!",
"generic_views_count_0": "{{count}} преглед", "generic_views_count_0": "{{count}} преглед",
"generic_views_count_1": "{{count}} прегледа", "generic_views_count_1": "{{count}} прегледа",
"generic_views_count_2": "{{count}} прегледа" "generic_views_count_2": "{{count}} прегледа",
"Import YouTube watch history (.json)": "Увези YouTube историју гледањa (.json)",
"toggle_theme": "Укључи тему",
"Add to playlist": "Додај на плејлисту",
"Answer": "Одговор",
"Search for videos": "Претражите видео снимке",
"carousel_go_to": "Иди на слајд `x`",
"Add to playlist: ": "Додајте на плејлисту: ",
"carousel_skip": "Прескочи карусел",
"The Popular feed has been disabled by the administrator.": "Администратор је онемогућио фид „Популарно“.",
"carousel_slide": "Слајд {{current}} од {{total}}"
} }

View file

@ -20,15 +20,15 @@
"No": "Nej", "No": "Nej",
"Import and Export Data": "Importera och exportera data", "Import and Export Data": "Importera och exportera data",
"Import": "Importera", "Import": "Importera",
"Import Invidious data": "Importera Invidious-data", "Import Invidious data": "Importera Invidious JSON data",
"Import YouTube subscriptions": "Importera YouTube-prenumerationer", "Import YouTube subscriptions": "Importera YouTube/OPML prenumerationer",
"Import FreeTube subscriptions (.db)": "Importera FreeTube-prenumerationer (.db)", "Import FreeTube subscriptions (.db)": "Importera FreeTube-prenumerationer (.db)",
"Import NewPipe subscriptions (.json)": "Importera NewPipe-prenumerationer (.json)", "Import NewPipe subscriptions (.json)": "Importera NewPipe-prenumerationer (.json)",
"Import NewPipe data (.zip)": "Importera NewPipe-data (.zip)", "Import NewPipe data (.zip)": "Importera NewPipe-data (.zip)",
"Export": "Exportera", "Export": "Exportera",
"Export subscriptions as OPML": "Exportera prenumerationer som OPML", "Export subscriptions as OPML": "Exportera prenumerationer som OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportera prenumerationer som OPML (för NewPipe och FreeTube)", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportera prenumerationer som OPML (för NewPipe och FreeTube)",
"Export data as JSON": "Exportera data som JSON", "Export data as JSON": "Exportera Invidious data som JSON",
"Delete account?": "Radera konto?", "Delete account?": "Radera konto?",
"History": "Historik", "History": "Historik",
"An alternative front-end to YouTube": "Ett alternativt gränssnitt till YouTube", "An alternative front-end to YouTube": "Ett alternativt gränssnitt till YouTube",
@ -63,7 +63,7 @@
"preferences_related_videos_label": "Visa relaterade videor? ", "preferences_related_videos_label": "Visa relaterade videor? ",
"preferences_annotations_label": "Visa länkar-i-videon som förval? ", "preferences_annotations_label": "Visa länkar-i-videon som förval? ",
"preferences_extend_desc_label": "Förläng videobeskrivning automatiskt: ", "preferences_extend_desc_label": "Förläng videobeskrivning automatiskt: ",
"preferences_vr_mode_label": "Interaktiva 360-gradervideos: ", "preferences_vr_mode_label": "Interaktiva 360-gradervideos (kräver WebGL): ",
"preferences_category_visual": "Visuella inställningar", "preferences_category_visual": "Visuella inställningar",
"preferences_player_style_label": "Spelarstil: ", "preferences_player_style_label": "Spelarstil: ",
"Dark mode: ": "Mörkt läge: ", "Dark mode: ": "Mörkt läge: ",
@ -152,7 +152,7 @@
"View YouTube comments": "Visa YouTube-kommentarer", "View YouTube comments": "Visa YouTube-kommentarer",
"View more comments on Reddit": "Visa flera kommentarer på Reddit", "View more comments on Reddit": "Visa flera kommentarer på Reddit",
"View `x` comments": { "View `x` comments": {
"([^.,0-9]|^)1([^.,0-9]|$)": "Visa `x` kommentarer", "([^.,0-9]|^)1([^.,0-9]|$)": "Visa `x` kommentar",
"": "Visa `x` kommentarer" "": "Visa `x` kommentarer"
}, },
"View Reddit comments": "Visa Reddit-kommentarer", "View Reddit comments": "Visa Reddit-kommentarer",
@ -167,7 +167,7 @@
"Wrong username or password": "Ogiltigt användarnamn eller lösenord", "Wrong username or password": "Ogiltigt användarnamn eller lösenord",
"Password cannot be empty": "Lösenordet kan inte vara tomt", "Password cannot be empty": "Lösenordet kan inte vara tomt",
"Password cannot be longer than 55 characters": "Lösenordet kan inte vara längre än 55 tecken", "Password cannot be longer than 55 characters": "Lösenordet kan inte vara längre än 55 tecken",
"Please log in": "Logga in", "Please log in": "Snälla logga in",
"Invidious Private Feed for `x`": "Ogiltig privat flöde för `x`", "Invidious Private Feed for `x`": "Ogiltig privat flöde för `x`",
"channel:`x`": "kanal `x`", "channel:`x`": "kanal `x`",
"Deleted or invalid channel": "Raderad eller ogiltig kanal", "Deleted or invalid channel": "Raderad eller ogiltig kanal",
@ -311,8 +311,8 @@
"%A %B %-d, %Y": "%A %B %-d, %Y", "%A %B %-d, %Y": "%A %B %-d, %Y",
"(edited)": "(redigerad)", "(edited)": "(redigerad)",
"YouTube comment permalink": "Permanent YouTube-länk till innehållet", "YouTube comment permalink": "Permanent YouTube-länk till innehållet",
"permalink": "permalänk", "permalink": "permanent länk",
"`x` marked it with a ❤": "`x` lämnade ett ❤", "`x` marked it with a ❤": "`x` markerade det med ett ❤",
"Audio mode": "Ljudläge", "Audio mode": "Ljudläge",
"Video mode": "Videoläge", "Video mode": "Videoläge",
"channel_tab_videos_label": "Videor", "channel_tab_videos_label": "Videor",
@ -320,30 +320,30 @@
"channel_tab_community_label": "Gemenskap", "channel_tab_community_label": "Gemenskap",
"search_filters_sort_option_relevance": "Relevans", "search_filters_sort_option_relevance": "Relevans",
"search_filters_sort_option_rating": "Rankning", "search_filters_sort_option_rating": "Rankning",
"search_filters_sort_option_date": "Datum", "search_filters_sort_option_date": "Uppladdnings Datum",
"search_filters_sort_option_views": "Visningar", "search_filters_sort_option_views": "Visningar",
"search_filters_type_label": "Typ", "search_filters_type_label": "Typ",
"search_filters_duration_label": "Varaktighet", "search_filters_duration_label": "Varaktighet",
"search_filters_features_label": "Funktioner", "search_filters_features_label": "Funktioner",
"search_filters_sort_label": "Sortera efter", "search_filters_sort_label": "Sortera efter",
"search_filters_date_option_hour": "timme", "search_filters_date_option_hour": "Senaste Timmen",
"search_filters_date_option_today": "idag", "search_filters_date_option_today": "Idag",
"search_filters_date_option_week": "vecka", "search_filters_date_option_week": "Denna vecka",
"search_filters_date_option_month": "månad", "search_filters_date_option_month": "Denna månad",
"search_filters_date_option_year": "år", "search_filters_date_option_year": "Detta år",
"search_filters_type_option_video": "video", "search_filters_type_option_video": "Video",
"search_filters_type_option_channel": "kanal", "search_filters_type_option_channel": "Kanal",
"search_filters_type_option_playlist": "spellista", "search_filters_type_option_playlist": "Spellista",
"search_filters_type_option_movie": "film", "search_filters_type_option_movie": "Film",
"search_filters_type_option_show": "tv-serie", "search_filters_type_option_show": "Serie",
"search_filters_features_option_hd": "hd", "search_filters_features_option_hd": "HD",
"search_filters_features_option_subtitles": "undertexter", "search_filters_features_option_subtitles": "Undertexter/CC",
"search_filters_features_option_c_commons": "creative_commons", "search_filters_features_option_c_commons": "Creative Commons",
"search_filters_features_option_three_d": "3d", "search_filters_features_option_three_d": "3D",
"search_filters_features_option_live": "live", "search_filters_features_option_live": "Live",
"search_filters_features_option_four_k": "4k", "search_filters_features_option_four_k": "4K",
"search_filters_features_option_location": "plats", "search_filters_features_option_location": "Plats",
"search_filters_features_option_hdr": "hdr", "search_filters_features_option_hdr": "HDR",
"Current version: ": "Nuvarande version: ", "Current version: ": "Nuvarande version: ",
"next_steps_error_message_refresh": "Uppdatera", "next_steps_error_message_refresh": "Uppdatera",
"next_steps_error_message_go_to_youtube": "Gå till Youtube", "next_steps_error_message_go_to_youtube": "Gå till Youtube",
@ -352,5 +352,149 @@
"search_filters_duration_option_long": "Lång (> 20 minuter)", "search_filters_duration_option_long": "Lång (> 20 minuter)",
"footer_documentation": "Dokumentation", "footer_documentation": "Dokumentation",
"search_filters_duration_option_short": "Kort (< 4 minuter)", "search_filters_duration_option_short": "Kort (< 4 minuter)",
"search_filters_title": "Filter" "search_filters_title": "Filter",
"Korean (auto-generated)": "Koreanska (auto-genererad)",
"search_filters_features_option_three_sixty": "360°",
"preferences_quality_dash_option_worst": "Sämst",
"channel_tab_podcasts_label": "Podcaster",
"preferences_save_player_pos_label": "Spara uppspelningsposition: ",
"Spanish (Mexico)": "Spanska (Mexiko)",
"preferences_region_label": "Innehållsland: ",
"generic_subscriptions_count": "{{count}} prenumeration",
"generic_subscriptions_count_plural": "{{count}} prenumerationer",
"search_filters_apply_button": "Använd valda filter",
"Download is disabled": "Nedladdning är inaktiverad",
"comments_points_count": "{{count}} poäng",
"comments_points_count_plural": "{{count}} poäng",
"preferences_quality_dash_option_2160p": "2160p",
"German (auto-generated)": "Tyska (auto-genererad)",
"Japanese (auto-generated)": "Japanska (auto-genererad)",
"preferences_quality_option_medium": "Medium",
"footer_donate_page": "Donera",
"search_message_change_filters_or_query": "Prova att bredda din sökfråga och/eller ändra filtren.",
"crash_page_before_reporting": "Innan du rapporterar en bugg, se till att du har:",
"preferences_quality_dash_option_best": "Bäst",
"Channel Sponsor": "Kanal Sponsor",
"generic_videos_count": "{{count}} video",
"generic_videos_count_plural": "{{count}} videor",
"videoinfo_started_streaming_x_ago": "Började sända `x` sedan",
"videoinfo_youTube_embed_link": "Bädda in",
"channel_tab_streams_label": "Livesändningar",
"playlist_button_add_items": "Lägg till videor",
"generic_count_minutes": "{{count}}minut",
"generic_count_minutes_plural": "{{count}}minuter",
"preferences_quality_dash_option_720p": "720p",
"preferences_watch_history_label": "Aktivera visningshistorik: ",
"user_saved_playlists": "`x` sparade spellistor",
"Spanish (Spain)": "Spanska (Spanien)",
"invidious": "Invidious",
"crash_page_refresh": "försökte <a href=\"`x`\">uppdatera sidan</a>",
"Chinese (Hong Kong)": "Kinesiska (Hong Kong)",
"Artist: ": "Artist: ",
"generic_count_months": "{{count}}månad",
"generic_count_months_plural": "{{count}}månader",
"search_message_use_another_instance": " Du kan också <a href=\"`x`\">söka på en annan instans</a>.",
"generic_subscribers_count": "{{count}} prenumerant",
"generic_subscribers_count_plural": "{{count}} prenumeranter",
"download_subtitles": "Undertexter - `x` (.vtt)",
"generic_button_save": "Spara",
"crash_page_search_issue": "sökte efter <a href=\"`x`\">befintliga problem på GitHub</a>",
"generic_button_cancel": "Avbryt",
"none": "ingen",
"English (United States)": "English (Förenta staterna)",
"subscriptions_unseen_notifs_count": "{{count}}osedd notifikation",
"subscriptions_unseen_notifs_count_plural": "{{count}}osedda notifikationer",
"Album: ": "Album: ",
"preferences_quality_option_dash": "DASH (adaptiv kvalitet)",
"preferences_quality_dash_option_1080p": "1080p",
"Video unavailable": "Video inte tillgänglig",
"tokens_count": "{{count}}nyckel",
"tokens_count_plural": "{{count}}nycklar",
"Chinese (China)": "Kinesiska (Kina)",
"Italian (auto-generated)": "Italienska (auto-genererad)",
"channel_tab_shorts_label": "Shorts",
"preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_option_360p": "360p",
"search_message_no_results": "Inga resultat hittades.",
"channel_tab_releases_label": "Releaser",
"preferences_quality_dash_option_144p": "144p",
"Interlingue": "Interlingue (auto-genererad)",
"Song: ": "Låt: ",
"generic_channels_count": "{{count}} kanal",
"generic_channels_count_plural": "{{count}} kanaler",
"Chinese (Taiwan)": "Kinesiska (Taiwan)",
"preferences_quality_dash_label": "Önskad DASH-videokvalitet: ",
"adminprefs_modified_source_code_url_label": "URL till modifierad källkodslager",
"Turkish (auto-generated)": "Turkiska (auto-genererad)",
"Indonesian (auto-generated)": "Indonesiska (auto-genererad)",
"Portuguese (auto-generated)": "Portugisiska (auto-genererad)",
"generic_count_years": "{{count}}år",
"generic_count_years_plural": "{{count}}år",
"videoinfo_invidious_embed_link": "Bädda in länk",
"Popular enabled: ": "Populär aktiverad: ",
"Spanish (auto-generated)": "Spanska (auto-genererad)",
"preferences_quality_option_small": "Liten",
"English (United Kingdom)": "Engelska (Storbritannien)",
"channel_tab_playlists_label": "Spellistor",
"generic_button_edit": "Redigera",
"generic_playlists_count": "{{count}} spellista",
"generic_playlists_count_plural": "{{count}} spellistor",
"preferences_quality_option_hd720": "HD720p",
"search_filters_features_option_purchased": "Köpt",
"search_filters_date_option_none": "Vilket datum som helst",
"preferences_quality_dash_option_auto": "Auto",
"Cantonese (Hong Kong)": "Katonesiska (Hong Kong)",
"crash_page_report_issue": "Om inget av ovanstående hjälpte, vänligen <a href=\"`x`\">öppna ett nytt nummer på GitHub</a> (helst på engelska) och inkludera följande text i ditt meddelande (översätt INTE den texten):",
"crash_page_switch_instance": "försökte <a href=\"`x`\">använda en annan instans</a>",
"generic_count_weeks": "{{count}}vecka",
"generic_count_weeks_plural": "{{count}}veckor",
"videoinfo_watch_on_youTube": "Titta på YouTube",
"Music in this video": "Musik i denna video",
"footer_modfied_source_code": "Modifierad källkod",
"generic_button_rss": "RSS",
"preferences_quality_dash_option_4320p": "4320p",
"generic_count_hours": "{{count}}timme",
"generic_count_hours_plural": "{{count}}timmar",
"French (auto-generated)": "Franska (auto-genererad)",
"crash_page_read_the_faq": "läs <a href=\"`x`\">Vanliga frågor (FAQ)</a>",
"user_created_playlists": "`x` skapade spellistor",
"channel_tab_channels_label": "Kanaler",
"search_filters_type_option_all": "Vilken typ som helst",
"Russian (auto-generated)": "Ryska (auto-genererad)",
"preferences_quality_dash_option_480p": "480p",
"comments_view_x_replies": "Se {{count}} svar",
"comments_view_x_replies_plural": "Se {{count}} svar",
"footer_original_source_code": "Ursprunglig källkod",
"Portuguese (Brazil)": "Portugisiska (Brasilien)",
"search_filters_features_option_vr180": "VR180",
"error_video_not_in_playlist": "Den begärda videon finns inte i den här spellistan. <a href=\"`x`\">Klicka här för startsidan för spellistan.</a>",
"Dutch (auto-generated)": "Nederländska (auto-genererad)",
"generic_count_days": "{{count}}dag",
"generic_count_days_plural": "{{count}}dagar",
"Vietnamese (auto-generated)": "Vietnamesiska (auto-genererad)",
"search_filters_duration_option_none": "Vilken varaktighet som helst",
"preferences_quality_dash_option_240p": "240p",
"Chinese": "Kinesiska",
"preferences_automatic_instance_redirect_label": "Automatisk instansomdirigering (återgång till redirect.invidious.io): ",
"generic_button_delete": "Radera",
"Import YouTube playlist (.csv)": "Importera YouTube spellista (.csv)",
"next_steps_error_message": "Därefter bör du försöka: ",
"Standard YouTube license": "Standard YouTube licens",
"Import YouTube watch history (.json)": "Importera YouTube visningshistorik (.json)",
"search_filters_duration_option_medium": "Medium (4 - 20 minuter)",
"generic_count_seconds": "{{count}}sekund",
"generic_count_seconds_plural": "{{count}}sekunder",
"search_filters_date_label": "Uppladdningsdatum",
"crash_page_you_found_a_bug": "Det verkar som att du har hittat en bugg i Invidious!",
"generic_views_count": "{{count}} visning",
"generic_views_count_plural": "{{count}} visningar",
"toggle_theme": "Växla tema",
"Add to playlist": "Lägg till i spellista",
"Add to playlist: ": "Lägg till i spellista: ",
"Answer": "Svara",
"Search for videos": "Sök efter videor",
"The Popular feed has been disabled by the administrator.": "Det populära flödet har inaktiverats av administratören.",
"carousel_slide": "Bildspel {{current}} av {{total}}",
"carousel_skip": "Hoppa över karusellen",
"carousel_go_to": "Gå till bildspel `x`"
} }

7
locales/tk.json Normal file
View file

@ -0,0 +1,7 @@
{
"Add to playlist": "Aýdym sanawyna goş",
"Add to playlist: ": "Pleýliste goş: ",
"Answer": "Jogap",
"Search for videos": "Wideo gözläň",
"The Popular feed has been disabled by the administrator.": "Trende bolan administrator tarapyndan ýapyldy."
}

View file

@ -21,7 +21,7 @@
"Import and Export Data": "Verileri İçe ve Dışa Aktar", "Import and Export Data": "Verileri İçe ve Dışa Aktar",
"Import": "İçe Aktar", "Import": "İçe Aktar",
"Import Invidious data": "Invidious JSON Verilerini İçe Aktar", "Import Invidious data": "Invidious JSON Verilerini İçe Aktar",
"Import YouTube subscriptions": "YouTube/OPML Aboneliklerini İçe Aktar", "Import YouTube subscriptions": "YouTube CSV veya OPML Aboneliklerini İçe Aktar",
"Import FreeTube subscriptions (.db)": "FreeTube Aboneliklerini İçe Aktar (.db)", "Import FreeTube subscriptions (.db)": "FreeTube Aboneliklerini İçe Aktar (.db)",
"Import NewPipe subscriptions (.json)": "NewPipe Aboneliklerini İçe Aktar (.json)", "Import NewPipe subscriptions (.json)": "NewPipe Aboneliklerini İçe Aktar (.json)",
"Import NewPipe data (.zip)": "NewPipe Verilerini İçe Aktar (.zip)", "Import NewPipe data (.zip)": "NewPipe Verilerini İçe Aktar (.zip)",
@ -486,5 +486,15 @@
"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": "{{count}} kanal",
"generic_channels_count_plural": "{{count}} kanal" "generic_channels_count_plural": "{{count}} kanal",
"Import YouTube watch history (.json)": "YouTube İzleme Geçmişini İçe Aktar (.json)",
"toggle_theme": "Temayı Değiştir",
"Add to playlist": "Oynatma listesine ekle",
"Add to playlist: ": "Oynatma listesine ekle: ",
"Answer": "Yanıt",
"Search for videos": "Video ara",
"carousel_slide": "Sunum {{current}} / {{total}}",
"carousel_skip": "Kayar menüyü atla",
"carousel_go_to": "`x` sunumuna git",
"The Popular feed has been disabled by the administrator.": "Popüler akışı yönetici tarafından devre dışı bırakıldı."
} }

View file

@ -127,7 +127,7 @@
"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": "Показати анотації",
@ -503,5 +503,15 @@
"generic_button_save": "Зберегти", "generic_button_save": "Зберегти",
"generic_channels_count_0": "{{count}} канал", "generic_channels_count_0": "{{count}} канал",
"generic_channels_count_1": "{{count}} канали", "generic_channels_count_1": "{{count}} канали",
"generic_channels_count_2": "{{count}} каналів" "generic_channels_count_2": "{{count}} каналів",
"Import YouTube watch history (.json)": "Імпортувати історію переглядів YouTube (.json)",
"toggle_theme": "Перемкнути тему",
"Add to playlist": "Додати до списку відтворення",
"Add to playlist: ": "Додати до списку відтворення: ",
"Answer": "Відповідь",
"Search for videos": "Шукати відео",
"The Popular feed has been disabled by the administrator.": "Стрічка Популярні вимкнена адміністратором.",
"carousel_slide": "Слайд {{current}} з {{total}}",
"carousel_skip": "Пропустити карусель",
"carousel_go_to": "Перейти до слайда `x`"
} }

View file

@ -1,62 +1,62 @@
{ {
"generic_videos_count_0": "{{count}} video", "generic_videos_count_0": "{{count}} video",
"generic_subscribers_count_0": "{{count}} người theo dõi", "generic_subscribers_count_0": "{{count}} người đăng ký",
"LIVE": "TRỰC TIẾP", "LIVE": "TRỰC TIẾP",
"Shared `x` ago": "Đã chia sẻ `x` trước", "Shared `x` ago": "Đã chia sẻ `x` trước",
"Unsubscribe": "Hủy theo dõi", "Unsubscribe": "Hủy đăng ký",
"Subscribe": "Theo dõi", "Subscribe": "Đăng ký",
"View channel on YouTube": "Xem kênh trên YouTube", "View channel on YouTube": "Xem kênh trên YouTube",
"View playlist on YouTube": "Xem danh sách phát trên YouTube", "View playlist on YouTube": "Xem danh sách phát trên YouTube",
"newest": "mới nhất", "newest": "Mới nhất",
"oldest": "lâu đời nhất", "oldest": " nhất",
"popular": "phổ biến", "popular": "Phổ biến",
"last": "Cuối cùng", "last": "cuối cùng",
"Next page": "Trang tiếp theo", "Next page": "Trang tiếp theo",
"Previous page": "Trang trước", "Previous page": "Trang trước",
"Clear watch history?": "Xóa lịch sử xem?", "Clear watch history?": "Xóa lịch sử xem?",
"New password": "Mật khẩu mới", "New password": "Mật khẩu mới",
"New passwords must match": "Mật khẩu mới phải khớp", "New passwords must match": "Mật khẩu mới phải khớp",
"Authorize token?": "Cấp phép mã thông báo?", "Authorize token?": "Cấp phép mã thông báo?",
"Authorize token for `x`?": "Cấp phép mã thông báo cho` x`?", "Authorize token for `x`?": "Cấp phép mã thông báo cho `x`?",
"Yes": "Đúng", "Yes": "",
"No": "Không", "No": "Không",
"Import and Export Data": "Nhập và xuất dữ liệu", "Import and Export Data": "Nhập và xuất dữ liệu",
"Import": "Nhập", "Import": "Nhập",
"Import Invidious data": "Nhập dữ liệu Invidious JSON", "Import Invidious data": "Nhập dữ liệu Invidious dưới dạng JSON",
"Import YouTube subscriptions": "Nhập dữ liệu thuê bao YouTube/OPML", "Import YouTube subscriptions": "Nhập các kênh đã đăng ký từ YouTube/OPML",
"Import FreeTube subscriptions (.db)": "Nhập đăng ký FreeTube (.db)", "Import FreeTube subscriptions (.db)": "Nhập các kênh đã đăng ký từ FreeTube (.db)",
"Import NewPipe subscriptions (.json)": "Nhập đăng ký NewPipe (.json)", "Import NewPipe subscriptions (.json)": "Nhập các kênh đã đăng ký từ NewPipe (.json)",
"Import NewPipe data (.zip)": "Nhập dữ liệu NewPipe (.zip)", "Import NewPipe data (.zip)": "Nhập dữ liệu từ NewPipe (.zip)",
"Export": "Xuất", "Export": "Xuất",
"Export subscriptions as OPML": "Xuất đăng ký dưới dạng OPML", "Export subscriptions as OPML": "Xuất các kênh đã đăng ký dưới dạng OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Xuất đăng ký dưới dạng OPML (cho NewPipe & FreeTube)", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Xuất các kênh đã đăng ký dưới dạng OPML (cho NewPipe & FreeTube)",
"Export data as JSON": "Xuất dữ liệu Invidious dưới dạng JSON", "Export data as JSON": "Xuất dữ liệu Invidious dưới dạng JSON",
"Delete account?": "Xóa tài khoản?", "Delete account?": "Xóa tài khoản?",
"History": "Lịch sử", "History": "Lịch sử",
"An alternative front-end to YouTube": "Giao diện người dùng thay thế cho YouTube", "An alternative front-end to YouTube": "Giao diện thay thế cho YouTube",
"JavaScript license information": "Thông tin giấy phép JavaScript", "JavaScript license information": "Thông tin giấy phép JavaScript",
"source": "nguồn", "source": "nguồn",
"Log in": "Đăng nhập", "Log in": "Đăng nhập",
"Log in/register": "Đăng nhập / đăng ký", "Log in/register": "Đăng nhập / đăng ký",
"User ID": "Tên người dùng", "User ID": "Mã nhận dạng người dùng",
"Password": "Mật khẩu", "Password": "Mật khẩu",
"Time (h:mm:ss):": "Thời gian (h: mm: ss):", "Time (h:mm:ss):": "Thời gian (h:mm:ss):",
"Text CAPTCHA": "Nhắn tin tới CAPTCHA", "Text CAPTCHA": "CAPTCHA dạng chữ",
"Image CAPTCHA": "Hình ảnh CAPTCHA", "Image CAPTCHA": "CAPTCHA dạng ảnh",
"Sign In": "Đăng nhập", "Sign In": "Đăng nhập",
"Register": "Đăng ký", "Register": "Đăng ký",
"E-mail": "E-mail", "E-mail": "E-mail",
"Preferences": "Sở thích", "Preferences": "Cài đặt",
"preferences_category_player": "Tùy chọn trình phát video", "preferences_category_player": "Tùy chọn trình phát video",
"preferences_video_loop_label": "Luôn lặp lại: ", "preferences_video_loop_label": "Luôn lặp lại: ",
"preferences_autoplay_label": "Tự chạy: ", "preferences_autoplay_label": "Tự động phát: ",
"preferences_continue_label": "Phát kế tiếp theo mặc định: ", "preferences_continue_label": "Phát kế tiếp theo mặc định: ",
"preferences_continue_autoplay_label": "Tự động phát video tiếp theo: ", "preferences_continue_autoplay_label": "Tự động phát video tiếp theo: ",
"preferences_listen_label": "Nghe theo mặc định: ", "preferences_listen_label": "Nghe theo mặc định: ",
"preferences_local_label": "Video proxy: ", "preferences_local_label": "Máy chủ sử lý video: ",
"preferences_speed_label": "Tốc độ mặc định: ", "preferences_speed_label": "Tốc độ mặc định: ",
"preferences_quality_label": "Chất lượng video ưa thích: ", "preferences_quality_label": "Chất lượng video: ",
"preferences_volume_label": "Âm lượng trình phát video: ", "preferences_volume_label": "Âm lượng video: ",
"preferences_comments_label": "Nhận xét mặc định: ", "preferences_comments_label": "Nhận xét mặc định: ",
"youtube": "YouTube", "youtube": "YouTube",
"reddit": "Reddit", "reddit": "Reddit",
@ -64,7 +64,7 @@
"Fallback captions: ": "Phụ đề dự phòng: ", "Fallback captions: ": "Phụ đề dự phòng: ",
"preferences_related_videos_label": "Hiển thị các video có liên quan: ", "preferences_related_videos_label": "Hiển thị các video có liên quan: ",
"preferences_annotations_label": "Hiển thị chú thích theo mặc định: ", "preferences_annotations_label": "Hiển thị chú thích theo mặc định: ",
"preferences_extend_desc_label": "Tự động mở rộng mô tả video: ", "preferences_extend_desc_label": "Tự động mở rộng phần mô tả của video: ",
"preferences_vr_mode_label": "Video 360 độ tương tác (yêu cầu WebGL): ", "preferences_vr_mode_label": "Video 360 độ tương tác (yêu cầu WebGL): ",
"preferences_category_visual": "Tùy chọn hình ảnh", "preferences_category_visual": "Tùy chọn hình ảnh",
"preferences_player_style_label": "Phong cách trình phát: ", "preferences_player_style_label": "Phong cách trình phát: ",
@ -82,24 +82,24 @@
"preferences_sort_label": "Sắp xếp video theo: ", "preferences_sort_label": "Sắp xếp video theo: ",
"published": "được phát hành", "published": "được phát hành",
"published - reverse": "đã xuất bản - đảo ngược", "published - reverse": "đã xuất bản - đảo ngược",
"alphabetically": "theo thứ tự bảng chữ cái", "alphabetically": "Thứ tự (A - Z)",
"alphabetically - reverse": "theo thứ tự bảng chữ cái - đảo ngược", "alphabetically - reverse": "Thứ tự (Z - A)",
"channel name": "Tên kênh", "channel name": "Tên kênh (A - Z)",
"channel name - reverse": "tên kênh - đảo ngược", "channel name - reverse": "Tên kênh (Z - A)",
"Only show latest video from channel: ": "Chỉ hiển thị video mới nhất từ kênh: ", "Only show latest video from channel: ": "Chỉ hiển thị video mới nhất từ kênh: ",
"Only show latest unwatched video from channel: ": "Chỉ hiển thị video chưa xem mới nhất từ kênh: ", "Only show latest unwatched video from channel: ": "Chỉ hiển thị video chưa xem mới nhất từ kênh: ",
"preferences_unseen_only_label": "Chỉ hiển thị chưa xem: ", "preferences_unseen_only_label": "Chỉ hiển thị các video chưa từng xem: ",
"preferences_notifications_only_label": "Chỉ hiển thị thông báo (nếu có): ", "preferences_notifications_only_label": "Chỉ hiển thị thông báo (nếu có): ",
"Enable web notifications": "Bật thông báo web", "Enable web notifications": "Bật thông báo web",
"`x` uploaded a video": "` x` đã tải lên một video", "`x` uploaded a video": "`x` đã tải lên một video",
"`x` is live": "` x` đang phát trực tiếp", "`x` is live": "`x` đang phát trực tiếp",
"preferences_category_data": "Tùy chọn dữ liệu", "preferences_category_data": "Tùy chọn dữ liệu",
"Clear watch history": "Xóa lịch sử xem", "Clear watch history": "Xóa lịch sử xem",
"Import/export data": "Nhập / xuất dữ liệu", "Import/export data": "Nhập / xuất dữ liệu",
"Change password": "Đổi mật khẩu", "Change password": "Đổi mật khẩu",
"Manage subscriptions": "Quản lý các mục đăng kí", "Manage subscriptions": "Quản lý các mục đăng kí",
"Manage tokens": "Quản lý mã thông báo", "Manage tokens": "Quản lý mã thông báo",
"Watch history": "Lịch sử xem", "Watch history": "Xem lịch sử",
"Delete account": "Xóa tài khoản", "Delete account": "Xóa tài khoản",
"preferences_category_admin": "Tùy chọn quản trị viên", "preferences_category_admin": "Tùy chọn quản trị viên",
"preferences_default_home_label": "Trang chủ mặc định: ", "preferences_default_home_label": "Trang chủ mặc định: ",
@ -121,7 +121,7 @@
"View privacy policy.": "Xem chính sách bảo mật.", "View privacy policy.": "Xem chính sách bảo mật.",
"Trending": "Xu hướng", "Trending": "Xu hướng",
"Public": "Công khai", "Public": "Công khai",
"Unlisted": "Không hiển thị", "Unlisted": "Không công khai",
"Private": "Riêng tư", "Private": "Riêng tư",
"View all playlists": "Xem tất cả danh sách phát", "View all playlists": "Xem tất cả danh sách phát",
"Updated `x` ago": "Đã cập nhật` x` trước", "Updated `x` ago": "Đã cập nhật` x` trước",
@ -131,24 +131,24 @@
"Title": "Tiêu đề", "Title": "Tiêu đề",
"Playlist privacy": "Bảo mật danh sách phát", "Playlist privacy": "Bảo mật danh sách phát",
"Editing playlist `x`": "Chỉnh sửa danh sách phát` x`", "Editing playlist `x`": "Chỉnh sửa danh sách phát` x`",
"Show more": "Cho xem nhiều hơn", "Show more": "Hiển thị thêm",
"Show less": "Hiện ít hơn", "Show less": "Hiển thị ít hơn",
"Watch on YouTube": "Xem trên YouTube", "Watch on YouTube": "Xem trên YouTube",
"Switch Invidious Instance": "Chuyển phiên bản Invidious", "Switch Invidious Instance": "Chuyển phiên bản Invidious",
"Hide annotations": "Ẩn chú thích", "Hide annotations": "Ẩn chú thích",
"Show annotations": "Hiển thị chú thích", "Show annotations": "Hiển thị chú thích",
"Genre: ": "Thể loại: ", "Genre: ": "Thể loại: ",
"License: ": "Giấy phép: ", "License: ": "Giấy phép: ",
"Family friendly? ": "Gia đình thân thiện? ", "Family friendly? ": "Thân thiện với gia đình? ",
"Wilson score: ": "Điểm số Wilson: ", "Wilson score: ": "Điểm số Wilson: ",
"Engagement: ": "Hôn ước: ", "Engagement: ": "Hôn ước: ",
"Whitelisted regions: ": "Các vùng nằm trong danh sách trắng: ", "Whitelisted regions: ": "Các vùng nằm trong danh sách trắng: ",
"Blacklisted regions: ": "Khu vực nằm trong danh sách đen: ", "Blacklisted regions: ": "Các vùng nằm trong danh sách đen: ",
"Shared `x`": "Chia sẻ` x`", "Shared `x`": "Chia sẻ` x`",
"View Reddit comments": "Xem nhận xét trên Reddit", "View Reddit comments": "Xem nh luận trên Reddit",
"Hide replies": "Ẩn câu trả lời", "Hide replies": "Ẩn phản hồi",
"Show replies": "Hiển thị câu trả lời", "Show replies": "Hiển thị phản hồi",
"Incorrect password": "Mật khẩu không đúng", "Incorrect password": "Mật khẩu không chính xác",
"Wrong answer": "Câu trả lời sai", "Wrong answer": "Câu trả lời sai",
"Erroneous CAPTCHA": "CAPTCHA bị lỗi", "Erroneous CAPTCHA": "CAPTCHA bị lỗi",
"CAPTCHA is a required field": "CAPTCHA là trường bắt buộc", "CAPTCHA is a required field": "CAPTCHA là trường bắt buộc",
@ -190,35 +190,35 @@
"Bulgarian": "Tiếng Bungari", "Bulgarian": "Tiếng Bungari",
"Burmese": "Tiếng Miến Điện", "Burmese": "Tiếng Miến Điện",
"Catalan": "Tiếng Catalan", "Catalan": "Tiếng Catalan",
"Cebuano": "Cebuano", "Cebuano": "Tiếng Cebu",
"Chinese (Simplified)": "Tiếng Trung (Giản thể)", "Chinese (Simplified)": "Tiếng Trung (Giản thể)",
"Chinese (Traditional)": "Tiếng Trung (Phồn thể)", "Chinese (Traditional)": "Tiếng Trung (Phồn thể)",
"Corsican": "Corsican", "Corsican": "Tiếng Corse",
"Croatian": "Tiếng Croatia", "Croatian": "Tiếng Croatia",
"Czech": "Tiếng Séc", "Czech": "Tiếng Séc",
"Danish": "Người Đan Mạch", "Danish": "Tiếng Đan Mạch",
"Dutch": "Tiếng Hà Lan", "Dutch": "Tiếng Hà Lan",
"Esperanto": "Quốc tế ngữ", "Esperanto": "Quốc tế ngữ",
"Estonian": "Tiếng Estonia", "Estonian": "Tiếng Estonia",
"Filipino": "Filipino", "Filipino": "Tiếng Philippines",
"Finnish": "Tiếng Phần Lan", "Finnish": "Tiếng Phần Lan",
"French": "Người Pháp", "French": "Tiếng Pháp",
"Galician": "Tiếng Galicia", "Galician": "Tiếng Galicia",
"Georgian": "Tiếng Georgia", "Georgian": "Tiếng Georgia",
"German": "Tiếng Đức", "German": "Tiếng Đức",
"Greek": "Người Hy Lạp", "Greek": "Tiếng Hy Lạp",
"Gujarati": "Gujarati", "Gujarati": "Tiếng Gujarat",
"Haitian Creole": "Tiếng Creole của Haiti", "Haitian Creole": "Tiếng Creole (Haiti)",
"Hausa": "Hausa", "Hausa": "Tiếng Hausa",
"Hawaiian": "Tiếng Hawaii", "Hawaiian": "Tiếng Hawaii",
"Hebrew": "Tiếng Do Thái", "Hebrew": "Tiếng Do Thái",
"Hindi": "Tiếng Hindi", "Hindi": "Tiếng Hindi",
"Hmong": "Hmong", "Hmong": "Tiếng Hmong",
"Hungarian": "Người Hungary", "Hungarian": "Tiếng Hungary",
"Icelandic": "Tiếng Iceland", "Icelandic": "Tiếng Iceland",
"Igbo": "Igbo", "Igbo": "Tiếng Igbo",
"Indonesian": "Tiếng Indonesia", "Indonesian": "Tiếng Indonesia",
"Irish": "Tiếng Ailen", "Irish": "Tiếng Ireland",
"Italian": "Tiếng Ý", "Italian": "Tiếng Ý",
"Japanese": "Tiếng Nhật", "Japanese": "Tiếng Nhật",
"Javanese": "Tiếng Java", "Javanese": "Tiếng Java",
@ -237,37 +237,37 @@
"Malagasy": "Tiếng Malagasy", "Malagasy": "Tiếng Malagasy",
"Malay": "Tiếng Mã Lai", "Malay": "Tiếng Mã Lai",
"Malayalam": "Tiếng Malayalam", "Malayalam": "Tiếng Malayalam",
"Maltese": "Cây nho", "Maltese": "Tiếng Malta",
"Maori": "Tiếng Maori", "Maori": "Tiếng Maori",
"Marathi": "Marathi", "Marathi": "Tiếng Marathi",
"Mongolian": "Tiếng Mông Cổ", "Mongolian": "Tiếng Mông Cổ",
"Nepali": "Tiếng Nepal", "Nepali": "Tiếng Nepal",
"Norwegian Bokmål": "Tiếng Na Uy Bokmål", "Norwegian Bokmål": "Tiếng Na Uy (Bokmål)",
"Nyanja": "Nyanja", "Nyanja": "Tiếng Chewa / Nyanja",
"Pashto": "Pashto", "Pashto": "Tiếng Pashtun",
"Persian": "Tiếng Ba Tư", "Persian": "Tiếng Ba Tư",
"Polish": "Đánh bóng", "Polish": "Tiếng Ba Lan",
"Portuguese": "Tiếng Bồ Đào Nha", "Portuguese": "Tiếng Bồ Đào Nha",
"Punjabi": "Punjabi", "Punjabi": "Tiếng Punjab",
"Romanian": "Tiếng Rumani", "Romanian": "Tiếng Rumani",
"Russian": "Tiếng Nga", "Russian": "Tiếng Nga",
"Samoan": "Samoan", "Samoan": "Tiếng Samoa",
"Scottish Gaelic": "Tiếng Gaelic Scotland", "Scottish Gaelic": "Tiếng Gaelic (Scotland)",
"Serbian": "Tiếng Serbia", "Serbian": "Tiếng Serbia",
"Shona": "Shona", "Shona": "Tiếng Shona",
"Sindhi": "Sindhi", "Sindhi": "Tiếng Sindh",
"Sinhala": "Sinhala", "Sinhala": "Tiếng Sinhala",
"Slovak": "Tiếng Slovak", "Slovak": "Tiếng Slovak",
"Slovenian": "Tiếng Slovenia", "Slovenian": "Tiếng Slovenia",
"Somali": "Tiếng Somali", "Somali": "Tiếng Somali",
"Southern Sotho": "Southern Sotho", "Southern Sotho": "Southern Sotho",
"Spanish": "Người Tây Ban Nha", "Spanish": "Tiếng Tây Ban Nha",
"Spanish (Latin America)": "Tiếng Tây Ban Nha (Mỹ Latinh)", "Spanish (Latin America)": "Tiếng Tây Ban Nha (Mỹ Latinh)",
"Sundanese": "Tiếng Sundan", "Sundanese": "Tiếng Sundan",
"Swahili": "Tiếng Swahili", "Swahili": "Tiếng Swahili",
"Swedish": "Tiếng Thụy Điển", "Swedish": "Tiếng Thụy Điển",
"Tajik": "Tajik", "Tajik": "Tiếng Tajik",
"Tamil": "Tamil", "Tamil": "Tiếng Tamil",
"Telugu": "Tiếng Telugu", "Telugu": "Tiếng Telugu",
"Thai": "Tiếng Thái", "Thai": "Tiếng Thái",
"Turkish": "Tiếng Thổ Nhĩ Kỳ", "Turkish": "Tiếng Thổ Nhĩ Kỳ",
@ -275,17 +275,17 @@
"Urdu": "Tiếng Urdu", "Urdu": "Tiếng Urdu",
"Uzbek": "Tiếng Uzbek", "Uzbek": "Tiếng Uzbek",
"Vietnamese": "Tiếng Việt", "Vietnamese": "Tiếng Việt",
"Welsh": "Người xứ Wales", "Welsh": "Tiếng Wales",
"Western Frisian": "Western Frisian", "Western Frisian": "Tiếng Tây Frisia",
"Xhosa": "Xhosa", "Xhosa": "Tiếng Nam Phi",
"Yiddish": "Yiddish", "Yiddish": "Tiếng Yiddish",
"Yoruba": "Yoruba", "Yoruba": "Tiếng Yoruba",
"Zulu": "Tiếng Zulu", "Zulu": "Tiếng Zulu",
"Fallback comments: ": "Nhận xét dự phòng: ", "Fallback comments: ": "Nhận xét dự phòng: ",
"Popular": "Phổ biến", "Popular": "Phổ biến",
"Search": "Tìm kiếm", "Search": "Tìm kiếm",
"Top": "Hàng đầu", "Top": "Hàng đầu",
"About": "Trong khoảng", "About": "Giới thiệu",
"Rating: ": "Xếp hạng: ", "Rating: ": "Xếp hạng: ",
"preferences_locale_label": "Ngôn ngữ: ", "preferences_locale_label": "Ngôn ngữ: ",
"View as playlist": "Xem dưới dạng danh sách phát", "View as playlist": "Xem dưới dạng danh sách phát",
@ -295,45 +295,45 @@
"News": "Tin tức", "News": "Tin tức",
"Movies": "Phim", "Movies": "Phim",
"Download": "Tải xuống", "Download": "Tải xuống",
"Download as: ": "Tải tệp dưới dạng: ", "Download as: ": "Tải xuống dưới dạng: ",
"%A %B %-d, %Y": "% A% B% -d,% Y", "%A %B %-d, %Y": "% A% B% -d,% Y",
"(edited)": "(đã chỉnh sửa)", "(edited)": "(đã chỉnh sửa)",
"YouTube comment permalink": "Liên kết cố định nhận xét trên YouTube", "YouTube comment permalink": "Liên kết cố định nhận xét trên YouTube",
"permalink": "liên kết cố định", "permalink": "liên kết cố định",
"`x` marked it with a ❤": "` x` đã đánh dấu nó bằng một ❤", "`x` marked it with a ❤": "` x` đã đánh dấu nó bằng một ❤",
"Audio mode": "Chế độ âm thanh", "Audio mode": "Chế độ audio",
"Video mode": "Chế độ quay", "Video mode": "Chế độ video",
"channel_tab_videos_label": "Video", "channel_tab_videos_label": "Video",
"Playlists": "Danh sách phát", "Playlists": "Danh sách phát",
"channel_tab_community_label": "Cộng đồng", "channel_tab_community_label": "Cộng đồng",
"search_filters_sort_option_relevance": "liên quan", "search_filters_sort_option_relevance": "Liên quan",
"search_filters_sort_option_rating": "Xếp hạng", "search_filters_sort_option_rating": "Xếp hạng",
"search_filters_sort_option_date": "ngày", "search_filters_sort_option_date": "Ngày tải lên",
"search_filters_sort_option_views": "lượt xem", "search_filters_sort_option_views": "Lượt xem",
"search_filters_type_label": "content_type", "search_filters_type_label": "Thể loại",
"search_filters_duration_label": "thời lượng", "search_filters_duration_label": "Thời lượng",
"search_filters_features_label": "đặc trưng", "search_filters_features_label": "Đặc điểm",
"search_filters_sort_label": "sắp xếp", "search_filters_sort_label": "Sắp xếp theo",
"search_filters_date_option_hour": "giờ", "search_filters_date_option_hour": "Một giờ qua",
"search_filters_date_option_today": "hôm nay", "search_filters_date_option_today": "Hôm nay",
"search_filters_date_option_week": "tuần", "search_filters_date_option_week": "Tuần này",
"search_filters_date_option_month": "tháng", "search_filters_date_option_month": "Tháng này",
"search_filters_date_option_year": "năm", "search_filters_date_option_year": "Năm này",
"search_filters_type_option_video": "video", "search_filters_type_option_video": "video",
"search_filters_type_option_channel": "kênh", "search_filters_type_option_channel": "Kênh",
"search_filters_type_option_playlist": "danh sách phát", "search_filters_type_option_playlist": "Danh sách phát",
"search_filters_type_option_movie": "bộ phim", "search_filters_type_option_movie": "Phim",
"search_filters_type_option_show": "chỉ", "search_filters_type_option_show": "Hiện",
"search_filters_features_option_hd": "hd", "search_filters_features_option_hd": "HD",
"search_filters_features_option_subtitles": "phụ đề", "search_filters_features_option_subtitles": "Phụ đề",
"search_filters_features_option_c_commons": "Commons sáng tạo", "search_filters_features_option_c_commons": "Giấy phép Creative Commons",
"search_filters_features_option_three_d": "3d", "search_filters_features_option_three_d": "3D",
"search_filters_features_option_live": "trực tiếp", "search_filters_features_option_live": "Trực tiếp",
"search_filters_features_option_four_k": "4k", "search_filters_features_option_four_k": "4K",
"search_filters_features_option_location": "vị trí", "search_filters_features_option_location": "Vị trí",
"search_filters_features_option_hdr": "hdr", "search_filters_features_option_hdr": "HDR",
"Current version: ": "Phiên bản hiện tại: ", "Current version: ": "Phiên bản hiện tại: ",
"search_filters_title": "bộ lọc", "search_filters_title": "Bộ lọc",
"generic_playlists_count": "{{count}} danh sách phát", "generic_playlists_count": "{{count}} danh sách phát",
"generic_views_count": "{{count}} lượt xem", "generic_views_count": "{{count}} lượt xem",
"View `x` comments": { "View `x` comments": {
@ -341,40 +341,40 @@
"([^.,0-9]|^)1([^.,0-9]|$)": "Hiển thị `x`bình luận" "([^.,0-9]|^)1([^.,0-9]|$)": "Hiển thị `x`bình luận"
}, },
"Song: ": "Ca khúc: ", "Song: ": "Ca khúc: ",
"Premieres in `x`": "Trình chiếu lần đầu vào `x`", "Premieres in `x`": "Trình chiếu `x`",
"preferences_quality_dash_option_worst": "Thấp nhất", "preferences_quality_dash_option_worst": "T nhất",
"preferences_watch_history_label": "Bật lịch sử video đã xem ", "preferences_watch_history_label": "Bật lịch sử video đã xem ",
"preferences_quality_option_hd720": "HD720", "preferences_quality_option_hd720": "HD720",
"unsubscribe": "hủy đăng kí", "unsubscribe": "hủy đăng kí",
"revoke": "gỡ bỏ", "revoke": "gỡ bỏ",
"preferences_quality_dash_label": "Chất lượng video DASH ưa thích ", "preferences_quality_dash_label": "Chất lượng video DASH ",
"preferences_quality_dash_option_auto": "Tự động", "preferences_quality_dash_option_auto": "Tự động",
"Subscriptions": "Thuê bao", "Subscriptions": "Thuê bao",
"View YouTube comments": "Hiển thị bình luận trên YouTube", "View YouTube comments": "Hiển thị bình luận t YouTube",
"View more comments on Reddit": "Hiển thị thêm bình luận từ Reddit", "View more comments on Reddit": "Hiển thị thêm bình luận từ Reddit",
"Music in this video": "Nhạc trong video này", "Music in this video": "Nhạc trong video này",
"Artist: ": "Nghệ sĩ: ", "Artist: ": "Nghệ sĩ: ",
"Premieres `x`": "Phát lần đầu `x`", "Premieres `x`": "Phát lần đầu `x`",
"preferences_region_label": "Nội dung theo quốc gia ", "preferences_region_label": "Nội dung theo quốc gia ",
"search_message_change_filters_or_query": "Thử mở rộng nội dung tìm kiếm hoặc thay đổi bộ lọc.", "search_message_change_filters_or_query": "Thử mở rộng nội dung tìm kiếm hoặc thay đổi bộ lọc.",
"preferences_quality_option_small": "Nhỏ", "preferences_quality_option_small": "Thấp",
"preferences_quality_dash_option_144p": "144p", "preferences_quality_dash_option_144p": "144p",
"invidious": "Invidious", "invidious": "Invidious",
"preferences_quality_dash_option_240p": "240p", "preferences_quality_dash_option_240p": "240p",
"Import/export": "Xuất/nhập dữ liệu", "Import/export": "Nhập/Xuất",
"preferences_quality_dash_option_4320p": "4320p", "preferences_quality_dash_option_4320p": "4320p (8K)",
"preferences_quality_option_dash": "DASH (tự tối ưu chất lượng)", "preferences_quality_option_dash": "DASH (tự tối ưu chất lượng)",
"generic_subscriptions_count_0": "{{count}} người đăng kí", "generic_subscriptions_count_0": "{{count}} người đăng kí",
"preferences_quality_dash_option_1440p": "1440p", "preferences_quality_dash_option_1440p": "1440p (2K)",
"preferences_quality_dash_option_480p": "480p", "preferences_quality_dash_option_480p": "480p",
"preferences_quality_dash_option_2160p": "2160p", "preferences_quality_dash_option_2160p": "2160p (4K)",
"search_message_no_results": "Tìm kiếm không có kết quả.", "search_message_no_results": "Tìm kiếm không có kết quả.",
"preferences_quality_dash_option_1080p": "1080p", "preferences_quality_dash_option_1080p": "1080p",
"preferences_quality_dash_option_720p": "720p", "preferences_quality_dash_option_720p": "720p",
"preferences_quality_option_medium": "Trung bình", "preferences_quality_option_medium": "Trung bình",
"Load more": "Hiển thị thêm", "Load more": "Tải thêm",
"comments_points_count_0": "{{count}} điểm", "comments_points_count_0": "{{count}} điểm",
"Import YouTube playlist (.csv)": "Nhập danh sách phát YouTube (.csv)", "Import YouTube playlist (.csv)": "Nhập các danh sách phát từ YouTube (.csv)",
"preferences_quality_dash_option_best": "Tốt nhất", "preferences_quality_dash_option_best": "Tốt nhất",
"preferences_quality_dash_option_360p": "360p", "preferences_quality_dash_option_360p": "360p",
"subscriptions_unseen_notifs_count_0": "{{count}} thông báo chưa đọc", "subscriptions_unseen_notifs_count_0": "{{count}} thông báo chưa đọc",
@ -382,10 +382,102 @@
"search_message_use_another_instance": " Bạn cũng có thể tìm kiếm <a href=\"`x`\"> ở một phiên bản khác</a>.", "search_message_use_another_instance": " Bạn cũng có thể tìm kiếm <a href=\"`x`\"> ở một phiên bản khác</a>.",
"Standard YouTube license": "Giấy phép YouTube thông thường", "Standard YouTube license": "Giấy phép YouTube thông thường",
"Album: ": "Album: ", "Album: ": "Album: ",
"preferences_save_player_pos_label": "Lưu vị trí xem cuối cùng ", "preferences_save_player_pos_label": "Lưu vị trí xem: ",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Xin chào! Có vẻ như bạn đã tắt JavaScript. Bấm vào đây để xem bình luận, lưu ý rằng thời gian tải có thể lâu hơn.", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Xin chào! Có vẻ như bạn đã tắt JavaScript. Bấm vào đây để xem bình luận, lưu ý rằng thời gian tải có thể lâu hơn.",
"Chinese (China)": "Tiếng Trung (Trung Quốc)", "Chinese (China)": "Tiếng Trung (Trung Quốc)",
"generic_button_cancel": "Hủy", "generic_button_cancel": "Hủy",
"Chinese": "Tiếng Trung", "Chinese": "Tiếng Trung",
"generic_button_delete": "Xóa" "generic_button_delete": "Xóa",
"Korean (auto-generated)": "Tiếng Hàn (được tạo tự động)",
"search_filters_features_option_three_sixty": "360°",
"channel_tab_podcasts_label": "Podcast",
"Spanish (Mexico)": "Tiếng Tây Ban Nha (Mexico)",
"search_filters_apply_button": "Áp dụng các mục đã chọn",
"Download is disabled": "Tải xuống đã bị vô hiệu hóa.",
"next_steps_error_message_go_to_youtube": "Đi đến YouTube",
"German (auto-generated)": "Tiếng Đức (được tạo tự động)",
"Japanese (auto-generated)": "Tiếng Nhật (được tạo tự động)",
"footer_donate_page": "Ủng hộ",
"crash_page_before_reporting": "Trước khi báo cáo lỗi, hãy chắc chắn rằng bạn đã:",
"Channel Sponsor": "Nhà tài trợ của kênh",
"videoinfo_started_streaming_x_ago": "Đã bắt đầu phát sóng `x` trước",
"videoinfo_youTube_embed_link": "Nhúng",
"channel_tab_streams_label": "Phát trực tiếp",
"playlist_button_add_items": "Thêm video",
"generic_count_minutes_0": "{{count}} phút",
"user_saved_playlists": "`x` danh sách phát đã lưu",
"Spanish (Spain)": "Tiếng Tây Ban Nha (Tây Ban Nha)",
"crash_page_refresh": "Đã thử <a href=\"`x`\">tải lại trang</a>",
"Chinese (Hong Kong)": "Tiếng Trung (Hồng Kông)",
"generic_count_months_0": "{{count}} tháng",
"download_subtitles": "Phụ đề - `x` (.vtt)",
"generic_button_save": "Lưu",
"crash_page_search_issue": "Tìm <a href=\"`x`\">lỗi có sẵn trên GitHub</a>",
"none": "không",
"English (United States)": "Tiếng Anh (Mỹ)",
"next_steps_error_message_refresh": "Tải lại",
"Video unavailable": "Video không có sẵn",
"footer_source_code": "Mã nguồn",
"search_filters_duration_option_short": "Ngắn (< 4 phút)",
"search_filters_duration_option_long": "Dài (> 20 phút)",
"tokens_count_0": "{{count}} mã thông báo",
"Italian (auto-generated)": "Tiếng Ý (được tạo tự động)",
"channel_tab_shorts_label": "Shorts",
"channel_tab_releases_label": "Mới tải lên",
"`x` ago": "`x` trước",
"Interlingue": "Tiếng Khoa học Quốc tế",
"generic_channels_count_0": "{{count}} kênh",
"Chinese (Taiwan)": "Tiếng Trung (Đài Loan)",
"adminprefs_modified_source_code_url_label": "URL tới kho lưu trữ mã nguồn đã sửa đổi",
"Turkish (auto-generated)": "Tiếng Thổ Nhĩ Kỳ (được tạo tự động)",
"Indonesian (auto-generated)": "Tiếng Indonesia (được tạo tự động)",
"Portuguese (auto-generated)": "Tiếng Bồ Đào Nha (được tạo tự động)",
"generic_count_years_0": "{{count}} năm",
"videoinfo_invidious_embed_link": "Liên kết nhúng",
"Popular enabled: ": "Đã bật phổ biến: ",
"Spanish (auto-generated)": "Tiếng Tây Ban Nha (được tạo tự động)",
"English (United Kingdom)": "Tiếng Anh Anh",
"channel_tab_playlists_label": "Danh sách phát",
"generic_button_edit": "Sửa",
"search_filters_features_option_purchased": "Đã mua",
"search_filters_date_option_none": "Mọi thời điểm",
"Cantonese (Hong Kong)": "Tiếng Quảng Châu (Hồng Kông)",
"crash_page_report_issue": "Nếu các điều trên không giúp được, xin hãy <a href=\"`x`\">tạo vấn đề mới trên GitHub</a> (ưu tiên tiếng Anh) và đính kèm đoạn chữ sau trong nội dung (giữ nguyên KHÔNG dịch):",
"crash_page_switch_instance": "Đã thử <a href=\"`x`\">dùng một phiên bản khác</a>",
"generic_count_weeks_0": "{{count}} tuần",
"videoinfo_watch_on_youTube": "Xem trên YouTube",
"footer_modfied_source_code": "Mã nguồn đã chỉnh sửa",
"generic_button_rss": "RSS",
"generic_count_hours_0": "{{count}} giờ",
"French (auto-generated)": "Tiếng Pháp (được tạo tự động)",
"crash_page_read_the_faq": "Đọc <a href=\"`x`\">Hỏi đáp thường gặp (FAQ)</a>",
"user_created_playlists": "`x` danh sách phát đã tạo",
"channel_tab_channels_label": "Kênh",
"search_filters_type_option_all": "Mọi thể loại",
"Russian (auto-generated)": "Tiếng Nga (được tạo tự động)",
"comments_view_x_replies_0": "Xem {{count}} lượt trả lời",
"footer_original_source_code": "Mã nguồn gốc",
"Portuguese (Brazil)": "Tiếng Bồ Đào Nha (Brazil)",
"search_filters_features_option_vr180": "VR180",
"error_video_not_in_playlist": "Video không tồn tại trong danh sách phát. <a href=\"`x`\">Bấm để trở về trang chủ của danh sách phát.</a>",
"Dutch (auto-generated)": "Tiếng Hà Lan (được tạo tự động)",
"generic_count_days_0": "{{count}} ngày",
"Vietnamese (auto-generated)": "Tiếng Việt (được tạo tự động)",
"search_filters_duration_option_none": "Mọi thời lượng",
"footer_documentation": "Tài liệu",
"next_steps_error_message": "Bạn có thể thử: ",
"Import YouTube watch history (.json)": "Nhập lịch sử xem từ YouTube (.json)",
"search_filters_duration_option_medium": "Trung bình (4 - 20 phút)",
"generic_count_seconds_0": "{{count}} giây",
"search_filters_date_label": "Ngày tải lên",
"crash_page_you_found_a_bug": "Có vẻ như bạn đã tìm ra lỗi trong Indivious!",
"Add to playlist": "Thêm vào danh sách phát",
"Add to playlist: ": "Thêm vào danh sách phát: ",
"Answer": "Trả lời",
"toggle_theme": "Bật/tắt diện mạo",
"carousel_slide": "Trang {{current}} trên tổng {{total}} trang",
"carousel_skip": "Bỏ qua Carousel",
"carousel_go_to": "Đi tới trang `x`",
"Search for videos": "Tìm kiếm video",
"The Popular feed has been disabled by the administrator.": "Bảng tin phổ biến đã bị tắt bởi ban quản lý."
} }

View file

@ -26,7 +26,7 @@
"Import and Export Data": "导入与导出数据", "Import and Export Data": "导入与导出数据",
"Import": "导入", "Import": "导入",
"Import Invidious data": "导入 Invidious JSON 数据", "Import Invidious data": "导入 Invidious JSON 数据",
"Import YouTube subscriptions": "导入 YouTube/OPML 订阅", "Import YouTube subscriptions": "导入 YouTube CSV 或 OPML 订阅",
"Import FreeTube subscriptions (.db)": "导入 FreeTube 订阅 (.db)", "Import FreeTube subscriptions (.db)": "导入 FreeTube 订阅 (.db)",
"Import NewPipe subscriptions (.json)": "导入 NewPipe 订阅 (.json)", "Import NewPipe subscriptions (.json)": "导入 NewPipe 订阅 (.json)",
"Import NewPipe data (.zip)": "导入 NewPipe 数据 (.zip)", "Import NewPipe data (.zip)": "导入 NewPipe 数据 (.zip)",
@ -470,5 +470,14 @@
"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}} 个频道" "generic_channels_count_0": "{{count}} 个频道",
"toggle_theme": "切换主题",
"Add to playlist": "添加到播放列表",
"Add to playlist: ": "添加到播放列表: ",
"Answer": "响应",
"Search for videos": "搜索视频",
"The Popular feed has been disabled by the administrator.": "“流行”源已被管理员禁用。",
"carousel_slide": "当前为第 {{current}} 张图,共 {{total}} 张图",
"carousel_skip": "跳过图集",
"carousel_go_to": "转到图 `x`"
} }

View file

@ -26,7 +26,7 @@
"Import and Export Data": "匯入與匯出資料", "Import and Export Data": "匯入與匯出資料",
"Import": "匯入", "Import": "匯入",
"Import Invidious data": "匯入 Invidious JSON 資料", "Import Invidious data": "匯入 Invidious JSON 資料",
"Import YouTube subscriptions": "匯入 YouTube/OPML 訂閱", "Import YouTube subscriptions": "匯入 YouTube CSV 或 OPML 訂閱",
"Import FreeTube subscriptions (.db)": "匯入 FreeTube 訂閱 (.db)", "Import FreeTube subscriptions (.db)": "匯入 FreeTube 訂閱 (.db)",
"Import NewPipe subscriptions (.json)": "匯入 NewPipe 訂閱 (.json)", "Import NewPipe subscriptions (.json)": "匯入 NewPipe 訂閱 (.json)",
"Import NewPipe data (.zip)": "匯入 NewPipe 資料 (.zip)", "Import NewPipe data (.zip)": "匯入 NewPipe 資料 (.zip)",
@ -470,5 +470,14 @@
"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}} 個頻道" "generic_channels_count_0": "{{count}} 個頻道",
"toggle_theme": "切換佈景主題",
"Add to playlist": "新增至播放清單",
"Add to playlist: ": "新增至播放清單: ",
"Answer": "答案",
"Search for videos": "搜尋影片",
"carousel_slide": "第 {{current}} 張投影片,共 {{total}} 張",
"carousel_skip": "略過輪播",
"carousel_go_to": "跳到投影片 `x`",
"The Popular feed has been disabled by the administrator.": "熱門 feed 已被管理員停用。"
} }

View file

@ -1,34 +1,27 @@
require "../../spec_helper.cr" require "../../spec_helper.cr"
MockLines = [ MockLines = ["Line 1", "Line 2"]
{ MockLinesWithEscapableCharacter = ["<Line 1>", "&Line 2>", '\u200E' + "Line\u200F 3", "\u00A0Line 4"]
"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 Spectator.describe "WebVTT::Builder" do
it "correctly builds a vtt file" do it "correctly builds a vtt file" do
result = WebVTT.build do |vtt| result = WebVTT.build do |vtt|
MockLines.each do |line| 2.times do |i|
vtt.cue(line["start_time"], line["end_time"], line["text"]) vtt.cue(
Time::Span.new(seconds: i),
Time::Span.new(seconds: i + 1),
MockLines[i]
)
end end
end end
expect(result).to eq([ expect(result).to eq([
"WEBVTT", "WEBVTT",
"", "",
"00:00:01.000 --> 00:00:02.000", "00:00:00.000 --> 00:00:01.000",
"Line 1", "Line 1",
"", "",
"00:00:02.000 --> 00:00:03.000", "00:00:01.000 --> 00:00:02.000",
"Line 2", "Line 2",
"", "",
"", "",
@ -42,8 +35,12 @@ Spectator.describe "WebVTT::Builder" do
} }
result = WebVTT.build(setting_fields) do |vtt| result = WebVTT.build(setting_fields) do |vtt|
MockLines.each do |line| 2.times do |i|
vtt.cue(line["start_time"], line["end_time"], line["text"]) vtt.cue(
Time::Span.new(seconds: i),
Time::Span.new(seconds: i + 1),
MockLines[i]
)
end end
end end
@ -52,13 +49,39 @@ Spectator.describe "WebVTT::Builder" do
"Kind: captions", "Kind: captions",
"Language: en", "Language: en",
"", "",
"00:00:01.000 --> 00:00:02.000", "00:00:00.000 --> 00:00:01.000",
"Line 1", "Line 1",
"", "",
"00:00:02.000 --> 00:00:03.000", "00:00:01.000 --> 00:00:02.000",
"Line 2", "Line 2",
"", "",
"", "",
].join('\n')) ].join('\n'))
end end
it "properly escapes characters" do
result = WebVTT.build do |vtt|
4.times do |i|
vtt.cue(Time::Span.new(seconds: i), Time::Span.new(seconds: i + 1), MockLinesWithEscapableCharacter[i])
end
end
expect(result).to eq([
"WEBVTT",
"",
"00:00:00.000 --> 00:00:01.000",
"&lt;Line 1&gt;",
"",
"00:00:01.000 --> 00:00:02.000",
"&amp;Line 2&gt;",
"",
"00:00:02.000 --> 00:00:03.000",
"&lrm;Line&rlm; 3",
"",
"00:00:03.000 --> 00:00:04.000",
"&nbsp;Line 4",
"",
"",
].join('\n'))
end
end end

View file

@ -17,7 +17,7 @@ FORM_TESTS = {
"cy" => I18next::Plurals::PluralForms::Special_Welsh, "cy" => I18next::Plurals::PluralForms::Special_Welsh,
"fr" => I18next::Plurals::PluralForms::Special_French_Portuguese, "fr" => I18next::Plurals::PluralForms::Special_French_Portuguese,
"en" => I18next::Plurals::PluralForms::Single_not_one, "en" => I18next::Plurals::PluralForms::Single_not_one,
"es" => I18next::Plurals::PluralForms::Single_not_one, "es" => I18next::Plurals::PluralForms::Special_Spanish_Italian,
"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,
@ -33,7 +33,8 @@ FORM_TESTS = {
"mt" => I18next::Plurals::PluralForms::Special_Maltese, "mt" => I18next::Plurals::PluralForms::Special_Maltese,
"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::Special_French_Portuguese,
"pt-PT" => I18next::Plurals::PluralForms::Single_gt_one,
"pt-BR" => I18next::Plurals::PluralForms::Special_French_Portuguese, "pt-BR" => I18next::Plurals::PluralForms::Special_French_Portuguese,
"ro" => I18next::Plurals::PluralForms::Special_Romanian, "ro" => I18next::Plurals::PluralForms::Special_Romanian,
"sk" => I18next::Plurals::PluralForms::Special_Czech_Slovak, "sk" => I18next::Plurals::PluralForms::Special_Czech_Slovak,
@ -77,10 +78,10 @@ SUFFIX_TESTS = {
{num: 10, suffix: "_plural"}, {num: 10, suffix: "_plural"},
], ],
"es" => [ "es" => [
{num: 0, suffix: "_plural"}, {num: 0, suffix: "_2"},
{num: 1, suffix: ""}, {num: 1, suffix: "_0"},
{num: 10, suffix: "_plural"}, {num: 10, suffix: "_2"},
{num: 6_000_000, suffix: "_plural"}, {num: 6_000_000, suffix: "_1"},
], ],
"fr" => [ "fr" => [
{num: 0, suffix: "_0"}, {num: 0, suffix: "_0"},

View file

@ -12,45 +12,45 @@ end
# page of Youtube with any browser devtools HTML inspector. # page of Youtube with any browser devtools HTML inspector.
DATE_FILTERS = { DATE_FILTERS = {
Invidious::Search::Filters::Date::Hour => "EgIIAQ%3D%3D", Invidious::Search::Filters::Date::Hour => "EgIIAfABAQ%3D%3D",
Invidious::Search::Filters::Date::Today => "EgIIAg%3D%3D", Invidious::Search::Filters::Date::Today => "EgIIAvABAQ%3D%3D",
Invidious::Search::Filters::Date::Week => "EgIIAw%3D%3D", Invidious::Search::Filters::Date::Week => "EgIIA_ABAQ%3D%3D",
Invidious::Search::Filters::Date::Month => "EgIIBA%3D%3D", Invidious::Search::Filters::Date::Month => "EgIIBPABAQ%3D%3D",
Invidious::Search::Filters::Date::Year => "EgIIBQ%3D%3D", Invidious::Search::Filters::Date::Year => "EgIIBfABAQ%3D%3D",
} }
TYPE_FILTERS = { TYPE_FILTERS = {
Invidious::Search::Filters::Type::Video => "EgIQAQ%3D%3D", Invidious::Search::Filters::Type::Video => "EgIQAfABAQ%3D%3D",
Invidious::Search::Filters::Type::Channel => "EgIQAg%3D%3D", Invidious::Search::Filters::Type::Channel => "EgIQAvABAQ%3D%3D",
Invidious::Search::Filters::Type::Playlist => "EgIQAw%3D%3D", Invidious::Search::Filters::Type::Playlist => "EgIQA_ABAQ%3D%3D",
Invidious::Search::Filters::Type::Movie => "EgIQBA%3D%3D", Invidious::Search::Filters::Type::Movie => "EgIQBPABAQ%3D%3D",
} }
DURATION_FILTERS = { DURATION_FILTERS = {
Invidious::Search::Filters::Duration::Short => "EgIYAQ%3D%3D", Invidious::Search::Filters::Duration::Short => "EgIYAfABAQ%3D%3D",
Invidious::Search::Filters::Duration::Medium => "EgIYAw%3D%3D", Invidious::Search::Filters::Duration::Medium => "EgIYA_ABAQ%3D%3D",
Invidious::Search::Filters::Duration::Long => "EgIYAg%3D%3D", Invidious::Search::Filters::Duration::Long => "EgIYAvABAQ%3D%3D",
} }
FEATURE_FILTERS = { FEATURE_FILTERS = {
Invidious::Search::Filters::Features::Live => "EgJAAQ%3D%3D", Invidious::Search::Filters::Features::Live => "EgJAAfABAQ%3D%3D",
Invidious::Search::Filters::Features::FourK => "EgJwAQ%3D%3D", Invidious::Search::Filters::Features::FourK => "EgJwAfABAQ%3D%3D",
Invidious::Search::Filters::Features::HD => "EgIgAQ%3D%3D", Invidious::Search::Filters::Features::HD => "EgIgAfABAQ%3D%3D",
Invidious::Search::Filters::Features::Subtitles => "EgIoAQ%3D%3D", Invidious::Search::Filters::Features::Subtitles => "EgIoAfABAQ%3D%3D",
Invidious::Search::Filters::Features::CCommons => "EgIwAQ%3D%3D", Invidious::Search::Filters::Features::CCommons => "EgIwAfABAQ%3D%3D",
Invidious::Search::Filters::Features::ThreeSixty => "EgJ4AQ%3D%3D", Invidious::Search::Filters::Features::ThreeSixty => "EgJ4AfABAQ%3D%3D",
Invidious::Search::Filters::Features::VR180 => "EgPQAQE%3D", Invidious::Search::Filters::Features::VR180 => "EgPQAQHwAQE%3D",
Invidious::Search::Filters::Features::ThreeD => "EgI4AQ%3D%3D", Invidious::Search::Filters::Features::ThreeD => "EgI4AfABAQ%3D%3D",
Invidious::Search::Filters::Features::HDR => "EgPIAQE%3D", Invidious::Search::Filters::Features::HDR => "EgPIAQHwAQE%3D",
Invidious::Search::Filters::Features::Location => "EgO4AQE%3D", Invidious::Search::Filters::Features::Location => "EgO4AQHwAQE%3D",
Invidious::Search::Filters::Features::Purchased => "EgJIAQ%3D%3D", Invidious::Search::Filters::Features::Purchased => "EgJIAfABAQ%3D%3D",
} }
SORT_FILTERS = { SORT_FILTERS = {
Invidious::Search::Filters::Sort::Relevance => "", Invidious::Search::Filters::Sort::Relevance => "8AEB",
Invidious::Search::Filters::Sort::Date => "CAI%3D", Invidious::Search::Filters::Sort::Date => "CALwAQE%3D",
Invidious::Search::Filters::Sort::Views => "CAM%3D", Invidious::Search::Filters::Sort::Views => "CAPwAQE%3D",
Invidious::Search::Filters::Sort::Rating => "CAE%3D", Invidious::Search::Filters::Sort::Rating => "CAHwAQE%3D",
} }
Spectator.describe Invidious::Search::Filters do Spectator.describe Invidious::Search::Filters do

View file

@ -185,6 +185,8 @@ Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(CONNECTION_CHANNEL
Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new
Invidious::Jobs.start_all Invidious::Jobs.start_all
def popular_videos def popular_videos

View file

@ -14,6 +14,7 @@ record AboutChannel,
is_family_friendly : Bool, is_family_friendly : Bool,
allowed_regions : Array(String), allowed_regions : Array(String),
tabs : Array(String), tabs : Array(String),
tags : Array(String),
verified : Bool verified : Bool
def get_about_info(ucid, locale) : AboutChannel def get_about_info(ucid, locale) : AboutChannel
@ -43,6 +44,8 @@ def get_about_info(ucid, locale) : AboutChannel
auto_generated = true auto_generated = true
end end
tags = [] of String
if auto_generated if auto_generated
author = initdata["header"]["interactiveTabbedHeaderRenderer"]["title"]["simpleText"].as_s author = initdata["header"]["interactiveTabbedHeaderRenderer"]["title"]["simpleText"].as_s
author_url = initdata["microformat"]["microformatDataRenderer"]["urlCanonical"].as_s author_url = initdata["microformat"]["microformatDataRenderer"]["urlCanonical"].as_s
@ -52,7 +55,13 @@ def get_about_info(ucid, locale) : AboutChannel
banners = initdata["header"]["interactiveTabbedHeaderRenderer"]?.try &.["banner"]?.try &.["thumbnails"]? banners = initdata["header"]["interactiveTabbedHeaderRenderer"]?.try &.["banner"]?.try &.["thumbnails"]?
banner = banners.try &.[-1]?.try &.["url"].as_s? banner = banners.try &.[-1]?.try &.["url"].as_s?
description_node = initdata["header"]["interactiveTabbedHeaderRenderer"]["description"] description_base_node = initdata["header"]["interactiveTabbedHeaderRenderer"]["description"]
# some channels have the description in a simpleText
# ex: https://www.youtube.com/channel/UCQvWX73GQygcwXOTSf_VDVg/
description_node = description_base_node.dig?("simpleText") || description_base_node
tags = initdata.dig?("header", "interactiveTabbedHeaderRenderer", "badges")
.try &.as_a.map(&.["metadataBadgeRenderer"]["label"].as_s) || [] of String
else else
author = initdata["metadata"]["channelMetadataRenderer"]["title"].as_s author = initdata["metadata"]["channelMetadataRenderer"]["title"].as_s
author_url = initdata["metadata"]["channelMetadataRenderer"]["channelUrl"].as_s author_url = initdata["metadata"]["channelMetadataRenderer"]["channelUrl"].as_s
@ -70,6 +79,7 @@ def get_about_info(ucid, locale) : AboutChannel
# end # end
description_node = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]? description_node = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]?
tags = initdata.dig?("microformat", "microformatDataRenderer", "tags").try &.as_a.map(&.as_s) || [] of String
end end
is_family_friendly = initdata["microformat"]["microformatDataRenderer"]["familySafe"].as_bool is_family_friendly = initdata["microformat"]["microformatDataRenderer"]["familySafe"].as_bool
@ -131,7 +141,8 @@ def get_about_info(ucid, locale) : AboutChannel
# ["description"]["simpleText"] and ["primaryLinks"][0]["title"]["simpleText"] # ["description"]["simpleText"] and ["primaryLinks"][0]["title"]["simpleText"]
auto_generated = ( auto_generated = (
(channel_about_meta["primaryLinks"]?.try &.size) == 1 && \ (channel_about_meta["primaryLinks"]?.try &.size) == 1 && \
extract_text(channel_about_meta.dig?("primaryLinks", 0, "title")) == "Auto-generated by YouTube" extract_text(channel_about_meta.dig?("primaryLinks", 0, "title")) == "Auto-generated by YouTube" ||
channel_about_meta.dig?("links", 0, "channelExternalLinkViewModel", "title", "content").try &.as_s == "Auto-generated by YouTube"
) )
end end
end end
@ -155,6 +166,7 @@ def get_about_info(ucid, locale) : AboutChannel
is_family_friendly: is_family_friendly, is_family_friendly: is_family_friendly,
allowed_regions: allowed_regions, allowed_regions: allowed_regions,
tabs: tab_names, tabs: tab_names,
tags: tags,
verified: author_verified || false, verified: author_verified || false,
) )
end end

View file

@ -64,15 +64,15 @@ def content_to_comment_html(content, video_id : String? = "")
# check for custom emojis # check for custom emojis
if run["emoji"]? if run["emoji"]?
if run["emoji"]["isCustomEmoji"]?.try &.as_bool if run["emoji"]["isCustomEmoji"]?.try &.as_bool
if emojiImage = run.dig?("emoji", "image") if emoji_image = run.dig?("emoji", "image")
emojiAlt = emojiImage.dig?("accessibility", "accessibilityData", "label").try &.as_s || text emoji_alt = emoji_image.dig?("accessibility", "accessibilityData", "label").try &.as_s || text
emojiThumb = emojiImage["thumbnails"][0] emoji_thumb = emoji_image["thumbnails"][0]
text = String.build do |str| text = String.build do |str|
str << %(<img alt=") << emojiAlt << "\" " str << %(<img alt=") << emoji_alt << "\" "
str << %(src="/ggpht) << URI.parse(emojiThumb["url"].as_s).request_target << "\" " str << %(src="/ggpht) << URI.parse(emoji_thumb["url"].as_s).request_target << "\" "
str << %(title=") << emojiAlt << "\" " str << %(title=") << emoji_alt << "\" "
str << %(width=") << emojiThumb["width"] << "\" " str << %(width=") << emoji_thumb["width"] << "\" "
str << %(height=") << emojiThumb["height"] << "\" " str << %(height=") << emoji_thumb["height"] << "\" "
str << %(class="channel-emoji" />) str << %(class="channel-emoji" />)
end end
else else

View file

@ -57,7 +57,7 @@ module Invidious::Comments
return initial_data return initial_data
end end
def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", isPost = false) def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", is_post = false)
contents = nil contents = nil
if on_response_received_endpoints = response["onResponseReceivedEndpoints"]? if on_response_received_endpoints = response["onResponseReceivedEndpoints"]?
@ -104,6 +104,8 @@ module Invidious::Comments
end end
end end
mutations = response.dig?("frameworkUpdates", "entityBatchUpdate", "mutations").try &.as_a || [] of JSON::Any
response = JSON.build do |json| response = JSON.build do |json|
json.object do json.object do
if header if header
@ -113,7 +115,7 @@ module Invidious::Comments
json.field "commentCount", comment_count json.field "commentCount", comment_count
end end
if isPost if is_post
json.field "postId", id json.field "postId", id
else else
json.field "videoId", id json.field "videoId", id
@ -131,18 +133,76 @@ module Invidious::Comments
node_replies = node["replies"]["commentRepliesRenderer"] node_replies = node["replies"]["commentRepliesRenderer"]
end end
if cvm = node["commentViewModel"]?
# two commentViewModels for inital request
# one commentViewModel when getting a replies to a comment
cvm = cvm["commentViewModel"] if cvm["commentViewModel"]?
comment_key = cvm["commentKey"]
toolbar_key = cvm["toolbarStateKey"]
comment_mutation = mutations.find { |i| i.dig?("payload", "commentEntityPayload", "key") == comment_key }
toolbar_mutation = mutations.find { |i| i.dig?("entityKey") == toolbar_key }
if !comment_mutation.nil? && !toolbar_mutation.nil?
# todo parse styleRuns, commandRuns and attachmentRuns for comments
html_content = parse_description(comment_mutation.dig("payload", "commentEntityPayload", "properties", "content"), id)
comment_author = comment_mutation.dig("payload", "commentEntityPayload", "author")
json.field "authorId", comment_author["channelId"].as_s
json.field "authorUrl", "/channel/#{comment_author["channelId"].as_s}"
json.field "author", comment_author["displayName"].as_s
json.field "verified", comment_author["isVerified"].as_bool
json.field "authorThumbnails" do
json.array do
comment_mutation.dig?("payload", "commentEntityPayload", "avatar", "image", "sources").try &.as_a.each do |thumbnail|
json.object do
json.field "url", thumbnail["url"]
json.field "width", thumbnail["width"]
json.field "height", thumbnail["height"]
end
end
end
end
json.field "authorIsChannelOwner", comment_author["isCreator"].as_bool
json.field "isSponsor", (comment_author["sponsorBadgeUrl"]? != nil)
if sponsor_badge_url = comment_author["sponsorBadgeUrl"]?
# Sponsor icon thumbnails always have one object and there's only ever the url property in it
json.field "sponsorIconUrl", sponsor_badge_url
end
comment_toolbar = comment_mutation.dig("payload", "commentEntityPayload", "toolbar")
json.field "likeCount", short_text_to_number(comment_toolbar["likeCountNotliked"].as_s)
reply_count = short_text_to_number(comment_toolbar["replyCount"]?.try &.as_s || "0")
if heart_state = toolbar_mutation.dig?("payload", "engagementToolbarStateEntityPayload", "heartState")
if heart_state.as_s == "TOOLBAR_HEART_STATE_HEARTED"
json.field "creatorHeart" do
json.object do
json.field "creatorThumbnail", comment_toolbar["creatorThumbnailUrl"].as_s
json.field "creatorName", comment_toolbar["heartActiveTooltip"].as_s.sub("❤ by ", "")
end
end
end
end
published_text = comment_mutation.dig?("payload", "commentEntityPayload", "properties", "publishedTime").try &.as_s
end
json.field "isPinned", (cvm.dig?("pinnedText") != nil)
json.field "commentId", cvm["commentId"]
else
if node["comment"]? if node["comment"]?
node_comment = node["comment"]["commentRenderer"] node_comment = node["comment"]["commentRenderer"]
else else
node_comment = node["commentRenderer"] node_comment = node["commentRenderer"]
end end
json.field "commentId", node_comment["commentId"]
content_html = node_comment["contentText"]?.try { |t| parse_content(t, id) } || "" html_content = node_comment["contentText"]?.try { |t| parse_content(t, id) }
author = node_comment["authorText"]?.try &.["simpleText"]? || ""
json.field "verified", (node_comment["authorCommentBadge"]? != nil) json.field "verified", (node_comment["authorCommentBadge"]? != nil)
json.field "author", author json.field "author", node_comment["authorText"]?.try &.["simpleText"]? || ""
json.field "authorThumbnails" do json.field "authorThumbnails" do
json.array do json.array do
node_comment["authorThumbnail"]["thumbnails"].as_a.each do |thumbnail| node_comment["authorThumbnail"]["thumbnails"].as_a.each do |thumbnail|
@ -155,6 +215,19 @@ module Invidious::Comments
end end
end end
if comment_action_buttons_renderer = node_comment.dig?("actionButtons", "commentActionButtonsRenderer")
json.field "likeCount", comment_action_buttons_renderer["likeButton"]["toggleButtonRenderer"]["accessibilityData"]["accessibilityData"]["label"].as_s.scan(/\d/).map(&.[0]).join.to_i
if comment_action_buttons_renderer["creatorHeart"]?
heart_data = comment_action_buttons_renderer["creatorHeart"]["creatorHeartRenderer"]["creatorThumbnail"]
json.field "creatorHeart" do
json.object do
json.field "creatorThumbnail", heart_data["thumbnails"][-1]["url"]
json.field "creatorName", heart_data["accessibility"]["accessibilityData"]["label"]
end
end
end
end
if node_comment["authorEndpoint"]? if node_comment["authorEndpoint"]?
json.field "authorId", node_comment["authorEndpoint"]["browseEndpoint"]["browseId"] json.field "authorId", node_comment["authorEndpoint"]["browseEndpoint"]["browseId"]
json.field "authorUrl", node_comment["authorEndpoint"]["browseEndpoint"]["canonicalBaseUrl"] json.field "authorUrl", node_comment["authorEndpoint"]["browseEndpoint"]["canonicalBaseUrl"]
@ -163,41 +236,35 @@ module Invidious::Comments
json.field "authorUrl", "" json.field "authorUrl", ""
end end
published_text = node_comment["publishedTimeText"]["runs"][0]["text"].as_s json.field "authorIsChannelOwner", node_comment["authorIsChannelOwner"]
published = decode_date(published_text.rchop(" (edited)"))
if published_text.includes?(" (edited)")
json.field "isEdited", true
else
json.field "isEdited", false
end
json.field "content", html_to_content(content_html)
json.field "contentHtml", content_html
json.field "isPinned", (node_comment["pinnedCommentBadge"]? != nil) json.field "isPinned", (node_comment["pinnedCommentBadge"]? != nil)
published_text = node_comment["publishedTimeText"]["runs"][0]["text"].as_s
json.field "isSponsor", (node_comment["sponsorCommentBadge"]? != nil) json.field "isSponsor", (node_comment["sponsorCommentBadge"]? != nil)
if node_comment["sponsorCommentBadge"]? if node_comment["sponsorCommentBadge"]?
# Sponsor icon thumbnails always have one object and there's only ever the url property in it # Sponsor icon thumbnails always have one object and there's only ever the url property in it
json.field "sponsorIconUrl", node_comment.dig("sponsorCommentBadge", "sponsorCommentBadgeRenderer", "customBadge", "thumbnails", 0, "url").to_s json.field "sponsorIconUrl", node_comment.dig("sponsorCommentBadge", "sponsorCommentBadgeRenderer", "customBadge", "thumbnails", 0, "url").to_s
end end
reply_count = node_comment["replyCount"]?
end
content_html = html_content || ""
json.field "content", html_to_content(content_html)
json.field "contentHtml", content_html
if published_text != nil
published_text = published_text.to_s
if published_text.includes?(" (edited)")
json.field "isEdited", true
published = decode_date(published_text.rchop(" (edited)"))
else
json.field "isEdited", false
published = decode_date(published_text)
end
json.field "published", published.to_unix json.field "published", published.to_unix
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale)) json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
comment_action_buttons_renderer = node_comment["actionButtons"]["commentActionButtonsRenderer"]
json.field "likeCount", comment_action_buttons_renderer["likeButton"]["toggleButtonRenderer"]["accessibilityData"]["accessibilityData"]["label"].as_s.scan(/\d/).map(&.[0]).join.to_i
json.field "commentId", node_comment["commentId"]
json.field "authorIsChannelOwner", node_comment["authorIsChannelOwner"]
if comment_action_buttons_renderer["creatorHeart"]?
hearth_data = comment_action_buttons_renderer["creatorHeart"]["creatorHeartRenderer"]["creatorThumbnail"]
json.field "creatorHeart" do
json.object do
json.field "creatorThumbnail", hearth_data["thumbnails"][-1]["url"]
json.field "creatorName", hearth_data["accessibility"]["accessibilityData"]["label"]
end
end
end end
if node_replies && !response["commentRepliesContinuation"]? if node_replies && !response["commentRepliesContinuation"]?
@ -210,7 +277,7 @@ module Invidious::Comments
json.field "replies" do json.field "replies" do
json.object do json.object do
json.field "replyCount", node_comment["replyCount"]? || 1 json.field "replyCount", reply_count || 1
json.field "continuation", continuation json.field "continuation", continuation
end end
end end
@ -236,7 +303,6 @@ module Invidious::Comments
if format == "html" if format == "html"
response = JSON.parse(response) response = JSON.parse(response)
content_html = Frontend::Comments.template_youtube(response, locale, thin_mode) content_html = Frontend::Comments.template_youtube(response, locale, thin_mode)
response = JSON.build do |json| response = JSON.build do |json|
json.object do json.object do
json.field "contentHtml", content_html json.field "contentHtml", content_html

View file

@ -15,7 +15,7 @@ module Invidious::Database::Statistics
PG_DB.query_one(request, as: Int64) PG_DB.query_one(request, as: Int64)
end end
def count_users_active_1m : Int64 def count_users_active_6m : Int64
request = <<-SQL request = <<-SQL
SELECT count(*) FROM users SELECT count(*) FROM users
WHERE CURRENT_TIMESTAMP - updated < '6 months' WHERE CURRENT_TIMESTAMP - updated < '6 months'
@ -24,7 +24,7 @@ module Invidious::Database::Statistics
PG_DB.query_one(request, as: Int64) PG_DB.query_one(request, as: Int64)
end end
def count_users_active_6m : Int64 def count_users_active_1m : Int64
request = <<-SQL request = <<-SQL
SELECT count(*) FROM users SELECT count(*) FROM users
WHERE CURRENT_TIMESTAMP - updated < '1 month' WHERE CURRENT_TIMESTAMP - updated < '1 month'

View file

@ -33,7 +33,7 @@ module Invidious::Frontend::Comments
<a href="javascript:void(0)" data-onclick="toggle_parent">[ ]</a> <a href="javascript:void(0)" data-onclick="toggle_parent">[ ]</a>
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b> <b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b>
#{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)} #{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)}
<span title="#{child.created_utc.to_s(translate(locale, "%a %B %-d %T %Y UTC"))}">#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}</span> <span title="#{child.created_utc.to_s("%a %B %-d %T %Y UTC")}">#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}</span>
<a href="https://www.reddit.com#{child.permalink}" title="#{translate(locale, "permalink")}">#{translate(locale, "permalink")}</a> <a href="https://www.reddit.com#{child.permalink}" title="#{translate(locale, "permalink")}">#{translate(locale, "permalink")}</a>
</p> </p>
<div> <div>

View file

@ -107,6 +107,36 @@ module Invidious::Frontend::Comments
</div> </div>
END_HTML END_HTML
end end
when "multiImage"
html << <<-END_HTML
<section class="carousel">
<a class="skip-link" href="#skip-#{child["commentId"]}">#{translate(locale, "carousel_skip")}</a>
<div class="slides">
END_HTML
image_array = attachment["images"].as_a
image_array.each_index do |i|
html << <<-END_HTML
<div class="slides-item slide-#{i + 1}" id="#{child["commentId"]}-slide-#{i + 1}" aria-label="#{translate(locale, "carousel_slide", {"current" => (i + 1).to_s, "total" => image_array.size.to_s})}" tabindex="0">
<img loading="lazy" src="/ggpht#{URI.parse(image_array[i][1]["url"].as_s).request_target}" alt="" />
</div>
END_HTML
end
html << <<-END_HTML
</div>
<div class="carousel__nav">
END_HTML
attachment["images"].as_a.each_index do |i|
html << <<-END_HTML
<a class="slider-nav" href="##{child["commentId"]}-slide-#{i + 1}" aria-label="#{translate(locale, "carousel_go_to", (i + 1).to_s)}" tabindex="-1" aria-hidden="true">#{i + 1}</a>
END_HTML
end
html << <<-END_HTML
</div>
<div id="skip-#{child["commentId"]}"></div>
</section>
END_HTML
else nil # Ignore else nil # Ignore
end end
end end

View file

@ -142,63 +142,8 @@ class APIHandler < Kemal::Handler
exclude ["/api/v1/auth/notifications"], "POST" exclude ["/api/v1/auth/notifications"], "POST"
def call(env) def call(env)
return call_next env unless only_match? env env.response.headers["Access-Control-Allow-Origin"] = "*" if only_match?(env)
env.response.headers["Access-Control-Allow-Origin"] = "*"
# Since /api/v1/notifications is an event-stream, we don't want
# to wrap the response
return call_next env if exclude_match? env
# Here we swap out the socket IO so we can modify the response as needed
output = env.response.output
env.response.output = IO::Memory.new
begin
call_next env call_next env
env.response.output.rewind
if env.response.output.as(IO::Memory).size != 0 &&
env.response.headers.includes_word?("Content-Type", "application/json")
response = JSON.parse(env.response.output)
if fields_text = env.params.query["fields"]?
begin
JSONFilter.filter(response, fields_text)
rescue ex
env.response.status_code = 400
response = {"error" => ex.message}
end
end
if env.params.query["pretty"]?.try &.== "1"
response = response.to_pretty_json
else
response = response.to_json
end
else
response = env.response.output.gets_to_end
end
rescue ex
env.response.content_type = "application/json" if env.response.headers.includes_word?("Content-Type", "text/html")
env.response.status_code = 500
if env.response.headers.includes_word?("Content-Type", "application/json")
response = {"error" => ex.message || "Unspecified error"}
if env.params.query["pretty"]?.try &.== "1"
response = response.to_pretty_json
else
response = response.to_json
end
end
ensure
env.response.output = output
env.response.print response
env.response.flush
end
end end
end end

View file

@ -78,15 +78,6 @@ def create_notification_stream(env, topics, connection_channel)
video.published = published video.published = published
response = JSON.parse(video.to_json(locale, nil)) response = JSON.parse(video.to_json(locale, nil))
if fields_text = env.params.query["fields"]?
begin
JSONFilter.filter(response, fields_text)
rescue ex
env.response.status_code = 400
response = {"error" => ex.message}
end
end
env.response.puts "id: #{id}" env.response.puts "id: #{id}"
env.response.puts "data: #{response.to_json}" env.response.puts "data: #{response.to_json}"
env.response.puts env.response.puts
@ -113,15 +104,6 @@ def create_notification_stream(env, topics, connection_channel)
Invidious::Database::ChannelVideos.select_notfications(topic, since_unix).each do |video| Invidious::Database::ChannelVideos.select_notfications(topic, since_unix).each do |video|
response = JSON.parse(video.to_json(locale)) response = JSON.parse(video.to_json(locale))
if fields_text = env.params.query["fields"]?
begin
JSONFilter.filter(response, fields_text)
rescue ex
env.response.status_code = 400
response = {"error" => ex.message}
end
end
env.response.puts "id: #{id}" env.response.puts "id: #{id}"
env.response.puts "data: #{response.to_json}" env.response.puts "data: #{response.to_json}"
env.response.puts env.response.puts
@ -155,15 +137,6 @@ def create_notification_stream(env, topics, connection_channel)
video.published = Time.unix(published) video.published = Time.unix(published)
response = JSON.parse(video.to_json(locale, nil)) response = JSON.parse(video.to_json(locale, nil))
if fields_text = env.params.query["fields"]?
begin
JSONFilter.filter(response, fields_text)
rescue ex
env.response.status_code = 400
response = {"error" => ex.message}
end
end
env.response.puts "id: #{id}" env.response.puts "id: #{id}"
env.response.puts "data: #{response.to_json}" env.response.puts "data: #{response.to_json}"
env.response.puts env.response.puts

View file

@ -78,7 +78,7 @@ def load_all_locales
return locales return locales
end end
def translate(locale : String?, key : String, text : String | Nil = nil) : String def translate(locale : String?, key : String, text : String | Hash(String, String) | Nil = nil) : String
# Log a warning if "key" doesn't exist in en-US locale and return # Log a warning if "key" doesn't exist in en-US locale and return
# that key as the text, so this is more or less transparent to the user. # that key as the text, so this is more or less transparent to the user.
if !LOCALES["en-US"].has_key?(key) if !LOCALES["en-US"].has_key?(key)
@ -101,6 +101,7 @@ def translate(locale : String?, key : String, text : String | Nil = nil) : Strin
match_length = 0 match_length = 0
raw_data.as_h.each do |hash_key, value| raw_data.as_h.each do |hash_key, value|
if text.is_a?(String)
if md = text.try &.match(/#{hash_key}/) if md = text.try &.match(/#{hash_key}/)
if md[0].size >= match_length if md[0].size >= match_length
translation = value.as_s translation = value.as_s
@ -108,14 +109,20 @@ def translate(locale : String?, key : String, text : String | Nil = nil) : Strin
end end
end end
end end
end
when .as_s? when .as_s?
translation = raw_data.as_s translation = raw_data.as_s
else else
raise "Invalid translation \"#{raw_data}\"" raise "Invalid translation \"#{raw_data}\""
end end
if text if text.is_a?(String)
translation = translation.gsub("`x`", text) translation = translation.gsub("`x`", text)
elsif text.is_a?(Hash(String, String))
# adds support for multi string interpolation. Based on i18next https://www.i18next.com/translation-function/interpolation#basic
text.each_key do |hash_key|
translation = translation.gsub("{{#{hash_key}}}", text[hash_key])
end
end end
return translation return translation

View file

@ -47,19 +47,19 @@ module I18next::Plurals
private PLURAL_SETS = { private PLURAL_SETS = {
PluralForms::Single_gt_one => [ PluralForms::Single_gt_one => [
"ach", "ak", "am", "arn", "br", "fil", "gun", "ln", "mfe", "mg", "ach", "ak", "am", "arn", "br", "fa", "fil", "gun", "ln", "mfe", "mg",
"mi", "oc", "pt", "tg", "tl", "ti", "tr", "uz", "wa", "mi", "oc", "pt-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", "et", "eu", "fi", "fo", "fur", "fy", "gl", "gu", "ha", "hi",
"hu", "hy", "ia", "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", "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 => [
"ay", "bo", "cgg", "fa", "ht", "id", "ja", "jbo", "ka", "km", "ko", "ky", "ay", "bo", "cgg", "ht", "id", "ja", "jbo", "ka", "km", "ko", "ky",
"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 => [
@ -90,10 +90,12 @@ module I18next::Plurals
"sk" => PluralForms::Special_Czech_Slovak, "sk" => PluralForms::Special_Czech_Slovak,
"sl" => PluralForms::Special_Slovenian, "sl" => PluralForms::Special_Slovenian,
# Mixed v3/v4 rules # Mixed v3/v4 rules
"es" => PluralForms::Special_Spanish_Italian,
"fr" => PluralForms::Special_French_Portuguese, "fr" => PluralForms::Special_French_Portuguese,
"hr" => PluralForms::Special_Hungarian_Serbian, "hr" => PluralForms::Special_Hungarian_Serbian,
"it" => PluralForms::Special_Spanish_Italian, "it" => PluralForms::Special_Spanish_Italian,
"pt-BR" => PluralForms::Special_French_Portuguese, "pt" => PluralForms::Special_French_Portuguese,
"pt" => PluralForms::Special_French_Portuguese,
"sr" => PluralForms::Special_Hungarian_Serbian, "sr" => PluralForms::Special_Hungarian_Serbian,
} }
@ -165,7 +167,7 @@ module I18next::Plurals
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
if !locale.matches?(/^pt-BR$/) if !locale.matches?(/^pt-PT$/)
locale = locale.split('-')[0] locale = locale.split('-')[0]
end end

View file

@ -1,248 +0,0 @@
module JSONFilter
alias BracketIndex = Hash(Int64, Int64)
alias GroupedFieldsValue = String | Array(GroupedFieldsValue)
alias GroupedFieldsList = Array(GroupedFieldsValue)
class FieldsParser
class ParseError < Exception
end
# Returns the `Regex` pattern used to match nest groups
def self.nest_group_pattern : Regex
# uses a '.' character to match json keys as they are allowed
# to contain any unicode codepoint
/(?:|,)(?<groupname>[^,\n]*?)\(/
end
# Returns the `Regex` pattern used to check if there are any empty nest groups
def self.unnamed_nest_group_pattern : Regex
/^\(|\(\(|\/\(/
end
def self.parse_fields(fields_text : String, &) : Nil
if fields_text.empty?
raise FieldsParser::ParseError.new "Fields is empty"
end
opening_bracket_count = fields_text.count('(')
closing_bracket_count = fields_text.count(')')
if opening_bracket_count != closing_bracket_count
bracket_type = opening_bracket_count > closing_bracket_count ? "opening" : "closing"
raise FieldsParser::ParseError.new "There are too many #{bracket_type} brackets (#{opening_bracket_count}:#{closing_bracket_count})"
elsif match_result = unnamed_nest_group_pattern.match(fields_text)
raise FieldsParser::ParseError.new "Unnamed nest group at position #{match_result.begin}"
end
# first, handle top-level single nested properties: items/id, playlistItems/snippet, etc
parse_single_nests(fields_text) { |nest_list| yield nest_list }
# next, handle nest groups: items(id, etag, etc)
parse_nest_groups(fields_text) { |nest_list| yield nest_list }
end
def self.parse_single_nests(fields_text : String, &) : Nil
single_nests = remove_nest_groups(fields_text)
if !single_nests.empty?
property_nests = single_nests.split(',')
property_nests.each do |nest|
nest_list = nest.split('/')
if nest_list.includes? ""
raise FieldsParser::ParseError.new "Empty key in nest list: #{nest_list}"
end
yield nest_list
end
# else
# raise FieldsParser::ParseError.new "Empty key in nest list 22: #{fields_text} | #{single_nests}"
end
end
def self.parse_nest_groups(fields_text : String, &) : Nil
nest_stack = [] of NamedTuple(group_name: String, closing_bracket_index: Int64)
bracket_pairs = get_bracket_pairs(fields_text, true)
text_index = 0
regex_index = 0
while regex_result = self.nest_group_pattern.match(fields_text, regex_index)
raw_match = regex_result[0]
group_name = regex_result["groupname"]
text_index = regex_result.begin
regex_index = regex_result.end
if text_index.nil? || regex_index.nil?
raise FieldsParser::ParseError.new "Received invalid index while parsing nest groups: text_index: #{text_index} | regex_index: #{regex_index}"
end
offset = raw_match.starts_with?(',') ? 1 : 0
opening_bracket_index = (text_index + group_name.size) + offset
closing_bracket_index = bracket_pairs[opening_bracket_index]
content_start = opening_bracket_index + 1
content = fields_text[content_start...closing_bracket_index]
if content.empty?
raise FieldsParser::ParseError.new "Empty nest group at position #{content_start}"
else
content = remove_nest_groups(content)
end
while nest_stack.size > 0 && closing_bracket_index > nest_stack[nest_stack.size - 1][:closing_bracket_index]
if nest_stack.size
nest_stack.pop
end
end
group_name.split('/').each do |name|
nest_stack.push({
group_name: name,
closing_bracket_index: closing_bracket_index,
})
end
if !content.empty?
properties = content.split(',')
properties.each do |prop|
nest_list = nest_stack.map { |nest_prop| nest_prop[:group_name] }
if !prop.empty?
if prop.includes?('/')
parse_single_nests(prop) { |list| nest_list += list }
else
nest_list.push prop
end
else
raise FieldsParser::ParseError.new "Empty key in nest list: #{nest_list << prop}"
end
yield nest_list
end
end
end
end
def self.remove_nest_groups(text : String) : String
content_bracket_pairs = get_bracket_pairs(text, false)
content_bracket_pairs.each_key.to_a.reverse.each do |opening_bracket|
closing_bracket = content_bracket_pairs[opening_bracket]
last_comma = text.rindex(',', opening_bracket) || 0
text = text[0...last_comma] + text[closing_bracket + 1...text.size]
end
return text.starts_with?(',') ? text[1...text.size] : text
end
def self.get_bracket_pairs(text : String, recursive = true) : BracketIndex
istart = [] of Int64
bracket_index = BracketIndex.new
text.each_char_with_index do |char, index|
if char == '('
istart.push(index.to_i64)
end
if char == ')'
begin
opening = istart.pop
if recursive || (!recursive && istart.size == 0)
bracket_index[opening] = index.to_i64
end
rescue
raise FieldsParser::ParseError.new "No matching opening parenthesis at: #{index}"
end
end
end
if istart.size != 0
idx = istart.pop
raise FieldsParser::ParseError.new "No matching closing parenthesis at: #{idx}"
end
return bracket_index
end
end
class FieldsGrouper
alias SkeletonValue = Hash(String, SkeletonValue)
def self.create_json_skeleton(fields_text : String) : SkeletonValue
root_hash = {} of String => SkeletonValue
FieldsParser.parse_fields(fields_text) do |nest_list|
current_item = root_hash
nest_list.each do |key|
if current_item[key]?
current_item = current_item[key]
else
current_item[key] = {} of String => SkeletonValue
current_item = current_item[key]
end
end
end
root_hash
end
def self.create_grouped_fields_list(json_skeleton : SkeletonValue) : GroupedFieldsList
grouped_fields_list = GroupedFieldsList.new
json_skeleton.each do |key, value|
grouped_fields_list.push key
nested_keys = create_grouped_fields_list(value)
grouped_fields_list.push nested_keys unless nested_keys.empty?
end
return grouped_fields_list
end
end
class FilterError < Exception
end
def self.filter(item : JSON::Any, fields_text : String, in_place : Bool = true)
skeleton = FieldsGrouper.create_json_skeleton(fields_text)
grouped_fields_list = FieldsGrouper.create_grouped_fields_list(skeleton)
filter(item, grouped_fields_list, in_place)
end
def self.filter(item : JSON::Any, grouped_fields_list : GroupedFieldsList, in_place : Bool = true) : JSON::Any
item = item.clone unless in_place
if !item.as_h? && !item.as_a?
raise FilterError.new "Can't filter '#{item}' by #{grouped_fields_list}"
end
top_level_keys = Array(String).new
grouped_fields_list.each do |value|
if value.is_a? String
top_level_keys.push value
elsif value.is_a? Array
if !top_level_keys.empty?
key_to_filter = top_level_keys.last
if item.as_h?
filter(item[key_to_filter], value, in_place: true)
elsif item.as_a?
item.as_a.each { |arr_item| filter(arr_item[key_to_filter], value, in_place: true) }
end
else
raise FilterError.new "Tried to filter while top level keys list is empty"
end
end
end
if item.as_h?
item.as_h.select! top_level_keys
elsif item.as_a?
item.as_a.map { |value| filter(value, top_level_keys, in_place: true) }
end
item
end
end

View file

@ -262,7 +262,7 @@ def get_referer(env, fallback = "/", unroll = true)
end end
referer = referer.request_target referer = referer.request_target
referer = "/" + referer.gsub(/[^\/?@&%=\-_.:,0-9a-zA-Z]/, "").lstrip("/\\") referer = "/" + referer.gsub(/[^\/?@&%=\-_.:,*0-9a-zA-Z]/, "").lstrip("/\\")
if referer == env.request.path if referer == env.request.path
referer = fallback referer = fallback
@ -323,68 +323,6 @@ def parse_range(range)
return 0_i64, nil return 0_i64, nil
end end
def fetch_random_instance
begin
instance_api_client = make_client(URI.parse("https://api.invidious.io"))
# Timeouts
instance_api_client.connect_timeout = 10.seconds
instance_api_client.dns_timeout = 10.seconds
instance_list = JSON.parse(instance_api_client.get("/instances.json").body).as_a
instance_api_client.close
rescue Socket::ConnectError | IO::TimeoutError | JSON::ParseException
instance_list = [] of JSON::Any
end
filtered_instance_list = [] of String
instance_list.each do |data|
# TODO Check if current URL is onion instance and use .onion types if so.
if data[1]["type"] == "https"
# Instances can have statistics disabled, which is an requirement of version validation.
# as_nil? doesn't exist. Thus we'll have to handle the error raised if as_nil fails.
begin
data[1]["stats"].as_nil
next
rescue TypeCastError
end
# stats endpoint could also lack the software dict.
next if data[1]["stats"]["software"]?.nil?
# Makes sure the instance isn't too outdated.
if remote_version = data[1]["stats"]?.try &.["software"]?.try &.["version"]
remote_commit_date = remote_version.as_s.match(/\d{4}\.\d{2}\.\d{2}/)
next if !remote_commit_date
remote_commit_date = Time.parse(remote_commit_date[0], "%Y.%m.%d", Time::Location::UTC)
local_commit_date = Time.parse(CURRENT_VERSION, "%Y.%m.%d", Time::Location::UTC)
next if (remote_commit_date - local_commit_date).abs.days > 30
begin
data[1]["monitor"].as_nil
health = data[1]["monitor"].as_h["dailyRatios"][0].as_h["ratio"]
filtered_instance_list << data[0].as_s if health.to_s.to_f > 90
rescue TypeCastError
# We can't check the health if the monitoring is broken. Thus we'll just add it to the list
# and move on. Ideally we'll ignore any instance that has broken health monitoring but due to the fact that
# it's an error that often occurs with all the instances at the same time, we have to just skip the check.
filtered_instance_list << data[0].as_s
end
end
end
end
# If for some reason no instances managed to get fetched successfully then we'll just redirect to redirect.invidious.io
if filtered_instance_list.size == 0
return "redirect.invidious.io"
end
return filtered_instance_list.sample(1)[0]
end
def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = "") : String def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = "") : String
str = uri.to_s.sub(/^https?:\/\//, "") str = uri.to_s.sub(/^https?:\/\//, "")
if str.size > max_length if str.size > max_length

View file

@ -4,13 +4,23 @@
module WebVTT module WebVTT
# A WebVTT builder generates WebVTT files # A WebVTT builder generates WebVTT files
private class Builder private class Builder
# See https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API#cue_payload
private ESCAPE_SUBSTITUTIONS = {
'&' => "&amp;",
'<' => "&lt;",
'>' => "&gt;",
'\u200E' => "&lrm;",
'\u200F' => "&rlm;",
'\u00A0' => "&nbsp;",
}
def initialize(@io : IO) def initialize(@io : IO)
end end
# Writes an vtt cue with the specified time stamp and contents # Writes an vtt cue with the specified time stamp and contents
def cue(start_time : Time::Span, end_time : Time::Span, text : String) def cue(start_time : Time::Span, end_time : Time::Span, text : String)
timestamp(start_time, end_time) timestamp(start_time, end_time)
@io << text @io << self.escape(text)
@io << "\n\n" @io << "\n\n"
end end
@ -29,6 +39,10 @@ module WebVTT
@io << '.' << timestamp.milliseconds.to_s.rjust(3, '0') @io << '.' << timestamp.milliseconds.to_s.rjust(3, '0')
end end
private def escape(text : String) : String
return text.gsub(ESCAPE_SUBSTITUTIONS)
end
def document(setting_fields : Hash(String, String)? = nil, &) def document(setting_fields : Hash(String, String)? = nil, &)
@io << "WEBVTT\n" @io << "WEBVTT\n"

View file

@ -0,0 +1,97 @@
class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
# We update the internals of a constant as so it can be accessed from anywhere
# within the codebase
#
# "INSTANCES" => Array(Tuple(String, String)) # region, instance
INSTANCES = {"INSTANCES" => [] of Tuple(String, String)}
def initialize
end
def begin
loop do
refresh_instances
LOGGER.info("InstanceListRefreshJob: Done, sleeping for 30 minutes")
sleep 30.minute
Fiber.yield
end
end
# Refreshes the list of instances used for redirects.
#
# Does the following three checks for each instance
# - Is it a clear-net instance?
# - Is it an instance with a good uptime?
# - Is it an updated instance?
private def refresh_instances
raw_instance_list = self.fetch_instances
filtered_instance_list = [] of Tuple(String, String)
raw_instance_list.each do |instance_data|
# TODO allow Tor hidden service instances when the current instance
# is also a hidden service. Same for i2p and any other non-clearnet instances.
begin
domain = instance_data[0]
info = instance_data[1]
stats = info["stats"]
next unless info["type"] == "https"
next if bad_uptime?(info["monitor"])
next if outdated?(stats["software"]["version"])
filtered_instance_list << {info["region"].as_s, domain.as_s}
rescue ex
if domain
LOGGER.info("InstanceListRefreshJob: failed to parse information from '#{domain}' because \"#{ex}\"\n\"#{ex.backtrace.join('\n')}\" ")
else
LOGGER.info("InstanceListRefreshJob: failed to parse information from an instance because \"#{ex}\"\n\"#{ex.backtrace.join('\n')}\" ")
end
end
end
if !filtered_instance_list.empty?
INSTANCES["INSTANCES"] = filtered_instance_list
end
end
# Fetches information regarding instances from api.invidious.io or an otherwise configured URL
private def fetch_instances : Array(JSON::Any)
begin
# We directly call the stdlib HTTP::Client here as it allows us to negate the effects
# of the force_resolve config option. This is needed as api.invidious.io does not support ipv6
# and as such the following request raises if we were to use force_resolve with the ipv6 value.
instance_api_client = HTTP::Client.new(URI.parse("https://api.invidious.io"))
# Timeouts
instance_api_client.connect_timeout = 10.seconds
instance_api_client.dns_timeout = 10.seconds
raw_instance_list = JSON.parse(instance_api_client.get("/instances.json").body).as_a
instance_api_client.close
rescue ex : Socket::ConnectError | IO::TimeoutError | JSON::ParseException
raw_instance_list = [] of JSON::Any
end
return raw_instance_list
end
# Checks if the given target instance is outdated
private def outdated?(target_instance_version) : Bool
remote_commit_date = target_instance_version.as_s.match(/\d{4}\.\d{2}\.\d{2}/)
return false if !remote_commit_date
remote_commit_date = Time.parse(remote_commit_date[0], "%Y.%m.%d", Time::Location::UTC)
local_commit_date = Time.parse(CURRENT_VERSION, "%Y.%m.%d", Time::Location::UTC)
return (remote_commit_date - local_commit_date).abs.days > 30
end
# Checks if the uptime of the target instance is greater than 90% over a 30 day period
private def bad_uptime?(target_instance_health_monitor) : Bool
return true if !target_instance_health_monitor["down"].as_bool == false
return true if target_instance_health_monitor["uptime"].as_f < 90
return false
end
end

View file

@ -56,8 +56,8 @@ class Invidious::Jobs::StatisticsRefreshJob < Invidious::Jobs::BaseJob
users = STATISTICS.dig("usage", "users").as(Hash(String, Int64)) users = STATISTICS.dig("usage", "users").as(Hash(String, Int64))
users["total"] = Invidious::Database::Statistics.count_users_total users["total"] = Invidious::Database::Statistics.count_users_total
users["activeHalfyear"] = Invidious::Database::Statistics.count_users_active_1m users["activeHalfyear"] = Invidious::Database::Statistics.count_users_active_6m
users["activeMonth"] = Invidious::Database::Statistics.count_users_active_6m users["activeMonth"] = Invidious::Database::Statistics.count_users_active_1m
STATISTICS["metadata"] = { STATISTICS["metadata"] = {
"updatedAt" => Time.utc.to_unix, "updatedAt" => Time.utc.to_unix,

View file

@ -62,6 +62,7 @@ module Invidious::JSONify::APIv1
json.field "rating", 0_i64 json.field "rating", 0_i64
json.field "isListed", video.is_listed json.field "isListed", video.is_listed
json.field "liveNow", video.live_now json.field "liveNow", video.live_now
json.field "isPostLiveDvr", video.post_live_dvr
json.field "isUpcoming", video.is_upcoming json.field "isUpcoming", video.is_upcoming
if video.premiere_timestamp if video.premiere_timestamp
@ -160,6 +161,8 @@ module Invidious::JSONify::APIv1
json.field "type", fmt["mimeType"] json.field "type", fmt["mimeType"]
json.field "quality", fmt["quality"] json.field "quality", fmt["quality"]
json.field "bitrate", fmt["bitrate"].as_i.to_s if fmt["bitrate"]?
fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"]) fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
if fmt_info if fmt_info
fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.as_i || 30 fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.as_i || 30
@ -227,6 +230,7 @@ module Invidious::JSONify::APIv1
json.field "author", rv["author"] json.field "author", rv["author"]
json.field "authorUrl", "/channel/#{rv["ucid"]?}" json.field "authorUrl", "/channel/#{rv["ucid"]?}"
json.field "authorId", rv["ucid"]? json.field "authorId", rv["ucid"]?
json.field "authorVerified", rv["author_verified"] == "true"
if rv["author_thumbnail"]? if rv["author_thumbnail"]?
json.field "authorThumbnails" do json.field "authorThumbnails" do
json.array do json.array do

View file

@ -21,7 +21,13 @@ module Invidious::Routes::API::Manifest
end end
if dashmpd = video.dash_manifest_url if dashmpd = video.dash_manifest_url
manifest = YT_POOL.client &.get(URI.parse(dashmpd).request_target).body response = YT_POOL.client &.get(URI.parse(dashmpd).request_target)
if response.status_code != 200
haltf env, status_code: response.status_code
end
manifest = response.body
manifest = manifest.gsub(/<BaseURL>[^<]+<\/BaseURL>/) do |baseurl| manifest = manifest.gsub(/<BaseURL>[^<]+<\/BaseURL>/) do |baseurl|
url = baseurl.lchop("<BaseURL>") url = baseurl.lchop("<BaseURL>")

View file

@ -90,6 +90,7 @@ module Invidious::Routes::API::V1::Channels
json.field "allowedRegions", channel.allowed_regions json.field "allowedRegions", channel.allowed_regions
json.field "tabs", channel.tabs json.field "tabs", channel.tabs
json.field "tags", channel.tags
json.field "authorVerified", channel.verified json.field "authorVerified", channel.verified
json.field "latestVideos" do json.field "latestVideos" do
@ -393,7 +394,7 @@ module Invidious::Routes::API::V1::Channels
else else
comments = YoutubeAPI.browse(continuation: continuation) comments = YoutubeAPI.browse(continuation: continuation)
end end
return Comments.parse_youtube(id, comments, format, locale, thin_mode, isPost: true) return Comments.parse_youtube(id, comments, format, locale, thin_mode, is_post: true)
end end
def self.channels(env) def self.channels(env)

View file

@ -191,6 +191,8 @@ module Invidious::Routes::API::V1::Misc
json.object do json.object do
json.field "ucid", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]? 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 "videoId", sub_endpoint["videoId"].as_s if sub_endpoint["videoId"]?
json.field "playlistId", sub_endpoint["playlistId"].as_s if sub_endpoint["playlistId"]?
json.field "startTimeSeconds", sub_endpoint["startTimeSeconds"].as_i if sub_endpoint["startTimeSeconds"]?
json.field "params", params.try &.as_s json.field "params", params.try &.as_s
json.field "pageType", pageType json.field "pageType", pageType
end end

View file

@ -32,11 +32,14 @@ module Invidious::Routes::API::V1::Search
begin begin
client = HTTP::Client.new("suggestqueries-clients6.youtube.com") client = HTTP::Client.new("suggestqueries-clients6.youtube.com")
url = "/complete/search?client=youtube&hl=en&gl=#{region}&q=#{URI.encode_www_form(query)}&xssi=t&gs_ri=youtube&ds=yt" client.before_request { |r| add_yt_headers(r) }
url = "/complete/search?client=youtube&hl=en&gl=#{region}&q=#{URI.encode_www_form(query)}&gs_ri=youtube&ds=yt"
response = client.get(url).body response = client.get(url).body
client.close
body = JSON.parse(response[5..-1]).as_a body = JSON.parse(response[19..-2]).as_a
suggestions = body[1].as_a[0..-2] suggestions = body[1].as_a[0..-2]
JSON.build do |json| JSON.build do |json|

View file

@ -363,4 +363,47 @@ module Invidious::Routes::API::V1::Videos
end end
end end
end end
def self.clips(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
clip_id = env.params.url["id"]
region = env.params.query["region"]?
proxy = {"1", "true"}.any? &.== env.params.query["local"]?
response = YoutubeAPI.resolve_url("https://www.youtube.com/clip/#{clip_id}")
return error_json(400, "Invalid clip ID") if response["error"]?
video_id = response.dig?("endpoint", "watchEndpoint", "videoId").try &.as_s
return error_json(400, "Invalid clip ID") if video_id.nil?
start_time = nil
end_time = nil
clip_title = nil
if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s
start_time, end_time, clip_title = parse_clip_parameters(params)
end
begin
video = get_video(video_id, region: region)
rescue ex : NotFoundException
return error_json(404, ex)
rescue ex
return error_json(500, ex)
end
return JSON.build do |json|
json.object do
json.field "startTime", start_time
json.field "endTime", end_time
json.field "clipTitle", clip_title
json.field "video" do
Invidious::JSONify::APIv1.video(video, json, locale: locale, proxy: proxy)
end
end
end
end
end end

View file

@ -231,7 +231,7 @@ module Invidious::Routes::Channels
if nojs if nojs
comments = Comments.fetch_community_post_comments(ucid, id) comments = Comments.fetch_community_post_comments(ucid, id)
comment_html = JSON.parse(Comments.parse_youtube(id, comments, "html", locale, thin_mode, isPost: true))["contentHtml"] comment_html = JSON.parse(Comments.parse_youtube(id, comments, "html", locale, thin_mode, is_post: true))["contentHtml"]
end end
templated "post" templated "post"
end end

View file

@ -407,14 +407,23 @@ module Invidious::Routes::Feeds
end end
spawn do spawn do
rss = XML.parse_html(body) # TODO: unify this with the other almost identical looking parts in this and channels.cr somehow?
rss.xpath_nodes("//feed/entry").each do |entry| namespaces = {
id = entry.xpath_node("videoid").not_nil!.content "yt" => "http://www.youtube.com/xml/schemas/2015",
author = entry.xpath_node("author/name").not_nil!.content "default" => "http://www.w3.org/2005/Atom",
published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content) }
updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content) rss = XML.parse(body)
rss.xpath_nodes("//default:feed/default:entry", namespaces).each do |entry|
id = entry.xpath_node("yt:videoId", namespaces).not_nil!.content
author = entry.xpath_node("default:author/default:name", namespaces).not_nil!.content
published = Time.parse_rfc3339(entry.xpath_node("default:published", namespaces).not_nil!.content)
updated = Time.parse_rfc3339(entry.xpath_node("default:updated", namespaces).not_nil!.content)
begin
video = get_video(id, force_refresh: true) video = get_video(id, force_refresh: true)
rescue
next # skip this video since it raised an exception (e.g. it is a scheduled live event)
end
if CONFIG.enable_user_notifications if CONFIG.enable_user_notifications
# Deliver notifications to `/api/v1/auth/notifications` # Deliver notifications to `/api/v1/auth/notifications`

View file

@ -40,7 +40,16 @@ module Invidious::Routes::Misc
def self.cross_instance_redirect(env) def self.cross_instance_redirect(env)
referer = get_referer(env) referer = get_referer(env)
instance_url = fetch_random_instance
instance_list = Invidious::Jobs::InstanceListRefreshJob::INSTANCES["INSTANCES"]
if instance_list.empty?
instance_url = "redirect.invidious.io"
else
# Sample returns an array
# Instances are packaged as {region, domain} in the instance list
instance_url = instance_list.sample(1)[0][1]
end
env.redirect "https://#{instance_url}#{referer}" env.redirect "https://#{instance_url}#{referer}"
end end
end end

View file

@ -42,7 +42,7 @@ module Invidious::Routes::VideoPlayback
headers["Range"] = "bytes=#{range_for_head}" headers["Range"] = "bytes=#{range_for_head}"
end end
client = make_client(URI.parse(host), region) client = make_client(URI.parse(host), region, force_resolve = true)
response = HTTP::Client::Response.new(500) response = HTTP::Client::Response.new(500)
error = "" error = ""
5.times do 5.times do
@ -57,7 +57,7 @@ module Invidious::Routes::VideoPlayback
if new_host != host if new_host != host
host = new_host host = new_host
client.close client.close
client = make_client(URI.parse(new_host), region) client = make_client(URI.parse(new_host), region, force_resolve = true)
end end
url = "#{location.request_target}&host=#{location.host}#{region ? "&region=#{region}" : ""}" url = "#{location.request_target}&host=#{location.host}#{region ? "&region=#{region}" : ""}"
@ -71,7 +71,7 @@ module Invidious::Routes::VideoPlayback
fvip = "3" fvip = "3"
host = "https://r#{fvip}---#{mn}.googlevideo.com" host = "https://r#{fvip}---#{mn}.googlevideo.com"
client = make_client(URI.parse(host), region) client = make_client(URI.parse(host), region, force_resolve = true)
rescue ex rescue ex
error = ex.message error = ex.message
end end
@ -196,7 +196,7 @@ module Invidious::Routes::VideoPlayback
break break
else else
client.close client.close
client = make_client(URI.parse(host), region) client = make_client(URI.parse(host), region, force_resolve = true)
end end
end end

View file

@ -275,6 +275,12 @@ module Invidious::Routes::Watch
return error_template(400, "Invalid clip ID") if response["error"]? return error_template(400, "Invalid clip ID") if response["error"]?
if video_id = response.dig?("endpoint", "watchEndpoint", "videoId") if video_id = response.dig?("endpoint", "watchEndpoint", "videoId")
if params = response.dig?("endpoint", "watchEndpoint", "params").try &.as_s
start_time, end_time, _ = parse_clip_parameters(params)
env.params.query["start"] = start_time.to_s if start_time != nil
env.params.query["end"] = end_time.to_s if end_time != nil
end
return env.redirect "/watch?v=#{video_id}&#{env.params.query}" return env.redirect "/watch?v=#{video_id}&#{env.params.query}"
else else
return error_template(404, "The requested clip doesn't exist") return error_template(404, "The requested clip doesn't exist")

View file

@ -235,6 +235,7 @@ module Invidious::Routing
get "/api/v1/captions/:id", {{namespace}}::Videos, :captions get "/api/v1/captions/:id", {{namespace}}::Videos, :captions
get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations
get "/api/v1/comments/:id", {{namespace}}::Videos, :comments get "/api/v1/comments/:id", {{namespace}}::Videos, :comments
get "/api/v1/clips/:id", {{namespace}}::Videos, :clips
# Feeds # Feeds
get "/api/v1/trending", {{namespace}}::Feeds, :trending get "/api/v1/trending", {{namespace}}::Feeds, :trending

View file

@ -300,9 +300,9 @@ module Invidious::Search
object["9:varint"] = ((page - 1) * 20).to_i64 object["9:varint"] = ((page - 1) * 20).to_i64
end end
# If the object is empty, return an empty string, # Prevent censoring of self harm topics
# otherwise encode to protobuf then to base64 # See https://github.com/iv-org/invidious/issues/4398
return "" if object.empty? object["30:varint"] = 1.to_i64
return object return object
.try { |i| Protodec::Any.cast_json(i) } .try { |i| Protodec::Any.cast_json(i) }

View file

@ -22,12 +22,14 @@ def fetch_trending(trending_type, region, locale)
extracted = [] of SearchItem extracted = [] of SearchItem
deduplicate = items.size > 1
items.each do |itm| items.each do |itm|
if itm.is_a?(Category) if itm.is_a?(Category)
# Ignore the smaller categories, as they generally contain a sponsored # Ignore the smaller categories, as they generally contain a sponsored
# channel, which brings a lot of noise on the trending page. # channel, which brings a lot of noise on the trending page.
# See: https://github.com/iv-org/invidious/issues/2989 # See: https://github.com/iv-org/invidious/issues/2989
next if itm.contents.size < 24 next if (itm.contents.size < 24 && deduplicate)
extracted.concat extract_category(itm) extracted.concat extract_category(itm)
else else

View file

@ -82,6 +82,10 @@ struct Video
return (self.video_type == VideoType::Livestream) return (self.video_type == VideoType::Livestream)
end end
def post_live_dvr
return info["isPostLiveDvr"].as_bool
end
def premiere_timestamp : Time? def premiere_timestamp : Time?
info info
.dig?("microformat", "playerMicroformatRenderer", "liveBroadcastDetails", "startTimestamp") .dig?("microformat", "playerMicroformatRenderer", "liveBroadcastDetails", "startTimestamp")
@ -394,17 +398,6 @@ def fetch_video(id, region)
.dig?("microformat", "playerMicroformatRenderer", "availableCountries") .dig?("microformat", "playerMicroformatRenderer", "availableCountries")
.try &.as_a.map &.as_s || [] of String .try &.as_a.map &.as_s || [] of String
# Check for region-blocks
if info["reason"]?.try &.as_s.includes?("your country")
bypass_regions = PROXY_LIST.keys & allowed_regions
if !bypass_regions.empty?
region = bypass_regions[rand(bypass_regions.size)]
region_info = extract_video_info(video_id: id, proxy_region: region)
region_info["region"] = JSON::Any.new(region) if region
info = region_info if !region_info["reason"]?
end
end
if reason = info["reason"]? if reason = info["reason"]?
if reason == "Video unavailable" if reason == "Video unavailable"
raise NotFoundException.new(reason.as_s || "") raise NotFoundException.new(reason.as_s || "")

View file

@ -0,0 +1,22 @@
require "json"
# returns start_time, end_time and clip_title
def parse_clip_parameters(params) : {Float64?, Float64?, String?}
decoded_protobuf = params.try { |i| URI.decode_www_form(i) }
.try { |i| Base64.decode(i) }
.try { |i| IO::Memory.new(i) }
.try { |i| Protodec::Any.parse(i) }
start_time = decoded_protobuf
.try(&.["50:0:embedded"]["2:1:varint"].as_i64)
.try { |i| i/1000 }
end_time = decoded_protobuf
.try(&.["50:0:embedded"]["3:2:varint"].as_i64)
.try { |i| i/1000 }
clip_title = decoded_protobuf
.try(&.["50:0:embedded"]["4:3:string"].as_s)
return start_time, end_time, clip_title
end

View file

@ -7,7 +7,19 @@ private def copy_string(str : String::Builder, iter : Iterator, count : Int) : I
cp = iter.next cp = iter.next
break if cp.is_a?(Iterator::Stop) break if cp.is_a?(Iterator::Stop)
if cp == 0x26 # Ampersand (&)
str << "&amp;"
elsif cp == 0x27 # Single quote (')
str << "&#39;"
elsif cp == 0x22 # Double quote (")
str << "&quot;"
elsif cp == 0x3C # Less-than (<)
str << "&lt;"
elsif cp == 0x3E # Greater than (>)
str << "&gt;"
else
str << cp.chr str << cp.chr
end
# A codepoint from the SMP counts twice # A codepoint from the SMP counts twice
copied += 1 if cp > 0xFFFF copied += 1 if cp > 0xFFFF

View file

@ -50,9 +50,9 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
} }
end end
def extract_video_info(video_id : String, proxy_region : String? = nil) def extract_video_info(video_id : String)
# Init client config for the API # Init client config for the API
client_config = YoutubeAPI::ClientConfig.new(proxy_region: proxy_region) client_config = YoutubeAPI::ClientConfig.new
# Fetch data from the player endpoint # Fetch data from the player endpoint
player_response = YoutubeAPI.player(video_id: video_id, params: "", client_config: client_config) player_response = YoutubeAPI.player(video_id: video_id, params: "", client_config: client_config)
@ -107,11 +107,7 @@ def extract_video_info(video_id : String, proxy_region : String? = nil)
# decrypted URLs and maybe fix throttling issues (#2194). See the # decrypted URLs and maybe fix throttling issues (#2194). See the
# following issue for an explanation about decrypted URLs: # following issue for an explanation about decrypted URLs:
# https://github.com/TeamNewPipe/NewPipeExtractor/issues/562 # https://github.com/TeamNewPipe/NewPipeExtractor/issues/562
client_config.client_type = YoutubeAPI::ClientType::Android client_config.client_type = YoutubeAPI::ClientType::AndroidTestSuite
new_player_response = try_fetch_streaming_data(video_id, client_config)
elsif !reason.includes?("your country") # Handled separately
# The Android embedded client could help here
client_config.client_type = YoutubeAPI::ClientType::AndroidScreenEmbed
new_player_response = try_fetch_streaming_data(video_id, client_config) new_player_response = try_fetch_streaming_data(video_id, client_config)
end end
@ -123,8 +119,9 @@ def extract_video_info(video_id : String, proxy_region : String? = nil)
# Replace player response and reset reason # Replace player response and reset reason
if !new_player_response.nil? if !new_player_response.nil?
# Preserve storyboard data before replacement # Preserve captions & storyboard data before replacement
new_player_response["storyboards"] = player_response["storyboards"] if player_response["storyboards"]? new_player_response["storyboards"] = player_response["storyboards"] if player_response["storyboards"]?
new_player_response["captions"] = player_response["captions"] if player_response["captions"]?
player_response = new_player_response player_response = new_player_response
params.delete("reason") params.delete("reason")
@ -142,8 +139,7 @@ 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.")
# 2AMBCgIQBg is a workaround for streaming URLs that returns a 403. response = YoutubeAPI.player(video_id: id, params: "2AMB", client_config: client_config)
response = YoutubeAPI.player(video_id: id, params: "2AMBCgIQBg", 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}.")
@ -151,7 +147,7 @@ def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConf
if id != response.dig("videoDetails", "videoId") if id != response.dig("videoDetails", "videoId")
# YouTube may return a different video player response than expected. # YouTube may return a different video player response than expected.
# See: https://github.com/TeamNewPipe/NewPipe/issues/8713 # See: https://github.com/TeamNewPipe/NewPipe/issues/8713
raise VideoNotAvailableException.new( raise InfoException.new(
"The video returned by YouTube isn't the requested one. (#{client_config.client_type} client)" "The video returned by YouTube isn't the requested one. (#{client_config.client_type} client)"
) )
elsif playability_status == "OK" elsif playability_status == "OK"
@ -215,6 +211,9 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
live_now = microformat.dig?("liveBroadcastDetails", "isLiveNow") live_now = microformat.dig?("liveBroadcastDetails", "isLiveNow")
.try &.as_bool || false .try &.as_bool || false
post_live_dvr = video_details.dig?("isPostLiveDvr")
.try &.as_bool || false
# Extra video infos # Extra video infos
allowed_regions = microformat["availableCountries"]? allowed_regions = microformat["availableCountries"]?
@ -266,7 +265,18 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
.try &.dig?("videoActions", "menuRenderer", "topLevelButtons") .try &.dig?("videoActions", "menuRenderer", "topLevelButtons")
if toplevel_buttons if toplevel_buttons
likes_button = toplevel_buttons.try &.as_a # New Format as of december 2023
likes_button = toplevel_buttons.dig?(0,
"segmentedLikeDislikeButtonViewModel",
"likeButtonViewModel",
"likeButtonViewModel",
"toggleButtonViewModel",
"toggleButtonViewModel",
"defaultButtonViewModel",
"buttonViewModel"
)
likes_button ||= toplevel_buttons.try &.as_a
.find(&.dig?("toggleButtonRenderer", "defaultIcon", "iconType").=== "LIKE") .find(&.dig?("toggleButtonRenderer", "defaultIcon", "iconType").=== "LIKE")
.try &.["toggleButtonRenderer"] .try &.["toggleButtonRenderer"]
@ -279,9 +289,10 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
) )
if likes_button if likes_button
likes_txt = likes_button.dig?("accessibilityText")
# Note: The like count from `toggledText` is off by one, as it would # Note: The like count from `toggledText` is off by one, as it would
# represent the new like count in the event where the user clicks on "like". # represent the new like count in the event where the user clicks on "like".
likes_txt = (likes_button["defaultText"]? || likes_button["toggledText"]?) likes_txt ||= (likes_button["defaultText"]? || likes_button["toggledText"]?)
.try &.dig?("accessibility", "accessibilityData", "label") .try &.dig?("accessibility", "accessibilityData", "label")
likes = likes_txt.as_s.gsub(/\D/, "").to_i64? if likes_txt likes = likes_txt.as_s.gsub(/\D/, "").to_i64? if likes_txt
@ -404,6 +415,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
"isListed" => JSON::Any.new(is_listed || false), "isListed" => JSON::Any.new(is_listed || false),
"isUpcoming" => JSON::Any.new(is_upcoming || false), "isUpcoming" => JSON::Any.new(is_upcoming || false),
"keywords" => JSON::Any.new(keywords.map { |v| JSON::Any.new(v) }), "keywords" => JSON::Any.new(keywords.map { |v| JSON::Any.new(v) }),
"isPostLiveDvr" => JSON::Any.new(post_live_dvr),
# Related videos # Related videos
"relatedVideos" => JSON::Any.new(related), "relatedVideos" => JSON::Any.new(related),
# Description # Description

View file

@ -82,11 +82,19 @@
</div> </div>
<div class="video-card-row flexible"> <div class="video-card-row flexible">
<div class="flex-left"><a href="/channel/<%= item.ucid %>"> <div class="flex-left">
<% if !item.ucid.to_s.empty? %>
<a href="/channel/<%= item.ucid %>">
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %> <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
<%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%> <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
</p> </p>
</a></div> </a>
<% else %>
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
<%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
</p>
<% end %>
</div>
</div> </div>
<% when Category %> <% when Category %>
<% else %> <% else %>
@ -160,11 +168,19 @@
</div> </div>
<div class="video-card-row flexible"> <div class="video-card-row flexible">
<div class="flex-left"><a href="/channel/<%= item.ucid %>"> <div class="flex-left">
<% if !item.ucid.to_s.empty? %>
<a href="/channel/<%= item.ucid %>">
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %> <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
<%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%> <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
</p> </p>
</a></div> </a>
<% else %>
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
<%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
</p>
<% end %>
</div>
<%= rendered "components/video-context-buttons" %> <%= rendered "components/video-context-buttons" %>
</div> </div>

View file

@ -1,5 +1,9 @@
<%
locale = env.get("preferences").as(Preferences).locale
dark_mode = env.get("preferences").as(Preferences).dark_mode
%>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="<%= env.get("preferences").as(Preferences).locale %>"> <html lang="<%= locale %>">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@ -17,19 +21,14 @@
<link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/carousel.css?v=<%= ASSET_COMMIT %>">
<script src="/js/_helpers.js?v=<%= ASSET_COMMIT %>"></script> <script src="/js/_helpers.js?v=<%= ASSET_COMMIT %>"></script>
</head> </head>
<%
locale = env.get("preferences").as(Preferences).locale
dark_mode = env.get("preferences").as(Preferences).dark_mode
%>
<body class="<%= dark_mode.blank? ? "no" : dark_mode %>-theme"> <body class="<%= dark_mode.blank? ? "no" : dark_mode %>-theme">
<span style="display:none" id="dark_mode_pref"><%= env.get("preferences").as(Preferences).dark_mode %></span> <span style="display:none" id="dark_mode_pref"><%= dark_mode %></span>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-md-2-24"></div> <div class="pure-u-1 pure-u-xl-20-24" id="contents">
<div class="pure-u-1 pure-u-md-20-24" id="contents">
<div class="pure-g navbar h-box"> <div class="pure-g navbar h-box">
<% if navbar_search %> <% if navbar_search %>
<div class="pure-u-1 pure-u-md-4-24"> <div class="pure-u-1 pure-u-md-4-24">
@ -43,8 +42,8 @@
<div class="pure-u-1 pure-u-md-8-24 user-field"> <div class="pure-u-1 pure-u-md-8-24 user-field">
<% if env.get? "user" %> <% if env.get? "user" %>
<div class="pure-u-1-4"> <div class="pure-u-1-4">
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading"> <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
<% if env.get("preferences").as(Preferences).dark_mode == "dark" %> <% if dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i> <i class="icon ion-ios-sunny"></i>
<% else %> <% else %>
<i class="icon ion-ios-moon"></i> <i class="icon ion-ios-moon"></i>
@ -81,8 +80,8 @@
</div> </div>
<% else %> <% else %>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading"> <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
<% if env.get("preferences").as(Preferences).dark_mode == "dark" %> <% if dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i> <i class="icon ion-ios-sunny"></i>
<% else %> <% else %>
<i class="icon ion-ios-moon"></i> <i class="icon ion-ios-moon"></i>
@ -156,7 +155,6 @@
</footer> </footer>
</div> </div>
<div class="pure-u-1 pure-u-md-2-24"></div>
</div> </div>
<script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script> <script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script>
<script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script> <script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script>

View file

@ -118,7 +118,7 @@ we're going to need to do it here in order to allow for translations.
link_yt_embed = URI.new(scheme: "https", host: "www.youtube.com", path: "/embed/#{video.id}") link_yt_embed = URI.new(scheme: "https", host: "www.youtube.com", path: "/embed/#{video.id}")
if !plid.nil? && !continuation.nil? if !plid.nil? && !continuation.nil?
link_yt_param = URI::Params{"plid" => [plid], "index" => [continuation.to_s]} link_yt_param = URI::Params{"list" => [plid], "index" => [continuation.to_s]}
link_yt_watch = IV::HttpServer::Utils.add_params_to_url(link_yt_watch, link_yt_param) link_yt_watch = IV::HttpServer::Utils.add_params_to_url(link_yt_watch, link_yt_param)
link_yt_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param) link_yt_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param)
end end
@ -346,7 +346,7 @@ we're going to need to do it here in order to allow for translations.
<h5 class="pure-g"> <h5 class="pure-g">
<div class="pure-u-14-24"> <div class="pure-u-14-24">
<% if rv["ucid"]? %> <% if !rv["ucid"].empty? %>
<b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b> <b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b>
<% else %> <% else %>
<b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b> <b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b>

View file

@ -24,11 +24,7 @@ struct YoutubeConnectionPool
@pool = build_pool() @pool = build_pool()
end end
def client(region = nil, &block) def client(&block)
if region
conn = make_client(url, region)
response = yield conn
else
conn = pool.checkout conn = pool.checkout
begin begin
response = yield conn response = yield conn
@ -43,7 +39,6 @@ struct YoutubeConnectionPool
ensure ensure
pool.release(conn) pool.release(conn)
end end
end
response response
end end
@ -59,29 +54,23 @@ struct YoutubeConnectionPool
end end
end end
def make_client(url : URI, region = nil) def make_client(url : URI, region = nil, force_resolve : Bool = false)
client = HTTPClient.new(url, OpenSSL::SSL::Context::Client.insecure) client = HTTP::Client.new(url)
# Force the usage of a specific configured IP Family
if force_resolve
client.family = CONFIG.force_resolve client.family = CONFIG.force_resolve
end
client.before_request { |r| add_yt_headers(r) } if url.host == "www.youtube.com" client.before_request { |r| add_yt_headers(r) } if url.host == "www.youtube.com"
client.read_timeout = 10.seconds client.read_timeout = 10.seconds
client.connect_timeout = 10.seconds client.connect_timeout = 10.seconds
if region
PROXY_LIST[region]?.try &.sample(40).each do |proxy|
begin
proxy = HTTPProxy.new(proxy_host: proxy[:ip], proxy_port: proxy[:port])
client.set_proxy(proxy)
break
rescue ex
end
end
end
return client return client
end end
def make_client(url : URI, region = nil, &block) def make_client(url : URI, region = nil, force_resolve : Bool = false, &block)
client = make_client(url, region) client = make_client(url, region, force_resolve)
begin begin
yield client yield client
ensure ensure

View file

@ -822,9 +822,9 @@ module HelperExtractors
end end
# Retrieves the ID required for querying the InnerTube browse endpoint. # Retrieves the ID required for querying the InnerTube browse endpoint.
# Raises when it's unable to do so # Returns an empty string when it's unable to do so
def self.get_browse_id(container) def self.get_browse_id(container)
return container.dig("navigationEndpoint", "browseEndpoint", "browseId").as_s return container.dig?("navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
end end
end end

File diff suppressed because one or more lines are too long

View file

@ -6,18 +6,23 @@ module YoutubeAPI
extend self extend self
private DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" private DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
private ANDROID_API_KEY = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w"
private ANDROID_APP_VERSION = "18.20.38" # For Android versions, see https://en.wikipedia.org/wiki/Android_version_history
# github.com/TeamNewPipe/NewPipeExtractor/blob/943b7c033bb9d07ead63ddab4441c287653e4384/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java#L1308 private ANDROID_APP_VERSION = "19.14.42"
private ANDROID_USER_AGENT = "com.google.android.youtube/18.20.38 (Linux; U; Android 12; US) gzip" private ANDROID_USER_AGENT = "com.google.android.youtube/19.14.42 (Linux; U; Android 12; US) gzip"
private ANDROID_SDK_VERSION = 31_i64 private ANDROID_SDK_VERSION = 31_i64
private ANDROID_VERSION = "12" private ANDROID_VERSION = "12"
private IOS_APP_VERSION = "18.21.3" private ANDROID_TS_APP_VERSION = "1.9"
# github.com/TeamNewPipe/NewPipeExtractor/blob/943b7c033bb9d07ead63ddab4441c287653e4384/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java#L1330 private ANDROID_TS_USER_AGENT = "com.google.android.youtube/1.9 (Linux; U; Android 12; US) gzip"
private IOS_USER_AGENT = "com.google.ios.youtube/18.21.3 (iPhone14,5; U; CPU iOS 15_6 like Mac OS X;)"
# github.com/TeamNewPipe/NewPipeExtractor/blob/943b7c033bb9d07ead63ddab4441c287653e4384/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java#L1224 # For Apple device names, see https://gist.github.com/adamawolf/3048717
private IOS_VERSION = "15.6.0.19G71" # For iOS versions, see https://en.wikipedia.org/wiki/IOS_version_history#Releases,
# then go to the dedicated article of the major version you want.
private IOS_APP_VERSION = "19.16.3"
private IOS_USER_AGENT = "com.google.ios.youtube/19.16.3 (iPhone14,5; U; CPU iOS 17_4 like Mac OS X;)"
private IOS_VERSION = "17.4.0.21E219" # Major.Minor.Patch.Build
private WINDOWS_VERSION = "10.0" private WINDOWS_VERSION = "10.0"
@ -31,6 +36,7 @@ module YoutubeAPI
Android Android
AndroidEmbeddedPlayer AndroidEmbeddedPlayer
AndroidScreenEmbed AndroidScreenEmbed
AndroidTestSuite
IOS IOS
IOSEmbedded IOSEmbedded
@ -45,7 +51,7 @@ module YoutubeAPI
ClientType::Web => { ClientType::Web => {
name: "WEB", name: "WEB",
name_proto: "1", name_proto: "1",
version: "2.20230602.01.00", version: "2.20240304.00.00",
api_key: DEFAULT_API_KEY, api_key: DEFAULT_API_KEY,
screen: "WATCH_FULL_SCREEN", screen: "WATCH_FULL_SCREEN",
os_name: "Windows", os_name: "Windows",
@ -55,7 +61,7 @@ module YoutubeAPI
ClientType::WebEmbeddedPlayer => { ClientType::WebEmbeddedPlayer => {
name: "WEB_EMBEDDED_PLAYER", name: "WEB_EMBEDDED_PLAYER",
name_proto: "56", name_proto: "56",
version: "1.20220803.01.00", version: "1.20240303.00.00",
api_key: DEFAULT_API_KEY, api_key: DEFAULT_API_KEY,
screen: "EMBED", screen: "EMBED",
os_name: "Windows", os_name: "Windows",
@ -65,7 +71,7 @@ module YoutubeAPI
ClientType::WebMobile => { ClientType::WebMobile => {
name: "MWEB", name: "MWEB",
name_proto: "2", name_proto: "2",
version: "2.20230531.05.00", version: "2.20240304.08.00",
api_key: DEFAULT_API_KEY, api_key: DEFAULT_API_KEY,
os_name: "Android", os_name: "Android",
os_version: ANDROID_VERSION, os_version: ANDROID_VERSION,
@ -74,7 +80,7 @@ module YoutubeAPI
ClientType::WebScreenEmbed => { ClientType::WebScreenEmbed => {
name: "WEB", name: "WEB",
name_proto: "1", name_proto: "1",
version: "2.20220804.00.00", version: "2.20240304.00.00",
api_key: DEFAULT_API_KEY, api_key: DEFAULT_API_KEY,
screen: "EMBED", screen: "EMBED",
os_name: "Windows", os_name: "Windows",
@ -88,7 +94,7 @@ module YoutubeAPI
name: "ANDROID", name: "ANDROID",
name_proto: "3", name_proto: "3",
version: ANDROID_APP_VERSION, version: ANDROID_APP_VERSION,
api_key: "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w", api_key: ANDROID_API_KEY,
android_sdk_version: ANDROID_SDK_VERSION, android_sdk_version: ANDROID_SDK_VERSION,
user_agent: ANDROID_USER_AGENT, user_agent: ANDROID_USER_AGENT,
os_name: "Android", os_name: "Android",
@ -99,7 +105,7 @@ module YoutubeAPI
name: "ANDROID_EMBEDDED_PLAYER", name: "ANDROID_EMBEDDED_PLAYER",
name_proto: "55", name_proto: "55",
version: ANDROID_APP_VERSION, version: ANDROID_APP_VERSION,
api_key: DEFAULT_API_KEY, api_key: "AIzaSyCjc_pVEDi4qsv5MtC2dMXzpIaDoRFLsxw",
}, },
ClientType::AndroidScreenEmbed => { ClientType::AndroidScreenEmbed => {
name: "ANDROID", name: "ANDROID",
@ -113,6 +119,17 @@ module YoutubeAPI
os_version: ANDROID_VERSION, os_version: ANDROID_VERSION,
platform: "MOBILE", platform: "MOBILE",
}, },
ClientType::AndroidTestSuite => {
name: "ANDROID_TESTSUITE",
name_proto: "30",
version: ANDROID_TS_APP_VERSION,
api_key: ANDROID_API_KEY,
android_sdk_version: ANDROID_SDK_VERSION,
user_agent: ANDROID_TS_USER_AGENT,
os_name: "Android",
os_version: ANDROID_VERSION,
platform: "MOBILE",
},
# IOS # IOS
@ -143,9 +160,9 @@ module YoutubeAPI
ClientType::IOSMusic => { ClientType::IOSMusic => {
name: "IOS_MUSIC", name: "IOS_MUSIC",
name_proto: "26", name_proto: "26",
version: "5.21", version: "6.42",
api_key: "AIzaSyBAETezhkwP0ZWA02RsqT1zu78Fpt0bC_s", api_key: "AIzaSyBAETezhkwP0ZWA02RsqT1zu78Fpt0bC_s",
user_agent: "com.google.ios.youtubemusic/5.21 (iPhone14,5; U; CPU iOS 15_6 like Mac OS X;)", user_agent: "com.google.ios.youtubemusic/6.42 (iPhone14,5; U; CPU iOS 17_4 like Mac OS X;)",
device_make: "Apple", device_make: "Apple",
device_model: "iPhone14,5", device_model: "iPhone14,5",
os_name: "iPhone", os_name: "iPhone",
@ -158,7 +175,7 @@ module YoutubeAPI
ClientType::TvHtml5 => { ClientType::TvHtml5 => {
name: "TVHTML5", name: "TVHTML5",
name_proto: "7", name_proto: "7",
version: "7.20220325", version: "7.20240304.10.00",
api_key: DEFAULT_API_KEY, api_key: DEFAULT_API_KEY,
}, },
ClientType::TvHtml5ScreenEmbed => { ClientType::TvHtml5ScreenEmbed => {
@ -187,10 +204,6 @@ module YoutubeAPI
# conf_2 = ClientConfig.new(client_type: ClientType::Android) # conf_2 = ClientConfig.new(client_type: ClientType::Android)
# YoutubeAPI::player(video_id: "dQw4w9WgXcQ", client_config: conf_2) # YoutubeAPI::player(video_id: "dQw4w9WgXcQ", client_config: conf_2)
# #
# # Proxy request through russian proxies
# conf_3 = ClientConfig.new(proxy_region: "RU")
# YoutubeAPI::next({video_id: "dQw4w9WgXcQ"}, client_config: conf_3)
# ```
# #
struct ClientConfig struct ClientConfig
# Type of client to emulate. # Type of client to emulate.
@ -201,16 +214,11 @@ module YoutubeAPI
# (this is passed as the `gl` parameter). # (this is passed as the `gl` parameter).
property region : String | Nil property region : String | Nil
# ISO code of country where the proxy is located.
# Used in case of geo-restricted videos.
property proxy_region : String | Nil
# Initialization function # Initialization function
def initialize( def initialize(
*, *,
@client_type = ClientType::Web, @client_type = ClientType::Web,
@region = "US", @region = "US"
@proxy_region = nil
) )
end end
@ -272,7 +280,6 @@ module YoutubeAPI
return { return {
client_type: self.name, client_type: self.name,
region: @region, region: @region,
proxy_region: @proxy_region,
}.to_s }.to_s
end end
end end
@ -619,7 +626,7 @@ module YoutubeAPI
LOGGER.trace("YoutubeAPI: POST data: #{data}") LOGGER.trace("YoutubeAPI: POST data: #{data}")
# Send the POST request # Send the POST request
body = YT_POOL.client(client_config.proxy_region) do |client| body = YT_POOL.client() do |client|
client.post(url, headers: headers, body: data.to_json) do |response| client.post(url, headers: headers, body: data.to_json) do |response|
self._decompress(response.body_io, response.headers["Content-Encoding"]?) self._decompress(response.body_io, response.headers["Content-Encoding"]?)
end end