diff --git a/.gitignore b/.gitignore index 5cfd4889..db6d4a1d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ debug /dnsfilter.txt /querylog.json /querylog.json.1 +/scripts/translations/node_modules +/scripts/translations/oneskyapp.json # Test output dnsfilter/dnsfilter.TestLotsOfRules*.pprof diff --git a/README.md b/README.md index ddc0a918..6accbe81 100644 --- a/README.md +++ b/README.md @@ -141,10 +141,44 @@ cd AdGuardHome make ``` +### How to update translations + +Before updating translations you need to install dependencies: +``` +cd scripts/translations +npm install +``` + +Create file `oneskyapp.json` in `scripts/translations` folder. + +Example of `oneskyapp.json` +``` +{ + "url": "https://platform.api.onesky.io/1/projects/", + "projectId": , + "apiKey": , + "secretKey": +} +``` + +#### Upload translations +``` +node upload.js +``` + +#### Download translations +``` +node download.js +``` + ## Contributing You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls +If you want to help with AdGuard Home translations, please learn more about translating AdGuard products here: https://kb.adguard.com/en/general/adguard-translations + +Here is a direct link to AdGuard Home project: http://translate.adguard.com/collaboration/project?id=153384 + ## Reporting issues If you run into any problem or have a suggestion, head to [this page](https://github.com/AdguardTeam/AdGuardHome/issues) and click on the `New issue` button. diff --git a/client/package-lock.json b/client/package-lock.json index a4170f6e..e7080ade 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -94,6 +94,21 @@ "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", "dev": true }, + "@babel/runtime": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.5.tgz", + "integrity": "sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA==", + "requires": { + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, "@babel/template": { "version": "7.0.0-beta.44", "resolved": "http://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz", @@ -3111,6 +3126,15 @@ "sha.js": "^2.4.8" } }, + "create-react-context": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.3.tgz", + "integrity": "sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==", + "requires": { + "fbjs": "^0.8.0", + "gud": "^1.0.0" + } + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -6230,6 +6254,11 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "handle-thing": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", @@ -6543,6 +6572,14 @@ "uglify-js": "3.4.x" } }, + "html-parse-stringify2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz", + "integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=", + "requires": { + "void-elements": "^2.0.1" + } + }, "html-tags": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", @@ -6700,6 +6737,16 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "i18next": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-12.0.0.tgz", + "integrity": "sha512-Zy/nFpmBZxgmi6k9HkHbf+MwvAwiY5BDzNjNfvyLPKyalc2YBwwZtblESDlTKLDO8XSv23qYRY2uZcADDlRSjQ==" + }, + "i18next-browser-languagedetector": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.3.tgz", + "integrity": "sha512-sJZ2n9Vgax0vGer23hJMwyO3FRO7P0dq2DXZPXWE329g3snfJUcw+S24Mp3lqJaxL/0McDu4BD75ds6pzIfhhw==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -12850,6 +12897,32 @@ "prop-types": "^15.6.0" } }, + "react-i18next": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-8.3.8.tgz", + "integrity": "sha512-ZcSpakSBcDxPJkl34fv/SI0TaoTDvVDrk4WpDF+WElorine+dHUjGMAA6RG5Km2KcLNW1t4GLunHprgKiqDrSw==", + "requires": { + "@babel/runtime": "^7.1.2", + "create-react-context": "0.2.3", + "hoist-non-react-statics": "3.0.1", + "html-parse-stringify2": "2.0.1" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz", + "integrity": "sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==", + "requires": { + "react-is": "^16.3.2" + } + } + } + }, + "react-is": { + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", + "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==" + }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -15599,6 +15672,11 @@ "indexof": "0.0.1" } }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", diff --git a/client/package.json b/client/package.json index 9e3a16e8..7d200e80 100644 --- a/client/package.json +++ b/client/package.json @@ -14,12 +14,15 @@ "classnames": "^2.2.6", "date-fns": "^1.29.0", "file-saver": "^1.3.8", + "i18next": "^12.0.0", + "i18next-browser-languagedetector": "^2.2.3", "lodash": "^4.17.10", "nanoid": "^1.2.3", "prop-types": "^15.6.1", "react": "^16.4.0", "react-click-outside": "^3.0.1", "react-dom": "^16.4.0", + "react-i18next": "^8.2.0", "react-modal": "^3.4.5", "react-redux": "^5.0.7", "react-redux-loading-bar": "^4.0.7", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json new file mode 100644 index 00000000..7b6fba15 --- /dev/null +++ b/client/src/__locales/en.json @@ -0,0 +1,124 @@ +{ + "back": "Back", + "dashboard": "Dashboard", + "settings": "Settings", + "filters": "Filters", + "query_log": "Query Log", + "faq": "FAQ", + "version": "version", + "address": "address", + "on": "ON", + "off": "OFF", + "copyright": "Copyright", + "homepage": "Homepage", + "report_an_issue": "Report an issue", + "enable_protection": "Enable protection", + "enabled_protection": "Enabled protection", + "disable_protection": "Disable protection", + "disabled_protection": "Disabled protection", + "refresh_statics": "Refresh statistics", + "dns_query": "DNS Queries", + "blocked_by": "Blocked by", + "stats_malware_phishing": "Blocked malware\/phishing", + "stats_adult": "Blocked adult websites", + "stats_query_domain": "Top queried domains", + "for_last_24_hours": "for the last 24 hours", + "no_domains_found": "No domains found", + "requests_count": "Requests count", + "top_blocked_domains": "Top blocked domains", + "top_clients": "Top clients", + "no_clients_found": "No clients found", + "general_statistics": "General statistics", + "number_of_dns_query_24_hours": "A number of DNS quieries processed for the last 24 hours", + "number_of_dns_query_blocked_24_hours": "A number of DNS requests blocked by adblock filters and hosts blocklists", + "number_of_dns_query_blocked_24_hours_by_sec": "A number of DNS requests blocked by the AdGuard browsing security module", + "number_of_dns_query_blocked_24_hours_adult": "A number of adult websites blocked", + "enforced_save_search": "Enforced safe search", + "number_of_dns_query_to_safe_search": "A number of DNS requests to search engines for which Safe Search was enforced", + "average_processing_time": "Average processing time", + "average_processing_time_hint": "Average time in milliseconds on processing a DNS request", + "block_domain_use_filters_and_hosts": "Block domains using filters and hosts files", + "filters_block_toggle_hint": "You can setup blocking rules in the Filters<\/a> settings.", + "use_adguard_browsing_sec": "Use AdGuard browsing security web service", + "use_adguard_browsing_sec_hint": "AdGuard Home will check if domain is blacklisted by the browsing security web service. It will use privacy-friendly lookup API to perform the check: only a short prefix of the domain name SHA256 hash is sent to the server.", + "use_adguard_parental": "Use AdGuard parental control web service", + "use_adguard_parental_hint": "AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.", + "enforce_safe_search": "Enforce safe search", + "enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, and Yandex.", + "no_servers_specified": "No servers specified", + "no_settings": "No settings", + "general_settings": "General settings", + "upstream_dns": "Upstream DNS servers", + "upstream_dns_hint": "If you keep this field empty, AdGuard Home will use Cloudflare DNS<\/a> as an upstream. Use tls:\/\/ prefix for DNS over TLS servers.", + "test_upstream_btn": "Test upstreams", + "apply_btn": "Apply", + "disabled_filtering_toast": "Disabled filtering", + "enabled_filtering_toast": "Enabled filtering", + "disabled_safe_browsing_toast": "Disabled safebrowsing", + "enabled_safe_browsing_toast": "Enabled safebrowsing", + "disabled_parental_toast": "Disabled parental control", + "enabled_parental_toast": "Enabled parental control", + "disabled_safe_search_toast": "Disabled safe search", + "enabled_save_search_toast": "Enabled safe search", + "enabled_table_header": "Enabled", + "name_table_header": "Name", + "filter_url_table_header": "Filter URL", + "rules_count_table_header": "Rules count", + "last_time_updated_table_header": "Last time updated", + "actions_table_header": "Actions", + "delete_table_action": "Delete", + "filters_and_hosts": "Filters and hosts blocklists", + "filters_and_hosts_hint": "AdGuard Home understands basic adblock rules and hosts files syntax.", + "no_filters_added": "No filters added", + "add_filter_btn": "Add filter", + "cancel_btn": "Cancel", + "enter_name_hint": "Enter name", + "enter_url_hint": "Enter URL", + "check_updates_btn": "Check updates", + "new_filter_btn": "New filter subscription", + "enter_valid_filter_url": "Enter a valid URL to a filter subscription or a hosts file.", + "custom_filter_rules": "Custom filtering rules", + "custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.", + "examples_title": "Examples", + "example_meaning_filter_block": "block access to the example.org domain and all its subdomains", + "example_meaning_filter_whitelist": "unblock access to the example.org domain and all its subdomains", + "example_meaning_host_block": "AdGuard Home will now return 127.0.0.1 address for the example.org domain (but not its subdomains).", + "example_comment": "! Here goes a comment", + "example_comment_meaning": "just a comment", + "example_comment_hash": "# Also a comment", + "all_filters_up_to_date_toast": "All filters are already up-to-date", + "updated_upstream_dns_toast": "Updated the upstream DNS servers", + "dns_test_ok_toast": "Specified DNS servers are working correctly", + "dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly", + "unblock_btn": "Unblock", + "block_btn": "Block", + "time_table_header": "Time", + "domain_name_table_header": "Domain name", + "type_table_header": "Type", + "response_table_header": "Response", + "empty_response_status": "Empty", + "show_all_filter_type": "Show all", + "show_filtered_type": "Show filtered", + "no_logs_found": "No logs found", + "disabled_log_btn": "Disable log", + "download_log_file_btn": "Download log file", + "refresh_btn": "Refresh", + "enabled_log_btn": "Enable log", + "last_dns_queries": "Last 5000 DNS queries", + "previous_btn": "Previous", + "next_btn": "Next", + "loading_table_status": "Loading...", + "page_table_footer_text": "Page", + "of_table_footer_text": "of", + "rows_table_footer_text": "rows", + "updated_custom_filtering_toast": "Updated the custom filtering rules", + "rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules", + "rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules", + "query_log_disabled_toast": "Query log disabled", + "query_log_enabled_toast": "Query log enabled", + "source_label": "Source", + "found_in_known_domain_db": "Found in the known domains database.", + "category_label": "Category", + "rule_label": "Rule", + "filter_label": "Filter" +} \ No newline at end of file diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json new file mode 100644 index 00000000..e0f19e08 --- /dev/null +++ b/client/src/__locales/ru.json @@ -0,0 +1,124 @@ +{ + "back": "\u041d\u0430\u0437\u0430\u0434", + "dashboard": "\u041f\u0430\u043d\u0435\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f", + "settings": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", + "filters": "\u0424\u0438\u043b\u044c\u0442\u0440\u044b", + "query_log": "\u0416\u0443\u0440\u043d\u0430\u043b", + "faq": "FAQ", + "version": "\u0432\u0435\u0440\u0441\u0438\u044f", + "address": "\u0430\u0434\u0440\u0435\u0441", + "on": "\u0412\u043a\u043b", + "off": "\u0412\u044b\u043a\u043b", + "copyright": "\u0412\u0441\u0435 \u043f\u0440\u0430\u0432\u0430 \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u044b", + "homepage": "\u0413\u043b\u0430\u0432\u043d\u0430\u044f", + "report_an_issue": "\u0421\u043e\u043e\u0431\u0449\u0438\u0442\u044c \u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0435", + "enable_protection": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0443", + "enabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430 \u0432\u043a\u043b.", + "disable_protection": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0443", + "disabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430 \u0432\u044b\u043a\u043b.", + "refresh_statics": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443", + "dns_query": "DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u044b", + "blocked_by": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e ", + "stats_malware_phishing": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0435 \u0438 \u0444\u0438\u0448\u0438\u043d\u0433\u043e\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b", + "stats_adult": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \"\u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0435\" \u0441\u0430\u0439\u0442\u044b", + "stats_query_domain": "\u0427\u0430\u0441\u0442\u043e \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u0434\u043e\u043c\u0435\u043d\u044b", + "for_last_24_hours": "\u0437\u0430 24 \u0447\u0430\u0441\u0430", + "no_domains_found": "\u0414\u043e\u043c\u0435\u043d\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b", + "requests_count": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432", + "top_blocked_domains": "\u0427\u0430\u0441\u0442\u043e \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c\u044b\u0435 \u0434\u043e\u043c\u0435\u043d\u044b", + "top_clients": "\u0427\u0430\u0441\u0442\u044b\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u044b", + "no_clients_found": "\u041a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e", + "general_statistics": "\u041e\u0431\u0449\u0430\u044f \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430", + "number_of_dns_query_24_hours": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0437\u0430 24 \u0447\u0430\u0441\u0430", + "number_of_dns_query_blocked_24_hours": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u043c\u0438 \u0438 \u0431\u043b\u043e\u043a-\u0441\u043f\u0438\u0441\u043a\u0430\u043c\u0438", + "number_of_dns_query_blocked_24_hours_by_sec": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u043c \u0410\u043d\u0442\u0438\u0444\u0438\u0448\u0438\u043d\u0433\u0430 AdGuard", + "number_of_dns_query_blocked_24_hours_adult": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \"\u0441\u0430\u0439\u0442\u043e\u0432 \u0434\u043b\u044f \u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0445\"", + "enforced_save_search": "\u0423\u0441\u0438\u043b\u0435\u043d\u043d\u044b\u0439 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a", + "number_of_dns_query_to_safe_search": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 DNS \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u044b\u043b \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a", + "average_processing_time": "\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430", + "average_processing_time_hint": "\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 DNS \u0432 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445", + "block_domain_use_filters_and_hosts": "\u0411\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0445\u043e\u0441\u0442\u043e\u0432", + "filters_block_toggle_hint": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438 \u0432 \"\u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0445\"<\/a>.", + "use_adguard_browsing_sec": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0443\u044e \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e AdGuard", + "use_adguard_browsing_sec_hint": "AdGuard Home \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442, \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043b\u0438 \u0434\u043e\u043c\u0435\u043d \u0432 \u0432\u0435\u0431-\u0441\u043b\u0443\u0436\u0431\u0443 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c API, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443: \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u0438\u043c\u0435\u043d\u0438 \u0434\u043e\u043c\u0435\u043d\u0430 SHA256.", + "use_adguard_parental": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043c\u043e\u0434\u0443\u043b\u044c \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f AdGuard ", + "use_adguard_parental_hint": "AdGuard Home \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u0438 \u0434\u043e\u043c\u0435\u043d \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b 18+. \u041e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 API \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0434\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438, \u0447\u0442\u043e \u0438 \u0432\u0435\u0431-\u0441\u043b\u0443\u0436\u0431\u0430 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430.", + "enforce_safe_search": "\u0423\u0441\u0438\u043b\u0438\u0442\u044c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a", + "enforce_save_search_hint": "AdGuard Home \u043c\u043e\u0436\u0435\u0442 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u0445: Google, Youtube, Bing \u0438 Yandex.", + "no_servers_specified": "\u041d\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432", + "no_settings": "\u041d\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a", + "general_settings": "\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", + "upstream_dns": "Upstream DNS-\u0441\u0435\u0440\u0432\u0435\u0440\u044b", + "upstream_dns_hint": "\u0415\u0441\u043b\u0438 \u0432\u044b \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u0435 \u044d\u0442\u043e \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0442\u043e AdGuard Home \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 Cloudflare DNS<\/a> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 upstream. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 tls:\/\/ \u0434\u043b\u044f DNS \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u044b TLS.", + "test_upstream_btn": "\u0422\u0435\u0441\u0442 upstream \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432", + "apply_btn": "\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c", + "disabled_filtering_toast": "\u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u0432\u044b\u043a\u043b.", + "enabled_filtering_toast": "\u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u0432\u043a\u043b.", + "disabled_safe_browsing_toast": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0432\u044b\u043a\u043b.", + "enabled_safe_browsing_toast": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0432\u043a\u043b.", + "disabled_parental_toast": "\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0432\u044b\u043a\u043b.", + "enabled_parental_toast": "\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0432\u043a\u043b.", + "disabled_safe_search_toast": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432\u044b\u043a\u043b.", + "enabled_save_search_toast": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432\u043a\u043b.", + "enabled_table_header": "\u0412\u043a\u043b.", + "name_table_header": "\u0418\u043c\u044f", + "filter_url_table_header": "URL \u0444\u0438\u043b\u044c\u0442\u0440\u0430", + "rules_count_table_header": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0440\u0430\u0432\u0438\u043b:", + "last_time_updated_table_header": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435", + "actions_table_header": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f", + "delete_table_action": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c", + "filters_and_hosts": "\u0424\u0438\u043b\u044c\u0442\u0440\u044b \u0438 \u0447\u0435\u0440\u043d\u044b\u0435 \u0441\u043f\u0438\u0441\u043a\u0438 hosts", + "filters_and_hosts_hint": "AdGuard Home \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0435\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438 \u0438 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u0444\u0430\u0439\u043b\u043e\u0432 hosts.", + "no_filters_added": "\u0424\u0438\u043b\u044c\u0442\u0440\u044b \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b", + "add_filter_btn": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440", + "cancel_btn": "\u041e\u0442\u043c\u0435\u043d\u0430", + "enter_name_hint": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f", + "enter_url_hint": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 URL", + "check_updates_btn": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f", + "new_filter_btn": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430", + "enter_valid_filter_url": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 URL \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u0444\u0438\u043b\u044c\u0442\u0440 \u0438\u043b\u0438 \u0444\u0430\u0439\u043b hosts.", + "custom_filter_rules": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043e \u0438\u0437 \u0430\u0432\u0442\u043e\u0440\u0441\u043a\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u0430\u0432\u0438\u043b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438", + "custom_filter_rules_hint": "\u0412\u0432\u043e\u0434\u0438\u0442\u0435 \u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443 \u043f\u0440\u0430\u0432\u0438\u043b\u0443 \u043d\u0430 \u0441\u0442\u0440\u043e\u0447\u043a\u0443. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438 \u0438\u043b\u0438 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u0444\u0430\u0439\u043b\u043e\u0432 hosts.", + "examples_title": "\u041f\u0440\u0438\u043c\u0435\u0440\u044b", + "example_meaning_filter_block": "\u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0434\u043e\u043c\u0435\u043d\u0443 example.org \u0438 \u0432\u0441\u0435\u043c \u0435\u0433\u043e \u043f\u043e\u0434\u0434\u043e\u043c\u0435\u043d\u0430\u043c", + "example_meaning_filter_whitelist": "\u0440\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0434\u043e\u043c\u0435\u043d\u0443 example.org \u0438 \u0432\u0441\u0435\u043c \u0435\u0433\u043e \u043f\u043e\u0434\u0434\u043e\u043c\u0435\u043d\u0430\u043c", + "example_meaning_host_block": "\u0422\u0435\u043f\u0435\u0440\u044c AdGuard Home \u0432\u0435\u0440\u043d\u0435\u0442 127.0.0.1 \u0434\u043b\u044f \u0434\u043e\u043c\u0435\u043d\u0430 example.org (\u043d\u043e \u043d\u0435 \u0434\u043b\u044f \u0435\u0433\u043e \u043f\u043e\u0434\u0434\u043e\u043c\u0435\u043d\u043e\u0432).", + "example_comment": "! \u0422\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435", + "example_comment_meaning": "\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439", + "example_comment_hash": "# \u0418 \u0432\u043e\u0442 \u0442\u0430\u043a \u0442\u043e\u0436\u0435", + "all_filters_up_to_date_toast": "\u0412\u0441\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b", + "updated_upstream_dns_toast": "Upstream DNS-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b", + "dns_test_ok_toast": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u044b DNS \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e", + "dns_test_not_ok_toast": "\u0421\u0435\u0440\u0432\u0435\u0440 \"{{key}}\": \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f", + "unblock_btn": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c", + "block_btn": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c", + "time_table_header": "\u0412\u0440\u0435\u043c\u044f", + "domain_name_table_header": "\u0414\u043e\u043c\u0435\u043d", + "type_table_header": "\u0422\u0438\u043f", + "response_table_header": "\u041e\u0442\u0432\u0435\u0442", + "empty_response_status": "\u041f\u0443\u0441\u0442\u043e", + "show_all_filter_type": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0441\u0435", + "show_filtered_type": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043e\u0442\u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435", + "no_logs_found": "\u041b\u043e\u0433\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b", + "disabled_log_btn": "\u0416\u0443\u0440\u043d\u0430\u043b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u044b\u043a\u043b.", + "download_log_file_btn": "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043e\u0442\u0447\u0451\u0442", + "refresh_btn": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c", + "enabled_log_btn": "\u0416\u0443\u0440\u043d\u0430\u043b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u043a\u043b.", + "last_dns_queries": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 5000 DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432", + "previous_btn": "\u041d\u0430\u0437\u0430\u0434", + "next_btn": "\u0412\u043f\u0435\u0440\u0451\u0434", + "loading_table_status": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430...", + "page_table_footer_text": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430", + "of_table_footer_text": "\u0438\u0437", + "rows_table_footer_text": "\u0441\u0442\u0440\u043e\u043a", + "updated_custom_filtering_toast": "\u0412\u043d\u0435\u0441\u0435\u043d\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0430", + "rule_removed_from_custom_filtering_toast": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043e \u0438\u0437 \u0430\u0432\u0442\u043e\u0440\u0441\u043a\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u0430\u0432\u0438\u043b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438", + "rule_added_to_custom_filtering_toast": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e", + "query_log_disabled_toast": "\u0416\u0443\u0440\u043d\u0430\u043b \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u044b\u043a\u043b.", + "query_log_enabled_toast": "\u0416\u0443\u0440\u043d\u0430\u043b \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u043a\u043b.", + "source_label": "\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a", + "found_in_known_domain_db": "\u041d\u0430\u0439\u0434\u0435\u043d \u0432 \u0431\u0430\u0437\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432.", + "category_label": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f", + "rule_label": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e", + "filter_label": "\u0424\u0438\u043b\u044c\u0442\u0440" +} \ No newline at end of file diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json new file mode 100644 index 00000000..86f7a65b --- /dev/null +++ b/client/src/__locales/vi.json @@ -0,0 +1,124 @@ +{ + "back": "Quay l\u1ea1i", + "dashboard": "T\u1ed5ng quan", + "settings": "C\u00e0i \u0111\u1eb7t", + "filters": "B\u1ed9 l\u1ecdc", + "query_log": "L\u1ecbch s\u1eed truy v\u1ea5n", + "faq": "H\u1ecfi \u0111\u00e1p", + "version": "phi\u00ean b\u1ea3n", + "address": "\u0111\u1ecba ch\u1ec9", + "on": "\u0110ang b\u1eadt", + "off": "\u0110ang t\u1eaft", + "copyright": "B\u1ea3n quy\u1ec1n", + "homepage": "Trang ch\u1ee7", + "report_an_issue": "B\u00e1o l\u1ed7i", + "enable_protection": "B\u1eadt b\u1ea3o v\u1ec7", + "enabled_protection": "\u0110\u00e3 b\u1eadt b\u1ea3o v\u1ec7", + "disable_protection": "T\u1eaft b\u1ea3o v\u1ec7", + "disabled_protection": "\u0110\u00e3 t\u1eaft b\u1ea3o v\u1ec7", + "refresh_statics": "L\u00e0m m\u1edbi th\u1ed1ng k\u00ea", + "dns_query": "Truy v\u1ea5n DNS", + "blocked_by": "Ch\u1eb7n b\u1edfi", + "stats_malware_phishing": "M\u00e3 \u0111\u1ed9c\/l\u1eeba \u0111\u1ea3o \u0111\u00e3 ch\u1eb7n", + "stats_adult": "Website ng\u01b0\u1eddi l\u1edbn \u0111\u00e3 ch\u1eb7n", + "stats_query_domain": "T\u00ean mi\u1ec1n truy v\u1ea5n nhi\u1ec1u", + "for_last_24_hours": "trong 24 gi\u1edd qua", + "no_domains_found": "Kh\u00f4ng c\u00f3 t\u00ean mi\u1ec1n n\u00e0o", + "requests_count": "S\u1ed1 l\u1ea7n y\u00eau c\u1ea7u", + "top_blocked_domains": "T\u00ean mi\u1ec1n ch\u1eb7n nhi\u1ec1u", + "top_clients": "Client d\u00f9ng nhi\u1ec1u", + "no_clients_found": "Kh\u00f4ng c\u00f3 client n\u00e0o", + "general_statistics": "Th\u1ed1ng k\u00ea chung", + "number_of_dns_query_24_hours": "S\u1ed1 y\u00eau c\u1ea7u DNS \u0111\u00e3 x\u1eed l\u00fd trong 24 gi\u1edd qua", + "number_of_dns_query_blocked_24_hours": "S\u1ed1 y\u00eau c\u1ea7u DNS b\u1ecb ch\u1eb7n b\u1edfi b\u1ed9 l\u1ecdc qu\u1ea3ng c\u00e1o v\u00e0 danh s\u00e1ch ch\u1eb7n host", + "number_of_dns_query_blocked_24_hours_by_sec": "S\u1ed1 y\u00eau c\u1ea7u DNS b\u1ecb ch\u1eb7n b\u1edfi ch\u1ebf \u0111\u1ed9 b\u1ea3o v\u1ec7 duy\u1ec7t web AdGuard", + "number_of_dns_query_blocked_24_hours_adult": "S\u1ed1 website ng\u01b0\u1eddi l\u1edbn \u0111\u00e3 ch\u1eb7n", + "enforced_save_search": "T\u00ecm ki\u1ebfm an to\u00e0n", + "number_of_dns_query_to_safe_search": "S\u1ed1 y\u00eau c\u1ea7u DNS t\u1edbi c\u00f4ng c\u1ee5 t\u00ecm ki\u1ebfm \u0111\u00e3 chuy\u1ec3n th\u00e0nh t\u00ecm ki\u1ebfm an to\u00e0n", + "average_processing_time": "Th\u1eddi gian x\u1eed l\u00fd trung b\u00ecnh", + "average_processing_time_hint": "Th\u1eddi gian trung b\u00ecnh cho m\u1ed9t y\u00eau c\u1ea7u DNS t\u00ednh b\u1eb1ng mili gi\u00e2y", + "block_domain_use_filters_and_hosts": "Ch\u1eb7n t\u00ean mi\u1ec1n s\u1eed d\u1ee5ng c\u00e1c b\u1ed9 l\u1ecdc v\u00e0 file hosts", + "filters_block_toggle_hint": "B\u1ea1n c\u00f3 th\u1ec3 thi\u1ebft l\u1eadp quy t\u1eafc ch\u1eb7n t\u1ea1i c\u00e0i \u0111\u1eb7t B\u1ed9 l\u1ecdc<\/a>.", + "use_adguard_browsing_sec": "S\u1eed d\u1ee5ng d\u1ecbch v\u1ee5 b\u1ea3o v\u1ec7 duy\u1ec7t web AdGuard", + "use_adguard_browsing_sec_hint": "AdGuard Home s\u1ebd ki\u1ec3m tra t\u00ean mi\u1ec1n v\u1edbi d\u1ecbch v\u1ee5 b\u1ea3o v\u1ec7 duy\u1ec7t web. T\u00ednh n\u0103ng s\u1eed d\u1ee5ng m\u1ed9t API th\u00e2n thi\u1ec7n v\u1edbi quy\u1ec1n ri\u00eang t\u01b0: ch\u1ec9 m\u1ed9t ph\u1ea7n ng\u1eafn ti\u1ec1n t\u1ed1 m\u00e3 b\u0103m SHA256 \u0111\u01b0\u1ee3c g\u1eedi \u0111\u1ebfn m\u00e1y ch\u1ee7", + "use_adguard_parental": "S\u1eed d\u1ee5ng d\u1ecbch v\u1ee5 qu\u1ea3n l\u00fd c\u1ee7a ph\u1ee5 huynh AdGuard", + "use_adguard_parental_hint": "AdGuard Home s\u1ebd ki\u1ec3m tra n\u1ebfu t\u00ean mi\u1ec1n ch\u1ee9a t\u1eeb kho\u00e1 ng\u01b0\u1eddi l\u1edbn. T\u00ednh n\u0103ng s\u1eed d\u1ee5ng API th\u00e2n thi\u1ec7n v\u1edbi quy\u1ec1n ri\u00eang t\u01b0 t\u01b0\u01a1ng t\u1ef1 v\u1edbi d\u1ecbch v\u1ee5 b\u1ea3o v\u1ec7 duy\u1ec7t web", + "enforce_safe_search": "B\u1eaft bu\u1ed9c t\u00ecm ki\u1ebfm an to\u00e0n", + "enforce_save_search_hint": "AdGuard Home c\u00f3 th\u1ec3 b\u1eaft bu\u1ed9c t\u00ecm ki\u1ebfm an to\u00e0n v\u1edbi c\u00e1c d\u1ecbch v\u1ee5 t\u00ecm ki\u1ebfm: Google, Youtube, Bing, Yandex.", + "no_servers_specified": "Kh\u00f4ng c\u00f3 m\u00e1y ch\u1ee7 n\u00e0o \u0111\u01b0\u1ee3c li\u1ec7t k\u00ea", + "no_settings": "Kh\u00f4ng c\u00f3 c\u00e0i \u0111\u1eb7t n\u00e0o", + "general_settings": "C\u00e0i \u0111\u1eb7t chung", + "upstream_dns": "M\u00e1y ch\u1ee7 DNS t\u00ecm ki\u1ebfm", + "upstream_dns_hint": "N\u1ebfu b\u1ea1n \u0111\u1ec3 tr\u1ed1ng m\u1ee5c n\u00e0y, AdGuard Home s\u1ebd s\u1eed d\u1ee5ng Cloudflare DNS<\/a> \u0111\u1ec3 t\u00ecm ki\u1ebfm. S\u1eed d\u1ee5ng ti\u1ec1n t\u1ed1 tls:\/\/ cho c\u00e1c m\u00e1y ch\u1ee7 DNS d\u1ef1a tr\u00ean TLS.", + "test_upstream_btn": "Ki\u1ec3m tra", + "apply_btn": "\u00c1p d\u1ee5ng", + "disabled_filtering_toast": "\u0110\u00e3 t\u1eaft ch\u1eb7n qu\u1ea3ng c\u00e1o", + "enabled_filtering_toast": "\u0110\u00e3 b\u1eadt ch\u1eb7n qu\u1ea3ng c\u00e1o", + "disabled_safe_browsing_toast": "\u0110\u00e3 t\u1eaft b\u1ea3o v\u1ec7 duy\u1ec7t web", + "enabled_safe_browsing_toast": "\u0110\u00e3 b\u1eadt b\u1ea3o v\u1ec7 duy\u1ec7t web", + "disabled_parental_toast": "\u0110\u00e3 t\u1eaft qu\u1ea3n l\u00fd c\u1ee7a ph\u1ee5 huynh", + "enabled_parental_toast": "\u0110\u00e3 b\u1eadt qu\u1ea3n l\u00fd c\u1ee7a ph\u1ee5 huynh", + "disabled_safe_search_toast": "\u0110\u00e3 t\u1eaft t\u00ecm ki\u1ebfm an to\u00e0n", + "enabled_save_search_toast": "\u0110\u00e3 b\u1eadt t\u00ecm ki\u1ebfm an to\u00e0n", + "enabled_table_header": "K\u00edch ho\u1ea1t", + "name_table_header": "T\u00ean", + "filter_url_table_header": "URL b\u1ed9 l\u1ecdc", + "rules_count_table_header": "S\u1ed1 quy t\u1eafc", + "last_time_updated_table_header": "C\u1eadp nh\u1eadt cu\u1ed1i", + "actions_table_header": "Thao t\u00e1c", + "delete_table_action": "Xo\u00e1", + "filters_and_hosts": "Danh s\u00e1ch b\u1ed9 l\u1ecdc v\u00e0 hosts", + "filters_and_hosts_hint": "AdGuard home hi\u1ec3u c\u00e1c quy t\u1eafc ch\u1eb7n qu\u1ea3ng c\u00e1o \u0111\u01a1n gi\u1ea3n v\u00e0 c\u00fa ph\u00e1p file hosts", + "no_filters_added": "Kh\u00f4ng c\u00f3 b\u1ed9 l\u1ecdc n\u00e0o \u0111\u01b0\u1ee3c th\u00eam", + "add_filter_btn": "Th\u00eam b\u1ed9 l\u1ecdc", + "cancel_btn": "Hu\u1ef7", + "enter_name_hint": "Nh\u1eadp t\u00ean", + "enter_url_hint": "Nh\u1eadp URL", + "check_updates_btn": "Ki\u1ec3m tra c\u1eadp nh\u1eadt", + "new_filter_btn": "\u0110\u0103ng k\u00fd b\u1ed9 l\u1ecdc m\u1edbi", + "enter_valid_filter_url": "Nh\u1eadp URL h\u1ee3p l\u1ec7 c\u1ee7a b\u1ed9 l\u1ecdc ho\u1eb7c file hosts", + "custom_filter_rules": "Quy t\u1eafc l\u1ecdc tu\u1ef3 ch\u1ec9nh", + "custom_filter_rules_hint": "Nh\u1eadp m\u1ed7i quy t\u1eafc 1 d\u00f2ng. C\u00f3 th\u1ec3 s\u1eed d\u1ee5ng quy t\u1eafc ch\u1eb7n qu\u1ea3ng c\u00e1o ho\u1eb7c c\u00fa ph\u00e1p file host", + "examples_title": "V\u00ed d\u1ee5", + "example_meaning_filter_block": "Ch\u1eb7n truy c\u1eadp t\u1edbi t\u00ean mi\u1ec1n example.org v\u00e0 t\u1ea5t c\u1ea3 t\u00ean mi\u1ec1n con", + "example_meaning_filter_whitelist": "Kh\u00f4ng ch\u1eb7n truy c\u1eadp t\u1edbi t\u00ean mi\u1ec1n example.org v\u00e0 t\u1ea5t c\u1ea3 t\u00ean mi\u1ec1n con", + "example_meaning_host_block": "AdGuard Home s\u1ebd ph\u1ea3n h\u1ed3i \u0111\u1ecba ch\u1ec9 IP 127.0.0.1 cho t\u00ean mi\u1ec1n example.org (kh\u00f4ng \u00e1p d\u1ee5ng t\u00ean mi\u1ec1n con)", + "example_comment": "! \u0110\u00e2y l\u00e0 m\u1ed9t ch\u00fa th\u00edch", + "example_comment_meaning": "Ch\u1ec9 l\u00e0 m\u1ed9t ch\u00fa th\u00edch", + "example_comment_hash": "# C\u0169ng l\u00e0 m\u1ed9t ch\u00fa th\u00edch", + "all_filters_up_to_date_toast": "T\u1ea5t c\u1ea3 b\u1ed9 l\u1ecdc \u0111\u00e3 \u0111\u01b0\u1ee3c c\u1eadp nh\u1eadt", + "updated_upstream_dns_toast": "\u0110\u00e3 c\u1eadp nh\u1eadt m\u00e1y ch\u1ee7 DNS t\u00ecm ki\u1ebfm", + "dns_test_ok_toast": "M\u00e1y ch\u1ee7 DNS c\u00f3 th\u1ec3 s\u1eed d\u1ee5ng", + "dns_test_not_ok_toast": "M\u00e1y ch\u1ee7 '{{key}}': kh\u00f4ng th\u1ec3 s\u1eed d\u1ee5ng, vui l\u00f2ng ki\u1ec3m tra b\u1ea1n \u0111\u00e3 \u0111i\u1ec1n ch\u00ednh x\u00e1c", + "unblock_btn": "B\u1ecf ch\u1eb7n", + "block_btn": "Ch\u1eb7n", + "time_table_header": "Th\u1eddi gian", + "domain_name_table_header": "T\u00ean mi\u1ec1n", + "type_table_header": "Lo\u1ea1i", + "response_table_header": "Ph\u1ea3n h\u1ed3i", + "empty_response_status": "R\u1ed7ng", + "show_all_filter_type": "Hi\u1ec7n t\u1ea5t c\u1ea3", + "show_filtered_type": "Ch\u1ec9 hi\u1ec7n \u0111\u00e3 ch\u1eb7n", + "no_logs_found": "Kh\u00f4ng c\u00f3 l\u1ecbch s\u1eed truy v\u1ea5n", + "disabled_log_btn": "T\u1eaft l\u1ecbch s\u1eed truy v\u1ea5n", + "download_log_file_btn": "T\u1ea3i t\u1eadp tin l\u1ecbch s\u1eed truy v\u1ea5n", + "refresh_btn": "L\u00e0m m\u1edbi", + "enabled_log_btn": "B\u1eadt l\u1ecbch s\u1eed truy v\u1ea5n", + "last_dns_queries": "5000 truy v\u1ea5n DNS g\u1ea7n nh\u1ea5t", + "previous_btn": "Trang tr\u01b0\u1edbc", + "next_btn": "Trang sau", + "loading_table_status": "\u0110ang t\u1ea3i...", + "page_table_footer_text": "Trang", + "of_table_footer_text": "c\u1ee7a", + "rows_table_footer_text": "h\u00e0ng", + "updated_custom_filtering_toast": "\u0110\u00e3 c\u1eadp nh\u1eadt quy t\u1eafc l\u1ecdc tu\u1ef3 ch\u1ec9nh", + "rule_removed_from_custom_filtering_toast": "Quy t\u1eafc \u0111\u00e3 \u0111\u01b0\u1ee3c xo\u00e1 kh\u1ecfi quy t\u1eafc l\u1ecdc tu\u1ef3 ch\u1ec9nh", + "rule_added_to_custom_filtering_toast": "Quy t\u1eafc \u0111\u00e3 \u0111\u01b0\u1ee3c th\u00eam v\u00e0o quy t\u1eafc l\u1ecdc tu\u1ef3 ch\u1ec9nh", + "query_log_disabled_toast": "\u0110\u00e3 t\u1eaft l\u1ecbch s\u1eed truy v\u1ea5n", + "query_log_enabled_toast": "\u0110\u00e3 b\u1eadt l\u1ecbch s\u1eed truy v\u1ea5n", + "source_label": "Ngu\u1ed3n", + "found_in_known_domain_db": "T\u00ecm th\u1ea5y trong c\u01a1 s\u1edf d\u1eef li\u1ec7u t\u00ean mi\u1ec1n", + "category_label": "Th\u1ec3 lo\u1ea1i", + "rule_label": "Quy t\u1eafc", + "filter_label": "B\u1ed9 l\u1ecdc" +} \ No newline at end of file diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 396f9136..a4da8c9a 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -1,5 +1,6 @@ import { createAction } from 'redux-actions'; import round from 'lodash/round'; +import { t } from 'i18next'; import { showLoading, hideLoading } from 'react-redux-loading-bar'; import { normalizeHistory, normalizeFilteringStatus, normalizeLogs } from '../helpers/helpers'; @@ -21,40 +22,40 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => { switch (settingKey) { case 'filtering': if (status) { - successMessage = 'Disabled filtering'; + successMessage = 'disabled_filtering_toast'; await apiClient.disableFiltering(); } else { - successMessage = 'Enabled filtering'; + successMessage = 'enabled_filtering_toast'; await apiClient.enableFiltering(); } dispatch(toggleSettingStatus({ settingKey })); break; case 'safebrowsing': if (status) { - successMessage = 'Disabled safebrowsing'; + successMessage = 'disabled_safe_browsing_toast'; await apiClient.disableSafebrowsing(); } else { - successMessage = 'Enabled safebrowsing'; + successMessage = 'enabled_safe_browsing_toast'; await apiClient.enableSafebrowsing(); } dispatch(toggleSettingStatus({ settingKey })); break; case 'parental': if (status) { - successMessage = 'Disabled parental control'; + successMessage = 'disabled_parental_toast'; await apiClient.disableParentalControl(); } else { - successMessage = 'Enabled parental control'; + successMessage = 'enabled_parental_toast'; await apiClient.enableParentalControl(); } dispatch(toggleSettingStatus({ settingKey })); break; case 'safesearch': if (status) { - successMessage = 'Disabled safe search'; + successMessage = 'disabled_safe_search_toast'; await apiClient.disableSafesearch(); } else { - successMessage = 'Enabled safe search'; + successMessage = 'enabled_save_search_toast'; await apiClient.enableSafesearch(); } dispatch(toggleSettingStatus({ settingKey })); @@ -123,10 +124,10 @@ export const toggleProtection = status => async (dispatch) => { try { if (status) { - successMessage = 'Disabled protection'; + successMessage = 'disabled_protection'; await apiClient.disableGlobalProtection(); } else { - successMessage = 'Enabled protection'; + successMessage = 'enabled_protection'; await apiClient.enableGlobalProtection(); } @@ -271,14 +272,14 @@ export const toggleLogStatus = queryLogEnabled => async (dispatch) => { let successMessage; if (queryLogEnabled) { toggleMethod = apiClient.disableQueryLog.bind(apiClient); - successMessage = 'disabled'; + successMessage = 'query_log_disabled_toast'; } else { toggleMethod = apiClient.enableQueryLog.bind(apiClient); - successMessage = 'enabled'; + successMessage = 'query_log_enabled_toast'; } try { await toggleMethod(); - dispatch(addSuccessToast(`Query log ${successMessage}`)); + dispatch(addSuccessToast(successMessage)); dispatch(toggleLogStatusSuccess()); } catch (error) { dispatch(addErrorToast({ error })); @@ -297,7 +298,7 @@ export const setRules = rules => async (dispatch) => { .replace(/^\n/g, '') .replace(/\n\s*\n/g, '\n'); await apiClient.setRules(replacedLineEndings); - dispatch(addSuccessToast('Updated the custom filtering rules')); + dispatch(addSuccessToast('updated_custom_filtering_toast')); dispatch(setRulesSuccess()); } catch (error) { dispatch(addErrorToast({ error })); @@ -359,7 +360,7 @@ export const refreshFilters = () => async (dispatch) => { if (refreshText.includes('OK')) { if (refreshText.includes('OK 0')) { - dispatch(addSuccessToast('All filters are already up-to-date')); + dispatch(addSuccessToast('all_filters_up_to_date_toast')); } else { dispatch(addSuccessToast(refreshText.replace(/OK /g, ''))); } @@ -456,7 +457,7 @@ export const setUpstream = url => async (dispatch) => { dispatch(setUpstreamRequest()); try { await apiClient.setUpstream(url); - dispatch(addSuccessToast('Updated the upstream DNS servers')); + dispatch(addSuccessToast('updated_upstream_dns_toast')); dispatch(setUpstreamSuccess()); } catch (error) { dispatch(addErrorToast({ error })); @@ -476,13 +477,13 @@ export const testUpstream = servers => async (dispatch) => { const testMessages = Object.keys(upstreamResponse).map((key) => { const message = upstreamResponse[key]; if (message !== 'OK') { - dispatch(addErrorToast({ error: `Server "${key}": could not be used, please check that you've written it correctly` })); + dispatch(addErrorToast({ error: t('dns_test_not_ok_toast', { key }) })); } return message; }); if (testMessages.every(message => message === 'OK')) { - dispatch(addSuccessToast('Specified DNS servers are working correctly')); + dispatch(addSuccessToast('dns_test_ok_toast')); } dispatch(testUpstreamSuccess()); @@ -491,3 +492,33 @@ export const testUpstream = servers => async (dispatch) => { dispatch(testUpstreamFailure()); } }; + +export const changeLanguageRequest = createAction('CHANGE_LANGUAGE_REQUEST'); +export const changeLanguageFailure = createAction('CHANGE_LANGUAGE_FAILURE'); +export const changeLanguageSuccess = createAction('CHANGE_LANGUAGE_SUCCESS'); + +export const changeLanguage = lang => async (dispatch) => { + dispatch(changeLanguageRequest()); + try { + await apiClient.changeLanguage(lang); + dispatch(changeLanguageSuccess()); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(changeLanguageFailure()); + } +}; + +export const getLanguageRequest = createAction('GET_LANGUAGE_REQUEST'); +export const getLanguageFailure = createAction('GET_LANGUAGE_FAILURE'); +export const getLanguageSuccess = createAction('GET_LANGUAGE_SUCCESS'); + +export const getLanguage = () => async (dispatch) => { + dispatch(getLanguageRequest()); + try { + const language = await apiClient.getCurrentLanguage(); + dispatch(getLanguageSuccess(language)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getLanguageFailure()); + } +}; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 0e9a1c74..f0d90941 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -284,4 +284,22 @@ export default class Api { const { path, method } = this.SAFESEARCH_DISABLE; return this.makeRequest(path, method); } + + // Language + CURRENT_LANGUAGE = { path: 'i18n/current_language', method: 'GET' }; + CHANGE_LANGUAGE = { path: 'i18n/change_language', method: 'POST' }; + + getCurrentLanguage() { + const { path, method } = this.CURRENT_LANGUAGE; + return this.makeRequest(path, method); + } + + changeLanguage(lang) { + const { path, method } = this.CHANGE_LANGUAGE; + const parameters = { + data: lang, + headers: { 'Content-Type': 'text/plain' }, + }; + return this.makeRequest(path, method, parameters); + } } diff --git a/client/src/components/App/index.js b/client/src/components/App/index.js index b4b040a9..98c37afa 100644 --- a/client/src/components/App/index.js +++ b/client/src/components/App/index.js @@ -17,6 +17,7 @@ import Footer from '../ui/Footer'; import Toasts from '../Toasts'; import Status from '../ui/Status'; import Update from '../ui/Update'; +import i18n from '../../i18n'; class App extends Component { componentDidMount() { @@ -24,10 +25,32 @@ class App extends Component { this.props.getVersion(); } + componentDidUpdate(prevProps) { + if (this.props.dashboard.language !== prevProps.dashboard.language) { + this.setLanguage(); + } + } + handleStatusChange = () => { this.props.enableDns(); }; + setLanguage = () => { + const { processing, language } = this.props.dashboard; + + if (!processing) { + if (!language) { + this.props.changeLanguage(i18n.language); + } else { + i18n.changeLanguage(language); + } + } + + i18n.on('languageChanged', (lang) => { + this.props.changeLanguage(lang); + }); + } + render() { const { dashboard } = this.props; const updateAvailable = @@ -78,6 +101,7 @@ App.propTypes = { isCoreRunning: PropTypes.bool, error: PropTypes.string, getVersion: PropTypes.func, + changeLanguage: PropTypes.func, }; export default App; diff --git a/client/src/components/Dashboard/BlockedDomains.js b/client/src/components/Dashboard/BlockedDomains.js index d2a81029..25b3c8d6 100644 --- a/client/src/components/Dashboard/BlockedDomains.js +++ b/client/src/components/Dashboard/BlockedDomains.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; import map from 'lodash/map'; +import { withNamespaces, Trans } from 'react-i18next'; import Card from '../ui/Card'; import Cell from '../ui/Cell'; @@ -29,7 +30,7 @@ class BlockedDomains extends Component { ); }, }, { - Header: 'Requests count', + Header: requests_count, accessor: 'domain', maxWidth: 190, Cell: ({ value }) => { @@ -48,15 +49,16 @@ class BlockedDomains extends Component { }]; render() { + const { t } = this.props; return ( - + ( { ip: prop, domain: value } ))} columns={this.columns} showPagination={false} - noDataText="No domains found" + noDataText={ t('no_domains_found') } minRows={6} className="-striped -highlight card-table-overflow stats__table" /> @@ -71,6 +73,7 @@ BlockedDomains.propTypes = { replacedSafebrowsing: PropTypes.number.isRequired, replacedParental: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, + t: PropTypes.func, }; -export default BlockedDomains; +export default withNamespaces()(BlockedDomains); diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index a9fa4792..bd9bb4c4 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; import map from 'lodash/map'; +import { Trans, withNamespaces } from 'react-i18next'; import Card from '../ui/Card'; import Cell from '../ui/Cell'; @@ -25,7 +26,7 @@ class Clients extends Component { Cell: ({ value }) => (
{value}
), sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10), }, { - Header: 'Requests count', + Header: requests_count, accessor: 'count', Cell: ({ value }) => { const percent = getPercent(this.props.dnsQueries, value); @@ -38,15 +39,16 @@ class Clients extends Component { }]; render() { + const { t } = this.props; return ( - + ( { ip: prop, count: value } ))} columns={this.columns} showPagination={false} - noDataText="No clients found" + noDataText={ t('no_clients_found') } minRows={6} className="-striped -highlight card-table-overflow" /> @@ -59,6 +61,7 @@ Clients.propTypes = { topClients: PropTypes.object.isRequired, dnsQueries: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, + t: PropTypes.func, }; -export default Clients; +export default withNamespaces()(Clients); diff --git a/client/src/components/Dashboard/Counters.js b/client/src/components/Dashboard/Counters.js index a6260e10..af710cb0 100644 --- a/client/src/components/Dashboard/Counters.js +++ b/client/src/components/Dashboard/Counters.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { Trans, withNamespaces } from 'react-i18next'; import Card from '../ui/Card'; import Tooltip from '../ui/Tooltip'; @@ -7,13 +8,13 @@ import Tooltip from '../ui/Tooltip'; const tooltipType = 'tooltip-custom--narrow'; const Counters = props => ( - +
- DNS Queries - + dns_query + @@ -23,8 +24,8 @@ const Counters = props => (
- Blocked by Filters - + blocked_by filters + @@ -34,8 +35,8 @@ const Counters = props => (
- Blocked malware/phishing - + stats_malware_phishing + @@ -45,8 +46,8 @@ const Counters = props => (
- Blocked adult websites - + stats_adult + @@ -56,8 +57,8 @@ const Counters = props => (
- Enforced safe search - + enforced_save_search + @@ -67,8 +68,8 @@ const Counters = props => (
- Average processing time - + average_processing_time + @@ -89,6 +90,7 @@ Counters.propTypes = { replacedSafesearch: PropTypes.number.isRequired, avgProcessingTime: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, + t: PropTypes.func, }; -export default Counters; +export default withNamespaces()(Counters); diff --git a/client/src/components/Dashboard/QueriedDomains.js b/client/src/components/Dashboard/QueriedDomains.js index 7ffc2555..4d16a27c 100644 --- a/client/src/components/Dashboard/QueriedDomains.js +++ b/client/src/components/Dashboard/QueriedDomains.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; import map from 'lodash/map'; +import { withNamespaces, Trans } from 'react-i18next'; import Card from '../ui/Card'; import Cell from '../ui/Cell'; @@ -38,7 +39,7 @@ class QueriedDomains extends Component { ); }, }, { - Header: 'Requests count', + Header: requests_count, accessor: 'count', maxWidth: 190, Cell: ({ value }) => { @@ -52,15 +53,16 @@ class QueriedDomains extends Component { }]; render() { + const { t } = this.props; return ( - + ( { ip: prop, count: value } ))} columns={this.columns} showPagination={false} - noDataText="No domains found" + noDataText={ t('no_domains_found') } minRows={6} className="-striped -highlight card-table-overflow stats__table" /> @@ -73,6 +75,7 @@ QueriedDomains.propTypes = { topQueriedDomains: PropTypes.object.isRequired, dnsQueries: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, + t: PropTypes.func, }; -export default QueriedDomains; +export default withNamespaces()(QueriedDomains); diff --git a/client/src/components/Dashboard/Statistics.js b/client/src/components/Dashboard/Statistics.js index a54e0c00..898ccf22 100644 --- a/client/src/components/Dashboard/Statistics.js +++ b/client/src/components/Dashboard/Statistics.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { Trans, withNamespaces } from 'react-i18next'; import Card from '../ui/Card'; import Line from '../ui/Line'; @@ -24,13 +25,13 @@ class Statistics extends Component { return (
- +
{dnsQueries}
- DNS Queries + dns_query
@@ -39,7 +40,7 @@ class Statistics extends Component {
- +
{blockedFiltering} @@ -48,7 +49,7 @@ class Statistics extends Component { {getPercent(dnsQueries, blockedFiltering)}
- Blocked by Filters + blocked_by filters
@@ -57,7 +58,7 @@ class Statistics extends Component {
- +
{replacedSafebrowsing} @@ -66,7 +67,7 @@ class Statistics extends Component { {getPercent(dnsQueries, replacedSafebrowsing)}
- Blocked malware/phishing + stats_malware_phishing
@@ -75,7 +76,7 @@ class Statistics extends Component {
- +
{replacedParental} @@ -84,7 +85,7 @@ class Statistics extends Component { {getPercent(dnsQueries, replacedParental)}
- Blocked adult websites + stats_adult
@@ -106,4 +107,4 @@ Statistics.propTypes = { refreshButton: PropTypes.node.isRequired, }; -export default Statistics; +export default withNamespaces()(Statistics); diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index bf49b2a4..767e8d1a 100644 --- a/client/src/components/Dashboard/index.js +++ b/client/src/components/Dashboard/index.js @@ -1,6 +1,7 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import 'whatwg-fetch'; +import { Trans, withNamespaces } from 'react-i18next'; import Statistics from './Statistics'; import Counters from './Counters'; @@ -25,30 +26,30 @@ class Dashboard extends Component { getToggleFilteringButton = () => { const { protectionEnabled } = this.props.dashboard; - const buttonText = protectionEnabled ? 'Disable' : 'Enable'; + const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection'; const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success'; return ( ); } render() { - const { dashboard } = this.props; + const { dashboard, t } = this.props; const dashboardProcessing = dashboard.processing || dashboard.processingStats || dashboard.processingStatsHistory || dashboard.processingTopStats; - const refreshFullButton = ; + const refreshFullButton = ; const refreshButton =