diff --git a/src/dialogs/confirm.js b/src/dialogs/confirm.js index e42e36045..393dd592a 100644 --- a/src/dialogs/confirm.js +++ b/src/dialogs/confirm.js @@ -8,7 +8,7 @@ import restoreTheme from "lib/restoreTheme"; * @param {string} titleText Title text * @param {string} [message] Alert message * @param {boolean} [isHTML] Whether the message is HTML - * @param {{checkboxText?: string, returnState?: boolean}} [options] + * @param {{checkboxText?: string, returnState?: boolean, direction?: "ltr" | "rtl", aboveOverlay?: boolean}} [options] * @returns {Promise} */ function confirm(titleText, message, isHTML, options = {}) { @@ -55,7 +55,8 @@ function confirm(titleText, message, isHTML, options = {}) { }, }); const confirmDiv = tag("div", { - className: "prompt confirm", + className: `prompt confirm${options.aboveOverlay ? " above-overlay" : ""}`, + dir: options.direction, children: [ titleSpan, messageSpan, diff --git a/src/dialogs/style.scss b/src/dialogs/style.scss index 91fe9ab80..0380b5c0d 100644 --- a/src/dialogs/style.scss +++ b/src/dialogs/style.scss @@ -49,6 +49,14 @@ z-index: 113; } + &.above-overlay { + z-index: 1001; + + & + .mask { + z-index: 1000; + } + } + &.multi { textarea:not(:first-of-type) { margin-top: 20px; diff --git a/src/lang/ar-ye.json b/src/lang/ar-ye.json index 70ca153df..9149ce9f1 100644 --- a/src/lang/ar-ye.json +++ b/src/lang/ar-ye.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "تقييمات الإضافة", + "plugin-review:account-check-failed": "تعذّر التحقق من حسابك. حاول مرة أخرى.", + "plugin-review:sign-in": "سجّل الدخول لكتابة تقييم", + "plugin-review:edit": "عدّل تقييمك", + "plugin-review:add": "أضف تقييمًا...", + "plugin-review:placeholder": "شارك تجربتك...", + "plugin-review:input-label": "تقييم الإضافة", + "plugin-review:vote-group-label": "التصويت", + "plugin-review:thumbs-up": "أعجبني", + "plugin-review:thumbs-down": "لم يعجبني", + "plugin-review:delete": "حذف التقييم", + "plugin-review:load-failed": "تعذّر تحميل التقييمات.", + "plugin-review:retry": "إعادة المحاولة", + "plugin-review:empty-title": "لا توجد تقييمات بعد", + "plugin-review:empty-description": "كن أول من يشارك تجربته.", + "plugin-review:yours": "تقييمك", + "plugin-review:community": "تقييمات المجتمع", + "plugin-review:no-other-reviews": "لا توجد تقييمات أخرى بعد.", + "plugin-review:login-incomplete": "لم يكتمل تسجيل الدخول.", + "plugin-review:sign-in-failed": "تعذّر تسجيل الدخول.", + "plugin-review:session-expired-review": "انتهت جلستك. سجّل الدخول مجددًا لكتابة تقييم.", + "plugin-review:save-failed": "تعذّر حفظ التقييم.", + "plugin-review:delete-confirmation": "هل تريد حذف تقييمك؟ لا يمكن التراجع عن هذا الإجراء.", + "plugin-review:session-expired-continue": "انتهت جلستك. سجّل الدخول مجددًا للمتابعة.", + "plugin-review:delete-failed": "تعذّر حذف التقييم.", + "plugin-review:you": "أنت", + "plugin-review:edit-action": "تعديل التقييم", + "plugin-review:recommended": "أوصى بهذه الإضافة", + "plugin-review:error-max-length": "يجب ألا يتجاوز التقييم {max} حرفًا.", + "plugin-review:error-downvote-comment": "أخبر المطوّر بما يمكن تحسينه.", + "plugin-review:error-empty": "أضف تعليقًا أو اختر «أعجبني».", + "plugin-review:unrated": "غير مقيّم" } diff --git a/src/lang/be-by.json b/src/lang/be-by.json index 98f392e34..240f92549 100644 --- a/src/lang/be-by.json +++ b/src/lang/be-by.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Водгукі пра плагін", + "plugin-review:account-check-failed": "Не ўдалося праверыць ваш уліковы запіс. Паспрабуйце яшчэ раз.", + "plugin-review:sign-in": "Увайсці, каб пакінуць водгук", + "plugin-review:edit": "Рэдагаваць свой водгук", + "plugin-review:add": "Дадаць водгук…", + "plugin-review:placeholder": "Падзяліцеся сваім досведам…", + "plugin-review:input-label": "Водгук пра плагін", + "plugin-review:vote-group-label": "Ацэнка", + "plugin-review:thumbs-up": "Падабаецца", + "plugin-review:thumbs-down": "Не падабаецца", + "plugin-review:delete": "Выдаліць водгук", + "plugin-review:load-failed": "Не ўдалося загрузіць водгукі.", + "plugin-review:retry": "Паспрабаваць яшчэ раз", + "plugin-review:empty-title": "Водгукаў пакуль няма", + "plugin-review:empty-description": "Падзяліцеся сваім досведам першымі.", + "plugin-review:yours": "Ваш водгук", + "plugin-review:community": "Водгукі супольнасці", + "plugin-review:no-other-reviews": "Іншых водгукаў пакуль няма.", + "plugin-review:login-incomplete": "Уваход не быў завершаны.", + "plugin-review:sign-in-failed": "Не ўдалося ўвайсці.", + "plugin-review:session-expired-review": "Сеанс скончыўся. Увайдзіце зноў, каб пакінуць водгук.", + "plugin-review:save-failed": "Не ўдалося захаваць водгук.", + "plugin-review:delete-confirmation": "Выдаліць ваш водгук? Гэта дзеянне нельга скасаваць.", + "plugin-review:session-expired-continue": "Сеанс скончыўся. Увайдзіце зноў, каб працягнуць.", + "plugin-review:delete-failed": "Не ўдалося выдаліць водгук.", + "plugin-review:you": "Вы", + "plugin-review:edit-action": "Рэдагаваць водгук", + "plugin-review:recommended": "Рэкамендуе гэты плагін", + "plugin-review:error-max-length": "Водгук павінен змяшчаць не больш за {max} сімвалаў.", + "plugin-review:error-downvote-comment": "Раскажыце распрацоўшчыку, што можна палепшыць.", + "plugin-review:error-empty": "Дадайце каментарый або выберыце «Падабаецца».", + "plugin-review:unrated": "Без ацэнкі" } diff --git a/src/lang/bn-bd.json b/src/lang/bn-bd.json index 36323c674..3dad21869 100644 --- a/src/lang/bn-bd.json +++ b/src/lang/bn-bd.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "প্লাগইন পর্যালোচনা", + "plugin-review:account-check-failed": "আপনার অ্যাকাউন্ট যাচাই করা যায়নি। আবার চেষ্টা করুন।", + "plugin-review:sign-in": "পর্যালোচনা দিতে সাইন ইন করুন", + "plugin-review:edit": "আপনার পর্যালোচনা সম্পাদনা করুন", + "plugin-review:add": "পর্যালোচনা যোগ করুন...", + "plugin-review:placeholder": "আপনার অভিজ্ঞতা শেয়ার করুন...", + "plugin-review:input-label": "প্লাগইন পর্যালোচনা", + "plugin-review:vote-group-label": "ভোট", + "plugin-review:thumbs-up": "পছন্দ", + "plugin-review:thumbs-down": "অপছন্দ", + "plugin-review:delete": "পর্যালোচনা মুছুন", + "plugin-review:load-failed": "পর্যালোচনাগুলো লোড করা যায়নি।", + "plugin-review:retry": "আবার চেষ্টা করুন", + "plugin-review:empty-title": "এখনও কোনো পর্যালোচনা নেই", + "plugin-review:empty-description": "সবার আগে আপনার অভিজ্ঞতা শেয়ার করুন।", + "plugin-review:yours": "আপনার পর্যালোচনা", + "plugin-review:community": "কমিউনিটির পর্যালোচনা", + "plugin-review:no-other-reviews": "এখনও অন্য কোনো পর্যালোচনা নেই।", + "plugin-review:login-incomplete": "সাইন ইন সম্পন্ন হয়নি।", + "plugin-review:sign-in-failed": "সাইন ইন করা যায়নি।", + "plugin-review:session-expired-review": "আপনার সেশনের মেয়াদ শেষ। পর্যালোচনা দিতে আবার সাইন ইন করুন।", + "plugin-review:save-failed": "পর্যালোচনা সংরক্ষণ করা যায়নি।", + "plugin-review:delete-confirmation": "আপনার পর্যালোচনা মুছবেন? এটি আর ফিরিয়ে আনা যাবে না।", + "plugin-review:session-expired-continue": "আপনার সেশনের মেয়াদ শেষ। চালিয়ে যেতে আবার সাইন ইন করুন।", + "plugin-review:delete-failed": "পর্যালোচনা মোছা যায়নি।", + "plugin-review:you": "আপনি", + "plugin-review:edit-action": "পর্যালোচনা সম্পাদনা করুন", + "plugin-review:recommended": "এই প্লাগইনটি সুপারিশ করেছেন", + "plugin-review:error-max-length": "পর্যালোচনা {max} অক্ষর বা তার কম হতে হবে।", + "plugin-review:error-downvote-comment": "কী উন্নত করা যায় তা ডেভেলপারকে জানান।", + "plugin-review:error-empty": "একটি মন্তব্য যোগ করুন বা পছন্দ নির্বাচন করুন।", + "plugin-review:unrated": "এখনও মূল্যায়ন নেই" } diff --git a/src/lang/cs-cz.json b/src/lang/cs-cz.json index 52e5bbb07..6e3070fff 100644 --- a/src/lang/cs-cz.json +++ b/src/lang/cs-cz.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Recenze pluginu", + "plugin-review:account-check-failed": "Váš účet se nepodařilo ověřit. Zkuste to znovu.", + "plugin-review:sign-in": "Přihlásit se a napsat recenzi", + "plugin-review:edit": "Upravit vaši recenzi", + "plugin-review:add": "Přidat recenzi…", + "plugin-review:placeholder": "Podělte se o své zkušenosti…", + "plugin-review:input-label": "Recenze pluginu", + "plugin-review:vote-group-label": "Hlas", + "plugin-review:thumbs-up": "Líbí se mi", + "plugin-review:thumbs-down": "Nelíbí se mi", + "plugin-review:delete": "Smazat recenzi", + "plugin-review:load-failed": "Recenze se nepodařilo načíst.", + "plugin-review:retry": "Zkusit znovu", + "plugin-review:empty-title": "Zatím žádné recenze", + "plugin-review:empty-description": "Podělte se o zkušenost jako první.", + "plugin-review:yours": "Vaše recenze", + "plugin-review:community": "Recenze komunity", + "plugin-review:no-other-reviews": "Zatím žádné další recenze.", + "plugin-review:login-incomplete": "Přihlášení nebylo dokončeno.", + "plugin-review:sign-in-failed": "Přihlášení se nezdařilo.", + "plugin-review:session-expired-review": "Vaše relace vypršela. Chcete-li napsat recenzi, přihlaste se znovu.", + "plugin-review:save-failed": "Recenzi se nepodařilo uložit.", + "plugin-review:delete-confirmation": "Smazat vaši recenzi? Tuto akci nelze vrátit zpět.", + "plugin-review:session-expired-continue": "Vaše relace vypršela. Chcete-li pokračovat, přihlaste se znovu.", + "plugin-review:delete-failed": "Recenzi se nepodařilo smazat.", + "plugin-review:you": "Vy", + "plugin-review:edit-action": "Upravit recenzi", + "plugin-review:recommended": "Doporučuje tento plugin", + "plugin-review:error-max-length": "Recenze může mít nejvýše {max} znaků.", + "plugin-review:error-downvote-comment": "Sdělte vývojáři, co by se dalo zlepšit.", + "plugin-review:error-empty": "Přidejte komentář nebo zvolte Líbí se mi.", + "plugin-review:unrated": "Nehodnoceno" } diff --git a/src/lang/de-de.json b/src/lang/de-de.json index d1fb0ed42..157c1e985 100644 --- a/src/lang/de-de.json +++ b/src/lang/de-de.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Plugin-Bewertungen", + "plugin-review:account-check-failed": "Dein Konto konnte nicht überprüft werden. Versuche es erneut.", + "plugin-review:sign-in": "Zum Bewerten anmelden", + "plugin-review:edit": "Deine Bewertung bearbeiten", + "plugin-review:add": "Bewertung hinzufügen…", + "plugin-review:placeholder": "Teile deine Erfahrung…", + "plugin-review:input-label": "Plugin-Bewertung", + "plugin-review:vote-group-label": "Stimme", + "plugin-review:thumbs-up": "Gefällt mir", + "plugin-review:thumbs-down": "Gefällt mir nicht", + "plugin-review:delete": "Bewertung löschen", + "plugin-review:load-failed": "Bewertungen konnten nicht geladen werden.", + "plugin-review:retry": "Erneut versuchen", + "plugin-review:empty-title": "Noch keine Bewertungen", + "plugin-review:empty-description": "Teile als Erste oder Erster deine Erfahrung.", + "plugin-review:yours": "Deine Bewertung", + "plugin-review:community": "Bewertungen der Community", + "plugin-review:no-other-reviews": "Noch keine weiteren Bewertungen.", + "plugin-review:login-incomplete": "Die Anmeldung wurde nicht abgeschlossen.", + "plugin-review:sign-in-failed": "Anmeldung nicht möglich.", + "plugin-review:session-expired-review": "Deine Sitzung ist abgelaufen. Melde dich erneut an, um eine Bewertung abzugeben.", + "plugin-review:save-failed": "Bewertung konnte nicht gespeichert werden.", + "plugin-review:delete-confirmation": "Deine Bewertung löschen? Dies kann nicht rückgängig gemacht werden.", + "plugin-review:session-expired-continue": "Deine Sitzung ist abgelaufen. Melde dich erneut an, um fortzufahren.", + "plugin-review:delete-failed": "Bewertung konnte nicht gelöscht werden.", + "plugin-review:you": "Du", + "plugin-review:edit-action": "Bewertung bearbeiten", + "plugin-review:recommended": "Empfiehlt dieses Plugin", + "plugin-review:error-max-length": "Die Bewertung darf höchstens {max} Zeichen lang sein.", + "plugin-review:error-downvote-comment": "Sage dem Entwickler, was verbessert werden könnte.", + "plugin-review:error-empty": "Füge einen Kommentar hinzu oder wähle „Gefällt mir“.", + "plugin-review:unrated": "Nicht bewertet" } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index fa275a376..23ae1c267 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -835,5 +835,37 @@ "managed": "Managed", "acode service": "Acode Service", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Plugin reviews", + "plugin-review:account-check-failed": "Unable to check your account. Try again.", + "plugin-review:sign-in": "Sign in to review", + "plugin-review:edit": "Edit your review", + "plugin-review:add": "Add a review...", + "plugin-review:placeholder": "Share your experience...", + "plugin-review:input-label": "Plugin review", + "plugin-review:vote-group-label": "Vote", + "plugin-review:thumbs-up": "Thumbs up", + "plugin-review:thumbs-down": "Thumbs down", + "plugin-review:delete": "Delete review", + "plugin-review:load-failed": "Unable to load reviews.", + "plugin-review:retry": "Retry", + "plugin-review:empty-title": "No reviews yet", + "plugin-review:empty-description": "Be the first to share your experience.", + "plugin-review:yours": "Your review", + "plugin-review:community": "Community reviews", + "plugin-review:no-other-reviews": "No other reviews yet.", + "plugin-review:login-incomplete": "Login was not completed.", + "plugin-review:sign-in-failed": "Unable to sign in.", + "plugin-review:session-expired-review": "Your session expired. Sign in again to review.", + "plugin-review:save-failed": "Unable to save review.", + "plugin-review:delete-confirmation": "Delete your review? This cannot be undone.", + "plugin-review:session-expired-continue": "Your session expired. Sign in again to continue.", + "plugin-review:delete-failed": "Unable to delete review.", + "plugin-review:you": "You", + "plugin-review:edit-action": "Edit review", + "plugin-review:recommended": "Recommended this plugin", + "plugin-review:error-max-length": "Review must be {max} characters or less.", + "plugin-review:error-downvote-comment": "Tell the developer what could be improved.", + "plugin-review:error-empty": "Add a comment or choose thumbs up.", + "plugin-review:unrated": "Unrated" } diff --git a/src/lang/es-sv.json b/src/lang/es-sv.json index 8c3108cb6..18ee5d614 100644 --- a/src/lang/es-sv.json +++ b/src/lang/es-sv.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Reseñas del complemento", + "plugin-review:account-check-failed": "No se pudo verificar tu cuenta. Inténtalo de nuevo.", + "plugin-review:sign-in": "Inicia sesión para opinar", + "plugin-review:edit": "Editar tu reseña", + "plugin-review:add": "Agregar una reseña...", + "plugin-review:placeholder": "Comparte tu experiencia...", + "plugin-review:input-label": "Reseña del complemento", + "plugin-review:vote-group-label": "Voto", + "plugin-review:thumbs-up": "Me gusta", + "plugin-review:thumbs-down": "No me gusta", + "plugin-review:delete": "Eliminar reseña", + "plugin-review:load-failed": "No se pudieron cargar las reseñas.", + "plugin-review:retry": "Reintentar", + "plugin-review:empty-title": "Aún no hay reseñas", + "plugin-review:empty-description": "Sé la primera persona en compartir tu experiencia.", + "plugin-review:yours": "Tu reseña", + "plugin-review:community": "Reseñas de la comunidad", + "plugin-review:no-other-reviews": "Aún no hay otras reseñas.", + "plugin-review:login-incomplete": "No se completó el inicio de sesión.", + "plugin-review:sign-in-failed": "No se pudo iniciar sesión.", + "plugin-review:session-expired-review": "Tu sesión venció. Inicia sesión de nuevo para opinar.", + "plugin-review:save-failed": "No se pudo guardar la reseña.", + "plugin-review:delete-confirmation": "¿Eliminar tu reseña? Esta acción no se puede deshacer.", + "plugin-review:session-expired-continue": "Tu sesión venció. Inicia sesión de nuevo para continuar.", + "plugin-review:delete-failed": "No se pudo eliminar la reseña.", + "plugin-review:you": "Tú", + "plugin-review:edit-action": "Editar reseña", + "plugin-review:recommended": "Recomendó este complemento", + "plugin-review:error-max-length": "La reseña debe tener {max} caracteres o menos.", + "plugin-review:error-downvote-comment": "Dile al desarrollador qué podría mejorar.", + "plugin-review:error-empty": "Agrega un comentario o elige Me gusta.", + "plugin-review:unrated": "Sin calificar" } diff --git a/src/lang/fr-fr.json b/src/lang/fr-fr.json index f4edc7a62..4f7b96c83 100644 --- a/src/lang/fr-fr.json +++ b/src/lang/fr-fr.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Avis sur l’extension", + "plugin-review:account-check-failed": "Impossible de vérifier votre compte. Réessayez.", + "plugin-review:sign-in": "Se connecter pour donner un avis", + "plugin-review:edit": "Modifier votre avis", + "plugin-review:add": "Ajouter un avis…", + "plugin-review:placeholder": "Partagez votre expérience…", + "plugin-review:input-label": "Avis sur l’extension", + "plugin-review:vote-group-label": "Vote", + "plugin-review:thumbs-up": "J’aime", + "plugin-review:thumbs-down": "Je n’aime pas", + "plugin-review:delete": "Supprimer l’avis", + "plugin-review:load-failed": "Impossible de charger les avis.", + "plugin-review:retry": "Réessayer", + "plugin-review:empty-title": "Aucun avis pour le moment", + "plugin-review:empty-description": "Soyez la première personne à partager votre expérience.", + "plugin-review:yours": "Votre avis", + "plugin-review:community": "Avis de la communauté", + "plugin-review:no-other-reviews": "Aucun autre avis pour le moment.", + "plugin-review:login-incomplete": "La connexion n’a pas abouti.", + "plugin-review:sign-in-failed": "Impossible de se connecter.", + "plugin-review:session-expired-review": "Votre session a expiré. Reconnectez-vous pour donner un avis.", + "plugin-review:save-failed": "Impossible d’enregistrer l’avis.", + "plugin-review:delete-confirmation": "Supprimer votre avis ? Cette action est irréversible.", + "plugin-review:session-expired-continue": "Votre session a expiré. Reconnectez-vous pour continuer.", + "plugin-review:delete-failed": "Impossible de supprimer l’avis.", + "plugin-review:you": "Vous", + "plugin-review:edit-action": "Modifier l’avis", + "plugin-review:recommended": "Recommande cette extension", + "plugin-review:error-max-length": "L’avis doit contenir au maximum {max} caractères.", + "plugin-review:error-downvote-comment": "Indiquez au développeur ce qui pourrait être amélioré.", + "plugin-review:error-empty": "Ajoutez un commentaire ou choisissez J’aime.", + "plugin-review:unrated": "Non évalué" } diff --git a/src/lang/he-il.json b/src/lang/he-il.json index 9d67df02c..5e65d36f1 100644 --- a/src/lang/he-il.json +++ b/src/lang/he-il.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "ביקורות על התוסף", + "plugin-review:account-check-failed": "לא ניתן לבדוק את החשבון שלך. נסה שוב.", + "plugin-review:sign-in": "יש להתחבר כדי לכתוב ביקורת", + "plugin-review:edit": "עריכת הביקורת שלך", + "plugin-review:add": "הוספת ביקורת...", + "plugin-review:placeholder": "שתף את החוויה שלך...", + "plugin-review:input-label": "ביקורת על התוסף", + "plugin-review:vote-group-label": "הצבעה", + "plugin-review:thumbs-up": "אהבתי", + "plugin-review:thumbs-down": "לא אהבתי", + "plugin-review:delete": "מחיקת ביקורת", + "plugin-review:load-failed": "לא ניתן לטעון את הביקורות.", + "plugin-review:retry": "נסה שוב", + "plugin-review:empty-title": "אין עדיין ביקורות", + "plugin-review:empty-description": "היה הראשון לשתף את החוויה שלך.", + "plugin-review:yours": "הביקורת שלך", + "plugin-review:community": "ביקורות הקהילה", + "plugin-review:no-other-reviews": "אין עדיין ביקורות נוספות.", + "plugin-review:login-incomplete": "ההתחברות לא הושלמה.", + "plugin-review:sign-in-failed": "לא ניתן להתחבר.", + "plugin-review:session-expired-review": "פג תוקף ההפעלה שלך. התחבר שוב כדי לכתוב ביקורת.", + "plugin-review:save-failed": "לא ניתן לשמור את הביקורת.", + "plugin-review:delete-confirmation": "למחוק את הביקורת שלך? לא ניתן לבטל פעולה זו.", + "plugin-review:session-expired-continue": "פג תוקף ההפעלה שלך. התחבר שוב כדי להמשיך.", + "plugin-review:delete-failed": "לא ניתן למחוק את הביקורת.", + "plugin-review:you": "אתה", + "plugin-review:edit-action": "עריכת ביקורת", + "plugin-review:recommended": "המליץ על התוסף הזה", + "plugin-review:error-max-length": "הביקורת צריכה להכיל עד {max} תווים.", + "plugin-review:error-downvote-comment": "ספר למפתח מה אפשר לשפר.", + "plugin-review:error-empty": "הוסף תגובה או בחר באהבתי.", + "plugin-review:unrated": "ללא דירוג" } diff --git a/src/lang/hi-in.json b/src/lang/hi-in.json index bd1a21d6b..f1ff01655 100644 --- a/src/lang/hi-in.json +++ b/src/lang/hi-in.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "कॉपी करते टाइम एक्सक्लूज़न्स अप्लाई करें", "settings-info-app-use-file-operation-exclusions": "कॉपी और पेस्ट ऑपरेशन्स के दौरान एक्सक्लूज़न पैटर्न्स से मैच करने वाले फ़ाइल्स और फ़ोल्डर्स स्किप कर।", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "प्लगइन समीक्षाएँ", + "plugin-review:account-check-failed": "आपके खाते की जाँच नहीं हो सकी। फिर से कोशिश करें।", + "plugin-review:sign-in": "समीक्षा देने के लिए साइन इन करें", + "plugin-review:edit": "अपनी समीक्षा संपादित करें", + "plugin-review:add": "समीक्षा जोड़ें...", + "plugin-review:placeholder": "अपना अनुभव साझा करें...", + "plugin-review:input-label": "प्लगइन समीक्षा", + "plugin-review:vote-group-label": "वोट", + "plugin-review:thumbs-up": "पसंद", + "plugin-review:thumbs-down": "नापसंद", + "plugin-review:delete": "समीक्षा हटाएँ", + "plugin-review:load-failed": "समीक्षाएँ लोड नहीं हो सकीं।", + "plugin-review:retry": "फिर से कोशिश करें", + "plugin-review:empty-title": "अभी कोई समीक्षा नहीं है", + "plugin-review:empty-description": "अपना अनुभव साझा करने वाले पहले व्यक्ति बनें।", + "plugin-review:yours": "आपकी समीक्षा", + "plugin-review:community": "समुदाय की समीक्षाएँ", + "plugin-review:no-other-reviews": "अभी कोई अन्य समीक्षा नहीं है।", + "plugin-review:login-incomplete": "साइन इन पूरा नहीं हुआ।", + "plugin-review:sign-in-failed": "साइन इन नहीं हो सका।", + "plugin-review:session-expired-review": "आपका सत्र समाप्त हो गया। समीक्षा देने के लिए फिर से साइन इन करें।", + "plugin-review:save-failed": "समीक्षा सहेजी नहीं जा सकी।", + "plugin-review:delete-confirmation": "अपनी समीक्षा हटाएँ? इसे वापस नहीं किया जा सकता।", + "plugin-review:session-expired-continue": "आपका सत्र समाप्त हो गया। जारी रखने के लिए फिर से साइन इन करें।", + "plugin-review:delete-failed": "समीक्षा हटाई नहीं जा सकी।", + "plugin-review:you": "आप", + "plugin-review:edit-action": "समीक्षा संपादित करें", + "plugin-review:recommended": "इस प्लगइन की सिफ़ारिश की", + "plugin-review:error-max-length": "समीक्षा {max} वर्णों या उससे कम की होनी चाहिए।", + "plugin-review:error-downvote-comment": "डेवलपर को बताएँ कि क्या बेहतर किया जा सकता है।", + "plugin-review:error-empty": "टिप्पणी जोड़ें या पसंद चुनें।", + "plugin-review:unrated": "अभी रेटिंग नहीं" } diff --git a/src/lang/hu-hu.json b/src/lang/hu-hu.json index 769ce29a0..061b674d7 100644 --- a/src/lang/hu-hu.json +++ b/src/lang/hu-hu.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Kizárások használata másoláskor", "settings-info-app-use-file-operation-exclusions": "A kizárási mintáknak megfelelő fájlok és mappák kihagyása másoláskor és beillesztéskor.", "horizontal scroll margin": "Vízszintes görgetési margó", - "settings-info-horizontal-scroll-margin": "A leghosszabb sor után egy üres hely lesz hozzáadva a képernyő széléhez, ha a sorok tördelése ki van kapcsolva. Adjon meg egy nem negatív egész pixelértéket." + "settings-info-horizontal-scroll-margin": "A leghosszabb sor után egy üres hely lesz hozzáadva a képernyő széléhez, ha a sorok tördelése ki van kapcsolva. Adjon meg egy nem negatív egész pixelértéket.", + "plugin-review:dialog-label": "Bővítményértékelések", + "plugin-review:account-check-failed": "Nem sikerült ellenőrizni a fiókodat. Próbáld újra.", + "plugin-review:sign-in": "Jelentkezz be az értékeléshez", + "plugin-review:edit": "Értékelésed szerkesztése", + "plugin-review:add": "Értékelés hozzáadása…", + "plugin-review:placeholder": "Oszd meg a tapasztalataidat…", + "plugin-review:input-label": "Bővítmény értékelése", + "plugin-review:vote-group-label": "Szavazat", + "plugin-review:thumbs-up": "Tetszik", + "plugin-review:thumbs-down": "Nem tetszik", + "plugin-review:delete": "Értékelés törlése", + "plugin-review:load-failed": "Nem sikerült betölteni az értékeléseket.", + "plugin-review:retry": "Újrapróbálkozás", + "plugin-review:empty-title": "Még nincsenek értékelések", + "plugin-review:empty-description": "Oszd meg elsőként a tapasztalataidat.", + "plugin-review:yours": "Saját értékelésed", + "plugin-review:community": "Közösségi értékelések", + "plugin-review:no-other-reviews": "Még nincsenek további értékelések.", + "plugin-review:login-incomplete": "A bejelentkezés nem fejeződött be.", + "plugin-review:sign-in-failed": "Nem sikerült bejelentkezni.", + "plugin-review:session-expired-review": "A munkameneted lejárt. Jelentkezz be újra az értékeléshez.", + "plugin-review:save-failed": "Nem sikerült menteni az értékelést.", + "plugin-review:delete-confirmation": "Törlöd az értékelésedet? Ez nem vonható vissza.", + "plugin-review:session-expired-continue": "A munkameneted lejárt. Jelentkezz be újra a folytatáshoz.", + "plugin-review:delete-failed": "Nem sikerült törölni az értékelést.", + "plugin-review:you": "Te", + "plugin-review:edit-action": "Értékelés szerkesztése", + "plugin-review:recommended": "Ajánlja ezt a bővítményt", + "plugin-review:error-max-length": "Az értékelés legfeljebb {max} karakter lehet.", + "plugin-review:error-downvote-comment": "Írd meg a fejlesztőnek, min lehetne javítani.", + "plugin-review:error-empty": "Adj hozzá megjegyzést, vagy válaszd a Tetszik lehetőséget.", + "plugin-review:unrated": "Nincs értékelve" } diff --git a/src/lang/id-id.json b/src/lang/id-id.json index 13dda6774..f2560fd6d 100644 --- a/src/lang/id-id.json +++ b/src/lang/id-id.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Terapkan pengecualian saat menyalin", "settings-info-app-use-file-operation-exclusions": "Lewati berkas dan folder yang cocok dengan pola pengecualian selama operasi salin dan tempel.", "horizontal scroll margin": "Margin gulir horizontal", - "settings-info-horizontal-scroll-margin": "Menambahkan ruang kosong setelah baris terpanjang saat pembungkus kata (word wrap) dinonaktifkan. Masukkan nilai piksel bilangan bulat non-negatif." + "settings-info-horizontal-scroll-margin": "Menambahkan ruang kosong setelah baris terpanjang saat pembungkus kata (word wrap) dinonaktifkan. Masukkan nilai piksel bilangan bulat non-negatif.", + "plugin-review:dialog-label": "Ulasan plugin", + "plugin-review:account-check-failed": "Akun Anda tidak dapat diperiksa. Coba lagi.", + "plugin-review:sign-in": "Masuk untuk memberi ulasan", + "plugin-review:edit": "Edit ulasan Anda", + "plugin-review:add": "Tambahkan ulasan...", + "plugin-review:placeholder": "Bagikan pengalaman Anda...", + "plugin-review:input-label": "Ulasan plugin", + "plugin-review:vote-group-label": "Suara", + "plugin-review:thumbs-up": "Suka", + "plugin-review:thumbs-down": "Tidak suka", + "plugin-review:delete": "Hapus ulasan", + "plugin-review:load-failed": "Ulasan tidak dapat dimuat.", + "plugin-review:retry": "Coba lagi", + "plugin-review:empty-title": "Belum ada ulasan", + "plugin-review:empty-description": "Jadilah yang pertama membagikan pengalaman Anda.", + "plugin-review:yours": "Ulasan Anda", + "plugin-review:community": "Ulasan komunitas", + "plugin-review:no-other-reviews": "Belum ada ulasan lain.", + "plugin-review:login-incomplete": "Proses masuk tidak selesai.", + "plugin-review:sign-in-failed": "Tidak dapat masuk.", + "plugin-review:session-expired-review": "Sesi Anda telah berakhir. Masuk lagi untuk memberi ulasan.", + "plugin-review:save-failed": "Ulasan tidak dapat disimpan.", + "plugin-review:delete-confirmation": "Hapus ulasan Anda? Tindakan ini tidak dapat dibatalkan.", + "plugin-review:session-expired-continue": "Sesi Anda telah berakhir. Masuk lagi untuk melanjutkan.", + "plugin-review:delete-failed": "Ulasan tidak dapat dihapus.", + "plugin-review:you": "Anda", + "plugin-review:edit-action": "Edit ulasan", + "plugin-review:recommended": "Merekomendasikan plugin ini", + "plugin-review:error-max-length": "Ulasan harus terdiri dari {max} karakter atau kurang.", + "plugin-review:error-downvote-comment": "Beri tahu developer apa yang dapat ditingkatkan.", + "plugin-review:error-empty": "Tambahkan komentar atau pilih Suka.", + "plugin-review:unrated": "Belum dinilai" } diff --git a/src/lang/index.d.ts b/src/lang/index.d.ts index 076f09696..5777e1047 100644 --- a/src/lang/index.d.ts +++ b/src/lang/index.d.ts @@ -839,6 +839,38 @@ declare type LangStrings = { "acode service": string; "horizontal scroll margin": string; "settings-info-horizontal-scroll-margin": string; + "plugin-review:dialog-label": string; + "plugin-review:account-check-failed": string; + "plugin-review:sign-in": string; + "plugin-review:edit": string; + "plugin-review:add": string; + "plugin-review:placeholder": string; + "plugin-review:input-label": string; + "plugin-review:vote-group-label": string; + "plugin-review:thumbs-up": string; + "plugin-review:thumbs-down": string; + "plugin-review:delete": string; + "plugin-review:load-failed": string; + "plugin-review:retry": string; + "plugin-review:empty-title": string; + "plugin-review:empty-description": string; + "plugin-review:yours": string; + "plugin-review:community": string; + "plugin-review:no-other-reviews": string; + "plugin-review:login-incomplete": string; + "plugin-review:sign-in-failed": string; + "plugin-review:session-expired-review": string; + "plugin-review:save-failed": string; + "plugin-review:delete-confirmation": string; + "plugin-review:session-expired-continue": string; + "plugin-review:delete-failed": string; + "plugin-review:you": string; + "plugin-review:edit-action": string; + "plugin-review:recommended": string; + "plugin-review:error-max-length": string; + "plugin-review:error-downvote-comment": string; + "plugin-review:error-empty": string; + "plugin-review:unrated": string; }; declare var strings: LangStrings; diff --git a/src/lang/ir-fa.json b/src/lang/ir-fa.json index b19d8c385..7d9cbedea 100644 --- a/src/lang/ir-fa.json +++ b/src/lang/ir-fa.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "نظرهای افزونه", + "plugin-review:account-check-failed": "امکان بررسی حساب شما نبود. دوباره تلاش کنید.", + "plugin-review:sign-in": "برای ثبت نظر وارد شوید", + "plugin-review:edit": "نظر خود را ویرایش کنید", + "plugin-review:add": "افزودن نظر...", + "plugin-review:placeholder": "تجربهٔ خود را به اشتراک بگذارید...", + "plugin-review:input-label": "نظر افزونه", + "plugin-review:vote-group-label": "رأی", + "plugin-review:thumbs-up": "پسندیدم", + "plugin-review:thumbs-down": "نپسندیدم", + "plugin-review:delete": "حذف نظر", + "plugin-review:load-failed": "بارگیری نظرها ممکن نبود.", + "plugin-review:retry": "تلاش دوباره", + "plugin-review:empty-title": "هنوز نظری ثبت نشده است", + "plugin-review:empty-description": "نخستین نفری باشید که تجربهٔ خود را به اشتراک می‌گذارد.", + "plugin-review:yours": "نظر شما", + "plugin-review:community": "نظرهای کاربران", + "plugin-review:no-other-reviews": "هنوز نظر دیگری ثبت نشده است.", + "plugin-review:login-incomplete": "ورود کامل نشد.", + "plugin-review:sign-in-failed": "ورود ممکن نبود.", + "plugin-review:session-expired-review": "نشست شما منقضی شده است. برای ثبت نظر دوباره وارد شوید.", + "plugin-review:save-failed": "ذخیرهٔ نظر ممکن نبود.", + "plugin-review:delete-confirmation": "نظر شما حذف شود؟ این کار قابل بازگشت نیست.", + "plugin-review:session-expired-continue": "نشست شما منقضی شده است. برای ادامه دوباره وارد شوید.", + "plugin-review:delete-failed": "حذف نظر ممکن نبود.", + "plugin-review:you": "شما", + "plugin-review:edit-action": "ویرایش نظر", + "plugin-review:recommended": "این افزونه را پیشنهاد کرده است", + "plugin-review:error-max-length": "نظر باید حداکثر {max} نویسه باشد.", + "plugin-review:error-downvote-comment": "به توسعه‌دهنده بگویید چه چیزی می‌تواند بهتر شود.", + "plugin-review:error-empty": "یک دیدگاه بنویسید یا «پسندیدم» را انتخاب کنید.", + "plugin-review:unrated": "بدون امتیاز" } diff --git a/src/lang/it-it.json b/src/lang/it-it.json index dbb883ce3..28c6ba3bd 100644 --- a/src/lang/it-it.json +++ b/src/lang/it-it.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Recensioni del plugin", + "plugin-review:account-check-failed": "Impossibile verificare il tuo account. Riprova.", + "plugin-review:sign-in": "Accedi per lasciare una recensione", + "plugin-review:edit": "Modifica la tua recensione", + "plugin-review:add": "Aggiungi una recensione…", + "plugin-review:placeholder": "Condividi la tua esperienza…", + "plugin-review:input-label": "Recensione del plugin", + "plugin-review:vote-group-label": "Voto", + "plugin-review:thumbs-up": "Mi piace", + "plugin-review:thumbs-down": "Non mi piace", + "plugin-review:delete": "Elimina recensione", + "plugin-review:load-failed": "Impossibile caricare le recensioni.", + "plugin-review:retry": "Riprova", + "plugin-review:empty-title": "Ancora nessuna recensione", + "plugin-review:empty-description": "Condividi per primo la tua esperienza.", + "plugin-review:yours": "La tua recensione", + "plugin-review:community": "Recensioni della community", + "plugin-review:no-other-reviews": "Ancora nessun'altra recensione.", + "plugin-review:login-incomplete": "Accesso non completato.", + "plugin-review:sign-in-failed": "Impossibile accedere.", + "plugin-review:session-expired-review": "La sessione è scaduta. Accedi di nuovo per lasciare una recensione.", + "plugin-review:save-failed": "Impossibile salvare la recensione.", + "plugin-review:delete-confirmation": "Eliminare la tua recensione? Questa azione non può essere annullata.", + "plugin-review:session-expired-continue": "La sessione è scaduta. Accedi di nuovo per continuare.", + "plugin-review:delete-failed": "Impossibile eliminare la recensione.", + "plugin-review:you": "Tu", + "plugin-review:edit-action": "Modifica recensione", + "plugin-review:recommended": "Consiglia questo plugin", + "plugin-review:error-max-length": "La recensione deve contenere al massimo {max} caratteri.", + "plugin-review:error-downvote-comment": "Spiega allo sviluppatore cosa potrebbe essere migliorato.", + "plugin-review:error-empty": "Aggiungi un commento o scegli Mi piace.", + "plugin-review:unrated": "Non valutato" } diff --git a/src/lang/ja-jp.json b/src/lang/ja-jp.json index ddd1e5b63..2329d81b8 100644 --- a/src/lang/ja-jp.json +++ b/src/lang/ja-jp.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "プラグインのレビュー", + "plugin-review:account-check-failed": "アカウントを確認できませんでした。もう一度お試しください。", + "plugin-review:sign-in": "ログインしてレビュー", + "plugin-review:edit": "レビューを編集", + "plugin-review:add": "レビューを追加…", + "plugin-review:placeholder": "使用した感想を共有…", + "plugin-review:input-label": "プラグインのレビュー", + "plugin-review:vote-group-label": "評価", + "plugin-review:thumbs-up": "高評価", + "plugin-review:thumbs-down": "低評価", + "plugin-review:delete": "レビューを削除", + "plugin-review:load-failed": "レビューを読み込めませんでした。", + "plugin-review:retry": "再試行", + "plugin-review:empty-title": "まだレビューはありません", + "plugin-review:empty-description": "最初のレビューを投稿しましょう。", + "plugin-review:yours": "あなたのレビュー", + "plugin-review:community": "コミュニティのレビュー", + "plugin-review:no-other-reviews": "ほかのレビューはまだありません。", + "plugin-review:login-incomplete": "ログインが完了しませんでした。", + "plugin-review:sign-in-failed": "ログインできませんでした。", + "plugin-review:session-expired-review": "セッションの有効期限が切れました。もう一度ログインしてレビューしてください。", + "plugin-review:save-failed": "レビューを保存できませんでした。", + "plugin-review:delete-confirmation": "レビューを削除しますか?この操作は元に戻せません。", + "plugin-review:session-expired-continue": "セッションの有効期限が切れました。続行するにはもう一度ログインしてください。", + "plugin-review:delete-failed": "レビューを削除できませんでした。", + "plugin-review:you": "あなた", + "plugin-review:edit-action": "レビューを編集", + "plugin-review:recommended": "このプラグインをおすすめしています", + "plugin-review:error-max-length": "レビューは {max} 文字以内で入力してください。", + "plugin-review:error-downvote-comment": "改善してほしい点を開発者に伝えてください。", + "plugin-review:error-empty": "コメントを追加するか、高評価を選んでください。", + "plugin-review:unrated": "未評価" } diff --git a/src/lang/ko-kr.json b/src/lang/ko-kr.json index 20ae0a314..d8d1daec7 100644 --- a/src/lang/ko-kr.json +++ b/src/lang/ko-kr.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "플러그인 리뷰", + "plugin-review:account-check-failed": "계정을 확인할 수 없습니다. 다시 시도하세요.", + "plugin-review:sign-in": "로그인하고 리뷰 작성", + "plugin-review:edit": "내 리뷰 수정", + "plugin-review:add": "리뷰 추가...", + "plugin-review:placeholder": "사용 경험을 공유하세요...", + "plugin-review:input-label": "플러그인 리뷰", + "plugin-review:vote-group-label": "평가", + "plugin-review:thumbs-up": "좋아요", + "plugin-review:thumbs-down": "싫어요", + "plugin-review:delete": "리뷰 삭제", + "plugin-review:load-failed": "리뷰를 불러올 수 없습니다.", + "plugin-review:retry": "다시 시도", + "plugin-review:empty-title": "아직 리뷰가 없습니다", + "plugin-review:empty-description": "첫 번째로 사용 경험을 공유해 보세요.", + "plugin-review:yours": "내 리뷰", + "plugin-review:community": "커뮤니티 리뷰", + "plugin-review:no-other-reviews": "아직 다른 리뷰가 없습니다.", + "plugin-review:login-incomplete": "로그인이 완료되지 않았습니다.", + "plugin-review:sign-in-failed": "로그인할 수 없습니다.", + "plugin-review:session-expired-review": "세션이 만료되었습니다. 다시 로그인하고 리뷰를 작성하세요.", + "plugin-review:save-failed": "리뷰를 저장할 수 없습니다.", + "plugin-review:delete-confirmation": "리뷰를 삭제할까요? 이 작업은 되돌릴 수 없습니다.", + "plugin-review:session-expired-continue": "세션이 만료되었습니다. 계속하려면 다시 로그인하세요.", + "plugin-review:delete-failed": "리뷰를 삭제할 수 없습니다.", + "plugin-review:you": "나", + "plugin-review:edit-action": "리뷰 수정", + "plugin-review:recommended": "이 플러그인을 추천함", + "plugin-review:error-max-length": "리뷰는 {max}자 이하여야 합니다.", + "plugin-review:error-downvote-comment": "개발자에게 개선할 점을 알려 주세요.", + "plugin-review:error-empty": "댓글을 추가하거나 좋아요를 선택하세요.", + "plugin-review:unrated": "평가 없음" } diff --git a/src/lang/ml-in.json b/src/lang/ml-in.json index bcc5171dc..137e1311d 100644 --- a/src/lang/ml-in.json +++ b/src/lang/ml-in.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "പ്ലഗിൻ അവലോകനങ്ങൾ", + "plugin-review:account-check-failed": "നിങ്ങളുടെ അക്കൗണ്ട് പരിശോധിക്കാനായില്ല. വീണ്ടും ശ്രമിക്കുക.", + "plugin-review:sign-in": "അവലോകനം നൽകാൻ സൈൻ ഇൻ ചെയ്യുക", + "plugin-review:edit": "നിങ്ങളുടെ അവലോകനം തിരുത്തുക", + "plugin-review:add": "അവലോകനം ചേർക്കുക...", + "plugin-review:placeholder": "നിങ്ങളുടെ അനുഭവം പങ്കിടുക...", + "plugin-review:input-label": "പ്ലഗിൻ അവലോകനം", + "plugin-review:vote-group-label": "വോട്ട്", + "plugin-review:thumbs-up": "ഇഷ്ടപ്പെട്ടു", + "plugin-review:thumbs-down": "ഇഷ്ടപ്പെട്ടില്ല", + "plugin-review:delete": "അവലോകനം ഇല്ലാതാക്കുക", + "plugin-review:load-failed": "അവലോകനങ്ങൾ ലോഡ് ചെയ്യാനായില്ല.", + "plugin-review:retry": "വീണ്ടും ശ്രമിക്കുക", + "plugin-review:empty-title": "ഇതുവരെ അവലോകനങ്ങളൊന്നുമില്ല", + "plugin-review:empty-description": "നിങ്ങളുടെ അനുഭവം ആദ്യം പങ്കിടൂ.", + "plugin-review:yours": "നിങ്ങളുടെ അവലോകനം", + "plugin-review:community": "കമ്മ്യൂണിറ്റി അവലോകനങ്ങൾ", + "plugin-review:no-other-reviews": "മറ്റ് അവലോകനങ്ങളൊന്നും ഇതുവരെ ഇല്ല.", + "plugin-review:login-incomplete": "സൈൻ ഇൻ പൂർത്തിയായില്ല.", + "plugin-review:sign-in-failed": "സൈൻ ഇൻ ചെയ്യാനായില്ല.", + "plugin-review:session-expired-review": "നിങ്ങളുടെ സെഷൻ കാലഹരണപ്പെട്ടു. അവലോകനം നൽകാൻ വീണ്ടും സൈൻ ഇൻ ചെയ്യുക.", + "plugin-review:save-failed": "അവലോകനം സേവ് ചെയ്യാനായില്ല.", + "plugin-review:delete-confirmation": "നിങ്ങളുടെ അവലോകനം ഇല്ലാതാക്കണോ? ഇത് പഴയപടിയാക്കാനാകില്ല.", + "plugin-review:session-expired-continue": "നിങ്ങളുടെ സെഷൻ കാലഹരണപ്പെട്ടു. തുടരാൻ വീണ്ടും സൈൻ ഇൻ ചെയ്യുക.", + "plugin-review:delete-failed": "അവലോകനം ഇല്ലാതാക്കാനായില്ല.", + "plugin-review:you": "നിങ്ങൾ", + "plugin-review:edit-action": "അവലോകനം തിരുത്തുക", + "plugin-review:recommended": "ഈ പ്ലഗിൻ ശുപാർശ ചെയ്തു", + "plugin-review:error-max-length": "അവലോകനം {max} അക്ഷരങ്ങളോ അതിൽ കുറവോ ആയിരിക്കണം.", + "plugin-review:error-downvote-comment": "എന്താണ് മെച്ചപ്പെടുത്താനാകുന്നതെന്ന് ഡെവലപ്പറോട് പറയുക.", + "plugin-review:error-empty": "ഒരു അഭിപ്രായം ചേർക്കുക അല്ലെങ്കിൽ ഇഷ്ടപ്പെട്ടു തിരഞ്ഞെടുക്കുക.", + "plugin-review:unrated": "വിലയിരുത്തിയിട്ടില്ല" } diff --git a/src/lang/mm-unicode.json b/src/lang/mm-unicode.json index 00262a2a5..2a504340e 100644 --- a/src/lang/mm-unicode.json +++ b/src/lang/mm-unicode.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "ပလပ်အင် သုံးသပ်ချက်များ", + "plugin-review:account-check-failed": "သင့်အကောင့်ကို စစ်ဆေး၍မရပါ။ ထပ်မံကြိုးစားပါ။", + "plugin-review:sign-in": "သုံးသပ်ချက်ရေးရန် အကောင့်ဝင်ပါ", + "plugin-review:edit": "သင့်သုံးသပ်ချက်ကို ပြင်ဆင်ပါ", + "plugin-review:add": "သုံးသပ်ချက် ထည့်ရန်...", + "plugin-review:placeholder": "သင့်အတွေ့အကြုံကို မျှဝေပါ...", + "plugin-review:input-label": "ပလပ်အင် သုံးသပ်ချက်", + "plugin-review:vote-group-label": "မဲ", + "plugin-review:thumbs-up": "ကြိုက်သည်", + "plugin-review:thumbs-down": "မကြိုက်ပါ", + "plugin-review:delete": "သုံးသပ်ချက် ဖျက်ရန်", + "plugin-review:load-failed": "သုံးသပ်ချက်များကို ဖွင့်၍မရပါ။", + "plugin-review:retry": "ထပ်မံကြိုးစားပါ", + "plugin-review:empty-title": "သုံးသပ်ချက် မရှိသေးပါ", + "plugin-review:empty-description": "သင့်အတွေ့အကြုံကို ပထမဆုံး မျှဝေပါ။", + "plugin-review:yours": "သင့်သုံးသပ်ချက်", + "plugin-review:community": "အသိုင်းအဝိုင်း သုံးသပ်ချက်များ", + "plugin-review:no-other-reviews": "အခြားသုံးသပ်ချက် မရှိသေးပါ။", + "plugin-review:login-incomplete": "အကောင့်ဝင်ခြင်း မပြီးဆုံးပါ။", + "plugin-review:sign-in-failed": "အကောင့်ဝင်၍မရပါ။", + "plugin-review:session-expired-review": "သင့်အသုံးပြုချိန် ကုန်ဆုံးသွားပါပြီ။ သုံးသပ်ချက်ရေးရန် ပြန်ဝင်ပါ။", + "plugin-review:save-failed": "သုံးသပ်ချက်ကို သိမ်း၍မရပါ။", + "plugin-review:delete-confirmation": "သင့်သုံးသပ်ချက်ကို ဖျက်မလား။ ပြန်ယူ၍မရပါ။", + "plugin-review:session-expired-continue": "သင့်အသုံးပြုချိန် ကုန်ဆုံးသွားပါပြီ။ ဆက်လုပ်ရန် ပြန်ဝင်ပါ။", + "plugin-review:delete-failed": "သုံးသပ်ချက်ကို ဖျက်၍မရပါ။", + "plugin-review:you": "သင်", + "plugin-review:edit-action": "သုံးသပ်ချက် ပြင်ဆင်ရန်", + "plugin-review:recommended": "ဤပလပ်အင်ကို အကြံပြုထားသည်", + "plugin-review:error-max-length": "သုံးသပ်ချက်သည် စာလုံးရေ {max} လုံး သို့မဟုတ် ထိုထက်နည်းရမည်။", + "plugin-review:error-downvote-comment": "မည်သည့်အရာကို ပိုကောင်းအောင်လုပ်နိုင်ကြောင်း developer ကို ပြောပါ။", + "plugin-review:error-empty": "မှတ်ချက်ထည့်ပါ သို့မဟုတ် ကြိုက်သည်ကို ရွေးပါ။", + "plugin-review:unrated": "အဆင့်မသတ်မှတ်ရသေးပါ" } diff --git a/src/lang/mm-zawgyi.json b/src/lang/mm-zawgyi.json index 84cf0cfc9..2962e2486 100644 --- a/src/lang/mm-zawgyi.json +++ b/src/lang/mm-zawgyi.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "ပလပ္အင္ သုံးသပ္ခ်က္မ်ား", + "plugin-review:account-check-failed": "သင့္အေကာင့္ကို စစ္ေဆး၍မရပါ။ ထပ္မံႀကိဳးစားပါ။", + "plugin-review:sign-in": "သုံးသပ္ခ်က္ေရးရန္ အေကာင့္ဝင္ပါ", + "plugin-review:edit": "သင့္သုံးသပ္ခ်က္ကို ျပင္ဆင္ပါ", + "plugin-review:add": "သုံးသပ္ခ်က္ ထည့္ရန္...", + "plugin-review:placeholder": "သင့္အေတြ႕အႀကဳံကို မွ်ေဝပါ...", + "plugin-review:input-label": "ပလပ္အင္ သုံးသပ္ခ်က္", + "plugin-review:vote-group-label": "မဲ", + "plugin-review:thumbs-up": "ႀကိဳက္သည္", + "plugin-review:thumbs-down": "မႀကိဳက္ပါ", + "plugin-review:delete": "သုံးသပ္ခ်က္ ဖ်က္ရန္", + "plugin-review:load-failed": "သုံးသပ္ခ်က္မ်ားကို ဖြင့္၍မရပါ။", + "plugin-review:retry": "ထပ္မံႀကိဳးစားပါ", + "plugin-review:empty-title": "သုံးသပ္ခ်က္ မရွိေသးပါ", + "plugin-review:empty-description": "သင့္အေတြ႕အႀကဳံကို ပထမဆုံး မွ်ေဝပါ။", + "plugin-review:yours": "သင့္သုံးသပ္ခ်က္", + "plugin-review:community": "အသိုင္းအဝိုင္း သုံးသပ္ခ်က္မ်ား", + "plugin-review:no-other-reviews": "အျခားသုံးသပ္ခ်က္ မရွိေသးပါ။", + "plugin-review:login-incomplete": "အေကာင့္ဝင္ျခင္း မၿပီးဆုံးပါ။", + "plugin-review:sign-in-failed": "အေကာင့္ဝင္၍မရပါ။", + "plugin-review:session-expired-review": "သင့္အသုံးျပဳခ်ိန္ ကုန္ဆုံးသြားပါၿပီ။ သုံးသပ္ခ်က္ေရးရန္ ျပန္ဝင္ပါ။", + "plugin-review:save-failed": "သုံးသပ္ခ်က္ကို သိမ္း၍မရပါ။", + "plugin-review:delete-confirmation": "သင့္သုံးသပ္ခ်က္ကို ဖ်က္မလား။ ျပန္ယူ၍မရပါ။", + "plugin-review:session-expired-continue": "သင့္အသုံးျပဳခ်ိန္ ကုန္ဆုံးသြားပါၿပီ။ ဆက္လုပ္ရန္ ျပန္ဝင္ပါ။", + "plugin-review:delete-failed": "သုံးသပ္ခ်က္ကို ဖ်က္၍မရပါ။", + "plugin-review:you": "သင္", + "plugin-review:edit-action": "သုံးသပ္ခ်က္ ျပင္ဆင္ရန္", + "plugin-review:recommended": "ဤပလပ္အင္ကို အႀကံျပဳထားသည္", + "plugin-review:error-max-length": "သုံးသပ္ခ်က္သည္ စာလုံးေရ {max} လုံး သို႔မဟုတ္ ထိုထက္နည္းရမည္။", + "plugin-review:error-downvote-comment": "မည္သည့္အရာကို ပိုေကာင္းေအာင္လုပ္ႏိုင္ေၾကာင္း developer ကို ေျပာပါ။", + "plugin-review:error-empty": "မွတ္ခ်က္ထည့္ပါ သို႔မဟုတ္ ႀကိဳက္သည္ကို ေ႐ြးပါ။", + "plugin-review:unrated": "အဆင့္မသတ္မွတ္ရေသးပါ" } diff --git a/src/lang/pl-pl.json b/src/lang/pl-pl.json index 1634787b0..9f9aa5249 100644 --- a/src/lang/pl-pl.json +++ b/src/lang/pl-pl.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Opinie o wtyczce", + "plugin-review:account-check-failed": "Nie udało się sprawdzić Twojego konta. Spróbuj ponownie.", + "plugin-review:sign-in": "Zaloguj się, aby dodać opinię", + "plugin-review:edit": "Edytuj swoją opinię", + "plugin-review:add": "Dodaj opinię…", + "plugin-review:placeholder": "Podziel się swoim doświadczeniem…", + "plugin-review:input-label": "Opinia o wtyczce", + "plugin-review:vote-group-label": "Głos", + "plugin-review:thumbs-up": "Lubię", + "plugin-review:thumbs-down": "Nie lubię", + "plugin-review:delete": "Usuń opinię", + "plugin-review:load-failed": "Nie udało się wczytać opinii.", + "plugin-review:retry": "Spróbuj ponownie", + "plugin-review:empty-title": "Brak opinii", + "plugin-review:empty-description": "Podziel się swoim doświadczeniem jako pierwsza osoba.", + "plugin-review:yours": "Twoja opinia", + "plugin-review:community": "Opinie społeczności", + "plugin-review:no-other-reviews": "Nie ma jeszcze innych opinii.", + "plugin-review:login-incomplete": "Logowanie nie zostało ukończone.", + "plugin-review:sign-in-failed": "Nie udało się zalogować.", + "plugin-review:session-expired-review": "Twoja sesja wygasła. Zaloguj się ponownie, aby dodać opinię.", + "plugin-review:save-failed": "Nie udało się zapisać opinii.", + "plugin-review:delete-confirmation": "Usunąć Twoją opinię? Tej operacji nie można cofnąć.", + "plugin-review:session-expired-continue": "Twoja sesja wygasła. Zaloguj się ponownie, aby kontynuować.", + "plugin-review:delete-failed": "Nie udało się usunąć opinii.", + "plugin-review:you": "Ty", + "plugin-review:edit-action": "Edytuj opinię", + "plugin-review:recommended": "Poleca tę wtyczkę", + "plugin-review:error-max-length": "Opinia może mieć maksymalnie {max} znaków.", + "plugin-review:error-downvote-comment": "Powiedz deweloperowi, co można poprawić.", + "plugin-review:error-empty": "Dodaj komentarz lub wybierz „Lubię”.", + "plugin-review:unrated": "Bez oceny" } diff --git a/src/lang/pt-br.json b/src/lang/pt-br.json index 9f6552f76..1affbea19 100644 --- a/src/lang/pt-br.json +++ b/src/lang/pt-br.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Avaliações do plugin", + "plugin-review:account-check-failed": "Não foi possível verificar sua conta. Tente novamente.", + "plugin-review:sign-in": "Entre para avaliar", + "plugin-review:edit": "Editar sua avaliação", + "plugin-review:add": "Adicionar uma avaliação...", + "plugin-review:placeholder": "Compartilhe sua experiência...", + "plugin-review:input-label": "Avaliação do plugin", + "plugin-review:vote-group-label": "Voto", + "plugin-review:thumbs-up": "Gostei", + "plugin-review:thumbs-down": "Não gostei", + "plugin-review:delete": "Excluir avaliação", + "plugin-review:load-failed": "Não foi possível carregar as avaliações.", + "plugin-review:retry": "Tentar novamente", + "plugin-review:empty-title": "Ainda não há avaliações", + "plugin-review:empty-description": "Seja a primeira pessoa a compartilhar sua experiência.", + "plugin-review:yours": "Sua avaliação", + "plugin-review:community": "Avaliações da comunidade", + "plugin-review:no-other-reviews": "Ainda não há outras avaliações.", + "plugin-review:login-incomplete": "O login não foi concluído.", + "plugin-review:sign-in-failed": "Não foi possível entrar.", + "plugin-review:session-expired-review": "Sua sessão expirou. Entre novamente para avaliar.", + "plugin-review:save-failed": "Não foi possível salvar a avaliação.", + "plugin-review:delete-confirmation": "Excluir sua avaliação? Esta ação não pode ser desfeita.", + "plugin-review:session-expired-continue": "Sua sessão expirou. Entre novamente para continuar.", + "plugin-review:delete-failed": "Não foi possível excluir a avaliação.", + "plugin-review:you": "Você", + "plugin-review:edit-action": "Editar avaliação", + "plugin-review:recommended": "Recomendou este plugin", + "plugin-review:error-max-length": "A avaliação deve ter no máximo {max} caracteres.", + "plugin-review:error-downvote-comment": "Diga ao desenvolvedor o que pode ser melhorado.", + "plugin-review:error-empty": "Adicione um comentário ou escolha Gostei.", + "plugin-review:unrated": "Sem avaliação" } diff --git a/src/lang/pu-in.json b/src/lang/pu-in.json index 0cdbc0b31..22734a25c 100644 --- a/src/lang/pu-in.json +++ b/src/lang/pu-in.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "ਪਲੱਗਇਨ ਸਮੀਖਿਆਵਾਂ", + "plugin-review:account-check-failed": "ਤੁਹਾਡੇ ਖਾਤੇ ਦੀ ਜਾਂਚ ਨਹੀਂ ਹੋ ਸਕੀ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।", + "plugin-review:sign-in": "ਸਮੀਖਿਆ ਦੇਣ ਲਈ ਸਾਈਨ ਇਨ ਕਰੋ", + "plugin-review:edit": "ਆਪਣੀ ਸਮੀਖਿਆ ਸੋਧੋ", + "plugin-review:add": "ਸਮੀਖਿਆ ਸ਼ਾਮਲ ਕਰੋ...", + "plugin-review:placeholder": "ਆਪਣਾ ਅਨੁਭਵ ਸਾਂਝਾ ਕਰੋ...", + "plugin-review:input-label": "ਪਲੱਗਇਨ ਸਮੀਖਿਆ", + "plugin-review:vote-group-label": "ਵੋਟ", + "plugin-review:thumbs-up": "ਪਸੰਦ", + "plugin-review:thumbs-down": "ਨਾਪਸੰਦ", + "plugin-review:delete": "ਸਮੀਖਿਆ ਮਿਟਾਓ", + "plugin-review:load-failed": "ਸਮੀਖਿਆਵਾਂ ਲੋਡ ਨਹੀਂ ਹੋ ਸਕੀਆਂ।", + "plugin-review:retry": "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ", + "plugin-review:empty-title": "ਹਾਲੇ ਕੋਈ ਸਮੀਖਿਆ ਨਹੀਂ", + "plugin-review:empty-description": "ਆਪਣਾ ਅਨੁਭਵ ਸਾਂਝਾ ਕਰਨ ਵਾਲੇ ਪਹਿਲੇ ਵਿਅਕਤੀ ਬਣੋ।", + "plugin-review:yours": "ਤੁਹਾਡੀ ਸਮੀਖਿਆ", + "plugin-review:community": "ਭਾਈਚਾਰੇ ਦੀਆਂ ਸਮੀਖਿਆਵਾਂ", + "plugin-review:no-other-reviews": "ਹਾਲੇ ਹੋਰ ਕੋਈ ਸਮੀਖਿਆ ਨਹੀਂ।", + "plugin-review:login-incomplete": "ਸਾਈਨ ਇਨ ਪੂਰਾ ਨਹੀਂ ਹੋਇਆ।", + "plugin-review:sign-in-failed": "ਸਾਈਨ ਇਨ ਨਹੀਂ ਹੋ ਸਕਿਆ।", + "plugin-review:session-expired-review": "ਤੁਹਾਡਾ ਸੈਸ਼ਨ ਖਤਮ ਹੋ ਗਿਆ। ਸਮੀਖਿਆ ਦੇਣ ਲਈ ਦੁਬਾਰਾ ਸਾਈਨ ਇਨ ਕਰੋ।", + "plugin-review:save-failed": "ਸਮੀਖਿਆ ਸੰਭਾਲੀ ਨਹੀਂ ਜਾ ਸਕੀ।", + "plugin-review:delete-confirmation": "ਆਪਣੀ ਸਮੀਖਿਆ ਮਿਟਾਉਣੀ ਹੈ? ਇਹ ਵਾਪਸ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।", + "plugin-review:session-expired-continue": "ਤੁਹਾਡਾ ਸੈਸ਼ਨ ਖਤਮ ਹੋ ਗਿਆ। ਜਾਰੀ ਰੱਖਣ ਲਈ ਦੁਬਾਰਾ ਸਾਈਨ ਇਨ ਕਰੋ।", + "plugin-review:delete-failed": "ਸਮੀਖਿਆ ਮਿਟਾਈ ਨਹੀਂ ਜਾ ਸਕੀ।", + "plugin-review:you": "ਤੁਸੀਂ", + "plugin-review:edit-action": "ਸਮੀਖਿਆ ਸੋਧੋ", + "plugin-review:recommended": "ਇਸ ਪਲੱਗਇਨ ਦੀ ਸਿਫ਼ਾਰਸ਼ ਕੀਤੀ", + "plugin-review:error-max-length": "ਸਮੀਖਿਆ {max} ਅੱਖਰਾਂ ਜਾਂ ਇਸ ਤੋਂ ਘੱਟ ਦੀ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ।", + "plugin-review:error-downvote-comment": "ਡਿਵੈਲਪਰ ਨੂੰ ਦੱਸੋ ਕਿ ਕੀ ਸੁਧਾਰਿਆ ਜਾ ਸਕਦਾ ਹੈ।", + "plugin-review:error-empty": "ਟਿੱਪਣੀ ਸ਼ਾਮਲ ਕਰੋ ਜਾਂ ਪਸੰਦ ਚੁਣੋ।", + "plugin-review:unrated": "ਹਾਲੇ ਮੁਲਾਂਕਣ ਨਹੀਂ" } diff --git a/src/lang/ru-ru.json b/src/lang/ru-ru.json index 4095204dc..1d28242fc 100644 --- a/src/lang/ru-ru.json +++ b/src/lang/ru-ru.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Отзывы о плагине", + "plugin-review:account-check-failed": "Не удалось проверить вашу учётную запись. Повторите попытку.", + "plugin-review:sign-in": "Войти, чтобы оставить отзыв", + "plugin-review:edit": "Редактировать свой отзыв", + "plugin-review:add": "Добавить отзыв…", + "plugin-review:placeholder": "Поделитесь своим опытом…", + "plugin-review:input-label": "Отзыв о плагине", + "plugin-review:vote-group-label": "Оценка", + "plugin-review:thumbs-up": "Нравится", + "plugin-review:thumbs-down": "Не нравится", + "plugin-review:delete": "Удалить отзыв", + "plugin-review:load-failed": "Не удалось загрузить отзывы.", + "plugin-review:retry": "Повторить", + "plugin-review:empty-title": "Отзывов пока нет", + "plugin-review:empty-description": "Станьте первым, кто поделится своим опытом.", + "plugin-review:yours": "Ваш отзыв", + "plugin-review:community": "Отзывы сообщества", + "plugin-review:no-other-reviews": "Других отзывов пока нет.", + "plugin-review:login-incomplete": "Вход не был завершён.", + "plugin-review:sign-in-failed": "Не удалось войти.", + "plugin-review:session-expired-review": "Сеанс истёк. Войдите снова, чтобы оставить отзыв.", + "plugin-review:save-failed": "Не удалось сохранить отзыв.", + "plugin-review:delete-confirmation": "Удалить ваш отзыв? Это действие нельзя отменить.", + "plugin-review:session-expired-continue": "Сеанс истёк. Войдите снова, чтобы продолжить.", + "plugin-review:delete-failed": "Не удалось удалить отзыв.", + "plugin-review:you": "Вы", + "plugin-review:edit-action": "Редактировать отзыв", + "plugin-review:recommended": "Рекомендует этот плагин", + "plugin-review:error-max-length": "Отзыв должен содержать не более {max} символов.", + "plugin-review:error-downvote-comment": "Расскажите разработчику, что можно улучшить.", + "plugin-review:error-empty": "Добавьте комментарий или выберите «Нравится».", + "plugin-review:unrated": "Нет оценок" } diff --git a/src/lang/tl-ph.json b/src/lang/tl-ph.json index 509640d0c..811030d74 100644 --- a/src/lang/tl-ph.json +++ b/src/lang/tl-ph.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Mga review ng plugin", + "plugin-review:account-check-failed": "Hindi masuri ang iyong account. Subukan muli.", + "plugin-review:sign-in": "Mag-sign in para mag-review", + "plugin-review:edit": "I-edit ang iyong review", + "plugin-review:add": "Magdagdag ng review...", + "plugin-review:placeholder": "Ibahagi ang iyong karanasan...", + "plugin-review:input-label": "Review ng plugin", + "plugin-review:vote-group-label": "Boto", + "plugin-review:thumbs-up": "Gusto", + "plugin-review:thumbs-down": "Hindi gusto", + "plugin-review:delete": "I-delete ang review", + "plugin-review:load-failed": "Hindi ma-load ang mga review.", + "plugin-review:retry": "Subukan muli", + "plugin-review:empty-title": "Wala pang review", + "plugin-review:empty-description": "Mauna sa pagbabahagi ng iyong karanasan.", + "plugin-review:yours": "Iyong review", + "plugin-review:community": "Mga review ng komunidad", + "plugin-review:no-other-reviews": "Wala pang ibang review.", + "plugin-review:login-incomplete": "Hindi natapos ang pag-sign in.", + "plugin-review:sign-in-failed": "Hindi makapag-sign in.", + "plugin-review:session-expired-review": "Nag-expire ang iyong session. Mag-sign in muli para mag-review.", + "plugin-review:save-failed": "Hindi mai-save ang review.", + "plugin-review:delete-confirmation": "I-delete ang iyong review? Hindi na ito maibabalik.", + "plugin-review:session-expired-continue": "Nag-expire ang iyong session. Mag-sign in muli para magpatuloy.", + "plugin-review:delete-failed": "Hindi ma-delete ang review.", + "plugin-review:you": "Ikaw", + "plugin-review:edit-action": "I-edit ang review", + "plugin-review:recommended": "Inirerekomenda ang plugin na ito", + "plugin-review:error-max-length": "Dapat {max} character o mas kaunti ang review.", + "plugin-review:error-downvote-comment": "Sabihin sa developer kung ano ang maaaring mapabuti.", + "plugin-review:error-empty": "Magdagdag ng komento o piliin ang Gusto.", + "plugin-review:unrated": "Walang rating" } diff --git a/src/lang/tr-tr.json b/src/lang/tr-tr.json index 327b4556d..742f48ad5 100644 --- a/src/lang/tr-tr.json +++ b/src/lang/tr-tr.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Eklenti değerlendirmeleri", + "plugin-review:account-check-failed": "Hesabınız kontrol edilemedi. Tekrar deneyin.", + "plugin-review:sign-in": "Değerlendirmek için oturum açın", + "plugin-review:edit": "Değerlendirmenizi düzenleyin", + "plugin-review:add": "Değerlendirme ekleyin...", + "plugin-review:placeholder": "Deneyiminizi paylaşın...", + "plugin-review:input-label": "Eklenti değerlendirmesi", + "plugin-review:vote-group-label": "Oy", + "plugin-review:thumbs-up": "Beğendim", + "plugin-review:thumbs-down": "Beğenmedim", + "plugin-review:delete": "Değerlendirmeyi sil", + "plugin-review:load-failed": "Değerlendirmeler yüklenemedi.", + "plugin-review:retry": "Tekrar dene", + "plugin-review:empty-title": "Henüz değerlendirme yok", + "plugin-review:empty-description": "Deneyiminizi ilk paylaşan siz olun.", + "plugin-review:yours": "Değerlendirmeniz", + "plugin-review:community": "Topluluk değerlendirmeleri", + "plugin-review:no-other-reviews": "Henüz başka değerlendirme yok.", + "plugin-review:login-incomplete": "Oturum açma tamamlanmadı.", + "plugin-review:sign-in-failed": "Oturum açılamadı.", + "plugin-review:session-expired-review": "Oturumunuzun süresi doldu. Değerlendirmek için tekrar oturum açın.", + "plugin-review:save-failed": "Değerlendirme kaydedilemedi.", + "plugin-review:delete-confirmation": "Değerlendirmeniz silinsin mi? Bu işlem geri alınamaz.", + "plugin-review:session-expired-continue": "Oturumunuzun süresi doldu. Devam etmek için tekrar oturum açın.", + "plugin-review:delete-failed": "Değerlendirme silinemedi.", + "plugin-review:you": "Siz", + "plugin-review:edit-action": "Değerlendirmeyi düzenle", + "plugin-review:recommended": "Bu eklentiyi önerdi", + "plugin-review:error-max-length": "Değerlendirme en fazla {max} karakter olmalıdır.", + "plugin-review:error-downvote-comment": "Geliştiriciye nelerin iyileştirilebileceğini söyleyin.", + "plugin-review:error-empty": "Yorum ekleyin veya Beğendim’i seçin.", + "plugin-review:unrated": "Değerlendirilmedi" } diff --git a/src/lang/uk-ua.json b/src/lang/uk-ua.json index 1f14b264a..b6522b1d6 100644 --- a/src/lang/uk-ua.json +++ b/src/lang/uk-ua.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Відгуки про плагін", + "plugin-review:account-check-failed": "Не вдалося перевірити ваш обліковий запис. Спробуйте ще раз.", + "plugin-review:sign-in": "Увійти, щоб залишити відгук", + "plugin-review:edit": "Редагувати свій відгук", + "plugin-review:add": "Додати відгук…", + "plugin-review:placeholder": "Поділіться своїм досвідом…", + "plugin-review:input-label": "Відгук про плагін", + "plugin-review:vote-group-label": "Оцінка", + "plugin-review:thumbs-up": "Подобається", + "plugin-review:thumbs-down": "Не подобається", + "plugin-review:delete": "Видалити відгук", + "plugin-review:load-failed": "Не вдалося завантажити відгуки.", + "plugin-review:retry": "Спробувати ще раз", + "plugin-review:empty-title": "Відгуків ще немає", + "plugin-review:empty-description": "Поділіться своїм досвідом першими.", + "plugin-review:yours": "Ваш відгук", + "plugin-review:community": "Відгуки спільноти", + "plugin-review:no-other-reviews": "Інших відгуків ще немає.", + "plugin-review:login-incomplete": "Вхід не було завершено.", + "plugin-review:sign-in-failed": "Не вдалося ввійти.", + "plugin-review:session-expired-review": "Сеанс завершився. Увійдіть знову, щоб залишити відгук.", + "plugin-review:save-failed": "Не вдалося зберегти відгук.", + "plugin-review:delete-confirmation": "Видалити ваш відгук? Цю дію не можна скасувати.", + "plugin-review:session-expired-continue": "Сеанс завершився. Увійдіть знову, щоб продовжити.", + "plugin-review:delete-failed": "Не вдалося видалити відгук.", + "plugin-review:you": "Ви", + "plugin-review:edit-action": "Редагувати відгук", + "plugin-review:recommended": "Рекомендує цей плагін", + "plugin-review:error-max-length": "Відгук має містити не більше {max} символів.", + "plugin-review:error-downvote-comment": "Розкажіть розробнику, що можна покращити.", + "plugin-review:error-empty": "Додайте коментар або виберіть «Подобається».", + "plugin-review:unrated": "Без оцінки" } diff --git a/src/lang/uz-uz.json b/src/lang/uz-uz.json index eec005214..1f4272b96 100644 --- a/src/lang/uz-uz.json +++ b/src/lang/uz-uz.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Plagin sharhlari", + "plugin-review:account-check-failed": "Hisobingizni tekshirib bo‘lmadi. Qayta urinib ko‘ring.", + "plugin-review:sign-in": "Sharh qoldirish uchun kiring", + "plugin-review:edit": "Sharhingizni tahrirlash", + "plugin-review:add": "Sharh qo‘shish...", + "plugin-review:placeholder": "Tajribangizni ulashing...", + "plugin-review:input-label": "Plagin sharhi", + "plugin-review:vote-group-label": "Ovoz", + "plugin-review:thumbs-up": "Yoqdi", + "plugin-review:thumbs-down": "Yoqmadi", + "plugin-review:delete": "Sharhni o‘chirish", + "plugin-review:load-failed": "Sharhlarni yuklab bo‘lmadi.", + "plugin-review:retry": "Qayta urinish", + "plugin-review:empty-title": "Hozircha sharhlar yo‘q", + "plugin-review:empty-description": "Tajribangizni birinchi bo‘lib ulashing.", + "plugin-review:yours": "Sharhingiz", + "plugin-review:community": "Hamjamiyat sharhlari", + "plugin-review:no-other-reviews": "Hozircha boshqa sharhlar yo‘q.", + "plugin-review:login-incomplete": "Kirish yakunlanmadi.", + "plugin-review:sign-in-failed": "Tizimga kirib bo‘lmadi.", + "plugin-review:session-expired-review": "Seans muddati tugadi. Sharh qoldirish uchun qayta kiring.", + "plugin-review:save-failed": "Sharhni saqlab bo‘lmadi.", + "plugin-review:delete-confirmation": "Sharhingiz o‘chirilsinmi? Buni ortga qaytarib bo‘lmaydi.", + "plugin-review:session-expired-continue": "Seans muddati tugadi. Davom etish uchun qayta kiring.", + "plugin-review:delete-failed": "Sharhni o‘chirib bo‘lmadi.", + "plugin-review:you": "Siz", + "plugin-review:edit-action": "Sharhni tahrirlash", + "plugin-review:recommended": "Bu plaginni tavsiya qildi", + "plugin-review:error-max-length": "Sharh {max} ta yoki undan kam belgidan iborat bo‘lishi kerak.", + "plugin-review:error-downvote-comment": "Dasturchiga nimani yaxshilash mumkinligini ayting.", + "plugin-review:error-empty": "Izoh qo‘shing yoki “Yoqdi”ni tanlang.", + "plugin-review:unrated": "Baholanmagan" } diff --git a/src/lang/vi-vn.json b/src/lang/vi-vn.json index 4c992e250..fd4eee262 100644 --- a/src/lang/vi-vn.json +++ b/src/lang/vi-vn.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "Đánh giá plugin", + "plugin-review:account-check-failed": "Không thể kiểm tra tài khoản của bạn. Hãy thử lại.", + "plugin-review:sign-in": "Đăng nhập để đánh giá", + "plugin-review:edit": "Chỉnh sửa đánh giá của bạn", + "plugin-review:add": "Thêm đánh giá...", + "plugin-review:placeholder": "Chia sẻ trải nghiệm của bạn...", + "plugin-review:input-label": "Đánh giá plugin", + "plugin-review:vote-group-label": "Bình chọn", + "plugin-review:thumbs-up": "Thích", + "plugin-review:thumbs-down": "Không thích", + "plugin-review:delete": "Xóa đánh giá", + "plugin-review:load-failed": "Không thể tải các đánh giá.", + "plugin-review:retry": "Thử lại", + "plugin-review:empty-title": "Chưa có đánh giá", + "plugin-review:empty-description": "Hãy là người đầu tiên chia sẻ trải nghiệm.", + "plugin-review:yours": "Đánh giá của bạn", + "plugin-review:community": "Đánh giá từ cộng đồng", + "plugin-review:no-other-reviews": "Chưa có đánh giá nào khác.", + "plugin-review:login-incomplete": "Quá trình đăng nhập chưa hoàn tất.", + "plugin-review:sign-in-failed": "Không thể đăng nhập.", + "plugin-review:session-expired-review": "Phiên của bạn đã hết hạn. Hãy đăng nhập lại để đánh giá.", + "plugin-review:save-failed": "Không thể lưu đánh giá.", + "plugin-review:delete-confirmation": "Xóa đánh giá của bạn? Bạn không thể hoàn tác thao tác này.", + "plugin-review:session-expired-continue": "Phiên của bạn đã hết hạn. Hãy đăng nhập lại để tiếp tục.", + "plugin-review:delete-failed": "Không thể xóa đánh giá.", + "plugin-review:you": "Bạn", + "plugin-review:edit-action": "Chỉnh sửa đánh giá", + "plugin-review:recommended": "Đã đề xuất plugin này", + "plugin-review:error-max-length": "Đánh giá phải có tối đa {max} ký tự.", + "plugin-review:error-downvote-comment": "Hãy cho nhà phát triển biết điều gì có thể được cải thiện.", + "plugin-review:error-empty": "Thêm bình luận hoặc chọn Thích.", + "plugin-review:unrated": "Chưa đánh giá" } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 377aa0f00..d8067ddb0 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "复制时应用排除项", "settings-info-app-use-file-operation-exclusions": "在复制和粘贴操作期间,跳过符合排除规则的文件和文件夹。", "horizontal scroll margin": "水平滚动边距", - "settings-info-horizontal-scroll-margin": "在关闭自动换行时,为最长行的末尾添加空白区域。请输入任意非负整数的像素值。" + "settings-info-horizontal-scroll-margin": "在关闭自动换行时,为最长行的末尾添加空白区域。请输入任意非负整数的像素值。", + "plugin-review:dialog-label": "插件评价", + "plugin-review:account-check-failed": "无法检查你的账户,请重试。", + "plugin-review:sign-in": "登录后评价", + "plugin-review:edit": "编辑你的评价", + "plugin-review:add": "添加评价…", + "plugin-review:placeholder": "分享你的使用体验…", + "plugin-review:input-label": "插件评价", + "plugin-review:vote-group-label": "投票", + "plugin-review:thumbs-up": "赞", + "plugin-review:thumbs-down": "踩", + "plugin-review:delete": "删除评价", + "plugin-review:load-failed": "无法加载评价。", + "plugin-review:retry": "重试", + "plugin-review:empty-title": "暂无评价", + "plugin-review:empty-description": "成为第一个分享使用体验的人。", + "plugin-review:yours": "你的评价", + "plugin-review:community": "社区评价", + "plugin-review:no-other-reviews": "暂无其他评价。", + "plugin-review:login-incomplete": "登录未完成。", + "plugin-review:sign-in-failed": "无法登录。", + "plugin-review:session-expired-review": "会话已过期,请重新登录后评价。", + "plugin-review:save-failed": "无法保存评价。", + "plugin-review:delete-confirmation": "要删除你的评价吗?此操作无法撤销。", + "plugin-review:session-expired-continue": "会话已过期,请重新登录后继续。", + "plugin-review:delete-failed": "无法删除评价。", + "plugin-review:you": "你", + "plugin-review:edit-action": "编辑评价", + "plugin-review:recommended": "推荐了此插件", + "plugin-review:error-max-length": "评价不能超过 {max} 个字符。", + "plugin-review:error-downvote-comment": "告诉开发者哪些地方可以改进。", + "plugin-review:error-empty": "添加评论或选择“赞”。", + "plugin-review:unrated": "暂无评分" } diff --git a/src/lang/zh-hant.json b/src/lang/zh-hant.json index 2e450d79c..2bbe289d2 100644 --- a/src/lang/zh-hant.json +++ b/src/lang/zh-hant.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "複製時應用排除項", "settings-info-app-use-file-operation-exclusions": "在複製和粘貼操作期間,跳過符合排除規則的文件和文件夾。", "horizontal scroll margin": "水平滾動邊距", - "settings-info-horizontal-scroll-margin": "在關閉自動換行時,爲最長行的末尾添加空白區域。請輸入任意非負整數的像素值。" + "settings-info-horizontal-scroll-margin": "在關閉自動換行時,爲最長行的末尾添加空白區域。請輸入任意非負整數的像素值。", + "plugin-review:dialog-label": "外掛評論", + "plugin-review:account-check-failed": "無法檢查你的帳戶,請再試一次。", + "plugin-review:sign-in": "登入以撰寫評論", + "plugin-review:edit": "編輯你的評論", + "plugin-review:add": "新增評論…", + "plugin-review:placeholder": "分享你的使用體驗…", + "plugin-review:input-label": "外掛評論", + "plugin-review:vote-group-label": "投票", + "plugin-review:thumbs-up": "讚", + "plugin-review:thumbs-down": "倒讚", + "plugin-review:delete": "刪除評論", + "plugin-review:load-failed": "無法載入評論。", + "plugin-review:retry": "重試", + "plugin-review:empty-title": "尚無評論", + "plugin-review:empty-description": "成為第一個分享使用體驗的人。", + "plugin-review:yours": "你的評論", + "plugin-review:community": "社群評論", + "plugin-review:no-other-reviews": "尚無其他評論。", + "plugin-review:login-incomplete": "登入未完成。", + "plugin-review:sign-in-failed": "無法登入。", + "plugin-review:session-expired-review": "工作階段已過期,請重新登入以撰寫評論。", + "plugin-review:save-failed": "無法儲存評論。", + "plugin-review:delete-confirmation": "要刪除你的評論嗎?此操作無法復原。", + "plugin-review:session-expired-continue": "工作階段已過期,請重新登入以繼續。", + "plugin-review:delete-failed": "無法刪除評論。", + "plugin-review:you": "你", + "plugin-review:edit-action": "編輯評論", + "plugin-review:recommended": "推薦了此外掛", + "plugin-review:error-max-length": "評論不得超過 {max} 個字元。", + "plugin-review:error-downvote-comment": "告訴開發者哪些地方可以改進。", + "plugin-review:error-empty": "新增留言或選擇「讚」。", + "plugin-review:unrated": "尚未評分" } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 734c10caf..add0b58a0 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -835,5 +835,37 @@ "apply exclusions when copying": "Apply exclusions when copying", "settings-info-app-use-file-operation-exclusions": "Skip files and folders matching the exclusion patterns during copy and paste operations.", "horizontal scroll margin": "Horizontal scroll margin", - "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value." + "settings-info-horizontal-scroll-margin": "Adds empty space after the longest line when word wrap is off. Enter any non-negative whole-pixel value.", + "plugin-review:dialog-label": "外掛評論", + "plugin-review:account-check-failed": "無法檢查你的帳號,請再試一次。", + "plugin-review:sign-in": "登入以撰寫評論", + "plugin-review:edit": "編輯你的評論", + "plugin-review:add": "新增評論…", + "plugin-review:placeholder": "分享你的使用心得…", + "plugin-review:input-label": "外掛評論", + "plugin-review:vote-group-label": "評分", + "plugin-review:thumbs-up": "讚", + "plugin-review:thumbs-down": "倒讚", + "plugin-review:delete": "刪除評論", + "plugin-review:load-failed": "無法載入評論。", + "plugin-review:retry": "重試", + "plugin-review:empty-title": "目前沒有評論", + "plugin-review:empty-description": "成為第一個分享使用心得的人。", + "plugin-review:yours": "你的評論", + "plugin-review:community": "社群評論", + "plugin-review:no-other-reviews": "目前沒有其他評論。", + "plugin-review:login-incomplete": "登入未完成。", + "plugin-review:sign-in-failed": "無法登入。", + "plugin-review:session-expired-review": "工作階段已過期,請重新登入以撰寫評論。", + "plugin-review:save-failed": "無法儲存評論。", + "plugin-review:delete-confirmation": "要刪除你的評論嗎?此動作無法復原。", + "plugin-review:session-expired-continue": "工作階段已過期,請重新登入以繼續。", + "plugin-review:delete-failed": "無法刪除評論。", + "plugin-review:you": "你", + "plugin-review:edit-action": "編輯評論", + "plugin-review:recommended": "推薦這個外掛", + "plugin-review:error-max-length": "評論不得超過 {max} 個字元。", + "plugin-review:error-downvote-comment": "告訴開發者哪些地方可以改進。", + "plugin-review:error-empty": "新增留言或選擇「讚」。", + "plugin-review:unrated": "尚未評分" } diff --git a/src/lib/lang.js b/src/lib/lang.js index b8a2966ab..77a21e1c6 100644 --- a/src/lib/lang.js +++ b/src/lib/lang.js @@ -187,6 +187,34 @@ const langMap = { }, }; +const intlLocaleOverrides = { + "ir-fa": "fa-IR", + "mm-unicode": "my-MM", + "mm-zawgyi": "my-MM", + "pu-in": "pa-IN", +}; +const rtlLanguages = new Set(["ar", "fa", "he"]); + +export function getIntlLocale(code) { + const normalizedCode = code?.toLowerCase(); + const locale = + intlLocaleOverrides[normalizedCode] || + (normalizedCode in langMap + ? normalizedCode + : globalThis.navigator?.language || "en-US"); + + try { + return Intl.getCanonicalLocales(locale)[0]; + } catch { + return "en-US"; + } +} + +export function getLocaleDirection(locale) { + const language = locale?.split("-")[0]?.toLowerCase(); + return rtlLanguages.has(language) ? "rtl" : "ltr"; +} + export default { async set(code) { code = code?.toLowerCase(); diff --git a/src/pages/plugin/plugin.scss b/src/pages/plugin/plugin.scss index cf88e1b22..68c1bc211 100644 --- a/src/pages/plugin/plugin.scss +++ b/src/pages/plugin/plugin.scss @@ -608,117 +608,515 @@ position: fixed; display: flex; flex-direction: column; - width: 100vw; - height: 70vh; - top: auto; + width: min(100vw, 720px); + height: 76vh; + height: min(76dvh, 680px); + max-height: calc(100vh - 16px - env(safe-area-inset-top)); + max-height: calc(100dvh - 16px - env(safe-area-inset-top)); + inset-inline: 0; bottom: 0; - background-color: rgb(255, 255, 255); + margin: auto; background-color: var(--secondary-color); + color: var(--primary-text-color); z-index: 999; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); box-shadow: 0 0 20px var(--box-shadow-color); - border-radius: 40px 40px 0 0; + border-radius: 24px 24px 0 0; overflow: hidden; animation: slide-up 0.3s ease-in-out; + overscroll-behavior: contain; &.hide { - transition: all 0.3s ease-in-out !important; + transition: transform 0.3s ease-in-out !important; transform: translateY(100%) !important; } .reviews-header { - height: 40px; + position: relative; + flex: 0 0 52px; display: flex; align-items: center; justify-content: center; + border-bottom: 1px solid var(--border-color); + background: color-mix(in srgb, var(--secondary-color), var(--popup-background-color) 28%); + user-select: none; + touch-action: pan-y; - &::after { - content: ""; + .reviews-drag-handle { + position: absolute; + top: 7px; + width: 40px; + height: 4px; + border-radius: 999px; + background: color-mix(in srgb, var(--secondary-text-color) 50%, transparent); + } + + strong { + margin-top: 5px; + font-size: 16px; + font-weight: 600; + text-transform: capitalize; + } + + .reviews-close { + position: absolute; + inset-inline-end: 8px; + bottom: 6px; width: 40px; - height: 5px; - border-radius: 2.5px; - background-color: rgba(0, 0, 0, 0.2); + height: 40px; + padding: 0; + border: 0; + border-radius: 50%; + background: transparent; + color: var(--secondary-text-color); + font-size: 18px; + + &:active { + background: color-mix(in srgb, var(--primary-text-color) 7%, transparent); + } + } + } + + button { + font: inherit; + + &:focus-visible { + outline: 2px solid var(--active-icon-color); + outline-offset: 2px; + } + + &:disabled { + cursor: default; + opacity: 0.55; + } + } + + .review-composer { + flex: 0 0 auto; + min-height: 64px; + padding: 12px 16px; + box-sizing: border-box; + border-bottom: 1px solid var(--border-color); + background: var(--secondary-color); + + &.review-composer-hidden { + display: none; + } + + &.loading { + @include mixins.loader(24px); } } - .write-review { - padding: 0 10px; + .review-entry-surface { + width: 100%; + padding: 10px; + box-sizing: border-box; + border: 1px solid var(--popup-border-color); + border-radius: 14px; + background: color-mix(in srgb, var(--secondary-color), var(--popup-background-color) 56%); + } + + .review-add-row, + .review-sign-in, + .review-editor-main { display: flex; - align-content: center; - justify-content: center; - width: fit-content; - margin: auto; + align-items: center; + gap: 12px; + } - span { - height: fit-content; - margin: auto; + .review-editor-main { + align-items: flex-start; + } + + .review-avatar, + .user-profile { + position: relative; + display: grid; + flex: 0 0 auto; + place-items: center; + width: 36px; + height: 36px; + overflow: hidden; + border-radius: 50%; + background: color-mix(in srgb, var(--primary-text-color) 8%, transparent); + color: var(--secondary-text-color); + font-size: 20px; + + > img { + position: absolute; + inset: 0; + display: block; + width: 100%; + height: 100%; + object-fit: cover; } + } + + .review-sign-in > .review-avatar.icon { + display: grid; + place-items: center; + background: color-mix(in srgb, var(--primary-text-color) 8%, transparent); + color: var(--secondary-text-color); + font-size: 20px; + } + + .review-sign-in-content, + .review-editor-fields { + min-width: 0; + flex: 1; + } - .icon { - height: 30px; - margin-right: 10px; + .review-editor-fields { + padding: 8px 10px 4px; + box-sizing: border-box; + border: 1px solid var(--border-color); + border-radius: 12px; + background: var(--secondary-color); + transition: border-color 160ms ease, box-shadow 160ms ease; + + &:focus-within { + border-color: var(--active-icon-color); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--active-icon-color) 12%, transparent); } } + .review-add-button { + width: 100%; + min-height: 40px; + padding: 7px 10px; + border: 0; + border-radius: 10px; + background: transparent; + color: var(--secondary-text-color); + text-align: start; + font-weight: 500; + + &:active { + background: color-mix(in srgb, var(--primary-text-color) 6%, transparent); + color: var(--primary-text-color); + } + } + + .review-editor textarea { + display: block; + width: 100%; + min-height: 32px; + max-height: 144px; + padding: 3px 0; + box-sizing: border-box; + resize: none; + overflow-y: auto; + border: 0; + border-radius: 0; + outline: none; + background: transparent; + color: var(--primary-text-color); + font: inherit; + line-height: 1.4; + + &::placeholder { + color: var(--secondary-text-color); + } + } + + .review-editor-meta { + display: flex; + justify-content: space-between; + gap: 8px; + min-height: 18px; + padding-top: 2px; + } + + .review-error { + color: var(--error-text-color); + line-height: 1.35; + } + + .review-counter { + margin-inline-start: auto; + color: var(--secondary-text-color); + white-space: nowrap; + font-size: 11px; + } + + .review-editor-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 10px; + margin-top: 10px; + padding-inline-start: 48px; + } + + .review-vote-actions, + .review-form-actions { + display: flex; + align-items: center; + gap: 4px; + } + + .review-vote-actions { + margin-inline-end: auto; + padding: 2px; + border: 1px solid var(--border-color); + border-radius: 999px; + background: var(--secondary-color); + } + + .review-vote-button, + .review-delete-button, + .review-edit-button { + display: grid; + place-items: center; + width: 34px; + height: 34px; + padding: 0; + border: 0; + border-radius: 50%; + background: transparent; + color: var(--secondary-text-color); + + &.selected { + background: color-mix(in srgb, var(--button-background-color) 16%, transparent); + color: var(--button-background-color); + } + + &:active { + background: color-mix(in srgb, var(--primary-text-color) 7%, transparent); + } + } + + .review-vote-down .icon, + .review-vote-indicator.down .icon { + transform: rotate(180deg); + } + + .review-delete-button { + margin-inline-end: 6px; + background: color-mix(in srgb, var(--error-text-color) 9%, transparent); + color: var(--error-text-color); + } + + .review-cancel-button, + .review-save-button, + .reviews-state button { + min-height: 36px; + padding: 7px 14px; + border: 0; + border-radius: 18px; + font-weight: 500; + } + + .review-cancel-button { + background: transparent; + color: var(--primary-text-color); + } + + .review-save-button, + .reviews-state button { + min-width: 76px; + background: var(--button-background-color); + color: var(--button-text-color); + } + .reviews-body { + min-height: 0; flex: 1; overflow: auto; + padding: 4px 0 max(16px, env(safe-area-inset-bottom)); + box-sizing: border-box; + overscroll-behavior: contain; &.loading { @include mixins.loader(30px); } + .reviews-state { + min-height: 180px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + padding: 24px; + box-sizing: border-box; + color: var(--secondary-text-color); + text-align: center; + + &.reviews-empty .icon { + font-size: 30px; + } + + strong { + color: var(--primary-text-color); + } + } + + .reviews-section { + padding: 10px 12px 0; + } + + .reviews-section-label { + padding: 0 4px 7px; + color: color-mix(in srgb, var(--secondary-text-color), transparent 12%); + font-size: 12px; + font-weight: 600; + line-height: 1.3; + } + + .reviews-card { + overflow: hidden; + border: 1px solid var(--popup-border-color); + border-radius: 14px; + background: color-mix(in srgb, var(--secondary-color), var(--popup-background-color) 48%); + } + + .own-review-card { + border-color: color-mix(in srgb, var(--active-icon-color) 38%, var(--border-color)); + background: color-mix(in srgb, var(--secondary-color), var(--active-icon-color) 6%); + } + + .reviews-community-empty { + display: grid; + min-height: 72px; + place-items: center; + color: var(--secondary-text-color); + } + .review { - padding: 10px; + padding: 14px 16px; display: flex; flex-direction: column; - border-bottom: solid 1px rgba(255, 255, 255, 0.1); + border-bottom: 1px solid var(--border-color); + + &:last-child { + border-bottom: 0; + } .review-author { display: flex; - height: 30px; + min-height: 36px; align-items: center; justify-content: flex-start; - margin-bottom: 5px; - - .vote { - height: 40px; - width: 40px; - background-position: center; - background-repeat: no-repeat; - background-size: 20px; + gap: 10px; + margin-bottom: 6px; + + .review-author-identity { + min-width: 0; + display: flex; + align-items: baseline; + gap: 6px; + overflow: hidden; + + time { + flex: 0 0 auto; + color: color-mix(in srgb, var(--secondary-text-color), transparent 30%); + font-size: 0.85em; + font-weight: 400; + white-space: nowrap; + } + } + + .user-name { + min-width: 0; + display: flex; + align-items: baseline; + gap: 6px; + overflow: hidden; + font-weight: 500; + text-overflow: ellipsis; + white-space: nowrap; + + small { + color: var(--secondary-text-color); + font-weight: 400; + } } .user-profile { - height: 30px; - width: 30px; - border-radius: 50%; - background-position: center; - background-repeat: no-repeat; - background-size: cover; - margin-right: 10px; - font-weight: 400; + width: 32px; + height: 32px; } } + .review-vote-indicator { + display: grid; + place-items: center; + width: 28px; + height: 28px; + margin-inline-start: auto; + color: var(--secondary-text-color); + } + + .review-edit-button { + width: 32px; + height: 32px; + margin-inline-start: 0; + background: color-mix(in srgb, var(--button-background-color) 10%, transparent); + color: var(--button-background-color); + } + .review-body { - display: flex; - align-items: center; - font-weight: 300; - overflow: auto; + margin: 0; + margin-inline-start: 42px; + color: var(--primary-text-color); + font-weight: 400; + line-height: 1.45; + overflow-wrap: anywhere; + white-space: pre-wrap; + + &.review-vote-only { + color: var(--secondary-text-color); + font-size: 0.92em; + font-style: italic; + } } .author-reply { + margin: 10px 0 0; + margin-inline-start: 42px; + padding: 10px 12px; + border-inline-start: 3px solid var(--active-icon-color); + border-radius: 8px; + background: color-mix(in srgb, var(--primary-text-color) 6%, transparent); + line-height: 1.4; + &::before { display: block; content: attr(data-author); - font-weight: 400; - margin-bottom: 10px; + margin-bottom: 4px; + font-weight: 600; } - - padding: 10px; - background-color: rgba(0, 0, 0, 0.1); } } } + + @media (max-width: 480px) { + border-radius: 20px 20px 0 0; + + .review-composer { + padding-inline: 12px; + } + + .review-entry-surface { + padding: 9px; + } + + .reviews-body .reviews-section { + padding-inline: 10px; + } + + .reviews-body .review { + padding-inline: 12px; + } + + .review-editor-actions { + padding-inline-start: 0; + flex-wrap: wrap; + } + + .review-form-actions { + gap: 2px; + } + } } diff --git a/src/pages/plugin/plugin.view.js b/src/pages/plugin/plugin.view.js index f93bc6494..ed0cc0e53 100644 --- a/src/pages/plugin/plugin.view.js +++ b/src/pages/plugin/plugin.view.js @@ -6,13 +6,27 @@ import dayjsRelativeTime from "dayjs/esm/plugin/relativeTime"; import dayjsUpdateLocale from "dayjs/esm/plugin/updateLocale"; import dayjsUtc from "dayjs/esm/plugin/utc"; import alert from "dialogs/alert"; +import confirm from "dialogs/confirm"; import DOMPurify from "dompurify"; import Ref from "html-tag-js/ref"; import actionStack from "lib/actionStack"; import auth, { loginEvents } from "lib/auth"; import config from "lib/config"; +import { getIntlLocale, getLocaleDirection } from "lib/lang"; +import settings from "lib/settings"; import helpers from "utils/helpers"; import Url from "utils/Url"; +import { + formatReviewDate, + getRatingClass, + getRatingLabel, + hasReviewChanged, + partitionPluginReviews, + REVIEW_MAX_COMMENT_LENGTH, + resolveReviewAvatarUrl, + updateReviewStats, + validateReviewDraft, +} from "./reviewUtils"; dayjs.extend(dayjsRelativeTime); dayjs.extend(dayjsUtc); @@ -68,7 +82,16 @@ export default (props) => { unsupportedEditor, } = props; - let rating = "unrated"; + let reviewStats = { + votesUp: Number(votesUp) || 0, + votesDown: Number(votesDown) || 0, + commentCount: Number(commentCount) || 0, + }; + let rating = getRatingLabel(reviewStats.votesUp, reviewStats.votesDown); + const getRatingText = () => + rating === "unrated" ? strings["plugin-review:unrated"] : rating; + const ratingValueRef = Ref(); + const commentCountRef = Ref(); const keywords = typeof keywordsRaw === "string" ? JSON.parse(keywordsRaw) : keywordsRaw; @@ -77,10 +100,14 @@ export default (props) => { ? JSON.parse(contributorsRaw) : contributorsRaw; - if (votesUp || votesDown) { - rating = `${Math.round((votesUp / (votesUp + votesDown)) * 100)}%`; - } const showPurchaseWarning = !helpers.shouldAllowExternalPurchase(); + const applyReviewStats = (nextStats) => { + reviewStats = nextStats; + rating = getRatingLabel(reviewStats.votesUp, reviewStats.votesDown); + ratingValueRef.textContent = getRatingText(); + ratingValueRef.className = getRatingClass(rating); + commentCountRef.textContent = reviewStats.commentCount; + }; const formatUpdatedDate = (dateString) => { if (!dateString) return null; @@ -158,18 +185,25 @@ export default (props) => {
- = 80 ? "rating-high" : rating.replace("%", "") >= 50 ? "rating-medium" : "rating-low"}`} - > - {rating} + + {getRatingText()}
+ showReviews({ + pluginId: id, + author, + stats: reviewStats, + onStatsChange: applyReviewStats, + }) + } > - {commentCount} + + {reviewStats.commentCount} + {strings.reviews}
@@ -449,10 +483,28 @@ function Version({ ); } -async function showReviews(pluginId, author) { +async function showReviews({ pluginId, author, stats, onStatsChange }) { + const locale = getIntlLocale(settings.value?.lang); const mask = Ref(); const body = Ref(); + const composer = Ref(); const container = Ref(); + const state = { + busy: false, + closed: false, + draftComment: "", + draftDirty: false, + draftVote: 0, + editorError: "", + expanded: false, + reviewListError: null, + reviews: [], + resumeDraftAfterSignIn: false, + stats: { ...stats }, + user: null, + userReview: null, + }; + let removeDragListeners = () => {}; actionStack.push({ id: "reviews", @@ -468,50 +520,574 @@ async function showReviews(pluginId, author) { >, ); app.append( -
-
-
- - - Review - +
+
+ + {strings.reviews} +
+
, ); - try { - const reviews = await fsOperation( - config.API_BASE, - `/comments/${pluginId}`, - ).readFile("json"); - if (!reviews.length) { - body.style.textAlign = "center"; - body.textContent = "No reviews yet"; + await initialize(); + + async function initialize() { + composer.el.classList.add("loading"); + body.el.classList.add("loading"); + state.reviewListError = null; + + const [reviewsResult, userResult] = await Promise.allSettled([ + loadReviews(), + auth.getLoggedInUser(), + ]); + if (state.closed) return; + + if (reviewsResult.status === "fulfilled") { + state.reviews = reviewsResult.value; + } else { + state.reviewListError = reviewsResult.reason; + } + + if (userResult.status === "fulfilled") { + state.user = userResult.value; + if (state.user) { + const { ownReview: listedReview } = partitionPluginReviews( + state.reviews, + null, + state.user.id, + ); + try { + state.userReview = (await loadUserReview()) || listedReview || null; + } catch { + state.userReview = listedReview || null; + } + } + } else { + state.editorError = strings["plugin-review:account-check-failed"]; + } + + composer.el.classList.remove("loading"); + body.el.classList.remove("loading"); + renderComposer(); + renderReviews(); + } + + function renderComposer({ focus = false } = {}) { + if (state.closed) return; + composer.el.classList.remove("loading"); + composer.el.classList.remove("review-composer-hidden"); + + if (!state.user) { + const errorMessage = state.editorError; + composer.el.replaceChildren( +
+ +
+ + {errorMessage ? ( + {errorMessage} + ) : null} +
+
, + ); + return; + } + + if (!state.expanded && state.userReview) { + composer.el.replaceChildren(); + composer.el.classList.add("review-composer-hidden"); + return; + } + + if (!state.expanded) { + composer.el.replaceChildren( +
+ + +
, + ); + return; + } + + const textarea = Ref(); + const counter = Ref(); + const error = Ref(); + const saveButton = Ref(); + const upButton = Ref(); + const upIcon = Ref(); + const downButton = Ref(); + const downIcon = Ref(); + + composer.el.replaceChildren( +
+
+ +
+ +
+ + +
+
+
+
+
+ + +
+
+ {state.userReview ? ( + + ) : null} + + +
+
+
, + ); + + textarea.el.value = state.draftComment; + autosizeReviewInput(textarea.el); + updateEditorState(); + if (focus) requestAnimationFrame(() => textarea.el.focus()); + + function toggleVote(vote) { + state.draftVote = state.draftVote === vote ? 0 : vote; + state.draftDirty = true; + state.editorError = ""; + updateEditorState(); + } + + function updateEditorState() { + const validation = validateReviewDraft({ + comment: state.draftComment, + vote: state.draftVote, + }); + const unchanged = !hasReviewChanged(state.userReview, validation); + const validationMessage = + state.draftDirty && validation.errorKey + ? getReviewString(validation.errorKey, { + max: REVIEW_MAX_COMMENT_LENGTH, + }) + : ""; + + counter.textContent = `${state.draftComment.length}/${REVIEW_MAX_COMMENT_LENGTH}`; + error.textContent = state.editorError || validationMessage; + upButton.el.classList.toggle("selected", state.draftVote === 1); + downButton.el.classList.toggle("selected", state.draftVote === -1); + upButton.el.setAttribute("aria-pressed", state.draftVote === 1); + downButton.el.setAttribute("aria-pressed", state.draftVote === -1); + upIcon.className = `icon ${state.draftVote === 1 ? "like-solid" : "like"}`; + downIcon.className = `icon ${state.draftVote === -1 ? "like-solid" : "like"}`; + saveButton.disabled = Boolean( + validation.errorKey || unchanged || state.busy, + ); + saveButton.textContent = state.busy + ? strings["loading..."] + : state.userReview + ? strings.update + : strings.save; + } + } + + function renderReviews() { + if (state.closed) return; + body.el.classList.remove("loading"); + + if (state.reviewListError) { + body.el.replaceChildren( +
+ {strings["plugin-review:load-failed"]} + +
, + ); return; } - reviews.forEach((review) => { - if (!review.comment) return; - review.author = author; - body.append(); + const { ownReview, communityReviews } = partitionPluginReviews( + state.reviews, + state.userReview, + state.user?.id, + ); + const showOwnReview = Boolean(ownReview && !state.expanded); + + if (!showOwnReview && !communityReviews.length && !state.expanded) { + body.el.replaceChildren( +
+ + {strings["plugin-review:empty-title"]} + {strings["plugin-review:empty-description"]} +
, + ); + return; + } + + const sections = []; + if (showOwnReview) { + sections.push( +
+
+ {strings["plugin-review:yours"]} +
+
+ +
+
, + ); + } + + sections.push( +
+
+ {strings["plugin-review:community"]} +
+ {communityReviews.length ? ( +
+ {communityReviews.map((review) => ( + + ))} +
+ ) : ( +
+ {strings["plugin-review:no-other-reviews"]} +
+ )} +
, + ); + + body.el.replaceChildren(...sections); + } + + function openEditor() { + state.draftComment = state.userReview?.comment || ""; + state.draftVote = state.userReview?.vote || 0; + state.draftDirty = false; + state.editorError = ""; + state.expanded = true; + state.resumeDraftAfterSignIn = false; + renderComposer({ focus: true }); + renderReviews(); + } + + function cancelEditor() { + if (state.busy) return; + state.expanded = false; + state.editorError = ""; + state.resumeDraftAfterSignIn = false; + renderComposer(); + renderReviews(); + } + + async function signIn() { + if (state.busy) return; + state.busy = true; + state.editorError = ""; + renderComposer(); + + try { + await auth.login(); + const user = await auth.getLoggedInUser(true); + if (!user) { + state.busy = false; + state.editorError = strings["plugin-review:login-incomplete"]; + renderComposer(); + return; + } + state.user = user; + const { ownReview: listedReview } = partitionPluginReviews( + state.reviews, + null, + state.user.id, + ); + state.userReview = await loadUserReview().catch( + () => listedReview || null, + ); + state.busy = false; + if (state.resumeDraftAfterSignIn) { + state.resumeDraftAfterSignIn = false; + state.editorError = ""; + state.expanded = true; + renderComposer({ focus: true }); + renderReviews(); + } else { + openEditor(); + } + } catch { + state.busy = false; + state.editorError = strings["plugin-review:sign-in-failed"]; + renderComposer(); + } + } + + async function saveReview() { + if (state.busy) return; + const review = validateReviewDraft({ + comment: state.draftComment, + vote: state.draftVote, }); - } catch (error) { - body.textContent = error.message; - } finally { - body.classList.remove("loading"); + if (review.errorKey || !hasReviewChanged(state.userReview, review)) return; + + state.busy = true; + state.editorError = ""; + renderComposer(); + + try { + const result = await requestJson(Url.join(config.API_BASE, "comment"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + plugin_id: pluginId, + comment: review.comment, + vote: review.vote, + }), + }); + const previousReview = state.userReview; + state.userReview = { + ...previousReview, + id: result.id, + plugin_id: pluginId, + user_id: state.user.id, + name: state.user.name, + github: state.user.github, + comment: result.comment, + vote: result.vote, + updated_at: new Date().toISOString(), + }; + state.reviews = [ + state.userReview, + ...state.reviews.filter( + (item) => + item.id !== state.userReview.id && item.user_id !== state.user.id, + ), + ]; + state.stats = updateReviewStats( + state.stats, + previousReview, + state.userReview, + ); + onStatsChange(state.stats); + state.busy = false; + state.expanded = false; + state.editorError = ""; + state.resumeDraftAfterSignIn = false; + renderComposer(); + renderReviews(); + toast(strings.success); + } catch (error) { + state.busy = false; + if (error.status === 401) { + state.user = null; + state.expanded = false; + state.resumeDraftAfterSignIn = true; + state.editorError = strings["plugin-review:session-expired-review"]; + renderComposer(); + } else { + state.editorError = strings["plugin-review:save-failed"]; + renderComposer({ focus: true }); + } + } + } + + async function deleteReview() { + if (state.busy || !state.userReview?.id) return; + const approved = await confirm( + strings.delete, + strings["plugin-review:delete-confirmation"], + false, + { + direction: getLocaleDirection(locale), + aboveOverlay: true, + }, + ); + if (!approved || state.closed) return; + + state.busy = true; + state.editorError = ""; + renderComposer(); + + try { + const previousReview = state.userReview; + await requestJson( + Url.join(config.API_BASE, "comment", previousReview.id), + { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }, + ); + state.reviews = state.reviews.filter( + (item) => + item.id !== previousReview.id && item.user_id !== state.user.id, + ); + state.stats = updateReviewStats(state.stats, previousReview, null); + onStatsChange(state.stats); + state.userReview = null; + state.busy = false; + state.expanded = false; + renderComposer(); + renderReviews(); + toast(strings.success); + } catch (error) { + state.busy = false; + if (error.status === 401) { + state.user = null; + state.expanded = false; + state.editorError = strings["plugin-review:session-expired-continue"]; + renderComposer(); + } else { + state.editorError = strings["plugin-review:delete-failed"]; + renderComposer({ focus: true }); + } + } + } + + async function retryReviews() { + body.el.classList.add("loading"); + state.reviewListError = null; + try { + state.reviews = await loadReviews(); + } catch (error) { + state.reviewListError = error; + } finally { + renderReviews(); + } + } + + function loadReviews() { + return fsOperation(config.API_BASE, `/comments/${pluginId}`).readFile( + "json", + ); + } + + async function loadUserReview() { + const review = await requestJson( + Url.join(config.API_BASE, "user", "comment", pluginId), + ); + return review?.id ? review : null; + } + + async function requestJson(url, options) { + const response = await fetch(url, options); + const result = await response.json().catch(() => ({})); + if (!response.ok) { + const error = new Error(); + error.status = response.status; + throw error; + } + return result; } function closeReviews() { + if (state.closed) return; + state.closed = true; + removeDragListeners(); actionStack.remove("reviews"); - container.classList.add("hide"); + container.el.classList.add("hide"); setTimeout(() => { - mask.el.remove(); - container.el.remove(); + mask.el?.remove(); + container.el?.remove(); }, 300); } @@ -519,32 +1095,38 @@ async function showReviews(pluginId, author) { * @param {TouchEvent} e */ function ontouchstart(e) { + if (state.closed || e.touches.length !== 1) return; + removeDragListeners(); const { clientY } = e.touches[0]; const { top } = container.el.getBoundingClientRect(); const y = clientY - top; let dy = 0; - container.style.transition = "none"; + container.el.style.transition = "none"; document.addEventListener("touchmove", ontouchmove); document.addEventListener("touchend", ontouchend); document.addEventListener("touchcancel", ontouchend); + removeDragListeners = () => { + document.removeEventListener("touchmove", ontouchmove); + document.removeEventListener("touchend", ontouchend); + document.removeEventListener("touchcancel", ontouchend); + }; function ontouchmove(e) { + if (e.touches.length !== 1) return; const { clientY } = e.touches[0]; dy = clientY - top - y; if (dy < 0) dy = 0; - container.style.transform = `translateY(${dy}px)`; + container.el.style.transform = `translateY(${dy}px)`; } function ontouchend() { - document.removeEventListener("touchmove", ontouchmove); - document.removeEventListener("touchend", ontouchend); - document.removeEventListener("touchcancel", ontouchcancel); + removeDragListeners(); if (dy < 100) { - container.style.transition = "transform 0.3s ease-in-out"; - container.style.transform = "translateY(0)"; + container.el.style.transition = "transform 0.3s ease-in-out"; + container.el.style.transform = "translateY(0)"; return; } closeReviews(); @@ -552,50 +1134,102 @@ async function showReviews(pluginId, author) { } } +function ReviewAvatar({ user, className = "review-avatar" }) { + const avatarUrl = resolveReviewAvatarUrl(user); + return ( + + ); +} + +function autosizeReviewInput(textarea) { + textarea.style.height = "auto"; + textarea.style.height = `${Math.min(textarea.scrollHeight, 144)}px`; +} + +function getReviewString(key, replacements = {}) { + let value = strings[key] || key; + for (const [name, replacement] of Object.entries(replacements)) { + value = value.replaceAll(`{${name}}`, String(replacement)); + } + return value; +} + function Review({ name, github, + avatar_url: avatarUrl, + updated_at: updatedAt, vote, comment, author, author_reply: authorReply, + isOwn, + onEdit, + locale, }) { - let dp = Url.join(config.API_BASE, `../user.png`); - let voteImage = Ref(); - let review = Ref(); - - if (github) { - dp = `https://avatars.githubusercontent.com/${github}`; - } - - if (vote === 1) { - voteImage.style.backgroundImage = `url(${Url.join(config.API_BASE, `../thumbs-up.gif`)})`; - } else if (vote === -1) { - voteImage.style.backgroundImage = `url(${Url.join(config.API_BASE, `../thumbs-down.gif`)})`; - } - - if (authorReply) { - setTimeout(() => { - review.append( -

