mirror of
https://gitea.invidious.io/iv-org/invidious-copy-2023-06-08.git
synced 2024-08-15 00:53:38 +00:00
users: separate login and register pages + restyle
This commit is contained in:
parent
542a60e599
commit
42fe574652
52 changed files with 648 additions and 455 deletions
|
@ -9,6 +9,76 @@
|
|||
* Licensed under AGPLv3
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* login/Register pages
|
||||
*/
|
||||
|
||||
.login-container,
|
||||
.register-container {
|
||||
width: max-content;
|
||||
margin: 12vh auto;
|
||||
text-align: end;
|
||||
padding: 2em;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.login-container label,
|
||||
.register-container label {
|
||||
width: max-content !important;
|
||||
margin-right: 1.5em !important;
|
||||
}
|
||||
|
||||
.login-container .login-submit-button,
|
||||
.register-container .register-submit-button {
|
||||
width: max-content;
|
||||
margin: 1.75em auto 1em auto;
|
||||
}
|
||||
|
||||
.login-container p,
|
||||
.register-container p {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-container .username-pass,
|
||||
.register-container .username-pass {
|
||||
width: max-content;
|
||||
margin: auto;
|
||||
padding: 0 2em;
|
||||
}
|
||||
|
||||
.captcha {
|
||||
margin: 2em 0.5em;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
/* Background color accent using transparency */
|
||||
/* TODO: handle themes better, ffs */
|
||||
.light-theme .captcha { background-color: #0002; }
|
||||
.dark-theme .captcha { background-color: #fff2; }
|
||||
|
||||
@media (prefers-color-scheme: light) { .no-theme .captcha { background-color: #0002; } }
|
||||
@media (prefers-color-scheme: dark) { .no-theme .captcha { background-color: #fff2; } }
|
||||
|
||||
|
||||
.captcha div {
|
||||
padding: 0.5em;
|
||||
}
|
||||
.captcha img {
|
||||
width: 10.5em;
|
||||
height: auto;
|
||||
}
|
||||
.captcha label {
|
||||
width: auto !important;
|
||||
margin: .4em 0 !important;;
|
||||
}
|
||||
.captcha a {
|
||||
color: #335d7a;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* User menu
|
||||
*/
|
||||
|
|
|
@ -162,11 +162,8 @@
|
|||
"Show replies": "عرض الردود",
|
||||
"Incorrect password": "كلمة السر غير صحيحة",
|
||||
"Wrong answer": "إجابة خاطئة",
|
||||
"Erroneous CAPTCHA": "الكابتشا CAPTCHA غير صاحلة",
|
||||
"CAPTCHA is a required field": "مكان الكابتشا CAPTCHA مطلوب",
|
||||
"User ID is a required field": "مكان اسم المستخدم مطلوب",
|
||||
"Password is a required field": "مكان كلمة السر مطلوب",
|
||||
"Wrong username or password": "اسم المستخدم او كلمة السر غير صحيح",
|
||||
"Password cannot be empty": "لا يمكن أن تكون كلمة السر فارغة",
|
||||
"Password cannot be longer than 55 characters": "يجب أن لا تتعدى كلمة السر 55 حرفًا",
|
||||
"Please log in": "الرجاء تسجيل الدخول",
|
||||
|
@ -185,7 +182,6 @@
|
|||
"Could not pull trending pages.": "لم يستطع عرض الصفحات الراجئة.",
|
||||
"Hidden field \"challenge\" is a required field": "مكان مخفي \"تحدي\" مكان مطلوب",
|
||||
"Hidden field \"token\" is a required field": "مكان مخفي \"رمز\" مكان مطلوب",
|
||||
"Erroneous challenge": "تحدي غير صالح",
|
||||
"Erroneous token": "رمز مميز خاطئ",
|
||||
"No such user": "مستخدم غير صالح",
|
||||
"Token is expired, please try again": "الرمز منتهى الصلاحية، الرجاء المحاولة مرة اخرى",
|
||||
|
|
|
@ -419,7 +419,6 @@
|
|||
"Playlist privacy": "Soukromí playlistu",
|
||||
"Wrong answer": "Špatná odpověď",
|
||||
"Could not pull trending pages.": "Nepodařilo se získat trendy stránky.",
|
||||
"Erroneous CAPTCHA": "Chybná CAPTCHA",
|
||||
"Password is a required field": "Heslo je vyžadované pole",
|
||||
"preferences_automatic_instance_redirect_label": "Automatické přesměrování instance (fallback na redirect.invidious.io): ",
|
||||
"Switch Invidious Instance": "Přepnout instanci Invidious",
|
||||
|
@ -427,7 +426,7 @@
|
|||
"footer_source_code": "Zdrojový kód",
|
||||
"View YouTube comments": "Zobrazit YouTube komentáře",
|
||||
"Blacklisted regions: ": "Oblasti na černé listině: ",
|
||||
"Wrong username or password": "Nesprávné uživatelské jméno nebo heslo",
|
||||
"error_invalid_username_or_password": "Nesprávné uživatelské jméno nebo heslo",
|
||||
"Please sign in using 'Log in with Google'": "Přihlaste se prosím pomocí Googlu",
|
||||
"Password cannot be empty": "Heslo nemůže být prázné",
|
||||
"preferences_category_misc": "Různá nastavení",
|
||||
|
@ -457,9 +456,7 @@
|
|||
"Load more": "Načíst další",
|
||||
"Not a playlist.": "Není playlist.",
|
||||
"Playlist does not exist.": "Playlist neexistuje.",
|
||||
"Erroneous challenge": "Chybná výzva",
|
||||
"Premieres `x`": "Premiéra `x`",
|
||||
"CAPTCHA is a required field": "CAPTCHA je vyžadované pole",
|
||||
"`x` ago": "Před `x`",
|
||||
"search_message_change_filters_or_query": "Zkuste rozšířit vyhledávaný dotaz a/nebo změnit filtry.",
|
||||
"search_filters_date_option_none": "Jakékoli datum",
|
||||
|
|
|
@ -157,11 +157,9 @@
|
|||
"Show replies": "Vis svar",
|
||||
"Incorrect password": "Forkert adgangskode",
|
||||
"Wrong answer": "Forkert svar",
|
||||
"Erroneous CAPTCHA": "Fejlagtig CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA er et obligatorisk felt",
|
||||
"User ID is a required field": "Bruger ID er et krævet felt",
|
||||
"Password is a required field": "Adgangskode er et obligatorisk felt",
|
||||
"Wrong username or password": "Forkert brugernavn eller adgangskode",
|
||||
"error_invalid_username_or_password": "Forkert brugernavn eller adgangskode",
|
||||
"Please sign in using 'Log in with Google'": "Log ind via 'Log ind med Google'",
|
||||
"Password cannot be empty": "Adgangskoden må ikke være tom",
|
||||
"Password cannot be longer than 55 characters": "Adgangskoden må ikke være længere end 55 tegn",
|
||||
|
@ -306,7 +304,6 @@
|
|||
"Marathi": "Marathi",
|
||||
"Sindhi": "Sindhi",
|
||||
"preferences_category_misc": "Diverse indstillinger",
|
||||
"Erroneous challenge": "Fejlagtig udfordring",
|
||||
"Hindi": "Hindi",
|
||||
"Igbo": "Igbo",
|
||||
"Javanese": "Javanesisk",
|
||||
|
|
|
@ -162,11 +162,9 @@
|
|||
"Show replies": "Antworten anzeigen",
|
||||
"Incorrect password": "Falsches Passwort",
|
||||
"Wrong answer": "Ungültige Antwort",
|
||||
"Erroneous CAPTCHA": "Ungültiges CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA ist eine erforderliche Eingabe",
|
||||
"User ID is a required field": "Benutzer ID ist eine erforderliche Eingabe",
|
||||
"Password is a required field": "Passwort ist eine erforderliche Eingabe",
|
||||
"Wrong username or password": "Ungültiger Benutzername oder Passwort",
|
||||
"error_invalid_username_or_password": "Ungültiger Benutzername oder Passwort",
|
||||
"Password cannot be empty": "Passwort darf nicht leer sein",
|
||||
"Password cannot be longer than 55 characters": "Passwort darf nicht länger als 55 Zeichen sein",
|
||||
"Please log in": "Bitte anmelden",
|
||||
|
@ -185,7 +183,6 @@
|
|||
"Could not pull trending pages.": "Trendenz-Seiten konnten nicht geladen werden.",
|
||||
"Hidden field \"challenge\" is a required field": "Verstecktes Feld „challenge“ ist eine erforderliche Eingabe",
|
||||
"Hidden field \"token\" is a required field": "Verstecktes Feld „token“ ist eine erforderliche Eingabe",
|
||||
"Erroneous challenge": "Ungültiger Test",
|
||||
"Erroneous token": "Ungültiger Token",
|
||||
"No such user": "Ungültiger Benutzer",
|
||||
"Token is expired, please try again": "Token ist abgelaufen, bitte erneut versuchen",
|
||||
|
|
|
@ -153,11 +153,9 @@
|
|||
"Show replies": "Προβολή απαντήσεων",
|
||||
"Incorrect password": "Λανθασμένος κωδικός πρόσβασης",
|
||||
"Wrong answer": "Λανθασμένη απάντηση",
|
||||
"Erroneous CAPTCHA": "Λανθασμένο CAPTCHA",
|
||||
"CAPTCHA is a required field": "Το CAPTCHA είναι απαιτούμενο πεδίο",
|
||||
"User ID is a required field": "Η ταυτότητα χρήστη είναι απαιτούμενο πεδίο",
|
||||
"Password is a required field": "Ο κωδικός πρόσβασης είναι απαιτούμενο πεδίο",
|
||||
"Wrong username or password": "Λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης",
|
||||
"error_invalid_username_or_password": "Λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης",
|
||||
"Password cannot be empty": "Ο κωδικός πρόσβασης δεν γίνεται να είναι κενός",
|
||||
"Password cannot be longer than 55 characters": "Ο κωδικός πρόσβασης δεν γίνεται να υπερβαίνει τους 55 χαρακτήρες",
|
||||
"Please log in": "Συνδεθείτε",
|
||||
|
@ -176,7 +174,6 @@
|
|||
"Could not pull trending pages.": "Αδυναμία λήψης σελίδας τάσεων.",
|
||||
"Hidden field \"challenge\" is a required field": "Το Κρυφό πεδίο \"δοκιμασία\" είναι απαραίτητο",
|
||||
"Hidden field \"token\" is a required field": "Το κρυφό πεδίο \"αναγνωριστικό διασύνδεσης\" είναι απαραίτητο",
|
||||
"Erroneous challenge": "Λανθασμένη δοκιμασία",
|
||||
"Erroneous token": "Λανθασμένο αναγνωριστικό διασύνδεσης",
|
||||
"No such user": "Μη υπαρκτός χρήστης",
|
||||
"Token is expired, please try again": "Το αναγνωριστικό διασύνδεσης έχει λήξει, παρακαλώ ξαναπροσπαθήστε",
|
||||
|
|
|
@ -44,15 +44,6 @@
|
|||
"An alternative front-end to YouTube": "An alternative front-end to YouTube",
|
||||
"JavaScript license information": "JavaScript license information",
|
||||
"source": "source",
|
||||
"Log in": "Log in",
|
||||
"Log in/register": "Log in/register",
|
||||
"User ID": "User ID",
|
||||
"Password": "Password",
|
||||
"Time (h:mm:ss):": "Time (h:mm:ss):",
|
||||
"Text CAPTCHA": "Text CAPTCHA",
|
||||
"Image CAPTCHA": "Image CAPTCHA",
|
||||
"Sign In": "Sign In",
|
||||
"Register": "Register",
|
||||
"E-mail": "E-mail",
|
||||
"Preferences": "Preferences",
|
||||
"preferences_category_player": "Player preferences",
|
||||
|
@ -200,11 +191,6 @@
|
|||
"Show replies": "Show replies",
|
||||
"Incorrect password": "Incorrect password",
|
||||
"Wrong answer": "Wrong answer",
|
||||
"Erroneous CAPTCHA": "Erroneous CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA is a required field",
|
||||
"User ID is a required field": "User ID is a required field",
|
||||
"Password is a required field": "Password is a required field",
|
||||
"Wrong username or password": "Wrong username or password",
|
||||
"Password cannot be empty": "Password cannot be empty",
|
||||
"Password cannot be longer than 55 characters": "Password cannot be longer than 55 characters",
|
||||
"Please log in": "Please log in",
|
||||
|
@ -225,9 +211,7 @@
|
|||
"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",
|
||||
|
@ -464,5 +448,26 @@
|
|||
"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_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>",
|
||||
"error_required_field_username": "Username is a required field",
|
||||
"error_required_field_password": "Password is a required field",
|
||||
"error_login_disabled": "Login has been disabled by the administrator",
|
||||
"error_registration_disabled": "Registration has been disabled by the administrator",
|
||||
"error_invalid_username_or_password": "Invalid username or password",
|
||||
"error_invalid_captcha": "Invalid CAPTCHA",
|
||||
"error_passwords_dont_match": "Passwords don't match",
|
||||
"error_username_already_registered": "Username is already in use. Please try a different one.",
|
||||
"error_database_unavailable": "Database unavailable, please try again later.<br/>\nIf the problem persist, please contact the instance admin.",
|
||||
"login_page_title_login": "Sign in",
|
||||
"login_page_title_register": "Register",
|
||||
"login_page_login_button": "Sign in",
|
||||
"login_page_register_button": "Register",
|
||||
"login_page_username_label": "Username",
|
||||
"login_page_password_label": "Password",
|
||||
"login_page_confirm_label": "Confirm password",
|
||||
"login_page_goto_register_prompt": "Don't have an account yet? <a href=\"`x`\">Register here!</a>",
|
||||
"login_page_goto_login_prompt": "Already have an account? <a href=\"`x`\">Log in</a>",
|
||||
"Time (h:mm:ss):": "Time (h:mm:ss):",
|
||||
"login_page_request_text_captcha": "Text CAPTCHA",
|
||||
"login_page_request_image_captcha": "Image CAPTCHA"
|
||||
}
|
||||
|
|
|
@ -162,11 +162,9 @@
|
|||
"Show replies": "Montri respondojn",
|
||||
"Incorrect password": "Malbona pasvorto",
|
||||
"Wrong answer": "Nevalida respondo",
|
||||
"Erroneous CAPTCHA": "Nevalida CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA estas deviga kampo",
|
||||
"User ID is a required field": "Uzula identigilo estas deviga kampo",
|
||||
"Password is a required field": "Pasvorto estas deviga kampo",
|
||||
"Wrong username or password": "Nevalida uzantnomo aŭ pasvorto",
|
||||
"error_invalid_username_or_password": "Nevalida uzantnomo aŭ pasvorto",
|
||||
"Please sign in using 'Log in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'",
|
||||
"Password cannot be empty": "Pasvorto ne povas esti malplena",
|
||||
"Password cannot be longer than 55 characters": "Pasvorto ne povas esti pli longa ol 55 signoj",
|
||||
|
@ -186,7 +184,6 @@
|
|||
"Could not pull trending pages.": "Ne povis venigi tendencajn paĝojn.",
|
||||
"Hidden field \"challenge\" is a required field": "Kaŝita kampo \"challenge\" estas deviga kampo",
|
||||
"Hidden field \"token\" is a required field": "Kaŝita kampo \"token\" estas deviga kampo",
|
||||
"Erroneous challenge": "Nevalida defio",
|
||||
"Erroneous token": "Nevalida ĵetono",
|
||||
"No such user": "Nevalida uzanto",
|
||||
"Token is expired, please try again": "Ĵetono senvalidiĝis, bonvolu provi denove",
|
||||
|
|
|
@ -162,11 +162,9 @@
|
|||
"Show replies": "Mostrar las respuestas",
|
||||
"Incorrect password": "Contraseña incorrecta",
|
||||
"Wrong answer": "Respuesta no válida",
|
||||
"Erroneous CAPTCHA": "CAPTCHA no válido",
|
||||
"CAPTCHA is a required field": "El CAPTCHA es un campo obligatorio",
|
||||
"User ID is a required field": "El nombre es un campo obligatorio",
|
||||
"Password is a required field": "La contraseña es un campo obligatorio",
|
||||
"Wrong username or password": "Nombre o contraseña incorrecto",
|
||||
"error_invalid_username_or_password": "Nombre o contraseña incorrecto",
|
||||
"Please sign in using 'Log in with Google'": "Inicie sesión con «Iniciar sesión con Google»",
|
||||
"Password cannot be empty": "La contraseña no puede estar en blanco",
|
||||
"Password cannot be longer than 55 characters": "La contraseña no puede tener más de 55 caracteres",
|
||||
|
@ -186,7 +184,6 @@
|
|||
"Could not pull trending pages.": "No se han podido obtener las páginas de tendencias.",
|
||||
"Hidden field \"challenge\" is a required field": "El campo oculto «desafío» es un campo obligatorio",
|
||||
"Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio",
|
||||
"Erroneous challenge": "Desafío no válido",
|
||||
"Erroneous token": "Símbolo no válido",
|
||||
"No such user": "Usuario no existe",
|
||||
"Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo",
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
"Wrong answer": "Vale vastus",
|
||||
"User ID is a required field": "Kasutaja ID on kohustuslik väli",
|
||||
"Password is a required field": "Salasõna on kohustuslik väli",
|
||||
"Wrong username or password": "Vale kasutajanimi või salasõna",
|
||||
"error_invalid_username_or_password": "Vale kasutajanimi või salasõna",
|
||||
"Please sign in using 'Log in with Google'": "Palun kasutage 'Logi sisse Google'iga'",
|
||||
"Password cannot be longer than 55 characters": "Salasõna ei tohi olla pikem kui 55 tähemärki",
|
||||
"Password cannot be empty": "Salasõna ei tohi olla tühi",
|
||||
|
@ -293,7 +293,6 @@
|
|||
"Lithuanian": "Leedu",
|
||||
"Videos": "Videod",
|
||||
"Community": "Kogukond",
|
||||
"CAPTCHA is a required field": "CAPTCHA on kohustuslik väli",
|
||||
"comments_points_count": "{{count}} punkt",
|
||||
"comments_points_count_plural": "{{count}} punkti",
|
||||
"Chinese": "Hiina",
|
||||
|
|
|
@ -164,7 +164,7 @@
|
|||
"Premieres `x`": "'x' estrenaldiak",
|
||||
"Wrong answer": "Erantzun ez zuzena",
|
||||
"Password is a required field": "Pasahitza beharrezkoa da",
|
||||
"Wrong username or password": "Pasahitza edo ezizena gaizki",
|
||||
"error_invalid_username_or_password": "Pasahitza edo ezizena gaizki",
|
||||
"Password cannot be longer than 55 characters": "Pasahitza 55 karaktere baino luzeagoa ezin da izan",
|
||||
"This channel does not exist.": "Kanal hau ez dago.",
|
||||
"`x` ago": "duela 'x'",
|
||||
|
@ -191,12 +191,10 @@
|
|||
"Danish": "Daniera",
|
||||
"Dutch": "Alemaniera",
|
||||
"Esperanto": "Esperanto",
|
||||
"Erroneous challenge": "Erronka okerra",
|
||||
"View all playlists": "Zerrenda guztiak ikusi",
|
||||
"Show annotations": "Oharrak erakutsi",
|
||||
"Empty playlist": "Zerrenda hutsik",
|
||||
"Please log in": "Sartu, mesedez",
|
||||
"CAPTCHA is a required field": "CAPTCHA beharrezko eremua da",
|
||||
"preferences_category_data": "Dataren lehentasunak",
|
||||
"preferences_default_home_label": "Homepage lehenetsia: ",
|
||||
"preferences_automatic_instance_redirect_label": "berbideratze adibide automatikoa (atzera egin berbideratzeko: invidious.io) ",
|
||||
|
@ -246,7 +244,6 @@
|
|||
"revoke": "ukatu",
|
||||
"preferences_continue_label": "Hurrengo lehenetsia jo: ",
|
||||
"Whitelisted regions: ": "Zuri zerrendaren zonaldeak: ",
|
||||
"Erroneous CAPTCHA": "CAPTCHA gaizki",
|
||||
"Deleted or invalid channel": "Ezgai edota ezabatutako kanala",
|
||||
"Could not create mix.": "Nahastea ezin sortu.",
|
||||
"Not a playlist.": "Ez da zerrenda.",
|
||||
|
|
|
@ -169,11 +169,9 @@
|
|||
"Show replies": "نمایش پاسخ ها",
|
||||
"Incorrect password": "گذرواژه نا درست",
|
||||
"Wrong answer": "پاسخ غلط",
|
||||
"Erroneous CAPTCHA": "CAPTCHA نا درست",
|
||||
"CAPTCHA is a required field": "CAPTCHA یک فیلد ضروری است",
|
||||
"User ID is a required field": "شناسه کاربری یک فیلد ضروری است",
|
||||
"Password is a required field": "گذرواژه یک فیلد ضروری است",
|
||||
"Wrong username or password": "نام کاربری یا گذرواژه غلط است",
|
||||
"error_invalid_username_or_password": "نام کاربری یا گذرواژه غلط است",
|
||||
"Please sign in using 'Log in with Google'": "لطفا با استفاده از 'ورود توسط گوگل' وارد شوید",
|
||||
"Password cannot be empty": "گذرواژه نمیتواند خالی باشد",
|
||||
"Password cannot be longer than 55 characters": "گذر واژه نمیتواند از ۵۵ کاراکتر بیشتر باشد",
|
||||
|
@ -195,7 +193,6 @@
|
|||
"Could not pull trending pages.": "نمیتوان صفحه های پر طرفدار را بکشد.",
|
||||
"Hidden field \"challenge\" is a required field": "فیلد مخفی \"چالش\" یک فیلد ضروری است",
|
||||
"Hidden field \"token\" is a required field": "فیلد مخفی \"توکن\" یک فیلد ضروری است",
|
||||
"Erroneous challenge": "چالش غلط",
|
||||
"Erroneous token": "توکن غلط",
|
||||
"No such user": "چنین کاربری وجود ندارد",
|
||||
"Token is expired, please try again": "توکن ضروری است، لطفا دوباره تلاش کنید",
|
||||
|
|
|
@ -161,11 +161,9 @@
|
|||
"Show replies": "Näytä vastaukset",
|
||||
"Incorrect password": "Väärä salasana",
|
||||
"Wrong answer": "Väärä vastaus",
|
||||
"Erroneous CAPTCHA": "Virheellinen CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA-kenttä vaaditaan",
|
||||
"User ID is a required field": "Käyttäjätunnus vaaditaan",
|
||||
"Password is a required field": "Salasana vaaditaan",
|
||||
"Wrong username or password": "Väärä käyttäjänimi tai salasana",
|
||||
"error_invalid_username_or_password": "Väärä käyttäjänimi tai salasana",
|
||||
"Password cannot be empty": "Salasana ei voi olla tyhjä",
|
||||
"Password cannot be longer than 55 characters": "Salasana ei voi olla yli 55 merkkiä pitkä",
|
||||
"Please log in": "Kirjaudu sisään, ole hyvä",
|
||||
|
@ -184,7 +182,6 @@
|
|||
"Could not pull trending pages.": "Nousussa olevien sivujen lataus epäonnistui.",
|
||||
"Hidden field \"challenge\" is a required field": "Piilotettu kenttä \"challenge\" vaaditaan",
|
||||
"Hidden field \"token\" is a required field": "Piilotettu kenttä \"tunnus\" vaaditaan",
|
||||
"Erroneous challenge": "Virheellinen haaste",
|
||||
"Erroneous token": "Virheellinen tunnus",
|
||||
"No such user": "Käyttäjää ei ole olemassa",
|
||||
"Token is expired, please try again": "Tunnus on vanhentunut, yritä uudestaan",
|
||||
|
|
|
@ -44,15 +44,6 @@
|
|||
"An alternative front-end to YouTube": "Un front-end alternatif à YouTube",
|
||||
"JavaScript license information": "Informations sur les licences JavaScript",
|
||||
"source": "source",
|
||||
"Log in": "Se connecter",
|
||||
"Log in/register": "Se connecter/S'inscrire",
|
||||
"User ID": "Identifiant utilisateur",
|
||||
"Password": "Mot de passe",
|
||||
"Time (h:mm:ss):": "Heure (h:mm:ss) :",
|
||||
"Text CAPTCHA": "CAPTCHA textuel",
|
||||
"Image CAPTCHA": "CAPTCHA graphique",
|
||||
"Sign In": "Se connecter",
|
||||
"Register": "S'inscrire",
|
||||
"E-mail": "E-mail",
|
||||
"Preferences": "Préférences",
|
||||
"preferences_category_player": "Préférences du lecteur",
|
||||
|
@ -177,11 +168,9 @@
|
|||
"Show replies": "Afficher les réponses",
|
||||
"Incorrect password": "Mot de passe incorrect",
|
||||
"Wrong answer": "Réponse invalide",
|
||||
"Erroneous CAPTCHA": "CAPTCHA invalide",
|
||||
"CAPTCHA is a required field": "Veuillez entrer un CAPTCHA",
|
||||
"User ID is a required field": "Veuillez entrer un Identifiant Utilisateur",
|
||||
"Password is a required field": "Veuillez entrer un Mot de passe",
|
||||
"Wrong username or password": "Nom d'utilisateur ou mot de passe invalide",
|
||||
"error_invalid_username_or_password": "Nom d'utilisateur ou mot de passe invalide",
|
||||
"Password cannot be empty": "Le mot de passe ne peut pas être vide",
|
||||
"Password cannot be longer than 55 characters": "Le mot de passe ne doit pas comporter plus de 55 caractères",
|
||||
"Please log in": "Veuillez vous connecter",
|
||||
|
@ -204,7 +193,6 @@
|
|||
"Could not pull trending pages.": "Impossible de charger les pages de tendances.",
|
||||
"Hidden field \"challenge\" is a required field": "Le champ masqué \"challenge\" est un champ obligatoire",
|
||||
"Hidden field \"token\" is a required field": "Le champ caché « token » est requis",
|
||||
"Erroneous challenge": "Challenge invalide",
|
||||
"Erroneous token": "Token invalide",
|
||||
"No such user": "Cet utilisateur n'existe pas",
|
||||
"Token is expired, please try again": "Le token est expiré, veuillez réessayer",
|
||||
|
@ -464,5 +452,26 @@
|
|||
"search_filters_date_label": "Date d'ajout",
|
||||
"search_filters_features_option_vr180": "VR180",
|
||||
"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>",
|
||||
"error_required_field_username": "Le champ \"nom d'utilisateur\" est requis",
|
||||
"error_required_field_password": "Le champ \"mot de passe\" est requis",
|
||||
"error_login_disabled": "L'administrateur a interdit l'utilisation des comptes utilisateur",
|
||||
"error_registration_disabled": "L'administrateur a interdit la création de comptes utilisateur",
|
||||
"error_invalid_username_or_password": "Mot de passe ou nom d'utilisateur invalide",
|
||||
"error_invalid_captcha": "CAPTCHA invalide",
|
||||
"error_passwords_dont_match": "Les mots de passe ne correspondent pas",
|
||||
"error_username_already_registered": "Ce nom d'utilisateur n'est pas disponible. Merci d'en utiliser un autre.",
|
||||
"error_database_unavailable": "Base de données indisponible, merci de réessayer plus tard.<br/>\nSi le problèm persiste, merci de contacter l'administrateur de cette instance.",
|
||||
"login_page_title_login": "Connexion",
|
||||
"login_page_title_register": "Créer un compte",
|
||||
"login_page_login_button": "Connexion",
|
||||
"login_page_register_button": "Créer un compte",
|
||||
"login_page_username_label": "Nom d'utilisateur",
|
||||
"login_page_password_label": "Mot de passe",
|
||||
"login_page_confirm_label": "Confirmer le mot de passe",
|
||||
"login_page_goto_register_prompt": "Pas encore de compte? <a href=\"`x`\">S'enregistrer</a>",
|
||||
"login_page_goto_login_prompt": "Déjà un compte? <a href=\"`x`\">Se connecter</a>",
|
||||
"Time (h:mm:ss):": "Time (h:mm:ss):",
|
||||
"login_page_request_text_captcha": "Changer pour un CAPTCHA textuel",
|
||||
"login_page_request_image_captcha": "Changer pour un CAPTCHA visuel"
|
||||
}
|
||||
|
|
|
@ -130,10 +130,9 @@
|
|||
"Show replies": "הצגת תגובות",
|
||||
"Incorrect password": "סיסמה שגויה",
|
||||
"Wrong answer": "תשובה שגויה",
|
||||
"CAPTCHA is a required field": "שדה CAPTCHA הוא שדה חובה",
|
||||
"User ID is a required field": "חובה למלא את שדה שם המשתמש",
|
||||
"Password is a required field": "חובה למלא את שדה הסיסמה",
|
||||
"Wrong username or password": "שם משתמש שגוי או סיסמה שגויה",
|
||||
"error_invalid_username_or_password": "שם משתמש שגוי או סיסמה שגויה",
|
||||
"Please sign in using 'Log in with Google'": "נא להתחבר בעזרת \"התחברות עם Google\"",
|
||||
"Password cannot be longer than 55 characters": "על אורך הסיסמה להיות 55 תווים לכל היותר",
|
||||
"Please log in": "נא להתחבר",
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
"License: ": "लाइसेंस: ",
|
||||
"Wilson score: ": "Wilson स्कोर: ",
|
||||
"Wrong answer": "गलत जवाब",
|
||||
"Erroneous CAPTCHA": "गलत CAPTCHA",
|
||||
"Please log in": "कृपया लॉग-इन करें",
|
||||
"Bosnian": "बोस्नियाई",
|
||||
"Bulgarian": "बुल्गारियाई",
|
||||
|
@ -221,10 +220,9 @@
|
|||
"Hide replies": "जवाब छिपाएँ",
|
||||
"Show replies": "जवाब दिखाएँ",
|
||||
"Incorrect password": "गलत पासवर्ड",
|
||||
"CAPTCHA is a required field": "CAPTCHA एक ज़रूरी फ़ील्ड है",
|
||||
"User ID is a required field": "सदस्य ID एक ज़रूरी फ़ील्ड है",
|
||||
"Password is a required field": "पासवर्ड एक ज़रूरी फ़ील्ड है",
|
||||
"Wrong username or password": "गलत सदस्यनाम या पासवर्ड",
|
||||
"error_invalid_username_or_password": "गलत सदस्यनाम या पासवर्ड",
|
||||
"Please sign in using 'Log in with Google'": "कृपया 'Google के साथ लॉग-इन करें' के साथ साइन-इन करें",
|
||||
"Password cannot be empty": "पासवर्ड खाली नहीं हो सकता",
|
||||
"Password cannot be longer than 55 characters": "पासवर्ड में अधिकतम 55 अक्षर हो सकते हैं",
|
||||
|
@ -244,7 +242,6 @@
|
|||
"Could not pull trending pages.": "रुझान के पृष्ठ प्राप्त न किए जा सके।",
|
||||
"Hidden field \"challenge\" is a required field": "छिपाया गया फ़ील्ड \"चुनौती\" एक आवश्यक फ़ील्ड है",
|
||||
"Hidden field \"token\" is a required field": "छिपाया गया फ़ील्ड \"टोकन\" एक आवश्यक फ़ील्ड है",
|
||||
"Erroneous challenge": "त्रुटिपूर्ण चुनौती",
|
||||
"Erroneous token": "त्रुटिपूर्ण टोकन",
|
||||
"No such user": "यह सदस्य मौजूद नहीं हैं",
|
||||
"Token is expired, please try again": "टोकन की समय-सीमा समाप्त हो चुकी है, कृपया दोबारा कोशिश करें",
|
||||
|
|
|
@ -162,11 +162,9 @@
|
|||
"Show replies": "Prikaži odgovore",
|
||||
"Incorrect password": "Neispravna lozinka",
|
||||
"Wrong answer": "Krivi odgovor",
|
||||
"Erroneous CAPTCHA": "Neispravan CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA je obavezno polje",
|
||||
"User ID is a required field": "Korisnički ID je obavezno polje",
|
||||
"Password is a required field": "Polje lozinke je obavezno polje",
|
||||
"Wrong username or password": "Krivo korisničko ime ili lozinka",
|
||||
"error_invalid_username_or_password": "Krivo korisničko ime ili lozinka",
|
||||
"Password cannot be empty": "Polje lozinke ne smije ostati prazno",
|
||||
"Password cannot be longer than 55 characters": "Lozinka ne može biti duža od 55 znakova",
|
||||
"Please log in": "Prijavi se",
|
||||
|
@ -185,7 +183,6 @@
|
|||
"Could not pull trending pages.": "Neuspjelo preuzimanje stranica u trendu.",
|
||||
"Hidden field \"challenge\" is a required field": "Skriveno polje „izazov” je obavezno polje",
|
||||
"Hidden field \"token\" is a required field": "Skriveno polje „token” je obavezno polje",
|
||||
"Erroneous challenge": "Neispravan izazov",
|
||||
"Erroneous token": "Neispravan token",
|
||||
"No such user": "Takav korisnik ne postoji",
|
||||
"Token is expired, please try again": "Token je istekao, pokušaj ponovo",
|
||||
|
|
|
@ -171,11 +171,9 @@
|
|||
"Show replies": "Válaszok mutatása",
|
||||
"Incorrect password": "A jelszó nem megfelelő",
|
||||
"Wrong answer": "Nem jól válaszoltál.",
|
||||
"Erroneous CAPTCHA": "A CAPTCHA hibás.",
|
||||
"CAPTCHA is a required field": "A CAPTCHA-mezőt ki kell tölteni.",
|
||||
"User ID is a required field": "A felhasználói azonosítót meg kell adni.",
|
||||
"Password is a required field": "Meg kell adni egy jelszót.",
|
||||
"Wrong username or password": "Vagy a felhasználói név, vagy pedig a jelszó nem megfelelő.",
|
||||
"error_invalid_username_or_password": "Vagy a felhasználói név, vagy pedig a jelszó nem megfelelő.",
|
||||
"Password cannot be empty": "A jelszót nem lehet kihagyni.",
|
||||
"Password cannot be longer than 55 characters": "A jelszó nem lehet hosszabb 55 karakternél.",
|
||||
"Please log in": "Kérjük, jelentkezz be.",
|
||||
|
@ -198,7 +196,6 @@
|
|||
"Could not pull trending pages.": "Nem lehetett betölteni a felkapott videók oldalát.",
|
||||
"Hidden field \"challenge\" is a required field": "A rejtett „challenge” mezőt ki kell tölteni.",
|
||||
"Hidden field \"token\" is a required field": "A rejtett „token” mezőt ki kell tölteni.",
|
||||
"Erroneous challenge": "Hibás challenge",
|
||||
"Erroneous token": "Hibás token",
|
||||
"No such user": "Nincs ilyen felhasználó",
|
||||
"Token is expired, please try again": "A token lejárt. Kérjük, próbáld meg újból.",
|
||||
|
|
|
@ -169,11 +169,9 @@
|
|||
"Show replies": "Lihat balasan",
|
||||
"Incorrect password": "Kata sandi salah",
|
||||
"Wrong answer": "Jawaban salah",
|
||||
"Erroneous CAPTCHA": "CAPTCHA salah",
|
||||
"CAPTCHA is a required field": "CAPTCHA perlu diisi",
|
||||
"User ID is a required field": "ID pengguna perlu diisi",
|
||||
"Password is a required field": "Kata sandi perlu diisi",
|
||||
"Wrong username or password": "Nama pengguna atau kata sandi salah",
|
||||
"error_invalid_username_or_password": "Nama pengguna atau kata sandi salah",
|
||||
"Password cannot be empty": "Kata sandi tidak boleh kosong",
|
||||
"Password cannot be longer than 55 characters": "Kata sandi tidak boleh lebih dari 55 karakter",
|
||||
"Please log in": "Harap masuk",
|
||||
|
@ -194,7 +192,6 @@
|
|||
"Could not pull trending pages.": "Tidak bisa mendapatkan laman tren.",
|
||||
"Hidden field \"challenge\" is a required field": "Bidang \"tantangan\" tersembunyi wajib diisi",
|
||||
"Hidden field \"token\" is a required field": "Bidang \"token\" tersembunyi wajib diisi",
|
||||
"Erroneous challenge": "Tantangan salah",
|
||||
"Erroneous token": "Token salah",
|
||||
"No such user": "Tidak ada pengguna demikian",
|
||||
"Token is expired, please try again": "Token kadaluwarsa, harap coba lagi",
|
||||
|
|
|
@ -153,11 +153,9 @@
|
|||
"Show replies": "Sýna svör",
|
||||
"Incorrect password": "Rangt lykilorð",
|
||||
"Wrong answer": "Rangt svar",
|
||||
"Erroneous CAPTCHA": "Rangt CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA er nauðsynlegur reitur",
|
||||
"User ID is a required field": "Notandakenni er nauðsynlegur reitur",
|
||||
"Password is a required field": "Lykilorð er nauðsynlegur reitur",
|
||||
"Wrong username or password": "Rangt notandanafn eða lykilorð",
|
||||
"error_invalid_username_or_password": "Rangt notandanafn eða lykilorð",
|
||||
"Password cannot be empty": "Lykilorð má ekki vera autt",
|
||||
"Password cannot be longer than 55 characters": "Lykilorð má ekki vera lengra en 55 stafir",
|
||||
"Please log in": "Vinsamlegast skráðu þig inn",
|
||||
|
@ -176,7 +174,6 @@
|
|||
"Could not pull trending pages.": "Ekki tókst að draga vinsælar síður.",
|
||||
"Hidden field \"challenge\" is a required field": "Falinn reitur \"áskorun\" er nauðsynlegur reitur",
|
||||
"Hidden field \"token\" is a required field": "Falinn reitur \"tákn\" er nauðsynlegur reitur",
|
||||
"Erroneous challenge": "Röng áskorun",
|
||||
"Erroneous token": "Rangt tákn",
|
||||
"No such user": "Enginn slíkur notandi",
|
||||
"Token is expired, please try again": "Tákn er útrunnið, vinsamlegast reyndu aftur",
|
||||
|
|
|
@ -167,11 +167,9 @@
|
|||
"Show replies": "Mostra le risposte",
|
||||
"Incorrect password": "Password sbagliata",
|
||||
"Wrong answer": "Risposta errata",
|
||||
"Erroneous CAPTCHA": "CAPTCHA errato",
|
||||
"CAPTCHA is a required field": "Il CAPTCHA è un campo obbligatorio",
|
||||
"User ID is a required field": "L'ID utente è obbligatorio",
|
||||
"Password is a required field": "La password è un campo obbligatorio",
|
||||
"Wrong username or password": "Nome utente o password errati",
|
||||
"error_invalid_username_or_password": "Nome utente o password errati",
|
||||
"Password cannot be empty": "La password non può essere vuota",
|
||||
"Password cannot be longer than 55 characters": "La password non può contenere più di 55 caratteri",
|
||||
"Please log in": "Per favore, accedi",
|
||||
|
@ -190,7 +188,6 @@
|
|||
"Could not pull trending pages.": "Impossibile recuperare le tendenze.",
|
||||
"Hidden field \"challenge\" is a required field": "Il campo nascosto \"challenge\" è obbligatorio",
|
||||
"Hidden field \"token\" is a required field": "Il campo nascosto «token» è obbligatorio",
|
||||
"Erroneous challenge": "Campo «challenge» non valido",
|
||||
"Erroneous token": "Campo \"token\" non valido",
|
||||
"No such user": "Utente non valido",
|
||||
"Token is expired, please try again": "Gettone scaduto, riprova",
|
||||
|
|
|
@ -169,11 +169,9 @@
|
|||
"Show replies": "返信を表示",
|
||||
"Incorrect password": "パスワードが間違っています",
|
||||
"Wrong answer": "回答が間違っています",
|
||||
"Erroneous CAPTCHA": "CAPTCHA が間違っています",
|
||||
"CAPTCHA is a required field": "CAPTCHA は必須項目です",
|
||||
"User ID is a required field": "ユーザー ID は必須項目です",
|
||||
"Password is a required field": "パスワードは必須項目です",
|
||||
"Wrong username or password": "ユーザー名またはパスワードが間違っています",
|
||||
"error_invalid_username_or_password": "ユーザー名またはパスワードが間違っています",
|
||||
"Password cannot be empty": "パスワードを空にすることはできません",
|
||||
"Password cannot be longer than 55 characters": "パスワードは55文字より長くできません",
|
||||
"Please log in": "ログインをしてください",
|
||||
|
@ -194,7 +192,6 @@
|
|||
"Could not pull trending pages.": "急上昇ページを取得できませんでした。",
|
||||
"Hidden field \"challenge\" is a required field": "非表示項目 \"challenge\" は必須項目です",
|
||||
"Hidden field \"token\" is a required field": "非表示項目 \"token\" は必須項目です",
|
||||
"Erroneous challenge": "チャレンジが間違っています",
|
||||
"Erroneous token": "トークンが間違っています",
|
||||
"No such user": "ユーザーが存在しません",
|
||||
"Token is expired, please try again": "トークンが期限切れです。再度試してください",
|
||||
|
|
|
@ -234,7 +234,6 @@
|
|||
"Hausa": "하우사어",
|
||||
"No such user": "해당 사용자 없음",
|
||||
"Erroneous token": "잘못된 token",
|
||||
"Erroneous challenge": "잘못된 challenge",
|
||||
"Hidden field \"token\" is a required field": "숨겨진 필드 \"token\"은 필수 필드입니다",
|
||||
"Hidden field \"challenge\" is a required field": "숨겨진 필드 \"challenge\"는 필수 필드입니다",
|
||||
"Could not pull trending pages.": "인기 급상승 페이지를 가져올 수 없습니다.",
|
||||
|
@ -276,11 +275,9 @@
|
|||
"Please log in": "로그인하세요",
|
||||
"Password cannot be longer than 55 characters": "비밀번호는 55자 이하여야 합니다",
|
||||
"Password cannot be empty": "비밀번호는 비워둘 수 없습니다",
|
||||
"Wrong username or password": "잘못된 사용자 이름 또는 비밀번호",
|
||||
"error_invalid_username_or_password": "잘못된 사용자 이름 또는 비밀번호",
|
||||
"Password is a required field": "비밀번호는 필수 필드입니다",
|
||||
"User ID is a required field": "사용자 ID는 필수 필드입니다",
|
||||
"CAPTCHA is a required field": "CAPTCHA는 필수 필드입니다",
|
||||
"Erroneous CAPTCHA": "잘못된 CAPTCHA",
|
||||
"Blacklisted regions: ": "차단된 지역: ",
|
||||
"Playlists": "재생목록",
|
||||
"View as playlist": "재생목록으로 보기",
|
||||
|
|
|
@ -162,11 +162,9 @@
|
|||
"Show replies": "Rodyti atsakymus",
|
||||
"Incorrect password": "Slaptažodis neteisingas",
|
||||
"Wrong answer": "Atsakymas neteisingas",
|
||||
"Erroneous CAPTCHA": "Klaidinga CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA yra reikalinga šiam laukeliui",
|
||||
"User ID is a required field": "Vartotojo ID yra reikalingas šiam laukeliui",
|
||||
"Password is a required field": "Slaptažodis yra reikalingas šiam laukeliui",
|
||||
"Wrong username or password": "Neteisingas vartotojo vardas arba slaptažodis",
|
||||
"error_invalid_username_or_password": "Neteisingas vartotojo vardas arba slaptažodis",
|
||||
"Password cannot be empty": "Slaptažodžio laukelis negali būti tuščias",
|
||||
"Password cannot be longer than 55 characters": "Slaptažodis negali būti ilgesnis nei 55 simboliai",
|
||||
"Please log in": "Prašome prisijungti",
|
||||
|
@ -185,7 +183,6 @@
|
|||
"Could not pull trending pages.": "Nepavyko ištraukti tendencijų puslapių.",
|
||||
"Hidden field \"challenge\" is a required field": "Paslėptas laukas „iššūkis“ yra privalomas laukas",
|
||||
"Hidden field \"token\" is a required field": "Paslėptas laukas „žetonas“ yra privalomas laukas",
|
||||
"Erroneous challenge": "Klaidingas iššūkis",
|
||||
"Erroneous token": "Klaidingas žetonas",
|
||||
"No such user": "Nėra tokio vartotojo",
|
||||
"Token is expired, please try again": "Žetonas pasibaigęs, prašome bandyti dar kartą",
|
||||
|
|
|
@ -162,11 +162,9 @@
|
|||
"Show replies": "Vis svar",
|
||||
"Incorrect password": "Feil passord",
|
||||
"Wrong answer": "Ugyldig svar",
|
||||
"Erroneous CAPTCHA": "Ugyldig CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA er et påkrevd felt",
|
||||
"User ID is a required field": "Bruker-ID er et påkrevd felt",
|
||||
"Password is a required field": "Passord er et påkrevd felt",
|
||||
"Wrong username or password": "Ugyldig brukernavn eller passord",
|
||||
"error_invalid_username_or_password": "Ugyldig brukernavn eller passord",
|
||||
"Password cannot be empty": "Passordet kan ikke være tomt",
|
||||
"Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn",
|
||||
"Please log in": "Logg inn",
|
||||
|
@ -185,7 +183,6 @@
|
|||
"Could not pull trending pages.": "Kunne ikke hente trendsettende sider.",
|
||||
"Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt",
|
||||
"Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt",
|
||||
"Erroneous challenge": "Ugyldig utfordring",
|
||||
"Erroneous token": "Ugyldig symbol",
|
||||
"No such user": "Ugyldig bruker",
|
||||
"Token is expired, please try again": "Symbol utløpt, prøv igjen",
|
||||
|
|
|
@ -157,11 +157,9 @@
|
|||
"Show replies": "Antwoorden tonen",
|
||||
"Incorrect password": "Wachtwoord is onjuist",
|
||||
"Wrong answer": "Onjuist antwoord",
|
||||
"Erroneous CAPTCHA": "Onjuiste CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA is vereist",
|
||||
"User ID is a required field": "Gebruikers-id is vereist",
|
||||
"Password is a required field": "Wachtwoord is vereist",
|
||||
"Wrong username or password": "Onjuiste gebruikersnaam of wachtwoord",
|
||||
"error_invalid_username_or_password": "Onjuiste gebruikersnaam of wachtwoord",
|
||||
"Please sign in using 'Log in with Google'": "Log in via 'Inloggen met Google'",
|
||||
"Password cannot be empty": "Het wachtwoordveld mag niet leeg zijn",
|
||||
"Password cannot be longer than 55 characters": "Het wachtwoord mag niet langer dan 55 tekens zijn",
|
||||
|
@ -181,7 +179,6 @@
|
|||
"Could not pull trending pages.": "Kan uitgelichte pagina's niet ophalen.",
|
||||
"Hidden field \"challenge\" is a required field": "Verborgen veld \"uitdaging\" is vereist",
|
||||
"Hidden field \"token\" is a required field": "Verborgen veld \"toegangssleutel\" is vereist",
|
||||
"Erroneous challenge": "Ongeldige uitdaging",
|
||||
"Erroneous token": "Ongeldige toegangssleutel",
|
||||
"No such user": "Gebruiker bestaat niet",
|
||||
"Token is expired, please try again": "Toegangssleutel verlopen; probeer het opnieuw",
|
||||
|
|
|
@ -161,11 +161,9 @@
|
|||
"Show replies": "Pokaż odpowiedzi",
|
||||
"Incorrect password": "Niepoprawne hasło",
|
||||
"Wrong answer": "Niepoprawna odpowiedź",
|
||||
"Erroneous CAPTCHA": "CAPTCHA wykonane błędnie",
|
||||
"CAPTCHA is a required field": "CAPTCHA jest polem wymaganym",
|
||||
"User ID is a required field": "ID użytkownika jest polem wymaganym",
|
||||
"Password is a required field": "Hasło jest polem wymaganym",
|
||||
"Wrong username or password": "Niepoprawny login lub hasło",
|
||||
"error_invalid_username_or_password": "Niepoprawny login lub hasło",
|
||||
"Please sign in using 'Log in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"",
|
||||
"Password cannot be empty": "Hasło nie może być puste",
|
||||
"Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków",
|
||||
|
@ -185,7 +183,6 @@
|
|||
"Could not pull trending pages.": "Nie udało się pobrać strony na czasie.",
|
||||
"Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym",
|
||||
"Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym",
|
||||
"Erroneous challenge": "Niepoprawne wyzwanie",
|
||||
"Erroneous token": "Niepoprawny token",
|
||||
"No such user": "Niepoprawny użytkownik",
|
||||
"Token is expired, please try again": "Token wygasł, spróbuj ponownie",
|
||||
|
|
|
@ -164,11 +164,9 @@
|
|||
"Show replies": "Mostrar respostas",
|
||||
"Incorrect password": "Senha incorreta",
|
||||
"Wrong answer": "Resposta incorreta",
|
||||
"Erroneous CAPTCHA": "CAPTCHA inválido",
|
||||
"CAPTCHA is a required field": "O CAPTCHA é um campo obrigatório",
|
||||
"User ID is a required field": "O nome de usuário é um campo obrigatório",
|
||||
"Password is a required field": "A senha é um campo obrigatório",
|
||||
"Wrong username or password": "Nome de usuário ou senha inválidos",
|
||||
"error_invalid_username_or_password": "Nome de usuário ou senha inválidos",
|
||||
"Please sign in using 'Log in with Google'": "Por favor, entre usando 'Entrar com conta Google'",
|
||||
"Password cannot be empty": "A senha não pode ficar em branco",
|
||||
"Password cannot be longer than 55 characters": "A senha não pode ter mais que 55 caracteres",
|
||||
|
@ -188,7 +186,6 @@
|
|||
"Could not pull trending pages.": "Não foi possível obter as páginas dos vídeos em alta.",
|
||||
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
|
||||
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é obrigatório",
|
||||
"Erroneous challenge": "Desafio inválido",
|
||||
"Erroneous token": "Token inválido",
|
||||
"No such user": "Usuário inválido",
|
||||
"Token is expired, please try again": "Token expirou, tente novamente",
|
||||
|
|
|
@ -164,11 +164,9 @@
|
|||
"Show replies": "Mostrar respostas",
|
||||
"Incorrect password": "Palavra-chave incorreta",
|
||||
"Wrong answer": "Resposta errada",
|
||||
"Erroneous CAPTCHA": "CAPTCHA inválido",
|
||||
"CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
|
||||
"User ID is a required field": "O nome de utilizador é um campo obrigatório",
|
||||
"Password is a required field": "Palavra-chave é um campo obrigatório",
|
||||
"Wrong username or password": "Nome de utilizador ou palavra-chave incorreto",
|
||||
"error_invalid_username_or_password": "Nome de utilizador ou palavra-chave incorreto",
|
||||
"Please sign in using 'Log in with Google'": "Por favor, inicie sessão usando 'Iniciar sessão com o Google'",
|
||||
"Password cannot be empty": "A palavra-chave não pode estar vazia",
|
||||
"Password cannot be longer than 55 characters": "A palavra-chave não pode ser superior a 55 caracteres",
|
||||
|
@ -188,7 +186,6 @@
|
|||
"Could not pull trending pages.": "Não foi possível obter as páginas de tendências.",
|
||||
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
|
||||
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é um campo obrigatório",
|
||||
"Erroneous challenge": "Desafio inválido",
|
||||
"Erroneous token": "Token inválido",
|
||||
"No such user": "Utilizador inválido",
|
||||
"Token is expired, please try again": "Token expirou, tente novamente",
|
||||
|
|
|
@ -106,7 +106,6 @@
|
|||
"Token is expired, please try again": "Token expirou, tente novamente",
|
||||
"No such user": "Utilizador inválido",
|
||||
"Erroneous token": "Token inválido",
|
||||
"Erroneous challenge": "Desafio inválido",
|
||||
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é um campo obrigatório",
|
||||
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
|
||||
"Playlist does not exist.": "A lista de reprodução não existe.",
|
||||
|
@ -123,11 +122,9 @@
|
|||
"Password cannot be longer than 55 characters": "A palavra-chave não pode ser superior a 55 caracteres",
|
||||
"Password cannot be empty": "A palavra-chave não pode estar vazia",
|
||||
"Please sign in using 'Log in with Google'": "Por favor, inicie sessão usando 'Iniciar sessão com o Google'",
|
||||
"Wrong username or password": "Nome de utilizador ou palavra-chave incorreto",
|
||||
"error_invalid_username_or_password": "Nome de utilizador ou palavra-chave incorreto",
|
||||
"Password is a required field": "Palavra-chave é um campo obrigatório",
|
||||
"User ID is a required field": "O nome de utilizador é um campo obrigatório",
|
||||
"CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
|
||||
"Erroneous CAPTCHA": "CAPTCHA inválido",
|
||||
"Wrong answer": "Resposta errada",
|
||||
"Incorrect password": "Palavra-chave incorreta",
|
||||
"Show replies": "Mostrar respostas",
|
||||
|
|
|
@ -153,11 +153,9 @@
|
|||
"Show replies": "Afișați replicile",
|
||||
"Incorrect password": "Parolă incorectă",
|
||||
"Wrong answer": "Răspuns invalid",
|
||||
"Erroneous CAPTCHA": "CAPTCHA invalid",
|
||||
"CAPTCHA is a required field": "Câmpul CAPTCHA este obligatoriu",
|
||||
"User ID is a required field": "Câmpul ID Utilizator este obligatoriu",
|
||||
"Password is a required field": "Câmpul Parolă este obligatoriu",
|
||||
"Wrong username or password": "Nume de utilizator sau parolă invalidă",
|
||||
"error_invalid_username_or_password": "Nume de utilizator sau parolă invalidă",
|
||||
"Please sign in using 'Log in with Google'": "Vă rog conectați-vă folosind \"Conectați-vă cu Google\"",
|
||||
"Password cannot be empty": "Parola nu poate fi goală",
|
||||
"Password cannot be longer than 55 characters": "Parola nu poate să conțină mai mult de 55 de caractere",
|
||||
|
@ -177,7 +175,6 @@
|
|||
"Could not pull trending pages.": "Încărcarea paginilor de tendințe a eșuat.",
|
||||
"Hidden field \"challenge\" is a required field": "Câmpul ascuns \"challenge\" este un câmp obligatoriu",
|
||||
"Hidden field \"token\" is a required field": "Câmpul ascuns \"token\" este un câmp obligatoriu",
|
||||
"Erroneous challenge": "Challenge invalid",
|
||||
"Erroneous token": "Token invalid",
|
||||
"No such user": "Acest utilizator nu există",
|
||||
"Token is expired, please try again": "Jetonul a expirat, vă rugăm să încercați din nou",
|
||||
|
|
|
@ -162,11 +162,9 @@
|
|||
"Show replies": "Показать ответы",
|
||||
"Incorrect password": "Неправильный пароль",
|
||||
"Wrong answer": "Неправильный ответ",
|
||||
"Erroneous CAPTCHA": "Неправильная капча",
|
||||
"CAPTCHA is a required field": "Необходимо решить капчу",
|
||||
"User ID is a required field": "Необходимо ввести ID пользователя",
|
||||
"Password is a required field": "Необходимо ввести пароль",
|
||||
"Wrong username or password": "Неправильный логин или пароль",
|
||||
"error_invalid_username_or_password": "Неправильный логин или пароль",
|
||||
"Please sign in using 'Log in with Google'": "Пожалуйста, нажмите «Войти через Google»",
|
||||
"Password cannot be empty": "Пароль не может быть пустым",
|
||||
"Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов",
|
||||
|
@ -186,7 +184,6 @@
|
|||
"Could not pull trending pages.": "Не удаётся загрузить страницы «в тренде».",
|
||||
"Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле «challenge»",
|
||||
"Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле «токен»",
|
||||
"Erroneous challenge": "Неправильный ответ в «challenge»",
|
||||
"Erroneous token": "Неправильный токен",
|
||||
"No such user": "Пользователь не найден",
|
||||
"Token is expired, please try again": "Срок действия токена истёк, попробуйте позже",
|
||||
|
|
|
@ -126,7 +126,6 @@
|
|||
"comments_points_count_2": "{{count}} točke",
|
||||
"comments_points_count_3": "{{count}} točk",
|
||||
"Hidden field \"token\" is a required field": "Skrito polje »žeton« je zahtevano polje",
|
||||
"Erroneous challenge": "Napačen izziv",
|
||||
"English": "angleščina",
|
||||
"English (United States)": "angleščina (Združene države)",
|
||||
"Albanian": "albanščina",
|
||||
|
@ -329,10 +328,9 @@
|
|||
"Premieres in `x`": "Premiere v `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.": "Živjo! Izgleda, da imaš izklopljene JavaScripte . Klikni tukaj, če si želiš ogledati komentarje, vendar vedi, da bo lahko nalaganje trajajo nekoliko dlje.",
|
||||
"Show replies": "Pokaži odgovore",
|
||||
"Erroneous CAPTCHA": "Napačna CAPTCHA",
|
||||
"User ID is a required field": "ID uporabnika je obvezno polje",
|
||||
"Password is a required field": "Geslo je obvezno polje",
|
||||
"Wrong username or password": "Napačno uporabniško ime ali geslo",
|
||||
"error_invalid_username_or_password": "Napačno uporabniško ime ali geslo",
|
||||
"Password cannot be longer than 55 characters": "Geslo ne sme biti daljše od 55 znakov",
|
||||
"channel:`x`": "kanal: `x`",
|
||||
"Could not fetch comments": "Ni bilo mogoče pridobiti komentarjev",
|
||||
|
@ -350,7 +348,6 @@
|
|||
"Token is expired, please try again": "Žeton je potekel, poskusi znova",
|
||||
"English (United Kingdom)": "angleščina (Združeno kraljestvo)",
|
||||
"Wrong answer": "Napačen odgovor",
|
||||
"CAPTCHA is a required field": "CAPTCHA je obvezno polje",
|
||||
"Could not get channel info.": "Ni bilo mogoče dobiti informacij o kanalu.",
|
||||
"comments_view_x_replies_0": "Poglej {{count}} odgovor",
|
||||
"comments_view_x_replies_1": "Poglej {{count}} odgovora",
|
||||
|
|
|
@ -157,11 +157,9 @@
|
|||
"Blacklisted regions: ": "Rajone të palejuara: ",
|
||||
"Premieres in `x`": "Premiera në `x`",
|
||||
"Wrong answer": "Përgjigje e gabuar",
|
||||
"Erroneous CAPTCHA": "CAPTCHA e gabuar",
|
||||
"CAPTCHA is a required field": "CAPTCHA është fushë e domosdoshme",
|
||||
"User ID is a required field": "ID-ja e përdoruesit është fushë e domosdoshme",
|
||||
"Password is a required field": "Fusha e fjalëkalimit është e domosdoshme",
|
||||
"Wrong username or password": "Emër përdoruesi ose fjalëkalim i gabuar",
|
||||
"error_invalid_username_or_password": "Emër përdoruesi ose fjalëkalim i gabuar",
|
||||
"Please sign in using 'Log in with Google'": "Ju lutemi, bëni hyrjen duke përdorur “Bëni hyrjen me Google”",
|
||||
"Password cannot be empty": "Fjalëkalimi s’mund të jetë i zbrazët",
|
||||
"Password cannot be longer than 55 characters": "Fjalëkalimi s’mund të jetë më i gjatë se 55 shenja",
|
||||
|
|
|
@ -56,7 +56,6 @@
|
|||
"Editing playlist `x`": "Izmena plej liste `x`",
|
||||
"Please sign in using 'Log in with Google'": "Molimo Vas da se prijavite pomoću 'Log in with Google'",
|
||||
"Playlist does not exist.": "Nepostojeća plej lista.",
|
||||
"Erroneous challenge": "Pogrešan izazov",
|
||||
"Maltese": "Malteški",
|
||||
"Download": "Preuzmi",
|
||||
"Download as: ": "Preuzmi kao: ",
|
||||
|
@ -77,7 +76,7 @@
|
|||
"Switch Invidious Instance": "Promeni Invidious instancu",
|
||||
"Hide annotations": "Sakrij napomene",
|
||||
"User ID is a required field": "Korisnički ID je obavezno polje",
|
||||
"Wrong username or password": "Pogrešno korisničko ime ili lozinka",
|
||||
"error_invalid_username_or_password": "Pogrešno korisničko ime ili lozinka",
|
||||
"Please log in": "Molimo vas da se prijavite",
|
||||
"channel:`x`": "kanal:`x`",
|
||||
"Could not fetch comments": "Uzimanje komentara nije uspelo",
|
||||
|
@ -178,7 +177,6 @@
|
|||
"": "Prikaži `x` komentara"
|
||||
},
|
||||
"View Reddit comments": "Prikaži Reddit komentare",
|
||||
"CAPTCHA is a required field": "CAPTCHA je obavezno polje",
|
||||
"Croatian": "Hrvatski",
|
||||
"Estonian": "Estonski",
|
||||
"Filipino": "Filipino",
|
||||
|
@ -278,7 +276,6 @@
|
|||
"Wrong answer": "Pogrešan odgovor",
|
||||
"preferences_quality_label": "Preferirani video kvalitet: ",
|
||||
"Hide replies": "Sakrij odgovore",
|
||||
"Erroneous CAPTCHA": "Pogrešna CAPTCHA",
|
||||
"Erroneous token": "Pogrešan žeton",
|
||||
"Czech": "Češki",
|
||||
"Latin": "Latinski",
|
||||
|
|
|
@ -147,7 +147,6 @@
|
|||
"Burmese": "Бурмански",
|
||||
"preferences_quality_dash_label": "Преферирани квалитет DASH видео формата: ",
|
||||
"Erroneous token": "Погрешан жетон",
|
||||
"CAPTCHA is a required field": "CAPTCHA је обавезно поље",
|
||||
"No such user": "Непостојећи корисник",
|
||||
"Chinese (Traditional)": "Кинески (Традиционални)",
|
||||
"adminprefs_modified_source_code_url_label": "УРЛ веза до складишта са Измењеном Изворном Кодом",
|
||||
|
@ -191,7 +190,7 @@
|
|||
"preferences_category_misc": "Остала подешавања",
|
||||
"User ID is a required field": "Кориснички ИД је обавезно поље",
|
||||
"Password is a required field": "Лозинка је обавезно поље",
|
||||
"Wrong username or password": "Погрешно корисничко име или лозинка",
|
||||
"error_invalid_username_or_password": "Погрешно корисничко име или лозинка",
|
||||
"Please sign in using 'Log in with Google'": "Молимо Вас да се пријавите помоћу 'Log in with Google'",
|
||||
"Password cannot be empty": "Лозинка не може бити празна",
|
||||
"Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 карактера",
|
||||
|
@ -267,7 +266,6 @@
|
|||
"Shared `x`": "Подељено `x`",
|
||||
"Playlists": "Плеј листе",
|
||||
"Yoruba": "Јоруба",
|
||||
"Erroneous challenge": "Погрешан изазов",
|
||||
"Danish": "Дански",
|
||||
"Could not get channel info.": "Узимање података о каналу није успело.",
|
||||
"search_filters_features_option_hd": "HD",
|
||||
|
@ -352,7 +350,6 @@
|
|||
"footer_source_code": "Изворна Кода",
|
||||
"search_filters_features_option_three_d": "3D",
|
||||
"search_filters_features_option_four_k": "4K",
|
||||
"Erroneous CAPTCHA": "Погрешна CAPTCHA",
|
||||
"`x` ago": "пре `x`",
|
||||
"Arabic": "Арапски",
|
||||
"Bulgarian": "Бугарски",
|
||||
|
|
|
@ -160,11 +160,9 @@
|
|||
"Show replies": "Visa svar",
|
||||
"Incorrect password": "Fel lösenord",
|
||||
"Wrong answer": "Fel svar",
|
||||
"Erroneous CAPTCHA": "Ogiltig CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA är ett obligatoriskt fält",
|
||||
"User ID is a required field": "Användar-ID är ett obligatoriskt fält",
|
||||
"Password is a required field": "Lösenord är ett obligatoriskt fält",
|
||||
"Wrong username or password": "Ogiltigt användarnamn eller lösenord",
|
||||
"error_invalid_username_or_password": "Ogiltigt användarnamn eller lösenord",
|
||||
"Please sign in using 'Log in with Google'": "Logga in genom \"Google-inloggning\"",
|
||||
"Password cannot be empty": "Lösenordet kan inte vara tomt",
|
||||
"Password cannot be longer than 55 characters": "Lösenordet kan inte vara längre än 55 tecken",
|
||||
|
@ -184,7 +182,6 @@
|
|||
"Could not pull trending pages.": "Kunde inte hämta trendande sidor.",
|
||||
"Hidden field \"challenge\" is a required field": "Dolt fält \"challenge\" är ett obligatoriskt fält",
|
||||
"Hidden field \"token\" is a required field": "Dolt fält \"token\" är ett obligatoriskt fält",
|
||||
"Erroneous challenge": "Felaktig challenge",
|
||||
"Erroneous token": "Felaktig token",
|
||||
"No such user": "Ogiltig användare",
|
||||
"Token is expired, please try again": "Token föråldrad, försök igen",
|
||||
|
|
|
@ -162,11 +162,9 @@
|
|||
"Show replies": "Cevapları göster",
|
||||
"Incorrect password": "Yanlış parola",
|
||||
"Wrong answer": "Yanlış cevap",
|
||||
"Erroneous CAPTCHA": "Hatalı CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA 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",
|
||||
"Wrong username or password": "Yanlış kullanıcı adı ya da parola",
|
||||
"error_invalid_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",
|
||||
"Password cannot be empty": "Parola boş olamaz",
|
||||
"Password cannot be longer than 55 characters": "Parola 55 karakterden uzun olamaz",
|
||||
|
@ -186,7 +184,6 @@
|
|||
"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 \"token\" is a required field": "\"belirteç\" gizli alanı zorunlu bir alandır",
|
||||
"Erroneous challenge": "Hatalı challenge",
|
||||
"Erroneous token": "Hatalı belirteç",
|
||||
"No such user": "Böyle bir kullanıcı yok",
|
||||
"Token is expired, please try again": "Belirtecin süresi doldu, lütfen tekrar deneyin",
|
||||
|
|
|
@ -153,11 +153,9 @@
|
|||
"Show replies": "Показати відповіді",
|
||||
"Incorrect password": "Неправильний пароль",
|
||||
"Wrong answer": "Неправильна відповідь",
|
||||
"Erroneous CAPTCHA": "Неправильна капча",
|
||||
"CAPTCHA is a required field": "Необхідно пройти CAPTCHA",
|
||||
"User ID is a required field": "Необхідно ввести ID користувача",
|
||||
"Password is a required field": "Необхідно ввести пароль",
|
||||
"Wrong username or password": "Неправильний логін чи пароль",
|
||||
"error_invalid_username_or_password": "Неправильний логін чи пароль",
|
||||
"Please sign in using 'Log in with Google'": "Будь ласка, натисніть «Увійти через Google»",
|
||||
"Password cannot be empty": "Пароль не може бути порожнім",
|
||||
"Password cannot be longer than 55 characters": "Пароль не може бути довшим за 55 знаків",
|
||||
|
@ -177,7 +175,6 @@
|
|||
"Could not pull trending pages.": "Не вдається завантажити сторінки «у тренді».",
|
||||
"Hidden field \"challenge\" is a required field": "Необхідно заповнити приховане поле «challenge»",
|
||||
"Hidden field \"token\" is a required field": "Необхідно заповнити приховане поле «token»",
|
||||
"Erroneous challenge": "Неправильна відповідь у «challenge»",
|
||||
"Erroneous token": "Недійсний токен",
|
||||
"No such user": "Недопустиме ім’я користувача",
|
||||
"Token is expired, please try again": "Термін дії токена закінчився, спробуйте пізніше",
|
||||
|
|
|
@ -150,11 +150,9 @@
|
|||
"Show replies": "Hiển thị câu trả lời",
|
||||
"Incorrect password": "Mật khẩu không đúng",
|
||||
"Wrong answer": "Câu trả lời sai",
|
||||
"Erroneous CAPTCHA": "CAPTCHA bị lỗi",
|
||||
"CAPTCHA is a required field": "CAPTCHA là trường bắt buộc",
|
||||
"User ID is a required field": "User ID là trường bắt buộc",
|
||||
"Password is a required field": "Mật khẩu là trường bắt buộc",
|
||||
"Wrong username or password": "Tên người dùng hoặc mật khẩu sai",
|
||||
"error_invalid_username_or_password": "Tên người dùng hoặc mật khẩu sai",
|
||||
"Password cannot be empty": "Mật khẩu không được để trống",
|
||||
"Password cannot be longer than 55 characters": "Mật khẩu không được dài hơn 55 ký tự",
|
||||
"Please log in": "Xin vui lòng đăng nhập",
|
||||
|
@ -171,7 +169,6 @@
|
|||
"Could not pull trending pages.": "Không thể kéo các trang thịnh hành.",
|
||||
"Hidden field \"challenge\" is a required field": "Trường ẩn \"challenge\" là trường bắt buộc",
|
||||
"Hidden field \"token\" is a required field": "Trường ẩn \"token\" là trường bắt buộc",
|
||||
"Erroneous challenge": "Thử thách sai",
|
||||
"Erroneous token": "Mã thông báo bị lỗi",
|
||||
"No such user": "Không có người dùng như vậy",
|
||||
"Token is expired, please try again": "Token đã hết hạn, vui lòng thử lại",
|
||||
|
|
|
@ -169,11 +169,9 @@
|
|||
"Show replies": "显示回复",
|
||||
"Incorrect password": "密码错误",
|
||||
"Wrong answer": "错误的回复",
|
||||
"Erroneous CAPTCHA": "验证码错误",
|
||||
"CAPTCHA is a required field": "验证码必填",
|
||||
"User ID is a required field": "用户名必填",
|
||||
"Password is a required field": "密码必填",
|
||||
"Wrong username or password": "用户名或密码错误",
|
||||
"error_invalid_username_or_password": "用户名或密码错误",
|
||||
"Password cannot be empty": "密码不能为空",
|
||||
"Password cannot be longer than 55 characters": "密码长度不能大于 55",
|
||||
"Please log in": "请登录",
|
||||
|
@ -194,7 +192,6 @@
|
|||
"Could not pull trending pages.": "无法获取“时下流行”页面。",
|
||||
"Hidden field \"challenge\" is a required field": "隐藏表单项 \"challenge\" 为必填",
|
||||
"Hidden field \"token\" is a required field": "隐藏表单项 \"token\" 为必填",
|
||||
"Erroneous challenge": "错误的验证回复(challenge)",
|
||||
"Erroneous token": "错误的令牌",
|
||||
"No such user": "用户不存在",
|
||||
"Token is expired, please try again": "令牌过期,请重试",
|
||||
|
|
|
@ -169,11 +169,9 @@
|
|||
"Show replies": "顯示回覆",
|
||||
"Incorrect password": "不正確的密碼",
|
||||
"Wrong answer": "錯誤的答案",
|
||||
"Erroneous CAPTCHA": "錯誤的 CAPTCHA",
|
||||
"CAPTCHA is a required field": "CAPTCHA 為必填欄位",
|
||||
"User ID is a required field": "使用者 ID 為必填欄位",
|
||||
"Password is a required field": "密碼為必填欄位",
|
||||
"Wrong username or password": "錯誤的使用者名稱或密碼",
|
||||
"error_invalid_username_or_password": "錯誤的使用者名稱或密碼",
|
||||
"Password cannot be empty": "密碼不能為空",
|
||||
"Password cannot be longer than 55 characters": "密碼不能長於55個字元",
|
||||
"Please log in": "請登入",
|
||||
|
@ -194,7 +192,6 @@
|
|||
"Could not pull trending pages.": "無法拉取趨勢頁面。",
|
||||
"Hidden field \"challenge\" is a required field": "隱藏的欄位 \"challenge\" 是必填欄位",
|
||||
"Hidden field \"token\" is a required field": "隱藏的欄位 \"token\" 是必填欄位",
|
||||
"Erroneous challenge": "錯誤的 challenge",
|
||||
"Erroneous token": "錯誤的 token",
|
||||
"No such user": "無此使用者",
|
||||
"Token is expired, please try again": "Token 已過期,請再試一次",
|
||||
|
|
135
src/invidious/frontend/login_register.cr
Normal file
135
src/invidious/frontend/login_register.cr
Normal file
|
@ -0,0 +1,135 @@
|
|||
module Invidious::Frontend::LoginRegister
|
||||
extend self
|
||||
|
||||
# HTML form input template
|
||||
#
|
||||
# `id` defines the CSS `id` attribute, as well as the localized label text.
|
||||
# `type` defines the type of input (text, password, etc...)
|
||||
private macro text_input(id, type)
|
||||
str << %(\t\t\t<div class="pure-control-group">\n)
|
||||
str << "\t\t\t\t<label for='{{id}}'>"
|
||||
str << translate(locale, "login_page_{{id}}_label")
|
||||
str << "</label><input type='{{type}}' id='{{id}}'>\n"
|
||||
str << "\t\t\t</div>\n"
|
||||
end
|
||||
|
||||
# Submit button template
|
||||
#
|
||||
# `variant` provided defines the CSS class name, as well
|
||||
# as the associated localized text string.
|
||||
private macro submit_button(variant)
|
||||
str << %(\t<div class="pure-controls {{variant}}-submit-button">\n)
|
||||
str << %(\t\t<button type='submit' class="pure-button pure-button-primary">)
|
||||
str << translate(locale, "login_page_{{variant}}_button")
|
||||
str << "</button>\n"
|
||||
str << "\t</div>\n"
|
||||
end
|
||||
|
||||
# Generate the log-in form's HTML
|
||||
def gen_login_form(
|
||||
env : HTTP::Server::Context,
|
||||
account_type : String = "invidious",
|
||||
captcha : User::Captcha? = nil
|
||||
) : String
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
|
||||
# Create the parameters for the form URL
|
||||
params = HTTP::Params.new
|
||||
params["type"] = account_type
|
||||
|
||||
if referer = env.get?("referer").try &.as(String)
|
||||
params["referer"] = referer
|
||||
end
|
||||
|
||||
url = URI.new(path: "/login", query: params)
|
||||
|
||||
return String.build(1200) do |str|
|
||||
# Begin log-in form
|
||||
str << %(\t<form class="pure-form" method='post' action=") << url << %(">)
|
||||
|
||||
# Form content
|
||||
case account_type
|
||||
when "invidious"
|
||||
# Text inputs
|
||||
str << %(\t\t<div class="username-pass-fields">)
|
||||
str << %(<fieldset class="pure-form-aligned">\n)
|
||||
|
||||
text_input(username, text)
|
||||
text_input(password, password)
|
||||
|
||||
str << "\t\t</fieldset></div>\n"
|
||||
|
||||
# Captcha, if required
|
||||
if !captcha.nil? && !captcha.type.none?
|
||||
str << rendered "components/captcha"
|
||||
end
|
||||
end
|
||||
|
||||
# End of log-in form
|
||||
str << "\t</form>\n"
|
||||
|
||||
submit_button(login)
|
||||
|
||||
# Prompt for the register page (we reuse the form's
|
||||
# uri object for simplicity sake)
|
||||
url.path = "/register"
|
||||
|
||||
str << "\t<p>"
|
||||
str << translate(locale, "login_page_goto_register_prompt", url.to_s)
|
||||
str << "</p>\n"
|
||||
end
|
||||
end
|
||||
|
||||
# Generate the registration form's HTML
|
||||
def gen_register_form(
|
||||
env : HTTP::Server::Context,
|
||||
captcha : User::Captcha?
|
||||
) : String
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
|
||||
# Create the parameters for the form URL
|
||||
params = HTTP::Params.new
|
||||
if referer = env.get?("referer").try &.as(String)
|
||||
params["referer"] = referer
|
||||
end
|
||||
|
||||
url = URI.new(path: "/register", query: params)
|
||||
|
||||
return String.build(1200) do |str|
|
||||
# Begin registration form
|
||||
str << %(\t<form class="pure-form" method='post' action=") << url << %(">)
|
||||
|
||||
# Text inputs
|
||||
str << %(\t\t<div class="username-pass-fields">)
|
||||
str << %(<fieldset class="pure-form-aligned">\n)
|
||||
|
||||
text_input(username, text)
|
||||
str << "<br/>"
|
||||
|
||||
text_input(password, password)
|
||||
text_input(confirm, password)
|
||||
|
||||
str << "\t\t</fieldset></div>\n"
|
||||
|
||||
# Captcha, if required
|
||||
if !captcha.nil? && !captcha.type.none?
|
||||
str << rendered "components/captcha"
|
||||
end
|
||||
|
||||
# End of registration form
|
||||
str << "\t</form>\n"
|
||||
|
||||
submit_button(register)
|
||||
|
||||
# Prompt for the login page (we reuse the form's
|
||||
# uri object for simplicity sake)
|
||||
url.path = "/login"
|
||||
|
||||
str << "\t<p>"
|
||||
str << translate(locale, "login_page_goto_login_prompt", url.to_s)
|
||||
str << "</p>\n"
|
||||
|
||||
str << "</div>"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -48,9 +48,9 @@ module Invidious::Routes::Account
|
|||
return error_template(400, ex)
|
||||
end
|
||||
|
||||
password = env.params.body["password"]?
|
||||
if !password
|
||||
return error_template(401, "Password is a required field")
|
||||
old_password = env.params.body["password"]?
|
||||
if old_password.nil? || old_password.empty?
|
||||
return error_template(401, "error_required_field_password")
|
||||
end
|
||||
|
||||
new_passwords = env.params.body.select { |k, v| k.match(/^new_password\[\d+\]$/) }.map { |k, v| v }
|
||||
|
@ -68,7 +68,7 @@ module Invidious::Routes::Account
|
|||
return error_template(400, "Password cannot be longer than 55 characters")
|
||||
end
|
||||
|
||||
if !Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
|
||||
if !user.validate_password(old_password)
|
||||
return error_template(401, "Incorrect password")
|
||||
end
|
||||
|
||||
|
|
|
@ -3,187 +3,160 @@
|
|||
module Invidious::Routes::Login
|
||||
def self.login_page(env)
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
|
||||
user = env.get? "user"
|
||||
|
||||
return env.redirect "/feed/subscriptions" if user
|
||||
|
||||
if !CONFIG.login_enabled
|
||||
return error_template(400, "Login has been disabled by administrator.")
|
||||
end
|
||||
|
||||
referer = get_referer(env, "/feed/subscriptions")
|
||||
|
||||
email = nil
|
||||
password = nil
|
||||
captcha = nil
|
||||
return env.redirect referer if env.get? "user"
|
||||
|
||||
account_type = env.params.query["type"]?
|
||||
account_type ||= "invidious"
|
||||
if !CONFIG.login_enabled
|
||||
return error_template(403, "error_login_disabled")
|
||||
end
|
||||
|
||||
captcha_type = env.params.query["captcha"]?
|
||||
captcha_type ||= "image"
|
||||
account_type = env.params.query["type"]? || "invidious"
|
||||
|
||||
tfa = env.params.query["tfa"]?
|
||||
prompt = nil
|
||||
captcha_type = User::Captcha.parse_type(env.params.query)
|
||||
captcha = User::Captcha.generate(captcha_type)
|
||||
|
||||
templated "user/login"
|
||||
return templated "user/login"
|
||||
end
|
||||
|
||||
def self.login(env)
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
|
||||
referer = get_referer(env, "/feed/subscriptions")
|
||||
|
||||
if !CONFIG.login_enabled
|
||||
return error_template(403, "Login has been disabled by administrator.")
|
||||
return error_template(403, "error_login_disabled")
|
||||
end
|
||||
|
||||
# https://stackoverflow.com/a/574698
|
||||
email = env.params.body["email"]?.try &.downcase.byte_slice(0, 254)
|
||||
password = env.params.body["password"]?
|
||||
# Verify captcha
|
||||
if CONFIG.captcha_enabled
|
||||
begin
|
||||
captcha_verified = User::Captcha.verify(env)
|
||||
raise InfoException.new("error_invalid_captcha") if !captcha_verified
|
||||
rescue ex
|
||||
return error_template(400, ex)
|
||||
end
|
||||
end
|
||||
|
||||
account_type = env.params.query["type"]?
|
||||
account_type ||= "invidious"
|
||||
account_type = env.params.query["type"]? || "invidious"
|
||||
|
||||
case account_type
|
||||
when "invidious"
|
||||
if !email
|
||||
return error_template(401, "User ID is a required field")
|
||||
# https://stackoverflow.com/a/574698
|
||||
username = env.params.body["username"]?.try &.downcase.byte_slice(0, 254)
|
||||
password = env.params.body["password"]?
|
||||
|
||||
if username.nil? || username.empty? || password.nil? || password.empty?
|
||||
return error_template(403, "error_invalid_username_or_password")
|
||||
end
|
||||
|
||||
if !password
|
||||
return error_template(401, "Password is a required field")
|
||||
end
|
||||
|
||||
user = Invidious::Database::Users.select(email: email)
|
||||
|
||||
if user
|
||||
if Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
|
||||
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
|
||||
Invidious::Database::SessionIDs.insert(sid, email)
|
||||
|
||||
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
|
||||
else
|
||||
return error_template(401, "Wrong username or password")
|
||||
end
|
||||
|
||||
# Since this user has already registered, we don't want to overwrite their preferences
|
||||
if env.request.cookies["PREFS"]?
|
||||
cookie = env.request.cookies["PREFS"]
|
||||
cookie.expires = Time.utc(1990, 1, 1)
|
||||
env.response.cookies << cookie
|
||||
end
|
||||
else
|
||||
if !CONFIG.registration_enabled
|
||||
return error_template(400, "Registration has been disabled by administrator.")
|
||||
end
|
||||
|
||||
if password.empty?
|
||||
return error_template(401, "Password cannot be empty")
|
||||
end
|
||||
|
||||
# See https://security.stackexchange.com/a/39851
|
||||
if password.bytesize > 55
|
||||
return error_template(400, "Password cannot be longer than 55 characters")
|
||||
end
|
||||
|
||||
password = password.byte_slice(0, 55)
|
||||
|
||||
if CONFIG.captcha_enabled
|
||||
captcha_type = env.params.body["captcha_type"]?
|
||||
answer = env.params.body["answer"]?
|
||||
change_type = env.params.body["change_type"]?
|
||||
|
||||
if !captcha_type || change_type
|
||||
if change_type
|
||||
captcha_type = change_type
|
||||
end
|
||||
captcha_type ||= "image"
|
||||
|
||||
account_type = "invidious"
|
||||
tfa = false
|
||||
prompt = ""
|
||||
|
||||
if captcha_type == "image"
|
||||
captcha = Invidious::User::Captcha.generate_image(HMAC_KEY)
|
||||
else
|
||||
captcha = Invidious::User::Captcha.generate_text(HMAC_KEY)
|
||||
end
|
||||
|
||||
return templated "user/login"
|
||||
end
|
||||
|
||||
tokens = env.params.body.select { |k, _| k.match(/^token\[\d+\]$/) }.map { |_, v| v }
|
||||
|
||||
answer ||= ""
|
||||
captcha_type ||= "image"
|
||||
|
||||
case captcha_type
|
||||
when "image"
|
||||
answer = answer.lstrip('0')
|
||||
answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
|
||||
|
||||
begin
|
||||
validate_request(tokens[0], answer, env.request, HMAC_KEY, locale)
|
||||
rescue ex
|
||||
return error_template(400, ex)
|
||||
end
|
||||
else # "text"
|
||||
answer = Digest::MD5.hexdigest(answer.downcase.strip)
|
||||
|
||||
if tokens.empty?
|
||||
return error_template(500, "Erroneous CAPTCHA")
|
||||
end
|
||||
|
||||
found_valid_captcha = false
|
||||
error_exception = Exception.new
|
||||
tokens.each do |tok|
|
||||
begin
|
||||
validate_request(tok, answer, env.request, HMAC_KEY, locale)
|
||||
found_valid_captcha = true
|
||||
rescue ex
|
||||
error_exception = ex
|
||||
end
|
||||
end
|
||||
|
||||
if !found_valid_captcha
|
||||
return error_template(500, error_exception)
|
||||
end
|
||||
end
|
||||
end
|
||||
user = Database::Users.select(email: username)
|
||||
|
||||
if !user.nil? && user.validate_password(password)
|
||||
# Generate session ID and store it.
|
||||
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
|
||||
user, sid = create_user(sid, email, password)
|
||||
|
||||
if language_header = env.request.headers["Accept-Language"]?
|
||||
if language = ANG.language_negotiator.best(language_header, LOCALES.keys)
|
||||
user.preferences.locale = language.header
|
||||
end
|
||||
begin
|
||||
Database::SessionIDs.insert(sid, username)
|
||||
rescue ex
|
||||
return error_template(500, "error_database_unavailable")
|
||||
end
|
||||
|
||||
Invidious::Database::Users.insert(user)
|
||||
Invidious::Database::SessionIDs.insert(sid, email)
|
||||
|
||||
view_name = "subscriptions_#{sha256(user.email)}"
|
||||
PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
|
||||
|
||||
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
|
||||
|
||||
if env.request.cookies["PREFS"]?
|
||||
user.preferences = env.get("preferences").as(Preferences)
|
||||
Invidious::Database::Users.update_preferences(user)
|
||||
|
||||
cookie = env.request.cookies["PREFS"]
|
||||
cookie.expires = Time.utc(1990, 1, 1)
|
||||
env.response.cookies << cookie
|
||||
end
|
||||
# Generate cookies
|
||||
env.response.cookies["SID"] = User::Cookies.sid(CONFIG.domain, sid)
|
||||
env.response.cookies["PREFS"] = User::Cookies.prefs(CONFIG.domain, user.preferences)
|
||||
else
|
||||
return error_template(403, "error_invalid_username_or_password")
|
||||
end
|
||||
|
||||
env.redirect referer
|
||||
else
|
||||
env.redirect referer
|
||||
end
|
||||
|
||||
return env.redirect referer
|
||||
end
|
||||
|
||||
def self.register_page(env)
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
referer = get_referer(env, "/feed/subscriptions")
|
||||
|
||||
return env.redirect referer if env.get? "user"
|
||||
|
||||
if !CONFIG.registration_enabled
|
||||
return error_template(403, "error_registration_disabled")
|
||||
end
|
||||
|
||||
captcha_type = User::Captcha.parse_type(env.params.query)
|
||||
captcha = User::Captcha.generate(captcha_type)
|
||||
|
||||
return templated "user/register"
|
||||
end
|
||||
|
||||
def self.register(env)
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
referer = get_referer(env, "/feed/subscriptions")
|
||||
|
||||
if !CONFIG.registration_enabled
|
||||
return error_template(403, "error_registration_disabled")
|
||||
end
|
||||
|
||||
# https://stackoverflow.com/a/574698
|
||||
username = env.params.body["username"]?.try &.downcase.byte_slice(0, 254)
|
||||
password = env.params.body["password"]?
|
||||
confirm = env.params.body["confirm"]?
|
||||
|
||||
if username.nil? || username.empty?
|
||||
return error_template(400, "error_required_field_username")
|
||||
end
|
||||
|
||||
if password.nil? || password.empty? || confirm.nil? || confirm.empty?
|
||||
return error_template(400, "error_required_field_password")
|
||||
end
|
||||
|
||||
if password != confirm
|
||||
return error_template(400, "error_passwords_dont_match")
|
||||
end
|
||||
|
||||
# TODO: find a way to allow longer passwords
|
||||
# See https://security.stackexchange.com/a/39851
|
||||
if password.bytesize > 55
|
||||
return error_template(400, "Password cannot be longer than 55 characters")
|
||||
end
|
||||
|
||||
# Verify captcha
|
||||
if CONFIG.captcha_enabled
|
||||
begin
|
||||
captcha_verified = User::Captcha.verify(env)
|
||||
raise InfoException.new("error_invalid_captcha") if !captcha_verified
|
||||
rescue ex
|
||||
return error_template(400, ex)
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure that user doesn't exist!!
|
||||
user_check = Database::Users.select(email: username)
|
||||
if !user_check.nil?
|
||||
return error_template(400, "error_username_already_registered")
|
||||
end
|
||||
|
||||
# Generate session ID
|
||||
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
|
||||
user = User.create(sid, username, password)
|
||||
|
||||
# Use the preferences from user cookie (pre-registration)
|
||||
# and save them into the account. Otherwise, make a new one.
|
||||
if env.request.cookies["PREFS"]?
|
||||
user.preferences = env.get("preferences").as(Preferences)
|
||||
end
|
||||
|
||||
# Create the proper DN
|
||||
# TODO: use DB transaction here to avoid corrupted states
|
||||
Database::Users.insert(user)
|
||||
Database::SessionIDs.insert(sid, username)
|
||||
|
||||
view_name = "subscriptions_#{sha256(user.email)}"
|
||||
PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
|
||||
|
||||
# Generate cookies
|
||||
env.response.cookies["SID"] = User::Cookies.sid(CONFIG.domain, sid)
|
||||
env.response.cookies["PREFS"] = User::Cookies.prefs(CONFIG.domain, user.preferences)
|
||||
|
||||
return env.redirect referer
|
||||
end
|
||||
|
||||
def self.signout(env)
|
||||
|
@ -214,6 +187,6 @@ module Invidious::Routes::Login
|
|||
env.response.cookies << cookie
|
||||
end
|
||||
|
||||
env.redirect referer
|
||||
return env.redirect referer
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,9 +51,11 @@ module Invidious::Routing
|
|||
# -------------------
|
||||
|
||||
def register_user_routes
|
||||
# User login/out
|
||||
# User login/out and registration
|
||||
get "/login", Routes::Login, :login_page
|
||||
post "/login", Routes::Login, :login
|
||||
get "/register", Routes::Login, :register_page
|
||||
post "/register", Routes::Login, :register
|
||||
post "/signout", Routes::Login, :signout
|
||||
|
||||
# User preferences
|
||||
|
|
|
@ -1,12 +1,57 @@
|
|||
require "openssl/hmac"
|
||||
|
||||
struct Invidious::User
|
||||
module Captcha
|
||||
extend self
|
||||
|
||||
struct Captcha
|
||||
private TEXTCAPTCHA_URL = URI.parse("https://textcaptcha.com")
|
||||
|
||||
def generate_image(key)
|
||||
# Structure that holds the type, the question string and the
|
||||
# cryptographically signed response(s).
|
||||
getter type : Type
|
||||
getter question : String
|
||||
getter tokens : Array(String)
|
||||
|
||||
def initialize(@type, @question, @tokens)
|
||||
end
|
||||
|
||||
# -------------------
|
||||
# Type parsing
|
||||
# -------------------
|
||||
|
||||
enum Type
|
||||
None
|
||||
Text
|
||||
Image
|
||||
end
|
||||
|
||||
def self.parse_type(params : HTTP::Params) : Type
|
||||
if CONFIG.captcha_enabled
|
||||
type_text = params["captcha"]? || "image"
|
||||
type = Type.parse?(type_text) || Type::Image
|
||||
|
||||
# You opened the dev tools, didn't you? :P
|
||||
type = Type::Image if type.none?
|
||||
else
|
||||
type = Type::None
|
||||
end
|
||||
|
||||
return type
|
||||
end
|
||||
|
||||
# -------------------
|
||||
# Generators
|
||||
# -------------------
|
||||
|
||||
# High-level method that calls the captcha generator for the given type.
|
||||
def self.generate(type : Type) : Captcha?
|
||||
case type
|
||||
when .image? then return gen_image_captcha(HMAC_KEY)
|
||||
when .text? then return gen_text_captcha(HMAC_KEY)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
private def self.gen_image_captcha(key) : Captcha
|
||||
second = Random::Secure.rand(12)
|
||||
second_angle = second * 30
|
||||
second = second * 5
|
||||
|
@ -17,9 +62,6 @@ struct Invidious::User
|
|||
|
||||
hour = Random::Secure.rand(12)
|
||||
hour_angle = hour * 30 + minute_angle.to_f / 12
|
||||
if hour == 0
|
||||
hour = 12
|
||||
end
|
||||
|
||||
clock_svg = <<-END_SVG
|
||||
<svg viewBox="0 0 100 100" width="200px" height="200px">
|
||||
|
@ -52,16 +94,43 @@ struct Invidious::User
|
|||
Base64.strict_encode(proc.output.gets_to_end)
|
||||
end
|
||||
|
||||
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
|
||||
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
|
||||
answer_raw = self.format_time(hour, minute, second, validate: false)
|
||||
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer_raw)
|
||||
|
||||
return {
|
||||
LOGGER.trace("Captcha: image question is #{answer_raw} (anwser digest: #{answer})")
|
||||
|
||||
return Captcha.new(
|
||||
type: Type::Image,
|
||||
question: image,
|
||||
tokens: {generate_response(answer, {":login"}, key, use_nonce: true)},
|
||||
}
|
||||
tokens: [generate_response(answer, {":login"}, key, use_nonce: true)],
|
||||
)
|
||||
end
|
||||
|
||||
def generate_text(key)
|
||||
private def self.format_time(hours : Int, minutes : Int, seconds : Int, *, validate : Bool)
|
||||
# Check for incorrect answers
|
||||
if validate
|
||||
raise Exception.new if !(0..23).includes?(hours)
|
||||
raise Exception.new if !(0..59).includes?(minutes)
|
||||
raise Exception.new if !(0..59).includes?(seconds)
|
||||
end
|
||||
|
||||
# Normalize hours
|
||||
case hours
|
||||
when .zero? then hours = 12
|
||||
when .> 12 then hours -= 12
|
||||
end
|
||||
|
||||
# Craft answer string
|
||||
return String.build(8) do |answer|
|
||||
answer << hours.to_s(precision: 2)
|
||||
answer << ':'
|
||||
answer << minutes.to_s(precision: 2)
|
||||
answer << ':'
|
||||
answer << seconds.to_s(precision: 2)
|
||||
end
|
||||
end
|
||||
|
||||
private def self.gen_text_captcha(key) : Captcha
|
||||
response = make_client(TEXTCAPTCHA_URL, &.get("/github.com/iv.org/invidious.json").body)
|
||||
response = JSON.parse(response)
|
||||
|
||||
|
@ -69,10 +138,70 @@ struct Invidious::User
|
|||
generate_response(answer.as_s, {":login"}, key, use_nonce: true)
|
||||
end
|
||||
|
||||
return {
|
||||
question: response["q"].as_s,
|
||||
tokens: tokens,
|
||||
}
|
||||
question = response["q"].as_s
|
||||
|
||||
LOGGER.trace("Captcha: text question is #{question}: (answers digests: #{tokens})")
|
||||
|
||||
return Captcha.new(
|
||||
type: Type::Text,
|
||||
question: question,
|
||||
tokens: tokens,
|
||||
)
|
||||
end
|
||||
|
||||
# -------------------
|
||||
# Validation
|
||||
# -------------------
|
||||
|
||||
# Return true if the captcha was succesfully validated
|
||||
# Otherwise, raise the appropriate Exception
|
||||
def self.verify(env) : Bool
|
||||
captcha_type = self.parse_type(env.params.body)
|
||||
|
||||
answer = env.params.body["answer"]? || ""
|
||||
tokens = env.params.body.fetch_all("token")
|
||||
|
||||
if answer.empty? || tokens.empty?
|
||||
LOGGER.debug("Captcha: validate: got error_invalid_captcha, answer or token is empty")
|
||||
raise InfoException.new("error_invalid_captcha")
|
||||
end
|
||||
|
||||
case captcha_type
|
||||
when .image?
|
||||
begin
|
||||
hours, minutes, seconds = answer.split(':').map &.to_i
|
||||
answer = self.format_time(hours, minutes, seconds, validate: true)
|
||||
rescue ex
|
||||
LOGGER.debug("Captcha: validate: got error_invalid_captcha, answer to image captcha failed to parse")
|
||||
raise InfoException.new("error_invalid_captcha")
|
||||
end
|
||||
|
||||
answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
|
||||
|
||||
# Raises on error
|
||||
validate_request(tokens[0], answer, env.request, HMAC_KEY)
|
||||
return true
|
||||
when .text?
|
||||
answer = Digest::MD5.hexdigest(answer.downcase.strip)
|
||||
|
||||
error_exception = InfoException.new
|
||||
|
||||
tokens.each do |tok|
|
||||
begin
|
||||
# Raises on error
|
||||
validate_request(tok, answer, env.request, HMAC_KEY)
|
||||
return true
|
||||
rescue ex
|
||||
error_exception = ex
|
||||
end
|
||||
end
|
||||
|
||||
LOGGER.debug("Captcha: validate: bad answer to text captcha")
|
||||
raise error_exception
|
||||
end
|
||||
|
||||
# Just to be safe
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,16 +4,36 @@ struct Invidious::User
|
|||
include DB::Serializable
|
||||
|
||||
property updated : Time
|
||||
property notifications : Array(String)
|
||||
property subscriptions : Array(String)
|
||||
property notifications : Array(String) = [] of String
|
||||
property subscriptions : Array(String) = [] of String
|
||||
property email : String
|
||||
|
||||
@[DB::Field(converter: Invidious::User::PreferencesConverter)]
|
||||
property preferences : Preferences
|
||||
property password : String?
|
||||
property token : String
|
||||
property watched : Array(String)
|
||||
property feed_needs_update : Bool?
|
||||
property watched : Array(String) = [] of String
|
||||
property feed_needs_update : Bool? = true
|
||||
|
||||
def initialize(*, @email, @token, @password)
|
||||
@updated = Time.utc
|
||||
@preferences = Preferences.new(CONFIG.default_user_preferences.to_tuple)
|
||||
end
|
||||
|
||||
def self.create(sid, email, password) : User
|
||||
hashed_pwd = Crypto::Bcrypt::Password.create(password, cost: 10)
|
||||
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
|
||||
|
||||
return User.new(email: email, token: token, password: hashed_pwd.to_s)
|
||||
end
|
||||
|
||||
def validate_password(password : String) : Bool
|
||||
# Damned Google accounts were stored with a nil password
|
||||
stored_password = @password
|
||||
return false if stored_password.nil?
|
||||
|
||||
return Crypto::Bcrypt::Password.new(stored_password).verify(password)
|
||||
end
|
||||
|
||||
module PreferencesConverter
|
||||
def self.from_rs(rs)
|
||||
|
|
|
@ -3,25 +3,6 @@ require "crypto/bcrypt/password"
|
|||
# Materialized views may not be defined using bound parameters (`$1` as used elsewhere)
|
||||
MATERIALIZED_VIEW_SQL = ->(email : String) { "SELECT cv.* FROM channel_videos cv WHERE EXISTS (SELECT subscriptions FROM users u WHERE cv.ucid = ANY (u.subscriptions) AND u.email = E'#{email.gsub({'\'' => "\\'", '\\' => "\\\\"})}') ORDER BY published DESC" }
|
||||
|
||||
def create_user(sid, email, password)
|
||||
password = Crypto::Bcrypt::Password.create(password, cost: 10)
|
||||
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
|
||||
|
||||
user = Invidious::User.new({
|
||||
updated: Time.utc,
|
||||
notifications: [] of String,
|
||||
subscriptions: [] of String,
|
||||
email: email,
|
||||
preferences: Preferences.new(CONFIG.default_user_preferences.to_tuple),
|
||||
password: password.to_s,
|
||||
token: token,
|
||||
watched: [] of String,
|
||||
feed_needs_update: true,
|
||||
})
|
||||
|
||||
return user, sid
|
||||
end
|
||||
|
||||
def get_subscription_feed(user, max_results = 40, page = 1)
|
||||
limit = max_results.clamp(0, MAX_ITEMS_PER_PAGE)
|
||||
offset = (page - 1) * limit
|
||||
|
|
39
src/invidious/views/components/captcha.ecr
Normal file
39
src/invidious/views/components/captcha.ecr
Normal file
|
@ -0,0 +1,39 @@
|
|||
<div class="captcha flexible">
|
||||
|
||||
<input type='hidden' name='captcha_type' value="<%= captcha.type %>">
|
||||
|
||||
<%- captcha.tokens.each do |tok| -%>
|
||||
<input type='hidden' name='tokens' value="<%= HTML.escape(tok) %>">
|
||||
<%- end -%>
|
||||
|
||||
<%- if captcha.type.image? %>
|
||||
|
||||
<div class="left">
|
||||
<img src="<%= captcha.question %>"/>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<fieldset class="pure-form-stacked">
|
||||
<div class="pure-control-group">
|
||||
<label for='answer'><%= translate(locale, "Time (h:mm:ss):") %></label><input
|
||||
type='text' name='answer' type='text' placeholder="hh:mm:ss">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<a src="<%= url %>&captcha_type=text"><%= translate(locale, "login_page_request_text_captcha") %></a>
|
||||
</div>
|
||||
|
||||
<%- else # type.text? %>
|
||||
|
||||
<fieldset class="pure-form-stacked">
|
||||
<div class="pure-control-group">
|
||||
<label for='answer' lang="en"><%= captcha.question %></label><input
|
||||
type='text' name='answer' type='text' placeholder="<%= translate(locale, "Answer") %>">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<a src=") << url << %(&captcha_type=image"><%= translate(locale, "login_page_request_image_captcha") %></a>
|
||||
|
||||
<%- end %>
|
||||
|
||||
</div>
|
|
@ -1,77 +1,10 @@
|
|||
<% content_for "header" do %>
|
||||
<title><%= translate(locale, "Log in") %> - Invidious</title>
|
||||
<title><%= translate(locale, "login_page_title_login") %> - Invidious</title>
|
||||
<link rel="stylesheet" href="/css/user.css?v=<%= ASSET_COMMIT %>">
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-lg-1-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5">
|
||||
<div class="h-box">
|
||||
<%- case account_type when -%>
|
||||
<%- when "invidious" -%>
|
||||
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=invidious" method="post">
|
||||
<fieldset>
|
||||
<% if email %>
|
||||
<input name="email" type="hidden" value="<%= HTML.escape(email) %>">
|
||||
<% else %>
|
||||
<label for="email"><%= translate(locale, "User ID") %> :</label>
|
||||
<input required class="pure-input-1" name="email" type="text" placeholder="<%= translate(locale, "User ID") %>">
|
||||
<% end %>
|
||||
|
||||
<% if password %>
|
||||
<input name="password" type="hidden" value="<%= HTML.escape(password) %>">
|
||||
<% else %>
|
||||
<label for="password"><%= translate(locale, "Password") %> :</label>
|
||||
<input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
|
||||
<% end %>
|
||||
|
||||
<% if captcha %>
|
||||
<% case captcha_type when %>
|
||||
<% when "image" %>
|
||||
<% captcha = captcha.not_nil! %>
|
||||
<img style="width:50%" src='<%= captcha[:question] %>'/>
|
||||
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
|
||||
<% end %>
|
||||
<input type="hidden" name="captcha_type" value="image">
|
||||
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
||||
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
|
||||
<% else # "text" %>
|
||||
<% captcha = captcha.not_nil! %>
|
||||
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
|
||||
<% end %>
|
||||
<input type="hidden" name="captcha_type" value="text">
|
||||
<label for="answer"><%= captcha[:question] %></label>
|
||||
<input type="text" name="answer" type="text" placeholder="<%= translate(locale, "Answer") %>">
|
||||
<% end %>
|
||||
|
||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||
<%= translate(locale, "Register") %>
|
||||
</button>
|
||||
|
||||
<% case captcha_type when %>
|
||||
<% when "image" %>
|
||||
<label>
|
||||
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="text">
|
||||
<%= translate(locale, "Text CAPTCHA") %>
|
||||
</button>
|
||||
</label>
|
||||
<% else # "text" %>
|
||||
<label>
|
||||
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="image">
|
||||
<%= translate(locale, "Image CAPTCHA") %>
|
||||
</button>
|
||||
</label>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
|
||||
</button>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
</form>
|
||||
<%- end -%>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5"></div>
|
||||
<div class="h-box">
|
||||
<div class="login-container">
|
||||
<%= Invidious::Frontend::LoginRegister.gen_login_form(env, account_type, captcha) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
10
src/invidious/views/user/register.ecr
Normal file
10
src/invidious/views/user/register.ecr
Normal file
|
@ -0,0 +1,10 @@
|
|||
<% content_for "header" do %>
|
||||
<title><%= translate(locale, "login_page_title_register") %> - Invidious</title>
|
||||
<link rel="stylesheet" href="/css/user.css?v=<%= ASSET_COMMIT %>">
|
||||
<% end %>
|
||||
|
||||
<div class="h-box">
|
||||
<div class="register-container">
|
||||
<%= Invidious::Frontend::LoginRegister.gen_register_form(env, captcha) %>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in a new issue