summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--README.md4
-rw-r--r--assets/js/player.js97
-rw-r--r--assets/js/themes.js21
-rw-r--r--docker/Dockerfile2
-rw-r--r--locales/ar.json31
-rw-r--r--locales/da.json269
-rw-r--r--locales/de.json45
-rw-r--r--locales/en-US.json35
-rw-r--r--locales/es.json31
-rw-r--r--locales/fa.json97
-rw-r--r--locales/fr.json35
-rw-r--r--locales/hr.json67
-rw-r--r--locales/hu-HU.json428
-rw-r--r--locales/id.json31
-rw-r--r--locales/ja.json18
-rw-r--r--locales/nb-NO.json32
-rw-r--r--locales/nl.json52
-rw-r--r--locales/sq.json1
-rw-r--r--locales/sr.json792
-rw-r--r--locales/sr_Cyrl.json495
-rw-r--r--locales/tr.json31
-rw-r--r--locales/zh-CN.json2
-rw-r--r--locales/zh-TW.json31
-rw-r--r--spec/helpers_spec.cr19
-rw-r--r--src/invidious.cr36
-rw-r--r--src/invidious/comments.cr41
-rw-r--r--src/invidious/config.cr1
-rw-r--r--src/invidious/helpers/errors.cr18
-rw-r--r--src/invidious/helpers/helpers.cr2
-rw-r--r--src/invidious/helpers/i18n.cr138
-rw-r--r--src/invidious/helpers/serialized_yt_data.cr16
-rw-r--r--src/invidious/helpers/utils.cr13
-rw-r--r--src/invidious/routes/api/v1/authenticated.cr18
-rw-r--r--src/invidious/routes/api/v1/channels.cr12
-rw-r--r--src/invidious/routes/api/v1/feeds.cr4
-rw-r--r--src/invidious/routes/api/v1/misc.cr6
-rw-r--r--src/invidious/routes/api/v1/search.cr4
-rw-r--r--src/invidious/routes/api/v1/videos.cr10
-rw-r--r--src/invidious/routes/channels.cr4
-rw-r--r--src/invidious/routes/embed.cr4
-rw-r--r--src/invidious/routes/feeds.cr18
-rw-r--r--src/invidious/routes/login.cr6
-rw-r--r--src/invidious/routes/misc.cr6
-rw-r--r--src/invidious/routes/playlists.cr22
-rw-r--r--src/invidious/routes/preferences.cr11
-rw-r--r--src/invidious/routes/search.cr6
-rw-r--r--src/invidious/routes/video_playback.cr4
-rw-r--r--src/invidious/routes/watch.cr2
-rw-r--r--src/invidious/trending.cr10
-rw-r--r--src/invidious/user/preferences.cr1
-rw-r--r--src/invidious/videos.cr124
-rw-r--r--src/invidious/views/components/player.ecr6
-rw-r--r--src/invidious/views/components/video-context-buttons.ecr2
-rw-r--r--src/invidious/views/feeds/playlists.ecr4
-rw-r--r--src/invidious/views/playlist.ecr2
-rw-r--r--src/invidious/views/preferences.ecr13
-rw-r--r--src/invidious/views/template.ecr6
-rw-r--r--src/invidious/views/watch.ecr10
-rw-r--r--src/invidious/yt_backend/extractors.cr22
60 files changed, 2208 insertions, 1068 deletions
diff --git a/Makefile b/Makefile
index 7271d9bd..b27db613 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@
RELEASE := 1
STATIC := 0
-DISABLE_LSQUIC := 0
+DISABLE_QUIC := 0
NO_DBG_SYMBOLS := 0
@@ -27,8 +27,8 @@ else
FLAGS += --debug
endif
-ifeq ($(DISABLE_LSQUIC), 1)
- FLAGS += -Ddisable_lsquic
+ifeq ($(DISABLE_QUIC), 1)
+ FLAGS += -Ddisable_quic
endif
@@ -108,7 +108,7 @@ help:
echo "RELEASE Make a release build (Default: 1)"
echo "STATIC Link librariess tatically (Default: 1)"
echo ""
- echo "DISABLE_LSQUIC Don't use lsquic (Default: 0)"
+ echo "DISABLE_QUIC Disable support for QUIC (Default: 0)"
echo "NO_DBG_SYMBOLS Strip debug symbols (Default: 0)"
diff --git a/README.md b/README.md
index 29b8397a..720da1b4 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,10 @@
<a href="https://web.libera.chat/?channel=#invidious">
<img alt="Libera.chat (IRC)" src="https://img.shields.io/badge/IRC%20%28Libera.chat%29-%23invidious-darkgreen">
</a>
+ <br>
+ <a rel="me" href="https://social.tchncs.de/@invidious">
+ <img alt="Mastodon: @invidious@social.tchncs.de" src="https://img.shields.io/badge/Mastodon-%40invidious%40social.tchncs.de-darkgreen">
+ </a>
</div>
diff --git a/assets/js/player.js b/assets/js/player.js
index a461c53d..5ff55eb3 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -38,6 +38,8 @@ embed_url.searchParams.delete('v');
short_url = location.origin + '/' + video_data.id + embed_url.search;
embed_url = location.origin + '/embed/' + video_data.id + embed_url.search;
+var save_player_pos_key = "save_player_pos";
+
var shareOptions = {
socials: ['fbFeed', 'tw', 'reddit', 'email'],
@@ -57,6 +59,16 @@ videojs.Hls.xhr.beforeRequest = function(options) {
var player = videojs('player', options);
+const storage = (() => {
+ try {
+ if (localStorage.length !== -1) {
+ return localStorage;
+ }
+ } catch (e) {
+ console.info('No storage available: ' + e);
+ }
+ return undefined;
+})();
if (location.pathname.startsWith('/embed/')) {
player.overlay({
@@ -199,6 +211,32 @@ if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data.
player.getChild('bigPlayButton').hide();
}
+if (video_data.params.save_player_pos) {
+ const url = new URL(location);
+ const hasTimeParam = url.searchParams.has("t");
+ const remeberedTime = get_video_time();
+ let lastUpdated = 0;
+
+ if(!hasTimeParam) {
+ set_seconds_after_start(remeberedTime);
+ }
+
+ const updateTime = () => {
+ const raw = player.currentTime();
+ const time = Math.floor(raw);
+
+ if(lastUpdated !== time && raw <= video_data.length_seconds - 15) {
+ save_video_time(time);
+ lastUpdated = time;
+ }
+ };
+
+ player.on("timeupdate", updateTime);
+}
+else {
+ remove_all_video_times();
+}
+
if (video_data.params.autoplay) {
var bpb = player.getChild('bigPlayButton');
bpb.hide();
@@ -330,6 +368,65 @@ function skip_seconds(delta) {
player.currentTime(newTime);
}
+function set_seconds_after_start(delta) {
+ const start = video_data.params.video_start;
+ player.currentTime(start + delta);
+}
+
+function save_video_time(seconds) {
+ const videoId = video_data.id;
+ const all_video_times = get_all_video_times();
+
+ all_video_times[videoId] = seconds;
+
+ set_all_video_times(all_video_times);
+}
+
+function get_video_time() {
+ try {
+ const videoId = video_data.id;
+ const all_video_times = get_all_video_times();
+ const timestamp = all_video_times[videoId];
+
+ return timestamp || 0;
+ }
+ catch {
+ return 0;
+ }
+}
+
+function set_all_video_times(times) {
+ if (storage) {
+ if (times) {
+ try {
+ storage.setItem(save_player_pos_key, JSON.stringify(times));
+ } catch (e) {
+ console.debug('set_all_video_times: ' + e);
+ }
+ } else {
+ storage.removeItem(save_player_pos_key);
+ }
+ }
+}
+
+function get_all_video_times() {
+ if (storage) {
+ const raw = storage.getItem(save_player_pos_key);
+ if (raw !== null) {
+ try {
+ return JSON.parse(raw);
+ } catch (e) {
+ console.debug('get_all_video_times: ' + e);
+ }
+ }
+ }
+ return {};
+}
+
+function remove_all_video_times() {
+ set_all_video_times(null);
+}
+
function set_time_percent(percent) {
const duration = player.duration();
const newTime = duration * (percent / 100);
diff --git a/assets/js/themes.js b/assets/js/themes.js
index 543b849e..470f10bf 100644
--- a/assets/js/themes.js
+++ b/assets/js/themes.js
@@ -11,7 +11,9 @@ toggle_theme.addEventListener('click', function () {
xhr.open('GET', url, true);
set_mode(dark_mode);
- window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light');
+ try {
+ window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light');
+ } catch {}
xhr.send();
});
@@ -23,9 +25,12 @@ window.addEventListener('storage', function (e) {
});
window.addEventListener('DOMContentLoaded', function () {
- window.localStorage.setItem('dark_mode', document.getElementById('dark_mode_pref').textContent);
- // Update localStorage if dark mode preference changed on preferences page
- update_mode(window.localStorage.dark_mode);
+ const dark_mode = document.getElementById('dark_mode_pref').textContent;
+ try {
+ // Update localStorage if dark mode preference changed on preferences page
+ window.localStorage.setItem('dark_mode', dark_mode);
+ } catch {}
+ update_mode(dark_mode);
});
@@ -37,9 +42,11 @@ lightScheme.addListener(scheme_switch);
function scheme_switch (e) {
// ignore this method if we have a preference set
- if (localStorage.getItem('dark_mode')) {
- return;
- }
+ try {
+ if (localStorage.getItem('dark_mode')) {
+ return;
+ }
+ } catch {}
if (e.matches) {
if (e.media.includes("dark")) {
set_mode(true);
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 5f1c0a11..c73821da 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -18,7 +18,7 @@ COPY ./.git/ ./.git/
RUN crystal spec --warnings all \
--link-flags "-lxml2 -llzma"
-RUN if [ ${release} == 1 ] ; then \
+RUN if [ "${release}" == 1 ] ; then \
crystal build ./src/invidious.cr \
--release \
--static --warnings all \
diff --git a/locales/ar.json b/locales/ar.json
index f2d457b6..3e9d3ac6 100644
--- a/locales/ar.json
+++ b/locales/ar.json
@@ -433,5 +433,34 @@
"footer_documentation": "التوثيق",
"footer_donate_page": "تبرّع",
"preferences_region_label": "بلد المحتوى:. ",
- "preferences_quality_dash_label": "جودة الفيديو المفضلة dash: "
+ "preferences_quality_dash_label": "جودة فيديو DASH المفضلة: ",
+ "preferences_quality_option_dash": "DASH (جودة تكييفية)",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_medium": "متوسطة",
+ "preferences_quality_option_small": "صغيرة",
+ "preferences_quality_dash_option_auto": "تلقائي",
+ "preferences_quality_dash_option_best": "الأفضل",
+ "preferences_quality_dash_option_worst": "أسوأ",
+ "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",
+ "purchased": "تم شراؤها",
+ "none": "لاشيء",
+ "videoinfo_started_streaming_x_ago": "بدأ البث منذ `x`",
+ "videoinfo_watch_on_youTube": "مشاهدة على يوتيوب",
+ "videoinfo_youTube_embed_link": "مضمن",
+ "videoinfo_invidious_embed_link": "رابط مضمن",
+ "user_created_playlists": "'x' إنشاء قوائم التشغيل",
+ "user_saved_playlists": "قوائم التشغيل المحفوظة 'x'",
+ "Video unavailable": "الفيديو غير متوفر",
+ "360": "360°",
+ "download_subtitles": "ترجمات - 'x' (.vtt)",
+ "invidious": "الخيالي",
+ "preferences_save_player_pos_label": "احفظ وقت الفيديو الحالي: "
}
diff --git a/locales/da.json b/locales/da.json
index 72e282bb..c08984d9 100644
--- a/locales/da.json
+++ b/locales/da.json
@@ -11,7 +11,7 @@
"([^.,0-9]|^)1([^.,0-9]|$)": "`x` afspilningslister",
"": "`x` afspilningslister"
},
- "LIVE": "DIREKTE",
+ "LIVE": "LIVE",
"Shared `x` ago": "Delt for `x` siden",
"Unsubscribe": "Opsig abonnement",
"Subscribe": "Abonner",
@@ -44,7 +44,7 @@
"Export data as JSON": "Exporter data som JSON",
"Delete account?": "Slet konto?",
"History": "Historik",
- "An alternative front-end to YouTube": "En alternativ forside til YouTube",
+ "An alternative front-end to YouTube": "Et alternativt front-end til YouTube",
"JavaScript license information": "JavaScript licens information",
"source": "kilde",
"Log in": "Log på",
@@ -72,7 +72,7 @@
"preferences_volume_label": "Lydstyrke: ",
"preferences_comments_label": "Standard kommentarer: ",
"youtube": "YouTube",
- "reddit": "reddit",
+ "reddit": "Reddit",
"preferences_captions_label": "Standard undertekster: ",
"Fallback captions: ": "Alternative undertekster: ",
"preferences_related_videos_label": "Vis relaterede videoer: ",
@@ -150,8 +150,8 @@
"Unlisted": "Skjult",
"Private": "Privat",
"View all playlists": "Vis alle afspilningslister",
- "Updated `x` ago": "Opdateret for 'x' siden",
- "Delete playlist `x`?": "Opdateret `x` siden",
+ "Updated `x` ago": "Opdateret for `x` siden",
+ "Delete playlist `x`?": "Fjern spilleliste `x`?",
"Delete playlist": "Slet afspilningsliste",
"Create playlist": "Opret afspilningsliste",
"Title": "Titel",
@@ -176,7 +176,7 @@
},
"Premieres in `x`": "Har premiere om `x`",
"Premieres `x`": "Har premiere om `x`",
- "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Det ser ud til at du har JavaScript slået fra. Klik her for at se kommentarer, vær opmærksom på at de kan tage længere om at loade.",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Det ser ud til at du har JavaScript slået fra. Klik her for at se kommentarer, vær opmærksom på at de kan tage længere om at indlæse.",
"View YouTube comments": "Vis YouTube kommentarer",
"View more comments on Reddit": "Se flere kommentarer på Reddit",
"View `x` comments": {
@@ -190,17 +190,17 @@
"Quota exceeded, try again in a few hours": "Kvota overskredet, prøv igen om et par timer",
"Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Login fejlet, tjek at totrinsbekræftelse (Authenticator eller SMS) er slået til.",
"Invalid TFA code": "Ugyldig TFA kode",
- "Login failed. This may be because two-factor authentication is not turned on for your account.": "Login fejlede. Det er måske på grund af to-faktor-autentisering ikk er slået til for din konto.",
+ "Login failed. This may be because two-factor authentication is not turned on for your account.": "Login fejlede. Dette kan skyldes, at to-faktor autentificering ikke er aktiveret for din konto.",
"Wrong answer": "Forkert svar",
"Erroneous CAPTCHA": "Fejlagtig CAPTCHA",
- "CAPTCHA is a required field": "CAPTCHA er et krævet felt",
+ "CAPTCHA is a required field": "CAPTCHA er et obligatorisk felt",
"User ID is a required field": "Bruger ID er et krævet felt",
- "Password is a required field": "Adgangskode er et krævet felt",
+ "Password is a required field": "Adgangskode er et obligatorisk felt",
"Wrong username or password": "Forkert brugernavn eller adgangskode",
- "Please sign in using 'Log in with Google'": "Venligst tjek in via 'Log in med Google'",
- "Password cannot be empty": "Adgangskode kan ikke være tom",
+ "Please sign in using 'Log in with Google'": "Log ind via 'Log ind med Google'",
+ "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 in",
+ "Please log in": "Venligst log ind",
"channel:`x`": "kanal: 'x'",
"Deleted or invalid channel": "Slettet eller invalid kanal",
"This channel does not exist.": "Denne kanal eksisterer ikke.",
@@ -219,5 +219,248 @@
"Could not create mix.": "Kunne ikke skabe blanding.",
"Empty playlist": "Tom playliste",
"Not a playlist.": "Ikke en playliste.",
- "Playlist does not exist.": "Playlist eksisterer ikke."
+ "Playlist does not exist.": "Playlist eksisterer ikke.",
+ "Esperanto": "Esperanto",
+ "Czech": "Tjekkisk",
+ "Danish": "Dansk",
+ "Community": "Samfund",
+ "Afrikaans": "Afrikansk",
+ "Portuguese": "Portugisisk",
+ "Ukrainian": "Ukrainsk",
+ "Fallback comments: ": "Fallback kommentarer: ",
+ "Popular": "Populær",
+ "footer_donate_page": "Doner",
+ "Gujarati": "Gujarati",
+ "Punjabi": "Punjabi",
+ "Sundanese": "Sundanesisk",
+ "Urdu": "Urdu",
+ "preferences_region_label": "Indhold land: ",
+ "Hidden field \"challenge\" is a required field": "Det skjulte felt \"challenge\" er et påkrævet felt",
+ "Albanian": "Albansk",
+ "preferences_quality_dash_label": "Fortrukket DASH video kvalitet: ",
+ "live": "Direkte",
+ "Lao": "Lao-tse",
+ "Filipino": "Filippinsk",
+ "Greek": "Græsk",
+ "Kurdish": "Kurdisk",
+ "Malay": "Malaysisk",
+ "Romanian": "Rumænsk",
+ "Somali": "Somalisk",
+ "`x` years": {
+ "": "`x`år",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` år"
+ },
+ "preferences_locale_label": "Sprog: ",
+ "News": "Nyheder",
+ "permalink": "permalink",
+ "date": "Upload dato",
+ "features": "Funktioner",
+ "filter": "Filter",
+ "Khmer": "Khmer",
+ "Finnish": "Finsk",
+ "week": "Denne uge",
+ "Korean": "Koreansk",
+ "Telugu": "Telugu",
+ "Malayalam": "Malayalam",
+ "View as playlist": "Se som spilleliste",
+ "Hungarian": "Ungarsk",
+ "Welsh": "Walisisk",
+ "subtitles": "Undertekster/CC",
+ "Bosnian": "Bosnisk",
+ "Yiddish": "Jiddisch",
+ "Belarusian": "Belarussisk",
+ "today": "I dag",
+ "Shona": "Shona",
+ "Slovenian": "Slovensk",
+ "Gaming": "Gaming",
+ "Bangla": "Bengali",
+ "Swahili": "Swahili",
+ "`x` marked it with a ❤": "`x`markeret med et ❤",
+ "Kyrgyz": "Kirgisisk",
+ "Turkish": "Tyrkisk",
+ "adminprefs_modified_source_code_url_label": "URL-adresse til modificeret kildekodelager",
+ "Switch Invidious Instance": "Skift Invidious instans",
+ "Samoan": "Samoansk",
+ "Spanish": "Spansk",
+ "%A %B %-d, %Y": "%A %B %-d, %Y",
+ "footer_documentation": "Dokumentation",
+ "Pashto": "Pashto",
+ "footer_modfied_source_code": "Modificeret Kildekode",
+ "Released under the AGPLv3 on Github.": "Udgivet under AGPLv3 på Github.",
+ "Tajik": "Tadsjikisk",
+ "`x` months": {
+ "": "`x`måneder",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x`måned"
+ },
+ "month": "Denne måned",
+ "Hebrew": "Hebraisk",
+ "Kannada": "Kannada",
+ "`x` weeks": {
+ "": "`x`uger",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x`uge"
+ },
+ "Current version: ": "Nuværende version: ",
+ "Amharic": "Amharisk",
+ "`x` days": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x`dag",
+ "": "`x`dage"
+ },
+ "Swedish": "Svensk",
+ "Corsican": "Korsikansk",
+ "movie": "Film",
+ "Could not pull trending pages.": "Kunne ikke hente trending sider.",
+ "English": "Engelsk",
+ "hd": "HD",
+ "Hausa": "Islandsk",
+ "year": "Dette år",
+ "Japanese": "Japansk",
+ "content_type": "Type",
+ "Icelandic": "Islandsk",
+ "Basque": "Baskisk",
+ "rating": "Bedømmelse",
+ "Yoruba": "Yoruba",
+ "Erroneous token": "Fejlagtig token",
+ "Videos": "Videoer",
+ "show": "Vis",
+ "Luxembourgish": "Luxemboursk",
+ "Vietnamese": "Vietnamesisk",
+ "Latvian": "Lettisk",
+ "Indonesian": "Indonesisk",
+ "duration": "Varighed",
+ "footer_original_source_code": "Original kildekode",
+ "Search": "Søg",
+ "Serbian": "Serbisk",
+ "Armenian": "Armensk",
+ "Bulgarian": "Bulgarsk",
+ "French": "Fransk",
+ "Burmese": "Burmesisk",
+ "Macedonian": "Makedonsk",
+ "Southern Sotho": "Sydlige Sotho",
+ "About": "Omkring",
+ "Malagasy": "Madagaskiske",
+ "Rating: ": "Bedømmelse: ",
+ "Movies": "Film",
+ "YouTube comment permalink": "Youtube kommentarer permalink",
+ "location": "Lokation",
+ "hdr": "HDR",
+ "Cebuano": "Cebuano (Sugbuanon)",
+ "Nyanja": "Nyanja",
+ "Chinese (Simplified)": "Kinesisk (forenklet)",
+ "Chinese (Traditional)": "Kinesisk (traditionelt)",
+ "Dutch": "Hollandsk",
+ "Estonian": "Estisk",
+ "preferences_automatic_instance_redirect_label": "Automatisk eksempel omdirigering (Fallback til redirect.invidious.io): ",
+ "Nepali": "Nepalesisk",
+ "Norwegian Bokmål": "Norsk Bokmål",
+ "`x` hours": {
+ "": "`x` timer",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x`time"
+ },
+ "`x` minutes": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` minut",
+ "": "`x` minuter"
+ },
+ "`x` seconds": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` sekund",
+ "": "`x` sekunder"
+ },
+ "(edited)": "(ændret)",
+ "preferences_show_nick_label": "Vis kælenavn på toppen: ",
+ "Galician": "Galisisk",
+ "German": "Tysk",
+ "Maori": "Maori",
+ "Slovak": "Slovakisk",
+ "relevance": "Relevans",
+ "hour": "Sidste time",
+ "playlist": "Spilleliste",
+ "long": "Lang (> 20 minutter)",
+ "creative_commons": "Creative Commons",
+ "Marathi": "Marathi",
+ "Sindhi": "Sindhi",
+ "preferences_category_misc": "Diverse indstillinger",
+ "Erroneous challenge": "Fejlagtig udfordring",
+ "Hindi": "Hindi",
+ "Igbo": "Igbo",
+ "Javanese": "Javanesisk",
+ "Kazakh": "Kasabhisk",
+ "Latin": "Latinsk",
+ "Lithuanian": "Lituaisk",
+ "Mongolian": "Mongolsk",
+ "Spanish (Latin America)": "Spansk (Latinamerika)",
+ "Uzbek": "Usbekisk",
+ "Western Frisian": "Vestfrisisk",
+ "Top": "Top",
+ "Music": "Musik",
+ "views": "Antal visninger",
+ "sort": "Sorter efter",
+ "Zulu": "Zulu",
+ "Invidious Private Feed for `x`": "Invidious Privat Feed til `x`",
+ "English (auto-generated)": "Engelsk (autogenereret)",
+ "Arabic": "Arabisk",
+ "Croatian": "Kroatisk",
+ "Hawaiian": "Hawaiiansk",
+ "Maltese": "Maltesisk",
+ "Polish": "Polsk",
+ "Russian": "Russisk",
+ "Download": "Hent",
+ "Download as: ": "Hent som: ",
+ "Playlists": "Spillelister",
+ "next_steps_error_message_refresh": "Opdater",
+ "next_steps_error_message_go_to_youtube": "Gå til Youtube",
+ "footer_source_code": "Kildekode",
+ "Tamil": "Tamil",
+ "Xhosa": "Xhosa",
+ "next_steps_error_message": "Efter det burde du prøve at: ",
+ "Sinhala": "Singalesisk (Sinhala)",
+ "Thai": "Thai",
+ "Broken? Try another Invidious Instance": "I stykker? Prøv en anden Invidious instans",
+ "No such user": "Brugeren findes ikke",
+ "Token is expired, please try again": "Token er udløbet, prøv igen",
+ "Catalan": "Catalansk",
+ "Haitian Creole": "Haitiansk",
+ "Irish": "Irsk",
+ "Persian": "Persisk",
+ "Scottish Gaelic": "Skotsk Gælisk",
+ "Default": "Standard",
+ "Video mode": "Videotilstand",
+ "short": "Kort (< 4 minutter)",
+ "Hidden field \"token\" is a required field": "Det skjulte felt \"token\" er et påkrævet felt",
+ "Azerbaijani": "Aserbajdsjansk",
+ "Georgian": "Georgisk",
+ "Italian": "Italiensk",
+ "Audio mode": "Lydtilstand",
+ "video": "Video",
+ "channel": "Kanal",
+ "3d": "3D",
+ "4k": "4K",
+ "Hmong": "Hmong",
+ "preferences_quality_option_medium": "Medium",
+ "preferences_quality_option_small": "Lille",
+ "preferences_quality_dash_option_best": "Bedste",
+ "preferences_quality_dash_option_worst": "Værste",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "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_144p": "144p",
+ "invidious": "Invidious",
+ "purchased": "Købt",
+ "360": "360°",
+ "none": "ingen",
+ "videoinfo_started_streaming_x_ago": "Streamen blev startet for `x`siden",
+ "videoinfo_watch_on_youTube": "Se på YouTube",
+ "videoinfo_youTube_embed_link": "Integrer",
+ "videoinfo_invidious_embed_link": "Integrer Link",
+ "download_subtitles": "Undertekster - `x`(.vtt)",
+ "user_created_playlists": "`x`opretede spillelister",
+ "user_saved_playlists": "´x`gemte spillelister",
+ "Video unavailable": "Video ikke tilgængelig",
+ "preferences_save_player_pos_label": "Gem den nuværende videotid: ",
+ "preferences_quality_dash_option_auto": "Auto",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_option_dash": "DASH (adaptiv kvalitet)",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_240p": "240p"
}
diff --git a/locales/de.json b/locales/de.json
index 7ec11fef..9ab5b211 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -19,8 +19,8 @@
"View playlist on YouTube": "Wiedergabeliste auf YouTube anzeigen",
"newest": "neueste",
"oldest": "älteste",
- "popular": "beliebt",
- "last": "letzte",
+ "popular": "beliebteste",
+ "last": "neueste",
"Next page": "Nächste Seite",
"Previous page": "Vorherige Seite",
"Clear watch history?": "Verlauf löschen?",
@@ -72,7 +72,7 @@
"preferences_volume_label": "Wiedergabelautstärke: ",
"preferences_comments_label": "Standardkommentare: ",
"youtube": "YouTube",
- "reddit": "reddit",
+ "reddit": "Reddit",
"preferences_captions_label": "Standarduntertitel: ",
"Fallback captions: ": "Ersatzuntertitel: ",
"preferences_related_videos_label": "Ähnliche Videos anzeigen? ",
@@ -84,7 +84,7 @@
"Dark mode: ": "Nachtmodus: ",
"preferences_dark_mode_label": "Modus: ",
"dark": "Nachtmodus",
- "light": "heller Modus",
+ "light": "hell",
"preferences_thin_mode_label": "Schlanker Modus: ",
"preferences_category_misc": "Sonstige Einstellungen",
"preferences_automatic_instance_redirect_label": "Automatische Instanzweiterleitung (über redirect.invidious.io): ",
@@ -149,7 +149,7 @@
"Source available here.": "Quellcode verfügbar hier.",
"View JavaScript license information.": "Javascript Lizenzinformationen anzeigen.",
"View privacy policy.": "Datenschutzerklärung einsehen.",
- "Trending": "Trending",
+ "Trending": "Angesagt",
"Public": "Öffentlich",
"Unlisted": "Nicht aufgeführt",
"Private": "Privat",
@@ -422,7 +422,7 @@
"filter": "Filtern",
"Current version: ": "Aktuelle Version: ",
"next_steps_error_message": "Danach folgendes versuchen: ",
- "next_steps_error_message_refresh": "Neuladen",
+ "next_steps_error_message_refresh": "Aktualisieren",
"next_steps_error_message_go_to_youtube": "Zu YouTube gehen",
"footer_donate_page": "Spende",
"long": "Lang (> 20 Minuten)",
@@ -431,5 +431,36 @@
"footer_documentation": "Dokumentation",
"footer_source_code": "Quellcode",
"adminprefs_modified_source_code_url_label": "URL zum Repositorie des modifizierten Quellcodes",
- "short": "Kurz (< 4 Minuten)"
+ "short": "Kurz (< 4 Minuten)",
+ "preferences_region_label": "Land der Inhalte: ",
+ "preferences_quality_option_dash": "DASH (automatische Qualität)",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_medium": "Mittel",
+ "preferences_quality_option_small": "Niedrig",
+ "preferences_quality_dash_option_auto": "Auto",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "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",
+ "invidious": "Invidious",
+ "videoinfo_invidious_embed_link": "Link zum Einbetten",
+ "download_subtitles": "Untertitel - `x` (.vtt)",
+ "Video unavailable": "Video nicht verfügbar",
+ "user_created_playlists": "`x` Wiedergabelisten erstellt",
+ "user_saved_playlists": "`x` Wiedergabelisten gespeichert",
+ "preferences_save_player_pos_label": "Aktuelle Position speichern: ",
+ "360": "360°",
+ "preferences_quality_dash_option_best": "Höchste",
+ "preferences_quality_dash_option_worst": "Niedrigste",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "videoinfo_youTube_embed_link": "Eingebettet",
+ "purchased": "Gekauft",
+ "none": "keine",
+ "videoinfo_started_streaming_x_ago": "Stream begann vor `x`",
+ "videoinfo_watch_on_youTube": "Auf YouTube ansehen",
+ "preferences_quality_dash_label": "Bevorzugte DASH-Videoqualität: "
}
diff --git a/locales/en-US.json b/locales/en-US.json
index 036c22f4..94aac89e 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -69,11 +69,28 @@
"preferences_local_label": "Proxy videos: ",
"preferences_speed_label": "Default speed: ",
"preferences_quality_label": "Preferred video quality: ",
- "preferences_quality_dash_label": "Preferred dash video quality: ",
+ "preferences_quality_option_dash": "DASH (adaptative quality)",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_medium": "Medium",
+ "preferences_quality_option_small": "Small",
+ "preferences_quality_dash_label": "Preferred DASH video quality: ",
+ "preferences_quality_dash_option_auto": "Auto",
+ "preferences_quality_dash_option_best": "Best",
+ "preferences_quality_dash_option_worst": "Worst",
+ "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",
"preferences_volume_label": "Player volume: ",
"preferences_comments_label": "Default comments: ",
"youtube": "YouTube",
"reddit": "Reddit",
+ "invidious": "Invidious",
"preferences_captions_label": "Default captions: ",
"Fallback captions: ": "Fallback captions: ",
"preferences_related_videos_label": "Show related videos: ",
@@ -89,7 +106,7 @@
"light": "light",
"preferences_thin_mode_label": "Thin mode: ",
"preferences_category_misc": "Miscellaneous preferences",
- "preferences_automatic_instance_redirect_label": "Automaticatic instance redirection (fallback to redirect.invidious.io): ",
+ "preferences_automatic_instance_redirect_label": "Automatic instance redirection (fallback to redirect.invidious.io): ",
"preferences_category_subscription": "Subscription preferences",
"preferences_annotations_subscribed_label": "Show annotations by default for subscribed channels? ",
"Redirect homepage to feed: ": "Redirect homepage to feed: ",
@@ -423,6 +440,8 @@
"4k": "4K",
"location": "Location",
"hdr": "HDR",
+ "purchased" : "Purchased",
+ "360" : "360°",
"filter": "Filter",
"Current version: ": "Current version: ",
"next_steps_error_message": "After which you should try to: ",
@@ -433,5 +452,15 @@
"footer_source_code": "Source code",
"footer_original_source_code": "Original source code",
"footer_modfied_source_code": "Modified Source code",
- "adminprefs_modified_source_code_url_label": "URL to modified source code repository"
+ "adminprefs_modified_source_code_url_label": "URL to modified source code repository",
+ "none": "none",
+ "videoinfo_started_streaming_x_ago": "Started streaming `x` ago",
+ "videoinfo_watch_on_youTube": "Watch on YouTube",
+ "videoinfo_youTube_embed_link": "Embed",
+ "videoinfo_invidious_embed_link": "Embed Link",
+ "download_subtitles": "Subtitles - `x` (.vtt)",
+ "user_created_playlists": "`x` created playlists",
+ "user_saved_playlists": "`x` saved playlists",
+ "Video unavailable": "Video unavailable",
+ "preferences_save_player_pos_label": "Save the current video time: "
}
diff --git a/locales/es.json b/locales/es.json
index 1403a731..9f876ccb 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -433,5 +433,34 @@
"footer_modfied_source_code": "Código fuente modificado",
"footer_donate_page": "Donar",
"preferences_region_label": "País del contenido: ",
- "preferences_quality_dash_label": "Calidad de vídeo DASH preferida: "
+ "preferences_quality_dash_label": "Calidad de vídeo DASH preferida: ",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_medium": "Intermedia",
+ "preferences_quality_dash_option_auto": "Automática",
+ "none": "ninguno",
+ "videoinfo_started_streaming_x_ago": "Comenzó difusión hace `x`",
+ "download_subtitles": "Subtítulos- `x` (.vtt)",
+ "user_created_playlists": "`x` listas de reproducción creadas",
+ "user_saved_playlists": "`x` listas de reproducción guardadas",
+ "Video unavailable": "Vídeo no disponible",
+ "videoinfo_youTube_embed_link": "Embeber",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "invidious": "Invidious",
+ "preferences_quality_dash_option_480p": "480p",
+ "preferences_quality_option_dash": "DASH (calidad adaptativa)",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_quality_dash_option_360p": "360p",
+ "preferences_quality_dash_option_240p": "240p",
+ "preferences_quality_dash_option_144p": "144p",
+ "preferences_quality_option_small": "Pequeña",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_best": "La mejor",
+ "preferences_quality_dash_option_worst": "La peor",
+ "videoinfo_invidious_embed_link": "Enlace para Embeber",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "purchased": "Comprado",
+ "360": "360°",
+ "videoinfo_watch_on_youTube": "Ver en YouTube",
+ "preferences_save_player_pos_label": "Guardar el tiempo del vídeo actual: "
}
diff --git a/locales/fa.json b/locales/fa.json
index efee1cdb..1f723a63 100644
--- a/locales/fa.json
+++ b/locales/fa.json
@@ -1,51 +1,51 @@
{
"`x` subscribers": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` مشترکان",
- "": "`x` مشترکان"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` دنبال کننده",
+ "": "`x` دنبال کننده"
},
"`x` videos": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` ویدیو ها",
- "": "`x` ویدیو ها"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` ویدئو",
+ "": "`x` ویدئو"
},
"`x` playlists": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` سیاههٔ پخش",
- "": "`x` سیاهه‌های پخش"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` فهرست پخش",
+ "": "`x` فهرست پخش"
},
"LIVE": "زنده",
- "Shared `x` ago": "به اشتراک گذاشته شده `x` پیش",
+ "Shared `x` ago": "`x` پیش به اشتراک گذاشته شده",
"Unsubscribe": "لغو اشتراک",
"Subscribe": "مشترک شدن",
- "View channel on YouTube": "نمایش کانال در یوتیوب",
- "View playlist on YouTube": "نمایش سیاههٔ پخش در یوتیوب",
- "newest": "جدید تر",
- "oldest": "قدیمی تر",
+ "View channel on YouTube": "دیدن کانال در یوتیوب",
+ "View playlist on YouTube": "دیدن فهرست پخش در یوتیوب",
+ "newest": "تازه‌ترین",
+ "oldest": "کهنه‌ترین",
"popular": "محبوب",
"last": "آخرین",
"Next page": "صفحه بعد",
"Previous page": "صفحه قبل",
"Clear watch history?": "پاک کردن تاریخچه نمایش؟",
- "New password": "گذرواژه جدید",
- "New passwords must match": "گذارواژه های جدید باید باهم همخوانی داشته باشند",
+ "New password": "گذرواژه تازه",
+ "New passwords must match": "گذارواژه های تازه باید باهم همخوانی داشته باشند",
"Cannot change password for Google accounts": "نمیتوان گذرواژه را برای حساب های کاربری گوگل تغییر داد",
"Authorize token?": "توکن دسترسی؟",
"Authorize token for `x`?": "توکن دسترسی برای `x`؟",
"Yes": "بله",
"No": "خیر",
- "Import and Export Data": "وارد کردن و خارج کردن داده ها",
- "Import": "وارد کردن",
- "Import Invidious data": "وارد کردن داده Invidious",
- "Import YouTube subscriptions": "وارد کردن اشتراک های یوتیوب",
- "Import FreeTube subscriptions (.db)": "وارد کردن اشتراک های فری توب (.db)",
- "Import NewPipe subscriptions (.json)": "وارد کردن اشتراک های نیو پایپ (.json)",
- "Import NewPipe data (.zip)": "وارد کردن داده نیو پایپ (.zip)",
- "Export": "خارج کردن",
- "Export subscriptions as OPML": "خارج کردن اشتراک ها به عنوان OPML",
- "Export subscriptions as OPML (for NewPipe & FreeTube)": "خارج کردن اشتراک ها به عنوان OPML (برای فری توب و نیو پایپ)",
- "Export data as JSON": "خارج کردن داده ها به عنوان JSON",
+ "Import and Export Data": "درون‌برد و برون‌برد داده",
+ "Import": "درون‌برد",
+ "Import Invidious data": "درون‌برد داده اینویدیوس",
+ "Import YouTube subscriptions": "درون‌برد اشتراک‌های یوتیوب",
+ "Import FreeTube subscriptions (.db)": "درون‌برد اشتراک‌های فری‌تیوب (.db)",
+ "Import NewPipe subscriptions (.json)": "درون‌برد اشتراک‌های نیوپایپ (.json)",
+ "Import NewPipe data (.zip)": "درون‌برد داده نیوپایپ (.zip)",
+ "Export": "برون‌برد",
+ "Export subscriptions as OPML": "برون‌برد اشتراک‌ها در قالب OPML",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "برون‌برد اشتراک‌ها در قالب OPML (برای نیوپایپ و فری‌تیوب)",
+ "Export data as JSON": "برون‌برد داده در قالب JSON",
"Delete account?": "حذف حساب کاربری؟",
"History": "تاریخچه",
- "An alternative front-end to YouTube": "یک فرانت-اند جایگذین برای یوتیوب",
- "JavaScript license information": "اطلاعات مجوز جاوا اسکریپت",
+ "An alternative front-end to YouTube": "یک پیشانه جایگزین برای یوتیوب",
+ "JavaScript license information": "اطلاعات پروانه جاوااسکریپت",
"source": "منبع",
"Log in": "ورود",
"Log in/register": "ورود/ثبت نام",
@@ -53,15 +53,15 @@
"User ID": "شناسه کاربری",
"Password": "گذرواژه",
"Time (h:mm:ss):": "زمان (h:mm:ss):",
- "Text CAPTCHA": "متن CAPTCHA",
- "Image CAPTCHA": "تصویر CAPTCHA",
+ "Text CAPTCHA": "کپچای متنی",
+ "Image CAPTCHA": "کپچای تصویری",
"Sign In": "ورود",
"Register": "ثبت نام",
"E-mail": "ایمیل",
"Google verification code": "کد تایید گوگل",
"Preferences": "ترجیحات",
"preferences_category_player": "ترجیحات نمایش‌دهنده",
- "preferences_video_loop_label": "همیشه تکرار شنوده: ",
+ "preferences_video_loop_label": "همواره ویدئو را بازپخش کن ",
"preferences_autoplay_label": "نمایش خودکار: ",
"preferences_continue_label": "پخش بعدی به طور پیشفرض: ",
"preferences_continue_autoplay_label": "پخش خودکار ویدیو بعدی: ",
@@ -423,5 +423,42 @@
"Current version: ": "نسخه فعلی: ",
"next_steps_error_message": "اکنون بایستی یکی از این موارد را امتحان کنید: ",
"next_steps_error_message_refresh": "تازه‌سازی",
- "next_steps_error_message_go_to_youtube": "رفتن به یوتیوب"
+ "next_steps_error_message_go_to_youtube": "رفتن به یوتیوب",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_dash": "DASH (کیفیت قابل تطبیق)",
+ "preferences_quality_option_medium": "میانه",
+ "preferences_quality_option_small": "پایین",
+ "preferences_quality_dash_option_auto": "خودکار",
+ "preferences_quality_dash_option_best": "بهترین",
+ "preferences_quality_dash_option_worst": "بدترین",
+ "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",
+ "invidious": "اینویدیوس",
+ "360": "360°",
+ "footer_donate_page": "کمک مالی",
+ "footer_source_code": "کد منبع",
+ "footer_modfied_source_code": "کد منبع ویرایش شده",
+ "none": "هیچ‌کدام",
+ "videoinfo_started_streaming_x_ago": "پخش جریانی `x` پیش آغاز شد",
+ "videoinfo_watch_on_youTube": "تماشا در یوتیوب",
+ "videoinfo_youTube_embed_link": "توکار",
+ "videoinfo_invidious_embed_link": "پیوند توکار",
+ "download_subtitles": "زیرنویس‌ها - `x` (.vtt)",
+ "Video unavailable": "ویدئو دردسترس نیست",
+ "preferences_save_player_pos_label": "ذخیره زمان کنونی ویدئو: ",
+ "purchased": "خریداری شده",
+ "preferences_quality_dash_label": "کیفیت ترجیحی ویدئو DASH: ",
+ "preferences_region_label": "کشور محتوا: ",
+ "footer_documentation": "مستندات",
+ "footer_original_source_code": "کد منبع اصلی",
+ "long": "بلند (> 20 دقیقه)",
+ "adminprefs_modified_source_code_url_label": "URL مخزن کد منبع ویریش شده",
+ "short": "کوتاه (< 4 دقیقه)"
}
diff --git a/locales/fr.json b/locales/fr.json
index 00971576..5ebd6f70 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -87,7 +87,7 @@
"light": "clair",
"preferences_thin_mode_label": "Mode léger : ",
"preferences_category_misc": "Paramètres divers",
- "preferences_automatic_instance_redirect_label": "Redirection vers une autre instance automatique (via redirect.invidious.io) : ",
+ "preferences_automatic_instance_redirect_label": "Redirection automatique vers une autre instance (via redirect.invidious.io) : ",
"preferences_category_subscription": "Préférences des abonnements",
"preferences_annotations_subscribed_label": "Afficher les annotations par défaut sur les chaînes auxquelles vous êtes abonnés : ",
"Redirect homepage to feed: ": "Rediriger la page d'accueil vers la page d'abonnements : ",
@@ -424,7 +424,7 @@
"next_steps_error_message": "Vous pouvez essayer de : ",
"next_steps_error_message_refresh": "Rafraîchir la page",
"next_steps_error_message_go_to_youtube": "Aller sur YouTube",
- "preferences_quality_dash_label": "Qualité préférée de la vidéo du tableau de bord : ",
+ "preferences_quality_dash_label": "Qualité vidéo DASH préférée : ",
"footer_source_code": "Code source",
"preferences_region_label": "Pays du contenu : ",
"footer_donate_page": "Faire un don",
@@ -433,5 +433,34 @@
"long": "Longue (> 20 minutes)",
"adminprefs_modified_source_code_url_label": "URL du dépôt du code source modifié",
"footer_documentation": "Documentation",
- "footer_original_source_code": "Code source original"
+ "footer_original_source_code": "Code source original",
+ "preferences_quality_option_medium": "Moyenne",
+ "preferences_quality_option_small": "Petite",
+ "preferences_quality_dash_option_auto": "Auto",
+ "preferences_quality_dash_option_best": "La plus haute",
+ "preferences_quality_dash_option_worst": "La plus basse",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "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",
+ "invidious": "Invidious",
+ "360": "360°",
+ "none": "aucun",
+ "videoinfo_started_streaming_x_ago": "En stream depuis `x`",
+ "videoinfo_watch_on_youTube": "Regarder sur YouTube",
+ "videoinfo_youTube_embed_link": "Intégrer",
+ "purchased": "Acheter",
+ "videoinfo_invidious_embed_link": "Lien intégré",
+ "download_subtitles": "Sous-titres - `x` (.vtt)",
+ "user_saved_playlists": "`x` listes de lecture sauvegardées",
+ "Video unavailable": "Vidéo non disponible",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_dash": "DASH (qualité adaptative)",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "user_created_playlists": "`x` listes de lecture créées",
+ "preferences_save_player_pos_label": "Sauvegarder la durée actuelle de la vidéo : "
}
diff --git a/locales/hr.json b/locales/hr.json
index 9b5f9250..02c5d784 100644
--- a/locales/hr.json
+++ b/locales/hr.json
@@ -8,15 +8,15 @@
"": "`x` videa"
},
"`x` playlists": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` playliste",
- "": "`x` playliste"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` zbirka",
+ "": "`x` zbirke"
},
"LIVE": "UŽIVO",
"Shared `x` ago": "Dijeljeno prije `x`",
"Unsubscribe": "Odjavi pretplatu",
"Subscribe": "Pretplati se",
"View channel on YouTube": "Prikaži kanal na YouTubeu",
- "View playlist on YouTube": "Prikaži playlistu na YouTubeu",
+ "View playlist on YouTube": "Prikaži zbirku na YouTubeu",
"newest": "najnovije",
"oldest": "najstarije",
"popular": "popularni",
@@ -87,7 +87,7 @@
"light": "svijetlo",
"preferences_thin_mode_label": "Pojednostavljen prikaz: ",
"preferences_category_misc": "Razne postavke",
- "preferences_automatic_instance_redirect_label": "Automatsko preusmjeravanje instance (u krajnjem slučaju koristi redirect.invidious.io): ",
+ "preferences_automatic_instance_redirect_label": "Automatsko preusmjeravanje instance (u krajnjem slučaju će se koristiti redirect.invidious.io): ",
"preferences_category_subscription": "Postavke pretplata",
"preferences_annotations_subscribed_label": "Standardno prikaži napomene za pretplaćene kanale: ",
"Redirect homepage to feed: ": "Preusmjeri početnu stranicu na feed: ",
@@ -153,14 +153,14 @@
"Public": "Javno",
"Unlisted": "Nenavedeno",
"Private": "Privatno",
- "View all playlists": "Prikaži sve playliste",
+ "View all playlists": "Prikaži sve zbirke",
"Updated `x` ago": "Aktualizirano prije `x`",
- "Delete playlist `x`?": "Izbrisati playlistu `x`?",
- "Delete playlist": "Izbriši playlistu",
- "Create playlist": "Stvori playlistu",
+ "Delete playlist `x`?": "Izbrisati zbirku `x`?",
+ "Delete playlist": "Izbriši zbirku",
+ "Create playlist": "Stvori zbirku",
"Title": "Naslov",
- "Playlist privacy": "Privatnost playliste",
- "Editing playlist `x`": "Uređivanje playliste `x`",
+ "Playlist privacy": "Privatnost zbirke",
+ "Editing playlist `x`": "Uređivanje zbirke `x`",
"Show more": "Pokaži više",
"Show less": "Pokaži manje",
"Watch on YouTube": "Gledaj na YouTubeu",
@@ -224,9 +224,9 @@
"": "`x` bodova"
},
"Could not create mix.": "Neuspjelo stvaranje miksa.",
- "Empty playlist": "Prazna playlista",
- "Not a playlist.": "Nije playlista.",
- "Playlist does not exist.": "Playlista ne postoji.",
+ "Empty playlist": "Prazna zbirka",
+ "Not a playlist.": "Nije zbirka.",
+ "Playlist does not exist.": "Zbirka ne postoji.",
"Could not pull trending pages.": "Neuspjelo preuzimanje stranica u trendu.",
"Hidden field \"challenge\" is a required field": "Skriveno polje „izazov” je obavezno polje",
"Hidden field \"token\" is a required field": "Skriveno polje „token” je obavezno polje",
@@ -375,7 +375,7 @@
"About": "Informacije",
"Rating: ": "Ocjena: ",
"preferences_locale_label": "Jezik: ",
- "View as playlist": "Prikaži kao playlistu",
+ "View as playlist": "Prikaži kao zbirku",
"Default": "Standardno",
"Music": "Glazba",
"Gaming": "Videoigre",
@@ -383,7 +383,7 @@
"Movies": "Filmovi",
"Download": "Preuzmi",
"Download as: ": "Preuzmi kao: ",
- "%A %B %-d, %Y": "%A, %-d. %B %Y.",
+ "%A %B %-d, %Y": "%A, %-d. %B %Y",
"(edited)": "(uređeno)",
"YouTube comment permalink": "Stalna poveznica YouTube komentara",
"permalink": "stalna poveznica",
@@ -391,7 +391,7 @@
"Audio mode": "Audio modus",
"Video mode": "Videomodus",
"Videos": "Videa",
- "Playlists": "Playliste",
+ "Playlists": "Zbirke",
"Community": "Zajednica",
"relevance": "značaj",
"rating": "ocjena",
@@ -408,7 +408,7 @@
"year": "godina",
"video": "video",
"channel": "kanal",
- "playlist": "playlista",
+ "playlist": "Zbirka",
"movie": "film",
"show": "emisija",
"hd": "hd",
@@ -421,7 +421,7 @@
"hdr": "hdr",
"filter": "filtar",
"Current version: ": "Trenutačna verzija: ",
- "next_steps_error_message": "Nakon toga pokušaj sljedeće: ",
+ "next_steps_error_message": "Nakon toga bi trebali pokušati sljedeće: ",
"next_steps_error_message_refresh": "Aktualiziraj stranicu",
"next_steps_error_message_go_to_youtube": "Idi na YouTube",
"footer_donate_page": "Doniraj",
@@ -433,5 +433,34 @@
"footer_documentation": "Dokumentacija",
"footer_original_source_code": "Izvoran izvorni kod",
"preferences_region_label": "Zemlja sadržaja: ",
- "preferences_quality_dash_label": "Preferirana kvaliteta dash-videa: "
+ "preferences_quality_dash_label": "Preferirana DASH videokvaliteta: ",
+ "preferences_quality_option_dash": "DASH (adaptativna kvaliteta)",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_medium": "Srednja",
+ "preferences_quality_dash_option_worst": "Najgora",
+ "preferences_quality_dash_option_4320p": "4320 p",
+ "preferences_quality_dash_option_2160p": "2160 p",
+ "preferences_quality_dash_option_1440p": "1440 p",
+ "preferences_quality_dash_option_1080p": "1080 p",
+ "preferences_quality_dash_option_360p": "360 p",
+ "preferences_quality_dash_option_240p": "240 p",
+ "preferences_quality_dash_option_144p": "144 p",
+ "invidious": "Invidious",
+ "purchased": "Kupljeno",
+ "360": "360 °",
+ "none": "bez",
+ "videoinfo_youTube_embed_link": "Ugradi",
+ "user_created_playlists": "`x` stvorene zbirke",
+ "user_saved_playlists": "`x` spremljene zbirke",
+ "Video unavailable": "Video nedostupan",
+ "preferences_save_player_pos_label": "Spremi trenutačno vrijeme videa: ",
+ "videoinfo_watch_on_youTube": "Gledaj na YouTubeu",
+ "download_subtitles": "Podnaslovi - `x` (.vtt)",
+ "preferences_quality_dash_option_auto": "Automatska",
+ "preferences_quality_option_small": "Niska",
+ "preferences_quality_dash_option_best": "Najbolja",
+ "preferences_quality_dash_option_720p": "720 p",
+ "preferences_quality_dash_option_480p": "480 p",
+ "videoinfo_started_streaming_x_ago": "Započet prijenos prije `x`",
+ "videoinfo_invidious_embed_link": "Ugradi poveznicu"
}
diff --git a/locales/hu-HU.json b/locales/hu-HU.json
index 2e721a0d..14248e87 100644
--- a/locales/hu-HU.json
+++ b/locales/hu-HU.json
@@ -1,224 +1,234 @@
{
"`x` subscribers": {
- "": "`x` feliratkozó"
+ "": "`x` feliratkozó",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` feliratkozó"
},
"`x` videos": {
- "": "`x` videó"
+ "": "`x` videó",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` videó"
},
"`x` playlists": {
- "": "`x` playlist"
+ "": "`x` lejátszási lista",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` lejátszási lista"
},
"LIVE": "ÉLŐ",
- "Shared `x` ago": "`x` óta megosztva",
+ "Shared `x` ago": "`x` ezelőtt lett megosztva",
"Unsubscribe": "Leiratkozás",
"Subscribe": "Feliratkozás",
- "View channel on YouTube": "csatorna megtekintése a YouTube-on",
- "View playlist on YouTube": "lejátszási lista megtekintése a YouTube-on",
+ "View channel on YouTube": "Csatorna megnézése YouTube-on",
+ "View playlist on YouTube": "Lejátszási lista megnézése YouTube-on",
"newest": "legújabb",
"oldest": "legrégibb",
"popular": "népszerű",
"last": "utolsó",
"Next page": "Következő oldal",
"Previous page": "Előző oldal",
- "Clear watch history?": "Megtekintési napló törlése?",
+ "Clear watch history?": "Törölve legyen a megnézett videók listája?",
"New password": "Új jelszó",
- "New passwords must match": "Az új jelszavaknak egyezniük kell",
- "Cannot change password for Google accounts": "Google fiók jelszavát nem lehet megváltoztatni",
- "Authorize token?": "Token felhatalmazása?",
- "Authorize token for `x`?": "Token felhatalmazása `x`-ra?",
+ "New passwords must match": "Az új jelszavaknak egyezniük kell!",
+ "Cannot change password for Google accounts": "A Google-fiók jelszavát nem lehet megváltoztatni.",
+ "Authorize token?": "Engedélyezve legyen a token?",
+ "Authorize token for `x`?": "Engedélyezve legyen a token erre? „`x`”",
"Yes": "Igen",
"No": "Nem",
"Import and Export Data": "Adatok importálása és exportálása",
"Import": "Importálás",
- "Import Invidious data": "Invidious adatainak importálása",
- "Import YouTube subscriptions": "YouTube feliratkozások importálása",
- "Import FreeTube subscriptions (.db)": "FreeTube feliratkozások importálása (.db)",
- "Import NewPipe subscriptions (.json)": "NewPipe feliratkozások importálása (.json)",
+ "Import Invidious data": "Az Invidious adatainak importálása",
+ "Import YouTube subscriptions": "YouTube-feliratkozások importálása",
+ "Import FreeTube subscriptions (.db)": "FreeTube-feliratkozások importálása (.db)",
+ "Import NewPipe subscriptions (.json)": "NewPipe-feliratkozások importálása (.json)",
"Import NewPipe data (.zip)": "NewPipe adatainak importálása (.zip)",
"Export": "Exportálás",
"Export subscriptions as OPML": "Feliratkozások exportálása OPML-ként",
- "Export subscriptions as OPML (for NewPipe & FreeTube)": "Feliratkozások exportálása OPML-ként (NewPipe és FreeTube számára)",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "Feliratkozások exportálása OPML-ként (NewPipe-hoz és FreeTube-hoz)",
"Export data as JSON": "Adat exportálása JSON-ként",
- "Delete account?": "Fiók törlése?",
- "History": "Megtekintési napló",
+ "Delete account?": "Törlésre kerüljön a fiók?",
+ "History": "Megnézések naplója",
"An alternative front-end to YouTube": "Alternatív YouTube front-end",
- "JavaScript license information": "JavaScript licensz információ",
+ "JavaScript license information": "A JavaScript licencinformációja",
"source": "forrás",
"Log in": "Bejelentkezés",
"Log in/register": "Bejelentkezés/Regisztráció",
- "Log in with Google": "Bejelentkezés Google fiókkal",
- "User ID": "Felhasználó-ID",
+ "Log in with Google": "Bejelentkezés Google-fiókkal",
+ "User ID": "Felhasználói azonosító",
"Password": "Jelszó",
- "Time (h:mm:ss):": "Idő (h:mm:ss):",
- "Text CAPTCHA": "Szöveg-CAPTCHA",
- "Image CAPTCHA": "Kép-CAPTCHA",
+ "Time (h:mm:ss):": "A pontos idő (ó:pp:mm):",
+ "Text CAPTCHA": "Szöveges CAPTCHA kérése",
+ "Image CAPTCHA": "Kép CAPTCHA kérése",
"Sign In": "Bejelentkezés",
"Register": "Regisztráció",
- "E-mail": "E-mail",
- "Google verification code": "Google verifikációs kód",
+ "E-mail": "E-mail-cím",
+ "Google verification code": "A Google ellenőrző kódja",
"Preferences": "Beállítások",
- "preferences_category_player": "Lejátszó beállítások",
- "preferences_video_loop_label": "Mindig loop-ol: ",
+ "preferences_category_player": "Lejátszó beállításai",
+ "preferences_video_loop_label": "Videó állandó ismétlése: ",
"preferences_autoplay_label": "Automatikus lejátszás: ",
- "preferences_continue_label": "Következő lejátszása alapértelmezésben: ",
- "preferences_continue_autoplay_label": "Következő automatikus lejátszása: ",
- "preferences_listen_label": "Hallgatás alapértelmezésben: ",
- "preferences_local_label": "Videók proxyzása: ",
+ "preferences_continue_label": "A következő videót mindig automatikusan játssza le: ",
+ "preferences_continue_autoplay_label": "A következő videó automatikus lejátszása: ",
+ "preferences_listen_label": "Mindig csak a hangsáv lejátszása: ",
+ "preferences_local_label": "Videók proxyn keresztüli lejátszása: ",
"preferences_speed_label": "Alapértelmezett sebesség: ",
- "preferences_quality_label": "Kívánt video minőség: ",
+ "preferences_quality_label": "Videó minősége: ",
"preferences_volume_label": "Hangerő: ",
- "preferences_comments_label": "Alapértelmezett kommentek: ",
+ "preferences_comments_label": "Mindig innen legyenek betöltve a hozzászólások: ",
"youtube": "YouTube",
- "reddit": "reddit",
- "preferences_captions_label": "Alapértelmezett feliratok: ",
+ "reddit": "Reddit",
+ "preferences_captions_label": "Felirat nyelvének sorrendje: ",
"Fallback captions: ": "Másodlagos feliratok: ",
- "preferences_related_videos_label": "Hasonló videók mutatása: ",
- "preferences_annotations_label": "Szövegmagyarázatok mutatása alapértelmezésben: ",
- "preferences_extend_desc_label": "Automatikusan növelje meg a videó leírását",
- "preferences_vr_mode_label": "Interaktív 360° videók",
- "preferences_category_visual": "Kinézeti beállítások",
- "preferences_player_style_label": "Lejátszó stílusa: ",
- "Dark mode: ": "Sötét mód: ",
+ "preferences_related_videos_label": "Hasonló videók ajánlása: ",
+ "preferences_annotations_label": "Szövegmagyarázatok alapértelmezett mutatása: ",
+ "preferences_extend_desc_label": "A videó leírása automatikusan látható: ",
+ "preferences_vr_mode_label": "Interaktív, 360°-os videók ",
+ "preferences_category_visual": "Kinézet, elrendezés és régió beállításai",
+ "preferences_player_style_label": "Lejátszó kinézete: ",
+ "Dark mode: ": "Elsötétített mód: ",
"preferences_dark_mode_label": "Téma: ",
"dark": "sötét",
"light": "világos",
"preferences_thin_mode_label": "Vékony mód: ",
- "preferences_category_subscription": "Feliratkozási beállítások",
- "preferences_annotations_subscribed_label": "Szövegmagyarázatok mutatása alapértelmezésben feliratkozott csatornák esetében: ",
- "Redirect homepage to feed: ": "Kezdő oldal átirányitása a feed-re: ",
- "preferences_max_results_label": "Feed-ben mutatott videók száma: ",
- "preferences_sort_label": "Videók sorrendje: ",
- "published": "közzétéve",
- "published - reverse": "közzétéve - fordítva",
- "alphabetically": "ABC sorrend",
- "alphabetically - reverse": "ABC sorrend - fordítva",
- "channel name": "csatorna neve",
- "channel name - reverse": "csatorna neve - fordítva",
- "Only show latest video from channel: ": "Csak a legutolsó videó mutatása a csatornából: ",
- "Only show latest unwatched video from channel: ": "Csak a legutolsó nem megtekintett videó mutatása a csatornából: ",
- "preferences_unseen_only_label": "Csak a nem megtekintettek mutatása: ",
- "preferences_notifications_only_label": "Csak értesítések mutatása (ha van): ",
- "Enable web notifications": "Web értesítések bekapcsolása",
+ "preferences_category_subscription": "Feliratkozott tartalmak beállításai",
+ "preferences_annotations_subscribed_label": "A feliratkozott csatornák szövegmagyarázatainak alapértelmezett mutatása: ",
+ "Redirect homepage to feed: ": "Kezdőoldal átirányitása a feedre: ",
+ "preferences_max_results_label": "Feedben mutatott videók száma: ",
+ "preferences_sort_label": "Videók rendezése: ",
+ "published": "közzététel szerint",
+ "published - reverse": "közzététel szerint – fordított sorrendben",
+ "alphabetically": "ABC-sorrend szerint",
+ "alphabetically - reverse": "Fordított ABC-sorrend szerint",
+ "channel name": "csatorna neve szerint",
+ "channel name - reverse": "csatorna neve szerint – fordított sorrendben",
+ "Only show latest video from channel: ": "Csak a csatorna legújabb videójának mutatása: ",
+ "Only show latest unwatched video from channel: ": "Csak a csatorna legújabb, de még nem megnézett videójának mutatása: ",
+ "preferences_unseen_only_label": "A még nem megnézett videók mutatása: ",
+ "preferences_notifications_only_label": "Csak az értesítések mutatása (ha van): ",
+ "Enable web notifications": "Böngészőn belüli értesítések bekapcsolása",
"`x` uploaded a video": "`x` feltöltött egy videót",
- "`x` is live": "`x` élő",
- "preferences_category_data": "Adat beállítások",
- "Clear watch history": "Megtekintési napló törlése",
- "Import/export data": "Adat Import/Export",
- "Change password": "Jelszócsere",
+ "`x` is live": "`x` élőben közvetít",
+ "preferences_category_data": "Fiók beállításai és egyéb lehetőségek",
+ "Clear watch history": "Megnézett videók listájának törlése",
+ "Import/export data": "Adatok importálása vagy exportálása",
+ "Change password": "Jelszó megváltoztatása",
"Manage subscriptions": "Feliratkozások kezelése",
"Manage tokens": "Tokenek kezelése",
- "Watch history": "Megtekintési napló",
+ "Watch history": "Megnézett videók",
"Delete account": "Fiók törlése",
- "preferences_category_admin": "Adminisztrátor beállítások",
- "preferences_default_home_label": "Alapértelmezett oldal: ",
- "preferences_feed_menu_label": "Feed menü: ",
- "Top enabled: ": "Top lista engedélyezve: ",
+ "preferences_category_admin": "Adminisztrátorok beállításai",
+ "preferences_default_home_label": "Kezdőoldal: ",
+ "preferences_feed_menu_label": "Feed menü sorrendje: ",
+ "Top enabled: ": "Toplista engedélyezve: ",
"CAPTCHA enabled: ": "CAPTCHA engedélyezve: ",
"Login enabled: ": "Bejelentkezés engedélyezve: ",
- "Registration enabled: ": "Registztráció engedélyezve: ",
- "Report statistics: ": "Statisztikák gyűjtése: ",
+ "Registration enabled: ": "Regisztráció engedélyezve: ",
+ "Report statistics: ": "Statisztika jelentése: ",
"Save preferences": "Beállítások mentése",
- "Subscription manager": "Feliratkozás kezelő",
- "Token manager": "Token kezelő",
+ "Subscription manager": "Feliratkozások kezelője",
+ "Token manager": "Tokenek kezelője",
"Token": "Token",
"`x` subscriptions": {
- "": "`x` feliratkozás"
+ "": "`x` csatornára van feliratkozás",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` csatornára van feliratkozás"
},
"`x` tokens": {
- "": "`x` token"
+ "": "`x` token",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` token"
},
- "Import/export": "Import/export",
+ "Import/export": "Importálás/exportálás",
"unsubscribe": "leiratkozás",
"revoke": "visszavonás",
"Subscriptions": "Feliratkozások",
"`x` unseen notifications": {
- "": "`x` kimaradt érdesítés"
+ "": "`x` kimaradt értesítés",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` kimaradt értesítés"
},
- "search": "keresés",
+ "search": "Videó keresése",
"Log out": "Kijelentkezés",
- "Source available here.": "A forráskód itt érhető el.",
- "View JavaScript license information.": "JavaScript licensz inforkációk megtekintése.",
- "View privacy policy.": "Adatvédelmi irányelvek megtekintése.",
+ "Source available here.": "A forráskód itt érhető el",
+ "View JavaScript license information.": "JavaScript licencinformáció megnyitása",
+ "View privacy policy.": "Adatvédelmi szabályzat megnyitása",
"Trending": "Felkapott",
- "Public": "Nyilvános",
- "Unlisted": "Nem nyilvános",
- "Private": "Privát",
- "View all playlists": "Minden lejátszási lista megtekintése",
- "Updated `x` ago": "Frissitve: `x`",
- "Delete playlist `x`?": "`x` playlist törlése?",
+ "Public": "nyilvános",
+ "Unlisted": "nem nyilvános",
+ "Private": "magán",
+ "View all playlists": "Összes lejátszási lista megnézése",
+ "Updated `x` ago": "`x` ezelőtt lett frissítve",
+ "Delete playlist `x`?": "Törlésre kerüljön ez a lejátszási lista? „`x`”",
"Delete playlist": "Lejátszási lista törlése",
"Create playlist": "Lejátszási lista létrehozása",
- "Title": "Cím",
+ "Title": "Lejátszási lista címe",
"Playlist privacy": "Lejátszási lista láthatósága",
- "Editing playlist `x`": "`x` lista szerkesztése",
- "Show more": "Mutass többet",
- "Show less": "Mutass kevesebbet",
- "Watch on YouTube": "Megtekintés a YouTube-on",
- "Hide annotations": "Szövegmagyarázat elrejtése",
- "Show annotations": "Szövegmagyarázat mutatása",
+ "Editing playlist `x`": "„`x`” lejátszási lista szerkesztése",
+ "Show more": "Többi szöveg mutatása",
+ "Show less": "Kevesebb szöveg mutatása",
+ "Watch on YouTube": "Megnézés a YouTube-on",
+ "Hide annotations": "Megjegyzések elrejtése",
+ "Show annotations": "Megjegyzések mutatása",
"Genre: ": "Műfaj: ",
- "License: ": "Licensz: ",
+ "License: ": "Licenc: ",
"Family friendly? ": "Családbarát? ",
"Wilson score: ": "Wilson-pontszám: ",
- "Engagement: ": "elkötelezettség: ",
+ "Engagement: ": "Visszajelzési mutató: ",
"Whitelisted regions: ": "Engedélyezett régiók: ",
"Blacklisted regions: ": "Tiltott régiók: ",
- "Shared `x`": "Megosztva `x`",
+ "Shared `x`": "`x` osztották meg",
"`x` views": {
- "": "`x` megtekintés"
+ "": "`x` alkalommal nézték meg",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` alkalommal nézték meg"
},
- "Premieres in `x`": "premierel `x` múlva",
- "Premieres `x`": "`x`-t premierel",
- "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Úgy látszik, hogy a JavaScript ki van kapcsolva a böngésződben. Kattints ide hogy megtekintsd a kommenteket, de tudd, hogy így kicsit tovább tarthat a betöltés.",
- "View YouTube comments": "YouTube kommentek megtekintése",
- "View more comments on Reddit": "További kommentek megtekintése Redditen",
+ "Premieres in `x`": "`x` később lesz a premierje",
+ "Premieres `x`": "`x` lesz a premierje",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Helló! Úgy tűnik a JavaScript ki van kapcsolva a böngészőben. Ide kattintva lehet olvasni a hozzászólásokat, de a betöltésük így kicsit több időbe fog telni.",
+ "View YouTube comments": "YouTube-on lévő hozzászólások olvasása",
+ "View more comments on Reddit": "A többi hozzászólás olvasása Redditen",
"View `x` comments": {
- "": "`x` komment megtekintése"
+ "": "`x` hozzászólás olvasása",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` hozzászólás olvasása"
},
- "View Reddit comments": "Reddit kommentek megtekintése",
+ "View Reddit comments": "Redditen lévő hozzászólások olvasása",
"Hide replies": "Válaszok elrejtése",
"Show replies": "Válaszok mutatása",
- "Incorrect password": "Helytelen jelszó",
- "Quota exceeded, try again in a few hours": "Kvóta túllépve, próbálkozz pár órával később",
- "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Sikertelen bejelentkezés. Győződj meg róla, hogy a kétfaktoros hitelesítés (hitelesítő vagy SMS) engedélyezve van.",
- "Login failed. This may be because two-factor authentication is not turned on for your account.": "Sikertelen bejelentkezés. Győződj meg róla, hogy a kétfaktoros hitelesítés engedélyezve van.",
- "Wrong answer": "Rossz válasz",
- "Erroneous CAPTCHA": "Hibás CAPTCHA",
- "CAPTCHA is a required field": "A CAPTCHA kötelező",
- "User ID is a required field": "A felhasználó-ID kötelező",
- "Password is a required field": "A jelszó kötelező",
- "Wrong username or password": "Rossz felhasználónév vagy jelszó",
- "Please sign in using 'Log in with Google'": "Kérem, jelentkezzen be a \"Bejelentkezés Google-el\"",
- "Password cannot be empty": "A jelszó nem lehet üres",
- "Password cannot be longer than 55 characters": "A jelszó nem lehet hosszabb 55 karakternél",
- "Please log in": "Kérem lépjen be",
- "Invidious Private Feed for `x`": "`x` Invidious privát feed-je",
- "channel:`x`": "`x` csatorna",
- "Deleted or invalid channel": "Törölt vagy nemlétező csatorna",
- "This channel does not exist.": "Ez a csatorna nem létezik.",
- "Could not get channel info.": "Nem sikerült lekérni a csatorna adatokat.",
- "Could not fetch comments": "Nem sikerült lekérni a kommenteket",
+ "Incorrect password": "A jelszó nem megfelelő",
+ "Quota exceeded, try again in a few hours": "A kvótát meghaladták. Néhány órával később próbáld meg újból betölteni.",
+ "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Nem sikerült bejelentkezni. A kétlépcsős (hitelesítő vagy szöveges üzenet általi) hitelesítésnek bekapcsolva kell lennie.",
+ "Login failed. This may be because two-factor authentication is not turned on for your account.": "Nem sikerült bejelentkezni. Ennek oka lehet, hogy a kétlépcsős hitelesítés nincs bekapcsolva a fiók beállításaiban.",
+ "Wrong answer": "Nem jól válaszoltál.",
+ "Erroneous CAPTCHA": "A CAPTCHA hibás.",
+ "CAPTCHA is a required field": "A CAPTCHA-mezőt ki kell tölteni.",
+ "User ID is a required field": "A felhasználói azonosítót meg kell adni!",
+ "Password is a required field": "Meg kell adni egy jelszót.",
+ "Wrong username or password": "Vagy a felhasználói név, vagy pedig a jelszó nem megfelelő.",
+ "Please sign in using 'Log in with Google'": "A „Bejelentkezés Google-el” gombbal jelentkezz be!",
+ "Password cannot be empty": "A jelszót nem lehet kihagyni.",
+ "Password cannot be longer than 55 characters": "A jelszó nem lehet hosszabb 55 karakternél.",
+ "Please log in": "Kérjük, jelentkezz be!",
+ "Invidious Private Feed for `x`": "„`x`” Invidious magán feedje",
+ "channel:`x`": "`x` csatornája",
+ "Deleted or invalid channel": "A csatorna érvénytelen, vagy pedig törölve lett.",
+ "This channel does not exist.": "Nincs ilyen csatorna.",
+ "Could not get channel info.": "Nem lehetett betölteni a csatorna adatait.",
+ "Could not fetch comments": "Nem lehetett betölteni a hozzászólásokat.",
"View `x` replies": {
- "": "`x` válasz megtekintése"
+ "": "`x` válasz olvasása",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` válasz olvasása"
},
- "`x` ago": "`x` óta",
- "Load more": "További betöltése",
+ "`x` ago": "`x` ezelőtt",
+ "Load more": "Többi hozzászólás betöltése",
"`x` points": {
- "": "`x` pont"
+ "": "`x` pont",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` pont"
},
- "Could not create mix.": "Nem tudok mix-et készíteni.",
+ "Could not create mix.": "A válogatást nem lehetett elkészíteni.",
"Empty playlist": "Üres lejátszási lista",
- "Not a playlist.": "Nem lejátszási lista.",
+ "Not a playlist.": "Ez nem egy lejátszási lista.",
"Playlist does not exist.": "Nincs ilyen lejátszási lista.",
- "Could not pull trending pages.": "Nem sikerült lekérni a felkapott oldalt.",
- "Hidden field \"challenge\" is a required field": "A rejtett \"challenge\" mező kötelező",
- "Hidden field \"token\" is a required field": "A rejtett \"token\" mező kötelező",
+ "Could not pull trending pages.": "Nem lehetett betölteni a felkapott videók oldalát.",
+ "Hidden field \"challenge\" is a required field": "A rejtett „challenge” mezőt ki kell tölteni.",
+ "Hidden field \"token\" is a required field": "A rejtett „token” mezőt ki kell tölteni.",
"Erroneous challenge": "Hibás challenge",
"Erroneous token": "Hibás token",
"No such user": "Nincs ilyen felhasználó",
- "Token is expired, please try again": "Lejárt token, kérem próbáld újra",
+ "Token is expired, please try again": "A token lejárt. Kérjük, próbáld meg újból!",
"English": "angol",
- "English (auto-generated)": "angol (automatikusan generált)",
+ "English (auto-generated)": "angol (automatikusan létrehozott)",
"Afrikaans": "afrikaans",
"Albanian": "albán",
"Amharic": "amhara",
@@ -245,10 +255,10 @@
"Filipino": "filippínó",
"Finnish": "finn",
"French": "francia",
- "Galician": "galíciai",
+ "Galician": "galiciai",
"Georgian": "grúz",
"German": "német",
- "Greek": "görök",
+ "Greek": "görög",
"Gujarati": "gudzsaráti",
"Haitian Creole": "haiti kreol",
"Hausa": "hausza",
@@ -259,13 +269,13 @@
"Hungarian": "magyar",
"Icelandic": "izlandi",
"Igbo": "igbo",
- "Indonesian": "indonéziai",
+ "Indonesian": "indonéz",
"Irish": "ír",
"Italian": "olasz",
"Japanese": "japán",
"Javanese": "jávai",
"Kannada": "kannada",
- "Kazakh": "kazah",
+ "Kazakh": "kazak",
"Khmer": "khmer",
"Korean": "koreai",
"Kurdish": "kurd",
@@ -275,17 +285,17 @@
"Latvian": "lett",
"Lithuanian": "litván",
"Luxembourgish": "luxemburgi",
- "Macedonian": "macedóniai",
+ "Macedonian": "macedón",
"Malagasy": "madagaszkári",
"Malay": "maláj",
"Malayalam": "malajálam",
"Maltese": "máltai",
"Maori": "maori",
- "Marathi": "Maráthi",
+ "Marathi": "maráthi",
"Mongolian": "mongol",
"Nepali": "nepáli",
- "Norwegian Bokmål": "bokmål",
- "Nyanja": "nyánja",
+ "Norwegian Bokmål": "norvég (bokmål)",
+ "Nyanja": "njándzsa (csicseva)",
"Pashto": "pastu",
"Persian": "perzsa",
"Polish": "lengyel",
@@ -296,19 +306,19 @@
"Samoan": "szamoai",
"Scottish Gaelic": "skót gael",
"Serbian": "szerb",
- "Shona": "shona",
- "Sindhi": "szindhi",
+ "Shona": "sona",
+ "Sindhi": "szindi",
"Sinhala": "szingaléz",
"Slovak": "szlovák",
"Slovenian": "szlovén",
"Somali": "szomáliai",
- "Southern Sotho": "déli szothó",
+ "Southern Sotho": "déli szútú",
"Spanish": "spanyol",
- "Spanish (Latin America)": "spanyol (Latin-Amerika)",
+ "Spanish (Latin America)": "spanyol (latinamerikai)",
"Sundanese": "szunda",
"Swahili": "szuahéli",
- "Swedish": "svld",
- "Tajik": "tadzsik",
+ "Swedish": "svéd",
+ "Tajik": "tádzsik",
"Tamil": "tamil",
"Telugu": "telugu",
"Thai": "thai",
@@ -322,49 +332,135 @@
"Yoruba": "joruba",
"Zulu": "zulu",
"`x` years": {
- "": "`x` év"
+ "": "`x` évvel",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` évvel"
},
"`x` months": {
- "": "`x` hónap"
+ "": "x` hónappal",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` hónappal"
},
"`x` weeks": {
- "": "`x` hét"
+ "": "`x` héttel",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` héttel"
},
"`x` days": {
- "": "`x` nap"
+ "": "`x` nappal",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` nappal"
},
"`x` hours": {
- "": "`x` óra"
+ "": "`x` órával",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` órával"
},
"`x` minutes": {
- "": "`x` perc"
+ "": "`x` perccel",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` perccel"
},
"`x` seconds": {
- "": "`x` másodperc"
+ "": "`x` másodperccel",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` másodperccel"
},
"Fallback comments: ": "Másodlagos kommentek: ",
"Popular": "Népszerű",
- "Search": "Keresés",
+ "Search": "Keresési oldal",
"Top": "Top",
"About": "Leírás",
"Rating: ": "Besorolás: ",
"preferences_locale_label": "Nyelv: ",
- "View as playlist": "Megtekintés lejátszási listaként",
+ "View as playlist": "Megnézés lejátszási listában",
"Default": "Alapértelmezett",
- "Music": "Zene",
+ "Music": "Zenék",
"Gaming": "Játékok",
"News": "Hírek",
"Movies": "Filmek",
"Download": "Letöltés",
- "Download as: ": "Letöltés mint: ",
+ "Download as: ": "Letöltés másként: ",
"(edited)": "(szerkesztve)",
- "YouTube comment permalink": "YouTube komment permalink",
+ "YouTube comment permalink": "YouTube-hozzászólás permalinkje",
"permalink": "permalink",
- "`x` marked it with a ❤": "`x` jelölte ❤-vel",
- "Audio mode": "Audió mód",
- "Video mode": "Hang mód",
+ "`x` marked it with a ❤": "`x` egy ❤ jellel jelölte meg",
+ "Audio mode": "Csak hanggal",
+ "Video mode": "Hanggal és képpel",
"Videos": "Videók",
"Playlists": "Lejátszási listák",
"Community": "Közösség",
- "Current version: ": "Jelenlegi verzió: "
+ "Current version: ": "Jelenlegi verzió: ",
+ "preferences_quality_option_medium": "Közepes",
+ "preferences_quality_dash_option_auto": "Automatikus",
+ "preferences_quality_dash_option_best": "Legjobb",
+ "preferences_quality_dash_option_worst": "Legrosszabb",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "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",
+ "invidious": "Invidious",
+ "videoinfo_started_streaming_x_ago": "`x` ezelőtt kezdte streamelni",
+ "views": "Megnézések száma szerint",
+ "purchased": "Megvásárolva",
+ "360": "360°-os",
+ "footer_original_source_code": "Eredeti forráskód",
+ "none": "egyik sem",
+ "videoinfo_watch_on_youTube": "Megnézés a YouTube-on",
+ "videoinfo_youTube_embed_link": "Beágyazás",
+ "videoinfo_invidious_embed_link": "Beágyazás linkje",
+ "download_subtitles": "Felirat – `x` (.vtt)",
+ "user_created_playlists": "`x` létrehozott lejátszási lista",
+ "user_saved_playlists": "`x` mentett lejátszási lista",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_dash": "DASH (adaptív minőség)",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_label": "DASH-videó minősége: ",
+ "preferences_quality_option_small": "Rossz",
+ "date": "Feltöltés dátuma szerint",
+ "Video unavailable": "A videó nem érhető el",
+ "preferences_save_player_pos_label": "A videó folytatása onnan, ahol félbe lett hagyva: ",
+ "preferences_show_nick_label": "Becenév mutatása felül: ",
+ "Released under the AGPLv3 on Github.": "Az AGPLv3 licenc alapján, a GitHubon elérhető",
+ "3d": "3D-ben",
+ "live": "Élőben",
+ "filter": "Szűrők",
+ "next_steps_error_message_refresh": "Újratöltés",
+ "footer_donate_page": "Adakozás",
+ "footer_source_code": "Forráskód",
+ "footer_modfied_source_code": "Módosított forráskód",
+ "adminprefs_modified_source_code_url_label": "A módosított forráskód repositoryjának URL-je:",
+ "preferences_automatic_instance_redirect_label": "Váltáskor másik Invidious oldal automatikus betöltése (redirect.invidious.io töltődik, ha nem működne): ",
+ "preferences_region_label": "Ország tartalmainak mutatása: ",
+ "relevance": "Relevancia szerint",
+ "rating": "Besorolás szerint",
+ "content_type": "Típus",
+ "today": "Mai napon",
+ "channel": "Csatorna",
+ "video": "Videó",
+ "playlist": "Lejátszási lista",
+ "creative_commons": "Creative Commons",
+ "features": "Jellemzők",
+ "sort": "Rendezés módja",
+ "preferences_category_misc": "További beállítások",
+ "%A %B %-d, %Y": "%Y. %B %-d %A",
+ "long": "Hosszú (több, mint 20 perces)",
+ "year": "Ebben az évben",
+ "hour": "Az elmúlt órában",
+ "movie": "Film",
+ "hdr": "HDR",
+ "Broken? Try another Invidious Instance": "Nem működik? Próbáld meg egy másik Invidious oldallal!",
+ "duration": "Játékidő",
+ "next_steps_error_message": "Az alábbi lehetőségek állnak rendelkezésre: ",
+ "Xhosa": "xhosza",
+ "Switch Invidious Instance": "Váltás másik Invidious-oldalra",
+ "Urdu": "urdu",
+ "week": "Ezen a héten",
+ "Invalid TFA code": "A kétlépéses hitelesítés kódja nem megfelelő",
+ "footer_documentation": "Dokumentáció",
+ "hd": "HD",
+ "next_steps_error_message_go_to_youtube": "Ugrás a YouTube-ra",
+ "show": "Műsor",
+ "4k": "4K",
+ "short": "Rövid (kevesebb, mint 4 perces)",
+ "month": "Ebben a hónapban",
+ "subtitles": "Felirattal",
+ "location": "Közelben"
}
diff --git a/locales/id.json b/locales/id.json
index 054a2557..b3918955 100644
--- a/locales/id.json
+++ b/locales/id.json
@@ -433,5 +433,34 @@
"footer_modfied_source_code": "Kode sumber yang dimodifikasi",
"footer_documentation": "Dokumentasi",
"preferences_region_label": "Konten dari negara: ",
- "preferences_quality_dash_label": "Kualitas video dash yang disukai: "
+ "preferences_quality_dash_label": "Kualitas video DASH yang disukai: ",
+ "preferences_quality_option_medium": "Medium",
+ "preferences_quality_option_small": "Rendah",
+ "preferences_quality_dash_option_best": "Terbaik",
+ "preferences_quality_dash_option_worst": "Terburuk",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_quality_dash_option_360p": "360p",
+ "preferences_quality_dash_option_240p": "240p",
+ "preferences_quality_dash_option_144p": "144p",
+ "invidious": "Invidious",
+ "purchased": "Dibeli",
+ "360": "360°",
+ "none": "tidak ada",
+ "videoinfo_watch_on_youTube": "Tonton di YouTube",
+ "videoinfo_youTube_embed_link": "Tersemat",
+ "videoinfo_invidious_embed_link": "Tautan Tersemat",
+ "download_subtitles": "Takarir- `x` (.vtt)",
+ "user_saved_playlists": "`x` daftar putar yang disimpan",
+ "videoinfo_started_streaming_x_ago": "Mulai siaran `x` yang lalu",
+ "user_created_playlists": "`x` daftar putar yang dibuat",
+ "preferences_quality_option_dash": "DASH (kualitas adaptif)",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_auto": "Otomatis",
+ "preferences_quality_dash_option_480p": "480p",
+ "Video unavailable": "Video tidak tersedia",
+ "preferences_save_player_pos_label": "Simpan waktu video saat ini: "
}
diff --git a/locales/ja.json b/locales/ja.json
index c7764d33..bf858f1f 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -433,5 +433,21 @@
"long": "20 分以上",
"preferences_region_label": "地域: ",
"footer_donate_page": "寄付する",
- "preferences_quality_dash_label": "優先するDash画質 : "
+ "preferences_quality_dash_label": "優先するDash画質 : ",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "preferences_quality_dash_option_240p": "240p",
+ "preferences_quality_dash_option_144p": "144p",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_medium": "中",
+ "preferences_quality_option_small": "小",
+ "invidious": "Invidious",
+ "preferences_quality_dash_option_auto": "自動",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_quality_dash_option_360p": "360p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_480p": "480p",
+ "videoinfo_youTube_embed_link": "埋め込み",
+ "videoinfo_invidious_embed_link": "埋め込みリンク"
}
diff --git a/locales/nb-NO.json b/locales/nb-NO.json
index 3dd76c1f..14361224 100644
--- a/locales/nb-NO.json
+++ b/locales/nb-NO.json
@@ -431,5 +431,35 @@
"footer_source_code": "Kildekode",
"footer_original_source_code": "Opprinnelig kildekode",
"footer_modfied_source_code": "Endret kildekode",
- "adminprefs_modified_source_code_url_label": "Nettadresse til kodelager inneholdende endret kildekode"
+ "adminprefs_modified_source_code_url_label": "Nettadresse til kodelager inneholdende endret kildekode",
+ "preferences_quality_dash_label": "Foretrukket DASH-videokvalitet: ",
+ "preferences_region_label": "Innholdsland: ",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_small": "Lav",
+ "preferences_quality_dash_option_auto": "Auto",
+ "preferences_quality_dash_option_best": "Best",
+ "preferences_quality_dash_option_worst": "Verst",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "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",
+ "invidious": "Invidious",
+ "purchased": "Kjøpt",
+ "360": "360°",
+ "none": "intet",
+ "videoinfo_watch_on_youTube": "Se på YouTube",
+ "videoinfo_youTube_embed_link": "Bak inn",
+ "videoinfo_invidious_embed_link": "Bak inn lenke",
+ "download_subtitles": "Undertekster - `x` (.vtt)",
+ "user_created_playlists": "`x` spillelister opprettet",
+ "user_saved_playlists": "`x` spillelister lagret",
+ "Video unavailable": "Utilgjengelig video",
+ "preferences_quality_option_dash": "DASH (tilpasset kvalitet)",
+ "preferences_quality_option_medium": "Medium",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "videoinfo_started_streaming_x_ago": "Strømmen startet for `x` siden"
}
diff --git a/locales/nl.json b/locales/nl.json
index e523246b..c51d6e18 100644
--- a/locales/nl.json
+++ b/locales/nl.json
@@ -42,7 +42,7 @@
"Export subscriptions as OPML": "Abonnementen exporteren als OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Abonnementen exporteren als OPML (voor NewPipe en FreeTube)",
"Export data as JSON": "Gegevens exporteren als JSON",
- "Delete account?": "Wil je je account verwijderen?",
+ "Delete account?": "Wilt u uw account verwijderen?",
"History": "Geschiedenis",
"An alternative front-end to YouTube": "Een alternatief front-end voor YouTube",
"JavaScript license information": "JavaScript-licentieinformatie",
@@ -414,5 +414,53 @@
"location": "locatie",
"hdr": "HDR",
"filter": "verfijnen",
- "Current version: ": "Huidige versie: "
+ "Current version: ": "Huidige versie: ",
+ "Switch Invidious Instance": "Schakel tussen de Invidious Instanties",
+ "preferences_automatic_instance_redirect_label": "Automatische instantie-omleiding (terugval naar redirect.invidious.io): ",
+ "preferences_quality_dash_label": "Gewenste DASH-videokwaliteit: ",
+ "preferences_region_label": "Inhoud land: ",
+ "preferences_category_misc": "Diverse voorkeuren",
+ "preferences_show_nick_label": "Toon bijnaam bovenaan: ",
+ "Released under the AGPLv3 on Github.": "Uitgebracht onder de AGPLv3 op Github.",
+ "short": "Kort (<4 minuten)",
+ "next_steps_error_message_refresh": "Vernieuwen",
+ "next_steps_error_message_go_to_youtube": "Ga naar YouTube",
+ "footer_donate_page": "Doneren",
+ "footer_documentation": "Documentatie",
+ "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",
+ "Broken? Try another Invidious Instance": "Kapot? Probeer een andere Invidious Instantie",
+ "next_steps_error_message": "Waarna u moet proberen om: ",
+ "footer_source_code": "Bron-code",
+ "long": "Lang (> 20 minuten)",
+ "preferences_quality_option_dash": "DASH (adaptieve kwaliteit)",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_medium": "Gemiddeld",
+ "preferences_quality_option_small": "Klein",
+ "preferences_quality_dash_option_auto": "Automatisch",
+ "preferences_quality_dash_option_best": "Beste",
+ "preferences_quality_dash_option_worst": "Slechtste",
+ "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",
+ "invidious": "Invidious",
+ "videoinfo_started_streaming_x_ago": "Stream `x` geleden begonnen",
+ "videoinfo_watch_on_youTube": "Bekijken op YouTube",
+ "videoinfo_youTube_embed_link": "Inbedden",
+ "videoinfo_invidious_embed_link": "Link ingebedde versie",
+ "download_subtitles": "Ondertiteling - `x` (.vtt)",
+ "user_created_playlists": "`x` afspeellijsten aangemaakt",
+ "user_saved_playlists": "`x` afspeellijsten opgeslagen",
+ "Video unavailable": "Video onbeschikbaar",
+ "preferences_save_player_pos_label": "Huidig afspeeltijdstip opslaan: ",
+ "none": "geen",
+ "purchased": "Gekocht",
+ "360": "360º"
}
diff --git a/locales/sq.json b/locales/sq.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/locales/sq.json
@@ -0,0 +1 @@
+{}
diff --git a/locales/sr.json b/locales/sr.json
index e0713b43..03b6cf9e 100644
--- a/locales/sr.json
+++ b/locales/sr.json
@@ -1,437 +1,437 @@
{
"`x` subscribers": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` пратилаца",
- "": "`x` пратилаца"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` pratilac",
+ "": "`x` pratilaca"
},
"`x` videos": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` видео записа",
- "": "`x` видео записа"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` video zapis",
+ "": "`x` video zapisa"
},
"`x` playlists": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` списака извођења",
- "": "`x` списака извођења"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` spisak izvođenja",
+ "": "`x` spisaka izvođenja"
},
- "LIVE": "УЖИВО",
- "Shared `x` ago": "Подељено пре `x`",
- "Unsubscribe": "Прекини праћење",
- "Subscribe": "Прати",
- "View channel on YouTube": "Погледај канал на YouTube-у",
- "View playlist on YouTube": "Погледај списак извођења на YouTube-у",
- "newest": "најновије",
- "oldest": "најстарије",
- "popular": "популарно",
- "last": "последње",
- "Next page": "Следећа страница",
- "Previous page": "Претходна страница",
- "Clear watch history?": "Избрисати повест прегледања?",
- "New password": "Нова запорка",
- "New passwords must match": "Нове запорке морају бити истоветне",
- "Cannot change password for Google accounts": "Није могуће променити запорку за Google налоге",
- "Authorize token?": "Овласти токен?",
- "Authorize token for `x`?": "Овласти токен за `x`?",
- "Yes": "Да",
- "No": "Не",
- "Import and Export Data": "Увоз и извоз података",
- "Import": "Увези",
- "Import Invidious data": "Увези податке са Invidious-а",
- "Import YouTube subscriptions": "Увези праћења са YouTube-а",
- "Import FreeTube subscriptions (.db)": "Увези праћења са FreeTube-а (.db)",
- "Import NewPipe subscriptions (.json)": "Увези праћења са NewPipe-а (.json)",
- "Import NewPipe data (.zip)": "Увези податке са NewPipe-а (.zip)",
- "Export": "Извези",
- "Export subscriptions as OPML": "Извези праћења као OPML датотеку",
- "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извези праћења као OPML датотеку (за NewPipe и FreeTube)",
- "Export data as JSON": "Извези податке као JSON датотеку",
- "Delete account?": "Избрисати рачун?",
- "History": "Повест",
- "An alternative front-end to YouTube": "Заменски кориснички слој за YouTube",
- "JavaScript license information": "Извештај о JavaScript одобрењу",
- "source": "извор",
- "Log in": "Пријави се",
- "Log in/register": "Пријави се/Отвори налог",
- "Log in with Google": "Пријави се помоћу Google-а",
- "User ID": "Кориснички ИД",
- "Password": "Запорка",
- "Time (h:mm:ss):": "Време (ч:мм:сс):",
- "Text CAPTCHA": "Знаковни CAPTCHA",
- "Image CAPTCHA": "Сликовни CAPTCHA",
- "Sign In": "Пријава",
- "Register": "Отвори налог",
- "E-mail": "Е-пошта",
- "Google verification code": "Google-ов оверни кôд",
- "Preferences": "Подешавања",
- "preferences_category_player": "Подешавања репродуктора",
- "preferences_video_loop_label": "Увек понављај: ",
- "preferences_autoplay_label": "Самопуштање: ",
- "preferences_continue_label": "Увек подразумевано пуштај следеће: ",
- "preferences_continue_autoplay_label": "Самопуштање следећег видео записа: ",
- "preferences_listen_label": "Увек подразумевано укључен само звук: ",
- "preferences_local_label": "Приказ видео записа преко посредника: ",
- "Playlist privacy": "Подешавања приватности плеј листе",
- "Editing playlist `x`": "Измена плеј листе `x`",
- "Please sign in using 'Log in with Google'": "Молимо Вас да се пријавите помоћу 'Log in with Google'",
- "Playlist does not exist.": "Непостојећа плеј листа.",
- "Erroneous challenge": "Погрешан изазов",
- "Maltese": "Малтешки",
- "Download": "Преузми",
- "Download as: ": "Преузми као: ",
- "Quota exceeded, try again in a few hours": "Квота је премашена, молимо Вас да покушате за пар сати",
- "Bangla": "Бангла/Бенгалски",
- "preferences_quality_dash_label": "Преферирани квалитет dash видео формата: ",
- "Token manager": "Меначер токена",
- "Token": "Токен",
+ "LIVE": "UŽIVO",
+ "Shared `x` ago": "Podeljeno pre `x`",
+ "Unsubscribe": "Prekini praćenje",
+ "Subscribe": "Prati",
+ "View channel on YouTube": "Pogledaj kanal na YouTube-u",
+ "View playlist on YouTube": "Pogledaj spisak izvođenja na YouTube-u",
+ "newest": "najnovije",
+ "oldest": "najstarije",
+ "popular": "popularno",
+ "last": "poslednje",
+ "Next page": "Sledeća stranica",
+ "Previous page": "Prethodna stranica",
+ "Clear watch history?": "Izbrisati povest pregledanja?",
+ "New password": "Nova lozinka",
+ "New passwords must match": "Nove lozinke moraju biti istovetne",
+ "Cannot change password for Google accounts": "Nije moguće promeniti lozinku za Google naloge",
+ "Authorize token?": "Ovlasti žeton?",
+ "Authorize token for `x`?": "Ovlasti žeton za `x`?",
+ "Yes": "Da",
+ "No": "Ne",
+ "Import and Export Data": "Uvoz i Izvoz Podataka",
+ "Import": "Uvezi",
+ "Import Invidious data": "Uvezi podatke sa Invidious-a",
+ "Import YouTube subscriptions": "Uvezi praćenja sa YouTube-a",
+ "Import FreeTube subscriptions (.db)": "Uvezi praćenja sa FreeTube-a (.db)",
+ "Import NewPipe subscriptions (.json)": "Uvezi praćenja sa NewPipe-a (.json)",
+ "Import NewPipe data (.zip)": "Uvezi podatke sa NewPipe-a (.zip)",
+ "Export": "Izvezi",
+ "Export subscriptions as OPML": "Izvezi praćenja kao OPML datoteku",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "Izvezi praćenja kao OPML datoteku (za NewPipe i FreeTube)",
+ "Export data as JSON": "Izvezi podatke kao JSON datoteku",
+ "Delete account?": "Izbrišite nalog?",
+ "History": "Istorija",
+ "An alternative front-end to YouTube": "Zamenski korisnički sloj za YouTube",
+ "JavaScript license information": "Izveštaj o JavaScript odobrenju",
+ "source": "izvor",
+ "Log in": "Prijavi se",
+ "Log in/register": "Prijavi se/Otvori nalog",
+ "Log in with Google": "Prijavi se pomoću Google-a",
+ "User ID": "Korisnički ID",
+ "Password": "Lozinka",
+ "Time (h:mm:ss):": "Vreme (č:mm:ss):",
+ "Text CAPTCHA": "Znakovni CAPTCHA",
+ "Image CAPTCHA": "Slikovni CAPTCHA",
+ "Sign In": "Prijava",
+ "Register": "Otvori nalog",
+ "E-mail": "E-pošta",
+ "Google verification code": "Google-ova overna koda",
+ "Preferences": "Podešavanja",
+ "preferences_category_player": "Podešavanja reproduktora",
+ "preferences_video_loop_label": "Uvek ponavljaj: ",
+ "preferences_autoplay_label": "Samopuštanje: ",
+ "preferences_continue_label": "Uvek podrazumevano puštaj sledeće: ",
+ "preferences_continue_autoplay_label": "Samopuštanje sledećeg video zapisa: ",
+ "preferences_listen_label": "Uvek podrazumevano uključen samo zvuk: ",
+ "preferences_local_label": "Prikaz video zapisa preko posrednika: ",
+ "Playlist privacy": "Podešavanja privatnosti plej liste",
+ "Editing playlist `x`": "Izmena plej liste `x`",
+ "Please sign in using 'Log in with Google'": "Molimo Vas da se prijavite pomoću 'Log in with Google'",
+ "Playlist does not exist.": "Nepostojeća plej lista.",
+ "Erroneous challenge": "Pogrešan izazov",
+ "Maltese": "Malteški",
+ "Download": "Preuzmi",
+ "Download as: ": "Preuzmi kao: ",
+ "Quota exceeded, try again in a few hours": "Kvota je premašena, molimo vas da pokušate ponovo za par sati",
+ "Bangla": "Bangla/Bengalski",
+ "preferences_quality_dash_label": "Preferirani kvalitet DASH video formata: ",
+ "Token manager": "Upravljanje žetonima",
+ "Token": "Žeton",
"`x` tokens": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` токен",
- "": "`x` токена"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` žeton",
+ "": "`x` žetona"
},
- "Import/export": "Увези/Извези",
- "revoke": "опозови",
+ "Import/export": "Uvezi/Izvezi",
+ "revoke": "opozovi",
"`x` unseen notifications": {
- "": "`x` непрегледаних обавештења",
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` непрегледаних обавештења"
+ "": "`x` nepregledanih obaveštenja",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` nepregledanо obaveštenjе"
},
- "search": "претрага",
- "Log out": "Одјава",
- "Source available here.": "Изворни код.",
- "Trending": "У тренду",
- "Updated `x` ago": "Ажурирано пре `x`",
- "Delete playlist `x`?": "Обриши плеј листу `x`?",
- "Create playlist": "Направи плеј листу",
- "Show less": "Прикажи мање",
- "Switch Invidious Instance": "Промени Invidious инстанцу",
- "Hide annotations": "Сакриј напомене",
- "User ID is a required field": "Кориснички ID је обавезно поље",
- "Wrong username or password": "Погрешно корисничко име или лозинка",
- "Please log in": "Молимо Вас да се пријавите",
- "channel:`x`": "канал:`x`",
- "Could not fetch comments": "Узимање коментара није успело",
+ "search": "pretraga",
+ "Log out": "Odjava",
+ "Source available here.": "Izvorna koda je ovde dostupna.",
+ "Trending": "U trendu",
+ "Updated `x` ago": "Ažurirano pre `x`",
+ "Delete playlist `x`?": "Obriši plej listu `x`?",
+ "Create playlist": "Napravi plej listu",
+ "Show less": "Prikaži manje",
+ "Switch Invidious Instance": "Promeni Invidious instancu",
+ "Hide annotations": "Sakrij napomene",
+ "User ID is a required field": "Korisnički ID je obavezno polje",
+ "Wrong username or password": "Pogrešno korisničko ime ili lozinka",
+ "Please log in": "Molimo vas da se prijavite",
+ "channel:`x`": "kanal:`x`",
+ "Could not fetch comments": "Uzimanje komentara nije uspelo",
"`x` points": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` поен",
- "": "`x` поена"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` poen",
+ "": "`x` poena"
},
- "Could not create mix.": "Прављење микса није успело.",
- "Empty playlist": "Празна плеј листа",
- "Not a playlist.": "Није плеј листа.",
- "Could not pull trending pages.": "Учитавање 'У току' странције није успело.",
- "Token is expired, please try again": "Токен је истекао, молимо Вас да покушате поново",
- "English (auto-generated)": "Енглески (аутоматски генерисано)",
- "Afrikaans": "Африканс",
- "Albanian": "Албански",
- "Armenian": "Јерменски",
- "Azerbaijani": "Азербејџански",
- "Basque": "Баскијски",
- "Bosnian": "Српски (Босна и Херцеговина)",
- "Bulgarian": "Бугарски",
- "Burmese": "Бурмански",
- "Catalan": "Каталонски",
- "Cebuano": "Cebuano",
- "Chinese (Traditional)": "Кинески (Традиционални)",
- "Corsican": "Корзикански",
- "Danish": "Дански",
- "Kannada": "Канада (Језик)",
- "Kazakh": "Казашки",
- "Russian": "Руски",
- "Scottish Gaelic": "Шкотски Гелски",
- "Sinhala": "Синхалешки",
- "Slovak": "Словачки",
- "Spanish": "Шпански",
- "Spanish (Latin America)": "Шпански (Јужна Америка)",
- "Sundanese": "Сундски",
- "Swedish": "Шведски",
- "Tajik": "Таџички",
- "Telugu": "Телугу",
- "Turkish": "Турски",
- "Ukrainian": "Украјински",
- "Urdu": "Урду",
- "Uzbek": "Узбечки",
- "Vietnamese": "Вијетнамски",
+ "Could not create mix.": "Pravljenje miksa nije uspelo.",
+ "Empty playlist": "Prazna plej lista",
+ "Not a playlist.": "Nije plej lista.",
+ "Could not pull trending pages.": "Učitavanje 'U toku' stranica nije uspelo.",
+ "Token is expired, please try again": "Žeton je istekao, molimo vas da pokušate ponovo",
+ "English (auto-generated)": "Engleski (automatski generisano)",
+ "Afrikaans": "Afrikans",
+ "Albanian": "Albanski",
+ "Armenian": "Jermenski",
+ "Azerbaijani": "Azerbejdžanski",
+ "Basque": "Baskijski",
+ "Bosnian": "Bosanski",
+ "Bulgarian": "Bugarski",
+ "Burmese": "Burmanski",
+ "Catalan": "Katalonski",
+ "Cebuano": "Sebuano",
+ "Chinese (Traditional)": "Kineski (Tradicionalni)",
+ "Corsican": "Korzikanski",
+ "Danish": "Danski",
+ "Kannada": "Kanada (Jezik)",
+ "Kazakh": "Kazaški",
+ "Russian": "Ruski",
+ "Scottish Gaelic": "Škotski Gelski",
+ "Sinhala": "Sinhaleški",
+ "Slovak": "Slovački",
+ "Spanish": "Španski",
+ "Spanish (Latin America)": "Španski (Južna Amerika)",
+ "Sundanese": "Sundski",
+ "Swedish": "Švedski",
+ "Tajik": "Tadžički",
+ "Telugu": "Telugu",
+ "Turkish": "Turski",
+ "Ukrainian": "Ukrajinski",
+ "Urdu": "Urdu",
+ "Uzbek": "Uzbečki",
+ "Vietnamese": "Vijetnamski",
"`x` minutes": {
- "": "`x` минута",
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` минут"
+ "": "`x` minuta",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` minut"
},
"`x` seconds": {
- "": "`x` секунди",
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` секунда"
+ "": "`x` sekundi",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` sekunda"
},
- "Rating: ": "Оцена/е: ",
- "View as playlist": "Погледај као плеј листу",
- "Default": "Подразумеван/о",
- "Gaming": "Гејминг",
- "Movies": "Филмови",
+ "Rating: ": "Ocena/e: ",
+ "View as playlist": "Pogledaj kao plej listu",
+ "Default": "Podrazumevan/o",
+ "Gaming": "Igrice",
+ "Movies": "Filmovi",
"%A %B %-d, %Y": "%A %B %-d, %Y",
- "(edited)": "(измењено)",
- "YouTube comment permalink": "YouTube коментар трајна веза",
- "Audio mode": "Аудио мод",
- "Playlists": "Плеј листе",
- "relevance": "Ревелантно",
- "rating": "Оцене",
- "date": "Датум отпремања",
- "views": "Број прегледа",
- "`x` marked it with a ❤": "`x` је означио/ла ово са ❤",
- "duration": "Трајање",
- "features": "Карактеристике",
- "hour": "Последњи сат",
- "week": "Ове недеље",
- "month": "Овај месец",
- "year": "Ове године",
- "video": "Видео",
- "playlist": "Плеј листа",
- "movie": "Филм",
- "long": "Дуг (> 20 минута)",
+ "(edited)": "(izmenjeno)",
+ "YouTube comment permalink": "YouTube komentar trajna veza",
+ "Audio mode": "Audio mod",
+ "Playlists": "Plej liste",
+ "relevance": "Relevantnost",
+ "rating": "Ocene",
+ "date": "Datum otpremanja",
+ "views": "Broj pregleda",
+ "`x` marked it with a ❤": "`x` je označio/la ovo sa ❤",
+ "duration": "Trajanje",
+ "features": "Karakteristike",
+ "hour": "Poslednji sat",
+ "week": "Ove sedmice",
+ "month": "Ovaj mesec",
+ "year": "Ove godine",
+ "video": "Video",
+ "playlist": "Plej lista",
+ "movie": "Film",
+ "long": "Dugo (> 20 minuta)",
"hd": "HD",
- "creative_commons": "Creative Commons (Лиценца)",
- "3d": "3Д",
- "hdr": "Видео високе резолуције",
- "filter": "Филтер",
- "next_steps_error_message": "Можете урадити нешта од следећег: ",
- "next_steps_error_message_go_to_youtube": "Иди на YouTube",
- "footer_documentation": "Документација",
- "preferences_region_label": "Држава порекла садржаја: ",
- "preferences_player_style_label": "Стил плејера: ",
- "preferences_dark_mode_label": "Изглед/Тема: ",
- "light": "светла",
- "preferences_thin_mode_label": "Компактни режим: ",
- "preferences_category_misc": "Остала подешавања",
- "preferences_automatic_instance_redirect_label": "Аутоматско пребацивање на другу инстанцу у случају отказивања (пређи на redirect.invidious.io): ",
- "alphabetically - reverse": "по алфабету - обрнуто",
- "Enable web notifications": "Омогући обавештења у веб претраживачу",
- "`x` is live": "`x` преноси уживо",
- "Manage tokens": "Управљај токенима",
- "Watch history": "Историја гледања",
- "preferences_feed_menu_label": "Feed мени: ",
- "preferences_show_nick_label": "Прикажи надимке на врху: ",
- "CAPTCHA enabled: ": "CAPTCHA омогућена: ",
- "Registration enabled: ": "Регистрација омогућена: ",
- "Subscription manager": "Менаџер претплата",
- "Wilson score: ": "Wilson скор: ",
- "Engagement: ": "Ангажовање: ",
- "Whitelisted regions: ": "Дозвољени региони: ",
- "Shared `x`": "Подељено `x`",
+ "creative_commons": "Creative Commons (Licenca)",
+ "3d": "3D",
+ "hdr": "Video Visoke Rezolucije",
+ "filter": "Filter",
+ "next_steps_error_message": "Nakon čega bi trebali probati: ",
+ "next_steps_error_message_go_to_youtube": "Idi na YouTube",
+ "footer_documentation": "Dokumentacija",
+ "preferences_region_label": "Država porekla sadržaja: ",
+ "preferences_player_style_label": "Stil plejera: ",
+ "preferences_dark_mode_label": "Izgled/Tema: ",
+ "light": "svetlo",
+ "preferences_thin_mode_label": "Kompaktni režim: ",
+ "preferences_category_misc": "Ostala podešavanja",
+ "preferences_automatic_instance_redirect_label": "Automatsko prebacivanje na drugu instancu u slučaju otkazivanja (preči će nazad na redirect.invidious.io): ",
+ "alphabetically - reverse": "po alfabetu - obrnuto",
+ "Enable web notifications": "Omogući obaveštenja u veb pretraživaču",
+ "`x` is live": "`x` prenosi uživo",
+ "Manage tokens": "Upravljaj žetonima",
+ "Watch history": "Istorija gledanja",
+ "preferences_feed_menu_label": "Dovodna stranica: ",
+ "preferences_show_nick_label": "Prikaži nadimke na vrhu: ",
+ "CAPTCHA enabled: ": "CAPTCHA omogućena: ",
+ "Registration enabled: ": "Registracija omogućena: ",
+ "Subscription manager": "Upravljanje praćenjima",
+ "Wilson score: ": "Wilsonova ocena: ",
+ "Engagement: ": "Angažovanje: ",
+ "Whitelisted regions: ": "Dozvoljene oblasti: ",
+ "Shared `x`": "Podeljeno `x`",
"`x` views": {
- "": "`x` прегледа",
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` преглед"
+ "": "`x` pregleda",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` pregled"
},
- "Premieres in `x`": "Премијера у `x`",
- "Premieres `x`": "Премијере у `x`",
- "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Хеј! Изгледа да сте онемогућили JavaScript. Кликните овде да видите коментаре, имајте на уму да ово може да потраје дуже док се не учитају.",
+ "Premieres in `x`": "Premera u `x`",
+ "Premieres `x`": "Premere u `x`",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hej! Izgleda da ste onemogućili JavaScript. Kliknite ovde da vidite komentare, čuvajte na umu da ovo može da potraje duže dok se ne učitaju.",
"View `x` comments": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "Прикажи `x` коментар",
- "": "Прикажи `x` коментара"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "Prikaži `x` komentar",
+ "": "Prikaži `x` komentara"
},
- "View Reddit comments": "Прикажи Reddit коментаре",
- "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Неуспешна пријава, проверите да ли сте упалили двофакторну аутентикацију (Аутентикатор или СМС).",
- "CAPTCHA is a required field": "CAPTCHA је обавезно поље",
- "Croatian": "Хрватски",
- "Estonian": "Естонски",
- "Filipino": "Филипино",
- "French": "Француски",
- "Galician": "Галицијски",
- "German": "Немачки",
- "Greek": "Грчки",
- "Hausa": "Хауса",
- "Italian": "Италијански",
- "Khmer": "Кмерски",
- "Kurdish": "Курдски",
- "Kyrgyz": "Киргиски",
- "Latvian": "Летонски",
- "Lithuanian": "Литвански",
- "Macedonian": "Македонски",
- "Malagasy": "Малгашки",
- "Malay": "Малајски",
- "Marathi": "Маратхи",
- "Mongolian": "Монголски",
- "Norwegian Bokmål": "Норвешки Бокмал",
- "Nyanja": "Чева",
- "Pashto": "Паштунски",
- "Persian": "Персијски",
- "Punjabi": "Пунџаби",
- "Romanian": "Румунски",
- "Welsh": "Велшки",
- "Western Frisian": "Западнофризијски",
+ "View Reddit comments": "Prikaži Reddit komentare",
+ "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Neuspešna prijava, proverite da li ste upalili dvofaktornu autentikaciju (Autentikator ili SMS).",
+ "CAPTCHA is a required field": "CAPTCHA je obavezno polje",
+ "Croatian": "Hrvatski",
+ "Estonian": "Estonski",
+ "Filipino": "Filipino",
+ "French": "Francuski",
+ "Galician": "Galicijski",
+ "German": "Nemački",
+ "Greek": "Grčki",
+ "Hausa": "Hausa",
+ "Italian": "Talijanski",
+ "Khmer": "Kmerski",
+ "Kurdish": "Kurdski",
+ "Kyrgyz": "Kirgiski",
+ "Latvian": "Letonski",
+ "Lithuanian": "Litvanski",
+ "Macedonian": "Makedonski",
+ "Malagasy": "Malgaški",
+ "Malay": "Malajski",
+ "Marathi": "Marathi",
+ "Mongolian": "Mongolski",
+ "Norwegian Bokmål": "Norveški Bokmal",
+ "Nyanja": "Čeva",
+ "Pashto": "Paštunski",
+ "Persian": "Persijski",
+ "Punjabi": "Pundžabi",
+ "Romanian": "Rumunski",
+ "Welsh": "Velški",
+ "Western Frisian": "Zapadnofrizijski",
"`x` years": {
- "": "`x` година",
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` година"
+ "": "`x` godina",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` godina"
},
"`x` weeks": {
- "": "`x` недеља",
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` недеља"
+ "": "`x` sedmica",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` sedmica"
},
"`x` days": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` дан",
- "": "`x` дана"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` dan",
+ "": "`x` dana"
},
"`x` hours": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` сат",
- "": "`x` сати"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` sat",
+ "": "`x` sati"
},
- "Fallback comments: ": "Коментари у случају отказивања: ",
- "Popular": "Популарно",
- "Search": "Претрага",
- "About": "О програму",
- "footer_source_code": "Изворни Код",
- "footer_original_source_code": "Оригинални изворни код",
- "preferences_related_videos_label": "Прикажи сличне видео клипове: ",
- "preferences_annotations_label": "Прикажи напомене подразумевано: ",
- "preferences_extend_desc_label": "Аутоматски прикажи цео опис видеа: ",
- "preferences_vr_mode_label": "Интерактивни видео клипови у 360 степени: ",
- "preferences_category_visual": "Визуелне преференце",
- "preferences_captions_label": "Подразумевани титл: ",
- "Music": "Музика",
- "content_type": "Тип",
- "Broken? Try another Invidious Instance": "Не функционише исправно? Пробајте другу Invidious инстанцу",
- "Tamil": "Тамилски",
- "Save preferences": "Сачувај подешавања",
- "Only show latest unwatched video from channel: ": "Прикажи само последње видео клипове који нису погледани са канала: ",
- "Xhosa": "Коса (Језик)",
- "channel": "Канал",
- "Hungarian": "Мађарски",
- "Maori": "Маори (Језик)",
- "Manage subscriptions": "Управљај претплатама",
- "Hindi": "Хинди",
- "`x` ago": "пре `x`",
- "Import/export data": "Увези/Извези податке",
- "`x` uploaded a video": "`x` је отпремио/ла видео клип",
+ "Fallback comments: ": "Komentari u slučaju otkazivanja: ",
+ "Popular": "Popularno",
+ "Search": "Pretraga",
+ "About": "O programu",
+ "footer_source_code": "Izvorna Koda",
+ "footer_original_source_code": "Originalna Izvorna Koda",
+ "preferences_related_videos_label": "Prikaži slične video klipove: ",
+ "preferences_annotations_label": "Prikaži napomene podrazumevano: ",
+ "preferences_extend_desc_label": "Automatski prikaži ceo opis videa: ",
+ "preferences_vr_mode_label": "Interaktivni video klipovi u 360 stepeni: ",
+ "preferences_category_visual": "Vizuelne preference",
+ "preferences_captions_label": "Podrazumevani titl: ",
+ "Music": "Muzika",
+ "content_type": "Tip",
+ "Broken? Try another Invidious Instance": "Ne funkcioniše ispravno? Probajte drugu Invidious instancu",
+ "Tamil": "Tamilski",
+ "Save preferences": "Sačuvaj podešavanja",
+ "Only show latest unwatched video from channel: ": "Prikaži samo poslednje video klipove koji nisu pogledani sa kanala: ",
+ "Xhosa": "Kosa (Jezik)",
+ "channel": "Kanal",
+ "Hungarian": "Mađarski",
+ "Maori": "Maori (Jezik)",
+ "Manage subscriptions": "Upravljaj zapisima",
+ "Hindi": "Hindi",
+ "`x` ago": "pre `x`",
+ "Import/export data": "Uvezi/Izvezi podatke",
+ "`x` uploaded a video": "`x` je otpremio/la video klip",
"View `x` replies": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "Погледај `x` одоговор",
- "": "Погледај `x` одоговора"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "Prikaži `x` odgovor",
+ "": "Prikaži `x` odgovora"
},
- "Delete account": "Обриши налог",
- "preferences_default_home_label": "Подразумевана почетна страница: ",
- "Serbian": "Српски",
- "License: ": "Лиценца: ",
- "live": "Уживо",
- "Report statistics: ": "Извештавај о статистици: ",
- "Only show latest video from channel: ": "Приказуј последње видео клипове само са канала: ",
- "channel name - reverse": "име канала - обрнуто",
- "Could not get channel info.": "Узимање података о каналу није успело.",
- "View privacy policy.": "Погледај политику приватности.",
- "Change password": "Промени лозинку",
- "Malayalam": "Малајалам",
- "View more comments on Reddit": "Прикажи више коментара на Reddit-у",
- "Portuguese": "Португалски",
- "View YouTube comments": "Прикажи YouTube коментаре",
- "published - reverse": "објављено - обрнуто",
- "Dutch": "Белгијски",
- "preferences_volume_label": "Јачина звука: ",
- "preferences_locale_label": "Језик: ",
+ "Delete account": "Obriši nalog",
+ "preferences_default_home_label": "Podrazumevana početna stranica: ",
+ "Serbian": "Srpski",
+ "License: ": "Licenca: ",
+ "live": "Uživo",
+ "Report statistics: ": "Izveštavaj o statistici: ",
+ "Only show latest video from channel: ": "Prikazuj poslednje video klipove samo sa kanala: ",
+ "channel name - reverse": "ime kanala - obrnuto",
+ "Could not get channel info.": "Uzimanje podataka o kanalu nije uspelo.",
+ "View privacy policy.": "Pogledaj izveštaj o privatnosti.",
+ "Change password": "Promeni lozinku",
+ "Malayalam": "Malajalam",
+ "View more comments on Reddit": "Prikaži više komentara na Reddit-u",
+ "Portuguese": "Portugalski",
+ "View YouTube comments": "Prikaži YouTube komentare",
+ "published - reverse": "objavljeno - obrnuto",
+ "Dutch": "Holandski",
+ "preferences_volume_label": "Jačina zvuka: ",
+ "preferences_locale_label": "Jezik: ",
"`x` subscriptions": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` пратиоц",
- "": "`x` пратилаца"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` praćenje",
+ "": "`x` praćenja"
},
- "adminprefs_modified_source_code_url_label": "URL веза до репозиторијума са измењеним изворним кодом",
- "Community": "Заједница",
- "Video mode": "Видео мод",
- "Fallback captions: ": "Титл у случају да главни није доступан: ",
- "Private": "Приватно",
- "alphabetically": "по алфабету",
- "No such user": "Непостојећи корисник",
- "Subscriptions": "Праћења",
- "today": "Данас",
- "Finnish": "Фински",
- "Lao": "Лаоски",
- "Login enabled: ": "Пријава омогућена: ",
- "Shona": "Шона",
- "location": "Локација",
- "Load more": "Учитај више",
- "Released under the AGPLv3 on Github.": "Избачено под лиценцом AGPLv3 на Github-у.",
- "Slovenian": "Словеначки",
- "View JavaScript license information.": "Погледај информације лиценце везане за JavaScript.",
- "Chinese (Simplified)": "Кинески (Поједностављени)",
- "preferences_comments_label": "Подразумевани коментари: ",
- "Incorrect password": "Нетачна лозинка",
- "Show replies": "Прикажи одговоре",
- "Invidious Private Feed for `x`": "Invidious приватни Feed за `x`",
- "Watch on YouTube": "Гледај на YouTube-у",
- "Wrong answer": "Погрешан одговор",
- "preferences_quality_label": "Преферирани видео квалитет: ",
- "Hide replies": "Сакриј одговоре",
- "Invalid TFA code": "Неважећи TFA код",
- "Login failed. This may be because two-factor authentication is not turned on for your account.": "Неуспешна пријава! Ово се можда дешава јер двофакторна аутентикација није омогућена на Вашем налогу.",
- "Erroneous CAPTCHA": "Погрешна CAPTCHA",
- "Erroneous token": "Погрешан токен",
- "Czech": "Чешки",
- "Latin": "Латински",
- "Videos": "Видео клипови",
+ "adminprefs_modified_source_code_url_label": "URL veza do skladišta sa Izmenjenom Izvornom Kodom",
+ "Community": "Zajednica",
+ "Video mode": "Video mod",
+ "Fallback captions: ": "Titl u slučaju da glavni nije dostupan: ",
+ "Private": "Privatno",
+ "alphabetically": "po alfabetu",
+ "No such user": "Nepostojeći korisnik",
+ "Subscriptions": "Praćenja",
+ "today": "Danas",
+ "Finnish": "Finski",
+ "Lao": "Laoski",
+ "Login enabled: ": "Prijava omogućena: ",
+ "Shona": "Šona",
+ "location": "Lokacija",
+ "Load more": "Učitaj više",
+ "Released under the AGPLv3 on Github.": "Izbačeno pod licencom AGPLv3 na Github-u.",
+ "Slovenian": "Slovenački",
+ "View JavaScript license information.": "Pogledaj informacije licence vezane za JavaScript.",
+ "Chinese (Simplified)": "Kineski (Pojednostavljeni)",
+ "preferences_comments_label": "Podrazumevani komentari: ",
+ "Incorrect password": "Netačna lozinka",
+ "Show replies": "Prikaži odgovore",
+ "Invidious Private Feed for `x`": "Invidious Privatni Dovod za `x`",
+ "Watch on YouTube": "Gledaj na YouTube-u",
+ "Wrong answer": "Pogrešan odgovor",
+ "preferences_quality_label": "Preferirani video kvalitet: ",
+ "Hide replies": "Sakrij odgovore",
+ "Invalid TFA code": "Nevažeća TFA koda",
+ "Login failed. This may be because two-factor authentication is not turned on for your account.": "Neuspešna prijava! Ovo se možda dešava jer dvofaktorna autentikacija nije omogućena na vašem nalogu.",
+ "Erroneous CAPTCHA": "Pogrešna CAPTCHA",
+ "Erroneous token": "Pogrešan žeton",
+ "Czech": "Češki",
+ "Latin": "Latinski",
+ "Videos": "Video klipovi",
"4k": "4К",
- "footer_donate_page": "Донирај",
- "English": "Енглески",
- "Arabic": "Арапски",
- "Unlisted": "Ненаведено",
- "Hidden field \"challenge\" is a required field": "Сакривено \"challenge\" поље је обавезно",
- "Hidden field \"token\" is a required field": "Сакривено \"token\" поље је обавезно",
- "Georgian": "Грузијски",
- "Hawaiian": "Хавајски",
- "Hebrew": "Хебрејски",
- "Icelandic": "Исландски",
- "Igbo": "Игбо",
- "Japanese": "Јапански",
- "Javanese": "Јавански",
- "Sindhi": "Синди",
- "Swahili": "Свахили",
- "Yiddish": "Јидиш",
- "Zulu": "Зулу",
- "subtitles": "Титл",
- "Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 карактера",
- "This channel does not exist.": "Овај канал не постоји.",
- "Belarusian": "Белоруски",
- "Gujarati": "Гуџарати",
- "Haitian Creole": "Хаићански Креолски",
- "Somali": "Сомалијски",
- "Top": "Врх",
- "footer_modfied_source_code": "Измењени изворни код",
- "preferences_category_subscription": "Подешавања праћења",
- "preferences_annotations_subscribed_label": "Подразумевано приказати напомене за канале које пратите? ",
- "preferences_max_results_label": "Број видео клипова приказаних у листи (feed-у): ",
- "preferences_sort_label": "Сортирај видео клипове по: ",
- "preferences_unseen_only_label": "Прикажи само видео клипове који нису погледани: ",
- "preferences_notifications_only_label": "Прикажи само обавештења (ако их уопште има): ",
- "preferences_category_data": "Подешавања података",
- "Clear watch history": "Обриши историју гледања",
- "preferences_category_admin": "Администраторска подешавања",
- "published": "објављено",
- "sort": "Сортирај према",
- "show": "Шоу",
- "short": "Кратак (< 4 минута)",
- "Current version: ": "Тренутна верзија: ",
- "Top enabled: ": "Врх омогућен: ",
- "Public": "Јавно",
- "Delete playlist": "Обриши плеј листу",
- "Title": "Наслов",
- "Show annotations": "Прикажи напомене",
- "Password cannot be empty": "Лозинка не може бити празна",
- "Deleted or invalid channel": "Обрисан или непостојећи канал",
- "Esperanto": "Есперанто",
- "Hmong": "Хмонг",
- "Luxembourgish": "Луксембуршки",
- "Nepali": "Непалски",
- "Samoan": "Самоански",
- "News": "Вести",
- "permalink": "трајна веза",
- "Password is a required field": "Лозинка је обавезно поље",
- "Amharic": "Амхарски",
- "Indonesian": "Индонежански",
- "Irish": "Ирски",
- "Korean": "Корејски",
- "Southern Sotho": "Сото",
- "Thai": "Тајски",
+ "footer_donate_page": "Doniraj",
+ "English": "Engleski",
+ "Arabic": "Arapski",
+ "Unlisted": "Nenavedeno",
+ "Hidden field \"challenge\" is a required field": "Sakriveno \"challenge\" polje je obavezno",
+ "Hidden field \"token\" is a required field": "Sakriveno \"token\" polje je obavezno",
+ "Georgian": "Gruzijski",
+ "Hawaiian": "Havajski",
+ "Hebrew": "Hebrejski",
+ "Icelandic": "Islandski",
+ "Igbo": "Igbo",
+ "Japanese": "Japanski",
+ "Javanese": "Javanski",
+ "Sindhi": "Sindi",
+ "Swahili": "Svahili",
+ "Yiddish": "Jidiš",
+ "Zulu": "Zulu",
+ "subtitles": "Titl/Prevod",
+ "Password cannot be longer than 55 characters": "Lozinka ne može biti duža od 55 karaktera",
+ "This channel does not exist.": "Ovaj kanal ne postoji.",
+ "Belarusian": "Beloruski",
+ "Gujarati": "Gudžarati",
+ "Haitian Creole": "Haićanski Kreolski",
+ "Somali": "Somalijski",
+ "Top": "Vrh",
+ "footer_modfied_source_code": "Izmenjena Izvorna Koda",
+ "preferences_category_subscription": "Podešavanja praćenja",
+ "preferences_annotations_subscribed_label": "Podrazumevano prikazati napomene za kanale koje pratite? ",
+ "preferences_max_results_label": "Broj video klipova prikazanih u dovodnoj listi: ",
+ "preferences_sort_label": "Sortiraj video klipove po: ",
+ "preferences_unseen_only_label": "Prikaži samo video klipove koji nisu pogledani: ",
+ "preferences_notifications_only_label": "Prikaži samo obaveštenja (ako ih uopšte ima): ",
+ "preferences_category_data": "Podešavanja podataka",
+ "Clear watch history": "Obriši istoriju gledanja",
+ "preferences_category_admin": "Administratorska podešavanja",
+ "published": "objavljeno",
+ "sort": "Poredaj prema",
+ "show": "Emisija",
+ "short": "Kratko (< 4 minute)",
+ "Current version: ": "Trenutna verzija: ",
+ "Top enabled: ": "Vrh omogućen: ",
+ "Public": "Javno",
+ "Delete playlist": "Obriši plej listu",
+ "Title": "Naslov",
+ "Show annotations": "Prikaži napomene",
+ "Password cannot be empty": "Lozinka ne može biti prazna",
+ "Deleted or invalid channel": "Obrisan ili nepostojeći kanal",
+ "Esperanto": "Esperanto",
+ "Hmong": "Hmong",
+ "Luxembourgish": "Luksemburški",
+ "Nepali": "Nepalski",
+ "Samoan": "Samoanski",
+ "News": "Vesti",
+ "permalink": "trajna veza",
+ "Password is a required field": "Lozinka je obavezno polje",
+ "Amharic": "Amharski",
+ "Indonesian": "Indonežanski",
+ "Irish": "Irski",
+ "Korean": "Korejski",
+ "Southern Sotho": "Južni Soto",
+ "Thai": "Tajski",
"`x` months": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` месец",
- "": "`x` месеци"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` mesec",
+ "": "`x` meseci"
},
- "preferences_speed_label": "Подразумевана брзина: ",
- "Dark mode: ": "Тамни режим: ",
- "dark": "тамна",
- "Redirect homepage to feed: ": "Пребаци са почетне странице на листу (feed): ",
- "channel name": "име канала",
- "View all playlists": "Прегледај све плеј листе",
- "Show more": "Прикажи више",
- "Genre: ": "Жанр: ",
- "Family friendly? ": "Погодно за породицу? ",
- "next_steps_error_message_refresh": "Освежи страницу",
+ "preferences_speed_label": "Podrazumevana brzina: ",
+ "Dark mode: ": "Tamni režim: ",
+ "dark": "tamno",
+ "Redirect homepage to feed: ": "Prebaci sa početne stranice na dovodnu listu: ",
+ "channel name": "ime kanala",
+ "View all playlists": "Pregledaj sve plej liste",
+ "Show more": "Prikaži više",
+ "Genre: ": "Žanr: ",
+ "Family friendly? ": "Pogodno za porodicu? ",
+ "next_steps_error_message_refresh": "Osveži stranicu",
"youtube": "YouTube",
"reddit": "Reddit",
- "unsubscribe": "прекини са праћењем",
- "Blacklisted regions: ": "Блокирани региони: ",
- "Polish": "Пољски",
- "Yoruba": "Јоруба"
+ "unsubscribe": "prekini sa praćenjem",
+ "Blacklisted regions: ": "Zabranjene oblasti: ",
+ "Polish": "Poljski",
+ "Yoruba": "Joruba"
}
diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json
index 3a6d6c0b..628fab85 100644
--- a/locales/sr_Cyrl.json
+++ b/locales/sr_Cyrl.json
@@ -1,170 +1,437 @@
{
"`x` subscribers": {
- "": "`x` пратилац"
+ "": "`x` пратилацa",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` пратилац"
},
"`x` videos": {
- "": "`x` видеа"
+ "": "`x` видео записа",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` видео запис"
},
"`x` playlists": {
- "": "`x` плејлиста/е"
+ "": "`x` списака извођења",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` списак извођења"
},
"LIVE": "УЖИВО",
- "Shared `x` ago": "Објављено пре `x`",
- "Unsubscribe": "Прекините праћење",
- "Subscribe": "Пратите",
- "View channel on YouTube": "Погледајте канал на YouTube-у",
- "View playlist on YouTube": "Погледајте плејлисту на YouTube-у",
+ "Shared `x` ago": "Подељено пре `x`",
+ "Unsubscribe": "Прекини праћење",
+ "Subscribe": "Прати",
+ "View channel on YouTube": "Погледај канал на YouTube-у",
+ "View playlist on YouTube": "Погледај списак извођења на YоуТубе-у",
"newest": "најновије",
"oldest": "најстарије",
"popular": "популарно",
"last": "последње",
"Next page": "Следећа страна",
"Previous page": "Претходна страна",
- "Clear watch history?": "Обришите историју прегледања?",
+ "Clear watch history?": "Избрисати повест прегледања?",
"New password": "Нова лозинка",
- "New passwords must match": "Нове лозинке се морају поклапати",
+ "New passwords must match": "Нове лозинке морају бити истоветне",
"Cannot change password for Google accounts": "Није могуће променити лозинку за Google налоге",
- "Authorize token?": "Овластите токен?",
- "Authorize token for `x`?": "Овластите токен за `x`?",
+ "Authorize token?": "Овласти жетон?",
+ "Authorize token for `x`?": "Овласти жетон за `x`?",
"Yes": "Да",
"No": "Не",
"Import and Export Data": "Увоз и извоз података",
- "Import": "Увезите",
- "Import Invidious data": "Увезите Invidious податке",
- "Import YouTube subscriptions": "Увезите праћења са YouTube-а",
- "Import FreeTube subscriptions (.db)": "Увезите праћења са FreeTube-а (.db)",
- "Import NewPipe subscriptions (.json)": "Увезите праћења са NewPipe-а (.json)",
- "Import NewPipe data (.zip)": "Увезите NewPipe податке (.zip)",
- "Export": "Извезите",
- "Export subscriptions as OPML": "Извезите праћења у OPML формату",
- "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извезите праћења у OPML формату (за NewPipe и FreeTube )",
- "Export data as JSON": "Изветизе податке у JSON формату",
+ "Import": "Увези",
+ "Import Invidious data": "Увези податке са Individious-а",
+ "Import YouTube subscriptions": "Увези праћења са YouTube-а",
+ "Import FreeTube subscriptions (.db)": "Увези праћења са FreeTube-а (.db)",
+ "Import NewPipe subscriptions (.json)": "Увези праћења са NewPipe-а (.json)",
+ "Import NewPipe data (.zip)": "Увези податке са NewPipe-a (.zip)",
+ "Export": "Извези",
+ "Export subscriptions as OPML": "Извези праћења као ОПМЛ датотеку",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "Извези праћења као ОПМЛ датотеку (за NewPipe и FreeTube)",
+ "Export data as JSON": "Извези податке као JSON датотеку",
"Delete account?": "Избришите налог?",
"History": "Историја",
- "An alternative front-end to YouTube": "Алтернативни фронтенд за YouTube",
- "JavaScript license information": "Извештај о JavaScript лиценци",
+ "An alternative front-end to YouTube": "Заменски кориснички слој за YouTube",
+ "JavaScript license information": "Извештај о JavaScript одобрењу",
"source": "извор",
- "Log in": "Пријавите се",
- "Log in/register": "Пријавите се/направите налог",
- "Log in with Google": "Пријавите се помоћу Google-а",
- "User ID": "ИД корисника",
+ "Log in": "Пријави се",
+ "Log in/register": "Пријави се/Отворите налог",
+ "Log in with Google": "Пријави се помоћу Google-а",
+ "User ID": "Кориснички ИД",
"Password": "Лозинка",
- "Time (h:mm:ss):": "Колико је сати? (ч:мм:сс):",
- "Text CAPTCHA": "Текстуална CAPTCHA",
- "Image CAPTCHA": "Сликовна CAPTCHA",
- "Sign In": "Пријавите се",
- "Register": "Направите налог",
+ "Time (h:mm:ss):": "Време (ч:мм:сс):",
+ "Text CAPTCHA": "Знаковни ЦАПТЧА",
+ "Image CAPTCHA": "Сликовни CAPTCHA",
+ "Sign In": "Пријава",
+ "Register": "Отвори налог",
"E-mail": "Е-пошта",
- "Google verification code": "Google верификациони кôд",
+ "Google verification code": "Google-ова оверна кода",
"Preferences": "Подешавања",
- "preferences_category_player": "Подешавања видео плејера",
+ "preferences_category_player": "Подешавања репродуктора",
"preferences_video_loop_label": "Увек понављај: ",
- "preferences_autoplay_label": "Аутоматско пуштање: ",
- "preferences_continue_label": "Увек пуштај следеће: ",
- "preferences_continue_autoplay_label": "Аутоматско пуштање следећег видеа: ",
- "preferences_listen_label": "Режим слушања као подразумевано: ",
- "preferences_local_label": "Пуштање видеа кроз прокси сервер: ",
- "preferences_speed_label": "Подразумевана брзина репродукције: ",
- "preferences_quality_label": "Претпостављени квалитет видеа: ",
+ "preferences_autoplay_label": "Самопуштање: ",
+ "preferences_continue_label": "Увек подразумевано пуштај следеће: ",
+ "preferences_continue_autoplay_label": "Самопуштање следећег видео записа: ",
+ "preferences_listen_label": "Увек подразумевано укључен само звук: ",
+ "preferences_local_label": "Приказ видео записа преко посредника: ",
+ "preferences_speed_label": "Подразумевана брзина: ",
+ "preferences_quality_label": "Преферирани видео квалитет: ",
"preferences_volume_label": "Јачина звука: ",
"preferences_comments_label": "Подразумевани коментари: ",
- "youtube": "са YouTube-а",
- "reddit": "са редита",
- "preferences_captions_label": "Подразумевани титлови: ",
- "Fallback captions: ": "Алтернативни титлови: ",
- "preferences_related_videos_label": "Прикажи сличне видее: ",
- "preferences_annotations_label": "Увек приказуј анотације: ",
- "preferences_category_visual": "Подешавања изгледа",
+ "youtube": "YouTube",
+ "reddit": "Reddit",
+ "preferences_captions_label": "Подразумевани титл: ",
+ "Fallback captions: ": "Титл у случају да главни није доступан: ",
+ "preferences_related_videos_label": "Прикажи сличне видео клипове: ",
+ "preferences_annotations_label": "Прикажи напомене подразумевано: ",
+ "preferences_category_visual": "Визуелне преференце",
"preferences_player_style_label": "Стил плејера: ",
"Dark mode: ": "Тамни режим: ",
- "preferences_dark_mode_label": "Тема: ",
- "dark": "тамна",
- "light": "светла",
- "preferences_thin_mode_label": "Узани режим: ",
- "preferences_category_subscription": "Подешавања о праћењима",
- "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": "називу канала - обрнуто",
- "Only show latest video from channel: ": "Прикажи само најновији видео са канала: ",
- "Only show latest unwatched video from channel: ": "Прикажи само најновији негледани видео са канала: ",
- "preferences_unseen_only_label": "Прикажи само негледано: ",
- "preferences_notifications_only_label": "Прикажи само обавештења (ако их има): ",
- "Enable web notifications": "Укључи обавештења преко претраживача",
- "`x` uploaded a video": "`x`је објавио/ла видео",
- "`x` is live": "`x` емитује уживо",
- "preferences_category_data": "Подешавања о подацима",
- "Clear watch history": "Обришите историју прегледања",
- "Import/export data": "Увезите или извезите податке",
- "Change password": "Промените лозинку",
- "Manage subscriptions": "Управљајте праћењима",
- "Manage tokens": "Управљајте токенима",
- "Watch history": "Историја прегледања",
- "Delete account": "Избришите налог",
- "preferences_category_admin": "Подешавања администратора",
- "preferences_default_home_label": "Подразумевана главна страница: ",
- "preferences_feed_menu_label": "Мени довода: ",
- "CAPTCHA enabled: ": "CAPTCHA укључена?: ",
- "Login enabled: ": "Пријава укључена?: ",
- "Registration enabled: ": "Регистрација укључена?: ",
+ "preferences_dark_mode_label": "Изглед/Тема: ",
+ "dark": "тамно",
+ "light": "светло",
+ "preferences_thin_mode_label": "Компактни режим: ",
+ "preferences_category_subscription": "Подешавања праћења",
+ "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": "име канала - обрнуто",
+ "Only show latest video from channel: ": "Приказуј последње видео клипове само са канала: ",
+ "Only show latest unwatched video from channel: ": "Прикажи само последње видео клипове који нису погледани са канала: ",
+ "preferences_unseen_only_label": "Прикажи само видео клипове који нису погледани: ",
+ "preferences_notifications_only_label": "Прикажи само обавештења (ако их уопште има): ",
+ "Enable web notifications": "Омогући обавештења у веб претраживачу",
+ "`x` uploaded a video": "`x` је отпремио/ла видео клип",
+ "`x` is live": "`x` преноси уживо",
+ "preferences_category_data": "Подешавања података",
+ "Clear watch history": "Обриши историју гледања",
+ "Import/export data": "Увези/Извези податке",
+ "Change password": "Промени лозинку",
+ "Manage subscriptions": "Управљај записима",
+ "Manage tokens": "Управљај жетонима",
+ "Watch history": "Историја гледања",
+ "Delete account": "Обриши налог",
+ "preferences_category_admin": "Администраторска подешавања",
+ "preferences_default_home_label": "Подразумевана почетна страница: ",
+ "preferences_feed_menu_label": "Доводна страница: ",
+ "CAPTCHA enabled: ": "CAPTCHA омогућена: ",
+ "Login enabled: ": "Пријава омогућена: ",
+ "Registration enabled: ": "Регистрација омогућена: ",
"Save preferences": "Сачувај подешавања",
"Subscription manager": "Управљање праћењима",
- "Token manager": "Управљање токенима",
- "Token": "Токен",
+ "Token manager": "Управљање жетонима",
+ "Token": "Жетон",
"`x` subscriptions": {
- "": "`x`праћења"
+ "": "`x` праћења",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` праћење"
},
"`x` tokens": {
- "": "`x`токена"
+ "": "`x` жетона",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` жетон"
},
- "Import/export": "Увези/извези",
- "unsubscribe": "укини праћење",
+ "Import/export": "Увези/Извези",
+ "unsubscribe": "прекини са праћењем",
"revoke": "опозови",
"Subscriptions": "Праћења",
"`x` unseen notifications": {
- "": "`x` непрочитаних обавештења"
+ "": "`x` непрочитаних обавештења",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` непрегледано обавештење"
},
"search": "претрага",
- "Log out": "Одјавите се",
- "Source available here.": "Изворни код доступан овде.",
- "View JavaScript license information.": "Прикажи информације о JavaScript лиценци.",
- "View privacy policy.": "Прикажи извештај о приватности.",
+ "Log out": "Одјава",
+ "Source available here.": "Изворна кода је овде доступна.",
+ "View JavaScript license information.": "Погледај информације лиценце везане за JavaScript.",
+ "View privacy policy.": "Погледај извештај о приватности.",
"Trending": "У тренду",
"Public": "Јавно",
- "Unlisted": "По позиву",
+ "Unlisted": "Ненаведено",
"Private": "Приватно",
- "View all playlists": "Прикажи све плејлисте",
+ "View all playlists": "Прегледај све плеј листе",
"Updated `x` ago": "Ажурирано пре `x`",
- "Delete playlist `x`?": "Избриши плејлисту `x`?",
- "Delete playlist": "Избриши плејлисту",
- "Create playlist": "Направи плејлисту",
+ "Delete playlist `x`?": "Обриши плеј листу `x`?",
+ "Delete playlist": "Обриши плеј листу",
+ "Create playlist": "Направи плеј листу",
"Title": "Наслов",
- "Playlist privacy": "Видљивост плејлисте",
- "Editing playlist `x`": "Уређујете плејлисту `x`",
- "Watch on YouTube": "Гледајте на YouTube-у",
- "Hide annotations": "Сакриј анотације",
- "Show annotations": "Прикажи анотације",
+ "Playlist privacy": "Подешавања приватности плеј листе",
+ "Editing playlist `x`": "Измена плеј листе `x`",
+ "Watch on YouTube": "Гледај на YouTube-у",
+ "Hide annotations": "Сакриј напомене",
+ "Show annotations": "Прикажи напомене",
"Genre: ": "Жанр: ",
"License: ": "Лиценца: ",
"Engagement: ": "Ангажовање: ",
"Whitelisted regions: ": "Дозвољене области: ",
"Blacklisted regions: ": "Забрањене области: ",
"`x` views": {
- "": "`x` прегледа"
+ "": "`x` прегледа",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` преглед"
},
- "Premieres in `x`": "Емитује се уживо за `x`",
- "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Здраво! Изгледа да је искључен JavaScript. Кликните овде да бисте приказали коментаре. Требаће мало дуже да се учитају.",
- "View YouTube comments": "Прикажи коментаре са YouTube-а",
- "View more comments on Reddit": "Прикажи још коментара на Reddit-у",
- "View Reddit comments": "Прикажи коментаре са Reddit-а",
+ "Premieres in `x`": "Премера у `x`",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Хеј! Изгледа да сте онемогућили JavaScript. Кликните овде да видите коментаре, чувајте на уму да ово може да потраје дуже док се не учитају.",
+ "View YouTube comments": "Прикажи YouTube коментаре",
+ "View more comments on Reddit": "Прикажи више коментара на Reddit-у",
+ "View Reddit comments": "Прикажи Reddit коментаре",
"Hide replies": "Сакриј одговоре",
"Show replies": "Прикажи одговоре",
- "Incorrect password": "Неисправна лозинка",
- "Current version: ": "Тренутна верзија: "
+ "Incorrect password": "Нетачна лозинка",
+ "Current version: ": "Тренутна верзија: ",
+ "Wilson score: ": "Wилсонова оцена: ",
+ "Burmese": "Бурмански",
+ "preferences_quality_dash_label": "Преферирани квалитет DASH видео формата: ",
+ "Erroneous token": "Погрешан жетон",
+ "Quota exceeded, try again in a few hours": "Квота је премашена, молимо вас да покушате поново за пар сати",
+ "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Неуспешна пријава, проверите да ли сте упалили двофакторну аутентикацију (Аутентикатор или СМС).",
+ "CAPTCHA is a required field": "CAPTCHA је обавезно поље",
+ "No such user": "Непостојећи корисник",
+ "Chinese (Traditional)": "Кинески (Традиционални)",
+ "adminprefs_modified_source_code_url_label": "УРЛ веза до складишта са Измењеном Изворном Кодом",
+ "`x` hours": {
+ "": "`x` сати",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` сат"
+ },
+ "Lao": "Лаоски",
+ "Czech": "Чешки",
+ "Kannada": "Канада (Језик)",
+ "Polish": "Пољски",
+ "Cebuano": "Себуано",
+ "preferences_show_nick_label": "Прикажи надимке на врху: ",
+ "Report statistics: ": "Извештавај о статистици: ",
+ "Show more": "Прикажи више",
+ "Login failed. This may be because two-factor authentication is not turned on for your account.": "Неуспешна пријава! Ово се можда дешава јер двофакторна аутентикација није омогућена на vашем налогу.",
+ "Wrong answer": "Погрешан одговор",
+ "Hidden field \"token\" is a required field": "Сакривено \"token\" поље је обавезно",
+ "English": "Енглески",
+ "Albanian": "Албански",
+ "Amharic": "Амхарски",
+ "Azerbaijani": "Азербејџански",
+ "Basque": "Баскијски",
+ "Belarusian": "Белоруски",
+ "Chinese (Simplified)": "Кинески (Поједностављени)",
+ "Croatian": "Хрватски",
+ "Dutch": "Холандски",
+ "Esperanto": "Есперанто",
+ "Finnish": "Фински",
+ "French": "Француски",
+ "Georgian": "Грузијски",
+ "Greek": "Грчки",
+ "Hausa": "Хауса",
+ "video": "Видео",
+ "playlist": "Плеј листа",
+ "movie": "Филм",
+ "long": "Дуго (> 20 минута)",
+ "creative_commons": "Creative Commons (Лиценца)",
+ "live": "Уживо",
+ "location": "Локација",
+ "filter": "Филтер",
+ "next_steps_error_message": "Након чега би требали пробати: ",
+ "footer_donate_page": "Донирај",
+ "footer_documentation": "Документација",
+ "footer_modfied_source_code": "Измењена Изворна Кода",
+ "preferences_region_label": "Држава порекла садржаја: ",
+ "preferences_category_misc": "Остала подешавања",
+ "User ID is a required field": "Кориснички ИД је обавезно поље",
+ "Password is a required field": "Лозинка је обавезно поље",
+ "Wrong username or password": "Погрешно корисничко име или лозинка",
+ "Please sign in using 'Log in with Google'": "Молимо Вас да се пријавите помоћу 'Log in with Google'",
+ "Password cannot be empty": "Лозинка не може бити празна",
+ "Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 карактера",
+ "Invidious Private Feed for `x`": "Инвидиоус Приватни Довод за `x`",
+ "Deleted or invalid channel": "Обрисан или непостојећи канал",
+ "This channel does not exist.": "Овај канал не постоји.",
+ "`x` points": {
+ "": "`x` поена",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` поен"
+ },
+ "Could not create mix.": "Прављење микса није успело.",
+ "Empty playlist": "Празна плеј листа",
+ "Not a playlist.": "Није плеј листа.",
+ "Playlist does not exist.": "Непостојећа плеј листа.",
+ "Could not pull trending pages.": "Учитавање 'У току' страница није успело.",
+ "Hidden field \"challenge\" is a required field": "Сакривено \"challenge\" поље је обавезно",
+ "Telugu": "Телугу",
+ "Turkish": "Турски",
+ "Urdu": "Урду",
+ "Western Frisian": "Западнофрисијски",
+ "Xhosa": "Коса (Језик)",
+ "Yiddish": "Јидиш",
+ "`x` years": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` година",
+ "": "`x` година"
+ },
+ "`x` weeks": {
+ "": "`x` седмица",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` седмица"
+ },
+ "Hawaiian": "Хавајски",
+ "Hmong": "Хмонг",
+ "Hungarian": "Мађарски",
+ "Igbo": "Игбо",
+ "Javanese": "Јавански",
+ "Khmer": "Кмерски",
+ "Kyrgyz": "Киргиски",
+ "Macedonian": "Македонски",
+ "Maori": "Маори (Језик)",
+ "Marathi": "Маратхи",
+ "Nepali": "Непалски",
+ "Norwegian Bokmål": "Норвешки Бокмал",
+ "Nyanja": "Чева",
+ "Russian": "Руски",
+ "Scottish Gaelic": "Шкотски Гелски",
+ "Shona": "Шона",
+ "Slovak": "Словачки",
+ "Spanish (Latin America)": "Шпански (Јужна Америка)",
+ "Sundanese": "Сундски",
+ "Swahili": "Свахили",
+ "Tajik": "Таџички",
+ "`x` days": {
+ "": "`x` дана",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` дан"
+ },
+ "`x` minutes": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` минут",
+ "": "`x` минута"
+ },
+ "`x` seconds": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` секунда",
+ "": "`x` секунди"
+ },
+ "Search": "Претрага",
+ "Rating: ": "Ocena/e: ",
+ "Default": "Подразумеван/о",
+ "News": "Вести",
+ "Download": "Преузми",
+ "(edited)": "(измењено)",
+ "`x` marked it with a ❤": "`x` је означио/ла ово са ❤",
+ "Audio mode": "Аудио мод",
+ "Videos": "Видео клипови",
+ "views": "Број прегледа",
+ "features": "Карактеристике",
+ "today": "Данас",
+ "%A %B %-d, %Y": "%A %B %-d, %Y",
+ "preferences_locale_label": "Језик: ",
+ "Persian": "Перзијски",
+ "View `x` comments": {
+ "": "Прикажи `x` коментара",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "Прикажи `x` коментар"
+ },
+ "channel": "Канал",
+ "Haitian Creole": "Хаићански Креолски",
+ "Armenian": "Јерменски",
+ "View `x` replies": {
+ "": "Прикажи `x` одговора",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "Прикажи `x` одговор"
+ },
+ "next_steps_error_message_go_to_youtube": "Иди на YouTube",
+ "`x` months": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` месец",
+ "": "`x` месеци"
+ },
+ "Indonesian": "Индонежански",
+ "preferences_vr_mode_label": "Интерактивни видео клипови у 360 степени: ",
+ "Switch Invidious Instance": "Промени Invidious инстанцу",
+ "Portuguese": "Португалски",
+ "week": "Ове седмице",
+ "show": "Емисија",
+ "Fallback comments: ": "Коментари у случају отказивања: ",
+ "hdr": "Видео Високе Резолуције",
+ "About": "О програму",
+ "Kazakh": "Казашки",
+ "Shared `x`": "Подељено `x`",
+ "Playlists": "Плеј листе",
+ "Yoruba": "Јоруба",
+ "Erroneous challenge": "Погрешан изазов",
+ "Danish": "Дански",
+ "Could not get channel info.": "Узимање података о каналу није успело.",
+ "hd": "HD",
+ "Slovenian": "Словеначки",
+ "Load more": "Учитај више",
+ "German": "Немачки",
+ "Luxembourgish": "Луксембуршки",
+ "Mongolian": "Монголски",
+ "Latvian": "Летонски",
+ "channel:`x`": "kanal:`x`",
+ "Southern Sotho": "Јужни Сото",
+ "Popular": "Популарно",
+ "Gujarati": "Гуџарати",
+ "year": "Ове године",
+ "Irish": "Ирски",
+ "YouTube comment permalink": "YouTube коментар трајна веза",
+ "Malagasy": "Малгашки",
+ "Token is expired, please try again": "Жетон је истекао, молимо вас да покушате поново",
+ "short": "Кратко (< 4 минуте)",
+ "Samoan": "Самоански",
+ "Tamil": "Тамилски",
+ "Ukrainian": "Украјински",
+ "permalink": "трајна веза",
+ "Pashto": "Паштунски",
+ "Community": "Заједница",
+ "Sindhi": "Синди",
+ "Could not fetch comments": "Узимање коментара није успело",
+ "Bangla": "Бангла/Бенгалски",
+ "Uzbek": "Узбечки",
+ "Lithuanian": "Литвански",
+ "Icelandic": "Исландски",
+ "Thai": "Тајски",
+ "month": "Овај месец",
+ "content_type": "Тип",
+ "hour": "Последњи сат",
+ "Spanish": "Шпански",
+ "date": "Датум отпремања",
+ "View as playlist": "Погледај као плеј листу",
+ "relevance": "Релевантност",
+ "Estonian": "Естонски",
+ "Sinhala": "Синхалешки",
+ "Corsican": "Корзикански",
+ "Filipino": "Филипино",
+ "Gaming": "Игрице",
+ "Movies": "Филмови",
+ "rating": "Оцене",
+ "Top enabled: ": "Врх омогућен: ",
+ "Released under the AGPLv3 on Github.": "Избачено под лиценцом AGPLv3 на Github-у.",
+ "Afrikaans": "Африканс",
+ "preferences_automatic_instance_redirect_label": "Аутоматско пребацивање на другу инстанцу у случају отказивања (пречи ће назад на редирецт.инвидиоус.ио): ",
+ "Invalid TFA code": "Неважећа TFA кода",
+ "Please log in": "Молимо вас да се пријавите",
+ "English (auto-generated)": "Енглески (аутоматски генерисано)",
+ "Hindi": "Хинди",
+ "Italian": "Талијански",
+ "Malayalam": "Малајалам",
+ "Punjabi": "Пунџаби",
+ "Somali": "Сомалијски",
+ "Vietnamese": "Вијетнамски",
+ "Welsh": "Велшки",
+ "Zulu": "Зулу",
+ "Maltese": "Малтешки",
+ "Swedish": "Шведски",
+ "Music": "Музика",
+ "Download as: ": "Преузми као: ",
+ "duration": "Трајање",
+ "sort": "Поредај према",
+ "subtitles": "Титл/Превод",
+ "preferences_extend_desc_label": "Аутоматски прикажи цео опис видеа: ",
+ "Show less": "Прикажи мање",
+ "Broken? Try another Invidious Instance": "Не функционише исправно? Пробајте другу Invidious инстанцу",
+ "Family friendly? ": "Погодно за породицу? ",
+ "Premieres `x`": "Премерe у `x`",
+ "Bosnian": "Босански",
+ "Catalan": "Каталонски",
+ "Japanese": "Јапански",
+ "Latin": "Латински",
+ "next_steps_error_message_refresh": "Освежи страницу",
+ "footer_original_source_code": "Оригинална Изворна Кода",
+ "Romanian": "Румунски",
+ "Serbian": "Српски",
+ "Top": "Врх",
+ "Video mode": "Видео мод",
+ "footer_source_code": "Изворна Кода",
+ "3d": "3D",
+ "4k": "4K",
+ "Erroneous CAPTCHA": "Погрешна CAPTCHA",
+ "`x` ago": "пре `x`",
+ "Arabic": "Арапски",
+ "Bulgarian": "Бугарски",
+ "Galician": "Галицијски",
+ "Hebrew": "Хебрејски",
+ "Korean": "Корејски",
+ "Kurdish": "Курдски",
+ "Malay": "Малајски"
}
diff --git a/locales/tr.json b/locales/tr.json
index 6e753bfc..cf427666 100644
--- a/locales/tr.json
+++ b/locales/tr.json
@@ -433,5 +433,34 @@
"adminprefs_modified_source_code_url_label": "Değiştirilmiş kaynak kodları deposunun URL'si",
"footer_donate_page": "Bağış yap",
"preferences_region_label": "İçerik ülkesi: ",
- "preferences_quality_dash_label": "Tercih edilen dash video kalitesi: "
+ "preferences_quality_dash_label": "Tercih edilen DASH video kalitesi: ",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_dash_option_best": "En iyi",
+ "preferences_quality_dash_option_worst": "En kötü",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_480p": "480p",
+ "preferences_quality_dash_option_360p": "360p",
+ "preferences_quality_dash_option_240p": "240p",
+ "preferences_quality_dash_option_144p": "144p",
+ "invidious": "Invidious",
+ "none": "yok",
+ "videoinfo_started_streaming_x_ago": "`x` önce yayına başladı",
+ "videoinfo_youTube_embed_link": "Göm",
+ "videoinfo_invidious_embed_link": "Bağlantıyı Göm",
+ "user_created_playlists": "`x` oluşturulan oynatma listeleri",
+ "user_saved_playlists": "`x` kaydedilen oynatma listeleri",
+ "preferences_quality_option_small": "Küçük",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_quality_option_medium": "Orta",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "Video unavailable": "Video kullanılamıyor",
+ "preferences_quality_option_dash": "DASH (uyarlanabilir kalite)",
+ "preferences_quality_dash_option_auto": "Otomatik",
+ "purchased": "Satın alınan",
+ "360": "360°",
+ "videoinfo_watch_on_youTube": "YouTube'da izle",
+ "download_subtitles": "Alt yazılar - `x` (.vtt)",
+ "preferences_save_player_pos_label": "Geçerli video zamanını kaydet: "
}
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index 8821ccf9..ed5d82ce 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -433,5 +433,5 @@
"footer_original_source_code": "原始源代码",
"footer_donate_page": "捐赠",
"preferences_region_label": "内容国家: ",
- "preferences_quality_dash_label": "首选 dash 视频分辨率: "
+ "preferences_quality_dash_label": "首选 DASH 视频分辨率: "
}
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index b23e439a..aad51069 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -433,5 +433,34 @@
"adminprefs_modified_source_code_url_label": "修改後的原始碼倉庫 URL",
"footer_donate_page": "捐款",
"preferences_region_label": "內容國家: ",
- "preferences_quality_dash_label": "偏好的 dash 影片品質: "
+ "preferences_quality_dash_label": "偏好的 DASH 影片品質: ",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_dash_option_worst": "最差",
+ "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",
+ "invidious": "Invidious",
+ "purchased": "已購買",
+ "360": "360°",
+ "none": "無",
+ "videoinfo_started_streaming_x_ago": "`x` 前開始串流",
+ "videoinfo_watch_on_youTube": "在 YouTube 上觀看",
+ "videoinfo_youTube_embed_link": "嵌入",
+ "videoinfo_invidious_embed_link": "嵌入連結",
+ "download_subtitles": "字幕 - `x` (.vtt)",
+ "user_created_playlists": "`x` 已建立的播放清單",
+ "user_saved_playlists": "`x` 已儲存的播放清單",
+ "Video unavailable": "影片不可用",
+ "preferences_quality_option_small": "小",
+ "preferences_quality_option_dash": "DASH(主動調整品質)",
+ "preferences_quality_option_medium": "中等",
+ "preferences_quality_dash_option_auto": "自動",
+ "preferences_quality_dash_option_best": "最佳",
+ "preferences_save_player_pos_label": "儲存目前影片時間: "
}
diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr
index 002c8bdd..4215b2bd 100644
--- a/spec/helpers_spec.cr
+++ b/spec/helpers_spec.cr
@@ -77,16 +77,6 @@ describe "Helper" do
end
end
- describe "#produce_comment_reply_continuation" do
- it "correctly produces a continuation token for replies to a given comment" do
- produce_comment_reply_continuation("cIHQWOoJeag", "UCq6VFHwMzcMXbuKyG7SQYIg", "Ugx1IP_wGVv3WtGWcdV4AaABAg").should eq("EiYSC2NJSFFXT29KZWFnwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd4MUlQX3dHVnYzV3RHV2NkVjRBYUFCQWciAggAKhhVQ3E2VkZId016Y01YYnVLeUc3U1FZSWcyC2NJSFFXT29KZWFnQAFICg%3D%3D")
-
- produce_comment_reply_continuation("cIHQWOoJeag", "UCq6VFHwMzcMXbuKyG7SQYIg", "Ugza62y_TlmTu9o2RfF4AaABAg").should eq("EiYSC2NJSFFXT29KZWFnwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd6YTYyeV9UbG1UdTlvMlJmRjRBYUFCQWciAggAKhhVQ3E2VkZId016Y01YYnVLeUc3U1FZSWcyC2NJSFFXT29KZWFnQAFICg%3D%3D")
-
- produce_comment_reply_continuation("_cE8xSu6swE", "UC1AZY74-dGVPe6bfxFwwEMg", "UgyBUaRGHB9Jmt1dsUZ4AaABAg").should eq("EiYSC19jRTh4U3U2c3dFwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd5QlVhUkdIQjlKbXQxZHNVWjRBYUFCQWciAggAKhhVQzFBWlk3NC1kR1ZQZTZiZnhGd3dFTWcyC19jRTh4U3U2c3dFQAFICg%3D%3D")
- end
- end
-
describe "#produce_channel_community_continuation" do
it "correctly produces a continuation token for a channel community" do
produce_channel_community_continuation("UCCj956IF62FbT7Gouszaj9w", "Egljb21tdW5pdHm4").should eq("4qmFsgIsEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaEEVnbGpiMjF0ZFc1cGRIbTQ%3D")
@@ -107,15 +97,6 @@ describe "Helper" do
end
end
- describe "#extract_plid" do
- it "correctly extracts playlist ID from trending URL" do
- extract_plid("/feed/trending?bp=4gIuCggvbS8wNHJsZhIiUExGZ3F1TG5MNTlhbVBud2pLbmNhZUp3MDYzZlU1M3Q0cA%3D%3D").should eq("PLFgquLnL59amPnwjKncaeJw063fU53t4p")
- extract_plid("/feed/trending?bp=4gIvCgkvbS8wYnp2bTISIlBMaUN2Vkp6QnVwS2tDaFNnUDdGWFhDclo2aEp4NmtlTm0%3D").should eq("PLiCvVJzBupKkChSgP7FXXCrZ6hJx6keNm")
- extract_plid("/feed/trending?bp=4gIuCggvbS8wNWpoZxIiUEwzWlE1Q3BOdWxRbUtPUDNJekdsYWN0V1c4dklYX0hFUA%3D%3D").should eq("PL3ZQ5CpNulQmKOP3IzGlactWW8vIX_HEP")
- extract_plid("/feed/trending?bp=4gIuCggvbS8wMnZ4bhIiUEx6akZiYUZ6c21NUnFhdEJnVTdPeGNGTkZhQ2hqTkVERA%3D%3D").should eq("PLzjFbaFzsmMRqatBgU7OxcFNFaChjNEDD")
- end
- end
-
describe "#sign_token" do
it "correctly signs a given hash" do
token = {
diff --git a/src/invidious.cr b/src/invidious.cr
index 21a12ff2..ade13608 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -408,7 +408,7 @@ define_video_playback_routes()
# Users
post "/watch_ajax" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -478,7 +478,7 @@ end
# /modify_notifications?receive_all_updates=false&receive_no_updates=false
# will "unding" all subscriptions.
get "/modify_notifications" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -551,7 +551,7 @@ get "/modify_notifications" do |env|
end
post "/subscription_ajax" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -621,7 +621,7 @@ post "/subscription_ajax" do |env|
end
get "/subscription_manager" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -724,7 +724,7 @@ get "/subscription_manager" do |env|
end
get "/data_control" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
referer = get_referer(env)
@@ -739,7 +739,7 @@ get "/data_control" do |env|
end
post "/data_control" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
referer = get_referer(env)
@@ -902,7 +902,7 @@ post "/data_control" do |env|
end
get "/change_password" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -920,7 +920,7 @@ get "/change_password" do |env|
end
post "/change_password" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -976,7 +976,7 @@ post "/change_password" do |env|
end
get "/delete_account" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -994,7 +994,7 @@ get "/delete_account" do |env|
end
post "/delete_account" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -1028,7 +1028,7 @@ post "/delete_account" do |env|
end
get "/clear_watch_history" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -1046,7 +1046,7 @@ get "/clear_watch_history" do |env|
end
post "/clear_watch_history" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -1071,7 +1071,7 @@ post "/clear_watch_history" do |env|
end
get "/authorize_token" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -1099,7 +1099,7 @@ get "/authorize_token" do |env|
end
post "/authorize_token" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -1147,7 +1147,7 @@ post "/authorize_token" do |env|
end
get "/token_manager" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -1165,7 +1165,7 @@ get "/token_manager" do |env|
end
post "/token_ajax" do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -1225,7 +1225,7 @@ end
{"/channel/:ucid/live", "/user/:user/live", "/c/:user/live"}.each do |route|
get route do |env|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
# Appears to be a bug in routing, having several routes configured
# as `/a/:a`, `/b/:a`, `/c/:a` results in 404
@@ -1347,7 +1347,7 @@ error 404 do |env|
end
error 500 do |env, ex|
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
error_template(500, ex)
end
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index ffdce000..12a80bc4 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -60,8 +60,6 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b
case cursor
when nil, ""
ctoken = produce_comment_continuation(id, cursor: "", sort_by: sort_by)
- # when .starts_with? "Ug"
- # ctoken = produce_comment_reply_continuation(id, video.ucid, cursor)
when .starts_with? "ADSJ"
ctoken = produce_comment_continuation(id, cursor: cursor, sort_by: sort_by)
else
@@ -575,7 +573,9 @@ def content_to_comment_html(content)
url = "/watch?v=#{url.request_target.lstrip('/')}"
elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com")
if url.path == "/redirect"
- url = HTTP::Params.parse(url.query.not_nil!)["q"]
+ # Sometimes, links can be corrupted (why?) so make sure to fallback
+ # nicely. See https://github.com/iv-org/invidious/issues/2682
+ url = HTTP::Params.parse(url.query.not_nil!)["q"]? || ""
else
url = url.request_target
end
@@ -645,38 +645,3 @@ def produce_comment_continuation(video_id, cursor = "", sort_by = "top")
return continuation
end
-
-def produce_comment_reply_continuation(video_id, ucid, comment_id)
- object = {
- "2:embedded" => {
- "2:string" => video_id,
- "24:varint" => 1_i64,
- "25:varint" => 1_i64,
- "28:varint" => 1_i64,
- "36:embedded" => {
- "5:varint" => -1_i64,
- "8:varint" => 0_i64,
- },
- },
- "3:varint" => 6_i64,
- "6:embedded" => {
- "3:embedded" => {
- "2:string" => comment_id,
- "4:embedded" => {
- "1:varint" => 0_i64,
- },
- "5:string" => ucid,
- "6:string" => video_id,
- "8:varint" => 1_i64,
- "9:varint" => 10_i64,
- },
- },
- }
-
- continuation = object.try { |i| Protodec::Any.cast_json(i) }
- .try { |i| Protodec::Any.from_json(i) }
- .try { |i| Base64.urlsafe_encode(i) }
- .try { |i| URI.encode_www_form(i) }
-
- return continuation
-end
diff --git a/src/invidious/config.cr b/src/invidious/config.cr
index 578e31fd..c4a8bf83 100644
--- a/src/invidious/config.cr
+++ b/src/invidious/config.cr
@@ -42,6 +42,7 @@ struct ConfigPreferences
property volume : Int32 = 100
property vr_mode : Bool = true
property show_nick : Bool = true
+ property save_player_pos : Bool = false
def to_tuple
{% begin %}
diff --git a/src/invidious/helpers/errors.cr b/src/invidious/helpers/errors.cr
index e5c77fbc..d10762c5 100644
--- a/src/invidious/helpers/errors.cr
+++ b/src/invidious/helpers/errors.cr
@@ -22,7 +22,7 @@ def github_details(summary : String, content : String)
return HTML.escape(details)
end
-def error_template_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, exception : Exception)
+def error_template_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception)
if exception.is_a?(InfoException)
return error_template_helper(env, locale, status_code, exception.message || "")
end
@@ -46,7 +46,7 @@ def error_template_helper(env : HTTP::Server::Context, locale : Hash(String, JSO
return templated "error"
end
-def error_template_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String)
+def error_template_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String)
env.response.content_type = "text/html"
env.response.status_code = status_code
error_message = translate(locale, message)
@@ -58,7 +58,7 @@ macro error_atom(*args)
error_atom_helper(env, locale, {{*args}})
end
-def error_atom_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, exception : Exception)
+def error_atom_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception)
if exception.is_a?(InfoException)
return error_atom_helper(env, locale, status_code, exception.message || "")
end
@@ -67,7 +67,7 @@ def error_atom_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::A
return "<error>#{exception.inspect_with_backtrace}</error>"
end
-def error_atom_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String)
+def error_atom_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String)
env.response.content_type = "application/atom+xml"
env.response.status_code = status_code
return "<error>#{message}</error>"
@@ -77,7 +77,7 @@ macro error_json(*args)
error_json_helper(env, locale, {{*args}})
end
-def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, exception : Exception, additional_fields : Hash(String, Object) | Nil)
+def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception, additional_fields : Hash(String, Object) | Nil)
if exception.is_a?(InfoException)
return error_json_helper(env, locale, status_code, exception.message || "", additional_fields)
end
@@ -90,11 +90,11 @@ def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::A
return error_message.to_json
end
-def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, exception : Exception)
+def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception)
return error_json_helper(env, locale, status_code, exception, nil)
end
-def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String, additional_fields : Hash(String, Object) | Nil)
+def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String, additional_fields : Hash(String, Object) | Nil)
env.response.content_type = "application/json"
env.response.status_code = status_code
error_message = {"error" => message}
@@ -104,11 +104,11 @@ def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::A
return error_message.to_json
end
-def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String)
+def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String)
error_json_helper(env, locale, status_code, message, nil)
end
-def error_redirect_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil)
+def error_redirect_helper(env : HTTP::Server::Context, locale : String?)
request_path = env.request.path
if request_path.starts_with?("/search") || request_path.starts_with?("/watch") ||
diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr
index c3b356a9..96a78eb9 100644
--- a/src/invidious/helpers/helpers.cr
+++ b/src/invidious/helpers/helpers.cr
@@ -190,7 +190,7 @@ def create_notification_stream(env, topics, connection_channel)
connection = Channel(PQ::Notification).new(8)
connection_channel.send({true, connection})
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
since = env.params.query["since"]?.try &.to_i?
id = 0
diff --git a/src/invidious/helpers/i18n.cr b/src/invidious/helpers/i18n.cr
index 9e42fad0..fd3ddbad 100644
--- a/src/invidious/helpers/i18n.cr
+++ b/src/invidious/helpers/i18n.cr
@@ -1,46 +1,47 @@
# "bn_BD" => load_locale("bn_BD"), # Bengali (Bangladesh) [Incomplete]
# "eu" => load_locale("eu"), # Basque [Incomplete]
-# "si" => load_locale("si"), # Sinhala [Incomplete]
# "sk" => load_locale("sk"), # Slovak [Incomplete]
-# "sr" => load_locale("sr"), # Serbian [Incomplete]
-# "sr_Cyrl" => load_locale("sr_Cyrl"), # Serbian (cyrillic) [Incomplete]
-LOCALES = {
- "ar" => load_locale("ar"), # Arabic
- "cs" => load_locale("cs"), # Czech
- "da" => load_locale("da"), # Danish
- "de" => load_locale("de"), # German
- "el" => load_locale("el"), # Greek
- "en-US" => load_locale("en-US"), # English (US)
- "eo" => load_locale("eo"), # Esperanto
- "es" => load_locale("es"), # Spanish
- "fa" => load_locale("fa"), # Persian
- "fi" => load_locale("fi"), # Finnish
- "fr" => load_locale("fr"), # French
- "he" => load_locale("he"), # Hebrew
- "hr" => load_locale("hr"), # Croatian
- "hu-HU" => load_locale("hu-HU"), # Hungarian
- "id" => load_locale("id"), # Indonesian
- "is" => load_locale("is"), # Icelandic
- "it" => load_locale("it"), # Italian
- "ja" => load_locale("ja"), # Japanese
- "ko" => load_locale("ko"), # Korean
- "lt" => load_locale("lt"), # Lithuanian
- "nb-NO" => load_locale("nb-NO"), # Norwegian Bokmål
- "nl" => load_locale("nl"), # Dutch
- "pl" => load_locale("pl"), # Polish
- "pt" => load_locale("pt"), # Portuguese
- "pt-BR" => load_locale("pt-BR"), # Portuguese (Brazil)
- "pt-PT" => load_locale("pt-PT"), # Portuguese (Portugal)
- "ro" => load_locale("ro"), # Romanian
- "ru" => load_locale("ru"), # Russian
- "sv-SE" => load_locale("sv-SE"), # Swedish
- "tr" => load_locale("tr"), # Turkish
- "uk" => load_locale("uk"), # Ukrainian
- "vi" => load_locale("vi"), # Vietnamese
- "zh-CN" => load_locale("zh-CN"), # Chinese (Simplified)
- "zh-TW" => load_locale("zh-TW"), # Chinese (Traditional)
+LOCALES_LIST = {
+ "ar" => "العربية", # Arabic
+ "cs" => "Čeština", # Czech
+ "da" => "Dansk", # Danish
+ "de" => "Deutsch", # German
+ "el" => "Ελληνικά", # Greek
+ "en-US" => "English", # English
+ "eo" => "Esperanto", # Esperanto
+ "es" => "Español", # Spanish
+ "fa" => "فارسی", # Persian
+ "fi" => "Suomi", # Finnish
+ "fr" => "Français", # French
+ "he" => "עברית", # Hebrew
+ "hr" => "Hrvatski", # Croatian
+ "hu-HU" => "Magyar Nyelv", # Hungarian
+ "id" => "Bahasa Indonesia", # Indonesian
+ "is" => "Íslenska", # Icelandic
+ "it" => "Italiano", # Italian
+ "ja" => "日本語", # Japanese
+ "ko" => "한국어", # Korean
+ "lt" => "Lietuvių", # Lithuanian
+ "nb-NO" => "Norsk bokmål", # Norwegian Bokmål
+ "nl" => "Nederlands", # Dutch
+ "pl" => "Polski", # Polish
+ "pt" => "Português", # Portuguese
+ "pt-BR" => "Português Brasileiro", # Portuguese (Brazil)
+ "pt-PT" => "Português de Portugal", # Portuguese (Portugal)
+ "ro" => "Română", # Romanian
+ "ru" => "русский", # Russian
+ "sr" => "srpski (latinica)", # Serbian (Latin)
+ "sr_Cyrl" => "српски (ћирилица)", # Serbian (Cyrillic)
+ "sv-SE" => "Svenska", # Swedish
+ "tr" => "Türkçe", # Turkish
+ "uk" => "Українська", # Ukrainian
+ "vi" => "Tiếng Việt", # Vietnamese
+ "zh-CN" => "汉语", # Chinese (Simplified)
+ "zh-TW" => "漢語", # Chinese (Traditional)
}
+LOCALES = load_all_locales()
+
CONTENT_REGIONS = {
"AE", "AR", "AT", "AU", "AZ", "BA", "BD", "BE", "BG", "BH", "BO", "BR", "BY",
"CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE",
@@ -53,35 +54,50 @@ CONTENT_REGIONS = {
"YE", "ZA", "ZW",
}
-def load_locale(name)
- return JSON.parse(File.read("locales/#{name}.json")).as_h
+def load_all_locales
+ locales = {} of String => Hash(String, JSON::Any)
+
+ LOCALES_LIST.each_key do |name|
+ locales[name] = JSON.parse(File.read("locales/#{name}.json")).as_h
+ end
+
+ return locales
end
-def translate(locale : Hash(String, JSON::Any) | Nil, translation : String, text : String | Nil = nil)
- # if locale && !locale[translation]?
- # puts "Could not find translation for #{translation.dump}"
- # end
+def translate(locale : String?, key : String, text : 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)
+ LOGGER.warn("i18n: Missing translation key \"#{key}\"")
+ return key
+ end
+
+ # Default to english, whenever the locale doesn't exist,
+ # or the key requested has not been translated
+ if locale && LOCALES.has_key?(locale) && LOCALES[locale].has_key?(key)
+ raw_data = LOCALES[locale][key]
+ else
+ raw_data = LOCALES["en-US"][key]
+ end
- if locale && locale[translation]?
- case locale[translation]
- when .as_h?
- match_length = 0
+ case raw_data
+ when .as_h?
+ # Init
+ translation = ""
+ match_length = 0
- locale[translation].as_h.each do |key, value|
- if md = text.try &.match(/#{key}/)
- if md[0].size >= match_length
- translation = value.as_s
- match_length = md[0].size
- end
+ raw_data.as_h.each do |key, value|
+ if md = text.try &.match(/#{key}/)
+ if md[0].size >= match_length
+ translation = value.as_s
+ match_length = md[0].size
end
end
- when .as_s?
- if !locale[translation].as_s.empty?
- translation = locale[translation].as_s
- end
- else
- raise "Invalid translation #{translation}"
end
+ when .as_s?
+ translation = raw_data.as_s
+ else
+ raise "Invalid translation \"#{raw_data}\""
end
if text
@@ -91,7 +107,7 @@ def translate(locale : Hash(String, JSON::Any) | Nil, translation : String, text
return translation
end
-def translate_bool(locale : Hash(String, JSON::Any) | Nil, translation : Bool)
+def translate_bool(locale : String?, translation : Bool)
case translation
when true
return translate(locale, "Yes")
diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr
index f92b7b89..bfbc237c 100644
--- a/src/invidious/helpers/serialized_yt_data.cr
+++ b/src/invidious/helpers/serialized_yt_data.cr
@@ -64,7 +64,7 @@ struct SearchVideo
end
end
- def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder)
+ def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "video"
json.field "title", self.title
@@ -96,7 +96,7 @@ struct SearchVideo
end
# TODO: remove the locale and follow the crystal convention
- def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil)
+ def to_json(locale : String?, _json : Nil)
JSON.build do |json|
to_json(locale, json)
end
@@ -130,7 +130,7 @@ struct SearchPlaylist
property videos : Array(SearchPlaylistVideo)
property thumbnail : String?
- def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder)
+ def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "playlist"
json.field "title", self.title
@@ -161,7 +161,7 @@ struct SearchPlaylist
end
# TODO: remove the locale and follow the crystal convention
- def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil)
+ def to_json(locale : String?, _json : Nil)
JSON.build do |json|
to_json(locale, json)
end
@@ -183,7 +183,7 @@ struct SearchChannel
property description_html : String
property auto_generated : Bool
- def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder)
+ def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "channel"
json.field "author", self.author
@@ -214,7 +214,7 @@ struct SearchChannel
end
# TODO: remove the locale and follow the crystal convention
- def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil)
+ def to_json(locale : String?, _json : Nil)
JSON.build do |json|
to_json(locale, json)
end
@@ -234,7 +234,7 @@ class Category
property description_html : String
property badges : Array(Tuple(String, String))?
- def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder)
+ def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "category"
json.field "title", self.title
@@ -249,7 +249,7 @@ class Category
end
# TODO: remove the locale and follow the crystal convention
- def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil)
+ def to_json(locale : String?, _json : Nil)
JSON.build do |json|
to_json(locale, json)
end
diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr
index 603b4e1f..7bbbcb92 100644
--- a/src/invidious/helpers/utils.cr
+++ b/src/invidious/helpers/utils.cr
@@ -21,10 +21,17 @@ def elapsed_text(elapsed)
end
def decode_length_seconds(string)
- length_seconds = string.gsub(/[^0-9:]/, "").split(":").map &.to_i
+ length_seconds = string.gsub(/[^0-9:]/, "")
+ return 0_i32 if length_seconds.empty?
+
+ length_seconds = length_seconds.split(":").map { |x| x.to_i? || 0 }
length_seconds = [0] * (3 - length_seconds.size) + length_seconds
- length_seconds = Time::Span.new hours: length_seconds[0], minutes: length_seconds[1], seconds: length_seconds[2]
- length_seconds = length_seconds.total_seconds.to_i
+
+ length_seconds = Time::Span.new(
+ hours: length_seconds[0],
+ minutes: length_seconds[1],
+ seconds: length_seconds[2]
+ ).total_seconds.to_i32
return length_seconds
end
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index cdd9e2f6..aaf728ff 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -36,7 +36,7 @@ module Invidious::Routes::API::V1::Authenticated
env.response.content_type = "application/json"
user = env.get("user").as(User)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
max_results = env.params.query["max_results"]?.try &.to_i?
max_results ||= user.preferences.max_results
@@ -122,7 +122,7 @@ module Invidious::Routes::API::V1::Authenticated
end
def self.list_playlists(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
user = env.get("user").as(User)
@@ -141,7 +141,7 @@ module Invidious::Routes::API::V1::Authenticated
def self.create_playlist(env)
env.response.content_type = "application/json"
user = env.get("user").as(User)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
title = env.params.json["title"]?.try &.as(String).delete("<>").byte_slice(0, 150)
if !title
@@ -167,7 +167,7 @@ module Invidious::Routes::API::V1::Authenticated
end
def self.update_playlist_attribute(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
user = env.get("user").as(User)
@@ -200,7 +200,7 @@ module Invidious::Routes::API::V1::Authenticated
end
def self.delete_playlist(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
user = env.get("user").as(User)
@@ -223,7 +223,7 @@ module Invidious::Routes::API::V1::Authenticated
end
def self.insert_video_into_playlist(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
user = env.get("user").as(User)
@@ -281,7 +281,7 @@ module Invidious::Routes::API::V1::Authenticated
end
def self.delete_video_in_playlist(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
user = env.get("user").as(User)
@@ -334,7 +334,7 @@ module Invidious::Routes::API::V1::Authenticated
def self.register_token(env)
user = env.get("user").as(User)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
case env.request.headers["Content-Type"]?
when "application/x-www-form-urlencoded"
@@ -396,7 +396,7 @@ module Invidious::Routes::API::V1::Authenticated
end
def self.unregister_token(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
user = env.get("user").as(User)
scopes = env.get("scopes").as(Array(String))
diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr
index da39661c..8b6df3fd 100644
--- a/src/invidious/routes/api/v1/channels.cr
+++ b/src/invidious/routes/api/v1/channels.cr
@@ -1,6 +1,6 @@
module Invidious::Routes::API::V1::Channels
def self.home(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -124,7 +124,7 @@ module Invidious::Routes::API::V1::Channels
end
def self.latest(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -146,7 +146,7 @@ module Invidious::Routes::API::V1::Channels
end
def self.videos(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -182,7 +182,7 @@ module Invidious::Routes::API::V1::Channels
end
def self.playlists(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -219,7 +219,7 @@ module Invidious::Routes::API::V1::Channels
end
def self.community(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -242,7 +242,7 @@ module Invidious::Routes::API::V1::Channels
end
def self.search(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
diff --git a/src/invidious/routes/api/v1/feeds.cr b/src/invidious/routes/api/v1/feeds.cr
index bb8f661b..41865f34 100644
--- a/src/invidious/routes/api/v1/feeds.cr
+++ b/src/invidious/routes/api/v1/feeds.cr
@@ -1,6 +1,6 @@
module Invidious::Routes::API::V1::Feeds
def self.trending(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -25,7 +25,7 @@ module Invidious::Routes::API::V1::Feeds
end
def self.popular(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr
index 80b59fd5..1621c9ef 100644
--- a/src/invidious/routes/api/v1/misc.cr
+++ b/src/invidious/routes/api/v1/misc.cr
@@ -1,7 +1,7 @@
module Invidious::Routes::API::V1::Misc
# Stats API endpoint for Invidious
def self.stats(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
if !CONFIG.statistics_enabled
@@ -15,7 +15,7 @@ module Invidious::Routes::API::V1::Misc
# user playlists and Invidious playlists. This means that we can't
# reasonably split them yet. This should be addressed in APIv2
def self.get_playlist(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
plid = env.params.url["plid"]
@@ -84,7 +84,7 @@ module Invidious::Routes::API::V1::Misc
end
def self.mixes(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr
index 7234dcdd..a3b6c795 100644
--- a/src/invidious/routes/api/v1/search.cr
+++ b/src/invidious/routes/api/v1/search.cr
@@ -1,6 +1,6 @@
module Invidious::Routes::API::V1::Search
def self.search(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
region = env.params.query["region"]?
env.response.content_type = "application/json"
@@ -43,7 +43,7 @@ module Invidious::Routes::API::V1::Search
end
def self.search_suggestions(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
region = env.params.query["region"]?
env.response.content_type = "application/json"
diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr
index 1edee29c..4c7179ce 100644
--- a/src/invidious/routes/api/v1/videos.cr
+++ b/src/invidious/routes/api/v1/videos.cr
@@ -1,6 +1,6 @@
module Invidious::Routes::API::V1::Videos
def self.videos(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -20,7 +20,7 @@ module Invidious::Routes::API::V1::Videos
end
def self.captions(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -149,7 +149,7 @@ module Invidious::Routes::API::V1::Videos
# thumbnails for individual scenes in a video.
# See https://support.jwplayer.com/articles/how-to-add-preview-thumbnails
def self.storyboards(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json"
@@ -223,7 +223,7 @@ module Invidious::Routes::API::V1::Videos
end
def self.annotations(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "text/xml"
@@ -293,7 +293,7 @@ module Invidious::Routes::API::V1::Videos
end
def self.comments(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
region = env.params.query["region"]?
env.response.content_type = "application/json"
diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr
index 29748cd0..6cb1e1f7 100644
--- a/src/invidious/routes/channels.cr
+++ b/src/invidious/routes/channels.cr
@@ -104,7 +104,7 @@ module Invidious::Routes::Channels
# Redirects brand url channels to a normal /channel/:ucid route
def self.brand_redirect(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
# /attribution_link endpoint needs both the `a` and `u` parameter
# and in order to avoid detection from YouTube we should only send the required ones
@@ -148,7 +148,7 @@ module Invidious::Routes::Channels
end
private def self.fetch_basic_information(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
if user
diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr
index ffbf8c14..049ee344 100644
--- a/src/invidious/routes/embed.cr
+++ b/src/invidious/routes/embed.cr
@@ -2,7 +2,7 @@
module Invidious::Routes::Embed
def self.redirect(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
if plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "")
begin
@@ -26,7 +26,7 @@ module Invidious::Routes::Embed
end
def self.show(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
id = env.params.url["id"]
plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "")
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index f4a8467b..9650bcf4 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -6,7 +6,7 @@ module Invidious::Routes::Feeds
end
def self.playlists(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
referer = get_referer(env)
@@ -31,7 +31,7 @@ module Invidious::Routes::Feeds
end
def self.popular(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
if CONFIG.popular_enabled
templated "feeds/popular"
@@ -42,7 +42,7 @@ module Invidious::Routes::Feeds
end
def self.trending(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
trending_type = env.params.query["type"]?
trending_type ||= "Default"
@@ -60,7 +60,7 @@ module Invidious::Routes::Feeds
end
def self.subscriptions(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -108,7 +108,7 @@ module Invidious::Routes::Feeds
end
def self.history(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
referer = get_referer(env)
@@ -137,7 +137,7 @@ module Invidious::Routes::Feeds
# RSS feeds
def self.rss_channel(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.headers["Content-Type"] = "application/atom+xml"
env.response.content_type = "application/atom+xml"
@@ -209,7 +209,7 @@ module Invidious::Routes::Feeds
end
def self.rss_private(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.headers["Content-Type"] = "application/atom+xml"
env.response.content_type = "application/atom+xml"
@@ -253,7 +253,7 @@ module Invidious::Routes::Feeds
end
def self.rss_playlist(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.headers["Content-Type"] = "application/atom+xml"
env.response.content_type = "application/atom+xml"
@@ -374,7 +374,7 @@ module Invidious::Routes::Feeds
end
def self.push_notifications_post(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
token = env.params.url["token"]
body = env.request.body.not_nil!.gets_to_end
diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr
index 562d88e5..2a50561d 100644
--- a/src/invidious/routes/login.cr
+++ b/src/invidious/routes/login.cr
@@ -2,7 +2,7 @@
module Invidious::Routes::Login
def self.login_page(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
@@ -31,7 +31,7 @@ module Invidious::Routes::Login
end
def self.login(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env, "/feed/subscriptions")
@@ -491,7 +491,7 @@ module Invidious::Routes::Login
end
def self.signout(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
diff --git a/src/invidious/routes/misc.cr b/src/invidious/routes/misc.cr
index 3ea4c272..d6bd9571 100644
--- a/src/invidious/routes/misc.cr
+++ b/src/invidious/routes/misc.cr
@@ -3,7 +3,7 @@
module Invidious::Routes::Misc
def self.home(env)
preferences = env.get("preferences").as(Preferences)
- locale = LOCALES[preferences.locale]?
+ locale = preferences.locale
user = env.get? "user"
case preferences.default_home
@@ -29,12 +29,12 @@ module Invidious::Routes::Misc
end
def self.privacy(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
templated "privacy"
end
def self.licenses(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
rendered "licenses"
end
diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr
index 21126d7e..7b7bd03f 100644
--- a/src/invidious/routes/playlists.cr
+++ b/src/invidious/routes/playlists.cr
@@ -2,7 +2,7 @@
module Invidious::Routes::Playlists
def self.new(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -18,7 +18,7 @@ module Invidious::Routes::Playlists
end
def self.create(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -56,7 +56,7 @@ module Invidious::Routes::Playlists
end
def self.subscribe(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
referer = get_referer(env)
@@ -73,7 +73,7 @@ module Invidious::Routes::Playlists
end
def self.delete_page(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -96,7 +96,7 @@ module Invidious::Routes::Playlists
end
def self.delete(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -129,7 +129,7 @@ module Invidious::Routes::Playlists
end
def self.edit(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -169,7 +169,7 @@ module Invidious::Routes::Playlists
end
def self.update(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -213,7 +213,7 @@ module Invidious::Routes::Playlists
end
def self.add_playlist_items_page(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -260,7 +260,7 @@ module Invidious::Routes::Playlists
end
def self.playlist_ajax(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
@@ -387,7 +387,7 @@ module Invidious::Routes::Playlists
end
def self.show(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
user = env.get?("user").try &.as(User)
referer = get_referer(env)
@@ -435,7 +435,7 @@ module Invidious::Routes::Playlists
end
def self.mix(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
rdid = env.params.query["list"]?
if !rdid
diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr
index 8793d4e9..15c00700 100644
--- a/src/invidious/routes/preferences.cr
+++ b/src/invidious/routes/preferences.cr
@@ -2,7 +2,7 @@
module Invidious::Routes::PreferencesRoute
def self.show(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env)
@@ -12,7 +12,7 @@ module Invidious::Routes::PreferencesRoute
end
def self.update(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env)
video_loop = env.params.body["video_loop"]?.try &.as(String)
@@ -70,6 +70,10 @@ module Invidious::Routes::PreferencesRoute
vr_mode ||= "off"
vr_mode = vr_mode == "on"
+ save_player_pos = env.params.body["save_player_pos"]?.try &.as(String)
+ save_player_pos ||= "off"
+ save_player_pos = save_player_pos == "on"
+
show_nick = env.params.body["show_nick"]?.try &.as(String)
show_nick ||= "off"
show_nick = show_nick == "on"
@@ -165,6 +169,7 @@ module Invidious::Routes::PreferencesRoute
extend_desc: extend_desc,
vr_mode: vr_mode,
show_nick: show_nick,
+ save_player_pos: save_player_pos,
}.to_json).to_json
if user = env.get? "user"
@@ -227,7 +232,7 @@ module Invidious::Routes::PreferencesRoute
end
def self.toggle_theme(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env, unroll: false)
redirect = env.params.query["redirect"]?
diff --git a/src/invidious/routes/search.cr b/src/invidious/routes/search.cr
index 3f1e219f..c256d156 100644
--- a/src/invidious/routes/search.cr
+++ b/src/invidious/routes/search.cr
@@ -2,7 +2,7 @@
module Invidious::Routes::Search
def self.opensearch(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/opensearchdescription+xml"
XML.build(indent: " ", encoding: "UTF-8") do |xml|
@@ -18,7 +18,7 @@ module Invidious::Routes::Search
end
def self.results(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
query = env.params.query["search_query"]?
query ||= env.params.query["q"]?
@@ -37,7 +37,7 @@ module Invidious::Routes::Search
end
def self.search(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
region = env.params.query["region"]?
query = env.params.query["search_query"]?
diff --git a/src/invidious/routes/video_playback.cr b/src/invidious/routes/video_playback.cr
index 5c64f669..06ba6b8c 100644
--- a/src/invidious/routes/video_playback.cr
+++ b/src/invidious/routes/video_playback.cr
@@ -1,7 +1,7 @@
module Invidious::Routes::VideoPlayback
# /videoplayback
def self.get_video_playback(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
query_params = env.params.query
fvip = query_params["fvip"]? || "3"
@@ -240,7 +240,7 @@ module Invidious::Routes::VideoPlayback
download_widget = JSON.parse(env.params.query["download_widget"])
id = download_widget["id"].as_s
- title = download_widget["title"].as_s
+ title = URI.decode_www_form(download_widget["title"].as_s)
if label = download_widget["label"]?
return env.redirect "/api/v1/captions/#{id}?label=#{label}&title=#{title}"
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index abcf427e..b24222ff 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -2,7 +2,7 @@
module Invidious::Routes::Watch
def self.handle(env)
- locale = LOCALES[env.get("preferences").as(Preferences).locale]?
+ locale = env.get("preferences").as(Preferences).locale
region = env.params.query["region"]?
if env.params.query.to_s.includes?("%20") || env.params.query.to_s.includes?("+")
diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr
index 25bab4d2..1f957081 100644
--- a/src/invidious/trending.cr
+++ b/src/invidious/trending.cr
@@ -20,13 +20,3 @@ def fetch_trending(trending_type, region, locale)
return {trending, plid}
end
-
-def extract_plid(url)
- return url.try { |i| URI.parse(i).query }
- .try { |i| HTTP::Params.parse(i)["bp"] }
- .try { |i| URI.decode_www_form(i) }
- .try { |i| Base64.decode(i) }
- .try { |i| IO::Memory.new(i) }
- .try { |i| Protodec::Any.parse(i) }
- .try &.["44:0:embedded"]?.try &.["2:1:string"]?.try &.as_s
-end
diff --git a/src/invidious/user/preferences.cr b/src/invidious/user/preferences.cr
index c15876f5..bf7ea401 100644
--- a/src/invidious/user/preferences.cr
+++ b/src/invidious/user/preferences.cr
@@ -53,6 +53,7 @@ struct Preferences
property video_loop : Bool = CONFIG.default_user_preferences.video_loop
property extend_desc : Bool = CONFIG.default_user_preferences.extend_desc
property volume : Int32 = CONFIG.default_user_preferences.volume
+ property save_player_pos : Bool = CONFIG.default_user_preferences.save_player_pos
module BoolToString
def self.to_json(value : String, json : JSON::Builder)
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
index 4406284f..d4ef0900 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -246,6 +246,7 @@ struct VideoPreferences
property video_start : Float64 | Int32
property volume : Int32
property vr_mode : Bool
+ property save_player_pos : Bool
end
struct Video
@@ -275,7 +276,7 @@ struct Video
end
end
- def to_json(locale : Hash(String, JSON::Any) | Nil, json : JSON::Builder)
+ def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "video"
@@ -475,7 +476,7 @@ struct Video
end
# TODO: remove the locale and follow the crystal convention
- def to_json(locale : Hash(String, JSON::Any) | Nil, _json : Nil)
+ def to_json(locale : String?, _json : Nil)
JSON.build { |json| to_json(locale, json) }
end
@@ -885,42 +886,84 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_
}
).try { |a| JSON::Any.new(a) } || JSON::Any.new([] of JSON::Any)
- primary_results = player_response.try &.["contents"]?.try &.["twoColumnWatchNextResults"]?.try &.["results"]?
- .try &.["results"]?.try &.["contents"]?
- sentiment_bar = primary_results.try &.as_a.select(&.["videoPrimaryInfoRenderer"]?)[0]?
- .try &.["videoPrimaryInfoRenderer"]?
- .try &.["sentimentBar"]?
- .try &.["sentimentBarRenderer"]?
- .try &.["tooltip"]?
- .try &.as_s
-
- likes, dislikes = sentiment_bar.try &.split(" / ", 2).map &.gsub(/\D/, "").to_i64 || {0_i64, 0_i64}
- params["likes"] = JSON::Any.new(likes)
- params["dislikes"] = JSON::Any.new(dislikes)
-
- params["descriptionHtml"] = JSON::Any.new(primary_results.try &.as_a.select(&.["videoSecondaryInfoRenderer"]?)[0]?
- .try &.["videoSecondaryInfoRenderer"]?.try &.["description"]?.try &.["runs"]?
- .try &.as_a.try { |t| content_to_comment_html(t).gsub("\n", "<br/>") } || "<p></p>")
-
- metadata = primary_results.try &.as_a.select(&.["videoSecondaryInfoRenderer"]?)[0]?
- .try &.["videoSecondaryInfoRenderer"]?
- .try &.["metadataRowContainer"]?
- .try &.["metadataRowContainerRenderer"]?
- .try &.["rows"]?
- .try &.as_a
+ # Top level elements
+
+ primary_results = player_response
+ .dig?("contents", "twoColumnWatchNextResults", "results", "results", "contents")
+
+ video_primary_renderer = primary_results
+ .try &.as_a.find(&.["videoPrimaryInfoRenderer"]?)
+ .try &.["videoPrimaryInfoRenderer"]
+
+ video_secondary_renderer = primary_results
+ .try &.as_a.find(&.["videoSecondaryInfoRenderer"]?)
+ .try &.["videoSecondaryInfoRenderer"]
+
+ # Likes/dislikes
+
+ toplevel_buttons = video_primary_renderer
+ .try &.dig?("videoActions", "menuRenderer", "topLevelButtons")
+
+ if toplevel_buttons
+ likes_button = toplevel_buttons.as_a
+ .find(&.dig("toggleButtonRenderer", "defaultIcon", "iconType").as_s.== "LIKE")
+ .try &.["toggleButtonRenderer"]
+
+ if likes_button
+ likes_txt = (likes_button["defaultText"]? || likes_button["toggledText"]?)
+ .try &.dig?("accessibility", "accessibilityData", "label")
+ likes = likes_txt.as_s.gsub(/\D/, "").to_i64? if likes_txt
+
+ LOGGER.trace("extract_video_info: Found \"likes\" button. Button text is \"#{likes_txt}\"")
+ LOGGER.debug("extract_video_info: Likes count is #{likes}") if likes
+ end
+
+ dislikes_button = toplevel_buttons.as_a
+ .find(&.dig("toggleButtonRenderer", "defaultIcon", "iconType").as_s.== "DISLIKE")
+ .try &.["toggleButtonRenderer"]
+
+ if dislikes_button
+ dislikes_txt = (dislikes_button["defaultText"]? || dislikes_button["toggledText"]?)
+ .try &.dig?("accessibility", "accessibilityData", "label")
+ dislikes = dislikes_txt.as_s.gsub(/\D/, "").to_i64? if dislikes_txt
+
+ LOGGER.trace("extract_video_info: Found \"dislikes\" button. Button text is \"#{dislikes_txt}\"")
+ LOGGER.debug("extract_video_info: Dislikes count is #{dislikes}") if dislikes
+ end
+ end
+
+ if likes && likes != 0_i64 && (!dislikes || dislikes == 0_i64)
+ if rating = player_response.dig?("videoDetails", "averageRating").try { |x| x.as_i64? || x.as_f? }
+ dislikes = (likes * ((5 - rating)/(rating - 1))).round.to_i64
+ LOGGER.debug("extract_video_info: Dislikes count (using fallback method) is #{dislikes}")
+ end
+ end
+
+ params["likes"] = JSON::Any.new(likes || 0_i64)
+ params["dislikes"] = JSON::Any.new(dislikes || 0_i64)
+
+ # Description
+
+ description_html = video_secondary_renderer.try &.dig?("description", "runs")
+ .try &.as_a.try { |t| content_to_comment_html(t).gsub("\n", "<br/>") }
+
+ params["descriptionHtml"] = JSON::Any.new(description_html || "<p></p>")
+
+ # Video metadata
+
+ metadata = video_secondary_renderer
+ .try &.dig?("metadataRowContainer", "metadataRowContainerRenderer", "rows")
+ .try &.as_a
params["genre"] = params["microformat"]?.try &.["playerMicroformatRenderer"]?.try &.["category"]? || JSON::Any.new("")
params["genreUrl"] = JSON::Any.new(nil)
metadata.try &.each do |row|
title = row["metadataRowRenderer"]?.try &.["title"]?.try &.["simpleText"]?.try &.as_s
- contents = row["metadataRowRenderer"]?
- .try &.["contents"]?
- .try &.as_a[0]?
+ contents = row.dig?("metadataRowRenderer", "contents", 0)
if title.try &.== "Category"
- contents = contents.try &.["runs"]?
- .try &.as_a[0]?
+ contents = contents.try &.dig?("runs", 0)
params["genre"] = JSON::Any.new(contents.try &.["text"]?.try &.as_s || "")
params["genreUcid"] = JSON::Any.new(contents.try &.["navigationEndpoint"]?.try &.["browseEndpoint"]?
@@ -935,17 +978,19 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_
end
end
- author_info = primary_results.try &.as_a.select(&.["videoSecondaryInfoRenderer"]?)[0]?
- .try &.["videoSecondaryInfoRenderer"]?.try &.["owner"]?.try &.["videoOwnerRenderer"]?
+ # Author infos
- params["authorThumbnail"] = JSON::Any.new(author_info.try &.["thumbnail"]?
- .try &.["thumbnails"]?.try &.as_a[0]?.try &.["url"]?
- .try &.as_s || "")
+ author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer")
+ author_thumbnail = author_info.try &.dig?("thumbnail", "thumbnails", 0, "url")
+
+ params["authorThumbnail"] = JSON::Any.new(author_thumbnail.try &.as_s || "")
params["subCountText"] = JSON::Any.new(author_info.try &.["subscriberCountText"]?
- .try { |t| t["simpleText"]? || t["runs"]?.try &.[0]?.try &.["text"]? }.try &.as_s.split(" ", 2)[0] || "-")
+ .try { |t| t["simpleText"]? || t.dig?("runs", 0, "text") }.try &.as_s.split(" ", 2)[0] || "-")
+
+ # Return data
- params
+ return params
end
def get_video(id, db, refresh = true, region = nil, force_refresh = false)
@@ -1046,6 +1091,7 @@ def process_video_params(query, preferences)
extend_desc = query["extend_desc"]?.try { |q| (q == "true" || q == "1").to_unsafe }
volume = query["volume"]?.try &.to_i?
vr_mode = query["vr_mode"]?.try { |q| (q == "true" || q == "1").to_unsafe }
+ save_player_pos = query["save_player_pos"]?.try { |q| (q == "true" || q == "1").to_unsafe }
if preferences
# region ||= preferences.region
@@ -1066,6 +1112,7 @@ def process_video_params(query, preferences)
extend_desc ||= preferences.extend_desc.to_unsafe
volume ||= preferences.volume
vr_mode ||= preferences.vr_mode.to_unsafe
+ save_player_pos ||= preferences.save_player_pos.to_unsafe
end
annotations ||= CONFIG.default_user_preferences.annotations.to_unsafe
@@ -1085,6 +1132,7 @@ def process_video_params(query, preferences)
extend_desc ||= CONFIG.default_user_preferences.extend_desc.to_unsafe
volume ||= CONFIG.default_user_preferences.volume
vr_mode ||= CONFIG.default_user_preferences.vr_mode.to_unsafe
+ save_player_pos ||= CONFIG.default_user_preferences.save_player_pos.to_unsafe
annotations = annotations == 1
autoplay = autoplay == 1
@@ -1096,6 +1144,7 @@ def process_video_params(query, preferences)
video_loop = video_loop == 1
extend_desc = extend_desc == 1
vr_mode = vr_mode == 1
+ save_player_pos = save_player_pos == 1
if CONFIG.disabled?("dash") && quality == "dash"
quality = "high"
@@ -1146,6 +1195,7 @@ def process_video_params(query, preferences)
video_start: video_start,
volume: volume,
vr_mode: vr_mode,
+ save_player_pos: save_player_pos,
})
return params
diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr
index 6418f66b..206ba380 100644
--- a/src/invidious/views/components/player.ecr
+++ b/src/invidious/views/components/player.ecr
@@ -32,13 +32,11 @@
<% end %>
<% preferred_captions.each do |caption| %>
- <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>&hl=<%= env.get("preferences").as(Preferences).locale %>"
- label="<%= caption.name %>">
+ <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>">
<% end %>
<% captions.each do |caption| %>
- <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>&hl=<%= env.get("preferences").as(Preferences).locale %>"
- label="<%= caption.name %>">
+ <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>">
<% end %>
<% end %>
</video>
diff --git a/src/invidious/views/components/video-context-buttons.ecr b/src/invidious/views/components/video-context-buttons.ecr
index daa107f0..ddb6c983 100644
--- a/src/invidious/views/components/video-context-buttons.ecr
+++ b/src/invidious/views/components/video-context-buttons.ecr
@@ -1,6 +1,6 @@
<div class="flex-right">
<div class="icon-buttons">
- <a title="<%=translate(locale, "Watch on YouTube")%>" href="https://www.youtube.com/watch<%=endpoint_params%>">
+ <a title="<%=translate(locale, "videoinfo_watch_on_youTube")%>" href="https://www.youtube.com/watch<%=endpoint_params%>">
<i class="icon ion-logo-youtube"></i>
</a>
<a title="<%=translate(locale, "Audio mode")%>" href="/watch<%=endpoint_params%>&listen=1">
diff --git a/src/invidious/views/feeds/playlists.ecr b/src/invidious/views/feeds/playlists.ecr
index 868cfeda..a59344c4 100644
--- a/src/invidious/views/feeds/playlists.ecr
+++ b/src/invidious/views/feeds/playlists.ecr
@@ -6,7 +6,7 @@
<div class="pure-g h-box">
<div class="pure-u-2-3">
- <h3><%= translate(locale, "`x` created playlists", %(<span id="count">#{items_created.size}</span>)) %></h3>
+ <h3><%= translate(locale, "user_created_playlists", %(<span id="count">#{items_created.size}</span>)) %></h3>
</div>
<div class="pure-u-1-3" style="text-align:right">
<h3>
@@ -23,7 +23,7 @@
<div class="pure-g h-box">
<div class="pure-u-1">
- <h3><%= translate(locale, "`x` saved playlists", %(<span id="count">#{items_saved.size}</span>)) %></h3>
+ <h3><%= translate(locale, "user_saved_playlists", %(<span id="count">#{items_saved.size}</span>)) %></h3>
</div>
</div>
diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr
index d0518de7..136981da 100644
--- a/src/invidious/views/playlist.ecr
+++ b/src/invidious/views/playlist.ecr
@@ -47,7 +47,7 @@
<%= translate(locale, "Switch Invidious Instance") %>
</a>
<% else %>
- <a href="https://redirect.invidious.io<%= env.request.resource %>">
+ <a href="https://redirect.invidious.io/playlist?list=<%= playlist.id %>">
<%= translate(locale, "Switch Invidious Instance") %>
</a>
<% end %>
diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr
index 374edc99..96904259 100644
--- a/src/invidious/views/preferences.ecr
+++ b/src/invidious/views/preferences.ecr
@@ -51,7 +51,7 @@
<select name="quality" id="quality">
<% {"dash", "hd720", "medium", "small"}.each do |option| %>
<% if !(option == "dash" && CONFIG.disabled?("dash")) %>
- <option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, option) %></option>
+ <option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, "preferences_quality_option_" + option) %></option>
<% end %>
<% end %>
</select>
@@ -62,7 +62,7 @@
<label for="quality_dash"><%= translate(locale, "preferences_quality_dash_label") %></label>
<select name="quality_dash" id="quality_dash">
<% {"auto", "best", "4320p", "2160p", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p", "worst"}.each do |option| %>
- <option value="<%= option %>" <% if preferences.quality_dash == option %> selected <% end %>><%= translate(locale, option) %></option>
+ <option value="<%= option %>" <% if preferences.quality_dash == option %> selected <% end %>><%= translate(locale, "preferences_quality_dash_option_" + option) %></option>
<% end %>
</select>
</div>
@@ -116,13 +116,18 @@
<input name="vr_mode" id="vr_mode" type="checkbox" <% if preferences.vr_mode %>checked<% end %>>
</div>
+ <div class="pure-control-group">
+ <label for="save_player_pos"><%= translate(locale, "preferences_save_player_pos_label") %></label>
+ <input name="save_player_pos" id="save_player_pos" type="checkbox" <% if preferences.save_player_pos %>checked<% end %>>
+ </div>
+
<legend><%= translate(locale, "preferences_category_visual") %></legend>
<div class="pure-control-group">
<label for="locale"><%= translate(locale, "preferences_locale_label") %></label>
<select name="locale" id="locale">
- <% LOCALES.each_key do |option| %>
- <option value="<%= option %>" <% if preferences.locale == option %> selected <% end %>><%= option %></option>
+ <% LOCALES_LIST.each do |iso_name, full_name| %>
+ <option value="<%= iso_name %>" <% if preferences.locale == iso_name %> selected <% end %>><%= HTML.escape(full_name) %></option>
<% end %>
</select>
</div>
diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr
index 3fb2fe18..5b6e6ab8 100644
--- a/src/invidious/views/template.ecr
+++ b/src/invidious/views/template.ecr
@@ -19,8 +19,10 @@
<link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>">
</head>
-<% locale = LOCALES[env.get("preferences").as(Preferences).locale]? %>
-<% dark_mode = env.get("preferences").as(Preferences).dark_mode %>
+<%
+ 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>
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index 9cf00393..b85ea59d 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -103,7 +103,7 @@ we're going to need to do it here in order to allow for translations.
</h3>
<% elsif video.live_now %>
<h3>
- <%= video.premiere_timestamp.try { |t| translate(locale, "Started streaming `x` ago", recode_date((Time.utc - t).ago, locale)) } %>
+ <%= video.premiere_timestamp.try { |t| translate(locale, "videoinfo_started_streaming_x_ago", recode_date((Time.utc - t).ago, locale)) } %>
</h3>
<% end %>
</div>
@@ -112,8 +112,8 @@ we're going to need to do it here in order to allow for translations.
<div class="pure-u-1 pure-u-lg-1-5">
<div class="h-box">
<span id="watch-on-youtube">
- <a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "Watch on YouTube") %></a>
- (<a href="https://www.youtube.com/embed/<%= video.id %>"><%= translate(locale, "Embed") %></a>)
+ <a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "videoinfo_watch_on_youTube") %></a>
+ (<a href="https://www.youtube.com/embed/<%= video.id %>"><%= translate(locale, "videoinfo_youTube_embed_link") %></a>)
</span>
<p id="watch-on-another-invidious-instance">
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
@@ -123,7 +123,7 @@ we're going to need to do it here in order to allow for translations.
<% end %>
</p>
<p id="embed-link">
- <a href="<%= embed_link %>"><%= translate(locale, "Embed Link") %></a>
+ <a href="<%= embed_link %>"><%= translate(locale, "videoinfo_invidious_embed_link") %></a>
</p>
<p id="annotations">
<% if params.annotations %>
@@ -189,7 +189,7 @@ we're going to need to do it here in order to allow for translations.
<% end %>
<% captions.each do |caption| %>
<option value='{"id":"<%= video.id %>","label":"<%= caption.name %>","title":"<%= URI.encode_www_form(video.title) %>-<%= video.id %>.<%= caption.language_code %>.vtt"}'>
- <%= translate(locale, "Subtitles - `x` (.vtt)", caption.name) %>
+ <%= translate(locale, "download_subtitles", translate(locale, caption.name)) %>
</option>
<% end %>
</select>
diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr
index 8398ca8e..66b3cdef 100644
--- a/src/invidious/yt_backend/extractors.cr
+++ b/src/invidious/yt_backend/extractors.cr
@@ -49,6 +49,9 @@ private module Parsers
if author_info = item_contents.dig?("ownerText", "runs", 0)
author = author_info["text"].as_s
author_id = HelperExtractors.get_browse_id(author_info)
+ elsif author_info = item_contents.dig?("shortBylineText", "runs", 0)
+ author = author_info["text"].as_s
+ author_id = HelperExtractors.get_browse_id(author_info)
else
author = author_fallback.name
author_id = author_fallback.id
@@ -68,18 +71,25 @@ private module Parsers
view_count = item_contents.dig?("viewCountText", "simpleText").try &.as_s.gsub(/\D+/, "").to_i64? || 0_i64
description_html = item_contents["descriptionSnippet"]?.try { |t| parse_content(t) } || ""
- # The length information *should* only always exist in "lengthText". However, the legacy Invidious code
- # extracts from "thumbnailOverlays" when it doesn't. More testing is needed to see if this is
- # actually needed
+ # The length information generally exist in "lengthText". However, the info can sometimes
+ # be retrieved from "thumbnailOverlays" (e.g when the video is a "shorts" one).
if length_container = item_contents["lengthText"]?
length_seconds = decode_length_seconds(length_container["simpleText"].as_s)
elsif length_container = item_contents["thumbnailOverlays"]?.try &.as_a.find(&.["thumbnailOverlayTimeStatusRenderer"]?)
# This needs to only go down the `simpleText` path (if possible). If more situations came up that requires
# a specific pathway then we should add an argument to extract_text that'll make this possible
- length_seconds = length_container.dig?("thumbnailOverlayTimeStatusRenderer", "text", "simpleText")
+ length_text = length_container.dig?("thumbnailOverlayTimeStatusRenderer", "text", "simpleText")
- if length_seconds
- length_seconds = decode_length_seconds(length_seconds.as_s)
+ if length_text
+ length_text = length_text.as_s
+
+ if length_text == "SHORTS"
+ # Approximate length to one minute, as "shorts" generally don't exceed that length.
+ # TODO: Add some sort of metadata for the type of video (normal, live, premiere, shorts)
+ length_seconds = 60_i32
+ else
+ length_seconds = decode_length_seconds(length_text)
+ end
else
length_seconds = 0
end