- {authorReply} -

, - ); - }, 0); - } - + const reviewDate = formatReviewDate(updatedAt, Date.now(), locale); + const hasComment = typeof comment === "string" && Boolean(comment.trim()); return ( -
+
- - {name} - + /> + + + {name} + {isOwn ? {strings["plugin-review:you"]} : null} + + {reviewDate ? ( + + ) : null} + + {vote ? ( + + + + ) : null} + {isOwn ? ( + + ) : null}
-

{comment}

-
+ {hasComment ? ( +

{comment}

+ ) : isOwn && Number(vote) === 1 ? ( +

+ {strings["plugin-review:recommended"]} +

+ ) : null} + {authorReply ? ( +

+ {authorReply} +

+ ) : null} + ); } diff --git a/src/pages/plugin/reviewUtils.js b/src/pages/plugin/reviewUtils.js new file mode 100644 index 000000000..73722c6d5 --- /dev/null +++ b/src/pages/plugin/reviewUtils.js @@ -0,0 +1,180 @@ +export const REVIEW_MAX_COMMENT_LENGTH = 250; + +const SECOND = 1000; +const MINUTE = 60 * SECOND; +const HOUR = 60 * MINUTE; +const DAY = 24 * HOUR; + +function parseReviewDate(value) { + if (typeof value !== "string" || !value.trim()) return null; + + const trimmed = value.trim(); + const normalized = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test( + trimmed, + ) + ? `${trimmed.replace(" ", "T")}Z` + : trimmed; + const date = new Date(normalized); + return Number.isNaN(date.getTime()) ? null : date; +} + +export function formatReviewDate(value, now = Date.now(), locale = "en-US") { + const date = parseReviewDate(value); + const currentTime = now instanceof Date ? now.getTime() : Number(now); + if (!date || !Number.isFinite(currentTime)) return null; + + const difference = date.getTime() - currentTime; + const absoluteDifference = Math.abs(difference); + let unit = "second"; + let amount = 0; + + if (absoluteDifference < 45 * SECOND) { + amount = 0; + } else if (absoluteDifference < 90 * SECOND) { + unit = "minute"; + amount = Math.sign(difference); + } else if (absoluteDifference < 45 * MINUTE) { + unit = "minute"; + amount = Math.round(difference / MINUTE); + } else if (absoluteDifference < 90 * MINUTE) { + unit = "hour"; + amount = Math.sign(difference); + } else if (absoluteDifference < 22 * HOUR) { + unit = "hour"; + amount = Math.round(difference / HOUR); + } else if (absoluteDifference < 36 * HOUR) { + unit = "day"; + amount = Math.sign(difference); + } else if (absoluteDifference < 26 * DAY) { + unit = "day"; + amount = Math.round(difference / DAY); + } else if (absoluteDifference < 45 * DAY) { + unit = "month"; + amount = Math.sign(difference); + } else if (absoluteDifference < 320 * DAY) { + unit = "month"; + amount = Math.round(difference / (30 * DAY)); + } else if (absoluteDifference < 548 * DAY) { + unit = "year"; + amount = Math.sign(difference); + } else { + unit = "year"; + amount = Math.round(difference / (365 * DAY)); + } + + const relative = new Intl.RelativeTimeFormat(locale, { + numeric: "auto", + }).format(amount, unit); + + return { + dateTime: date.toISOString(), + relative, + title: date.toLocaleString(locale, { + dateStyle: "medium", + timeStyle: "short", + }), + }; +} + +export function resolveReviewAvatarUrl(user = {}) { + const avatarUrl = + typeof user?.avatar_url === "string" ? user.avatar_url.trim() : ""; + if (avatarUrl) return avatarUrl; + + const github = typeof user?.github === "string" ? user.github.trim() : ""; + return github ? `https://avatars.githubusercontent.com/${github}` : null; +} + +function hasSameId(left, right) { + if (left === null || left === undefined) return false; + if (right === null || right === undefined) return false; + return String(left) === String(right); +} + +export function partitionPluginReviews(reviews = [], currentReview, userId) { + const reviewList = Array.isArray(reviews) ? reviews : []; + const ownReview = + currentReview || + reviewList.find((review) => hasSameId(review?.user_id, userId)) || + null; + const communityReviews = reviewList.filter((review) => { + if (!review?.comment?.trim()) return false; + if (hasSameId(review.user_id, userId)) return false; + if (hasSameId(review.id, ownReview?.id)) return false; + return !hasSameId(review.user_id, ownReview?.user_id); + }); + + return { ownReview, communityReviews }; +} + +export function normalizeReviewDraft(draft = {}) { + const { comment = "", vote = 0 } = draft || {}; + return { + comment: typeof comment === "string" ? comment.trim() : "", + vote: [-1, 0, 1].includes(Number(vote)) ? Number(vote) : 0, + }; +} + +export function validateReviewDraft(draft) { + const normalized = normalizeReviewDraft(draft); + + if (normalized.comment.length > REVIEW_MAX_COMMENT_LENGTH) { + return { + ...normalized, + errorKey: "plugin-review:error-max-length", + }; + } + if (normalized.vote === -1 && !normalized.comment) { + return { + ...normalized, + errorKey: "plugin-review:error-downvote-comment", + }; + } + if (normalized.vote === 0 && !normalized.comment) { + return { ...normalized, errorKey: "plugin-review:error-empty" }; + } + + return normalized; +} + +export function hasReviewChanged(previousReview, nextReview) { + const previous = normalizeReviewDraft(previousReview); + const next = normalizeReviewDraft(nextReview); + return previous.comment !== next.comment || previous.vote !== next.vote; +} + +export function updateReviewStats(stats, previousReview, nextReview) { + const previous = normalizeReviewDraft(previousReview); + const next = normalizeReviewDraft(nextReview); + let votesUp = Number(stats?.votesUp) || 0; + let votesDown = Number(stats?.votesDown) || 0; + let commentCount = Number(stats?.commentCount) || 0; + + if (previous.vote === 1) votesUp -= 1; + if (previous.vote === -1) votesDown -= 1; + if (next.vote === 1) votesUp += 1; + if (next.vote === -1) votesDown += 1; + if (previous.comment) commentCount -= 1; + if (next.comment) commentCount += 1; + + return { + votesUp: Math.max(0, votesUp), + votesDown: Math.max(0, votesDown), + commentCount: Math.max(0, commentCount), + }; +} + +export function getRatingLabel(votesUp, votesDown) { + const up = Number(votesUp) || 0; + const down = Number(votesDown) || 0; + const total = up + down; + return total ? `${Math.round((up / total) * 100)}%` : "unrated"; +} + +export function getRatingClass(rating) { + if (rating === "unrated") return "rating-value"; + const value = Number.parseInt(rating, 10); + if (value >= 80) return "rating-value rating-high"; + if (value >= 50) return "rating-value rating-medium"; + return "rating-value rating-low"; +} diff --git a/tests/unit/confirm.test.js b/tests/unit/confirm.test.js new file mode 100644 index 000000000..cec4ae2d4 --- /dev/null +++ b/tests/unit/confirm.test.js @@ -0,0 +1,103 @@ +// @vitest-environment happy-dom + +import tag from "html-tag-js"; +import { + afterAll, + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; + +const mocks = vi.hoisted(() => ({ + push: vi.fn(), + remove: vi.fn(), + restoreTheme: vi.fn(), +})); + +vi.mock("components/checkbox", () => ({ default: () => null })); +vi.mock("dompurify", () => ({ + default: { sanitize: vi.fn((value) => value) }, +})); +vi.mock("lib/actionStack", () => ({ + default: { + push: mocks.push, + remove: mocks.remove, + }, +})); +vi.mock("lib/restoreTheme", () => ({ default: mocks.restoreTheme })); + +import confirm from "dialogs/confirm"; + +const originalApp = globalThis.app; +const originalStrings = globalThis.strings; +const originalTag = globalThis.tag; + +function restoreGlobal(name, value) { + if (value === undefined) { + Reflect.deleteProperty(globalThis, name); + } else { + globalThis[name] = value; + } +} + +function getDialog() { + return document.querySelector(".prompt.confirm"); +} + +function getButtons() { + return document.querySelectorAll(".button-container button"); +} + +describe("confirm dialog layering", () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.clearAllMocks(); + document.body.replaceChildren(); + globalThis.tag = tag; + globalThis.app = document.body; + globalThis.strings = { ok: "OK", cancel: "Cancel" }; + }); + + afterEach(() => { + vi.runOnlyPendingTimers(); + vi.useRealTimers(); + document.body.replaceChildren(); + }); + + afterAll(() => { + restoreGlobal("app", originalApp); + restoreGlobal("strings", originalStrings); + restoreGlobal("tag", originalTag); + }); + + it("keeps the default layer and resolves false when cancelled", async () => { + const result = confirm("Delete", "Delete this review?"); + const dialog = getDialog(); + + expect(dialog).not.toBeNull(); + expect(dialog.className).toBe("prompt confirm"); + + getButtons()[0].click(); + + await expect(result).resolves.toBe(false); + }); + + it("applies the overlay layer without losing RTL and resolves true", async () => { + const result = confirm("Delete", "Delete this review?", false, { + direction: "rtl", + aboveOverlay: true, + }); + const dialog = getDialog(); + + expect(dialog).not.toBeNull(); + expect(dialog.className).toBe("prompt confirm above-overlay"); + expect(dialog.dir).toBe("rtl"); + + getButtons()[1].click(); + + await expect(result).resolves.toBe(true); + }); +}); diff --git a/tests/unit/pluginReview.test.js b/tests/unit/pluginReview.test.js new file mode 100644 index 000000000..efb70b1bb --- /dev/null +++ b/tests/unit/pluginReview.test.js @@ -0,0 +1,183 @@ +import { describe, expect, it } from "vitest"; +import { getIntlLocale, getLocaleDirection } from "lib/lang"; +import { + formatReviewDate, + getRatingClass, + getRatingLabel, + hasReviewChanged, + normalizeReviewDraft, + partitionPluginReviews, + resolveReviewAvatarUrl, + updateReviewStats, + validateReviewDraft, +} from "pages/plugin/reviewUtils"; + +describe("plugin review dates", () => { + it("formats database timestamps as website-style relative dates", () => { + const formatted = formatReviewDate( + "2026-08-26 12:00:00", + Date.parse("2026-08-28T12:00:00Z"), + "en-US", + ); + + expect(formatted?.dateTime).toBe("2026-08-26T12:00:00.000Z"); + expect(formatted?.relative).toBe("2 days ago"); + expect(formatted?.title).toBeTruthy(); + }); + + it("uses the selected locale for relative and absolute dates", () => { + const value = "2026-08-26 12:00:00"; + const now = Date.parse("2026-08-28T12:00:00Z"); + const formatted = formatReviewDate(value, now, "fr-FR"); + const date = new Date("2026-08-26T12:00:00Z"); + + expect(formatted?.relative).toBe( + new Intl.RelativeTimeFormat("fr-FR", { numeric: "auto" }).format( + -2, + "day", + ), + ); + expect(formatted?.title).toBe( + date.toLocaleString("fr-FR", { + dateStyle: "medium", + timeStyle: "short", + }), + ); + }); + + it("normalizes Acode-specific locale codes", () => { + expect(getIntlLocale("pu-in")).toBe("pa-IN"); + expect(getIntlLocale("ir-fa")).toBe("fa-IR"); + expect(getIntlLocale("mm-unicode")).toBe("my-MM"); + expect(getIntlLocale("mm-zawgyi")).toBe("my-MM"); + expect(getLocaleDirection("fa-IR")).toBe("rtl"); + expect(getLocaleDirection("ar-YE")).toBe("rtl"); + expect(getLocaleDirection("he-IL")).toBe("rtl"); + expect(getLocaleDirection("de-DE")).toBe("ltr"); + }); + + it("hides missing or invalid review dates", () => { + expect(formatReviewDate()).toBeNull(); + expect(formatReviewDate("not-a-date")).toBeNull(); + }); +}); + +describe("plugin review avatars", () => { + it("prefers an explicit avatar and falls back to GitHub", () => { + expect( + resolveReviewAvatarUrl({ + avatar_url: " https://example.com/avatar.png ", + github: "acode-foundation", + }), + ).toBe("https://example.com/avatar.png"); + expect(resolveReviewAvatarUrl({ github: " acode-foundation " })).toBe( + "https://avatars.githubusercontent.com/acode-foundation", + ); + }); + + it("uses the local placeholder when no avatar is available", () => { + expect(resolveReviewAvatarUrl()).toBeNull(); + expect(resolveReviewAvatarUrl({ avatar_url: " ", github: " " })).toBeNull(); + }); +}); + +describe("plugin review ordering", () => { + it("pins and deduplicates the current user's review across id types", () => { + const reviews = [ + { id: 1, user_id: 10, comment: "First" }, + { id: 2, user_id: "7", comment: "Mine from the list" }, + { id: 3, user_id: 11, comment: "Second" }, + ]; + const currentReview = { + id: "2", + user_id: 7, + comment: "", + vote: 1, + }; + + expect(partitionPluginReviews(reviews, currentReview, "7")).toEqual({ + ownReview: currentReview, + communityReviews: [reviews[0], reviews[2]], + }); + }); +}); + +describe("plugin review draft", () => { + it("normalizes whitespace and preserves valid votes", () => { + expect(normalizeReviewDraft({ comment: " Very useful ", vote: "1" })).toEqual( + { + comment: "Very useful", + vote: 1, + }, + ); + }); + + it("supports an upvote without text and validates negative feedback", () => { + expect(validateReviewDraft({ vote: 1 })).toEqual({ comment: "", vote: 1 }); + expect(validateReviewDraft({ comment: " ", vote: -1 }).errorKey).toBe( + "plugin-review:error-downvote-comment", + ); + expect(validateReviewDraft({ comment: " ", vote: 0 }).errorKey).toBe( + "plugin-review:error-empty", + ); + }); + + it("enforces the 250 character limit", () => { + expect(validateReviewDraft({ comment: "x".repeat(250), vote: 0 })).toEqual({ + comment: "x".repeat(250), + vote: 0, + }); + expect( + validateReviewDraft({ comment: "x".repeat(251), vote: 0 }).errorKey, + ).toBe("plugin-review:error-max-length"); + }); + + it("compares normalized drafts to prevent duplicate submissions", () => { + expect( + hasReviewChanged( + { comment: "Useful", vote: 1 }, + { comment: " Useful ", vote: "1" }, + ), + ).toBe(false); + expect( + hasReviewChanged( + { comment: "Useful", vote: 1 }, + { comment: "Updated", vote: 1 }, + ), + ).toBe(true); + }); +}); + +describe("plugin review metrics", () => { + it("moves votes and comment counts without inflation", () => { + expect( + updateReviewStats( + { votesUp: 8, votesDown: 2, commentCount: 4 }, + { vote: 1, comment: "Old" }, + { vote: -1, comment: "New" }, + ), + ).toEqual({ votesUp: 7, votesDown: 3, commentCount: 4 }); + }); + + it("handles vote-only reviews and deletion", () => { + const added = updateReviewStats( + { votesUp: 0, votesDown: 0, commentCount: 0 }, + null, + { vote: 1, comment: "" }, + ); + expect(added).toEqual({ votesUp: 1, votesDown: 0, commentCount: 0 }); + expect(updateReviewStats(added, { vote: 1 }, null)).toEqual({ + votesUp: 0, + votesDown: 0, + commentCount: 0, + }); + }); + + it("formats rating labels and visual states", () => { + expect(getRatingLabel(0, 0)).toBe("unrated"); + expect(getRatingLabel(8, 2)).toBe("80%"); + expect(getRatingClass("80%")).toContain("rating-high"); + expect(getRatingClass("50%")).toContain("rating-medium"); + expect(getRatingClass("49%")).toContain("rating-low"); + }); +});