diff --git a/.eslintrc.js b/.eslintrc.js
index 9bd7a8431..7906a2eeb 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -143,5 +143,6 @@ module.exports = {
tsx: 'never',
},
],
+ 'no-console': 'error',
},
};
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97d6773b6..4b2f38d9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [Unreleased]
+
+### Changed
+
+- UI Design of options page.
+
+[Unreleased]: https://github.com/AdguardTeam/AdGuardVPNExtension/compare/v2.3.2...HEAD
+
## [2.3.2] - 2024-12-10
### Added
diff --git a/src/_locales/ar/messages.json b/src/_locales/ar/messages.json
index 8ed10e962..36c555856 100644
--- a/src/_locales/ar/messages.json
+++ b/src/_locales/ar/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "حول"
},
+ "all_rights_reserved": {
+ "message": "جميع الحقوق محفوظة."
+ },
"options_support_title": {
"message": "الدعم"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "حدث خطأ. يرجى المحاولة مرة أخرى لاحقاً."
},
- "options_bug_report_send_button": {
- "message": "إرسال"
- },
- "options_bug_report_sending_button": {
- "message": "جارٍ الإرسال..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "صِف المشكلة هنا..."
- },
- "options_bug_report_page_success": {
- "message": "شكراً لك، تم إرسال رسالتك بنجاح!"
- },
"options_bug_report_new_report_button": {
"message": "تقرير جديد"
},
@@ -118,13 +109,13 @@
"message": "الحساب"
},
"account_edit": {
- "message": "إدارة الحساب"
+ "message": "فتح إعدادات الحساب"
},
"account_version": {
"message": "الإصدار"
},
"account_free": {
- "message": "حساب مجاني"
+ "message": "الإصدار المجاني"
},
"account_unlimited": {
"message": "اشتراك غير محدود"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "الحصول على اشتراك"
},
- "account_max_devices_count": {
- "message": "ما يصل إلى %num% أجهزة متزامنة"
- },
"account_valid_until": {
"message": "صالح حتى %date%"
},
@@ -157,7 +145,7 @@
"message": "احصل على إصدار غير محدود!"
},
"rate_description": {
- "message": "مرحبًا، نحن فريق من AdGuard ومن المهم لنا معرفة ما مدى رضاك عن هذا المنتج. ندعوك لتقيمنا."
+ "message": "هل تستمتع ب AdGuard VPN؟"
},
"rate_hide": {
"message": "إخفاء"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "سياسة الخصوصية"
},
- "settings_title": {
- "message": "الإعدادات"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "احصل على جيجا مجانية!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "إعادة إرسال الرابط"
},
- "settings_free_gbs_add_device_title": {
- "message": "إضافة جهاز آخر"
- },
- "settings_free_gbs_add_device_info": {
- "message": "قم بتثبيت AdGuard VPN على جهاز آخر، وقم بتسجيل الدخول إلى حساب AdGuard الخاص بك عليه، واحصل على %your_gb% جيجابايت"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "الأجهزة المضافة"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "تهانينا على تثبيت AdGuard VPN على أجهزة متعددة. ابق آمنًا!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "انتقل إلى المنتجات"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "حظر WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC هو برنامج خاص متواجد في معظم متصفحات الإنترنت يمكن أن يسمح لموقع ويب التابع لجهة خارجية باكتشاف عنوانك على الأنترنت IP واستخدامه لتحديد هويتك"
- },
- "settings_context_menus_title": {
- "message": "أضف عنصر AdGuard VPN إلى قائمة سياق المتصفح"
- },
"settings_help_us_improve_title": {
- "message": "ساعدنا لنتحسن المنتج"
- },
- "settings_help_us_improve_description": {
- "message": "السمح لـ AdGuard VPN بجمع تقارير الأعطال والبيانات الفنية والتفاعلية بشكل مجهول."
+ "message": "إرسال تقارير الأعطال المجهولة"
},
"context_menu_selective_mode": {
"message": "الوضع الانتقائي"
@@ -352,22 +319,13 @@
"message": "تحرير خادم DNS المخصص"
},
"settings_dns_add_custom_server_info": {
- "message": "اقرأ المزيد حول خوادم DNS"
+ "message": "مزودي DNS المعروفين"
},
"settings_dns_add_custom_server_name": {
"message": "اسم الخادم"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "اسم الخادم"
- },
"settings_dns_add_custom_server_address": {
- "message": "عنوان خادم DNS"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "عنوان IP للخادم أو اسم مجال TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "عنوان خادم DNS غير صالح"
+ "message": "عناوين الخادم"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "خادم DNS مضاف بالفعل"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN قيد التشغيل للنطاقات والنطاقات الفرعية المحددة"
},
"settings_exclusion_domain_name": {
- "message": "اسم النطاق:"
+ "message": "اسم النطاق"
},
"settings_exclusion_subdomain_name": {
- "message": "اسم النطاق الفرعي:"
+ "message": "النطاق الفرعي"
},
"settings_exclusion_add_from_list": {
"message": "من القائمة"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "إزالة جميع الاستثناءات"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "هل أنت متأكد أنك تريد الاستمرار؟ ستتم إزالة قائمة الاستثناءات بأكملها."
- },
"settings_exclusion_title": {
"message": "الاستثناءات"
},
@@ -938,12 +893,6 @@
"popup_connections_limit_title": {
"message": "تم الوصول إلى الحد الأقصى للاتصالات"
},
- "options_signedout_page_title": {
- "message": "لقد قمت بتسجيل الخروج من AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "يرجى ملاحظة: أنت غير متصل بشبكة VPN ولا يتم حماية بياناتك! لمواصلة التصفح بخصوصية، انقر فوق أيقونة AdGuard VPN وقم بتسجيل الدخول مرة أخرى."
- },
"notification_data_limit_reached_title": {
"message": "تم الوصول إلى الحد الأقصى من البيانات الشهرية"
},
diff --git a/src/_locales/be/messages.json b/src/_locales/be/messages.json
index 676b6156d..3c1f6e936 100644
--- a/src/_locales/be/messages.json
+++ b/src/_locales/be/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Пра нас"
},
+ "all_rights_reserved": {
+ "message": "Усе правы захаваныя."
+ },
"options_support_title": {
"message": "Падтрымка"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Адбылася памылка. Паспрабуйце пазней."
},
- "options_bug_report_send_button": {
- "message": "Адправіць"
- },
- "options_bug_report_sending_button": {
- "message": "Адпраўленне..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Апішыце знойдзеную памылку..."
- },
- "options_bug_report_page_success": {
- "message": "Дзякуй, ваша паведамленне адпраўлена!"
- },
"options_bug_report_new_report_button": {
"message": "Новая справаздача"
},
@@ -118,13 +109,13 @@
"message": "Акаўнт"
},
"account_edit": {
- "message": "Кіраваць акаўнтам"
+ "message": "Адкрыць налады ўліковага запісу"
},
"account_version": {
"message": "Версія"
},
"account_free": {
- "message": "Бясплатны акаўнт"
+ "message": "Бясплатная версія"
},
"account_unlimited": {
"message": "Неабмежаваная падпіска"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Атрымаць падпіску"
},
- "account_max_devices_count": {
- "message": "Да %num% адначасовых прылад"
- },
"account_valid_until": {
"message": "Дзейнічае да %date%"
},
@@ -157,7 +145,7 @@
"message": "Атрымайце безлімітную версію!"
},
"rate_description": {
- "message": "Прывітанне, мы каманда AdGuard, і для нас вельмі важна ведаць вашу думку пра наш прадукт. Калі ласка, ацаніце яго."
+ "message": "Вам падабаецца AdGuard VPN?"
},
"rate_hide": {
"message": "Схаваць"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Палітыка прыватнасці"
},
- "settings_title": {
- "message": "Налады"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Атрымаць дармовы трафік!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Паўторна адправіць спасылку"
},
- "settings_free_gbs_add_device_title": {
- "message": "Дадайце іншую прыладу"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Усталюйце AdGuard VPN яшчэ на адну прыладу, увайдзіце на ёй у свой уліковы запіс AdGuard і атрымайце %your_gb% ГБ"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Прылады дададзены"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Віншуем з устаноўкай AdGuard VPN на некалькіх платформах. Заставайцеся ў бяспецы!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Перайсці ў \"Прадукты\""
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Блакаваць WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC — гэта адмысловая тэхналогія, што выкарыстоўваецца большасцю браўзараў, якая дазваляе кожнаму ўэб-сайту выявіць ваш рэальны IP-адрас і выкарыстоўваць яго для вашай ідэнтыфікацыі"
- },
- "settings_context_menus_title": {
- "message": "Дадаць AdGuard у кантэкставае меню браўзара"
- },
"settings_help_us_improve_title": {
- "message": "Дапамажыце нам стаць лепш"
- },
- "settings_help_us_improve_description": {
- "message": "Дазвольце AdGuard VPN збіраць справаздачы пра збоі, тэхнічныя дадзеныя і дадзеныя пра ўзаемадзеянне. Гэта ананімна."
+ "message": "Адпраўляць ананімныя справаздачы аб збоях"
},
"context_menu_selective_mode": {
"message": "Выбарковы рэжым"
@@ -352,22 +319,13 @@
"message": "Рэдагаваць карыстальніцкі сервер DNS"
},
"settings_dns_add_custom_server_info": {
- "message": "Больш пра DNS-серверы"
+ "message": "Вядомыя DNS-правайдары"
},
"settings_dns_add_custom_server_name": {
"message": "Назва сервера"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Назва сервера"
- },
"settings_dns_add_custom_server_address": {
- "message": "Адрас DNS-сервера"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP-адрас сервера або даменнае імя TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Некарэктны адрас DNS-сервера"
+ "message": "Адрас сервера"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS-сервер ужо дададзены"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN падлучаны для даменаў і паддаменаў, адзначаных галачкай"
},
"settings_exclusion_domain_name": {
- "message": "Даменавае імя:"
+ "message": "Дамен"
},
"settings_exclusion_subdomain_name": {
- "message": "Назва субдамена:"
+ "message": "Паддамен"
},
"settings_exclusion_add_from_list": {
"message": "Са спіса"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Выдаліць усе выняткі"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Вы ўпэўнены, што хочаце працягнуць? Увесь спіс выняткаў будзе выдалены."
- },
"settings_exclusion_title": {
"message": "Выняткі"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Дасягнуты ліміт падлучэнняў"
},
- "options_signedout_page_title": {
- "message": "Вы выйшлі з AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Звярніце ўвагу, што злучэнне не абаронена. Каб працягнуць карыстацца інтэрнэтам бяспечна, націсніце на іконку AdGuard VPN і зноў увайдзіце ў дадатак."
- },
"notification_data_limit_reached_title": {
"message": "Дасягнуты месячны ліміт дадзеных"
},
diff --git a/src/_locales/bg/messages.json b/src/_locales/bg/messages.json
index 980cc9e5f..1d5b96f11 100644
--- a/src/_locales/bg/messages.json
+++ b/src/_locales/bg/messages.json
@@ -5,6 +5,9 @@
"about_title": {
"message": "Относно"
},
+ "all_rights_reserved": {
+ "message": "Всички права запазени."
+ },
"options_support_title": {
"message": "Поддръжка"
},
@@ -32,8 +35,8 @@
"privacy_policy": {
"message": "Правила за поверителност"
},
- "settings_title": {
- "message": "Настройки"
+ "settings_general_title": {
+ "message": "General"
},
"settings_webrtc_label": {
"message": "Блокирай WebRTC"
@@ -41,9 +44,6 @@
"settings_dns_add_custom_server_name": {
"message": "Име на сървъра"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Име на сървъра"
- },
"settings_dns_add_custom_server_save": {
"message": "Запази"
},
@@ -77,6 +77,9 @@
"settings_exclusion_actions": {
"message": "Действия"
},
+ "settings_exclusion_domain_name": {
+ "message": "Име на домейн"
+ },
"settings_exclusion_modal_cancel": {
"message": "Отказ"
},
diff --git a/src/_locales/ca/messages.json b/src/_locales/ca/messages.json
index 86c65ce89..e1bf42abb 100644
--- a/src/_locales/ca/messages.json
+++ b/src/_locales/ca/messages.json
@@ -2,6 +2,9 @@
"short_name": {
"message": "AdGuard VPN"
},
+ "all_rights_reserved": {
+ "message": "Tots els drets reservats."
+ },
"options_support_title": {
"message": "Assistència tècnica"
},
@@ -17,12 +20,6 @@
"options_report_bug_title": {
"message": "Informa d'un error"
},
- "options_bug_report_send_button": {
- "message": "Envia"
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Descriu el problema aquí..."
- },
"options_bug_report_textarea_label": {
"message": "Missatge"
},
@@ -44,9 +41,6 @@
"eula": {
"message": "EULA"
},
- "settings_title": {
- "message": "Configuració"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Vés als productes"
},
@@ -62,12 +56,6 @@
"settings_dns_add_custom_server_name": {
"message": "Nom del servidor"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nom del servidor"
- },
- "settings_dns_add_custom_server_address": {
- "message": "Adreça del servidor DNS"
- },
"settings_dns_selector_default_title": {
"message": "Per defecte"
},
@@ -84,7 +72,7 @@
"message": "Accions"
},
"settings_exclusion_domain_name": {
- "message": "Nom de domini:"
+ "message": "Nom de domini"
},
"settings_exclusion_placeholder_search": {
"message": "Buscar"
diff --git a/src/_locales/cs/messages.json b/src/_locales/cs/messages.json
index 7864a1fc1..b97df74f2 100644
--- a/src/_locales/cs/messages.json
+++ b/src/_locales/cs/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "O aplikaci"
},
+ "all_rights_reserved": {
+ "message": "Všechna práva vyhrazena."
+ },
"options_support_title": {
"message": "Podpora"
},
@@ -97,13 +100,13 @@
"message": "Došlo k chybě. Prosím, zkuste to znovu později."
},
"options_bug_report_send_button": {
- "message": "Odeslat"
+ "message": "Odeslat hlášení"
},
"options_bug_report_sending_button": {
- "message": "Odesílání..."
+ "message": "Odesílání hlášení..."
},
"options_bug_report_textarea_placeholder": {
- "message": "Zde popište nalezenou chybu..."
+ "message": "Uveďte podrobnosti"
},
"options_bug_report_page_success": {
"message": "Děkujeme, Vaše zpráva byla úspěšně odeslána!"
@@ -118,13 +121,13 @@
"message": "Účet"
},
"account_edit": {
- "message": "Spravovat účet"
+ "message": "Otevřít nastavení účtu"
},
"account_version": {
"message": "Verze"
},
"account_free": {
- "message": "Účet zdarma"
+ "message": "Bezplatná verze"
},
"account_unlimited": {
"message": "Neomezené předplatné"
@@ -142,7 +145,7 @@
"message": "Získat předplatné"
},
"account_max_devices_count": {
- "message": "Až %num% připojená zařízení současně"
+ "message": "Až %num% zařízení připojených současně"
},
"account_valid_until": {
"message": "Platí do %date%"
@@ -157,7 +160,7 @@
"message": "Získat neomezenou verzi!"
},
"rate_description": {
- "message": "Ahoj, jsme tým AdGuardu a je pro nás velmi důležité znát Váš názor na náš produkt. Prosíme ohodnoťte jej."
+ "message": "Užíváte si AdGuard VPN?"
},
"rate_hide": {
"message": "Skrýt"
@@ -234,8 +237,8 @@
"privacy_policy": {
"message": "Zásady ochrany osobních údajů"
},
- "settings_title": {
- "message": "Nastavení"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Získejte GB zdarma!"
@@ -277,16 +280,16 @@
"message": "Znovu odeslat odkaz"
},
"settings_free_gbs_add_device_title": {
- "message": "Přidat další zařízení"
+ "message": "Přidat další platformu"
},
"settings_free_gbs_add_device_info": {
- "message": "Nainstalujte si AdGuard VPN na další zařízení, přihlaste se na něm ke svému účtu AdGuard a získejte %your_gb% GB"
+ "message": "Nainstalujte si AdGuard VPN pro iOS, Windows nebo Android, přihlaste se ke svému účtu AdGuard a získejte %your_gb% GB."
},
"settings_free_gbs_devices_added_title": {
- "message": "Přidaná zařízení"
+ "message": "Další platforma přidána"
},
"settings_free_gbs_devices_added_info": {
- "message": "Gratulujeme k instalaci AdGuard VPN na vícero zařízení. Zůstaňte v bezpečí!"
+ "message": "Gratulujeme k instalaci AdGuard VPN na více platformách. Zůstaňte v bezpečí!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Přejít na produkty"
@@ -313,16 +316,19 @@
"message": "Blokovat WebRTC"
},
"settings_webrtc_desc": {
- "message": "WebRTC je speciální program ve většině internetových prohlížečů, který umožňuje webové stránce třetí strany zjistit vaši skutečnou IP adresu a použít ji k vaší identifikaci"
+ "message": "Blokuje WebRTC, známou zranitelnost, která může prozradit vaši skutečnou IP adresu, i když používáte proxy server nebo VPN"
},
"settings_context_menus_title": {
- "message": "Přidat AdGuard VPN do kontextového menu prohlížeče"
+ "message": "Zobrazit AdGuard VPN v kontextovém menu prohlížeče"
+ },
+ "settings_context_menus_description": {
+ "message": "Správa výjimek VPN pro konkrétní webovou stránku"
},
"settings_help_us_improve_title": {
- "message": "Pomozte nám vylepšit"
+ "message": "Odesílání anonymních hlášení o pádu aplikace"
},
"settings_help_us_improve_description": {
- "message": "Umožněte službě AdGuard VPN shromažďovat zprávy o selhání, technické a interakční údaje. Je to anonymní."
+ "message": "Upozorněte vývojáře AdGuard VPN, pokud se něco pokazí"
},
"context_menu_selective_mode": {
"message": "Selektivní režim"
@@ -339,6 +345,12 @@
"settings_dns_label": {
"message": "DNS server"
},
+ "settings_dns_description": {
+ "message": "Řešení požadavků DNS, blokování reklam a slídičů a šifrování provozu DNS při připojení k VPN"
+ },
+ "settings_dns_description_current": {
+ "message": "Aktuální: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Populární DNS servery"
},
@@ -352,22 +364,22 @@
"message": "Editovat vlastní DNS server"
},
"settings_dns_add_custom_server_info": {
- "message": "Přečtěte si více o serverech DNS"
+ "message": "Známí poskytovatelé DNS"
},
"settings_dns_add_custom_server_name": {
"message": "Název serveru"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Název serveru"
+ "message": "Můj DNS server"
},
"settings_dns_add_custom_server_address": {
- "message": "Adresa serveru DNS"
+ "message": "Adresa serveru"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "IP serveru nebo název domény TLS"
+ "message": "IP adresa nebo tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Neplatná adresa DNS serveru"
+ "message": "Neplatná adresa serveru"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Server DNS je již přidán"
@@ -384,6 +396,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Vlastní DNS server byl smazán"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Vlastní DNS server byl upraven"
+ },
"settings_dns_selector_default_title": {
"message": "Výchozí"
},
@@ -487,10 +502,10 @@
"message": "AdGuard VPN je zapnuta pro zaškrtnuté domény a subdomény"
},
"settings_exclusion_domain_name": {
- "message": "Název domény:"
+ "message": "Název domény"
},
"settings_exclusion_subdomain_name": {
- "message": "Název subdomény:"
+ "message": "Subdoména"
},
"settings_exclusion_add_from_list": {
"message": "Ze seznamu"
@@ -550,7 +565,7 @@
"message": "Odstranit všechny výjimky"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Opravdu chcete pokračovat? Celý seznam výjimek bude odstraněn."
+ "message": "Chcete odstranit celý seznam výjimek?"
},
"settings_exclusion_title": {
"message": "Výjimky"
@@ -945,8 +960,11 @@
"options_signedout_page_title": {
"message": "Odhlásili jste se z AdGuard VPN"
},
+ "options_signedout_page_description_not_secure": {
+ "message": "Vaše připojení není zabezpečeno!"
+ },
"options_signedout_page_description": {
- "message": "Upozornění: nejste připojeni k síti VPN a váš provoz není chráněn! Chcete-li pokračovat v soukromém surfování, klikněte na ikonu AdGuard VPN a znovu se přihlaste."
+ "message": "Chcete-li pokračovat v soukromém procházení internetu, klikněte na ikonu AdGuard VPN a znovu se přihlaste."
},
"notification_data_limit_reached_title": {
"message": "Měsíční datový limit je dosažen"
diff --git a/src/_locales/da/messages.json b/src/_locales/da/messages.json
index f3b258439..1d7b44fdc 100644
--- a/src/_locales/da/messages.json
+++ b/src/_locales/da/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Om"
},
+ "all_rights_reserved": {
+ "message": "Alle rettigheder forbeholdes."
+ },
"options_support_title": {
"message": "Support"
},
@@ -97,16 +100,16 @@
"message": "En fejl opstod. Prøv igen senere."
},
"options_bug_report_send_button": {
- "message": "Indsend"
+ "message": "Indsend rapport"
},
"options_bug_report_sending_button": {
- "message": "Indsender..."
+ "message": "Indsender rapport..."
},
"options_bug_report_textarea_placeholder": {
- "message": "Beskriv problemet hér..."
+ "message": "Angiv detaljer"
},
"options_bug_report_page_success": {
- "message": "Tak, din besked er indsendt!"
+ "message": "Tak, beskeden er indsendt!"
},
"options_bug_report_new_report_button": {
"message": "Ny rapport"
@@ -118,13 +121,13 @@
"message": "Konto"
},
"account_edit": {
- "message": "Håndtér konto"
+ "message": "Åbn kontoindstillinger"
},
"account_version": {
"message": "Version"
},
"account_free": {
- "message": "Gratis konto"
+ "message": "Gratis version"
},
"account_unlimited": {
"message": "Ubegrænset abonnement"
@@ -142,7 +145,7 @@
"message": "Køb abonnement"
},
"account_max_devices_count": {
- "message": "Op til %num% samtidige enheder"
+ "message": "Op til %num% samtidige enheder"
},
"account_valid_until": {
"message": "Gyldig indtil %date%"
@@ -157,7 +160,7 @@
"message": "Køb den ubegrænsede version!"
},
"rate_description": {
- "message": "Hej, vi er et team fra AdGuard, og det er meget vigtigt for os at kende din mening om vores produkt. Bedøm det derfor venligst."
+ "message": "Har gavn af AdGuard VPN?"
},
"rate_hide": {
"message": "Skjul"
@@ -234,8 +237,8 @@
"privacy_policy": {
"message": "Fortrolighedspolitik"
},
- "settings_title": {
- "message": "Indstillinger"
+ "settings_general_title": {
+ "message": "Generalt"
},
"referral_get_free_traffic": {
"message": "Få gratis GB!"
@@ -277,16 +280,16 @@
"message": "Gensend link"
},
"settings_free_gbs_add_device_title": {
- "message": "Tilføj en ny enhed"
+ "message": "Tilføj endnu en platform"
},
"settings_free_gbs_add_device_info": {
- "message": "Installér AdGuard VPN på en enhed mere, log ind på AdGuard-kontoen fra den, og få %your_gb% GB"
+ "message": "Installér AdGuard VPN til Windows, Android, iOS eller Mac, log dernæst ind på AdGuard-kontoen og få %your_gb% GB."
},
"settings_free_gbs_devices_added_title": {
- "message": "Enheder tilføjet"
+ "message": "Endnu en platform tilføjet"
},
"settings_free_gbs_devices_added_info": {
- "message": "Tillykke med installationen af AdGuard VPN på flere enheder. Pas på dig selv!"
+ "message": "Tillykke med installationen af AdGuard VPN på flere platforme. Forbliv sikret!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Gå til Produkter"
@@ -313,16 +316,19 @@
"message": "Bloker WebRTC"
},
"settings_webrtc_desc": {
- "message": "WebRTC er et specialprogram i de fleste Internetbrowsere, der kan tillade et tredjepartswebsted at registrere din rigtige IP-adresse og bruge den til at identificere dig"
+ "message": "Blokér WebRTC, en kendt sårbarhed, der kan lække den reelle IP-adresse selv ved brug af proxy eller VPN"
},
"settings_context_menus_title": {
- "message": "Føj AdGuard VPN-element til browserens kontekstmenu"
+ "message": "Vis AdGuard VPN i webbrowserens kontekstmenu"
+ },
+ "settings_context_menus_description": {
+ "message": "Håndtér VPN-undtagelser for et bestemt websted"
},
"settings_help_us_improve_title": {
- "message": "Hjælp os med at blive bedre"
+ "message": "Indsend anonyme nedbrudsrapporter"
},
"settings_help_us_improve_description": {
- "message": "Tillad AdGuard VPN anonymiseret indsamling af nedbrudsrapporter, tekniske data og interaktionsdata."
+ "message": "Underret AdGuard VPN-udviklerne, hvis noget går galt"
},
"context_menu_selective_mode": {
"message": "Selektiv tilstand"
@@ -339,6 +345,12 @@
"settings_dns_label": {
"message": "DNS-server"
},
+ "settings_dns_description": {
+ "message": "Opløs DNS--forespørgsler, blokér annoncer og trackere samt kryptér DNS-trafik, når VPN er aktiveret"
+ },
+ "settings_dns_description_current": {
+ "message": "Nuværende: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Populære DNS-servere"
},
@@ -352,22 +364,22 @@
"message": "Redigere tilpasset DNS-server"
},
"settings_dns_add_custom_server_info": {
- "message": "Læs mere om DNS-servere"
+ "message": "Kendte DNS-udbydere"
},
"settings_dns_add_custom_server_name": {
"message": "Servernavn"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Servernavn"
+ "message": "Min DNS-server"
},
"settings_dns_add_custom_server_address": {
- "message": "DNS-serveradresse"
+ "message": "Serveradresse"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "Server-IP eller TLS-domænenavn"
+ "message": "IP-adresse eller tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Ugyldig DNS-serveradresse"
+ "message": "Ugyldig serveradresse"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS-server allerede tilføjet"
@@ -384,6 +396,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Tilpasset DNS-server slettet"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Tilpasset DNS-server redigeret"
+ },
"settings_dns_selector_default_title": {
"message": "Standard"
},
@@ -487,10 +502,10 @@
"message": "AdGuard VPN er slået til for afkrydsede domæner og underdomæner"
},
"settings_exclusion_domain_name": {
- "message": "Domænenavn:"
+ "message": "Domænenavn"
},
"settings_exclusion_subdomain_name": {
- "message": "Underdomænenavn:"
+ "message": "Underdomæne"
},
"settings_exclusion_add_from_list": {
"message": "Fra listen"
@@ -550,7 +565,7 @@
"message": "Fjern alle undtagelser"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Sikker på, at du vil fortsætte? Hele listen over undtagelser fjernes."
+ "message": "Skal hele listen over undtagelser fjernes?"
},
"settings_exclusion_title": {
"message": "Undtagelser"
@@ -943,10 +958,13 @@
"message": "Maks. antal forbindelser nået"
},
"options_signedout_page_title": {
- "message": "Du er logget ud af AdGuard VPN"
+ "message": "Der er logget ud af AdGuard VPN"
+ },
+ "options_signedout_page_description_not_secure": {
+ "message": "Forbindelsen er ikke sikret!"
},
"options_signedout_page_description": {
- "message": "Bemærk: Der er ikke forbundet til VPN, og trafikken er ikke beskyttet! For fortsat at surfe anonymt, så klik på AdGuard VPN-ikonet, og log ind igen."
+ "message": "For at fortsætte med at surfe anonymt, klik på AdGuard VPN-ikonet, og log ind igen."
},
"notification_data_limit_reached_title": {
"message": "Månedlig datakvote opbrugt"
diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json
index 626d0adfa..8104831f9 100644
--- a/src/_locales/de/messages.json
+++ b/src/_locales/de/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Über"
},
+ "all_rights_reserved": {
+ "message": "Alle Rechte vorbehalten."
+ },
"options_support_title": {
"message": "Support"
},
@@ -97,13 +100,13 @@
"message": "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal."
},
"options_bug_report_send_button": {
- "message": "Senden"
+ "message": "Bericht senden"
},
"options_bug_report_sending_button": {
- "message": "Wird gesendet …"
+ "message": "Bericht wird gesendet …"
},
"options_bug_report_textarea_placeholder": {
- "message": "Beschreiben Sie das Problem hier …"
+ "message": "Details bereitstellen"
},
"options_bug_report_page_success": {
"message": "Vielen Dank, Ihre Nachricht wurde erfolgreich gesendet!"
@@ -118,13 +121,13 @@
"message": "Konto"
},
"account_edit": {
- "message": "Konto verwalten"
+ "message": "Konteneinstellungen öffnen"
},
"account_version": {
"message": "Version"
},
"account_free": {
- "message": "Kostenloses Konto"
+ "message": "Kostenlose Version"
},
"account_unlimited": {
"message": "Unbegrenztes Abonnement"
@@ -142,7 +145,7 @@
"message": "Abonnement erwerben"
},
"account_max_devices_count": {
- "message": "Bis zu %num% Geräte "
+ "message": "Bis zu %num% gleichzeitige Geräte"
},
"account_valid_until": {
"message": "Gültig bis %date%"
@@ -157,7 +160,7 @@
"message": "Holen Sie sich die Uneingeschränkte Version!"
},
"rate_description": {
- "message": "Hallo, dem Team von AdGuard ist es sehr wichtig, Ihre Meinung zu unserem Produkt zu erfahren. Bitte geben Sie eine Bewertung ab."
+ "message": "Sind Sie mit AdGuard VPN zufrieden?"
},
"rate_hide": {
"message": "Ausblenden"
@@ -234,8 +237,8 @@
"privacy_policy": {
"message": "Datenschutzerklärung"
},
- "settings_title": {
- "message": "Einstellungen"
+ "settings_general_title": {
+ "message": "Allgemein"
},
"referral_get_free_traffic": {
"message": "Holen Sie sich kostenlosen VPN-Traffic!"
@@ -277,16 +280,16 @@
"message": "Link erneut senden"
},
"settings_free_gbs_add_device_title": {
- "message": "Weiteres Gerät hinzufügen"
+ "message": "Eine weitere Plattform hinzufügen"
},
"settings_free_gbs_add_device_info": {
- "message": "Installieren Sie AdGuard VPN auf einem weiteren Gerät, melden Sie sich mit diesem Gerät bei Ihrem AdGuard-Konto an, und Sie erhalten %your_gb% GB"
+ "message": "Installieren Sie AdGuard VPN für iOS, Mac, Windows oder Android, melden Sie sich dort bei Ihrem AdGuard-Konto an und Sie erhalten %your_gb% GB."
},
"settings_free_gbs_devices_added_title": {
- "message": "Geräte hinzugefügt"
+ "message": "Weitere Plattform hinzugefügt"
},
"settings_free_gbs_devices_added_info": {
- "message": "Sie haben AdGuard VPN auf mehreren Geräten installiert. Bleiben Sie geschützt!"
+ "message": "Herzlichen Glückwunsch zur Installation von AdGuard VPN auf mehreren Plattformen. Bleiben Sie geschützt!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Zu den Produkten wechseln"
@@ -313,16 +316,19 @@
"message": "WebRTC sperren"
},
"settings_webrtc_desc": {
- "message": "WebRTC ist ein spezielles Programm in den meisten Internet-Browsern, womit Drittanbieter Ihre wirkliche IP-Adresse ermitteln und Sie identifizieren können"
+ "message": "Sperrt WebRTC, eine bekannte Sicherheitslücke, die Ihre echte IP-Adresse preisgeben kann, selbst wenn Sie einen Proxy oder ein VPN verwenden"
},
"settings_context_menus_title": {
- "message": "AdGuard VPN zum Kontextmenü des Browsers hinzufügen"
+ "message": "AdGuard VPN im Kontextmenü des Browsers anzeigen"
+ },
+ "settings_context_menus_description": {
+ "message": "Verwalten von VPN-Ausschlüssen für eine bestimmte Website"
},
"settings_help_us_improve_title": {
- "message": "AdGuard helfen, besser zu werden"
+ "message": "Anonyme Absturzberichte senden"
},
"settings_help_us_improve_description": {
- "message": "AdGuard VPN erlauben, Absturzberichte, technische und Interaktionsdaten zu sammeln. Dies erfolgt anonymisiert."
+ "message": "AdGuard VPN-Entwickler benachrichtigen, wenn ein Problem auftritt"
},
"context_menu_selective_mode": {
"message": "Selektiver Modus"
@@ -339,6 +345,12 @@
"settings_dns_label": {
"message": "DNS-Server"
},
+ "settings_dns_description": {
+ "message": "Löst DNS-Anfragen auf, sperrt Werbung und Tracker und verschlüsselt DNS-Verkehr, wenn Sie mit einem VPN verbunden sind"
+ },
+ "settings_dns_description_current": {
+ "message": "Aktuell: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Beliebte DNS-Server"
},
@@ -352,22 +364,22 @@
"message": "Benutzerdefinierten DNS-Server bearbeiten"
},
"settings_dns_add_custom_server_info": {
- "message": "Erfahren Sie mehr über DNS-Server"
+ "message": "Bekannte DNS-Anbieter"
},
"settings_dns_add_custom_server_name": {
"message": "Servername"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Servername"
+ "message": "Meine DNS-Server"
},
"settings_dns_add_custom_server_address": {
- "message": "Adresse des DNS-Servers"
+ "message": "Adresse des Servers"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "Server-IP oder TLS-Domainname"
+ "message": "IP address oder tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Ungültige DNS-Adresse"
+ "message": "Ungültige Server-Adresse"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS-Server bereits hinzugefügt"
@@ -384,6 +396,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Benutzerdefinierter DNS-Server entfernt"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Benutzerdefinierten DNS-Server bearbeitet"
+ },
"settings_dns_selector_default_title": {
"message": "Standard"
},
@@ -487,10 +502,10 @@
"message": "AdGuard VPN ist für die ausgewählten Domains und Subdomains aktiviert"
},
"settings_exclusion_domain_name": {
- "message": "Domainname:"
+ "message": "Domainname"
},
"settings_exclusion_subdomain_name": {
- "message": "Subdomain-Name:"
+ "message": "Subdomain"
},
"settings_exclusion_add_from_list": {
"message": "Aus der Liste"
@@ -550,7 +565,7 @@
"message": "Alle Ausschlüsse entfernen"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Möchten Sie fortfahren? Die gesamte Ausschlussliste wird entfernt."
+ "message": "Möchten Sie die gesamte Liste der Ausschlüsse leeren?"
},
"settings_exclusion_title": {
"message": "Ausschlüsse"
@@ -943,10 +958,13 @@
"message": "Max. Anzahl Verbindungen erreicht"
},
"options_signedout_page_title": {
- "message": "Sie haben sich bei AdGuard VPN abgemeldet"
+ "message": "Sie haben sich von AdGuard VPN abgemeldet"
+ },
+ "options_signedout_page_description_not_secure": {
+ "message": "Ihre Verbindung erfolgt unverschlüsselt!"
},
"options_signedout_page_description": {
- "message": "Sie sind mit AdGuard VPN nicht verbunden. Ihr Traffic ist nicht geschützt. Um privat weiterzusurfen, klicken Sie auf das AdGuard VPN-Symbol und melden Sie sich erneut an."
+ "message": "Um privat weiterzusurfen, klicken Sie auf das AdGuard VPN-Symbol und melden Sie sich erneut an."
},
"notification_data_limit_reached_title": {
"message": "Monatliches Datenlimit erreicht"
diff --git a/src/_locales/el/messages.json b/src/_locales/el/messages.json
index f101002ae..59ff5eed2 100644
--- a/src/_locales/el/messages.json
+++ b/src/_locales/el/messages.json
@@ -38,6 +38,9 @@
"about_title": {
"message": "Σχετικά με"
},
+ "all_rights_reserved": {
+ "message": "Όλα τα δικαιώματα διατηρούνται."
+ },
"options_support_title": {
"message": "Υποστήριξη"
},
@@ -74,18 +77,6 @@
"options_bug_report_request_error": {
"message": "Παρουσιάστηκε σφάλμα. Παρακαλώ δοκιμάστε ξανά αργότερα."
},
- "options_bug_report_send_button": {
- "message": "Υποβολή"
- },
- "options_bug_report_sending_button": {
- "message": "Υποβολή..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Περιγράψτε το πρόβλημα εδώ..."
- },
- "options_bug_report_page_success": {
- "message": "Σας ευχαριστούμε, το μήνυμά σας υποβλήθηκε με επιτυχία!"
- },
"options_bug_report_new_report_button": {
"message": "Νέα αναφορά"
},
@@ -98,9 +89,6 @@
"account_version": {
"message": "Έκδοση"
},
- "rate_description": {
- "message": "Γεια σας, είμαστε μια ομάδα από το AdGuard και είναι πολύ σημαντικό να γνωρίζουμε τη γνώμη σας για το προϊόν μας. Παρακαλώ αξιολογήστε το."
- },
"rate_hide": {
"message": "Απόκρυψη"
},
@@ -161,9 +149,6 @@
"privacy_policy": {
"message": "Πολιτική Απορρήτου"
},
- "settings_title": {
- "message": "Ρυθμίσεις"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Μεταβείτε στα Προϊόντα"
},
@@ -176,18 +161,6 @@
"settings_webrtc_label": {
"message": "Αποκλεισμός WebRTC"
},
- "settings_webrtc_desc": {
- "message": "Το WebRTC είναι ένα ειδικό πρόγραμμα στα περισσότερα προγράμματα περιήγησης που μπορούν να επιτρέψουν σε έναν ιστότοπο τρίτου μέρους να εντοπίσει την πραγματική σας διεύθυνση IP και να την χρησιμοποιήσει για να σας αναγνωρίσει"
- },
- "settings_context_menus_title": {
- "message": "Προσθήκη στοιχείου AdGuard VPN στο μενού περιβάλλοντος του προγράμματος περιήγησης"
- },
- "settings_help_us_improve_title": {
- "message": "Βοηθήστε μας να βελτιωθούμε"
- },
- "settings_help_us_improve_description": {
- "message": "Επιτρέψτε στο AdGuard VPN να συλλέγει αναφορές σφαλμάτων, τεχνικά δεδομένα και δεδομένα αλληλεπίδρασης. Αυτό είναι ανώνυμο."
- },
"context_menu_selective_mode": {
"message": "Επιλεκτική λειτουργία"
},
@@ -215,12 +188,6 @@
"settings_dns_add_custom_server_name": {
"message": "Όνομα διακομιστή"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Όνομα διακομιστή"
- },
- "settings_dns_add_custom_server_address": {
- "message": "Διεύθυνση διακομιστή DNS"
- },
"settings_dns_add_custom_server_save_and_select": {
"message": "Αποθήκευση και επιλογή"
},
@@ -291,7 +258,7 @@
"message": "Ενέργειες"
},
"settings_exclusion_domain_name": {
- "message": "Όνομα τομέα:"
+ "message": "Όνομα τομέα"
},
"settings_exclusion_modal_cancel": {
"message": "Άκυρο"
@@ -500,9 +467,6 @@
"popup_connections_limit_title": {
"message": "Συμπληρώθηκε το όριο συνδέσεων"
},
- "options_signedout_page_title": {
- "message": "Έχετε αποσυνδεθεί από το AdGuard VPN"
- },
"name": {
"message": "AdGuard VPN — free & secure proxy"
},
diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
index 6fc6dc042..522f30963 100644
--- a/src/_locales/en/messages.json
+++ b/src/_locales/en/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "About"
},
+ "all_rights_reserved": {
+ "message": "All rights reserved."
+ },
"options_support_title": {
"message": "Support"
},
@@ -97,16 +100,16 @@
"message": "An error occurred. Please try again later."
},
"options_bug_report_send_button": {
- "message": "Submit"
+ "message": "Send report"
},
"options_bug_report_sending_button": {
- "message": "Submitting..."
+ "message": "Sending report..."
},
"options_bug_report_textarea_placeholder": {
- "message": "Describe the problem here..."
+ "message": "Provide details"
},
"options_bug_report_page_success": {
- "message": "Thank you, your message was submitted successfully!"
+ "message": "Thank you, your message sent successfully!"
},
"options_bug_report_new_report_button": {
"message": "New report"
@@ -118,13 +121,13 @@
"message": "Account"
},
"account_edit": {
- "message": "Manage account"
+ "message": "Open account settings"
},
"account_version": {
"message": "Version"
},
"account_free": {
- "message": "Free account"
+ "message": "Free version"
},
"account_unlimited": {
"message": "Unlimited subscription"
@@ -142,7 +145,7 @@
"message": "Get subscription"
},
"account_max_devices_count": {
- "message": "Up to %num% simultaneous devices"
+ "message": "Up to %num% simultaneous devices"
},
"account_valid_until": {
"message": "Valid until %date%"
@@ -157,7 +160,7 @@
"message": "Get the Unlimited version!"
},
"rate_description": {
- "message": "Hey, we are a team from AdGuard and it is very important for us to know your opinion about our product. Please rate it."
+ "message": "Enjoying AdGuard VPN?"
},
"rate_hide": {
"message": "Hide"
@@ -234,8 +237,8 @@
"privacy_policy": {
"message": "Privacy Policy"
},
- "settings_title": {
- "message": "Settings"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Get free GBs!"
@@ -277,16 +280,16 @@
"message": "Resend link"
},
"settings_free_gbs_add_device_title": {
- "message": "Add another device"
+ "message": "Add another platform"
},
"settings_free_gbs_add_device_info": {
- "message": "Install AdGuard VPN on one more device, log in to your AdGuard account on it, and get %your_gb% GB"
+ "message": "Install AdGuard VPN for iOS, Mac, Windows, or Android, log in to your AdGuard account there, and get %your_gb% GB."
},
"settings_free_gbs_devices_added_title": {
- "message": "Devices added"
+ "message": "Another platform added"
},
"settings_free_gbs_devices_added_info": {
- "message": "Congrats with installing AdGuard VPN on multiple devices. Stay safe!"
+ "message": "Congrats with installing AdGuard VPN on multiple platforms. Stay safe!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Go to Products"
@@ -313,16 +316,19 @@
"message": "Block WebRTC"
},
"settings_webrtc_desc": {
- "message": "WebRTC is a special program in most Internet browsers that can allow a third-party website to detect your real IP address and use it to identify you"
+ "message": "Block WebRTC, a known vulnerability that can leak your real IP address even if you use a proxy or VPN"
},
"settings_context_menus_title": {
- "message": "Add AdGuard VPN item to the browser’s context menu"
+ "message": "Display AdGuard VPN in the browser context menu"
+ },
+ "settings_context_menus_description": {
+ "message": "Manage VPN exclusions for a specific website"
},
"settings_help_us_improve_title": {
- "message": "Help us improve"
+ "message": "Send anonymous crash reports"
},
"settings_help_us_improve_description": {
- "message": "Allow AdGuard VPN to gather crash reports, technical and interaction data. This is anonymous."
+ "message": "Notify AdGuard VPN developers if something goes wrong"
},
"context_menu_selective_mode": {
"message": "Selective mode"
@@ -339,6 +345,12 @@
"settings_dns_label": {
"message": "DNS server"
},
+ "settings_dns_description": {
+ "message": "Resolve DNS requests, block ads and trackers, and encrypt DNS traffic when you're connected to VPN"
+ },
+ "settings_dns_description_current": {
+ "message": "Current: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Popular DNS servers"
},
@@ -352,22 +364,22 @@
"message": "Edit custom DNS server"
},
"settings_dns_add_custom_server_info": {
- "message": "Read more about DNS servers"
+ "message": "Known DNS providers"
},
"settings_dns_add_custom_server_name": {
"message": "Server name"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Server name"
+ "message": "My DNS Server"
},
"settings_dns_add_custom_server_address": {
- "message": "DNS server address"
+ "message": "Server address"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "Server IP or TLS domain name"
+ "message": "IP address or tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Invalid DNS server address"
+ "message": "Invalid server address"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS server already added"
@@ -384,6 +396,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Custom DNS server deleted"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Custom DNS server edited"
+ },
"settings_dns_selector_default_title": {
"message": "Default"
},
@@ -487,10 +502,10 @@
"message": "AdGuard VPN is on for the checked domains and subdomains"
},
"settings_exclusion_domain_name": {
- "message": "Domain name:"
+ "message": "Domain name"
},
"settings_exclusion_subdomain_name": {
- "message": "Subdomain name:"
+ "message": "Subdomain"
},
"settings_exclusion_add_from_list": {
"message": "From the list"
@@ -550,7 +565,7 @@
"message": "Remove all exclusions"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Are you sure you want to continue? The entire list of exclusions will be removed."
+ "message": "Do you want to remove the entire list of exclusions?"
},
"settings_exclusion_title": {
"message": "Exclusions"
@@ -943,10 +958,13 @@
"message": "Connections limit reached"
},
"options_signedout_page_title": {
- "message": "You have signed out of AdGuard VPN"
+ "message": "You have logged out of AdGuard VPN"
+ },
+ "options_signedout_page_description_not_secure": {
+ "message": "Your connection is not secured!"
},
"options_signedout_page_description": {
- "message": "Please note: you are not connected to VPN and your traffic is not protected! To continue surfing privately, click the AdGuard VPN icon and sign in again."
+ "message": "To continue surfing privately, click the AdGuard VPN icon and log in again."
},
"notification_data_limit_reached_title": {
"message": "Monthly data limit reached"
diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json
index 88032f834..ae0e5a59a 100644
--- a/src/_locales/es/messages.json
+++ b/src/_locales/es/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Acerca de"
},
+ "all_rights_reserved": {
+ "message": "Todos los derechos reservados."
+ },
"options_support_title": {
"message": "Asistencia técnica"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Se ha producido un error. Por favor inténtalo de nuevo más tarde."
},
- "options_bug_report_send_button": {
- "message": "Enviar"
- },
- "options_bug_report_sending_button": {
- "message": "Enviando..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Describe el problema aquí..."
- },
- "options_bug_report_page_success": {
- "message": "¡Gracias, tu mensaje se envió correctamente!"
- },
"options_bug_report_new_report_button": {
"message": "Nuevo informe"
},
@@ -118,13 +109,13 @@
"message": "Cuenta"
},
"account_edit": {
- "message": "Administrar cuenta"
+ "message": "Abrir la configuración de la cuenta"
},
"account_version": {
"message": "Versión"
},
"account_free": {
- "message": "Cuenta gratuita"
+ "message": "Versión gratuita"
},
"account_unlimited": {
"message": "Suscripción ilimitada"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Obtener suscripción"
},
- "account_max_devices_count": {
- "message": "Hasta %num% dispositivos simultáneos"
- },
"account_valid_until": {
"message": "Válido hasta el %date%"
},
@@ -157,7 +145,7 @@
"message": "¡Obtén la versión ilimitada!"
},
"rate_description": {
- "message": "Hola, somos el equipo de AdGuard, y es muy importante para nosotros saber tu opinión sobre nuestro producto. Por favor, califícalo."
+ "message": "¿Te gusta AdGuard VPN?"
},
"rate_hide": {
"message": "Ocultar"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Política de privacidad"
},
- "settings_title": {
- "message": "Configuración"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "¡Obtén GBs gratis!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Reenviar enlace"
},
- "settings_free_gbs_add_device_title": {
- "message": "Añadir otro dispositivo"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Instala AdGuard VPN en un dispositivo más, inicia sesión en tu cuenta AdGuard en él y obtén %your_gb% GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Dispositivos añadidos"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Felicidades por instalar AdGuard VPN en varios dispositivos. ¡Mantente a salvo!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Ir a los productos"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Bloquear WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC es un programa especial en la mayoría de los navegadores de Internet, y puede permitir que un sitio web de terceros detecte tu dirección IP real y la use para identificarte"
- },
- "settings_context_menus_title": {
- "message": "Añadir el elemento AdGuard VPN al menú contextual del navegador"
- },
"settings_help_us_improve_title": {
- "message": "Ayúdanos a mejorar"
- },
- "settings_help_us_improve_description": {
- "message": "Permite que AdGuard VPN recopile informes de fallas, datos técnicos y de interacción. Esto es anónimo."
+ "message": "Enviar informes de fallos anónimos"
},
"context_menu_selective_mode": {
"message": "Modo selectivo"
@@ -352,22 +319,13 @@
"message": "Editar servidor DNS personalizado"
},
"settings_dns_add_custom_server_info": {
- "message": "Más información sobre los servidores DNS"
+ "message": "Proveedores de DNS conocidos"
},
"settings_dns_add_custom_server_name": {
"message": "Nombre del servidor"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nombre del servidor"
- },
"settings_dns_add_custom_server_address": {
- "message": "Dirección del servidor DNS"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP del servidor o nombre de dominio TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Dirección de servidor DNS no válido"
+ "message": "Dirección del servidor"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Servidor DNS ya añadido"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN está habilitado para los dominios y subdominios marcados"
},
"settings_exclusion_domain_name": {
- "message": "Nombre del dominio:"
+ "message": "Nombre del dominio"
},
"settings_exclusion_subdomain_name": {
- "message": "Nombre del subdominio:"
+ "message": "Subdominio"
},
"settings_exclusion_add_from_list": {
"message": "De la lista"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Eliminar todas las exclusiones"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "¿Estás seguro de que quieres continuar? Se eliminará toda la lista de exclusiones."
- },
"settings_exclusion_title": {
"message": "Exclusiones"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Se alcanzó el límite de conexiones"
},
- "options_signedout_page_title": {
- "message": "Has cerrado sesión en AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Tener en cuenta: ¡no estás conectado a la VPN y tu tráfico no está protegido! Para seguir navegando de forma privada, haz clic en el icono de AdGuard VPN y vuelve a iniciar sesión."
- },
"notification_data_limit_reached_title": {
"message": "Se alcanzó el límite de datos mensual"
},
diff --git a/src/_locales/fa/messages.json b/src/_locales/fa/messages.json
index 23a629084..a757f0502 100644
--- a/src/_locales/fa/messages.json
+++ b/src/_locales/fa/messages.json
@@ -14,6 +14,9 @@
"about_title": {
"message": "درباره"
},
+ "all_rights_reserved": {
+ "message": "همه حقوق محفوظ است."
+ },
"options_support_title": {
"message": "پشتيباني"
},
@@ -35,9 +38,6 @@
"options_bug_report_include_log_label": {
"message": "شامل یک گزارش تشخیصی به این پیام"
},
- "options_bug_report_send_button": {
- "message": "ارسال"
- },
"options_bug_report_textarea_label": {
"message": "پيام"
},
@@ -47,6 +47,9 @@
"account_version": {
"message": "نسخه"
},
+ "account_free": {
+ "message": "رایگان"
+ },
"rate_hide": {
"message": "پنهان کن"
},
@@ -95,8 +98,8 @@
"privacy_policy": {
"message": "سیاست حریم خصوصی"
},
- "settings_title": {
- "message": "تنظیمات"
+ "settings_general_title": {
+ "message": "General"
},
"settings_free_gbs_add_device_products_button": {
"message": "برو به محصولات"
@@ -128,21 +131,9 @@
"settings_dns_edit_custom_server": {
"message": "افزودن سرور DNS دلخواه"
},
- "settings_dns_add_custom_server_info": {
- "message": "در مورد سرورهای DNS بیشتر بخوانید"
- },
"settings_dns_add_custom_server_name": {
"message": "نام سرور"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "نام سرور"
- },
- "settings_dns_add_custom_server_address": {
- "message": "آدرس DNS سرور"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "آدرس DNS نامعتبر"
- },
"settings_dns_add_custom_server_save_and_select": {
"message": "ذخیره و انتخاب"
},
@@ -212,6 +203,9 @@
"settings_exclusion_actions": {
"message": "اقدامات"
},
+ "settings_exclusion_domain_name": {
+ "message": "نام دامنه"
+ },
"settings_exclusion_modal_cancel": {
"message": "لغو"
},
diff --git a/src/_locales/fi/messages.json b/src/_locales/fi/messages.json
index c91813591..75a44e8c6 100644
--- a/src/_locales/fi/messages.json
+++ b/src/_locales/fi/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Tietoja"
},
+ "all_rights_reserved": {
+ "message": "Kaikki oikeudet pidätetään."
+ },
"options_support_title": {
"message": "Tuki"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Tapahtui virhe. Yritä myöhemmin uudelleen."
},
- "options_bug_report_send_button": {
- "message": "Lähetä"
- },
- "options_bug_report_sending_button": {
- "message": "Lähetetään..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Kuvaile ongelmaa..."
- },
- "options_bug_report_page_success": {
- "message": "Kiitos, viestisi lähetys onnistui!"
- },
"options_bug_report_new_report_button": {
"message": "Uusi raportti"
},
@@ -118,13 +109,13 @@
"message": "Tili"
},
"account_edit": {
- "message": "Hallitse tiliä"
+ "message": "Avaa tilin asetukset"
},
"account_version": {
"message": "Versio"
},
"account_free": {
- "message": "Ilmainen tili"
+ "message": "Ilmaisversio"
},
"account_unlimited": {
"message": "Unlimited-tilaus"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Hanki tilaus"
},
- "account_max_devices_count": {
- "message": "Samanaikaisesti %num% laiteelle"
- },
"account_valid_until": {
"message": "Voimassa %date% asti"
},
@@ -157,7 +145,7 @@
"message": "Hanki Unlimited-tilaus!"
},
"rate_description": {
- "message": "Hei. Olemme AdGuard VPN:n tiimi ja kuulisimme erittäin mielellämme mielipiteesi tuotteestamme."
+ "message": "Nautitko AdGuard VPN:stä?"
},
"rate_hide": {
"message": "Piilota"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Tietosuojakäytäntö"
},
- "settings_title": {
- "message": "Asetukset"
+ "settings_general_title": {
+ "message": "Yleiset"
},
"referral_get_free_traffic": {
"message": "Ansaitse ilmaisia gigatavuja!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Lähetä linkki uudelleen"
},
- "settings_free_gbs_add_device_title": {
- "message": "Lisää uusi laite"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Asenna AdGuard VPN uudelle laitteelle ja kirjaudu AdGuard-tilillesi niin saat %your_gb% Gt"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Laitteita lisätty"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Onnittelut AdGuard VPN:n asennuksesta useille laitteille. Pysy turvassa!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Tutustu tuotteisiimme"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Estä WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC on useimpien selainten käyttämä rajapinta, jonka avulla kolmansien osapuolten verkkosivustot voivat havaita todellisen IP-osoitteesi ja käyttää sitä tunnistamiseesi."
- },
- "settings_context_menus_title": {
- "message": "Lisää AdGuard VPN selaimen valikkoon"
- },
"settings_help_us_improve_title": {
- "message": "Auta meitä parantamaan"
- },
- "settings_help_us_improve_description": {
- "message": "Salli AdGuard VPN:n kerätä nimettömiä virheraportteja ja teknisiä tietoja sen käytöstä."
+ "message": "Lähetä anonyymejä virheraportteja"
},
"context_menu_selective_mode": {
"message": "Valikoiva tila"
@@ -352,22 +319,13 @@
"message": "Muokkaa omaa DNS-palvelinta"
},
"settings_dns_add_custom_server_info": {
- "message": "Lue lisää DNS-palvelimista"
+ "message": "Tunnetut DNS-palveluntarjoajat"
},
"settings_dns_add_custom_server_name": {
"message": "Palvelimen nimi"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Palvelimen nimi"
- },
"settings_dns_add_custom_server_address": {
- "message": "DNS-palvelimen osoite"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "Palvelimen IP-osoite tai TLS-verkkotunnus"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Virheellinen DNS-palvelimen osoite"
+ "message": "Palvelimen osoite"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS-palvelin on jo lisätty"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN toimii valituilla verkko- ja aliverkkotunnuksilla."
},
"settings_exclusion_domain_name": {
- "message": "Verkkotunnus:"
+ "message": "Verkkotunnus"
},
"settings_exclusion_subdomain_name": {
- "message": "Aliverkkotunnuksen nimi:"
+ "message": "Aliverkkotunnus"
},
"settings_exclusion_add_from_list": {
"message": "Hakemistosta"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Poista kaikki poikkeukset"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Haluatko varmasti jatkaa? Koko lista ja kaikki sen sisältämät poikkeukset poistetaan."
- },
"settings_exclusion_title": {
"message": "Poikkeukset"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Yhteysraja saavutettu"
},
- "options_signedout_page_title": {
- "message": "Olet kirjautunut ulos AdGuard VPN -palvelusta"
- },
- "options_signedout_page_description": {
- "message": "Huomioi: Et ole yhteydessä VPN:ään, eikä tietoliikennettäsi ole suojattu! Jatkaaksesi selausta yksityisesti, paina AdGuard VPN -kuvaketta ja kirjaudu uudelleen."
- },
"notification_data_limit_reached_title": {
"message": "Kuukautinen tiedonsiirtorajoitus on saavutettu"
},
diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json
index 4e2d95af0..f8bc60c6d 100644
--- a/src/_locales/fr/messages.json
+++ b/src/_locales/fr/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "À propos"
},
+ "all_rights_reserved": {
+ "message": "Tous droits réservés."
+ },
"options_support_title": {
"message": "Assistance"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Une erreur est survenue. Veuillez réessayer plus tard."
},
- "options_bug_report_send_button": {
- "message": "Envoyer"
- },
- "options_bug_report_sending_button": {
- "message": "Envoi en cours..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Décrivez le problème ici..."
- },
- "options_bug_report_page_success": {
- "message": "Merci, votre message a bien été envoyé !"
- },
"options_bug_report_new_report_button": {
"message": "Nouveau signalement"
},
@@ -118,13 +109,13 @@
"message": "Compte"
},
"account_edit": {
- "message": "Gérer le compte"
+ "message": "Ouvrir les paramètres du compte"
},
"account_version": {
"message": "Version"
},
"account_free": {
- "message": "Compte gratuit"
+ "message": "Version gratuite"
},
"account_unlimited": {
"message": "Abonnement illimité"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Obtenir un abonnement"
},
- "account_max_devices_count": {
- "message": "Jusqu'à %num% appareils simultanés"
- },
"account_valid_until": {
"message": "Valable jusqu'au %date%"
},
@@ -157,7 +145,7 @@
"message": "Obtenez la version Illimitée !"
},
"rate_description": {
- "message": "Hé, nous sommes une équipe d’AdGuard et il est très important pour nous de connaître votre opinion sur notre produit. Veuillez le noter."
+ "message": "Appréciez-vous AdGuard VPN ?"
},
"rate_hide": {
"message": "Masquer"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Politique de confidentialité"
},
- "settings_title": {
- "message": "Paramètres"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Obtenez des Go gratuits !"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Renvoyer le lien"
},
- "settings_free_gbs_add_device_title": {
- "message": "Ajouter un autre appareil"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Installez AdGuard VPN sur un autre appareil, connectez-vous avec votre compte AdGuard sur celui-ci et obtenez %your_gb% Go"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Appareils ajoutés"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Félicitations pour l'installation d'AdGuard VPN sur plusieurs appareils. Restez prudent !"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Aller vers Produits"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Bloquer WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC est un programme spécial inclus dans la majorité des navigateurs Internet qui peut permettre à un site tiers de détecter votre adresse IP et de l'utiliser pour vous identifier"
- },
- "settings_context_menus_title": {
- "message": "Ajouter AdGuard VPN au menu contextuel du navigateur"
- },
"settings_help_us_improve_title": {
- "message": "Aidez nous à améliorer"
- },
- "settings_help_us_improve_description": {
- "message": "Autorisez AdGuard VPN à collecter des rapports de plantage, des données techniques et celles d’interaction. Ceci est anonyme."
+ "message": "Envoyer des rapports de plantage anonymes"
},
"context_menu_selective_mode": {
"message": "Mode sélectif"
@@ -352,22 +319,13 @@
"message": "Éditer le serveur DNS personnalisé"
},
"settings_dns_add_custom_server_info": {
- "message": "En savoir plus sur les serveurs DNS"
+ "message": "Fournisseurs DNS connus"
},
"settings_dns_add_custom_server_name": {
"message": "Nom du serveur"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nom du serveur"
- },
"settings_dns_add_custom_server_address": {
- "message": "Adresse du serveur DNS"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP du serveur ou nom de domaine TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Adresse DNS non-valide"
+ "message": "Adresse du serveur"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Serveur DNS déjà ajouté"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN est activé pour les domaines et sous-domaines cochés"
},
"settings_exclusion_domain_name": {
- "message": "Nom de domaine :"
+ "message": "Nom de domaine"
},
"settings_exclusion_subdomain_name": {
- "message": "Nom de sous-domaine :"
+ "message": "Sous-domaine"
},
"settings_exclusion_add_from_list": {
"message": "Depuis la liste"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Supprimer toutes les exclusions"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Souhaitez-vous vraiment continuer ? La liste entière des exclusions sera supprimée."
- },
"settings_exclusion_title": {
"message": "Exclusions"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "La limite des connexions est atteinte"
},
- "options_signedout_page_title": {
- "message": "Vous vous êtes déconnecté(e) de AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Votre connexion n'est pas sécurisée ! Pour continuer à surfer l'Internet en toute confidentialité, cliquez l'icône AdGuard VPN et reconnectez-vous."
- },
"notification_data_limit_reached_title": {
"message": "La limite mensuelle de données"
},
diff --git a/src/_locales/he/messages.json b/src/_locales/he/messages.json
index d4ead5315..3b8c48b69 100644
--- a/src/_locales/he/messages.json
+++ b/src/_locales/he/messages.json
@@ -50,6 +50,9 @@
"about_title": {
"message": "אודות"
},
+ "all_rights_reserved": {
+ "message": "כל הזכויות שמורות."
+ },
"options_support_title": {
"message": "תמיכה"
},
@@ -89,18 +92,6 @@
"options_bug_report_request_error": {
"message": "שגיאה התרחשה. אנא נסה שוב מאוחר יותר."
},
- "options_bug_report_send_button": {
- "message": "הגש"
- },
- "options_bug_report_sending_button": {
- "message": "מגיש…"
- },
- "options_bug_report_textarea_placeholder": {
- "message": "תאר את הבעיה כאן…"
- },
- "options_bug_report_page_success": {
- "message": "תודה, ההודעה שלך הוגשה בהצלחה!"
- },
"options_bug_report_new_report_button": {
"message": "דוח חדש"
},
@@ -110,15 +101,9 @@
"account_title": {
"message": "חשבון"
},
- "account_edit": {
- "message": "נהל חשבון"
- },
"account_version": {
"message": "גרסה"
},
- "account_free": {
- "message": "חשבון חינמי"
- },
"account_unlimited": {
"message": "מינוי בלתי מוגבל"
},
@@ -134,18 +119,12 @@
"account_get_subscription": {
"message": "השג מינוי"
},
- "account_max_devices_count": {
- "message": "עד %num% מכשירים"
- },
"account_sign_out": {
"message": "צא"
},
"account_unlimited_title": {
"message": "קבל את הגרסה הבלתי מוגבלת!"
},
- "rate_description": {
- "message": "היי, אנחנו צוות של AdGuard וזה חשוב מאוד עבורנו לדעת מה דעתך על המוצר שלנו. אנא דרג אותו."
- },
"rate_hide": {
"message": "הסתר"
},
@@ -221,8 +200,8 @@
"privacy_policy": {
"message": "מדיניות פרטיות"
},
- "settings_title": {
- "message": "הגדרות"
+ "settings_general_title": {
+ "message": "General"
},
"settings_referral_invited_friends": {
"message": "חברים מוזמנים %count%/%limit%"
@@ -239,18 +218,6 @@
"settings_webrtc_label": {
"message": "חסום WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC הוא תוכנית מיוחדת ברוב הדפדפנים אשר יכולה להתיר לאתר צד־שלישי לגלות את כתובת ה־IP האמיתית שלך ולהשתמש בה כדי לזהות אותך"
- },
- "settings_context_menus_title": {
- "message": "הוסף פריט AdGuard VPN אל תפריט ההקשר של הדפדפן"
- },
- "settings_help_us_improve_title": {
- "message": "עזור לנו להשתפר"
- },
- "settings_help_us_improve_description": {
- "message": "התר אל AdGuard VPN לאסוף דוחות קריסה, נתונים טכנים ונתוני הידוד. המידע שנאסף אלמוני."
- },
"context_menu_selective_mode": {
"message": "מצב בררני"
},
@@ -278,14 +245,8 @@
"settings_dns_add_custom_server_name": {
"message": "שם שרת"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "שם שרת"
- },
"settings_dns_add_custom_server_address": {
- "message": "כתובת שרת DNS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "כתובת שרת DNS בלתי תקפה"
+ "message": "כתובת שרת"
},
"settings_dns_add_custom_server_save_and_select": {
"message": "שמור ובחר"
@@ -381,10 +342,10 @@
"message": "AdGuard VPN מופעל עבור התחומים והתת־תחומים המסומנים"
},
"settings_exclusion_domain_name": {
- "message": "שם תחום:"
+ "message": "שם תחום"
},
"settings_exclusion_subdomain_name": {
- "message": "שם תת־תחום:"
+ "message": "תת־תחום"
},
"settings_exclusion_add_from_list": {
"message": "מתוך הרשימה"
@@ -443,9 +404,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "הסר את כל ההחרגות"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "האם אתה בטוח שאתה רוצה להמשיך? רשימת ההחרגות תוסר."
- },
"settings_exclusion_title": {
"message": "החרגות"
},
@@ -675,12 +633,6 @@
"popup_connections_limit_title": {
"message": "מגבלת חיבורים הושגה"
},
- "options_signedout_page_title": {
- "message": "יצאת מן AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "אנא שים לב: אתה לא מחובר אל VPN והתעבורה שלך לא מוגנת! כדי להמשיך לגלוש בפרטיות, לחץ על האיקון של AdGuard VPN והיכנס שוב."
- },
"notification_data_limit_reached_title": {
"message": "מגבלת נתונים חודשית הושגה"
},
diff --git a/src/_locales/hr/messages.json b/src/_locales/hr/messages.json
index 04243b02e..c5bb727a9 100644
--- a/src/_locales/hr/messages.json
+++ b/src/_locales/hr/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "O"
},
+ "all_rights_reserved": {
+ "message": "Sva prava pridržana."
+ },
"options_support_title": {
"message": "Podrška"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Došlo je do pogreške. Pokušajte kasnije."
},
- "options_bug_report_send_button": {
- "message": "Pošalji"
- },
- "options_bug_report_sending_button": {
- "message": "Slanje..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Ovdje opišite problem..."
- },
- "options_bug_report_page_success": {
- "message": "Hvala vam, vaša je poruka uspješno poslana!"
- },
"options_bug_report_new_report_button": {
"message": "Novi izvještaj"
},
@@ -118,13 +109,13 @@
"message": "Račun"
},
"account_edit": {
- "message": "Upravljanje"
+ "message": "Otvoriti postavke računa"
},
"account_version": {
"message": "Verzija"
},
"account_free": {
- "message": "Besplatno"
+ "message": "Besplatna verzija"
},
"account_unlimited": {
"message": "Neograničena pretlata"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Kupi pretplatu"
},
- "account_max_devices_count": {
- "message": "Do %num% uređaja istovremeno"
- },
"account_valid_until": {
"message": "Vrijedi do %date%"
},
@@ -157,7 +145,7 @@
"message": "Kupite neograničenu inačicu!"
},
"rate_description": {
- "message": "Hej, mi smo tim iz AdGuarda i vrlo nam je važno čuti vaše mišljenje o našem proizvodu. Molimo ocijenite ga."
+ "message": "Uživate li u AdGuard VPN-u?"
},
"rate_hide": {
"message": "Sakrij"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Politika privatnosti"
},
- "settings_title": {
- "message": "Postavke"
+ "settings_general_title": {
+ "message": "Općenito"
},
"referral_get_free_traffic": {
"message": "Dobijte besplatne GB!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Ponovno pošalji vezu"
},
- "settings_free_gbs_add_device_title": {
- "message": "Dodajte novi uređaj"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Instalirajte AdGuard VPN na još jedan uređaj, prijavite se na svoj AdGuard račun i dobijte %your_gb% gigabajta"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Dodani uređaji"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Čestitamo na instaliranju AdGuard VPN-a na više uređaja. Ostanite sigurni!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Idi na Proizvode"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Blokiraj WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC je poseban program u većini preglednika koji može omogućiti web stranicama trećih strana, otkrivanje vaše stvarne IP adrese i koristiti za identificiranje"
- },
- "settings_context_menus_title": {
- "message": "Dodajte AdGuard VPN stavku u kontekstni izbornik preglednika"
- },
"settings_help_us_improve_title": {
- "message": "Pomognite nam postati bolji"
- },
- "settings_help_us_improve_description": {
- "message": "Omogućite AdGuard VPN-u da prikuplja izvješća o rušenju aplikacije, tehničke podatke i podatke o interakciji. Ovo je anonimno."
+ "message": "Šaljite anonimna izvješća o padu programa"
},
"context_menu_selective_mode": {
"message": "Selektivni način"
@@ -352,22 +319,13 @@
"message": "Uredi prilagođeni DNS poslužitelj"
},
"settings_dns_add_custom_server_info": {
- "message": "Pročitajte više o DNS poslužiteljima"
+ "message": "Poznati davatelji DNS-a"
},
"settings_dns_add_custom_server_name": {
"message": "Naziv poslužitelja"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Naziv poslužitelja"
- },
"settings_dns_add_custom_server_address": {
- "message": "Adresa DNS poslužitelja"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP poslužitelja ili TLS naziv domene"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Adresa DNS poslužitelja nije valjana"
+ "message": "Adresa poslužitelja"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS poslužitelj je već dodan"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN uključen je za provjerene domene i poddomene"
},
"settings_exclusion_domain_name": {
- "message": "Naziv domene:"
+ "message": "Naziv domene"
},
"settings_exclusion_subdomain_name": {
- "message": "Naziv poddomene:"
+ "message": "Poddomena"
},
"settings_exclusion_add_from_list": {
"message": "S popisa"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Ukloni sva izuzimanja"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Jeste li sigurni da želite nastaviti? Cijeli popis isključenja bit će uklonjen."
- },
"settings_exclusion_title": {
"message": "Iznimke"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Dosegnuto ograničenje veza"
},
- "options_signedout_page_title": {
- "message": "Odjavili ste se iz AdGuard VPN-a"
- },
- "options_signedout_page_description": {
- "message": "Imajte na umu da vaša veza nije sigurna. Da biste nastavili privatno surfati, kliknite na ikonu AdGuard VPN i ponovno se prijavite."
- },
"notification_data_limit_reached_title": {
"message": "Dosegnuli ste mjesečno ograničenje podataka."
},
diff --git a/src/_locales/hu/messages.json b/src/_locales/hu/messages.json
index 5a234a0ca..c60ad911f 100644
--- a/src/_locales/hu/messages.json
+++ b/src/_locales/hu/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Névjegy"
},
+ "all_rights_reserved": {
+ "message": "Minden jog fenntartva."
+ },
"options_support_title": {
"message": "Támogatás"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Valamilyen hiba történt, próbálja újból később."
},
- "options_bug_report_send_button": {
- "message": "Küldés"
- },
- "options_bug_report_sending_button": {
- "message": "Küldés..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Írja le itt a problémát..."
- },
- "options_bug_report_page_success": {
- "message": "Köszönjük, az üzenetét sikeresen elküldtük!"
- },
"options_bug_report_new_report_button": {
"message": "Új jelentés"
},
@@ -118,13 +109,13 @@
"message": "Fiók"
},
"account_edit": {
- "message": "Fiók kezelése"
+ "message": "Fiókbeállításokat megnyitni"
},
"account_version": {
"message": "Verzió"
},
"account_free": {
- "message": "Ingyenes fiók"
+ "message": "Ingyenes verzió"
},
"account_unlimited": {
"message": "Korlátlan előfizetés"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Előfizetés vásárlása"
},
- "account_max_devices_count": {
- "message": "Akár %num% egyidejű eszköz"
- },
"account_valid_until": {
"message": "Érvényesség vége: %date%"
},
@@ -157,7 +145,7 @@
"message": "Szerezze meg a korlátlan verziót!"
},
"rate_description": {
- "message": "Üdvözöljük! Az AdGuard csapatának fontos, hogy megismerje a termékkel kapcsolatos véleményét. Kérjük, szánjon rá néhány percet, és értékelje."
+ "message": "Élvezi az AdGuard VPN-t?"
},
"rate_hide": {
"message": "Elrejtés"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Adatvédelmi irányelvek"
},
- "settings_title": {
- "message": "Beállítások"
+ "settings_general_title": {
+ "message": "Általános"
},
"referral_get_free_traffic": {
"message": "Szerezz ingyenes GB-okat!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Link újraküldése"
},
- "settings_free_gbs_add_device_title": {
- "message": "Egy másik eszköz hozzáadása"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Telepítse az AdGuard VPN-t még egy eszközre, jelentkezzen be rajta az AdGuard-fiókjába, és kapja meg a %your_gb% GB-ot."
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Hozzáadott eszközök"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Gratulálok az AdGuard VPN több eszközre történő telepítéséhez. Maradj biztonságban!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Ugrás a termékekhez"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "WebRTC tiltása"
},
- "settings_webrtc_desc": {
- "message": "A WebRTC egy speciális program a legtöbb internetes böngészőben, amely lehetővé teszi egy harmadik fél webhelyének, hogy megismerje az Ön valódi IP-címét, és felhasználja azt az Ön azonosítására"
- },
- "settings_context_menus_title": {
- "message": "Adja hozzá az AdGuard VPN elemet a böngésző helyi menüjéhez"
- },
"settings_help_us_improve_title": {
- "message": "Segítsen nekünk a fejlesztésben"
- },
- "settings_help_us_improve_description": {
- "message": "Engedélyezés az AdGuard VPN számára, hogy hibahelentéseket, műszaki adatokat és használati statisztikákat küldjön. Ez névtelenül történik."
+ "message": "Névtelen hibajelentések küldése"
},
"context_menu_selective_mode": {
"message": "Szelektív mód"
@@ -352,22 +319,13 @@
"message": "Egyéni DNS szerver módosítása"
},
"settings_dns_add_custom_server_info": {
- "message": "További információ a DNS-kiszolgálókról"
+ "message": "Ismert DNS-szolgáltatók"
},
"settings_dns_add_custom_server_name": {
"message": "Szerver neve"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Szerver neve"
- },
"settings_dns_add_custom_server_address": {
- "message": "DNS szerver címe"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "Kiszolgáló IP vagy TLS tartománynév"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Érvénytelen DNS szerver cím"
+ "message": "Szerver cím"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS-kiszolgáló már hozzáadva"
@@ -487,10 +445,10 @@
"message": "Az AdGuard VPN be van kapcsolva a jelölt domaineken és aldomaineken"
},
"settings_exclusion_domain_name": {
- "message": "Domain név:"
+ "message": "Domain név"
},
"settings_exclusion_subdomain_name": {
- "message": "Aldomain neve:"
+ "message": "Aldomain"
},
"settings_exclusion_add_from_list": {
"message": "A listából"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Összes kivétel törlése"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Biztosan folytatja? A kivételek teljes listája törölve lesz."
- },
"settings_exclusion_title": {
"message": "Kivételek"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Kapcsolódási korlát elérve"
},
- "options_signedout_page_title": {
- "message": "Kijelentkezett az AdGuard VPN-ből"
- },
- "options_signedout_page_description": {
- "message": "Kérjük, vegye figyelembe: nem csatlakozik a VPN-hez, ezért az adatforgalma nem védett! Ha továbbra is privát módon szeretne böngészni, kattintson az AdGuard VPN ikonra, és jelentkezzen be újra."
- },
"notification_data_limit_reached_title": {
"message": "Elérte a havi adatkorlátot"
},
diff --git a/src/_locales/hy/messages.json b/src/_locales/hy/messages.json
index 2143411a5..1eae3e65e 100644
--- a/src/_locales/hy/messages.json
+++ b/src/_locales/hy/messages.json
@@ -32,8 +32,8 @@
"privacy_policy": {
"message": "Գաղտնիության քաղաքականություն"
},
- "settings_title": {
- "message": "Կարգավորումներ"
+ "settings_general_title": {
+ "message": "General"
},
"settings_webrtc_label": {
"message": "Արգելափակել WebRTC-ին"
diff --git a/src/_locales/id/messages.json b/src/_locales/id/messages.json
index 3608556ab..7329e988f 100644
--- a/src/_locales/id/messages.json
+++ b/src/_locales/id/messages.json
@@ -35,6 +35,9 @@
"about_title": {
"message": "Tentang"
},
+ "all_rights_reserved": {
+ "message": "Hak cipta dilindungi undang-undang."
+ },
"options_support_title": {
"message": "Dukungan"
},
@@ -62,21 +65,21 @@
"options_bug_report_email_invalid": {
"message": "Alamat e-mail salah"
},
- "options_bug_report_send_button": {
- "message": "Kirim"
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Jelaskan bug yang ditemukan disini..."
- },
"options_bug_report_textarea_label": {
"message": "Pesan"
},
"account_title": {
"message": "Akun"
},
+ "account_edit": {
+ "message": "Buka pengaturan akun"
+ },
"account_version": {
"message": "Versi"
},
+ "account_free": {
+ "message": "Gratis"
+ },
"account_get_subscription": {
"message": "Langganan sekarang"
},
@@ -90,7 +93,7 @@
"message": "Keluar"
},
"rate_description": {
- "message": "Hai, kami adalah tim dari AdGuard dan ini sangat penting bagi kami mengetahui opini Anda tentang produk kami. Silakan beri nilai."
+ "message": "Menikmati AdGuard VPN?"
},
"rate_hide": {
"message": "Sembunyikan"
@@ -161,8 +164,8 @@
"privacy_policy": {
"message": "Kebijakan Privasi"
},
- "settings_title": {
- "message": "Pengaturan"
+ "settings_general_title": {
+ "message": "General"
},
"settings_referral_invited_friends": {
"message": "Teman yang diundang %count%/%limit%"
@@ -179,9 +182,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Kirim ulang tautan"
},
- "settings_free_gbs_add_device_info": {
- "message": "Pasang AdGuard VPN di satu perangkat lagi, masuk ke akun AdGuard Anda di perangkat tersebut, dan dapatkan %your_gb% GB"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Buka Produk"
},
@@ -194,15 +194,6 @@
"settings_webrtc_label": {
"message": "Blokir WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC adalah program khusus di sebagian besar peramban Internet yang memungkinkan situs web pihak ketiga mendeteksi alamat IP asli dan memakainya untuk mengidentifikasi Anda"
- },
- "settings_context_menus_title": {
- "message": "Tambahkan item AdGuard VPN ke menu konteks peramban"
- },
- "settings_help_us_improve_title": {
- "message": "Bantu kami meningkatkan"
- },
"context_menu_selective_mode": {
"message": "Mode selektif"
},
@@ -227,20 +218,11 @@
"settings_dns_edit_custom_server": {
"message": "Edit server DNS kustom"
},
- "settings_dns_add_custom_server_info": {
- "message": "Baca lebih lanjut tentang server DNS"
- },
"settings_dns_add_custom_server_name": {
"message": "Nama server"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nama server"
- },
"settings_dns_add_custom_server_address": {
- "message": "Alamat server DNS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Alamat server DNS tidak valid"
+ "message": "Alamat server"
},
"settings_dns_add_custom_server_save_and_select": {
"message": "Simpan dan pilih"
@@ -327,7 +309,10 @@
"message": "AdGuard VPN aktif untuk domain dan subdomain yang diperiksa"
},
"settings_exclusion_domain_name": {
- "message": "Nama domain:"
+ "message": "Nama domain"
+ },
+ "settings_exclusion_subdomain_name": {
+ "message": "Subdomain"
},
"settings_exclusion_add_from_list": {
"message": "Dari daftar"
diff --git a/src/_locales/it/messages.json b/src/_locales/it/messages.json
index a50923d2a..3c6e47c2b 100644
--- a/src/_locales/it/messages.json
+++ b/src/_locales/it/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Informazioni su"
},
+ "all_rights_reserved": {
+ "message": "Tutti i diritti riservati."
+ },
"options_support_title": {
"message": "Supporto"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Si è verificato un errore. Riprova più tardi."
},
- "options_bug_report_send_button": {
- "message": "Invia"
- },
- "options_bug_report_sending_button": {
- "message": "Invio..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Descrivi qui il problema..."
- },
- "options_bug_report_page_success": {
- "message": "Grazie, il tuo messaggio è stato correttamente inviato!"
- },
"options_bug_report_new_report_button": {
"message": "Nuova segnalazione"
},
@@ -118,13 +109,13 @@
"message": "Profilo"
},
"account_edit": {
- "message": "Gestisci account"
+ "message": "Apri impostazioni account"
},
"account_version": {
"message": "Versione"
},
"account_free": {
- "message": "Account gratuito"
+ "message": "Versione gratuita"
},
"account_unlimited": {
"message": "Abbonamento illimitato"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Ottieni l'abbonamento"
},
- "account_max_devices_count": {
- "message": "Fino a %num% dispositivi simultanei"
- },
"account_valid_until": {
"message": "Valida fino al %date%"
},
@@ -157,7 +145,7 @@
"message": "Ottieni versione Illimitata!"
},
"rate_description": {
- "message": "Ehi, siamo la squadra AdGuard ed è molto importante per noi conoscere la vostra opinione sul nostro prodotto. Per favore, valutatelo."
+ "message": "Goditi AdGuard VPN?"
},
"rate_hide": {
"message": "Nascondi"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Politica sulla Riservatezza"
},
- "settings_title": {
- "message": "Impostazioni"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Ottieni GB gratuiti!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Reinvia il link"
},
- "settings_free_gbs_add_device_title": {
- "message": "Aggiungi un altro dispositivo"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Installa AdGuard VPN su un altro dispositivo, accedi al tuo account AdGuard su di esso e ottieni %your_gb% GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Dispositivi aggiunti"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Congratulazioni per l'installazione di AdGuard VPN su più dispositivi. Rimani al sicuro!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Vai ai Prodotti"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Blocca WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC è uno speciale programma presente nella maggior parte dei browser internet che può consentire a un sito web di terze parti di rilevare il tuo indirizzo IP reale ed utilizzarlo per identificarti"
- },
- "settings_context_menus_title": {
- "message": "Aggiungi l'elemento AdGuard VPN al menù contestuale del browser"
- },
"settings_help_us_improve_title": {
- "message": "Aiutaci a migliorare"
- },
- "settings_help_us_improve_description": {
- "message": "Consenti ad AdGuard VPN di raccogliere segnalazioni sugli arresti anomali, ed i dati di interazione. Ciò avverrà in maniera anonima."
+ "message": "Invia rapporti anonimi sugli arresti anomali"
},
"context_menu_selective_mode": {
"message": "Modalità selettiva"
@@ -352,22 +319,13 @@
"message": "Modifica server DNS personalizzato"
},
"settings_dns_add_custom_server_info": {
- "message": "Scopri di più sui server DNS"
+ "message": "Provider DNS noti"
},
"settings_dns_add_custom_server_name": {
"message": "Nome server"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nome server"
- },
"settings_dns_add_custom_server_address": {
- "message": "Indirizzo server DNS"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP del server o nome di dominio TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Indirizzo di server DNS non valido"
+ "message": "Indirizzo server"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Server DNS già aggiunto"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN funzionerà sui domini e sottodomini verificati"
},
"settings_exclusion_domain_name": {
- "message": "Nome dominio:"
+ "message": "Nome dominio"
},
"settings_exclusion_subdomain_name": {
- "message": "Nome sottodominio:"
+ "message": "Sottodominio"
},
"settings_exclusion_add_from_list": {
"message": "Dall'elenco"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Rimuovi tutte le esclusioni"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Sei sicuro di voler proseguire? L'intero elenco di esclusioni verrà rimosso."
- },
"settings_exclusion_title": {
"message": "Esclusioni"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Limite connessioni raggiunto"
},
- "options_signedout_page_title": {
- "message": "Ti sei disconnesso da AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Attenzione: non siete connessi alla VPN e il vostro traffico non è protetto! Per continuare a navigare privatamente, fare clic sull'icona AdGuard VPN e accedere nuovamente."
- },
"notification_data_limit_reached_title": {
"message": "Limite dei dati mensile raggiunto"
},
diff --git a/src/_locales/ja/messages.json b/src/_locales/ja/messages.json
index ab43f6d8f..dc88651bc 100644
--- a/src/_locales/ja/messages.json
+++ b/src/_locales/ja/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "AdGuard VPNについて"
},
+ "all_rights_reserved": {
+ "message": "All rights reserved."
+ },
"options_support_title": {
"message": "お客様サポート"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "エラーが発生しました。後ほどもう一度お試しください。"
},
- "options_bug_report_send_button": {
- "message": "送信する"
- },
- "options_bug_report_sending_button": {
- "message": "送信中…"
- },
- "options_bug_report_textarea_placeholder": {
- "message": "こちらに問題の概要をご記入ください…"
- },
- "options_bug_report_page_success": {
- "message": "送信完了!ありがとうございます。"
- },
"options_bug_report_new_report_button": {
"message": "新しい報告"
},
@@ -118,13 +109,13 @@
"message": "アカウント"
},
"account_edit": {
- "message": "アカウント管理"
+ "message": "アカウント設定を開く"
},
"account_version": {
"message": "バージョン"
},
"account_free": {
- "message": "無料プラン"
+ "message": "無料版"
},
"account_unlimited": {
"message": "無制限プラン"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "サブスクリプションを購入する"
},
- "account_max_devices_count": {
- "message": "同時接続可能端末数: %num%台"
- },
"account_valid_until": {
"message": "有効期間: %date% まで"
},
@@ -157,7 +145,7 @@
"message": "無制限版を入手しよう!"
},
"rate_description": {
- "message": "AdGuardチームです。私たちにとってユーザー様のご意見が一番大事です。ぜひ本製品をご評価いただくようお願いいたします。"
+ "message": "AdGuard VPN に満足されていますでしょうか?"
},
"rate_hide": {
"message": "非表示"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "プライバシーポリシー"
},
- "settings_title": {
- "message": "設定"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "無料GBをGET!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "リンクを再送信する"
},
- "settings_free_gbs_add_device_title": {
- "message": "もう1台の端末を追加する"
- },
- "settings_free_gbs_add_device_info": {
- "message": "AdGuard VPNをもう1台のデバイスにインストールし、そのデバイスでAdGuardアカウントにログインしますと、%your_gb%GBを取得できます。"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "デバイスが追加されました。"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "AdGuard VPNを複数のデバイスにインストールすることができ、おめでとうございます。安全にお過ごしください。"
- },
"settings_free_gbs_add_device_products_button": {
"message": "製品一覧へ移動する"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "WebRTCをブロックする"
},
- "settings_webrtc_desc": {
- "message": "WebRTCは、ほとんどのインターネットブラウザーにある特別なプログラムであり、サードパーティ(第三者)Webサイトがあなたの実際のIPアドレスを検出し、それを使用してあなたを識別することを可能にします。"
- },
- "settings_context_menus_title": {
- "message": "ブラウザの右クリックメニューにAdGuard VPNを追加する"
- },
"settings_help_us_improve_title": {
- "message": "AdGuard VPNの改善に協力する"
- },
- "settings_help_us_improve_description": {
- "message": "AdGuard VPNによる、クラッシュレポート、技術データ、およびインタラクションデータの収集を許可します。※この情報は完全に匿名です。"
+ "message": "匿名のクラッシュレポートを送信する"
},
"context_menu_selective_mode": {
"message": "指定モード"
@@ -352,22 +319,13 @@
"message": "カスタムDNSサーバーを編集"
},
"settings_dns_add_custom_server_info": {
- "message": "DNSサーバーについて、詳しくはこちら"
+ "message": "既知のDNSプロバイダー"
},
"settings_dns_add_custom_server_name": {
"message": "サーバー名"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "サーバー名"
- },
"settings_dns_add_custom_server_address": {
- "message": "DNSサーバアドレス"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "サーバーIPまたはTLSドメイン名"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "DNSサーバーアドレスが無効です"
+ "message": "サーバーアドレス"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "このDNSサーバーはすでに追加されています"
@@ -487,10 +445,10 @@
"message": "AdGuard VPNはチェック入りドメインとサブドメインに対してオンです"
},
"settings_exclusion_domain_name": {
- "message": "ドメイン名:"
+ "message": "ドメイン名"
},
"settings_exclusion_subdomain_name": {
- "message": "サブドメイン名:"
+ "message": "サブドメイン"
},
"settings_exclusion_add_from_list": {
"message": "リストから選ぶ"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "すべての除外項目を削除する"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "続行してよろしいですか?除外リストが完全に削除されます。"
- },
"settings_exclusion_title": {
"message": "VPN対象からの除外"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "同時接続可能端末数の上限に達成"
},
- "options_signedout_page_title": {
- "message": "AdGuard VPNからログアウトしました"
- },
- "options_signedout_page_description": {
- "message": "※VPNに接続されていないので、通信が保護されていません。安全にネット閲覧を続けるには、AdGuard VPNのアイコンをクリックし、再度ログインしてください。"
- },
"notification_data_limit_reached_title": {
"message": "月間無料通信量の上限に達しました。"
},
diff --git a/src/_locales/ko/messages.json b/src/_locales/ko/messages.json
index 44bfb3d87..a3573f2a3 100644
--- a/src/_locales/ko/messages.json
+++ b/src/_locales/ko/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "정보"
},
+ "all_rights_reserved": {
+ "message": "All rights reserved."
+ },
"options_support_title": {
"message": "고객 지원"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "오류가 발생했습니다. 다시 시도해 주십시오."
},
- "options_bug_report_send_button": {
- "message": "제출"
- },
- "options_bug_report_sending_button": {
- "message": "전송 중..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "문제를 설명해 주세요…"
- },
- "options_bug_report_page_success": {
- "message": "감사합니다. 메시지가 전송되었습니다!"
- },
"options_bug_report_new_report_button": {
"message": "새로운 보고서"
},
@@ -118,13 +109,13 @@
"message": "계정"
},
"account_edit": {
- "message": "계정 관리"
+ "message": "계정 설정 열기"
},
"account_version": {
"message": "버전"
},
"account_free": {
- "message": "무료 계정"
+ "message": "무료 버전"
},
"account_unlimited": {
"message": "무제한 구독"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "구독 구매"
},
- "account_max_devices_count": {
- "message": "최대 %num%개의 동시 연결"
- },
"account_valid_until": {
"message": "%date%까지 유효"
},
@@ -157,7 +145,7 @@
"message": "무제한 버전 이용하기!"
},
"rate_description": {
- "message": "안녕하세요, AdGuard 팀은 제품에 대한 사용자 의견을 중요하게 생각하고 있습니다. 만족도를 평가해주세요."
+ "message": "AdGuard VPN을 즐기세요?"
},
"rate_hide": {
"message": "숨기기"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "개인정보처리방침"
},
- "settings_title": {
- "message": "설정"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "무료 GB를 받으세요!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "링크 재전송"
},
- "settings_free_gbs_add_device_title": {
- "message": "다른 기기 추가"
- },
- "settings_free_gbs_add_device_info": {
- "message": "AdGuard VPN을 하나 이상의 디바이스에 설치하시고, AdGuard 계정으로 로그인하여 %your_gb%GB 를 얻으세요."
- },
- "settings_free_gbs_devices_added_title": {
- "message": "기기가 추가됨"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "여러 디바이스에 AdGuard VPN을 설치하신 것을 축하드립니다. 안전하게 지내세요!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "제품들로"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "WebRTC 차단"
},
- "settings_webrtc_desc": {
- "message": "WebRTC는 대부분의 브라우저에서 제3자 웹사이트가 실제 IP 주소를 감지하고 식별하는 데에 사용될 수 있는 특수한 프로그램입니다"
- },
- "settings_context_menus_title": {
- "message": "브라우저 콘텍스트 메뉴에 AdGuard VPN 항목 추가"
- },
"settings_help_us_improve_title": {
- "message": "개선할 수 있도록 도와주세요"
- },
- "settings_help_us_improve_description": {
- "message": "AdGuard VPN이 충돌 보고서, 기술 및 상호작용 데이터를 수집할 수 있도록 허용해주세요. 익명으로 처리됩니다."
+ "message": "익명의 충돌 보고서 전송"
},
"context_menu_selective_mode": {
"message": "선별 모드"
@@ -352,22 +319,13 @@
"message": "사용자 정의 DNS 서버 수정"
},
"settings_dns_add_custom_server_info": {
- "message": "DNS 서버에 대해 자세히 알아보기"
+ "message": "알려진 DNS 공급자"
},
"settings_dns_add_custom_server_name": {
"message": "서버 이름"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "서버 이름"
- },
"settings_dns_add_custom_server_address": {
- "message": "DNS 서버 주소"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "서버 IP 또는 TLS 도메인 이름"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "유효하지 않은 DNS 주소"
+ "message": "서버 주소"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS 서버가 이미 추가되었습니다"
@@ -487,10 +445,10 @@
"message": "선택한 도메인 및 서브 도메인에 대해 AdGuard VPN이 켜집니다"
},
"settings_exclusion_domain_name": {
- "message": "도메인 명:"
+ "message": "도메인명"
},
"settings_exclusion_subdomain_name": {
- "message": "서브 도메인 이름:"
+ "message": "서브 도메인"
},
"settings_exclusion_add_from_list": {
"message": "목록에서 선택"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "모든 예외 항목 제거"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "계속하시겠습니까? 예외 목록이 제거됩니다."
- },
"settings_exclusion_title": {
"message": "예외 목록"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "연결 수 제한 도달"
},
- "options_signedout_page_title": {
- "message": "AdGuard VPN에서 로그아웃하셨습니다"
- },
- "options_signedout_page_description": {
- "message": "VPN에 연결되어 있지 않고 트래픽이 보호되지 않습니다. 비공개적으로 인터넷 서핑을 계속하려면 AdGuard VPN 아이콘을 누르고 다시 로그인하세요."
- },
"notification_data_limit_reached_title": {
"message": "월별 데이터 한도에 도달했습니다"
},
diff --git a/src/_locales/lt/messages.json b/src/_locales/lt/messages.json
index 391605860..525c6d574 100644
--- a/src/_locales/lt/messages.json
+++ b/src/_locales/lt/messages.json
@@ -36,6 +36,9 @@
"about_title": {
"message": "Apie"
},
+ "all_rights_reserved": {
+ "message": "Visos teisės saugomos."
+ },
"options_support_title": {
"message": "Palaikymas"
},
@@ -45,18 +48,12 @@
"options_bug_report_include_log_label": {
"message": "Įtraukti diagnostinę ataskaitą į pranešimą"
},
- "options_bug_report_send_button": {
- "message": "Pateikti"
- },
"options_bug_report_textarea_label": {
"message": "Pranešimas"
},
"account_title": {
"message": "Paskyra"
},
- "account_edit": {
- "message": "Redaguoti"
- },
"account_version": {
"message": "Versija"
},
@@ -102,9 +99,6 @@
"privacy_policy": {
"message": "Privatumo Politika"
},
- "settings_title": {
- "message": "Nustatymai"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Eiti į svetainę"
},
@@ -132,12 +126,6 @@
"settings_dns_add_custom_server_name": {
"message": "Serverio pavadinimas"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Serverio pavadinimas"
- },
- "settings_dns_add_custom_server_address": {
- "message": "DNS serverio adresas"
- },
"settings_dns_add_custom_server_save_and_select": {
"message": "Išsaugoti ir pasirinkti"
},
@@ -205,7 +193,7 @@
"message": "Veiksmai"
},
"settings_exclusion_domain_name": {
- "message": "Domeno vardas:"
+ "message": "Domenas"
},
"settings_exclusion_modal_cancel": {
"message": "Atšaukti"
diff --git a/src/_locales/mk/messages.json b/src/_locales/mk/messages.json
index a2aa5693b..95369bbc3 100644
--- a/src/_locales/mk/messages.json
+++ b/src/_locales/mk/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "За програмата"
},
+ "all_rights_reserved": {
+ "message": "Сите права се задржани."
+ },
"options_support_title": {
"message": "Поддршка"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Настана грешка, обидете се повторно подоцна."
},
- "options_bug_report_send_button": {
- "message": "Поднеси"
- },
- "options_bug_report_sending_button": {
- "message": "Се поднесува..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Опишете го проблемот овде..."
- },
- "options_bug_report_page_success": {
- "message": "Ви благодариме, вашата порака беше успешно испратена!"
- },
"options_bug_report_new_report_button": {
"message": "Нов извештај"
},
@@ -118,13 +109,13 @@
"message": "Сметка"
},
"account_edit": {
- "message": "Управување со сметката"
+ "message": "Отворете ги поставките за сметката"
},
"account_version": {
"message": "Верзија"
},
"account_free": {
- "message": "Бесплатна сметка"
+ "message": "Бесплатна верзија"
},
"account_unlimited": {
"message": "Неограничена претплата"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Земете претплата"
},
- "account_max_devices_count": {
- "message": "До %num% симултани уреди"
- },
"account_valid_until": {
"message": "Валидност до %date%"
},
@@ -156,9 +144,6 @@
"account_unlimited_title": {
"message": "Добијте ја неограничената верзија!"
},
- "rate_description": {
- "message": "Еј, ние сме тим од AdGuard и ни е многу важно да го знаеме вашето мислење за нашиот производ. Ве молиме оценете го."
- },
"rate_hide": {
"message": "Сокриј"
},
@@ -234,9 +219,6 @@
"privacy_policy": {
"message": "Политика за приватност"
},
- "settings_title": {
- "message": "Поставки"
- },
"referral_get_free_traffic": {
"message": "Добијте бесплатни ГБ!"
},
@@ -276,18 +258,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Повторно испратете линк"
},
- "settings_free_gbs_add_device_title": {
- "message": "Додадете друг уред"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Инсталирајте AdGuard VPN на уште еден уред, најавете се на вашата сметка на AdGuard на него и добијте %your_gb% ГБ"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Уредите се додадени"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Честитки за инсталирањето на AdGuard VPN на повеќе уреди. Останете безбедни!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Проверете ги производите"
},
@@ -312,17 +282,8 @@
"settings_webrtc_label": {
"message": "Блокирај WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC е специјална програма во повеќето интернет прелистувачи што може да дозволи веб-локација од трета страна да ја открие вашата вистинска IP адреса и да ја користи за да ве идентификува"
- },
- "settings_context_menus_title": {
- "message": "Додајте ставка AdGuard VPN во контекстното мени на прелистувачот"
- },
"settings_help_us_improve_title": {
- "message": "Помогнете ни да се подобриме"
- },
- "settings_help_us_improve_description": {
- "message": "Дозволете AdGuard VPN да собира извештаи за падови, технички и податоци за интеракција. Ова е анонимно."
+ "message": "Испратете анонимни извештаи за падови"
},
"context_menu_selective_mode": {
"message": "Селективен режим"
@@ -352,22 +313,13 @@
"message": "Уредете прилагоден DNS сервер"
},
"settings_dns_add_custom_server_info": {
- "message": "Прочитајте повеќе за DNS серверите"
+ "message": "Познати DNS провајдери"
},
"settings_dns_add_custom_server_name": {
"message": "Име на серверот"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Име на серверот"
- },
"settings_dns_add_custom_server_address": {
- "message": "Адреса на DNS сервер"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP на сервер или име на TLS домен"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Неважечка адреса на DNS-сервер"
+ "message": "Адреса на серверот"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS-серверот е веќе додаден"
@@ -487,10 +439,10 @@
"message": "AdGuard VPN е вклучен за проверените домени и поддомени"
},
"settings_exclusion_domain_name": {
- "message": "Име на домен:"
+ "message": "Име на домен"
},
"settings_exclusion_subdomain_name": {
- "message": "Име на поддомен:"
+ "message": "Поддомен"
},
"settings_exclusion_add_from_list": {
"message": "Од списокот"
@@ -549,9 +501,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Отстранете ги сите исклучоци"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Дали сте сигурни дека сакате да продолжите? Целата листа со исклучоци ќе биде отстранета."
- },
"settings_exclusion_title": {
"message": "Исклучоци"
},
@@ -942,12 +891,6 @@
"popup_connections_limit_title": {
"message": "Достигнато е ограничувањето на врските"
},
- "options_signedout_page_title": {
- "message": "Се одјавивте од AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Ве молиме имајте предвид: не сте поврзани со VPN и вашиот сообраќај не е заштитен! За да продолжите да сурфате приватно, кликнете на иконата AdGuard VPN и најавете се повторно."
- },
"notification_data_limit_reached_title": {
"message": "Достигнат е месечниот лимит за податоци"
},
diff --git a/src/_locales/ms/messages.json b/src/_locales/ms/messages.json
index a13f48581..1e5b2eb76 100644
--- a/src/_locales/ms/messages.json
+++ b/src/_locales/ms/messages.json
@@ -2,6 +2,9 @@
"short_name": {
"message": "AdGuard VPN"
},
+ "all_rights_reserved": {
+ "message": "Semua hakcipta terpelihara."
+ },
"options_support_report_title": {
"message": "Laporkan pepijat"
},
@@ -32,12 +35,6 @@
"settings_dns_add_custom_server_name": {
"message": "Nama pelayan"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nama pelayan"
- },
- "settings_dns_add_custom_server_address": {
- "message": "Alamat pelayan DNS"
- },
"settings_dns_add_custom_server_save": {
"message": "Simpan"
},
@@ -47,9 +44,6 @@
"settings_theme_system": {
"message": "Lalai sistem"
},
- "settings_exclusion_domain_name": {
- "message": "Nama domain:"
- },
"settings_exclusion_modal_remove": {
"message": "Buang"
},
diff --git a/src/_locales/nb/messages.json b/src/_locales/nb/messages.json
index 80ea14479..7076e7e65 100644
--- a/src/_locales/nb/messages.json
+++ b/src/_locales/nb/messages.json
@@ -38,6 +38,9 @@
"about_title": {
"message": "Om"
},
+ "all_rights_reserved": {
+ "message": "Alle rettigheter reservert."
+ },
"options_support_title": {
"message": "Kundestøtte"
},
@@ -71,12 +74,6 @@
"options_bug_report_request_error": {
"message": "En feil oppstod. Vennligst prøv igjen senere."
},
- "options_bug_report_send_button": {
- "message": "Send"
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Beskriv problemet her …"
- },
"options_bug_report_new_report_button": {
"message": "Ny rapport"
},
@@ -89,12 +86,6 @@
"account_version": {
"message": "Versjon"
},
- "account_free": {
- "message": "Gratiskonto"
- },
- "rate_description": {
- "message": "Heisann, vi er et team fra AdGuard og det er veldig viktig for oss å vite om din mening om vårt produkt. Vennligst gi det en vurdering."
- },
"rate_hide": {
"message": "Skjul"
},
@@ -158,8 +149,8 @@
"privacy_policy": {
"message": "Personvern"
},
- "settings_title": {
- "message": "Innstillinger"
+ "settings_general_title": {
+ "message": "General"
},
"settings_free_gbs": {
"message": "Gratis GB-er"
@@ -167,9 +158,6 @@
"settings_free_gbs_confirm_email_title": {
"message": "Bekreft E-postadressen din"
},
- "settings_free_gbs_add_device_title": {
- "message": "Legg til en annen enhet"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Gå til Produkter"
},
@@ -182,15 +170,6 @@
"settings_webrtc_label": {
"message": "Blokker WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC er et spesielt program i de fleste nettlesere som kan la et tredjepartsnettsted oppdage din ekte IP-adresse og bruke det til å identifisere deg"
- },
- "settings_context_menus_title": {
- "message": "Legg til AdGuard VPN-element i nettleserens sammenhengsmeny"
- },
- "settings_help_us_improve_title": {
- "message": "Hjelp oss med å forbedre"
- },
"context_menu_selective_mode": {
"message": "Selektiv modus"
},
@@ -209,12 +188,6 @@
"settings_dns_add_custom_server_name": {
"message": "Tjenerens navn"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Tjenerens navn"
- },
- "settings_dns_add_custom_server_address": {
- "message": "DNS-tjeneradresse"
- },
"settings_dns_add_custom_server_save_and_select": {
"message": "Lagre og velg"
},
@@ -294,7 +267,7 @@
"message": "alle underdomener"
},
"settings_exclusion_domain_name": {
- "message": "Domenenavn:"
+ "message": "Domenenavn"
},
"settings_exclusion_modal_cancel": {
"message": "Avbryt"
diff --git a/src/_locales/nl/messages.json b/src/_locales/nl/messages.json
index 5e747f754..b91eecc6b 100644
--- a/src/_locales/nl/messages.json
+++ b/src/_locales/nl/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Over"
},
+ "all_rights_reserved": {
+ "message": "Alle rechten voorbehouden."
+ },
"options_support_title": {
"message": "Ondersteuning"
},
@@ -97,13 +100,13 @@
"message": "Er is een fout opgetreden. Probeer het later opnieuw."
},
"options_bug_report_send_button": {
- "message": "Verzenden"
+ "message": "Rapport verzenden"
},
"options_bug_report_sending_button": {
- "message": "Verzenden..."
+ "message": "Rapport verzenden..."
},
"options_bug_report_textarea_placeholder": {
- "message": "Beschrijf hier je probleem..."
+ "message": "Details verstrekken"
},
"options_bug_report_page_success": {
"message": "Bedankt, je bericht is succesvol verzonden!"
@@ -118,13 +121,13 @@
"message": "Account"
},
"account_edit": {
- "message": "Account beheren"
+ "message": "Accountinstellingen openen"
},
"account_version": {
"message": "Versie"
},
"account_free": {
- "message": "Gratis account"
+ "message": "Gratis versie"
},
"account_unlimited": {
"message": "Onbeperkt abonnement"
@@ -142,7 +145,7 @@
"message": "Abonnement afsluiten"
},
"account_max_devices_count": {
- "message": "Tot %num% gelijktijdige apparaten"
+ "message": "Tot %num% gelijktijdige apparaten"
},
"account_valid_until": {
"message": "Geldig tot %date%"
@@ -157,7 +160,7 @@
"message": "De Unlimited-versie verkrijgen!"
},
"rate_description": {
- "message": "Hallo, we zijn een team van AdGuard en het is erg belangrijk voor ons om jouw mening over ons product te weten. Beoordeel het alsjeblieft."
+ "message": "Geniet je van AdGuard VPN?"
},
"rate_hide": {
"message": "Verbergen"
@@ -234,8 +237,8 @@
"privacy_policy": {
"message": "Privacybeleid"
},
- "settings_title": {
- "message": "Instellingen"
+ "settings_general_title": {
+ "message": "Algemeen"
},
"referral_get_free_traffic": {
"message": "Gratis GB's verkrijgen!"
@@ -277,16 +280,16 @@
"message": "Link opnieuw verzenden"
},
"settings_free_gbs_add_device_title": {
- "message": "Nog een apparaat toevoegen"
+ "message": "Nog een platform toevoegen"
},
"settings_free_gbs_add_device_info": {
- "message": "Installeer AdGuard VPN op nog een apparaat, meld je aan bij je AdGuard-account en ontvang %your_gb% GB"
+ "message": "Installeer AdGuard VPN voor iOS, Windows, of Android, meld je aan bij je AdGuard-account daar, en ontvang %your_gb% GB."
},
"settings_free_gbs_devices_added_title": {
- "message": "Apparaten toegevoegd"
+ "message": "Nog een platform toegevoegd"
},
"settings_free_gbs_devices_added_info": {
- "message": "Gefeliciteerd met het installeren van AdGuard VPN op meerdere apparaten. Let op je veiligheid!"
+ "message": "Gefeliciteerd met het installeren van AdGuard VPN op meerdere platforms. Blijf veilig!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Ga naar producten"
@@ -313,16 +316,19 @@
"message": "Blokkeer WebRTC"
},
"settings_webrtc_desc": {
- "message": "WebRTC is een speciaal programma in de meeste internetbrowsers waarmee websites van derden uw echte IP-adres kunnen detecteren en gebruiken om u te identificeren"
+ "message": "WebRTC blokkeren, een bekende kwetsbaarheid die je echte IP-adres kan lekken, zelfs als je een proxy of VPN gebruikt"
},
"settings_context_menus_title": {
- "message": "AdGuard VPN-item toevoegen aan het contextmenu van de webbrowser"
+ "message": "AdGuard VPN weergeven in het contextmenu van de browser"
+ },
+ "settings_context_menus_description": {
+ "message": "VPN-uitsluitingen beheren voor een specifieke website"
},
"settings_help_us_improve_title": {
- "message": "Help ons te verbeteren"
+ "message": "Anonieme crashrapporten verzenden"
},
"settings_help_us_improve_description": {
- "message": "AdGuard VPN toestaan om crash rapporten, technische en interactieve data te verzamelen. Dit is anoniem."
+ "message": "AdGuard VPN-ontwikkelaars op de hoogte brengen als er iets misgaat"
},
"context_menu_selective_mode": {
"message": "Selectieve modus"
@@ -339,6 +345,12 @@
"settings_dns_label": {
"message": "DNS-server"
},
+ "settings_dns_description": {
+ "message": "DNS-verzoeken omzetten, advertenties en volgers blokkeren, en DNS-verkeer versleutelen wanneer je bent verbonden met VPN"
+ },
+ "settings_dns_description_current": {
+ "message": "Huidige: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Populaire DNS-servers"
},
@@ -352,22 +364,22 @@
"message": "Aangepaste DNS-server bewerken"
},
"settings_dns_add_custom_server_info": {
- "message": "Lees meer over DNS-servers"
+ "message": "Bekende DNS-providers"
},
"settings_dns_add_custom_server_name": {
"message": "Servernaam"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Servernaam"
+ "message": "Mijn DNS-server"
},
"settings_dns_add_custom_server_address": {
- "message": "DNS-server adres"
+ "message": "Serveradres"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "Server-IP- of TLS-domeinnaam"
+ "message": "IP-adres of tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Ongeldig DNS-serveradres"
+ "message": "Ongeldig serveradres"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS-server al toegevoegd"
@@ -384,6 +396,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Aangepaste DNS-server verwijderd"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Aangepaste DNS-server bewerkt"
+ },
"settings_dns_selector_default_title": {
"message": "Standaard"
},
@@ -487,10 +502,10 @@
"message": "AdGuard VPN is ingeschakeld voor de geselecteerde domeinen en subdomeinen"
},
"settings_exclusion_domain_name": {
- "message": "Domeinnaam:"
+ "message": "Domeinnaam"
},
"settings_exclusion_subdomain_name": {
- "message": "Subdomeinnaam:"
+ "message": "Subdomein"
},
"settings_exclusion_add_from_list": {
"message": "Van de lijst"
@@ -550,7 +565,7 @@
"message": "Alle uitsluitingen verwijderen"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Weet je zeker dat je door wilt gaan? De volledige lijst met uitsluitingen wordt verwijderd."
+ "message": "Wilt je de volledige lijst met uitsluitingen verwijderen?"
},
"settings_exclusion_title": {
"message": "Uitsluitingen"
@@ -583,7 +598,7 @@
"message": "Neem contact met ons op indien dit bericht steeds herhaald wordt"
},
"global_error_learn_more": {
- "message": "Meer weten"
+ "message": "Meer informatie"
},
"global_error_try_again": {
"message": "Probeer opnieuw"
@@ -937,16 +952,19 @@
"message": "Abonneren"
},
"popup_connections_limit_description_cta_button_premium": {
- "message": "Meer info"
+ "message": "Meer informatie"
},
"popup_connections_limit_title": {
"message": "Maximaal aantal verbindingen bereikt"
},
"options_signedout_page_title": {
- "message": "Je bent afgemeld bij AdGuard VPN"
+ "message": "Je hebt je afgemeld bij AdGuard VPN"
+ },
+ "options_signedout_page_description_not_secure": {
+ "message": "Je verbinding is niet beveiligd!"
},
"options_signedout_page_description": {
- "message": "Let op: je bent niet verbonden met VPN en je verkeer is niet beveiligd! Om privé verder te surfen, klik je op het AdGuard VPN-pictogram en meld je opnieuw aan."
+ "message": "Om privé te blijven surfen, klik je op het AdGuard VPN-pictogram en meld je je opnieuw aan."
},
"notification_data_limit_reached_title": {
"message": "Maandelijkse datalimiet bereikt"
diff --git a/src/_locales/pl/messages.json b/src/_locales/pl/messages.json
index 447b9498f..d5dcb7fdc 100644
--- a/src/_locales/pl/messages.json
+++ b/src/_locales/pl/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "O programie"
},
+ "all_rights_reserved": {
+ "message": "Wszelkie prawa zastrzeżone."
+ },
"options_support_title": {
"message": "Wsparcie"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Wystąpił błąd. Spróbuj ponownie później."
},
- "options_bug_report_send_button": {
- "message": "Prześlij"
- },
- "options_bug_report_sending_button": {
- "message": "Wysyłanie..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Tutaj opisz problem..."
- },
- "options_bug_report_page_success": {
- "message": "Dziękujemy, Twoja wiadomość została wysłana pomyślnie!"
- },
"options_bug_report_new_report_button": {
"message": "Nowy raport"
},
@@ -118,13 +109,13 @@
"message": "Konto"
},
"account_edit": {
- "message": "Zarządzaj"
+ "message": "Otworzyć ustawienia konta"
},
"account_version": {
"message": "Wersja"
},
"account_free": {
- "message": "Darmowe konto"
+ "message": "Darmowa wersja"
},
"account_unlimited": {
"message": "Nieograniczona subskrypcja"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Uzyskaj subskrypcję"
},
- "account_max_devices_count": {
- "message": "Do %num% urządzeń jednocześnie"
- },
"account_valid_until": {
"message": "Ważne do %date%"
},
@@ -157,7 +145,7 @@
"message": "Zdobądź wersję Unlimited!"
},
"rate_description": {
- "message": "Hej, jako zespół AdGuard zależy nam na Twojej opinii o naszym produkcie. Prosimy, podziel się swoim zdaniem i oceń go."
+ "message": "Podoba Ci się AdGuard VPN?"
},
"rate_hide": {
"message": "Ukryj"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Polityka prywatności"
},
- "settings_title": {
- "message": "Ustawienia"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Zdobądź darmowe GB!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Wyślij link ponownie"
},
- "settings_free_gbs_add_device_title": {
- "message": "Dodaj kolejne urządzenie"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Zainstaluj AdGuard VPN na jeszcze jednym urządzeniu, zaloguj się na swoje konto AdGuard i zdobądź %your_gb% GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Dodane urządzenia"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Gratulujemy instalacji AdGuard VPN na wielu urządzeniach. Bądź bezpieczny!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Idź do produktów"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Blokuj WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC to specjalny program, występujący w większości przeglądarek, który umożliwia stronom trzecim odkrycie Twojego prawdziwego adresu IP i użycie go do Twojej identyfikacji"
- },
- "settings_context_menus_title": {
- "message": "Dodaj AdGuard domenu kontekstowego przeglądarki"
- },
"settings_help_us_improve_title": {
- "message": "Pomóż nam ulepszyć"
- },
- "settings_help_us_improve_description": {
- "message": "Pozwól AdGuard VPN zbierać raporty o awariach, dane techniczne i dane dotyczące interakcji. To jest anonimowe."
+ "message": "Zgłaszaj anonimowo raporty o awariach"
},
"context_menu_selective_mode": {
"message": "Tryb selektywny"
@@ -352,22 +319,13 @@
"message": "Edytuj niestandardowy serwer DNS"
},
"settings_dns_add_custom_server_info": {
- "message": "Przeczytaj więcej o serwerach DNS"
+ "message": "Znani dostawcy DNS"
},
"settings_dns_add_custom_server_name": {
"message": "Nazwa Serwera"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nazwa Serwera"
- },
"settings_dns_add_custom_server_address": {
- "message": "Adres serwera DNS"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP serwera lub nazwa domeny TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Nieprawidłowy adres serwera DNS"
+ "message": "Adres serwera"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Serwer DNS już dodany"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN jest włączony dla zaznaczonych domen i subdomen"
},
"settings_exclusion_domain_name": {
- "message": "Nazwa domeny:"
+ "message": "Nazwa domeny"
},
"settings_exclusion_subdomain_name": {
- "message": "Nazwa subdomeny:"
+ "message": "Subdomena"
},
"settings_exclusion_add_from_list": {
"message": "Z listy"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Usuń wszystkie wykluczenia"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Jesteś pewien, że chcesz kontynuować? Cała lista wykluczeń zostanie usunięta."
- },
"settings_exclusion_title": {
"message": "Wyjątki"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Osiągnięto limit połączeń"
},
- "options_signedout_page_title": {
- "message": "Wylogowano z AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Uwaga: nie jesteś podłączony do VPN i Twój ruch nie jest chroniony! Aby kontynuować prywatne surfowanie, kliknij ikonę AdGuard VPN i zaloguj się ponownie."
- },
"notification_data_limit_reached_title": {
"message": "Osiągnięty miesięczny limit danych"
},
diff --git a/src/_locales/pt_BR/messages.json b/src/_locales/pt_BR/messages.json
index 696969bb9..6800d5081 100644
--- a/src/_locales/pt_BR/messages.json
+++ b/src/_locales/pt_BR/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Sobre"
},
+ "all_rights_reserved": {
+ "message": "Todos os direitos reservados"
+ },
"options_support_title": {
"message": "Suporte"
},
@@ -97,13 +100,10 @@
"message": "Um erro ocorreu. Por favor, tente novamente mais tarde."
},
"options_bug_report_send_button": {
- "message": "Enviar"
+ "message": "Enviar relatório"
},
"options_bug_report_sending_button": {
- "message": "Enviando..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Descreva o problema aqui..."
+ "message": "Enviando relatório..."
},
"options_bug_report_page_success": {
"message": "Obrigado, sua mensagem foi enviada com sucesso!"
@@ -118,13 +118,13 @@
"message": "Conta"
},
"account_edit": {
- "message": "Gerenciar conta"
+ "message": "Abrir configurações da conta"
},
"account_version": {
"message": "Versão"
},
"account_free": {
- "message": "Conta gratuita"
+ "message": "Versão gratuita"
},
"account_unlimited": {
"message": "Assinatura ilimitada"
@@ -142,7 +142,7 @@
"message": "Obter assinatura"
},
"account_max_devices_count": {
- "message": "Até %num% dispositivos simultâneos"
+ "message": "Até %num% dispositivos simultâneos"
},
"account_valid_until": {
"message": "Válido até %date%"
@@ -157,7 +157,7 @@
"message": "Obtenha a versão ilimitada!"
},
"rate_description": {
- "message": "Ei, somos a equipe da AdGuard e é muito importante para nós saber sua opinião sobre nosso produto. Por favor, avalie."
+ "message": "Está gostando do AdGuard VPN?"
},
"rate_hide": {
"message": "Ocultar"
@@ -234,8 +234,8 @@
"privacy_policy": {
"message": "Política de privacidade"
},
- "settings_title": {
- "message": "Configurações"
+ "settings_general_title": {
+ "message": "Geral"
},
"referral_get_free_traffic": {
"message": "Obtenha GBs grátis!"
@@ -277,16 +277,16 @@
"message": "Reenviar link"
},
"settings_free_gbs_add_device_title": {
- "message": "Adicione outro dispositivo"
+ "message": "Adicionar outra plataforma"
},
"settings_free_gbs_add_device_info": {
- "message": "Instale o AdGuard VPN em mais um dispositivo, entre na sua conta AdGuard, e obtenha %your_gb% GB"
+ "message": "Instale o AdGuard VPN para iOS, Mac, Windows ou Android, entre na sua conta do AdGuard e ganhe %your_gb% GB."
},
"settings_free_gbs_devices_added_title": {
- "message": "Dispositivos adicionados"
+ "message": "Outra plataforma adicionada"
},
"settings_free_gbs_devices_added_info": {
- "message": "Parabéns por instalar o AdGuard VPN em vários dispositivos. Fique seguro!"
+ "message": "Parabéns por instalar o AdGuard VPN em várias plataformas. Fique seguro!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Ir para produtos"
@@ -312,17 +312,14 @@
"settings_webrtc_label": {
"message": "Bloquear WebRTC"
},
- "settings_webrtc_desc": {
- "message": "O WebRTC é um programa especial na maioria dos navegadores da Internet que permite que um site de terceiros detecte seu endereço IP real e use-o para identificá-lo"
- },
- "settings_context_menus_title": {
- "message": "Adicione o item VPN do AdGuard ao menu de contexto do navegador"
+ "settings_context_menus_description": {
+ "message": "Gerenciar exclusões de VPN para um site específico"
},
"settings_help_us_improve_title": {
- "message": "Ajude-nos a melhorar"
+ "message": "Envie relatórios de erro anônimos"
},
"settings_help_us_improve_description": {
- "message": "Permita que o AdGuard VPN colete relatórios de falhas, dados técnicos e de interação. Isso é anônimo."
+ "message": "Notifique os desenvolvedores do AdGuard VPN se algo estiver errado"
},
"context_menu_selective_mode": {
"message": "Modo seletivo"
@@ -352,22 +349,22 @@
"message": "Editar servidor DNS personalizado"
},
"settings_dns_add_custom_server_info": {
- "message": "Leia mais sobre servidores DNS"
+ "message": "Provedores de DNS conhecidos"
},
"settings_dns_add_custom_server_name": {
"message": "Nome do servidor"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Nome do servidor"
+ "message": "Meu Servidor DNS"
},
"settings_dns_add_custom_server_address": {
- "message": "Endereço de servidor DNS"
+ "message": "Endereço de servidor"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "IP do servidor ou nome de domínio TLS"
+ "message": "Endereço de IP ou tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Endereço de servidor DNS inválido"
+ "message": "Endereço de servidor inválido"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Servidor DNS já adicionado"
@@ -384,6 +381,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Servidor DNS personalizado excluído"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Servidor DNS personalizado editado"
+ },
"settings_dns_selector_default_title": {
"message": "Padrão"
},
@@ -487,10 +487,10 @@
"message": "O AdGuard VPN está ativado para os domínios e subdomínios marcados"
},
"settings_exclusion_domain_name": {
- "message": "Nome de domínio:"
+ "message": "Nome de domínio"
},
"settings_exclusion_subdomain_name": {
- "message": "Nome do subdomínio:"
+ "message": "Subdomínio"
},
"settings_exclusion_add_from_list": {
"message": "Da lista"
@@ -550,7 +550,7 @@
"message": "Remover todas as exclusões"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Você tem certeza que quer continuar? A lista de exclusões será removida."
+ "message": "Você deseja remover a lista inteira de exclusões?"
},
"settings_exclusion_title": {
"message": "Exclusões"
@@ -945,8 +945,11 @@
"options_signedout_page_title": {
"message": "Você saiu do AdGuard VPN"
},
+ "options_signedout_page_description_not_secure": {
+ "message": "A sua conexão não é segura!"
+ },
"options_signedout_page_description": {
- "message": "Por favor note: você não está conectado à VPN e seu tráfego não está protegido! Para continuar navegando com privacidade, clique no ícone AdGuard VPN e entre novamente."
+ "message": "Para continuar a navegar com privacidade, clique no ícone do AdGuard VPN e entre novamente."
},
"notification_data_limit_reached_title": {
"message": "Limite mensal de dados atingido"
diff --git a/src/_locales/pt_PT/messages.json b/src/_locales/pt_PT/messages.json
index a49f2a4c3..f66f4c0c6 100644
--- a/src/_locales/pt_PT/messages.json
+++ b/src/_locales/pt_PT/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Acerca de"
},
+ "all_rights_reserved": {
+ "message": "Todos os direitos reservados."
+ },
"options_support_title": {
"message": "Assistência técnica"
},
@@ -97,16 +100,13 @@
"message": "Um erro ocorreu. Por favor, tente novamente mais tarde."
},
"options_bug_report_send_button": {
- "message": "Enviar"
+ "message": "Envie relatório"
},
"options_bug_report_sending_button": {
- "message": "A enviar..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Descreva o problema aqui..."
+ "message": "A enviar relatório..."
},
"options_bug_report_page_success": {
- "message": "Obrigado, tua mensagem foi enviada com sucesso!"
+ "message": "Obrigado, a sua mensagem foi enviada com sucesso!"
},
"options_bug_report_new_report_button": {
"message": "Novo relatório"
@@ -118,13 +118,13 @@
"message": "Conta"
},
"account_edit": {
- "message": "Gerir conta"
+ "message": "Abrir configurações da conta"
},
"account_version": {
"message": "Versão"
},
"account_free": {
- "message": "Conta gratuita"
+ "message": "Versão gratuita"
},
"account_unlimited": {
"message": "Subscrição ilimitada"
@@ -141,9 +141,6 @@
"account_get_subscription": {
"message": "Obter subscrição"
},
- "account_max_devices_count": {
- "message": "Até %num% dispositivos simultâneos"
- },
"account_valid_until": {
"message": "Válido até %date%"
},
@@ -157,7 +154,7 @@
"message": "Obtenha a versão ilimitada!"
},
"rate_description": {
- "message": "Ei, somos a equipa do AdGuard e é muito importante para nós saber sua opinião sobre nosso produto. Por favor, avalie."
+ "message": "Está a gostar do AdGuard VPN?"
},
"rate_hide": {
"message": "Esconder"
@@ -234,8 +231,8 @@
"privacy_policy": {
"message": "Política de privacidade"
},
- "settings_title": {
- "message": "Definições"
+ "settings_general_title": {
+ "message": "Geral"
},
"referral_get_free_traffic": {
"message": "Obtenha GBs grátis!"
@@ -276,17 +273,8 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Reenviar link"
},
- "settings_free_gbs_add_device_title": {
- "message": "Adicione noutro dispositivo"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Instale o AdGuard VPN em mais um dispositivo, inicie sessão na sua conta AdGuard, e obtenha %your_gb% GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Dispositivos adicionados"
- },
"settings_free_gbs_devices_added_info": {
- "message": "Parabéns por instalar o AdGuard VPN em vários dispositivos. Fique seguro!"
+ "message": "Parabéns por instalar o AdGuard VPN em várias plataformas. Continue seguro!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Ir para Produtos"
@@ -312,17 +300,17 @@
"settings_webrtc_label": {
"message": "Bloquear WebRTC"
},
- "settings_webrtc_desc": {
- "message": "O WebRTC é um programa especial na maioria dos navegadores da Internet que permite que um sítio de terceiros detecte seu endereço IP real e use-o para identificá-lo"
- },
"settings_context_menus_title": {
- "message": "Adicionar o item AdGuard VPN aomenu de contexto do navegador"
+ "message": "Exibe AdGuard VPN no menu de contexto do navegador"
+ },
+ "settings_context_menus_description": {
+ "message": "Gerir exclusão de VPN para um sítio específico"
},
"settings_help_us_improve_title": {
- "message": "Ajude-nos a melhorar"
+ "message": "Envie relatórios de falhas anônimos"
},
"settings_help_us_improve_description": {
- "message": "Permita que o AdGuard VPN colete relatórios de falhas, dados técnicos e de interação. Isso é anónimo."
+ "message": "Notifique os programadores do AdGuard VPN se algo correr mal"
},
"context_menu_selective_mode": {
"message": "Modo seletivo"
@@ -339,6 +327,12 @@
"settings_dns_label": {
"message": "Servidor DNS"
},
+ "settings_dns_description": {
+ "message": "Resolve solicitações DNS, bloquear anúncios e monitorizadores, e encriptar o tráfego DNS quando está ligado à VPN"
+ },
+ "settings_dns_description_current": {
+ "message": "Atual: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Servidores DNS populares"
},
@@ -352,22 +346,19 @@
"message": "Editar servidor de DNS personalizado"
},
"settings_dns_add_custom_server_info": {
- "message": "Leia mais sobre servidores DNS"
+ "message": "Provedores de DNS conhecidos"
},
"settings_dns_add_custom_server_name": {
"message": "Nome do servidor"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nome do servidor"
- },
"settings_dns_add_custom_server_address": {
- "message": "Endereço de servidor DNS"
+ "message": "Endereço de servidor"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "IP do servidor ou nome de domínio TLS"
+ "message": "Endereço de IP ou tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Endereço de servidor DNS inválido"
+ "message": "Endereço de servidor inválido"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Servidor DNS já adicionado"
@@ -384,6 +375,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Servidor DNS personalizado excluído"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Servidor DNS personalizado editado"
+ },
"settings_dns_selector_default_title": {
"message": "Predefinido"
},
@@ -487,10 +481,10 @@
"message": "O AdGuard VPN está ativado para os domínios e subdomínios marcados"
},
"settings_exclusion_domain_name": {
- "message": "Nome do domínio:"
+ "message": "Nome do domínio"
},
"settings_exclusion_subdomain_name": {
- "message": "Nome do subdomínio:"
+ "message": "Subdomínio"
},
"settings_exclusion_add_from_list": {
"message": "Da lista"
@@ -550,7 +544,7 @@
"message": "Remover todas as exclusões"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Você tem certeza que quer continuar? A lista de exclusões será removida."
+ "message": "Deseja remover toda a lista de exclusões?"
},
"settings_exclusion_title": {
"message": "Exclusões"
@@ -943,10 +937,13 @@
"message": "Limite de conexões atingido"
},
"options_signedout_page_title": {
- "message": "Você saiu do AdGuard VPN"
+ "message": "Encerrou a sessão do AdGuard VPN"
+ },
+ "options_signedout_page_description_not_secure": {
+ "message": "A ligação não é segura!"
},
"options_signedout_page_description": {
- "message": "Por favor note: você não está conectado à VPN e o seu tráfego não está protegido! Para continuar a navegar com privacidade, clique no ícone AdGuard VPN e inicie sessão novamente."
+ "message": "Para continuar a navegar em privacidade, clique no ícone do AdGuard VPN e inicie sessão novamente."
},
"notification_data_limit_reached_title": {
"message": "Limite mensal de dados atingido"
diff --git a/src/_locales/ro/messages.json b/src/_locales/ro/messages.json
index 30e4a6ee6..2f71f9e54 100644
--- a/src/_locales/ro/messages.json
+++ b/src/_locales/ro/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Despre"
},
+ "all_rights_reserved": {
+ "message": "Toate drepturile rezervate."
+ },
"options_support_title": {
"message": "Suport"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "A apărut o eroare. Vă rugăm să încercați din nou mai târziu."
},
- "options_bug_report_send_button": {
- "message": "Trimite"
- },
- "options_bug_report_sending_button": {
- "message": "Se trimite..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Descrieți problema aici..."
- },
- "options_bug_report_page_success": {
- "message": "Vă mulțumim, mesajul dvs. a fost trimis cu succes!"
- },
"options_bug_report_new_report_button": {
"message": "Raport nou"
},
@@ -118,13 +109,13 @@
"message": "Cont"
},
"account_edit": {
- "message": "Gestionați contul"
+ "message": "A deschide setările contului"
},
"account_version": {
"message": "Versiune"
},
"account_free": {
- "message": "Cont gratuit"
+ "message": "Versiune gratuită"
},
"account_unlimited": {
"message": "Abonament nelimitat"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Obțineți abonamentul"
},
- "account_max_devices_count": {
- "message": "Până la %num% dispozitive simultan"
- },
"account_valid_until": {
"message": "Valabil până la %date%"
},
@@ -157,7 +145,7 @@
"message": "Obțineți versiunea nelimitată!"
},
"rate_description": {
- "message": "Hei, suntem o echipă de la AdGuard și pentru noi este esențial să vă știm părerea despre produsul nostru. Vă rugăm să-l evaluați."
+ "message": "Vă bucurați de AdGuard VPN?"
},
"rate_hide": {
"message": "Ascunde"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Politică Intimitate"
},
- "settings_title": {
- "message": "Setări"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Obțineți GB gratuit!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Trimiteți din nou linkul"
},
- "settings_free_gbs_add_device_title": {
- "message": "Adăugați alt dispozitiv"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Instalați AdGuard VPN pe încă un dispozitiv, apoi conectați-vă de pe acesta la contul AdGuard și primiți %your_gb% GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Dispozitive adăugate"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Felicitări pentru instalarea AdGuard VPN pe mai multe dispozitive. Rămâneți în siguranță!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Mergi la Produse"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Blocați WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC este un program special în cele mai multe browsere internet care poate permite unui site terț să vă detecteze adresa IP reală pentru a vă identifica"
- },
- "settings_context_menus_title": {
- "message": "Adăugați AdGuard VPN la meniul contextual al browserului"
- },
"settings_help_us_improve_title": {
- "message": "Ajutați-ne să ne îmbunătățim"
- },
- "settings_help_us_improve_description": {
- "message": "Permiteți AdGuard VPN să adune rapoarte de avarie, date tehnice și de interacțiune. Acest lucru este anonim."
+ "message": "Trimiteți rapoarte de blocare anonime"
},
"context_menu_selective_mode": {
"message": "Modul selectiv"
@@ -352,22 +319,13 @@
"message": "Editarea serverului DNS personalizat"
},
"settings_dns_add_custom_server_info": {
- "message": "Aflați mai multe despre serverele DNS"
+ "message": "Furnizori DNS cunoscuți"
},
"settings_dns_add_custom_server_name": {
"message": "Nume de server"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Nume de server"
- },
"settings_dns_add_custom_server_address": {
- "message": "Adresă server DNS"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP-ul serverului sau numele de domeniu TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Adresă DNS invalidă"
+ "message": "Adresa serverului"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Server DNS deja adăugat"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN este activat pentru domeniile și subdomeniile verificate"
},
"settings_exclusion_domain_name": {
- "message": "Nume domeniu:"
+ "message": "Nume domeniu"
},
"settings_exclusion_subdomain_name": {
- "message": "Numele subdomeniului:"
+ "message": "Subdomeniu"
},
"settings_exclusion_add_from_list": {
"message": "Din listă"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Eliminați toate excluderile"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Sunteți sigur că doriți să continuați? Întreaga listă de excluderi va fi eliminată."
- },
"settings_exclusion_title": {
"message": "Excluderi"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Limita de conexiuni a fost atinsă"
},
- "options_signedout_page_title": {
- "message": "V-ați deconectat de la AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Vă rugăm să rețineți: nu sunteți conectat la VPN și traficul dvs. nu este protejat! Pentru a continua să navigați în mod privat, faceți clic pe pictograma AdGuard VPN și conectați-vă din nou."
- },
"notification_data_limit_reached_title": {
"message": "Limita lunară de date a fost atinsă"
},
diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json
index 865de19c7..8d7d8908f 100644
--- a/src/_locales/ru/messages.json
+++ b/src/_locales/ru/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "О программе"
},
+ "all_rights_reserved": {
+ "message": "Все права защищены."
+ },
"options_support_title": {
"message": "Поддержка"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Произошла ошибка. Попробуйте позже."
},
- "options_bug_report_send_button": {
- "message": "Отправить"
- },
- "options_bug_report_sending_button": {
- "message": "Отправка..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Опишите найденную ошибку..."
- },
- "options_bug_report_page_success": {
- "message": "Спасибо, ваше сообщение отправлено!"
- },
"options_bug_report_new_report_button": {
"message": "Новый отчет"
},
@@ -118,13 +109,13 @@
"message": "Аккаунт"
},
"account_edit": {
- "message": "Перейти в аккаунт"
+ "message": "Открыть настройки аккаунта"
},
"account_version": {
"message": "Версия"
},
"account_free": {
- "message": "Бесплатный аккаунт"
+ "message": "Бесплатная версия"
},
"account_unlimited": {
"message": "Безлимитная подписка"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Купить подписку"
},
- "account_max_devices_count": {
- "message": "До %num% устройств одновременно"
- },
"account_valid_until": {
"message": "Действует до %date%"
},
@@ -157,7 +145,7 @@
"message": "Получите безлимитную версию!"
},
"rate_description": {
- "message": "Привет, мы команда AdGuard, и для нас очень важно знать ваше мнение о нашем продукте. Пожалуйста, оцените его.\n"
+ "message": "Вы довольны AdGuard VPN?"
},
"rate_hide": {
"message": "Скрыть"
@@ -234,9 +222,6 @@
"privacy_policy": {
"message": "Политика конфиденциальности"
},
- "settings_title": {
- "message": "Настройки"
- },
"referral_get_free_traffic": {
"message": "Получите бесплатные ГБ!"
},
@@ -276,18 +261,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Отправить ссылку ещё раз"
},
- "settings_free_gbs_add_device_title": {
- "message": "Добавьте ещё устройство"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Установите AdGuard VPN ещё на одном устройстве, войдите на нём в свою учётную запись AdGuard и получите %your_gb% ГБ"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Устройства добавлены"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Спасибо, что установили AdGuard VPN на нескольких устройствах. Оставайтесь в безопасности!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Перейти в «Продукты»"
},
@@ -312,17 +285,8 @@
"settings_webrtc_label": {
"message": "Блокировать WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC — это специальная технология, используемая большинством браузеров, которая позволяет стороннему веб-сайту обнаружить ваш реальный IP-адрес и использовать его для вашей идентификации."
- },
- "settings_context_menus_title": {
- "message": "Добавить AdGuard VPN в контекстное меню браузера\n"
- },
"settings_help_us_improve_title": {
- "message": "Помогите нам стать лучше"
- },
- "settings_help_us_improve_description": {
- "message": "Разрешить AdGuard VPN собирать отчёты о сбоях, технические данные и данные о взаимодействии. Это анонимно."
+ "message": "Отправлять анонимные отчёты о сбоях"
},
"context_menu_selective_mode": {
"message": "Выборочный режим"
@@ -352,22 +316,13 @@
"message": "Изменить пользовательский DNS-сервер"
},
"settings_dns_add_custom_server_info": {
- "message": "Подробнее о DNS-серверах"
+ "message": "Известные DNS-провайдеры"
},
"settings_dns_add_custom_server_name": {
"message": "Имя сервера"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Имя сервера"
- },
"settings_dns_add_custom_server_address": {
- "message": "Адрес сервера DNS"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP-адрес сервера или доменное имя TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Некорректный адрес DNS-сервера"
+ "message": "Адрес сервера"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Такой DNS-сервер уже есть"
@@ -487,10 +442,10 @@
"message": "AdGuard VPN подключен для доменов и поддоменов, отмеченных галочкой"
},
"settings_exclusion_domain_name": {
- "message": "Доменное имя:"
+ "message": "Домен"
},
"settings_exclusion_subdomain_name": {
- "message": "Имя поддомена:"
+ "message": "Поддомен"
},
"settings_exclusion_add_from_list": {
"message": "Из списка"
@@ -549,9 +504,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Удалить все исключения"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Вы уверены, что хотите продолжить? Список исключений будет удалён."
- },
"settings_exclusion_title": {
"message": "Исключения"
},
@@ -942,12 +894,6 @@
"popup_connections_limit_title": {
"message": "Достигнут лимит подключений"
},
- "options_signedout_page_title": {
- "message": "Вы вышли из AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Вы не подключены к AdGuard VPN. Чтобы оставаться в безопасности, нажмите на иконку AdGuard VPN и войдите в аккаунт заново."
- },
"notification_data_limit_reached_title": {
"message": "Лимит трафика в этом месяце исчерпан"
},
diff --git a/src/_locales/sk/messages.json b/src/_locales/sk/messages.json
index b1dc12e89..00519cbb5 100644
--- a/src/_locales/sk/messages.json
+++ b/src/_locales/sk/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "O aplikácii"
},
+ "all_rights_reserved": {
+ "message": "Všetky práva vyhradené."
+ },
"options_support_title": {
"message": "Podpora"
},
@@ -97,13 +100,13 @@
"message": "Vyskytla sa chyba. Skúste zopakovať neskôr."
},
"options_bug_report_send_button": {
- "message": "Poslať"
+ "message": "Odoslať hlásenie"
},
"options_bug_report_sending_button": {
- "message": "Odosielam..."
+ "message": "Odosielanie hlásenia..."
},
"options_bug_report_textarea_placeholder": {
- "message": "Sem opíšte problém..."
+ "message": "Uveďte podrobnosti"
},
"options_bug_report_page_success": {
"message": "Ďakujeme, Vaša správa bola úspešne odoslaná!"
@@ -118,13 +121,13 @@
"message": "Účet"
},
"account_edit": {
- "message": "Správa účtu"
+ "message": "Otvorenie nastavení účtu"
},
"account_version": {
"message": "Verzia"
},
"account_free": {
- "message": "Bezplatný účet"
+ "message": "Bezplatná verzia"
},
"account_unlimited": {
"message": "Neobmedzené predplatné"
@@ -142,7 +145,7 @@
"message": "Získať predplatné"
},
"account_max_devices_count": {
- "message": "Až %num% súčasne pripojených zariadení"
+ "message": "Až %num% súčasne pripojených zariadení"
},
"account_valid_until": {
"message": "Platí do %date%"
@@ -157,7 +160,7 @@
"message": "Získať Neobmedzenú verziu!"
},
"rate_description": {
- "message": "Hej, sme tím spoločnosti AdGuard a je pre nás veľmi dôležité poznať Váš názor na náš produkt. Ohodnoťte ho."
+ "message": "Páči sa Vám AdGuard VPN?"
},
"rate_hide": {
"message": "Skryť"
@@ -234,8 +237,8 @@
"privacy_policy": {
"message": "Pravidlá ochrany súkromia"
},
- "settings_title": {
- "message": "Nastavenia"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Získajte bezplatné GB!"
@@ -277,16 +280,16 @@
"message": "Znovu poslať odkaz"
},
"settings_free_gbs_add_device_title": {
- "message": "Pridajte ďalšie zariadenie"
+ "message": "Pridajte ďalšiu platformu"
},
"settings_free_gbs_add_device_info": {
- "message": "Nainštalujte AdGuard VPN na ďalšie zariadenie, prihláste sa na ňom do svojho účtu AdGuard a získajte %your_gb% GB"
+ "message": "Nainštalujte si AdGuard VPN pre iOS, Mac, Windows alebo Android, prihláste sa do svojho účtu AdGuard a získajte %your_gb% GB."
},
"settings_free_gbs_devices_added_title": {
- "message": "Zariadenia boli pridané"
+ "message": "Ďalšia platforma bola pridaná"
},
"settings_free_gbs_devices_added_info": {
- "message": "Gratulujeme k inštalácii AdGuard VPN na viacerých zariadeniach. Zostaňte v bezpečí!"
+ "message": "Gratulujeme k inštalácii AdGuard VPN na viacerých platformách. Zostaňte v bezpečí!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Ísť na Produkty"
@@ -313,16 +316,19 @@
"message": "Blokovať WebRTC"
},
"settings_webrtc_desc": {
- "message": "WebRTC je špeciálny program vo väčšine internetových prehliadačov, ktorý umožňuje webovej stránke tretej strany zistiť vašu skutočnú IP adresu a použiť ju na Vašu identifikáciu"
+ "message": "Blokovanie WebRTC, známej zraniteľnosti, ktorá môže prezradiť Vašu skutočnú IP adresu, aj keď používate proxy server alebo VPN"
},
"settings_context_menus_title": {
- "message": "Pridajte položku AdGuard VPN do kontextovej ponuky prehliadača"
+ "message": "Zobrazenie AdGuard VPN v kontextovej ponuke prehliadača"
+ },
+ "settings_context_menus_description": {
+ "message": "Správa výnimiek VPN pre konkrétnu webovú lokalitu"
},
"settings_help_us_improve_title": {
- "message": "Pomôžte nám vylepšiť"
+ "message": "Odosielať anonymné hlásenia o zlyhaní"
},
"settings_help_us_improve_description": {
- "message": "Umožnite AdGuard VPN zhromažďovať správy o zlyhaní, technické údaje a údaje o interakciách. Je to anonymné."
+ "message": "Upozornite vývojárov AdGuard VPN, ak sa niečo pokazí"
},
"context_menu_selective_mode": {
"message": "Výberový režim"
@@ -339,6 +345,12 @@
"settings_dns_label": {
"message": "DNS server"
},
+ "settings_dns_description": {
+ "message": "Riešenie DNS dopytov, blokovanie reklám a sledovacích zariadení a šifrovanie prevádzky DNS, keď ste pripojený k VPN sieti"
+ },
+ "settings_dns_description_current": {
+ "message": "Aktuálne: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Populárne DNS servery"
},
@@ -352,22 +364,22 @@
"message": "Upraviť vlastný DNS server"
},
"settings_dns_add_custom_server_info": {
- "message": "Prečítajte si viac o DNS serveroch"
+ "message": "Známi poskytovatelia DNS"
},
"settings_dns_add_custom_server_name": {
"message": "Meno servera"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Meno servera"
+ "message": "Môj DNS server"
},
"settings_dns_add_custom_server_address": {
- "message": "Adresa DNS servera"
+ "message": "Adresy serverov"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "IP adresa servera alebo názov TLS domény"
+ "message": "IP adresa alebo tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Neplatná adresa DNS servera"
+ "message": "Neplatná adresa servera"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Server DNS už bol pridaný"
@@ -384,6 +396,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Vlastný DNS server bol odstránený"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Vlastný DNS server bol upravený"
+ },
"settings_dns_selector_default_title": {
"message": "Predvolené"
},
@@ -487,10 +502,10 @@
"message": "AdGuard VPN je zapnutá pre začiarknuté domény a subdomény"
},
"settings_exclusion_domain_name": {
- "message": "Meno domény:"
+ "message": "Meno domény"
},
"settings_exclusion_subdomain_name": {
- "message": "Meno subdomény:"
+ "message": "Subdoména"
},
"settings_exclusion_add_from_list": {
"message": "Zo zoznamu"
@@ -550,7 +565,7 @@
"message": "Odstrániť všetky výnimky"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Ste si istý, že chcete pokračovať? Celý zoznam výnimiek bude odstránený."
+ "message": "Chcete vymazať celý zoznam výnimiek?"
},
"settings_exclusion_title": {
"message": "Výnimky"
@@ -945,8 +960,11 @@
"options_signedout_page_title": {
"message": "Odhlásili ste sa z AdGuard VPN"
},
+ "options_signedout_page_description_not_secure": {
+ "message": "Vaše pripojenie nie je zabezpečené!"
+ },
"options_signedout_page_description": {
- "message": "Upozornenie: nie ste pripojení k sieti VPN a Vaša komunikácia nie je chránená! Ak chcete pokračovať v súkromnom surfovaní, kliknite na ikonu AdGuard VPN a znovu sa prihláste."
+ "message": "Ak chcete pokračovať v súkromnom surfovaní, kliknite na ikonu AdGuard VPN a prihláste sa znova."
},
"notification_data_limit_reached_title": {
"message": "Dosiahnutý mesačný dátový limit"
diff --git a/src/_locales/sl/messages.json b/src/_locales/sl/messages.json
index fadfef10d..7c067828f 100644
--- a/src/_locales/sl/messages.json
+++ b/src/_locales/sl/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Vizitka"
},
+ "all_rights_reserved": {
+ "message": "Vse pravice so pridržane."
+ },
"options_support_title": {
"message": "Podpora"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Prišlo je do napake, poskusite pozneje znova."
},
- "options_bug_report_send_button": {
- "message": "Pošlji"
- },
- "options_bug_report_sending_button": {
- "message": "Pošiljanje..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Tukaj opišite težavo..."
- },
- "options_bug_report_page_success": {
- "message": "Hvala, vaše sporočilo je bilo uspešno poslano!"
- },
"options_bug_report_new_report_button": {
"message": "Novo poročilo"
},
@@ -118,13 +109,13 @@
"message": "Račun"
},
"account_edit": {
- "message": "Upravljanje računa"
+ "message": "Odpri nastavitve računa"
},
"account_version": {
"message": "Različica"
},
"account_free": {
- "message": "Brezplačen račun"
+ "message": "Brezplačna različica"
},
"account_unlimited": {
"message": "Neomejena naročnina"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Pridobite naročnino"
},
- "account_max_devices_count": {
- "message": "Do %num% sočasnih naprav"
- },
"account_valid_until": {
"message": "Velja do %date%"
},
@@ -157,7 +145,7 @@
"message": "Pridobite neomejeno različico!"
},
"rate_description": {
- "message": "Hej, smo ekipa AdGuarda in zelo pomembno nam je, da vemo vaše mnenje o našem izdelku. Prosimo ocenite ga."
+ "message": "Uživate v AdGuard VPN?"
},
"rate_hide": {
"message": "Skrij"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Politika zasebnosti"
},
- "settings_title": {
- "message": "Nastavitve"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Pridobite brezplačne GB!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Znova pošlji povezavo"
},
- "settings_free_gbs_add_device_title": {
- "message": "Dodaj drugo napravo"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Namestite AdGuard VPN še na eno napravo, na njej se prijavite v svoj račun AdGuard in pridobite %your_gb% GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Naprave so dodane"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Čestitke za namestitev AdGuard VPN na več naprav. Ostanite varni!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Pojdi na izdelke"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Onemogoči WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC je poseben program v večini spletnih brskalnikov, ki lahko spletnemi strani tretjih oseb omogoči zaznavanje vašega resničnega naslova IP in ga uporabi za vašo identifikacijo"
- },
- "settings_context_menus_title": {
- "message": "Dodaj vnos AdGuard VPN-ja v priročni meni brskalnika"
- },
"settings_help_us_improve_title": {
- "message": "Pomagajte nam izboljšati"
- },
- "settings_help_us_improve_description": {
- "message": "Dovoli AdGuard VPN, da zbira poročila o zrušitvah, tehnične podatke in podatke o vzajemnem delovanju. To je anonimno."
+ "message": "Pošlji anonimna poročila o zrušitvah"
},
"context_menu_selective_mode": {
"message": "Izbirni način"
@@ -352,22 +319,13 @@
"message": "Uredi strežnik DNS po meri"
},
"settings_dns_add_custom_server_info": {
- "message": "Preberite več o strežnikih DNS"
+ "message": "Znani ponudniki DNS"
},
"settings_dns_add_custom_server_name": {
"message": "Ime strežnika"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Ime strežnika"
- },
"settings_dns_add_custom_server_address": {
- "message": "Naslov DNS strežnika"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "IP strežnika ali ime domene TLS"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Neveljaven naslov DNS"
+ "message": "Naslov strežnika"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS strežnik je že dodan"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN je zapnutá pre zaškrtnuté domény a subdomény"
},
"settings_exclusion_domain_name": {
- "message": "Ime domene:"
+ "message": "Ime domene"
},
"settings_exclusion_subdomain_name": {
- "message": "Ime poddomene:"
+ "message": "Poddomena"
},
"settings_exclusion_add_from_list": {
"message": "S seznama"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Odstranite vse izključitve"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Ste prepričani, da želite nadaljevati? Celoten seznam izključitev bo odstranjen."
- },
"settings_exclusion_title": {
"message": "Izjeme"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Dosežena je omejitev povezave"
},
- "options_signedout_page_title": {
- "message": "Odjavili ste se iz AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Upoštevajte: niste povezani z omrežjem VPN in vaš promet ni zaščiten! Če želite nadaljevati zasebno brskanje, kliknite ikono AdGuard VPN in se ponovno prijavite."
- },
"notification_data_limit_reached_title": {
"message": "Mesečna omejitev podatkov je dosežena"
},
diff --git a/src/_locales/sr-Latn/messages.json b/src/_locales/sr-Latn/messages.json
index 0f43a9a2b..54c7554b3 100644
--- a/src/_locales/sr-Latn/messages.json
+++ b/src/_locales/sr-Latn/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "O programu"
},
+ "all_rights_reserved": {
+ "message": "Sva prava zadržana."
+ },
"options_support_title": {
"message": "Podrška"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Došlo je do greške. Molimo vas da pokušate kasnije."
},
- "options_bug_report_send_button": {
- "message": "Pošalji"
- },
- "options_bug_report_sending_button": {
- "message": "Šaljem..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Opišite na kakvu ste grešku naišli ovde..."
- },
- "options_bug_report_page_success": {
- "message": "Hvala vam. Vaša poruka je uspešno poslata!"
- },
"options_bug_report_new_report_button": {
"message": "Novi izveštaj"
},
@@ -118,13 +109,13 @@
"message": "Nalog"
},
"account_edit": {
- "message": "Upravljaj"
+ "message": "Otvoriti postavki naloga"
},
"account_version": {
"message": "Verzija"
},
"account_free": {
- "message": "Besplatni nalog"
+ "message": "Besplatna verzija"
},
"account_unlimited": {
"message": "Neograničena pretplata"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Pretplati se"
},
- "account_max_devices_count": {
- "message": "Do %num% istovremenih uređaja"
- },
"account_valid_until": {
"message": "Važi do %date%"
},
@@ -157,7 +145,7 @@
"message": "Nabavite neograničenu verziju!"
},
"rate_description": {
- "message": "Zdravo. Mi smo AdGuard tim i veoma nam je važno da znamo vaše mišljenje o našem proizvodu. Molimo vas da ga ocenite."
+ "message": "Uživate u AdGuard VPN-u?"
},
"rate_hide": {
"message": "Sakrij"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Politika privatnosti"
},
- "settings_title": {
- "message": "Postavke"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Nabavite besplatan VPn saobraćaj!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Ponovo pošalji link"
},
- "settings_free_gbs_add_device_title": {
- "message": "Dodaj drugi uređaj"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Instalirajte AdGuard VPN na još jedan uređaj, prijavite se na svoj AdGuard nalog na njemu i %your_gb% GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Dodati uređaji"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Čestitam na instaliranju AdGuard VPN-a na više uređaja. Ostanite bezbedni!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Idi na proizvode"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Blokiraj WebRTC"
},
- "settings_webrtc_desc": {
- "message": "VebRTC je poseban program u većini Internet pretraživača koji može omogućiti veb lokaciji trećih strana da otkrije vašu stvarnu IP adresu i pomoću nje vas identifikuje"
- },
- "settings_context_menus_title": {
- "message": "Dodaj AdGuard VPN u kontekstni meni preglednika"
- },
"settings_help_us_improve_title": {
- "message": "Pomozite da se poboljšamo"
- },
- "settings_help_us_improve_description": {
- "message": "Dozvolite AdGuard VPN-u da prikuplja izveštaje o padu, tehničke i interakcijske podatke. Ovo je anonimno."
+ "message": "Slati anonimne izveštaje o rušenju"
},
"context_menu_selective_mode": {
"message": "Selektivni način rada"
@@ -352,22 +319,13 @@
"message": "Izmeni prilagođeni DNS server"
},
"settings_dns_add_custom_server_info": {
- "message": "Pročitajte više o DNS serverima"
+ "message": "Poznati DNS dobavljači"
},
"settings_dns_add_custom_server_name": {
"message": "Ime servera"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Ime servera"
- },
"settings_dns_add_custom_server_address": {
- "message": "Adresa DNS servera"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "Ime IP ili TLS domena servera"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Nevažeća adresa DNS servera"
+ "message": "Adresa servera"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS server je već dodat"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN je uključen za označene domene i poddomene"
},
"settings_exclusion_domain_name": {
- "message": "Domen:"
+ "message": "Ime domena"
},
"settings_exclusion_subdomain_name": {
- "message": "Ime poddomena:"
+ "message": "Poddomen"
},
"settings_exclusion_add_from_list": {
"message": "Sa liste"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Ukloni sve izuzetke"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Jeste li sigurni da želite da nastavite? Čitava lista izuzetaka će biti uklonjena."
- },
"settings_exclusion_title": {
"message": "Izuzeci"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Dosegnuto je ograničenje povezivanja"
},
- "options_signedout_page_title": {
- "message": "Odjavili ste se iz AdGuard VPN-a"
- },
- "options_signedout_page_description": {
- "message": "Pažnja: niste povezani sa VPN-om i vaš saobraćaj nije zaštićen! Da biste nastavili privatno surfovanje, kliknite na ikonu AdGuard VPN i ponovo se prijavite."
- },
"notification_data_limit_reached_title": {
"message": "Dostigli ste mesečno ograničenje za podatke."
},
diff --git a/src/_locales/sv/messages.json b/src/_locales/sv/messages.json
index 9cfc156ad..f81dd385f 100644
--- a/src/_locales/sv/messages.json
+++ b/src/_locales/sv/messages.json
@@ -51,6 +51,9 @@
"about_title": {
"message": "Om"
},
+ "all_rights_reserved": {
+ "message": "Alla rättigheter förbehållna."
+ },
"options_support_title": {
"message": "Support"
},
@@ -90,18 +93,6 @@
"options_bug_report_request_error": {
"message": "Ett fel uppstod. Försök igen senare."
},
- "options_bug_report_send_button": {
- "message": "Skicka"
- },
- "options_bug_report_sending_button": {
- "message": "Skickar..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Beskriv problemet här..."
- },
- "options_bug_report_page_success": {
- "message": "Tack, ditt meddelande skickades framgångsrikt!"
- },
"options_bug_report_new_report_button": {
"message": "Ny rapport"
},
@@ -112,13 +103,13 @@
"message": "Konto"
},
"account_edit": {
- "message": "Hantera konto"
+ "message": "Öppna kontoinställningar"
},
"account_version": {
"message": "Version"
},
"account_free": {
- "message": "Gratis konto"
+ "message": "Gratis version"
},
"account_get_subscription": {
"message": "Skaffa prenumeration"
@@ -132,9 +123,6 @@
"account_sign_out": {
"message": "Logga ut"
},
- "rate_description": {
- "message": "Hej, vi är ett team från AdGuard och det är mycket viktigt för oss att veta din åsikt om vår produkt. Var snäll och prissätta det."
- },
"rate_hide": {
"message": "Dölj"
},
@@ -210,8 +198,8 @@
"privacy_policy": {
"message": "Integritetspolicy"
},
- "settings_title": {
- "message": "Inställningar"
+ "settings_general_title": {
+ "message": "Allmänt"
},
"settings_referral_copy_link": {
"message": "Kopiera inbjudningslänken"
@@ -231,18 +219,6 @@
"settings_webrtc_label": {
"message": "Blockera WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC är ett speciellt program i de flesta webbläsare som kan tillåta en tredje parts webbplats att upptäcka din riktiga IP adress och använda den för att identifiera dig"
- },
- "settings_context_menus_title": {
- "message": "Lägg till AdGuard VPN objekt i webbläsarens snabbmeny"
- },
- "settings_help_us_improve_title": {
- "message": "Hjälp oss att förbättra"
- },
- "settings_help_us_improve_description": {
- "message": "Tillåt AdGuard VPN att samla in kraschrapporter, tekniska data och interaktionsdata. Det här är anonymt."
- },
"context_menu_selective_mode": {
"message": "Selektivt läge"
},
@@ -264,20 +240,11 @@
"settings_dns_edit_custom_server": {
"message": "Redigera anpassad DNS-server"
},
- "settings_dns_add_custom_server_info": {
- "message": "Läs mer om DNS-servrar"
- },
"settings_dns_add_custom_server_name": {
"message": "Servernamn"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Servernamn"
- },
"settings_dns_add_custom_server_address": {
- "message": "DNS-serveradress"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Ogiltig DNS-serveradress"
+ "message": "Serveradress"
},
"settings_dns_add_custom_server_save_and_select": {
"message": "Spara och välj"
@@ -364,7 +331,10 @@
"message": "AdGuard VPN är aktiverat för de i kryssade domänerna och underdomänerna"
},
"settings_exclusion_domain_name": {
- "message": "Domännamn:"
+ "message": "Domännamn"
+ },
+ "settings_exclusion_subdomain_name": {
+ "message": "Underdomän"
},
"settings_exclusion_add_from_list": {
"message": "Från listan"
@@ -706,12 +676,6 @@
"popup_connections_limit_title": {
"message": "Anslutningsgränsen har nåtts"
},
- "options_signedout_page_title": {
- "message": "Du har loggat ut ur AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Observera att din anslutning inte är säker. För att fortsätta surfa privat, klicka på AdGuard VPN ikonen och logga in igen."
- },
"confirm_email_title": {
"message": "Bekräfta din e-postadress"
},
diff --git a/src/_locales/tr/messages.json b/src/_locales/tr/messages.json
index 6ec8be8d9..df980d8c0 100644
--- a/src/_locales/tr/messages.json
+++ b/src/_locales/tr/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Hakkında"
},
+ "all_rights_reserved": {
+ "message": "Tüm hakları saklıdır."
+ },
"options_support_title": {
"message": "Destek"
},
@@ -96,14 +99,8 @@
"options_bug_report_request_error": {
"message": "Bir hata oluştu. Lütfen daha sonra tekrar deneyin."
},
- "options_bug_report_send_button": {
- "message": "Gönder"
- },
- "options_bug_report_sending_button": {
- "message": "Gönderiliyor..."
- },
"options_bug_report_textarea_placeholder": {
- "message": "Sorunu burada açıklayın..."
+ "message": "Ayrıntıları sağlayın"
},
"options_bug_report_page_success": {
"message": "Teşekkürler, mesajınız başarıyla gönderildi!"
@@ -118,13 +115,13 @@
"message": "Hesap"
},
"account_edit": {
- "message": "Hesabı yönet"
+ "message": "Hesap ayarlarını aç"
},
"account_version": {
"message": "Sürüm"
},
"account_free": {
- "message": "Ücretsiz hesap"
+ "message": "Ücretsiz sürüm"
},
"account_unlimited": {
"message": "Sınırsız abonelik"
@@ -142,7 +139,7 @@
"message": "Abonelik al"
},
"account_max_devices_count": {
- "message": "Aynı anda %num% cihaza kadar"
+ "message": "Aynı anda %num% cihaza kadar"
},
"account_valid_until": {
"message": "%date% tarihine kadar geçerli"
@@ -157,7 +154,7 @@
"message": "Sınırsız sürümü edinin!"
},
"rate_description": {
- "message": "Hey, biz AdGuard'dan bir ekibiz ve ürünümüz hakkındaki düşüncelerinizi bilmek bizim için çok önemlidir. Lütfen oylayın."
+ "message": "AdGuard VPN'in keyfini çıkarıyor musunuz?"
},
"rate_hide": {
"message": "Gizle"
@@ -234,8 +231,8 @@
"privacy_policy": {
"message": "Gizlilik Politikası"
},
- "settings_title": {
- "message": "Ayarlar"
+ "settings_general_title": {
+ "message": "Genel"
},
"referral_get_free_traffic": {
"message": "Ücretsiz GB'lar edinin!"
@@ -277,16 +274,16 @@
"message": "Bağlantıyı yeniden gönder"
},
"settings_free_gbs_add_device_title": {
- "message": "Başka bir cihaz ekle"
+ "message": "Başka bir platform ekle"
},
"settings_free_gbs_add_device_info": {
- "message": "AdGuard VPN'i bir cihaza daha yükleyin, AdGuard hesabınıza giriş yapın ve %your_gb% GB edinin"
+ "message": "iOS, Mac, Windows veya Android için AdGuard VPN'i yükleyin, orada AdGuard hesabınıza giriş yapın ve %your_gb% GB edinin."
},
"settings_free_gbs_devices_added_title": {
- "message": "Cihaz eklendi"
+ "message": "Başka bir platform eklendi"
},
"settings_free_gbs_devices_added_info": {
- "message": "AdGuard VPN'i birden fazla cihaza yüklediğiniz için tebrikler. Güvende kalın!"
+ "message": "AdGuard VPN'i birden çok platforma yüklediğiniz için tebrikler. Güvende kalın!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Ürünlere git"
@@ -313,16 +310,19 @@
"message": "WebRTC'i engelle"
},
"settings_webrtc_desc": {
- "message": "WebRTC çoğu internet tarayıcısında, üçüncü taraf bir sitenin gerçek IP adresinizi tespit etmesine ve sizi tanımlamak için kullanmasına izin veren özel bir programdır"
+ "message": "Proxy veya VPN kullansanız bile gerçek IP adresinizi sızdırabilecek bilinen bir güvenlik açığı olan WebRTC'i engelleyin"
},
"settings_context_menus_title": {
- "message": "Tarayıcının sağ tık menüsüne AdGuard VPN öğesini ekle"
+ "message": "Tarayıcının sağ tık menüsünde AdGuard VPN'i göster"
+ },
+ "settings_context_menus_description": {
+ "message": "Belirli bir site için VPN istisnalarını yönetin"
},
"settings_help_us_improve_title": {
- "message": "Geliştirmemize yardımcı ol"
+ "message": "Anonim çökme raporları gönder"
},
"settings_help_us_improve_description": {
- "message": "AdGuard VPN'in çökme raporları, teknik ve etkileşim verilerini toplamasına izin verin. Bu anonimdir."
+ "message": "Bir şeyler ters giderse AdGuard VPN geliştiricilerini bilgilendirin"
},
"context_menu_selective_mode": {
"message": "Seçici mod"
@@ -339,6 +339,12 @@
"settings_dns_label": {
"message": "DNS sunucusu"
},
+ "settings_dns_description": {
+ "message": "VPN'e bağlandığınızda DNS isteklerini çözün, reklamları ve izleyicileri engelleyin ve DNS trafiğini şifreleyin"
+ },
+ "settings_dns_description_current": {
+ "message": "Şu anki: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Popüler DNS sunucuları"
},
@@ -352,22 +358,22 @@
"message": "Özel DNS sunucusunu düzenle"
},
"settings_dns_add_custom_server_info": {
- "message": "DNS sunucuları hakkında daha fazla bilgi edinin"
+ "message": "Bilinen DNS sağlayıcıları"
},
"settings_dns_add_custom_server_name": {
"message": "Sunucu adı"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Sunucu adı"
+ "message": "DNS Sunucum"
},
"settings_dns_add_custom_server_address": {
- "message": "DNS sunucusu adresi"
+ "message": "Sunucu adresi"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "Sunucu IP'si veya TLS alan adı"
+ "message": "IP adresi veya tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Geçersiz DNS sunucu adresi"
+ "message": "Geçersiz sunucu adresi"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS sunucusu zaten eklendi"
@@ -384,6 +390,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Özel DNS sunucusu silindi"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Özel DNS sunucusu düzenlendi"
+ },
"settings_dns_selector_default_title": {
"message": "Varsayılan"
},
@@ -487,10 +496,10 @@
"message": "AdGuard VPN, seçili alan adları ve alt alan adları için açık"
},
"settings_exclusion_domain_name": {
- "message": "Alan adı:"
+ "message": "Alan adı"
},
"settings_exclusion_subdomain_name": {
- "message": "Alt alan adı:"
+ "message": "Alt alan adı"
},
"settings_exclusion_add_from_list": {
"message": "Listeden"
@@ -550,7 +559,7 @@
"message": "Tüm istisnaları kaldır"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Devam etmek istediğinize emin misiniz? Tüm istisnalar listesi kaldırılacaktır."
+ "message": "Tüm istisnaların listesini kaldırmak istiyor musunuz?"
},
"settings_exclusion_title": {
"message": "İstisnalar"
@@ -945,8 +954,11 @@
"options_signedout_page_title": {
"message": "AdGuard VPN'den çıkış yaptınız"
},
+ "options_signedout_page_description_not_secure": {
+ "message": "Bağlantınız güvenli değil!"
+ },
"options_signedout_page_description": {
- "message": "Lütfen unutmayın: VPN'e bağlı değilsiniz ve trafiğiniz korunmuyor! Gizli olarak gezinmeye devam etmek için AdGuard VPN simgesine tıklayın ve tekrar giriş yapın."
+ "message": "Gizli olarak gezinmeye devam etmek için AdGuard VPN simgesine tıklayın ve tekrar giriş yapın."
},
"notification_data_limit_reached_title": {
"message": "Aylık veri sınırına ulaşıldı"
diff --git a/src/_locales/uk/messages.json b/src/_locales/uk/messages.json
index 1ff4958b0..d7a2e98c0 100644
--- a/src/_locales/uk/messages.json
+++ b/src/_locales/uk/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Про розширення"
},
+ "all_rights_reserved": {
+ "message": "Усі права захищено."
+ },
"options_support_title": {
"message": "Підтримка"
},
@@ -97,13 +100,13 @@
"message": "Сталася помилка, спробуйте пізніше."
},
"options_bug_report_send_button": {
- "message": "Надіслати"
+ "message": "Надіслати звіт"
},
"options_bug_report_sending_button": {
- "message": "Надсилання..."
+ "message": "Надсилання звіту..."
},
"options_bug_report_textarea_placeholder": {
- "message": "Опишіть проблему тут..."
+ "message": "Надайте детальну інформацію"
},
"options_bug_report_page_success": {
"message": "Дякуємо, ваше повідомлення успішно надіслано!"
@@ -118,13 +121,13 @@
"message": "Обліковий запис"
},
"account_edit": {
- "message": "Керувати обліковим записом"
+ "message": "Налаштування облікового запису"
},
"account_version": {
"message": "Версія"
},
"account_free": {
- "message": "Безплатний обліковий запис"
+ "message": "Безкоштовна версія"
},
"account_unlimited": {
"message": "Безлімітна передплата"
@@ -142,7 +145,7 @@
"message": "Отримати передплату"
},
"account_max_devices_count": {
- "message": "До %num% пристроїв одночасно"
+ "message": "До %num% пристроїв одночасно"
},
"account_valid_until": {
"message": "Діє до %date%"
@@ -157,7 +160,7 @@
"message": "Отримайте безлімітну версію!"
},
"rate_description": {
- "message": "Для нас дуже важливо дізнатись вашу думку про це розширення. Оцінить його, будь ласка."
+ "message": "Подобається AdGuard VPN?"
},
"rate_hide": {
"message": "Приховати"
@@ -234,8 +237,8 @@
"privacy_policy": {
"message": "Політика конфіденційності"
},
- "settings_title": {
- "message": "Налаштування"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Отримайте безплатний VPN-трафік!"
@@ -277,16 +280,16 @@
"message": "Надіслати повторно"
},
"settings_free_gbs_add_device_title": {
- "message": "Додайте інший пристрій"
+ "message": "Додати іншу платформу"
},
"settings_free_gbs_add_device_info": {
- "message": "Встановіть AdGuard VPN на ще один пристрій, увійдіть до облікового запису AdGuard на ньому — отримайте %your_gb% ГБ"
+ "message": "Встановіть AdGuard VPN для iOS, Mac, Windows або Android, увійдіть до свого облікового запису AdGuard там і отримайте %your_gb% ГБ."
},
"settings_free_gbs_devices_added_title": {
- "message": "Пристрої додано"
+ "message": "Іншу платформу додано"
},
"settings_free_gbs_devices_added_info": {
- "message": "Вітаємо, тепер AdGuard VPN захищатиме декілька ваших пристроїв!"
+ "message": "Вітаємо з встановленням AdGuard VPN на декількох платформах. Залишайтеся в безпеці!"
},
"settings_free_gbs_add_device_products_button": {
"message": "Перейти до продуктів"
@@ -313,16 +316,19 @@
"message": "Блокувати WebRTC"
},
"settings_webrtc_desc": {
- "message": "WebRTC — це спеціальна технологія, яка реалізована у більшості браузерів та дає можливість стороннім сайтам виявляти вашу реальну IP-адресу та використовувати її для ідентифікації вас"
+ "message": "Блокує WebRTC — відому вразливість, яка може призвести до витоку вашої реальної IP-адреси, навіть якщо ви використовуєте проксі або VPN"
},
"settings_context_menus_title": {
- "message": "Додати AdGuard VPN в контекстне меню браузера"
+ "message": "Показувати AdGuard VPN у контекстному меню браузера"
+ },
+ "settings_context_menus_description": {
+ "message": "Керувати винятками VPN для конкретного сайту"
},
"settings_help_us_improve_title": {
- "message": "Допоможіть нам покращити програму"
+ "message": "Надсилати анонімні повідомлення про збої"
},
"settings_help_us_improve_description": {
- "message": "Дозволити AdGuard VPN збирати анонімну технічну інформацію про збої та помилки."
+ "message": "Повідомте розробників AdGuard VPN, якщо щось не так"
},
"context_menu_selective_mode": {
"message": "Вибірковий режим"
@@ -339,6 +345,12 @@
"settings_dns_label": {
"message": "DNS-сервер"
},
+ "settings_dns_description": {
+ "message": "Вирішуйте DNS-запити, блокуйте рекламу та трекери, шифруйте DNS-трафік, коли ви підʼєднані до VPN"
+ },
+ "settings_dns_description_current": {
+ "message": "Зараз обрано: %dnsServerName%"
+ },
"settings_dns_popular_servers": {
"message": "Популярні DNS-сервери"
},
@@ -352,22 +364,22 @@
"message": "Змінити власний DNS-сервер"
},
"settings_dns_add_custom_server_info": {
- "message": "Докладніше про DNS-сервери"
+ "message": "Відомі DNS-провайдери"
},
"settings_dns_add_custom_server_name": {
"message": "Назва сервера"
},
"settings_dns_add_custom_server_name_placeholder": {
- "message": "Назва сервера"
+ "message": "Мій DNS-сервер"
},
"settings_dns_add_custom_server_address": {
- "message": "Адреса DNS-сервера"
+ "message": "Адреса сервера"
},
"settings_dns_add_custom_server_address_placeholder": {
- "message": "IP-адреса або TLS-домен сервера"
+ "message": "IP-адреса або tls://"
},
"settings_dns_add_custom_server_invalid_address": {
- "message": "Недійсна адреса DNS-сервера"
+ "message": "Недійсна адреса сервера"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS-сервер уже додано"
@@ -384,6 +396,9 @@
"settings_dns_delete_custom_server_notification": {
"message": "Власний DNS-сервер видалено"
},
+ "settings_dns_edit_custom_server_notification": {
+ "message": "Власний DNS-сервер відредаговано"
+ },
"settings_dns_selector_default_title": {
"message": "Усталений"
},
@@ -487,10 +502,10 @@
"message": "AdGuard VPN увімкнено для помічених галкою доменів та піддоменів"
},
"settings_exclusion_domain_name": {
- "message": "Назва домену:"
+ "message": "Назва домену"
},
"settings_exclusion_subdomain_name": {
- "message": "Назва піддомену:"
+ "message": "Піддомен"
},
"settings_exclusion_add_from_list": {
"message": "Зі списку"
@@ -550,7 +565,7 @@
"message": "Видалити всі винятки"
},
"settings_exclusions_remove_all_exclusions_message": {
- "message": "Ви дійсно хочете продовжити? Увесь список винятнів буде видалено."
+ "message": "Ви дійсно хочете видалити весь список винятків?"
},
"settings_exclusion_title": {
"message": "Винятки"
@@ -945,8 +960,11 @@
"options_signedout_page_title": {
"message": "Ви вийшли з AdGuard VPN"
},
+ "options_signedout_page_description_not_secure": {
+ "message": "Ваше зʼєднання не захищене!"
+ },
"options_signedout_page_description": {
- "message": "Зверніть увагу, що тепер ви не підʼєднані до VPN та ваш трафік не захищено! Щоб безпечно користуватись інтернетом, натисніть на іконку AdGuard VPN та увійдіть знову."
+ "message": "Щоб продовжувати безпечно серфити інтернетом, натисніть на іконку AdGuard VPN та увійдіть знову."
},
"notification_data_limit_reached_title": {
"message": "Вичерпано місячний ліміт трафіку"
diff --git a/src/_locales/vi/messages.json b/src/_locales/vi/messages.json
index 7e0382afc..2dc2837a9 100644
--- a/src/_locales/vi/messages.json
+++ b/src/_locales/vi/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "Thông tin"
},
+ "all_rights_reserved": {
+ "message": "Tất cả các quyền đã được đăng ký trước"
+ },
"options_support_title": {
"message": "Hỗ trợ"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "Đã xảy ra lỗi. Vui lòng thử lại sau."
},
- "options_bug_report_send_button": {
- "message": "Gửi"
- },
- "options_bug_report_sending_button": {
- "message": "Đang gửi..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "Mô tả vấn đề ở đây..."
- },
- "options_bug_report_page_success": {
- "message": "Cảm ơn bạn, tin nhắn của bạn đã được gửi thành công!"
- },
"options_bug_report_new_report_button": {
"message": "Báo cáo mới"
},
@@ -118,13 +109,13 @@
"message": "Tài khoản"
},
"account_edit": {
- "message": "Chỉnh sửa"
+ "message": "Mở cài đặt tài khoản"
},
"account_version": {
"message": "Phiên bản"
},
"account_free": {
- "message": "Tài khoản miễn phí"
+ "message": "Phiên bản miễn phí"
},
"account_unlimited": {
"message": "Đăng ký không giới hạn"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "Đăng ký"
},
- "account_max_devices_count": {
- "message": "Cùng lúc tối đa %num% thiết bị"
- },
"account_valid_until": {
"message": "Có giá trị đến %date%"
},
@@ -157,7 +145,7 @@
"message": "Tải ngay phiên bản Không giới hạn!"
},
"rate_description": {
- "message": "Xin chào, chúng tôi là một nhóm đến từ AdGuard và điều rất quan trọng là chúng tôi phải biết ý kiến của bạn về sản phẩm của chúng tôi. Hãy đánh giá nó."
+ "message": "Tận hưởng việc sử dụng AdGuard VPN?"
},
"rate_hide": {
"message": "Ẩn"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "Chính sách quyền riêng tư"
},
- "settings_title": {
- "message": "Cài đặt"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "Nhận GB miễn phí!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "Gửi lại liên kết"
},
- "settings_free_gbs_add_device_title": {
- "message": "Thêm thiết bị khác"
- },
- "settings_free_gbs_add_device_info": {
- "message": "Cài đặt AdGuard VPN trên một thiết bị khác, đăng nhập vào tài khoản AdGuard của bạn trên thiết bị đó và nhận %your_gb% GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "Đã thêm thiết bị"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "Chúc mừng bạn đã cài đặt AdGuard VPN trên nhiều thiết bị. Giữ an toàn!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "Đi đến Sản phẩm"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "Chặn WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC là một chương trình đặc biệt trong hầu hết các trình duyệt Internet có thể cho phép trang web của bên thứ ba phát hiện địa chỉ IP thực của bạn và sử dụng nó để nhận dạng bạn"
- },
- "settings_context_menus_title": {
- "message": "Thêm AdGuard vào menu ngữ cảnh của trình duyệt"
- },
"settings_help_us_improve_title": {
- "message": "Giúp chúng tôi cải tiến"
- },
- "settings_help_us_improve_description": {
- "message": "Cho phép AdGuard VPN thu thập báo cáo sự cố, dữ liệu kỹ thuật và tương tác. Đây là ẩn danh."
+ "message": "Gửi báo cáo sự cố ẩn danh"
},
"context_menu_selective_mode": {
"message": "Chế độ chọn lọc"
@@ -352,22 +319,13 @@
"message": "Chỉnh sửa máy chủ DNS tuỳ chỉnh"
},
"settings_dns_add_custom_server_info": {
- "message": "Đọc thêm về máy chủ DNS"
+ "message": "Các nhà cung cấp DNS đã biết"
},
"settings_dns_add_custom_server_name": {
"message": "Tên máy chủ"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "Tên máy chủ"
- },
"settings_dns_add_custom_server_address": {
- "message": "Địa chỉ máy chủ DNS"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "Tên miền IP hoặc TLS của máy chủ"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "Địa chỉ máy chủ DNS không hợp lệ"
+ "message": "Địa chỉ máy chủ"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "Máy chủ DNS đã được thêm vào"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN được bật với những tên miền và tên miền con được chọn"
},
"settings_exclusion_domain_name": {
- "message": "Tên miền:"
+ "message": "Tên miền"
},
"settings_exclusion_subdomain_name": {
- "message": "Tên miền phụ:"
+ "message": "Tên miền con"
},
"settings_exclusion_add_from_list": {
"message": "Từ danh sách"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "Xóa tất cả các loại trừ"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "Bạn có chắc muốn tiếp tục? Tất cả danh sách loại trừ sẽ bị xóa."
- },
"settings_exclusion_title": {
"message": "Loại trừ"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "Đã đạt đến giới hạn kết nối"
},
- "options_signedout_page_title": {
- "message": "Bạn đã đăng xuất khỏi AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "Xin lưu ý rằng kết nối của bạn không an toàn. Để tiếp tục lướt web ở chế độ riêng tư, hãy nhấp vào biểu tượng AdGuard VPN và đăng nhập lại."
- },
"notification_data_limit_reached_title": {
"message": "Đã đạt đến giới hạn dữ liệu hàng tháng. Cần thêm?"
},
diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json
index cf4f9f65e..a78bf8dbe 100644
--- a/src/_locales/zh_CN/messages.json
+++ b/src/_locales/zh_CN/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "关于"
},
+ "all_rights_reserved": {
+ "message": "保留所有版权。"
+ },
"options_support_title": {
"message": "支持"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "发生错误,请稍后再试。"
},
- "options_bug_report_send_button": {
- "message": "提交"
- },
- "options_bug_report_sending_button": {
- "message": "正在提交..."
- },
- "options_bug_report_textarea_placeholder": {
- "message": "描述出现什么错误..."
- },
- "options_bug_report_page_success": {
- "message": "感谢您,您的信息已发送成功!"
- },
"options_bug_report_new_report_button": {
"message": "新报告"
},
@@ -118,13 +109,13 @@
"message": "账号"
},
"account_edit": {
- "message": "管理账号"
+ "message": "打开账号设置"
},
"account_version": {
"message": "版本"
},
"account_free": {
- "message": "免费账号"
+ "message": "免费版"
},
"account_unlimited": {
"message": "无限订阅"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "获取订阅"
},
- "account_max_devices_count": {
- "message": "最多可以同时连接 %num% 台设备"
- },
"account_valid_until": {
"message": "有效期至 %date%"
},
@@ -157,7 +145,7 @@
"message": "获取无限版!"
},
"rate_description": {
- "message": "嗨,我们是 AdGuard 团队!对我们来说,了解用户对我们产品的评价是非常重要的。请您对 AdGuard 产品进行评价。"
+ "message": "喜欢 AdGuard VPN 吗?"
},
"rate_hide": {
"message": "隐藏"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "隐私策略"
},
- "settings_title": {
- "message": "设置"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "获取免费流量!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "重新发送链接"
},
- "settings_free_gbs_add_device_title": {
- "message": "添加其他设备"
- },
- "settings_free_gbs_add_device_info": {
- "message": "在另一台设备上安装 AdGuard VPN,在上面登录您的 AdGuard 账号,获得 %your_gb%GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "设备添加成功"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "恭喜您在多台设备上安装 AdGuard VPN。注意安全!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "转到产品"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "拦截 WebRTC"
},
- "settings_webrtc_desc": {
- "message": "WebRTC 是一个适用于大多数浏览器中的特殊程序,允许第三方网站检测您的真实 IP 地址并用来辨认您的身份"
- },
- "settings_context_menus_title": {
- "message": "将 AdGuard VPN 添加到浏览器上下文菜单"
- },
"settings_help_us_improve_title": {
- "message": "帮我们进一步改进"
- },
- "settings_help_us_improve_description": {
- "message": "允许 AdGuard VPN 收集崩溃报告,技术和交互数据。数据将被匿名处理。"
+ "message": "匿名发送崩溃报告"
},
"context_menu_selective_mode": {
"message": "选择模式"
@@ -352,22 +319,13 @@
"message": "编辑自定义 DNS 服务器"
},
"settings_dns_add_custom_server_info": {
- "message": "阅读更多有关 DNS 服务器的详细信息"
+ "message": "已知的 DNS 提供商"
},
"settings_dns_add_custom_server_name": {
"message": "服务器名称"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "服务器名称"
- },
"settings_dns_add_custom_server_address": {
- "message": "DNS 服务器地址"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "服务器 IP 或 TLS 域名"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "无效的 DNS 服务器地址"
+ "message": "服务器地址"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "DNS 服务器添加成功"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN 已为所选域名及子域名启用"
},
"settings_exclusion_domain_name": {
- "message": "域名:"
+ "message": "域名"
},
"settings_exclusion_subdomain_name": {
- "message": "子域名名称:"
+ "message": "子域名"
},
"settings_exclusion_add_from_list": {
"message": "从列表中"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "删除所有排除项"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "您确定想要继续吗?完整排除项列表将会被移除。"
- },
"settings_exclusion_title": {
"message": "排除项"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "已达到连接限制"
},
- "options_signedout_page_title": {
- "message": "您已退出 AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "请注意:您未连接连接 VPN,因此您的流量不受保护!为了继续匿名浏览,请点击 AdGuard VPN 图标并重新登录。"
- },
"notification_data_limit_reached_title": {
"message": "已达到每月流量限制"
},
diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json
index da1909c10..4196d7ad5 100644
--- a/src/_locales/zh_TW/messages.json
+++ b/src/_locales/zh_TW/messages.json
@@ -57,6 +57,9 @@
"about_title": {
"message": "關於"
},
+ "all_rights_reserved": {
+ "message": "保留所有的權利。"
+ },
"options_support_title": {
"message": "支援"
},
@@ -96,18 +99,6 @@
"options_bug_report_request_error": {
"message": "錯誤已發生。請稍後再試。"
},
- "options_bug_report_send_button": {
- "message": "提交"
- },
- "options_bug_report_sending_button": {
- "message": "正在提交…"
- },
- "options_bug_report_textarea_placeholder": {
- "message": "於此描述該問題…"
- },
- "options_bug_report_page_success": {
- "message": "感謝您,您的訊息被成功地提交了!"
- },
"options_bug_report_new_report_button": {
"message": "新的報告"
},
@@ -118,13 +109,13 @@
"message": "帳戶"
},
"account_edit": {
- "message": "管理帳戶"
+ "message": "開啟帳戶設定"
},
"account_version": {
"message": "版本"
},
"account_free": {
- "message": "免費帳戶"
+ "message": "免費版"
},
"account_unlimited": {
"message": "無限訂閱"
@@ -141,9 +132,6 @@
"account_get_subscription": {
"message": "取得訂閱"
},
- "account_max_devices_count": {
- "message": "最多可以同時連線 %num% 台裝置"
- },
"account_valid_until": {
"message": "有效期限至 %date%"
},
@@ -157,7 +145,7 @@
"message": "獲取無限版!"
},
"rate_description": {
- "message": "嗨,我們是來自 AdGuard 的團隊,而了解您對我們的產品之意見對於我們是非常重要的。請對它評分。"
+ "message": "喜歡 AdGuard VPN 嗎?"
},
"rate_hide": {
"message": "隱藏"
@@ -234,8 +222,8 @@
"privacy_policy": {
"message": "隱私政策"
},
- "settings_title": {
- "message": "設定"
+ "settings_general_title": {
+ "message": "General"
},
"referral_get_free_traffic": {
"message": "取得免費的流量!"
@@ -276,18 +264,6 @@
"settings_free_gbs_confirm_email_resend_link_button": {
"message": "重新傳送連結"
},
- "settings_free_gbs_add_device_title": {
- "message": "新增其他裝置"
- },
- "settings_free_gbs_add_device_info": {
- "message": "在另一台裝置上安裝 AdGuard VPN,在上面登入您的 AdGuard 帳戶,取得 %your_gb%GB"
- },
- "settings_free_gbs_devices_added_title": {
- "message": "裝置新增成功"
- },
- "settings_free_gbs_devices_added_info": {
- "message": "恭喜您在多台裝置上安裝 AdGuard VPN。注意安全!"
- },
"settings_free_gbs_add_device_products_button": {
"message": "至產品"
},
@@ -312,17 +288,8 @@
"settings_webrtc_label": {
"message": "封鎖網路即時通訊(WebRTC)"
},
- "settings_webrtc_desc": {
- "message": "網路即時通訊(WebRTC)是在大多數網際網路瀏覽器中的一款特殊的程式,其可允許第三方的網站偵測您真實的 IP 位址並使用它識別您"
- },
- "settings_context_menus_title": {
- "message": "新增 AdGuard VPN 項目至瀏覽器之內容功能表"
- },
"settings_help_us_improve_title": {
- "message": "幫助我們改善"
- },
- "settings_help_us_improve_description": {
- "message": "允許 AdGuard VPN 收集崩潰報告、技術的資料和相互作用資訊。這是匿名的。"
+ "message": "匿名傳送崩潰報告"
},
"context_menu_selective_mode": {
"message": "選擇性模式"
@@ -352,22 +319,13 @@
"message": "編輯自訂的 DNS 伺服器"
},
"settings_dns_add_custom_server_info": {
- "message": "閱讀更多關於 DNS 伺服器"
+ "message": "已知的 DNS 供應商"
},
"settings_dns_add_custom_server_name": {
"message": "伺服器名稱"
},
- "settings_dns_add_custom_server_name_placeholder": {
- "message": "伺服器名稱"
- },
"settings_dns_add_custom_server_address": {
- "message": "DNS 伺服器位址"
- },
- "settings_dns_add_custom_server_address_placeholder": {
- "message": "伺服器 IP 或 TLS 域名"
- },
- "settings_dns_add_custom_server_invalid_address": {
- "message": "無效的 DNS 伺服器位址"
+ "message": "伺服器位址"
},
"settings_dns_add_custom_server_duplicate_address": {
"message": "已新增 DNS 伺服器"
@@ -487,10 +445,10 @@
"message": "AdGuard VPN 已為所選的網域和子網域啟用"
},
"settings_exclusion_domain_name": {
- "message": "域名:"
+ "message": "域名"
},
"settings_exclusion_subdomain_name": {
- "message": "子網域名稱:"
+ "message": "子網域"
},
"settings_exclusion_add_from_list": {
"message": "從該清單中"
@@ -549,9 +507,6 @@
"settings_exclusions_remove_all_exclusions": {
"message": "移除所有例外"
},
- "settings_exclusions_remove_all_exclusions_message": {
- "message": "您確定您想要繼續嗎?例外的整個清單將被移除。"
- },
"settings_exclusion_title": {
"message": "例外"
},
@@ -942,12 +897,6 @@
"popup_connections_limit_title": {
"message": "已達到連線限制"
},
- "options_signedout_page_title": {
- "message": "您已登出 AdGuard VPN"
- },
- "options_signedout_page_description": {
- "message": "請注意:您未連線 VPN,因此您的流量不受保護!要繼續秘密地瀏覽,點擊 AdGuard VPN 圖示並再次登入。"
- },
"notification_data_limit_reached_title": {
"message": "已達到每月的流量限制"
},
diff --git a/src/assets/images/add-device.svg b/src/assets/images/add-device.svg
index ce4758680..25e162ef3 100644
--- a/src/assets/images/add-device.svg
+++ b/src/assets/images/add-device.svg
@@ -1,283 +1 @@
-
+
\ No newline at end of file
diff --git a/src/assets/images/all-locations.svg b/src/assets/images/all-locations.svg
index c9d880a7a..c1cec9086 100644
--- a/src/assets/images/all-locations.svg
+++ b/src/assets/images/all-locations.svg
@@ -1,20 +1 @@
-
+
\ No newline at end of file
diff --git a/src/assets/images/confirm-email-task.svg b/src/assets/images/confirm-email-task.svg
index ad1fc93be..06794b5c6 100644
--- a/src/assets/images/confirm-email-task.svg
+++ b/src/assets/images/confirm-email-task.svg
@@ -1,120 +1 @@
-
+
\ No newline at end of file
diff --git a/src/assets/images/connect-devices.svg b/src/assets/images/connect-devices.svg
index 1a1b3c80f..84bcea0af 100644
--- a/src/assets/images/connect-devices.svg
+++ b/src/assets/images/connect-devices.svg
@@ -1,8 +1 @@
-
+
\ No newline at end of file
diff --git a/src/assets/images/ip-icon.svg b/src/assets/images/ip-icon.svg
deleted file mode 100644
index 42028182e..000000000
--- a/src/assets/images/ip-icon.svg
+++ /dev/null
@@ -1,52 +0,0 @@
-
diff --git a/src/assets/images/ninja-like.svg b/src/assets/images/ninja-like.svg
index 7d58b4008..b7edbd006 100644
--- a/src/assets/images/ninja-like.svg
+++ b/src/assets/images/ninja-like.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/assets/images/products.svg b/src/assets/images/products.svg
index 20b6653f4..d27ca2d6e 100644
--- a/src/assets/images/products.svg
+++ b/src/assets/images/products.svg
@@ -1,10 +1 @@
-
+
\ No newline at end of file
diff --git a/src/assets/images/referral.svg b/src/assets/images/referral.svg
index 9ef9753e1..adf21d57f 100644
--- a/src/assets/images/referral.svg
+++ b/src/assets/images/referral.svg
@@ -1,128 +1 @@
-
+
\ No newline at end of file
diff --git a/src/assets/images/signed-out.svg b/src/assets/images/signed-out.svg
new file mode 100644
index 000000000..5e7823305
--- /dev/null
+++ b/src/assets/images/signed-out.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/star-active.svg b/src/assets/images/star-active.svg
deleted file mode 100644
index a7275ee2b..000000000
--- a/src/assets/images/star-active.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/src/assets/images/star.svg b/src/assets/images/star.svg
deleted file mode 100644
index 9a2c0d9a5..000000000
--- a/src/assets/images/star.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/src/assets/images/unlimited-data.svg b/src/assets/images/unlimited-data.svg
index 1ec3ba132..4c6900b26 100644
--- a/src/assets/images/unlimited-data.svg
+++ b/src/assets/images/unlimited-data.svg
@@ -1,11 +1 @@
-
+
\ No newline at end of file
diff --git a/src/assets/images/unlimited.svg b/src/assets/images/unlimited.svg
index 00911a906..4aebb8714 100644
--- a/src/assets/images/unlimited.svg
+++ b/src/assets/images/unlimited.svg
@@ -1,17 +1 @@
-
+
\ No newline at end of file
diff --git a/src/background/config.ts b/src/background/config.ts
index 155c9cdae..882eed5c2 100644
--- a/src/background/config.ts
+++ b/src/background/config.ts
@@ -7,6 +7,7 @@ import { runtime } from './browserApi/runtime';
*/
const enum ForwarderUrlQueryKey {
AdguardDnsKb = 'ADGUARD_DNS_KB',
+ AdguardDnsProvidersKb = 'ADGUARD_DNS_PROVIDERS_KB',
ComparePage = 'COMPARE_PAGE',
DeviceCount = 'DEVICE_COUNT',
EditAccount = 'EDIT_ACCOUNT',
@@ -66,6 +67,7 @@ const {
STAGE_ENV,
// keep them sorted
ADGUARD_DNS_KB,
+ ADGUARD_DNS_PROVIDERS_KB,
COMPARE_PAGE,
DEVICE_COUNT,
EDIT_ACCOUNT,
@@ -103,6 +105,7 @@ const FORWARDER_DOMAIN = CONFIG.FORWARDER_DOMAIN; // eslint-disable-line prefer-
*/
const FORWARDER_URL_QUERIES: ForwarderUrlQueries = {
ADGUARD_DNS_KB,
+ ADGUARD_DNS_PROVIDERS_KB,
COMPARE_PAGE,
DEVICE_COUNT,
EDIT_ACCOUNT,
diff --git a/src/common/styles/media.pcss b/src/common/styles/media.pcss
index 7996e1973..45ecd5585 100644
--- a/src/common/styles/media.pcss
+++ b/src/common/styles/media.pcss
@@ -4,4 +4,7 @@
*/
@custom-media --tablet (max-width: 875px);
-@custom-media --mobile (max-width: 600px);
+@custom-media --tablet-sm (max-width: 600px);
+@custom-media --mobile (max-width: 400px);
+
+@custom-media --touch-screen (hover: none);
\ No newline at end of file
diff --git a/src/options/components/About/About.tsx b/src/options/components/About/About.tsx
index b32ea74b3..ab230ee4c 100644
--- a/src/options/components/About/About.tsx
+++ b/src/options/components/About/About.tsx
@@ -2,10 +2,10 @@ import React, { useContext } from 'react';
import { observer } from 'mobx-react';
import { FORWARDER_URL_QUERIES } from '../../../background/config';
+import { translator } from '../../../common/translator';
+import { getForwarderUrl } from '../../../common/helpers';
import { rootStore } from '../../stores';
import { Title } from '../ui/Title';
-import { getForwarderUrl } from '../../../common/helpers';
-import { reactTranslator } from '../../../common/reactTranslator';
import './about.pcss';
@@ -18,44 +18,61 @@ export const About = observer(() => {
const eulaUrl = getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.EULA);
const privacyUrl = getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.PRIVACY);
- const aboutVersionStr = `${reactTranslator.getMessage('account_version')} ${settingsStore.appVersion}`;
+ const aboutVersionStr = `${translator.getMessage('account_version')} ${settingsStore.appVersion}`;
+
+ const currentYear = new Date().getFullYear();
+ const copyRightText = `© 2009-${currentYear} Adguard Software Ltd.`;
return (
<>
-
+
-
-
- {reactTranslator.getMessage('short_name')}
+
diff --git a/src/options/components/About/about.pcss b/src/options/components/About/about.pcss
index 670cb39e0..1ea66a97f 100644
--- a/src/options/components/About/about.pcss
+++ b/src/options/components/About/about.pcss
@@ -1,41 +1,29 @@
.about {
- padding-top: 32px;
- margin-left: 16px;
+ &__info {
+ padding: 16px 16px 0;
- &__header {
- margin-bottom: 48px;
- }
+ &-name {
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.3;
+ }
- &__title {
- font-size: 16px;
- line-height: 19px;
- font-weight: 500;
+ &-version {
+ color: var(--text-description-description-default);
+ font-size: 14px;
+ line-height: 1.3;
+ }
}
- &__version {
+ &__copyright {
+ padding: 8px 16px 24px;
+ color: var(--text-description-description-default);
font-size: 14px;
- line-height: 17px;
- font-weight: 400;
- color: var(--gray88);
- }
-
- &__description {
- max-width: 385px;
- line-height: 22px;
- }
-
- &__nav {
- display: flex;
- flex-direction: column;
+ line-height: 1.3;
}
&__link {
- margin-bottom: 16px;
- color: var(--green700);
- transition: var(--t3) color;
-
- &:hover {
- color: var(--green900);
- }
+ text-decoration: none;
}
}
diff --git a/src/options/components/Account/Account.tsx b/src/options/components/Account/Account.tsx
index 5579f0719..e2f78f43b 100644
--- a/src/options/components/Account/Account.tsx
+++ b/src/options/components/Account/Account.tsx
@@ -1,15 +1,16 @@
-import React, { type ReactNode, useContext } from 'react';
+import React, { useContext } from 'react';
import { observer } from 'mobx-react';
-import { rootStore } from '../../stores';
-import { Title } from '../ui/Title';
import { FORWARDER_URL_QUERIES } from '../../../background/config';
-import { getForwarderUrl } from '../../../common/helpers';
-import { reactTranslator } from '../../../common/reactTranslator';
import { translator } from '../../../common/translator';
+import { reactTranslator } from '../../../common/reactTranslator';
import { SubscriptionType } from '../../../common/constants';
+import { getForwarderUrl } from '../../../common/helpers';
+import { rootStore } from '../../stores';
+import { Title } from '../ui/Title';
+import { Button } from '../ui/Button';
-import { Features } from './Features/Features';
+import { Features } from './Features';
import './account.pcss';
@@ -20,27 +21,26 @@ export const Account = observer(() => {
currentUsername,
isPremiumToken,
premiumFeatures,
- hidePremiumFeatures,
- openPremiumPromoPage,
subscriptionType,
subscriptionTimeExpiresIso,
forwarderDomain,
} = settingsStore;
+ const { maxDevicesCount } = authStore;
const editAccountUrl = getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.EDIT_ACCOUNT);
- const { maxDevicesCount } = authStore;
+ const showGetSubscriptionButton = !isPremiumToken && !premiumFeatures;
- const signOut = async (): Promise
=> {
+ const handleSignOut = async (): Promise => {
await authStore.deauthenticate();
};
- const hideFeatures = async (): Promise => {
- await hidePremiumFeatures();
+ const handleHideFeatures = async (): Promise => {
+ await settingsStore.hidePremiumFeatures();
};
- const upgrade = async (): Promise => {
- await openPremiumPromoPage();
+ const handleUpgrade = async (): Promise => {
+ await settingsStore.openPremiumPromoPage();
};
let expiresDate;
@@ -63,7 +63,7 @@ export const Account = observer(() => {
[SubscriptionType.TwoYears]: translator.getMessage('account_two_year_paid'),
};
- const getAccountType = (): ReactNode => {
+ const getAccountType = (): React.ReactNode => {
if (!isPremiumToken) {
return translator.getMessage('account_free');
}
@@ -77,86 +77,82 @@ export const Account = observer(() => {
return (
<>
-
-
-
-
- {getAccountType()}
-
- {expiresDate && (
-
- {reactTranslator.getMessage('account_valid_until', {
- date: expiresDate,
- b: (chunks: any) => (
-
- {chunks}
-
- ),
- })}
+
+
+ {getAccountType()}
- )}
- {maxDevicesCount !== undefined && (
-
- {reactTranslator.getMessage('account_max_devices_count', {
- num: maxDevicesCount,
+ {expiresDate && isPremiumToken && (
+
+ {reactTranslator.getMessage('account_valid_until', {
+ date: expiresDate,
+ b: (chunks: any) => (
+ {chunks}
+ ),
+ })}
+
+ )}
+ {maxDevicesCount !== undefined && (
+
+ {reactTranslator.getMessage('account_max_devices_count', {
+ num: maxDevicesCount,
+ b: (chunks: any) => (
+ {chunks}
+ ),
+ })}
+
+ )}
+
+ {reactTranslator.getMessage('account_logged_in_as', {
+ username: currentUsername,
b: (chunks: any) => (
-
- {chunks}
-
+ {chunks}
),
})}
- )}
-
- {reactTranslator.getMessage('account_logged_in_as', {
- username: currentUsername,
- b: (chunks: any) => (
-
- {chunks}
-
- ),
- })}
-
-
-
- {(!isPremiumToken && premiumFeatures) && (
-
-
-
-
-
-
)}
+ />
+
+ {!isPremiumToken && premiumFeatures && (
+
+
+
+
+
+
+
+ )}
>
);
});
diff --git a/src/options/components/Account/Features/Features.tsx b/src/options/components/Account/Features/Features.tsx
index cdef28b55..e388105ba 100644
--- a/src/options/components/Account/Features/Features.tsx
+++ b/src/options/components/Account/Features/Features.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { UNLIMITED_FEATURES } from '../../../../common/components/constants';
-import { reactTranslator } from '../../../../common/reactTranslator';
+import { translator } from '../../../../common/translator';
import { Title } from '../../ui/Title';
import './features.pcss';
@@ -9,7 +9,11 @@ import './features.pcss';
export const Features = () => {
return (
-
+
{UNLIMITED_FEATURES.map((feature) => {
const { image, title, info } = feature;
diff --git a/src/options/components/Account/Features/features.pcss b/src/options/components/Account/Features/features.pcss
index 72ef2bbb3..c2d20935c 100644
--- a/src/options/components/Account/Features/features.pcss
+++ b/src/options/components/Account/Features/features.pcss
@@ -1,33 +1,39 @@
.features {
- padding-right: 16px;
+ &__title {
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+
+ &__list {
+ margin: 10px 0;
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+ }
&__item {
- margin-bottom: 24px;
display: flex;
- align-items: center;
+ align-items: flex-start;
+ padding: 16px;
+ column-gap: 8px;
}
&__image {
- width: 56px;
- height: 56px;
- margin-right: 24px;
+ width: 48px;
+ height: 48px;
flex-shrink: 0;
}
&__title {
- font-weight: 500;
- font-size: 16px;
- line-height: 24px;
+ color: var(--text-main-text-main-default);
+ font-size: 18px;
+ font-weight: 700;
+ line-height: 1.2;
}
&__desc {
- color: var(--gray88);
- font-size: 14px;
- line-height: 18px;
- }
-
- & .content__title {
- margin: 0 0 32px 0;
- padding-left: 0;
+ color: var(--text-description-description-default);
+ font-size: 16px;
+ line-height: 1.5;
}
}
diff --git a/src/options/components/Account/account.pcss b/src/options/components/Account/account.pcss
index d10f6065b..c35a4bd62 100644
--- a/src/options/components/Account/account.pcss
+++ b/src/options/components/Account/account.pcss
@@ -1,44 +1,39 @@
.account {
- margin-left: 16px;
- font-weight: 400;
- font-size: 14px;
- line-height: 18px;
+ &__description {
+ color: var(--text-main-text-main-default);
+ }
&__info {
- padding-top: 8px;
-
- &-item {
- font-size: 16px;
- line-height: 21px;
- font-weight: 400;
- margin-bottom: 16px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- &--bold {
- font-weight: 600;
- }
+ & + & {
+ margin-top: 24px;
}
}
&__actions {
- margin-top: 24px;
display: flex;
- gap: 16px;
+ flex-direction: column;
+ margin-top: 24px;
+ padding: 0 16px;
+ row-gap: 16px;
- @media (--mobile) {
- flex-direction: column;
- padding-right: 16px;
+ &--features {
+ margin-top: 0;
+ }
+ }
- .button {
- width: 100%;
- }
+ &__action {
+ text-decoration: none;
+ width: 260px;
+ /* Overriding button's default font style */
+ font-size: 16px;
+ font-weight: 600;
+
+ @media (--mobile) {
+ width: 100%;
}
}
&__features {
- padding-top: 48px;
+ padding-top: 40px;
}
}
diff --git a/src/options/components/App/App.tsx b/src/options/components/App/App.tsx
index 0d28ceb8f..b85f64270 100644
--- a/src/options/components/App/App.tsx
+++ b/src/options/components/App/App.tsx
@@ -1,31 +1,27 @@
import React, { useContext, useEffect, useLayoutEffect } from 'react';
import { observer } from 'mobx-react';
import { HashRouter, Route, Switch } from 'react-router-dom';
-import Modal from 'react-modal';
-import { RequestStatus } from '../../stores/consts';
+import { useAppearanceTheme } from '../../../common/useAppearanceTheme';
import { rootStore } from '../../stores';
+import { RequestStatus } from '../../stores/consts';
+import { useCustomDnsFromQuery } from '../../hooks/useQueryStringData';
+import { useMessageHandler } from '../../hooks/useMessageHandler';
+import { Notifications } from '../ui/Notifications';
+import { Icons } from '../ui/Icon';
+import { Preloader } from '../Preloader';
+import { SignedOut } from '../SignedOut';
import { Sidebar } from '../Sidebar';
-import { Settings } from '../Settings';
-import { FreeGbs } from '../FreeGbs';
+import { General } from '../General';
+import { Exclusions } from '../Exclusions';
import { Account } from '../Account';
-import { About } from '../About';
-import { SignedOut } from '../SignedOut';
-import { Preloader } from '../Preloader';
-import Icons from '../ui/Icons';
import { Support } from '../Support';
-import { Notifications } from '../ui/Notifications';
-import { useAppearanceTheme } from '../../../common/useAppearanceTheme';
-import { Exclusions } from '../Exclusions';
-import { useCustomDnsFromQuery } from '../../hooks/useQueryStringData';
-
-import { useMessageHandler } from './useMessageHandler';
+import { About } from '../About';
+import { FreeGbs } from '../FreeGbs';
import '../../styles/main.pcss';
import './app.pcss';
-Modal.setAppElement('#root');
-
const getContent = (
authenticated: boolean,
requestProcessState: RequestStatus,
@@ -39,7 +35,7 @@ const getContent = (
-
+
@@ -47,7 +43,7 @@ const getContent = (
{!isPremiumToken && (
)}
-
+
diff --git a/src/options/components/App/app.pcss b/src/options/components/App/app.pcss
index 0570d33f8..96dc7693a 100644
--- a/src/options/components/App/app.pcss
+++ b/src/options/components/App/app.pcss
@@ -15,59 +15,29 @@
padding: 40px 0;
flex: 1 1 auto;
display: flex;
- align-items: flex-start;
- justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
@media (--tablet) {
padding: 32px 0;
}
&__wrapper {
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
width: 100%;
max-width: 630px;
- }
-
- &__title {
- width: 100%;
- display: inline-flex;
- align-items: center;
- justify-content: space-between;
- padding-left: 16px;
- margin-top: 0;
- margin-bottom: 16px;
- font-size: 32px;
- line-height: 38px;
- &--pointer {
- cursor: pointer;
- margin: 0;
- padding-left: 0;
+ @media (--tablet) {
+ padding: 0 16px;
}
}
-
- &__subtitle {
- margin: 0 0 16px 16px;
- font-size: 16px;
- line-height: 24px;
- }
-
- &__link {
- font-size: 1.3rem;
- font-weight: 400;
- color: var(--brand-primary);
- }
}
-.modal {
- &__title {
- letter-spacing: 0;
- font-size: 32px;
- font-weight: 700;
- line-height: 1.2;
- margin-bottom: 12px;
-
- @media (--mobile) {
- font-size: 24px;
- }
- }
+.text-ellipsis {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
diff --git a/src/options/components/Exclusions/Actions/Actions.tsx b/src/options/components/Exclusions/Actions/Actions.tsx
index 2068a2fef..a5e1eb2d8 100644
--- a/src/options/components/Exclusions/Actions/Actions.tsx
+++ b/src/options/components/Exclusions/Actions/Actions.tsx
@@ -2,28 +2,33 @@
import React, { useContext, useState, useRef } from 'react';
import { observer } from 'mobx-react';
-import classnames from 'classnames';
import identity from 'lodash/identity';
import format from 'date-fns/format';
import JSZip from 'jszip';
import FileSaver from 'file-saver';
import { rootStore } from '../../../stores';
-import { reactTranslator } from '../../../../common/reactTranslator';
import { translator } from '../../../../common/translator';
import { isValidExclusion } from '../../../../common/utils/string';
import { type ExclusionsContentMap } from '../../../../common/constants';
import { log } from '../../../../common/logger';
import { messenger } from '../../../../common/messenger';
import { ExclusionsMode } from '../../../../common/exclusionsConstants';
-import { useOutsideClick } from '../../../../common/components/ui/useOutsideClick';
+import { Select } from '../../ui/Select';
-import { SelectListModal } from './SelectListModal/SelectListModal';
+import { SelectListModal } from './SelectListModal';
import { ExclusionDataType, type ExclusionsImportData, readExclusionsFile } from './fileHelpers';
import { RemoveAllModal } from './RemoveAllModal';
import './actions.pcss';
+enum Action {
+ Default = 'default',
+ Export = 'export',
+ Import = 'import',
+ Remove = 'remove',
+}
+
const prepareExclusionsAfterImport = (exclusionsString: string) => {
return exclusionsString
.split('\n')
@@ -72,26 +77,19 @@ const exportExclusions = async () => {
export const Actions = observer(() => {
const { exclusionsStore, notificationsStore } = useContext(rootStore);
-
- const [isMoreActionsMenuOpen, setIsMoreActionsMenuOpen] = useState(false);
+ const { selectListModalOpen } = exclusionsStore;
const importEl = useRef
(null);
- const moreActionsMenu = useRef(null);
- const [isSelectListModalOpen, setSelectListModalState] = useState(false);
const [fileContent, setFileContent] = useState('');
- const ref = useRef(null);
-
- useOutsideClick(ref, () => setIsMoreActionsMenuOpen(false));
-
const closeSelectListModal = () => {
- setSelectListModalState(false);
+ exclusionsStore.closeSelectListModal();
setFileContent('');
};
const openSelectListModal = () => {
- setSelectListModalState(true);
+ exclusionsStore.openSelectListModal();
};
const handleRegularClick = async () => {
@@ -102,7 +100,7 @@ export const Actions = observer(() => {
{ count: exclusionsAddedCount },
),
{
- action: reactTranslator.getMessage('settings_exclusions_undo'),
+ action: translator.getMessage('settings_exclusions_undo'),
handler: exclusionsStore.restoreExclusions,
},
);
@@ -118,7 +116,7 @@ export const Actions = observer(() => {
{ count: exclusionsAddedCount },
),
{
- action: reactTranslator.getMessage('settings_exclusions_undo'),
+ action: translator.getMessage('settings_exclusions_undo'),
handler: exclusionsStore.restoreExclusions,
},
);
@@ -132,28 +130,26 @@ export const Actions = observer(() => {
return null;
};
- const onExportExclusionsClick = async () => {
- setIsMoreActionsMenuOpen(false);
- await exportExclusions();
- };
-
- const onImportExclusionsClick = () => {
- setIsMoreActionsMenuOpen(false);
- if (importEl.current) {
- importEl.current.click();
+ const handleAction = async (action: Action) => {
+ switch (action) {
+ case Action.Export: {
+ await exportExclusions();
+ break;
+ }
+ case Action.Import: {
+ if (importEl.current) {
+ importEl.current.click();
+ }
+ break;
+ }
+ case Action.Remove: {
+ await exclusionsStore.openRemoveAllModal();
+ break;
+ }
+ default: break;
}
};
- const onRemoveAllClick = async () => {
- setIsMoreActionsMenuOpen(false);
- await exclusionsStore.openRemoveAllModal();
- };
-
- const onMoreActionsClick = (e: React.MouseEvent) => {
- e.stopPropagation();
- setIsMoreActionsMenuOpen(!isMoreActionsMenuOpen);
- };
-
const handleExclusionsData = async (exclusionsData: ExclusionsImportData[]) => {
const txtExclusionsData = exclusionsData.find((d) => d.type === ExclusionDataType.Txt);
@@ -201,7 +197,7 @@ export const Actions = observer(() => {
{ count: exclusionsAdded },
),
{
- action: reactTranslator.getMessage('settings_exclusions_undo'),
+ action: translator.getMessage('settings_exclusions_undo'),
handler: exclusionsStore.restoreExclusions,
},
);
@@ -214,57 +210,34 @@ export const Actions = observer(() => {
}
};
- const moreActionsListClassnames = classnames('actions__more-actions-list', {
- visible: isMoreActionsMenuOpen,
- });
-
- const exportClassnames = classnames({
- 'actions__hidden-action': exclusionsStore.isAllExclusionsListsEmpty,
- });
-
- const removeAllClassnames = classnames({
- 'actions__hidden-action': exclusionsStore.isCurrentModeExclusionsListEmpty,
- });
-
return (
<>
-
-
-
-
-
-
-
+
+
{
/>
{
const { exclusionsStore, notificationsStore } = useContext(rootStore);
@@ -20,41 +19,32 @@ export const RemoveAllModal = observer(() => {
await exclusionsStore.clearExclusionsList();
closeModal();
notificationsStore.notifySuccess(
- reactTranslator.getMessage('options_exclusions_remove_all_success'),
+ translator.getMessage('options_exclusions_remove_all_success'),
{
- action: reactTranslator.getMessage('settings_exclusions_undo'),
+ action: translator.getMessage('settings_exclusions_undo'),
handler: exclusionsStore.restoreExclusions,
},
);
};
return (
-
+
+
+ >
+ )}
isOpen={isOpen}
- closeModal={closeModal}
- title={reactTranslator.getMessage('settings_exclusions_remove_all_exclusions')}
- >
-
-
- {reactTranslator.getMessage('settings_exclusions_remove_all_exclusions_message')}
-
-
-
-
-
-
-
+ className="exclusions__modal exclusions__modal--empty-body"
+ size="medium"
+ onClose={closeModal}
+ />
);
});
diff --git a/src/options/components/Exclusions/Actions/RemoveAllModal/remove-all-modal.pcss b/src/options/components/Exclusions/Actions/RemoveAllModal/remove-all-modal.pcss
deleted file mode 100644
index 6ad1acd90..000000000
--- a/src/options/components/Exclusions/Actions/RemoveAllModal/remove-all-modal.pcss
+++ /dev/null
@@ -1,20 +0,0 @@
-.remove-all-modal {
-
- &__message {
- margin-bottom: 24px;
- max-width: 480px;
- }
-
- & .button {
- min-width: 180px;
- }
-
- & .button--primary {
- background: var(--redDark);
-
- &:focus,
- &:hover {
- background: var(--red);
- }
- }
-}
diff --git a/src/options/components/Exclusions/Actions/SelectListModal/SelectListModal.tsx b/src/options/components/Exclusions/Actions/SelectListModal/SelectListModal.tsx
index 4ce49b971..83229e987 100644
--- a/src/options/components/Exclusions/Actions/SelectListModal/SelectListModal.tsx
+++ b/src/options/components/Exclusions/Actions/SelectListModal/SelectListModal.tsx
@@ -1,12 +1,11 @@
import React, { useState, useContext, useEffect } from 'react';
-import classnames from 'classnames';
-
-import { reactTranslator } from '../../../../../common/reactTranslator';
-import { ExclusionsModal } from '../../ExclusionsModal/ExclusionsModal';
-import { Title } from '../../../ui/Title';
+import { translator } from '../../../../../common/translator';
import { ExclusionsMode } from '../../../../../common/exclusionsConstants';
import { rootStore } from '../../../../stores';
+import { Radio } from '../../../ui/Radio';
+import { Modal } from '../../../ui/Modal';
+import { Button } from '../../../ui/Button';
interface SelectListModalProps {
isOpen: boolean;
@@ -31,8 +30,8 @@ export const SelectListModal = ({
}, [currentMode, isOpen]);
const modesInfo = {
- [ExclusionsMode.Regular]: reactTranslator.getMessage('options_exclusions_import_select_regular'),
- [ExclusionsMode.Selective]: reactTranslator.getMessage('options_exclusions_import_select_selective'),
+ [ExclusionsMode.Regular]: translator.getMessage('options_exclusions_import_select_regular'),
+ [ExclusionsMode.Selective]: translator.getMessage('options_exclusions_import_select_selective'),
};
const handleImportClick = async () => {
@@ -43,58 +42,36 @@ export const SelectListModal = ({
}
};
- const renderRadioButton = (exclusionsType: ExclusionsMode) => {
- const enabled = exclusionsType === selectedList;
- const titleClass = classnames('radio__title', { 'radio__title--active': enabled });
- const xlinkHref = classnames({
- '#bullet_on': enabled,
- '#bullet_off': !enabled,
- });
-
- return (
- setSelectedList(exclusionsType)}
- >
-
-
-
- {modesInfo[exclusionsType]}
-
-
-
- );
- };
+ const renderRadioButton = (exclusionsType: ExclusionsMode) => (
+
+ );
return (
-
+
+
+ >
+ )}
+ className="exclusions__modal--radio"
+ onClose={closeModal}
>
-
{renderRadioButton(ExclusionsMode.Selective)}
{renderRadioButton(ExclusionsMode.Regular)}
-
-
-
-
-
+
);
};
diff --git a/src/options/components/Exclusions/Actions/SelectListModal/index.ts b/src/options/components/Exclusions/Actions/SelectListModal/index.ts
new file mode 100644
index 000000000..d2de40125
--- /dev/null
+++ b/src/options/components/Exclusions/Actions/SelectListModal/index.ts
@@ -0,0 +1 @@
+export { SelectListModal } from './SelectListModal';
diff --git a/src/options/components/Exclusions/Actions/actions.pcss b/src/options/components/Exclusions/Actions/actions.pcss
index b7f1a4f9c..03e0f26e8 100644
--- a/src/options/components/Exclusions/Actions/actions.pcss
+++ b/src/options/components/Exclusions/Actions/actions.pcss
@@ -1,73 +1,19 @@
.actions {
- position: absolute;
- top: 0;
- right: 0;
-
- &__button {
- color: var(--gray88);
- font-size: 16px;
- border: none;
- background-size: 10px;
- padding: 5px 25px 5px 5px;
- }
-
- &__more-actions-list {
- position: absolute;
- right: 0;
- top: 30px;
- min-width: 190px;
- z-index: 1;
- background: var(--white);
- list-style: none;
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
- visibility: hidden;
- outline: none;
-
- &.visible {
- visibility: visible;
- }
-
- & button {
- padding: 15px 35px 15px 25px;
- cursor: pointer;
- white-space: nowrap;
- background: none;
- border: none;
- font-size: inherit;
- width: 100%;
- display: flex;
-
- &:hover {
- background-color: var(--grayF3);
-
- @media (prefers-color-scheme: dark) {
- background-color: var(--grayD8);
- }
- }
- }
- }
-
- &__hidden-action {
- display: none;
+ .select__btn {
+ column-gap: 4px;
+ color: var(--text-placeholder-placeholder-default);
}
-}
-.light-mode {
- .actions {
- &__more-actions-list button {
- &:hover {
- background-color: var(--grayF3);
- }
- }
+ .select__list {
+ right: 24px;
+ width: 180px;
+ border-radius: 0;
+ box-shadow: var(--small-light-shadow-br);
}
-}
-.dark-mode {
- .actions {
- &__more-actions-list button {
- &:hover {
- background-color: var(--grayD8);
- }
- }
+ .select__item {
+ padding: 16px;
+ font-size: 14px;
+ line-height: 16px;
}
-}
+}
\ No newline at end of file
diff --git a/src/options/components/Exclusions/ChildrenList/ChildrenList.tsx b/src/options/components/Exclusions/ChildrenList/ChildrenList.tsx
index 6885ff403..f0330d1a5 100644
--- a/src/options/components/Exclusions/ChildrenList/ChildrenList.tsx
+++ b/src/options/components/Exclusions/ChildrenList/ChildrenList.tsx
@@ -3,16 +3,15 @@ import { observer } from 'mobx-react';
import { rootStore } from '../../../stores';
import { Title } from '../../ui/Title';
-import { reactTranslator } from '../../../../common/reactTranslator';
+import { translator } from '../../../../common/translator';
import { ExclusionsMode, ExclusionsType } from '../../../../common/exclusionsConstants';
import { isTopLevel } from '../../../../common/utils/url';
+import { Button } from '../../ui/Button';
+import { Exclusion } from '../Exclusion';
-import { ChildrenListItem } from './ChildrenListItem';
import { SubdomainModal } from './SubdomainModal';
import { ResetServiceModal } from './ResetServiceModal';
-import './children-list.pcss';
-
export const ChildrenList = observer(() => {
const { exclusionsStore } = useContext(rootStore);
const { selectedExclusion } = exclusionsStore;
@@ -22,8 +21,8 @@ export const ChildrenList = observer(() => {
}
const subtitle = exclusionsStore.currentMode === ExclusionsMode.Regular
- ? reactTranslator.getMessage('settings_exclusion_group_settings_subtitle_regular_mode')
- : reactTranslator.getMessage('settings_exclusion_group_settings_subtitle_selective_mode');
+ ? translator.getMessage('settings_exclusion_group_settings_subtitle_regular_mode')
+ : translator.getMessage('settings_exclusion_group_settings_subtitle_selective_mode');
const goBackHandler = () => {
exclusionsStore.goBackHandler();
@@ -43,63 +42,41 @@ export const ChildrenList = observer(() => {
const isExclusionsGroup = selectedExclusion.type === ExclusionsType.Group
&& !isTopLevel(selectedExclusion.hostname);
- const renderResetButton = () => {
- return (
-
- );
- };
-
- const renderAddSubdomainButton = () => {
- return (
-
- );
- };
-
- const renderExclusions = () => exclusionsStore.sortedExclusions?.map((exclusion) => {
- if (exclusion) {
- return ;
- }
- return undefined;
- });
-
return (
<>
-
-
-
-
+ {exclusionsStore.sortedExclusions?.map((exclusion) => {
+ return (
+
+ );
+ })}
+ {isExclusionsGroup && (
+
+
-
-
- {renderExclusions()}
-
- {isModifiedService && renderResetButton()}
- {isExclusionsGroup && renderAddSubdomainButton()}
+ )}
+ {isModifiedService && (
+
+ )}
>
diff --git a/src/options/components/Exclusions/ChildrenList/ChildrenListItem/ChildrenListItem.tsx b/src/options/components/Exclusions/ChildrenList/ChildrenListItem/ChildrenListItem.tsx
deleted file mode 100644
index 1e8df8873..000000000
--- a/src/options/components/Exclusions/ChildrenList/ChildrenListItem/ChildrenListItem.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import classnames from 'classnames';
-
-import { rootStore } from '../../../../stores';
-import { StateCheckbox } from '../../StateCheckbox';
-import { type ExclusionDtoInterface, ExclusionState, ExclusionsType } from '../../../../../common/exclusionsConstants';
-import { reactTranslator } from '../../../../../common/reactTranslator';
-
-import './children-list-item.pcss';
-
-interface ChildrenListItemProps {
- exclusion: ExclusionDtoInterface;
-}
-
-export const ChildrenListItem = observer(({ exclusion }: ChildrenListItemProps) => {
- const { exclusionsStore, notificationsStore } = useContext(rootStore);
- const { selectedExclusion } = exclusionsStore;
-
- const toggleState = () => {
- exclusionsStore.toggleExclusionState(exclusion.id);
- };
-
- const removeExclusion = (exclusion: ExclusionDtoInterface) => async () => {
- const deletedExclusionsCount = await exclusionsStore.removeExclusion(exclusion);
- notificationsStore.notifySuccess(
- reactTranslator.getMessage(
- 'options_exclusions_deleted_exclusions',
- { count: deletedExclusionsCount },
- ),
- {
- action: reactTranslator.getMessage('settings_exclusions_undo'),
- handler: exclusionsStore.restoreExclusions,
- },
- );
- };
-
- const showGroupSettings = (id: string) => () => {
- exclusionsStore.setSelectedExclusionId(id);
- };
-
- const getExclusionStatus = (hostname: string) => {
- if (hostname === selectedExclusion?.hostname) {
- return reactTranslator.getMessage('settings_exclusion_status_domain');
- }
- if (hostname.startsWith('*')) {
- return reactTranslator.getMessage('settings_exclusion_status_all_subdomains');
- }
- return reactTranslator.getMessage('settings_exclusion_status_subdomain');
- };
-
- const wildcardExclusion = `*.${selectedExclusion?.hostname}`;
-
- const exclusionClassNames = (hostname: string) => classnames('children-list-item', {
- 'children-list-item__domains-details': selectedExclusion?.type === ExclusionsType.Group,
- useless: hostname !== selectedExclusion?.hostname
- && !hostname.startsWith(wildcardExclusion)
- && selectedExclusion?.children.some((exclusion) => {
- return exclusion.hostname.startsWith(wildcardExclusion)
- && exclusion.state === ExclusionState.Enabled;
- }),
- });
-
- const renderServiceExclusionItem = () => {
- return (
-
- );
- };
-
- const renderGroupExclusionItem = () => {
- return (
-
- {exclusion.hostname}
-
- {getExclusionStatus(exclusion.hostname)}
-
-
- );
- };
-
- const renderExclusion = () => {
- return selectedExclusion?.type === ExclusionsType.Service
- ? renderServiceExclusionItem()
- : renderGroupExclusionItem();
- };
-
- return (
-
-
- {renderExclusion()}
-
-
- );
-});
diff --git a/src/options/components/Exclusions/ChildrenList/ChildrenListItem/children-list-item.pcss b/src/options/components/Exclusions/ChildrenList/ChildrenListItem/children-list-item.pcss
deleted file mode 100644
index a1d59577e..000000000
--- a/src/options/components/Exclusions/ChildrenList/ChildrenListItem/children-list-item.pcss
+++ /dev/null
@@ -1,91 +0,0 @@
-.children-list-item {
- position: relative;
- display: flex;
- align-items: flex-start;
- padding: 8px 0 8px 16px;
- border-radius: 4px;
-
- &__domains-details {
- padding: 8px 0 8px 32px;
-
- &:first-child {
- padding-left: 16px;
- }
- }
-
- &:hover {
- background: var(--grayF3);
-
- & .children-list-item__remove-button {
- visibility: visible;
- }
- }
-
- @media (--tablet) {
- .children-list-item__remove-button {
- visibility: visible;
- }
- }
-
- &__group-hostname {
- font-size: 16px;
- line-height: 19px;
- color: var(--gray-base);
- cursor: default;
- word-break: break-all;
- padding-right: 50px;
-
- &__status {
- font-size: 14px;
- line-height: 16px;
- color: var(--gray88);
- padding-left: 1px;
- margin-top: 8px;
- }
- }
-
- &__service-hostname {
- font-size: 16px;
- line-height: 24px;
- font-weight: 400;
- color: var(--gray-base);
- width: 85%;
- cursor: pointer;
- background: none;
- border: none;
- display: flex;
- padding: 0;
- }
-
- &__arrow {
- width: 24px;
- height: 24px;
- position: absolute;
- top: 10px;
- right: 48px;
- }
-
- &__remove-button {
- position: absolute;
- right: 16px;
- border: none;
- background: none;
- cursor: pointer;
- visibility: hidden;
- padding: 0;
- max-height: 24px;
-
- &:hover {
- filter: brightness(.7);
- }
-
- &__icon {
- width: 24px;
- height: 24px;
- }
- }
-
- &.useless {
- opacity: .5;
- }
-}
diff --git a/src/options/components/Exclusions/ChildrenList/ChildrenListItem/index.ts b/src/options/components/Exclusions/ChildrenList/ChildrenListItem/index.ts
deleted file mode 100644
index 45e3659e8..000000000
--- a/src/options/components/Exclusions/ChildrenList/ChildrenListItem/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { ChildrenListItem } from './ChildrenListItem';
diff --git a/src/options/components/Exclusions/ChildrenList/ResetServiceModal/ResetServiceModal.tsx b/src/options/components/Exclusions/ChildrenList/ResetServiceModal/ResetServiceModal.tsx
index 5fdbaf01f..e0ad0e26b 100644
--- a/src/options/components/Exclusions/ChildrenList/ResetServiceModal/ResetServiceModal.tsx
+++ b/src/options/components/Exclusions/ChildrenList/ResetServiceModal/ResetServiceModal.tsx
@@ -1,13 +1,10 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
-import Modal from 'react-modal';
import { rootStore } from '../../../../stores';
-import { reactTranslator } from '../../../../../common/reactTranslator';
-import { Title } from '../../../ui/Title';
import { translator } from '../../../../../common/translator';
-
-import './reset-service-modal.pcss';
+import { Modal } from '../../../ui/Modal';
+import { Button } from '../../../ui/Button';
export const ResetServiceModal = observer(() => {
const { exclusionsStore } = useContext(rootStore);
@@ -28,45 +25,27 @@ export const ResetServiceModal = observer(() => {
return (
-
-
-
- {reactTranslator.getMessage(
+ title={translator.getMessage('settings_exclusion_reset_to_default')}
+ description={
+ translator.getMessage(
'settings_exclusion_reset_service_confirmation_message',
{ hostname: selectedExclusion.hostname },
- )}
-
-
-
-
-
-
+ )
+ }
+ actions={(
+ <>
+
+
+ >
+ )}
+ isOpen={exclusionsStore.resetServiceModalOpen}
+ className="exclusions__modal exclusions__modal--empty-body"
+ size="medium"
+ onClose={closeModal}
+ />
);
});
diff --git a/src/options/components/Exclusions/ChildrenList/ResetServiceModal/reset-service-modal.pcss b/src/options/components/Exclusions/ChildrenList/ResetServiceModal/reset-service-modal.pcss
deleted file mode 100644
index 53aab394c..000000000
--- a/src/options/components/Exclusions/ChildrenList/ResetServiceModal/reset-service-modal.pcss
+++ /dev/null
@@ -1,15 +0,0 @@
-.reset-service-modal {
- &__actions {
- margin-top: 35px;
- display: flex;
- gap: 16px;
-
- @media (--mobile) {
- flex-direction: column-reverse;
-
- .button {
- width: 100%;
- }
- }
- }
-}
diff --git a/src/options/components/Exclusions/ChildrenList/SubdomainModal/SubdomainModal.tsx b/src/options/components/Exclusions/ChildrenList/SubdomainModal/SubdomainModal.tsx
index 4223d85b4..7a00e60df 100644
--- a/src/options/components/Exclusions/ChildrenList/SubdomainModal/SubdomainModal.tsx
+++ b/src/options/components/Exclusions/ChildrenList/SubdomainModal/SubdomainModal.tsx
@@ -1,41 +1,48 @@
import React, { useContext, useState } from 'react';
import { observer } from 'mobx-react';
-import { ExclusionsModal } from '../../ExclusionsModal/ExclusionsModal';
import { rootStore } from '../../../../stores';
-import { reactTranslator } from '../../../../../common/reactTranslator';
-
-import './subdomain-modal.pcss';
+import { translator } from '../../../../../common/translator';
+import { Modal } from '../../../ui/Modal';
+import { Button } from '../../../ui/Button';
+import { Input } from '../../../ui/Input';
export const SubdomainModal = observer(() => {
const { exclusionsStore, notificationsStore } = useContext(rootStore);
const [inputValue, setInputValue] = useState('');
+ const [inputError, setInputError] = useState
(null);
const isOpen = exclusionsStore.addSubdomainModalOpen;
+ const formId = 'add-subdomain-form';
const closeModal = () => {
exclusionsStore.closeAddSubdomainModal();
setInputValue('');
};
- const addSubdomain = async (
- e: React.FormEvent | React.MouseEvent,
- ) => {
+ const handleInputChange = (value: string) => {
+ setInputValue(value);
+ setInputError(null);
+ };
+
+ const addSubdomain = async (e: React.FormEvent) => {
e.preventDefault();
+
if (inputValue.includes(' ')) {
- notificationsStore.notifyError(reactTranslator.getMessage('settings_exclusion_invalid_subdomain'));
+ setInputError(translator.getMessage('settings_exclusion_invalid_subdomain'));
return;
}
+
if (inputValue) {
const addedExclusionsCount = await exclusionsStore.addSubdomainToExclusions(inputValue);
notificationsStore.notifySuccess(
- reactTranslator.getMessage(
+ translator.getMessage(
'options_exclusions_added_exclusions',
{ count: addedExclusionsCount },
),
{
- action: reactTranslator.getMessage('settings_exclusions_undo'),
+ action: translator.getMessage('settings_exclusions_undo'),
handler: exclusionsStore.restoreExclusions,
},
);
@@ -45,50 +52,36 @@ export const SubdomainModal = observer(() => {
};
return (
-
+
+
+ >
+ )}
+ className="exclusions__modal"
+ size="medium"
+ onClose={closeModal}
>
-
-
+
);
});
diff --git a/src/options/components/Exclusions/ChildrenList/SubdomainModal/subdomain-modal.pcss b/src/options/components/Exclusions/ChildrenList/SubdomainModal/subdomain-modal.pcss
deleted file mode 100644
index 7a3d64419..000000000
--- a/src/options/components/Exclusions/ChildrenList/SubdomainModal/subdomain-modal.pcss
+++ /dev/null
@@ -1,50 +0,0 @@
-.subdomain-modal {
- margin: 14px 0 0 0;
-
- &__actions {
- margin-top: 24px;
- display: flex;
- gap: 16px;
-
- @media (--mobile) {
- flex-direction: column-reverse;
-
- .button {
- width: 100%;
- }
- }
- }
-
- & label {
- position: relative;
- display: block;
- }
-
- &__hostname {
- position: absolute;
- max-width: 120px;
- overflow: hidden;
- text-overflow: ellipsis;
- text-wrap: nowrap;
- top: 38px;
- right: 16px;
- padding-left: 15px;
- border-left: 1px solid var(--grayD8);
- }
-}
-
-.light-mode {
- .subdomain-modal {
- &__input {
- background-color: var(--grayF7);
- }
- }
-}
-
-.dark-mode {
- .subdomain-modal {
- &__input {
- background-color: #131313;
- }
- }
-}
diff --git a/src/options/components/Exclusions/ChildrenList/children-list.pcss b/src/options/components/Exclusions/ChildrenList/children-list.pcss
deleted file mode 100644
index a20b9b602..000000000
--- a/src/options/components/Exclusions/ChildrenList/children-list.pcss
+++ /dev/null
@@ -1,30 +0,0 @@
-.children-list {
-
- &__title {
- display: flex;
- align-items: flex-start;
- margin: 0 0 16px 16px;
- padding-right: 16px;
-
- & .content__subtitle {
- margin: 0 0 16px 0;
- }
- }
-
- &__add-subdomain {
- cursor: pointer;
- display: flex;
- margin: 16px 0 0 16px;
- padding: 8px 16px;
-
- &__text {
- font-size: 16px;
- line-height: 24px;
- margin-left: 16px;
- }
- }
-
- &__reset {
- margin: 16px 0 0 16px;
- }
-}
diff --git a/src/options/components/Exclusions/Exclusion/Exclusion.tsx b/src/options/components/Exclusions/Exclusion/Exclusion.tsx
new file mode 100644
index 000000000..f11416e96
--- /dev/null
+++ b/src/options/components/Exclusions/Exclusion/Exclusion.tsx
@@ -0,0 +1,181 @@
+import React, { useContext, useState } from 'react';
+import { observer } from 'mobx-react';
+
+import classNames from 'classnames';
+
+import { type ExclusionDtoInterface, ExclusionState, ExclusionsType } from '../../../../common/exclusionsConstants';
+import { translator } from '../../../../common/translator';
+import { rootStore } from '../../../stores';
+import { Icon } from '../../ui/Icon';
+import { SearchHighlighter } from '../Search';
+
+import './exclusion.pcss';
+
+export interface ExclusionProps {
+ exclusion: ExclusionDtoInterface;
+ hasIcon?: boolean;
+}
+
+const ICONS_ORIGIN = 'https://icons.adguardvpn.com';
+
+function getIconUrl(exclusion: ExclusionDtoInterface) {
+ if (exclusion.type === ExclusionsType.Service && exclusion.iconUrl) {
+ return exclusion.iconUrl;
+ }
+
+ if (exclusion.type === ExclusionsType.Group) {
+ return `${ICONS_ORIGIN}/icon?domain=${exclusion.hostname}`;
+ }
+
+ return null;
+}
+
+function getExclusionDescription(hostname: string, exclusion?: ExclusionDtoInterface | null) {
+ if (hostname === exclusion?.hostname) {
+ return translator.getMessage('settings_exclusion_status_domain');
+ }
+ if (hostname.startsWith('*')) {
+ return translator.getMessage('settings_exclusion_status_all_subdomains');
+ }
+ return translator.getMessage('settings_exclusion_status_subdomain');
+}
+
+function isUselessExclusion(hostname: string, exclusion?: ExclusionDtoInterface | null) {
+ const wildcardExclusion = `*.${exclusion?.hostname}`;
+
+ return (
+ hostname !== exclusion?.hostname
+ && !hostname.startsWith(wildcardExclusion)
+ && exclusion?.children.some((exclusion) => {
+ return exclusion.hostname.startsWith(wildcardExclusion)
+ && exclusion.state === ExclusionState.Enabled;
+ })
+ );
+}
+
+export const Exclusion = observer(({
+ exclusion,
+ hasIcon = false,
+}: ExclusionProps) => {
+ const { exclusionsStore, notificationsStore } = useContext(rootStore);
+ const { selectedExclusion } = exclusionsStore;
+
+ const [iconLoaded, setIconLoaded] = useState(false);
+
+ const hasChildren = exclusion.children.length !== 0;
+ const isGroupExclusion = selectedExclusion?.type === ExclusionsType.Group;
+ const isDomain = exclusion.hostname === selectedExclusion?.hostname;
+ const description = getExclusionDescription(exclusion.hostname, selectedExclusion);
+ const isUseless = isUselessExclusion(exclusion.hostname, selectedExclusion);
+
+ const stateMap = {
+ [ExclusionState.Disabled]: {
+ icon: 'checkbox-disabled',
+ className: 'exclusion--disabled',
+ },
+ [ExclusionState.Enabled]: {
+ icon: 'checkbox-enabled',
+ className: 'exclusion--enabled',
+ },
+ [ExclusionState.PartlyEnabled]: {
+ icon: 'checkbox-partly-enabled',
+ className: 'exclusion--partly-enabled',
+ },
+ };
+
+ const classes = classNames(
+ 'exclusion',
+ stateMap[exclusion.state].className,
+ iconLoaded && 'exclusion--icon-loaded',
+ isGroupExclusion && !isDomain && 'exclusion--group',
+ isUseless && 'exclusion--useless',
+ );
+
+ const checkIconName = stateMap[exclusion.state].icon;
+ const iconUrl = getIconUrl(exclusion);
+
+ const handleIconLoaded = () => {
+ setIconLoaded(true);
+ };
+
+ const handleClick = () => {
+ exclusionsStore.toggleExclusionState(exclusion.id);
+ };
+
+ const handleForwardClick = () => {
+ if (!hasChildren) {
+ handleClick();
+ return;
+ }
+
+ exclusionsStore.setSelectedExclusionId(exclusion.id);
+ };
+
+ const handleDeleteClick = async () => {
+ const deletedExclusionsCount = await exclusionsStore.removeExclusion(exclusion);
+ const message = translator.getMessage(
+ 'options_exclusions_deleted_exclusions',
+ { count: deletedExclusionsCount },
+ );
+ notificationsStore.notifySuccess(message, {
+ action: translator.getMessage('settings_exclusions_undo'),
+ handler: () => exclusionsStore.restoreExclusions(),
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+ );
+});
diff --git a/src/options/components/Exclusions/Exclusion/exclusion.pcss b/src/options/components/Exclusions/Exclusion/exclusion.pcss
new file mode 100644
index 000000000..fc2e3ec98
--- /dev/null
+++ b/src/options/components/Exclusions/Exclusion/exclusion.pcss
@@ -0,0 +1,152 @@
+.exclusion {
+ &--group {
+ padding-left: 40px;
+
+ &:first-child {
+ padding-left: 0;
+ }
+ }
+
+ &__wrapper {
+ display: flex;
+ align-items: stretch;
+ justify-content: center;
+ background-color: transparent;
+ transition: var(--t3) background-color;
+ border-radius: 8px;
+
+ &:hover,
+ &:focus-within {
+ background-color: var(--fills-backgrounds-page-background-hovered);
+ }
+
+ &:active {
+ background-color: var(--fills-backgrounds-page-background-pressed);
+ }
+ }
+
+ &__btn,
+ &__check,
+ &__delete {
+ outline-offset: -2px;
+ }
+
+ &__btn {
+ flex: 1 1 auto;
+ display: block;
+ text-align: left;
+ padding: 16px 0;
+ overflow: hidden;
+ white-space: nowrap;
+
+ &-content {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ column-gap: 16px;
+ overflow: hidden;
+ }
+
+ &-description {
+ display: block;
+ color: var(--text-description-description-default);
+ font-size: 14px;
+ line-height: 1.5;
+ }
+
+ &-globe-icon,
+ &-forward-icon {
+ flex-shrink: 0;
+ }
+
+ &-globe-icon {
+ color: var(--stroke-icons-product-icon-default);
+ }
+
+ &-forward-icon {
+ transition: var(--t3) color;
+ transform: rotate(-90deg);
+ }
+
+ &:hover &-forward-icon,
+ &:focus-visible &-forward-icon {
+ color: var(--text-main-text-main-default);
+ }
+
+ .exclusion--icon-loaded &-globe-icon {
+ display: none;
+ }
+
+ &-icon {
+ flex-shrink: 0;
+ width: 24px;
+ height: 24px;
+ object-fit: cover;
+ display: none;
+
+ .exclusion--icon-loaded & {
+ display: inline-block;
+ }
+ }
+
+ &-title {
+ flex: 1 1 auto;
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 1.5;
+ }
+ }
+
+ &__check,
+ &__delete {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ flex-shrink: 0;
+ padding: 16px;
+ border-radius: 8px;
+
+ &-icon {
+ color: currentColor;
+ }
+ }
+
+ &__check {
+ color: var(--stroke-icons-product-icon-default);
+
+ .exclusion--disabled & {
+ color: var(--stroke-icons-gray-icons-default);
+ }
+
+ .exclusion--useless & {
+ color: var(--stroke-icons-product-icon-disabled);
+ }
+ }
+
+ &:hover,
+ &:active,
+ &:focus-visible,
+ &:focus-within {
+ .exclusion__delete {
+ opacity: 1;
+ }
+ }
+
+ &__delete {
+ color: var(--stroke-icons-gray-icons-default);
+ /* Use opacity instead of display to not cause layout shifts */
+ opacity: 0;
+ transition: var(--t3) color, var(--t3) opacity;
+
+ @media (--touch-screen) {
+ opacity: 1;
+ }
+
+ &:hover,
+ &:active,
+ &:focus-visible {
+ color: var(--stroke-icons-error-icon-default);
+ }
+ }
+}
diff --git a/src/options/components/Exclusions/Exclusion/index.ts b/src/options/components/Exclusions/Exclusion/index.ts
new file mode 100644
index 000000000..bd5fe953f
--- /dev/null
+++ b/src/options/components/Exclusions/Exclusion/index.ts
@@ -0,0 +1 @@
+export { Exclusion } from './Exclusion';
diff --git a/src/options/components/Exclusions/Exclusions.tsx b/src/options/components/Exclusions/Exclusions.tsx
index 2fbab406b..a23ae42a4 100644
--- a/src/options/components/Exclusions/Exclusions.tsx
+++ b/src/options/components/Exclusions/Exclusions.tsx
@@ -4,16 +4,17 @@ import { observer } from 'mobx-react';
import { rootStore } from '../../stores';
import { Title } from '../ui/Title';
import { translator } from '../../../common/translator';
-import { ExclusionsMode } from '../../../common/exclusionsConstants';
import { reactTranslator } from '../../../common/reactTranslator';
+import { ExclusionsMode } from '../../../common/exclusionsConstants';
+import { Icon } from '../ui/Icon';
+import { Button } from '../ui/Button';
import { ModeSelectorModal } from './ModeSelectorModal';
import { Actions } from './Actions';
import { List } from './List';
-import { AddExclusionModal } from './ExclusionsModal/AddExclusionsModal';
-import { ConfirmAddModal } from './ExclusionsModal/ConfirmAddModal';
+import { AddExclusionModal, ConfirmAddModal } from './ExclusionsModal';
import { ChildrenList } from './ChildrenList';
-import { ExclusionsSearch } from './Search/ExclusionsSearch';
+import { ExclusionsSearch } from './Search';
import './exclusions.pcss';
@@ -35,13 +36,11 @@ export const Exclusions = observer(() => {
return (
);
},
@@ -62,8 +61,8 @@ export const Exclusions = observer(() => {
if (exclusionsStore.currentMode === ExclusionsMode.Selective
&& !exclusionsStore.exclusionsTree.children.length) {
return (
-
- {reactTranslator.getMessage('settings_exclusion_selective_mode_warning')}
+
+ {translator.getMessage('settings_exclusion_selective_mode_warning')}
);
}
@@ -71,37 +70,30 @@ export const Exclusions = observer(() => {
};
return (
-
-
-
-
- {modeInfo}
-
- {renderSelectiveModeWarning()}
-
-
-
-
-
-
-
+ {translator.getMessage('settings_exclusion_add_website')}
+
+ )}
+
+
);
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/AddExclusionModal.tsx b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/AddExclusionModal.tsx
index e10e3c457..8723f3eef 100644
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/AddExclusionModal.tsx
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/AddExclusionModal.tsx
@@ -3,18 +3,20 @@ import { observer } from 'mobx-react';
import classnames from 'classnames';
-import { ExclusionsModal } from '../ExclusionsModal';
import { rootStore } from '../../../../stores';
import { AddExclusionMode } from '../../../../stores/ExclusionsStore';
-import { reactTranslator } from '../../../../../common/reactTranslator';
+import { translator } from '../../../../../common/translator';
+import { Modal } from '../../../ui/Modal';
+import { Button } from '../../../ui/Button';
-import { ServiceMode } from './ServiceMode/ServiceMode';
-import { ManualMode } from './ManualMode/ManualMode';
+import { SERVICE_FORM_ID, ServiceMode } from './ServiceMode';
+import { MANUAL_FORM_ID, ManualMode } from './ManualMode';
-import '../exclusions-modal.pcss';
+import './add-exclusion-modal.pcss';
export const AddExclusionModal = observer(() => {
const { exclusionsStore } = useContext(rootStore);
+ const { addExclusionMode } = exclusionsStore;
const isOpen = exclusionsStore.addExclusionModalOpen;
@@ -33,50 +35,74 @@ export const AddExclusionModal = observer(() => {
const ModeSelectButtons = {
service: {
classname: classnames(
- 'mode-select-button',
- { enabled: exclusionsStore.addExclusionMode === AddExclusionMode.Service },
+ 'add-exclusion-modal__tabs-item has-tab-focus',
+ addExclusionMode === AddExclusionMode.Service && 'add-exclusion-modal__tabs-item--active',
),
},
manual: {
classname: classnames(
- 'mode-select-button',
- { enabled: exclusionsStore.addExclusionMode === AddExclusionMode.Manual },
+ 'add-exclusion-modal__tabs-item has-tab-focus',
+ addExclusionMode === AddExclusionMode.Manual && 'add-exclusion-modal__tabs-item--active',
),
},
};
const MODE_MAP = {
- [AddExclusionMode.Service]: () =>
,
- [AddExclusionMode.Manual]: () =>
,
+ [AddExclusionMode.Service]: {
+ formId: SERVICE_FORM_ID,
+ btnText: translator.getMessage('settings_exclusion_modal_save'),
+ btnDisabled: !exclusionsStore.servicesToToggle.length,
+ content: () =>
,
+ },
+ [AddExclusionMode.Manual]: {
+ formId: MANUAL_FORM_ID,
+ btnText: translator.getMessage('settings_exclusion_add_manually_add'),
+ btnDisabled: false,
+ content: () =>
,
+ },
};
- const mode = MODE_MAP[exclusionsStore.addExclusionMode];
+ const mode = MODE_MAP[addExclusionMode];
return (
-
+
+ {translator.getMessage('settings_exclusion_modal_cancel')}
+
+
+ {mode.btnText}
+
+ >
+ )}
+ className="exclusions__modal add-exclusion-modal"
+ size="medium"
+ onClose={onClose}
>
-
+
- {reactTranslator.getMessage('settings_exclusion_add_from_list')}
+
+ {translator.getMessage('settings_exclusion_add_from_list')}
+
- {reactTranslator.getMessage('settings_exclusion_add_manually')}
+
+ {translator.getMessage('settings_exclusion_add_manually')}
+
-
- {mode()}
-
-
+ {mode.content()}
+
);
});
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/ManualMode.tsx b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/ManualMode.tsx
index 2cd2b439d..e7709fc3d 100644
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/ManualMode.tsx
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/ManualMode.tsx
@@ -1,10 +1,13 @@
import React, { useContext, useState } from 'react';
import { rootStore } from '../../../../../stores';
-import { reactTranslator } from '../../../../../../common/reactTranslator';
+import { translator } from '../../../../../../common/translator';
+import { Input } from '../../../../ui/Input';
import './manual-mode.pcss';
+export const MANUAL_FORM_ID = 'add-exclusion-form-manual';
+
export const ManualMode = () => {
const { exclusionsStore, notificationsStore } = useContext(rootStore);
@@ -14,20 +17,18 @@ export const ManualMode = () => {
exclusionsStore.closeAddExclusionModal();
};
- const addUrl = async (
- e: React.FormEvent
| React.MouseEvent,
- ) => {
+ const addUrl = async (e: React.FormEvent) => {
e.preventDefault();
if (exclusionsStore.validateUrl(inputValue)) {
const addedExclusionsCount = await exclusionsStore.addUrlToExclusions(inputValue);
notificationsStore.notifySuccess(
- reactTranslator.getMessage(
+ translator.getMessage(
'options_exclusions_added_exclusions',
{ count: addedExclusionsCount },
),
{
- action: reactTranslator.getMessage('settings_exclusions_undo'),
+ action: translator.getMessage('settings_exclusions_undo'),
handler: exclusionsStore.restoreExclusions,
},
);
@@ -40,48 +41,19 @@ export const ManualMode = () => {
return (
);
};
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/index.ts b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/index.ts
new file mode 100644
index 000000000..49f2829a5
--- /dev/null
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/index.ts
@@ -0,0 +1 @@
+export { ManualMode, MANUAL_FORM_ID } from './ManualMode';
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/manual-mode.pcss b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/manual-mode.pcss
index c69f2a39e..9bf78f5ae 100644
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/manual-mode.pcss
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ManualMode/manual-mode.pcss
@@ -1,21 +1,4 @@
.manual-mode {
- text-align: left;
- height: 315px;
-
- &__actions {
- position: absolute;
- right: 32px;
- left: 32px;
- bottom: 30px;
- display: flex;
- gap: 16px;
-
- @media (--mobile) {
- flex-direction: column-reverse;
-
- .button {
- width: 100%;
- }
- }
- }
-}
+ /* To match height of service mode */
+ height: 320px;
+}
\ No newline at end of file
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceCategory.tsx b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceCategory.tsx
index 78347a5fb..7593e170a 100644
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceCategory.tsx
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceCategory.tsx
@@ -6,11 +6,10 @@ import cn from 'classnames';
import { rootStore } from '../../../../../stores';
import { type PreparedServiceCategory } from '../../../../../stores/ExclusionsStore';
import { containsIgnoreCase } from '../../../Search/SearchHighlighter/helpers';
+import { Icon } from '../../../../ui/Icon';
import { ServiceRow } from './ServiceRow';
-import s from './service-category.module.pcss';
-
export interface ServiceCategoryProps {
category: PreparedServiceCategory;
}
@@ -39,15 +38,10 @@ export const ServiceCategory = observer(({
const unfoldCategory = exclusionsStore.unfoldAllServiceCategories
|| exclusionsStore.unfoldedServiceCategories.some((id) => id === category.id);
- const categoryClassname = cn('category', {
- category__unfolded: unfoldCategory,
- category__folded: !unfoldCategory,
- });
-
- const categoryServicesClassname = cn('category__services', {
- [s.show]: unfoldCategory,
- [s.hide]: !unfoldCategory,
- });
+ const categoryClassname = cn(
+ 'service-mode-category',
+ unfoldCategory && 'service-mode-category--active',
+ );
if (filteredServices.length === 0) {
return null;
@@ -56,21 +50,19 @@ export const ServiceCategory = observer(({
return (
-
- {category.name}
+
+
+ {category.name}
+
-
- {
- filteredServices.map((service) => {
- return (
);
- })
- }
+
+ {filteredServices.map((service) => {
+ return ();
+ })}
);
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceMode.tsx b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceMode.tsx
index 3a875e205..50a66e4ec 100644
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceMode.tsx
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceMode.tsx
@@ -2,13 +2,15 @@ import React, { useContext } from 'react';
import { observer } from 'mobx-react';
import { rootStore } from '../../../../../stores';
-import { ServicesSearch } from '../../../Search/ServicesSearch';
-import { reactTranslator } from '../../../../../../common/reactTranslator';
+import { ServicesSearch } from '../../../Search';
+import { translator } from '../../../../../../common/translator';
import { ServicesList } from './ServicesList';
import './service-mode.pcss';
+export const SERVICE_FORM_ID = 'add-exclusion-form-service';
+
export const ServiceMode = observer(() => {
const { exclusionsStore, notificationsStore } = useContext(rootStore);
@@ -16,22 +18,24 @@ export const ServiceMode = observer(() => {
exclusionsStore.closeAddExclusionModal();
};
- const handleSaveServices = async () => {
+ const handleSaveServices = async (e: React.FormEvent
) => {
+ e.preventDefault();
+
const toggleServicesResult = await exclusionsStore.toggleServices();
const { added, deleted } = toggleServicesResult;
const addedExclusionsMessage = added
- ? reactTranslator.getMessage('options_exclusions_added_exclusions', { count: added })
+ ? translator.getMessage('options_exclusions_added_exclusions', { count: added })
: '';
const deletedExclusionsMessage = deleted
- ? reactTranslator.getMessage('options_exclusions_deleted_exclusions', { count: deleted })
+ ? translator.getMessage('options_exclusions_deleted_exclusions', { count: deleted })
: '';
notificationsStore.notifySuccess(
`${addedExclusionsMessage} ${deletedExclusionsMessage}`,
{
- action: reactTranslator.getMessage('settings_exclusions_undo'),
+ action: translator.getMessage('settings_exclusions_undo'),
handler: exclusionsStore.restoreExclusions,
},
);
@@ -40,26 +44,13 @@ export const ServiceMode = observer(() => {
};
return (
-
+
);
});
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceRow.tsx b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceRow.tsx
index be9bf7f01..e71cb564e 100644
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceRow.tsx
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServiceRow.tsx
@@ -1,12 +1,12 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
+import classNames from 'classnames';
+
import { rootStore } from '../../../../../stores';
import { ExclusionState, type ServiceDto } from '../../../../../../common/exclusionsConstants';
import { SearchHighlighter } from '../../../Search/SearchHighlighter';
-import { reactTranslator } from '../../../../../../common/reactTranslator';
-
-import './service-mode.pcss';
+import { translator } from '../../../../../../common/translator';
/**
* Returns true if service can be added, and returns false if service can be removed
@@ -40,43 +40,39 @@ export const ServiceRow = observer(({ service }: ServiceRowProps) => {
exclusionsStore.addToServicesToToggle(service.serviceId);
};
- const buttonsMap = {
- add: () => (
-
- {reactTranslator.getMessage('settings_exclusion_add')}
-
- ),
- remove: () => (
-
- {reactTranslator.getMessage('settings_exclusion_modal_remove')}
-
- ),
- };
-
const serviceCanBeAdded = canAddService(service, exclusionsStore.servicesToToggle);
- const actionButton = serviceCanBeAdded ? buttonsMap.add() : buttonsMap.remove();
+ const classes = classNames(
+ 'service-mode-item has-tab-focus',
+ !serviceCanBeAdded && 'service-mode-item--active',
+ );
+
+ const buttonText = serviceCanBeAdded
+ ? translator.getMessage('settings_exclusion_add')
+ : translator.getMessage('settings_exclusion_modal_remove');
+
+ const clickHandler = serviceCanBeAdded ? addService : removeService;
return (
-
-

-
+
+
+
-
- {actionButton}
+
+ {buttonText}
-
+
);
});
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServicesList.tsx b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServicesList.tsx
index c01bec86c..65894aa2e 100644
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServicesList.tsx
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/ServicesList.tsx
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
import { observer } from 'mobx-react';
import { rootStore } from '../../../../../stores';
-import { reactTranslator } from '../../../../../../common/reactTranslator';
+import { translator } from '../../../../../../common/translator';
import { ServiceCategory } from './ServiceCategory';
@@ -11,32 +11,37 @@ export const ServicesList = observer(() => {
const { categories } = exclusionsStore.preparedServicesData;
+ let content: React.ReactNode;
if (!Object.keys(categories).length) {
- return (
-
- {reactTranslator.getMessage('settings_exclusion_connection_problem')}
+ content = (
+
+ {translator.getMessage('settings_exclusion_connection_problem')}
);
- }
-
- if (exclusionsStore.isServicesSearchEmpty) {
- return (
-
- {reactTranslator.getMessage('settings_exclusion_nothing_found')}
+ } else if (exclusionsStore.isServicesSearchEmpty) {
+ content = (
+
+ {translator.getMessage('settings_exclusion_nothing_found')}
+
+ );
+ } else {
+ content = (
+
+ {Object.values(categories).map((category) => {
+ return (
+
+ );
+ })}
);
}
return (
-
- {Object.values(categories).map((category) => {
- return (
-
- );
- })}
-
+
+ {content}
+
);
});
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/index.ts b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/index.ts
new file mode 100644
index 000000000..04c6065c9
--- /dev/null
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/index.ts
@@ -0,0 +1 @@
+export { ServiceMode, SERVICE_FORM_ID } from './ServiceMode';
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/service-category.module.pcss b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/service-category.module.pcss
deleted file mode 100644
index f50e89865..000000000
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/service-category.module.pcss
+++ /dev/null
@@ -1,7 +0,0 @@
-.show {
- display: block;
-}
-
-.hide {
- display: none;
-}
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/service-mode.pcss b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/service-mode.pcss
index 4ab0ea39b..d4d3e6900 100644
--- a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/service-mode.pcss
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/ServiceMode/service-mode.pcss
@@ -1,118 +1,115 @@
.service-mode {
+ display: flex;
+ flex-direction: column;
+ /* To match height of manual mode in case if searching is active */
+ min-height: 320px;
- &__actions {
- position: absolute;
- right: 32px;
- left: 32px;
- bottom: 30px;
- display: flex;
- gap: 16px;
+ &__search {
+ padding: 8px 0;
+ }
- @media (--mobile) {
- flex-direction: column-reverse;
+ &__content {
+ flex: 1 1 auto;
+ }
- .button {
- width: 100%;
- }
+ &-list {
+ &__empty {
+ text-align: center;
+ margin: 50px 0;
+ color: var(--text-description-description-default);
}
}
-}
-
-.services-list {
- margin-top: 20px;
- max-height: 180px;
- overflow-y: auto;
- scrollbar-width: thin;
- &__connection-problem {
- text-align: center;
- margin-top: 100px;
- }
-}
+ &-category {
+ &__btn {
+ width: 100%;
+ padding: 16px;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ column-gap: 16px;
+ transition: var(--t3) background-color;
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.5;
+ outline-offset: -2px;
-.category {
- cursor: pointer;
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-backgrounds-page-background-hovered);
+ }
- &__title {
- padding: 8px 16px;
- font-size: 16px;
- line-height: 19px;
- font-weight: 400;
- display: flex;
- background: none;
- border: none;
+ &:active {
+ background-color: var(--fills-backgrounds-page-background-pressed);
+ }
- &__arrow {
- margin-right: 8px;
- }
- }
+ &-icon {
+ transition: var(--t3) transform;
+ transform: rotate(-90deg);
- &__unfolded {
- & .icon {
- transform: rotate(90deg);
+ .service-mode-category--active & {
+ transform: rotate(0);
+ }
+ }
}
- }
- &__services {
- background: var(--grayF3);
- padding: 5px 10px;
- border-radius: 5px;
- cursor: default;
+ &__services {
+ display: none;
- @media (prefers-color-scheme: dark) {
- background-color: #131313;
+ .service-mode-category--active & {
+ display: block;
+ }
}
}
-}
-.service-row {
- display: flex;
- align-content: center;
- padding: 8px;
- position: relative;
- cursor: default;
-
- &__title {
- font-size: 16px;
- margin-left: 12px;
- }
+ &-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ column-gap: 16px;
+ padding: 16px;
+ width: 100%;
+ border-radius: 8px;
+ transition: var(--t3) background-color;
+ outline-offset: -2px;
+
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-backgrounds-page-background-hovered);
+ }
- &__icon {
- width: 24px;
- height: 24px;
- position: relative;
- overflow: hidden;
- }
+ &:active {
+ background-color: var(--fills-backgrounds-page-background-pressed);
+ }
- &__actions {
- margin-left: auto;
+ &__img {
+ flex-shrink: 0;
+ width: 24px;
+ height: 24px;
+ object-fit: cover;
+ }
- & .simple-button {
+ &__title {
+ text-align: left;
+ flex: 1 1 auto;
font-size: 16px;
+ font-weight: 600;
+ line-height: 1.5;
+ color: var(--text-main-text-main-default);
}
- &__remove {
- color: var(--gray88);
+ &__action {
+ flex-shrink: 0;
+ color: var(--text-links-link-default);
+ text-align: right;
+ font-size: 16px;
+ transition: var(--t3) color;
- &:hover {
- color: var(--gray-base);
+ .service-mode-item--active & {
+ color: var(--text-main-text-main-default);
}
}
}
}
-
-.light-mode {
- .category {
- &__services {
- background: var(--grayF3);
- }
- }
-}
-
-.dark-mode {
- .category {
- &__services {
- background-color: #131313;
- }
- }
-}
diff --git a/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/add-exclusion-modal.pcss b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/add-exclusion-modal.pcss
new file mode 100644
index 000000000..776e009c4
--- /dev/null
+++ b/src/options/components/Exclusions/ExclusionsModal/AddExclusionsModal/add-exclusion-modal.pcss
@@ -0,0 +1,63 @@
+.add-exclusion-modal {
+ .modal__wrapper {
+ flex: 1 1 auto;
+ max-height: 400px;
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ scrollbar-width: thin;
+ }
+
+ .modal__header {
+ padding-bottom: 16px;
+ }
+
+ .modal__actions {
+ padding-top: 14px;
+ }
+
+ &__tabs {
+ display: flex;
+ column-gap: 8px;
+ align-items: flex-start;
+ justify-content: flex-start;
+ margin: 16px 0 24px;
+
+ &-item {
+ padding: 8px 16px;
+ border-radius: 8px;
+ font-size: 16px;
+ line-height: 1.5;
+ outline-offset: -2px;
+ transition: var(--t3) color, var(--t3) background-color;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-backgrounds-page-background-hovered);
+ }
+
+ &:active {
+ background-color: var(--fills-backgrounds-page-background-pressed);
+ }
+
+ &--active {
+ &,
+ &:hover,
+ &:focus-visible,
+ &:active {
+ color: var(--gray-0);
+ background: var(--gray-80);
+ }
+ }
+
+ &-text {
+ width: 100%;
+ text-align: left;
+ }
+ }
+ }
+}
diff --git a/src/options/components/Exclusions/ExclusionsModal/ConfirmAddModal/ConfirmAddModal.tsx b/src/options/components/Exclusions/ExclusionsModal/ConfirmAddModal/ConfirmAddModal.tsx
index 2427de7a0..b13bc53cc 100644
--- a/src/options/components/Exclusions/ExclusionsModal/ConfirmAddModal/ConfirmAddModal.tsx
+++ b/src/options/components/Exclusions/ExclusionsModal/ConfirmAddModal/ConfirmAddModal.tsx
@@ -1,12 +1,10 @@
import React, { useContext } from 'react';
-import Modal from 'react-modal';
import { observer } from 'mobx-react';
-import { Title } from '../../../ui/Title';
import { rootStore } from '../../../../stores';
-import { reactTranslator } from '../../../../../common/reactTranslator';
-
-import './confirm-add-modal.pcss';
+import { translator } from '../../../../../common/translator';
+import { Modal } from '../../../ui/Modal';
+import { Button } from '../../../ui/Button';
export const ConfirmAddModal = observer(() => {
const { exclusionsStore, notificationsStore } = useContext(rootStore);
@@ -20,12 +18,12 @@ export const ConfirmAddModal = observer(() => {
if (urlToConfirm) {
const addedExclusionsCount = await exclusionsStore.addUrlToExclusions(urlToConfirm);
notificationsStore.notifySuccess(
- reactTranslator.getMessage(
+ translator.getMessage(
'options_exclusions_added_exclusions',
{ count: addedExclusionsCount },
),
{
- action: reactTranslator.getMessage('settings_exclusions_undo'),
+ action: translator.getMessage('settings_exclusions_undo'),
handler: exclusionsStore.restoreExclusions,
},
);
@@ -35,44 +33,22 @@ export const ConfirmAddModal = observer(() => {
return (
+
+ {translator.getMessage('settings_exclusion_modal_cancel')}
+
+
+ {translator.getMessage('settings_exclusion_add')}
+
+ >
+ )}
isOpen={confirmAddModalOpen}
- className="modal modal-exclusions confirm-add-modal"
- overlayClassName="overlay overlay--fullscreen"
- onRequestClose={closeModal}
- >
-
-
-
-
-
-
- {reactTranslator.getMessage('settings_exclusions_add_invalid_domain', { url: urlToConfirm })}
-
-
-
-
- {reactTranslator.getMessage('settings_exclusion_modal_cancel')}
-
-
- {reactTranslator.getMessage('settings_exclusion_add')}
-
-
-
+ className="exclusions__modal exclusions__modal--empty-body"
+ size="medium"
+ onClose={closeModal}
+ />
);
});
diff --git a/src/options/components/Exclusions/ExclusionsModal/ConfirmAddModal/confirm-add-modal.pcss b/src/options/components/Exclusions/ExclusionsModal/ConfirmAddModal/confirm-add-modal.pcss
deleted file mode 100644
index 4c45d53f1..000000000
--- a/src/options/components/Exclusions/ExclusionsModal/ConfirmAddModal/confirm-add-modal.pcss
+++ /dev/null
@@ -1,22 +0,0 @@
-.confirm-add-modal {
- min-height: initial !important;
-
- &__message {
- max-width: 420px;
- margin-bottom: 24px;
- word-break: break-word;
- }
-
- &__actions {
- display: flex;
- gap: 16px;
-
- @media (--mobile) {
- flex-direction: column-reverse;
-
- .button {
- width: 100%;
- }
- }
- }
-}
diff --git a/src/options/components/Exclusions/ExclusionsModal/ExclusionsModal.tsx b/src/options/components/Exclusions/ExclusionsModal/ExclusionsModal.tsx
deleted file mode 100644
index 6bada1ffb..000000000
--- a/src/options/components/Exclusions/ExclusionsModal/ExclusionsModal.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import Modal from 'react-modal';
-
-import './exclusions-modal.pcss';
-
-interface ExclusionsModalProps {
- isOpen: boolean,
- closeModal: () => void,
- title?: React.ReactNode,
- description?: string | React.ReactNode,
- children?: React.ReactNode,
-}
-
-export const ExclusionsModal = ({
- isOpen,
- closeModal,
- title,
- description,
- children,
-}: ExclusionsModalProps) => {
- return (
-
-
-
-
- {
- title && {title}
- }
- {
- description && {description}
- }
- {children}
-
- );
-};
diff --git a/src/options/components/Exclusions/ExclusionsModal/exclusions-modal.pcss b/src/options/components/Exclusions/ExclusionsModal/exclusions-modal.pcss
deleted file mode 100644
index 82f00a2a7..000000000
--- a/src/options/components/Exclusions/ExclusionsModal/exclusions-modal.pcss
+++ /dev/null
@@ -1,39 +0,0 @@
-.modal {
- &__mode-selectors {
- margin: 8px 0 24px;
- }
-
- &__mode {
- height: 320px;
-
- @media (--mobile) {
- /* TODO: Hardcoded value, remove after re-design (AG-38059) */
- height: 392px;
- }
- }
-
- & .content__title {
- margin: 0 8px 8px 0;
- padding-left: 0;
- }
-
- & .content__subtitle {
- margin: 0 0 24px 0;
- }
-}
-
-.mode-select-button {
- color: var(--gray-base);
- border: none;
- background: none;
- padding: 8px 16px;
- border-radius: 4px;
- font-size: 16px;
- line-height: 19px;
- cursor: pointer;
-
- &.enabled {
- color: var(--grayF3);
- background-color: var(--gray-base);
- }
-}
diff --git a/src/options/components/Exclusions/ExclusionsModal/index.ts b/src/options/components/Exclusions/ExclusionsModal/index.ts
new file mode 100644
index 000000000..a4c344455
--- /dev/null
+++ b/src/options/components/Exclusions/ExclusionsModal/index.ts
@@ -0,0 +1,2 @@
+export { AddExclusionModal } from './AddExclusionsModal';
+export { ConfirmAddModal } from './ConfirmAddModal';
diff --git a/src/options/components/Exclusions/List/List.tsx b/src/options/components/Exclusions/List/List.tsx
index a89478d3d..7dca8b617 100644
--- a/src/options/components/Exclusions/List/List.tsx
+++ b/src/options/components/Exclusions/List/List.tsx
@@ -2,32 +2,31 @@ import React, { useContext } from 'react';
import { observer } from 'mobx-react';
import { rootStore } from '../../../stores';
-import { reactTranslator } from '../../../../common/reactTranslator';
+import { translator } from '../../../../common/translator';
import { type ExclusionDtoInterface } from '../../../../common/exclusionsConstants';
import { Loader } from '../Loader';
-
-import { ListItem } from './ListItem';
+import { Exclusion } from '../Exclusion';
export const List = observer(() => {
const { exclusionsStore } = useContext(rootStore);
if (exclusionsStore.exclusionsSearchValue && !exclusionsStore.preparedExclusions.length) {
return (
-
- {reactTranslator.getMessage('settings_exclusion_nothing_found')}
+
+ {translator.getMessage('settings_exclusion_nothing_found')}
);
}
return (
-
- {
- exclusionsStore.preparedExclusions.map((exclusion: ExclusionDtoInterface) => (
-
- ))
- }
-
+ {exclusionsStore.preparedExclusions.map((exclusion: ExclusionDtoInterface) => (
+
+ ))}
);
diff --git a/src/options/components/Exclusions/List/ListItem/ListItem.tsx b/src/options/components/Exclusions/List/ListItem/ListItem.tsx
deleted file mode 100644
index 7398e42b5..000000000
--- a/src/options/components/Exclusions/List/ListItem/ListItem.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import React, { useContext, useRef, useEffect } from 'react';
-import { observer } from 'mobx-react';
-
-import cn from 'classnames';
-
-import { ExclusionsType, type ExclusionDtoInterface } from '../../../../../common/exclusionsConstants';
-import { StateCheckbox } from '../../StateCheckbox';
-import { rootStore } from '../../../../stores';
-import { SearchHighlighter } from '../../Search/SearchHighlighter';
-import { reactTranslator } from '../../../../../common/reactTranslator';
-
-import './list-item.pcss';
-
-const ICON_FOR_DOMAIN = 'https://icons.adguardvpn.com/icon?domain=';
-
-interface ListItemProps {
- exclusion: ExclusionDtoInterface;
-}
-
-export const ListItem = observer(({ exclusion }: ListItemProps) => {
- const { exclusionsStore, notificationsStore } = useContext(rootStore);
-
- const icon = useRef
(null);
-
- const removeExclusion = (exclusion: ExclusionDtoInterface) => async () => {
- const deletedExclusionsCount = await exclusionsStore.removeExclusion(exclusion);
- notificationsStore.notifySuccess(reactTranslator.getMessage(
- 'options_exclusions_deleted_exclusions',
- { count: deletedExclusionsCount },
- ), {
- action: reactTranslator.getMessage('settings_exclusions_undo'),
- handler: exclusionsStore.restoreExclusions,
- });
- };
-
- const toggleState = () => {
- exclusionsStore.toggleExclusionState(exclusion.id);
- };
-
- const followToChildren = (exclusion: ExclusionDtoInterface) => () => {
- if (exclusion.children.length === 0) {
- return;
- }
- exclusionsStore.setSelectedExclusionId(exclusion.id);
- };
-
- const listIndexTitleClasses = (hasChildren: boolean) => cn('list-item__title', {
- 'ip-title': !hasChildren,
- });
-
- useEffect(() => {
- if (icon.current && exclusion.type === ExclusionsType.Service && exclusion.iconUrl) {
- icon.current.onerror = () => {
- if (icon.current) {
- icon.current.src = './assets/images/ip-icon.svg';
- }
- };
- icon.current.src = exclusion.iconUrl;
- }
-
- if (exclusion.type === ExclusionsType.Group) {
- const preloadedIcon = new Image();
- preloadedIcon.src = `${ICON_FOR_DOMAIN}${exclusion.hostname}`;
- preloadedIcon.onload = () => {
- if (icon.current) {
- icon.current.src = preloadedIcon.src;
- }
- };
- }
- });
-
- return (
-
-
- 0)}
- onClick={followToChildren(exclusion)}
- >
-
-
-
-
-
-
-
-
- );
-});
diff --git a/src/options/components/Exclusions/List/ListItem/index.ts b/src/options/components/Exclusions/List/ListItem/index.ts
deleted file mode 100644
index b5eb55cd4..000000000
--- a/src/options/components/Exclusions/List/ListItem/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { ListItem } from './ListItem';
diff --git a/src/options/components/Exclusions/List/ListItem/list-item.pcss b/src/options/components/Exclusions/List/ListItem/list-item.pcss
deleted file mode 100644
index 4cac85358..000000000
--- a/src/options/components/Exclusions/List/ListItem/list-item.pcss
+++ /dev/null
@@ -1,76 +0,0 @@
-.list-item {
- display: flex;
- align-items: center;
- border-radius: 5px;
- list-style: none;
- position: relative;
-
- &__title {
- display: flex;
- align-items: center;
- width: 85%;
- min-width: 450px;
- padding: 8px 50px 8px 0;
- word-break: break-all;
- cursor: pointer;
- font-size: 1.6rem;
- border: none;
- background: none;
-
- &.ip-title {
- cursor: default;
-
- & .list-item__arrow {
- display: none;
- }
- }
-
- &__icon {
- width: 24px;
- height: 24px;
- margin-right: 12px;
- }
- }
-
- &__arrow {
- width: 24px;
- height: 24px;
- position: absolute;
- right: 48px;
- }
-
- &__remove-button {
- position: absolute;
- right: 16px;
- border: none;
- max-height: 24px;
- padding: 0;
- background: none;
- cursor: pointer;
- visibility: hidden;
- transition: var(--t3) background;
-
- &:hover {
- filter: brightness(.7);
- }
-
- &__icon {
- width: 24px;
- height: 24px;
- }
- }
-
- &:hover {
- background: var(--grayF3);
-
- & .list-item__remove-button {
- visibility: visible;
- }
- }
-
- @media (--tablet) {
- .list-item__remove-button {
- visibility: visible;
- }
- }
-}
diff --git a/src/options/components/Exclusions/Loader/Loader.tsx b/src/options/components/Exclusions/Loader/Loader.tsx
index 22bcb9762..126230c00 100644
--- a/src/options/components/Exclusions/Loader/Loader.tsx
+++ b/src/options/components/Exclusions/Loader/Loader.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
import classnames from 'classnames';
import { rootStore } from '../../../stores';
+import { Icon } from '../../ui/Icon';
import './loader.pcss';
@@ -16,9 +17,8 @@ export const Loader = observer(() => {
return (
);
});
diff --git a/src/options/components/Exclusions/Loader/loader.pcss b/src/options/components/Exclusions/Loader/loader.pcss
index e7e584457..35085869a 100644
--- a/src/options/components/Exclusions/Loader/loader.pcss
+++ b/src/options/components/Exclusions/Loader/loader.pcss
@@ -10,17 +10,29 @@
height: 100%;
top: 0;
left: 0;
- background: var(--white-fog);
justify-content: center;
align-items: center;
+ &__overlay {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background-color: var(--fills-backgrounds-page-background-default);
+ opacity: 0.7;
+ }
+
&__visible {
display: flex;
}
&__spinner {
+ position: relative;
animation: spin 2s linear infinite;
width: 30px;
+ height: 30px;
+ color: var(--stroke-icons-product-icon-default);
}
&__container {
diff --git a/src/options/components/Exclusions/ModeSelectorModal/ModeSelectorModal.tsx b/src/options/components/Exclusions/ModeSelectorModal/ModeSelectorModal.tsx
index c4c7068c6..345b81e35 100644
--- a/src/options/components/Exclusions/ModeSelectorModal/ModeSelectorModal.tsx
+++ b/src/options/components/Exclusions/ModeSelectorModal/ModeSelectorModal.tsx
@@ -1,14 +1,12 @@
import React, { useContext, useState } from 'react';
-import Modal from 'react-modal';
import { observer } from 'mobx-react';
-import classnames from 'classnames';
-
import { rootStore } from '../../../stores';
import { ExclusionsMode } from '../../../../common/exclusionsConstants';
import { translator } from '../../../../common/translator';
-
-import '../../ui/radio.pcss';
+import { Modal } from '../../ui/Modal';
+import { Radio } from '../../ui/Radio';
+import { Button } from '../../ui/Button';
export const ModeSelectorModal = observer(() => {
const { exclusionsStore } = useContext(rootStore);
@@ -30,72 +28,35 @@ export const ModeSelectorModal = observer(() => {
[ExclusionsMode.Selective]: translator.getMessage('settings_exclusion_selective_title'),
};
- const renderRadioButton = (exclusionsType: ExclusionsMode) => {
- const enabled = exclusionsType === mode;
- const titleClass = classnames('radio__title', { 'radio__title--active': enabled });
-
- const xlinkHref = classnames({
- '#bullet_on': enabled,
- '#bullet_off': !enabled,
- });
-
- return (
- setMode(exclusionsType)}
- >
-
-
-
- {titles[exclusionsType]}
-
-
-
- );
- };
+ const renderRadioButton = (exclusionsType: ExclusionsMode) => (
+
+ );
return (
+
+ {translator.getMessage('settings_dns_add_custom_server_save_and_select')}
+
+
+ {translator.getMessage('settings_exclusion_modal_cancel')}
+
+ >
+ )}
+ className="exclusions__modal--radio"
+ onClose={closeModal}
>
-
-
-
-
- {translator.getMessage('settings_exclusion_change_mode_modal_title')}
-
-
- {renderRadioButton(ExclusionsMode.Regular)}
- {renderRadioButton(ExclusionsMode.Selective)}
-
-
-
- {translator.getMessage('settings_exclusion_modal_cancel')}
-
-
- {translator.getMessage('settings_exclusion_modal_save')}
-
-
+ {renderRadioButton(ExclusionsMode.Regular)}
+ {renderRadioButton(ExclusionsMode.Selective)}
);
});
diff --git a/src/options/components/Exclusions/Search/ExclusionsSearch/ExclusionsSearch.tsx b/src/options/components/Exclusions/Search/ExclusionsSearch/ExclusionsSearch.tsx
index ef80be464..861918359 100644
--- a/src/options/components/Exclusions/Search/ExclusionsSearch/ExclusionsSearch.tsx
+++ b/src/options/components/Exclusions/Search/ExclusionsSearch/ExclusionsSearch.tsx
@@ -1,9 +1,9 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
-import { Search } from '../Search';
import { rootStore } from '../../../../stores';
-import { reactTranslator } from '../../../../../common/reactTranslator';
+import { translator } from '../../../../../common/translator';
+import { Input } from '../../../ui/Input';
export const ExclusionsSearch = observer(() => {
const { exclusionsStore } = useContext(rootStore);
@@ -12,16 +12,13 @@ export const ExclusionsSearch = observer(() => {
exclusionsStore.setExclusionsSearchValue(value);
};
- const onClear = () => {
- exclusionsStore.setExclusionsSearchValue('');
- };
-
return (
-
+
+
+
);
});
diff --git a/src/options/components/Exclusions/Search/Search.tsx b/src/options/components/Exclusions/Search/Search.tsx
deleted file mode 100644
index a583d81b3..000000000
--- a/src/options/components/Exclusions/Search/Search.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-
-import './search.pcss';
-
-interface SearchProps {
- placeholder: React.ReactNode,
- value: string,
- onChange: (value: string) => void,
- onClear: () => void,
-}
-
-export const Search = ({
- placeholder,
- value,
- onChange,
- onClear,
-}: SearchProps) => {
- const handleChange = (e: React.ChangeEvent) => {
- onChange(e.target.value);
- };
-
- const handleClear = () => {
- onClear();
- };
-
- return (
-
- );
-};
diff --git a/src/options/components/Exclusions/Search/ServicesSearch/ServicesSearch.tsx b/src/options/components/Exclusions/Search/ServicesSearch/ServicesSearch.tsx
index 3b8270427..b7e46ae11 100644
--- a/src/options/components/Exclusions/Search/ServicesSearch/ServicesSearch.tsx
+++ b/src/options/components/Exclusions/Search/ServicesSearch/ServicesSearch.tsx
@@ -1,9 +1,9 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
-import { Search } from '../Search';
import { rootStore } from '../../../../stores';
-import { reactTranslator } from '../../../../../common/reactTranslator';
+import { translator } from '../../../../../common/translator';
+import { Input } from '../../../ui/Input';
export const ServicesSearch = observer(() => {
const { exclusionsStore } = useContext(rootStore);
@@ -12,16 +12,13 @@ export const ServicesSearch = observer(() => {
exclusionsStore.setServicesSearchValue(value);
};
- const onClear = () => {
- exclusionsStore.setServicesSearchValue('');
- };
-
return (
-
+
+
+
);
});
diff --git a/src/options/components/Exclusions/Search/index.ts b/src/options/components/Exclusions/Search/index.ts
index 6860ea7e1..d41b6d47a 100644
--- a/src/options/components/Exclusions/Search/index.ts
+++ b/src/options/components/Exclusions/Search/index.ts
@@ -1 +1,3 @@
-export { Search } from './Search';
+export { ExclusionsSearch } from './ExclusionsSearch';
+export { ServicesSearch } from './ServicesSearch';
+export { SearchHighlighter } from './SearchHighlighter';
diff --git a/src/options/components/Exclusions/Search/search.pcss b/src/options/components/Exclusions/Search/search.pcss
deleted file mode 100644
index 7da32fa40..000000000
--- a/src/options/components/Exclusions/Search/search.pcss
+++ /dev/null
@@ -1,35 +0,0 @@
-.search {
- position: relative;
-
- &__cross {
- width: 24px;
- height: 24px;
- color: var(--gray88);
-
- &:hover {
- filter: brightness(.7);
- }
- }
-
- &__nothing-found {
- text-align: center;
- margin-top: 90px;
- color: var(--gray88);
- }
-}
-
-.light-mode {
- .search {
- &__input {
- background-color: var(--grayF7);
- }
- }
-}
-
-.dark-mode {
- .search {
- &__input {
- background-color: #131313;
- }
- }
-}
diff --git a/src/options/components/Exclusions/StateCheckbox/StateCheckbox.tsx b/src/options/components/Exclusions/StateCheckbox/StateCheckbox.tsx
deleted file mode 100644
index 82da73026..000000000
--- a/src/options/components/Exclusions/StateCheckbox/StateCheckbox.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-
-import { ExclusionState } from '../../../../common/exclusionsConstants';
-
-import './statecheckbox.pcss';
-
-interface StateCheckboxProps {
- state: ExclusionState,
- toggleHandler: () => void,
- extraClassName?: string,
-}
-
-const getStateIcon = (state: ExclusionState) => {
- if (state === ExclusionState.Enabled) {
- return '#enabled';
- }
- if (state === ExclusionState.PartlyEnabled) {
- return '#partly-enabled';
- }
- return '#disabled';
-};
-
-export const StateCheckbox = ({
- state,
- toggleHandler,
- extraClassName,
-}: StateCheckboxProps) => {
- const btnClassNames = extraClassName
- ? `state-checkbox ${extraClassName}`
- : 'state-checkbox';
- return (
-
-
-
- );
-};
diff --git a/src/options/components/Exclusions/StateCheckbox/index.ts b/src/options/components/Exclusions/StateCheckbox/index.ts
deleted file mode 100644
index 898e85868..000000000
--- a/src/options/components/Exclusions/StateCheckbox/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { StateCheckbox } from './StateCheckbox';
diff --git a/src/options/components/Exclusions/StateCheckbox/statecheckbox.pcss b/src/options/components/Exclusions/StateCheckbox/statecheckbox.pcss
deleted file mode 100644
index 54ca06427..000000000
--- a/src/options/components/Exclusions/StateCheckbox/statecheckbox.pcss
+++ /dev/null
@@ -1,17 +0,0 @@
-.state-checkbox {
- margin-right: 16px;
- padding: 0;
- background: none;
- border: none;
- cursor: pointer;
- max-height: 24px;
-
- &__list-item {
- margin-right: 12px;
- }
-
- &__icon {
- width: 24px;
- height: 24px;
- }
-}
diff --git a/src/options/components/Exclusions/exclusions.pcss b/src/options/components/Exclusions/exclusions.pcss
index 0e502ca0c..feb0f8fe8 100644
--- a/src/options/components/Exclusions/exclusions.pcss
+++ b/src/options/components/Exclusions/exclusions.pcss
@@ -1,316 +1,110 @@
.exclusions {
- &__mode {
- position: relative;
- margin-right: 16px;
-
- &--info {
- padding: 0 16px;
- }
-
- &--link {
- color: var(--green700);
- font-weight: bold;
- font-size: 1.6rem;
- text-decoration: underline;
- cursor: pointer;
- border: none;
- background: none;
- padding: 0;
+ .title__subtitle {
+ padding-right: 0;
+ }
- &:hover {
- filter: brightness(.8);
+ &__mode {
+ &-btn {
+ color: var(--text-links-link-default);
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.3;
+ text-decoration-line: underline;
+
+ &-icon {
+ color: currentColor;
+ width: 16px;
+ height: 16px;
+ margin-left: 2px;
}
}
- &--warning {
- color: var(--orange);
- margin: 8px 0 0 16px;
+ &-warning {
+ margin-top: 20px;
+ color: var(--text-links-attention-link-default);
+ font-size: 16px;
+ line-height: 1.3;
}
}
- &__wrapper {
- padding: 0 16px;
- }
-
&__search {
- margin: 38px 0 16px;
- width: 100%;
- }
-
- &__add-website {
- font-size: 16px;
- line-height: 24px;
- font-weight: 600;
- padding: 8px 0;
- margin: 16px 0 0 0;
- display: flex;
-
- &__label {
- margin-left: 12px;
- }
- }
-}
-
-.settings {
- &__title {
- margin-bottom: 30px;
- font-size: 1.8rem;
- font-weight: 700;
- }
-
- &__subtitle {
- margin-bottom: 15px;
- cursor: pointer;
- display: flex;
- align-items: center;
- }
-
- &__group {
padding: 16px 0;
- }
- &__group-ico {
- flex-shrink: 0;
- width: 24px;
- height: 24px;
- margin-right: 8px;
- background-repeat: no-repeat;
- background-position: 50%;
- color: var(--brand-primary);
- position: relative;
- top: -1px;
- }
-
- &__list,
- &__form {
- margin-left: 10px;
- }
-
- &__list-item {
- position: relative;
- width: 100%;
- padding: 8px 8px 8px 15px;
- border: 1px solid transparent;
- border-radius: 4px;
-
- &--active,
- &:hover,
- &:focus {
- border-color: #efefef;
- }
-
- &:last-child {
- margin-bottom: 0;
+ &--services {
+ padding: 8px 0;
}
}
- &__add {
- display: flex;
- align-items: center;
- padding: 10px 15px;
- font-size: 1.2rem;
- font-weight: 700;
- line-height: 1;
-
- &:focus {
- box-shadow: none;
+ &__modal {
+ .modal__header {
+ padding-bottom: 16px;
}
- }
- &__add-icon {
- margin-right: 12px;
- }
-
- &__control {
- &:first-child {
- margin-bottom: 25px;
- }
- }
-
- &__controls {
- display: flex;
- margin-bottom: 20px;
- flex-direction: column;
-
- &--import-export {
+ .modal__actions {
+ padding-top: 24px;
+ gap: 8px;
justify-content: space-between;
- align-items: center;
- }
- }
-
- &__import-export {
- margin-left: auto;
- }
- &__item {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding-right: 15px;
-
- &--dns-server {
- width: calc(100% + 16px);
- cursor: pointer;
- border: none;
- padding: 16px;
- margin: -12px 0 0 -16px;
- background: none;
- transition: var(--t3) background-color;
- border-radius: 8px;
- text-align: left;
-
- &:hover {
- background-color: var(--grayF3);
+ @media (--tablet-sm) {
+ flex-direction: column-reverse;
}
- & .icon--button {
- position: absolute;
- top: 15px;
- right: 15px;
+ .button {
+ width: 256px;
+ padding: 16px;
+ font-size: 16px;
+ font-weight: 600;
+
+ @media (--tablet-sm) {
+ width: 100%;
+ }
}
}
- }
-
- &__item-title {
- font-size: 1.6rem;
- font-weight: 500;
- color: var(--gray-base);
- }
-
- &__item-subtitle {
- margin: 5px 20px 0 0;
- font-size: 1.4rem;
- line-height: 1.6rem;
- color: var(--gray88);
- }
-
- &__item-content {
- max-width: 420px;
- }
- &__item-desc {
- display: flex;
- margin-top: 4px;
- font-size: 1.4rem;
- line-height: 1.6rem;
- color: var(--gray88);
- overflow: hidden;
- text-overflow: ellipsis;
- }
+ &--radio {
+ .modal__header {
+ padding-bottom: 24px;
+ }
- &__change-mode-actions {
- margin-top: -10px;
- display: flex;
- gap: 16px;
+ .modal__actions {
+ padding-top: 24px;
+ }
- @media (--mobile) {
- flex-direction: column-reverse;
+ .radio {
+ outline-offset: -2px;
- .button {
- width: 100%;
+ &__title {
+ font-weight: 400;
+ }
}
}
- }
-}
-
-.modal {
- max-height: 95%;
- max-width: 95%;
- overflow-y: auto;
- background: var(--white-strong);
- position: relative;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- padding: 32px;
- display: flex;
- flex-direction: column;
- outline: none;
- border-radius: 8px;
-
- &-exclusions {
- width: 600px;
- }
- &--big {
- width: 600px;
- }
-
- &__close-icon {
- position: fixed;
- right: 14px;
- top: 14px;
- }
+ &--empty-body {
+ .modal__header {
+ padding-bottom: 0;
+ }
- &__icon {
- width: 100%;
- min-height: 80px;
- background-image: url('../../../assets/images/trouble-head.svg');
- background-position: center;
- background-repeat: no-repeat;
- margin-top: 20px;
+ .modal__actions {
+ padding-top: 24px;
+ }
+ }
}
- &__message {
- margin: 0 30px 24px;
- font-size: 16px;
- line-height: 22px;
+ &__empty {
text-align: center;
+ margin: 50px 0;
+ color: var(--text-description-description-default);
}
- &__buttons {
- padding-top: 16px;
- }
-
- &__button {
+ &__reset-default {
margin-top: 16px;
- height: 44px;
- padding: 0 10px;
- min-width: 180px;
- border: 1px solid var(--gray88);
- border-radius: 8px;
- font-size: 16px;
- color: var(--gray88);
- letter-spacing: 0.38px;
- text-align: center;
- line-height: 16px;
-
- &--red {
- border-color: var(--redDark);
- background-color: var(--redDark);
- color: var(--white);
- &:hover {
- border-color: var(--red);
- background-color: var(--red);
- }
- }
-
- &--first {
- margin-right: 16px;
+ @media (--tablet-sm) {
+ margin: 16px 16px 0;
}
}
- &.select-mode {
- height: 400px;
-
- & .modal__button {
- width: 200px;
- margin: 0 auto;
- }
- }
-}
-
-.overlay {
- z-index: var(--modal-z);
- background-color: var(--overlay);
- position: fixed;
- top: 0;
-
- &--fullscreen {
- left: 0;
- right: 0;
- bottom: 0;
+ &__add-subdomain {
+ padding-left: 40px;
}
}
diff --git a/src/options/components/FreeGbs/AddDevice.tsx b/src/options/components/FreeGbs/AddDevice.tsx
index 4221dd8a1..dd439b784 100644
--- a/src/options/components/FreeGbs/AddDevice.tsx
+++ b/src/options/components/FreeGbs/AddDevice.tsx
@@ -1,75 +1,76 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
-import { rootStore } from '../../stores';
-import { Title } from '../ui/Title';
import { getForwarderUrl } from '../../../common/helpers';
-import { reactTranslator } from '../../../common/reactTranslator';
-import { COMPLETE_TASK_BONUS_GB } from '../../stores/consts';
+import { translator } from '../../../common/translator';
import { FORWARDER_URL_QUERIES } from '../../../background/config';
+import { COMPLETE_TASK_BONUS_GB } from '../../stores/consts';
+import { rootStore } from '../../stores';
+import { Icon } from '../ui/Icon';
+import { Title } from '../ui/Title';
+import { Button } from '../ui/Button';
export const AddDevice = observer(({ goBackHandler }: { goBackHandler: () => void }) => {
const { settingsStore } = useContext(rootStore);
const { multiplatformBonus, forwarderDomain } = settingsStore;
const otherProductsUrl = getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.OTHER_PRODUCTS);
+ const isCompleted = !multiplatformBonus.available;
- const getContent = () => {
- if (multiplatformBonus.available) {
- return (
- <>
-
-
- {reactTranslator.getMessage('settings_free_gbs_add_device_info', { your_gb: COMPLETE_TASK_BONUS_GB })}
-
-
-
-
- {reactTranslator.getMessage('settings_free_gbs_add_device_products_button')}
-
- >
- );
- }
+ const title = isCompleted
+ ? translator.getMessage('settings_free_gbs_devices_added_title')
+ : translator.getMessage('settings_free_gbs_add_device_title');
- return (
- <>
-
- {reactTranslator.getMessage('settings_free_gbs_devices_added_info')}
-
- {reactTranslator.getMessage('settings_free_gbs_go_back')}
-
- >
- );
- };
+ const description = isCompleted
+ ? translator.getMessage('settings_free_gbs_devices_added_info')
+ : translator.getMessage('settings_free_gbs_add_device_info', { your_gb: COMPLETE_TASK_BONUS_GB });
return (
-
-
-
-
-
- {getContent()}
+
+
+

+
+
+ {!isCompleted ? (
+ <>
+

+
+ >
+ ) : (
+
+ {translator.getMessage('settings_free_gbs_go_back')}
+
+ )}
+
);
});
diff --git a/src/options/components/FreeGbs/ConfirmEmail.tsx b/src/options/components/FreeGbs/ConfirmEmail.tsx
index 023fcbef1..93fde3ef8 100644
--- a/src/options/components/FreeGbs/ConfirmEmail.tsx
+++ b/src/options/components/FreeGbs/ConfirmEmail.tsx
@@ -1,10 +1,11 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
+import { translator } from '../../../common/translator';
import { rootStore } from '../../stores';
-import { reactTranslator } from '../../../common/reactTranslator';
-import { Title } from '../ui/Title';
import { COMPLETE_TASK_BONUS_GB } from '../../stores/consts';
+import { Button } from '../ui/Button';
+import { Title } from '../ui/Title';
export const ConfirmEmail = observer(({ goBackHandler }: { goBackHandler: () => void }) => {
const { settingsStore, notificationsStore } = useContext(rootStore);
@@ -12,56 +13,52 @@ export const ConfirmEmail = observer(({ goBackHandler }: { goBackHandler: () =>
const resendLink = async () => {
await resendConfirmationLink();
- notificationsStore.notifySuccess(reactTranslator.getMessage('resend_confirm_registration_link_notification'));
+ notificationsStore.notifySuccess(translator.getMessage('resend_confirm_registration_link_notification'));
};
- const getContent = () => {
- if (confirmBonus.available) {
- return (
- <>
-
-
- {reactTranslator.getMessage('settings_free_gbs_confirm_email_info', { your_gb: COMPLETE_TASK_BONUS_GB })}
-
-
- {reactTranslator.getMessage('settings_free_gbs_confirm_email_resend_link_button')}
-
- >
- );
- }
+ const isCompleted = !confirmBonus.available;
- return (
- <>
-
-
{reactTranslator.getMessage('confirm_email_done_info')}
-
- {reactTranslator.getMessage('settings_free_gbs_go_back')}
-
- >
- );
- };
+ const title = isCompleted
+ ? translator.getMessage('confirm_email_done_title')
+ : translator.getMessage('settings_free_gbs_confirm_email_title');
+
+ const description = isCompleted
+ ? translator.getMessage('confirm_email_done_info')
+ : translator.getMessage('settings_free_gbs_confirm_email_info', { your_gb: COMPLETE_TASK_BONUS_GB });
return (
-
-
-
-
-
- {getContent()}
+
+
+

+
+
+ {!isCompleted ? (
+
+ {translator.getMessage('settings_free_gbs_confirm_email_resend_link_button')}
+
+ ) : (
+
+ {translator.getMessage('settings_free_gbs_go_back')}
+
+ )}
+
);
});
diff --git a/src/options/components/FreeGbs/FreeGbs.tsx b/src/options/components/FreeGbs/FreeGbs.tsx
index ae49d94ab..b0c72b6a1 100644
--- a/src/options/components/FreeGbs/FreeGbs.tsx
+++ b/src/options/components/FreeGbs/FreeGbs.tsx
@@ -2,12 +2,15 @@ import React, { useContext, useEffect } from 'react';
import { observer } from 'mobx-react';
import { useHistory, useLocation } from 'react-router-dom';
-import { rootStore } from '../../stores';
-import { reactTranslator } from '../../../common/reactTranslator';
+import classNames from 'classnames';
+
import { translator } from '../../../common/translator';
-import { Title } from '../ui/Title';
import { DotsLoader } from '../../../common/components/DotsLoader';
+import { rootStore } from '../../stores';
import { RequestStatus, COMPLETE_TASK_BONUS_GB } from '../../stores/consts';
+import { Title } from '../ui/Title';
+import { Controls } from '../ui/Controls';
+import { Icon, IconButton } from '../ui/Icon';
import { InviteFriend } from './InviteFriend';
import { ConfirmEmail } from './ConfirmEmail';
@@ -48,7 +51,8 @@ export const FreeGbs = observer(() => {
const { invitesCount, maxInvitesCount } = invitesBonuses;
const history = useHistory();
- const query = new URLSearchParams(useLocation().search);
+ const { search } = useLocation();
+ const query = new URLSearchParams(search);
const goBackHandler = () => {
history.push(`/${FREE_GBS}`);
@@ -60,25 +64,25 @@ export const FreeGbs = observer(() => {
const inviteFriendTitle = `${translator.getMessage('settings_free_gbs_invite_friend')} (${invitesCount}/${maxInvitesCount})`;
- const itemsData = [
+ const itemsData: RenderItemProps[] = [
{
title: inviteFriendTitle,
- status: reactTranslator.getMessage('settings_free_gbs_invite_friend_get_GB', { num: maxInvitesCount }),
- statusDone: reactTranslator.getMessage('settings_free_gbs_invite_friend_complete', { num: maxInvitesCount }),
+ status: translator.getMessage('settings_free_gbs_invite_friend_get_GB', { num: maxInvitesCount }),
+ statusDone: translator.getMessage('settings_free_gbs_invite_friend_complete', { num: maxInvitesCount }),
query: INVITE_FRIEND,
completed: invitesQuestCompleted,
},
{
title: translator.getMessage('settings_free_gbs_confirm_email_title'),
- status: reactTranslator.getMessage('settings_free_gbs_get_GB', { num: COMPLETE_TASK_BONUS_GB }),
- statusDone: reactTranslator.getMessage('settings_free_gbs_task_complete', { num: COMPLETE_TASK_BONUS_GB }),
+ status: translator.getMessage('settings_free_gbs_get_GB', { num: COMPLETE_TASK_BONUS_GB }),
+ statusDone: translator.getMessage('settings_free_gbs_task_complete', { num: COMPLETE_TASK_BONUS_GB }),
query: CONFIRM_EMAIL,
completed: confirmEmailQuestCompleted,
},
{
title: translator.getMessage('settings_free_gbs_add_device_title'),
- status: reactTranslator.getMessage('settings_free_gbs_get_GB', { num: COMPLETE_TASK_BONUS_GB }),
- statusDone: reactTranslator.getMessage('settings_free_gbs_task_complete', { num: COMPLETE_TASK_BONUS_GB }),
+ status: translator.getMessage('settings_free_gbs_get_GB', { num: COMPLETE_TASK_BONUS_GB }),
+ statusDone: translator.getMessage('settings_free_gbs_task_complete', { num: COMPLETE_TASK_BONUS_GB }),
query: ADD_DEVICE,
completed: addDeviceQuestCompleted,
},
@@ -92,53 +96,41 @@ export const FreeGbs = observer(() => {
completed,
}: RenderItemProps) => {
return (
-
}
+ action={
}
onClick={() => clickItemHandler(query)}
- >
-
-
-
{title}
-
{completed ? statusDone : status}
-
-
-
+ />
);
};
- switch (true) {
- case query.has(INVITE_FRIEND): {
- return
;
- }
- case query.has(CONFIRM_EMAIL): {
- return
;
- }
- case query.has(ADD_DEVICE): {
- return
;
- }
- case bonusesDataRequestStatus !== RequestStatus.Done: {
- return
;
- }
- default: {
- return (
- <>
-
- {itemsData.map(renderItem)}
- >
- );
- }
+ if (query.has(INVITE_FRIEND)) {
+ return
;
+ }
+
+ if (query.has(CONFIRM_EMAIL)) {
+ return
;
+ }
+
+ if (query.has(ADD_DEVICE)) {
+ return
;
}
+
+ if (bonusesDataRequestStatus !== RequestStatus.Done) {
+ return
;
+ }
+
+ return (
+ <>
+
+ {itemsData.map(renderItem)}
+ >
+ );
});
diff --git a/src/options/components/FreeGbs/InviteFriend.tsx b/src/options/components/FreeGbs/InviteFriend.tsx
index 55a011083..1d798b21c 100644
--- a/src/options/components/FreeGbs/InviteFriend.tsx
+++ b/src/options/components/FreeGbs/InviteFriend.tsx
@@ -1,11 +1,13 @@
import React, { useContext, useEffect } from 'react';
import { observer } from 'mobx-react';
-import { reactTranslator } from '../../../common/reactTranslator';
-import { Title } from '../ui/Title';
-import { rootStore } from '../../stores';
import { DotsLoader } from '../../../common/components/DotsLoader';
+import { translator } from '../../../common/translator';
+import { rootStore } from '../../stores';
import { RequestStatus, COMPLETE_TASK_BONUS_GB } from '../../stores/consts';
+import { Input } from '../ui/Input';
+import { Button } from '../ui/Button';
+import { Title } from '../ui/Title';
export const InviteFriend = observer(({ goBackHandler }: { goBackHandler: () => void }) => {
const { settingsStore, notificationsStore } = useContext(rootStore);
@@ -13,6 +15,18 @@ export const InviteFriend = observer(({ goBackHandler }: { goBackHandler: () =>
const { invitesBonuses, bonusesDataRequestStatus } = settingsStore;
const { invitesCount, maxInvitesCount, inviteUrl } = invitesBonuses;
+ const isCompleted = invitesCount >= maxInvitesCount;
+
+ const title = translator.getMessage('settings_free_gbs_invite_friend');
+
+ const description = isCompleted
+ ? translator.getMessage('settings_free_gbs_invite_friend_completed_thank_you')
+ : translator.getMessage('settings_referral_info', {
+ your_gb: COMPLETE_TASK_BONUS_GB,
+ total_gb: maxInvitesCount,
+ friend_gb: COMPLETE_TASK_BONUS_GB,
+ });
+
useEffect(() => {
(async () => {
await settingsStore.updateBonusesData();
@@ -22,101 +36,62 @@ export const InviteFriend = observer(({ goBackHandler }: { goBackHandler: () =>
const handleCopyLink = async (e: React.FormEvent
) => {
e.preventDefault();
await navigator.clipboard.writeText(inviteUrl);
- notificationsStore.notifySuccess(reactTranslator.getMessage('settings_referral_link_copied'));
+ notificationsStore.notifySuccess(translator.getMessage('settings_referral_link_copied'));
};
if (bonusesDataRequestStatus !== RequestStatus.Done) {
return ;
}
- switch (true) {
- case bonusesDataRequestStatus !== RequestStatus.Done: {
- return ;
- }
- case invitesCount >= maxInvitesCount: {
- return (
-
-
-
-
-
-
-
{reactTranslator.getMessage('settings_free_gbs_invite_friend_completed_thank_you')}
-
- {reactTranslator.getMessage('settings_free_gbs_go_back')}
-
-
- );
- }
- default: {
- return (
-
-
-
-
-
-
-
- {reactTranslator.getMessage('settings_referral_info', {
- your_gb: COMPLETE_TASK_BONUS_GB,
- total_gb: maxInvitesCount,
- friend_gb: COMPLETE_TASK_BONUS_GB,
- })}
-
-
- {reactTranslator.getMessage(
- 'settings_referral_invited_friends',
- {
- count: invitesCount,
- limit: maxInvitesCount,
- },
- )}
-
+ return (
+
+
+

+
+
+ {!isCompleted ? (
+ ) : (
+
+ {translator.getMessage('settings_free_gbs_go_back')}
+
+ )}
+
+ {translator.getMessage('settings_referral_invited_friends', {
+ count: invitesCount,
+ limit: maxInvitesCount,
+ })}
- );
- }
- }
+
+
+ );
});
diff --git a/src/options/components/FreeGbs/free-gbs.pcss b/src/options/components/FreeGbs/free-gbs.pcss
index 5dde892ad..5f67c77a6 100644
--- a/src/options/components/FreeGbs/free-gbs.pcss
+++ b/src/options/components/FreeGbs/free-gbs.pcss
@@ -1,148 +1,119 @@
.free-gbs {
- &__item {
- display: flex;
- padding: 16px;
- padding-right: 48px;
- margin-left: 16px;
- border-radius: 8px;
- width: 100%;
- transform: translateX(-16px);
- align-items: center;
- cursor: pointer;
- transition: var(--t3) background-color;
- text-decoration: none;
- background: none;
- border: none;
- font-weight: 500;
- text-align: left;
-
- &:hover {
- background-color: var(--grayF3);
+ &__button {
+ &-check-icon {
+ color: var(--stroke-icons-gray-icons-default);
+ transition: var(--t3) color;
+
+ .free-gbs__button--done & {
+ color: var(--stroke-icons-product-icon-default);
+ }
}
- &--title {
- font-size: 16px;
- font-weight: 500;
- line-height: 19px;
- color: var(--gray-base);
+ &-arrow-icon {
+ transform: rotate(-90deg);
}
+ }
+}
- &--status {
- font-size: 14px;
- line-height: 18px;
- color: var(--gray88);
- }
+.free-gbs-task {
+ &__image {
+ width: 250px;
+ height: 250px;
+ }
- &--check-mark {
- position: relative;
- top: -10px;
- margin-right: 16px;
- }
+ &__title {
+ @media (--mobile) {
+ .title__text-start {
+ justify-content: center;
+ }
- &--arrow {
- position: absolute;
- right: 20px;
- top: 15px;
+ .title__subtitle {
+ text-align: center;
+ }
}
}
&__content {
padding: 0 16px;
- & .content__title {
- padding-left: 0;
- }
- }
+ .button {
+ flex-shrink: 0;
+ width: 200px;
- &__picture {
- width: 200px;
- height: 200px;
- background-repeat: no-repeat;
- margin: -20px 0 10px;
- }
-
- &__confirm-email-pic {
- background-image: url("../../../assets/images/confirm-email-task.svg");
+ @media (--mobile) {
+ width: 100%;
+ }
+ }
}
- &__add-device-pic {
- background-image: url("../../../assets/images/add-device.svg");
+ &__go-back-btn {
+ margin-top: 16px;
}
+}
- &__referral-pic {
- background-image: url("../../../assets/images/referral.svg");
+.invite-friend {
+ &__content {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ column-gap: 16px;
+
+ @media (--tablet-sm) {
+ flex-direction: column;
+ column-gap: 0;
+ row-gap: 16px;
+ align-items: flex-start;
+ justify-content: flex-start;
+ }
}
- &__info {
- max-width: 430px;
- margin-bottom: 25px;
+ &__counter {
+ margin-top: 24px;
font-size: 16px;
- line-height: 24px;
+ font-weight: 600;
+ line-height: 1.3;
}
- &__back-button {
- cursor: pointer;
- border: 0;
- background: transparent;
- transform: rotate(180deg);
- padding: 0;
- margin-left: -35px;
-
- @media (--tablet) {
- margin-left: 0;
- }
+ &__btn.button {
+ /* We are aligning height of button with input height */
+ padding: 14px 16px;
}
+}
- &__products-pic {
- display: block;
- margin-bottom: 26px;
+.confirm-email {
+ &__btn {
+ margin-top: 16px;
}
+}
- &__referral-status {
- margin-top: 25px;
- font-weight: bold;
- }
+.add-device {
+ &__link {
+ margin-top: 24px;
+ text-decoration: none;
- &__referral-link {
- margin-top: 30px;
- position: relative;
-
- & input {
- width: 100%;
- padding: 16px 23px;
- border: none;
- border-radius: 6px;
- background-color: var(--grayF2);
- color: var(--gray-base);
- margin: 7px 15px 30px 0;
- font-size: 16px;
- display: block;
+ &.button {
+ display: inline-flex;
+ width: auto;
+ min-width: 200px;
+
+ @media (--mobile) {
+ width: 100%;
+ }
}
- & label {
- font-size: 14px;
- color: var(--gray88);
+ &-icon {
+ color: currentColor;
}
}
- &__copy-link-message {
- font-size: 1.2rem;
- margin-left: 25px;
- visibility: hidden;
- position: absolute;
- min-width: 100px;
- top: 13px;
+ &__products {
+ width: 150px;
- &__display {
- visibility: visible;
+ @media (--mobile) {
+ display: block;
+ margin: 0 auto;
}
}
-
- &__external-link {
- margin: 0 7px 0 0;
- position: relative;
- top: -2px;
- }
}
.dots-loader {
diff --git a/src/options/components/General/ContextMenus.tsx b/src/options/components/General/ContextMenus.tsx
new file mode 100644
index 000000000..f0cd87706
--- /dev/null
+++ b/src/options/components/General/ContextMenus.tsx
@@ -0,0 +1,24 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { translator } from '../../../common/translator';
+import { rootStore } from '../../stores';
+import { ControlsSwitch } from '../ui/Controls';
+
+export const ContextMenus = observer(() => {
+ const { settingsStore } = useContext(rootStore);
+ const { contextMenusEnabled } = settingsStore;
+
+ const handleToggle = async (): Promise
=> {
+ await settingsStore.setContextMenusValue(!contextMenusEnabled);
+ };
+
+ return (
+
+ );
+});
diff --git a/src/options/components/General/DnsSettings/DnsSettings.tsx b/src/options/components/General/DnsSettings/DnsSettings.tsx
new file mode 100644
index 000000000..9bcb11eba
--- /dev/null
+++ b/src/options/components/General/DnsSettings/DnsSettings.tsx
@@ -0,0 +1,104 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { type DnsServerData } from '../../../../background/schema';
+import { DEFAULT_DNS_SERVER, POPULAR_DNS_SERVERS } from '../../../../background/dns/dnsConstants';
+import { translator } from '../../../../common/translator';
+import { rootStore } from '../../../stores';
+import { Title } from '../../ui/Title';
+import { Button } from '../../ui/Button';
+
+import { DnsSettingsServer } from './DnsSettingsServer';
+import { DnsSettingsServerModalAdd } from './DnsSettingsServerModalAdd';
+import { DnsSettingsServerModalEdit } from './DnsSettingsServerModalEdit';
+
+import './dns-settings.pcss';
+
+export const DnsSettings = observer(() => {
+ const { settingsStore, notificationsStore } = useContext(rootStore);
+
+ const handleGoBack = () => {
+ settingsStore.setShowDnsSettings(false);
+ };
+
+ const handleSelect = (dnsServerId: string) => {
+ settingsStore.setDnsServer(dnsServerId);
+ };
+
+ const handleAddClick = () => {
+ settingsStore.openCustomDnsModal();
+ };
+
+ const handleEditClick = (dnsServer: DnsServerData) => {
+ settingsStore.setDnsServerToEdit(dnsServer);
+ settingsStore.openCustomDnsModal();
+ };
+
+ const handleDeleteClick = (dnsServerId: string) => {
+ settingsStore.removeCustomDnsServer(dnsServerId);
+ notificationsStore.notifySuccess(
+ translator.getMessage('settings_dns_delete_custom_server_notification'),
+ {
+ action: translator.getMessage('settings_exclusions_undo'),
+ handler: () => settingsStore.restoreCustomDnsServersData(),
+ },
+ );
+ };
+
+ const renderDnsServer = (dnsServer: DnsServerData) => (
+
+ );
+
+ const renderCustomDnsServer = (dnsServer: DnsServerData) => (
+
+ );
+
+ return (
+ <>
+
+
+ {/* DEFAULT SERVER */}
+ {renderDnsServer(DEFAULT_DNS_SERVER)}
+
+ {/* POPULAR SERVERS */}
+
+ {POPULAR_DNS_SERVERS.map(renderDnsServer)}
+
+ {/* CUSTOM SERVERS */}
+
+ {settingsStore.customDnsServers.map(renderCustomDnsServer)}
+
+ {translator.getMessage('settings_dns_add_custom_server')}
+
+
+
+ >
+ );
+});
diff --git a/src/options/components/General/DnsSettings/DnsSettingsButton.tsx b/src/options/components/General/DnsSettings/DnsSettingsButton.tsx
new file mode 100644
index 000000000..9953d6320
--- /dev/null
+++ b/src/options/components/General/DnsSettings/DnsSettingsButton.tsx
@@ -0,0 +1,34 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { translator } from '../../../../common/translator';
+import { rootStore } from '../../../stores';
+import { Controls } from '../../ui/Controls';
+import { IconButton } from '../../ui/Icon';
+
+export const DnsSettingsButton = observer(() => {
+ const { settingsStore } = useContext(rootStore);
+ const { currentDnsServerName } = settingsStore;
+
+ const handleClick = () => {
+ settingsStore.setShowDnsSettings(true);
+ };
+
+ return (
+
+ {translator.getMessage('settings_dns_description')}
+
+
+ {translator.getMessage('settings_dns_description_current', {
+ dnsServerName: currentDnsServerName,
+ })}
+ >
+ )}
+ action={}
+ onClick={handleClick}
+ />
+ );
+});
diff --git a/src/options/components/General/DnsSettings/DnsSettingsServer.tsx b/src/options/components/General/DnsSettings/DnsSettingsServer.tsx
new file mode 100644
index 000000000..b1e97ce7b
--- /dev/null
+++ b/src/options/components/General/DnsSettings/DnsSettingsServer.tsx
@@ -0,0 +1,89 @@
+import React from 'react';
+
+import { type DnsServerData } from '../../../../background/schema';
+import { Radio } from '../../ui/Radio';
+import { IconButton, type IconButtonProps } from '../../ui/Icon';
+
+interface ModifyButtonProps {
+ icon: string;
+ hoverColor?: IconButtonProps['hoverColor'];
+ onClick: () => void;
+}
+
+function ModifyButton({ icon, hoverColor, onClick }: ModifyButtonProps) {
+ const handleClick = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onClick();
+ };
+
+ return (
+
+ );
+}
+
+interface DnsSettingsServerBaseProps {
+ name: string;
+ value: DnsServerData;
+ isActive: boolean;
+ onSelect: (dnsServerId: string) => void;
+}
+
+interface DnsSettingsServerDefinedProps extends DnsSettingsServerBaseProps {
+ custom?: false;
+}
+
+interface DnsSettingsServerCustomProps extends DnsSettingsServerBaseProps {
+ custom: true;
+ onEdit: (dnsServer: DnsServerData) => void;
+ onDelete: (dnsServerId: string) => void;
+}
+
+export type DnsSettingsServerProps = DnsSettingsServerDefinedProps | DnsSettingsServerCustomProps;
+
+export function DnsSettingsServer({
+ name,
+ value,
+ isActive,
+ onSelect,
+ ...restProps
+}: DnsSettingsServerProps) {
+ if (restProps.custom) {
+ return (
+
+ restProps.onEdit(value)}
+ />
+ restProps.onDelete(value.id)}
+ />
+
+ )}
+ />
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/options/components/General/DnsSettings/DnsSettingsServerModal.tsx b/src/options/components/General/DnsSettings/DnsSettingsServerModal.tsx
new file mode 100644
index 000000000..f30d80121
--- /dev/null
+++ b/src/options/components/General/DnsSettings/DnsSettingsServerModal.tsx
@@ -0,0 +1,116 @@
+import React, { useContext, useId, useState } from 'react';
+import { observer } from 'mobx-react';
+
+import { rootStore } from '../../../stores';
+import { translator } from '../../../../common/translator';
+import { Button } from '../../ui/Button';
+import { Modal } from '../../ui/Modal';
+import { Input } from '../../ui/Input';
+
+export interface DnsSettingsServerModalProps {
+ title: React.ReactNode;
+ description?: React.ReactNode;
+ submitBtnTitle?: React.ReactNode;
+ isOpen: boolean;
+ onSubmit: (dnsServerName: string, dnsServerAddress: string) => Promise;
+}
+
+export const DnsSettingsServerModal = observer(({
+ title,
+ description,
+ submitBtnTitle,
+ isOpen,
+ onSubmit,
+}: DnsSettingsServerModalProps) => {
+ const { settingsStore } = useContext(rootStore);
+
+ const {
+ dnsServerName,
+ dnsServerAddress,
+ } = settingsStore;
+
+ const formId = useId();
+
+ const [dnsServerAddressError, setDnsServerAddressError] = useState(null);
+
+ const handleCloseModal = () => {
+ settingsStore.closeCustomDnsModal();
+ settingsStore.setDnsServerName('');
+ settingsStore.setDnsServerAddress('');
+ settingsStore.setDnsServerToEdit(null);
+ setDnsServerAddressError(null);
+ };
+
+ const handleDnsServerNameChange = (value: string) => {
+ settingsStore.setDnsServerName(value);
+ };
+
+ const handleDnsServerAddressChange = (value: string) => {
+ settingsStore.setDnsServerAddress(value);
+ if (dnsServerAddressError) {
+ setDnsServerAddressError(null);
+ }
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ const dnsServerAddressError = await onSubmit(dnsServerName, dnsServerAddress);
+ if (dnsServerAddressError) {
+ setDnsServerAddressError(dnsServerAddressError);
+ return;
+ }
+ handleCloseModal();
+ };
+
+ const handleReset = (e: React.FormEvent) => {
+ e.preventDefault();
+ handleCloseModal();
+ };
+
+ return (
+
+
+ {submitBtnTitle}
+
+
+ {translator.getMessage('settings_exclusion_modal_cancel')}
+
+ >
+ )}
+ onClose={handleCloseModal}
+ >
+
+
+ );
+});
diff --git a/src/options/components/General/DnsSettings/DnsSettingsServerModalAdd.tsx b/src/options/components/General/DnsSettings/DnsSettingsServerModalAdd.tsx
new file mode 100644
index 000000000..ca13eafd1
--- /dev/null
+++ b/src/options/components/General/DnsSettings/DnsSettingsServerModalAdd.tsx
@@ -0,0 +1,56 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { rootStore } from '../../../stores';
+import { getForwarderUrl } from '../../../../common/helpers';
+import { translator } from '../../../../common/translator';
+import { FORWARDER_URL_QUERIES } from '../../../../background/config';
+
+import { DnsSettingsServerModal } from './DnsSettingsServerModal';
+import { normalizeDnsServerAddress, validateDnsServerAddress } from './validate';
+
+export const DnsSettingsServerModalAdd = observer(() => {
+ const { settingsStore, notificationsStore } = useContext(rootStore);
+ const {
+ forwarderDomain,
+ isCustomDnsModalOpen,
+ customDnsServers,
+ dnsServerToEdit,
+ } = settingsStore;
+
+ const adguardKnownDnsKbUrl = getForwarderUrl(
+ forwarderDomain,
+ FORWARDER_URL_QUERIES.ADGUARD_DNS_PROVIDERS_KB,
+ );
+
+ const handleSubmit = async (dnsServerName: string, dnsServerAddress: string) => {
+ const dnsServerAddressError = validateDnsServerAddress(customDnsServers, dnsServerAddress);
+ if (dnsServerAddressError) {
+ return dnsServerAddressError;
+ }
+ const normalizedDnsServerAddress = normalizeDnsServerAddress(dnsServerAddress);
+ const dnsServer = await settingsStore.addCustomDnsServer(dnsServerName, normalizedDnsServerAddress);
+ notificationsStore.notifySuccess(
+ translator.getMessage('settings_dns_add_custom_server_notification_success'),
+ {
+ action: translator.getMessage('settings_exclusions_undo'),
+ handler: () => settingsStore.removeCustomDnsServer(dnsServer.id),
+ },
+ );
+ return null;
+ };
+
+ return (
+
+ {translator.getMessage('settings_dns_add_custom_server_info')}
+
+ )}
+ submitBtnTitle={translator.getMessage('settings_dns_add_custom_server_save_and_select')}
+ isOpen={isCustomDnsModalOpen && !dnsServerToEdit}
+ onSubmit={handleSubmit}
+ />
+ );
+});
diff --git a/src/options/components/General/DnsSettings/DnsSettingsServerModalEdit.tsx b/src/options/components/General/DnsSettings/DnsSettingsServerModalEdit.tsx
new file mode 100644
index 000000000..4e07f5292
--- /dev/null
+++ b/src/options/components/General/DnsSettings/DnsSettingsServerModalEdit.tsx
@@ -0,0 +1,62 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { rootStore } from '../../../stores';
+import { translator } from '../../../../common/translator';
+
+import { DnsSettingsServerModal } from './DnsSettingsServerModal';
+import { normalizeDnsServerAddress, validateDnsServerAddress } from './validate';
+
+export const DnsSettingsServerModalEdit = observer(() => {
+ const { settingsStore, notificationsStore } = useContext(rootStore);
+
+ const {
+ isCustomDnsModalOpen,
+ customDnsServers,
+ dnsServerToEdit,
+ } = settingsStore;
+
+ const handleSubmit = async (dnsServerName: string, dnsServerAddress: string) => {
+ if (!dnsServerToEdit) {
+ return null;
+ }
+ const {
+ id,
+ title: oldDnsServerName,
+ address: oldDnsServerAddress,
+ } = dnsServerToEdit;
+
+ if (oldDnsServerAddress !== dnsServerAddress) {
+ // `oldDnsServerAddress` is dns address before editing,
+ // `dnsServerAddress` is the state of dns address form.
+ // if dns address was edited, it has to be verified.
+ const dnsServerAddressError = validateDnsServerAddress(customDnsServers, dnsServerAddress);
+ if (dnsServerAddressError) {
+ return dnsServerAddressError;
+ }
+ } else if (oldDnsServerName === dnsServerName) {
+ // If nothing changed just return
+ return null;
+ }
+ const normalizedDnsServerAddress = normalizeDnsServerAddress(dnsServerAddress);
+ await settingsStore.editCustomDnsServer(id, dnsServerName, normalizedDnsServerAddress);
+ notificationsStore.notifySuccess(
+ translator.getMessage('settings_dns_edit_custom_server_notification'),
+ {
+ action: translator.getMessage('settings_exclusions_undo'),
+ handler: () => settingsStore.editCustomDnsServer(id, oldDnsServerName, oldDnsServerAddress),
+ },
+ );
+
+ return null;
+ };
+
+ return (
+
+ );
+});
diff --git a/src/options/components/General/DnsSettings/dns-settings.pcss b/src/options/components/General/DnsSettings/dns-settings.pcss
new file mode 100644
index 000000000..1816981d4
--- /dev/null
+++ b/src/options/components/General/DnsSettings/dns-settings.pcss
@@ -0,0 +1,40 @@
+.dns-settings {
+ &__btn-icon {
+ transform: rotate(-90deg);
+ }
+
+ &__title {
+ margin-bottom: 24px;
+ }
+
+ &__popular-servers {
+ padding-top: 32px;
+ }
+
+ &__custom {
+ &:hover,
+ &:active,
+ &:focus-visible,
+ &:focus-within {
+ .dns-settings__custom-actions {
+ opacity: 1;
+ }
+ }
+
+ &-actions {
+ display: flex;
+ column-gap: 16px;
+ /* Use opacity instead of display to not cause layout shifts */
+ opacity: 0;
+ transition: var(--t3) opacity;
+
+ @media (--touch-screen) {
+ opacity: 1;
+ }
+ }
+
+ &-servers {
+ padding-top: 48px;
+ }
+ }
+}
diff --git a/src/options/components/General/DnsSettings/index.ts b/src/options/components/General/DnsSettings/index.ts
new file mode 100644
index 000000000..a62add4b0
--- /dev/null
+++ b/src/options/components/General/DnsSettings/index.ts
@@ -0,0 +1,2 @@
+export { DnsSettings } from './DnsSettings';
+export { DnsSettingsButton } from './DnsSettingsButton';
diff --git a/src/options/components/General/DnsSettings/validate.ts b/src/options/components/General/DnsSettings/validate.ts
new file mode 100644
index 000000000..faf82b4d5
--- /dev/null
+++ b/src/options/components/General/DnsSettings/validate.ts
@@ -0,0 +1,48 @@
+import { isIP } from 'is-ip';
+
+import { type DnsServerData } from '../../../../background/schema';
+import { translator } from '../../../../common/translator';
+
+const DOH_PREFIX = 'https://';
+const DOT_PREFIX = 'tls://';
+
+const DNS_SERVER_ERROR = {
+ INVALID: translator.getMessage('settings_dns_add_custom_server_invalid_address'),
+ DUPLICATE: translator.getMessage('settings_dns_add_custom_server_duplicate_address'),
+};
+
+/**
+ * Validate custom dns server address.
+ *
+ * @param customDnsServers List of custom dns servers.
+ * @param address Address to validate.
+ * @returns Error message if address is invalid, otherwise null.
+ */
+export const validateDnsServerAddress = (
+ customDnsServers: DnsServerData[],
+ address: string,
+): string | null => {
+ // check existing custom dns addresses
+ if (customDnsServers.some((server) => server.address === address)) {
+ return DNS_SERVER_ERROR.DUPLICATE;
+ }
+
+ // for the moment only plain dns and tls supported
+ if (address.startsWith(DOH_PREFIX) || !address.includes('.')) {
+ return DNS_SERVER_ERROR.INVALID;
+ }
+ return null;
+};
+
+/**
+ * Normalize dns server address.
+ *
+ * @param address Address to normalize.
+ * @returns Normalized address.
+ */
+export const normalizeDnsServerAddress = (address: string) => {
+ if (isIP(address) || address.startsWith(DOT_PREFIX)) {
+ return address;
+ }
+ return `${DOT_PREFIX}${address}`;
+};
diff --git a/src/options/components/General/General.tsx b/src/options/components/General/General.tsx
new file mode 100644
index 000000000..9c1021b3e
--- /dev/null
+++ b/src/options/components/General/General.tsx
@@ -0,0 +1,34 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { translator } from '../../../common/translator';
+import { rootStore } from '../../stores';
+import { Title } from '../ui/Title';
+
+import { QuickConnect } from './QuickConnect';
+import { Theme } from './Theme';
+import { ContextMenus } from './ContextMenus';
+import { HelpUsImprove } from './HelpUsImprove';
+import { WebRTC } from './WebRTC';
+import { DnsSettings, DnsSettingsButton } from './DnsSettings';
+
+export const General = observer(() => {
+ const { settingsStore } = useContext(rootStore);
+ const { showDnsSettings } = settingsStore;
+
+ if (showDnsSettings) {
+ return ;
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+});
diff --git a/src/options/components/General/HelpUsImprove.tsx b/src/options/components/General/HelpUsImprove.tsx
new file mode 100644
index 000000000..18ecbb3b0
--- /dev/null
+++ b/src/options/components/General/HelpUsImprove.tsx
@@ -0,0 +1,24 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { translator } from '../../../common/translator';
+import { rootStore } from '../../stores';
+import { ControlsSwitch } from '../ui/Controls';
+
+export const HelpUsImprove = observer(() => {
+ const { settingsStore } = useContext(rootStore);
+ const { helpUsImprove } = settingsStore;
+
+ const handleToggle = async (): Promise => {
+ await settingsStore.setHelpUsImproveValue(!helpUsImprove);
+ };
+
+ return (
+
+ );
+});
diff --git a/src/options/components/General/QuickConnect.tsx b/src/options/components/General/QuickConnect.tsx
new file mode 100644
index 000000000..cd4635c9d
--- /dev/null
+++ b/src/options/components/General/QuickConnect.tsx
@@ -0,0 +1,34 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { QuickConnectSetting } from '../../../common/constants';
+import { translator } from '../../../common/translator';
+import { rootStore } from '../../stores';
+import { ControlsSelect } from '../ui/Controls';
+
+export const QuickConnect = observer(() => {
+ const { settingsStore } = useContext(rootStore);
+
+ const handleChange = async (value: QuickConnectSetting): Promise => {
+ await settingsStore.setQuickConnectSetting(value);
+ };
+
+ return (
+
+ );
+});
diff --git a/src/options/components/General/Theme.tsx b/src/options/components/General/Theme.tsx
new file mode 100644
index 000000000..bbd852628
--- /dev/null
+++ b/src/options/components/General/Theme.tsx
@@ -0,0 +1,37 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { AppearanceTheme } from '../../../common/constants';
+import { translator } from '../../../common/translator';
+import { rootStore } from '../../stores';
+import { ControlsSelect } from '../ui/Controls';
+
+export const Theme = observer(() => {
+ const { settingsStore } = useContext(rootStore);
+
+ const handleChange = async (value: AppearanceTheme): Promise => {
+ await settingsStore.setAppearanceTheme(value);
+ };
+
+ return (
+
+ );
+});
diff --git a/src/options/components/General/WebRTC.tsx b/src/options/components/General/WebRTC.tsx
new file mode 100644
index 000000000..5ad84776b
--- /dev/null
+++ b/src/options/components/General/WebRTC.tsx
@@ -0,0 +1,24 @@
+import React, { useContext } from 'react';
+import { observer } from 'mobx-react';
+
+import { rootStore } from '../../stores';
+import { translator } from '../../../common/translator';
+import { ControlsSwitch } from '../ui/Controls';
+
+export const WebRTC = observer(() => {
+ const { settingsStore } = useContext(rootStore);
+ const { webRTCEnabled } = settingsStore;
+
+ const handleToggle = async (): Promise => {
+ await settingsStore.setWebRTCValue(!webRTCEnabled);
+ };
+
+ return (
+
+ );
+});
diff --git a/src/options/components/General/index.ts b/src/options/components/General/index.ts
new file mode 100644
index 000000000..82d723029
--- /dev/null
+++ b/src/options/components/General/index.ts
@@ -0,0 +1 @@
+export { General } from './General';
diff --git a/src/options/components/Preloader/Preloader.tsx b/src/options/components/Preloader/Preloader.tsx
index 6aef6858c..5d2a9c9c5 100644
--- a/src/options/components/Preloader/Preloader.tsx
+++ b/src/options/components/Preloader/Preloader.tsx
@@ -1,6 +1,5 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
-import Modal from 'react-modal';
import { rootStore } from '../../stores';
import { RequestStatus } from '../../stores/consts';
@@ -13,13 +12,13 @@ export const Preloader = observer(() => {
const isOpen = globalStore.status === RequestStatus.Pending
|| authStore.requestProcessState === RequestStatus.Pending;
+ if (!isOpen) {
+ return null;
+ }
+
return (
-
+
);
});
diff --git a/src/options/components/Preloader/preloader.pcss b/src/options/components/Preloader/preloader.pcss
index c2084a88c..2f5fb4161 100644
--- a/src/options/components/Preloader/preloader.pcss
+++ b/src/options/components/Preloader/preloader.pcss
@@ -9,20 +9,17 @@
}
.preloader {
+ z-index: var(--preloader-z);
+ position: fixed;
+ display: flex;
+ align-items: center;
+ justify-content: center;
outline: none;
-
- &__overlay {
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- width: 100%;
- height: 100%;
- position: fixed;
- top: 0;
- left: 0;
- background-color: rgba(255, 255, 255, 0.8);
- }
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background-color: var(--fills-backgrounds-page-background-default);
&__in {
width: 40px;
diff --git a/src/options/components/Settings/AppearanceTheme/AppearanceTheme.tsx b/src/options/components/Settings/AppearanceTheme/AppearanceTheme.tsx
deleted file mode 100644
index 984f974ec..000000000
--- a/src/options/components/Settings/AppearanceTheme/AppearanceTheme.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import { AppearanceTheme } from '../../../../common/constants';
-import { rootStore } from '../../../stores';
-import { Select } from '../../ui/Select';
-import { reactTranslator } from '../../../../common/reactTranslator';
-
-export const AppearanceThemeSetting = observer(() => {
- const { settingsStore } = useContext(rootStore);
-
- const handleSetAppearanceTheme = async (value: AppearanceTheme): Promise => {
- await settingsStore.setAppearanceTheme(value);
- };
-
- const THEMES = [
- {
- id: AppearanceTheme.System,
- title: reactTranslator.getMessage('settings_theme_system'),
- },
- {
- id: AppearanceTheme.Dark,
- title: reactTranslator.getMessage('settings_theme_dark'),
- },
- {
- id: AppearanceTheme.Light,
- title: reactTranslator.getMessage('settings_theme_light'),
- },
- ];
-
- return (
-
-
-
-
- {reactTranslator.getMessage('settings_theme_label')}
-
-
-
-
-
- );
-});
diff --git a/src/options/components/Settings/AppearanceTheme/index.ts b/src/options/components/Settings/AppearanceTheme/index.ts
deleted file mode 100644
index 07b0d848a..000000000
--- a/src/options/components/Settings/AppearanceTheme/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { AppearanceThemeSetting } from './AppearanceTheme';
diff --git a/src/options/components/Settings/ContextMenus/ContextMenus.tsx b/src/options/components/Settings/ContextMenus/ContextMenus.tsx
deleted file mode 100644
index 05fe2d651..000000000
--- a/src/options/components/Settings/ContextMenus/ContextMenus.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import { rootStore } from '../../../stores';
-import { Switch } from '../../ui/Switch';
-import { reactTranslator } from '../../../../common/reactTranslator';
-
-export const ContextMenus = observer(() => {
- const { settingsStore } = useContext(rootStore);
- const { contextMenusEnabled } = settingsStore;
-
- const handleToggle = async (): Promise => {
- await settingsStore.setContextMenusValue(!contextMenusEnabled);
- };
-
- return (
-
-
-
- );
-});
diff --git a/src/options/components/Settings/ContextMenus/index.ts b/src/options/components/Settings/ContextMenus/index.ts
deleted file mode 100644
index 2011c2b7a..000000000
--- a/src/options/components/Settings/ContextMenus/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { ContextMenus } from './ContextMenus';
diff --git a/src/options/components/Settings/DnsMenuItem/DnsMenuItem.tsx b/src/options/components/Settings/DnsMenuItem/DnsMenuItem.tsx
deleted file mode 100644
index bd1258f89..000000000
--- a/src/options/components/Settings/DnsMenuItem/DnsMenuItem.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import { rootStore } from '../../../stores';
-import { reactTranslator } from '../../../../common/reactTranslator';
-
-export const DnsMenuItem = observer(() => {
- const { settingsStore } = useContext(rootStore);
-
- const { currentDnsServerName, setShowDnsSettings } = settingsStore;
-
- const handleClick = () => {
- setShowDnsSettings(true);
- };
-
- return (
-
-
-
-
- {reactTranslator.getMessage('settings_dns_label')}
-
-
- {currentDnsServerName}
-
-
-
-
-
- );
-});
diff --git a/src/options/components/Settings/DnsMenuItem/index.ts b/src/options/components/Settings/DnsMenuItem/index.ts
deleted file mode 100644
index 907199652..000000000
--- a/src/options/components/Settings/DnsMenuItem/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { DnsMenuItem } from './DnsMenuItem';
diff --git a/src/options/components/Settings/DnsSettings/CustomDnsServerModal.tsx b/src/options/components/Settings/DnsSettings/CustomDnsServerModal.tsx
deleted file mode 100644
index e1cdfea83..000000000
--- a/src/options/components/Settings/DnsSettings/CustomDnsServerModal.tsx
+++ /dev/null
@@ -1,288 +0,0 @@
-import React, {
- useContext,
- useEffect,
- useState,
- type FormEvent,
-} from 'react';
-import Modal from 'react-modal';
-import { observer } from 'mobx-react';
-
-import { isIP } from 'is-ip';
-import classnames from 'classnames';
-
-import { rootStore } from '../../../stores';
-import { getForwarderUrl } from '../../../../common/helpers';
-import { reactTranslator } from '../../../../common/reactTranslator';
-import { translator } from '../../../../common/translator';
-import { FORWARDER_URL_QUERIES } from '../../../../background/config';
-
-const DOH_PREFIX = 'https://';
-const DOT_PREFIX = 'tls://';
-
-enum ModalType {
- AddDnsServer = 'addDnsServer',
- EditDnsServer = 'editDnsServer',
-}
-
-const DNS_SERVER_ERROR = {
- INVALID: translator.getMessage('settings_dns_add_custom_server_invalid_address'),
- DUPLICATE: translator.getMessage('settings_dns_add_custom_server_duplicate_address'),
-};
-
-export const CustomDnsServerModal = observer(() => {
- const { settingsStore, notificationsStore } = useContext(rootStore);
-
- const {
- forwarderDomain,
- dnsServerName,
- dnsServerAddress,
- setDnsServerName,
- setDnsServerAddress,
- } = settingsStore;
-
- const adguardDnsKbUrl = getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.ADGUARD_DNS_KB);
-
- const [dnsServerAddressError, setDnsServerAddressError] = useState(null);
-
- const handleAddressChange = (value: string) => {
- setDnsServerAddress(value);
- if (dnsServerAddressError) {
- setDnsServerAddressError(null);
- }
- };
-
- const clearDnsServerAddress = () => {
- setDnsServerAddress('');
- setDnsServerAddressError(null);
- };
-
- const clearInputs = (): void => {
- setDnsServerName('');
- setDnsServerAddress('');
- };
-
- const closeModal = (): void => {
- clearInputs();
- if (settingsStore.dnsServerToEdit) {
- settingsStore.setDnsServerToEdit(null);
- }
- setDnsServerAddressError(null);
- settingsStore.closeCustomDnsModal();
- };
-
- const validateDnsAddress = (address: string): string | null => {
- // check existing custom dns addresses
-
- if (settingsStore.customDnsServers.some((server) => server.address === address)) {
- return DNS_SERVER_ERROR.DUPLICATE;
- }
- // for the moment only plain dns and tls supported
- if (address.startsWith(DOH_PREFIX) || !address.includes('.')) {
- return DNS_SERVER_ERROR.INVALID;
- }
- return null;
- };
-
- const handleDnsAddress = (address: string) => {
- if (isIP(address) || address.startsWith(DOT_PREFIX)) {
- return address;
- }
- return `${DOT_PREFIX}${address}`;
- };
-
- const addDnsServer = async (): Promise => {
- const dnsServerAddressError = validateDnsAddress(dnsServerAddress);
- if (dnsServerAddressError) {
- setDnsServerAddressError(dnsServerAddressError);
- return;
- }
- const dnsAddressToAdd = handleDnsAddress(dnsServerAddress);
- const dnsServer = await settingsStore.addCustomDnsServer(dnsServerName, dnsAddressToAdd);
- notificationsStore.notifySuccess(
- reactTranslator.getMessage('settings_dns_add_custom_server_notification_success'),
- {
- action: reactTranslator.getMessage('settings_exclusions_undo'),
- handler: () => settingsStore.removeCustomDnsServer(dnsServer.id),
- },
- );
- closeModal();
- };
-
- const editDnsServer = async (): Promise => {
- if (!settingsStore.dnsServerToEdit) {
- return;
- }
- const { address } = settingsStore.dnsServerToEdit;
-
- // `address` is dns address before editing,
- // `dnsServerAddress` is the state of dns address form.
- // if dns address was edited, it has to be verified.
- if (address !== dnsServerAddress) {
- const dnsServerAddressError = validateDnsAddress(dnsServerAddress);
- if (dnsServerAddressError) {
- setDnsServerAddressError(dnsServerAddressError);
- return;
- }
- }
- const editedDnsAddress = handleDnsAddress(dnsServerAddress);
- await settingsStore.editCustomDnsServer(dnsServerName, editedDnsAddress);
- closeModal();
- };
-
- const modalType = settingsStore.dnsServerToEdit
- ? ModalType.EditDnsServer
- : ModalType.AddDnsServer;
-
- const modalData = {
- [ModalType.AddDnsServer]: {
- modalTitle: reactTranslator.getMessage('settings_dns_add_custom_server'),
- submitText: reactTranslator.getMessage('settings_dns_add_custom_server_save_and_select'),
- handler: addDnsServer,
- },
- [ModalType.EditDnsServer]: {
- modalTitle: reactTranslator.getMessage('settings_dns_edit_custom_server'),
- submitText: reactTranslator.getMessage('settings_dns_add_custom_server_save'),
- handler: editDnsServer,
- },
- };
-
- useEffect(() => {
- if (settingsStore.dnsServerToEdit) {
- const { title: serverName, address } = settingsStore.dnsServerToEdit;
-
- setDnsServerName(serverName);
- setDnsServerAddress(address);
- }
- }, [settingsStore.isCustomDnsModalOpen]);
-
- const ipAddressInputClasses = classnames(
- 'input__in',
- 'input__in--content',
- { 'input__in--clear': !dnsServerAddressError },
- { 'dns-settings__modal--input--error': dnsServerAddressError },
- );
-
- const handleSubmit = (e: FormEvent) => {
- e.preventDefault();
- modalData[modalType].handler();
- };
-
- return (
-
-
-
- );
-});
diff --git a/src/options/components/Settings/DnsSettings/DnsSettings.tsx b/src/options/components/Settings/DnsSettings/DnsSettings.tsx
deleted file mode 100644
index 45129dd65..000000000
--- a/src/options/components/Settings/DnsSettings/DnsSettings.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import classnames from 'classnames';
-
-import { DEFAULT_DNS_SERVER, POPULAR_DNS_SERVERS } from '../../../../background/dns/dnsConstants';
-import { rootStore } from '../../../stores';
-import { Title } from '../../ui/Title';
-import { reactTranslator } from '../../../../common/reactTranslator';
-import type { DnsServerData } from '../../../../background/schema';
-
-import { CustomDnsServerModal } from './CustomDnsServerModal';
-
-import './dns-settings.pcss';
-
-export const DnsSettings = observer(() => {
- const { settingsStore, notificationsStore } = useContext(rootStore);
-
- const handleDnsSelect = async (
- event: React.MouseEvent,
- ): Promise => {
- // To prevent multiple handles of one click from radio-button and
- // from parent div container.
- event.stopPropagation();
-
- const dnsServerId = event.currentTarget.id;
- await settingsStore.setDnsServer(dnsServerId);
- };
-
- const goBackHandler = (): void => {
- settingsStore.setShowDnsSettings(false);
- };
-
- const openAddDnsServerModal = () => {
- settingsStore.openCustomDnsModal();
- };
-
- const removeDnsServer = (
- event: React.MouseEvent,
- dnsServerId: string,
- ): void => {
- event.stopPropagation();
- settingsStore.removeCustomDnsServer(dnsServerId);
- notificationsStore.notifySuccess(
- reactTranslator.getMessage('settings_dns_delete_custom_server_notification'),
- {
- action: reactTranslator.getMessage('settings_exclusions_undo'),
- handler: () => settingsStore.restoreCustomDnsServersData(),
- },
- );
- };
-
- const openEditDnsServerModal = (
- event: React.MouseEvent,
- server: DnsServerData,
- ): void => {
- // To not trigger parent dns-button selector.
- event.stopPropagation();
- settingsStore.setDnsServerToEdit(server);
- settingsStore.openCustomDnsModal();
- };
-
- const renderRadioButton = (dnsServerId: string) => {
- const enabled = dnsServerId === settingsStore.dnsServer;
- const xlinkHref = classnames({
- '#bullet_on': enabled,
- '#bullet_off': !enabled,
- });
- // TODO: Dirty hack to support a11y, should use native .
- return (
-
-
-
- );
- };
-
- const renderActions = (dnsServerData: DnsServerData) => {
- return (
-
- openEditDnsServerModal(event, dnsServerData)}
- >
-
-
- removeDnsServer(event, dnsServerData.id)}
- >
-
-
-
- );
- };
-
- const renderDnsServer = (dnsServerData: DnsServerData) => {
- if (!dnsServerData) {
- return null;
- }
- const {
- id,
- title,
- address,
- desc,
- } = dnsServerData;
-
- // Custom servers doesn't have description
- const isCustom = !desc;
-
- return (
-
- {renderRadioButton(id)}
-
-
{title}
-
{isCustom ? address : desc}
-
- {isCustom && renderActions(dnsServerData)}
-
- );
- };
-
- return (
-
-
-
-
-
-
-
-
- {renderDnsServer(DEFAULT_DNS_SERVER)}
-
-
-
- {reactTranslator.getMessage('settings_dns_popular_servers')}
-
- {POPULAR_DNS_SERVERS.map(renderDnsServer)}
-
-
-
- {reactTranslator.getMessage('settings_dns_custom_servers')}
-
-
- {settingsStore.customDnsServers.map(renderDnsServer)}
-
-
-
-
- {reactTranslator.getMessage('settings_dns_add_custom_server')}
-
-
-
-
-
- );
-});
diff --git a/src/options/components/Settings/DnsSettings/dns-settings.pcss b/src/options/components/Settings/DnsSettings/dns-settings.pcss
deleted file mode 100644
index 125dd5e81..000000000
--- a/src/options/components/Settings/DnsSettings/dns-settings.pcss
+++ /dev/null
@@ -1,122 +0,0 @@
-.dns-settings {
- &__title {
- display: flex;
- align-items: flex-start;
- margin: 0 0 16px 16px;
- }
-
- &__items-group {
- padding-top: 32px;
- }
-
- &__item {
- display: flex;
- justify-content: flex-start;
- align-items: flex-start;
- cursor: pointer;
- transition: var(--t3) background-color;
- position: relative;
- background: none;
- border: none;
- border-radius: 8px;
- padding: 16px;
- width: 100%;
- text-align: left;
-
- &-title {
- font-weight: 500;
- font-size: 16px;
- line-height: 19px;
- color: var(--gray-base);
- }
-
- &-desc {
- font-weight: 400;
- font-size: 14px;
- line-height: 18px;
- margin-top: 4px;
- color: var(--gray88);
- }
-
-
- &:hover {
- background-color: var(--grayF3);
- }
-
- &--actions {
- position: absolute;
- right: 15px;
-
- &--button {
- margin-left: 16px;
- max-height: 24px;
- border: none;
-
- &:hover {
- opacity: .7;
- }
- }
- }
- }
-
- &__label {
- color: var(--gray88);
- font-size: 14px;
- line-height: 18px;
- margin-left: 16px;
- margin-bottom: 8px;
- }
-
- &__add-button {
- font-size: 16px;
- line-height: 24px;
- padding: 8px 16px;
- display: flex;
-
- &__label {
- margin-left: 16px;
- }
- }
-
- &__modal {
- width: 500px;
-
- &--content {
- display: block;
-
- & .form__actions .button {
- min-width: 180px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- padding: 12px 20px;
- }
- }
-
- &--link {
- text-decoration: none;
- color: var(--green700);
- cursor: pointer;
-
- &:hover {
- color: var(--green900);
- }
- }
-
- &--clear-icon {
- position: absolute;
- top: 36px;
- right: 15px;
- }
-
- &--input--error {
- box-shadow: inset 0 0 0 1px var(--redDark);
- }
-
- &--error-message {
- color: var(--redDark);
- font-size: 13px;
- margin-top: 8px;
- }
- }
-}
diff --git a/src/options/components/Settings/DnsSettings/index.ts b/src/options/components/Settings/DnsSettings/index.ts
deleted file mode 100644
index 3bee2afe2..000000000
--- a/src/options/components/Settings/DnsSettings/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { DnsSettings } from './DnsSettings';
diff --git a/src/options/components/Settings/HelpUsImprove/HelpUsImprove.tsx b/src/options/components/Settings/HelpUsImprove/HelpUsImprove.tsx
deleted file mode 100644
index 95d14659f..000000000
--- a/src/options/components/Settings/HelpUsImprove/HelpUsImprove.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import { rootStore } from '../../../stores';
-import { Switch } from '../../ui/Switch';
-import { reactTranslator } from '../../../../common/reactTranslator';
-
-export const HelpUsImprove = observer(() => {
- const { settingsStore } = useContext(rootStore);
- const { helpUsImprove } = settingsStore;
-
- const handleToggle = async (): Promise => {
- await settingsStore.setHelpUsImproveValue(!helpUsImprove);
- };
-
- return (
-
-
-
- );
-});
diff --git a/src/options/components/Settings/HelpUsImprove/index.ts b/src/options/components/Settings/HelpUsImprove/index.ts
deleted file mode 100644
index 29cf22fcf..000000000
--- a/src/options/components/Settings/HelpUsImprove/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { HelpUsImprove } from './HelpUsImprove';
diff --git a/src/options/components/Settings/QuickConnect/QuickConnect.tsx b/src/options/components/Settings/QuickConnect/QuickConnect.tsx
deleted file mode 100644
index 90cf13c0c..000000000
--- a/src/options/components/Settings/QuickConnect/QuickConnect.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import { QuickConnectSetting } from '../../../../common/constants';
-import { rootStore } from '../../../stores';
-import { Select } from '../../ui/Select';
-import { reactTranslator } from '../../../../common/reactTranslator';
-
-export const QuickConnect = observer(() => {
- const { settingsStore } = useContext(rootStore);
-
- const handleChangeSetting = async (value: QuickConnectSetting): Promise => {
- await settingsStore.setQuickConnectSetting(value);
- };
-
- const options = [
- {
- id: QuickConnectSetting.LastUsedLocation,
- title: reactTranslator.getMessage('settings_quick_connect_last_used'),
- },
- {
- id: QuickConnectSetting.FastestLocation,
- title: reactTranslator.getMessage('settings_quick_connect_fastest'),
- },
- ];
-
- return (
-
-
-
-
- {reactTranslator.getMessage('settings_quick_connect_title')}
-
-
- {reactTranslator.getMessage('settings_quick_connect_subtitle')}
-
-
-
-
-
- );
-});
diff --git a/src/options/components/Settings/QuickConnect/index.ts b/src/options/components/Settings/QuickConnect/index.ts
deleted file mode 100644
index cafbc1a66..000000000
--- a/src/options/components/Settings/QuickConnect/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { QuickConnect } from './QuickConnect';
diff --git a/src/options/components/Settings/Settings.tsx b/src/options/components/Settings/Settings.tsx
deleted file mode 100644
index 9437bda57..000000000
--- a/src/options/components/Settings/Settings.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import { Title } from '../ui/Title';
-import { reactTranslator } from '../../../common/reactTranslator';
-import { rootStore } from '../../stores';
-
-import { WebRTC } from './WebRTC';
-import { DnsMenuItem } from './DnsMenuItem';
-import { ContextMenus } from './ContextMenus';
-import { HelpUsImprove } from './HelpUsImprove';
-import { AppearanceThemeSetting } from './AppearanceTheme';
-import { QuickConnect } from './QuickConnect';
-import { DnsSettings } from './DnsSettings';
-
-export const Settings = observer(() => {
- const { settingsStore } = useContext(rootStore);
- const { showDnsSettings } = settingsStore;
-
- if (showDnsSettings) {
- return (
-
- );
- }
-
- return (
- <>
-
-
- >
- );
-});
diff --git a/src/options/components/Settings/WebRTC/WebRTC.tsx b/src/options/components/Settings/WebRTC/WebRTC.tsx
deleted file mode 100644
index 4ee66e57c..000000000
--- a/src/options/components/Settings/WebRTC/WebRTC.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React, { useContext } from 'react';
-import { observer } from 'mobx-react';
-
-import { rootStore } from '../../../stores';
-import { Switch } from '../../ui/Switch';
-import { reactTranslator } from '../../../../common/reactTranslator';
-
-export const WebRTC = observer(() => {
- const { settingsStore } = useContext(rootStore);
- const { webRTCEnabled } = settingsStore;
-
- const handleCheckboxChange = async (): Promise => {
- await settingsStore.setWebRTCValue(!webRTCEnabled);
- };
-
- return (
-
-
-
- );
-});
diff --git a/src/options/components/Settings/WebRTC/index.ts b/src/options/components/Settings/WebRTC/index.ts
deleted file mode 100644
index 8ac1c5d2c..000000000
--- a/src/options/components/Settings/WebRTC/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { WebRTC } from './WebRTC';
diff --git a/src/options/components/Settings/index.ts b/src/options/components/Settings/index.ts
deleted file mode 100644
index ec5d9791b..000000000
--- a/src/options/components/Settings/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Settings } from './Settings';
diff --git a/src/options/components/Sidebar/Rate/Rate.tsx b/src/options/components/Sidebar/Rate/Rate.tsx
index ae3e0c4ea..a3ce52a24 100644
--- a/src/options/components/Sidebar/Rate/Rate.tsx
+++ b/src/options/components/Sidebar/Rate/Rate.tsx
@@ -1,10 +1,12 @@
-import React, { Fragment, useContext } from 'react';
+import React, { useContext } from 'react';
import { observer } from 'mobx-react';
import { FORWARDER_URL_QUERIES } from '../../../../background/config';
import { getForwarderUrl } from '../../../../common/helpers';
+import { translator } from '../../../../common/translator';
import { rootStore } from '../../../stores';
-import { reactTranslator } from '../../../../common/reactTranslator';
+
+import { RateStar } from './RateStar';
import './rate.pcss';
@@ -12,22 +14,24 @@ const RATING_STARS = [5, 4, 3, 2, 1];
export const Rate = observer(() => {
const { settingsStore } = useContext(rootStore);
+
const {
- hideRate,
isRateVisible,
forwarderDomain,
} = settingsStore;
- const handleHideRate = async (): Promise => {
- await hideRate();
+ const handleHideRate = async () => {
+ await settingsStore.hideRate();
};
- const handleChange = async (e: React.ChangeEvent): Promise => {
- const { value } = e.target;
+ const handleChange = async (value: number) => {
+ if (value < 0 || value > 5) {
+ return;
+ }
await handleHideRate();
- if (value && parseInt(value, 10) >= 4) {
+ if (value >= 4) {
window.open(getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.OPTIONS_STORE), '_blank');
} else {
window.open(getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.FEEDBACK), '_blank');
@@ -40,33 +44,21 @@ export const Rate = observer(() => {
return (
-
- {reactTranslator.getMessage('rate_description')}
-
+
{RATING_STARS.map((star) => (
-
-
-
-
+
))}
+
+ {translator.getMessage('rate_description')}
+
- {reactTranslator.getMessage('rate_hide')}
+ {translator.getMessage('rate_hide')}
);
diff --git a/src/options/components/Sidebar/Rate/RateStar.tsx b/src/options/components/Sidebar/Rate/RateStar.tsx
new file mode 100644
index 000000000..7a7555567
--- /dev/null
+++ b/src/options/components/Sidebar/Rate/RateStar.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+
+import { Icon } from '../../ui/Icon';
+
+export interface RateStarProps {
+ value: number;
+ onChange: (value: number) => void;
+}
+
+export function RateStar({ value, onChange }: RateStarProps) {
+ const handleChange = (e: React.ChangeEvent) => {
+ const { value } = e.target;
+ const numberValue = parseInt(value, 10);
+ if (!Number.isNaN(numberValue) && Number.isFinite(numberValue)) {
+ onChange(numberValue);
+ }
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/options/components/Sidebar/Rate/rate.pcss b/src/options/components/Sidebar/Rate/rate.pcss
index c7d20280d..90e45b2d7 100644
--- a/src/options/components/Sidebar/Rate/rate.pcss
+++ b/src/options/components/Sidebar/Rate/rate.pcss
@@ -1,48 +1,39 @@
.rate {
- padding-top: 25px;
- border-top: 1px solid var(--border-color);
- margin: auto 40px 0;
+ padding: 0 32px;
- &__text {
- margin-bottom: 20px;
- line-height: 22px;
+ &__line {
+ width: 100%;
+ height: 1px;
+ margin-bottom: 24px;
+ background-color: var(--stroke-dividers-item-divider-default);
+ transition: var(--t3) background-color;
}
&__stars {
display: flex;
flex-direction: row-reverse;
- justify-content: space-between;
- margin-bottom: 20px;
+ justify-content: flex-end;
}
&__star {
- flex-shrink: 0;
- display: block;
- width: 35px;
- height: 25px;
- padding-right: 5px;
- font-size: 0;
- background-size: 25px 25px;
- background-position: left;
- background-repeat: no-repeat;
- background-image: url("../../../../assets/images/star.svg");
cursor: pointer;
-
- @media (--min-small) {
- width: 50px;
- height: 40px;
- background-size: 40px 40px;
- }
+ color: var(--stroke-icons-gray-icons-disabled);
+ transition: var(--t3) color;
&:hover,
- &:hover ~ .rate__star,
- &:hover ~ .rating__input:checked ~ .rating__star {
- background-image: url('../../../../assets/images/star-active.svg');
+ &:active,
+ &:focus-visible {
+ &,
+ & ~ .rate__star,
+ & ~ .rating__input:checked ~ .rating__star {
+ color: var(--stroke-icons-attention-icon-default);
+ }
}
- &:first-of-type {
- width: 25px;
- padding-right: 0;
+ &-icon {
+ width: 30px;
+ height: 30px;
+ color: currentColor;
}
}
@@ -50,23 +41,30 @@
display: none;
&:checked ~ .rate__star {
- background-image: url('../../../../assets/images/star-active.svg');
+ color: var(--stroke-icons-attention-icon-default);
}
}
- &__hide {
- padding: 0;
- font-size: 1.2rem;
- color: var(--gray-base);
- background-color: transparent;
- border: 0;
- cursor: pointer;
- opacity: 0.7;
+ &__title {
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ line-height: 1.3;
+ margin-top: 16px;
+ margin-bottom: 24px;
+ transition: var(--t3) color;
+ }
+
+ &__hide-btn {
+ text-align: left;
+ color: var(--text-description-description-default);
+ font-size: 14px;
+ line-height: 1.5;
+ transition: var(--t3) color;
&:hover,
- &:focus {
+ &:focus,
+ &:focus-visible {
text-decoration: underline;
- outline: 0;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/options/components/Sidebar/Sidebar.tsx b/src/options/components/Sidebar/Sidebar.tsx
index de05a0ee4..7f204f62f 100644
--- a/src/options/components/Sidebar/Sidebar.tsx
+++ b/src/options/components/Sidebar/Sidebar.tsx
@@ -1,10 +1,16 @@
-import React, { useContext, useEffect } from 'react';
+import React, {
+ useContext,
+ useEffect,
+ useLayoutEffect,
+ useState,
+} from 'react';
import { observer } from 'mobx-react';
import classNames from 'classnames';
import { translator } from '../../../common/translator';
import { rootStore } from '../../stores';
+import { IconButton } from '../ui/Icon';
import { SidebarLink } from './SidebarLink';
import { Rate } from './Rate';
@@ -22,6 +28,7 @@ export const Sidebar = observer(() => {
const {
isSidebarOpen,
+ isAnyModalOpen,
openSidebar,
closeSidebar,
} = uiStore;
@@ -31,7 +38,32 @@ export const Sidebar = observer(() => {
isSidebarOpen && 'sidebar--open',
);
- const isMobileScreen = window.matchMedia('(max-width: 865px)').matches;
+ const smallTabletQuery = '(max-width: 875px)';
+ const [isSmallTabletScreen, setIsSmallTabletScreen] = useState(window.matchMedia(smallTabletQuery).matches);
+
+ useLayoutEffect(() => {
+ const matchMedia = window.matchMedia(smallTabletQuery);
+
+ const handleScreenChange = (e: MediaQueryListEvent) => {
+ setIsSmallTabletScreen(e.matches);
+ };
+
+ // Triggered at the first client-side load and if query changes
+ setIsSmallTabletScreen(matchMedia.matches);
+
+ matchMedia.addEventListener('change', handleScreenChange);
+
+ return () => {
+ matchMedia.removeEventListener('change', handleScreenChange);
+ };
+ }, []);
+
+ /**
+ * Lock sidebar from tab focus in following scenarios:
+ * - Any modal open
+ * - Sidebar closed on mobile screen
+ */
+ const isSidebarLocked = isAnyModalOpen || (!isSidebarOpen && isSmallTabletScreen);
useEffect(() => {
(async () => {
@@ -48,25 +80,20 @@ export const Sidebar = observer(() => {
return (
- {/* TODO: Export icons to component (AG-38059) */}
-
-
-
+ className="sidebar__open-btn"
+ />
-
+
- {/* TODO: Export icons to component (AG-38059) */}
-
-
-
+ className="sidebar__close-btn"
+ />
diff --git a/src/options/components/Sidebar/sidebar.pcss b/src/options/components/Sidebar/sidebar.pcss
index 740967eda..0042bcaf8 100644
--- a/src/options/components/Sidebar/sidebar.pcss
+++ b/src/options/components/Sidebar/sidebar.pcss
@@ -22,32 +22,6 @@
}
}
- /* TODO: Export icons to component (AG-38059) */
- &__open-btn,
- &__close-btn {
- padding: 0;
- cursor: pointer;
- background-color: transparent;
- border: none;
- color: var(--stroke-icons-gray-icons-default);
- transition: var(--t3) color;
-
- &:hover,
- &:active,
- &:focus-visible {
- color: var(--text-main-text-main-default);
- }
-
- &-icon {
- display: inline-block;
- vertical-align: middle;
- font-size: 0;
- width: 24px;
- height: 24px;
- color: currentColor;
- }
- }
-
&__open-btn {
padding: 12px 8px;
}
@@ -141,13 +115,17 @@
font-size: 16px;
line-height: 1.3;
transition: var(--t3) background-color;
+ outline-offset: -2px;
&:hover,
- &:active,
&:focus-visible {
background-color: var(--fills-backgrounds-page-background-additional-hovered);
}
+ &:active {
+ background-color: var(--fills-backgrounds-page-background-additional-pressed);
+ }
+
&.sidebar__link--active {
font-weight: 600;
background-color: var(--fills-backgrounds-page-background-default);
@@ -163,3 +141,31 @@
}
}
}
+
+.logo {
+ width: 125px;
+ height: 24px;
+ background-image: url('../../../assets/images/logo-vpn.svg');
+ background-repeat: no-repeat;
+ background-size: contain;
+
+ @media (prefers-color-scheme: dark) {
+ background-image: url('../../../assets/images/logo-vpn-dark.svg');
+ }
+}
+
+.light-mode {
+ color-scheme: light;
+
+ .logo {
+ background-image: url('../../../assets/images/logo-vpn.svg');
+ }
+}
+
+.dark-mode {
+ color-scheme: dark;
+
+ .logo {
+ background-image: url('../../../assets/images/logo-vpn-dark.svg');
+ }
+}
diff --git a/src/options/components/SignedOut/SignedOut.tsx b/src/options/components/SignedOut/SignedOut.tsx
index 89a0e8a8f..0d7dc369c 100644
--- a/src/options/components/SignedOut/SignedOut.tsx
+++ b/src/options/components/SignedOut/SignedOut.tsx
@@ -1,19 +1,28 @@
import React from 'react';
-import { reactTranslator } from '../../../common/reactTranslator';
+import { translator } from '../../../common/translator';
import './signedout.pcss';
-export const SignedOut = () => {
+export function SignedOut() {
return (
-
-
- {reactTranslator.getMessage('options_signedout_page_title')}
-
-
- {reactTranslator.getMessage('options_signedout_page_description')}
+
+

+
+ {translator.getMessage('options_signedout_page_title')}
+
+
+ {translator.getMessage('options_signedout_page_description_not_secure')}
+
+
+ {translator.getMessage('options_signedout_page_description')}
+
);
-};
+}
diff --git a/src/options/components/SignedOut/signedout.pcss b/src/options/components/SignedOut/signedout.pcss
index 760181ccf..d7d0ea877 100644
--- a/src/options/components/SignedOut/signedout.pcss
+++ b/src/options/components/SignedOut/signedout.pcss
@@ -1,33 +1,54 @@
.signedout {
- height: calc(100vh - var(--footer-height));
- min-height: 500px;
+ z-index: var(--signed-out-z);
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
display: flex;
flex-direction: column;
- justify-content: center;
align-items: center;
+ justify-content: center;
+ background-color: var(--fills-backgrounds-page-background-default);
text-align: center;
+ color: var(--text-main-text-main-default);
+
+ &__content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ max-width: 800px;
+ padding: 0 16px;
+ }
&__image {
width: 190px;
height: 200px;
- margin-bottom: 16px;
- background-repeat: no-repeat;
- /* TODO not-found.svg no shadow */
- background-image: url("../../../assets/images/not-found.svg");
- background-position: center;
- background-size: contain;
}
&__title {
- font-size: 2.2rem;
+ font-size: 40px;
font-weight: 700;
- max-width: 300px;
+ line-height: 1.2;
+ margin-top: 16px;
+
+ @media (--tablet) {
+ font-size: 24px;
+ padding: 0 16px;
+ }
}
&__description {
- max-width: 330px;
- margin-top: 15px;
- font-size: 1.7rem;
- font-weight: 400;
+ font-size: 18px;
+ line-height: 1.5;
+ padding-top: 16px;
+ margin-top: 24px;
+
+ @media (--tablet) {
+ padding: 0 16px;
+ }
}
}
diff --git a/src/options/components/Support/BugReporter/BugReporter.tsx b/src/options/components/Support/BugReporter/BugReporter.tsx
index e5ff9a261..ce898531c 100644
--- a/src/options/components/Support/BugReporter/BugReporter.tsx
+++ b/src/options/components/Support/BugReporter/BugReporter.tsx
@@ -3,15 +3,15 @@ import { observer } from 'mobx-react';
import { useMachine } from '@xstate/react';
import identity from 'lodash/identity';
-import classnames from 'classnames';
-import { Title } from '../../ui/Title';
-import { Checkbox } from '../../ui/Checkbox';
-import { rootStore } from '../../../stores';
-import { messenger } from '../../../../common/messenger';
import { addMinDurationTime } from '../../../../common/helpers';
-import { reactTranslator } from '../../../../common/reactTranslator';
+import { messenger } from '../../../../common/messenger';
import { translator } from '../../../../common/translator';
+import { rootStore } from '../../../stores';
+import { Title } from '../../ui/Title';
+import { Input, TextArea } from '../../ui/Input';
+import { Checkbox } from '../../ui/Checkbox';
+import { Button } from '../../ui/Button';
import { RequestEvent, RequestState, requestMachine } from './requestMachine';
@@ -94,13 +94,20 @@ export const BugReporter = observer(() => {
},
};
+ const handleNewReport = () => {
+ sendToRequestMachine(RequestEvent.StartAgain);
+ setFormState(DEFAULT_FORM_STATE);
+ setEmailInput(DEFAULT_FORM_STATE[FormField.Email]);
+ setFormErrors(DEFAULT_ERROR_STATE);
+ };
+
const validateFields = (): FormError => {
return Object.keys(formState).reduce((acc: FormError, key) => {
const value = formState[key];
const validator = validators[key];
if (validator) {
if (typeof value === 'string') {
- acc[key] = validator(value);
+ acc[key] = validator(value.trim());
}
} else {
acc[key] = null;
@@ -153,20 +160,10 @@ export const BugReporter = observer(() => {
});
};
- const emailChangeHandler = (e: React.ChangeEvent
): void => {
- const { target: { value } } = e;
- setEmailInput(value);
- };
-
- const emailCleanHandler = (): void => {
- setEmailInput('');
- };
-
- let buttonText = reactTranslator.getMessage('options_bug_report_send_button');
+ let buttonText = translator.getMessage('options_bug_report_send_button');
let isButtonDisabled = !formState[FormField.Email] || !formState[FormField.Message];
-
if (requestState.matches(RequestState.Sending)) {
- buttonText = reactTranslator.getMessage('options_bug_report_sending_button');
+ buttonText = translator.getMessage('options_bug_report_sending_button');
isButtonDisabled = true;
}
@@ -174,139 +171,76 @@ export const BugReporter = observer(() => {
settingsStore.setShowBugReporter(false);
};
- const bugReportTitle = (
-
- );
-
if (requestState.matches(RequestState.Success)) {
- const newReportClickHandler = () => {
- sendToRequestMachine(RequestEvent.StartAgain);
- setFormState(DEFAULT_FORM_STATE);
- setEmailInput(DEFAULT_FORM_STATE[FormField.Email]);
- setFormErrors(DEFAULT_ERROR_STATE);
- };
-
return (
- <>
- {bugReportTitle}
-
-
-
-

-
- {reactTranslator.getMessage('options_bug_report_page_success')}
-
-
- {reactTranslator.getMessage('options_bug_report_new_report_button')}
-
-
+
+

+
+ {translator.getMessage('options_bug_report_page_success')}
- >
+
+ {translator.getMessage('options_bug_report_new_report_button')}
+
+
);
}
- const emailClassName = classnames('input', { 'input--error': formErrors[FormField.Email] });
- const messageClassName = classnames('input', { 'input--error': formErrors[FormField.Message] });
return (
<>
-
-
-
-
>
);
});
diff --git a/src/options/components/Support/BugReporter/bug-report.pcss b/src/options/components/Support/BugReporter/bug-report.pcss
index 97085ed7d..4c1f5c361 100644
--- a/src/options/components/Support/BugReporter/bug-report.pcss
+++ b/src/options/components/Support/BugReporter/bug-report.pcss
@@ -1,48 +1,57 @@
.bug-report {
- padding: 0 16px;
- height: 100%;
-
- &__title {
+ &__form {
display: flex;
- align-items: center;
- }
-
- &__description {
- margin-bottom: 24px;
- }
-
- &__input {
- width: 100%;
- padding: 16px 0;
- }
-
- &__label {
- font-size: 14px;
- line-height: 16px;
- color: var(--gray88);
- }
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ row-gap: 16px;
+ padding: 0 16px;
+ margin-top: 16px;
+
+ &-error {
+ color: var(--text-links-error-link-default);
+ font-size: 16px;
+ line-height: 1.5;
+ }
- &__action {
- min-width: 180px;
+ &-btn {
+ width: 260px;
+ margin-top: 16px;
- @media (--mobile) {
- width: 100%;
+ @media (--mobile) {
+ width: 100%;
+ }
}
}
- &__checkbox {
- margin-bottom: 32px;
- }
-
- &__done {
+ &__success {
+ flex: 1 1 auto;
display: flex;
flex-direction: column;
- justify-content: center;
align-items: center;
- height: 100%;
- }
+ justify-content: center;
+ text-align: center;
+ row-gap: 24px;
+ padding: 0 8px;
- &__image {
- margin-bottom: 17px;
+ &-image {
+ width: 160px;
+ }
+
+ &-title {
+ padding: 16px 0;
+ color: var(--text-main-text-main-default);
+ font-size: 24px;
+ font-weight: 700;
+ line-height: 1.2;
+ }
+
+ &-btn {
+ width: 260px;
+
+ @media (--mobile) {
+ width: 100%;
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/options/components/Support/Support.tsx b/src/options/components/Support/Support.tsx
index 197864602..56065e20e 100644
--- a/src/options/components/Support/Support.tsx
+++ b/src/options/components/Support/Support.tsx
@@ -1,12 +1,14 @@
-import React, { type ReactNode, useContext } from 'react';
+import React, { useContext } from 'react';
import { observer } from 'mobx-react';
-import { rootStore } from '../../stores';
+import { FORWARDER_URL_QUERIES } from '../../../background/config';
import { getForwarderUrl } from '../../../common/helpers';
import { messenger } from '../../../common/messenger';
-import { FORWARDER_URL_QUERIES } from '../../../background/config';
-import { Title } from '../ui/Title';
import { translator } from '../../../common/translator';
+import { rootStore } from '../../stores';
+import { Title } from '../ui/Title';
+import { Icon, IconButton } from '../ui/Icon';
+import { Controls } from '../ui/Controls';
import { BugReporter } from './BugReporter';
@@ -14,8 +16,8 @@ import './support.pcss';
interface SupportItems {
title: string;
- description: ReactNode;
- iconXlink: string;
+ description: React.ReactNode;
+ icon: string;
clickHandler: () => void;
}
@@ -35,17 +37,17 @@ export const Support = observer(() => {
{
title: translator.getMessage('options_support_faq_title'),
description: translator.getMessage('options_support_faq_description'),
- iconXlink: '#question',
+ icon: 'question',
clickHandler: createOpenUrlHandler(getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.FAQ)),
}, {
title: translator.getMessage('options_support_report_title'),
description: translator.getMessage('options_support_report_description'),
- iconXlink: '#bug',
+ icon: 'bug',
clickHandler: handleReportClick,
}, {
title: translator.getMessage('options_support_feedback_title'),
description: translator.getMessage('options_support_feedback_description'),
- iconXlink: '#send-feedback',
+ icon: 'send-feedback',
clickHandler: createOpenUrlHandler(getForwarderUrl(forwarderDomain, FORWARDER_URL_QUERIES.FEEDBACK)),
},
];
@@ -53,29 +55,18 @@ export const Support = observer(() => {
const renderSupportItem = ({
title,
description,
- iconXlink,
+ icon,
clickHandler,
}: SupportItems) => {
return (
-
}
+ action={
}
onClick={clickHandler}
- >
-
-
-
-
{title}
-
{description}
-
-
-
-
+ />
);
};
diff --git a/src/options/components/Support/support.pcss b/src/options/components/Support/support.pcss
index d972ecd90..5eb7fa978 100644
--- a/src/options/components/Support/support.pcss
+++ b/src/options/components/Support/support.pcss
@@ -1,62 +1,9 @@
-.support-items {
- .support-item {
- display: flex;
- padding: 16px;
- border: none;
- border-radius: 8px;
- width: 100%;
- align-items: center;
- cursor: pointer;
- transition: var(--t3) background-color;
- background: none;
- text-align: left;
- font-family: inherit;
-
- &:hover {
- background-color: #f3f3f3;
-
- @media (prefers-color-scheme: dark) {
- background-color: #131313;
- }
- }
-
- .support-item__area {
- display: flex;
- width: 100%;
-
- .icon {
- margin-right: 16px;
- transform: translateX(-2px);
- }
- }
-
- &__title {
- font-size: 16px;
- font-weight: 500;
- line-height: 19px;
- }
-
- &__description {
- font-size: 14px;
- line-height: 20px;
- color: var(--gray88);
- }
+.support {
+ &__icon {
+ color: var(--stroke-icons-product-icon-default);
}
-}
-
-
-.dark-mode {
- .support-item {
- &:hover {
- background-color: #131313;
- }
- }
-}
-.light-mode {
- .support-item {
- &:hover {
- background-color: #f3f3f3;
- }
+ &__btn-icon {
+ transform: rotate(-90deg);
}
}
diff --git a/src/options/components/ui/Button/Button.tsx b/src/options/components/ui/Button/Button.tsx
new file mode 100644
index 000000000..582cfa3fd
--- /dev/null
+++ b/src/options/components/ui/Button/Button.tsx
@@ -0,0 +1,100 @@
+import React, { type PropsWithChildren } from 'react';
+
+import classNames from 'classnames';
+
+import { Icon } from '../Icon';
+
+import './button.pcss';
+
+/**
+ * Button component props.
+ */
+export interface ButtonProps extends PropsWithChildren {
+ /**
+ * Button variant. Default is 'filled'.
+ */
+ variant?: 'filled' | 'outlined' | 'transparent';
+
+ /**
+ * Button type. Default is 'button'.
+ */
+ type?: 'button' | 'submit' | 'reset';
+
+ /**
+ * Button color. Default is 'primary'.
+ */
+ color?: 'primary' | 'danger';
+
+ /**
+ * Button size. Default is 'large'.
+ * - 'large' - 16px 24px padding, 18px font size.
+ * - 'medium' - 16px 16px padding, 16px font size.
+ *
+ * Supported only with 'filled' and 'outlined' variant.
+ */
+ size?: 'large' | 'medium';
+
+ /**
+ * Additional class name.
+ */
+ className?: string;
+
+ /**
+ * Is button disabled or not.
+ */
+ disabled?: boolean;
+
+ /**
+ * Form id to associate the button with.
+ */
+ form?: string;
+
+ /**
+ * Icon name to display before the text.
+ */
+ beforeIconName?: string;
+
+ /**
+ * Click event handler.
+ */
+ onClick?: (e: React.MouseEvent
) => void;
+}
+
+export function Button({
+ variant = 'filled',
+ type = 'button',
+ color = 'primary',
+ size = 'large',
+ className,
+ disabled,
+ form,
+ beforeIconName,
+ children,
+ onClick,
+}: ButtonProps) {
+ const classes = classNames(
+ 'button has-tab-focus',
+ `button--${variant}`,
+ `button--color-${color}`,
+ `button--size-${size}`,
+ className,
+ );
+
+ return (
+
+ {beforeIconName && (
+
+ )}
+
+ {children}
+
+
+ );
+}
diff --git a/src/options/components/ui/Button/button.pcss b/src/options/components/ui/Button/button.pcss
new file mode 100644
index 000000000..a04327e91
--- /dev/null
+++ b/src/options/components/ui/Button/button.pcss
@@ -0,0 +1,153 @@
+button {
+ padding: 0;
+ margin: 0;
+ border: none;
+ background-color: transparent;
+ cursor: pointer;
+ font-family: var(--font-stack);
+}
+
+.button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ column-gap: 12px;
+ font-family: var(--font-stack);
+ overflow: hidden;
+ text-align: center;
+ border-radius: 8px;
+ border: 1px solid transparent;
+ transition: var(--t3) color, var(--t3) background-color, var(--t3) border-color;
+
+ &:disabled {
+ cursor: default;
+ pointer-events: none;
+ }
+
+ &--filled,
+ &--outlined {
+ padding: 16px 24px;
+ font-size: 18px;
+ line-height: 1.3;
+
+ &.button--size-medium {
+ padding: 16px;
+ font-size: 16px;
+ line-height: 1.5;
+ }
+ }
+
+ &--filled {
+ color: var(--text-buttons-primary-default);
+ background-color: var(--fills-buttons-main-button-default);
+ border-color: var(--fills-buttons-main-button-default);
+
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-buttons-main-button-hovered);
+ border-color: var(--fills-buttons-main-button-hovered);
+ }
+
+ &:active {
+ background-color: var(--fills-buttons-main-button-pressed);
+ border-color: var(--fills-buttons-main-button-pressed);
+ }
+
+ &:disabled {
+ background-color: var(--fills-buttons-main-button-disabled);
+ border-color: var(--fills-buttons-main-button-disabled);
+ }
+
+ &.button--color-danger {
+ background-color: var(--fills-buttons-danger-button-default);
+ border-color: var(--fills-buttons-danger-button-default);
+
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-buttons-danger-button-hovered);
+ border-color: var(--fills-buttons-danger-button-hovered);
+ }
+
+ &:active {
+ background-color: var(--fills-buttons-danger-button-pressed);
+ border-color: var(--fills-buttons-danger-button-pressed);
+ }
+
+ &:disabled {
+ background-color: var(--fills-buttons-danger-button-disabled);
+ border-color: var(--fills-buttons-danger-button-disabled);
+ }
+ }
+ }
+
+ &--outlined {
+ color: var(--text-main-text-main-default);
+ border-color: var(--stroke-buttons-secondary-default);
+
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-buttons-secondary-hovered);
+ }
+
+ &:active {
+ background-color: var(--fills-buttons-secondary-pressed);
+ }
+
+ &:disabled {
+ color: var(--text-main-text-main-disabled);
+ border-color: var(--stroke-buttons-secondary-disabled);
+ }
+
+ &.button--color-danger {
+ color: var(--text-buttons-secondary-red-default);
+ border-color: var(--stroke-buttons-secondary-red-default);
+
+ &:disabled {
+ color: var(--text-buttons-secondary-red-disabled);
+ border-color: var(--stroke-buttons-secondary-red-disabled);
+ }
+ }
+ }
+
+ &--transparent {
+ width: 100%;
+ padding: 16px;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.3;
+ text-align: left;
+ color: var(--text-links-link-default);
+
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-backgrounds-page-background-hovered);
+ }
+
+ &:active {
+ background-color: var(--fills-backgrounds-page-background-pressed);
+ }
+
+ &:disabled {
+ color: var(--text-links-link-disabled);
+ }
+
+ &.button--color-danger {
+ color: var(--text-links-error-link-default);
+
+ &:disabled {
+ color: var(--text-links-error-link-disabled);
+ }
+ }
+ }
+
+ &__text {
+ flex: 1 1 auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ &__before-icon {
+ color: currentColor;
+ }
+}
\ No newline at end of file
diff --git a/src/options/components/ui/Button/index.ts b/src/options/components/ui/Button/index.ts
new file mode 100644
index 000000000..fe9c53c51
--- /dev/null
+++ b/src/options/components/ui/Button/index.ts
@@ -0,0 +1 @@
+export { Button } from './Button';
diff --git a/src/options/components/ui/Checkbox/Checkbox.tsx b/src/options/components/ui/Checkbox/Checkbox.tsx
index 4de2eb8b3..0b45a4af2 100644
--- a/src/options/components/ui/Checkbox/Checkbox.tsx
+++ b/src/options/components/ui/Checkbox/Checkbox.tsx
@@ -1,30 +1,57 @@
import React, { useState } from 'react';
+import classNames from 'classnames';
+
+import { Icon } from '../Icon';
+
import './checkbox.pcss';
-type CheckboxProps = {
- label: React.ReactNode,
- id: string,
- value: boolean,
-};
+export interface CheckboxProps {
+ id?: string;
+ label: React.ReactNode;
+ value: boolean;
+ onToggle?: () => void;
+}
-export const Checkbox = ({ label, id, value }: CheckboxProps) => {
+export function Checkbox({
+ id,
+ label,
+ value,
+ onToggle,
+}: CheckboxProps) {
const [checkedValue, setCheckedValue] = useState(value);
+ const classes = classNames(
+ 'checkbox has-tab-focus',
+ checkedValue && 'checkbox--active',
+ );
+ const iconName = `checkbox-${checkedValue ? 'enabled' : 'disabled'}`;
+
+ const handleChange = () => {
+ setCheckedValue((prevValue) => !prevValue);
+ if (onToggle) {
+ onToggle();
+ }
+ };
+
return (
-
+
);
-};
+}
diff --git a/src/options/components/ui/Checkbox/checkbox.pcss b/src/options/components/ui/Checkbox/checkbox.pcss
index ce84bc944..588810706 100644
--- a/src/options/components/ui/Checkbox/checkbox.pcss
+++ b/src/options/components/ui/Checkbox/checkbox.pcss
@@ -1,25 +1,27 @@
.checkbox {
- cursor: pointer;
- color: var(--gray-base);
display: flex;
- align-items: center;
+ align-items: flex-start;
+ justify-content: flex-start;
+ cursor: pointer;
- & .checkbox__in {
- appearance: none;
- width: 24px;
- height: 24px;
- margin: 0 16px 0 0;
- background-image: url('../../../../assets/images/unchecked.svg');
- background-position: center;
- background-repeat: no-repeat;
- background-size: contain;
+ &__icon {
+ flex-shrink: 0;
- &:checked {
- background-image: url('../../../../assets/images/checked.svg');
+ .checkbox--active & {
+ color: var(--stroke-icons-product-icon-default);
}
}
+ &__input {
+ appearance: none;
+ margin: 0;
+ }
+
&__label {
- cursor: pointer;
+ flex: 1 1 auto;
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ line-height: 1.5;
+ margin-left: 16px;
}
-}
+}
\ No newline at end of file
diff --git a/src/options/components/ui/Controls/Controls.tsx b/src/options/components/ui/Controls/Controls.tsx
new file mode 100644
index 000000000..6a688d8b2
--- /dev/null
+++ b/src/options/components/ui/Controls/Controls.tsx
@@ -0,0 +1,128 @@
+import React, { forwardRef, useImperativeHandle, useRef } from 'react';
+
+import classNames from 'classnames';
+
+import { useOutsideClick } from '../../../../common/components/ui/useOutsideClick';
+import { useOutsideFocus } from '../../../../common/components/ui/useOutsideFocus';
+
+import './controls.pcss';
+
+/**
+ * Controls component props.
+ */
+export interface ControlsProps {
+ /**
+ * Title of the controls.
+ */
+ title: React.ReactNode;
+
+ /**
+ * Description of the controls.
+ */
+ description?: React.ReactNode;
+
+ /**
+ * Action of the controls that goes before the content (will be rendered on left side).
+ */
+ beforeAction?: React.ReactNode;
+
+ /**
+ * Action of the controls that goes after the content (will be rendered on right side).
+ */
+ action?: React.ReactNode;
+
+ /**
+ * Whether the controls are active or not.
+ * If active, the controls will be highlighted with "pressed" background.
+ */
+ isActive?: boolean;
+
+ /**
+ * Additional class name.
+ */
+ className?: string;
+
+ /**
+ * Click event handler.
+ * If provided, controls will be hoverable.
+ */
+ onClick?: () => void;
+
+ /**
+ * Outside click event handler.
+ */
+ onOutsideClick?: () => void;
+
+ /**
+ * Outside focus event handler.
+ */
+ onOutsideFocus?: () => void;
+}
+
+export const Controls = forwardRef(
+ (props, forwardedRef) => {
+ const {
+ title,
+ description,
+ beforeAction,
+ action,
+ isActive,
+ className,
+ onClick,
+ onOutsideClick,
+ onOutsideFocus,
+ } = props;
+
+ const ref = useRef(null);
+
+ const classes = classNames(
+ 'controls',
+ !!onClick && 'controls--hoverable',
+ isActive && 'controls--active',
+ className,
+ );
+
+ const handleOutsideClick = () => {
+ if (onOutsideClick) {
+ onOutsideClick();
+ }
+ };
+
+ const handleOutsideFocus = () => {
+ if (onOutsideFocus) {
+ onOutsideFocus();
+ }
+ };
+
+ useOutsideClick(ref, handleOutsideClick);
+ useOutsideFocus(ref, handleOutsideFocus);
+ useImperativeHandle(forwardedRef, () => ref.current!);
+
+ return (
+
+ {beforeAction && (
+
+ {beforeAction}
+
+ )}
+
+
{title}
+ {description && (
+
+ {description}
+
+ )}
+
+ {action && (
+
+ {action}
+
+ )}
+
+ );
+ },
+);
diff --git a/src/options/components/ui/Controls/ControlsSelect.tsx b/src/options/components/ui/Controls/ControlsSelect.tsx
new file mode 100644
index 000000000..52038966d
--- /dev/null
+++ b/src/options/components/ui/Controls/ControlsSelect.tsx
@@ -0,0 +1,84 @@
+import React, { useRef, useState } from 'react';
+
+import classNames from 'classnames';
+
+import { Select, type SelectProps } from '../Select';
+
+import { Controls, type ControlsProps } from './Controls';
+
+/**
+ * ControlsSelect component props.
+ */
+export interface ControlsSelectProps {
+ /**
+ * Title of the controls.
+ */
+ title: ControlsProps['title'];
+
+ /**
+ * Description of the controls.
+ */
+ description?: ControlsProps['description'];
+
+ /**
+ * Current selected value of the select.
+ */
+ value: SelectProps['value'];
+
+ /**
+ * Options of the select.
+ */
+ options: SelectProps['options'];
+
+ /**
+ * Change event handler.
+ */
+ onChange: SelectProps['onChange'];
+}
+
+export function ControlsSelect({
+ title,
+ description,
+ value,
+ options,
+ onChange,
+}: ControlsSelectProps) {
+ const ref = useRef(null);
+ const classes = classNames(
+ 'controls--select',
+ // If description is not provided title and select should be aligned to center.
+ !description && 'controls--select-align-center',
+ );
+
+ const [isActive, setIsActive] = useState(false);
+
+ const handleClick = () => {
+ setIsActive((currentActive) => !currentActive);
+ };
+
+ const handleOutsideClickOrFocus = () => {
+ setIsActive(false);
+ };
+
+ return (
+
+ )}
+ isActive={isActive}
+ className={classes}
+ onClick={handleClick}
+ onOutsideClick={handleOutsideClickOrFocus}
+ onOutsideFocus={handleOutsideClickOrFocus}
+ />
+ );
+}
diff --git a/src/options/components/ui/Controls/ControlsSwitch.tsx b/src/options/components/ui/Controls/ControlsSwitch.tsx
new file mode 100644
index 000000000..2eb3651a1
--- /dev/null
+++ b/src/options/components/ui/Controls/ControlsSwitch.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+
+import { Switch, type SwitchProps } from '../Switch';
+
+import { type ControlsProps, Controls } from './Controls';
+
+/**
+ * ControlsSwitch component props.
+ */
+export interface ControlsSwitchProps {
+ /**
+ * Title of the controls.
+ */
+ title: ControlsProps['title'];
+
+ /**
+ * Description of the controls.
+ */
+ description?: ControlsProps['description'];
+
+ /**
+ * Is the switch active or not.
+ */
+ isActive: SwitchProps['isActive'];
+
+ /**
+ * Toggle event handler.
+ */
+ onToggle: SwitchProps['onToggle'];
+}
+
+export function ControlsSwitch({
+ title,
+ description,
+ isActive,
+ onToggle,
+}: ControlsSwitchProps) {
+ return (
+ }
+ onClick={onToggle}
+ />
+ );
+}
diff --git a/src/options/components/ui/Controls/controls.pcss b/src/options/components/ui/Controls/controls.pcss
new file mode 100644
index 000000000..cc1bf34bd
--- /dev/null
+++ b/src/options/components/ui/Controls/controls.pcss
@@ -0,0 +1,80 @@
+.controls {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ padding: 16px;
+ transition: var(--t3) background-color;
+
+ &--select {
+ position: relative;
+
+ .controls__content {
+ flex: 1 1 0;
+ min-width: min-content;
+ }
+
+ .controls__action {
+ flex: 0 1 auto;
+ min-width: 0;
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-end;
+
+ .select,
+ .select__btn {
+ width: 100%;
+ }
+ }
+
+ &-align-center {
+ align-items: center;
+ }
+ }
+
+ &--hoverable,
+ &--active {
+ cursor: pointer;
+ background-color: transparent;
+ border-radius: 8px;
+ }
+
+ &--hoverable:hover {
+ background-color: var(--fills-backgrounds-page-background-hovered);
+ }
+
+ &--hoverable:active,
+ &--active,
+ &--active:hover {
+ background-color: var(--fills-backgrounds-page-background-pressed);
+ }
+
+ &__content {
+ flex: 1 1 auto;
+ }
+
+ &__before-action {
+ margin-right: 12px;
+ flex-shrink: 0;
+ }
+
+ &__action {
+ margin-left: 16px;
+ flex-shrink: 0;
+ }
+
+ &__title {
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.3;
+ transition: var(--t3) color;
+ }
+
+ &__description {
+ color: var(--text-description-description-default);
+ font-size: 14px;
+ line-height: 1.3;
+ margin-top: 2px;
+ transition: var(--t3) color;
+ }
+}
diff --git a/src/options/components/ui/Controls/index.ts b/src/options/components/ui/Controls/index.ts
new file mode 100644
index 000000000..aaa9fa9a1
--- /dev/null
+++ b/src/options/components/ui/Controls/index.ts
@@ -0,0 +1,3 @@
+export { Controls } from './Controls';
+export { ControlsSelect } from './ControlsSelect';
+export { ControlsSwitch } from './ControlsSwitch';
diff --git a/src/options/components/ui/Icon/Icon.tsx b/src/options/components/ui/Icon/Icon.tsx
new file mode 100644
index 000000000..764cd9cda
--- /dev/null
+++ b/src/options/components/ui/Icon/Icon.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+
+import classNames from 'classnames';
+
+/**
+ * Icon component props.
+ */
+export interface IconProps {
+ /**
+ * The name of the icon to display.
+ *
+ * Full list of available icons can be found in the `Icons.tsx` file.
+ */
+ name: string;
+
+ /**
+ * Additional class name.
+ */
+ className?: string;
+}
+
+export function Icon({ name, className }: IconProps) {
+ const classes = classNames(
+ 'icon',
+ className,
+ );
+
+ return (
+
+ );
+}
diff --git a/src/options/components/ui/Icon/IconButton.tsx b/src/options/components/ui/Icon/IconButton.tsx
new file mode 100644
index 000000000..c0fe87553
--- /dev/null
+++ b/src/options/components/ui/Icon/IconButton.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+
+import classNames from 'classnames';
+
+import { Icon } from './Icon';
+
+/**
+ * IconButton component props.
+ */
+export interface IconButtonProps {
+ /**
+ * Button type. Default is 'button'.
+ */
+ type?: 'button' | 'submit' | 'reset';
+
+ /**
+ * Button hover color. Default is 'primary'.
+ */
+ hoverColor?: 'primary' | 'success' | 'error';
+
+ /**
+ * The name of the icon to display.
+ *
+ * Full list of available icons can be found in the `Icons.tsx` file.
+ */
+ name: string;
+
+ /**
+ * Additional class name.
+ */
+ className?: string;
+
+ /**
+ * Additional icon class name.
+ */
+ iconClassName?: string;
+
+ /**
+ * Click event handler.
+ */
+ onClick?: (e: React.MouseEvent) => void;
+}
+
+export function IconButton({
+ type = 'button',
+ hoverColor = 'primary',
+ name,
+ className,
+ iconClassName,
+ onClick,
+}: IconButtonProps) {
+ const classes = classNames(
+ 'icon-button has-tab-focus',
+ `icon-button--hover-${hoverColor}`,
+ className,
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/src/options/components/ui/Icon/Icons.tsx b/src/options/components/ui/Icon/Icons.tsx
new file mode 100644
index 000000000..c558ffb9a
--- /dev/null
+++ b/src/options/components/ui/Icon/Icons.tsx
@@ -0,0 +1,194 @@
+/* eslint-disable react/no-unknown-property */
+import React from 'react';
+
+import './icons.pcss';
+
+export const Icons = () => (
+
+);
diff --git a/src/options/components/ui/Icon/icons.pcss b/src/options/components/ui/Icon/icons.pcss
new file mode 100644
index 000000000..f0317abac
--- /dev/null
+++ b/src/options/components/ui/Icon/icons.pcss
@@ -0,0 +1,41 @@
+.icon {
+ display: inline-block;
+ vertical-align: middle;
+ flex-shrink: 0;
+ font-size: 0;
+ width: 24px;
+ height: 24px;
+ color: var(--stroke-icons-gray-icons-default);
+}
+
+.icon-button {
+ flex-shrink: 0;
+ color: var(--stroke-icons-gray-icons-default);
+ transition: var(--t3) color;
+
+ &:hover,
+ &:active,
+ &:focus-visible {
+ color: var(--text-main-text-main-default);
+ }
+
+ &--hover-success {
+ &:hover,
+ &:active,
+ &:focus-visible {
+ color: var(--stroke-icons-product-icon-default);
+ }
+ }
+
+ &--hover-error {
+ &:hover,
+ &:active,
+ &:focus-visible {
+ color: var(--stroke-icons-error-icon-default);
+ }
+ }
+
+ .icon {
+ color: currentColor;
+ }
+}
diff --git a/src/options/components/ui/Icon/index.ts b/src/options/components/ui/Icon/index.ts
new file mode 100644
index 000000000..c08e705d4
--- /dev/null
+++ b/src/options/components/ui/Icon/index.ts
@@ -0,0 +1,3 @@
+export { Icons } from './Icons';
+export { Icon } from './Icon';
+export { IconButton, type IconButtonProps } from './IconButton';
diff --git a/src/options/components/ui/Icons.tsx b/src/options/components/ui/Icons.tsx
deleted file mode 100644
index da0f104ea..000000000
--- a/src/options/components/ui/Icons.tsx
+++ /dev/null
@@ -1,190 +0,0 @@
-/* eslint-disable react/no-unknown-property */
-import React from 'react';
-
-import './icon.pcss';
-
-const Icons = () => (
-
-);
-
-export default Icons;
diff --git a/src/options/components/ui/Input/Input.tsx b/src/options/components/ui/Input/Input.tsx
new file mode 100644
index 000000000..e6ce31624
--- /dev/null
+++ b/src/options/components/ui/Input/Input.tsx
@@ -0,0 +1,144 @@
+import React from 'react';
+
+import classNames from 'classnames';
+
+import { IconButton } from '../Icon';
+
+import './input.pcss';
+
+/**
+ * Base props for the input component.
+ */
+export interface InputBaseProps {
+ /**
+ * ID of the element.
+ */
+ id?: string;
+
+ /**
+ * Name of the element.
+ */
+ name?: string;
+
+ /**
+ * Label of the element.
+ */
+ label?: React.ReactNode;
+
+ /**
+ * Placeholder of the element.
+ */
+ placeholder?: string;
+
+ /**
+ * Whether the element is required or not.
+ */
+ required?: boolean;
+
+ /**
+ * Current value of the element.
+ */
+ value: string;
+
+ /**
+ * Error message to display.
+ */
+ error?: React.ReactNode;
+
+ /**
+ * Change event handler.
+ */
+ onChange?: (value: string) => void;
+}
+
+/**
+ * Input component props.
+ */
+export interface InputProps extends InputBaseProps {
+ /**
+ * Type of the input. Default is 'text'.
+ */
+ type?: 'text' | 'email';
+
+ /**
+ * Whether the input is read-only or not.
+ */
+ readOnly?: boolean;
+
+ /**
+ * Postfix message to display.
+ */
+ postfix?: string;
+}
+
+export function Input({
+ id,
+ name,
+ type = 'text',
+ label,
+ placeholder,
+ required,
+ value,
+ readOnly,
+ error,
+ postfix,
+ onChange,
+}: InputProps) {
+ const classes = classNames(
+ 'input',
+ !!error && 'input--error',
+ );
+
+ const clearValue = () => {
+ if (onChange) {
+ onChange('');
+ }
+ };
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { value } = e.target;
+ if (onChange) {
+ onChange(value);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/options/components/ui/Input/TextArea.tsx b/src/options/components/ui/Input/TextArea.tsx
new file mode 100644
index 000000000..a12873ba4
--- /dev/null
+++ b/src/options/components/ui/Input/TextArea.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+
+import classNames from 'classnames';
+
+import { type InputBaseProps } from './Input';
+
+export function TextArea({
+ id,
+ name,
+ label,
+ placeholder,
+ value,
+ required,
+ onChange,
+ error,
+}: InputBaseProps) {
+ const classes = classNames(
+ 'input',
+ 'textarea',
+ !!error && 'input--error',
+ );
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { value } = e.target;
+ if (onChange) {
+ onChange(value);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/options/components/ui/Input/index.ts b/src/options/components/ui/Input/index.ts
new file mode 100644
index 000000000..1c2ac91c3
--- /dev/null
+++ b/src/options/components/ui/Input/index.ts
@@ -0,0 +1,2 @@
+export { Input } from './Input';
+export { TextArea } from './TextArea';
diff --git a/src/options/components/ui/Input/input.pcss b/src/options/components/ui/Input/input.pcss
new file mode 100644
index 000000000..584f8c839
--- /dev/null
+++ b/src/options/components/ui/Input/input.pcss
@@ -0,0 +1,95 @@
+.input {
+ display: block;
+ width: 100%;
+
+ &__label {
+ display: block;
+ margin-bottom: 8px;
+ color: var(--text-labels-labels-default);
+ font-size: 14px;
+ line-height: 1.3;
+ }
+
+ &__wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8px;
+ color: var(--text-labels-labels-default);
+ background: var(--fills-inputs-input-background-default);
+ border: 1px solid var(--stroke-inputs-inactive-input-stroke-default);
+ transition: var(--t3) border-color;
+ padding-right: 8px;
+
+ .textarea & {
+ padding-right: 0;
+ }
+
+ &:focus-within {
+ border-color: var(--stroke-inputs-active-input-stroke-default);
+ }
+
+ .input--error & {
+ border-color: var(--stroke-inputs-error-input-default);
+ }
+ }
+
+ &__native {
+ flex: 1 1 auto;
+ display: block;
+ width: 100%;
+ padding: 16px 8px 16px 16px;
+ color: var(--text-main-text-main-default);
+ outline: none;
+ text-overflow: ellipsis;
+ border: none;
+ background-color: transparent;
+
+ &--textarea {
+ padding: 16px;
+ }
+
+ .textarea & {
+ padding: 16px;
+ }
+
+ &, &::placeholder {
+ font-size: 16px;
+ line-height: 1.3;
+ font-family: var(--font-stack);
+ }
+
+ &::placeholder {
+ color: var(--text-placeholder-placeholder-default);
+ }
+ }
+
+ textarea.input__native {
+ resize: vertical;
+ min-height: 140px;
+ }
+
+ &__clear-btn {
+ flex-shrink: 0;
+ padding: 6px;
+ }
+
+ &__postfix {
+ flex-shrink: 0;
+ color: var(--text-main-text-main-default);
+ text-align: right;
+ font-size: 16px;
+ line-height: 1.3;
+ letter-spacing: -0.31px;
+ padding-left: 16px;
+ padding-right: 8px;
+ border-left: 1px solid var(--stroke-dividers-item-divider-default);
+ }
+
+ &__error {
+ margin-top: 8px;
+ color: var(--text-links-error-link-default);
+ font-size: 14px;
+ line-height: 1.3;
+ }
+}
diff --git a/src/options/components/ui/Modal/Modal.tsx b/src/options/components/ui/Modal/Modal.tsx
new file mode 100644
index 000000000..c645dd863
--- /dev/null
+++ b/src/options/components/ui/Modal/Modal.tsx
@@ -0,0 +1,119 @@
+import React, { useEffect, type PropsWithChildren } from 'react';
+
+import classNames from 'classnames';
+
+import { ESC_KEY_NAME } from '../../../../common/components/ui/useOutsideClick';
+import { ReactPortal } from '../ReactPortal';
+import { IconButton } from '../Icon';
+
+import './modal.pcss';
+
+/**
+ * Modal component props.
+ */
+export interface ModalProps extends PropsWithChildren {
+ /**
+ * Title of the modal.
+ */
+ title: React.ReactNode;
+
+ /**
+ * Description of the modal.
+ */
+ description?: React.ReactNode;
+
+ /**
+ * Actions of the modal. Also can be considered as footer.
+ */
+ actions?: React.ReactNode;
+
+ /**
+ * Flag that indicates whether the modal is open or not.
+ */
+ isOpen: boolean;
+
+ /**
+ * Size of the modal. Default is `large`.
+ * - 'large' - 720px width
+ * - 'medium' - 600px width
+ */
+ size?: 'large' | 'medium';
+
+ /**
+ * Additional class name.
+ */
+ className?: string;
+
+ /**
+ * Close event handler.
+ */
+ onClose: () => void;
+}
+
+export function Modal({
+ title,
+ description,
+ actions,
+ isOpen,
+ size = 'large',
+ className,
+ children,
+ onClose,
+}: ModalProps) {
+ const classes = classNames(
+ 'modal',
+ `modal--size-${size}`,
+ className,
+ );
+
+ useEffect(() => {
+ const listener = (event: KeyboardEvent) => {
+ if (event.key === ESC_KEY_NAME) {
+ onClose();
+ }
+ };
+
+ document.addEventListener('keydown', listener);
+
+ return () => {
+ document.removeEventListener('keydown', listener);
+ };
+ }, [onClose]);
+
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {children}
+
+ {actions && (
+
+ {actions}
+
+ )}
+
+
+
+ );
+}
diff --git a/src/options/components/ui/Modal/index.ts b/src/options/components/ui/Modal/index.ts
new file mode 100644
index 000000000..8deb0a3df
--- /dev/null
+++ b/src/options/components/ui/Modal/index.ts
@@ -0,0 +1 @@
+export { Modal } from './Modal';
diff --git a/src/options/components/ui/Modal/modal.pcss b/src/options/components/ui/Modal/modal.pcss
new file mode 100644
index 000000000..17ac4c7f2
--- /dev/null
+++ b/src/options/components/ui/Modal/modal.pcss
@@ -0,0 +1,117 @@
+.modal {
+ z-index: var(--modal-z);
+ position: fixed;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &, &__overlay {
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ &__overlay {
+ position: absolute;
+ background-color: var(--fills-backgrounds-dialog-background-default);
+ opacity: 0.5;
+ }
+
+ &__content {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ max-width: 95%;
+ max-height: 95%;
+ padding: 32px;
+ border-radius: 8px;
+ background: var(--fills-backgrounds-page-background-default);
+ box-shadow: var(--big-light-shadow-br);
+
+ .modal--size-large & {
+ width: 720px;
+ }
+
+ .modal--size-medium & {
+ width: 600px;
+ }
+
+ @media (--tablet-sm) {
+ padding: 24px 16px;
+ }
+ }
+
+ &__header {
+ flex-shrink: 0;
+ padding-bottom: 32px;
+ }
+
+ &__title {
+ flex-shrink: 0;
+ color: var(--text-main-text-main-default);
+ font-size: 32px;
+ font-weight: 700;
+ line-height: 1.2;
+ padding-right: 24px;
+
+ @media (--tablet-sm) {
+ font-size: 24px;
+ padding-right: 64px;
+ }
+ }
+
+ &__description {
+ flex-shrink: 0;
+ font-size: 16px;
+ line-height: 1.5;
+ margin-top: 12px;
+ color: var(--text-description-description-default);
+
+ @media (--tablet-sm) {
+ padding-right: 40px;
+ }
+ }
+
+ &__close-btn {
+ position: absolute;
+ right: 12px;
+ top: 12px;
+ padding: 4px;
+ }
+
+ &__wrapper {
+ overflow-y: auto;
+ scrollbar-width: thin;
+ }
+
+ &__form {
+ display: flex;
+ flex-direction: column;
+ row-gap: 32px;
+ }
+
+ &__actions {
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-start;
+ padding-top: 32px;
+ flex-shrink: 0;
+ display: flex;
+ gap: 16px;
+
+ @media (--tablet-sm) {
+ flex-direction: column;
+ }
+
+ .button {
+ width: 200px;
+
+ @media (--tablet-sm) {
+ width: 100%;
+ }
+ }
+ }
+}
diff --git a/src/options/components/ui/Notifications/NotificationItem.tsx b/src/options/components/ui/Notifications/NotificationItem.tsx
new file mode 100644
index 000000000..162f3c75e
--- /dev/null
+++ b/src/options/components/ui/Notifications/NotificationItem.tsx
@@ -0,0 +1,74 @@
+import React, { useEffect } from 'react';
+
+import classNames from 'classnames';
+
+import { type Notification } from '../../../stores/NotificationsStore/Notification';
+import { IconButton } from '../Icon';
+
+const NOTIFICATION_CLEAR_TIMEOUT_MS = 5 * 1000; // 5s
+
+/**
+ * NotificationItem component props.
+ */
+export interface NotificationItemProps {
+ /**
+ * Notification to display.
+ */
+ value: Notification;
+
+ /**
+ * Close event handler.
+ */
+ onClose: (notificationId: string) => void;
+}
+
+export function NotificationItem({ value, onClose }: NotificationItemProps) {
+ const isSuccess = value.isSuccess();
+ const isError = value.isError();
+
+ const classes = classNames(
+ 'notifications__item',
+ isSuccess && 'notifications__item--success',
+ isError && 'notifications__item--error',
+ );
+
+ const handleClose = () => {
+ onClose(value.id);
+ };
+
+ const handleAction = () => {
+ if (value.action) {
+ value.action.handler();
+ }
+
+ handleClose();
+ };
+
+ useEffect(() => {
+ const timeoutId = setTimeout(handleClose, NOTIFICATION_CLEAR_TIMEOUT_MS);
+
+ return () => {
+ clearTimeout(timeoutId);
+ };
+ }, [handleClose]);
+
+ return (
+
+
+
+ {value.message}
+
+ {value.action && (
+
+ {value.action.action}
+
+ )}
+
+
+
+ );
+}
diff --git a/src/options/components/ui/Notifications/NotificationUi/NotificationUi.tsx b/src/options/components/ui/Notifications/NotificationUi/NotificationUi.tsx
deleted file mode 100644
index 6882c8640..000000000
--- a/src/options/components/ui/Notifications/NotificationUi/NotificationUi.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React, { useContext, useEffect } from 'react';
-
-import cn from 'classnames';
-
-import { rootStore } from '../../../../stores';
-import type { Notification } from '../../../../stores/NotificationsStore/Notification';
-
-const NOTIFICATION_CLEAR_TIMEOUT_MS = 5 * 1000;
-
-interface NotificationProps {
- notification: Notification,
-}
-
-export const NotificationUi = ({ notification }: NotificationProps) => {
- const { notificationsStore } = useContext(rootStore);
-
- useEffect(() => {
- const timeoutId = setTimeout(() => {
- notificationsStore.removeNotification(notification.id);
- }, NOTIFICATION_CLEAR_TIMEOUT_MS);
-
- return () => {
- clearTimeout(timeoutId);
- };
- }, []);
-
- const handleCloseNotification = () => {
- notificationsStore.removeNotification(notification.id);
- };
-
- const isErrorNotification = notification.isError();
-
- const notificationClassnames = cn('notification', {
- danger: isErrorNotification,
- success: notification.isSuccess(),
- });
-
- let actionButton;
- if (notification.action) {
- const { action } = notification;
-
- const handleAction = () => {
- action.handler();
- handleCloseNotification();
- };
-
- actionButton = (
-
-
- {action.action}
-
-
- );
- }
-
- const closeButton = (
-
-
-
-
-
- );
-
- const titleIcon = isErrorNotification
- ? (
-
-
-
- )
- : null;
-
- return (
-
-
-
- {titleIcon}
-
- {notification.message}
-
-
- {closeButton}
-
- {actionButton}
-
- );
-};
diff --git a/src/options/components/ui/Notifications/NotificationUi/index.ts b/src/options/components/ui/Notifications/NotificationUi/index.ts
deleted file mode 100644
index 4a5aa5f83..000000000
--- a/src/options/components/ui/Notifications/NotificationUi/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { NotificationUi } from './NotificationUi';
diff --git a/src/options/components/ui/Notifications/Notifications.tsx b/src/options/components/ui/Notifications/Notifications.tsx
index 65b676b42..ca9bafafb 100644
--- a/src/options/components/ui/Notifications/Notifications.tsx
+++ b/src/options/components/ui/Notifications/Notifications.tsx
@@ -1,9 +1,9 @@
-import React, { useContext } from 'react';
+import React, { useContext, useRef } from 'react';
import { observer } from 'mobx-react';
import { rootStore } from '../../../stores';
-import { NotificationUi } from './NotificationUi';
+import { NotificationItem } from './NotificationItem';
import './notifications.pcss';
@@ -11,11 +11,23 @@ export const Notifications = observer(() => {
const { notificationsStore } = useContext(rootStore);
const { notifications } = notificationsStore;
+ const ref = useRef(null);
+
+ const firstThreeNotifications = notifications.slice(0, 3);
+
+ const handleClose = (notificationId: string) => {
+ notificationsStore.removeNotification(notificationId);
+ };
+
return (
-
- {notifications.map((notification) => {
- return
;
- })}
+
+ {firstThreeNotifications.map((notification) => (
+
+ ))}
);
});
diff --git a/src/options/components/ui/Notifications/notifications.pcss b/src/options/components/ui/Notifications/notifications.pcss
index ec53fb172..17164ddbd 100644
--- a/src/options/components/ui/Notifications/notifications.pcss
+++ b/src/options/components/ui/Notifications/notifications.pcss
@@ -1,55 +1,57 @@
.notifications {
- position: fixed;
z-index: var(--notifications-z);
+ position: fixed;
display: flex;
- justify-content: center;
- width: 632px;
- left: calc(50% - 190px);
- bottom: 24px;
flex-direction: column;
+ justify-content: center;
align-items: center;
+ row-gap: 16px;
+ bottom: 40px;
+ /* On wide screens we center by content (sidebar is not included) */
+ left: calc(50% + var(--sidebar-width) / 2);
+ transform: translateX(-50%);
+ width: 630px;
+ max-width: 90%;
@media (--tablet) {
- left: 16px;
- right: 16px;
- width: auto;
- }
-}
-
-.notification {
- display: flex;
- flex-direction: column;
- padding: 16px;
- border-radius: 4px;
- width: 100%;
- min-height: 56px;
- background-color: var(--grayF3);
- margin-bottom: 16px;
-
- &:last-child {
- margin-bottom: 0;
+ /* On small screens sidebar is hidden, so we center by screen */
+ left: 50%;
}
- &__result {
- display: inline-flex;
- align-items: center;
+ &__item {
+ display: flex;
+ align-items: flex-start;
justify-content: space-between;
+ column-gap: 8px;
width: 100%;
- }
+ padding: 16px;
+ border-radius: 4px;
+ background: var(--fills-snacks-desktop-snacks-default);
+ box-shadow: var(--small-light-shadow-br);
- &__action {
- margin-top: 16px;
- }
+ &-content {
+ flex: 1 1 auto;
+ }
+
+ &-message {
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ line-height: 1.3;
+ }
+
+ &-action {
+ margin-top: 16px;
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ line-height: 1.3;
- &__message {
- display: inline-flex;
- font-size: 16px;
- line-height: 21px;
- max-width: 93%;
- color: var(--gray-base);
+ .notifications__item--success & {
+ color: var(--text-links-link-default);
+ }
- &--title {
- line-height: 24px;
+ .notifications__item--error & {
+ color: var(--text-links-error-link-default);
+ }
}
}
}
diff --git a/src/options/components/ui/Popover/Popover.tsx b/src/options/components/ui/Popover/Popover.tsx
deleted file mode 100644
index 094c50226..000000000
--- a/src/options/components/ui/Popover/Popover.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-
-import './popover.pcss';
-
-export const Popover = ({ children }: { children: React.ReactNode }) => (
-
-
-
-
-
-
-
- {children}
-
-
-);
diff --git a/src/options/components/ui/Popover/index.ts b/src/options/components/ui/Popover/index.ts
deleted file mode 100644
index 6e5a63648..000000000
--- a/src/options/components/ui/Popover/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Popover } from './Popover';
diff --git a/src/options/components/ui/Popover/popover.pcss b/src/options/components/ui/Popover/popover.pcss
deleted file mode 100644
index 785c15c43..000000000
--- a/src/options/components/ui/Popover/popover.pcss
+++ /dev/null
@@ -1,83 +0,0 @@
-.popover-wrap {
- position: relative;
- display: inline-block;
- vertical-align: middle;
- align-self: flex-start;
-}
-
-.popover__trigger {
- position: relative;
- display: flex;
- margin: 0 8px;
- cursor: pointer;
-}
-
-.popover__trigger:after {
- content: "";
- position: absolute;
- bottom: -10px;
- left: -1px;
- width: 26px;
- height: 28px;
-}
-
-.popover__trigger--address {
- top: 0;
- margin: 0;
- line-height: 1.2;
-}
-
-.popover__trigger--address:after {
- display: none;
-}
-
-.popover__body {
- content: "";
- display: flex;
- flex-direction: column;
- position: absolute;
- top: calc(100% + 15px);
- right: -32px;
- z-index: 1;
- min-width: 295px;
- padding: 15px 15px 20px;
- font-size: 1.3rem;
- line-height: 1.4;
- white-space: normal;
- background-color: var(--white-strong);
- border-radius: 4px;
- transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
- visibility: hidden;
- opacity: 0;
- filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
-}
-
-.popover__body:after {
- content: "";
- position: absolute;
- top: -5px;
- right: 45px;
- width: 0;
- height: 0;
- border-left: 6px solid transparent;
- border-right: 6px solid transparent;
- border-bottom: 6px solid var(--white-strong);
-}
-
-.popover__trigger:hover + .popover__body,
-.popover__body:hover {
- visibility: visible;
- opacity: 1;
-}
-
-.popover__icon {
- width: 24px;
- height: 24px;
- color: #888;
- opacity: 0.3;
-}
-
-.popover__title {
- margin-bottom: 5px;
- font-size: 1.6rem;
-}
diff --git a/src/options/components/ui/Radio/Radio.tsx b/src/options/components/ui/Radio/Radio.tsx
new file mode 100644
index 000000000..07f17a1d5
--- /dev/null
+++ b/src/options/components/ui/Radio/Radio.tsx
@@ -0,0 +1,117 @@
+import React from 'react';
+
+import classNames from 'classnames';
+
+import './radio.pcss';
+
+/**
+ * Radio component props.
+ */
+export interface RadioProps
{
+ /**
+ * Radio id.
+ */
+ id?: string;
+
+ /**
+ * Radio name.
+ */
+ name: string;
+
+ /**
+ * Radio value.
+ */
+ value: T;
+
+ /**
+ * Is radio active or not.
+ */
+ isActive: boolean;
+
+ /**
+ * Radio title.
+ */
+ title: React.ReactNode;
+
+ /**
+ * Radio description.
+ */
+ description?: React.ReactNode;
+
+ /**
+ * Radio action. Rendered on the right side of the radio.
+ */
+ action?: React.ReactNode;
+
+ /**
+ * Additional class name.
+ */
+ className?: string;
+
+ /**
+ * Select radio handler.
+ */
+ onSelect: (value: T) => void;
+}
+
+export function Radio({
+ id,
+ name,
+ value,
+ isActive,
+ title,
+ description,
+ action,
+ className,
+ onSelect,
+}: RadioProps) {
+ const classes = classNames(
+ 'radio has-tab-focus',
+ isActive && 'radio--active',
+ className,
+ );
+
+ const handleChange = (e: React.ChangeEvent) => {
+ if (e.target.checked) {
+ onSelect(value);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/options/components/ui/Radio/index.ts b/src/options/components/ui/Radio/index.ts
new file mode 100644
index 000000000..de3697b1a
--- /dev/null
+++ b/src/options/components/ui/Radio/index.ts
@@ -0,0 +1 @@
+export { Radio } from './Radio';
diff --git a/src/options/components/ui/Radio/radio.pcss b/src/options/components/ui/Radio/radio.pcss
new file mode 100644
index 000000000..7ebb37fe0
--- /dev/null
+++ b/src/options/components/ui/Radio/radio.pcss
@@ -0,0 +1,92 @@
+.radio {
+ width: 100%;
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-start;
+ padding: 16px;
+ transition: var(--t3) background-color;
+ border-radius: 8px;
+ text-align: left;
+ cursor: pointer;
+
+ &:hover {
+ background-color: var(--fills-backgrounds-page-background-hovered);
+ }
+
+ &:active {
+ background-color: var(--fills-backgrounds-page-background-pressed);
+ }
+
+ &__input {
+ appearance: none;
+ margin: 0;
+ }
+
+ &__circle {
+ &-outer {
+ width: 24px;
+ height: 24px;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ border: 2px solid var(--stroke-icons-gray-icons-default);
+ padding: 4px;
+ transition: var(--t3) border-color;
+
+ .radio--active & {
+ border-color: var(--stroke-icons-product-icon-default);
+ }
+ }
+
+ &-inner {
+ display: block;
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background-color: var(--stroke-icons-gray-icons-default);
+ transition: var(--t3) background-color, var(--t3) transform;
+ transform: scale(0);
+
+ .radio--active & {
+ transform: scale(1);
+ background-color: var(--stroke-icons-product-icon-default);
+ }
+ }
+ }
+
+ &__content {
+ flex: 1 1 auto;
+ display: block;
+ margin-left: 12px;
+ margin-top: 1px;
+ }
+
+ &__title,
+ &__description {
+ display: block;
+ line-height: 1.3;
+ }
+
+ &__title {
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ font-weight: 600;
+
+ .radio--thin & {
+ font-weight: 400;
+ }
+ }
+
+ &__description {
+ color: var(--text-description-description-default);
+ font-size: 14px;
+ margin-top: 2px;
+ }
+
+ &__action {
+ flex-shrink: 0;
+ margin-left: 16px;
+ }
+}
diff --git a/src/options/components/ui/ReactPortal.tsx b/src/options/components/ui/ReactPortal.tsx
new file mode 100644
index 000000000..164dbbc0b
--- /dev/null
+++ b/src/options/components/ui/ReactPortal.tsx
@@ -0,0 +1,6 @@
+import { type PropsWithChildren } from 'react';
+import { createPortal } from 'react-dom';
+
+export function ReactPortal({ children }: PropsWithChildren) {
+ return createPortal(children, document.body);
+}
diff --git a/src/options/components/ui/Select/Select.tsx b/src/options/components/ui/Select/Select.tsx
index a9a7d6bcf..fde0dacdf 100644
--- a/src/options/components/ui/Select/Select.tsx
+++ b/src/options/components/ui/Select/Select.tsx
@@ -1,94 +1,183 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useRef, useState } from 'react';
+
+import classNames from 'classnames';
import { useOutsideClick } from '../../../../common/components/ui/useOutsideClick';
import { useOutsideFocus } from '../../../../common/components/ui/useOutsideFocus';
+import { Icon } from '../Icon';
import './select.pcss';
-type SelectProps = {
- currentValue: T,
- options: {
- id: T,
- title: React.ReactNode | string,
- desc?: React.ReactNode | string,
- }[],
- optionChange: (id: T) => void,
-};
-
-export const Select = (props: SelectProps) => {
- const {
- currentValue,
- options,
- optionChange,
- } = props;
-
- const [value, setValue] = useState(currentValue);
- const [hidden, setHidden] = useState(true);
+/**
+ * Select option item.
+ */
+interface SelectOptionItem {
+ /**
+ * Value of the option.
+ */
+ value: T;
+
+ /**
+ * Title of the option.
+ */
+ title: React.ReactNode;
+
+ /**
+ * Should skip rendering of the option.
+ */
+ shouldSkip?: boolean;
+
+ /**
+ * Is the select active or not. Used to control tab index.
+ */
+ isSelectActive?: boolean;
+}
+
+/**
+ * SelectOption component props.
+ */
+interface SelectOptionProps extends SelectOptionItem {
+ /**
+ * Is the option is selected one or not.
+ */
+ isActive?: boolean;
+
+ /**
+ * Click event handler.
+ */
+ onClick: (value: T) => void;
+}
+
+function SelectOption({
+ value,
+ title,
+ isActive,
+ shouldSkip,
+ isSelectActive,
+ onClick,
+}: SelectOptionProps) {
+ const classes = classNames(
+ 'select__item has-tab-focus',
+ isActive && 'select__item--active',
+ );
- const ref = useRef(null);
+ const handleClick = () => {
+ onClick(value);
+ };
- useOutsideClick(ref, () => setHidden(true));
+ if (shouldSkip) {
+ return null;
+ }
- useOutsideFocus(ref, () => setHidden(true));
+ return (
+
+ {title}
+
+
+ );
+}
+
+/**
+ * Select component props.
+ */
+export interface SelectProps {
+ /**
+ * Current selected value of the select.
+ */
+ value: T;
+
+ /**
+ * Options of the select.
+ */
+ options: SelectOptionItem[];
+
+ /**
+ * Is the select active or not.
+ */
+ isActive?: boolean;
+
+ /**
+ * Change event handler.
+ */
+ onChange: (value: T) => void;
+
+ /**
+ * Is active change event handler.
+ */
+ onIsActiveChange?: (value: boolean | ((oldValue: boolean) => boolean)) => void;
+}
+
+export function Select({
+ value,
+ options,
+ isActive: outsideActive,
+ onChange,
+ onIsActiveChange,
+}: SelectProps) {
+ const activeItem = options.find((option) => option.value === value);
- useEffect(() => {
- setValue(currentValue);
- });
+ const ref = useRef(null);
- const handleSelectClick = (event: React.MouseEvent): void => {
- event.stopPropagation();
- setHidden(!hidden);
- };
+ const [localActive, setLocalActive] = useState(false);
+ const isActive = outsideActive !== undefined ? outsideActive : localActive;
- const handleOptionClick = (id: T): void => {
- setValue(id);
- optionChange(id);
- setHidden(true);
- };
+ const classes = classNames(
+ 'select',
+ isActive && 'select--active',
+ );
- const isActiveOption = (id: T) => ((id === value) ? ' active' : '');
+ const setActive = (value: boolean | ((oldValue: boolean) => boolean)) => {
+ if (!onIsActiveChange) {
+ setLocalActive(value);
+ }
+ };
- const optionsList: React.RefObject = useRef(null);
+ const handleClose = () => {
+ setActive(false);
+ };
- useEffect(() => {
- if (optionsList.current) {
- optionsList.current.scrollTop = 0;
- }
- });
+ const handleToggle = () => {
+ setActive((currentActive) => !currentActive);
+ };
- const getTitle = (value: T) => {
- const option = options.find((op) => op.id === value);
- return option?.title;
+ const handleChange = (value: T) => {
+ handleClose();
+ onChange(value);
};
+ useOutsideClick(ref, handleClose);
+ useOutsideFocus(ref, handleClose);
+
return (
- // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
-
+
- {getTitle(value)}
+
+ {activeItem?.title}
+
+
-
- {options.map(({ id, title, desc }) => (
-
handleOptionClick(id)}
- type="button"
- >
- {title}
- {desc && (
- {desc}
)}
-
+
+ {options.map((option) => (
+
))}
);
-};
+}
diff --git a/src/options/components/ui/Select/index.ts b/src/options/components/ui/Select/index.ts
index 205709b20..0b2fb0df7 100644
--- a/src/options/components/ui/Select/index.ts
+++ b/src/options/components/ui/Select/index.ts
@@ -1 +1 @@
-export { Select } from './Select';
+export { Select, type SelectProps } from './Select';
diff --git a/src/options/components/ui/Select/select.pcss b/src/options/components/ui/Select/select.pcss
index 3df41de8c..b96346fae 100644
--- a/src/options/components/ui/Select/select.pcss
+++ b/src/options/components/ui/Select/select.pcss
@@ -1,116 +1,89 @@
-.selector {
- user-select: none;
+.select {
+ position: relative;
- &__value {
- padding: 0 30px 0 10px;
- background-color: var(--white-strong);
- background-image: url('../../../../assets/images/arrow-down.svg');
- background-position: 100% center;
- background-repeat: no-repeat;
- cursor: pointer;
- border: none;
+ .controls & {
+ position: static;
+ }
- &-title {
- color: var(--gray-base);
- font-size: 1.6rem;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-align: right;
- }
+ &__btn {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ column-gap: 16px;
+ color: var(--text-main-text-main-default);
+ font-size: 16px;
+ line-height: 1.3;
+ transform: var(--t3) color;
+ text-align: right;
+ overflow: hidden;
+
+ &-icon {
+ transition: var(--t3) transform;
- &-desc {
- color: var(--gray88);
- font-size: 1.3rem;
- line-height: 1.2;
- margin: 3px 20px 0 0;
+ .select--active & {
+ transform: rotate(180deg);
+ }
}
}
- &__options-list {
+ &__list {
+ z-index: var(--select-z);
+ width: 240px;
position: absolute;
- z-index: 10;
+ /* Default space at bottom of container block */
+ top: calc(100% + 16px);
right: 0;
- max-height: 265px;
- min-width: 180px;
- max-width: 512px;
- overflow-y: auto;
- box-shadow: 0 0 7px 3px var(--shadow);
- padding: 0;
- margin: 13px 0 0 0;
- background-color: var(--white-strong);
- scrollbar-width: thin;
-
- &[hidden] {
- display: none;
- }
-
- &::-webkit-scrollbar {
- width: 4px;
- }
+ display: flex;
+ flex-direction: column;
+ border-radius: 8px;
+ box-shadow: var(--big-light-shadow-br);
+ background-color: var(--fills-menus-dropdown-menu-background-default);
+ overflow: hidden;
+ transition: var(--t3) transform, var(--t3) opacity, var(--t3) background-color;
+ transform: translateY(-20%);
+ opacity: 0;
+ pointer-events: none;
- &::-webkit-scrollbar-thumb {
- border-radius: 0px;
- background-color: var(--gray88);
+ .select--active & {
+ transform: translateY(0);
+ opacity: 1;
+ pointer-events: auto;
}
}
- &__option-item {
- width: 100%;
+ &__item {
display: flex;
- padding: 16px 25px;
- cursor: pointer;
- border: none;
- transition: var(--t3) background-color;
- background-color: var(--white);
-
- &:hover,
- &.active {
- background-color: #f3f3f3;
+ align-items: center;
+ justify-content: space-between;
+ column-gap: 16px;
+ padding: 16px 16px 16px 24px;
+ background-color: transparent;
+ color: var(--text-main-text-main-default);
+ outline-offset: -4px;
+ font-size: 16px;
+ line-height: 1.3;
+ transition: var(--t3) background-color, var(--t3) color;
+ text-align: left;
- @media (prefers-color-scheme: dark) {
- background-color: #4d4d4d;
- }
- }
-
- &-title {
- color: var(--text-main);
- font-size: 1.6rem;
- line-height: 1.6rem;
+ &--active {
+ padding: 16px 8px 16px 24px;
}
- &-desc {
- color: var(--gray88);
- font-size: 1.3rem;
- line-height: 1.2;
- margin: 3px 20px 0 0;
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-menus-dropdown-menu-background-hovered);
}
- }
- &--gray {
- & .selector__value-title {
- color: var(--gray88);
+ &:active {
+ background-color: var(--fills-menus-dropdown-menu-background-pressed);
}
- }
-}
-.light-mode {
- .selector {
- &__option-item {
- &:hover,
- &.active {
- background-color: #f3f3f3;
- }
- }
- }
-}
+ &-icon {
+ color: var(--stroke-icons-product-icon-default);
+ display: none;
-.dark-mode {
- .selector {
- &__option-item {
- &:hover,
- &.active {
- background-color: #4d4d4d;
+ .select__item--active & {
+ display: inline-block;
}
}
}
diff --git a/src/options/components/ui/Switch/Switch.tsx b/src/options/components/ui/Switch/Switch.tsx
index 1397f8162..978a1ae95 100644
--- a/src/options/components/ui/Switch/Switch.tsx
+++ b/src/options/components/ui/Switch/Switch.tsx
@@ -1,43 +1,44 @@
import React from 'react';
-import classnames from 'classnames';
+import classNames from 'classnames';
import './switch.pcss';
-interface SwitchProps {
- title: string | React.ReactNode;
- desc?: string | React.ReactNode;
- checked: boolean;
- handleToggle: () => void;
+/**
+ * Switch component props.
+ */
+export interface SwitchProps {
+ /**
+ * Is the switch active or not.
+ */
+ isActive: boolean;
+
+ /**
+ * Toggle event handler.
+ */
+ onToggle: () => void;
}
-export const Switch = ({
- title,
- desc,
- checked,
- handleToggle,
-}: SwitchProps) => {
- const togglerClass = classnames('switch__toggler', {
- 'switch__toggler--active': checked,
- });
+export function Switch({ isActive, onToggle }: SwitchProps) {
+ const classes = classNames(
+ 'switch has-tab-focus',
+ isActive && 'switch--active',
+ );
+
+ const handleClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ onToggle();
+ };
return (
-
-
-
- {title}
-
- {desc && (
-
- {desc}
-
- )}
-
-
-
+
+
+
+
+
);
-};
+}
diff --git a/src/options/components/ui/Switch/index.ts b/src/options/components/ui/Switch/index.ts
index cee89a18b..9e9f77526 100644
--- a/src/options/components/ui/Switch/index.ts
+++ b/src/options/components/ui/Switch/index.ts
@@ -1 +1 @@
-export { Switch } from './Switch';
+export { Switch, type SwitchProps } from './Switch';
diff --git a/src/options/components/ui/Switch/switch.pcss b/src/options/components/ui/Switch/switch.pcss
index 7a3ae5787..6c47b2700 100644
--- a/src/options/components/ui/Switch/switch.pcss
+++ b/src/options/components/ui/Switch/switch.pcss
@@ -1,79 +1,48 @@
.switch {
- position: relative;
- display: flex;
- align-items: center;
- padding-right: 15px;
+ --background: var(--fills-switch-all-off-default);
+ --background-hovered: var(--fills-switch-all-off-hovered);
- &__info {
- width: 100%;
- /* TODO: Hardcoded value (switch width + margin), we should remove after re-design (AG-38059) */
- padding-right: 56px;
- }
+ padding: 2px 0;
- &__title {
- font-size: 1.6rem;
- font-weight: 500;
- color: var(--gray-base);
+ &--active {
+ --background: var(--fills-switch-all-on-default);
+ --background-hovered: var(--fills-switch-all-on-hovered);
}
- &__desc {
- margin: 5px 20px 0 0;
- font-size: 1.4rem;
- line-height: 1.6rem;
- color: var(--gray88);
+ &:hover,
+ &:active,
+ &:focus-visible,
+ .controls:hover &,
+ .controls:active & {
+ .switch__wrapper {
+ background-color: var(--background-hovered);
+ }
}
- &__toggler {
- position: absolute;
- right: 15px;
- display: flex;
+ &__wrapper {
+ position: relative;
+ display: block;
width: 40px;
height: 20px;
- border: none;
+ background-color: var(--background);
border-radius: 10px;
- background-color: var(--gray88);
transition: var(--t3) background-color;
- padding: 0;
- cursor: pointer;
-
- &:before {
- content: "";
- width: 16px;
- height: 16px;
- display: block;
- transform: translate(3px, 2px);
- border-radius: 100%;
- background-color: #fff;
- transition: var(--t3) transform;
- position: relative;
- }
-
- &--active {
- background-color: var(--green700);
-
- &:before {
- transform: translate(21px, 2px);
- }
- }
}
- &__icon {
+ &__knob {
position: absolute;
- flex-shrink: 0;
- top: 3px;
- width: 14px;
- height: 14px;
- color: #fff;
- stroke-width: 2;
-
- &--check {
- left: 5px;
- width: 14px;
- height: 13px;
- }
-
- &--cross {
- right: 4px;
+ top: 2px;
+ left: 2px;
+ display: block;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background-color: var(--fills-switch-all-knob-default);
+ transition: var(--t3) transform;
+ transform: translateX(0);
+
+ .switch--active & {
+ transform: translateX(20px);
}
}
}
diff --git a/src/options/components/ui/Title.tsx b/src/options/components/ui/Title.tsx
deleted file mode 100644
index 3b1bb5f9d..000000000
--- a/src/options/components/ui/Title.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-
-import cn from 'classnames';
-
-interface TitleProps {
- title: React.ReactNode,
- subtitle?: React.ReactNode | string,
- onClick?: () => void;
-}
-
-export const Title = ({ title, subtitle, onClick }: TitleProps) => {
- const renderSubtitle = (subtitle?: React.ReactNode | string) => {
- if (!subtitle) {
- return null;
- }
-
- return (
-
- {subtitle}
-
- );
- };
-
- return (
-
-
- {title}
-
- {renderSubtitle(subtitle)}
-
- );
-};
diff --git a/src/options/components/ui/Title/Title.tsx b/src/options/components/ui/Title/Title.tsx
new file mode 100644
index 000000000..2b0a1925f
--- /dev/null
+++ b/src/options/components/ui/Title/Title.tsx
@@ -0,0 +1,89 @@
+import React from 'react';
+
+import classNames from 'classnames';
+
+import { IconButton } from '../Icon';
+
+import './title.pcss';
+
+/**
+ * Title component props.
+ */
+export interface TitleProps {
+ /**
+ * Title text to display.
+ */
+ title: React.ReactNode;
+
+ /**
+ * Subtitle text to display.
+ */
+ subtitle?: React.ReactNode;
+
+ /**
+ * Action to display on the right side of the title.
+ */
+ action?: React.ReactNode;
+
+ /**
+ * Size of the title. Default is `large`.
+ * - 'large' - 24px font size
+ * - 'medium' - 20px font size
+ */
+ size?: 'large' | 'medium';
+
+ /**
+ * Additional class name.
+ */
+ className?: string;
+
+ /**
+ * Click event handler.
+ * If provided, title will be hoverable.
+ */
+ onClick?: () => void;
+}
+
+export function Title({
+ title,
+ subtitle,
+ action,
+ size = 'large',
+ className,
+ onClick,
+}: TitleProps) {
+ const isBackTitle = !!onClick;
+
+ const classes = classNames(
+ 'title',
+ `title--${size}`,
+ isBackTitle && 'title--hoverable',
+ className,
+ );
+
+ return (
+
+
+
+ {isBackTitle && (
+
+ )}
+ {title}
+
+ {action && (
+
+ {action}
+
+ )}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ );
+}
diff --git a/src/options/components/ui/Title/index.ts b/src/options/components/ui/Title/index.ts
new file mode 100644
index 000000000..177576b64
--- /dev/null
+++ b/src/options/components/ui/Title/index.ts
@@ -0,0 +1 @@
+export { Title } from './Title';
diff --git a/src/options/components/ui/Title/title.pcss b/src/options/components/ui/Title/title.pcss
new file mode 100644
index 000000000..eb9f5a896
--- /dev/null
+++ b/src/options/components/ui/Title/title.pcss
@@ -0,0 +1,69 @@
+.title {
+ padding: 16px;
+
+ &--hoverable {
+ cursor: pointer;
+ background-color: transparent;
+ transition: var(--t3) background-color;
+ border-radius: 8px;
+
+ &:hover,
+ &:focus-visible {
+ background-color: var(--fills-backgrounds-page-background-hovered);
+ }
+
+ &:active {
+ background-color: var(--fills-backgrounds-page-background-pressed);
+ }
+ }
+
+ &__text {
+ color: var(--text-main-text-main-default);
+ font-size: 24px;
+ font-weight: 700;
+ line-height: 1.2;
+
+ .title--large & {
+ font-size: 24px;
+ }
+
+ .title--medium & {
+ font-size: 20px;
+ }
+
+ &, &-start {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ column-gap: 16px;
+ }
+
+ &-start {
+ flex: 1 1 auto;
+
+ &-icon {
+ transform: rotate(90deg);
+ }
+ }
+ }
+
+ &__subtitle {
+ color: var(--text-description-description-default);
+ font-size: 16px;
+ line-height: 1.3;
+ margin-top: 12px;
+ padding-right: 24px;
+
+ b {
+ font-weight: 600;
+ }
+
+ .title--hoverable & {
+ padding-left: 28px;
+ }
+ }
+
+ &__action {
+ flex-shrink: 0;
+ }
+}
diff --git a/src/options/components/ui/icon.pcss b/src/options/components/ui/icon.pcss
deleted file mode 100644
index 8f5eac0ed..000000000
--- a/src/options/components/ui/icon.pcss
+++ /dev/null
@@ -1,47 +0,0 @@
-.icon {
- display: inline-block;
- vertical-align: middle;
-
- &--button {
- width: 24px;
- height: 24px;
- flex-shrink: 0;
- }
-
- &--cross,
- &--unchecked {
- color: var(--gray88);
- }
-
- &--check,
- &--checked,
- &--plus {
- color: var(--brand-primary);
- }
-
- &--back {
- position: relative;
- top: 1px;
- margin-right: 3px;
- }
-
- &--flip {
- transform: scaleX(-1);
- }
-
- &--support {
- width: 24px;
- height: 24px;
- color: var(--green700)
- }
-
- &--pencil {
- width: 16px;
- height: 16px;
- margin-left: 3px;
- }
-
- &--warning {
- margin-right: 8px;
- }
-}
diff --git a/src/options/components/ui/radio.pcss b/src/options/components/ui/radio.pcss
deleted file mode 100644
index 2072e0141..000000000
--- a/src/options/components/ui/radio.pcss
+++ /dev/null
@@ -1,44 +0,0 @@
-.radio {
- display: flex;
- align-items: center;
- cursor: pointer;
- margin-bottom: 16px;
- border: none;
- padding: 0;
- background: none;
-
- /* TODO: Delete this hack (needs to to support a11y) */
- &__container {
- width: 24px;
- height: 24px;
- margin: 0;
- padding: 0;
- border: none;
- background: transparent;
- margin: 0 16px 0 0;
-
- .radio__icon {
- margin: 0;
- }
- }
-
- &__icon {
- flex-shrink: 0;
- width: 24px;
- height: 24px;
- margin: 0 16px 0 0;
- color: var(--green700);
- }
-
- &__title {
- font-size: 16px;
- line-height: 19px;
- text-align: left;
- }
-
- &__description {
- font-size: 1.3rem;
- line-height: 1.1;
- color: var(--gray88);
- }
-}
diff --git a/src/options/components/App/useMessageHandler.ts b/src/options/hooks/useMessageHandler.ts
similarity index 90%
rename from src/options/components/App/useMessageHandler.ts
rename to src/options/hooks/useMessageHandler.ts
index 5e5d2c2b4..d0c3b80d0 100644
--- a/src/options/components/App/useMessageHandler.ts
+++ b/src/options/hooks/useMessageHandler.ts
@@ -1,11 +1,11 @@
import { useContext, useEffect, useRef } from 'react';
-import { notifier } from '../../../common/notifier';
-import { messenger } from '../../../common/messenger';
-import { MessageType } from '../../../common/constants';
-import { log } from '../../../common/logger';
-import { rootStore } from '../../stores';
-import { Prefs } from '../../../common/prefs';
+import { notifier } from '../../common/notifier';
+import { messenger } from '../../common/messenger';
+import { MessageType } from '../../common/constants';
+import { log } from '../../common/logger';
+import { Prefs } from '../../common/prefs';
+import { rootStore } from '../stores';
const NOTIFIER_EVENTS = [
notifier.types.AUTHENTICATE_SOCIAL_SUCCESS,
diff --git a/src/options/stores/ExclusionsStore.ts b/src/options/stores/ExclusionsStore.ts
index 58eb1419b..1696e16bb 100644
--- a/src/options/stores/ExclusionsStore.ts
+++ b/src/options/stores/ExclusionsStore.ts
@@ -41,7 +41,7 @@ export enum AddExclusionMode {
Manual = 'Manual',
}
-const DEFAULT_ADD_EXCLUSION_MODE = AddExclusionMode.Manual;
+const DEFAULT_ADD_EXCLUSION_MODE = AddExclusionMode.Service;
const findExclusionById = (
exclusionsTree: ExclusionDtoInterface,
@@ -96,6 +96,8 @@ export class ExclusionsStore {
@observable removeAllModalOpen = false;
+ @observable selectListModalOpen = false;
+
@observable confirmAddModalOpen = false;
@observable urlToConfirm: string | undefined;
@@ -215,7 +217,7 @@ export class ExclusionsStore {
this.setServicesSearchValue('');
this.servicesToToggle = [];
this.unfoldedServiceCategories = [];
- this.setAddExclusionMode(AddExclusionMode.Manual);
+ this.setAddExclusionMode(DEFAULT_ADD_EXCLUSION_MODE);
};
@action setAddExclusionMode = (mode: AddExclusionMode) => {
@@ -238,6 +240,14 @@ export class ExclusionsStore {
this.removeAllModalOpen = false;
};
+ @action openSelectListModal = () => {
+ this.selectListModalOpen = true;
+ };
+
+ @action closeSelectListModal = () => {
+ this.selectListModalOpen = false;
+ };
+
@computed
get preparedServicesData() {
const categories = this.servicesData.reduce((acc: PreparedServiceCategories, serviceData) => {
diff --git a/src/options/stores/SettingsStore.ts b/src/options/stores/SettingsStore.ts
index fdd9695fd..40808aa4a 100644
--- a/src/options/stores/SettingsStore.ts
+++ b/src/options/stores/SettingsStore.ts
@@ -246,15 +246,12 @@ export class SettingsStore {
};
@action editCustomDnsServer = async (
+ dnsServerId: string,
dnsServerName: string,
dnsServerAddress: string,
): Promise
=> {
- if (!this.dnsServerToEdit) {
- return;
- }
-
const editedDnsServers = await messenger.editCustomDnsServer({
- id: this.dnsServerToEdit.id,
+ id: dnsServerId,
title: dnsServerName,
address: dnsServerAddress,
});
@@ -279,6 +276,11 @@ export class SettingsStore {
};
@action setDnsServerToEdit = (value: DnsServerData | null): void => {
+ if (value) {
+ this.dnsServerName = value.title;
+ this.dnsServerAddress = value.address;
+ }
+
this.dnsServerToEdit = value;
};
diff --git a/src/options/stores/UiStore.ts b/src/options/stores/UiStore.ts
index 5d5eb2e2b..c3730c570 100644
--- a/src/options/stores/UiStore.ts
+++ b/src/options/stores/UiStore.ts
@@ -22,14 +22,25 @@ export class UiStore {
this.isSidebarOpen = false;
};
+ @computed get isAnyModalOpen(): boolean {
+ return (
+ this.rootStore.settingsStore.isCustomDnsModalOpen
+ || this.rootStore.exclusionsStore.modeSelectorModalOpen
+ || this.rootStore.exclusionsStore.removeAllModalOpen
+ || this.rootStore.exclusionsStore.selectListModalOpen
+ || this.rootStore.exclusionsStore.addExclusionModalOpen
+ || this.rootStore.exclusionsStore.confirmAddModalOpen
+ || this.rootStore.exclusionsStore.resetServiceModalOpen
+ || this.rootStore.exclusionsStore.addSubdomainModalOpen
+ );
+ }
+
/**
* This computed state indicates when content is overlapped with modal, sidebar, etc.
* It's used as scroll blocker if any modal, sidebar is open. And also in order to
* trap focus inside of that opened element.
- *
- * TODO: Bring modal states to this computed state (AG-38059).
*/
@computed get isContentLocked(): boolean {
- return this.isSidebarOpen;
+ return this.isSidebarOpen || this.isAnyModalOpen;
}
}
diff --git a/src/options/styles/button.pcss b/src/options/styles/button.pcss
deleted file mode 100644
index de1da2548..000000000
--- a/src/options/styles/button.pcss
+++ /dev/null
@@ -1,215 +0,0 @@
-button,
-.button {
- font-family: var(--font-stack);
-}
-
-.button {
- display: inline-block;
- vertical-align: middle;
- padding: 0;
- font-size: 16px;
- line-height: 24px;
- text-align: center;
- text-decoration: none;
- color: var(--brand-primary);
- background-color: transparent;
- border: 1px solid transparent;
- border-radius: 8px;
- cursor: pointer;
-
- &:focus {
- box-shadow: 0 0 2px 1px var(--shadow);
- }
-
- &:disabled {
- opacity: 0.5;
- cursor: default;
- }
-
- &--medium {
- min-width: 140px;
- padding: 12px 30px;
- }
-
- &--large {
- min-width: 256px;
- padding: 15px;
- font-weight: 600;
- }
-
- &--block {
- display: block;
- width: 100%;
- }
-
- &--primary {
- color: #fff;
- background-color: var(--green700);
- transition: var(--t3) background-color;
-
- &:hover,
- &:focus {
- color: #fff;
- background-color: var(--green900);
- }
-
- &:disabled {
- background-color: var(--graya4);
- }
- }
-
- &--outline-green {
- color: var(--green700);
- border-color: var(--green700);
- transition: var(--t3) background-color;
-
- &:hover,
- &:focus {
- border-color: var(--green900);
- color: var(--green900);
- }
- }
-
- &--outline-gray {
- color: var(--gray-base);
- border-color: var(--gray-base);
- transition: var(--t3) background-color;
-
- &:hover,
- &:focus {
- background-color: var(--grayF3);
-
- @media (prefers-color-scheme: dark) {
- background-color: rgba(243, 243, 243, 0.05);
- }
- }
- }
-
- &--outline-red {
- color: var(--redDark);
- border-color: var(--redDark);
- transition: var(--t3) background-color;
-
- &:hover,
- &:focus {
- background-color: rgba(191, 72, 41, 0.05);
- }
- }
-
- &--outline-secondary {
- border-color: var(--gray88);
- color: var(--gray88);
- transition: var(--t3) background-color;
-
- &:hover,
- &:active,
- &:focus {
- background-color: var(--grayF3);
- }
- }
-
- &--icon {
- font-size: 1.2rem;
- border-radius: 3px;
- border: none;
-
- &:hover {
- background-color: transparent;
- }
-
- &:focus {
- box-shadow: none;
- }
- }
-
- &--link {
- text-decoration: none;
- border: 0;
-
- &:hover,
- &:focus {
- text-decoration: underline;
- background-color: transparent;
- box-shadow: none;
- }
- }
-
- &--back {
- display: inline-flex;
- align-items: center;
- color: var(--gray88);
- margin-bottom: 60px;
- }
-
- &--control {
- font-size: 1.3rem;
- text-transform: uppercase;
- font-weight: bold;
- color: var(--gray88);
- line-height: 18px;
- margin-right: 16px;
- transition: var(--t3) color;
-
- &:hover,
- &:focus {
- color: var(--gray-base);
- background-color: transparent;
- box-shadow: none;
- }
-
- &:last-child {
- margin-right: 0;
- }
- }
-
- &--notification {
- color: var(--green700);
- border: none;
- border-radius: 0;
-
- &:focus {
- box-shadow: none;
- }
- }
-}
-
-.dark-mode {
- .button--outline-gray {
- &:hover,
- &:focus {
- background-color: rgba(243, 243, 243, 0.05);
- }
- }
-}
-
-.light-mode {
- .button--outline-gray {
- &:hover,
- &:focus {
- background-color: var(--grayF3);
- }
- }
-}
-
-.simple-button {
- border: none;
- background: none;
- color: var(--green700);
- transition: var(--t3) color;
- cursor: pointer;
-
- &:hover {
- color: var(--green900);
- }
-}
-
-.back-button {
- cursor: pointer;
- border: 0;
- background: transparent;
- padding: 7px 16px 7px 0;
-
- &:hover {
- filter: brightness(.7);
- }
-}
diff --git a/src/options/styles/common.pcss b/src/options/styles/common.pcss
index 40124d982..b37001d9c 100644
--- a/src/options/styles/common.pcss
+++ b/src/options/styles/common.pcss
@@ -23,61 +23,38 @@ body {
font-family: var(--font-stack);
font-size: 1.6rem;
line-height: 1.4;
- color: var(--gray-base);
- background-color: var(--white-strong);
+ color: var(--text-main-text-main-default);
+ background-color: var(--fills-backgrounds-page-background-default);
+ transition: var(--t3) background-color;
&.locked {
overflow-y: hidden;
}
}
-.hidden {
- display: none;
-}
-
-.logo {
- width: 125px;
- height: 24px;
- background-image: url('../../assets/images/logo-vpn.svg');
- background-repeat: no-repeat;
- background-size: contain;
-
- @media (prefers-color-scheme: dark) {
- background-image: url('../../assets/images/logo-vpn-dark.svg');
- }
-}
-
-.light-mode {
- color-scheme: light;
-
- .logo {
- background-image: url('../../assets/images/logo-vpn.svg');
- }
-}
-
-.dark-mode {
- color-scheme: dark;
+a {
+ color: var(--text-links-link-default);
+ text-decoration-line: underline;
+ outline: 2px solid transparent;
- .logo {
- background-image: url('../../assets/images/logo-vpn-dark.svg');
+ &:focus-visible {
+ outline-color: var(--product-primary-50);
}
}
.body_light,
.body_dark {
- background-color: var(--white-strong);
+ background-color: var(--fills-backgrounds-page-background-default);
+}
+
+.hidden {
+ display: none;
}
.has-tab-focus {
outline: 2px solid transparent;
- outline-offset: -2px;
&:focus-visible {
- outline-color: var(--primary-50);
+ outline-color: var(--product-primary-50);
}
}
-
-.general-settings {
- min-height: 680px;
- margin-left: 16px;
-}
\ No newline at end of file
diff --git a/src/options/styles/form.pcss b/src/options/styles/form.pcss
deleted file mode 100644
index 57dc23109..000000000
--- a/src/options/styles/form.pcss
+++ /dev/null
@@ -1,154 +0,0 @@
-.form {
- &--error {
- & .form__input {
- border: 1px solid var(--red);
- }
- }
-
- &__group {
- position: relative;
- width: 100%;
-
- &--settings {
- display: flex;
- padding: 5px 7px 15px 45px;
- }
- }
-
- &__input {
- width: 100%;
- height: 40px;
- padding: 10px 15px;
- font-family: var(--font-stack);
- font-size: 1.4rem;
- border-radius: 6px;
- border: 1px solid var(--gray88);
- outline: none;
- background-color: var(--white);
- color: var(--gray-base);
-
- :-webkit-autofill:focus,
- &:focus:-webkit-autofill {
- background-color: var(--white);
- }
-
- &::placeholder {
- font-size: 1.4rem;
- color: var(--grayD8);
- }
-
- &:focus,
- &:active {
- border-color: var(--gray-base);
- }
-
- &--transparent {
- width: 100%;
- height: initial;
- padding: 0 0 2px;
- font-size: 1.2rem;
- color: var(--gray-base);
- border: 0;
- border-radius: 0;
- background-color: transparent;
-
- &:focus,
- &:active {
- border-color: transparent;
- }
- }
-
- &:-webkit-autofill,
- &:-webkit-autofill:hover,
- &:-webkit-autofill:focus,
- &:-webkit-autofill:active {
- -webkit-box-shadow: 0 0 0 30px var(--white) inset;
- -webkit-text-fill-color: var(--gray-base)!important;
- }
- }
-
- &__submit {
- &--icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border: 0;
- border-radius: 0;
- color: var(--brand-primary);
- }
- }
-
- &__label {
- color: var(--gray88);
- font-size: 12px;
- line-height: 16px;
- display: inline-block;
- margin-bottom: 8px;
- }
-
- &__item {
- position: relative;
- margin-bottom: 20px;
-
- &--error {
- & .form__input {
- border: 1px solid var(--red);
- }
- }
- }
-
- &__item-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
-
- &__error {
- width: 100%;
- margin: -10px 0 -26px;
- font-size: 1.2rem;
- text-align: center;
- color: var(--red);
- }
-
- &__text {
- text-align: center;
-
- &--login {
- margin: 35px 0 40px;
- }
-
- &--register {
- margin-top: 35px;
- }
- }
-
- &__link {
- &--login,
- &--register {
- font-size: 1.6rem;
- vertical-align: baseline;
- }
-
- &--recover {
- font-size: 1.2rem;
- color: var(--pink);
- }
- }
-
- &__actions {
- margin-top: 25px;
- display: flex;
- gap: 16px;
-
- @media (--mobile) {
- flex-direction: column-reverse;
-
- .button {
- width: 100%;
- }
- }
- }
-}
diff --git a/src/options/styles/inputs.pcss b/src/options/styles/inputs.pcss
deleted file mode 100644
index e88bb5475..000000000
--- a/src/options/styles/inputs.pcss
+++ /dev/null
@@ -1,120 +0,0 @@
-.input {
- position: relative;
-
- &__label {
- font-size: 14px;
- line-height: 18px;
- color: var(--gray-5b);
- }
-
- &__in {
- border: 0;
- width: 100%;
- font-family: inherit;
- padding: 16px;
- font-size: 1.6rem;
- line-height: 20px;
- border-radius: 8px;
- outline: none;
- color: var(--gray-base);
- background-color: var(--grayF3);
-
- &:focus,
- &:active {
- box-shadow: inset 0 0 0 1px var(--grayD8);
- }
-
- &:disabled {
- opacity: 0.5;
- }
-
- &::placeholder {
- font-size: 1.6rem;
- color: var(--graya4);
- }
-
- &--content {
- margin-top: 8px;
- }
-
- &--clear {
- padding-right: 50px;
- box-shadow: inset 0 0 0 1px var(--grayd2);
- }
-
- &--subdomain {
- padding-right: 160px;
- }
-
- &--close {
- padding-right: 50px;
-
- &:focus,
- &:active {
- + .input__close {
- opacity: 1;
- }
- }
- }
-
- &--textarea {
- resize: none;
- height: 80px;
- overflow-y: auto;
- }
- }
-
- &__close {
- opacity: 0;
- position: absolute;
- top: calc(50% - 8px);
- right: 16px;
-
- &:focus {
- opacity: 1;
- }
- }
-
- &__clear {
- position: absolute;
- right: 10px;
- top: calc(50% - 13px);
- }
-
- &__error {
- display: none;
- line-height: 18px;
- margin-top: 5px;
- font-size: 14px;
- position: absolute;
- top: 100%;
- left: 0;
- color: var(--redDark);
- }
-
- &--error {
- & .input__in {
- box-shadow: inset 0 0 0 1px var(--redDark);
- }
-
- & .input__error {
- display: block;
- }
- }
-}
-
-.light-mode {
- .input {
- &__in {
- background-color: var(--grayF3);
- }
- }
-}
-
-.dark-mode {
- .input {
- &__in {
- background-color: #131313;
- }
- }
-}
diff --git a/src/options/styles/main.pcss b/src/options/styles/main.pcss
index e63358055..d6a7df48f 100644
--- a/src/options/styles/main.pcss
+++ b/src/options/styles/main.pcss
@@ -1,6 +1,3 @@
@import "vars.pcss";
@import "fonts.pcss";
@import "common.pcss";
-@import "form.pcss";
-@import "button.pcss";
-@import "inputs.pcss";
diff --git a/src/options/styles/vars.pcss b/src/options/styles/vars.pcss
index 27a4a0719..063d32870 100644
--- a/src/options/styles/vars.pcss
+++ b/src/options/styles/vars.pcss
@@ -1,48 +1,22 @@
:root {
- --container-width: 960px;
- --content-width: 732px;
- --border-color: #d8d8d8;
- --shadow: rgba(0, 0, 0, 0.1);
- --overlay: rgba(0, 0, 0, 0.5);
- --brand-primary: #79b07e;
- --brand-secondary: #4a8b5a;
- --brand-danger: #f56f23;
- --gray-base: #4d4d4d;
- --text-main: var(--gray-base);
- --green300: rgba(116, 163, 82, 0.05);
- --green700: #74a352;
- --green900: #568040;
- --gray88: #888888;
- --grayF2: #f2f2f2;
- --grayF3: #f3f3f3;
- --grayF6: #f6f6f6;
- --grayF7: #f7f7f7;
- --grayD8: #d8d8d8;
- --grayd2: #d2d2d2;
- --gray97: #979797;
- --graya4: #a4a4a4;
- --red: #df3812;
- --redDark: #bf4829;
- --pink: #ff7b7b;
- --white: #ffffff;
- --white-strong: #fff;
- --white-fog: rgba(255, 255, 255, 0.7);
- --orange: #d58500;
-
/* Common Variables */
--font-stack: "Roboto Flex", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Arial, sans-serif;
--t3: 0.3s ease;
/* Shadows */
--small-light-shadow-br: 4px 4px 8px 0px rgba(0, 0, 0, 0.10);
+ --big-light-shadow-br: 8px 8px 24px 0px rgba(0, 0, 0, 0.10);
/* Sizes */
--sidebar-width: 240px;
/* Z-Indices */
- --notifications-z: 3;
- --sidebar-z: 4;
- --modal-z: 5;
+ --select-z: 1;
+ --notifications-z: 2;
+ --sidebar-z: 3;
+ --modal-z: 4;
+ --signed-out-z: 5;
+ --preloader-z: 6;
/* Base Colors */
--gray-0: #FFFFFF;
@@ -56,114 +30,168 @@
--gray-80: #3D3D3D;
--gray-90: #1F1F1F;
--gray-100: #000000;
+ --product-primary-30: #B2CC9E;
+ --product-primary-50: #74A352;
+ --product-primary-60: #68924A;
+ --product-primary-70: #5C8141;
+ --orange-50: #D58500;
+ --red-30: #D9907D;
--red-50: #BF4829;
- --primary-50: #67B279;
+ --red-60: #AF4226;
+ --red-70: #9E3C22;
/* Text */
--text-main-text-main-default: var(--gray-80);
+ --text-main-text-main-disabled: var(--gray-50);
--text-description-description-default: var(--gray-70);
+ --text-buttons-primary-default: var(--gray-0);
+ --text-buttons-secondary-red-default: var(--red-50);
+ --text-buttons-secondary-red-disabled: var(--red-30);
+ --text-links-link-default: var(--product-primary-50);
+ --text-links-link-hovered: var(--product-primary-60);
+ --text-links-link-pressed: var(--product-primary-70);
+ --text-links-link-disabled: var(--product-primary-30);
+ --text-links-error-link-default: var(--red-50);
+ --text-links-error-link-disabled: var(--red-30);
+ --text-links-attention-link-default: var(--orange-50);
+ --text-labels-labels-default: var(--gray-70);
+ --text-placeholder-placeholder-default: var(--gray-50);
/* Fills */
--fills-backgrounds-page-background-default: var(--gray-0);
+ --fills-backgrounds-page-background-hovered: var(--gray-10);
+ --fills-backgrounds-page-background-pressed: var(--gray-20);
--fills-backgrounds-page-background-additional-default: var(--gray-10);
--fills-backgrounds-page-background-additional-hovered: var(--gray-20);
+ --fills-backgrounds-page-background-additional-pressed: var(--gray-30);
--fills-backgrounds-dialog-background-default: var(--gray-100);
+ --fills-menus-dropdown-menu-background-default: var(--gray-0);
+ --fills-menus-dropdown-menu-background-hovered: var(--gray-10);
+ --fills-menus-dropdown-menu-background-pressed: var(--gray-20);
+ --fills-switch-all-on-default: var(--product-primary-50);
+ --fills-switch-all-on-hovered: var(--product-primary-60);
+ --fills-switch-all-off-default: var(--gray-40);
+ --fills-switch-all-off-hovered: var(--gray-50);
+ --fills-switch-all-knob-default: var(--gray-0);
+ --fills-buttons-main-button-default: var(--product-primary-50);
+ --fills-buttons-main-button-hovered: var(--product-primary-60);
+ --fills-buttons-main-button-pressed: var(--product-primary-70);
+ --fills-buttons-main-button-disabled: var(--product-primary-30);
+ --fills-buttons-danger-button-default: var(--red-50);
+ --fills-buttons-danger-button-hovered: var(--red-60);
+ --fills-buttons-danger-button-pressed: var(--red-70);
+ --fills-buttons-danger-button-disabled: var(--red-30);
+ --fills-buttons-secondary-hovered: var(--gray-10);
+ --fills-buttons-secondary-pressed: var(--gray-20);
+ --fills-inputs-input-background-default: var(--gray-10);
+ --fills-snacks-desktop-snacks-default: var(--gray-10);
/* Stroke */
--stroke-dividers-item-divider-default: var(--gray-30);
--stroke-icons-gray-icons-default: var(--gray-60);
+ --stroke-icons-gray-icons-disabled: var(--gray-30);
+ --stroke-icons-product-icon-default: var(--product-primary-50);
+ --stroke-icons-product-icon-disabled: var(--product-primary-30);
+ --stroke-icons-attention-icon-default: var(--orange-50);
--stroke-icons-error-icon-default: var(--red-50);
+ --stroke-buttons-secondary-default: var(--gray-80);
+ --stroke-buttons-secondary-disabled: var(--gray-50);
+ --stroke-buttons-secondary-red-default: var(--red-50);
+ --stroke-buttons-secondary-red-disabled: var(--red-30);
+ --stroke-inputs-inactive-input-stroke-default: var(--gray-30);
+ --stroke-inputs-active-input-stroke-default: var(--gray-50);
+ --stroke-inputs-error-input-default: var(--red-50);
@media (prefers-color-scheme: dark) {
- --white: #131313;
- --white-strong: #000;
- --white-fog: rgba(0, 0, 0, 0.7);
- --gray-base: #d8d8d8;
- --text-main: #e4e4e4;
- --grayF2: #222222;
- --grayF3: #131313;
- --grayF6: #3d3d3d;
- --border-color: #4d4d4d;
- --brand-primary: #74A352;
- --green700: #568040;
- --green900: #3e5c2e;
- --grayD8: #4d4d4d;
- --grayd2: #5b5b5b;
- --overlay: rgba(51, 51, 51, 0.5);
-
/* Text */
--text-main-text-main-default: var(--gray-20);
--text-description-description-default: var(--gray-30);
+ --text-labels-labels-default: var(--gray-30);
/* Fills */
--fills-backgrounds-page-background-default: var(--gray-90);
+ --fills-backgrounds-page-background-hovered: var(--gray-80);
+ --fills-backgrounds-page-background-pressed: var(--gray-70);
--fills-backgrounds-page-background-additional-default: var(--gray-80);
--fills-backgrounds-page-background-additional-hovered: var(--gray-70);
+ --fills-backgrounds-page-background-additional-pressed: var(--gray-60);
+ --fills-menus-dropdown-menu-background-default: var(--gray-80);
+ --fills-menus-dropdown-menu-background-hovered: var(--gray-70);
+ --fills-menus-dropdown-menu-background-pressed: var(--gray-60);
+ --fills-switch-all-off-default: var(--gray-60);
+ --fills-switch-all-off-hovered: var(--gray-70);
+ --fills-buttons-secondary-hovered: var(--gray-80);
+ --fills-buttons-secondary-pressed: var(--gray-70);
+ --fills-inputs-input-background-default: var(--gray-80);
+ --fills-snacks-desktop-snacks-default: var(--gray-90);
/* Stroke */
--stroke-dividers-item-divider-default: var(--gray-70);
--stroke-icons-gray-icons-default: var(--gray-40);
+ --stroke-icons-gray-icons-disabled: var(--gray-70);
+ --stroke-buttons-secondary-default: var(--gray-20);
+ --stroke-inputs-inactive-input-stroke-default: var(--gray-70);
}
}
.dark-mode {
- --white: #131313;
- --white-strong: #000;
- --white-fog: rgba(0, 0, 0, 0.7);
- --gray-base: #d8d8d8;
- --text-main: #e4e4e4;
- --grayF2: #222222;
- --grayF3: #131313;
- --grayF6: #3d3d3d;
- --border-color: #4d4d4d;
- --brand-primary: #74A352;
- --green700: #568040;
- --green900: #3e5c2e;
- --grayD8: #4d4d4d;
- --grayd2: #5b5b5b;
- --overlay: rgba(51, 51, 51, 0.5);
-
/* Text */
--text-main-text-main-default: var(--gray-20);
--text-description-description-default: var(--gray-30);
+ --text-labels-labels-default: var(--gray-30);
/* Fills */
--fills-backgrounds-page-background-default: var(--gray-90);
+ --fills-backgrounds-page-background-hovered: var(--gray-80);
+ --fills-backgrounds-page-background-pressed: var(--gray-70);
--fills-backgrounds-page-background-additional-default: var(--gray-80);
--fills-backgrounds-page-background-additional-hovered: var(--gray-70);
+ --fills-backgrounds-page-background-additional-pressed: var(--gray-60);
+ --fills-menus-dropdown-menu-background-default: var(--gray-80);
+ --fills-menus-dropdown-menu-background-hovered: var(--gray-70);
+ --fills-menus-dropdown-menu-background-pressed: var(--gray-60);
+ --fills-switch-all-off-default: var(--gray-60);
+ --fills-switch-all-off-hovered: var(--gray-70);
+ --fills-buttons-secondary-hovered: var(--gray-80);
+ --fills-buttons-secondary-pressed: var(--gray-70);
+ --fills-inputs-input-background-default: var(--gray-80);
+ --fills-snacks-desktop-snacks-default: var(--gray-90);
/* Stroke */
--stroke-dividers-item-divider-default: var(--gray-70);
--stroke-icons-gray-icons-default: var(--gray-40);
+ --stroke-icons-gray-icons-disabled: var(--gray-70);
+ --stroke-buttons-secondary-default: var(--gray-20);
+ --stroke-inputs-inactive-input-stroke-default: var(--gray-70);
}
.light-mode {
- --white: #ffffff;
- --white-strong: #fff;
- --white-fog: rgba(255, 255, 255, 0.7);
- --gray-base: #4d4d4d;
- --text-main: var(--gray-base);
- --grayF2: #f2f2f2;
- --grayF3: #f3f3f3;
- --grayF6: #f6f6f6;
- --border-color: #d8d8d8;
- --brand-primary: #79b07e;
- --grayD8: #d8d8d8;
- --grayd2: #d2d2d2;
- --overlay: rgba(0, 0, 0, 0.5);
- --green700: #74A352;
- --green900: #568040;
-
/* Text */
--text-main-text-main-default: var(--gray-80);
--text-description-description-default: var(--gray-70);
+ --text-labels-labels-default: var(--gray-70);
/* Fills */
--fills-backgrounds-page-background-default: var(--gray-0);
+ --fills-backgrounds-page-background-hovered: var(--gray-10);
+ --fills-backgrounds-page-background-pressed: var(--gray-20);
--fills-backgrounds-page-background-additional-default: var(--gray-10);
--fills-backgrounds-page-background-additional-hovered: var(--gray-20);
+ --fills-backgrounds-page-background-additional-pressed: var(--gray-30);
+ --fills-menus-dropdown-menu-background-default: var(--gray-0);
+ --fills-menus-dropdown-menu-background-hovered: var(--gray-10);
+ --fills-menus-dropdown-menu-background-pressed: var(--gray-20);
+ --fills-switch-all-off-default: var(--gray-40);
+ --fills-switch-all-off-hovered: var(--gray-50);
+ --fills-buttons-secondary-hovered: var(--gray-10);
+ --fills-buttons-secondary-pressed: var(--gray-20);
+ --fills-inputs-input-background-default: var(--gray-10);
+ --fills-snacks-desktop-snacks-default: var(--gray-10);
/* Stroke */
--stroke-dividers-item-divider-default: var(--gray-30);
--stroke-icons-gray-icons-default: var(--gray-60);
+ --stroke-icons-gray-icons-disabled: var(--gray-30);
+ --stroke-buttons-secondary-default: var(--gray-80);
+ --stroke-inputs-inactive-input-stroke-default: var(--gray-30);
}
diff --git a/src/popup/components/ui/Icons.tsx b/src/popup/components/ui/Icons.tsx
index bf850b109..25d80571b 100644
--- a/src/popup/components/ui/Icons.tsx
+++ b/src/popup/components/ui/Icons.tsx
@@ -126,6 +126,7 @@ export const Icons = () => (
+ {/* TODO: there is star icon in popup, maybe worth refactoring */}
diff --git a/tasks/appConfig.ts b/tasks/appConfig.ts
index 58e29b32e..965fc446c 100644
--- a/tasks/appConfig.ts
+++ b/tasks/appConfig.ts
@@ -105,6 +105,8 @@ const COMMON_URL_QUERIES = {
UNINSTALL_PAGE: 'action=adguard_uninstal_ext&from=background_page&app=vpn_extension',
// AdGuard DNS Knowledge Base
ADGUARD_DNS_KB: 'action=adguard_dns_kb&from=options_screen&app=vpn_extension',
+ // AdGuard DNS Providers Knowledge Base
+ ADGUARD_DNS_PROVIDERS_KB: 'action=adguard_dns_providers_kb&from=options_screen&app=vpn_extension',
COMPARE_PAGE: 'action=compare&from=popup&app=vpn_extension',
// AG-25941
VPN_BLOCKED_GET_APP: 'action=vpn_blocked_get_app&from=popup&app=vpn_extension',