Merge branch 'master' of github.com:iv-org/invidious
このコミットが含まれているのは:
コミット
eac9e6aaa4
|
@ -490,8 +490,9 @@ hr {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Description Expansion Styling*/
|
/* Description Expansion Styling*/
|
||||||
#descexpansionbutton {
|
#descexpansionbutton,
|
||||||
display: none
|
#music-desc-expansion {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#descexpansionbutton ~ div {
|
#descexpansionbutton ~ div {
|
||||||
|
@ -509,6 +510,11 @@ hr {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label[for="descexpansionbutton"]:hover,
|
||||||
|
label[for="music-desc-expansion"]:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Bidi (bidirectional text) support */
|
/* Bidi (bidirectional text) support */
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
|
@ -517,14 +523,38 @@ h4,
|
||||||
h5,
|
h5,
|
||||||
p,
|
p,
|
||||||
#descriptionWrapper,
|
#descriptionWrapper,
|
||||||
#description-box {
|
#description-box,
|
||||||
unicode-bidi: plaintext;
|
#music-description-box {
|
||||||
text-align: start;
|
unicode-bidi: plaintext;
|
||||||
|
text-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
#descriptionWrapper {
|
#descriptionWrapper {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#music-description-box {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#music-desc-expansion:checked ~ #music-description-box {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#music-desc-expansion ~ label > h3 > .ion-ios-arrow-up,
|
||||||
|
#music-desc-expansion:checked ~ label > h3 > .ion-ios-arrow-down {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#music-desc-expansion:checked ~ label > h3 > .ion-ios-arrow-up,
|
||||||
|
#music-desc-expansion ~ label > h3 > .ion-ios-arrow-down {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select all the music items except the first one */
|
||||||
|
.music-item + .music-item {
|
||||||
|
border-top: 1px solid #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Center the "invidious" logo on the search page */
|
/* Center the "invidious" logo on the search page */
|
||||||
|
|
|
@ -295,6 +295,17 @@ https_only: false
|
||||||
##
|
##
|
||||||
#admins: [""]
|
#admins: [""]
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable the user notifications for all users
|
||||||
|
##
|
||||||
|
## Note: On large instances, it is recommended to set this option to 'false'
|
||||||
|
## in order to reduce the amount of data written to the database, and hence
|
||||||
|
## improve the overall performance of the instance.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#enable_user_notifications: true
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# Background jobs
|
# Background jobs
|
||||||
|
|
|
@ -43,7 +43,7 @@ RUN if [[ "${release}" == 1 && "${disable_quic}" == 1 ]] ; then \
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.16
|
FROM alpine:3.16
|
||||||
RUN apk add --no-cache librsvg ttf-opensans
|
RUN apk add --no-cache librsvg ttf-opensans tini
|
||||||
WORKDIR /invidious
|
WORKDIR /invidious
|
||||||
RUN addgroup -g 1000 -S invidious && \
|
RUN addgroup -g 1000 -S invidious && \
|
||||||
adduser -u 1000 -S invidious -G invidious
|
adduser -u 1000 -S invidious -G invidious
|
||||||
|
@ -58,4 +58,5 @@ RUN chmod o+rX -R ./assets ./config ./locales
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
USER invidious
|
USER invidious
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
CMD [ "/invidious/invidious" ]
|
CMD [ "/invidious/invidious" ]
|
||||||
|
|
|
@ -42,7 +42,7 @@ RUN if [[ "${release}" == 1 && "${disable_quic}" == 1 ]] ; then \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
FROM alpine:3.16
|
FROM alpine:3.16
|
||||||
RUN apk add --no-cache librsvg ttf-opensans
|
RUN apk add --no-cache librsvg ttf-opensans tini
|
||||||
WORKDIR /invidious
|
WORKDIR /invidious
|
||||||
RUN addgroup -g 1000 -S invidious && \
|
RUN addgroup -g 1000 -S invidious && \
|
||||||
adduser -u 1000 -S invidious -G invidious
|
adduser -u 1000 -S invidious -G invidious
|
||||||
|
@ -57,4 +57,5 @@ RUN chmod o+rX -R ./assets ./config ./locales
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
USER invidious
|
USER invidious
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
CMD [ "/invidious/invidious" ]
|
CMD [ "/invidious/invidious" ]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: postgresql
|
- name: postgresql
|
||||||
repository: https://charts.bitnami.com/bitnami/
|
repository: https://charts.bitnami.com/bitnami/
|
||||||
version: 11.1.3
|
version: 12.1.9
|
||||||
digest: sha256:79061645472b6fb342d45e8e5b3aacd018ef5067193e46a060bccdc99fe7f6e1
|
digest: sha256:71ff342a6c0a98bece3d7fe199983afb2113f8db65a3e3819de875af2c45add7
|
||||||
generated: "2022-03-02T05:57:20.081432389+13:00"
|
generated: "2023-01-20T20:42:32.757707004Z"
|
||||||
|
|
|
@ -17,6 +17,6 @@ maintainers:
|
||||||
email: mail@leonklingele.de
|
email: mail@leonklingele.de
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: postgresql
|
- name: postgresql
|
||||||
version: ~11.1.3
|
version: ~12.1.6
|
||||||
repository: "https://charts.bitnami.com/bitnami/"
|
repository: "https://charts.bitnami.com/bitnami/"
|
||||||
engine: gotpl
|
engine: gotpl
|
||||||
|
|
|
@ -34,6 +34,8 @@ securityContext:
|
||||||
|
|
||||||
# See https://github.com/bitnami/charts/tree/master/bitnami/postgresql
|
# See https://github.com/bitnami/charts/tree/master/bitnami/postgresql
|
||||||
postgresql:
|
postgresql:
|
||||||
|
image:
|
||||||
|
tag: 13
|
||||||
auth:
|
auth:
|
||||||
username: kemal
|
username: kemal
|
||||||
password: kemal
|
password: kemal
|
||||||
|
|
136
locales/ar.json
136
locales/ar.json
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"LIVE": "مُباشِر",
|
"LIVE": "مُباشِر",
|
||||||
"Shared `x` ago": "تمَّ رفع المقطع المرئيّ مُنذ `x`",
|
"Shared `x` ago": "تمَّ الرفع مُنذ `x`",
|
||||||
"Unsubscribe": "إلغاء الاشتراك",
|
"Unsubscribe": "إلغاء الاشتراك",
|
||||||
"Subscribe": "الإشتراك",
|
"Subscribe": "الاشتراك",
|
||||||
"View channel on YouTube": "زيارة القناة على موقع يوتيوب",
|
"View channel on YouTube": "زيارة القناة على يوتيوب",
|
||||||
"View playlist on YouTube": "عرض قائمة التشغيل على اليوتيوب",
|
"View playlist on YouTube": "عرض قائمة التشغيل على يوتيوب",
|
||||||
"newest": "الأجدد",
|
"newest": "الأحدث",
|
||||||
"oldest": "الأقدم",
|
"oldest": "الأقدم",
|
||||||
"popular": "الأكثر شعبية",
|
"popular": "الأكثر شعبية",
|
||||||
"last": "الأخيرة",
|
"last": "الأخيرة",
|
||||||
|
@ -96,8 +96,8 @@
|
||||||
"`x` is live": "`x` في بث مباشر",
|
"`x` is live": "`x` في بث مباشر",
|
||||||
"preferences_category_data": "إعدادات التفضيلات",
|
"preferences_category_data": "إعدادات التفضيلات",
|
||||||
"Clear watch history": "حذف سجل المشاهدة",
|
"Clear watch history": "حذف سجل المشاهدة",
|
||||||
"Import/export data": "إضافة\\استخراج البيانات",
|
"Import/export data": "إستيراد و تصدير البيانات",
|
||||||
"Change password": "غير كلمة السر",
|
"Change password": "تغير كلمة السر",
|
||||||
"Manage subscriptions": "إدارة الاشتراكات",
|
"Manage subscriptions": "إدارة الاشتراكات",
|
||||||
"Manage tokens": "إدارة الرموز",
|
"Manage tokens": "إدارة الرموز",
|
||||||
"Watch history": "سجل المشاهدة",
|
"Watch history": "سجل المشاهدة",
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
"Title": "العنوان",
|
"Title": "العنوان",
|
||||||
"Playlist privacy": "إعدادات الخصوصية",
|
"Playlist privacy": "إعدادات الخصوصية",
|
||||||
"Editing playlist `x`": "تعديل قائمة التشغيل `x`",
|
"Editing playlist `x`": "تعديل قائمة التشغيل `x`",
|
||||||
"Show more": "إظهار المزيد",
|
"Show more": "عرض المزيد",
|
||||||
"Show less": "عرض اقل",
|
"Show less": "عرض اقل",
|
||||||
"Watch on YouTube": "مشاهدة الفيديو على اليوتيوب",
|
"Watch on YouTube": "مشاهدة الفيديو على اليوتيوب",
|
||||||
"Switch Invidious Instance": "تبديل المثيل Invidious",
|
"Switch Invidious Instance": "تبديل المثيل Invidious",
|
||||||
|
@ -147,20 +147,20 @@
|
||||||
"License: ": "التراخيص: ",
|
"License: ": "التراخيص: ",
|
||||||
"Family friendly? ": "محتوى عائلي؟ ",
|
"Family friendly? ": "محتوى عائلي؟ ",
|
||||||
"Wilson score: ": "درجة ويلسون: ",
|
"Wilson score: ": "درجة ويلسون: ",
|
||||||
"Engagement: ": "نسبة المشاركة: ",
|
"Engagement: ": "نسبة التفاعل: ",
|
||||||
"Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ",
|
"Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ",
|
||||||
"Blacklisted regions: ": "الدول المحظور فيها هذا الفيديو: ",
|
"Blacklisted regions: ": "الدول المحظور فيها هذا الفيديو: ",
|
||||||
"Shared `x`": "شارك منذ `x`",
|
"Shared `x`": "تمت المشاركة في `x`",
|
||||||
"Premieres in `x`": "يعرض فى `x`",
|
"Premieres in `x`": "يعرض فى `x`",
|
||||||
"Premieres `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.": "أهلًا! يبدو أن جافاسكريبت معطلٌ لديك. اضغط هنا لعرض التعليقات، وَضَع في اعتبارك أنها ستأخذ وقتًا أطول للتحميل.",
|
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "أهلًا! يبدو أن جافاسكريبت معطلٌ لديك. اضغط هنا لعرض التعليقات، وَضَع في اعتبارك أنها ستأخذ وقتًا أطول للتحميل.",
|
||||||
"View YouTube comments": "عرض تعليقات اليوتيوب",
|
"View YouTube comments": "عرض تعليقات اليوتيوب",
|
||||||
"View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع Reddit",
|
"View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع ريديت",
|
||||||
"View `x` comments": {
|
"View `x` comments": {
|
||||||
"([^.,0-9]|^)1([^.,0-9]|$)": "عرض `x` تعليقات",
|
"([^.,0-9]|^)1([^.,0-9]|$)": "عرض `x` تعليقات",
|
||||||
"": "عرض `x` تعليقات"
|
"": "عرض `x` تعليقات"
|
||||||
},
|
},
|
||||||
"View Reddit comments": "عرض تعليقات ريدإت Reddit",
|
"View Reddit comments": "عرض تعليقات ريديت",
|
||||||
"Hide replies": "إخفاء الردود",
|
"Hide replies": "إخفاء الردود",
|
||||||
"Show replies": "عرض الردود",
|
"Show replies": "عرض الردود",
|
||||||
"Incorrect password": "كلمة السر غير صحيحة",
|
"Incorrect password": "كلمة السر غير صحيحة",
|
||||||
|
@ -182,20 +182,20 @@
|
||||||
"channel:`x`": "قناة:`x`",
|
"channel:`x`": "قناة:`x`",
|
||||||
"Deleted or invalid channel": "قناة ممسوحة او غير صالحة",
|
"Deleted or invalid channel": "قناة ممسوحة او غير صالحة",
|
||||||
"This channel does not exist.": "هذه القناة غير موجودة.",
|
"This channel does not exist.": "هذه القناة غير موجودة.",
|
||||||
"Could not get channel info.": "لم يستطع الحصول على معلومات القناة.",
|
"Could not get channel info.": "لم يتمكن الحصول على معلومات القناة.",
|
||||||
"Could not fetch comments": "لم يتمكن من إحضار التعليقات",
|
"Could not fetch comments": "لا يتمكن إحضار التعليقات",
|
||||||
"`x` ago": "`x` منذ",
|
"`x` ago": "`x` منذ",
|
||||||
"Load more": "عرض المزيد",
|
"Load more": "تحميل المزيد",
|
||||||
"Could not create mix.": "تعذر إنشاء مزيج.",
|
"Could not create mix.": "تعذر إنشاء مزيج.",
|
||||||
"Empty playlist": "قائمة التشغيل فارغة",
|
"Empty playlist": "قائمة التشغيل فارغة",
|
||||||
"Not a playlist.": "قائمة التشغيل غير صالحة.",
|
"Not a playlist.": "قائمة التشغيل غير صالحة.",
|
||||||
"Playlist does not exist.": "قائمة التشغيل غير موجودة.",
|
"Playlist does not exist.": "قائمة التشغيل غير موجودة.",
|
||||||
"Could not pull trending pages.": "لم يستطع عرض الصفحات الراجئة.",
|
"Could not pull trending pages.": "لا يتمكن عرض الصفحات الراجئة.",
|
||||||
"Hidden field \"challenge\" is a required field": "مكان مخفي \"تحدي\" مكان مطلوب",
|
"Hidden field \"challenge\" is a required field": "الحقل المخفي \"تحدي\" حقل مطلوب",
|
||||||
"Hidden field \"token\" is a required field": "مكان مخفي \"رمز\" مكان مطلوب",
|
"Hidden field \"token\" is a required field": "الحقل المخفي \"رمز\" حقل مطلوب",
|
||||||
"Erroneous challenge": "تحدي غير صالح",
|
"Erroneous challenge": "تحدي خاطئ",
|
||||||
"Erroneous token": "رمز مميز خاطئ",
|
"Erroneous token": "رمز مميز خاطئ",
|
||||||
"No such user": "مستخدم غير صالح",
|
"No such user": "مستخدم غير موجود",
|
||||||
"Token is expired, please try again": "الرمز منتهى الصلاحية، الرجاء المحاولة مرة اخرى",
|
"Token is expired, please try again": "الرمز منتهى الصلاحية، الرجاء المحاولة مرة اخرى",
|
||||||
"English": "إنجليزي",
|
"English": "إنجليزي",
|
||||||
"English (auto-generated)": "إنجليزي (تم إنشائه تلقائيًا)",
|
"English (auto-generated)": "إنجليزي (تم إنشائه تلقائيًا)",
|
||||||
|
@ -325,15 +325,15 @@
|
||||||
"`x` marked it with a ❤": "`x` أعجب بهذا",
|
"`x` marked it with a ❤": "`x` أعجب بهذا",
|
||||||
"Audio mode": "الوضع الصوتي",
|
"Audio mode": "الوضع الصوتي",
|
||||||
"Video mode": "وضع الفيديو",
|
"Video mode": "وضع الفيديو",
|
||||||
"Videos": "الفيديوهات",
|
"channel_tab_videos_label": "الفيديوهات",
|
||||||
"Playlists": "قوائم التشغيل",
|
"Playlists": "قوائم التشغيل",
|
||||||
"Community": "المجتمع",
|
"channel_tab_community_label": "المجتمع",
|
||||||
"search_filters_sort_option_relevance": "ملاؤم",
|
"search_filters_sort_option_relevance": "ملائمة",
|
||||||
"search_filters_sort_option_rating": "تقييم",
|
"search_filters_sort_option_rating": "تقييم",
|
||||||
"search_filters_sort_option_date": "التاريخ",
|
"search_filters_sort_option_date": "التاريخ",
|
||||||
"search_filters_sort_option_views": "مشاهدات",
|
"search_filters_sort_option_views": "مشاهدات",
|
||||||
"search_filters_type_label": "نوع المحتوى",
|
"search_filters_type_label": "نوع المحتوى",
|
||||||
"search_filters_duration_label": "المدة الزمنية",
|
"search_filters_duration_label": "المدة",
|
||||||
"search_filters_features_label": "الميزات",
|
"search_filters_features_label": "الميزات",
|
||||||
"search_filters_sort_label": "فرز",
|
"search_filters_sort_label": "فرز",
|
||||||
"search_filters_date_option_hour": "آخر ساعة",
|
"search_filters_date_option_hour": "آخر ساعة",
|
||||||
|
@ -351,8 +351,8 @@
|
||||||
"search_filters_features_option_c_commons": "المشاع الإبداعي",
|
"search_filters_features_option_c_commons": "المشاع الإبداعي",
|
||||||
"search_filters_features_option_three_d": "ثلاثي الأبعاد",
|
"search_filters_features_option_three_d": "ثلاثي الأبعاد",
|
||||||
"search_filters_features_option_live": "مباشر",
|
"search_filters_features_option_live": "مباشر",
|
||||||
"search_filters_features_option_four_k": "4k",
|
"search_filters_features_option_four_k": "4K",
|
||||||
"search_filters_features_option_location": "الأماكن",
|
"search_filters_features_option_location": "المكان",
|
||||||
"search_filters_features_option_hdr": "وضع التباين العالي",
|
"search_filters_features_option_hdr": "وضع التباين العالي",
|
||||||
"Current version: ": "الإصدار الحالي: ",
|
"Current version: ": "الإصدار الحالي: ",
|
||||||
"next_steps_error_message": "بعد ذلك يجب أن تحاول: ",
|
"next_steps_error_message": "بعد ذلك يجب أن تحاول: ",
|
||||||
|
@ -360,10 +360,10 @@
|
||||||
"next_steps_error_message_go_to_youtube": "انتقل إلى يوتيوب",
|
"next_steps_error_message_go_to_youtube": "انتقل إلى يوتيوب",
|
||||||
"search_filters_duration_option_short": "قصير (< 4 دقائق)",
|
"search_filters_duration_option_short": "قصير (< 4 دقائق)",
|
||||||
"search_filters_duration_option_long": "طويل (> 20 دقيقة)",
|
"search_filters_duration_option_long": "طويل (> 20 دقيقة)",
|
||||||
"footer_source_code": "شفرة المصدر",
|
"footer_source_code": "الكود المصدر",
|
||||||
"footer_original_source_code": "كود المصدر الأصلي",
|
"footer_original_source_code": "الكود المصدر الأصلي",
|
||||||
"footer_modfied_source_code": "شفرة المصدر المعدلة",
|
"footer_modfied_source_code": "الكود المصدر المعدل",
|
||||||
"adminprefs_modified_source_code_url_label": "URL إلى مستودع التعليمات البرمجية المصدرية المعدلة",
|
"adminprefs_modified_source_code_url_label": "URL إلى مستودع الكود المصدر المعدل",
|
||||||
"footer_documentation": "التوثيق",
|
"footer_documentation": "التوثيق",
|
||||||
"footer_donate_page": "تبرّع",
|
"footer_donate_page": "تبرّع",
|
||||||
"preferences_region_label": "بلد المحتوى: ",
|
"preferences_region_label": "بلد المحتوى: ",
|
||||||
|
@ -398,31 +398,31 @@
|
||||||
"invidious": "الخيالي",
|
"invidious": "الخيالي",
|
||||||
"preferences_save_player_pos_label": "حفظ موضع التشغيل: ",
|
"preferences_save_player_pos_label": "حفظ موضع التشغيل: ",
|
||||||
"crash_page_you_found_a_bug": "يبدو أنك قد وجدت خطأً برمجيًّا في Invidious!",
|
"crash_page_you_found_a_bug": "يبدو أنك قد وجدت خطأً برمجيًّا في Invidious!",
|
||||||
"generic_videos_count_0": "لا فيديوهات",
|
"generic_videos_count_0": "لا يوجد فيديوهات",
|
||||||
"generic_videos_count_1": "فيديو واحد",
|
"generic_videos_count_1": "فيديو واحد",
|
||||||
"generic_videos_count_2": "فيديوهين",
|
"generic_videos_count_2": "فيديوهين",
|
||||||
"generic_videos_count_3": "{{count}} فيديوهات",
|
"generic_videos_count_3": "{{count}} فيديوهات",
|
||||||
"generic_videos_count_4": "{{count}} فيديو",
|
"generic_videos_count_4": "{{count}} فيديو",
|
||||||
"generic_videos_count_5": "{{count}} فيديو",
|
"generic_videos_count_5": "{{count}} فيديو",
|
||||||
"generic_subscribers_count_0": "لا مشتركين",
|
"generic_subscribers_count_0": "لا يوجد مشترك",
|
||||||
"generic_subscribers_count_1": "مشترك واحد",
|
"generic_subscribers_count_1": "مشترك واحد",
|
||||||
"generic_subscribers_count_2": "مشتركان",
|
"generic_subscribers_count_2": "مشتركان",
|
||||||
"generic_subscribers_count_3": "{{count}} مشتركين",
|
"generic_subscribers_count_3": "{{count}} مشتركين",
|
||||||
"generic_subscribers_count_4": "{{count}} مشترك",
|
"generic_subscribers_count_4": "{{count}} مشترك",
|
||||||
"generic_subscribers_count_5": "{{count}} مشترك",
|
"generic_subscribers_count_5": "{{count}} مشترك",
|
||||||
"generic_views_count_0": "لا مشاهدات",
|
"generic_views_count_0": "لا يوجد مشاهدة",
|
||||||
"generic_views_count_1": "مشاهدة واحدة",
|
"generic_views_count_1": "مشاهدة واحدة",
|
||||||
"generic_views_count_2": "مشاهدتان",
|
"generic_views_count_2": "مشاهدتان",
|
||||||
"generic_views_count_3": "{{count}} مشاهدات",
|
"generic_views_count_3": "{{count}} مشاهدات",
|
||||||
"generic_views_count_4": "{{count}} مشاهدة",
|
"generic_views_count_4": "{{count}} مشاهدة",
|
||||||
"generic_views_count_5": "{{count}} مشاهدة",
|
"generic_views_count_5": "{{count}} مشاهدة",
|
||||||
"generic_subscriptions_count_0": "لا اشتراكات",
|
"generic_subscriptions_count_0": "لا يوجد اشتراك",
|
||||||
"generic_subscriptions_count_1": "اشتراك واحد",
|
"generic_subscriptions_count_1": "اشتراك واحد",
|
||||||
"generic_subscriptions_count_2": "اشتراكان",
|
"generic_subscriptions_count_2": "اشتراكان",
|
||||||
"generic_subscriptions_count_3": "{{count}} اشتراكات",
|
"generic_subscriptions_count_3": "{{count}} اشتراكات",
|
||||||
"generic_subscriptions_count_4": "{{count}} اشتراك",
|
"generic_subscriptions_count_4": "{{count}} اشتراك",
|
||||||
"generic_subscriptions_count_5": "{{count}} اشتراك",
|
"generic_subscriptions_count_5": "{{count}} اشتراك",
|
||||||
"generic_playlists_count_0": "لا قوائم تشغيل",
|
"generic_playlists_count_0": "لا يوجد قوائم تشغيل",
|
||||||
"generic_playlists_count_1": "قائمة تشغيل واحدة",
|
"generic_playlists_count_1": "قائمة تشغيل واحدة",
|
||||||
"generic_playlists_count_2": "قائمتا تشغيل",
|
"generic_playlists_count_2": "قائمتا تشغيل",
|
||||||
"generic_playlists_count_3": "{{count}} قوائم تشغيل",
|
"generic_playlists_count_3": "{{count}} قوائم تشغيل",
|
||||||
|
@ -463,10 +463,10 @@
|
||||||
"search_message_change_filters_or_query": "حاول توسيع استعلام البحث و / أو تغيير عوامل التصفية.",
|
"search_message_change_filters_or_query": "حاول توسيع استعلام البحث و / أو تغيير عوامل التصفية.",
|
||||||
"search_filters_date_label": "تاريخ الرفع",
|
"search_filters_date_label": "تاريخ الرفع",
|
||||||
"generic_count_weeks_0": "{{count}} أسبوع",
|
"generic_count_weeks_0": "{{count}} أسبوع",
|
||||||
"generic_count_weeks_1": "{{count}} أسبوع",
|
"generic_count_weeks_1": "أسبوع واحد",
|
||||||
"generic_count_weeks_2": "{{count}} أسبوع",
|
"generic_count_weeks_2": "أسبوعين",
|
||||||
"generic_count_weeks_3": "{{count}} أسبوع",
|
"generic_count_weeks_3": "{{count}} أسابيع",
|
||||||
"generic_count_weeks_4": "{{count}} أسابيع",
|
"generic_count_weeks_4": "{{count}} أسبوع",
|
||||||
"generic_count_weeks_5": "{{count}} أسبوع",
|
"generic_count_weeks_5": "{{count}} أسبوع",
|
||||||
"Popular enabled: ": "تم تمكين الشعبية: ",
|
"Popular enabled: ": "تم تمكين الشعبية: ",
|
||||||
"search_filters_duration_option_medium": "متوسط (4-20 دقيقة)",
|
"search_filters_duration_option_medium": "متوسط (4-20 دقيقة)",
|
||||||
|
@ -474,16 +474,16 @@
|
||||||
"search_filters_type_option_all": "أي نوع",
|
"search_filters_type_option_all": "أي نوع",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"generic_count_minutes_0": "{{count}} دقيقة",
|
"generic_count_minutes_0": "{{count}} دقيقة",
|
||||||
"generic_count_minutes_1": "{{count}} دقيقة",
|
"generic_count_minutes_1": "دقيقة واحدة",
|
||||||
"generic_count_minutes_2": "{{count}} دقيقة",
|
"generic_count_minutes_2": "دقيقتين",
|
||||||
"generic_count_minutes_3": "{{count}} دقيقة",
|
"generic_count_minutes_3": "{{count}} دقائق",
|
||||||
"generic_count_minutes_4": "{{count}} دقائق",
|
"generic_count_minutes_4": "{{count}} دقيقة",
|
||||||
"generic_count_minutes_5": "{{count}} دقيقة",
|
"generic_count_minutes_5": "{{count}} دقيقة",
|
||||||
"generic_count_hours_0": "{{count}} ساعة",
|
"generic_count_hours_0": "{{count}} ساعة",
|
||||||
"generic_count_hours_1": "{{count}} ساعة",
|
"generic_count_hours_1": "ساعة واحدة",
|
||||||
"generic_count_hours_2": "{{count}} ساعة",
|
"generic_count_hours_2": "ساعتين",
|
||||||
"generic_count_hours_3": "{{count}} ساعة",
|
"generic_count_hours_3": "{{count}} ساعات",
|
||||||
"generic_count_hours_4": "{{count}} ساعات",
|
"generic_count_hours_4": "{{count}} ساعة",
|
||||||
"generic_count_hours_5": "{{count}} ساعة",
|
"generic_count_hours_5": "{{count}} ساعة",
|
||||||
"comments_view_x_replies_0": "عرض رد {{count}}",
|
"comments_view_x_replies_0": "عرض رد {{count}}",
|
||||||
"comments_view_x_replies_1": "عرض رد {{count}}",
|
"comments_view_x_replies_1": "عرض رد {{count}}",
|
||||||
|
@ -493,10 +493,10 @@
|
||||||
"comments_view_x_replies_5": "عرض رد {{count}}",
|
"comments_view_x_replies_5": "عرض رد {{count}}",
|
||||||
"search_message_use_another_instance": " يمكنك أيضًا البحث عن <a href=\"`x`\"> في مثيل آخر </a>.",
|
"search_message_use_another_instance": " يمكنك أيضًا البحث عن <a href=\"`x`\"> في مثيل آخر </a>.",
|
||||||
"comments_points_count_0": "{{count}} نقطة",
|
"comments_points_count_0": "{{count}} نقطة",
|
||||||
"comments_points_count_1": "{{count}} نقطة",
|
"comments_points_count_1": "نقطة واحدة",
|
||||||
"comments_points_count_2": "{{count}} نقطة",
|
"comments_points_count_2": "نقطتان",
|
||||||
"comments_points_count_3": "{{count}} نقطة",
|
"comments_points_count_3": "{{count}} نقط",
|
||||||
"comments_points_count_4": "{{count}} نقاط",
|
"comments_points_count_4": "{{count}} نقطة",
|
||||||
"comments_points_count_5": "{{count}} نقطة",
|
"comments_points_count_5": "{{count}} نقطة",
|
||||||
"generic_count_years_0": "{{count}} السنة",
|
"generic_count_years_0": "{{count}} السنة",
|
||||||
"generic_count_years_1": "{{count}} السنة",
|
"generic_count_years_1": "{{count}} السنة",
|
||||||
|
@ -512,17 +512,17 @@
|
||||||
"tokens_count_5": "الرمز المميز {{count}}",
|
"tokens_count_5": "الرمز المميز {{count}}",
|
||||||
"search_filters_apply_button": "تطبيق الفلاتر المحددة",
|
"search_filters_apply_button": "تطبيق الفلاتر المحددة",
|
||||||
"search_filters_duration_option_none": "أي مدة",
|
"search_filters_duration_option_none": "أي مدة",
|
||||||
"subscriptions_unseen_notifs_count_0": "{{count}} إشعار غير مرئي",
|
"subscriptions_unseen_notifs_count_0": "{{count}} إشعار جديد",
|
||||||
"subscriptions_unseen_notifs_count_1": "{{count}} إشعار غير مرئي",
|
"subscriptions_unseen_notifs_count_1": "إشعار واحد جديد",
|
||||||
"subscriptions_unseen_notifs_count_2": "{{count}} إشعار غير مرئي",
|
"subscriptions_unseen_notifs_count_2": "إشعارين جديدين",
|
||||||
"subscriptions_unseen_notifs_count_3": "{{count}} إشعار غير مرئي",
|
"subscriptions_unseen_notifs_count_3": "{{count}} إشعارات جديدة",
|
||||||
"subscriptions_unseen_notifs_count_4": "{{count}} إشعارات غير مرئية",
|
"subscriptions_unseen_notifs_count_4": "{{count}} إشعارا جديد",
|
||||||
"subscriptions_unseen_notifs_count_5": "{{count}} إشعار غير مرئي",
|
"subscriptions_unseen_notifs_count_5": "{{count}} إشعار جديد",
|
||||||
"generic_count_days_0": "{{count}} يوم",
|
"generic_count_days_0": "{{count}} يوم",
|
||||||
"generic_count_days_1": "{{count}} يوم",
|
"generic_count_days_1": "يوم واحد",
|
||||||
"generic_count_days_2": "{{count}} يوم",
|
"generic_count_days_2": "يومين",
|
||||||
"generic_count_days_3": "{{count}} يوم",
|
"generic_count_days_3": "{{count}} أيام",
|
||||||
"generic_count_days_4": "{{count}} أيام",
|
"generic_count_days_4": "{{count}} يوم",
|
||||||
"generic_count_days_5": "{{count}} يوم",
|
"generic_count_days_5": "{{count}} يوم",
|
||||||
"generic_count_months_0": "{{count}} شهر",
|
"generic_count_months_0": "{{count}} شهر",
|
||||||
"generic_count_months_1": "{{count}} شهر",
|
"generic_count_months_1": "{{count}} شهر",
|
||||||
|
@ -531,10 +531,14 @@
|
||||||
"generic_count_months_4": "{{count}} شهور",
|
"generic_count_months_4": "{{count}} شهور",
|
||||||
"generic_count_months_5": "{{count}} شهر",
|
"generic_count_months_5": "{{count}} شهر",
|
||||||
"generic_count_seconds_0": "{{count}} ثانية",
|
"generic_count_seconds_0": "{{count}} ثانية",
|
||||||
"generic_count_seconds_1": "{{count}} ثانية",
|
"generic_count_seconds_1": "ثانية واحدة",
|
||||||
"generic_count_seconds_2": "{{count}} ثانية",
|
"generic_count_seconds_2": "ثانيتين",
|
||||||
"generic_count_seconds_3": "{{count}} ثانية",
|
"generic_count_seconds_3": "{{count}} ثوانٍ",
|
||||||
"generic_count_seconds_4": "{{count}} ثوانٍ",
|
"generic_count_seconds_4": "{{count}} ثانية",
|
||||||
"generic_count_seconds_5": "{{count}} ثانية",
|
"generic_count_seconds_5": "{{count}} ثانية",
|
||||||
"error_video_not_in_playlist": "الفيديو المطلوب غير موجود في قائمة التشغيل هذه. <a href=\"`x`\"> انقر هنا للحصول على الصفحة الرئيسية لقائمة التشغيل. </a>"
|
"error_video_not_in_playlist": "الفيديو المطلوب غير موجود في قائمة التشغيل هذه. <a href=\"`x`\"> انقر هنا للحصول على الصفحة الرئيسية لقائمة التشغيل. </a>",
|
||||||
|
"channel_tab_shorts_label": "الفيديوهات القصيرة",
|
||||||
|
"channel_tab_streams_label": "البث المباشر",
|
||||||
|
"channel_tab_playlists_label": "قوائم التشغيل",
|
||||||
|
"channel_tab_channels_label": "القنوات"
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
"Movies": "Películes",
|
"Movies": "Películes",
|
||||||
"Download": "Descarrega",
|
"Download": "Descarrega",
|
||||||
"Download as: ": "Descarrega com: ",
|
"Download as: ": "Descarrega com: ",
|
||||||
"Videos": "Vídeos",
|
"channel_tab_videos_label": "Vídeos",
|
||||||
"search_filters_type_label": "Tipus",
|
"search_filters_type_label": "Tipus",
|
||||||
"search_filters_duration_label": "Duració",
|
"search_filters_duration_label": "Duració",
|
||||||
"search_filters_sort_label": "Ordena per",
|
"search_filters_sort_label": "Ordena per",
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
"reddit": "Reddit",
|
"reddit": "Reddit",
|
||||||
"preferences_captions_label": "Výchozí titulky: ",
|
"preferences_captions_label": "Výchozí titulky: ",
|
||||||
"Fallback captions: ": "Záložní titulky: ",
|
"Fallback captions: ": "Záložní titulky: ",
|
||||||
"preferences_related_videos_label": "Zobrazit podobné videa: ",
|
"preferences_related_videos_label": "Zobrazit podobná videa: ",
|
||||||
"preferences_annotations_label": "Zobrazovat poznámky ve výchozím nastavení: ",
|
"preferences_annotations_label": "Zobrazovat poznámky ve výchozím nastavení: ",
|
||||||
"preferences_extend_desc_label": "Rozšířit automaticky popis u videa: ",
|
"preferences_extend_desc_label": "Rozšířit automaticky popis u videa: ",
|
||||||
"preferences_category_visual": "Nastavení vzhledu",
|
"preferences_category_visual": "Nastavení vzhledu",
|
||||||
|
@ -260,8 +260,8 @@
|
||||||
"`x` marked it with a ❤": "`x` to označil(a) se ❤",
|
"`x` marked it with a ❤": "`x` to označil(a) se ❤",
|
||||||
"Audio mode": "Audiový režim",
|
"Audio mode": "Audiový režim",
|
||||||
"Video mode": "Videový režim",
|
"Video mode": "Videový režim",
|
||||||
"Videos": "Videa",
|
"channel_tab_videos_label": "Videa",
|
||||||
"Community": "Komunita",
|
"channel_tab_community_label": "Komunita",
|
||||||
"search_filters_sort_option_rating": "Hodnocení",
|
"search_filters_sort_option_rating": "Hodnocení",
|
||||||
"search_filters_sort_option_date": "Datum nahrání",
|
"search_filters_sort_option_date": "Datum nahrání",
|
||||||
"search_filters_sort_option_views": "Počet zhlédnutí",
|
"search_filters_sort_option_views": "Počet zhlédnutí",
|
||||||
|
@ -488,5 +488,9 @@
|
||||||
"search_filters_sort_option_relevance": "Relevantnost",
|
"search_filters_sort_option_relevance": "Relevantnost",
|
||||||
"search_filters_apply_button": "Použít vybrané filtry",
|
"search_filters_apply_button": "Použít vybrané filtry",
|
||||||
"Popular enabled: ": "Populární povoleno: ",
|
"Popular enabled: ": "Populární povoleno: ",
|
||||||
"error_video_not_in_playlist": "Požadované video v tomto playlistu neexistuje. <a href=\"`x`\">Klikněte sem pro navštívení domovské stránky playlistu.</a>"
|
"error_video_not_in_playlist": "Požadované video v tomto playlistu neexistuje. <a href=\"`x`\">Klikněte sem pro navštívení domovské stránky playlistu.</a>",
|
||||||
|
"channel_tab_shorts_label": "Shorts",
|
||||||
|
"channel_tab_playlists_label": "Playlisty",
|
||||||
|
"channel_tab_channels_label": "Kanály",
|
||||||
|
"channel_tab_streams_label": "Živé přenosy"
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@
|
||||||
"Esperanto": "Esperanto",
|
"Esperanto": "Esperanto",
|
||||||
"Czech": "Tjekkisk",
|
"Czech": "Tjekkisk",
|
||||||
"Danish": "Dansk",
|
"Danish": "Dansk",
|
||||||
"Community": "Samfund",
|
"channel_tab_community_label": "Samfund",
|
||||||
"Afrikaans": "Afrikansk",
|
"Afrikaans": "Afrikansk",
|
||||||
"Portuguese": "Portugisisk",
|
"Portuguese": "Portugisisk",
|
||||||
"Ukrainian": "Ukrainsk",
|
"Ukrainian": "Ukrainsk",
|
||||||
|
@ -267,7 +267,7 @@
|
||||||
"search_filters_sort_option_rating": "Bedømmelse",
|
"search_filters_sort_option_rating": "Bedømmelse",
|
||||||
"Yoruba": "Yoruba",
|
"Yoruba": "Yoruba",
|
||||||
"Erroneous token": "Fejlagtig token",
|
"Erroneous token": "Fejlagtig token",
|
||||||
"Videos": "Videoer",
|
"channel_tab_videos_label": "Videoer",
|
||||||
"search_filters_type_option_show": "Vis",
|
"search_filters_type_option_show": "Vis",
|
||||||
"Luxembourgish": "Luxemboursk",
|
"Luxembourgish": "Luxemboursk",
|
||||||
"Vietnamese": "Vietnamesisk",
|
"Vietnamese": "Vietnamesisk",
|
||||||
|
|
|
@ -325,9 +325,9 @@
|
||||||
"`x` marked it with a ❤": "`x` markierte es mit einem ❤",
|
"`x` marked it with a ❤": "`x` markierte es mit einem ❤",
|
||||||
"Audio mode": "Audiomodus",
|
"Audio mode": "Audiomodus",
|
||||||
"Video mode": "Videomodus",
|
"Video mode": "Videomodus",
|
||||||
"Videos": "Videos",
|
"channel_tab_videos_label": "Videos",
|
||||||
"Playlists": "Wiedergabelisten",
|
"Playlists": "Wiedergabelisten",
|
||||||
"Community": "Gemeinschaft",
|
"channel_tab_community_label": "Gemeinschaft",
|
||||||
"search_filters_sort_option_relevance": "Relevanz",
|
"search_filters_sort_option_relevance": "Relevanz",
|
||||||
"search_filters_sort_option_rating": "Bewertung",
|
"search_filters_sort_option_rating": "Bewertung",
|
||||||
"search_filters_sort_option_date": "Datum",
|
"search_filters_sort_option_date": "Datum",
|
||||||
|
@ -471,5 +471,6 @@
|
||||||
"search_filters_apply_button": "Ausgewählte Filter anwenden",
|
"search_filters_apply_button": "Ausgewählte Filter anwenden",
|
||||||
"search_filters_duration_option_none": "Beliebige Länge",
|
"search_filters_duration_option_none": "Beliebige Länge",
|
||||||
"search_filters_date_label": "Upload-Datum",
|
"search_filters_date_label": "Upload-Datum",
|
||||||
"search_filters_date_option_none": "Beliebiges Datum"
|
"search_filters_date_option_none": "Beliebiges Datum",
|
||||||
|
"error_video_not_in_playlist": "Das angeforderte Video existiert nicht in dieser Wiedergabeliste. <a href=\"`x`\">Klicken Sie hier, um zur Startseite der Wiedergabeliste zu gelangen.</a>"
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,9 +315,9 @@
|
||||||
"`x` marked it with a ❤": "Ο χρηστης `x` έβαλε ❤",
|
"`x` marked it with a ❤": "Ο χρηστης `x` έβαλε ❤",
|
||||||
"Audio mode": "Λειτουργία ήχου",
|
"Audio mode": "Λειτουργία ήχου",
|
||||||
"Video mode": "Λειτουργία βίντεο",
|
"Video mode": "Λειτουργία βίντεο",
|
||||||
"Videos": "Βίντεο",
|
"channel_tab_videos_label": "Βίντεο",
|
||||||
"Playlists": "Λίστες Αναπαραγωγής",
|
"Playlists": "Λίστες Αναπαραγωγής",
|
||||||
"Community": "Κοινότητα",
|
"channel_tab_community_label": "Κοινότητα",
|
||||||
"Current version: ": "Τρέχουσα έκδοση: ",
|
"Current version: ": "Τρέχουσα έκδοση: ",
|
||||||
"generic_playlists_count": "{{count}} λίστα αναπαραγωγής",
|
"generic_playlists_count": "{{count}} λίστα αναπαραγωγής",
|
||||||
"generic_playlists_count_plural": "{{count}} λίστες αναπαραγωγής",
|
"generic_playlists_count_plural": "{{count}} λίστες αναπαραγωγής",
|
||||||
|
|
|
@ -188,6 +188,9 @@
|
||||||
"Engagement: ": "Engagement: ",
|
"Engagement: ": "Engagement: ",
|
||||||
"Whitelisted regions: ": "Whitelisted regions: ",
|
"Whitelisted regions: ": "Whitelisted regions: ",
|
||||||
"Blacklisted regions: ": "Blacklisted regions: ",
|
"Blacklisted regions: ": "Blacklisted regions: ",
|
||||||
|
"Music in this video": "Music in this video",
|
||||||
|
"Artist: ": "Artist: ",
|
||||||
|
"Album: ": "Album: ",
|
||||||
"Shared `x`": "Shared `x`",
|
"Shared `x`": "Shared `x`",
|
||||||
"Premieres in `x`": "Premieres in `x`",
|
"Premieres in `x`": "Premieres in `x`",
|
||||||
"Premieres `x`": "Premieres `x`",
|
"Premieres `x`": "Premieres `x`",
|
||||||
|
@ -404,9 +407,7 @@
|
||||||
"`x` marked it with a ❤": "`x` marked it with a ❤",
|
"`x` marked it with a ❤": "`x` marked it with a ❤",
|
||||||
"Audio mode": "Audio mode",
|
"Audio mode": "Audio mode",
|
||||||
"Video mode": "Video mode",
|
"Video mode": "Video mode",
|
||||||
"Videos": "Videos",
|
|
||||||
"Playlists": "Playlists",
|
"Playlists": "Playlists",
|
||||||
"Community": "Community",
|
|
||||||
"search_filters_title": "Filters",
|
"search_filters_title": "Filters",
|
||||||
"search_filters_date_label": "Upload date",
|
"search_filters_date_label": "Upload date",
|
||||||
"search_filters_date_option_none": "Any date",
|
"search_filters_date_option_none": "Any date",
|
||||||
|
@ -472,5 +473,11 @@
|
||||||
"crash_page_read_the_faq": "read the <a href=\"`x`\">Frequently Asked Questions (FAQ)</a>",
|
"crash_page_read_the_faq": "read the <a href=\"`x`\">Frequently Asked Questions (FAQ)</a>",
|
||||||
"crash_page_search_issue": "searched for <a href=\"`x`\">existing issues on GitHub</a>",
|
"crash_page_search_issue": "searched for <a href=\"`x`\">existing issues on GitHub</a>",
|
||||||
"crash_page_report_issue": "If none of the above helped, please <a href=\"`x`\">open a new issue on GitHub</a> (preferably in English) and include the following text in your message (do NOT translate that text):",
|
"crash_page_report_issue": "If none of the above helped, please <a href=\"`x`\">open a new issue on GitHub</a> (preferably in English) and include the following text in your message (do NOT translate that text):",
|
||||||
"error_video_not_in_playlist": "The requested video doesn't exist in this playlist. <a href=\"`x`\">Click here for the playlist home page.</a>"
|
"error_video_not_in_playlist": "The requested video doesn't exist in this playlist. <a href=\"`x`\">Click here for the playlist home page.</a>",
|
||||||
|
"channel_tab_videos_label": "Videos",
|
||||||
|
"channel_tab_shorts_label": "Shorts",
|
||||||
|
"channel_tab_streams_label": "Livestreams",
|
||||||
|
"channel_tab_playlists_label": "Playlists",
|
||||||
|
"channel_tab_community_label": "Community",
|
||||||
|
"channel_tab_channels_label": "Channels"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
"Subscribe": "Abonu",
|
"Subscribe": "Abonu",
|
||||||
"View channel on YouTube": "Vidu kanalon en JuTubo",
|
"View channel on YouTube": "Vidu kanalon en JuTubo",
|
||||||
"View playlist on YouTube": "Vidu ludliston en JuTubo",
|
"View playlist on YouTube": "Vidu ludliston en JuTubo",
|
||||||
"newest": "pli novaj",
|
"newest": "plej novaj",
|
||||||
"oldest": "pli malnovaj",
|
"oldest": "plej malnovaj",
|
||||||
"popular": "popularaj",
|
"popular": "popularaj",
|
||||||
"last": "lasta",
|
"last": "lasta",
|
||||||
"Next page": "Sekva paĝo",
|
"Next page": "Sekva paĝo",
|
||||||
|
@ -325,9 +325,9 @@
|
||||||
"`x` marked it with a ❤": "`x` markis ĝin per ❤",
|
"`x` marked it with a ❤": "`x` markis ĝin per ❤",
|
||||||
"Audio mode": "Aŭda reĝimo",
|
"Audio mode": "Aŭda reĝimo",
|
||||||
"Video mode": "Videa reĝimo",
|
"Video mode": "Videa reĝimo",
|
||||||
"Videos": "Filmetoj",
|
"channel_tab_videos_label": "Videoj",
|
||||||
"Playlists": "Ludlistoj",
|
"Playlists": "Ludlistoj",
|
||||||
"Community": "Komunumo",
|
"channel_tab_community_label": "Komunumo",
|
||||||
"search_filters_sort_option_relevance": "rilateco",
|
"search_filters_sort_option_relevance": "rilateco",
|
||||||
"search_filters_sort_option_rating": "takso",
|
"search_filters_sort_option_rating": "takso",
|
||||||
"search_filters_sort_option_date": "dato",
|
"search_filters_sort_option_date": "dato",
|
||||||
|
@ -472,5 +472,9 @@
|
||||||
"generic_subscribers_count_plural": "{{count}} abonantoj",
|
"generic_subscribers_count_plural": "{{count}} abonantoj",
|
||||||
"generic_count_months": "{{count}} monato",
|
"generic_count_months": "{{count}} monato",
|
||||||
"generic_count_months_plural": "{{count}} monatoj",
|
"generic_count_months_plural": "{{count}} monatoj",
|
||||||
"preferences_save_player_pos_label": "Konservi ludadan pozicion: "
|
"preferences_save_player_pos_label": "Konservi ludadan pozicion: ",
|
||||||
|
"channel_tab_streams_label": "Tujelsendoj",
|
||||||
|
"channel_tab_playlists_label": "Ludlistoj",
|
||||||
|
"channel_tab_channels_label": "Kanaloj",
|
||||||
|
"channel_tab_shorts_label": "Mallongaj"
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,9 +325,9 @@
|
||||||
"`x` marked it with a ❤": "`x` lo ha marcado con un ❤",
|
"`x` marked it with a ❤": "`x` lo ha marcado con un ❤",
|
||||||
"Audio mode": "Modo de audio",
|
"Audio mode": "Modo de audio",
|
||||||
"Video mode": "Modo de vídeo",
|
"Video mode": "Modo de vídeo",
|
||||||
"Videos": "Vídeos",
|
"channel_tab_videos_label": "Vídeos",
|
||||||
"Playlists": "Listas de reproducción",
|
"Playlists": "Listas de reproducción",
|
||||||
"Community": "Comunidad",
|
"channel_tab_community_label": "Comunidad",
|
||||||
"search_filters_sort_option_relevance": "relevancia",
|
"search_filters_sort_option_relevance": "relevancia",
|
||||||
"search_filters_sort_option_rating": "valoración",
|
"search_filters_sort_option_rating": "valoración",
|
||||||
"search_filters_sort_option_date": "fecha",
|
"search_filters_sort_option_date": "fecha",
|
||||||
|
@ -472,5 +472,9 @@
|
||||||
"search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.",
|
"search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.",
|
||||||
"search_filters_duration_option_medium": "Medio (4 - 20 minutes)",
|
"search_filters_duration_option_medium": "Medio (4 - 20 minutes)",
|
||||||
"Popular enabled: ": "¿Habilitar la sección popular? ",
|
"Popular enabled: ": "¿Habilitar la sección popular? ",
|
||||||
"error_video_not_in_playlist": "El vídeo solicitado no existe en esta lista de reproducción. <a href=\"`x`\">Haga clic aquí para acceder a la página de inicio de la lista de reproducción.</a>"
|
"error_video_not_in_playlist": "El vídeo solicitado no existe en esta lista de reproducción. <a href=\"`x`\">Haga clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
|
||||||
|
"channel_tab_streams_label": "Directos",
|
||||||
|
"channel_tab_channels_label": "Canales",
|
||||||
|
"channel_tab_shorts_label": "Cortos",
|
||||||
|
"channel_tab_playlists_label": "Listas de reproducción"
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,8 +296,8 @@
|
||||||
"Corsican": "Korsika",
|
"Corsican": "Korsika",
|
||||||
"Javanese": "Jaava",
|
"Javanese": "Jaava",
|
||||||
"Lithuanian": "Leedu",
|
"Lithuanian": "Leedu",
|
||||||
"Videos": "Videod",
|
"channel_tab_videos_label": "Videod",
|
||||||
"Community": "Kogukond",
|
"channel_tab_community_label": "Kogukond",
|
||||||
"CAPTCHA is a required field": "CAPTCHA on kohustuslik väli",
|
"CAPTCHA is a required field": "CAPTCHA on kohustuslik väli",
|
||||||
"comments_points_count": "{{count}} punkt",
|
"comments_points_count": "{{count}} punkt",
|
||||||
"comments_points_count_plural": "{{count}} punkti",
|
"comments_points_count_plural": "{{count}} punkti",
|
||||||
|
|
|
@ -341,9 +341,9 @@
|
||||||
"`x` marked it with a ❤": "`x` نشان گذاری شده با یک ❤",
|
"`x` marked it with a ❤": "`x` نشان گذاری شده با یک ❤",
|
||||||
"Audio mode": "حالت صدا",
|
"Audio mode": "حالت صدا",
|
||||||
"Video mode": "حالت ویدیو",
|
"Video mode": "حالت ویدیو",
|
||||||
"Videos": "ویدیو ها",
|
"channel_tab_videos_label": "ویدیو ها",
|
||||||
"Playlists": "سیاهههای پخش",
|
"Playlists": "سیاهههای پخش",
|
||||||
"Community": "اجتماع",
|
"channel_tab_community_label": "اجتماع",
|
||||||
"search_filters_sort_option_relevance": "مرتبط بودن",
|
"search_filters_sort_option_relevance": "مرتبط بودن",
|
||||||
"search_filters_sort_option_rating": "امتیاز",
|
"search_filters_sort_option_rating": "امتیاز",
|
||||||
"search_filters_sort_option_date": "تاریخ بارگذاری",
|
"search_filters_sort_option_date": "تاریخ بارگذاری",
|
||||||
|
@ -411,5 +411,18 @@
|
||||||
"search_filters_duration_option_long": "بلند (> 20 دقیقه)",
|
"search_filters_duration_option_long": "بلند (> 20 دقیقه)",
|
||||||
"adminprefs_modified_source_code_url_label": "URL مخزن کد منبع ویریش شده",
|
"adminprefs_modified_source_code_url_label": "URL مخزن کد منبع ویریش شده",
|
||||||
"search_filters_duration_option_short": "کوتاه (< 4 دقیقه)",
|
"search_filters_duration_option_short": "کوتاه (< 4 دقیقه)",
|
||||||
"search_filters_title": "پالایه"
|
"search_filters_title": "پالایه",
|
||||||
|
"Chinese (Hong Kong)": "چینی (هنگکنگ)",
|
||||||
|
"Dutch (auto-generated)": "هلندی (تولید خودکار)",
|
||||||
|
"preferences_watch_history_label": "فعالسازی تاریخچهی پخش ",
|
||||||
|
"Indonesian (auto-generated)": "اندونزیایی (تولید خودکار)",
|
||||||
|
"English (United States)": "انگلیسی (ایالات متحده)",
|
||||||
|
"Chinese": "چینی",
|
||||||
|
"Chinese (Taiwan)": "چینی (تایوان)",
|
||||||
|
"French (auto-generated)": "فرانسوی (تولید خودکار)",
|
||||||
|
"English (United Kingdom)": "انگلیسی (ایالات بریتانیا)",
|
||||||
|
"search_message_no_results": "نتیجهای یافت نشد.",
|
||||||
|
"search_message_change_filters_or_query": "سعی کنید جستوجوی خود را وسیعتر کنید و/یا فیلترها را تغییر دهید.",
|
||||||
|
"Chinese (China)": "چینی (چین)",
|
||||||
|
"German (auto-generated)": "آلمانی (تولید خودکار)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,9 +324,9 @@
|
||||||
"`x` marked it with a ❤": "`x` merkkasi ❤:llä",
|
"`x` marked it with a ❤": "`x` merkkasi ❤:llä",
|
||||||
"Audio mode": "Äänitila",
|
"Audio mode": "Äänitila",
|
||||||
"Video mode": "Videotila",
|
"Video mode": "Videotila",
|
||||||
"Videos": "Videot",
|
"channel_tab_videos_label": "Videot",
|
||||||
"Playlists": "Soittolistat",
|
"Playlists": "Soittolistat",
|
||||||
"Community": "Yhteisö",
|
"channel_tab_community_label": "Yhteisö",
|
||||||
"search_filters_sort_option_relevance": "Osuvuus",
|
"search_filters_sort_option_relevance": "Osuvuus",
|
||||||
"search_filters_sort_option_rating": "Arvostelu",
|
"search_filters_sort_option_rating": "Arvostelu",
|
||||||
"search_filters_sort_option_date": "Latauspäivämäärä",
|
"search_filters_sort_option_date": "Latauspäivämäärä",
|
||||||
|
@ -471,5 +471,6 @@
|
||||||
"search_message_use_another_instance": " Voit myös <a href=\"`x`\">hakea toisella instanssilla</a>.",
|
"search_message_use_another_instance": " Voit myös <a href=\"`x`\">hakea toisella instanssilla</a>.",
|
||||||
"search_filters_date_option_none": "Milloin tahansa",
|
"search_filters_date_option_none": "Milloin tahansa",
|
||||||
"search_filters_type_option_all": "Mikä tahansa tyyppi",
|
"search_filters_type_option_all": "Mikä tahansa tyyppi",
|
||||||
"Popular enabled: ": "Suosittu käytössä: "
|
"Popular enabled: ": "Suosittu käytössä: ",
|
||||||
|
"error_video_not_in_playlist": "Pyydettyä videota ei löydy tästä soittolistasta. <a href=\"`x`\">Klikkaa tähän päästäksesi soittolistan etusivulle.</a>"
|
||||||
}
|
}
|
||||||
|
|
|
@ -358,9 +358,9 @@
|
||||||
"`x` marked it with a ❤": "`x` l'a marqué d'un ❤",
|
"`x` marked it with a ❤": "`x` l'a marqué d'un ❤",
|
||||||
"Audio mode": "Mode audio",
|
"Audio mode": "Mode audio",
|
||||||
"Video mode": "Mode vidéo",
|
"Video mode": "Mode vidéo",
|
||||||
"Videos": "Vidéos",
|
"channel_tab_videos_label": "Vidéos",
|
||||||
"Playlists": "Listes de lecture",
|
"Playlists": "Listes de lecture",
|
||||||
"Community": "Communauté",
|
"channel_tab_community_label": "Communauté",
|
||||||
"search_filters_sort_option_relevance": "Pertinence",
|
"search_filters_sort_option_relevance": "Pertinence",
|
||||||
"search_filters_sort_option_rating": "Notation",
|
"search_filters_sort_option_rating": "Notation",
|
||||||
"search_filters_sort_option_date": "Date d'ajout",
|
"search_filters_sort_option_date": "Date d'ajout",
|
||||||
|
@ -472,5 +472,9 @@
|
||||||
"search_filters_date_label": "Date d'ajout",
|
"search_filters_date_label": "Date d'ajout",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_filters_duration_option_none": "Toutes les durées",
|
"search_filters_duration_option_none": "Toutes les durées",
|
||||||
"error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. <a href=\"`x`\">Cliquez ici pour retourner à la liste de lecture.</a>"
|
"error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. <a href=\"`x`\">Cliquez ici pour retourner à la liste de lecture.</a>",
|
||||||
|
"channel_tab_shorts_label": "Clips",
|
||||||
|
"channel_tab_streams_label": "En direct",
|
||||||
|
"channel_tab_playlists_label": "Listes de lecture",
|
||||||
|
"channel_tab_channels_label": "Chaînes"
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,9 +271,9 @@
|
||||||
"`x` marked it with a ❤": "סומנה ב־❤ על ידי `x`",
|
"`x` marked it with a ❤": "סומנה ב־❤ על ידי `x`",
|
||||||
"Audio mode": "Audio mode",
|
"Audio mode": "Audio mode",
|
||||||
"Video mode": "Video mode",
|
"Video mode": "Video mode",
|
||||||
"Videos": "סרטונים",
|
"channel_tab_videos_label": "סרטונים",
|
||||||
"Playlists": "פלייליסטים",
|
"Playlists": "פלייליסטים",
|
||||||
"Community": "קהילה",
|
"channel_tab_community_label": "קהילה",
|
||||||
"search_filters_sort_option_relevance": "רלוונטיות",
|
"search_filters_sort_option_relevance": "רלוונטיות",
|
||||||
"search_filters_sort_option_rating": "דירוג",
|
"search_filters_sort_option_rating": "דירוג",
|
||||||
"search_filters_sort_option_date": "תאריך העלאה",
|
"search_filters_sort_option_date": "תאריך העלאה",
|
||||||
|
|
|
@ -401,12 +401,12 @@
|
||||||
"(edited)": "(संपादित)",
|
"(edited)": "(संपादित)",
|
||||||
"YouTube comment permalink": "YouTube पर टिप्पणी की स्थायी कड़ी",
|
"YouTube comment permalink": "YouTube पर टिप्पणी की स्थायी कड़ी",
|
||||||
"permalink": "स्थायी कड़ी",
|
"permalink": "स्थायी कड़ी",
|
||||||
"Videos": "वीडियो",
|
"channel_tab_videos_label": "वीडियो",
|
||||||
"`x` marked it with a ❤": "`x` ने इसे एक ❤ से चिह्नित किया",
|
"`x` marked it with a ❤": "`x` ने इसे एक ❤ से चिह्नित किया",
|
||||||
"Audio mode": "ऑडियो मोड",
|
"Audio mode": "ऑडियो मोड",
|
||||||
"Playlists": "प्लेलिस्ट्स",
|
"Playlists": "प्लेलिस्ट्स",
|
||||||
"Video mode": "वीडियो मोड",
|
"Video mode": "वीडियो मोड",
|
||||||
"Community": "समुदाय",
|
"channel_tab_community_label": "समुदाय",
|
||||||
"search_filters_title": "फ़िल्टर",
|
"search_filters_title": "फ़िल्टर",
|
||||||
"search_filters_date_label": "अपलोड करने का समय",
|
"search_filters_date_label": "अपलोड करने का समय",
|
||||||
"search_filters_date_option_none": "कोई भी समय",
|
"search_filters_date_option_none": "कोई भी समय",
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
"View playlist on YouTube": "Prikaži zbirku na YouTubeu",
|
"View playlist on YouTube": "Prikaži zbirku na YouTubeu",
|
||||||
"newest": "najnovije",
|
"newest": "najnovije",
|
||||||
"oldest": "najstarije",
|
"oldest": "najstarije",
|
||||||
"popular": "popularni",
|
"popular": "popularne",
|
||||||
"last": "zadnji",
|
"last": "zadnje",
|
||||||
"Next page": "Sljedeća stranica",
|
"Next page": "Sljedeća stranica",
|
||||||
"Previous page": "Prethodna stranica",
|
"Previous page": "Prethodna stranica",
|
||||||
"Clear watch history?": "Izbrisati povijest gledanja?",
|
"Clear watch history?": "Izbrisati povijest gledanja?",
|
||||||
|
@ -43,9 +43,9 @@
|
||||||
"Time (h:mm:ss):": "Vrijeme (h:mm:ss):",
|
"Time (h:mm:ss):": "Vrijeme (h:mm:ss):",
|
||||||
"Text CAPTCHA": "Tekstualni CAPTCHA",
|
"Text CAPTCHA": "Tekstualni CAPTCHA",
|
||||||
"Image CAPTCHA": "Slikovni CAPTCHA",
|
"Image CAPTCHA": "Slikovni CAPTCHA",
|
||||||
"Sign In": "Prijava",
|
"Sign In": "Prijavi se",
|
||||||
"Register": "Registriraj se",
|
"Register": "Registriraj se",
|
||||||
"E-mail": "E-mail",
|
"E-mail": "E-mail adresa",
|
||||||
"Google verification code": "Googleov potvrdni kod",
|
"Google verification code": "Googleov potvrdni kod",
|
||||||
"Preferences": "Postavke",
|
"Preferences": "Postavke",
|
||||||
"preferences_category_player": "Postavke playera",
|
"preferences_category_player": "Postavke playera",
|
||||||
|
@ -325,9 +325,9 @@
|
||||||
"`x` marked it with a ❤": "Označeno sa ❤ od `x`",
|
"`x` marked it with a ❤": "Označeno sa ❤ od `x`",
|
||||||
"Audio mode": "Audio modus",
|
"Audio mode": "Audio modus",
|
||||||
"Video mode": "Videomodus",
|
"Video mode": "Videomodus",
|
||||||
"Videos": "Videa",
|
"channel_tab_videos_label": "Videa",
|
||||||
"Playlists": "Zbirke",
|
"Playlists": "Zbirke",
|
||||||
"Community": "Zajednica",
|
"channel_tab_community_label": "Zajednica",
|
||||||
"search_filters_sort_option_relevance": "Značaj",
|
"search_filters_sort_option_relevance": "Značaj",
|
||||||
"search_filters_sort_option_rating": "Ocjena",
|
"search_filters_sort_option_rating": "Ocjena",
|
||||||
"search_filters_sort_option_date": "Datum prijenosa",
|
"search_filters_sort_option_date": "Datum prijenosa",
|
||||||
|
@ -488,5 +488,9 @@
|
||||||
"search_filters_apply_button": "Primijeni odabrane filtre",
|
"search_filters_apply_button": "Primijeni odabrane filtre",
|
||||||
"search_filters_type_option_all": "Bilo koja vrsta",
|
"search_filters_type_option_all": "Bilo koja vrsta",
|
||||||
"Popular enabled: ": "Popularni aktivirani: ",
|
"Popular enabled: ": "Popularni aktivirani: ",
|
||||||
"error_video_not_in_playlist": "Traženi video ne postoji u ovoj zbirci. <a href=\"`x`\">Pritisni ovdje za početnu stranicu zbirke.</a>"
|
"error_video_not_in_playlist": "Traženi video ne postoji u ovoj zbirci. <a href=\"`x`\">Pritisni ovdje za početnu stranicu zbirke.</a>",
|
||||||
|
"channel_tab_streams_label": "Prijenosi uživo",
|
||||||
|
"channel_tab_playlists_label": "Zbirke",
|
||||||
|
"channel_tab_channels_label": "Kanali",
|
||||||
|
"channel_tab_shorts_label": "Kratka videa"
|
||||||
}
|
}
|
||||||
|
|
|
@ -348,9 +348,9 @@
|
||||||
"`x` marked it with a ❤": "`x` ❤ jelet adott a hozzászóláshoz",
|
"`x` marked it with a ❤": "`x` ❤ jelet adott a hozzászóláshoz",
|
||||||
"Audio mode": "Csak hanggal",
|
"Audio mode": "Csak hanggal",
|
||||||
"Video mode": "Hanggal és képpel",
|
"Video mode": "Hanggal és képpel",
|
||||||
"Videos": "Videói",
|
"channel_tab_videos_label": "Videói",
|
||||||
"Playlists": "Lejátszási listái",
|
"Playlists": "Lejátszási listái",
|
||||||
"Community": "Közösség",
|
"channel_tab_community_label": "Közösség",
|
||||||
"Current version: ": "Jelenlegi verzió: ",
|
"Current version: ": "Jelenlegi verzió: ",
|
||||||
"preferences_quality_option_medium": "Közepes",
|
"preferences_quality_option_medium": "Közepes",
|
||||||
"preferences_quality_dash_option_auto": "Automatikus",
|
"preferences_quality_dash_option_auto": "Automatikus",
|
||||||
|
@ -470,5 +470,7 @@
|
||||||
"search_filters_duration_option_none": "Mindegy",
|
"search_filters_duration_option_none": "Mindegy",
|
||||||
"search_filters_duration_option_medium": "Átlagos (4 és 20 perc között)",
|
"search_filters_duration_option_medium": "Átlagos (4 és 20 perc között)",
|
||||||
"search_filters_features_option_vr180": "180°-os virtuális valóság",
|
"search_filters_features_option_vr180": "180°-os virtuális valóság",
|
||||||
"search_filters_apply_button": "Keresés a megadott szűrőkkel"
|
"search_filters_apply_button": "Keresés a megadott szűrőkkel",
|
||||||
|
"Popular enabled: ": "Népszerű engedélyezve ",
|
||||||
|
"error_video_not_in_playlist": "A lejátszási listában keresett videó nem létezik. <a href=\"`x`\">Kattintson ide a lejátszási listához jutáshoz.</a>"
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,9 +341,9 @@
|
||||||
"`x` marked it with a ❤": "`x` telah ditandai dengan ❤",
|
"`x` marked it with a ❤": "`x` telah ditandai dengan ❤",
|
||||||
"Audio mode": "Mode audio",
|
"Audio mode": "Mode audio",
|
||||||
"Video mode": "Mode video",
|
"Video mode": "Mode video",
|
||||||
"Videos": "Video",
|
"channel_tab_videos_label": "Video",
|
||||||
"Playlists": "Daftar putar",
|
"Playlists": "Daftar putar",
|
||||||
"Community": "Komunitas",
|
"channel_tab_community_label": "Komunitas",
|
||||||
"search_filters_sort_option_relevance": "Relevansi",
|
"search_filters_sort_option_relevance": "Relevansi",
|
||||||
"search_filters_sort_option_rating": "Penilaian",
|
"search_filters_sort_option_rating": "Penilaian",
|
||||||
"search_filters_sort_option_date": "Tanggal Unggah",
|
"search_filters_sort_option_date": "Tanggal Unggah",
|
||||||
|
|
|
@ -315,9 +315,9 @@
|
||||||
"`x` marked it with a ❤": "`x` merkti það með ❤",
|
"`x` marked it with a ❤": "`x` merkti það með ❤",
|
||||||
"Audio mode": "Hljóð ham",
|
"Audio mode": "Hljóð ham",
|
||||||
"Video mode": "Myndband ham",
|
"Video mode": "Myndband ham",
|
||||||
"Videos": "Myndbönd",
|
"channel_tab_videos_label": "Myndbönd",
|
||||||
"Playlists": "Spilunarlistar",
|
"Playlists": "Spilunarlistar",
|
||||||
"Community": "Samfélag",
|
"channel_tab_community_label": "Samfélag",
|
||||||
"Current version: ": "Núverandi útgáfa: ",
|
"Current version: ": "Núverandi útgáfa: ",
|
||||||
"preferences_watch_history_label": "Virkja áhorfssögu: "
|
"preferences_watch_history_label": "Virkja áhorfssögu: "
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,7 +290,7 @@
|
||||||
"Southern Sotho": "Sotho del Sud",
|
"Southern Sotho": "Sotho del Sud",
|
||||||
"Spanish": "Spagnolo",
|
"Spanish": "Spagnolo",
|
||||||
"Spanish (Latin America)": "Spagnolo (America latina)",
|
"Spanish (Latin America)": "Spagnolo (America latina)",
|
||||||
"Sundanese": "Sudanese",
|
"Sundanese": "Sundanese",
|
||||||
"Swahili": "Swahili",
|
"Swahili": "Swahili",
|
||||||
"Swedish": "Svedese",
|
"Swedish": "Svedese",
|
||||||
"Tajik": "Tagico",
|
"Tajik": "Tagico",
|
||||||
|
@ -344,9 +344,8 @@
|
||||||
"`x` marked it with a ❤": "`x` l'ha contrassegnato con un ❤",
|
"`x` marked it with a ❤": "`x` l'ha contrassegnato con un ❤",
|
||||||
"Audio mode": "Modalità audio",
|
"Audio mode": "Modalità audio",
|
||||||
"Video mode": "Modalità video",
|
"Video mode": "Modalità video",
|
||||||
"Videos": "Video",
|
"channel_tab_videos_label": "Video",
|
||||||
"Playlists": "Playlist",
|
"Playlists": "Playlist",
|
||||||
"Community": "Comunità",
|
|
||||||
"search_filters_sort_option_relevance": "Pertinenza",
|
"search_filters_sort_option_relevance": "Pertinenza",
|
||||||
"search_filters_sort_option_rating": "Valutazione",
|
"search_filters_sort_option_rating": "Valutazione",
|
||||||
"search_filters_sort_option_date": "Data di caricamento",
|
"search_filters_sort_option_date": "Data di caricamento",
|
||||||
|
@ -472,5 +471,10 @@
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_filters_apply_button": "Applica filtri selezionati",
|
"search_filters_apply_button": "Applica filtri selezionati",
|
||||||
"crash_page_refresh": "provato a <a href=\"`x`\">ricaricare la pagina</a>",
|
"crash_page_refresh": "provato a <a href=\"`x`\">ricaricare la pagina</a>",
|
||||||
"error_video_not_in_playlist": "Il video richiesto non esiste in questa playlist. <a href=\"`x`\">Fai clic qui per la pagina iniziale della playlist.</a>"
|
"error_video_not_in_playlist": "Il video richiesto non esiste in questa playlist. <a href=\"`x`\">Fai clic qui per la pagina iniziale della playlist.</a>",
|
||||||
|
"channel_tab_shorts_label": "Short",
|
||||||
|
"channel_tab_playlists_label": "Playlist",
|
||||||
|
"channel_tab_channels_label": "Canali",
|
||||||
|
"channel_tab_streams_label": "Livestream",
|
||||||
|
"channel_tab_community_label": "Comunità"
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,9 +341,9 @@
|
||||||
"`x` marked it with a ❤": "`x` が❤を込めてマークしました",
|
"`x` marked it with a ❤": "`x` が❤を込めてマークしました",
|
||||||
"Audio mode": "オーディオモード",
|
"Audio mode": "オーディオモード",
|
||||||
"Video mode": "ビデオモード",
|
"Video mode": "ビデオモード",
|
||||||
"Videos": "動画",
|
"channel_tab_videos_label": "動画",
|
||||||
"Playlists": "プレイリスト",
|
"Playlists": "プレイリスト",
|
||||||
"Community": "コミュニティ",
|
"channel_tab_community_label": "コミュニティ",
|
||||||
"search_filters_sort_option_relevance": "関連",
|
"search_filters_sort_option_relevance": "関連",
|
||||||
"search_filters_sort_option_rating": "評価",
|
"search_filters_sort_option_rating": "評価",
|
||||||
"search_filters_sort_option_date": "時刻",
|
"search_filters_sort_option_date": "時刻",
|
||||||
|
@ -403,7 +403,7 @@
|
||||||
"none": "なし",
|
"none": "なし",
|
||||||
"download_subtitles": "字幕 - `x` (.vtt)",
|
"download_subtitles": "字幕 - `x` (.vtt)",
|
||||||
"search_filters_features_option_purchased": "購入済み",
|
"search_filters_features_option_purchased": "購入済み",
|
||||||
"preferences_quality_option_dash": "DASH (適切な品質)",
|
"preferences_quality_option_dash": "DASH (適応品質)",
|
||||||
"preferences_quality_dash_option_worst": "最悪",
|
"preferences_quality_dash_option_worst": "最悪",
|
||||||
"preferences_quality_dash_option_best": "最高",
|
"preferences_quality_dash_option_best": "最高",
|
||||||
"videoinfo_started_streaming_x_ago": "`x`分前に配信を開始",
|
"videoinfo_started_streaming_x_ago": "`x`分前に配信を開始",
|
||||||
|
@ -438,5 +438,20 @@
|
||||||
"search_message_no_results": "一致する検索結果はありませんでした",
|
"search_message_no_results": "一致する検索結果はありませんでした",
|
||||||
"English (United States)": "英語 (アメリカ)",
|
"English (United States)": "英語 (アメリカ)",
|
||||||
"search_filters_date_label": "アップロード日",
|
"search_filters_date_label": "アップロード日",
|
||||||
"search_filters_features_option_vr180": "VR180"
|
"search_filters_features_option_vr180": "VR180",
|
||||||
|
"crash_page_switch_instance": "<a href=\"`x`\">別のインスタンスを使用</a>しようとしました",
|
||||||
|
"crash_page_read_the_faq": "<a href=\"`x`\">よくある質問 (FAQ)</a> を読む",
|
||||||
|
"Popular enabled: ": "人気動画を有効化 ",
|
||||||
|
"search_message_use_another_instance": " <a href=\"`x`\">別のインスタンスで検索</a>することもできます。",
|
||||||
|
"search_filters_apply_button": "選択したフィルターを適用",
|
||||||
|
"user_saved_playlists": "`x` 個の保存済みプレイリスト",
|
||||||
|
"crash_page_you_found_a_bug": "Invidious でバグを見つけたようです。",
|
||||||
|
"crash_page_refresh": "<a href=\"`x`\">ページを更新</a>しようとしました",
|
||||||
|
"preferences_watch_history_label": "視聴履歴を有効化 ",
|
||||||
|
"search_filters_date_option_none": "任意の日付",
|
||||||
|
"search_filters_type_option_all": "いかなるタイプ",
|
||||||
|
"search_filters_duration_option_none": "任意の期間",
|
||||||
|
"search_filters_duration_option_medium": "ミディアム (4 ~ 20 分)",
|
||||||
|
"preferences_save_player_pos_label": "再生位置を保存: ",
|
||||||
|
"crash_page_before_reporting": "バグを報告する前に、次のことを確認してください。"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"preferences_sort_label": "동영상 정렬 기준: ",
|
"preferences_sort_label": "동영상 정렬 기준: ",
|
||||||
"preferences_max_results_label": "피드에 표시된 동영상 수: ",
|
"preferences_max_results_label": "피드에 표시된 동영상 수: ",
|
||||||
"Redirect homepage to feed: ": "피드로 홈페이지 리디렉션: ",
|
"Redirect homepage to feed: ": "피드로 홈페이지 리디렉션: ",
|
||||||
"preferences_annotations_subscribed_label": "구독한 채널에 기본적으로 특수효과를 표시하시겠습니까? ",
|
"preferences_annotations_subscribed_label": "구독한 채널에 기본으로 주석 표시: ",
|
||||||
"preferences_category_subscription": "구독 설정",
|
"preferences_category_subscription": "구독 설정",
|
||||||
"preferences_automatic_instance_redirect_label": "자동 인스턴스 리디렉션 (redirect.invidious.io로 대체): ",
|
"preferences_automatic_instance_redirect_label": "자동 인스턴스 리디렉션 (redirect.invidious.io로 대체): ",
|
||||||
"preferences_thin_mode_label": "단순 모드: ",
|
"preferences_thin_mode_label": "단순 모드: ",
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
"preferences_dark_mode_label": "테마: ",
|
"preferences_dark_mode_label": "테마: ",
|
||||||
"Dark mode: ": "다크 모드: ",
|
"Dark mode: ": "다크 모드: ",
|
||||||
"preferences_player_style_label": "플레이어 스타일: ",
|
"preferences_player_style_label": "플레이어 스타일: ",
|
||||||
"preferences_category_visual": "시각 설정",
|
"preferences_category_visual": "환경 설정",
|
||||||
"preferences_vr_mode_label": "VR 영상 활성화(WebGL 필요): ",
|
"preferences_vr_mode_label": "VR 영상 활성화(WebGL 필요): ",
|
||||||
"preferences_extend_desc_label": "자동으로 비디오 설명을 확장: ",
|
"preferences_extend_desc_label": "자동으로 비디오 설명을 확장: ",
|
||||||
"preferences_annotations_label": "기본으로 주석 표시: ",
|
"preferences_annotations_label": "기본으로 주석 표시: ",
|
||||||
|
@ -25,8 +25,8 @@
|
||||||
"preferences_quality_label": "선호하는 비디오 품질: ",
|
"preferences_quality_label": "선호하는 비디오 품질: ",
|
||||||
"preferences_speed_label": "기본 속도: ",
|
"preferences_speed_label": "기본 속도: ",
|
||||||
"preferences_local_label": "비디오를 프록시: ",
|
"preferences_local_label": "비디오를 프록시: ",
|
||||||
"preferences_listen_label": "라디오 모드 활성화: ",
|
"preferences_listen_label": "라디오 모드: ",
|
||||||
"preferences_continue_autoplay_label": "다음 동영상 자동재생 ",
|
"preferences_continue_autoplay_label": "다음 동영상 자동재생: ",
|
||||||
"preferences_continue_label": "다음 동영상으로 이동: ",
|
"preferences_continue_label": "다음 동영상으로 이동: ",
|
||||||
"preferences_autoplay_label": "자동재생: ",
|
"preferences_autoplay_label": "자동재생: ",
|
||||||
"preferences_video_loop_label": "항상 반복: ",
|
"preferences_video_loop_label": "항상 반복: ",
|
||||||
|
@ -37,8 +37,8 @@
|
||||||
"Register": "회원가입",
|
"Register": "회원가입",
|
||||||
"Sign In": "로그인",
|
"Sign In": "로그인",
|
||||||
"preferences_category_misc": "기타 설정",
|
"preferences_category_misc": "기타 설정",
|
||||||
"Image CAPTCHA": "이미지 CAPTCHA",
|
"Image CAPTCHA": "이미지 캡차",
|
||||||
"Text CAPTCHA": "텍스트 CAPTCHA",
|
"Text CAPTCHA": "텍스트 캡차",
|
||||||
"Time (h:mm:ss):": "시각 (h:mm:ss):",
|
"Time (h:mm:ss):": "시각 (h:mm:ss):",
|
||||||
"Password": "비밀번호",
|
"Password": "비밀번호",
|
||||||
"User ID": "사용자 ID",
|
"User ID": "사용자 ID",
|
||||||
|
@ -50,15 +50,15 @@
|
||||||
"An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
|
"An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
|
||||||
"History": "역사",
|
"History": "역사",
|
||||||
"Delete account?": "계정을 삭제 하시겠습니까?",
|
"Delete account?": "계정을 삭제 하시겠습니까?",
|
||||||
"Export data as JSON": "데이터를 JSON으로 내보내기",
|
"Export data as JSON": "JSON으로 데이터 내보내기",
|
||||||
"Export subscriptions as OPML (for NewPipe & FreeTube)": "구독을 OPML로 내보내기 (NewPipe 및 FreeTube 용)",
|
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML로 구독 내보내기 (뉴파이프 및 프리튜브)",
|
||||||
"Export subscriptions as OPML": "구독을 OPML로 내보내기",
|
"Export subscriptions as OPML": "OPML로 구독 내보내기",
|
||||||
"Export": "내보내기",
|
"Export": "내보내기",
|
||||||
"Import NewPipe data (.zip)": "NewPipe 데이터 가져오기 (.zip)",
|
"Import NewPipe data (.zip)": "뉴파이프 데이터 가져오기 (.zip)",
|
||||||
"Import NewPipe subscriptions (.json)": "NewPipe 구독을 가져오기 (.json)",
|
"Import NewPipe subscriptions (.json)": "뉴파이프 구독 가져오기 (.json)",
|
||||||
"Import FreeTube subscriptions (.db)": "FreeTube 구독 가져오기 (.db)",
|
"Import FreeTube subscriptions (.db)": "프리튜브 구독 가져오기 (.db)",
|
||||||
"Import YouTube subscriptions": "유튜브 구독 가져오기",
|
"Import YouTube subscriptions": "유튜브 구독 가져오기",
|
||||||
"Import Invidious data": "인비디어스 JSON 데이터 가져오기",
|
"Import Invidious data": "인비디어스 데이터 가져오기 (.json)",
|
||||||
"Import": "가져오기",
|
"Import": "가져오기",
|
||||||
"Import and Export Data": "데이터 가져오기 및 내보내기",
|
"Import and Export Data": "데이터 가져오기 및 내보내기",
|
||||||
"No": "아니요",
|
"No": "아니요",
|
||||||
|
@ -150,9 +150,9 @@
|
||||||
"Subscription manager": "구독 관리자",
|
"Subscription manager": "구독 관리자",
|
||||||
"Save preferences": "설정 저장",
|
"Save preferences": "설정 저장",
|
||||||
"Report statistics: ": "통계 보고: ",
|
"Report statistics: ": "통계 보고: ",
|
||||||
"Registration enabled: ": "등록 활성화: ",
|
"Registration enabled: ": "회원가입 활성화: ",
|
||||||
"Login enabled: ": "로그인 활성화: ",
|
"Login enabled: ": "로그인 활성화: ",
|
||||||
"CAPTCHA enabled: ": "CAPTCHA 활성화: ",
|
"CAPTCHA enabled: ": "캡차 활성화: ",
|
||||||
"Top enabled: ": "Top 활성화: ",
|
"Top enabled: ": "Top 활성화: ",
|
||||||
"preferences_show_nick_label": "상단에 닉네임 표시: ",
|
"preferences_show_nick_label": "상단에 닉네임 표시: ",
|
||||||
"preferences_feed_menu_label": "피드 메뉴: ",
|
"preferences_feed_menu_label": "피드 메뉴: ",
|
||||||
|
@ -187,8 +187,8 @@
|
||||||
"Polish": "폴란드어",
|
"Polish": "폴란드어",
|
||||||
"Persian": "페르시아어",
|
"Persian": "페르시아어",
|
||||||
"Pashto": "파슈토어",
|
"Pashto": "파슈토어",
|
||||||
"Nyanja": "체와어",
|
"Nyanja": "냔자어",
|
||||||
"Norwegian Bokmål": "보크몰",
|
"Norwegian Bokmål": "노르웨이 부크몰어",
|
||||||
"Nepali": "네팔어",
|
"Nepali": "네팔어",
|
||||||
"Mongolian": "몽골어",
|
"Mongolian": "몽골어",
|
||||||
"Marathi": "마라티어",
|
"Marathi": "마라티어",
|
||||||
|
@ -284,10 +284,10 @@
|
||||||
"Password cannot be empty": "비밀번호는 비워둘 수 없습니다",
|
"Password cannot be empty": "비밀번호는 비워둘 수 없습니다",
|
||||||
"Please sign in using 'Log in with Google'": "'구글로 로그인'을 사용하여 로그인하세요",
|
"Please sign in using 'Log in with Google'": "'구글로 로그인'을 사용하여 로그인하세요",
|
||||||
"Wrong username or password": "잘못된 사용자 이름 또는 비밀번호",
|
"Wrong username or password": "잘못된 사용자 이름 또는 비밀번호",
|
||||||
"Password is a required field": "비밀번호는 필수 필드입니다",
|
"Password is a required field": "비밀번호는 필수 입력란입니다",
|
||||||
"User ID is a required field": "사용자 ID는 필수 필드입니다",
|
"User ID is a required field": "사용자 ID는 필수 입력란입니다",
|
||||||
"CAPTCHA is a required field": "CAPTCHA는 필수 필드입니다",
|
"CAPTCHA is a required field": "캡차는 필수 입력란입니다",
|
||||||
"Erroneous CAPTCHA": "잘못된 CAPTCHA",
|
"Erroneous CAPTCHA": "잘못된 캡차",
|
||||||
"Login failed. This may be because two-factor authentication is not turned on for your account.": "로그인 실패. 계정에 이중 인증이 설정되어 있지 않기 때문일 수 있습니다.",
|
"Login failed. This may be because two-factor authentication is not turned on for your account.": "로그인 실패. 계정에 이중 인증이 설정되어 있지 않기 때문일 수 있습니다.",
|
||||||
"Blacklisted regions: ": "차단된 지역: ",
|
"Blacklisted regions: ": "차단된 지역: ",
|
||||||
"Playlists": "재생목록",
|
"Playlists": "재생목록",
|
||||||
|
@ -297,7 +297,7 @@
|
||||||
"Empty playlist": "재생목록 비어 있음",
|
"Empty playlist": "재생목록 비어 있음",
|
||||||
"Show annotations": "주석 보이기",
|
"Show annotations": "주석 보이기",
|
||||||
"Hide annotations": "주석 숨기기",
|
"Hide annotations": "주석 숨기기",
|
||||||
"Switch Invidious Instance": "Invidious 인스턴스 변경",
|
"Switch Invidious Instance": "인비디어스 인스턴스 변경",
|
||||||
"Spanish": "스페인어",
|
"Spanish": "스페인어",
|
||||||
"Southern Sotho": "소토어",
|
"Southern Sotho": "소토어",
|
||||||
"Somali": "소말리어",
|
"Somali": "소말리어",
|
||||||
|
@ -347,8 +347,8 @@
|
||||||
"search_filters_sort_option_date": "업로드 날짜",
|
"search_filters_sort_option_date": "업로드 날짜",
|
||||||
"search_filters_sort_option_rating": "평점",
|
"search_filters_sort_option_rating": "평점",
|
||||||
"search_filters_sort_option_relevance": "관련성",
|
"search_filters_sort_option_relevance": "관련성",
|
||||||
"Community": "커뮤니티",
|
"channel_tab_community_label": "커뮤니티",
|
||||||
"Videos": "동영상",
|
"channel_tab_videos_label": "동영상",
|
||||||
"Video mode": "비디오 모드",
|
"Video mode": "비디오 모드",
|
||||||
"Audio mode": "오디오 모드",
|
"Audio mode": "오디오 모드",
|
||||||
"permalink": "퍼머링크",
|
"permalink": "퍼머링크",
|
||||||
|
@ -383,7 +383,7 @@
|
||||||
"adminprefs_modified_source_code_url_label": "수정된 소스 코드 저장소의 URL",
|
"adminprefs_modified_source_code_url_label": "수정된 소스 코드 저장소의 URL",
|
||||||
"search_filters_title": "필터",
|
"search_filters_title": "필터",
|
||||||
"preferences_quality_dash_option_4320p": "4320p",
|
"preferences_quality_dash_option_4320p": "4320p",
|
||||||
"Popular enabled: ": "인기 급상승 활성화: ",
|
"Popular enabled: ": "인기 활성화: ",
|
||||||
"Dutch (auto-generated)": "네덜란드어 (자동 생성됨)",
|
"Dutch (auto-generated)": "네덜란드어 (자동 생성됨)",
|
||||||
"Chinese (Hong Kong)": "중국어 (홍콩)",
|
"Chinese (Hong Kong)": "중국어 (홍콩)",
|
||||||
"Chinese (Taiwan)": "중국어 (대만)",
|
"Chinese (Taiwan)": "중국어 (대만)",
|
||||||
|
@ -415,7 +415,7 @@
|
||||||
"Spanish (auto-generated)": "스페인어 (자동 생성됨)",
|
"Spanish (auto-generated)": "스페인어 (자동 생성됨)",
|
||||||
"preferences_quality_dash_option_1080p": "1080p",
|
"preferences_quality_dash_option_1080p": "1080p",
|
||||||
"preferences_quality_dash_option_worst": "최저",
|
"preferences_quality_dash_option_worst": "최저",
|
||||||
"preferences_watch_history_label": "시청 기록 활성화: ",
|
"preferences_watch_history_label": "시청 기록 저장: ",
|
||||||
"invidious": "인비디어스",
|
"invidious": "인비디어스",
|
||||||
"preferences_quality_option_small": "낮음",
|
"preferences_quality_option_small": "낮음",
|
||||||
"preferences_quality_dash_option_auto": "자동",
|
"preferences_quality_dash_option_auto": "자동",
|
||||||
|
@ -439,10 +439,10 @@
|
||||||
"footer_donate_page": "기부하기",
|
"footer_donate_page": "기부하기",
|
||||||
"preferences_quality_option_dash": "DASH (다양한 화질)",
|
"preferences_quality_option_dash": "DASH (다양한 화질)",
|
||||||
"preferences_quality_dash_option_360p": "360p",
|
"preferences_quality_dash_option_360p": "360p",
|
||||||
"preferences_save_player_pos_label": "이어서 보기 활성화: ",
|
"preferences_save_player_pos_label": "이어서 보기: ",
|
||||||
"none": "없음",
|
"none": "없음",
|
||||||
"videoinfo_started_streaming_x_ago": "`x` 전에 스트리밍을 시작했습니다",
|
"videoinfo_started_streaming_x_ago": "`x` 전에 스트리밍을 시작했습니다",
|
||||||
"crash_page_you_found_a_bug": "Invidious에서 버그를 찾은 것 같습니다!",
|
"crash_page_you_found_a_bug": "인비디어스에서 버그를 찾은 것 같습니다!",
|
||||||
"download_subtitles": "자막 - `x`(.vtt)",
|
"download_subtitles": "자막 - `x`(.vtt)",
|
||||||
"user_saved_playlists": "`x`개의 저장된 재생목록",
|
"user_saved_playlists": "`x`개의 저장된 재생목록",
|
||||||
"crash_page_before_reporting": "버그를 보고하기 전에 다음 사항이 있는지 확인합니다:",
|
"crash_page_before_reporting": "버그를 보고하기 전에 다음 사항이 있는지 확인합니다:",
|
||||||
|
@ -456,5 +456,9 @@
|
||||||
"crash_page_report_issue": "위의 방법 중 어느 것도 도움이 되지 않았다면, <a href=\"`x`\">깃허브에서 새 이슈를 열고</a>(가능하면 영어로) 메시지에 다음 텍스트를 포함하세요(해당 텍스트를 번역하지 마십시오):",
|
"crash_page_report_issue": "위의 방법 중 어느 것도 도움이 되지 않았다면, <a href=\"`x`\">깃허브에서 새 이슈를 열고</a>(가능하면 영어로) 메시지에 다음 텍스트를 포함하세요(해당 텍스트를 번역하지 마십시오):",
|
||||||
"videoinfo_youTube_embed_link": "임베드",
|
"videoinfo_youTube_embed_link": "임베드",
|
||||||
"videoinfo_invidious_embed_link": "임베드 링크",
|
"videoinfo_invidious_embed_link": "임베드 링크",
|
||||||
"error_video_not_in_playlist": "요청한 동영상이 이 재생목록에 없습니다. <a href=\"`x`\">재생목록 목록을 보려면 여기를 클릭하십시오.</a>"
|
"error_video_not_in_playlist": "요청한 동영상이 이 재생목록에 없습니다. <a href=\"`x`\">재생목록 목록을 보려면 여기를 클릭하십시오.</a>",
|
||||||
|
"channel_tab_shorts_label": "쇼츠",
|
||||||
|
"channel_tab_streams_label": "실시간 스트리밍",
|
||||||
|
"channel_tab_channels_label": "채널",
|
||||||
|
"channel_tab_playlists_label": "재생목록"
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,9 +325,9 @@
|
||||||
"`x` marked it with a ❤": "`x` pažymėjo tai su ❤",
|
"`x` marked it with a ❤": "`x` pažymėjo tai su ❤",
|
||||||
"Audio mode": "Garso rėžimas",
|
"Audio mode": "Garso rėžimas",
|
||||||
"Video mode": "Vaizdo rėžimas",
|
"Video mode": "Vaizdo rėžimas",
|
||||||
"Videos": "Vaizdo įrašai",
|
"channel_tab_videos_label": "Vaizdo įrašai",
|
||||||
"Playlists": "Grojaraiščiai",
|
"Playlists": "Grojaraiščiai",
|
||||||
"Community": "Bendruomenė",
|
"channel_tab_community_label": "Bendruomenė",
|
||||||
"search_filters_sort_option_relevance": "Aktualumas",
|
"search_filters_sort_option_relevance": "Aktualumas",
|
||||||
"search_filters_sort_option_rating": "Reitingas",
|
"search_filters_sort_option_rating": "Reitingas",
|
||||||
"search_filters_sort_option_date": "Įkėlimo data",
|
"search_filters_sort_option_date": "Įkėlimo data",
|
||||||
|
|
|
@ -325,9 +325,9 @@
|
||||||
"`x` marked it with a ❤": "`x` levnet et ❤",
|
"`x` marked it with a ❤": "`x` levnet et ❤",
|
||||||
"Audio mode": "Lydmodus",
|
"Audio mode": "Lydmodus",
|
||||||
"Video mode": "Video-modus",
|
"Video mode": "Video-modus",
|
||||||
"Videos": "Videoer",
|
"channel_tab_videos_label": "Videoer",
|
||||||
"Playlists": "Spillelister",
|
"Playlists": "Spillelister",
|
||||||
"Community": "Gemenskap",
|
"channel_tab_community_label": "Gemenskap",
|
||||||
"search_filters_sort_option_relevance": "relevans",
|
"search_filters_sort_option_relevance": "relevans",
|
||||||
"search_filters_sort_option_rating": "vurdering",
|
"search_filters_sort_option_rating": "vurdering",
|
||||||
"search_filters_sort_option_date": "dato",
|
"search_filters_sort_option_date": "dato",
|
||||||
|
|
|
@ -320,9 +320,9 @@
|
||||||
"`x` marked it with a ❤": "`x` heeft dit gemarkeerd met ❤",
|
"`x` marked it with a ❤": "`x` heeft dit gemarkeerd met ❤",
|
||||||
"Audio mode": "Audiomodus",
|
"Audio mode": "Audiomodus",
|
||||||
"Video mode": "Videomodus",
|
"Video mode": "Videomodus",
|
||||||
"Videos": "Video's",
|
"channel_tab_videos_label": "Video's",
|
||||||
"Playlists": "Afspeellijsten",
|
"Playlists": "Afspeellijsten",
|
||||||
"Community": "Gemeenschap",
|
"channel_tab_community_label": "Gemeenschap",
|
||||||
"search_filters_sort_option_relevance": "relevantie",
|
"search_filters_sort_option_relevance": "relevantie",
|
||||||
"search_filters_sort_option_rating": "beoordeling",
|
"search_filters_sort_option_rating": "beoordeling",
|
||||||
"search_filters_sort_option_date": "datum",
|
"search_filters_sort_option_date": "datum",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -324,9 +324,9 @@
|
||||||
"`x` marked it with a ❤": "`x` oznaczonych ❤",
|
"`x` marked it with a ❤": "`x` oznaczonych ❤",
|
||||||
"Audio mode": "Tryb audio",
|
"Audio mode": "Tryb audio",
|
||||||
"Video mode": "Tryb wideo",
|
"Video mode": "Tryb wideo",
|
||||||
"Videos": "Filmy",
|
"channel_tab_videos_label": "Wideo",
|
||||||
"Playlists": "Playlisty",
|
"Playlists": "Playlisty",
|
||||||
"Community": "Społeczność",
|
"channel_tab_community_label": "Społeczność",
|
||||||
"search_filters_sort_option_relevance": "Trafność",
|
"search_filters_sort_option_relevance": "Trafność",
|
||||||
"search_filters_sort_option_rating": "Ocena",
|
"search_filters_sort_option_rating": "Ocena",
|
||||||
"search_filters_sort_option_date": "Data przesłania",
|
"search_filters_sort_option_date": "Data przesłania",
|
||||||
|
@ -488,5 +488,9 @@
|
||||||
"search_message_use_another_instance": " Możesz także <a href=\"`x`\">wyszukać w innej instancji</a>.",
|
"search_message_use_another_instance": " Możesz także <a href=\"`x`\">wyszukać w innej instancji</a>.",
|
||||||
"search_filters_type_option_all": "Dowolny typ",
|
"search_filters_type_option_all": "Dowolny typ",
|
||||||
"search_filters_duration_option_none": "Dowolna długość",
|
"search_filters_duration_option_none": "Dowolna długość",
|
||||||
"search_filters_duration_option_medium": "Średnia (4-20 minut)"
|
"search_filters_duration_option_medium": "Średnia (4-20 minut)",
|
||||||
|
"channel_tab_streams_label": "Na żywo",
|
||||||
|
"channel_tab_channels_label": "Kanały",
|
||||||
|
"channel_tab_playlists_label": "Playlisty",
|
||||||
|
"channel_tab_shorts_label": "Shorts"
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,9 +341,9 @@
|
||||||
"`x` marked it with a ❤": "`x` foi marcado como ❤",
|
"`x` marked it with a ❤": "`x` foi marcado como ❤",
|
||||||
"Audio mode": "Modo de áudio",
|
"Audio mode": "Modo de áudio",
|
||||||
"Video mode": "Modo de vídeo",
|
"Video mode": "Modo de vídeo",
|
||||||
"Videos": "Vídeos",
|
"channel_tab_videos_label": "Vídeos",
|
||||||
"Playlists": "Listas de reprodução",
|
"Playlists": "Listas de reprodução",
|
||||||
"Community": "Comunidade",
|
"channel_tab_community_label": "Comunidade",
|
||||||
"search_filters_sort_option_relevance": "relevância",
|
"search_filters_sort_option_relevance": "relevância",
|
||||||
"search_filters_sort_option_rating": "avaliação",
|
"search_filters_sort_option_rating": "avaliação",
|
||||||
"search_filters_sort_option_date": "data",
|
"search_filters_sort_option_date": "data",
|
||||||
|
@ -471,5 +471,6 @@
|
||||||
"Turkish (auto-generated)": "Turco (gerado automaticamente)",
|
"Turkish (auto-generated)": "Turco (gerado automaticamente)",
|
||||||
"search_filters_duration_option_medium": "Médio (4 - 20 minutos)",
|
"search_filters_duration_option_medium": "Médio (4 - 20 minutos)",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"Popular enabled: ": "Popular habilitado: "
|
"Popular enabled: ": "Popular habilitado: ",
|
||||||
|
"error_video_not_in_playlist": "O vídeo solicitado não existe nesta playlist. <a href=\"`x`\">Clique aqui para acessar a página inicial da playlist.</a>"
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,14 @@
|
||||||
"Import and Export Data": "Importar e exportar dados",
|
"Import and Export Data": "Importar e exportar dados",
|
||||||
"Import": "Importar",
|
"Import": "Importar",
|
||||||
"Import Invidious data": "Importar dados JSON do Invidious",
|
"Import Invidious data": "Importar dados JSON do Invidious",
|
||||||
"Import YouTube subscriptions": "Importar subscrições OPML ou do YouTube",
|
"Import YouTube subscriptions": "Importar subscrições do YouTube/OPML",
|
||||||
"Import FreeTube subscriptions (.db)": "Importar subscrições do FreeTube (.db)",
|
"Import FreeTube subscriptions (.db)": "Importar subscrições do FreeTube (.db)",
|
||||||
"Import NewPipe subscriptions (.json)": "Importar subscrições do NewPipe (.json)",
|
"Import NewPipe subscriptions (.json)": "Importar subscrições do NewPipe (.json)",
|
||||||
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
|
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
|
||||||
"Export": "Exportar",
|
"Export": "Exportar",
|
||||||
"Export subscriptions as OPML": "Exportar subscrições como OPML",
|
"Export subscriptions as OPML": "Exportar subscrições como OPML",
|
||||||
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportar subscrições como OPML (para NewPipe e FreeTube)",
|
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportar subscrições como OPML (para NewPipe e FreeTube)",
|
||||||
"Export data as JSON": "Exportar dados do Invidious como JSON",
|
"Export data as JSON": "Exportar dados Invidious como JSON",
|
||||||
"Delete account?": "Eliminar conta?",
|
"Delete account?": "Eliminar conta?",
|
||||||
"History": "Histórico",
|
"History": "Histórico",
|
||||||
"An alternative front-end to YouTube": "Uma interface alternativa ao YouTube",
|
"An alternative front-end to YouTube": "Uma interface alternativa ao YouTube",
|
||||||
|
@ -341,9 +341,9 @@
|
||||||
"`x` marked it with a ❤": "`x` foi marcado como ❤",
|
"`x` marked it with a ❤": "`x` foi marcado como ❤",
|
||||||
"Audio mode": "Modo de áudio",
|
"Audio mode": "Modo de áudio",
|
||||||
"Video mode": "Modo de vídeo",
|
"Video mode": "Modo de vídeo",
|
||||||
"Videos": "Vídeos",
|
"channel_tab_videos_label": "Vídeos",
|
||||||
"Playlists": "Listas de reprodução",
|
"Playlists": "Listas de reprodução",
|
||||||
"Community": "Comunidade",
|
"channel_tab_community_label": "Comunidade",
|
||||||
"search_filters_sort_option_relevance": "Relevância",
|
"search_filters_sort_option_relevance": "Relevância",
|
||||||
"search_filters_sort_option_rating": "Avaliação",
|
"search_filters_sort_option_rating": "Avaliação",
|
||||||
"search_filters_sort_option_date": "Data de envio",
|
"search_filters_sort_option_date": "Data de envio",
|
||||||
|
@ -379,24 +379,24 @@
|
||||||
"generic_videos_count_plural": "{{count}} vídeos",
|
"generic_videos_count_plural": "{{count}} vídeos",
|
||||||
"generic_playlists_count": "{{count}} lista de reprodução",
|
"generic_playlists_count": "{{count}} lista de reprodução",
|
||||||
"generic_playlists_count_plural": "{{count}} listas de reprodução",
|
"generic_playlists_count_plural": "{{count}} listas de reprodução",
|
||||||
"generic_subscriptions_count": "{{count}} subscrição",
|
"generic_subscriptions_count": "{{count}} inscrição",
|
||||||
"generic_subscriptions_count_plural": "{{count}} subscrições",
|
"generic_subscriptions_count_plural": "{{count}} inscrições",
|
||||||
"generic_views_count": "{{count}} visualização",
|
"generic_views_count": "{{count}} visualização",
|
||||||
"generic_views_count_plural": "{{count}} visualizações",
|
"generic_views_count_plural": "{{count}} visualizações",
|
||||||
"generic_subscribers_count": "{{count}} subscritor",
|
"generic_subscribers_count": "{{count}} inscrito",
|
||||||
"generic_subscribers_count_plural": "{{count}} subscritores",
|
"generic_subscribers_count_plural": "{{count}} inscritos",
|
||||||
"preferences_quality_dash_option_4320p": "4320p",
|
"preferences_quality_dash_option_4320p": "4320p",
|
||||||
"preferences_quality_dash_label": "Qualidade de vídeo DASH preferencial ",
|
"preferences_quality_dash_label": "Qualidade de vídeo DASH preferida: ",
|
||||||
"preferences_quality_dash_option_2160p": "2160p",
|
"preferences_quality_dash_option_2160p": "2160p",
|
||||||
"subscriptions_unseen_notifs_count": "{{count}} notificação por ver",
|
"subscriptions_unseen_notifs_count": "{{count}} notificação não vista",
|
||||||
"subscriptions_unseen_notifs_count_plural": "{{count}} notificações por ver",
|
"subscriptions_unseen_notifs_count_plural": "{{count}} notificações não vistas",
|
||||||
"Popular enabled: ": "Página \"Popular\" ativada: ",
|
"Popular enabled: ": "Página \"popular\" ativada: ",
|
||||||
"search_message_no_results": "Nenhum resultado encontrado.",
|
"search_message_no_results": "Nenhum resultado encontrado.",
|
||||||
"preferences_quality_dash_option_auto": "Automática",
|
"preferences_quality_dash_option_auto": "Automático",
|
||||||
"preferences_region_label": "País para o conteúdo: ",
|
"preferences_region_label": "País do conteúdo: ",
|
||||||
"preferences_quality_dash_option_1440p": "1440p",
|
"preferences_quality_dash_option_1440p": "1440p",
|
||||||
"preferences_quality_dash_option_720p": "720p",
|
"preferences_quality_dash_option_720p": "720p",
|
||||||
"preferences_watch_history_label": "Ativar histórico de visualizações ",
|
"preferences_watch_history_label": "Ativar histórico de reprodução: ",
|
||||||
"preferences_quality_dash_option_best": "Melhor",
|
"preferences_quality_dash_option_best": "Melhor",
|
||||||
"preferences_quality_dash_option_worst": "Pior",
|
"preferences_quality_dash_option_worst": "Pior",
|
||||||
"preferences_quality_dash_option_144p": "144p",
|
"preferences_quality_dash_option_144p": "144p",
|
||||||
|
@ -404,13 +404,13 @@
|
||||||
"preferences_quality_option_hd720": "HD720",
|
"preferences_quality_option_hd720": "HD720",
|
||||||
"preferences_quality_option_dash": "DASH (qualidade adaptativa)",
|
"preferences_quality_option_dash": "DASH (qualidade adaptativa)",
|
||||||
"preferences_quality_option_medium": "Média",
|
"preferences_quality_option_medium": "Média",
|
||||||
"preferences_quality_option_small": "Pequena",
|
"preferences_quality_option_small": "Baixa",
|
||||||
"preferences_quality_dash_option_1080p": "1080p",
|
"preferences_quality_dash_option_1080p": "1080p",
|
||||||
"preferences_quality_dash_option_480p": "480p",
|
"preferences_quality_dash_option_480p": "480p",
|
||||||
"preferences_quality_dash_option_360p": "360p",
|
"preferences_quality_dash_option_360p": "360p",
|
||||||
"preferences_quality_dash_option_240p": "240p",
|
"preferences_quality_dash_option_240p": "240p",
|
||||||
"Video unavailable": "Vídeo indisponível",
|
"Video unavailable": "Vídeo não disponível",
|
||||||
"Russian (auto-generated)": "Russo (geradas automaticamente)",
|
"Russian (auto-generated)": "Russo (gerado automaticamente)",
|
||||||
"comments_view_x_replies": "Ver {{count}} resposta",
|
"comments_view_x_replies": "Ver {{count}} resposta",
|
||||||
"comments_view_x_replies_plural": "Ver {{count}} respostas",
|
"comments_view_x_replies_plural": "Ver {{count}} respostas",
|
||||||
"comments_points_count": "{{count}} ponto",
|
"comments_points_count": "{{count}} ponto",
|
||||||
|
@ -418,18 +418,18 @@
|
||||||
"English (United Kingdom)": "Inglês (Reino Unido)",
|
"English (United Kingdom)": "Inglês (Reino Unido)",
|
||||||
"Chinese (Hong Kong)": "Chinês (Hong Kong)",
|
"Chinese (Hong Kong)": "Chinês (Hong Kong)",
|
||||||
"Chinese (Taiwan)": "Chinês (Taiwan)",
|
"Chinese (Taiwan)": "Chinês (Taiwan)",
|
||||||
"Dutch (auto-generated)": "Holandês (geradas automaticamente)",
|
"Dutch (auto-generated)": "Holandês (gerado automaticamente)",
|
||||||
"French (auto-generated)": "Francês (geradas automaticamente)",
|
"French (auto-generated)": "Francês (gerado automaticamente)",
|
||||||
"German (auto-generated)": "Alemão (geradas automaticamente)",
|
"German (auto-generated)": "Alemão (gerado automaticamente)",
|
||||||
"Indonesian (auto-generated)": "Indonésio (geradas automaticamente)",
|
"Indonesian (auto-generated)": "Indonésio (gerado automaticamente)",
|
||||||
"Interlingue": "Interlingue",
|
"Interlingue": "Interlíngua",
|
||||||
"Italian (auto-generated)": "Italiano (geradas automaticamente)",
|
"Italian (auto-generated)": "Italiano (gerado automaticamente)",
|
||||||
"Japanese (auto-generated)": "Japonês (geradas automaticamente)",
|
"Japanese (auto-generated)": "Japonês (gerado automaticamente)",
|
||||||
"Korean (auto-generated)": "Coreano (geradas automaticamente)",
|
"Korean (auto-generated)": "Coreano (gerado automaticamente)",
|
||||||
"Portuguese (auto-generated)": "Português (geradas automaticamente)",
|
"Portuguese (auto-generated)": "Português (gerado automaticamente)",
|
||||||
"Portuguese (Brazil)": "Português (Brasil)",
|
"Portuguese (Brazil)": "Português (Brasil)",
|
||||||
"Spanish (Spain)": "Espanhol (Espanha)",
|
"Spanish (Spain)": "Espanhol (Espanha)",
|
||||||
"Vietnamese (auto-generated)": "Vietnamita (geradas automaticamente)",
|
"Vietnamese (auto-generated)": "Vietnamita (gerado automaticamente)",
|
||||||
"search_filters_type_option_all": "Qualquer tipo",
|
"search_filters_type_option_all": "Qualquer tipo",
|
||||||
"search_filters_duration_option_none": "Qualquer duração",
|
"search_filters_duration_option_none": "Qualquer duração",
|
||||||
"search_filters_duration_option_short": "Curto (< 4 minutos)",
|
"search_filters_duration_option_short": "Curto (< 4 minutos)",
|
||||||
|
@ -438,29 +438,39 @@
|
||||||
"search_filters_features_option_purchased": "Comprado",
|
"search_filters_features_option_purchased": "Comprado",
|
||||||
"search_filters_apply_button": "Aplicar filtros selecionados",
|
"search_filters_apply_button": "Aplicar filtros selecionados",
|
||||||
"videoinfo_watch_on_youTube": "Ver no YouTube",
|
"videoinfo_watch_on_youTube": "Ver no YouTube",
|
||||||
"videoinfo_youTube_embed_link": "Embutir",
|
"videoinfo_youTube_embed_link": "Incorporar",
|
||||||
"adminprefs_modified_source_code_url_label": "URL do repositório do código-fonte modificado",
|
"adminprefs_modified_source_code_url_label": "URL do repositório do código-fonte alterado",
|
||||||
"videoinfo_invidious_embed_link": "Ligação embutida",
|
"videoinfo_invidious_embed_link": "Incorporar hiperligação",
|
||||||
"none": "nenhum",
|
"none": "nenhum",
|
||||||
"videoinfo_started_streaming_x_ago": "Entrou em direto há `x`",
|
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão há `x`",
|
||||||
"download_subtitles": "Legendas - `x` (.vtt)",
|
"download_subtitles": "Legendas - `x` (.vtt)",
|
||||||
"user_created_playlists": "`x` listas de reprodução criadas",
|
"user_created_playlists": "`x` listas de reprodução criadas",
|
||||||
"user_saved_playlists": "`x` listas de reprodução guardadas",
|
"user_saved_playlists": "`x` listas de reprodução guardadas",
|
||||||
"preferences_save_player_pos_label": "Guardar posição de reprodução: ",
|
"preferences_save_player_pos_label": "Guardar a posição de reprodução atual do vídeo: ",
|
||||||
"Turkish (auto-generated)": "Turco (geradas automaticamente)",
|
"Turkish (auto-generated)": "Turco (gerado automaticamente)",
|
||||||
"Cantonese (Hong Kong)": "Cantonês (Hong Kong)",
|
"Cantonese (Hong Kong)": "Cantonês (Hong Kong)",
|
||||||
"Chinese (China)": "Chinês (China)",
|
"Chinese (China)": "Chinês (China)",
|
||||||
"Spanish (auto-generated)": "Espanhol (geradas automaticamente)",
|
"Spanish (auto-generated)": "Espanhol (gerado automaticamente)",
|
||||||
"Spanish (Mexico)": "Espanhol (México)",
|
"Spanish (Mexico)": "Espanhol (México)",
|
||||||
"English (United States)": "Inglês (Estados Unidos)",
|
"English (United States)": "Inglês (Estados Unidos)",
|
||||||
"footer_donate_page": "Doar",
|
"footer_donate_page": "Doar",
|
||||||
"footer_documentation": "Documentação",
|
"footer_documentation": "Documentação",
|
||||||
"footer_source_code": "Código-fonte",
|
"footer_source_code": "Código-fonte",
|
||||||
"footer_original_source_code": "Código-fonte original",
|
"footer_original_source_code": "Código-fonte original",
|
||||||
"footer_modfied_source_code": "Código-fonte modificado",
|
"footer_modfied_source_code": "Código-fonte alterado",
|
||||||
"Chinese": "Chinês",
|
"Chinese": "Chinês",
|
||||||
"search_filters_date_label": "Data de carregamento",
|
"search_filters_date_label": "Data de publicação",
|
||||||
"search_filters_date_option_none": "Qualquer data",
|
"search_filters_date_option_none": "Qualquer data",
|
||||||
"search_filters_features_option_three_sixty": "360°",
|
"search_filters_features_option_three_sixty": "360°",
|
||||||
"search_filters_features_option_vr180": "VR180"
|
"search_filters_features_option_vr180": "VR180",
|
||||||
|
"search_message_use_another_instance": " Também pode <a href=\"`x`\">pesquisar noutra instância</a>.",
|
||||||
|
"crash_page_you_found_a_bug": "Parece que encontrou um erro no Invidious!",
|
||||||
|
"crash_page_before_reporting": "Antes de reportar um erro, verifique se:",
|
||||||
|
"crash_page_read_the_faq": "leia as <a href=\"`x`\">Perguntas frequentes (FAQ)</a>",
|
||||||
|
"crash_page_search_issue": "procurou se <a href=\"`x`\">o erro já foi reportado no GitHub</a>",
|
||||||
|
"crash_page_report_issue": "Se nenhuma opção acima ajudou, por favor <a href=\"`x`\">abra um novo problema no Github</a> (preferencialmente em inglês) e inclua o seguinte texto tal qual (NÃO o traduza):",
|
||||||
|
"search_message_change_filters_or_query": "Tente alargar os termos genéricos da pesquisa e/ou alterar os filtros.",
|
||||||
|
"crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>",
|
||||||
|
"crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>",
|
||||||
|
"error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para a página inicial da lista de reprodução.</a>"
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,9 +267,9 @@
|
||||||
"Next page": "Próxima página",
|
"Next page": "Próxima página",
|
||||||
"last": "últimos",
|
"last": "últimos",
|
||||||
"Current version: ": "Versão atual: ",
|
"Current version: ": "Versão atual: ",
|
||||||
"Community": "Comunidade",
|
"channel_tab_community_label": "Comunidade",
|
||||||
"Playlists": "Listas de reprodução",
|
"Playlists": "Listas de reprodução",
|
||||||
"Videos": "Vídeos",
|
"channel_tab_videos_label": "Vídeos",
|
||||||
"Video mode": "Modo de vídeo",
|
"Video mode": "Modo de vídeo",
|
||||||
"Audio mode": "Modo de áudio",
|
"Audio mode": "Modo de áudio",
|
||||||
"`x` marked it with a ❤": "`x` foi marcado como ❤",
|
"`x` marked it with a ❤": "`x` foi marcado como ❤",
|
||||||
|
|
|
@ -315,9 +315,9 @@
|
||||||
"`x` marked it with a ❤": "`x` l-a marcat cu o ❤",
|
"`x` marked it with a ❤": "`x` l-a marcat cu o ❤",
|
||||||
"Audio mode": "Mod audio",
|
"Audio mode": "Mod audio",
|
||||||
"Video mode": "Mod video",
|
"Video mode": "Mod video",
|
||||||
"Videos": "Videoclipuri",
|
"channel_tab_videos_label": "Videoclipuri",
|
||||||
"Playlists": "Liste de redare",
|
"Playlists": "Liste de redare",
|
||||||
"Community": "Comunitate",
|
"channel_tab_community_label": "Comunitate",
|
||||||
"Current version: ": "Versiunea actuală: ",
|
"Current version: ": "Versiunea actuală: ",
|
||||||
"crash_page_read_the_faq": "citit lista <a href=\"`x`\">Întrebărilor Frecvente (FAQ)</a>",
|
"crash_page_read_the_faq": "citit lista <a href=\"`x`\">Întrebărilor Frecvente (FAQ)</a>",
|
||||||
"generic_count_days_0": "{{count}} zi",
|
"generic_count_days_0": "{{count}} zi",
|
||||||
|
|
|
@ -325,9 +325,9 @@
|
||||||
"`x` marked it with a ❤": "❤ от автора канала \"`x`\"",
|
"`x` marked it with a ❤": "❤ от автора канала \"`x`\"",
|
||||||
"Audio mode": "Аудио режим",
|
"Audio mode": "Аудио режим",
|
||||||
"Video mode": "Видео режим",
|
"Video mode": "Видео режим",
|
||||||
"Videos": "Видео",
|
"channel_tab_videos_label": "Видео",
|
||||||
"Playlists": "Плейлисты",
|
"Playlists": "Плейлисты",
|
||||||
"Community": "Сообщество",
|
"channel_tab_community_label": "Сообщество",
|
||||||
"search_filters_sort_option_relevance": "по актуальности",
|
"search_filters_sort_option_relevance": "по актуальности",
|
||||||
"search_filters_sort_option_rating": "по рейтингу",
|
"search_filters_sort_option_rating": "по рейтингу",
|
||||||
"search_filters_sort_option_date": "по дате загрузки",
|
"search_filters_sort_option_date": "по дате загрузки",
|
||||||
|
|
|
@ -222,7 +222,7 @@
|
||||||
"About": "O aplikaciji",
|
"About": "O aplikaciji",
|
||||||
"%A %B %-d, %Y": "%A %-d %B %Y",
|
"%A %B %-d, %Y": "%A %-d %B %Y",
|
||||||
"Audio mode": "Avdio način",
|
"Audio mode": "Avdio način",
|
||||||
"Videos": "Videoposnetki",
|
"channel_tab_videos_label": "Videoposnetki",
|
||||||
"search_filters_date_label": "Datum nalaganja",
|
"search_filters_date_label": "Datum nalaganja",
|
||||||
"search_filters_date_option_today": "Danes",
|
"search_filters_date_option_today": "Danes",
|
||||||
"search_filters_date_option_week": "Ta teden",
|
"search_filters_date_option_week": "Ta teden",
|
||||||
|
@ -455,7 +455,7 @@
|
||||||
"Download": "Prenesi",
|
"Download": "Prenesi",
|
||||||
"permalink": "stalna povezava",
|
"permalink": "stalna povezava",
|
||||||
"`x` marked it with a ❤": "`x` ga je označil/a z ❤",
|
"`x` marked it with a ❤": "`x` ga je označil/a z ❤",
|
||||||
"Community": "Skupnost",
|
"channel_tab_community_label": "Skupnost",
|
||||||
"search_filters_features_option_three_sixty": "360°",
|
"search_filters_features_option_three_sixty": "360°",
|
||||||
"Video mode": "Video način",
|
"Video mode": "Video način",
|
||||||
"search_filters_features_option_c_commons": "Creative Commons",
|
"search_filters_features_option_c_commons": "Creative Commons",
|
||||||
|
|
|
@ -259,10 +259,10 @@
|
||||||
"YouTube comment permalink": "Permalidhje komenti YouTube",
|
"YouTube comment permalink": "Permalidhje komenti YouTube",
|
||||||
"Audio mode": "Mënyrë për audion",
|
"Audio mode": "Mënyrë për audion",
|
||||||
"Playlists": "Luajlista",
|
"Playlists": "Luajlista",
|
||||||
"Community": "Bashkësi",
|
"channel_tab_community_label": "Bashkësi",
|
||||||
"search_filters_sort_option_relevance": "Rëndësi",
|
"search_filters_sort_option_relevance": "Rëndësi",
|
||||||
"Video mode": "Mënyrë video",
|
"Video mode": "Mënyrë video",
|
||||||
"Videos": "Video",
|
"channel_tab_videos_label": "Video",
|
||||||
"search_filters_sort_option_rating": "Vlerësim",
|
"search_filters_sort_option_rating": "Vlerësim",
|
||||||
"search_filters_sort_option_date": "Datë ngarkimi",
|
"search_filters_sort_option_date": "Datë ngarkimi",
|
||||||
"search_filters_sort_option_views": "Numër parjesh",
|
"search_filters_sort_option_views": "Numër parjesh",
|
||||||
|
@ -446,6 +446,22 @@
|
||||||
"Import YouTube subscriptions": "Importoni pajtime YouTube/OPML",
|
"Import YouTube subscriptions": "Importoni pajtime YouTube/OPML",
|
||||||
"Export data as JSON": "Eksportoji të dhënat Invidious si JSON",
|
"Export data as JSON": "Eksportoji të dhënat Invidious si JSON",
|
||||||
"preferences_vr_mode_label": "Video me ndërveprim 360 gradë (lyp WebGL): ",
|
"preferences_vr_mode_label": "Video me ndërveprim 360 gradë (lyp WebGL): ",
|
||||||
"Shared `x`": "Ndau me të tjerë `x`",
|
"Shared `x`": "Ndarë me të tjerë më `x`",
|
||||||
"search_filters_title": "Filtra"
|
"search_filters_title": "Filtra",
|
||||||
|
"Popular enabled: ": "Me populloret të aktivizuara: ",
|
||||||
|
"error_video_not_in_playlist": "Videoja e kërkuar s’ekziston në këtë luajlistë. <a href=\"`x`\">Klikoni këtu për faqen hyrëse të luajlistës.</a>",
|
||||||
|
"search_message_use_another_instance": " Mundeni edhe të <a href=\"`x`\">kërkoni në një instancë tjetër</a>.",
|
||||||
|
"search_filters_date_label": "Datë ngarkimi",
|
||||||
|
"preferences_watch_history_label": "Aktivizo historik parjesh: ",
|
||||||
|
"Top enabled: ": "Me kryesueset të aktivizuara: ",
|
||||||
|
"preferences_video_loop_label": "Përsërite gjithmonë: ",
|
||||||
|
"search_message_no_results": "S’u gjetën përfundime.",
|
||||||
|
"Could not pull trending pages.": "S’u morën dot faqet në modë.",
|
||||||
|
"search_filters_date_option_none": "Çfarëdo date",
|
||||||
|
"search_message_change_filters_or_query": "Provoni të zgjeroni kërkesën tuaj të kërkimit dhe/ose të ndryshoni filtrat.",
|
||||||
|
"search_filters_type_option_all": "Çfarëdo lloji",
|
||||||
|
"search_filters_duration_option_none": "Çfarëdo kohëzgjatjeje",
|
||||||
|
"search_filters_duration_option_medium": "Mesatare (4 - 20 minuta)",
|
||||||
|
"search_filters_features_option_vr180": "VR180",
|
||||||
|
"search_filters_apply_button": "Apliko filtrat e përzgjedhur"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,7 +257,7 @@
|
||||||
"preferences_volume_label": "Jačina zvuka: ",
|
"preferences_volume_label": "Jačina zvuka: ",
|
||||||
"preferences_locale_label": "Jezik: ",
|
"preferences_locale_label": "Jezik: ",
|
||||||
"adminprefs_modified_source_code_url_label": "URL veza do skladišta sa Izmenjenom Izvornom Kodom",
|
"adminprefs_modified_source_code_url_label": "URL veza do skladišta sa Izmenjenom Izvornom Kodom",
|
||||||
"Community": "Zajednica",
|
"channel_tab_community_label": "Zajednica",
|
||||||
"Video mode": "Video mod",
|
"Video mode": "Video mod",
|
||||||
"Fallback captions: ": "Titl u slučaju da glavni nije dostupan: ",
|
"Fallback captions: ": "Titl u slučaju da glavni nije dostupan: ",
|
||||||
"Private": "Privatno",
|
"Private": "Privatno",
|
||||||
|
@ -289,7 +289,7 @@
|
||||||
"Erroneous token": "Pogrešan žeton",
|
"Erroneous token": "Pogrešan žeton",
|
||||||
"Czech": "Češki",
|
"Czech": "Češki",
|
||||||
"Latin": "Latinski",
|
"Latin": "Latinski",
|
||||||
"Videos": "Video klipovi",
|
"channel_tab_videos_label": "Video klipovi",
|
||||||
"search_filters_features_option_four_k": "4К",
|
"search_filters_features_option_four_k": "4К",
|
||||||
"footer_donate_page": "Doniraj",
|
"footer_donate_page": "Doniraj",
|
||||||
"English": "Engleski",
|
"English": "Engleski",
|
||||||
|
|
|
@ -245,7 +245,7 @@
|
||||||
"(edited)": "(измењено)",
|
"(edited)": "(измењено)",
|
||||||
"`x` marked it with a ❤": "`x` је означио/ла ово са ❤",
|
"`x` marked it with a ❤": "`x` је означио/ла ово са ❤",
|
||||||
"Audio mode": "Аудио мод",
|
"Audio mode": "Аудио мод",
|
||||||
"Videos": "Видео клипови",
|
"channel_tab_videos_label": "Видео клипови",
|
||||||
"search_filters_sort_option_views": "Број прегледа",
|
"search_filters_sort_option_views": "Број прегледа",
|
||||||
"search_filters_features_label": "Карактеристике",
|
"search_filters_features_label": "Карактеристике",
|
||||||
"search_filters_date_option_today": "Данас",
|
"search_filters_date_option_today": "Данас",
|
||||||
|
@ -298,7 +298,7 @@
|
||||||
"Ukrainian": "Украјински",
|
"Ukrainian": "Украјински",
|
||||||
"permalink": "трајна веза",
|
"permalink": "трајна веза",
|
||||||
"Pashto": "Паштунски",
|
"Pashto": "Паштунски",
|
||||||
"Community": "Заједница",
|
"channel_tab_community_label": "Заједница",
|
||||||
"Sindhi": "Синди",
|
"Sindhi": "Синди",
|
||||||
"Could not fetch comments": "Узимање коментара није успело",
|
"Could not fetch comments": "Узимање коментара није успело",
|
||||||
"Bangla": "Бангла/Бенгалски",
|
"Bangla": "Бангла/Бенгалски",
|
||||||
|
|
|
@ -323,9 +323,9 @@
|
||||||
"`x` marked it with a ❤": "`x` lämnade ett ❤",
|
"`x` marked it with a ❤": "`x` lämnade ett ❤",
|
||||||
"Audio mode": "Ljudläge",
|
"Audio mode": "Ljudläge",
|
||||||
"Video mode": "Videoläge",
|
"Video mode": "Videoläge",
|
||||||
"Videos": "Videor",
|
"channel_tab_videos_label": "Videor",
|
||||||
"Playlists": "Spellistor",
|
"Playlists": "Spellistor",
|
||||||
"Community": "Gemenskap",
|
"channel_tab_community_label": "Gemenskap",
|
||||||
"search_filters_sort_option_relevance": "Relevans",
|
"search_filters_sort_option_relevance": "Relevans",
|
||||||
"search_filters_sort_option_rating": "Rankning",
|
"search_filters_sort_option_rating": "Rankning",
|
||||||
"search_filters_sort_option_date": "Datum",
|
"search_filters_sort_option_date": "Datum",
|
||||||
|
|
564
locales/tr.json
564
locales/tr.json
|
@ -1,126 +1,126 @@
|
||||||
{
|
{
|
||||||
"LIVE": "CANLI",
|
"LIVE": "CANLI",
|
||||||
"Shared `x` ago": "`x` önce paylaşıldı",
|
"Shared `x` ago": "`x` Önce Paylaşıldı",
|
||||||
"Unsubscribe": "Abonelikten çık",
|
"Unsubscribe": "Abonelikten Çık",
|
||||||
"Subscribe": "Abone ol",
|
"Subscribe": "Abone Ol",
|
||||||
"View channel on YouTube": "Kanalı YouTube'da görüntüle",
|
"View channel on YouTube": "Kanalı YouTube'da Görüntüle",
|
||||||
"View playlist on YouTube": "Oynatma listesini YouTube'da görüntüle",
|
"View playlist on YouTube": "Oynatma Listesini YouTube'da Görüntüle",
|
||||||
"newest": "en yeni",
|
"newest": "En Yeni",
|
||||||
"oldest": "en eski",
|
"oldest": "En Eski",
|
||||||
"popular": "popüler",
|
"popular": "Popüler",
|
||||||
"last": "son",
|
"last": "Son",
|
||||||
"Next page": "Sonraki sayfa",
|
"Next page": "Sonraki Sayfa",
|
||||||
"Previous page": "Önceki sayfa",
|
"Previous page": "Önceki Sayfa",
|
||||||
"Clear watch history?": "İzleme geçmişi temizlensin mi?",
|
"Clear watch history?": "İzleme geçmişi temizlensin mi?",
|
||||||
"New password": "Yeni parola",
|
"New password": "Yeni Parola",
|
||||||
"New passwords must match": "Yeni parolalar eşleşmek zorunda",
|
"New passwords must match": "Yeni Parolalar Eşleşmek Zorunda",
|
||||||
"Cannot change password for Google accounts": "Google hesapları için parola değiştirilemez",
|
"Cannot change password for Google accounts": "Google Hesapları İçin Parola Değiştirilemez",
|
||||||
"Authorize token?": "Belirteç yetkilendirilsin mi?",
|
"Authorize token?": "Belirteç yetkilendirilsin mi?",
|
||||||
"Authorize token for `x`?": "`x` için belirteç yetkilendirilsin mi?",
|
"Authorize token for `x`?": "`x` için belirteç yetkilendirilsin mi?",
|
||||||
"Yes": "Evet",
|
"Yes": "Evet",
|
||||||
"No": "Hayır",
|
"No": "Hayır",
|
||||||
"Import and Export Data": "Verileri İçe ve Dışa Aktar",
|
"Import and Export Data": "Verileri İçe ve Dışa Aktar",
|
||||||
"Import": "İçe aktar",
|
"Import": "İçe Aktar",
|
||||||
"Import Invidious data": "İnvidious JSON verilerini içe aktar",
|
"Import Invidious data": "Invidious JSON Verilerini İçe Aktar",
|
||||||
"Import YouTube subscriptions": "YouTube/OPML aboneliklerini içe aktar",
|
"Import YouTube subscriptions": "YouTube/OPML Aboneliklerini İçe Aktar",
|
||||||
"Import FreeTube subscriptions (.db)": "FreeTube aboneliklerini içe aktar (.db)",
|
"Import FreeTube subscriptions (.db)": "FreeTube Aboneliklerini İçe Aktar (.db)",
|
||||||
"Import NewPipe subscriptions (.json)": "NewPipe aboneliklerini içe aktar (.json)",
|
"Import NewPipe subscriptions (.json)": "NewPipe Aboneliklerini İçe Aktar (.json)",
|
||||||
"Import NewPipe data (.zip)": "NewPipe verilerini içe aktar (.zip)",
|
"Import NewPipe data (.zip)": "NewPipe Verilerini İçe Aktar (.zip)",
|
||||||
"Export": "Dışa aktar",
|
"Export": "Dışa Aktar",
|
||||||
"Export subscriptions as OPML": "Abonelikleri OPML olarak dışa aktar",
|
"Export subscriptions as OPML": "Abonelikleri OPML Olarak Dışa Aktar",
|
||||||
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Abonelikleri OPML olarak dışa aktar (NewPipe ve FreeTube için)",
|
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Abonelikleri OPML Olarak Dışa Aktar (NewPipe ve FreeTube İçin)",
|
||||||
"Export data as JSON": "Invidious verilerini JSON olarak dışa aktar",
|
"Export data as JSON": "İnvidious Verilerini JSON Olarak Dışa Aktar",
|
||||||
"Delete account?": "Hesap silinsin mi?",
|
"Delete account?": "Hesap silinsin mi?",
|
||||||
"History": "Geçmiş",
|
"History": "Geçmiş",
|
||||||
"An alternative front-end to YouTube": "YouTube için alternatif bir ön-yüz",
|
"An alternative front-end to YouTube": "YouTube İçin Alternatif Bir Ön-Yüz",
|
||||||
"JavaScript license information": "JavaScript lisans bilgileri",
|
"JavaScript license information": "JavaScript Lisans Bilgileri",
|
||||||
"source": "kaynak",
|
"source": "Kaynak",
|
||||||
"Log in": "Oturum aç",
|
"Log in": "Oturum Aç",
|
||||||
"Log in/register": "Oturum aç/kayıt ol",
|
"Log in/register": "Oturum Aç/Kayıt Ol",
|
||||||
"Log in with Google": "Google ile oturum aç",
|
"Log in with Google": "Google İle Oturum Aç",
|
||||||
"User ID": "Kullanıcı kimliği",
|
"User ID": "Kullanıcı Kimliği",
|
||||||
"Password": "Parola",
|
"Password": "Parola",
|
||||||
"Time (h:mm:ss):": "Zaman (h:mm:ss):",
|
"Time (h:mm:ss):": "Zaman (h:mm:ss):",
|
||||||
"Text CAPTCHA": "Metin CAPTCHA",
|
"Text CAPTCHA": "Metin CAPTCHA",
|
||||||
"Image CAPTCHA": "Resim CAPTCHA",
|
"Image CAPTCHA": "Resim CAPTCHA",
|
||||||
"Sign In": "Oturum Aç",
|
"Sign In": "Oturum Aç",
|
||||||
"Register": "Kayıt Ol",
|
"Register": "Kayıt Ol",
|
||||||
"E-mail": "E-posta",
|
"E-mail": "E-Posta",
|
||||||
"Google verification code": "Google doğrulama kodu",
|
"Google verification code": "Google Doğrulama Kodu",
|
||||||
"Preferences": "Tercihler",
|
"Preferences": "Tercihler",
|
||||||
"preferences_category_player": "Oynatıcı tercihleri",
|
"preferences_category_player": "Oynatıcı Tercihleri",
|
||||||
"preferences_video_loop_label": "Sürekli döngü: ",
|
"preferences_video_loop_label": "Sürekli Döngü: ",
|
||||||
"preferences_autoplay_label": "Otomatik oynat: ",
|
"preferences_autoplay_label": "Otomatik Oynat: ",
|
||||||
"preferences_continue_label": "Öntanımlı olarak sonrakini oynat: ",
|
"preferences_continue_label": "Öntanımlı Olarak Sonrakini Oynat: ",
|
||||||
"preferences_continue_autoplay_label": "Sonraki videoyu otomatik oynat: ",
|
"preferences_continue_autoplay_label": "Sonraki Videoyu Otomatik Oynat: ",
|
||||||
"preferences_listen_label": "Öntanımlı olarak dinle: ",
|
"preferences_listen_label": "Öntanımlı Olarak Dinle: ",
|
||||||
"preferences_local_label": "Videoları proxy'le: ",
|
"preferences_local_label": "Videolara Proxy Uygula: ",
|
||||||
"preferences_speed_label": "Öntanımlı hız: ",
|
"preferences_speed_label": "Öntanımlı Hız: ",
|
||||||
"preferences_quality_label": "Tercih edilen video kalitesi: ",
|
"preferences_quality_label": "Tercih Edilen Video Kalitesi: ",
|
||||||
"preferences_volume_label": "Oynatıcı ses seviyesi: ",
|
"preferences_volume_label": "Oynatıcı Ses Seviyesi: ",
|
||||||
"preferences_comments_label": "Öntanımlı yorumlar: ",
|
"preferences_comments_label": "Öntanımlı Yorumlar: ",
|
||||||
"youtube": "YouTube",
|
"youtube": "YouTube",
|
||||||
"reddit": "Reddit",
|
"reddit": "Reddit",
|
||||||
"preferences_captions_label": "Öntanımlı altyazılar: ",
|
"preferences_captions_label": "Öntanımlı Altyazılar: ",
|
||||||
"Fallback captions: ": "Yedek altyazılar: ",
|
"Fallback captions: ": "Yedek Altyazılar: ",
|
||||||
"preferences_related_videos_label": "İlgili videoları göster: ",
|
"preferences_related_videos_label": "İlgili Videoları Göster: ",
|
||||||
"preferences_annotations_label": "Öntanımlı olarak ek açıklamaları göster: ",
|
"preferences_annotations_label": "Öntanımlı Olarak Ek Açıklamaları Göster: ",
|
||||||
"preferences_extend_desc_label": "Video açıklamasını otomatik olarak genişlet: ",
|
"preferences_extend_desc_label": "Video Açıklamasını Otomatik Olarak Genişlet: ",
|
||||||
"preferences_vr_mode_label": "Etkileşimli 360 derece videolar (WebGL gerektirir): ",
|
"preferences_vr_mode_label": "Etkileşimli 360 Derece Videolar (WebGL Gerektirir): ",
|
||||||
"preferences_category_visual": "Görsel tercihler",
|
"preferences_category_visual": "Görsel Tercihler",
|
||||||
"preferences_player_style_label": "Oynatıcı biçimi: ",
|
"preferences_player_style_label": "Oynatıcı Biçimi: ",
|
||||||
"Dark mode: ": "Karanlık mod: ",
|
"Dark mode: ": "Koyu Mod: ",
|
||||||
"preferences_dark_mode_label": "Tema: ",
|
"preferences_dark_mode_label": "Tema: ",
|
||||||
"dark": "karanlık",
|
"dark": "Koyu",
|
||||||
"light": "aydınlık",
|
"light": "Açık",
|
||||||
"preferences_thin_mode_label": "İnce mod: ",
|
"preferences_thin_mode_label": "İnce Mod: ",
|
||||||
"preferences_category_misc": "Çeşitli tercihler",
|
"preferences_category_misc": "Çeşitli Tercihler",
|
||||||
"preferences_automatic_instance_redirect_label": "Otomatik örnek yeniden yönlendirmesi (yedek: redirect.invidious.io): ",
|
"preferences_automatic_instance_redirect_label": "Otomatik Örnek Yeniden Yönlendirmesi (Yedek: redirect.invidious.io): ",
|
||||||
"preferences_category_subscription": "Abonelik tercihleri",
|
"preferences_category_subscription": "Abonelik Tercihleri",
|
||||||
"preferences_annotations_subscribed_label": "Abone olunan kanallar için ek açıklamaları öntanımlı olarak göster: ",
|
"preferences_annotations_subscribed_label": "Abone Olunan Kanallar İçin Ek Açıklamaları Öntanımlı Olarak Göster: ",
|
||||||
"Redirect homepage to feed: ": "Ana sayfayı akışa yönlendir: ",
|
"Redirect homepage to feed: ": "Ana Sayfayı Akışa Yönlendir: ",
|
||||||
"preferences_max_results_label": "Akışta gösterilen video sayısı: ",
|
"preferences_max_results_label": "Akışta Gösterilen Video Sayısı: ",
|
||||||
"preferences_sort_label": "Videoları sıralama kriteri: ",
|
"preferences_sort_label": "Videoları Sıralama Kriteri: ",
|
||||||
"published": "yayınlandı",
|
"published": "Yayınlandı",
|
||||||
"published - reverse": "yayınlandı - ters",
|
"published - reverse": "Yayınlandı - Ters",
|
||||||
"alphabetically": "alfabetik olarak",
|
"alphabetically": "Alfabetik Olarak",
|
||||||
"alphabetically - reverse": "alfabetik olarak - ters",
|
"alphabetically - reverse": "Alfabetik Olarak - Ters",
|
||||||
"channel name": "kanal adı",
|
"channel name": "Kanal Adı",
|
||||||
"channel name - reverse": "kanal adı - ters",
|
"channel name - reverse": "Kanal Adı - Ters",
|
||||||
"Only show latest video from channel: ": "Sadece kanaldaki en son videoyu göster: ",
|
"Only show latest video from channel: ": "Sadece Kanaldaki En Son Videoyu Göster: ",
|
||||||
"Only show latest unwatched video from channel: ": "Sadece kanaldaki en son izlenmemiş videoyu göster: ",
|
"Only show latest unwatched video from channel: ": "Sadece Kanaldaki En Son İzlenmemiş Videoyu Göster: ",
|
||||||
"preferences_unseen_only_label": "Sadece izlenmemişleri göster: ",
|
"preferences_unseen_only_label": "Sadece İzlenmemişleri Göster: ",
|
||||||
"preferences_notifications_only_label": "Sadece bildirimleri göster (eğer varsa): ",
|
"preferences_notifications_only_label": "Sadece Bildirimleri Göster (Eğer Varsa): ",
|
||||||
"Enable web notifications": "Ağ bildirimlerini etkinleştir",
|
"Enable web notifications": "Ağ Bildirimlerini Etkinleştir",
|
||||||
"`x` uploaded a video": "`x` bir video yükledi",
|
"`x` uploaded a video": "`x` Bir Video Yükledi",
|
||||||
"`x` is live": "`x` canlı yayında",
|
"`x` is live": "`x` Canlı Yayında",
|
||||||
"preferences_category_data": "Veri tercihleri",
|
"preferences_category_data": "Veri Tercihleri",
|
||||||
"Clear watch history": "İzleme geçmişini temizle",
|
"Clear watch history": "İzleme Geçmişini Temizle",
|
||||||
"Import/export data": "Verileri içe/dışa aktar",
|
"Import/export data": "Verileri İçe/Dışa Aktar",
|
||||||
"Change password": "Parolayı değiştir",
|
"Change password": "Parolayı Değiştir",
|
||||||
"Manage subscriptions": "Abonelikleri yönet",
|
"Manage subscriptions": "Abonelikleri Yönet",
|
||||||
"Manage tokens": "Belirteçleri yönet",
|
"Manage tokens": "Belirteçleri Yönet",
|
||||||
"Watch history": "İzleme geçmişi",
|
"Watch history": "İzleme Geçmişi",
|
||||||
"Delete account": "Hesap silme",
|
"Delete account": "Hesap Silme",
|
||||||
"preferences_category_admin": "Yönetici tercihleri",
|
"preferences_category_admin": "Yönetici Tercihleri",
|
||||||
"preferences_default_home_label": "Öntanımlı ana sayfa: ",
|
"preferences_default_home_label": "Öntanımlı Ana Sayfa: ",
|
||||||
"preferences_feed_menu_label": "Akış menüsü: ",
|
"preferences_feed_menu_label": "Akış Menüsü: ",
|
||||||
"preferences_show_nick_label": "Takma adı üstte göster: ",
|
"preferences_show_nick_label": "Takma Adı Üstte Göster: ",
|
||||||
"Top enabled: ": "Top etkin: ",
|
"Top enabled: ": "Top Etkin: ",
|
||||||
"CAPTCHA enabled: ": "CAPTCHA etkin: ",
|
"CAPTCHA enabled: ": "CAPTCHA Etkin: ",
|
||||||
"Login enabled: ": "Oturum açma etkin: ",
|
"Login enabled: ": "Oturum Açma Etkin: ",
|
||||||
"Registration enabled: ": "Kayıt olma etkin: ",
|
"Registration enabled: ": "Kayıt Olma Etkin: ",
|
||||||
"Report statistics: ": "Rapor istatistikleri: ",
|
"Report statistics: ": "Rapor İstatistikleri: ",
|
||||||
"Save preferences": "Tercihleri kaydet",
|
"Save preferences": "Tercihleri Kaydet",
|
||||||
"Subscription manager": "Abonelik yöneticisi",
|
"Subscription manager": "Abonelik Yöneticisi",
|
||||||
"Token manager": "Belirteç yöneticisi",
|
"Token manager": "Belirteç Yöneticisi",
|
||||||
"Token": "Belirteç",
|
"Token": "Belirteç",
|
||||||
"Import/export": "İçe/dışa aktar",
|
"Import/export": "İçe/Dışa Aktar",
|
||||||
"unsubscribe": "abonelikten çık",
|
"unsubscribe": "Abonelikten Çık",
|
||||||
"revoke": "geri al",
|
"revoke": "Geri Al",
|
||||||
"Subscriptions": "Abonelikler",
|
"Subscriptions": "Abonelikler",
|
||||||
"search": "ara",
|
"search": "Ara",
|
||||||
"Log out": "Çıkış yap",
|
"Log out": "Çıkış Yap",
|
||||||
"Released under the AGPLv3 on Github.": "GitHub'da AGPLv3 altında yayınlandı.",
|
"Released under the AGPLv3 on Github.": "GitHub'da AGPLv3 altında yayınlandı.",
|
||||||
"Source available here.": "Kaynak kodları burada bulunabilir.",
|
"Source available here.": "Kaynak kodları burada bulunabilir.",
|
||||||
"View JavaScript license information.": "JavaScript lisans bilgilerini görüntüle.",
|
"View JavaScript license information.": "JavaScript lisans bilgilerini görüntüle.",
|
||||||
|
@ -129,76 +129,76 @@
|
||||||
"Public": "Genel",
|
"Public": "Genel",
|
||||||
"Unlisted": "Listelenmemiş",
|
"Unlisted": "Listelenmemiş",
|
||||||
"Private": "Özel",
|
"Private": "Özel",
|
||||||
"View all playlists": "Tüm oynatma listelerini görüntüle",
|
"View all playlists": "Tüm Oynatma Listelerini Görüntüle",
|
||||||
"Updated `x` ago": "`x` önce güncellendi",
|
"Updated `x` ago": "`x` Önce Güncellendi",
|
||||||
"Delete playlist `x`?": "`x` oynatma listesi silinsin mi?",
|
"Delete playlist `x`?": "`x` oynatma listesi silinsin mi?",
|
||||||
"Delete playlist": "Oynatma listesini sil",
|
"Delete playlist": "Oynatma Listesini Sil",
|
||||||
"Create playlist": "Oynatma listesi oluştur",
|
"Create playlist": "Oynatma Listesi Oluştur",
|
||||||
"Title": "Başlık",
|
"Title": "Başlık",
|
||||||
"Playlist privacy": "Oynatma listesi gizliliği",
|
"Playlist privacy": "Oynatma Listesi Gizliliği",
|
||||||
"Editing playlist `x`": "`x` oynatma listesi düzenleniyor",
|
"Editing playlist `x`": "`x` Oynatma Listesi Düzenleniyor",
|
||||||
"Show more": "Daha fazla göster",
|
"Show more": "Daha Fazla Göster",
|
||||||
"Show less": "Daha az göster",
|
"Show less": "Daha Az Göster",
|
||||||
"Watch on YouTube": "YouTube'da izle",
|
"Watch on YouTube": "YouTube'da İzle",
|
||||||
"Switch Invidious Instance": "Invidious Örneğini Değiştir",
|
"Switch Invidious Instance": "Invidious Örneğini Değiştir",
|
||||||
"Hide annotations": "Ek açıklamaları gizle",
|
"Hide annotations": "Ek Açıklamaları Gizle",
|
||||||
"Show annotations": "Ek açıklamaları göster",
|
"Show annotations": "Ek Açıklamaları Göster",
|
||||||
"Genre: ": "Tür: ",
|
"Genre: ": "Tür: ",
|
||||||
"License: ": "Lisans: ",
|
"License: ": "Lisans: ",
|
||||||
"Family friendly? ": "Aile için uygun mu? ",
|
"Family friendly? ": "Aile için uygun mu? ",
|
||||||
"Wilson score: ": "Wilson puanı: ",
|
"Wilson score: ": "Wilson Puanı: ",
|
||||||
"Engagement: ": "İzleyenlerin oy verme oranı: ",
|
"Engagement: ": "İzleyenlerin Oy Verme Oranı: ",
|
||||||
"Whitelisted regions: ": "Beyaz listeye alınan bölgeler: ",
|
"Whitelisted regions: ": "Beyaz Listeye Alınan Bölgeler: ",
|
||||||
"Blacklisted regions: ": "Kara listeye alınan bölgeler: ",
|
"Blacklisted regions: ": "Kara Listeye Alınan Bölgeler: ",
|
||||||
"Shared `x`": "`x` paylaşıldı",
|
"Shared `x`": "`x` Paylaşıldı",
|
||||||
"Premieres in `x`": "`x`içinde ilk gösterim",
|
"Premieres in `x`": "`x`İçinde İlk Gösterim",
|
||||||
"Premieres `x`": "`x` ilk gösterim",
|
"Premieres `x`": "`x` İlk Gösterim",
|
||||||
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Merhaba! JavaScript'i kapatmış gibi görünüyorsun. Yorumları görüntülemek için buraya tıkla, yüklenmelerinin biraz uzun sürebileceğini unutma.",
|
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Merhaba! JavaScript'i kapatmış gibi görünüyorsun. Yorumları görüntülemek için buraya tıkla, yüklenmelerinin biraz uzun sürebileceğini unutma.",
|
||||||
"View YouTube comments": "YouTube yorumlarını görüntüle",
|
"View YouTube comments": "YouTube Yorumlarını Görüntüle",
|
||||||
"View more comments on Reddit": "Reddit'te daha fazla yorum görüntüle",
|
"View more comments on Reddit": "Reddit'te Daha Fazla Yorum Görüntüle",
|
||||||
"View `x` comments": {
|
"View `x` comments": {
|
||||||
"([^.,0-9]|^)1([^.,0-9]|$)": "`x` yorumu görüntüle",
|
"([^.,0-9]|^)1([^.,0-9]|$)": "`x` Yorumu Görüntüle",
|
||||||
"": "`x` yorumu görüntüle"
|
"": "`x` Yorumu Görüntüle"
|
||||||
},
|
},
|
||||||
"View Reddit comments": "Reddit yorumlarını görüntüle",
|
"View Reddit comments": "Reddit Yorumlarını Görüntüle",
|
||||||
"Hide replies": "Cevapları gizle",
|
"Hide replies": "Cevapları Gizle",
|
||||||
"Show replies": "Cevapları göster",
|
"Show replies": "Cevapları Göster",
|
||||||
"Incorrect password": "Yanlış parola",
|
"Incorrect password": "Yanlış Parola",
|
||||||
"Quota exceeded, try again in a few hours": "Kota aşıldı, birkaç saat içinde tekrar deneyin",
|
"Quota exceeded, try again in a few hours": "Kota aşıldı, birkaç saat içinde tekrar deneyin.",
|
||||||
"Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Oturum açılamadı, iki faktörlü kimlik doğrulamanın (Authenticator ya da SMS) açık olduğundan emin olun.",
|
"Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Oturum açılamadı, iki faktörlü kimlik doğrulamanın (Kimlik Doğrulayıcı ya da SMS) açık olduğundan emin olun.",
|
||||||
"Invalid TFA code": "Geçersiz TFA kodu",
|
"Invalid TFA code": "Geçersiz TFA Kodu",
|
||||||
"Login failed. This may be because two-factor authentication is not turned on for your account.": "Giriş başarısız. Bunun nedeni, hesabınız için iki faktörlü kimlik doğrulamanın açık olmaması olabilir.",
|
"Login failed. This may be because two-factor authentication is not turned on for your account.": "Giriş başarısız. Bunun nedeni, hesabınız için iki faktörlü kimlik doğrulamanın açık olmaması olabilir.",
|
||||||
"Wrong answer": "Yanlış cevap",
|
"Wrong answer": "Yanlış Cevap",
|
||||||
"Erroneous CAPTCHA": "Hatalı CAPTCHA",
|
"Erroneous CAPTCHA": "Hatalı CAPTCHA",
|
||||||
"CAPTCHA is a required field": "CAPTCHA zorunlu bir alandır",
|
"CAPTCHA is a required field": "CAPTCHA Zorunlu Bir Alandır",
|
||||||
"User ID is a required field": "Kullanıcı kimliği zorunlu bir alandır",
|
"User ID is a required field": "Kullanıcı Kimliği Zorunlu Bir Alandır",
|
||||||
"Password is a required field": "Parola zorunlu bir alandır",
|
"Password is a required field": "Parola Zorunlu Bir Alandır",
|
||||||
"Wrong username or password": "Yanlış kullanıcı adı ya da parola",
|
"Wrong username or password": "Yanlış Kullanıcı Adı ya da Parola",
|
||||||
"Please sign in using 'Log in with Google'": "Lütfen 'Google ile giriş yap' seçeneğini kullanarak oturum açın",
|
"Please sign in using 'Log in with Google'": "Lütfen 'Google İle Giriş Yap' Seçeneğini Kullanarak Oturum Açın",
|
||||||
"Password cannot be empty": "Parola boş olamaz",
|
"Password cannot be empty": "Parola Boş Olamaz",
|
||||||
"Password cannot be longer than 55 characters": "Parola 55 karakterden uzun olamaz",
|
"Password cannot be longer than 55 characters": "Parola 55 Karakterden Uzun Olamaz",
|
||||||
"Please log in": "Lütfen oturum açın",
|
"Please log in": "Lütfen Oturum Açın",
|
||||||
"Invidious Private Feed for `x`": "`x` için İnvidious Özel Akışı",
|
"Invidious Private Feed for `x`": "`x` İçin Invidious Özel Akışı",
|
||||||
"channel:`x`": "kanal:`x`",
|
"channel:`x`": "Kanal:`x`",
|
||||||
"Deleted or invalid channel": "Silinmiş ya da geçersiz kanal",
|
"Deleted or invalid channel": "Silinmiş ya da Geçersiz Kanal",
|
||||||
"This channel does not exist.": "Bu kanal mevcut değil.",
|
"This channel does not exist.": "Bu kanal mevcut değil.",
|
||||||
"Could not get channel info.": "Kanal bilgisi alınamadı.",
|
"Could not get channel info.": "Kanal bilgisi alınamadı.",
|
||||||
"Could not fetch comments": "Yorumlar alınamadı",
|
"Could not fetch comments": "Yorumlar Alınamadı",
|
||||||
"`x` ago": "`x` önce",
|
"`x` ago": "`x` Önce",
|
||||||
"Load more": "Daha fazla yükle",
|
"Load more": "Daha Fazla Yükle",
|
||||||
"Could not create mix.": "Mix oluşturulamadı.",
|
"Could not create mix.": "Mix oluşturulamadı.",
|
||||||
"Empty playlist": "Boş oynatma listesi",
|
"Empty playlist": "Boş Oynatma Listesi",
|
||||||
"Not a playlist.": "Oynatma listesi değil.",
|
"Not a playlist.": "Oynatma listesi değil.",
|
||||||
"Playlist does not exist.": "Oynatma listesi mevcut değil.",
|
"Playlist does not exist.": "Oynatma listesi mevcut değil.",
|
||||||
"Could not pull trending pages.": "Trend sayfaları alınamıyor.",
|
"Could not pull trending pages.": "Trend sayfaları alınamıyor.",
|
||||||
"Hidden field \"challenge\" is a required field": "Gizli alan \"challenge\" zorunlu bir alandır",
|
"Hidden field \"challenge\" is a required field": "Gizli Alan \"Challenge\" Zorunlu Bir Alandır",
|
||||||
"Hidden field \"token\" is a required field": "\"belirteç\" gizli alanı zorunlu bir alandır",
|
"Hidden field \"token\" is a required field": "\"Belirteç\" Gizli Alanı Zorunlu Bir Alandır",
|
||||||
"Erroneous challenge": "Hatalı challenge",
|
"Erroneous challenge": "Hatalı Challenge",
|
||||||
"Erroneous token": "Hatalı belirteç",
|
"Erroneous token": "Hatalı Belirteç",
|
||||||
"No such user": "Böyle bir kullanıcı yok",
|
"No such user": "Böyle Bir Kullanıcı Yok",
|
||||||
"Token is expired, please try again": "Belirtecin süresi doldu, lütfen tekrar deneyin",
|
"Token is expired, please try again": "Belirtecin Süresi Doldu, Lütfen Tekrar Deneyin",
|
||||||
"English": "İngilizce",
|
"English": "İngilizce",
|
||||||
"English (auto-generated)": "İngilizce (otomatik oluşturuldu)",
|
"English (auto-generated)": "İngilizce (Otomatik Oluşturuldu)",
|
||||||
"Afrikaans": "Afrikanca",
|
"Afrikaans": "Afrikanca",
|
||||||
"Albanian": "Arnavutça",
|
"Albanian": "Arnavutça",
|
||||||
"Amharic": "Amharca",
|
"Amharic": "Amharca",
|
||||||
|
@ -230,9 +230,9 @@
|
||||||
"German": "Almanca",
|
"German": "Almanca",
|
||||||
"Greek": "Yunanca",
|
"Greek": "Yunanca",
|
||||||
"Gujarati": "Guceratça",
|
"Gujarati": "Guceratça",
|
||||||
"Haitian Creole": "Haiti Creole dili",
|
"Haitian Creole": "Haiti Creole Dili",
|
||||||
"Hausa": "Hausaca",
|
"Hausa": "Hausaca",
|
||||||
"Hawaiian": "Hawaii dili",
|
"Hawaiian": "Hawaii Dili",
|
||||||
"Hebrew": "İbranice",
|
"Hebrew": "İbranice",
|
||||||
"Hindi": "Hintçe",
|
"Hindi": "Hintçe",
|
||||||
"Hmong": "Hmong",
|
"Hmong": "Hmong",
|
||||||
|
@ -244,7 +244,7 @@
|
||||||
"Italian": "İtalyanca",
|
"Italian": "İtalyanca",
|
||||||
"Japanese": "Japonca",
|
"Japanese": "Japonca",
|
||||||
"Javanese": "Cava dili",
|
"Javanese": "Cava dili",
|
||||||
"Kannada": "Kannada dili",
|
"Kannada": "Kannada Dili",
|
||||||
"Kazakh": "Kazakça",
|
"Kazakh": "Kazakça",
|
||||||
"Khmer": "Kmerce",
|
"Khmer": "Kmerce",
|
||||||
"Korean": "Korece",
|
"Korean": "Korece",
|
||||||
|
@ -258,10 +258,10 @@
|
||||||
"Macedonian": "Makedonca",
|
"Macedonian": "Makedonca",
|
||||||
"Malagasy": "Malgaşça",
|
"Malagasy": "Malgaşça",
|
||||||
"Malay": "Malayca",
|
"Malay": "Malayca",
|
||||||
"Malayalam": "Malayalam dili",
|
"Malayalam": "Malayalam Dili",
|
||||||
"Maltese": "Maltaca",
|
"Maltese": "Maltaca",
|
||||||
"Maori": "Maori dili",
|
"Maori": "Maori Dili",
|
||||||
"Marathi": "Marati dili",
|
"Marathi": "Marati Dili",
|
||||||
"Mongolian": "Moğolca",
|
"Mongolian": "Moğolca",
|
||||||
"Nepali": "Nepalce",
|
"Nepali": "Nepalce",
|
||||||
"Norwegian Bokmål": "Norveççe Bokmål",
|
"Norwegian Bokmål": "Norveççe Bokmål",
|
||||||
|
@ -270,19 +270,19 @@
|
||||||
"Persian": "Farsça",
|
"Persian": "Farsça",
|
||||||
"Polish": "Lehçe",
|
"Polish": "Lehçe",
|
||||||
"Portuguese": "Portekizce",
|
"Portuguese": "Portekizce",
|
||||||
"Punjabi": "Pencap dili",
|
"Punjabi": "Pencap Dili",
|
||||||
"Romanian": "Rumence",
|
"Romanian": "Rumence",
|
||||||
"Russian": "Rusça",
|
"Russian": "Rusça",
|
||||||
"Samoan": "Samoa dili",
|
"Samoan": "Samoa Dili",
|
||||||
"Scottish Gaelic": "İskoç Galcesi",
|
"Scottish Gaelic": "İskoç Galcesi",
|
||||||
"Serbian": "Sırpça",
|
"Serbian": "Sırpça",
|
||||||
"Shona": "Şona dili",
|
"Shona": "Şona Dili",
|
||||||
"Sindhi": "Sintçe",
|
"Sindhi": "Sintçe",
|
||||||
"Sinhala": "Seylanca",
|
"Sinhala": "Seylanca",
|
||||||
"Slovak": "Slovakça",
|
"Slovak": "Slovakça",
|
||||||
"Slovenian": "Slovence",
|
"Slovenian": "Slovence",
|
||||||
"Somali": "Somalice",
|
"Somali": "Somalice",
|
||||||
"Southern Sotho": "Güney Sotho dili",
|
"Southern Sotho": "Güney Sotho Dili",
|
||||||
"Spanish": "İspanyolca",
|
"Spanish": "İspanyolca",
|
||||||
"Spanish (Latin America)": "İspanyolca (Latin Amerika)",
|
"Spanish (Latin America)": "İspanyolca (Latin Amerika)",
|
||||||
"Sundanese": "Sundaca",
|
"Sundanese": "Sundaca",
|
||||||
|
@ -290,7 +290,7 @@
|
||||||
"Swedish": "İsveççe",
|
"Swedish": "İsveççe",
|
||||||
"Tajik": "Tacikçe",
|
"Tajik": "Tacikçe",
|
||||||
"Tamil": "Tamilce",
|
"Tamil": "Tamilce",
|
||||||
"Telugu": "Telugu dili",
|
"Telugu": "Telugu Dili",
|
||||||
"Thai": "Tayca",
|
"Thai": "Tayca",
|
||||||
"Turkish": "Türkçe",
|
"Turkish": "Türkçe",
|
||||||
"Ukrainian": "Ukraynaca",
|
"Ukrainian": "Ukraynaca",
|
||||||
|
@ -299,178 +299,182 @@
|
||||||
"Vietnamese": "Vietnamca",
|
"Vietnamese": "Vietnamca",
|
||||||
"Welsh": "Galce",
|
"Welsh": "Galce",
|
||||||
"Western Frisian": "Batı Frizcesi",
|
"Western Frisian": "Batı Frizcesi",
|
||||||
"Xhosa": "Xhosa dili",
|
"Xhosa": "Xhosa Dili",
|
||||||
"Yiddish": "Yiddiş",
|
"Yiddish": "Yiddiş",
|
||||||
"Yoruba": "Yoruba dili",
|
"Yoruba": "Yoruba Dili",
|
||||||
"Zulu": "Zuluca",
|
"Zulu": "Zuluca",
|
||||||
"Fallback comments: ": "Yedek yorumlar: ",
|
"Fallback comments: ": "Yedek Yorumlar: ",
|
||||||
"Popular": "Popüler",
|
"Popular": "Popüler",
|
||||||
"Search": "Ara",
|
"Search": "Ara",
|
||||||
"Top": "Enler",
|
"Top": "Enler",
|
||||||
"About": "Hakkında",
|
"About": "Hakkında",
|
||||||
"Rating: ": "Değerlendirme: ",
|
"Rating: ": "Değerlendirme: ",
|
||||||
"preferences_locale_label": "Dil: ",
|
"preferences_locale_label": "Dil: ",
|
||||||
"View as playlist": "Oynatma listesi olarak görüntüle",
|
"View as playlist": "Oynatma Listesi Olarak Görüntüle",
|
||||||
"Default": "Öntanımlı",
|
"Default": "Öntanımlı",
|
||||||
"Music": "Müzik",
|
"Music": "Müzik",
|
||||||
"Gaming": "Oyun",
|
"Gaming": "Oyun",
|
||||||
"News": "Haberler",
|
"News": "Haberler",
|
||||||
"Movies": "Filmler",
|
"Movies": "Filmler",
|
||||||
"Download": "İndir",
|
"Download": "İndir",
|
||||||
"Download as: ": "Şu şekilde indir: ",
|
"Download as: ": "Şu Şekilde İndir: ",
|
||||||
"%A %B %-d, %Y": "%A %B %-d, %Y",
|
"%A %B %-d, %Y": "%A %B %-d, %Y",
|
||||||
"(edited)": "(düzenlendi)",
|
"(edited)": "(Düzenlendi)",
|
||||||
"YouTube comment permalink": "YouTube yorumu kalıcı linki",
|
"YouTube comment permalink": "YouTube Yorumu Kalıcı Linki",
|
||||||
"permalink": "kalıcı link",
|
"permalink": "Kalıcı Link",
|
||||||
"`x` marked it with a ❤": "`x` ❤ ile işaretledi",
|
"`x` marked it with a ❤": "`x` ❤ İle İşaretledi",
|
||||||
"Audio mode": "Ses modu",
|
"Audio mode": "Ses Modu",
|
||||||
"Video mode": "Video modu",
|
"Video mode": "Video Modu",
|
||||||
"Videos": "Videolar",
|
"channel_tab_videos_label": "Videolar",
|
||||||
"Playlists": "Oynatma listeleri",
|
"Playlists": "Oynatma Listeleri",
|
||||||
"Community": "Topluluk",
|
"channel_tab_community_label": "Topluluk",
|
||||||
"search_filters_sort_option_relevance": "İlgi",
|
"search_filters_sort_option_relevance": "İlgi",
|
||||||
"search_filters_sort_option_rating": "Değerlendirme",
|
"search_filters_sort_option_rating": "Değerlendirme",
|
||||||
"search_filters_sort_option_date": "Yükleme tarihi",
|
"search_filters_sort_option_date": "Yükleme Tarihi",
|
||||||
"search_filters_sort_option_views": "Görüntüleme sayısı",
|
"search_filters_sort_option_views": "Görüntüleme Sayısı",
|
||||||
"search_filters_type_label": "Tür",
|
"search_filters_type_label": "Tür",
|
||||||
"search_filters_duration_label": "Süre",
|
"search_filters_duration_label": "Süre",
|
||||||
"search_filters_features_label": "Özellikler",
|
"search_filters_features_label": "Özellikler",
|
||||||
"search_filters_sort_label": "Sıralama Ölçütü",
|
"search_filters_sort_label": "Sıralama Ölçütü",
|
||||||
"search_filters_date_option_hour": "Son Saat",
|
"search_filters_date_option_hour": "Son Saat",
|
||||||
"search_filters_date_option_today": "Bugün",
|
"search_filters_date_option_today": "Bugün",
|
||||||
"search_filters_date_option_week": "Bu hafta",
|
"search_filters_date_option_week": "Bu Hafta",
|
||||||
"search_filters_date_option_month": "Bu ay",
|
"search_filters_date_option_month": "Bu Ay",
|
||||||
"search_filters_date_option_year": "Bu yıl",
|
"search_filters_date_option_year": "Bu Yıl",
|
||||||
"search_filters_type_option_video": "Video",
|
"search_filters_type_option_video": "Video",
|
||||||
"search_filters_type_option_channel": "Kanal",
|
"search_filters_type_option_channel": "Kanal",
|
||||||
"search_filters_type_option_playlist": "Oynatma listesi",
|
"search_filters_type_option_playlist": "Oynatma Listesi",
|
||||||
"search_filters_type_option_movie": "Film",
|
"search_filters_type_option_movie": "Film",
|
||||||
"search_filters_type_option_show": "Gösteri",
|
"search_filters_type_option_show": "Gösteri",
|
||||||
"search_filters_features_option_hd": "HD",
|
"search_filters_features_option_hd": "HD",
|
||||||
"search_filters_features_option_subtitles": "Alt yazılar",
|
"search_filters_features_option_subtitles": "Alt Yazılar",
|
||||||
"search_filters_features_option_c_commons": "Creative Commons",
|
"search_filters_features_option_c_commons": "Yaratıcı",
|
||||||
"search_filters_features_option_three_d": "3B",
|
"search_filters_features_option_three_d": "3D",
|
||||||
"search_filters_features_option_live": "Canlı",
|
"search_filters_features_option_live": "Canlı",
|
||||||
"search_filters_features_option_four_k": "4K",
|
"search_filters_features_option_four_k": "4K",
|
||||||
"search_filters_features_option_location": "Konum",
|
"search_filters_features_option_location": "Konum",
|
||||||
"search_filters_features_option_hdr": "HDR",
|
"search_filters_features_option_hdr": "HDR",
|
||||||
"Current version: ": "Şu anki sürüm: ",
|
"Current version: ": "Şu Anki Sürüm: ",
|
||||||
"next_steps_error_message": "Bundan sonra şunları denemelisiniz: ",
|
"next_steps_error_message": "Bundan Sonra Şunları Denemelisiniz: ",
|
||||||
"next_steps_error_message_refresh": "Yenile",
|
"next_steps_error_message_refresh": "Yenile",
|
||||||
"next_steps_error_message_go_to_youtube": "YouTube'a git",
|
"next_steps_error_message_go_to_youtube": "YouTube'a Git",
|
||||||
"search_filters_duration_option_short": "Kısa (4 dakikadan az)",
|
"search_filters_duration_option_short": "Kısa (4 Dakikadan Az)",
|
||||||
"search_filters_duration_option_long": "Uzun (20 dakikadan fazla)",
|
"search_filters_duration_option_long": "Uzun (20 Dakikadan Fazla)",
|
||||||
"footer_documentation": "Belgelendirme",
|
"footer_documentation": "Belgelendirme",
|
||||||
"footer_source_code": "Kaynak kodları",
|
"footer_source_code": "Kaynak Kodları",
|
||||||
"footer_original_source_code": "Orijinal kaynak kodları",
|
"footer_original_source_code": "Orijinal Kaynak Kodları",
|
||||||
"footer_modfied_source_code": "Değiştirilmiş kaynak kodları",
|
"footer_modfied_source_code": "Değiştirilmiş Kaynak Kodları",
|
||||||
"adminprefs_modified_source_code_url_label": "Değiştirilmiş kaynak kodları deposunun URL'si",
|
"adminprefs_modified_source_code_url_label": "Değiştirilmiş Kaynak Kodları Deposunun URL'si",
|
||||||
"footer_donate_page": "Bağış yap",
|
"footer_donate_page": "Bağış Yap",
|
||||||
"preferences_region_label": "İçerik ülkesi: ",
|
"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_option_hd720": "HD720",
|
||||||
"preferences_quality_dash_option_best": "En iyi",
|
"preferences_quality_dash_option_best": "En İyi",
|
||||||
"preferences_quality_dash_option_worst": "En kötü",
|
"preferences_quality_dash_option_worst": "En Kötü",
|
||||||
"preferences_quality_dash_option_4320p": "4320p",
|
"preferences_quality_dash_option_4320p": "4320P",
|
||||||
"preferences_quality_dash_option_2160p": "2160p",
|
"preferences_quality_dash_option_2160p": "2160P",
|
||||||
"preferences_quality_dash_option_480p": "480p",
|
"preferences_quality_dash_option_480p": "480P",
|
||||||
"preferences_quality_dash_option_360p": "360p",
|
"preferences_quality_dash_option_360p": "360P",
|
||||||
"preferences_quality_dash_option_240p": "240p",
|
"preferences_quality_dash_option_240p": "240P",
|
||||||
"preferences_quality_dash_option_144p": "144p",
|
"preferences_quality_dash_option_144p": "144P",
|
||||||
"invidious": "Invidious",
|
"invidious": "Invidious",
|
||||||
"none": "yok",
|
"none": "Yok",
|
||||||
"videoinfo_started_streaming_x_ago": "`x` önce yayına başladı",
|
"videoinfo_started_streaming_x_ago": "`x` Önce Yayına Başladı",
|
||||||
"videoinfo_youTube_embed_link": "Göm",
|
"videoinfo_youTube_embed_link": "Entegre Et",
|
||||||
"videoinfo_invidious_embed_link": "Bağlantıyı Göm",
|
"videoinfo_invidious_embed_link": "Bağlantıyı Entegre Et",
|
||||||
"user_created_playlists": "`x` oluşturulan oynatma listeleri",
|
"user_created_playlists": "`x` Oluşturulan Oynatma Listeleri",
|
||||||
"user_saved_playlists": "`x` kaydedilen oynatma listeleri",
|
"user_saved_playlists": "`x` Kaydedilen Oynatma Listeleri",
|
||||||
"preferences_quality_option_small": "Küçük",
|
"preferences_quality_option_small": "Küçük",
|
||||||
"preferences_quality_dash_option_720p": "720p",
|
"preferences_quality_dash_option_720p": "720P",
|
||||||
"preferences_quality_option_medium": "Orta",
|
"preferences_quality_option_medium": "Orta",
|
||||||
"preferences_quality_dash_option_1440p": "1440p",
|
"preferences_quality_dash_option_1440p": "1440P",
|
||||||
"preferences_quality_dash_option_1080p": "1080p",
|
"preferences_quality_dash_option_1080p": "1080P",
|
||||||
"Video unavailable": "Video kullanılamıyor",
|
"Video unavailable": "Video Kullanılamıyor",
|
||||||
"preferences_quality_option_dash": "DASH (uyarlanabilir kalite)",
|
"preferences_quality_option_dash": "DASH (Uyarlanabilir Kalite)",
|
||||||
"preferences_quality_dash_option_auto": "Otomatik",
|
"preferences_quality_dash_option_auto": "Otomatik",
|
||||||
"search_filters_features_option_purchased": "Satın alınan",
|
"search_filters_features_option_purchased": "Satın Alınan",
|
||||||
"search_filters_features_option_three_sixty": "360°",
|
"search_filters_features_option_three_sixty": "360°",
|
||||||
"videoinfo_watch_on_youTube": "YouTube'da izle",
|
"videoinfo_watch_on_youTube": "YouTube'da İzle",
|
||||||
"download_subtitles": "Alt yazılar - `x` (.vtt)",
|
"download_subtitles": "Alt Yazılar - `x` (.vtt)",
|
||||||
"preferences_save_player_pos_label": "Oynatma konumunu kaydet: ",
|
"preferences_save_player_pos_label": "Oynatma Konumunu Kaydet: ",
|
||||||
"generic_views_count": "{{count}} görüntüleme",
|
"generic_views_count": "{{count}} Görüntüleme",
|
||||||
"generic_views_count_plural": "{{count}} görüntüleme",
|
"generic_views_count_plural": "{{count}} Görüntüleme",
|
||||||
"generic_subscribers_count": "{{count}} abone",
|
"generic_subscribers_count": "{{count}} Abone",
|
||||||
"generic_subscribers_count_plural": "{{count}} abone",
|
"generic_subscribers_count_plural": "{{count}} Abone",
|
||||||
"generic_subscriptions_count": "{{count}} abonelik",
|
"generic_subscriptions_count": "{{count}} Abonelik",
|
||||||
"generic_subscriptions_count_plural": "{{count}} abonelik",
|
"generic_subscriptions_count_plural": "{{count}} Abonelik",
|
||||||
"subscriptions_unseen_notifs_count": "{{count}} okunmamış bildirim",
|
"subscriptions_unseen_notifs_count": "{{count}} Okunmamış Bildirim",
|
||||||
"subscriptions_unseen_notifs_count_plural": "{{count}} okunmamış bildirim",
|
"subscriptions_unseen_notifs_count_plural": "{{count}} Okunmamış Bildirim",
|
||||||
"comments_points_count": "{{count}} puan",
|
"comments_points_count": "{{count}} Puan",
|
||||||
"comments_points_count_plural": "{{count}} puan",
|
"comments_points_count_plural": "{{count}} Puan",
|
||||||
"generic_count_hours": "{{count}} saat",
|
"generic_count_hours": "{{count}} Saat",
|
||||||
"generic_count_hours_plural": "{{count}} saat",
|
"generic_count_hours_plural": "{{count}} Saat",
|
||||||
"generic_count_minutes": "{{count}} dakika",
|
"generic_count_minutes": "{{count}} Dakika",
|
||||||
"generic_count_minutes_plural": "{{count}} dakika",
|
"generic_count_minutes_plural": "{{count}} Dakika",
|
||||||
"generic_count_seconds": "{{count}} saniye",
|
"generic_count_seconds": "{{count}} Saniye",
|
||||||
"generic_count_seconds_plural": "{{count}} saniye",
|
"generic_count_seconds_plural": "{{count}} Saniye",
|
||||||
"generic_playlists_count": "{{count}} oynatma listesi",
|
"generic_playlists_count": "{{count}} Oynatma Listesi",
|
||||||
"generic_playlists_count_plural": "{{count}} oynatma listesi",
|
"generic_playlists_count_plural": "{{count}} Oynatma Listesi",
|
||||||
"tokens_count": "{{count}} belirteç",
|
"tokens_count": "{{count}} Belirteç",
|
||||||
"tokens_count_plural": "{{count}} belirteç",
|
"tokens_count_plural": "{{count}} Belirteç",
|
||||||
"comments_view_x_replies": "{{count}} yanıtı görüntüle",
|
"comments_view_x_replies": "{{count}} Yanıtı Görüntüle",
|
||||||
"comments_view_x_replies_plural": "{{count}} yanıtı görüntüle",
|
"comments_view_x_replies_plural": "{{count}} Yanıtı Görüntüle",
|
||||||
"generic_count_years": "{{count}} yıl",
|
"generic_count_years": "{{count}} Yıl",
|
||||||
"generic_count_years_plural": "{{count}} yıl",
|
"generic_count_years_plural": "{{count}} Yıl",
|
||||||
"generic_count_months": "{{count}} ay",
|
"generic_count_months": "{{count}} Ay",
|
||||||
"generic_count_months_plural": "{{count}} ay",
|
"generic_count_months_plural": "{{count}} Ay",
|
||||||
"generic_count_days": "{{count}} gün",
|
"generic_count_days": "{{count}} Gün",
|
||||||
"generic_count_days_plural": "{{count}} gün",
|
"generic_count_days_plural": "{{count}} Gün",
|
||||||
"generic_videos_count": "{{count}} video",
|
"generic_videos_count": "{{count}} Video",
|
||||||
"generic_videos_count_plural": "{{count}} video",
|
"generic_videos_count_plural": "{{count}} Video",
|
||||||
"generic_count_weeks": "{{count}} hafta",
|
"generic_count_weeks": "{{count}} Hafta",
|
||||||
"generic_count_weeks_plural": "{{count}} hafta",
|
"generic_count_weeks_plural": "{{count}} Hafta",
|
||||||
"crash_page_you_found_a_bug": "Görünüşe göre Invidious'ta bir hata buldunuz!",
|
"crash_page_you_found_a_bug": "Görünüşe göre Invidious'ta bir hata buldunuz!",
|
||||||
"crash_page_before_reporting": "Bir hatayı bildirmeden önce, şunları yaptığınızdan emin olun:",
|
"crash_page_before_reporting": "Bir hatayı bildirmeden önce, şunları yaptığınızdan emin olun:",
|
||||||
"crash_page_refresh": "<a href=\"`x`\">sayfayı yenilemeye</a> çalıştınız",
|
"crash_page_refresh": "<a href=\"`x`\">Sayfayı Yenilemeye</a> Çalıştınız",
|
||||||
"crash_page_switch_instance": "<a href=\"`x`\">başka bir örnek kullanmaya</a> çalıştınız",
|
"crash_page_switch_instance": "<a href=\"`x`\">Başka Bir Örnek Kullanmaya</a> Çalıştınız",
|
||||||
"crash_page_read_the_faq": "<a href=\"`x`\">Sık Sorulan Soruları (SSS)</a> okudunuz",
|
"crash_page_read_the_faq": "<a href=\"`x`\">Sık Sorulan Soruları (SSS)</a> Okudunuz",
|
||||||
"crash_page_search_issue": "<a href=\"`x`\">GitHub'daki sorunlarda</a> aradınız",
|
"crash_page_search_issue": "<a href=\"`x`\">GitHub'daki Sorunlarda</a> Aradınız",
|
||||||
"crash_page_report_issue": "Yukarıdakilerin hiçbiri yardımcı olmadıysa, lütfen <a href=\"`x`\">GitHub'da yeni bir sorun açın</a> (tercihen İngilizce) ve mesajınıza aşağıdaki metni ekleyin (bu metni ÇEVİRMEYİN):",
|
"crash_page_report_issue": "Yukarıdakilerin hiçbiri yardımcı olmadıysa, lütfen <a href=\"`x`\">GitHub'da yeni bir sorun açın</a> (Tercihen İngilizce) ve mesajınıza aşağıdaki metni ekleyin (Bu metni ÇEVİRMEYİN):",
|
||||||
"English (United Kingdom)": "İngilizce (Birleşik Krallık)",
|
"English (United Kingdom)": "İngilizce (Birleşik Krallık)",
|
||||||
"Chinese": "Çince",
|
"Chinese": "Çince",
|
||||||
"Interlingue": "İnterlingue",
|
"Interlingue": "İnterlingue",
|
||||||
"Italian (auto-generated)": "İtalyanca (otomatik oluşturuldu)",
|
"Italian (auto-generated)": "İtalyanca (Otomatik Oluşturuldu)",
|
||||||
"Japanese (auto-generated)": "Japonca (otomatik oluşturuldu)",
|
"Japanese (auto-generated)": "Japonca (Otomatik Oluşturuldu)",
|
||||||
"Portuguese (Brazil)": "Portekizce (Brezilya)",
|
"Portuguese (Brazil)": "Portekizce (Brezilya)",
|
||||||
"Russian (auto-generated)": "Rusça (otomatik oluşturuldu)",
|
"Russian (auto-generated)": "Rusça (Otomatik Oluşturuldu)",
|
||||||
"Spanish (auto-generated)": "İspanyolca (otomatik oluşturuldu)",
|
"Spanish (auto-generated)": "İspanyolca (Otomatik Oluşturuldu)",
|
||||||
"Spanish (Mexico)": "İspanyolca (Meksika)",
|
"Spanish (Mexico)": "İspanyolca (Meksika)",
|
||||||
"English (United States)": "İngilizce (ABD)",
|
"English (United States)": "İngilizce (ABD)",
|
||||||
"Cantonese (Hong Kong)": "Kantonca (Hong Kong)",
|
"Cantonese (Hong Kong)": "Kantonca (Hong Kong)",
|
||||||
"Chinese (Taiwan)": "Çince (Tayvan)",
|
"Chinese (Taiwan)": "Çince (Tayvan)",
|
||||||
"Dutch (auto-generated)": "Felemenkçe (otomatik oluşturuldu)",
|
"Dutch (auto-generated)": "Felemenkçe (Otomatik Oluşturuldu)",
|
||||||
"Indonesian (auto-generated)": "Endonezyaca (otomatik oluşturuldu)",
|
"Indonesian (auto-generated)": "Endonezyaca (Otomatik Oluşturuldu)",
|
||||||
"Chinese (Hong Kong)": "Çince (Hong Kong)",
|
"Chinese (Hong Kong)": "Çince (Hong Kong)",
|
||||||
"French (auto-generated)": "Fransızca (otomatik oluşturuldu)",
|
"French (auto-generated)": "Fransızca (Otomatik Oluşturuldu)",
|
||||||
"Korean (auto-generated)": "Korece (otomatik oluşturuldu)",
|
"Korean (auto-generated)": "Korece (Otomatik Oluşturuldu)",
|
||||||
"Turkish (auto-generated)": "Türkçe (otomatik oluşturuldu)",
|
"Turkish (auto-generated)": "Türkçe (Otomatik Oluşturuldu)",
|
||||||
"Chinese (China)": "Çince (Çin)",
|
"Chinese (China)": "Çince (Çin)",
|
||||||
"German (auto-generated)": "Almanca (otomatik oluşturuldu)",
|
"German (auto-generated)": "Almanca (Otomatik Oluşturuldu)",
|
||||||
"Portuguese (auto-generated)": "Portekizce (otomatik oluşturuldu)",
|
"Portuguese (auto-generated)": "Portekizce (Otomatik Oluşturuldu)",
|
||||||
"Spanish (Spain)": "İspanyolca (İspanya)",
|
"Spanish (Spain)": "İspanyolca (İspanya)",
|
||||||
"Vietnamese (auto-generated)": "Vietnamca (otomatik oluşturuldu)",
|
"Vietnamese (auto-generated)": "Vietnamca (Otomatik Oluşturuldu)",
|
||||||
"preferences_watch_history_label": "İzleme geçmişini etkinleştir: ",
|
"preferences_watch_history_label": "İzleme Geçmişini Etkinleştir: ",
|
||||||
"search_message_use_another_instance": " Ayrıca <a href=\"`x`\">başka bir örnekte arayabilirsiniz</a>.",
|
"search_message_use_another_instance": " Ayrıca <a href=\"`x`\">başka bir örnekte arayabilirsiniz</a>.",
|
||||||
"search_filters_type_option_all": "Herhangi bir tür",
|
"search_filters_type_option_all": "Herhangi Bir Tür",
|
||||||
"search_filters_duration_option_none": "Herhangi bir süre",
|
"search_filters_duration_option_none": "Herhangi Bir Süre",
|
||||||
"search_message_no_results": "Sonuç bulunamadı.",
|
"search_message_no_results": "Sonuç bulunamadı.",
|
||||||
"search_filters_date_label": "Yükleme tarihi",
|
"search_filters_date_label": "Yükleme Tarihi",
|
||||||
"search_filters_apply_button": "Seçili filtreleri uygula",
|
"search_filters_apply_button": "Seçili Filtreleri Uygula",
|
||||||
"search_filters_date_option_none": "Herhangi bir tarih",
|
"search_filters_date_option_none": "Herhangi Bir Tarih",
|
||||||
"search_filters_duration_option_medium": "Orta (4 - 20 dakika)",
|
"search_filters_duration_option_medium": "Orta (4 - 20 Dakika)",
|
||||||
"search_filters_features_option_vr180": "VR180",
|
"search_filters_features_option_vr180": "VR180",
|
||||||
"search_filters_title": "Filtreler",
|
"search_filters_title": "Filtreler",
|
||||||
"search_message_change_filters_or_query": "Arama sorgunuzu genişletmeyi ve/veya filtreleri değiştirmeyi deneyin.",
|
"search_message_change_filters_or_query": "Arama sorgunuzu genişletmeyi ve/veya filtreleri değiştirmeyi deneyin.",
|
||||||
"Popular enabled: ": "Popüler etkin: ",
|
"Popular enabled: ": "Popüler Etkin: ",
|
||||||
"error_video_not_in_playlist": "İstenen video bu oynatma listesinde yok. <a href=\"`x`\">Oynatma listesi ana sayfası için buraya tıklayın.</a>"
|
"error_video_not_in_playlist": "İstenen video bu oynatma listesinde yok. <a href=\"`x`\">Oynatma listesi ana sayfası için buraya tıklayın.</a>",
|
||||||
|
"channel_tab_channels_label": "Kanallar",
|
||||||
|
"channel_tab_shorts_label": "Kısa Çekimler",
|
||||||
|
"channel_tab_streams_label": "Canlı Yayınlar",
|
||||||
|
"channel_tab_playlists_label": "Oynatma Listeleri"
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
"preferences_continue_label": "Завжди вмикати наступне відео: ",
|
"preferences_continue_label": "Завжди вмикати наступне відео: ",
|
||||||
"preferences_continue_autoplay_label": "Автовідтворення наступного відео: ",
|
"preferences_continue_autoplay_label": "Автовідтворення наступного відео: ",
|
||||||
"preferences_listen_label": "Режим «тільки звук» як усталений: ",
|
"preferences_listen_label": "Режим «тільки звук» як усталений: ",
|
||||||
"preferences_local_label": "Програвати відео через проксі? ",
|
"preferences_local_label": "Відтворення відео через проксі: ",
|
||||||
"preferences_speed_label": "Усталена швидкість відео: ",
|
"preferences_speed_label": "Усталена швидкість відео: ",
|
||||||
"preferences_quality_label": "Пріорітетна якість відео: ",
|
"preferences_quality_label": "Пріорітетна якість відео: ",
|
||||||
"preferences_volume_label": "Гучність відео: ",
|
"preferences_volume_label": "Гучність відео: ",
|
||||||
|
@ -63,13 +63,13 @@
|
||||||
"reddit": "Reddit",
|
"reddit": "Reddit",
|
||||||
"preferences_captions_label": "Основна мова субтитрів: ",
|
"preferences_captions_label": "Основна мова субтитрів: ",
|
||||||
"Fallback captions: ": "Запасна мова субтитрів: ",
|
"Fallback captions: ": "Запасна мова субтитрів: ",
|
||||||
"preferences_related_videos_label": "Показувати схожі відео? ",
|
"preferences_related_videos_label": "Показувати схожі відео: ",
|
||||||
"preferences_annotations_label": "Завжди показувати анотації? ",
|
"preferences_annotations_label": "Завжди показувати анотації: ",
|
||||||
"preferences_category_visual": "Налаштування сайту",
|
"preferences_category_visual": "Налаштування сайту",
|
||||||
"preferences_player_style_label": "Стиль програвача: ",
|
"preferences_player_style_label": "Стиль програвача: ",
|
||||||
"Dark mode: ": "Темне оформлення: ",
|
"Dark mode: ": "Темний режим: ",
|
||||||
"preferences_dark_mode_label": "Тема: ",
|
"preferences_dark_mode_label": "Тема: ",
|
||||||
"dark": "темна",
|
"dark": "Темна",
|
||||||
"light": "Світла",
|
"light": "Світла",
|
||||||
"preferences_thin_mode_label": "Полегшене оформлення: ",
|
"preferences_thin_mode_label": "Полегшене оформлення: ",
|
||||||
"preferences_category_subscription": "Налаштування підписок",
|
"preferences_category_subscription": "Налаштування підписок",
|
||||||
|
@ -101,11 +101,11 @@
|
||||||
"preferences_category_admin": "Адміністраторські налаштування",
|
"preferences_category_admin": "Адміністраторські налаштування",
|
||||||
"preferences_default_home_label": "Усталена домашня сторінка: ",
|
"preferences_default_home_label": "Усталена домашня сторінка: ",
|
||||||
"preferences_feed_menu_label": "Меню потоку з відео: ",
|
"preferences_feed_menu_label": "Меню потоку з відео: ",
|
||||||
"Top enabled: ": "Увімкнути топ відео? ",
|
"Top enabled: ": "Увімкнути топ відео: ",
|
||||||
"CAPTCHA enabled: ": "Увімкнути капчу? ",
|
"CAPTCHA enabled: ": "Увімкнути CAPTCHA: ",
|
||||||
"Login enabled: ": "Увімкнути авторизацію? ",
|
"Login enabled: ": "Увімкнути вхід: ",
|
||||||
"Registration enabled: ": "Увімкнути реєстрацію? ",
|
"Registration enabled: ": "Увімкнути реєстрацію: ",
|
||||||
"Report statistics: ": "Повідомляти статистику? ",
|
"Report statistics: ": "Повідомляти статистику: ",
|
||||||
"Save preferences": "Зберегти налаштування",
|
"Save preferences": "Зберегти налаштування",
|
||||||
"Subscription manager": "Менеджер підписок",
|
"Subscription manager": "Менеджер підписок",
|
||||||
"Token manager": "Менеджер токенів",
|
"Token manager": "Менеджер токенів",
|
||||||
|
@ -125,7 +125,7 @@
|
||||||
"Private": "Особистий",
|
"Private": "Особистий",
|
||||||
"View all playlists": "Переглянути всі списки відтворення",
|
"View all playlists": "Переглянути всі списки відтворення",
|
||||||
"Updated `x` ago": "Оновлено `x` тому",
|
"Updated `x` ago": "Оновлено `x` тому",
|
||||||
"Delete playlist `x`?": "Видалити список відтворення \"x\"?",
|
"Delete playlist `x`?": "Видалити список відтворення `x`?",
|
||||||
"Delete playlist": "Видалити список відтворення",
|
"Delete playlist": "Видалити список відтворення",
|
||||||
"Create playlist": "Створити список відтворення",
|
"Create playlist": "Створити список відтворення",
|
||||||
"Title": "Заголовок",
|
"Title": "Заголовок",
|
||||||
|
@ -315,9 +315,9 @@
|
||||||
"`x` marked it with a ❤": "❤ цьому від каналу `x`",
|
"`x` marked it with a ❤": "❤ цьому від каналу `x`",
|
||||||
"Audio mode": "Аудіорежим",
|
"Audio mode": "Аудіорежим",
|
||||||
"Video mode": "Відеорежим",
|
"Video mode": "Відеорежим",
|
||||||
"Videos": "Відео",
|
"channel_tab_videos_label": "Відео",
|
||||||
"Playlists": "Плейлисти",
|
"Playlists": "Плейлисти",
|
||||||
"Community": "Спільнота",
|
"channel_tab_community_label": "Спільнота",
|
||||||
"Current version: ": "Поточна версія: ",
|
"Current version: ": "Поточна версія: ",
|
||||||
"generic_views_count_0": "{{count}} перегляд",
|
"generic_views_count_0": "{{count}} перегляд",
|
||||||
"generic_views_count_1": "{{count}} перегляди",
|
"generic_views_count_1": "{{count}} перегляди",
|
||||||
|
@ -386,12 +386,12 @@
|
||||||
"Spanish (Mexico)": "Іспанська (Мексика)",
|
"Spanish (Mexico)": "Іспанська (Мексика)",
|
||||||
"Spanish (Spain)": "Іспанська (Іспанія)",
|
"Spanish (Spain)": "Іспанська (Іспанія)",
|
||||||
"next_steps_error_message_go_to_youtube": "Перейти до YouTube",
|
"next_steps_error_message_go_to_youtube": "Перейти до YouTube",
|
||||||
"footer_donate_page": "Пожертвувати",
|
"footer_donate_page": "Підтримати",
|
||||||
"footer_documentation": "Документація",
|
"footer_documentation": "Документація",
|
||||||
"footer_source_code": "Вихідний код",
|
"footer_source_code": "Джерельний код",
|
||||||
"footer_original_source_code": "Оригінал вихідного коду",
|
"footer_original_source_code": "Оригінал джерельного коду",
|
||||||
"footer_modfied_source_code": "Змінений вихідний код",
|
"footer_modfied_source_code": "Змінений джерельний код",
|
||||||
"adminprefs_modified_source_code_url_label": "URL-адреса репозиторію зміненого вихідного коду",
|
"adminprefs_modified_source_code_url_label": "URL-адреса репозиторію зміненого джерельного коду",
|
||||||
"none": "нема",
|
"none": "нема",
|
||||||
"videoinfo_started_streaming_x_ago": "Трансляцію розпочато `x` тому",
|
"videoinfo_started_streaming_x_ago": "Трансляцію розпочато `x` тому",
|
||||||
"crash_page_you_found_a_bug": "Схоже, ви знайшли ваду в Invidious!",
|
"crash_page_you_found_a_bug": "Схоже, ви знайшли ваду в Invidious!",
|
||||||
|
@ -408,7 +408,7 @@
|
||||||
"next_steps_error_message": "Після чого спробуйте: ",
|
"next_steps_error_message": "Після чого спробуйте: ",
|
||||||
"next_steps_error_message_refresh": "Оновити сторінку",
|
"next_steps_error_message_refresh": "Оновити сторінку",
|
||||||
"Search": "Пошук",
|
"Search": "Пошук",
|
||||||
"preferences_extend_desc_label": "Автоматично розширювати опис відео: ",
|
"preferences_extend_desc_label": "Автоматично розгортати опис відео: ",
|
||||||
"preferences_category_misc": "Різноманітні параметри",
|
"preferences_category_misc": "Різноманітні параметри",
|
||||||
"Show less": "Коротше",
|
"Show less": "Коротше",
|
||||||
"preferences_quality_option_small": "Низька",
|
"preferences_quality_option_small": "Низька",
|
||||||
|
@ -488,5 +488,9 @@
|
||||||
"search_filters_sort_option_rating": "Рейтингові",
|
"search_filters_sort_option_rating": "Рейтингові",
|
||||||
"search_filters_sort_option_views": "Популярні",
|
"search_filters_sort_option_views": "Популярні",
|
||||||
"Popular enabled: ": "Популярне ввімкнено: ",
|
"Popular enabled: ": "Популярне ввімкнено: ",
|
||||||
"error_video_not_in_playlist": "Запитуваного відео в цьому списку відтворення не існує. <a href=\"`x`\">Клацніть тут, щоб переглянути домашню сторінку списку відтворення.</a>"
|
"error_video_not_in_playlist": "Запитуваного відео в цьому списку відтворення не існує. <a href=\"`x`\">Клацніть тут, щоб переглянути домашню сторінку списку відтворення.</a>",
|
||||||
|
"channel_tab_shorts_label": "Shorts",
|
||||||
|
"channel_tab_streams_label": "Прямі трансляції",
|
||||||
|
"channel_tab_playlists_label": "Добірки",
|
||||||
|
"channel_tab_channels_label": "Канали"
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,9 +311,9 @@
|
||||||
"`x` marked it with a ❤": "` x` đã đánh dấu nó bằng một ❤",
|
"`x` marked it with a ❤": "` x` đã đánh dấu nó bằng một ❤",
|
||||||
"Audio mode": "Chế độ âm thanh",
|
"Audio mode": "Chế độ âm thanh",
|
||||||
"Video mode": "Chế độ quay",
|
"Video mode": "Chế độ quay",
|
||||||
"Videos": "Video",
|
"channel_tab_videos_label": "Video",
|
||||||
"Playlists": "Danh sách phát",
|
"Playlists": "Danh sách phát",
|
||||||
"Community": "Cộng đồng",
|
"channel_tab_community_label": "Cộng đồng",
|
||||||
"search_filters_sort_option_relevance": "liên quan",
|
"search_filters_sort_option_relevance": "liên quan",
|
||||||
"search_filters_sort_option_rating": "Xếp hạng",
|
"search_filters_sort_option_rating": "Xếp hạng",
|
||||||
"search_filters_sort_option_date": "ngày",
|
"search_filters_sort_option_date": "ngày",
|
||||||
|
|
|
@ -341,9 +341,9 @@
|
||||||
"`x` marked it with a ❤": "`x` 为此加 ❤",
|
"`x` marked it with a ❤": "`x` 为此加 ❤",
|
||||||
"Audio mode": "音频模式",
|
"Audio mode": "音频模式",
|
||||||
"Video mode": "视频模式",
|
"Video mode": "视频模式",
|
||||||
"Videos": "视频",
|
"channel_tab_videos_label": "视频",
|
||||||
"Playlists": "播放列表",
|
"Playlists": "播放列表",
|
||||||
"Community": "社区",
|
"channel_tab_community_label": "社区",
|
||||||
"search_filters_sort_option_relevance": "相关度",
|
"search_filters_sort_option_relevance": "相关度",
|
||||||
"search_filters_sort_option_rating": "评分",
|
"search_filters_sort_option_rating": "评分",
|
||||||
"search_filters_sort_option_date": "上传日期",
|
"search_filters_sort_option_date": "上传日期",
|
||||||
|
|
|
@ -341,9 +341,9 @@
|
||||||
"`x` marked it with a ❤": "`x` 為此標記 ❤",
|
"`x` marked it with a ❤": "`x` 為此標記 ❤",
|
||||||
"Audio mode": "音訊模式",
|
"Audio mode": "音訊模式",
|
||||||
"Video mode": "視訊模式",
|
"Video mode": "視訊模式",
|
||||||
"Videos": "影片",
|
"channel_tab_videos_label": "影片",
|
||||||
"Playlists": "播放清單",
|
"Playlists": "播放清單",
|
||||||
"Community": "社群",
|
"channel_tab_community_label": "社群",
|
||||||
"search_filters_sort_option_relevance": "關聯",
|
"search_filters_sort_option_relevance": "關聯",
|
||||||
"search_filters_sort_option_rating": "評分",
|
"search_filters_sort_option_rating": "評分",
|
||||||
"search_filters_sort_option_date": "日期",
|
"search_filters_sort_option_date": "日期",
|
||||||
|
@ -456,5 +456,9 @@
|
||||||
"search_filters_type_option_all": "任何類型",
|
"search_filters_type_option_all": "任何類型",
|
||||||
"search_filters_date_option_none": "任何日期",
|
"search_filters_date_option_none": "任何日期",
|
||||||
"Popular enabled: ": "已啟用人氣: ",
|
"Popular enabled: ": "已啟用人氣: ",
|
||||||
"error_video_not_in_playlist": "此播放清單不存在請求的影片。<a href=\"`x`\">點擊此處檢視播放清單首頁。</a>"
|
"error_video_not_in_playlist": "此播放清單不存在請求的影片。<a href=\"`x`\">點擊此處檢視播放清單首頁。</a>",
|
||||||
|
"channel_tab_shorts_label": "短片",
|
||||||
|
"channel_tab_playlists_label": "播放清單",
|
||||||
|
"channel_tab_channels_label": "頻道",
|
||||||
|
"channel_tab_streams_label": "直播"
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ dependencies_to_install.each do |dep|
|
||||||
dep = "videojs.markers" if dep == "videojs-markers"
|
dep = "videojs.markers" if dep == "videojs-markers"
|
||||||
|
|
||||||
if File.exists?("#{download_path}/package/dist/#{dep}.css")
|
if File.exists?("#{download_path}/package/dist/#{dep}.css")
|
||||||
if minified && File.exists?("#{tmp_dir_path}/#{dep}/package/dist/#{dep}.min.css")
|
if minified && File.exists?("#{download_path}/package/dist/#{dep}.min.css")
|
||||||
`mv #{download_path}/package/dist/#{dep}.min.css #{dest_path}/#{dep}.css`
|
`mv #{download_path}/package/dist/#{dep}.min.css #{dest_path}/#{dep}.css`
|
||||||
else
|
else
|
||||||
`mv #{download_path}/package/dist/#{dep}.css #{dest_path}/#{dep}.css`
|
`mv #{download_path}/package/dist/#{dep}.css #{dest_path}/#{dep}.css`
|
||||||
|
|
|
@ -34,7 +34,7 @@ shards:
|
||||||
|
|
||||||
protodec:
|
protodec:
|
||||||
git: https://github.com/iv-org/protodec.git
|
git: https://github.com/iv-org/protodec.git
|
||||||
version: 0.1.4
|
version: 0.1.5
|
||||||
|
|
||||||
radix:
|
radix:
|
||||||
git: https://github.com/luislavena/radix.git
|
git: https://github.com/luislavena/radix.git
|
||||||
|
|
|
@ -24,7 +24,7 @@ dependencies:
|
||||||
version: ~> 0.6.1
|
version: ~> 0.6.1
|
||||||
protodec:
|
protodec:
|
||||||
github: iv-org/protodec
|
github: iv-org/protodec
|
||||||
version: ~> 0.1.4
|
version: ~> 0.1.5
|
||||||
lsquic:
|
lsquic:
|
||||||
github: iv-org/lsquic.cr
|
github: iv-org/lsquic.cr
|
||||||
version: ~> 2.18.1-2
|
version: ~> 2.18.1-2
|
||||||
|
|
|
@ -4,7 +4,7 @@ Spectator.describe Invidious::Hashtag do
|
||||||
it "parses richItemRenderer containers (test 1)" do
|
it "parses richItemRenderer containers (test 1)" do
|
||||||
# Enable mock
|
# Enable mock
|
||||||
test_content = load_mock("hashtag/martingarrix_page1")
|
test_content = load_mock("hashtag/martingarrix_page1")
|
||||||
videos = extract_items(test_content)
|
videos, _ = extract_items(test_content)
|
||||||
|
|
||||||
expect(typeof(videos)).to eq(Array(SearchItem))
|
expect(typeof(videos)).to eq(Array(SearchItem))
|
||||||
expect(videos.size).to eq(60)
|
expect(videos.size).to eq(60)
|
||||||
|
@ -57,7 +57,7 @@ Spectator.describe Invidious::Hashtag do
|
||||||
it "parses richItemRenderer containers (test 2)" do
|
it "parses richItemRenderer containers (test 2)" do
|
||||||
# Enable mock
|
# Enable mock
|
||||||
test_content = load_mock("hashtag/martingarrix_page2")
|
test_content = load_mock("hashtag/martingarrix_page2")
|
||||||
videos = extract_items(test_content)
|
videos, _ = extract_items(test_content)
|
||||||
|
|
||||||
expect(typeof(videos)).to eq(Array(SearchItem))
|
expect(typeof(videos)).to eq(Array(SearchItem))
|
||||||
expect(videos.size).to eq(60)
|
expect(videos.size).to eq(60)
|
||||||
|
|
|
@ -23,12 +23,6 @@ Spectator.describe "Helper" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#produce_channel_playlists_url" do
|
|
||||||
it "correctly produces a /browse_ajax URL with the given UCID and cursor" do
|
|
||||||
expect(produce_channel_playlists_url("UCCj956IF62FbT7Gouszaj9w", "AIOkY9EQpi_gyn1_QrFuZ1reN81_MMmI1YmlBblw8j7JHItEFG5h7qcJTNd4W9x5Quk_CVZ028gW")).to eq("/browse_ajax?continuation=4qmFsgLNARIYVUNDajk1NklGNjJGYlQ3R291c3phajl3GrABRWdsd2JHRjViR2x6ZEhNd0FqZ0JZQUZxQUxnQkFIcG1VVlZzVUdFeGF6VlNWa1ozWVZZNWJtVlhOSGhZTVVaNVVtNVdZVTFZU214VWFtZDRXREF4VG1KVmEzaFhWekZ6VVcxS2MyUjZhSEZPTUhCSlUxaFNSbEpyWXpGaFJHUjRXVEJ3VlZSdFVUQldlbXcwVGxaR01XRXhPVVJXYkc5M1RXcG9ibFozSUFFWUF3PT0%3D&gl=US&hl=en")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#produce_comment_continuation" do
|
describe "#produce_comment_continuation" do
|
||||||
it "correctly produces a continuation token for comments" do
|
it "correctly produces a continuation token for comments" do
|
||||||
expect(produce_comment_continuation("_cE8xSu6swE", "ADSJ_i2qvJeFtL0htmS5_K5Ctj3eGFVBMWL9Wd42o3kmUL6_mAzdLp85-liQZL0mYr_16BhaggUqX652Sv9JqV6VXinShSP-ZT6rL4NolPBaPXVtJsO5_rA_qE3GubAuLFw9uzIIXU2-HnpXbdgPLWTFavfX206hqWmmpHwUOrmxQV_OX6tYkM3ux3rPAKCDrT8eWL7MU3bLiNcnbgkW8o0h8KYLL_8BPa8LcHbTv8pAoNkjerlX1x7K4pqxaXPoyz89qNlnh6rRx6AXgAzzoHH1dmcyQ8CIBeOHg-m4i8ZxdX4dP88XWrIFg-jJGhpGP8JUMDgZgavxVx225hUEYZMyrLGler5em4FgbG62YWC51moLDLeYEA")).to eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyjAMK9gJBRFNKX2kycXZKZUZ0TDBodG1TNV9LNUN0ajNlR0ZWQk1XTDlXZDQybzNrbVVMNl9tQXpkTHA4NS1saVFaTDBtWXJfMTZCaGFnZ1VxWDY1MlN2OUpxVjZWWGluU2hTUC1aVDZyTDROb2xQQmFQWFZ0SnNPNV9yQV9xRTNHdWJBdUxGdzl1eklJWFUyLUhucFhiZGdQTFdURmF2ZlgyMDZocVdtbXBId1VPcm14UVZfT1g2dFlrTTN1eDNyUEFLQ0RyVDhlV0w3TVUzYkxpTmNuYmdrVzhvMGg4S1lMTF84QlBhOExjSGJUdjhwQW9Oa2plcmxYMXg3SzRwcXhhWFBveXo4OXFObG5oNnJSeDZBWGdBenpvSEgxZG1jeVE4Q0lCZU9IZy1tNGk4WnhkWDRkUDg4WFdySUZnLWpKR2hwR1A4SlVNRGdaZ2F2eFZ4MjI1aFVFWVpNeXJMR2xlcjVlbTRGZ2JHNjJZV0M1MW1vTERMZVlFQSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D")
|
expect(produce_comment_continuation("_cE8xSu6swE", "ADSJ_i2qvJeFtL0htmS5_K5Ctj3eGFVBMWL9Wd42o3kmUL6_mAzdLp85-liQZL0mYr_16BhaggUqX652Sv9JqV6VXinShSP-ZT6rL4NolPBaPXVtJsO5_rA_qE3GubAuLFw9uzIIXU2-HnpXbdgPLWTFavfX206hqWmmpHwUOrmxQV_OX6tYkM3ux3rPAKCDrT8eWL7MU3bLiNcnbgkW8o0h8KYLL_8BPa8LcHbTv8pAoNkjerlX1x7K4pqxaXPoyz89qNlnh6rRx6AXgAzzoHH1dmcyQ8CIBeOHg-m4i8ZxdX4dP88XWrIFg-jJGhpGP8JUMDgZgavxVx225hUEYZMyrLGler5em4FgbG62YWC51moLDLeYEA")).to eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyjAMK9gJBRFNKX2kycXZKZUZ0TDBodG1TNV9LNUN0ajNlR0ZWQk1XTDlXZDQybzNrbVVMNl9tQXpkTHA4NS1saVFaTDBtWXJfMTZCaGFnZ1VxWDY1MlN2OUpxVjZWWGluU2hTUC1aVDZyTDROb2xQQmFQWFZ0SnNPNV9yQV9xRTNHdWJBdUxGdzl1eklJWFUyLUhucFhiZGdQTFdURmF2ZlgyMDZocVdtbXBId1VPcm14UVZfT1g2dFlrTTN1eDNyUEFLQ0RyVDhlV0w3TVUzYkxpTmNuYmdrVzhvMGg4S1lMTF84QlBhOExjSGJUdjhwQW9Oa2plcmxYMXg3SzRwcXhhWFBveXo4OXFObG5oNnJSeDZBWGdBenpvSEgxZG1jeVE4Q0lCZU9IZy1tNGk4WnhkWDRkUDg4WFdySUZnLWpKR2hwR1A4SlVNRGdaZ2F2eFZ4MjI1aFVFWVpNeXJMR2xlcjVlbTRGZ2JHNjJZV0M1MW1vTERMZVlFQSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D")
|
||||||
|
|
|
@ -34,6 +34,7 @@ require "protodec/utils"
|
||||||
|
|
||||||
require "./invidious/database/*"
|
require "./invidious/database/*"
|
||||||
require "./invidious/database/migrations/*"
|
require "./invidious/database/migrations/*"
|
||||||
|
require "./invidious/http_server/*"
|
||||||
require "./invidious/helpers/*"
|
require "./invidious/helpers/*"
|
||||||
require "./invidious/yt_backend/*"
|
require "./invidious/yt_backend/*"
|
||||||
require "./invidious/frontend/*"
|
require "./invidious/frontend/*"
|
||||||
|
@ -48,6 +49,13 @@ require "./invidious/search/*"
|
||||||
require "./invidious/routes/**"
|
require "./invidious/routes/**"
|
||||||
require "./invidious/jobs/**"
|
require "./invidious/jobs/**"
|
||||||
|
|
||||||
|
# Declare the base namespace for invidious
|
||||||
|
module Invidious
|
||||||
|
end
|
||||||
|
|
||||||
|
# Simple alias to make code easier to read
|
||||||
|
alias IV = Invidious
|
||||||
|
|
||||||
CONFIG = Config.load
|
CONFIG = Config.load
|
||||||
HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32)
|
HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32)
|
||||||
|
|
||||||
|
@ -172,7 +180,7 @@ if CONFIG.popular_enabled
|
||||||
Invidious::Jobs.register Invidious::Jobs::PullPopularVideosJob.new(PG_DB)
|
Invidious::Jobs.register Invidious::Jobs::PullPopularVideosJob.new(PG_DB)
|
||||||
end
|
end
|
||||||
|
|
||||||
CONNECTION_CHANNEL = Channel({Bool, Channel(PQ::Notification)}).new(32)
|
CONNECTION_CHANNEL = ::Channel({Bool, ::Channel(PQ::Notification)}).new(32)
|
||||||
Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(CONNECTION_CHANNEL, CONFIG.database_url)
|
Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(CONNECTION_CHANNEL, CONFIG.database_url)
|
||||||
|
|
||||||
Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
|
Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
|
||||||
|
|
|
@ -16,12 +16,6 @@ record AboutChannel,
|
||||||
tabs : Array(String),
|
tabs : Array(String),
|
||||||
verified : Bool
|
verified : Bool
|
||||||
|
|
||||||
record AboutRelatedChannel,
|
|
||||||
ucid : String,
|
|
||||||
author : String,
|
|
||||||
author_url : String,
|
|
||||||
author_thumbnail : String
|
|
||||||
|
|
||||||
def get_about_info(ucid, locale) : AboutChannel
|
def get_about_info(ucid, locale) : AboutChannel
|
||||||
begin
|
begin
|
||||||
# "EgVhYm91dA==" is the base64-encoded protobuf object {"2:string":"about"}
|
# "EgVhYm91dA==" is the base64-encoded protobuf object {"2:string":"about"}
|
||||||
|
@ -100,34 +94,46 @@ def get_about_info(ucid, locale) : AboutChannel
|
||||||
total_views = 0_i64
|
total_views = 0_i64
|
||||||
joined = Time.unix(0)
|
joined = Time.unix(0)
|
||||||
|
|
||||||
tabs = [] of String
|
tab_names = [] of String
|
||||||
|
|
||||||
tabs_json = initdata["contents"]["twoColumnBrowseResultsRenderer"]["tabs"]?.try &.as_a?
|
if tabs_json = initdata["contents"]["twoColumnBrowseResultsRenderer"]["tabs"]?
|
||||||
if !tabs_json.nil?
|
# Get the name of the tabs available on this channel
|
||||||
# Retrieve information from the tabs array. The index we are looking for varies between channels.
|
tab_names = tabs_json.as_a.compact_map do |entry|
|
||||||
tabs_json.each do |node|
|
name = entry.dig?("tabRenderer", "title").try &.as_s.downcase
|
||||||
# Try to find the about section which is located in only one of the tabs.
|
|
||||||
channel_about_meta = node["tabRenderer"]?.try &.["content"]?.try &.["sectionListRenderer"]?
|
|
||||||
.try &.["contents"]?.try &.[0]?.try &.["itemSectionRenderer"]?.try &.["contents"]?
|
|
||||||
.try &.[0]?.try &.["channelAboutFullMetadataRenderer"]?
|
|
||||||
|
|
||||||
if !channel_about_meta.nil?
|
# This is a small fix to not add extra code on the HTML side
|
||||||
total_views = channel_about_meta["viewCountText"]?.try &.["simpleText"]?.try &.as_s.gsub(/\D/, "").to_i64? || 0_i64
|
# I.e, the URL for the "live" tab is .../streams, so use "streams"
|
||||||
|
# everywhere for the sake of simplicity
|
||||||
# The joined text is split to several sub strings. The reduce joins those strings before parsing the date.
|
(name == "live") ? "streams" : name
|
||||||
joined = channel_about_meta["joinedDateText"]?.try &.["runs"]?.try &.as_a.reduce("") { |acc, nd| acc + nd["text"].as_s }
|
end
|
||||||
.try { |text| Time.parse(text, "Joined %b %-d, %Y", Time::Location.local) } || Time.unix(0)
|
|
||||||
|
# Get the currently active tab ("About")
|
||||||
# Normal Auto-generated channels
|
about_tab = extract_selected_tab(tabs_json)
|
||||||
# https://support.google.com/youtube/answer/2579942
|
|
||||||
# For auto-generated channels, channel_about_meta only has ["description"]["simpleText"] and ["primaryLinks"][0]["title"]["simpleText"]
|
# Try to find the about metadata section
|
||||||
if (channel_about_meta["primaryLinks"]?.try &.size || 0) == 1 && (channel_about_meta["primaryLinks"][0]?) &&
|
channel_about_meta = about_tab.dig?(
|
||||||
(channel_about_meta["primaryLinks"][0]["title"]?.try &.["simpleText"]?.try &.as_s? || "") == "Auto-generated by YouTube"
|
"content",
|
||||||
auto_generated = true
|
"sectionListRenderer", "contents", 0,
|
||||||
end
|
"itemSectionRenderer", "contents", 0,
|
||||||
end
|
"channelAboutFullMetadataRenderer"
|
||||||
|
)
|
||||||
|
|
||||||
|
if !channel_about_meta.nil?
|
||||||
|
total_views = channel_about_meta.dig?("viewCountText", "simpleText").try &.as_s.gsub(/\D/, "").to_i64? || 0_i64
|
||||||
|
|
||||||
|
# The joined text is split to several sub strings. The reduce joins those strings before parsing the date.
|
||||||
|
joined = extract_text(channel_about_meta["joinedDateText"]?)
|
||||||
|
.try { |text| Time.parse(text, "Joined %b %-d, %Y", Time::Location.local) } || Time.unix(0)
|
||||||
|
|
||||||
|
# Normal Auto-generated channels
|
||||||
|
# https://support.google.com/youtube/answer/2579942
|
||||||
|
# For auto-generated channels, channel_about_meta only has
|
||||||
|
# ["description"]["simpleText"] and ["primaryLinks"][0]["title"]["simpleText"]
|
||||||
|
auto_generated = (
|
||||||
|
(channel_about_meta["primaryLinks"]?.try &.size) == 1 && \
|
||||||
|
extract_text(channel_about_meta.dig?("primaryLinks", 0, "title")) == "Auto-generated by YouTube"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
tabs = tabs_json.reject { |node| node["tabRenderer"]?.nil? }.map(&.["tabRenderer"]["title"].as_s.downcase)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
sub_count = initdata
|
sub_count = initdata
|
||||||
|
@ -148,46 +154,20 @@ def get_about_info(ucid, locale) : AboutChannel
|
||||||
joined: joined,
|
joined: joined,
|
||||||
is_family_friendly: is_family_friendly,
|
is_family_friendly: is_family_friendly,
|
||||||
allowed_regions: allowed_regions,
|
allowed_regions: allowed_regions,
|
||||||
tabs: tabs,
|
tabs: tab_names,
|
||||||
verified: author_verified || false,
|
verified: author_verified || false,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_related_channels(about_channel : AboutChannel) : Array(AboutRelatedChannel)
|
def fetch_related_channels(about_channel : AboutChannel, continuation : String? = nil) : {Array(SearchChannel), String?}
|
||||||
# params is {"2:string":"channels"} encoded
|
if continuation.nil?
|
||||||
channels = YoutubeAPI.browse(browse_id: about_channel.ucid, params: "EghjaGFubmVscw%3D%3D")
|
# params is {"2:string":"channels"} encoded
|
||||||
|
initial_data = YoutubeAPI.browse(browse_id: about_channel.ucid, params: "EghjaGFubmVscw%3D%3D")
|
||||||
tabs = channels.dig?("contents", "twoColumnBrowseResultsRenderer", "tabs").try(&.as_a?) || [] of JSON::Any
|
else
|
||||||
tab = tabs.find(&.dig?("tabRenderer", "title").try(&.as_s?).try(&.== "Channels"))
|
initial_data = YoutubeAPI.browse(continuation)
|
||||||
|
|
||||||
return [] of AboutRelatedChannel if tab.nil?
|
|
||||||
|
|
||||||
items = tab.dig?(
|
|
||||||
"tabRenderer", "content",
|
|
||||||
"sectionListRenderer", "contents", 0,
|
|
||||||
"itemSectionRenderer", "contents", 0,
|
|
||||||
"gridRenderer", "items"
|
|
||||||
).try &.as_a?
|
|
||||||
|
|
||||||
related = [] of AboutRelatedChannel
|
|
||||||
return related if (items.nil? || items.empty?)
|
|
||||||
|
|
||||||
items.each do |item|
|
|
||||||
renderer = item["gridChannelRenderer"]?
|
|
||||||
next if !renderer
|
|
||||||
|
|
||||||
related_id = renderer.dig("channelId").as_s
|
|
||||||
related_title = renderer.dig("title", "simpleText").as_s
|
|
||||||
related_author_url = renderer.dig("navigationEndpoint", "browseEndpoint", "canonicalBaseUrl").as_s
|
|
||||||
related_author_thumbnail = HelperExtractors.get_thumbnails(renderer)
|
|
||||||
|
|
||||||
related << AboutRelatedChannel.new(
|
|
||||||
ucid: related_id,
|
|
||||||
author: related_title,
|
|
||||||
author_url: related_author_url,
|
|
||||||
author_thumbnail: related_author_thumbnail,
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return related
|
items, continuation = extract_items(initial_data)
|
||||||
|
|
||||||
|
return items.select(SearchChannel), continuation
|
||||||
end
|
end
|
||||||
|
|
|
@ -180,11 +180,16 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
||||||
|
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : author = #{author}, auto_generated = #{auto_generated}")
|
LOGGER.trace("fetch_channel: #{ucid} : author = #{author}, auto_generated = #{auto_generated}")
|
||||||
|
|
||||||
page = 1
|
channel = InvidiousChannel.new({
|
||||||
|
id: ucid,
|
||||||
|
author: author,
|
||||||
|
updated: Time.utc,
|
||||||
|
deleted: false,
|
||||||
|
subscribed: nil,
|
||||||
|
})
|
||||||
|
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : Downloading channel videos page")
|
LOGGER.trace("fetch_channel: #{ucid} : Downloading channel videos page")
|
||||||
initial_data = get_channel_videos_response(ucid, page, auto_generated: auto_generated)
|
videos, continuation = IV::Channel::Tabs.get_videos(channel)
|
||||||
videos = extract_videos(initial_data, author, ucid)
|
|
||||||
|
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : Extracting videos from channel RSS feed")
|
LOGGER.trace("fetch_channel: #{ucid} : Extracting videos from channel RSS feed")
|
||||||
rss.xpath_nodes("//feed/entry").each do |entry|
|
rss.xpath_nodes("//feed/entry").each do |entry|
|
||||||
|
@ -197,7 +202,9 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
||||||
views = entry.xpath_node("group/community/statistics").try &.["views"]?.try &.to_i64?
|
views = entry.xpath_node("group/community/statistics").try &.["views"]?.try &.to_i64?
|
||||||
views ||= 0_i64
|
views ||= 0_i64
|
||||||
|
|
||||||
channel_video = videos.select { |video| video.id == video_id }[0]?
|
channel_video = videos
|
||||||
|
.select(SearchVideo)
|
||||||
|
.select(&.id.== video_id)[0]?
|
||||||
|
|
||||||
length_seconds = channel_video.try &.length_seconds
|
length_seconds = channel_video.try &.length_seconds
|
||||||
length_seconds ||= 0
|
length_seconds ||= 0
|
||||||
|
@ -228,58 +235,56 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
||||||
|
|
||||||
if was_insert
|
if was_insert
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Inserted, updating subscriptions")
|
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Inserted, updating subscriptions")
|
||||||
Invidious::Database::Users.add_notification(video)
|
if CONFIG.enable_user_notifications
|
||||||
|
Invidious::Database::Users.add_notification(video)
|
||||||
|
else
|
||||||
|
Invidious::Database::Users.feed_needs_update(video)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Updated")
|
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Updated")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if pull_all_videos
|
if pull_all_videos
|
||||||
page += 1
|
|
||||||
|
|
||||||
ids = [] of String
|
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
initial_data = get_channel_videos_response(ucid, page, auto_generated: auto_generated)
|
# Keep fetching videos using the continuation token retrieved earlier
|
||||||
videos = extract_videos(initial_data, author, ucid)
|
videos, continuation = IV::Channel::Tabs.get_videos(channel, continuation: continuation)
|
||||||
|
|
||||||
count = videos.size
|
count = 0
|
||||||
videos = videos.map { |video| ChannelVideo.new({
|
videos.select(SearchVideo).each do |video|
|
||||||
id: video.id,
|
count += 1
|
||||||
title: video.title,
|
video = ChannelVideo.new({
|
||||||
published: video.published,
|
id: video.id,
|
||||||
updated: Time.utc,
|
title: video.title,
|
||||||
ucid: video.ucid,
|
published: video.published,
|
||||||
author: video.author,
|
updated: Time.utc,
|
||||||
length_seconds: video.length_seconds,
|
ucid: video.ucid,
|
||||||
live_now: video.live_now,
|
author: video.author,
|
||||||
premiere_timestamp: video.premiere_timestamp,
|
length_seconds: video.length_seconds,
|
||||||
views: video.views,
|
live_now: video.live_now,
|
||||||
}) }
|
premiere_timestamp: video.premiere_timestamp,
|
||||||
|
views: video.views,
|
||||||
videos.each do |video|
|
})
|
||||||
ids << video.id
|
|
||||||
|
|
||||||
# We are notified of Red videos elsewhere (PubSub), which includes a correct published date,
|
# We are notified of Red videos elsewhere (PubSub), which includes a correct published date,
|
||||||
# so since they don't provide a published date here we can safely ignore them.
|
# so since they don't provide a published date here we can safely ignore them.
|
||||||
if Time.utc - video.published > 1.minute
|
if Time.utc - video.published > 1.minute
|
||||||
was_insert = Invidious::Database::ChannelVideos.insert(video)
|
was_insert = Invidious::Database::ChannelVideos.insert(video)
|
||||||
Invidious::Database::Users.add_notification(video) if was_insert
|
if was_insert
|
||||||
|
if CONFIG.enable_user_notifications
|
||||||
|
Invidious::Database::Users.add_notification(video)
|
||||||
|
else
|
||||||
|
Invidious::Database::Users.feed_needs_update(video)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
break if count < 25
|
break if count < 25
|
||||||
page += 1
|
sleep 500.milliseconds
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
channel = InvidiousChannel.new({
|
channel.updated = Time.utc
|
||||||
id: ucid,
|
|
||||||
author: author,
|
|
||||||
updated: Time.utc,
|
|
||||||
deleted: false,
|
|
||||||
subscribed: nil,
|
|
||||||
})
|
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
end
|
end
|
||||||
|
|
|
@ -69,7 +69,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
|
||||||
next if !post
|
next if !post
|
||||||
|
|
||||||
content_html = post["contentText"]?.try { |t| parse_content(t) } || ""
|
content_html = post["contentText"]?.try { |t| parse_content(t) } || ""
|
||||||
author = post["authorText"]?.try &.["simpleText"]? || ""
|
author = post["authorText"]["runs"]?.try &.[0]?.try &.["text"]? || ""
|
||||||
|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "author", author
|
json.field "author", author
|
||||||
|
@ -189,6 +189,32 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
|
||||||
# when .has_key?("pollRenderer")
|
# when .has_key?("pollRenderer")
|
||||||
# attachment = attachment["pollRenderer"]
|
# attachment = attachment["pollRenderer"]
|
||||||
# json.field "type", "poll"
|
# json.field "type", "poll"
|
||||||
|
when .has_key?("postMultiImageRenderer")
|
||||||
|
attachment = attachment["postMultiImageRenderer"]
|
||||||
|
json.field "type", "multiImage"
|
||||||
|
json.field "images" do
|
||||||
|
json.array do
|
||||||
|
attachment["images"].as_a.each do |image|
|
||||||
|
json.array do
|
||||||
|
thumbnail = image["backstageImageRenderer"]["image"]["thumbnails"][0].as_h
|
||||||
|
width = thumbnail["width"].as_i
|
||||||
|
height = thumbnail["height"].as_i
|
||||||
|
aspect_ratio = (width.to_f / height.to_f)
|
||||||
|
url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
|
||||||
|
|
||||||
|
qualities = {320, 560, 640, 1280, 2000}
|
||||||
|
|
||||||
|
qualities.each do |quality|
|
||||||
|
json.object do
|
||||||
|
json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
|
||||||
|
json.field "width", quality
|
||||||
|
json.field "height", (quality / aspect_ratio).ceil.to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
json.field "type", "unknown"
|
json.field "type", "unknown"
|
||||||
json.field "error", "Unrecognized attachment type."
|
json.field "error", "Unrecognized attachment type."
|
||||||
|
|
|
@ -1,93 +1,28 @@
|
||||||
def fetch_channel_playlists(ucid, author, continuation, sort_by)
|
def fetch_channel_playlists(ucid, author, continuation, sort_by)
|
||||||
if continuation
|
if continuation
|
||||||
response_json = YoutubeAPI.browse(continuation)
|
initial_data = YoutubeAPI.browse(continuation)
|
||||||
continuation_items = response_json["onResponseReceivedActions"]?
|
|
||||||
.try &.[0]["appendContinuationItemsAction"]["continuationItems"]
|
|
||||||
|
|
||||||
return [] of SearchItem, nil if !continuation_items
|
|
||||||
|
|
||||||
items = [] of SearchItem
|
|
||||||
continuation_items.as_a.select(&.as_h.has_key?("gridPlaylistRenderer")).each { |item|
|
|
||||||
extract_item(item, author, ucid).try { |t| items << t }
|
|
||||||
}
|
|
||||||
|
|
||||||
continuation = continuation_items.as_a.last["continuationItemRenderer"]?
|
|
||||||
.try &.["continuationEndpoint"]["continuationCommand"]["token"].as_s
|
|
||||||
else
|
else
|
||||||
url = "/channel/#{ucid}/playlists?flow=list&view=1"
|
params =
|
||||||
|
case sort_by
|
||||||
|
when "last", "last_added"
|
||||||
|
# Equivalent to "&sort=lad"
|
||||||
|
# {"2:string": "playlists", "3:varint": 4, "4:varint": 1, "6:varint": 1}
|
||||||
|
"EglwbGF5bGlzdHMYBCABMAE%3D"
|
||||||
|
when "oldest", "oldest_created"
|
||||||
|
# formerly "&sort=da"
|
||||||
|
# Not available anymore :c or maybe ??
|
||||||
|
# {"2:string": "playlists", "3:varint": 2, "4:varint": 1, "6:varint": 1}
|
||||||
|
"EglwbGF5bGlzdHMYAiABMAE%3D"
|
||||||
|
# {"2:string": "playlists", "3:varint": 1, "4:varint": 1, "6:varint": 1}
|
||||||
|
# "EglwbGF5bGlzdHMYASABMAE%3D"
|
||||||
|
when "newest", "newest_created"
|
||||||
|
# Formerly "&sort=dd"
|
||||||
|
# {"2:string": "playlists", "3:varint": 3, "4:varint": 1, "6:varint": 1}
|
||||||
|
"EglwbGF5bGlzdHMYAyABMAE%3D"
|
||||||
|
end
|
||||||
|
|
||||||
case sort_by
|
initial_data = YoutubeAPI.browse(ucid, params: params || "")
|
||||||
when "last", "last_added"
|
|
||||||
#
|
|
||||||
when "oldest", "oldest_created"
|
|
||||||
url += "&sort=da"
|
|
||||||
when "newest", "newest_created"
|
|
||||||
url += "&sort=dd"
|
|
||||||
else nil # Ignore
|
|
||||||
end
|
|
||||||
|
|
||||||
response = YT_POOL.client &.get(url)
|
|
||||||
initial_data = extract_initial_data(response.body)
|
|
||||||
return [] of SearchItem, nil if !initial_data
|
|
||||||
|
|
||||||
items = extract_items(initial_data, author, ucid)
|
|
||||||
continuation = response.body.match(/"token":"(?<continuation>[^"]+)"/).try &.["continuation"]?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return items, continuation
|
return extract_items(initial_data, author, ucid)
|
||||||
end
|
|
||||||
|
|
||||||
# ## NOTE: DEPRECATED
|
|
||||||
# Reason -> Unstable
|
|
||||||
# The Protobuf object must be provided with an id of the last playlist from the current "page"
|
|
||||||
# in order to fetch the next one accurately
|
|
||||||
# (if the id isn't included, entries shift around erratically between pages,
|
|
||||||
# leading to repetitions and skip overs)
|
|
||||||
#
|
|
||||||
# Since it's impossible to produce the appropriate Protobuf without an id being provided by the user,
|
|
||||||
# it's better to stick to continuation tokens provided by the first request and onward
|
|
||||||
def produce_channel_playlists_url(ucid, cursor, sort = "newest", auto_generated = false)
|
|
||||||
object = {
|
|
||||||
"80226972:embedded" => {
|
|
||||||
"2:string" => ucid,
|
|
||||||
"3:base64" => {
|
|
||||||
"2:string" => "playlists",
|
|
||||||
"6:varint" => 2_i64,
|
|
||||||
"7:varint" => 1_i64,
|
|
||||||
"12:varint" => 1_i64,
|
|
||||||
"13:string" => "",
|
|
||||||
"23:varint" => 0_i64,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if cursor
|
|
||||||
cursor = Base64.urlsafe_encode(cursor, false) if !auto_generated
|
|
||||||
object["80226972:embedded"]["3:base64"].as(Hash)["15:string"] = cursor
|
|
||||||
end
|
|
||||||
|
|
||||||
if auto_generated
|
|
||||||
object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0x32_i64
|
|
||||||
else
|
|
||||||
object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 1_i64
|
|
||||||
case sort
|
|
||||||
when "oldest", "oldest_created"
|
|
||||||
object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 2_i64
|
|
||||||
when "newest", "newest_created"
|
|
||||||
object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 3_i64
|
|
||||||
when "last", "last_added"
|
|
||||||
object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 4_i64
|
|
||||||
else nil # Ignore
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
object["80226972:embedded"]["3:string"] = Base64.urlsafe_encode(Protodec::Any.from_json(Protodec::Any.cast_json(object["80226972:embedded"]["3:base64"])))
|
|
||||||
object["80226972:embedded"].delete("3:base64")
|
|
||||||
|
|
||||||
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 "/browse_ajax?continuation=#{continuation}&gl=JP&hl=en"
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,14 @@ def produce_channel_videos_continuation(ucid, page = 1, auto_generated = nil, so
|
||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
|
sort_by_numerical =
|
||||||
|
case sort_by
|
||||||
|
when "newest" then 1_i64
|
||||||
|
when "popular" then 2_i64
|
||||||
|
when "oldest" then 3_i64 # Broken as of 10/2022 :c
|
||||||
|
else 1_i64 # Fallback to "newest"
|
||||||
|
end
|
||||||
|
|
||||||
object_inner_1 = {
|
object_inner_1 = {
|
||||||
"110:embedded" => {
|
"110:embedded" => {
|
||||||
"3:embedded" => {
|
"3:embedded" => {
|
||||||
|
@ -24,7 +32,7 @@ def produce_channel_videos_continuation(ucid, page = 1, auto_generated = nil, so
|
||||||
"1:string" => object_inner_2_encoded,
|
"1:string" => object_inner_2_encoded,
|
||||||
"2:string" => "00000000-0000-0000-0000-000000000000",
|
"2:string" => "00000000-0000-0000-0000-000000000000",
|
||||||
},
|
},
|
||||||
"3:varint" => 1_i64,
|
"3:varint" => sort_by_numerical,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -52,34 +60,138 @@ def produce_channel_videos_continuation(ucid, page = 1, auto_generated = nil, so
|
||||||
return continuation
|
return continuation
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_channel_videos_response(ucid, page = 1, auto_generated = nil, sort_by = "newest")
|
|
||||||
continuation = produce_channel_videos_continuation(ucid, page,
|
|
||||||
auto_generated: auto_generated, sort_by: sort_by, v2: true)
|
|
||||||
|
|
||||||
return YoutubeAPI.browse(continuation)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_60_videos(ucid, author, page, auto_generated, sort_by = "newest")
|
|
||||||
videos = [] of SearchVideo
|
|
||||||
|
|
||||||
# 2.times do |i|
|
|
||||||
# initial_data = get_channel_videos_response(ucid, page * 2 + (i - 1), auto_generated: auto_generated, sort_by: sort_by)
|
|
||||||
initial_data = get_channel_videos_response(ucid, 1, auto_generated: auto_generated, sort_by: sort_by)
|
|
||||||
videos = extract_videos(initial_data, author, ucid)
|
|
||||||
# end
|
|
||||||
|
|
||||||
return videos.size, videos
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_latest_videos(ucid)
|
|
||||||
initial_data = get_channel_videos_response(ucid)
|
|
||||||
author = initial_data["metadata"]?.try &.["channelMetadataRenderer"]?.try &.["title"]?.try &.as_s
|
|
||||||
|
|
||||||
return extract_videos(initial_data, author, ucid)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Used in bypass_captcha_job.cr
|
# Used in bypass_captcha_job.cr
|
||||||
def produce_channel_videos_url(ucid, page = 1, auto_generated = nil, sort_by = "newest", v2 = false)
|
def produce_channel_videos_url(ucid, page = 1, auto_generated = nil, sort_by = "newest", v2 = false)
|
||||||
continuation = produce_channel_videos_continuation(ucid, page, auto_generated, sort_by, v2)
|
continuation = produce_channel_videos_continuation(ucid, page, auto_generated, sort_by, v2)
|
||||||
return "/browse_ajax?continuation=#{continuation}&gl=JP&hl=en"
|
return "/browse_ajax?continuation=#{continuation}&gl=JP&hl=en"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Invidious::Channel::Tabs
|
||||||
|
extend self
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# Regular videos
|
||||||
|
# -------------------
|
||||||
|
|
||||||
|
def make_initial_video_ctoken(ucid, sort_by) : String
|
||||||
|
return produce_channel_videos_continuation(ucid, sort_by: sort_by)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wrapper for AboutChannel, as we still need to call get_videos with
|
||||||
|
# an author name and ucid directly (e.g in RSS feeds).
|
||||||
|
# TODO: figure out how to get rid of that
|
||||||
|
def get_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||||
|
return get_videos(
|
||||||
|
channel.author, channel.ucid,
|
||||||
|
continuation: continuation, sort_by: sort_by
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wrapper for InvidiousChannel, as we still need to call get_videos with
|
||||||
|
# an author name and ucid directly (e.g in RSS feeds).
|
||||||
|
# TODO: figure out how to get rid of that
|
||||||
|
def get_videos(channel : InvidiousChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||||
|
return get_videos(
|
||||||
|
channel.author, channel.id,
|
||||||
|
continuation: continuation, sort_by: sort_by
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_videos(author : String, ucid : String, *, continuation : String? = nil, sort_by = "newest")
|
||||||
|
continuation ||= make_initial_video_ctoken(ucid, sort_by)
|
||||||
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
|
||||||
|
return extract_items(initial_data, author, ucid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_60_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||||
|
if continuation.nil?
|
||||||
|
# Fetch the first "page" of video
|
||||||
|
items, next_continuation = get_videos(channel, sort_by: sort_by)
|
||||||
|
else
|
||||||
|
# Fetch a "page" of videos using the given continuation token
|
||||||
|
items, next_continuation = get_videos(channel, continuation: continuation)
|
||||||
|
end
|
||||||
|
|
||||||
|
# If there is more to load, then load a second "page"
|
||||||
|
# and replace the previous continuation token
|
||||||
|
if !next_continuation.nil?
|
||||||
|
items_2, next_continuation = get_videos(channel, continuation: next_continuation)
|
||||||
|
items.concat items_2
|
||||||
|
end
|
||||||
|
|
||||||
|
return items, next_continuation
|
||||||
|
end
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# Shorts
|
||||||
|
# -------------------
|
||||||
|
|
||||||
|
private def fetch_shorts_data(ucid : String, continuation : String? = nil)
|
||||||
|
if continuation.nil?
|
||||||
|
# EgZzaG9ydHPyBgUKA5oBAA%3D%3D is the protobuf object to load "shorts"
|
||||||
|
# TODO: try to extract the continuation tokens that allows other sorting options
|
||||||
|
return YoutubeAPI.browse(ucid, params: "EgZzaG9ydHPyBgUKA5oBAA%3D%3D")
|
||||||
|
else
|
||||||
|
return YoutubeAPI.browse(continuation: continuation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_shorts(channel : AboutChannel, continuation : String? = nil)
|
||||||
|
initial_data = self.fetch_shorts_data(channel.ucid, continuation)
|
||||||
|
|
||||||
|
begin
|
||||||
|
# Try to parse the initial data fetched above
|
||||||
|
return extract_items(initial_data, channel.author, channel.ucid)
|
||||||
|
rescue ex : RetryOnceException
|
||||||
|
# Sometimes, for a completely unknown reason, the "reelItemRenderer"
|
||||||
|
# object is missing some critical information (it happens once in about
|
||||||
|
# 20 subsequent requests). Refreshing the page is required to properly
|
||||||
|
# show the "shorts" tab.
|
||||||
|
#
|
||||||
|
# In order to make the experience smoother for the user, we simulate
|
||||||
|
# said page refresh by fetching again the JSON. If that still doesn't
|
||||||
|
# work, we raise a BrokenTubeException, as something is really broken.
|
||||||
|
begin
|
||||||
|
initial_data = self.fetch_shorts_data(channel.ucid, continuation)
|
||||||
|
return extract_items(initial_data, channel.author, channel.ucid)
|
||||||
|
rescue ex : RetryOnceException
|
||||||
|
raise BrokenTubeException.new "reelPlayerHeaderSupportedRenderers"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# Livestreams
|
||||||
|
# -------------------
|
||||||
|
|
||||||
|
def get_livestreams(channel : AboutChannel, continuation : String? = nil)
|
||||||
|
if continuation.nil?
|
||||||
|
# EgdzdHJlYW1z8gYECgJ6AA%3D%3D is the protobuf object to load "streams"
|
||||||
|
initial_data = YoutubeAPI.browse(channel.ucid, params: "EgdzdHJlYW1z8gYECgJ6AA%3D%3D")
|
||||||
|
else
|
||||||
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
end
|
||||||
|
|
||||||
|
return extract_items(initial_data, channel.author, channel.ucid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_60_livestreams(channel : AboutChannel, continuation : String? = nil)
|
||||||
|
if continuation.nil?
|
||||||
|
# Fetch the first "page" of streams
|
||||||
|
items, next_continuation = get_livestreams(channel)
|
||||||
|
else
|
||||||
|
# Fetch a "page" of streams using the given continuation token
|
||||||
|
items, next_continuation = get_livestreams(channel, continuation: continuation)
|
||||||
|
end
|
||||||
|
|
||||||
|
# If there is more to load, then load a second "page"
|
||||||
|
# and replace the previous continuation token
|
||||||
|
if !next_continuation.nil?
|
||||||
|
items_2, next_continuation = get_livestreams(channel, continuation: next_continuation)
|
||||||
|
items.concat items_2
|
||||||
|
end
|
||||||
|
|
||||||
|
return items, next_continuation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -110,6 +110,8 @@ class Config
|
||||||
property hsts : Bool? = true
|
property hsts : Bool? = true
|
||||||
# Disable proxying server-wide: options: 'dash', 'livestreams', 'downloads', 'local'
|
# Disable proxying server-wide: options: 'dash', 'livestreams', 'downloads', 'local'
|
||||||
property disable_proxy : Bool? | Array(String)? = false
|
property disable_proxy : Bool? | Array(String)? = false
|
||||||
|
# Enable the user notifications for all users
|
||||||
|
property enable_user_notifications : Bool = true
|
||||||
|
|
||||||
# URL to the modified source code to be easily AGPL compliant
|
# URL to the modified source code to be easily AGPL compliant
|
||||||
# Will display in the footer, next to the main source code link
|
# Will display in the footer, next to the main source code link
|
||||||
|
|
|
@ -154,6 +154,16 @@ module Invidious::Database::Users
|
||||||
# Update (misc)
|
# Update (misc)
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
|
def feed_needs_update(video : ChannelVideo)
|
||||||
|
request = <<-SQL
|
||||||
|
UPDATE users
|
||||||
|
SET feed_needs_update = true
|
||||||
|
WHERE $1 = ANY(subscriptions)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
PG_DB.exec(request, video.ucid)
|
||||||
|
end
|
||||||
|
|
||||||
def update_preferences(user : User)
|
def update_preferences(user : User)
|
||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
UPDATE users
|
UPDATE users
|
||||||
|
|
|
@ -33,3 +33,8 @@ end
|
||||||
|
|
||||||
class VideoNotAvailableException < Exception
|
class VideoNotAvailableException < Exception
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Exception used to indicate that the JSON response from YT is missing
|
||||||
|
# some important informations, and that the query should be sent again.
|
||||||
|
class RetryOnceException < Exception
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
module Invidious::Frontend::ChannelPage
|
||||||
|
extend self
|
||||||
|
|
||||||
|
enum TabsAvailable
|
||||||
|
Videos
|
||||||
|
Shorts
|
||||||
|
Streams
|
||||||
|
Playlists
|
||||||
|
Community
|
||||||
|
Channels
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_tabs_links(locale : String, channel : AboutChannel, selected_tab : TabsAvailable)
|
||||||
|
return String.build(1500) do |str|
|
||||||
|
base_url = "/channel/#{channel.ucid}"
|
||||||
|
|
||||||
|
TabsAvailable.each do |tab|
|
||||||
|
# Ignore playlists, as it is not supported for auto-generated channels yet
|
||||||
|
next if (tab.playlists? && channel.auto_generated)
|
||||||
|
|
||||||
|
tab_name = tab.to_s.downcase
|
||||||
|
|
||||||
|
if channel.tabs.includes? tab_name
|
||||||
|
str << %(<div class="pure-u-1 pure-md-1-3">\n)
|
||||||
|
|
||||||
|
if tab == selected_tab
|
||||||
|
str << "\t<b>"
|
||||||
|
str << translate(locale, "channel_tab_#{tab_name}_label")
|
||||||
|
str << "</b>\n"
|
||||||
|
else
|
||||||
|
# Video tab doesn't have the last path component
|
||||||
|
url = tab.videos? ? base_url : "#{base_url}/#{tab_name}"
|
||||||
|
|
||||||
|
str << %(\t<a href=") << url << %(">)
|
||||||
|
str << translate(locale, "channel_tab_#{tab_name}_label")
|
||||||
|
str << "</a>\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
str << "</div>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,7 +8,8 @@ module Invidious::Hashtag
|
||||||
client_config = YoutubeAPI::ClientConfig.new(region: region)
|
client_config = YoutubeAPI::ClientConfig.new(region: region)
|
||||||
response = YoutubeAPI.browse(continuation: ctoken, client_config: client_config)
|
response = YoutubeAPI.browse(continuation: ctoken, client_config: client_config)
|
||||||
|
|
||||||
return extract_items(response)
|
items, _ = extract_items(response)
|
||||||
|
return items
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_continuation(hashtag : String, cursor : Int)
|
def generate_continuation(hashtag : String, cursor : Int)
|
||||||
|
|
|
@ -20,7 +20,7 @@ module JSONFilter
|
||||||
/^\(|\(\(|\/\(/
|
/^\(|\(\(|\/\(/
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parse_fields(fields_text : String) : Nil
|
def self.parse_fields(fields_text : String, &) : Nil
|
||||||
if fields_text.empty?
|
if fields_text.empty?
|
||||||
raise FieldsParser::ParseError.new "Fields is empty"
|
raise FieldsParser::ParseError.new "Fields is empty"
|
||||||
end
|
end
|
||||||
|
@ -42,7 +42,7 @@ module JSONFilter
|
||||||
parse_nest_groups(fields_text) { |nest_list| yield nest_list }
|
parse_nest_groups(fields_text) { |nest_list| yield nest_list }
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parse_single_nests(fields_text : String) : Nil
|
def self.parse_single_nests(fields_text : String, &) : Nil
|
||||||
single_nests = remove_nest_groups(fields_text)
|
single_nests = remove_nest_groups(fields_text)
|
||||||
|
|
||||||
if !single_nests.empty?
|
if !single_nests.empty?
|
||||||
|
@ -60,7 +60,7 @@ module JSONFilter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parse_nest_groups(fields_text : String) : Nil
|
def self.parse_nest_groups(fields_text : String, &) : Nil
|
||||||
nest_stack = [] of NamedTuple(group_name: String, closing_bracket_index: Int64)
|
nest_stack = [] of NamedTuple(group_name: String, closing_bracket_index: Int64)
|
||||||
bracket_pairs = get_bracket_pairs(fields_text, true)
|
bracket_pairs = get_bracket_pairs(fields_text, true)
|
||||||
|
|
||||||
|
|
|
@ -265,4 +265,11 @@ class Category
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
struct Continuation
|
||||||
|
getter token
|
||||||
|
|
||||||
|
def initialize(@token : String)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
alias SearchItem = SearchVideo | SearchChannel | SearchPlaylist | Category
|
alias SearchItem = SearchVideo | SearchChannel | SearchPlaylist | Category
|
||||||
|
|
|
@ -162,7 +162,7 @@ def number_with_separator(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
def short_text_to_number(short_text : String) : Int64
|
def short_text_to_number(short_text : String) : Int64
|
||||||
matches = /(?<number>\d+(\.\d+)?)\s?(?<suffix>[mMkKbB])?/.match(short_text)
|
matches = /(?<number>\d+(\.\d+)?)\s?(?<suffix>[mMkKbB]?)/.match(short_text)
|
||||||
number = matches.try &.["number"].to_f || 0.0
|
number = matches.try &.["number"].to_f || 0.0
|
||||||
|
|
||||||
case matches.try &.["suffix"].downcase
|
case matches.try &.["suffix"].downcase
|
||||||
|
@ -259,7 +259,7 @@ def get_referer(env, fallback = "/", unroll = true)
|
||||||
end
|
end
|
||||||
|
|
||||||
referer = referer.request_target
|
referer = referer.request_target
|
||||||
referer = "/" + referer.gsub(/[^\/?@&%=\-_.0-9a-zA-Z]/, "").lstrip("/\\")
|
referer = "/" + referer.gsub(/[^\/?@&%=\-_.:,0-9a-zA-Z]/, "").lstrip("/\\")
|
||||||
|
|
||||||
if referer == env.request.path
|
if referer == env.request.path
|
||||||
referer = fallback
|
referer = fallback
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
module Invidious::HttpServer
|
||||||
|
module Utils
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def proxy_video_url(raw_url : String, *, region : String? = nil, absolute : Bool = false)
|
||||||
|
url = URI.parse(raw_url)
|
||||||
|
|
||||||
|
# Add some URL parameters
|
||||||
|
params = url.query_params
|
||||||
|
params["host"] = url.host.not_nil! # Should never be nil, in theory
|
||||||
|
params["region"] = region if !region.nil?
|
||||||
|
|
||||||
|
if absolute
|
||||||
|
return "#{HOST_URL}#{url.request_target}?#{params}"
|
||||||
|
else
|
||||||
|
return "#{url.request_target}?#{params}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,12 +1,12 @@
|
||||||
class Invidious::Jobs::NotificationJob < Invidious::Jobs::BaseJob
|
class Invidious::Jobs::NotificationJob < Invidious::Jobs::BaseJob
|
||||||
private getter connection_channel : Channel({Bool, Channel(PQ::Notification)})
|
private getter connection_channel : ::Channel({Bool, ::Channel(PQ::Notification)})
|
||||||
private getter pg_url : URI
|
private getter pg_url : URI
|
||||||
|
|
||||||
def initialize(@connection_channel, @pg_url)
|
def initialize(@connection_channel, @pg_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def begin
|
def begin
|
||||||
connections = [] of Channel(PQ::Notification)
|
connections = [] of ::Channel(PQ::Notification)
|
||||||
|
|
||||||
PG.connect_listen(pg_url, "notifications") { |event| connections.each(&.send(event)) }
|
PG.connect_listen(pg_url, "notifications") { |event| connections.each(&.send(event)) }
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Invidious::Jobs::RefreshChannelsJob < Invidious::Jobs::BaseJob
|
||||||
max_fibers = CONFIG.channel_threads
|
max_fibers = CONFIG.channel_threads
|
||||||
lim_fibers = max_fibers
|
lim_fibers = max_fibers
|
||||||
active_fibers = 0
|
active_fibers = 0
|
||||||
active_channel = Channel(Bool).new
|
active_channel = ::Channel(Bool).new
|
||||||
backoff = 2.minutes
|
backoff = 2.minutes
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Invidious::Jobs::RefreshFeedsJob < Invidious::Jobs::BaseJob
|
||||||
def begin
|
def begin
|
||||||
max_fibers = CONFIG.feed_threads
|
max_fibers = CONFIG.feed_threads
|
||||||
active_fibers = 0
|
active_fibers = 0
|
||||||
active_channel = Channel(Bool).new
|
active_channel = ::Channel(Bool).new
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
db.query("SELECT email FROM users WHERE feed_needs_update = true OR feed_needs_update IS NULL") do |rs|
|
db.query("SELECT email FROM users WHERE feed_needs_update = true OR feed_needs_update IS NULL") do |rs|
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Invidious::Jobs::SubscribeToFeedsJob < Invidious::Jobs::BaseJob
|
||||||
end
|
end
|
||||||
|
|
||||||
active_fibers = 0
|
active_fibers = 0
|
||||||
active_channel = Channel(Bool).new
|
active_channel = ::Channel(Bool).new
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
db.query_all("SELECT id FROM channels WHERE CURRENT_TIMESTAMP - subscribed > interval '4 days' OR subscribed IS NULL") do |rs|
|
db.query_all("SELECT id FROM channels WHERE CURRENT_TIMESTAMP - subscribed > interval '4 days' OR subscribed IS NULL") do |rs|
|
||||||
|
|
|
@ -3,7 +3,7 @@ require "json"
|
||||||
module Invidious::JSONify::APIv1
|
module Invidious::JSONify::APIv1
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
def video(video : Video, json : JSON::Builder, *, locale : String?)
|
def video(video : Video, json : JSON::Builder, *, locale : String?, proxy : Bool = false)
|
||||||
json.object do
|
json.object do
|
||||||
json.field "type", video.video_type
|
json.field "type", video.video_type
|
||||||
|
|
||||||
|
@ -89,7 +89,14 @@ module Invidious::JSONify::APIv1
|
||||||
# Not available on MPEG-4 Timed Text (`text/mp4`) streams (livestreams only)
|
# Not available on MPEG-4 Timed Text (`text/mp4`) streams (livestreams only)
|
||||||
json.field "bitrate", fmt["bitrate"].as_i.to_s if fmt["bitrate"]?
|
json.field "bitrate", fmt["bitrate"].as_i.to_s if fmt["bitrate"]?
|
||||||
|
|
||||||
json.field "url", fmt["url"]
|
if proxy
|
||||||
|
json.field "url", Invidious::HttpServer::Utils.proxy_video_url(
|
||||||
|
fmt["url"].to_s, absolute: true
|
||||||
|
)
|
||||||
|
else
|
||||||
|
json.field "url", fmt["url"]
|
||||||
|
end
|
||||||
|
|
||||||
json.field "itag", fmt["itag"].as_i.to_s
|
json.field "itag", fmt["itag"].as_i.to_s
|
||||||
json.field "type", fmt["mimeType"]
|
json.field "type", fmt["mimeType"]
|
||||||
json.field "clen", fmt["contentLength"]? || "-1"
|
json.field "clen", fmt["contentLength"]? || "-1"
|
||||||
|
|
|
@ -203,7 +203,7 @@ module Invidious::Routes::Account
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
if !user
|
if !user
|
||||||
return env.redirect referer
|
return env.redirect "/login?referer=#{URI.encode_path_segment(env.request.resource)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
user = user.as(User)
|
user = user.as(User)
|
||||||
|
|
|
@ -29,7 +29,7 @@ module Invidious::Routes::API::Manifest
|
||||||
|
|
||||||
if local
|
if local
|
||||||
uri = URI.parse(url)
|
uri = URI.parse(url)
|
||||||
url = "#{uri.request_target}host/#{uri.host}/"
|
url = "#{HOST_URL}#{uri.request_target}host/#{uri.host}/"
|
||||||
end
|
end
|
||||||
|
|
||||||
"<BaseURL>#{url}</BaseURL>"
|
"<BaseURL>#{url}</BaseURL>"
|
||||||
|
@ -42,7 +42,7 @@ module Invidious::Routes::API::Manifest
|
||||||
|
|
||||||
if local
|
if local
|
||||||
adaptive_fmts.each do |fmt|
|
adaptive_fmts.each do |fmt|
|
||||||
fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target)
|
fmt["url"] = JSON::Any.new("#{HOST_URL}#{URI.parse(fmt["url"].as_s).request_target}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
module Invidious::Routes::API::V1::Channels
|
module Invidious::Routes::API::V1::Channels
|
||||||
def self.home(env)
|
# Macro to avoid duplicating some code below
|
||||||
locale = env.get("preferences").as(Preferences).locale
|
# This sets the `channel` variable, or handles Exceptions.
|
||||||
|
private macro get_channel
|
||||||
env.response.content_type = "application/json"
|
|
||||||
|
|
||||||
ucid = env.params.url["ucid"]
|
|
||||||
sort_by = env.params.query["sort_by"]?.try &.downcase
|
|
||||||
sort_by ||= "newest"
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
channel = get_about_info(ucid, locale)
|
channel = get_about_info(ucid, locale)
|
||||||
rescue ex : ChannelRedirect
|
rescue ex : ChannelRedirect
|
||||||
|
@ -18,17 +12,25 @@ module Invidious::Routes::API::V1::Channels
|
||||||
rescue ex
|
rescue ex
|
||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
page = 1
|
def self.home(env)
|
||||||
if channel.auto_generated
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
videos = [] of SearchVideo
|
ucid = env.params.url["ucid"]
|
||||||
count = 0
|
|
||||||
else
|
env.response.content_type = "application/json"
|
||||||
begin
|
|
||||||
count, videos = get_60_videos(channel.ucid, channel.author, page, channel.auto_generated, sort_by)
|
# Use the private macro defined above.
|
||||||
rescue ex
|
channel = nil # Make the compiler happy
|
||||||
return error_json(500, ex)
|
get_channel()
|
||||||
end
|
|
||||||
|
# Retrieve "sort by" setting from URL parameters
|
||||||
|
sort_by = env.params.query["sort_by"]?.try &.downcase || "newest"
|
||||||
|
|
||||||
|
begin
|
||||||
|
videos, _ = Channel::Tabs.get_videos(channel, sort_by: sort_by)
|
||||||
|
rescue ex
|
||||||
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
JSON.build do |json|
|
JSON.build do |json|
|
||||||
|
@ -100,31 +102,13 @@ module Invidious::Routes::API::V1::Channels
|
||||||
json.array do
|
json.array do
|
||||||
# Fetch related channels
|
# Fetch related channels
|
||||||
begin
|
begin
|
||||||
related_channels = fetch_related_channels(channel)
|
related_channels, _ = fetch_related_channels(channel)
|
||||||
rescue ex
|
rescue ex
|
||||||
related_channels = [] of AboutRelatedChannel
|
related_channels = [] of SearchChannel
|
||||||
end
|
end
|
||||||
|
|
||||||
related_channels.each do |related_channel|
|
related_channels.each do |related_channel|
|
||||||
json.object do
|
related_channel.to_json(locale, json)
|
||||||
json.field "author", related_channel.author
|
|
||||||
json.field "authorId", related_channel.ucid
|
|
||||||
json.field "authorUrl", related_channel.author_url
|
|
||||||
|
|
||||||
json.field "authorThumbnails" do
|
|
||||||
json.array do
|
|
||||||
qualities = {32, 48, 76, 100, 176, 512}
|
|
||||||
|
|
||||||
qualities.each do |quality|
|
|
||||||
json.object do
|
|
||||||
json.field "url", related_channel.author_thumbnail.gsub(/=\d+/, "=s#{quality}")
|
|
||||||
json.field "width", quality
|
|
||||||
json.field "height", quality
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end # relatedChannels
|
end # relatedChannels
|
||||||
|
@ -134,61 +118,112 @@ module Invidious::Routes::API::V1::Channels
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.latest(env)
|
def self.latest(env)
|
||||||
locale = env.get("preferences").as(Preferences).locale
|
# Remove parameters that could affect this endpoint's behavior
|
||||||
|
env.params.query.delete("sort_by") if env.params.query.has_key?("sort_by")
|
||||||
|
env.params.query.delete("continuation") if env.params.query.has_key?("continuation")
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
return self.videos(env)
|
||||||
|
|
||||||
ucid = env.params.url["ucid"]
|
|
||||||
|
|
||||||
begin
|
|
||||||
videos = get_latest_videos(ucid)
|
|
||||||
rescue ex
|
|
||||||
return error_json(500, ex)
|
|
||||||
end
|
|
||||||
|
|
||||||
JSON.build do |json|
|
|
||||||
json.array do
|
|
||||||
videos.each do |video|
|
|
||||||
video.to_json(locale, json)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.videos(env)
|
def self.videos(env)
|
||||||
locale = env.get("preferences").as(Preferences).locale
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
ucid = env.params.url["ucid"]
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
ucid = env.params.url["ucid"]
|
# Use the private macro defined above.
|
||||||
page = env.params.query["page"]?.try &.to_i?
|
channel = nil # Make the compiler happy
|
||||||
page ||= 1
|
get_channel()
|
||||||
sort_by = env.params.query["sort"]?.try &.downcase
|
|
||||||
sort_by ||= env.params.query["sort_by"]?.try &.downcase
|
# Retrieve some URL parameters
|
||||||
sort_by ||= "newest"
|
sort_by = env.params.query["sort_by"]?.try &.downcase || "newest"
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
channel = get_about_info(ucid, locale)
|
videos, next_continuation = Channel::Tabs.get_60_videos(
|
||||||
rescue ex : ChannelRedirect
|
channel, continuation: continuation, sort_by: sort_by
|
||||||
env.response.headers["Location"] = env.request.resource.gsub(ucid, ex.channel_id)
|
)
|
||||||
return error_json(302, "Channel is unavailable", {"authorId" => ex.channel_id})
|
|
||||||
rescue ex : NotFoundException
|
|
||||||
return error_json(404, ex)
|
|
||||||
rescue ex
|
rescue ex
|
||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
return JSON.build do |json|
|
||||||
count, videos = get_60_videos(channel.ucid, channel.author, page, channel.auto_generated, sort_by)
|
json.object do
|
||||||
rescue ex
|
json.field "videos" do
|
||||||
return error_json(500, ex)
|
json.array do
|
||||||
end
|
videos.each &.to_json(locale, json)
|
||||||
|
end
|
||||||
JSON.build do |json|
|
|
||||||
json.array do
|
|
||||||
videos.each do |video|
|
|
||||||
video.to_json(locale, json)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
json.field "continuation", next_continuation if next_continuation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.shorts(env)
|
||||||
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
ucid = env.params.url["ucid"]
|
||||||
|
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
|
# Use the private macro defined above.
|
||||||
|
channel = nil # Make the compiler happy
|
||||||
|
get_channel()
|
||||||
|
|
||||||
|
# Retrieve continuation from URL parameters
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
|
begin
|
||||||
|
videos, next_continuation = Channel::Tabs.get_shorts(
|
||||||
|
channel, continuation: continuation
|
||||||
|
)
|
||||||
|
rescue ex
|
||||||
|
return error_json(500, ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
return JSON.build do |json|
|
||||||
|
json.object do
|
||||||
|
json.field "videos" do
|
||||||
|
json.array do
|
||||||
|
videos.each &.to_json(locale, json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json.field "continuation", next_continuation if next_continuation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.streams(env)
|
||||||
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
ucid = env.params.url["ucid"]
|
||||||
|
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
|
# Use the private macro defined above.
|
||||||
|
channel = nil # Make the compiler happy
|
||||||
|
get_channel()
|
||||||
|
|
||||||
|
# Retrieve continuation from URL parameters
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
|
begin
|
||||||
|
videos, next_continuation = Channel::Tabs.get_60_livestreams(
|
||||||
|
channel, continuation: continuation
|
||||||
|
)
|
||||||
|
rescue ex
|
||||||
|
return error_json(500, ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
return JSON.build do |json|
|
||||||
|
json.object do
|
||||||
|
json.field "videos" do
|
||||||
|
json.array do
|
||||||
|
videos.each &.to_json(locale, json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json.field "continuation", next_continuation if next_continuation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -204,16 +239,9 @@ module Invidious::Routes::API::V1::Channels
|
||||||
env.params.query["sort_by"]?.try &.downcase ||
|
env.params.query["sort_by"]?.try &.downcase ||
|
||||||
"last"
|
"last"
|
||||||
|
|
||||||
begin
|
# Use the macro defined above
|
||||||
channel = get_about_info(ucid, locale)
|
channel = nil # Make the compiler happy
|
||||||
rescue ex : ChannelRedirect
|
get_channel()
|
||||||
env.response.headers["Location"] = env.request.resource.gsub(ucid, ex.channel_id)
|
|
||||||
return error_json(302, "Channel is unavailable", {"authorId" => ex.channel_id})
|
|
||||||
rescue ex : NotFoundException
|
|
||||||
return error_json(404, ex)
|
|
||||||
rescue ex
|
|
||||||
return error_json(500, ex)
|
|
||||||
end
|
|
||||||
|
|
||||||
items, continuation = fetch_channel_playlists(channel.ucid, channel.author, continuation, sort_by)
|
items, continuation = fetch_channel_playlists(channel.ucid, channel.author, continuation, sort_by)
|
||||||
|
|
||||||
|
@ -255,6 +283,37 @@ module Invidious::Routes::API::V1::Channels
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.channels(env)
|
||||||
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
ucid = env.params.url["ucid"]
|
||||||
|
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
|
# Use the macro defined above
|
||||||
|
channel = nil # Make the compiler happy
|
||||||
|
get_channel()
|
||||||
|
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
|
begin
|
||||||
|
items, next_continuation = fetch_related_channels(channel, continuation)
|
||||||
|
rescue ex
|
||||||
|
return error_json(500, ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
JSON.build do |json|
|
||||||
|
json.object do
|
||||||
|
json.field "relatedChannels" do
|
||||||
|
json.array do
|
||||||
|
items.each &.to_json(locale, json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json.field "continuation", next_continuation if next_continuation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.search(env)
|
def self.search(env)
|
||||||
locale = env.get("preferences").as(Preferences).locale
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
region = env.params.query["region"]?
|
region = env.params.query["region"]?
|
||||||
|
|
|
@ -6,6 +6,7 @@ module Invidious::Routes::API::V1::Videos
|
||||||
|
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
region = env.params.query["region"]?
|
region = env.params.query["region"]?
|
||||||
|
proxy = {"1", "true"}.any? &.== env.params.query["local"]?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
video = get_video(id, region: region)
|
video = get_video(id, region: region)
|
||||||
|
@ -15,7 +16,9 @@ module Invidious::Routes::API::V1::Videos
|
||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
video.to_json(locale, nil)
|
return JSON.build do |json|
|
||||||
|
Invidious::JSONify::APIv1.video(video, json, locale: locale, proxy: proxy)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.captions(env)
|
def self.captions(env)
|
||||||
|
@ -90,45 +93,50 @@ module Invidious::Routes::API::V1::Videos
|
||||||
# as well as some other markup that makes it cumbersome, so we try to fix that here
|
# as well as some other markup that makes it cumbersome, so we try to fix that here
|
||||||
if caption.name.includes? "auto-generated"
|
if caption.name.includes? "auto-generated"
|
||||||
caption_xml = YT_POOL.client &.get(url).body
|
caption_xml = YT_POOL.client &.get(url).body
|
||||||
caption_xml = XML.parse(caption_xml)
|
|
||||||
|
|
||||||
webvtt = String.build do |str|
|
if caption_xml.starts_with?("<?xml")
|
||||||
str << <<-END_VTT
|
webvtt = caption.timedtext_to_vtt(caption_xml, tlang)
|
||||||
WEBVTT
|
else
|
||||||
Kind: captions
|
caption_xml = XML.parse(caption_xml)
|
||||||
Language: #{tlang || caption.language_code}
|
|
||||||
|
webvtt = String.build do |str|
|
||||||
|
str << <<-END_VTT
|
||||||
|
WEBVTT
|
||||||
|
Kind: captions
|
||||||
|
Language: #{tlang || caption.language_code}
|
||||||
|
|
||||||
|
|
||||||
END_VTT
|
END_VTT
|
||||||
|
|
||||||
caption_nodes = caption_xml.xpath_nodes("//transcript/text")
|
caption_nodes = caption_xml.xpath_nodes("//transcript/text")
|
||||||
caption_nodes.each_with_index do |node, i|
|
caption_nodes.each_with_index do |node, i|
|
||||||
start_time = node["start"].to_f.seconds
|
start_time = node["start"].to_f.seconds
|
||||||
duration = node["dur"]?.try &.to_f.seconds
|
duration = node["dur"]?.try &.to_f.seconds
|
||||||
duration ||= start_time
|
duration ||= start_time
|
||||||
|
|
||||||
if caption_nodes.size > i + 1
|
if caption_nodes.size > i + 1
|
||||||
end_time = caption_nodes[i + 1]["start"].to_f.seconds
|
end_time = caption_nodes[i + 1]["start"].to_f.seconds
|
||||||
else
|
else
|
||||||
end_time = start_time + duration
|
end_time = start_time + duration
|
||||||
|
end
|
||||||
|
|
||||||
|
start_time = "#{start_time.hours.to_s.rjust(2, '0')}:#{start_time.minutes.to_s.rjust(2, '0')}:#{start_time.seconds.to_s.rjust(2, '0')}.#{start_time.milliseconds.to_s.rjust(3, '0')}"
|
||||||
|
end_time = "#{end_time.hours.to_s.rjust(2, '0')}:#{end_time.minutes.to_s.rjust(2, '0')}:#{end_time.seconds.to_s.rjust(2, '0')}.#{end_time.milliseconds.to_s.rjust(3, '0')}"
|
||||||
|
|
||||||
|
text = HTML.unescape(node.content)
|
||||||
|
text = text.gsub(/<font color="#[a-fA-F0-9]{6}">/, "")
|
||||||
|
text = text.gsub(/<\/font>/, "")
|
||||||
|
if md = text.match(/(?<name>.*) : (?<text>.*)/)
|
||||||
|
text = "<v #{md["name"]}>#{md["text"]}</v>"
|
||||||
|
end
|
||||||
|
|
||||||
|
str << <<-END_CUE
|
||||||
|
#{start_time} --> #{end_time}
|
||||||
|
#{text}
|
||||||
|
|
||||||
|
|
||||||
|
END_CUE
|
||||||
end
|
end
|
||||||
|
|
||||||
start_time = "#{start_time.hours.to_s.rjust(2, '0')}:#{start_time.minutes.to_s.rjust(2, '0')}:#{start_time.seconds.to_s.rjust(2, '0')}.#{start_time.milliseconds.to_s.rjust(3, '0')}"
|
|
||||||
end_time = "#{end_time.hours.to_s.rjust(2, '0')}:#{end_time.minutes.to_s.rjust(2, '0')}:#{end_time.seconds.to_s.rjust(2, '0')}.#{end_time.milliseconds.to_s.rjust(3, '0')}"
|
|
||||||
|
|
||||||
text = HTML.unescape(node.content)
|
|
||||||
text = text.gsub(/<font color="#[a-fA-F0-9]{6}">/, "")
|
|
||||||
text = text.gsub(/<\/font>/, "")
|
|
||||||
if md = text.match(/(?<name>.*) : (?<text>.*)/)
|
|
||||||
text = "<v #{md["name"]}>#{md["text"]}</v>"
|
|
||||||
end
|
|
||||||
|
|
||||||
str << <<-END_CUE
|
|
||||||
#{start_time} --> #{end_time}
|
|
||||||
#{text}
|
|
||||||
|
|
||||||
|
|
||||||
END_CUE
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -138,7 +146,12 @@ module Invidious::Routes::API::V1::Videos
|
||||||
#
|
#
|
||||||
# See: https://github.com/iv-org/invidious/issues/2391
|
# See: https://github.com/iv-org/invidious/issues/2391
|
||||||
webvtt = YT_POOL.client &.get("#{url}&format=vtt").body
|
webvtt = YT_POOL.client &.get("#{url}&format=vtt").body
|
||||||
.gsub(/([0-9:.]{12} --> [0-9:.]{12}).+/, "\\1")
|
if webvtt.starts_with?("<?xml")
|
||||||
|
webvtt = caption.timedtext_to_vtt(webvtt)
|
||||||
|
else
|
||||||
|
webvtt = YT_POOL.client &.get("#{url}&format=vtt").body
|
||||||
|
.gsub(/([0-9:.]{12} --> [0-9:.]{12}).+/, "\\1")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if title = env.params.query["title"]?
|
if title = env.params.query["title"]?
|
||||||
|
|
|
@ -7,21 +7,19 @@ module Invidious::Routes::Channels
|
||||||
|
|
||||||
def self.videos(env)
|
def self.videos(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = self.fetch_basic_information(env)
|
||||||
if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
return data
|
|
||||||
end
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
|
||||||
|
|
||||||
page = env.params.query["page"]?.try &.to_i?
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
page ||= 1
|
|
||||||
|
|
||||||
sort_by = env.params.query["sort_by"]?.try &.downcase
|
sort_by = env.params.query["sort_by"]?.try &.downcase
|
||||||
|
|
||||||
if channel.auto_generated
|
if channel.auto_generated
|
||||||
sort_options = {"last", "oldest", "newest"}
|
sort_options = {"last", "oldest", "newest"}
|
||||||
sort_by ||= "last"
|
|
||||||
|
|
||||||
items, continuation = fetch_channel_playlists(channel.ucid, channel.author, continuation, sort_by)
|
items, next_continuation = fetch_channel_playlists(
|
||||||
|
channel.ucid, channel.author, continuation, (sort_by || "last")
|
||||||
|
)
|
||||||
|
|
||||||
items.uniq! do |item|
|
items.uniq! do |item|
|
||||||
if item.responds_to?(:title)
|
if item.responds_to?(:title)
|
||||||
item.title
|
item.title
|
||||||
|
@ -33,34 +31,85 @@ module Invidious::Routes::Channels
|
||||||
items.each(&.author = "")
|
items.each(&.author = "")
|
||||||
else
|
else
|
||||||
sort_options = {"newest", "oldest", "popular"}
|
sort_options = {"newest", "oldest", "popular"}
|
||||||
sort_by ||= "newest"
|
|
||||||
|
|
||||||
count, items = get_60_videos(channel.ucid, channel.author, page, channel.auto_generated, sort_by)
|
# Fetch items and continuation token
|
||||||
|
items, next_continuation = Channel::Tabs.get_videos(
|
||||||
|
channel, continuation: continuation, sort_by: (sort_by || "newest")
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
selected_tab = Frontend::ChannelPage::TabsAvailable::Videos
|
||||||
|
templated "channel"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.shorts(env)
|
||||||
|
data = self.fetch_basic_information(env)
|
||||||
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
|
|
||||||
|
if !channel.tabs.includes? "shorts"
|
||||||
|
return env.redirect "/channel/#{channel.ucid}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: support sort option for shorts
|
||||||
|
sort_by = ""
|
||||||
|
sort_options = [] of String
|
||||||
|
|
||||||
|
# Fetch items and continuation token
|
||||||
|
items, next_continuation = Channel::Tabs.get_shorts(
|
||||||
|
channel, continuation: continuation
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_tab = Frontend::ChannelPage::TabsAvailable::Shorts
|
||||||
|
templated "channel"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.streams(env)
|
||||||
|
data = self.fetch_basic_information(env)
|
||||||
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
|
|
||||||
|
if !channel.tabs.includes? "streams"
|
||||||
|
return env.redirect "/channel/#{channel.ucid}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: support sort option for livestreams
|
||||||
|
sort_by = ""
|
||||||
|
sort_options = [] of String
|
||||||
|
|
||||||
|
# Fetch items and continuation token
|
||||||
|
items, next_continuation = Channel::Tabs.get_60_livestreams(
|
||||||
|
channel, continuation: continuation
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_tab = Frontend::ChannelPage::TabsAvailable::Streams
|
||||||
templated "channel"
|
templated "channel"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.playlists(env)
|
def self.playlists(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = self.fetch_basic_information(env)
|
||||||
if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
return data
|
|
||||||
end
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
|
|
||||||
sort_options = {"last", "oldest", "newest"}
|
sort_options = {"last", "oldest", "newest"}
|
||||||
sort_by = env.params.query["sort_by"]?.try &.downcase
|
sort_by = env.params.query["sort_by"]?.try &.downcase
|
||||||
sort_by ||= "last"
|
|
||||||
|
|
||||||
if channel.auto_generated
|
if channel.auto_generated
|
||||||
return env.redirect "/channel/#{channel.ucid}"
|
return env.redirect "/channel/#{channel.ucid}"
|
||||||
end
|
end
|
||||||
|
|
||||||
items, continuation = fetch_channel_playlists(channel.ucid, channel.author, continuation, sort_by)
|
items, next_continuation = fetch_channel_playlists(
|
||||||
|
channel.ucid, channel.author, continuation, (sort_by || "last")
|
||||||
|
)
|
||||||
|
|
||||||
items = items.select(SearchPlaylist).map(&.as(SearchPlaylist))
|
items = items.select(SearchPlaylist).map(&.as(SearchPlaylist))
|
||||||
items.each(&.author = "")
|
items.each(&.author = "")
|
||||||
|
|
||||||
templated "playlists"
|
selected_tab = Frontend::ChannelPage::TabsAvailable::Playlists
|
||||||
|
templated "channel"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.community(env)
|
def self.community(env)
|
||||||
|
@ -74,12 +123,15 @@ module Invidious::Routes::Channels
|
||||||
thin_mode = thin_mode == "true"
|
thin_mode = thin_mode == "true"
|
||||||
|
|
||||||
continuation = env.params.query["continuation"]?
|
continuation = env.params.query["continuation"]?
|
||||||
# sort_by = env.params.query["sort_by"]?.try &.downcase
|
|
||||||
|
|
||||||
if !channel.tabs.includes? "community"
|
if !channel.tabs.includes? "community"
|
||||||
return env.redirect "/channel/#{channel.ucid}"
|
return env.redirect "/channel/#{channel.ucid}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: support sort options for community posts
|
||||||
|
sort_by = ""
|
||||||
|
sort_options = [] of String
|
||||||
|
|
||||||
begin
|
begin
|
||||||
items = JSON.parse(fetch_channel_community(ucid, continuation, locale, "json", thin_mode))
|
items = JSON.parse(fetch_channel_community(ucid, continuation, locale, "json", thin_mode))
|
||||||
rescue ex : InfoException
|
rescue ex : InfoException
|
||||||
|
@ -95,6 +147,26 @@ module Invidious::Routes::Channels
|
||||||
templated "community"
|
templated "community"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.channels(env)
|
||||||
|
data = self.fetch_basic_information(env)
|
||||||
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
|
|
||||||
|
if channel.auto_generated
|
||||||
|
return env.redirect "/channel/#{channel.ucid}"
|
||||||
|
end
|
||||||
|
|
||||||
|
items, next_continuation = fetch_related_channels(channel, continuation)
|
||||||
|
|
||||||
|
# Featured/related channels can't be sorted
|
||||||
|
sort_options = [] of String
|
||||||
|
sort_by = nil
|
||||||
|
|
||||||
|
selected_tab = Frontend::ChannelPage::TabsAvailable::Channels
|
||||||
|
templated "channel"
|
||||||
|
end
|
||||||
|
|
||||||
def self.about(env)
|
def self.about(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = self.fetch_basic_information(env)
|
||||||
if !data.is_a?(Tuple)
|
if !data.is_a?(Tuple)
|
||||||
|
@ -125,7 +197,7 @@ module Invidious::Routes::Channels
|
||||||
end
|
end
|
||||||
|
|
||||||
selected_tab = env.request.path.split("/")[-1]
|
selected_tab = env.request.path.split("/")[-1]
|
||||||
if ["home", "videos", "playlists", "community", "channels", "about"].includes? selected_tab
|
if {"home", "videos", "shorts", "streams", "playlists", "community", "channels", "about"}.includes? selected_tab
|
||||||
url = "/channel/#{ucid}/#{selected_tab}"
|
url = "/channel/#{ucid}/#{selected_tab}"
|
||||||
else
|
else
|
||||||
url = "/channel/#{ucid}"
|
url = "/channel/#{ucid}"
|
||||||
|
|
|
@ -147,7 +147,7 @@ module Invidious::Routes::Embed
|
||||||
# PG_DB.exec("UPDATE users SET watched = array_append(watched, $1) WHERE email = $2", id, user.as(User).email)
|
# PG_DB.exec("UPDATE users SET watched = array_append(watched, $1) WHERE email = $2", id, user.as(User).email)
|
||||||
# end
|
# end
|
||||||
|
|
||||||
if notifications && notifications.includes? id
|
if CONFIG.enable_user_notifications && notifications && notifications.includes? id
|
||||||
Invidious::Database::Users.remove_notification(user.as(User), id)
|
Invidious::Database::Users.remove_notification(user.as(User), id)
|
||||||
env.get("user").as(User).notifications.delete(id)
|
env.get("user").as(User).notifications.delete(id)
|
||||||
notifications.delete(id)
|
notifications.delete(id)
|
||||||
|
|
|
@ -96,12 +96,14 @@ module Invidious::Routes::Feeds
|
||||||
|
|
||||||
videos, notifications = get_subscription_feed(user, max_results, page)
|
videos, notifications = get_subscription_feed(user, max_results, page)
|
||||||
|
|
||||||
# "updated" here is used for delivering new notifications, so if
|
if CONFIG.enable_user_notifications
|
||||||
# we know a user has looked at their feed e.g. in the past 10 minutes,
|
# "updated" here is used for delivering new notifications, so if
|
||||||
# they've already seen a video posted 20 minutes ago, and don't need
|
# we know a user has looked at their feed e.g. in the past 10 minutes,
|
||||||
# to be notified.
|
# they've already seen a video posted 20 minutes ago, and don't need
|
||||||
Invidious::Database::Users.clear_notifications(user)
|
# to be notified.
|
||||||
user.notifications = [] of String
|
Invidious::Database::Users.clear_notifications(user)
|
||||||
|
user.notifications = [] of String
|
||||||
|
end
|
||||||
env.set "user", user
|
env.set "user", user
|
||||||
|
|
||||||
templated "feeds/subscriptions"
|
templated "feeds/subscriptions"
|
||||||
|
@ -404,13 +406,15 @@ module Invidious::Routes::Feeds
|
||||||
|
|
||||||
video = get_video(id, force_refresh: true)
|
video = get_video(id, force_refresh: true)
|
||||||
|
|
||||||
# Deliver notifications to `/api/v1/auth/notifications`
|
if CONFIG.enable_user_notifications
|
||||||
payload = {
|
# Deliver notifications to `/api/v1/auth/notifications`
|
||||||
"topic" => video.ucid,
|
payload = {
|
||||||
"videoId" => video.id,
|
"topic" => video.ucid,
|
||||||
"published" => published.to_unix,
|
"videoId" => video.id,
|
||||||
}.to_json
|
"published" => published.to_unix,
|
||||||
PG_DB.exec("NOTIFY notifications, E'#{payload}'")
|
}.to_json
|
||||||
|
PG_DB.exec("NOTIFY notifications, E'#{payload}'")
|
||||||
|
end
|
||||||
|
|
||||||
video = ChannelVideo.new({
|
video = ChannelVideo.new({
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -426,7 +430,13 @@ module Invidious::Routes::Feeds
|
||||||
})
|
})
|
||||||
|
|
||||||
was_insert = Invidious::Database::ChannelVideos.insert(video, with_premiere_timestamp: true)
|
was_insert = Invidious::Database::ChannelVideos.insert(video, with_premiere_timestamp: true)
|
||||||
Invidious::Database::Users.add_notification(video) if was_insert
|
if was_insert
|
||||||
|
if CONFIG.enable_user_notifications
|
||||||
|
Invidious::Database::Users.add_notification(video)
|
||||||
|
else
|
||||||
|
Invidious::Database::Users.feed_needs_update(video)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@ module Invidious::Routes::Login
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
|
|
||||||
return env.redirect "/feed/subscriptions" if user
|
referer = get_referer(env, "/feed/subscriptions")
|
||||||
|
|
||||||
|
return env.redirect referer if user
|
||||||
|
|
||||||
if !CONFIG.login_enabled
|
if !CONFIG.login_enabled
|
||||||
return error_template(400, "Login has been disabled by administrator.")
|
return error_template(400, "Login has been disabled by administrator.")
|
||||||
end
|
end
|
||||||
|
|
||||||
referer = get_referer(env, "/feed/subscriptions")
|
|
||||||
|
|
||||||
email = nil
|
email = nil
|
||||||
password = nil
|
password = nil
|
||||||
captcha = nil
|
captcha = nil
|
||||||
|
|
|
@ -35,6 +35,13 @@ module Invidious::Routes::VideoPlayback
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# See: https://github.com/iv-org/invidious/issues/3302
|
||||||
|
range_header = env.request.headers["Range"]?
|
||||||
|
if range_header.nil?
|
||||||
|
range_for_head = query_params["range"]? || "0-640"
|
||||||
|
headers["Range"] = "bytes=#{range_for_head}"
|
||||||
|
end
|
||||||
|
|
||||||
client = make_client(URI.parse(host), region)
|
client = make_client(URI.parse(host), region)
|
||||||
response = HTTP::Client::Response.new(500)
|
response = HTTP::Client::Response.new(500)
|
||||||
error = ""
|
error = ""
|
||||||
|
@ -70,6 +77,9 @@ module Invidious::Routes::VideoPlayback
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Remove the Range header added previously.
|
||||||
|
headers.delete("Range") if range_header.nil?
|
||||||
|
|
||||||
if response.status_code >= 400
|
if response.status_code >= 400
|
||||||
env.response.content_type = "text/plain"
|
env.response.content_type = "text/plain"
|
||||||
haltf env, response.status_code
|
haltf env, response.status_code
|
||||||
|
@ -91,14 +101,8 @@ module Invidious::Routes::VideoPlayback
|
||||||
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
|
||||||
if location = resp.headers["Location"]?
|
if location = resp.headers["Location"]?
|
||||||
location = URI.parse(location)
|
url = Invidious::HttpServer::Utils.proxy_video_url(location, region: region)
|
||||||
location = "#{location.request_target}&host=#{location.host}"
|
return env.redirect url
|
||||||
|
|
||||||
if region
|
|
||||||
location += "®ion=#{region}"
|
|
||||||
end
|
|
||||||
|
|
||||||
return env.redirect location
|
|
||||||
end
|
end
|
||||||
|
|
||||||
IO.copy(resp.body_io, env.response)
|
IO.copy(resp.body_io, env.response)
|
||||||
|
|
|
@ -80,7 +80,7 @@ module Invidious::Routes::Watch
|
||||||
Invidious::Database::Users.mark_watched(user.as(User), id)
|
Invidious::Database::Users.mark_watched(user.as(User), id)
|
||||||
end
|
end
|
||||||
|
|
||||||
if notifications && notifications.includes? id
|
if CONFIG.enable_user_notifications && notifications && notifications.includes? id
|
||||||
Invidious::Database::Users.remove_notification(user.as(User), id)
|
Invidious::Database::Users.remove_notification(user.as(User), id)
|
||||||
env.get("user").as(User).notifications.delete(id)
|
env.get("user").as(User).notifications.delete(id)
|
||||||
notifications.delete(id)
|
notifications.delete(id)
|
||||||
|
|
|
@ -37,7 +37,9 @@ module Invidious::Routing
|
||||||
get "/feed/webhook/:token", Routes::Feeds, :push_notifications_get
|
get "/feed/webhook/:token", Routes::Feeds, :push_notifications_get
|
||||||
post "/feed/webhook/:token", Routes::Feeds, :push_notifications_post
|
post "/feed/webhook/:token", Routes::Feeds, :push_notifications_post
|
||||||
|
|
||||||
get "/modify_notifications", Routes::Notifications, :modify
|
if CONFIG.enable_user_notifications
|
||||||
|
get "/modify_notifications", Routes::Notifications, :modify
|
||||||
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
self.register_image_routes
|
self.register_image_routes
|
||||||
|
@ -115,18 +117,23 @@ module Invidious::Routing
|
||||||
get "/channel/:ucid", Routes::Channels, :home
|
get "/channel/:ucid", Routes::Channels, :home
|
||||||
get "/channel/:ucid/home", Routes::Channels, :home
|
get "/channel/:ucid/home", Routes::Channels, :home
|
||||||
get "/channel/:ucid/videos", Routes::Channels, :videos
|
get "/channel/:ucid/videos", Routes::Channels, :videos
|
||||||
|
get "/channel/:ucid/shorts", Routes::Channels, :shorts
|
||||||
|
get "/channel/:ucid/streams", Routes::Channels, :streams
|
||||||
get "/channel/:ucid/playlists", Routes::Channels, :playlists
|
get "/channel/:ucid/playlists", Routes::Channels, :playlists
|
||||||
get "/channel/:ucid/community", Routes::Channels, :community
|
get "/channel/:ucid/community", Routes::Channels, :community
|
||||||
|
get "/channel/:ucid/channels", Routes::Channels, :channels
|
||||||
get "/channel/:ucid/about", Routes::Channels, :about
|
get "/channel/:ucid/about", Routes::Channels, :about
|
||||||
get "/channel/:ucid/live", Routes::Channels, :live
|
get "/channel/:ucid/live", Routes::Channels, :live
|
||||||
get "/user/:user/live", Routes::Channels, :live
|
get "/user/:user/live", Routes::Channels, :live
|
||||||
get "/c/:user/live", Routes::Channels, :live
|
get "/c/:user/live", Routes::Channels, :live
|
||||||
|
|
||||||
["", "/videos", "/playlists", "/community", "/about"].each do |path|
|
{"", "/videos", "/shorts", "/streams", "/playlists", "/community", "/about"}.each do |path|
|
||||||
# /c/LinusTechTips
|
# /c/LinusTechTips
|
||||||
get "/c/:user#{path}", Routes::Channels, :brand_redirect
|
get "/c/:user#{path}", Routes::Channels, :brand_redirect
|
||||||
# /user/linustechtips | Not always the same as /c/
|
# /user/linustechtips | Not always the same as /c/
|
||||||
get "/user/:user#{path}", Routes::Channels, :brand_redirect
|
get "/user/:user#{path}", Routes::Channels, :brand_redirect
|
||||||
|
# /@LinusTechTips | Handle
|
||||||
|
get "/@:user#{path}", Routes::Channels, :brand_redirect
|
||||||
# /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow
|
# /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow
|
||||||
get "/attribution_link#{path}", Routes::Channels, :brand_redirect
|
get "/attribution_link#{path}", Routes::Channels, :brand_redirect
|
||||||
# /profile?user=linustechtips
|
# /profile?user=linustechtips
|
||||||
|
@ -220,6 +227,10 @@ module Invidious::Routing
|
||||||
|
|
||||||
# Channels
|
# Channels
|
||||||
get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home
|
get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home
|
||||||
|
get "/api/v1/channels/:ucid/shorts", {{namespace}}::Channels, :shorts
|
||||||
|
get "/api/v1/channels/:ucid/streams", {{namespace}}::Channels, :streams
|
||||||
|
get "/api/v1/channels/:ucid/channels", {{namespace}}::Channels, :channels
|
||||||
|
|
||||||
{% for route in {"videos", "latest", "playlists", "community", "search"} %}
|
{% for route in {"videos", "latest", "playlists", "community", "search"} %}
|
||||||
get "/api/v1/channels/#{{{route}}}/:ucid", {{namespace}}::Channels, :{{route}}
|
get "/api/v1/channels/#{{{route}}}/:ucid", {{namespace}}::Channels, :{{route}}
|
||||||
get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}}
|
get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}}
|
||||||
|
@ -260,8 +271,10 @@ module Invidious::Routing
|
||||||
post "/api/v1/auth/tokens/register", {{namespace}}::Authenticated, :register_token
|
post "/api/v1/auth/tokens/register", {{namespace}}::Authenticated, :register_token
|
||||||
post "/api/v1/auth/tokens/unregister", {{namespace}}::Authenticated, :unregister_token
|
post "/api/v1/auth/tokens/unregister", {{namespace}}::Authenticated, :unregister_token
|
||||||
|
|
||||||
get "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
|
if CONFIG.enable_user_notifications
|
||||||
post "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
|
get "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
|
||||||
|
post "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
|
||||||
|
end
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
get "/api/v1/stats", {{namespace}}::Misc, :stats
|
get "/api/v1/stats", {{namespace}}::Misc, :stats
|
||||||
|
|
|
@ -9,7 +9,8 @@ module Invidious::Search
|
||||||
client_config = YoutubeAPI::ClientConfig.new(region: query.region)
|
client_config = YoutubeAPI::ClientConfig.new(region: query.region)
|
||||||
initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config)
|
initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config)
|
||||||
|
|
||||||
return extract_items(initial_data)
|
items, _ = extract_items(initial_data)
|
||||||
|
return items
|
||||||
end
|
end
|
||||||
|
|
||||||
# Search a youtube channel
|
# Search a youtube channel
|
||||||
|
@ -30,16 +31,7 @@ module Invidious::Search
|
||||||
continuation = produce_channel_search_continuation(ucid, query.text, query.page)
|
continuation = produce_channel_search_continuation(ucid, query.text, query.page)
|
||||||
response_json = YoutubeAPI.browse(continuation)
|
response_json = YoutubeAPI.browse(continuation)
|
||||||
|
|
||||||
continuation_items = response_json["onResponseReceivedActions"]?
|
items, _ = extract_items(response_json, "", ucid)
|
||||||
.try &.[0]["appendContinuationItemsAction"]["continuationItems"]
|
|
||||||
|
|
||||||
return [] of SearchItem if !continuation_items
|
|
||||||
|
|
||||||
items = [] of SearchItem
|
|
||||||
continuation_items.as_a.select(&.as_h.has_key?("itemSectionRenderer")).each do |item|
|
|
||||||
extract_item(item["itemSectionRenderer"]["contents"].as_a[0]).try { |t| items << t }
|
|
||||||
end
|
|
||||||
|
|
||||||
return items
|
return items
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -247,6 +247,12 @@ struct Video
|
||||||
info["reason"]?.try &.as_s
|
info["reason"]?.try &.as_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def music : Array(VideoMusic)
|
||||||
|
info["music"].as_a.map { |music_json|
|
||||||
|
VideoMusic.new(music_json["album"].as_s, music_json["artist"].as_s, music_json["license"].as_s)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
# Macros defining getters/setters for various types of data
|
# Macros defining getters/setters for various types of data
|
||||||
|
|
||||||
private macro getset_string(name)
|
private macro getset_string(name)
|
||||||
|
|
|
@ -31,6 +31,72 @@ module Invidious::Videos
|
||||||
return captions_list
|
return captions_list
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def timedtext_to_vtt(timedtext : String, tlang = nil) : String
|
||||||
|
# In the future, we could just directly work with the url. This is more of a POC
|
||||||
|
cues = [] of XML::Node
|
||||||
|
tree = XML.parse(timedtext)
|
||||||
|
tree = tree.children.first
|
||||||
|
|
||||||
|
tree.children.each do |item|
|
||||||
|
if item.name == "body"
|
||||||
|
item.children.each do |cue|
|
||||||
|
if cue.name == "p" && !(cue.children.size == 1 && cue.children[0].content == "\n")
|
||||||
|
cues << cue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result = String.build do |result|
|
||||||
|
result << <<-END_VTT
|
||||||
|
WEBVTT
|
||||||
|
Kind: captions
|
||||||
|
Language: #{tlang || @language_code}
|
||||||
|
|
||||||
|
|
||||||
|
END_VTT
|
||||||
|
|
||||||
|
result << "\n\n"
|
||||||
|
|
||||||
|
cues.each_with_index do |node, i|
|
||||||
|
start_time = node["t"].to_f.milliseconds
|
||||||
|
|
||||||
|
duration = node["d"]?.try &.to_f.milliseconds
|
||||||
|
|
||||||
|
duration ||= start_time
|
||||||
|
|
||||||
|
if cues.size > i + 1
|
||||||
|
end_time = cues[i + 1]["t"].to_f.milliseconds
|
||||||
|
else
|
||||||
|
end_time = start_time + duration
|
||||||
|
end
|
||||||
|
|
||||||
|
# start_time
|
||||||
|
result << start_time.hours.to_s.rjust(2, '0')
|
||||||
|
result << ':' << start_time.minutes.to_s.rjust(2, '0')
|
||||||
|
result << ':' << start_time.seconds.to_s.rjust(2, '0')
|
||||||
|
result << '.' << start_time.milliseconds.to_s.rjust(3, '0')
|
||||||
|
|
||||||
|
result << " --> "
|
||||||
|
|
||||||
|
# end_time
|
||||||
|
result << end_time.hours.to_s.rjust(2, '0')
|
||||||
|
result << ':' << end_time.minutes.to_s.rjust(2, '0')
|
||||||
|
result << ':' << end_time.seconds.to_s.rjust(2, '0')
|
||||||
|
result << '.' << end_time.milliseconds.to_s.rjust(3, '0')
|
||||||
|
|
||||||
|
result << "\n"
|
||||||
|
|
||||||
|
node.children.each do |s|
|
||||||
|
result << s.content
|
||||||
|
end
|
||||||
|
result << "\n"
|
||||||
|
result << "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
# List of all caption languages available on Youtube.
|
# List of all caption languages available on Youtube.
|
||||||
LANGUAGES = {
|
LANGUAGES = {
|
||||||
"",
|
"",
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
struct VideoMusic
|
||||||
|
include JSON::Serializable
|
||||||
|
|
||||||
|
property album : String
|
||||||
|
property artist : String
|
||||||
|
property license : String
|
||||||
|
|
||||||
|
def initialize(@album : String, @artist : String, @license : String)
|
||||||
|
end
|
||||||
|
end
|
|
@ -66,8 +66,10 @@ def extract_video_info(video_id : String, proxy_region : String? = nil)
|
||||||
reason ||= subreason.try &.[]("runs").as_a.map(&.[]("text")).join("")
|
reason ||= subreason.try &.[]("runs").as_a.map(&.[]("text")).join("")
|
||||||
reason ||= player_response.dig("playabilityStatus", "reason").as_s
|
reason ||= player_response.dig("playabilityStatus", "reason").as_s
|
||||||
|
|
||||||
# Stop here if video is not a scheduled livestream
|
# Stop here if video is not a scheduled livestream or
|
||||||
if !{"LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status)
|
# for LOGIN_REQUIRED when videoDetails element is not found because retrying won't help
|
||||||
|
if !{"LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) ||
|
||||||
|
playability_status == "LOGIN_REQUIRED" && !player_response.dig?("videoDetails")
|
||||||
return {
|
return {
|
||||||
"version" => JSON::Any.new(Video::SCHEMA_VERSION.to_i64),
|
"version" => JSON::Any.new(Video::SCHEMA_VERSION.to_i64),
|
||||||
"reason" => JSON::Any.new(reason),
|
"reason" => JSON::Any.new(reason),
|
||||||
|
@ -309,6 +311,33 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Music section
|
||||||
|
|
||||||
|
music_list = [] of VideoMusic
|
||||||
|
music_desclist = player_response.dig?(
|
||||||
|
"engagementPanels", 1, "engagementPanelSectionListRenderer",
|
||||||
|
"content", "structuredDescriptionContentRenderer", "items", 2,
|
||||||
|
"videoDescriptionMusicSectionRenderer", "carouselLockups"
|
||||||
|
)
|
||||||
|
|
||||||
|
music_desclist.try &.as_a.each do |music_desc|
|
||||||
|
artist = nil
|
||||||
|
album = nil
|
||||||
|
music_license = nil
|
||||||
|
|
||||||
|
music_desc.dig?("carouselLockupRenderer", "infoRows").try &.as_a.each do |desc|
|
||||||
|
desc_title = extract_text(desc.dig?("infoRowRenderer", "title"))
|
||||||
|
if desc_title == "ARTIST"
|
||||||
|
artist = extract_text(desc.dig?("infoRowRenderer", "defaultMetadata"))
|
||||||
|
elsif desc_title == "ALBUM"
|
||||||
|
album = extract_text(desc.dig?("infoRowRenderer", "defaultMetadata"))
|
||||||
|
elsif desc_title == "LICENSES"
|
||||||
|
music_license = extract_text(desc.dig?("infoRowRenderer", "expandedMetadata"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
music_list << VideoMusic.new(album.to_s, artist.to_s, music_license.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
# Author infos
|
# Author infos
|
||||||
|
|
||||||
author = video_details["author"]?.try &.as_s
|
author = video_details["author"]?.try &.as_s
|
||||||
|
@ -359,6 +388,8 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
|
||||||
"genre" => JSON::Any.new(genre.try &.as_s || ""),
|
"genre" => JSON::Any.new(genre.try &.as_s || ""),
|
||||||
"genreUcid" => JSON::Any.new(genre_ucid.try &.as_s || ""),
|
"genreUcid" => JSON::Any.new(genre_ucid.try &.as_s || ""),
|
||||||
"license" => JSON::Any.new(license.try &.as_s || ""),
|
"license" => JSON::Any.new(license.try &.as_s || ""),
|
||||||
|
# Music section
|
||||||
|
"music" => JSON.parse(music_list.to_json),
|
||||||
# Author infos
|
# Author infos
|
||||||
"author" => JSON::Any.new(author || ""),
|
"author" => JSON::Any.new(author || ""),
|
||||||
"ucid" => JSON::Any.new(ucid || ""),
|
"ucid" => JSON::Any.new(ucid || ""),
|
||||||
|
|
|
@ -1,8 +1,24 @@
|
||||||
<% ucid = channel.ucid %>
|
<%-
|
||||||
<% author = HTML.escape(channel.author) %>
|
ucid = channel.ucid
|
||||||
<% channel_profile_pic = URI.parse(channel.author_thumbnail).request_target %>
|
author = HTML.escape(channel.author)
|
||||||
|
channel_profile_pic = URI.parse(channel.author_thumbnail).request_target
|
||||||
|
|
||||||
|
relative_url =
|
||||||
|
case selected_tab
|
||||||
|
when .shorts? then "/channel/#{ucid}/shorts"
|
||||||
|
when .streams? then "/channel/#{ucid}/streams"
|
||||||
|
when .playlists? then "/channel/#{ucid}/playlists"
|
||||||
|
when .channels? then "/channel/#{ucid}/channels"
|
||||||
|
else
|
||||||
|
"/channel/#{ucid}"
|
||||||
|
end
|
||||||
|
|
||||||
|
youtube_url = "https://www.youtube.com#{relative_url}"
|
||||||
|
redirect_url = Invidious::Frontend::Misc.redirect_url(env)
|
||||||
|
-%>
|
||||||
|
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
|
<%- if selected_tab.videos? -%>
|
||||||
<meta name="description" content="<%= channel.description %>">
|
<meta name="description" content="<%= channel.description %>">
|
||||||
<meta property="og:site_name" content="Invidious">
|
<meta property="og:site_name" content="Invidious">
|
||||||
<meta property="og:url" content="<%= HOST_URL %>/channel/<%= ucid %>">
|
<meta property="og:url" content="<%= HOST_URL %>/channel/<%= ucid %>">
|
||||||
|
@ -14,91 +30,14 @@
|
||||||
<meta name="twitter:title" content="<%= author %>">
|
<meta name="twitter:title" content="<%= author %>">
|
||||||
<meta name="twitter:description" content="<%= channel.description %>">
|
<meta name="twitter:description" content="<%= channel.description %>">
|
||||||
<meta name="twitter:image" content="/ggpht<%= channel_profile_pic %>">
|
<meta name="twitter:image" content="/ggpht<%= channel_profile_pic %>">
|
||||||
<link rel="alternate" href="https://www.youtube.com/channel/<%= ucid %>">
|
|
||||||
<title><%= author %> - Invidious</title>
|
|
||||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/channel/<%= ucid %>" />
|
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/channel/<%= ucid %>" />
|
||||||
|
<%- end -%>
|
||||||
|
|
||||||
|
<link rel="alternate" href="<%= youtube_url %>">
|
||||||
|
<title><%= author %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if channel.banner %>
|
<%= rendered "components/channel_info" %>
|
||||||
<div class="h-box">
|
|
||||||
<img style="width:100%" src="/ggpht<%= URI.parse(channel.banner.not_nil!.gsub("=w1060-", "=w1280-")).request_target %>">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
|
||||||
<div class="pure-u-2-3">
|
|
||||||
<div class="channel-profile">
|
|
||||||
<img src="/ggpht<%= channel_profile_pic %>">
|
|
||||||
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<h3 style="text-align:right">
|
|
||||||
<a href="/feed/channel/<%= ucid %>"><i class="icon ion-logo-rss"></i></a>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<div id="descriptionWrapper">
|
|
||||||
<p><span style="white-space:pre-wrap"><%= channel.description_html %></span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
|
||||||
<%= rendered "components/subscribe_widget" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<a href="https://www.youtube.com/channel/<%= ucid %>"><%= translate(locale, "View channel on YouTube") %></a>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
|
||||||
<a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
|
||||||
<% else %>
|
|
||||||
<a href="https://redirect.invidious.io<%= env.request.path %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% if !channel.auto_generated %>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<b><%= translate(locale, "Videos") %></b>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if channel.auto_generated %>
|
|
||||||
<b><%= translate(locale, "Playlists") %></b>
|
|
||||||
<% else %>
|
|
||||||
<a href="/channel/<%= ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if channel.tabs.includes? "community" %>
|
|
||||||
<a href="/channel/<%= ucid %>/community"><%= translate(locale, "Community") %></a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-3"></div>
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<div class="pure-g" style="text-align:right">
|
|
||||||
<% sort_options.each do |sort| %>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if sort_by == sort %>
|
|
||||||
<b><%= translate(locale, sort) %></b>
|
|
||||||
<% else %>
|
|
||||||
<a href="/channel/<%= ucid %>?page=<%= page %>&sort_by=<%= sort %>">
|
|
||||||
<%= translate(locale, sort) %>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -111,17 +50,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-lg-1-5">
|
<div class="pure-u-1 pure-u-md-4-5"></div>
|
||||||
<% if page > 1 %>
|
|
||||||
<a href="/channel/<%= ucid %>?page=<%= page - 1 %><% if sort_by != "newest" %>&sort_by=<%= URI.encode_www_form(sort_by) %><% end %>">
|
|
||||||
<%= translate(locale, "Previous page") %>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
|
||||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||||
<% if count == 60 %>
|
<% if next_continuation %>
|
||||||
<a href="/channel/<%= ucid %>?page=<%= page + 1 %><% if sort_by != "newest" %>&sort_by=<%= URI.encode_www_form(sort_by) %><% end %>">
|
<a href="<%= relative_url %>?continuation=<%= next_continuation %><% if sort_options.any? sort_by %>&sort_by=<%= sort_by %><% end %>">
|
||||||
<%= translate(locale, "Next page") %>
|
<%= translate(locale, "Next page") %>
|
||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,71 +1,21 @@
|
||||||
<% ucid = channel.ucid %>
|
<%-
|
||||||
<% author = HTML.escape(channel.author) %>
|
ucid = channel.ucid
|
||||||
|
author = HTML.escape(channel.author)
|
||||||
|
channel_profile_pic = URI.parse(channel.author_thumbnail).request_target
|
||||||
|
|
||||||
|
relative_url = "/channel/#{ucid}/community"
|
||||||
|
youtube_url = "https://www.youtube.com#{relative_url}"
|
||||||
|
redirect_url = Invidious::Frontend::Misc.redirect_url(env)
|
||||||
|
|
||||||
|
selected_tab = Invidious::Frontend::ChannelPage::TabsAvailable::Community
|
||||||
|
-%>
|
||||||
|
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
|
<link rel="alternate" href="<%= youtube_url %>">
|
||||||
<title><%= author %> - Invidious</title>
|
<title><%= author %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if channel.banner %>
|
<%= rendered "components/channel_info" %>
|
||||||
<div class="h-box">
|
|
||||||
<img style="width:100%" src="/ggpht<%= URI.parse(channel.banner.not_nil!.gsub("=w1060-", "=w1280-")).request_target %>">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
|
||||||
<div class="pure-u-2-3">
|
|
||||||
<div class="channel-profile">
|
|
||||||
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
|
|
||||||
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-3" style="text-align:right">
|
|
||||||
<h3 style="text-align:right">
|
|
||||||
<a href="/feed/channel/<%= channel.ucid %>"><i class="icon ion-logo-rss"></i></a>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<div id="descriptionWrapper">
|
|
||||||
<p><span style="white-space:pre-wrap"><%= XML.parse_html(channel.description_html).xpath_node(%q(.//pre)).try &.content %></span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
|
||||||
<%= rendered "components/subscribe_widget" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<a href="https://www.youtube.com/channel/<%= channel.ucid %>/community"><%= translate(locale, "View channel on YouTube") %></a>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
|
||||||
<a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
|
||||||
<% else %>
|
|
||||||
<a href="https://redirect.invidious.io<%= env.request.resource %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% if !channel.auto_generated %>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<a href="/channel/<%= channel.ucid %>"><%= translate(locale, "Videos") %></a>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<a href="/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if channel.tabs.includes? "community" %>
|
|
||||||
<b><%= translate(locale, "Community") %></b>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-2-3"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<hr>
|
<hr>
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
<% if channel.banner %>
|
||||||
|
<div class="h-box">
|
||||||
|
<img style="width:100%" src="/ggpht<%= URI.parse(channel.banner.not_nil!.gsub("=w1060-", "=w1280-")).request_target %>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-box">
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="pure-g h-box">
|
||||||
|
<div class="pure-u-2-3">
|
||||||
|
<div class="channel-profile">
|
||||||
|
<img src="/ggpht<%= channel_profile_pic %>">
|
||||||
|
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pure-u-1-3">
|
||||||
|
<h3 style="text-align:right">
|
||||||
|
<a href="/feed/channel/<%= ucid %>"><i class="icon ion-logo-rss"></i></a>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-box">
|
||||||
|
<div id="descriptionWrapper">
|
||||||
|
<p><span style="white-space:pre-wrap"><%= channel.description_html %></span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-box">
|
||||||
|
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
||||||
|
<%= rendered "components/subscribe_widget" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-g h-box">
|
||||||
|
<div class="pure-u-1-2">
|
||||||
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
|
<a href="<%= youtube_url %>"><%= translate(locale, "View channel on YouTube") %></a>
|
||||||
|
</div>
|
||||||
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
|
<a href="<%= redirect_url %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= Invidious::Frontend::ChannelPage.generate_tabs_links(locale, channel, selected_tab) %>
|
||||||
|
</div>
|
||||||
|
<div class="pure-u-1-2">
|
||||||
|
<div class="pure-g" style="text-align:end">
|
||||||
|
<% sort_options.each do |sort| %>
|
||||||
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
|
<% if sort_by == sort %>
|
||||||
|
<b><%= translate(locale, sort) %></b>
|
||||||
|
<% else %>
|
||||||
|
<a href="<%= relative_url %>?sort_by=<%= sort %>"><%= translate(locale, sort) %></a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -23,6 +23,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% if CONFIG.enable_user_notifications %>
|
||||||
|
|
||||||
<center>
|
<center>
|
||||||
<%= translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %>
|
<%= translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %>
|
||||||
</center>
|
</center>
|
||||||
|
@ -39,6 +41,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
<% ucid = channel.ucid %>
|
|
||||||
<% author = HTML.escape(channel.author) %>
|
|
||||||
|
|
||||||
<% content_for "header" do %>
|
|
||||||
<title><%= author %> - Invidious</title>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if channel.banner %>
|
|
||||||
<div class="h-box">
|
|
||||||
<img style="width:100%" src="/ggpht<%= URI.parse(channel.banner.not_nil!.gsub("=w1060-", "=w1280-")).request_target %>">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
|
||||||
<div class="pure-u-2-3">
|
|
||||||
<div class="channel-profile">
|
|
||||||
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
|
|
||||||
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-3" style="text-align:right">
|
|
||||||
<h3 style="text-align:right">
|
|
||||||
<a href="/feed/channel/<%= ucid %>"><i class="icon ion-logo-rss"></i></a>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<div id="descriptionWrapper">
|
|
||||||
<p><span style="white-space:pre-wrap"><%= XML.parse_html(channel.description_html).xpath_node(%q(.//pre)).try &.content if !channel.description_html.empty? %></span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
|
||||||
<%= rendered "components/subscribe_widget" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
|
||||||
<div class="pure-g pure-u-1-3">
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<a href="https://www.youtube.com/channel/<%= ucid %>/playlists"><%= translate(locale, "View channel on YouTube") %></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
|
||||||
<a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
|
||||||
<% else %>
|
|
||||||
<a href="https://redirect.invidious.io<%= env.request.resource %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<a href="/channel/<%= ucid %>"><%= translate(locale, "Videos") %></a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if !channel.auto_generated %>
|
|
||||||
<b><%= translate(locale, "Playlists") %></b>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if channel.tabs.includes? "community" %>
|
|
||||||
<a href="/channel/<%= ucid %>/community"><%= translate(locale, "Community") %></a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-3"></div>
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<div class="pure-g" style="text-align:right">
|
|
||||||
<% {"last", "oldest", "newest"}.each do |sort| %>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<% if sort_by == sort %>
|
|
||||||
<b><%= translate(locale, sort) %></b>
|
|
||||||
<% else %>
|
|
||||||
<a href="/channel/<%= ucid %>/playlists?sort_by=<%= sort %>">
|
|
||||||
<%= translate(locale, sort) %>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-g">
|
|
||||||
<% items.each do |item| %>
|
|
||||||
<%= rendered "components/item" %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
|
||||||
<div class="pure-u-1 pure-u-md-4-5"></div>
|
|
||||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
|
||||||
<% if continuation %>
|
|
||||||
<a href="/channel/<%= ucid %>/playlists?continuation=<%= continuation %><% if sort_by != "last" %>&sort_by=<%= URI.encode_www_form(sort_by) %><% end %>">
|
|
||||||
<%= translate(locale, "Next page") %>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -55,7 +55,7 @@
|
||||||
<div class="pure-u-1-4">
|
<div class="pure-u-1-4">
|
||||||
<a id="notification_ticker" title="<%= translate(locale, "Subscriptions") %>" href="/feed/subscriptions" class="pure-menu-heading">
|
<a id="notification_ticker" title="<%= translate(locale, "Subscriptions") %>" href="/feed/subscriptions" class="pure-menu-heading">
|
||||||
<% notification_count = env.get("user").as(Invidious::User).notifications.size %>
|
<% notification_count = env.get("user").as(Invidious::User).notifications.size %>
|
||||||
<% if notification_count > 0 %>
|
<% if CONFIG.enable_user_notifications && notification_count > 0 %>
|
||||||
<span id="notification_count"><%= notification_count %></span> <i class="icon ion-ios-notifications"></i>
|
<span id="notification_count"><%= notification_count %></span> <i class="icon ion-ios-notifications"></i>
|
||||||
<% else %>
|
<% else %>
|
||||||
<i class="icon ion-ios-notifications-outline"></i>
|
<i class="icon ion-ios-notifications-outline"></i>
|
||||||
|
@ -175,7 +175,9 @@
|
||||||
}.to_pretty_json
|
}.to_pretty_json
|
||||||
%>
|
%>
|
||||||
</script>
|
</script>
|
||||||
|
<% if CONFIG.enable_user_notifications %>
|
||||||
<script src="/js/notifications.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/notifications.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="import_youtube">
|
<label for="import_youtube">
|
||||||
<a rel="noopener" target="_blank" href="https://github.com/iv-org/documentation/blob/master/Export-YouTube-subscriptions.md">
|
<a rel="noopener" target="_blank" href="https://github.com/iv-org/documentation/blob/master/docs/export-youtube-subscriptions.md">
|
||||||
<%= translate(locale, "Import YouTube subscriptions") %>
|
<%= translate(locale, "Import YouTube subscriptions") %>
|
||||||
</a>
|
</a>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -244,6 +244,7 @@
|
||||||
<input name="unseen_only" id="unseen_only" type="checkbox" <% if preferences.unseen_only %>checked<% end %>>
|
<input name="unseen_only" id="unseen_only" type="checkbox" <% if preferences.unseen_only %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% if CONFIG.enable_user_notifications %>
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="notifications_only"><%= translate(locale, "preferences_notifications_only_label") %></label>
|
<label for="notifications_only"><%= translate(locale, "preferences_notifications_only_label") %></label>
|
||||||
<input name="notifications_only" id="notifications_only" type="checkbox" <% if preferences.notifications_only %>checked<% end %>>
|
<input name="notifications_only" id="notifications_only" type="checkbox" <% if preferences.notifications_only %>checked<% end %>>
|
||||||
|
@ -255,6 +256,7 @@
|
||||||
<a href="#" data-onclick="notification_requestPermission"><%= translate(locale, "Enable web notifications") %></a>
|
<a href="#" data-onclick="notification_requestPermission"><%= translate(locale, "Enable web notifications") %></a>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if env.get?("user") && CONFIG.admins.includes? env.get?("user").as(Invidious::User).email %>
|
<% if env.get?("user") && CONFIG.admins.includes? env.get?("user").as(Invidious::User).email %>
|
||||||
|
|
変更されたファイルが多すぎるため、一部のファイルは表示されません さらに表示
読み込み中…
新しいイシューから参照