summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/css/carousel.css119
-rw-r--r--assets/css/default.css2
-rw-r--r--assets/js/notifications.js4
-rw-r--r--docker-compose.yml5
-rw-r--r--locales/ar.json26
-rw-r--r--locales/bg.json3
-rw-r--r--locales/bn.json4
-rw-r--r--locales/ca.json3
-rw-r--r--locales/cs.json14
-rw-r--r--locales/da.json41
-rw-r--r--locales/de.json11
-rw-r--r--locales/en-US.json13
-rw-r--r--locales/es.json121
-rw-r--r--locales/eu.json22
-rw-r--r--locales/fa.json77
-rw-r--r--locales/fi.json2
-rw-r--r--locales/fr.json3
-rw-r--r--locales/hi.json18
-rw-r--r--locales/hr.json14
-rw-r--r--locales/ia.json45
-rw-r--r--locales/id.json3
-rw-r--r--locales/it.json12
-rw-r--r--locales/ja.json30
-rw-r--r--locales/ko.json20
-rw-r--r--locales/lmo.json232
-rw-r--r--locales/nb-NO.json3
-rw-r--r--locales/nl.json42
-rw-r--r--locales/pl.json30
-rw-r--r--locales/pt-BR.json264
-rw-r--r--locales/pt-PT.json10
-rw-r--r--locales/pt.json245
-rw-r--r--locales/ro.json3
-rw-r--r--locales/ru.json37
-rw-r--r--locales/sl.json3
-rw-r--r--locales/sq.json11
-rw-r--r--locales/sr.json14
-rw-r--r--locales/sr_Cyrl.json14
-rw-r--r--locales/sv-SE.json200
-rw-r--r--locales/tk.json7
-rw-r--r--locales/tr.json14
-rw-r--r--locales/uk.json14
-rw-r--r--locales/vi.json330
-rw-r--r--locales/zh-CN.json13
-rw-r--r--locales/zh-TW.json13
-rw-r--r--spec/helpers/vtt/builder_spec.cr65
-rw-r--r--spec/i18next_plurals_spec.cr13
-rw-r--r--src/invidious/channels/about.cr16
-rw-r--r--src/invidious/database/statistics.cr4
-rw-r--r--src/invidious/frontend/comments_reddit.cr2
-rw-r--r--src/invidious/frontend/comments_youtube.cr30
-rw-r--r--src/invidious/helpers/handlers.cr59
-rw-r--r--src/invidious/helpers/helpers.cr27
-rw-r--r--src/invidious/helpers/i18n.cr19
-rw-r--r--src/invidious/helpers/i18next.cr22
-rw-r--r--src/invidious/helpers/json_filter.cr248
-rw-r--r--src/invidious/helpers/utils.cr2
-rw-r--r--src/invidious/helpers/webvtt.cr16
-rw-r--r--src/invidious/jobs/statistics_refresh_job.cr4
-rw-r--r--src/invidious/routes/api/v1/channels.cr1
-rw-r--r--src/invidious/routes/api/v1/misc.cr2
-rw-r--r--src/invidious/routes/api/v1/search.cr7
-rw-r--r--src/invidious/routes/api/v1/videos.cr43
-rw-r--r--src/invidious/routes/watch.cr6
-rw-r--r--src/invidious/routing.cr1
-rw-r--r--src/invidious/videos/clip.cr22
-rw-r--r--src/invidious/videos/parser.cr5
-rw-r--r--src/invidious/views/components/item.ecr36
-rw-r--r--src/invidious/views/template.ecr26
-rw-r--r--src/invidious/views/watch.ecr4
-rw-r--r--src/invidious/yt_backend/extractors.cr4
-rw-r--r--src/invidious/yt_backend/youtube_api.cr33
71 files changed, 1833 insertions, 995 deletions
diff --git a/assets/css/carousel.css b/assets/css/carousel.css
new file mode 100644
index 00000000..4bae92e5
--- /dev/null
+++ b/assets/css/carousel.css
@@ -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;
+ }
+}
diff --git a/assets/css/default.css b/assets/css/default.css
index 00881253..a47762ec 100644
--- a/assets/css/default.css
+++ b/assets/css/default.css
@@ -13,6 +13,7 @@ body {
display: flex;
flex-direction: column;
min-height: 100vh;
+ margin: auto;
}
.h-box {
@@ -197,6 +198,7 @@ img.thumbnail {
display: block; /* See: https://stackoverflow.com/a/11635197 */
width: 100%;
object-fit: cover;
+ aspect-ratio: 16 / 9;
}
.thumbnail-placeholder {
diff --git a/assets/js/notifications.js b/assets/js/notifications.js
index 058553d9..55b7a15c 100644
--- a/assets/js/notifications.js
+++ b/assets/js/notifications.js
@@ -10,7 +10,7 @@ var notifications, delivered;
var notifications_mock = { close: function () { } };
function get_subscriptions() {
- helpers.xhr('GET', '/api/v1/auth/subscriptions?fields=authorId', {
+ helpers.xhr('GET', '/api/v1/auth/subscriptions', {
retries: 5,
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
// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
notifications = new SSE(
- '/api/v1/auth/notifications?fields=videoId,title,author,authorId,publishedText,published,authorThumbnails,liveNow', {
+ '/api/v1/auth/notifications', {
withCredentials: true,
payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
diff --git a/docker-compose.yml b/docker-compose.yml
index 42a5c06b..afda8726 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -32,13 +32,10 @@ services:
# statistics_enabled: false
hmac_key: "CHANGE_ME!!"
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
timeout: 5s
retries: 2
- depends_on:
- invidious-db:
- condition: service_healthy
invidious-db:
image: docker.io/library/postgres:14
diff --git a/locales/ar.json b/locales/ar.json
index 18298913..5d8b230f 100644
--- a/locales/ar.json
+++ b/locales/ar.json
@@ -15,13 +15,13 @@
"New password": "كلمة مرور جديدة",
"New passwords must match": "يَجبُ أن تكون كلمتا المرور متطابقتين",
"Authorize token?": "رمز التفويض؟",
- "Authorize token for `x`?": "السماح بالرمز المميز ل 'x'؟",
+ "Authorize token for `x`?": "السماح بالرمز المميز ل `x`؟",
"Yes": "نعم",
"No": "لا",
"Import and Export Data": "اِستيراد البيانات وتصديرها",
"Import": "استيراد",
"Import Invidious data": "استيراد بيانات JSON Invidious",
- "Import YouTube subscriptions": "استيراد اشتراكات YouTube/OPML",
+ "Import YouTube subscriptions": "استيراد الاشتراكات YouTube بتنسيق CSV أو OPML",
"Import FreeTube subscriptions (.db)": "استيراد اشتراكات فريتيوب (.db)",
"Import NewPipe subscriptions (.json)": "استيراد اشتراكات نيو بايب (.json)",
"Import NewPipe data (.zip)": "استيراد بيانات نيو بايب (.zip)",
@@ -41,7 +41,7 @@
"Time (h:mm:ss):": "الوقت (h:mm:ss):",
"Text CAPTCHA": "نص الكابتشا",
"Image CAPTCHA": "صورة الكابتشا",
- "Sign In": "تسجيل الدخول",
+ "Sign In": "إنشاء حساب",
"Register": "التسجيل",
"E-mail": "البريد الإلكتروني",
"Preferences": "الإعدادات",
@@ -170,7 +170,7 @@
"Password cannot be empty": "لا يمكن أن تكون كلمة السر فارغة",
"Password cannot be longer than 55 characters": "يجب أن لا تتعدى كلمة السر 55 حرفًا",
"Please log in": "الرجاء تسجيل الدخول",
- "Invidious Private Feed for `x`": "تغذية Invidious خاصة ل 'x'",
+ "Invidious Private Feed for `x`": "تغذية Invidious خاصة ل `x`",
"channel:`x`": "قناة:`x`",
"Deleted or invalid channel": "قناة ممسوحة او غير صالحة",
"This channel does not exist.": "هذه القناة غير موجودة.",
@@ -382,11 +382,11 @@
"videoinfo_watch_on_youTube": "مشاهدة على يوتيوب",
"videoinfo_youTube_embed_link": "مضمن",
"videoinfo_invidious_embed_link": "رابط مضمن",
- "user_created_playlists": "'x' إنشاء قوائم التشغيل",
- "user_saved_playlists": "قوائم التشغيل المحفوظة 'x'",
+ "user_created_playlists": "`x` إنشاء قوائم التشغيل",
+ "user_saved_playlists": "قوائم التشغيل المحفوظة `x`",
"Video unavailable": "الفيديو غير متوفر",
"search_filters_features_option_three_sixty": "360°",
- "download_subtitles": "ترجمات - 'x' (.vtt)",
+ "download_subtitles": "ترجمات - `x` (.vtt)",
"invidious": "الخيالي",
"preferences_save_player_pos_label": "حفظ موضع التشغيل: ",
"crash_page_you_found_a_bug": "يبدو أنك قد وجدت خطأً برمجيًّا في Invidious!",
@@ -554,5 +554,15 @@
"generic_channels_count_2": "{{count}} قناتان",
"generic_channels_count_3": "{{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`"
}
diff --git a/locales/bg.json b/locales/bg.json
index 82591ed8..bcce6a7a 100644
--- a/locales/bg.json
+++ b/locales/bg.json
@@ -486,5 +486,6 @@
"preferences_annotations_label": "Покажи анотаций по подразбиране: ",
"generic_views_count": "{{count}} гледане",
"generic_views_count_plural": "{{count}} гледания",
- "Next page": "Следваща страница"
+ "Next page": "Следваща страница",
+ "Import YouTube watch history (.json)": "Импортиране на историята на гледане от YouTube (.json)"
}
diff --git a/locales/bn.json b/locales/bn.json
index 9d1c7b24..501a1ca3 100644
--- a/locales/bn.json
+++ b/locales/bn.json
@@ -90,5 +90,7 @@
"preferences_quality_option_medium": "মধ্যম",
"preferences_quality_option_small": "ছোট",
"preferences_quality_dash_option_1080p": "১০৮০পি",
- "preferences_quality_dash_option_720p": "৭২০পি"
+ "preferences_quality_dash_option_720p": "৭২০পি",
+ "Add to playlist": "প্লেলিস্টে যোগ করুন",
+ "Add to playlist: ": "প্লেলিস্টে যোগ করুন: "
}
diff --git a/locales/ca.json b/locales/ca.json
index a718eb2b..4ae55804 100644
--- a/locales/ca.json
+++ b/locales/ca.json
@@ -486,5 +486,6 @@
"generic_channels_count_plural": "{{count}} canals",
"generic_button_edit": "Edita",
"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)"
}
diff --git a/locales/cs.json b/locales/cs.json
index 10c114eb..1350f146 100644
--- a/locales/cs.json
+++ b/locales/cs.json
@@ -21,7 +21,7 @@
"Import and Export Data": "Import a export dat",
"Import": "Importovat",
"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 NewPipe subscriptions (.json)": "Importovat odběry z NewPipe (.json)",
"Import NewPipe data (.zip)": "Importovat údeje z NewPipe (.zip)",
@@ -503,5 +503,15 @@
"playlist_button_add_items": "Přidat videa",
"generic_channels_count_0": "{{count}} kanál",
"generic_channels_count_1": "{{count}} kanály",
- "generic_channels_count_2": "{{count}} kanálů"
+ "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`"
}
diff --git a/locales/da.json b/locales/da.json
index 16607546..9cbb446a 100644
--- a/locales/da.json
+++ b/locales/da.json
@@ -165,12 +165,12 @@
"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",
"Please log in": "Venligst log ind",
- "channel:`x`": "kanal: 'x'",
+ "channel:`x`": "kanal: `x`",
"Deleted or invalid channel": "Slettet eller invalid kanal",
"This channel does not exist.": "Denne kanal eksisterer ikke.",
"Could not get channel info.": "Kunne ikke hente kanal info.",
"Could not fetch comments": "Kunne ikke hente kommentarer",
- "`x` ago": "'x' siden",
+ "`x` ago": "`x` siden",
"Load more": "Hent flere",
"Could not create mix.": "Kunne ikke skabe blanding.",
"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_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>",
- "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>"
}
diff --git a/locales/de.json b/locales/de.json
index 59c6a49c..46327f57 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -148,7 +148,7 @@
"Whitelisted regions: ": "Erlaubte Regionen: ",
"Blacklisted regions: ": "Unerlaubte Regionen: ",
"Shared `x`": "Geteilt `x`",
- "Premieres in `x`": "Zuerst gesehen in `x`",
+ "Premieres in `x`": "Premiere in `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.",
"View YouTube comments": "YouTube Kommentare anzeigen",
@@ -486,5 +486,12 @@
"channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Veröffentlichungen",
"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: "
}
diff --git a/locales/en-US.json b/locales/en-US.json
index a9f78165..3987f796 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -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_plural": "{{count}} channels",
"generic_views_count": "{{count}} view",
@@ -38,7 +43,7 @@
"Import and Export Data": "Import and Export Data",
"Import": "Import",
"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 watch history (.json)": "Import YouTube watch history (.json)",
"Import FreeTube subscriptions (.db)": "Import FreeTube subscriptions (.db)",
@@ -487,5 +492,9 @@
"channel_tab_releases_label": "Releases",
"channel_tab_playlists_label": "Playlists",
"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`"
}
diff --git a/locales/es.json b/locales/es.json
index 0b8463ea..1d082e60 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -21,7 +21,7 @@
"Import and Export Data": "Importación y exportación de datos",
"Import": "Importar",
"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 NewPipe subscriptions (.json)": "Importar suscripciones de NewPipe (.json)",
"Import NewPipe data (.zip)": "Importar datos de NewPipe (.zip)",
@@ -90,7 +90,7 @@
"preferences_notifications_only_label": "Mostrar solo notificaciones (si hay alguna): ",
"Enable web notifications": "Habilitar notificaciones web",
"`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",
"Clear watch history": "Borrar el historial de reproducción",
"Import/export data": "Importar/Exportar datos",
@@ -102,7 +102,7 @@
"preferences_category_admin": "Preferencias de administrador",
"preferences_default_home_label": "Página de inicio por defecto: ",
"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? ",
"CAPTCHA enabled: ": "¿Habilitar los CAPTCHA? ",
"Login enabled: ": "¿Habilitar el inicio de sesión? ",
@@ -133,7 +133,7 @@
"Create playlist": "Crear lista de reproducción",
"Title": "Título",
"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 less": "Mostrar menos",
"Watch on YouTube": "Ver en YouTube",
@@ -144,13 +144,13 @@
"License: ": "Licencia: ",
"Family friendly? ": "¿Filtrar contenidos? ",
"Wilson score: ": "Puntuación Wilson: ",
- "Engagement: ": "Compromiso: ",
+ "Engagement: ": "Retención: ",
"Whitelisted regions: ": "Regiones permitidas: ",
"Blacklisted regions: ": "Regiones bloqueadas: ",
"Shared `x`": "Compartido `x`",
"Premieres in `x`": "Se estrena en `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 more comments on Reddit": "Ver más comentarios en Reddit",
"View `x` comments": {
@@ -312,7 +312,7 @@
"Download as: ": "Descargar como: ",
"%A %B %-d, %Y": "%A %B %-d, %Y",
"(edited)": "(editado)",
- "YouTube comment permalink": "Enlace permanente de YouTube del comentario",
+ "YouTube comment permalink": "Enlace permanente de comentario de YouTube",
"permalink": "enlace permanente",
"`x` marked it with a ❤": "`x` lo ha marcado con un ❤",
"Audio mode": "Modo de audio",
@@ -324,10 +324,10 @@
"search_filters_sort_option_rating": "Valoración",
"search_filters_sort_option_date": "Fecha de subida",
"search_filters_sort_option_views": "Visualizaciones",
- "search_filters_type_label": "tipo de contenido",
- "search_filters_duration_label": "duración",
- "search_filters_features_label": "funcionalidades",
- "search_filters_sort_label": "ordenar",
+ "search_filters_type_label": "Tipo de contenido",
+ "search_filters_duration_label": "Duración",
+ "search_filters_features_label": "Funcionalidades",
+ "search_filters_sort_label": "Ordenar",
"search_filters_date_option_hour": "Última hora",
"search_filters_date_option_today": "Hoy",
"search_filters_date_option_week": "Esta semana",
@@ -390,43 +390,58 @@
"search_filters_features_option_three_sixty": "360°",
"videoinfo_watch_on_youTube": "Ver en YouTube",
"preferences_save_player_pos_label": "Guardar posición de reproducción: ",
- "generic_views_count": "{{count}} visualización",
- "generic_views_count_plural": "{{count}} visualizaciones",
- "generic_subscribers_count": "{{count}} suscriptor",
- "generic_subscribers_count_plural": "{{count}} suscriptores",
- "generic_subscriptions_count": "{{count}} suscripción",
- "generic_subscriptions_count_plural": "{{count}} suscripciones",
- "subscriptions_unseen_notifs_count": "{{count}} notificación no vista",
- "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones no vistas",
- "generic_count_days": "{{count}} día",
- "generic_count_days_plural": "{{count}} días",
- "comments_view_x_replies": "Ver {{count}} respuesta",
- "comments_view_x_replies_plural": "Ver {{count}} respuestas",
- "generic_count_weeks": "{{count}} semana",
- "generic_count_weeks_plural": "{{count}} semanas",
- "generic_playlists_count": "{{count}} lista de reproducción",
- "generic_playlists_count_plural": "{{count}} listas de reproducciones",
- "generic_videos_count": "{{count}} video",
- "generic_videos_count_plural": "{{count}} video",
- "generic_count_months": "{{count}} mes",
- "generic_count_months_plural": "{{count}} meses",
- "comments_points_count": "{{count}} punto",
- "comments_points_count_plural": "{{count}} puntos",
- "generic_count_years": "{{count}} año",
- "generic_count_years_plural": "{{count}} años",
- "generic_count_hours": "{{count}} hora",
- "generic_count_hours_plural": "{{count}} horas",
- "generic_count_minutes": "{{count}} minuto",
- "generic_count_minutes_plural": "{{count}} minutos",
- "generic_count_seconds": "{{count}} segundo",
- "generic_count_seconds_plural": "{{count}} segundos",
+ "generic_views_count_0": "{{count}} visualización",
+ "generic_views_count_1": "{{count}} visualizaciones",
+ "generic_views_count_2": "{{count}} visualizaciones",
+ "generic_subscribers_count_0": "{{count}} suscriptor",
+ "generic_subscribers_count_1": "{{count}} suscriptores",
+ "generic_subscribers_count_2": "{{count}} suscriptores",
+ "generic_subscriptions_count_0": "{{count}} suscripción",
+ "generic_subscriptions_count_1": "{{count}} suscripciones",
+ "generic_subscriptions_count_2": "{{count}} suscripciones",
+ "subscriptions_unseen_notifs_count_0": "{{count}} notificación sin ver",
+ "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones sin ver",
+ "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones sin ver",
+ "generic_count_days_0": "{{count}} día",
+ "generic_count_days_1": "{{count}} días",
+ "generic_count_days_2": "{{count}} días",
+ "comments_view_x_replies_0": "Ver {{count}} respuesta",
+ "comments_view_x_replies_1": "Ver {{count}} respuestas",
+ "comments_view_x_replies_2": "Ver {{count}} respuestas",
+ "generic_count_weeks_0": "{{count}} semana",
+ "generic_count_weeks_1": "{{count}} semanas",
+ "generic_count_weeks_2": "{{count}} semanas",
+ "generic_playlists_count_0": "{{count}} lista de reproducción",
+ "generic_playlists_count_1": "{{count}} listas de reproducciones",
+ "generic_playlists_count_2": "{{count}} listas de reproducciones",
+ "generic_videos_count_0": "{{count}} video",
+ "generic_videos_count_1": "{{count}} videos",
+ "generic_videos_count_2": "{{count}} videos",
+ "generic_count_months_0": "{{count}} mes",
+ "generic_count_months_1": "{{count}} meses",
+ "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_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_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_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)",
"Cantonese (Hong Kong)": "Cantonés (Hong Kong)",
"Dutch (auto-generated)": "Neerlandés (generados automáticamente)",
@@ -454,14 +469,15 @@
"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_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_type_option_all": "Cualquier tipo",
"search_filters_duration_option_none": "Cualquier duración",
"search_filters_features_option_vr180": "VR180",
"search_filters_apply_button": "Aplicar filtros",
- "tokens_count": "{{count}} token",
- "tokens_count_plural": "{{count}} tokens",
+ "tokens_count_0": "{{count}} token",
+ "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>.",
"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>",
@@ -485,6 +501,17 @@
"generic_button_rss": "RSS",
"channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Publicaciones",
- "generic_channels_count": "{{count}} canal",
- "generic_channels_count_plural": "{{count}} canales"
+ "generic_channels_count_0": "{{count}} canal",
+ "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`"
}
diff --git a/locales/eu.json b/locales/eu.json
index 8b365270..fbca537b 100644
--- a/locales/eu.json
+++ b/locales/eu.json
@@ -161,13 +161,13 @@
"Source available here.": "Iturburua hemen eskura.",
"View JavaScript license information.": "JavaScriptaren lizentzi adierazpena ikusi.",
"Blacklisted regions: ": "zerrenda beltzaren zonaldeak: ",
- "Premieres `x`": "'x' estrenaldiak",
+ "Premieres `x`": "`x` estrenaldiak",
"Wrong answer": "Erantzun ez zuzena",
"Password is a required field": "Pasahitza beharrezkoa da",
"Wrong username or password": "Pasahitza edo ezizena gaizki",
"Password cannot be longer than 55 characters": "Pasahitza 55 karaktere baino luzeagoa ezin da izan",
"This channel does not exist.": "Kanal hau ez dago.",
- "`x` ago": "duela 'x'",
+ "`x` ago": "duela `x`",
"Czech": "Txekiera",
"preferences_region_label": "Herrialdeko edukiera: ",
"preferences_sort_label": "Bideoak ordenatu: ",
@@ -207,24 +207,24 @@
"Public": "Orokorra",
"Unlisted": "Ez zerrendatua",
"Subscription manager": "Harpidetzen kudeatzailea",
- "Updated `x` ago": "Duela 'x' eguneratua",
+ "Updated `x` ago": "Duela `x` eguneratua",
"Hide replies": "Erantzunak izkutatu",
"preferences_thin_mode_label": "Urri eran: ",
"Show replies": "Erantzunak erakutsi",
"Watch on YouTube": "YouTuben ikusi",
- "Premieres in `x`": "'x'eko estrenaldiak",
- "Delete playlist `x`?": "'x' zerrenda ezabatu nahi?",
+ "Premieres in `x`": "`x`eko estrenaldiak",
+ "Delete playlist `x`?": "`x` zerrenda ezabatu nahi?",
"Token is expired, please try again": "Token kadukatua, saiatu berriro",
"CAPTCHA enabled: ": "CAPTCHA gaitu: ",
"Released under the AGPLv3 on Github.": "GitHubeko AGPLv3pean argitaratuta.",
- "channel:`x`": "Kanal: 'x'",
+ "channel:`x`": "Kanal: `x`",
"Georgian": "Georgiera",
"Incorrect password": "Pasahitza gaizki",
"Playlist does not exist.": "Zerrenda ez da existitzen.",
"preferences_category_misc": "Askotariko lehentasunak",
"View `x` comments": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "'x' iruzkina ikusi",
- "": "'x' iruzkinak ikusi"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` iruzkina ikusi",
+ "": "`x` iruzkinak ikusi"
},
"Report statistics: ": "Estatistikak adierazi: ",
"preferences_max_results_label": "Jotzeko bideo zerrendaren luzera: ",
@@ -237,7 +237,7 @@
"Hidden field \"challenge\" is a required field": "\"challenge\" eremu ezkutua beharrezkoa da",
"German": "Alemaniarra",
"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",
"preferences_video_loop_label": "Beti begiztatu: ",
"Only show latest unwatched video from channel: ": "kanalaren azken bideo ezikusia erakutsi soilik ",
@@ -261,9 +261,9 @@
"Hide annotations": "Oharrak izkutatu",
"Title": "Titulua",
"channel name": "Kanalaren izena",
- "Authorize token for `x`?": "Baimendu tokena 'x'tzako?",
+ "Authorize token for `x`?": "Baimendu tokena `x`tzako?",
"Private": "Pribatua",
- "Editing playlist `x`": "'x' zerrenda editatu",
+ "Editing playlist `x`": "`x` zerrenda editatu",
"Could not pull trending pages.": "Ezin ekarri orri arrakastatsuak.",
"crash_page_read_the_faq": "Bide <a href=\"`x`\"> (FAQ) ohiko galderak</a>"
}
diff --git a/locales/fa.json b/locales/fa.json
index 9b6c625d..d0251201 100644
--- a/locales/fa.json
+++ b/locales/fa.json
@@ -1,9 +1,14 @@
{
- "generic_views_count_0": "{{count}} بازدید",
- "generic_videos_count_0": "{{count}} ویدئو",
- "generic_playlists_count_0": "{{count}} فهرست پخش",
- "generic_subscribers_count_0": "{{count}} دنبال کننده",
- "generic_subscriptions_count_0": "{{count}} اشتراک ها",
+ "generic_views_count": "{{count}} بازدید",
+ "generic_views_count_plural": "{{count}} بازدید",
+ "generic_videos_count": "{{count}} ویدئو",
+ "generic_videos_count_plural": "{{count}} ویدئو",
+ "generic_playlists_count": "{{count}} فهرست پخش",
+ "generic_playlists_count_plural": "{{count}} فهرست پخش",
+ "generic_subscribers_count": "{{count}} دنبال کننده",
+ "generic_subscribers_count_plural": "{{count}} دنبال کننده",
+ "generic_subscriptions_count": "{{count}} اشتراک",
+ "generic_subscriptions_count_plural": "{{count}} اشتراک",
"LIVE": "زنده",
"Shared `x` ago": "`x` پیش به اشتراک گذاشته شده",
"Unsubscribe": "لغو اشتراک",
@@ -117,13 +122,15 @@
"Subscription manager": "مدیریت اشتراک",
"Token manager": "مدیر توکن",
"Token": "توکن",
- "tokens_count_0": "{{count}} توکن ها",
+ "tokens_count": "{{count}} توکن",
+ "tokens_count_plural": "{{count}} توکن",
"Import/export": "وارد کردن/خارج کردن",
"unsubscribe": "لغو اشتراک",
"revoke": "ابطال",
"Subscriptions": "اشتراک ها",
- "subscriptions_unseen_notifs_count_0": "{{count}} اعلان نادیده",
- "search": "جستجو",
+ "subscriptions_unseen_notifs_count": "{{count}} اعلان نادیده",
+ "subscriptions_unseen_notifs_count_plural": "{{count}} اعلان نادیده",
+ "search": "جست و جو",
"Log out": "خروج",
"Released under the AGPLv3 on Github.": "منتشر شده تحت پروانه AGPLv3 روی گیت‌هاب.",
"Source available here.": "منبع اینجا دردسترس است.",
@@ -183,10 +190,12 @@
"This channel does not exist.": "این کانال وجود ندارد.",
"Could not get channel info.": "نمیتوان اطلاعات کانال را دریافت کرد.",
"Could not fetch comments": "نمیتوان نظرات را دریافت کرد",
- "comments_view_x_replies_0": "نمایش {{count}} پاسخ ها",
+ "comments_view_x_replies": "نمایش {{count}} پاسخ",
+ "comments_view_x_replies_plural": "نمایش {{count}} پاسخ",
"`x` ago": "`x` پیش",
"Load more": "بارگذاری بیشتر",
- "comments_points_count_0": "{{count}} نقطه ها",
+ "comments_points_count": "{{count}} نقطه",
+ "comments_points_count_plural": "{{count}} نقطه",
"Could not create mix.": "نمیتوان میکس ساخت.",
"Empty playlist": "سیاههٔ پخش خالی",
"Not a playlist.": "یک سیاههٔ پخش نیست.",
@@ -304,16 +313,23 @@
"Yiddish": "ییدیش",
"Yoruba": "یوروبایی",
"Zulu": "زولو",
- "generic_count_years_0": "{{count}} سال",
- "generic_count_months_0": "{{count}} ماه",
- "generic_count_weeks_0": "{{count}} هفته",
- "generic_count_days_0": "{{count}} روز",
- "generic_count_hours_0": "{{count}} ساعت",
- "generic_count_minutes_0": "{{count}} دقیقه",
- "generic_count_seconds_0": "{{count}} ثانیه",
+ "generic_count_years": "{{count}} سال",
+ "generic_count_years_plural": "{{count}} سال",
+ "generic_count_months": "{{count}} ماه",
+ "generic_count_months_plural": "{{count}} ماه",
+ "generic_count_weeks": "{{count}} هفته",
+ "generic_count_weeks_plural": "{{count}} هفته",
+ "generic_count_days": "{{count}} روز",
+ "generic_count_days_plural": "{{count}} روز",
+ "generic_count_hours": "{{count}} ساعت",
+ "generic_count_hours_plural": "{{count}} ساعت",
+ "generic_count_minutes": "{{count}} دقیقه",
+ "generic_count_minutes_plural": "{{count}} دقیقه",
+ "generic_count_seconds": "{{count}} ثانیه",
+ "generic_count_seconds_plural": "{{count}} ثانیه",
"Fallback comments: ": "نظرات عقب گرد: ",
"Popular": "محبوب",
- "Search": "جستجو",
+ "Search": "جست و جو",
"Top": "بالا",
"About": "درباره",
"Rating: ": "رتبه دهی: ",
@@ -445,5 +461,28 @@
"Song: ": "آهنگ: ",
"Channel Sponsor": "اسپانسر کانال",
"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>"
}
diff --git a/locales/fi.json b/locales/fi.json
index 5d8578a5..14c2b0fc 100644
--- a/locales/fi.json
+++ b/locales/fi.json
@@ -14,7 +14,7 @@
"Clear watch history?": "Tyhjennä katseluhistoria?",
"New password": "Uusi salasana",
"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?",
"Yes": "Kyllä",
"No": "Ei",
diff --git a/locales/fr.json b/locales/fr.json
index 772c81c8..251e88bc 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -503,5 +503,6 @@
"Download is disabled": "Le téléchargement est désactivé",
"Import YouTube playlist (.csv)": "Importer des listes de lecture de Youtube (.csv)",
"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)"
}
diff --git a/locales/hi.json b/locales/hi.json
index 21807c50..0a1c09dd 100644
--- a/locales/hi.json
+++ b/locales/hi.json
@@ -62,7 +62,7 @@
"Import and Export Data": "डेटा को आयात और निर्यात करें",
"Import": "आयात करें",
"Import Invidious data": "Invidious JSON डेटा आयात करें",
- "Import YouTube subscriptions": "YouTube/OPML सदस्यताएँ आयात करें",
+ "Import YouTube subscriptions": "YouTube CSV या OPML सदस्यताएँ आयात करें",
"Import FreeTube subscriptions (.db)": "FreeTube सदस्यताएँ आयात करें (.db)",
"Import NewPipe subscriptions (.json)": "NewPipe सदस्यताएँ आयात करें (.json)",
"Import NewPipe data (.zip)": "NewPipe डेटा आयात करें (.zip)",
@@ -476,7 +476,7 @@
"generic_button_cancel": "रद्द करें",
"generic_button_rss": "आरएसएस",
"generic_button_edit": "संपादित करें",
- "generic_button_delete": "मिटाएं",
+ "generic_button_delete": "हटाएं",
"playlist_button_add_items": "वीडियो जोड़ें",
"Song: ": "गाना: ",
"channel_tab_podcasts_label": "पाॅडकास्ट",
@@ -484,5 +484,17 @@
"Import YouTube playlist (.csv)": "YouTube प्लेलिस्ट (.csv) आयात करें",
"Standard YouTube license": "मानक यूट्यूब लाइसेंस",
"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` पर जाएँ"
}
diff --git a/locales/hr.json b/locales/hr.json
index ef931202..91425248 100644
--- a/locales/hr.json
+++ b/locales/hr.json
@@ -21,7 +21,7 @@
"Import and Export Data": "Uvezi i izvezi podatke",
"Import": "Uvezi",
"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 NewPipe subscriptions (.json)": "Uvezi NewPipe pretplate (.json)",
"Import NewPipe data (.zip)": "Uvezi NewPipe podatke (.zip)",
@@ -503,5 +503,15 @@
"channel_tab_releases_label": "Izdanja",
"generic_channels_count_0": "{{count}} kanal",
"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"
}
diff --git a/locales/ia.json b/locales/ia.json
new file mode 100644
index 00000000..2c8cb2b0
--- /dev/null
+++ b/locales/ia.json
@@ -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"
+}
diff --git a/locales/id.json b/locales/id.json
index 8961880b..4c6e8548 100644
--- a/locales/id.json
+++ b/locales/id.json
@@ -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>",
"generic_button_delete": "Hapus",
"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)"
}
diff --git a/locales/it.json b/locales/it.json
index 7e1b12c6..79aa6c16 100644
--- a/locales/it.json
+++ b/locales/it.json
@@ -503,5 +503,15 @@
"channel_tab_podcasts_label": "Podcast",
"generic_channels_count_0": "{{count}} canale",
"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`"
}
diff --git a/locales/ja.json b/locales/ja.json
index 17e60998..d430b2a4 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -53,7 +53,7 @@
"preferences_category_player": "プレイヤーの設定",
"preferences_video_loop_label": "常にループ: ",
"preferences_autoplay_label": "自動再生: ",
- "preferences_continue_label": "次の動画を自動再生: ",
+ "preferences_continue_label": "次の動画に移動: ",
"preferences_continue_autoplay_label": "次の動画を自動再生: ",
"preferences_listen_label": "音声モードを使用: ",
"preferences_local_label": "動画視聴にプロキシを経由: ",
@@ -68,7 +68,7 @@
"preferences_related_videos_label": "関連動画を表示: ",
"preferences_annotations_label": "最初からアノテーションを表示: ",
"preferences_extend_desc_label": "動画の説明文を自動的に拡張: ",
- "preferences_vr_mode_label": "対話的な360°動画 (WebGL が必要): ",
+ "preferences_vr_mode_label": "対話的な360°動画 (WebGLが必要): ",
"preferences_category_visual": "外観設定",
"preferences_player_style_label": "プレイヤーのスタイル: ",
"Dark mode: ": "ダークモード: ",
@@ -125,9 +125,9 @@
"subscriptions_unseen_notifs_count_0": "{{count}}件の未読通知",
"search": "検索",
"Log out": "ログアウト",
- "Released under the AGPLv3 on Github.": "GitHub 上で AGPLv3 の元で公開",
+ "Released under the AGPLv3 on Github.": "GitHub上でAGPLv3の元で公開",
"Source available here.": "ソースはここで閲覧可能です。",
- "View JavaScript license information.": "JavaScript ライセンス情報",
+ "View JavaScript license information.": "JavaScriptライセンス情報",
"View privacy policy.": "個人情報保護方針",
"Trending": "急上昇",
"Public": "公開",
@@ -144,7 +144,7 @@
"Show more": "もっと見る",
"Show less": "表示を少なく",
"Watch on YouTube": "YouTubeで視聴",
- "Switch Invidious Instance": "Invidious インスタンスの変更",
+ "Switch Invidious Instance": "Invidiousインスタンスの変更",
"Hide annotations": "アノテーションを隠す",
"Show annotations": "アノテーションを表示",
"Genre: ": "ジャンル: ",
@@ -363,9 +363,9 @@
"search_filters_features_option_location": "場所",
"search_filters_features_option_hdr": "HDR",
"Current version: ": "現在のバージョン: ",
- "next_steps_error_message": "下記のものを試して下さい: ",
- "next_steps_error_message_refresh": "再読込",
- "next_steps_error_message_go_to_youtube": "YouTubeへ",
+ "next_steps_error_message": "以下をお試してください: ",
+ "next_steps_error_message_refresh": "再読み込み",
+ "next_steps_error_message_go_to_youtube": "YouTubeを開く",
"search_filters_duration_option_short": "4分未満",
"footer_documentation": "説明書",
"footer_source_code": "ソースコード",
@@ -459,7 +459,7 @@
"Song: ": "曲: ",
"Channel Sponsor": "チャンネルのスポンサー",
"Standard YouTube license": "標準 Youtube ライセンス",
- "Download is disabled": "ダウンロード: このインスタンスでは未対応",
+ "Download is disabled": "ダウンロード: このインスタンスは未対応",
"Import YouTube playlist (.csv)": "YouTube 再生リストをインポート (.csv)",
"generic_button_delete": "削除",
"generic_button_cancel": "キャンセル",
@@ -469,5 +469,15 @@
"generic_button_save": "保存",
"generic_button_rss": "RSS",
"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": "テーマの切り替え"
}
diff --git a/locales/ko.json b/locales/ko.json
index e496bd2a..7611e8e7 100644
--- a/locales/ko.json
+++ b/locales/ko.json
@@ -46,7 +46,7 @@
"source": "출처",
"JavaScript license information": "자바스크립트 라이선스 정보",
"An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
- "History": "역사",
+ "History": "시청 기록",
"Delete account?": "계정을 삭제 하시겠습니까?",
"Export data as JSON": "JSON으로 데이터 내보내기",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML로 구독 내보내기 (뉴파이프 및 프리튜브)",
@@ -351,7 +351,7 @@
"News": "뉴스",
"Gaming": "게임",
"Music": "음악",
- "Default": "디폴트",
+ "Default": "전체",
"Rating: ": "평점: ",
"About": "정보",
"Top": "최고",
@@ -460,7 +460,7 @@
"Music in this video": "동영상 속 음악",
"Artist: ": "아티스트: ",
"Download is disabled": "다운로드가 비활성화 되어있음",
- "Import YouTube playlist (.csv)": "유튜브 플레이리스트 가져오기 (.csv)",
+ "Import YouTube playlist (.csv)": "유튜브 재생목록 가져오기 (.csv)",
"playlist_button_add_items": "동영상 추가",
"channel_tab_podcasts_label": "팟캐스트",
"generic_button_delete": "삭제",
@@ -468,6 +468,16 @@
"generic_button_save": "저장",
"generic_button_cancel": "취소",
"generic_button_rss": "RSS",
- "channel_tab_releases_label": "출시",
- "generic_channels_count_0": "{{count}} 채널"
+ "channel_tab_releases_label": "발매",
+ "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}}"
}
diff --git a/locales/lmo.json b/locales/lmo.json
new file mode 100644
index 00000000..9d2fe2a8
--- /dev/null
+++ b/locales/lmo.json
@@ -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: "
+}
diff --git a/locales/nb-NO.json b/locales/nb-NO.json
index 08b1e0e2..cf0ee286 100644
--- a/locales/nb-NO.json
+++ b/locales/nb-NO.json
@@ -486,5 +486,6 @@
"generic_button_rss": "RSS",
"playlist_button_add_items": "Legg til videoer",
"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)"
}
diff --git a/locales/nl.json b/locales/nl.json
index aa5da731..d495a2d1 100644
--- a/locales/nl.json
+++ b/locales/nl.json
@@ -107,10 +107,10 @@
"Report statistics: ": "Statistieken bijhouden? ",
"Save preferences": "Instellingen opslaan",
"Subscription manager": "Abonnementen beheren",
- "Token manager": "Toegangssleutels beheren",
+ "Token manager": "Toegangssleutelbeheerder",
"Token": "Toegangssleutel",
"Import/export": "Importeren/Exporteren",
- "unsubscribe": "Deabonneren",
+ "unsubscribe": "deabonneren",
"revoke": "Intrekken",
"Subscriptions": "Abonnementen",
"search": "zoeken",
@@ -357,7 +357,7 @@
"footer_original_source_code": "Originele bron-code",
"footer_modfied_source_code": "Gewijzigde bron-code",
"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",
"search_filters_duration_option_long": "Lang (> 20 minuten)",
"preferences_quality_option_dash": "DASH (adaptieve kwaliteit)",
@@ -462,5 +462,39 @@
"Spanish (auto-generated)": "Spaans (automatisch gegenereerd)",
"crash_page_you_found_a_bug": "Je lijkt een bug in Invidious tegengekomen te zijn!",
"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"
}
diff --git a/locales/pl.json b/locales/pl.json
index 313f11cb..f24e9766 100644
--- a/locales/pl.json
+++ b/locales/pl.json
@@ -21,13 +21,13 @@
"Import and Export Data": "Import i eksport danych",
"Import": "Import",
"Import Invidious data": "Importuj dane JSON Invidious",
- "Import YouTube subscriptions": "Importuj subskrybcje z YouTube/OPML",
- "Import FreeTube subscriptions (.db)": "Importuj subskrybcje z FreeTube (.db)",
- "Import NewPipe subscriptions (.json)": "Importuj subskrybcje z NewPipe (.json)",
+ "Import YouTube subscriptions": "Importuj subskrypcje YouTube w formacie CSV lub OPML",
+ "Import FreeTube subscriptions (.db)": "Importuj subskrypcje FreeTube (.db)",
+ "Import NewPipe subscriptions (.json)": "Importuj subskrypcje NewPipe (.json)",
"Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)",
"Export": "Eksport",
- "Export subscriptions as OPML": "Eksportuj subskrybcje jako OPML",
- "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrybcje jako OPML (dla NewPipe i FreeTube)",
+ "Export subscriptions as OPML": "Eksportuj subskrypcje jako OPML",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrypcje jako OPML (dla NewPipe i FreeTube)",
"Export data as JSON": "Eksportuj dane Invidious jako JSON",
"Delete account?": "Usunąć konto?",
"History": "Historia",
@@ -73,7 +73,7 @@
"preferences_thin_mode_label": "Tryb minimalny: ",
"preferences_category_misc": "Różne preferencje",
"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: ",
"Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ",
"preferences_max_results_label": "Liczba filmów widoczna na stronie subskrybcji: ",
@@ -95,7 +95,7 @@
"Clear watch history": "Wyczyść historię",
"Import/export data": "Import/Eksport danych",
"Change password": "Zmień hasło",
- "Manage subscriptions": "Organizuj subskrybcje",
+ "Manage subscriptions": "Organizuj subskrypcje",
"Manage tokens": "Zarządzaj tokenami",
"Watch history": "Historia",
"Delete account": "Usuń konto",
@@ -115,7 +115,7 @@
"Import/export": "Import/Eksport",
"unsubscribe": "odsubskrybuj",
"revoke": "cofnij",
- "Subscriptions": "Subskrybcje",
+ "Subscriptions": "Subskrypcje",
"search": "szukaj",
"Log out": "Wyloguj",
"Source available here.": "Kod źródłowy dostępny tutaj.",
@@ -492,7 +492,7 @@
"Song: ": "Piosenka: ",
"Channel Sponsor": "Sponsor kanału",
"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_cancel": "Anuluj",
"generic_button_rss": "RSS",
@@ -503,5 +503,15 @@
"playlist_button_add_items": "Dodaj filmy",
"generic_channels_count_0": "{{count}} kanał",
"generic_channels_count_1": "{{count}} kanały",
- "generic_channels_count_2": "{{count}} kanałów"
+ "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`"
}
diff --git a/locales/pt-BR.json b/locales/pt-BR.json
index 1e089723..1637b5d8 100644
--- a/locales/pt-BR.json
+++ b/locales/pt-BR.json
@@ -1,27 +1,27 @@
{
"LIVE": "AO VIVO",
- "Shared `x` ago": "Compartilhado `x` atrás",
+ "Shared `x` ago": "Publicado há `x`",
"Unsubscribe": "Cancelar inscrição",
"Subscribe": "Inscrever-se",
"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",
"oldest": "mais antigos",
"popular": "populares",
- "last": "último",
+ "last": "últimos",
"Next page": "Próxima página",
"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 passwords must match": "Nova senha deve ser igual",
- "Authorize token?": "Autorizar o token?",
- "Authorize token for `x`?": "Autorizar o token para `x`?",
+ "New passwords must match": "As senhas devem ser iguais",
+ "Authorize token?": "Autorizar token?",
+ "Authorize token for `x`?": "Autorizar token para `x`?",
"Yes": "Sim",
"No": "Não",
- "Import and Export Data": "Importar e Exportar Dados",
+ "Import and Export Data": "Importar/exportar dados",
"Import": "Importar",
- "Import Invidious data": "Importar dados em JSON do Invidious",
- "Import YouTube subscriptions": "Importar inscrições do YouTube/OPML",
+ "Import Invidious data": "Importar dados JSON do Invidious",
+ "Import YouTube subscriptions": "Importar inscrições no formato CSV ou OPML do YouTube",
"Import FreeTube subscriptions (.db)": "Importar inscrições do FreeTube (.db)",
"Import NewPipe subscriptions (.json)": "Importar inscrições do NewPipe (.json)",
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
@@ -32,49 +32,49 @@
"Delete account?": "Excluir conta?",
"History": "Histórico",
"An alternative front-end to YouTube": "Uma interface alternativa para o YouTube",
- "JavaScript license information": "Informação de licença do JavaScript",
- "source": "código-fonte",
- "Log in": "Entrar",
- "Log in/register": "Entrar/Registrar",
+ "JavaScript license information": "Informações sobre a licença do JavaScript",
+ "source": "fonte",
+ "Log in": "Fazer login",
+ "Log in/register": "Fazer login/criar conta",
"User ID": "Usuário",
"Password": "Senha",
"Time (h:mm:ss):": "Hora (h:mm:ss):",
- "Text CAPTCHA": "CAPTCHA em texto",
- "Image CAPTCHA": "CAPTCHA em imagem",
+ "Text CAPTCHA": "Mudar para um desafio de texto",
+ "Image CAPTCHA": "Mudar para um desafio visual",
"Sign In": "Entrar",
- "Register": "Registrar",
+ "Register": "Criar conta",
"E-mail": "E-mail",
"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_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_listen_label": "Apenas áudio por padrão: ",
"preferences_local_label": "Usar proxy nos vídeos: ",
"preferences_speed_label": "Velocidade padrão: ",
"preferences_quality_label": "Qualidade de vídeo preferida: ",
"preferences_volume_label": "Volume de reprodução: ",
- "preferences_comments_label": "Preferência de comentários: ",
+ "preferences_comments_label": "Comentários padrão: ",
"youtube": "YouTube",
"reddit": "Reddit",
- "preferences_captions_label": "Preferência de legendas: ",
+ "preferences_captions_label": "Legendas padrão: ",
"Fallback captions: ": "Legendas alternativas: ",
"preferences_related_videos_label": "Mostrar vídeos relacionados: ",
"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_category_visual": "Preferências visuais",
- "preferences_player_style_label": "Estilo do tocador: ",
+ "preferences_player_style_label": "Estilo de reprodução: ",
"Dark mode: ": "Modo escuro: ",
"preferences_dark_mode_label": "Tema: ",
"dark": "escuro",
"light": "claro",
"preferences_thin_mode_label": "Modo compacto: ",
"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_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: ",
"preferences_max_results_label": "Número de vídeos no feed: ",
"preferences_sort_label": "Ordenar vídeos por: ",
@@ -84,30 +84,30 @@
"alphabetically - reverse": "alfabética - ordem inversa",
"channel name": "nome do canal",
"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 unwatched video from channel: ": "Mostrar apenas o vídeo mais recente não visualizado do canal: ",
- "preferences_unseen_only_label": "Mostrar apenas vídeos não visualizados: ",
- "preferences_notifications_only_label": "Mostrar apenas notificações (se existentes): ",
- "Enable web notifications": "Ativar notificações pela web",
- "`x` uploaded a video": "`x` publicou um novo vídeo",
+ "Only show latest video from channel: ": "Mostrar apenas vídeos mais recentes 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 assistido: ",
+ "preferences_notifications_only_label": "Mostrar apenas notificações (se houver): ",
+ "Enable web notifications": "Ativar notificações da Web",
+ "`x` uploaded a video": "`x` publicou um vídeo",
"`x` is live": "`x` está ao vivo",
"preferences_category_data": "Preferências de dados",
- "Clear watch history": "Limpar histórico de reprodução",
- "Import/export data": "Importar/Exportar dados",
+ "Clear watch history": "Limpar histórico de exibição",
+ "Import/export data": "Importar/exportar dados",
"Change password": "Alterar senha",
"Manage subscriptions": "Gerenciar inscrições",
"Manage tokens": "Gerenciar tokens",
- "Watch history": "Histórico de reprodução",
- "Delete account": "Apagar sua conta",
+ "Watch history": "Histórico de exibição",
+ "Delete account": "Excluir conta",
"preferences_category_admin": "Preferências de administrador",
- "preferences_default_home_label": "Página de início padrão: ",
- "preferences_feed_menu_label": "Menu do feed: ",
- "preferences_show_nick_label": "Mostrar o nickname no topo: ",
- "Top enabled: ": "Habilitar destaques: ",
- "CAPTCHA enabled: ": "Habilitar CAPTCHA: ",
- "Login enabled: ": "Habilitar login: ",
- "Registration enabled: ": "Habilitar registro: ",
- "Report statistics: ": "Habilitar estatísticas: ",
+ "preferences_default_home_label": "Página inicial padrão: ",
+ "preferences_feed_menu_label": "Guias de feed preferidos: ",
+ "preferences_show_nick_label": "Mostrar nome de usuário na parte superior: ",
+ "Top enabled: ": "Destaques ativados: ",
+ "CAPTCHA enabled: ": "CAPTCHA ativado: ",
+ "Login enabled: ": "Fazer login ativado: ",
+ "Registration enabled: ": "Criar conta ativado: ",
+ "Report statistics: ": "Relatório de estatísticas: ",
"Save preferences": "Salvar preferências",
"Subscription manager": "Gerenciador de inscrições",
"Token manager": "Gerenciador de tokens",
@@ -115,24 +115,24 @@
"tokens_count_0": "{{count}} token",
"tokens_count_1": "{{count}} tokens",
"tokens_count_2": "{{count}} tokens",
- "Import/export": "Importar/Exportar",
+ "Import/export": "Importar/exportar",
"unsubscribe": "cancelar inscrição",
"revoke": "revogar",
"Subscriptions": "Inscrições",
- "search": "Pesquisar",
+ "search": "pesquisar",
"Log out": "Sair",
"Released under the AGPLv3 on Github.": "Lançado sob a AGPLv3 no GitHub.",
"Source available here.": "Código-fonte disponível aqui.",
- "View JavaScript license information.": "Ver informações da licença do JavaScript.",
- "View privacy policy.": "Ver a política de privacidade.",
- "Trending": "Tendências",
+ "View JavaScript license information.": "Informações de licença JavaScript.",
+ "View privacy policy.": "Política de privacidade.",
+ "Trending": "Em alta",
"Public": "Público",
"Unlisted": "Não listado",
"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",
- "Delete playlist `x`?": "Apagar a playlist `x`?",
- "Delete playlist": "Apagar playlist",
+ "Delete playlist `x`?": "Excluir playlist `x`?",
+ "Delete playlist": "Excluir playlist",
"Create playlist": "Criar playlist",
"Title": "Título",
"Playlist privacy": "Privacidade da playlist",
@@ -140,24 +140,24 @@
"Show more": "Mostrar mais",
"Show less": "Mostrar menos",
"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",
"Show annotations": "Mostrar anotações",
"Genre: ": "Gênero: ",
"License: ": "Licença: ",
"Family friendly? ": "Filtrar conteúdo impróprio: ",
"Wilson score: ": "Pontuação de Wilson: ",
- "Engagement: ": "Empenho: ",
+ "Engagement: ": "Engajamento: ",
"Whitelisted regions: ": "Regiões permitidas: ",
"Blacklisted regions: ": "Regiões bloqueadas: ",
- "Shared `x`": "Compartilhado `x`",
+ "Shared `x`": "Publicado em `x`",
"Premieres in `x`": "Estreia em `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 more comments on Reddit": "Ver mais comentários no Reddit",
"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"
},
"View Reddit comments": "Ver comentários no Reddit",
@@ -166,7 +166,7 @@
"Incorrect password": "Senha incorreta",
"Wrong answer": "Resposta incorreta",
"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",
"Password is a required field": "A senha é um campo obrigatório",
"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",
"Invidious Private Feed for `x`": "Feed Privado do Invidious para `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.",
"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",
"`x` ago": "`x` atrás",
"Load more": "Carregar mais",
"Could not create mix.": "Não foi possível criar o mix.",
- "Empty playlist": "Lista de reprodução vazia",
- "Not a playlist.": "Não é uma lista de reprodução.",
- "Playlist does not exist.": "A lista de reprodução não existe.",
- "Could not pull trending pages.": "Não foi possível obter as páginas dos vídeos em alta.",
+ "Empty playlist": "Playlist vazia",
+ "Not a playlist.": "Não é uma playlist.",
+ "Playlist does not exist.": "A playlist não existe.",
+ "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 \"token\" is a required field": "O campo oculto \"token\" é obrigatório",
"Erroneous challenge": "Desafio inválido",
@@ -319,87 +319,87 @@
"generic_count_seconds_0": "{{count}} segundo",
"generic_count_seconds_1": "{{count}} segundos",
"generic_count_seconds_2": "{{count}} segundos",
- "Fallback comments: ": "Comentários alternativos: ",
+ "Fallback comments: ": "Alternativa para comentários: ",
"Popular": "Populares",
- "Search": "Procurar",
- "Top": "No topo",
+ "Search": "Pesquisar",
+ "Top": "Destaques",
"About": "Sobre",
"Rating: ": "Avaliação: ",
"preferences_locale_label": "Idioma: ",
- "View as playlist": "Ver como lista de reprodução",
+ "View as playlist": "Ver como playlist",
"Default": "Padrão",
"Music": "Músicas",
"Gaming": "Jogos",
"News": "Notícias",
"Movies": "Filmes",
- "Download": "Baixar",
+ "Download": "Download",
"Download as: ": "Baixar como: ",
"%A %B %-d, %Y": "%A %-d %B %Y",
"(edited)": "(editado)",
"YouTube comment permalink": "Link permanente do comentário no YouTube",
"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",
"Video mode": "Modo de vídeo",
"channel_tab_videos_label": "Vídeos",
- "Playlists": "Listas de reprodução",
+ "Playlists": "Playlists",
"channel_tab_community_label": "Comunidade",
- "search_filters_sort_option_relevance": "relevância",
- "search_filters_sort_option_rating": "avaliação",
- "search_filters_sort_option_date": "data",
- "search_filters_sort_option_views": "visualizações",
- "search_filters_type_label": "content_type",
- "search_filters_duration_label": "duração",
- "search_filters_features_label": "recursos",
- "search_filters_sort_label": "ordenar",
- "search_filters_date_option_hour": "hora",
- "search_filters_date_option_today": "hoje",
- "search_filters_date_option_week": "semana",
- "search_filters_date_option_month": "mês",
- "search_filters_date_option_year": "ano",
- "search_filters_type_option_video": "vídeo",
+ "search_filters_sort_option_relevance": "Relevância",
+ "search_filters_sort_option_rating": "Avaliação",
+ "search_filters_sort_option_date": "Data de publicação",
+ "search_filters_sort_option_views": "Visualizações",
+ "search_filters_type_label": "Tipo",
+ "search_filters_duration_label": "Duração",
+ "search_filters_features_label": "Características",
+ "search_filters_sort_label": "Ordenar por",
+ "search_filters_date_option_hour": "Últimas horas",
+ "search_filters_date_option_today": "Hoje",
+ "search_filters_date_option_week": "Esta semana",
+ "search_filters_date_option_month": "Este mês",
+ "search_filters_date_option_year": "Este ano",
+ "search_filters_type_option_video": "Vídeo",
"search_filters_type_option_channel": "Canal",
- "search_filters_type_option_playlist": "playlist",
- "search_filters_type_option_movie": "filme",
- "search_filters_type_option_show": "show",
- "search_filters_features_option_hd": "hd",
- "search_filters_features_option_subtitles": "legendas",
- "search_filters_features_option_c_commons": "creative_commons",
- "search_filters_features_option_three_d": "3d",
- "search_filters_features_option_live": "ao vivo",
- "search_filters_features_option_four_k": "4k",
- "search_filters_features_option_location": "localização",
- "search_filters_features_option_hdr": "hdr",
+ "search_filters_type_option_playlist": "Playlist",
+ "search_filters_type_option_movie": "Filme",
+ "search_filters_type_option_show": "Séries",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "Legendas",
+ "search_filters_features_option_c_commons": "Creative Commons",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_live": "AO VIVO",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_location": "Localização",
+ "search_filters_features_option_hdr": "HDR",
"Current version: ": "Versão atual: ",
"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",
- "footer_donate_page": "Doe",
- "adminprefs_modified_source_code_url_label": "URL para repositório de código fonte modificado",
+ "footer_donate_page": "Doar",
+ "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_short": "Curto (< 4 minutos)",
"footer_documentation": "Documentação",
- "footer_source_code": "Código fonte",
- "footer_original_source_code": "Código fonte original",
+ "footer_source_code": "Código-fonte",
+ "footer_original_source_code": "Código-fonte original",
"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_quality_dash_option_4320p": "4320p",
"generic_videos_count_0": "{{count}} vídeo",
"generic_videos_count_1": "{{count}} vídeos",
"generic_videos_count_2": "{{count}} vídeos",
- "generic_playlists_count_0": "{{count}} lista de reprodução",
- "generic_playlists_count_1": "{{count}} listas de reprodução",
- "generic_playlists_count_2": "{{count}} listas de reprodução",
+ "generic_playlists_count_0": "{{count}} playlist",
+ "generic_playlists_count_1": "{{count}} playlists",
+ "generic_playlists_count_2": "{{count}} playlists",
"generic_subscribers_count_0": "{{count}} inscrito",
"generic_subscribers_count_1": "{{count}} inscritos",
"generic_subscribers_count_2": "{{count}} inscritos",
"generic_subscriptions_count_0": "{{count}} inscrição",
"generic_subscriptions_count_1": "{{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_1": "{{count}} notificações não vistas",
- "subscriptions_unseen_notifs_count_2": "{{count}} notificações não vistas",
+ "subscriptions_unseen_notifs_count_0": "{{count}} notificação não visualizada",
+ "subscriptions_unseen_notifs_count_1": "{{count}} notificações não visualizadas",
+ "subscriptions_unseen_notifs_count_2": "{{count}} notificações não visualizadas",
"comments_view_x_replies_0": "Ver {{count}} resposta",
"comments_view_x_replies_1": "Ver {{count}} respostas",
"comments_view_x_replies_2": "Ver {{count}} respostas",
@@ -407,14 +407,14 @@
"comments_points_count_1": "{{count}} pontos",
"comments_points_count_2": "{{count}} pontos",
"crash_page_you_found_a_bug": "Parece que você encontrou um erro no Invidious!",
- "crash_page_before_reporting": "Antes de reportar um erro, verifique se você:",
- "preferences_save_player_pos_label": "Salvar a posição de reprodução: ",
+ "crash_page_before_reporting": "Antes de informar um erro, verifique se você:",
+ "preferences_save_player_pos_label": "Salvar posição de reprodução: ",
"search_filters_features_option_purchased": "Comprado",
"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_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_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_1": "{{count}} visualizações",
"generic_views_count_2": "{{count}} visualizações",
@@ -422,8 +422,8 @@
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_small": "Pequeno",
"preferences_quality_dash_option_auto": "Auto",
- "preferences_quality_dash_option_best": "Melhor",
- "preferences_quality_dash_option_worst": "Pior",
+ "preferences_quality_dash_option_best": "Melhor qualidade",
+ "preferences_quality_dash_option_worst": "Pior qualidade",
"preferences_quality_dash_option_2160p": "2160p",
"preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_option_1080p": "1080p",
@@ -435,17 +435,17 @@
"invidious": "Invidious",
"preferences_quality_option_medium": "Médio",
"search_filters_features_option_three_sixty": "360°",
- "none": "none",
+ "none": "nenhum",
"videoinfo_watch_on_youTube": "Assistir no YouTube",
- "videoinfo_youTube_embed_link": "Embutir",
- "videoinfo_invidious_embed_link": "Link Embutido",
+ "videoinfo_youTube_embed_link": "Embed",
+ "videoinfo_invidious_embed_link": "Embed link",
"download_subtitles": "Legendas - `x` (.vtt)",
- "user_created_playlists": "`x` listas de reprodução criadas",
- "user_saved_playlists": "`x` listas de reprodução salvas",
+ "user_created_playlists": "`x` playlists criadas",
+ "user_saved_playlists": "`x` playlists salvas",
"Video unavailable": "Vídeo indisponível",
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`",
"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_change_filters_or_query": "Tente ampliar sua consulta de pesquisa e/ou alterar os filtros.",
"English (United Kingdom)": "Inglês (Reino Unido)",
@@ -465,7 +465,7 @@
"Portuguese (Brazil)": "Português (Brasil)",
"Russian (auto-generated)": "Russo (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",
"Dutch (auto-generated)": "Holandês (gerado automaticamente)",
"French (auto-generated)": "Francês (gerado automaticamente)",
@@ -479,21 +479,21 @@
"Turkish (auto-generated)": "Turco (gerado automaticamente)",
"search_filters_duration_option_medium": "Médio (4 - 20 minutos)",
"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>",
"channel_tab_channels_label": "Canais",
- "channel_tab_playlists_label": "Listas de reprodução",
- "channel_tab_shorts_label": "Curtos",
- "channel_tab_streams_label": "Ao Vivo",
+ "channel_tab_playlists_label": "Playlists",
+ "channel_tab_shorts_label": "Shorts",
+ "channel_tab_streams_label": "Transmissão ao vivo",
"Music in this video": "Música neste vídeo",
"Artist: ": "Artista: ",
"Album: ": "Álbum: ",
"Standard YouTube license": "Licença padrão do YouTube",
"Song: ": "Música: ",
- "Channel Sponsor": "Patrocinador do Canal",
- "Download is disabled": "Download está desabilitado",
- "Import YouTube playlist (.csv)": "Importar lista de reprodução do YouTube (.csv)",
- "generic_button_delete": "Apagar",
+ "Channel Sponsor": "Patrocinador do canal",
+ "Download is disabled": "Download indisponível",
+ "Import YouTube playlist (.csv)": "Importar playlist do YouTube (.csv)",
+ "generic_button_delete": "Excluir",
"generic_button_save": "Salvar",
"generic_button_edit": "Editar",
"playlist_button_add_items": "Adicionar vídeos",
@@ -503,5 +503,15 @@
"generic_button_rss": "RSS",
"generic_channels_count_0": "{{count}} canal",
"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`"
}
diff --git a/locales/pt-PT.json b/locales/pt-PT.json
index 3834c9e2..f83a80a9 100644
--- a/locales/pt-PT.json
+++ b/locales/pt-PT.json
@@ -130,12 +130,12 @@
"Private": "Privado",
"View all playlists": "Ver todas as listas de reprodução",
"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",
"Create playlist": "Criar lista de reprodução",
"Title": "Título",
"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 less": "Mostrar menos",
"Watch on YouTube": "Ver no YouTube",
@@ -150,8 +150,8 @@
"Whitelisted regions: ": "Regiões permitidas: ",
"Blacklisted regions: ": "Regiões bloqueadas: ",
"Shared `x`": "Partilhado `x`",
- "Premieres in `x`": "Estreias em 'x'",
- "Premieres `x`": "Estreias 'x'",
+ "Premieres in `x`": "Estreias em `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.",
"View YouTube comments": "Ver comentários do YouTube",
"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",
"Please log in": "Por favor, inicie sessão",
"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",
"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.",
diff --git a/locales/pt.json b/locales/pt.json
index e7cc4810..463dbf3a 100644
--- a/locales/pt.json
+++ b/locales/pt.json
@@ -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_date": "Data de envio",
+ "search_filters_sort_option_date": "Data de carregamento",
"search_filters_sort_option_rating": "Avaliação",
"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 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_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_vr_mode_label": "Vídeos interativos de 360 graus (necessita de WebGL): ",
- "preferences_extend_desc_label": "Estender automaticamente a descrição do vídeo: ",
- "next_steps_error_message_go_to_youtube": "Ir ao YouTube",
+ "preferences_vr_mode_label": "Vídeos interativos de 360 graus (requer WebGL): ",
+ "preferences_extend_desc_label": "Expandir automaticamente a descrição do vídeo: ",
+ "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_refresh": "Atualizar",
+ "next_steps_error_message_refresh": "Recarregar",
"search_filters_features_option_hdr": "HDR",
"search_filters_features_option_location": "Localização",
"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_c_commons": "Creative Commons",
"search_filters_features_option_subtitles": "Legendas",
@@ -37,45 +37,52 @@
"search_filters_features_label": "Funcionalidades",
"search_filters_duration_label": "Duração",
"search_filters_type_label": "Tipo",
- "permalink": "hiperligação permanente",
- "YouTube comment permalink": "Hiperligação permanente do comentário no YouTube",
+ "permalink": "ligação permanente",
+ "YouTube comment permalink": "Ligação permanente do comentário no YouTube",
"Download as: ": "Descarregar como: ",
"Download": "Descarregar",
- "Default": "Predefinido",
+ "Default": "Padrão",
"Top": "Destaques",
"Search": "Pesquisar",
- "generic_count_years": "{{count}} segundo",
- "generic_count_years_plural": "{{count}} segundos",
- "generic_count_months": "{{count}} minuto",
- "generic_count_months_plural": "{{count}} minutos",
- "generic_count_weeks": "{{count}} hora",
- "generic_count_weeks_plural": "{{count}} horas",
- "generic_count_days": "{{count}} dia",
- "generic_count_days_plural": "{{count}} dias",
- "generic_count_hours": "{{count}} seman",
- "generic_count_hours_plural": "{{count}} semanas",
- "generic_count_minutes": "{{count}} mês",
- "generic_count_minutes_plural": "{{count}} meses",
- "generic_count_seconds": "{{count}} ano",
- "generic_count_seconds_plural": "{{count}} anos",
+ "generic_count_years_0": "{{count}} ano",
+ "generic_count_years_1": "{{count}} anos",
+ "generic_count_years_2": "{{count}} anos",
+ "generic_count_months_0": "{{count}} mês",
+ "generic_count_months_1": "{{count}} meses",
+ "generic_count_months_2": "{{count}} meses",
+ "generic_count_weeks_0": "{{count}} semana",
+ "generic_count_weeks_1": "{{count}} semanas",
+ "generic_count_weeks_2": "{{count}} semanas",
+ "generic_count_days_0": "{{count}} dia",
+ "generic_count_days_1": "{{count}} dias",
+ "generic_count_days_2": "{{count}} dias",
+ "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",
"Chinese (Traditional)": "Chinês (tradicional)",
"Chinese (Simplified)": "Chinês (simplificado)",
- "Could not pull trending pages.": "Não foi possível obter as páginas de tendências.",
- "Could not create mix.": "Não foi possível criar a mistura.",
+ "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 o mix.",
"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 `x`?": "Eliminar a lista de reprodução 'x'?",
+ "Delete playlist `x`?": "Eliminar lista de reprodução `x`?",
"search": "pesquisar",
"unsubscribe": "anular subscrição",
- "Import/export": "Importar / exportar",
+ "Import/export": "Importar/exportar",
"Save preferences": "Guardar preferências",
"Top enabled: ": "Destaques ativados: ",
"Delete account": "Eliminar conta",
- "Import/export data": "Importar / exportar dados",
+ "Import/export data": "Importar/exportar dados",
"preferences_annotations_label": "Mostrar anotações sempre: ",
- "preferences_continue_label": "Reproduzir sempre o próximo: ",
- "Sign In": "Iniciar sessão",
+ "preferences_continue_label": "Reproduzir sempre o seguinte: ",
+ "Sign In": "Entrar",
"Log in/register": "Iniciar sessão/registar",
"Delete account?": "Eliminar conta?",
"Import and Export Data": "Importar e exportar dados",
@@ -86,7 +93,7 @@
"Danish": "Dinamarquês",
"Czech": "Checo",
"Croatian": "Croata",
- "Corsican": "Corso",
+ "Corsican": "Córsego",
"Cebuano": "Cebuano",
"Catalan": "Catalão",
"Burmese": "Birmanês",
@@ -100,10 +107,10 @@
"Arabic": "Árabe",
"Amharic": "Amárico",
"Albanian": "Albanês",
- "Afrikaans": "Africano",
+ "Afrikaans": "Africânder",
"English (auto-generated)": "Inglês (auto-gerado)",
"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",
"Erroneous token": "Token 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 get channel info.": "Não foi possível obter as informações do canal.",
"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`",
"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 empty": "A palavra-chave não pode estar vazia",
- "Wrong username or password": "Nome de utilizador ou palavra-chave incorreto",
- "Password is a required field": "Palavra-chave é um campo obrigatório",
+ "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-passe não pode estar vazia",
+ "Wrong username or password": "Nome de utilizador ou palavra-passe incorreta",
+ "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",
"CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
"Erroneous CAPTCHA": "CAPTCHA inválido",
"Wrong answer": "Resposta errada",
- "Incorrect password": "Palavra-chave incorreta",
+ "Incorrect password": "Palavra-passe incorreta",
"Show replies": "Mostrar respostas",
"Hide replies": "Ocultar respostas",
"View Reddit comments": "Ver comentários do Reddit",
"View `x` comments": {
"": "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 YouTube comments": "Ver comentários do YouTube",
- "Premieres `x`": "Estreias 'x'",
- "Premieres in `x`": "Estreias em 'x'",
+ "Premieres `x`": "Estreia `x`",
+ "Premieres in `x`": "Estreia a `x`",
"Shared `x`": "Partilhado `x`",
"Blacklisted regions: ": "Regiões bloqueadas: ",
"Whitelisted regions: ": "Regiões permitidas: ",
@@ -151,43 +158,44 @@
"Show annotations": "Mostrar anotações",
"Hide annotations": "Ocultar anotações",
"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",
"Title": "Título",
"Create playlist": "Criar lista de reprodução",
- "Updated `x` ago": "Atualizado `x` atrás",
+ "Updated `x` ago": "Atualizado há `x`",
"View all playlists": "Ver todas as listas de reprodução",
"Private": "Privado",
"Unlisted": "Não listado",
"Public": "Público",
"Trending": "Tendências",
- "View privacy policy.": "Ver a política de privacidade.",
- "View JavaScript license information.": "Ver informações da licença do JavaScript.",
+ "View privacy policy.": "Ver política de privacidade.",
+ "View JavaScript license information.": "Ver informações da licença JavaScript.",
"Source available here.": "Código-fonte disponível aqui.",
"Log out": "Terminar sessão",
"Subscriptions": "Subscrições",
"revoke": "revogar",
- "tokens_count": "{{count}} token",
- "tokens_count_plural": "{{count}} tokens",
+ "tokens_count_0": "{{count}} token",
+ "tokens_count_1": "{{count}} tokens",
+ "tokens_count_2": "{{count}} tokens",
"Token": "Token",
- "Token manager": "Gerir tokens",
- "Subscription manager": "Gerir subscrições",
+ "Token manager": "Gestor de tokens",
+ "Subscription manager": "Gestor de subscrições",
"Report statistics: ": "Relatório de estatísticas: ",
"Registration enabled: ": "Registar ativado: ",
"Login enabled: ": "Iniciar sessão ativado: ",
"CAPTCHA enabled: ": "CAPTCHA ativado: ",
"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",
"Watch history": "Histórico de reprodução",
"Manage tokens": "Gerir tokens",
- "Manage subscriptions": "Gerir as subscrições",
- "Change password": "Alterar palavra-chave",
+ "Manage subscriptions": "Gerir subscrições",
+ "Change password": "Alterar palavra-passe",
"Clear watch history": "Limpar histórico de reprodução",
"preferences_category_data": "Preferências de dados",
"`x` is live": "`x` está em direto",
- "`x` uploaded a video": "`x` publicou um novo vídeo",
- "Enable web notifications": "Ativar notificações pela web",
+ "`x` uploaded a video": "`x` publicou um vídeo",
+ "Enable web notifications": "Ativar notificações web",
"preferences_notifications_only_label": "Mostrar apenas notificações (se existirem): ",
"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: ",
@@ -199,9 +207,9 @@
"published - reverse": "publicado - inverso",
"published": "publicado",
"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: ",
- "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_thin_mode_label": "Modo compacto: ",
"light": "claro",
@@ -212,11 +220,11 @@
"preferences_category_visual": "Preferências visuais",
"preferences_related_videos_label": "Mostrar vídeos relacionados: ",
"Fallback captions: ": "Legendas alternativas: ",
- "preferences_captions_label": "Legendas predefinidas: ",
+ "preferences_captions_label": "Legendas padrão: ",
"reddit": "Reddit",
"youtube": "YouTube",
- "preferences_comments_label": "Preferência dos comentários: ",
- "preferences_volume_label": "Volume da reprodução: ",
+ "preferences_comments_label": "Comentários padrão: ",
+ "preferences_volume_label": "Volume de reprodução: ",
"preferences_quality_label": "Qualidade de vídeo preferida: ",
"preferences_speed_label": "Velocidade preferida: ",
"preferences_local_label": "Usar proxy nos vídeos: ",
@@ -231,11 +239,11 @@
"Image CAPTCHA": "Imagem CAPTCHA",
"Text CAPTCHA": "Texto CAPTCHA",
"Time (h:mm:ss):": "Tempo (h:mm:ss):",
- "Password": "Palavra-chave",
+ "Password": "Palavra-passe",
"User ID": "Utilizador",
"Log in": "Iniciar sessão",
- "source": "código-fonte",
- "JavaScript license information": "Informação de licença do JavaScript",
+ "source": "fonte",
+ "JavaScript license information": "Informação da licença JavaScript",
"An alternative front-end to YouTube": "Uma interface alternativa ao YouTube",
"History": "Histórico",
"Export data as JSON": "Exportar dados Invidious como JSON",
@@ -245,18 +253,18 @@
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
"Import NewPipe subscriptions (.json)": "Importar subscrições do NewPipe (.json)",
"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": "Importar",
"No": "Não",
"Yes": "Sim",
- "Authorize token for `x`?": "Autorizar token para `x`?",
- "Authorize token?": "Autorizar token?",
- "New passwords must match": "As novas palavra-chaves devem corresponder",
- "New password": "Nova palavra-chave",
+ "Authorize token for `x`?": "Autorizar 'token' para `x`?",
+ "Authorize token?": "Autorizar 'token'?",
+ "New passwords must match": "As novas palavras-passe devem ser iguais",
+ "New password": "Nova palavra-passe",
"Clear watch history?": "Limpar histórico de reprodução?",
"Previous page": "Página anterior",
- "Next page": "Próxima página",
+ "Next page": "Página seguinte",
"last": "últimos",
"Current version: ": "Versão atual: ",
"channel_tab_community_label": "Comunidade",
@@ -264,19 +272,19 @@
"channel_tab_videos_label": "Vídeos",
"Video mode": "Modo de vídeo",
"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)",
"%A %B %-d, %Y": "%A %B %-d, %Y",
"Movies": "Filmes",
"News": "Notícias",
"Gaming": "Jogos",
- "Music": "Música",
+ "Music": "Músicas",
"View as playlist": "Ver como lista de reprodução",
"preferences_locale_label": "Idioma: ",
"Rating: ": "Avaliação: ",
- "About": "Sobre",
+ "About": "Acerca",
"Popular": "Popular",
- "Fallback comments: ": "Comentários alternativos: ",
+ "Fallback comments: ": "Alternativa para comentários: ",
"Zulu": "Zulu",
"Yoruba": "Ioruba",
"Yiddish": "Iídiche",
@@ -321,7 +329,7 @@
"Marathi": "Marathi",
"Maori": "Maori",
"Maltese": "Maltês",
- "Malayalam": "Malaiala",
+ "Malayalam": "Malaialaio",
"Malay": "Malaio",
"Malagasy": "Malgaxe",
"Macedonian": "Macedónio",
@@ -357,15 +365,15 @@
"Galician": "Galego",
"French": "Francês",
"Finnish": "Finlandês",
- "popular": "popular",
- "oldest": "mais antigos",
- "newest": "mais recentes",
+ "popular": "populares",
+ "oldest": "antigos",
+ "newest": "recentes",
"View playlist on YouTube": "Ver lista de reprodução no YouTube",
"View channel on YouTube": "Ver canal no YouTube",
"Subscribe": "Subscrever",
"Unsubscribe": "Anular subscrição",
"Shared `x` ago": "Partilhado `x` atrás",
- "LIVE": "AO VIVO",
+ "LIVE": "Direto",
"search_filters_duration_option_short": "Curto (< 4 minutos)",
"search_filters_duration_option_long": "Longo (> 20 minutos)",
"footer_source_code": "Código-fonte",
@@ -378,7 +386,7 @@
"preferences_quality_dash_label": "Qualidade de vídeo DASH preferida: ",
"preferences_quality_option_small": "Baixa",
"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_4320p": "4320p",
"preferences_quality_dash_option_2160p": "2160p",
@@ -389,7 +397,7 @@
"preferences_quality_dash_option_144p": "144p",
"search_filters_features_option_purchased": "Comprado",
"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",
"invidious": "Invidious",
"preferences_quality_option_medium": "Média",
@@ -400,33 +408,41 @@
"preferences_quality_dash_option_worst": "Pior",
"none": "nenhum",
"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)",
- "generic_views_count": "{{count}} visualização",
- "generic_views_count_plural": "{{count}} visualizações",
+ "generic_views_count_0": "{{count}} visualização",
+ "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`",
"user_saved_playlists": "`x` listas de reprodução guardadas",
- "generic_videos_count": "{{count}} vídeo",
- "generic_videos_count_plural": "{{count}} vídeos",
- "generic_playlists_count": "{{count}} lista de reprodução",
- "generic_playlists_count_plural": "{{count}} listas de reprodução",
- "subscriptions_unseen_notifs_count": "{{count}} notificação não vista",
- "subscriptions_unseen_notifs_count_plural": "{{count}} notificações não vistas",
- "comments_view_x_replies": "Ver {{count}} resposta",
- "comments_view_x_replies_plural": "Ver {{count}} respostas",
- "generic_subscribers_count": "{{count}} inscrito",
- "generic_subscribers_count_plural": "{{count}} inscritos",
- "generic_subscriptions_count": "{{count}} inscrição",
- "generic_subscriptions_count_plural": "{{count}} inscrições",
- "comments_points_count": "{{count}} ponto",
- "comments_points_count_plural": "{{count}} pontos",
+ "generic_videos_count_0": "{{count}} vídeo",
+ "generic_videos_count_1": "{{count}} vídeos",
+ "generic_videos_count_2": "{{count}} vídeos",
+ "generic_playlists_count_0": "{{count}} lista de reprodução",
+ "generic_playlists_count_1": "{{count}} listas de reprodução",
+ "generic_playlists_count_2": "{{count}} listas de reprodução",
+ "subscriptions_unseen_notifs_count_0": "{{count}} notificação não vista",
+ "subscriptions_unseen_notifs_count_1": "{{count}} notificações não vistas",
+ "subscriptions_unseen_notifs_count_2": "{{count}} notificações não vistas",
+ "comments_view_x_replies_0": "Ver {{count}} resposta",
+ "comments_view_x_replies_1": "Ver {{count}} respostas",
+ "comments_view_x_replies_2": "Ver {{count}} respostas",
+ "generic_subscribers_count_0": "{{count}} subscritor",
+ "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_before_reporting": "Antes de reportar um erro, verifique se:",
"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_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_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",
"search_filters_title": "Filtro",
"Chinese (Taiwan)": "Chinês (Taiwan)",
@@ -464,11 +480,11 @@
"search_filters_type_option_all": "Qualquer tipo",
"search_filters_duration_option_none": "Qualquer duração",
"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_channels_label": "Canais",
"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",
"Artist: ": "Artista: ",
"Album: ": "Álbum: ",
@@ -477,12 +493,25 @@
"Standard YouTube license": "Licença padrão do YouTube",
"Download is disabled": "A descarga está desativada",
"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_rss": "RSS",
"channel_tab_podcasts_label": "Podcasts",
"channel_tab_releases_label": "Lançamentos",
- "generic_button_save": "Salvar",
+ "generic_button_save": "Guardar",
"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."
}
diff --git a/locales/ro.json b/locales/ro.json
index 85bf746f..ccbeef63 100644
--- a/locales/ro.json
+++ b/locales/ro.json
@@ -478,5 +478,6 @@
"search_filters_type_option_all": "orice tip",
"preferences_quality_dash_option_240p": "240p",
"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"
}
diff --git a/locales/ru.json b/locales/ru.json
index 2769f3ab..61bf9e92 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -8,14 +8,14 @@
"newest": "сначала новые",
"oldest": "сначала старые",
"popular": "популярные",
- "last": "недавние",
+ "last": "последние",
"Next page": "Следующая страница",
"Previous page": "Предыдущая страница",
"Clear watch history?": "Очистить историю просмотров?",
"New password": "Новый пароль",
"New passwords must match": "Новые пароли не совпадают",
"Authorize token?": "Авторизовать токен?",
- "Authorize token for `x`?": "Авторизовать токен для `x`?",
+ "Authorize token for `x`?": "Токен авторизации для `x`?",
"Yes": "Да",
"No": "Нет",
"Import and Export Data": "Импорт и экспорт данных",
@@ -29,7 +29,7 @@
"Export subscriptions as OPML": "Экспортировать подписки в формате OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в формате OPML (для NewPipe и FreeTube)",
"Export data as JSON": "Экспортировать данные Invidious в формате JSON",
- "Delete account?": "Удалить учётку?",
+ "Delete account?": "Удалить учётную запись?",
"History": "История",
"An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube",
"JavaScript license information": "Информация о лицензиях JavaScript",
@@ -42,7 +42,7 @@
"Text CAPTCHA": "Текстовая капча (англ.)",
"Image CAPTCHA": "Капча-картинка",
"Sign In": "Войти",
- "Register": "Зарегистрироваться",
+ "Register": "Регистрация",
"E-mail": "Эл. почта",
"Preferences": "Настройки",
"preferences_category_player": "Настройки проигрывателя",
@@ -61,7 +61,7 @@
"preferences_captions_label": "Основной язык субтитров: ",
"Fallback captions: ": "Дополнительный язык субтитров: ",
"preferences_related_videos_label": "Показывать похожие видео? ",
- "preferences_annotations_label": "Всегда показывать аннотации? ",
+ "preferences_annotations_label": "Показывать аннотации по умолчанию: ",
"preferences_extend_desc_label": "Автоматически раскрывать описание видео: ",
"preferences_vr_mode_label": "Интерактивные 360-градусные видео (необходим WebGL): ",
"preferences_category_visual": "Настройки сайта",
@@ -77,13 +77,13 @@
"preferences_annotations_subscribed_label": "Всегда показывать аннотации на каналах из ваших подписок? ",
"Redirect homepage to feed: ": "Показывать подписки на главной странице: ",
"preferences_max_results_label": "Число видео в ленте: ",
- "preferences_sort_label": "Сортировать видео: ",
- "published": "по дате публикации",
- "published - reverse": "по дате публикации в обратном порядке",
- "alphabetically": "по алфавиту",
- "alphabetically - reverse": "по алфавиту в обратном порядке",
- "channel name": "по названию канала",
- "channel name - reverse": "по названию канала в обратном порядке",
+ "preferences_sort_label": "Сортировать видео по: ",
+ "published": "дате публикации",
+ "published - reverse": "дате публикации в обратном порядке",
+ "alphabetically": "алфавиту",
+ "alphabetically - reverse": "алфавиту в обратном порядке",
+ "channel name": "названию канала",
+ "channel name - reverse": "названию канала в обратном порядке",
"Only show latest video from channel: ": "Показывать только последние видео с каналов: ",
"Only show latest unwatched video from channel: ": "Показывать только последние непросмотренные видео с канала: ",
"preferences_unseen_only_label": "Показывать только непросмотренные видео: ",
@@ -134,8 +134,8 @@
"Title": "Заголовок",
"Playlist privacy": "Видимость плейлиста",
"Editing playlist `x`": "Редактирование плейлиста `x`",
- "Show more": "Развернуть",
- "Show less": "Свернуть",
+ "Show more": "Показать больше",
+ "Show less": "Показать меньше",
"Watch on YouTube": "Смотреть на YouTube",
"Switch Invidious Instance": "Сменить зеркало Invidious",
"Hide annotations": "Скрыть аннотации",
@@ -414,7 +414,7 @@
"generic_count_days_0": "{{count}} день",
"generic_count_days_1": "{{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_720p": "720p",
"generic_subscriptions_count_0": "{{count}} подписка",
@@ -466,7 +466,7 @@
"search_filters_features_option_three_sixty": "360°",
"Video unavailable": "Видео недоступно",
"preferences_save_player_pos_label": "Запоминать позицию: ",
- "preferences_region_label": "Страна: ",
+ "preferences_region_label": "Страна источник ",
"preferences_watch_history_label": "Включить историю просмотров: ",
"search_filters_title": "Фильтр",
"search_filters_duration_option_none": "Любой длины",
@@ -476,7 +476,7 @@
"search_message_no_results": "Ничего не найдено.",
"search_message_use_another_instance": " Дополнительно вы можете <a href=\"`x`\">поискать на других зеркалах</a>.",
"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_apply_button": "Применить фильтры",
"Popular enabled: ": "Популярное включено: ",
@@ -503,5 +503,6 @@
"channel_tab_podcasts_label": "Подкасты",
"generic_channels_count_0": "{{count}} канал",
"generic_channels_count_1": "{{count}} канала",
- "generic_channels_count_2": "{{count}} каналов"
+ "generic_channels_count_2": "{{count}} каналов",
+ "Import YouTube watch history (.json)": "Импортировать историю просмотра из YouTube (.json)"
}
diff --git a/locales/sl.json b/locales/sl.json
index 9a912f2d..3803d09c 100644
--- a/locales/sl.json
+++ b/locales/sl.json
@@ -520,5 +520,6 @@
"generic_channels_count_0": "{{count}} kanal",
"generic_channels_count_1": "{{count}} kanala",
"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)"
}
diff --git a/locales/sq.json b/locales/sq.json
index 41d4161c..363a70b0 100644
--- a/locales/sq.json
+++ b/locales/sq.json
@@ -79,7 +79,7 @@
"invidious": "Invidious",
"preferences_captions_label": "Titra parazgjedhje: ",
"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: ",
"preferences_dark_mode_label": "Temë: ",
"dark": "e errët",
@@ -477,5 +477,12 @@
"channel_tab_releases_label": "Hedhje në qarkullim",
"Song: ": "Pjesë: ",
"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"
}
diff --git a/locales/sr.json b/locales/sr.json
index f0e5518d..4b24e7c0 100644
--- a/locales/sr.json
+++ b/locales/sr.json
@@ -21,7 +21,7 @@
"Import and Export Data": "Uvoz i izvoz podataka",
"Import": "Uvezi",
"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 NewPipe subscriptions (.json)": "Uvezi NewPipe praćenja (.json)",
"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!",
"generic_views_count_0": "{{count}} pregled",
"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": "Подеси тему"
}
diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json
index bf439b28..57c6de9c 100644
--- a/locales/sr_Cyrl.json
+++ b/locales/sr_Cyrl.json
@@ -21,7 +21,7 @@
"Import and Export Data": "Увоз и извоз података",
"Import": "Увези",
"Import Invidious data": "Увези Invidious JSON податке",
- "Import YouTube subscriptions": "Увези YouTube/OPML праћења",
+ "Import YouTube subscriptions": "Увези YouTube CSV или OPML праћења",
"Import FreeTube subscriptions (.db)": "Увези FreeTube праћења (.db)",
"Import NewPipe subscriptions (.json)": "Увези NewPipe праћења (.json)",
"Import NewPipe data (.zip)": "Увези NewPipe податке (.zip)",
@@ -503,5 +503,15 @@
"crash_page_you_found_a_bug": "Изгледа да сте пронашли грешку у Invidious-у!",
"generic_views_count_0": "{{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}}"
}
diff --git a/locales/sv-SE.json b/locales/sv-SE.json
index a319fffd..76edc341 100644
--- a/locales/sv-SE.json
+++ b/locales/sv-SE.json
@@ -20,15 +20,15 @@
"No": "Nej",
"Import and Export Data": "Importera och exportera data",
"Import": "Importera",
- "Import Invidious data": "Importera Invidious-data",
- "Import YouTube subscriptions": "Importera YouTube-prenumerationer",
+ "Import Invidious data": "Importera Invidious JSON data",
+ "Import YouTube subscriptions": "Importera YouTube/OPML prenumerationer",
"Import FreeTube subscriptions (.db)": "Importera FreeTube-prenumerationer (.db)",
"Import NewPipe subscriptions (.json)": "Importera NewPipe-prenumerationer (.json)",
"Import NewPipe data (.zip)": "Importera NewPipe-data (.zip)",
"Export": "Exportera",
"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 data as JSON": "Exportera data som JSON",
+ "Export data as JSON": "Exportera Invidious data som JSON",
"Delete account?": "Radera konto?",
"History": "Historik",
"An alternative front-end to YouTube": "Ett alternativt gränssnitt till YouTube",
@@ -63,7 +63,7 @@
"preferences_related_videos_label": "Visa relaterade videor? ",
"preferences_annotations_label": "Visa länkar-i-videon som förval? ",
"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_player_style_label": "Spelarstil: ",
"Dark mode: ": "Mörkt läge: ",
@@ -152,7 +152,7 @@
"View YouTube comments": "Visa YouTube-kommentarer",
"View more comments on Reddit": "Visa flera kommentarer på Reddit",
"View `x` comments": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "Visa `x` kommentarer",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "Visa `x` kommentar",
"": "Visa `x` kommentarer"
},
"View Reddit comments": "Visa Reddit-kommentarer",
@@ -167,7 +167,7 @@
"Wrong username or password": "Ogiltigt användarnamn eller lösenord",
"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",
- "Please log in": "Logga in",
+ "Please log in": "Snälla logga in",
"Invidious Private Feed for `x`": "Ogiltig privat flöde för `x`",
"channel:`x`": "kanal `x`",
"Deleted or invalid channel": "Raderad eller ogiltig kanal",
@@ -311,8 +311,8 @@
"%A %B %-d, %Y": "%A %B %-d, %Y",
"(edited)": "(redigerad)",
"YouTube comment permalink": "Permanent YouTube-länk till innehållet",
- "permalink": "permalänk",
- "`x` marked it with a ❤": "`x` lämnade ett ❤",
+ "permalink": "permanent länk",
+ "`x` marked it with a ❤": "`x` markerade det med ett ❤",
"Audio mode": "Ljudläge",
"Video mode": "Videoläge",
"channel_tab_videos_label": "Videor",
@@ -320,30 +320,30 @@
"channel_tab_community_label": "Gemenskap",
"search_filters_sort_option_relevance": "Relevans",
"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_type_label": "Typ",
"search_filters_duration_label": "Varaktighet",
"search_filters_features_label": "Funktioner",
"search_filters_sort_label": "Sortera efter",
- "search_filters_date_option_hour": "timme",
- "search_filters_date_option_today": "idag",
- "search_filters_date_option_week": "vecka",
- "search_filters_date_option_month": "månad",
- "search_filters_date_option_year": "år",
- "search_filters_type_option_video": "video",
- "search_filters_type_option_channel": "kanal",
- "search_filters_type_option_playlist": "spellista",
- "search_filters_type_option_movie": "film",
- "search_filters_type_option_show": "tv-serie",
- "search_filters_features_option_hd": "hd",
- "search_filters_features_option_subtitles": "undertexter",
- "search_filters_features_option_c_commons": "creative_commons",
- "search_filters_features_option_three_d": "3d",
- "search_filters_features_option_live": "live",
- "search_filters_features_option_four_k": "4k",
- "search_filters_features_option_location": "plats",
- "search_filters_features_option_hdr": "hdr",
+ "search_filters_date_option_hour": "Senaste Timmen",
+ "search_filters_date_option_today": "Idag",
+ "search_filters_date_option_week": "Denna vecka",
+ "search_filters_date_option_month": "Denna månad",
+ "search_filters_date_option_year": "Detta år",
+ "search_filters_type_option_video": "Video",
+ "search_filters_type_option_channel": "Kanal",
+ "search_filters_type_option_playlist": "Spellista",
+ "search_filters_type_option_movie": "Film",
+ "search_filters_type_option_show": "Serie",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "Undertexter/CC",
+ "search_filters_features_option_c_commons": "Creative Commons",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_live": "Live",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_location": "Plats",
+ "search_filters_features_option_hdr": "HDR",
"Current version: ": "Nuvarande version: ",
"next_steps_error_message_refresh": "Uppdatera",
"next_steps_error_message_go_to_youtube": "Gå till Youtube",
@@ -352,5 +352,149 @@
"search_filters_duration_option_long": "Lång (> 20 minuter)",
"footer_documentation": "Dokumentation",
"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`"
}
diff --git a/locales/tk.json b/locales/tk.json
new file mode 100644
index 00000000..798ea6ce
--- /dev/null
+++ b/locales/tk.json
@@ -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."
+}
diff --git a/locales/tr.json b/locales/tr.json
index 0575a4dd..3b7bf3a4 100644
--- a/locales/tr.json
+++ b/locales/tr.json
@@ -21,7 +21,7 @@
"Import and Export Data": "Verileri İçe ve Dışa Aktar",
"Import": "İç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 NewPipe subscriptions (.json)": "NewPipe Aboneliklerini İçe Aktar (.json)",
"Import NewPipe data (.zip)": "NewPipe Verilerini İçe Aktar (.zip)",
@@ -486,5 +486,15 @@
"playlist_button_add_items": "Video ekle",
"channel_tab_podcasts_label": "Podcast'ler",
"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ı."
}
diff --git a/locales/uk.json b/locales/uk.json
index c26618fe..223772d9 100644
--- a/locales/uk.json
+++ b/locales/uk.json
@@ -127,7 +127,7 @@
"Create playlist": "Створити список відтворення",
"Title": "Заголовок",
"Playlist privacy": "Конфіденційність списку відтворення",
- "Editing playlist `x`": "Редагування списку відтворення \"x\"",
+ "Editing playlist `x`": "Редагування списку відтворення `x`",
"Watch on YouTube": "Дивитися на YouTube",
"Hide annotations": "Приховати анотації",
"Show annotations": "Показати анотації",
@@ -503,5 +503,15 @@
"generic_button_save": "Зберегти",
"generic_channels_count_0": "{{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`"
}
diff --git a/locales/vi.json b/locales/vi.json
index 9cb87d3e..229f8fa9 100644
--- a/locales/vi.json
+++ b/locales/vi.json
@@ -1,62 +1,62 @@
{
"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",
"Shared `x` ago": "Đã chia sẻ `x` trước",
- "Unsubscribe": "Hủy theo dõi",
- "Subscribe": "Theo dõi",
+ "Unsubscribe": "Hủy đăng ký",
+ "Subscribe": "Đăng ký",
"View channel on YouTube": "Xem kênh trên YouTube",
"View playlist on YouTube": "Xem danh sách phát trên YouTube",
- "newest": "mới nhất",
- "oldest": "lâu đời nhất",
- "popular": "phổ biến",
- "last": "Cuối cùng",
+ "newest": "Mới nhất",
+ "oldest": "Cũ nhất",
+ "popular": "Phổ biến",
+ "last": "cuối cùng",
"Next page": "Trang tiếp theo",
"Previous page": "Trang trước",
"Clear watch history?": "Xóa lịch sử xem?",
"New password": "Mật khẩu mới",
"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 for `x`?": "Cấp phép mã thông báo cho` x`?",
- "Yes": "Đúng",
+ "Authorize token for `x`?": "Cấp phép mã thông báo cho `x`?",
+ "Yes": "Có",
"No": "Không",
"Import and Export Data": "Nhập và xuất dữ liệu",
"Import": "Nhập",
- "Import Invidious data": "Nhập dữ liệu Invidious JSON",
- "Import YouTube subscriptions": "Nhập dữ liệu thuê bao YouTube/OPML",
- "Import FreeTube subscriptions (.db)": "Nhập đăng ký FreeTube (.db)",
- "Import NewPipe subscriptions (.json)": "Nhập đăng ký NewPipe (.json)",
- "Import NewPipe data (.zip)": "Nhập dữ liệu NewPipe (.zip)",
+ "Import Invidious data": "Nhập dữ liệu Invidious dưới dạng JSON",
+ "Import YouTube subscriptions": "Nhập các kênh đã đăng ký từ YouTube/OPML",
+ "Import FreeTube subscriptions (.db)": "Nhập các kênh đã đăng ký từ FreeTube (.db)",
+ "Import NewPipe subscriptions (.json)": "Nhập các kênh đã đăng ký từ NewPipe (.json)",
+ "Import NewPipe data (.zip)": "Nhập dữ liệu từ NewPipe (.zip)",
"Export": "Xuất",
- "Export subscriptions as OPML": "Xuất đă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": "Xuất các kênh đã đăng ký dưới dạng OPML",
+ "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",
"Delete account?": "Xóa tài khoản?",
"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",
"source": "nguồn",
"Log in": "Đăng nhập",
"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",
- "Time (h:mm:ss):": "Thời gian (h: mm: ss):",
- "Text CAPTCHA": "Nhắn tin tới CAPTCHA",
- "Image CAPTCHA": "Hình ảnh CAPTCHA",
+ "Time (h:mm:ss):": "Thời gian (h:mm:ss):",
+ "Text CAPTCHA": "CAPTCHA dạng chữ",
+ "Image CAPTCHA": "CAPTCHA dạng ảnh",
"Sign In": "Đăng nhập",
"Register": "Đăng ký",
"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_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_autoplay_label": "Tự động phát video tiếp theo: ",
"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_quality_label": "Chất lượng video ưa thích: ",
- "preferences_volume_label": "Âm lượng trình phát video: ",
+ "preferences_quality_label": "Chất lượng video: ",
+ "preferences_volume_label": "Âm lượng video: ",
"preferences_comments_label": "Nhận xét mặc định: ",
"youtube": "YouTube",
"reddit": "Reddit",
@@ -64,7 +64,7 @@
"Fallback captions: ": "Phụ đề dự phòng: ",
"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_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_category_visual": "Tùy chọn hình ảnh",
"preferences_player_style_label": "Phong cách trình phát: ",
@@ -82,24 +82,24 @@
"preferences_sort_label": "Sắp xếp video theo: ",
"published": "được phát hành",
"published - reverse": "đã xuất bản - đảo ngược",
- "alphabetically": "theo thứ tự bảng chữ cái",
- "alphabetically - reverse": "theo thứ tự bảng chữ cái - đảo ngược",
- "channel name": "Tên kênh",
- "channel name - reverse": "tên kênh - đảo ngược",
+ "alphabetically": "Thứ tự (A - Z)",
+ "alphabetically - reverse": "Thứ tự (Z - A)",
+ "channel name": "Tên kênh (A - Z)",
+ "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 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ó): ",
"Enable web notifications": "Bật thông báo web",
- "`x` uploaded a video": "` x` đã tải lên một video",
- "`x` is live": "` x` đang phát trực tiếp",
+ "`x` uploaded a video": "`x` đã tải lên một video",
+ "`x` is live": "`x` đang phát trực tiếp",
"preferences_category_data": "Tùy chọn dữ liệu",
"Clear watch history": "Xóa lịch sử xem",
"Import/export data": "Nhập / xuất dữ liệu",
"Change password": "Đổi mật khẩu",
"Manage subscriptions": "Quản lý các mục đăng kí",
"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",
"preferences_category_admin": "Tùy chọn quản trị viên",
"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.",
"Trending": "Xu hướng",
"Public": "Công khai",
- "Unlisted": "Không hiển thị",
+ "Unlisted": "Không công khai",
"Private": "Riêng tư",
"View all playlists": "Xem tất cả danh sách phát",
"Updated `x` ago": "Đã cập nhật` x` trước",
@@ -131,24 +131,24 @@
"Title": "Tiêu đề",
"Playlist privacy": "Bảo mật danh sách phát",
"Editing playlist `x`": "Chỉnh sửa danh sách phát` x`",
- "Show more": "Cho xem nhiều hơn",
- "Show less": "Hiện ít hơn",
+ "Show more": "Hiển thị thêm",
+ "Show less": "Hiển thị ít hơn",
"Watch on YouTube": "Xem trên YouTube",
"Switch Invidious Instance": "Chuyển phiên bản Invidious",
"Hide annotations": "Ẩn chú thích",
"Show annotations": "Hiển thị chú thích",
"Genre: ": "Thể loại: ",
"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: ",
"Engagement: ": "Hôn ước: ",
"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`",
- "View Reddit comments": "Xem nhận xét trên Reddit",
- "Hide replies": "Ẩn câu trả lời",
- "Show replies": "Hiển thị câu trả lời",
- "Incorrect password": "Mật khẩu không đúng",
+ "View Reddit comments": "Xem bình luận trên Reddit",
+ "Hide replies": "Ẩn phản hồi",
+ "Show replies": "Hiển thị phản hồi",
+ "Incorrect password": "Mật khẩu không chính xác",
"Wrong answer": "Câu trả lời sai",
"Erroneous CAPTCHA": "CAPTCHA bị lỗi",
"CAPTCHA is a required field": "CAPTCHA là trường bắt buộc",
@@ -190,35 +190,35 @@
"Bulgarian": "Tiếng Bungari",
"Burmese": "Tiếng Miến Điện",
"Catalan": "Tiếng Catalan",
- "Cebuano": "Cebuano",
+ "Cebuano": "Tiếng Cebu",
"Chinese (Simplified)": "Tiếng Trung (Giản thể)",
"Chinese (Traditional)": "Tiếng Trung (Phồn thể)",
- "Corsican": "Corsican",
+ "Corsican": "Tiếng Corse",
"Croatian": "Tiếng Croatia",
"Czech": "Tiếng Séc",
- "Danish": "Người Đan Mạch",
+ "Danish": "Tiếng Đan Mạch",
"Dutch": "Tiếng Hà Lan",
"Esperanto": "Quốc tế ngữ",
"Estonian": "Tiếng Estonia",
- "Filipino": "Filipino",
+ "Filipino": "Tiếng Philippines",
"Finnish": "Tiếng Phần Lan",
- "French": "Người Pháp",
+ "French": "Tiếng Pháp",
"Galician": "Tiếng Galicia",
"Georgian": "Tiếng Georgia",
"German": "Tiếng Đức",
- "Greek": "Người Hy Lạp",
- "Gujarati": "Gujarati",
- "Haitian Creole": "Tiếng Creole của Haiti",
- "Hausa": "Hausa",
+ "Greek": "Tiếng Hy Lạp",
+ "Gujarati": "Tiếng Gujarat",
+ "Haitian Creole": "Tiếng Creole (Haiti)",
+ "Hausa": "Tiếng Hausa",
"Hawaiian": "Tiếng Hawaii",
"Hebrew": "Tiếng Do Thái",
"Hindi": "Tiếng Hindi",
- "Hmong": "Hmong",
- "Hungarian": "Người Hungary",
+ "Hmong": "Tiếng Hmong",
+ "Hungarian": "Tiếng Hungary",
"Icelandic": "Tiếng Iceland",
- "Igbo": "Igbo",
+ "Igbo": "Tiếng Igbo",
"Indonesian": "Tiếng Indonesia",
- "Irish": "Tiếng Ailen",
+ "Irish": "Tiếng Ireland",
"Italian": "Tiếng Ý",
"Japanese": "Tiếng Nhật",
"Javanese": "Tiếng Java",
@@ -237,37 +237,37 @@
"Malagasy": "Tiếng Malagasy",
"Malay": "Tiếng Mã Lai",
"Malayalam": "Tiếng Malayalam",
- "Maltese": "Cây nho",
+ "Maltese": "Tiếng Malta",
"Maori": "Tiếng Maori",
- "Marathi": "Marathi",
+ "Marathi": "Tiếng Marathi",
"Mongolian": "Tiếng Mông Cổ",
"Nepali": "Tiếng Nepal",
- "Norwegian Bokmål": "Tiếng Na Uy Bokmål",
- "Nyanja": "Nyanja",
- "Pashto": "Pashto",
+ "Norwegian Bokmål": "Tiếng Na Uy (Bokmål)",
+ "Nyanja": "Tiếng Chewa / Nyanja",
+ "Pashto": "Tiếng Pashtun",
"Persian": "Tiếng Ba Tư",
- "Polish": "Đánh bóng",
+ "Polish": "Tiếng Ba Lan",
"Portuguese": "Tiếng Bồ Đào Nha",
- "Punjabi": "Punjabi",
+ "Punjabi": "Tiếng Punjab",
"Romanian": "Tiếng Rumani",
"Russian": "Tiếng Nga",
- "Samoan": "Samoan",
- "Scottish Gaelic": "Tiếng Gaelic Scotland",
+ "Samoan": "Tiếng Samoa",
+ "Scottish Gaelic": "Tiếng Gaelic (Scotland)",
"Serbian": "Tiếng Serbia",
- "Shona": "Shona",
- "Sindhi": "Sindhi",
- "Sinhala": "Sinhala",
+ "Shona": "Tiếng Shona",
+ "Sindhi": "Tiếng Sindh",
+ "Sinhala": "Tiếng Sinhala",
"Slovak": "Tiếng Slovak",
"Slovenian": "Tiếng Slovenia",
"Somali": "Tiếng Somali",
"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)",
"Sundanese": "Tiếng Sundan",
"Swahili": "Tiếng Swahili",
"Swedish": "Tiếng Thụy Điển",
- "Tajik": "Tajik",
- "Tamil": "Tamil",
+ "Tajik": "Tiếng Tajik",
+ "Tamil": "Tiếng Tamil",
"Telugu": "Tiếng Telugu",
"Thai": "Tiếng Thái",
"Turkish": "Tiếng Thổ Nhĩ Kỳ",
@@ -275,17 +275,17 @@
"Urdu": "Tiếng Urdu",
"Uzbek": "Tiếng Uzbek",
"Vietnamese": "Tiếng Việt",
- "Welsh": "Người xứ Wales",
- "Western Frisian": "Western Frisian",
- "Xhosa": "Xhosa",
- "Yiddish": "Yiddish",
- "Yoruba": "Yoruba",
+ "Welsh": "Tiếng Wales",
+ "Western Frisian": "Tiếng Tây Frisia",
+ "Xhosa": "Tiếng Nam Phi",
+ "Yiddish": "Tiếng Yiddish",
+ "Yoruba": "Tiếng Yoruba",
"Zulu": "Tiếng Zulu",
"Fallback comments: ": "Nhận xét dự phòng: ",
"Popular": "Phổ biến",
"Search": "Tìm kiếm",
"Top": "Hàng đầu",
- "About": "Trong khoảng",
+ "About": "Giới thiệu",
"Rating: ": "Xếp hạng: ",
"preferences_locale_label": "Ngôn ngữ: ",
"View as playlist": "Xem dưới dạng danh sách phát",
@@ -295,45 +295,45 @@
"News": "Tin tức",
"Movies": "Phim",
"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",
"(edited)": "(đã chỉnh sửa)",
"YouTube comment permalink": "Liên kết cố định nhận xét trên YouTube",
"permalink": "liên kết cố định",
"`x` marked it with a ❤": "` x` đã đánh dấu nó bằng một ❤",
- "Audio mode": "Chế độ âm thanh",
- "Video mode": "Chế độ quay",
+ "Audio mode": "Chế độ audio",
+ "Video mode": "Chế độ video",
"channel_tab_videos_label": "Video",
"Playlists": "Danh sách phát",
"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_date": "ngày",
- "search_filters_sort_option_views": "lượt xem",
- "search_filters_type_label": "content_type",
- "search_filters_duration_label": "thời lượng",
- "search_filters_features_label": "đặc trưng",
- "search_filters_sort_label": "sắp xếp",
- "search_filters_date_option_hour": "giờ",
- "search_filters_date_option_today": "hôm nay",
- "search_filters_date_option_week": "tuần",
- "search_filters_date_option_month": "tháng",
- "search_filters_date_option_year": "năm",
+ "search_filters_sort_option_date": "Ngày tải lên",
+ "search_filters_sort_option_views": "Lượt xem",
+ "search_filters_type_label": "Thể loại",
+ "search_filters_duration_label": "Thời lượng",
+ "search_filters_features_label": "Đặc điểm",
+ "search_filters_sort_label": "Sắp xếp theo",
+ "search_filters_date_option_hour": "Một giờ qua",
+ "search_filters_date_option_today": "Hôm nay",
+ "search_filters_date_option_week": "Tuần này",
+ "search_filters_date_option_month": "Tháng này",
+ "search_filters_date_option_year": "Năm này",
"search_filters_type_option_video": "video",
- "search_filters_type_option_channel": "kênh",
- "search_filters_type_option_playlist": "danh sách phát",
- "search_filters_type_option_movie": "bộ phim",
- "search_filters_type_option_show": "chỉ",
- "search_filters_features_option_hd": "hd",
- "search_filters_features_option_subtitles": "phụ đề",
- "search_filters_features_option_c_commons": "Commons sáng tạo",
- "search_filters_features_option_three_d": "3d",
- "search_filters_features_option_live": "trực tiếp",
- "search_filters_features_option_four_k": "4k",
- "search_filters_features_option_location": "vị trí",
- "search_filters_features_option_hdr": "hdr",
+ "search_filters_type_option_channel": "Kênh",
+ "search_filters_type_option_playlist": "Danh sách phát",
+ "search_filters_type_option_movie": "Phim",
+ "search_filters_type_option_show": "Hiện",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "Phụ đề",
+ "search_filters_features_option_c_commons": "Giấy phép Creative Commons",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_live": "Trực tiếp",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_location": "Vị trí",
+ "search_filters_features_option_hdr": "HDR",
"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_views_count": "{{count}} lượt xem",
"View `x` comments": {
@@ -341,40 +341,40 @@
"([^.,0-9]|^)1([^.,0-9]|$)": "Hiển thị `x`bình luận"
},
"Song: ": "Ca khúc: ",
- "Premieres in `x`": "Trình chiếu lần đầu vào `x`",
- "preferences_quality_dash_option_worst": "Thấp nhất",
+ "Premieres in `x`": "Trình chiếu ở `x`",
+ "preferences_quality_dash_option_worst": "Tệ nhất",
"preferences_watch_history_label": "Bật lịch sử video đã xem ",
"preferences_quality_option_hd720": "HD720",
"unsubscribe": "hủy đăng kí",
"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",
"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",
"Music in this video": "Nhạc trong video này",
"Artist: ": "Nghệ sĩ: ",
"Premieres `x`": "Phát lần đầu `x`",
"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.",
- "preferences_quality_option_small": "Nhỏ",
+ "preferences_quality_option_small": "Thấp",
"preferences_quality_dash_option_144p": "144p",
"invidious": "Invidious",
"preferences_quality_dash_option_240p": "240p",
- "Import/export": "Xuất/nhập dữ liệu",
- "preferences_quality_dash_option_4320p": "4320p",
+ "Import/export": "Nhập/Xuất",
+ "preferences_quality_dash_option_4320p": "4320p (8K)",
"preferences_quality_option_dash": "DASH (tự tối ưu chất lượng)",
"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_2160p": "2160p",
+ "preferences_quality_dash_option_2160p": "2160p (4K)",
"search_message_no_results": "Tìm kiếm không có kết quả.",
"preferences_quality_dash_option_1080p": "1080p",
"preferences_quality_dash_option_720p": "720p",
"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",
- "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_360p": "360p",
"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>.",
"Standard YouTube license": "Giấy phép YouTube thông thường",
"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.",
"Chinese (China)": "Tiếng Trung (Trung Quốc)",
"generic_button_cancel": "Hủy",
"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ý."
}
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index db86a9bf..756645f4 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -26,7 +26,7 @@
"Import and Export Data": "导入与导出数据",
"Import": "导入",
"Import Invidious data": "导入 Invidious JSON 数据",
- "Import YouTube subscriptions": "导入 YouTube/OPML 订阅",
+ "Import YouTube subscriptions": "导入 YouTube CSV 或 OPML 订阅",
"Import FreeTube subscriptions (.db)": "导入 FreeTube 订阅 (.db)",
"Import NewPipe subscriptions (.json)": "导入 NewPipe 订阅 (.json)",
"Import NewPipe data (.zip)": "导入 NewPipe 数据 (.zip)",
@@ -470,5 +470,14 @@
"generic_button_save": "保存",
"generic_button_rss": "RSS",
"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`"
}
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index 565f1d88..2584db9c 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -26,7 +26,7 @@
"Import and Export Data": "匯入與匯出資料",
"Import": "匯入",
"Import Invidious data": "匯入 Invidious JSON 資料",
- "Import YouTube subscriptions": "匯入 YouTube/OPML 訂閱",
+ "Import YouTube subscriptions": "匯入 YouTube CSV 或 OPML 訂閱",
"Import FreeTube subscriptions (.db)": "匯入 FreeTube 訂閱 (.db)",
"Import NewPipe subscriptions (.json)": "匯入 NewPipe 訂閱 (.json)",
"Import NewPipe data (.zip)": "匯入 NewPipe 資料 (.zip)",
@@ -470,5 +470,14 @@
"playlist_button_add_items": "新增影片",
"channel_tab_podcasts_label": "Podcast",
"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 已被管理員停用。"
}
diff --git a/spec/helpers/vtt/builder_spec.cr b/spec/helpers/vtt/builder_spec.cr
index 7b543ddc..dc1f4613 100644
--- a/spec/helpers/vtt/builder_spec.cr
+++ b/spec/helpers/vtt/builder_spec.cr
@@ -1,34 +1,27 @@
require "../../spec_helper.cr"
-MockLines = [
- {
- "start_time": Time::Span.new(seconds: 1),
- "end_time": Time::Span.new(seconds: 2),
- "text": "Line 1",
- },
-
- {
- "start_time": Time::Span.new(seconds: 2),
- "end_time": Time::Span.new(seconds: 3),
- "text": "Line 2",
- },
-]
+MockLines = ["Line 1", "Line 2"]
+MockLinesWithEscapableCharacter = ["<Line 1>", "&Line 2>", '\u200E' + "Line\u200F 3", "\u00A0Line 4"]
Spectator.describe "WebVTT::Builder" do
it "correctly builds a vtt file" do
result = WebVTT.build do |vtt|
- MockLines.each do |line|
- vtt.cue(line["start_time"], line["end_time"], line["text"])
+ 2.times do |i|
+ vtt.cue(
+ Time::Span.new(seconds: i),
+ Time::Span.new(seconds: i + 1),
+ MockLines[i]
+ )
end
end
expect(result).to eq([
"WEBVTT",
"",
- "00:00:01.000 --> 00:00:02.000",
+ "00:00:00.000 --> 00:00:01.000",
"Line 1",
"",
- "00:00:02.000 --> 00:00:03.000",
+ "00:00:01.000 --> 00:00:02.000",
"Line 2",
"",
"",
@@ -42,8 +35,12 @@ Spectator.describe "WebVTT::Builder" do
}
result = WebVTT.build(setting_fields) do |vtt|
- MockLines.each do |line|
- vtt.cue(line["start_time"], line["end_time"], line["text"])
+ 2.times do |i|
+ vtt.cue(
+ Time::Span.new(seconds: i),
+ Time::Span.new(seconds: i + 1),
+ MockLines[i]
+ )
end
end
@@ -52,13 +49,39 @@ Spectator.describe "WebVTT::Builder" do
"Kind: captions",
"Language: en",
"",
- "00:00:01.000 --> 00:00:02.000",
+ "00:00:00.000 --> 00:00:01.000",
"Line 1",
"",
- "00:00:02.000 --> 00:00:03.000",
+ "00:00:01.000 --> 00:00:02.000",
"Line 2",
"",
"",
].join('\n'))
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
diff --git a/spec/i18next_plurals_spec.cr b/spec/i18next_plurals_spec.cr
index dab97710..dcd0f5ec 100644
--- a/spec/i18next_plurals_spec.cr
+++ b/spec/i18next_plurals_spec.cr
@@ -17,7 +17,7 @@ FORM_TESTS = {
"cy" => I18next::Plurals::PluralForms::Special_Welsh,
"fr" => I18next::Plurals::PluralForms::Special_French_Portuguese,
"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,
"gd" => I18next::Plurals::PluralForms::Special_Scottish_Gaelic,
"he" => I18next::Plurals::PluralForms::Special_Hebrew,
@@ -33,7 +33,8 @@ FORM_TESTS = {
"mt" => I18next::Plurals::PluralForms::Special_Maltese,
"or" => I18next::Plurals::PluralForms::Special_Odia,
"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,
"ro" => I18next::Plurals::PluralForms::Special_Romanian,
"sk" => I18next::Plurals::PluralForms::Special_Czech_Slovak,
@@ -77,10 +78,10 @@ SUFFIX_TESTS = {
{num: 10, suffix: "_plural"},
],
"es" => [
- {num: 0, suffix: "_plural"},
- {num: 1, suffix: ""},
- {num: 10, suffix: "_plural"},
- {num: 6_000_000, suffix: "_plural"},
+ {num: 0, suffix: "_2"},
+ {num: 1, suffix: "_0"},
+ {num: 10, suffix: "_2"},
+ {num: 6_000_000, suffix: "_1"},
],
"fr" => [
{num: 0, suffix: "_0"},
diff --git a/src/invidious/channels/about.cr b/src/invidious/channels/about.cr
index 8b60a728..b5a27667 100644
--- a/src/invidious/channels/about.cr
+++ b/src/invidious/channels/about.cr
@@ -14,6 +14,7 @@ record AboutChannel,
is_family_friendly : Bool,
allowed_regions : Array(String),
tabs : Array(String),
+ tags : Array(String),
verified : Bool
def get_about_info(ucid, locale) : AboutChannel
@@ -43,6 +44,8 @@ def get_about_info(ucid, locale) : AboutChannel
auto_generated = true
end
+ tags = [] of String
+
if auto_generated
author = initdata["header"]["interactiveTabbedHeaderRenderer"]["title"]["simpleText"].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"]?
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
author = initdata["metadata"]["channelMetadataRenderer"]["title"].as_s
author_url = initdata["metadata"]["channelMetadataRenderer"]["channelUrl"].as_s
@@ -70,6 +79,7 @@ def get_about_info(ucid, locale) : AboutChannel
# end
description_node = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]?
+ tags = initdata.dig?("microformat", "microformatDataRenderer", "tags").try &.as_a.map(&.as_s) || [] of String
end
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"]
auto_generated = (
(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
@@ -155,6 +166,7 @@ def get_about_info(ucid, locale) : AboutChannel
is_family_friendly: is_family_friendly,
allowed_regions: allowed_regions,
tabs: tab_names,
+ tags: tags,
verified: author_verified || false,
)
end
diff --git a/src/invidious/database/statistics.cr b/src/invidious/database/statistics.cr
index 1df549e2..9e4963fd 100644
--- a/src/invidious/database/statistics.cr
+++ b/src/invidious/database/statistics.cr
@@ -15,7 +15,7 @@ module Invidious::Database::Statistics
PG_DB.query_one(request, as: Int64)
end
- def count_users_active_1m : Int64
+ def count_users_active_6m : Int64
request = <<-SQL
SELECT count(*) FROM users
WHERE CURRENT_TIMESTAMP - updated < '6 months'
@@ -24,7 +24,7 @@ module Invidious::Database::Statistics
PG_DB.query_one(request, as: Int64)
end
- def count_users_active_6m : Int64
+ def count_users_active_1m : Int64
request = <<-SQL
SELECT count(*) FROM users
WHERE CURRENT_TIMESTAMP - updated < '1 month'
diff --git a/src/invidious/frontend/comments_reddit.cr b/src/invidious/frontend/comments_reddit.cr
index b5647bae..4dda683e 100644
--- a/src/invidious/frontend/comments_reddit.cr
+++ b/src/invidious/frontend/comments_reddit.cr
@@ -33,7 +33,7 @@ module Invidious::Frontend::Comments
<a href="javascript:void(0)" data-onclick="toggle_parent">[ − ]</a>
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b>
#{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>
</p>
<div>
diff --git a/src/invidious/frontend/comments_youtube.cr b/src/invidious/frontend/comments_youtube.cr
index ecc0bc1b..aecac87f 100644
--- a/src/invidious/frontend/comments_youtube.cr
+++ b/src/invidious/frontend/comments_youtube.cr
@@ -107,6 +107,36 @@ module Invidious::Frontend::Comments
</div>
END_HTML
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
end
end
diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr
index d140a858..174f620d 100644
--- a/src/invidious/helpers/handlers.cr
+++ b/src/invidious/helpers/handlers.cr
@@ -142,63 +142,8 @@ class APIHandler < Kemal::Handler
exclude ["/api/v1/auth/notifications"], "POST"
def call(env)
- return call_next env unless 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
-
- 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
+ env.response.headers["Access-Control-Allow-Origin"] = "*" if only_match?(env)
+ call_next env
end
end
diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr
index 6dc9860e..6add0237 100644
--- a/src/invidious/helpers/helpers.cr
+++ b/src/invidious/helpers/helpers.cr
@@ -78,15 +78,6 @@ def create_notification_stream(env, topics, connection_channel)
video.published = published
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 "data: #{response.to_json}"
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|
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 "data: #{response.to_json}"
env.response.puts
@@ -155,15 +137,6 @@ def create_notification_stream(env, topics, connection_channel)
video.published = Time.unix(published)
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 "data: #{response.to_json}"
env.response.puts
diff --git a/src/invidious/helpers/i18n.cr b/src/invidious/helpers/i18n.cr
index 76e477a4..23a1aafc 100644
--- a/src/invidious/helpers/i18n.cr
+++ b/src/invidious/helpers/i18n.cr
@@ -78,7 +78,7 @@ def load_all_locales
return locales
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
# that key as the text, so this is more or less transparent to the user.
if !LOCALES["en-US"].has_key?(key)
@@ -101,10 +101,12 @@ def translate(locale : String?, key : String, text : String | Nil = nil) : Strin
match_length = 0
raw_data.as_h.each do |hash_key, value|
- if md = text.try &.match(/#{hash_key}/)
- if md[0].size >= match_length
- translation = value.as_s
- match_length = md[0].size
+ if text.is_a?(String)
+ if md = text.try &.match(/#{hash_key}/)
+ if md[0].size >= match_length
+ translation = value.as_s
+ match_length = md[0].size
+ end
end
end
end
@@ -114,8 +116,13 @@ def translate(locale : String?, key : String, text : String | Nil = nil) : Strin
raise "Invalid translation \"#{raw_data}\""
end
- if text
+ if text.is_a?(String)
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
return translation
diff --git a/src/invidious/helpers/i18next.cr b/src/invidious/helpers/i18next.cr
index 252af6b9..9f4077e1 100644
--- a/src/invidious/helpers/i18next.cr
+++ b/src/invidious/helpers/i18next.cr
@@ -47,19 +47,19 @@ module I18next::Plurals
private PLURAL_SETS = {
PluralForms::Single_gt_one => [
- "ach", "ak", "am", "arn", "br", "fil", "gun", "ln", "mfe", "mg",
- "mi", "oc", "pt", "tg", "tl", "ti", "tr", "uz", "wa",
+ "ach", "ak", "am", "arn", "br", "fa", "fil", "gun", "ln", "mfe", "mg",
+ "mi", "oc", "pt-PT", "tg", "tl", "ti", "tr", "uz", "wa",
],
PluralForms::Single_not_one => [
"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",
"nah", "nap", "nb", "ne", "nl", "nn", "no", "nso", "pa", "pap", "pms",
"ps", "rm", "sco", "se", "si", "so", "son", "sq", "sv", "sw",
"ta", "te", "tk", "ur", "yo",
],
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",
],
PluralForms::Dual_Slavic => [
@@ -90,11 +90,13 @@ module I18next::Plurals
"sk" => PluralForms::Special_Czech_Slovak,
"sl" => PluralForms::Special_Slovenian,
# Mixed v3/v4 rules
- "fr" => PluralForms::Special_French_Portuguese,
- "hr" => PluralForms::Special_Hungarian_Serbian,
- "it" => PluralForms::Special_Spanish_Italian,
- "pt-BR" => PluralForms::Special_French_Portuguese,
- "sr" => PluralForms::Special_Hungarian_Serbian,
+ "es" => PluralForms::Special_Spanish_Italian,
+ "fr" => PluralForms::Special_French_Portuguese,
+ "hr" => PluralForms::Special_Hungarian_Serbian,
+ "it" => PluralForms::Special_Spanish_Italian,
+ "pt" => PluralForms::Special_French_Portuguese,
+ "pt" => PluralForms::Special_French_Portuguese,
+ "sr" => PluralForms::Special_Hungarian_Serbian,
}
# These are the v1 and v2 compatible suffixes.
@@ -165,7 +167,7 @@ module I18next::Plurals
def get_plural_form(locale : String) : PluralForms
# 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]
end
diff --git a/src/invidious/helpers/json_filter.cr b/src/invidious/helpers/json_filter.cr
deleted file mode 100644
index 3f4080ba..00000000
--- a/src/invidious/helpers/json_filter.cr
+++ /dev/null
@@ -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
diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr
index a006d602..e438e3b9 100644
--- a/src/invidious/helpers/utils.cr
+++ b/src/invidious/helpers/utils.cr
@@ -262,7 +262,7 @@ def get_referer(env, fallback = "/", unroll = true)
end
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
referer = fallback
diff --git a/src/invidious/helpers/webvtt.cr b/src/invidious/helpers/webvtt.cr
index 56f761ed..260d250f 100644
--- a/src/invidious/helpers/webvtt.cr
+++ b/src/invidious/helpers/webvtt.cr
@@ -4,13 +4,23 @@
module WebVTT
# A WebVTT builder generates WebVTT files
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)
end
# Writes an vtt cue with the specified time stamp and contents
def cue(start_time : Time::Span, end_time : Time::Span, text : String)
timestamp(start_time, end_time)
- @io << text
+ @io << self.escape(text)
@io << "\n\n"
end
@@ -29,6 +39,10 @@ module WebVTT
@io << '.' << timestamp.milliseconds.to_s.rjust(3, '0')
end
+ private def escape(text : String) : String
+ return text.gsub(ESCAPE_SUBSTITUTIONS)
+ end
+
def document(setting_fields : Hash(String, String)? = nil, &)
@io << "WEBVTT\n"
diff --git a/src/invidious/jobs/statistics_refresh_job.cr b/src/invidious/jobs/statistics_refresh_job.cr
index 72d1ce88..66c91ad5 100644
--- a/src/invidious/jobs/statistics_refresh_job.cr
+++ b/src/invidious/jobs/statistics_refresh_job.cr
@@ -56,8 +56,8 @@ class Invidious::Jobs::StatisticsRefreshJob < Invidious::Jobs::BaseJob
users = STATISTICS.dig("usage", "users").as(Hash(String, Int64))
users["total"] = Invidious::Database::Statistics.count_users_total
- users["activeHalfyear"] = Invidious::Database::Statistics.count_users_active_1m
- users["activeMonth"] = Invidious::Database::Statistics.count_users_active_6m
+ users["activeHalfyear"] = Invidious::Database::Statistics.count_users_active_6m
+ users["activeMonth"] = Invidious::Database::Statistics.count_users_active_1m
STATISTICS["metadata"] = {
"updatedAt" => Time.utc.to_unix,
diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr
index 67018660..1d409c79 100644
--- a/src/invidious/routes/api/v1/channels.cr
+++ b/src/invidious/routes/api/v1/channels.cr
@@ -90,6 +90,7 @@ module Invidious::Routes::API::V1::Channels
json.field "allowedRegions", channel.allowed_regions
json.field "tabs", channel.tabs
+ json.field "tags", channel.tags
json.field "authorVerified", channel.verified
json.field "latestVideos" do
diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr
index b42ecd1a..12942906 100644
--- a/src/invidious/routes/api/v1/misc.cr
+++ b/src/invidious/routes/api/v1/misc.cr
@@ -191,6 +191,8 @@ module Invidious::Routes::API::V1::Misc
json.object do
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 "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 "pageType", pageType
end
diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr
index 9fb283c2..2922b060 100644
--- a/src/invidious/routes/api/v1/search.cr
+++ b/src/invidious/routes/api/v1/search.cr
@@ -32,11 +32,14 @@ module Invidious::Routes::API::V1::Search
begin
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
+ 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]
JSON.build do |json|
diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr
index 1017ac9d..9281f4dd 100644
--- a/src/invidious/routes/api/v1/videos.cr
+++ b/src/invidious/routes/api/v1/videos.cr
@@ -363,4 +363,47 @@ module Invidious::Routes::API::V1::Videos
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
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index 3d935f0a..aabe8dfc 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -275,6 +275,12 @@ module Invidious::Routes::Watch
return error_template(400, "Invalid clip ID") if response["error"]?
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}"
else
return error_template(404, "The requested clip doesn't exist")
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index d6bd991c..ba05da19 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -235,6 +235,7 @@ module Invidious::Routing
get "/api/v1/captions/:id", {{namespace}}::Videos, :captions
get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations
get "/api/v1/comments/:id", {{namespace}}::Videos, :comments
+ get "/api/v1/clips/:id", {{namespace}}::Videos, :clips
# Feeds
get "/api/v1/trending", {{namespace}}::Feeds, :trending
diff --git a/src/invidious/videos/clip.cr b/src/invidious/videos/clip.cr
new file mode 100644
index 00000000..29c57182
--- /dev/null
+++ b/src/invidious/videos/clip.cr
@@ -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
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 77520dbe..75fe4a36 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -142,8 +142,9 @@ end
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.")
- # 2AMBCgIQBg is a workaround for streaming URLs that returns a 403.
- response = YoutubeAPI.player(video_id: id, params: "2AMBCgIQBg", client_config: client_config)
+ # CgIIAdgDAQ%3D%3D is a workaround for streaming URLs that returns a 403.
+ # https://github.com/LuanRT/YouTube.js/pull/624
+ response = YoutubeAPI.player(video_id: id, params: "CgIIAdgDAQ%3D%3D", client_config: client_config)
playability_status = response["playabilityStatus"]["status"]
LOGGER.debug("try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}.")
diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr
index 031b46da..6d227cfc 100644
--- a/src/invidious/views/components/item.ecr
+++ b/src/invidious/views/components/item.ecr
@@ -82,11 +82,19 @@
</div>
<div class="video-card-row flexible">
- <div class="flex-left"><a href="/channel/<%= item.ucid %>">
- <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>
- </a></div>
+ <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) %>
+ <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
+ </p>
+ </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>
<% when Category %>
<% else %>
@@ -160,11 +168,19 @@
</div>
<div class="video-card-row flexible">
- <div class="flex-left"><a href="/channel/<%= item.ucid %>">
- <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>
- </a></div>
+ <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) %>
+ <%- if author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
+ </p>
+ </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" %>
</div>
diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr
index 77265679..9904b4fc 100644
--- a/src/invidious/views/template.ecr
+++ b/src/invidious/views/template.ecr
@@ -1,5 +1,9 @@
+<%
+ locale = env.get("preferences").as(Preferences).locale
+ dark_mode = env.get("preferences").as(Preferences).dark_mode
+%>
<!DOCTYPE html>
-<html lang="<%= env.get("preferences").as(Preferences).locale %>">
+<html lang="<%= locale %>">
<head>
<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/ionicons.min.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>
</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">
- <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-u-1 pure-u-md-2-24"></div>
- <div class="pure-u-1 pure-u-md-20-24" id="contents">
+ <div class="pure-u-1 pure-u-xl-20-24" id="contents">
<div class="pure-g navbar h-box">
<% if navbar_search %>
<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">
<% if env.get? "user" %>
<div class="pure-u-1-4">
- <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
- <% if env.get("preferences").as(Preferences).dark_mode == "dark" %>
+ <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
+ <% if dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i>
<% else %>
<i class="icon ion-ios-moon"></i>
@@ -81,8 +80,8 @@
</div>
<% else %>
<div class="pure-u-1-3">
- <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
- <% if env.get("preferences").as(Preferences).dark_mode == "dark" %>
+ <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
+ <% if dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i>
<% else %>
<i class="icon ion-ios-moon"></i>
@@ -156,7 +155,6 @@
</footer>
</div>
- <div class="pure-u-1 pure-u-md-2-24"></div>
</div>
<script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script>
<script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script>
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index 07474896..7a1cf2c3 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -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}")
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_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param)
end
@@ -346,7 +346,7 @@ we're going to need to do it here in order to allow for translations.
<h5 class="pure-g">
<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>
<% else %>
<b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b>
diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr
index 56325cf7..0e72957e 100644
--- a/src/invidious/yt_backend/extractors.cr
+++ b/src/invidious/yt_backend/extractors.cr
@@ -822,9 +822,9 @@ module HelperExtractors
end
# 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)
- return container.dig("navigationEndpoint", "browseEndpoint", "browseId").as_s
+ return container.dig?("navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
end
end
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index a5e621f2..9e0631f6 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -7,17 +7,18 @@ module YoutubeAPI
private DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
- private ANDROID_APP_VERSION = "18.20.38"
- # github.com/TeamNewPipe/NewPipeExtractor/blob/943b7c033bb9d07ead63ddab4441c287653e4384/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java#L1308
- private ANDROID_USER_AGENT = "com.google.android.youtube/18.20.38 (Linux; U; Android 12; US) gzip"
+ # For Android versions, see https://en.wikipedia.org/wiki/Android_version_history
+ private ANDROID_APP_VERSION = "19.09.36"
+ private ANDROID_USER_AGENT = "com.google.android.youtube/19.09.36 (Linux; U; Android 12; US) gzip"
private ANDROID_SDK_VERSION = 31_i64
private ANDROID_VERSION = "12"
- private IOS_APP_VERSION = "18.21.3"
- # github.com/TeamNewPipe/NewPipeExtractor/blob/943b7c033bb9d07ead63ddab4441c287653e4384/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java#L1330
- 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
- private IOS_VERSION = "15.6.0.19G71"
+ # For Apple device names, see https://gist.github.com/adamawolf/3048717
+ # 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.09.3"
+ private IOS_USER_AGENT = "com.google.ios.youtube/19.09.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"
@@ -45,7 +46,7 @@ module YoutubeAPI
ClientType::Web => {
name: "WEB",
name_proto: "1",
- version: "2.20230602.01.00",
+ version: "2.20240304.00.00",
api_key: DEFAULT_API_KEY,
screen: "WATCH_FULL_SCREEN",
os_name: "Windows",
@@ -55,7 +56,7 @@ module YoutubeAPI
ClientType::WebEmbeddedPlayer => {
name: "WEB_EMBEDDED_PLAYER",
name_proto: "56",
- version: "1.20220803.01.00",
+ version: "1.20240303.00.00",
api_key: DEFAULT_API_KEY,
screen: "EMBED",
os_name: "Windows",
@@ -65,7 +66,7 @@ module YoutubeAPI
ClientType::WebMobile => {
name: "MWEB",
name_proto: "2",
- version: "2.20230531.05.00",
+ version: "2.20240304.08.00",
api_key: DEFAULT_API_KEY,
os_name: "Android",
os_version: ANDROID_VERSION,
@@ -74,7 +75,7 @@ module YoutubeAPI
ClientType::WebScreenEmbed => {
name: "WEB",
name_proto: "1",
- version: "2.20220804.00.00",
+ version: "2.20240304.00.00",
api_key: DEFAULT_API_KEY,
screen: "EMBED",
os_name: "Windows",
@@ -99,7 +100,7 @@ module YoutubeAPI
name: "ANDROID_EMBEDDED_PLAYER",
name_proto: "55",
version: ANDROID_APP_VERSION,
- api_key: DEFAULT_API_KEY,
+ api_key: "AIzaSyCjc_pVEDi4qsv5MtC2dMXzpIaDoRFLsxw",
},
ClientType::AndroidScreenEmbed => {
name: "ANDROID",
@@ -143,9 +144,9 @@ module YoutubeAPI
ClientType::IOSMusic => {
name: "IOS_MUSIC",
name_proto: "26",
- version: "5.21",
+ version: "6.42",
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_model: "iPhone14,5",
os_name: "iPhone",
@@ -158,7 +159,7 @@ module YoutubeAPI
ClientType::TvHtml5 => {
name: "TVHTML5",
name_proto: "7",
- version: "7.20220325",
+ version: "7.20240304.10.00",
api_key: DEFAULT_API_KEY,
},
ClientType::TvHtml5ScreenEmbed => {