Convert i18n resource to key type

This commit is contained in:
Hoàng Rio 2018-11-09 13:51:28 +07:00
parent beab9a1be0
commit d47a23269d
No known key found for this signature in database
GPG Key ID: B19526CDE69535A1
22 changed files with 389 additions and 366 deletions

View File

@ -15,116 +15,120 @@ export default {
homepage: 'Homepage', homepage: 'Homepage',
report_an_issue: 'Report an issue', report_an_issue: 'Report an issue',
// Dashboard // Dashboard
enabled_protection:'Enable protection', enable_protection: 'Enable protection',
disabled_protection: 'Disable protection', enabled_protection: 'Enableed protection',
disable_protection: 'Disable protection',
disabled_protection: 'Disabled protection',
refresh_statics: 'Refresh statistics', refresh_statics: 'Refresh statistics',
dns_query: 'DNS Queries', dns_query: 'DNS Queries',
'Blocked by': 'Chặn bởi', blocked_by: 'Blocked by',
'Blocked malware/phishing': 'Mã độc/lừa đảo đã chặn', stats_malware_phishing: 'Blocked malware/phishing',
'Blocked adult websites': 'Website người lớn đã chặn', stats_adult: 'Blocked adult websites',
'Top queried domains': 'Tên miền truy vấn nhiều', stats_query_domain: 'Top queried domains',
'for the last 24 hours': 'trong 24 giờ qua', for_last_24_hours: 'for the last 24 hours',
'No domains found': 'Không có tên miền nào', no_domains_found: 'No domains found',
'Requests count': 'Số lần yêu cầu', requests_count: 'Requests count',
'Top blocked domains': 'Tên miền chặn nhiều', top_blocked_domains: 'Top blocked domains',
'Top clients': 'Client dùng nhiều', top_clients: 'Top clients',
'No clients found': 'Không có client nào', no_clients_found: 'No clients found',
'General statistics': 'Thống kê chung', general_statistics: 'General statistics',
'A number of DNS quieries processed for the last 24 hours': 'Số yêu cầu DNS đã xử lý trong 24 giờ qua', number_of_dns_query_24_hours: 'A number of DNS quieries processed for the last 24 hours',
'A number of DNS requests blocked by adblock filters and hosts blocklists': 'Số yêu cầu DNS bị chặn bởi bộ lọc quảng cáo và danh sách chặn host', number_of_dns_query_blocked_24_hours: 'A number of DNS requests blocked by adblock filters and hosts blocklists',
'A number of DNS requests blocked by the AdGuard browsing security module': 'Số yêu cầu DNS bị chặn bởi chế độ bảo vệ duyệt web AdGuard', number_of_dns_query_blocked_24_hours_by_sec: 'A number of DNS requests blocked by the AdGuard browsing security module',
'A number of adult websites blocked': 'Số website người lớn đã chặn', number_of_dns_query_blocked_24_hours_adult: 'A number of adult websites blocked',
'Enforced safe search': 'Tìm kiếm an toàn', enforced_save_search: 'Enforced safe search',
'A number of DNS requests to search engines for which Safe Search was enforced': 'Số yêu cầu DNS tới công cụ tìm kiếm đã chuyển thành tìm kiếm an toàn', number_of_dns_query_to_safe_search: 'A number of DNS requests to search engines for which Safe Search was enforced',
'Average processing time': 'Thời gian xử lý trung bình', average_processing_time: 'Average processing time',
'Average time in milliseconds on processing a DNS request': 'Thời gian trung bình cho một yêu cầu DNS tính bằng mili giây', average_processing_time_hint: 'Average time in milliseconds on processing a DNS request',
// Settings // Settings
'Block domains using filters and hosts files': 'Chặn tên miền sử dụng các bộ lọc và file hosts', block_domain_use_filters_and_hosts: 'Block domains using filters and hosts files',
'You can setup blocking rules in the <a href="#filters">Filters</a> settings.': 'Bạn có thể thiết lập quy tắc chặn tại cài đặt <a href="#filters">Bộ lọc</a>.', filters_block_toggle_hint: 'You can setup blocking rules in the <a href="#filters">Filters</a> settings.',
'Use AdGuard browsing security web service': 'Sử dụng dịch vụ bảo vệ duyệt web AdGuard', use_adguard_browsing_sec: 'Use AdGuard browsing security web service',
'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.': 'AdGuard Home sẽ kiểm tra tên miền với dịch vụ bảo vệ duyệt web. Tính năng sử dụng một API thân thiện với quyền riêng tư: chỉ một phần ngắn tiền tố mã băm SHA256 được gửi đến máy chủ', 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 control web service': 'Sử dụng dịch vụ quản lý của phụ huynh AdGuard', use_adguard_parental: 'Use AdGuard parental control web service',
'AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.': 'AdGuard Home sẽ kiểm tra nếu tên miền chứa từ khoá người lớn. Tính năng sử dụng API thân thiện với quyền riêng tư tương tự với dịch vụ bảo vệ duyệt web', 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': 'Bắt buộc tìm kiếm an toàn', enforce_safe_search: 'Enforce safe search',
'AdGuard Home can enforce safe search in the following search engines: Google, Bing, Yandex.': 'AdGuard Home có thể bắt buộc tìm kiếm an toàn với các dịch vụ tìm kiếm: Google, Bing, Yandex.', enforce_save_search_hint: 'AdGuard Home can enforce safe search in the following search engines: Google, Bing, Yandex.',
'No servers specified': 'Không có máy chủ nào được liệt kê', no_servers_specified: 'No servers specified',
'No settings': 'Không có cài đặt nào', no_settings: 'No settings',
'General settings': 'Cài đặt chung', general_settings: 'General settings',
'Upstream DNS servers': 'Máy chủ DNS tìm kiếm', upstream_dns: 'Upstream DNS servers',
'If you keep this field empty, AdGuard Home will use <a href="https://1.1.1.1/" target="_blank">Cloudflare DNS</a> as an upstream. Use tls:// prefix for DNS over TLS servers.': 'Nếu bạn để trống mục này, AdGuard Home sẽ sử dụng <a href="https://1.1.1.1/" target="_blank">Cloudflare DNS</a> để tìm kiếm. Sử dụng tiền tố tls:// cho các máy chủ DNS dựa trên TLS.', upstream_dns_hint: 'If you keep this field empty, AdGuard Home will use <a href="https://1.1.1.1/" target="_blank">Cloudflare DNS</a> as an upstream. Use tls:// prefix for DNS over TLS servers.',
'Test upstreams': 'Kiểm tra', test_upstream_btn: 'Test upstreams',
Apply: 'Áp dụng', apply_btn: 'Apply',
// Settings Toasts // Settings Toasts
'Disabled filtering': 'Đã tắt chặn quảng cáo', disabled_filtering_toast: 'Disabled filtering',
'Enabled filtering': 'Đã bật chặn quảng cáo', enabled_filtering_toast: 'Enabled filtering',
'Disabled safebrowsing': 'Đã tắt bảo vệ duyệt web', disabled_safe_browsing_toast: 'Disabled safebrowsing',
'Enabled safebrowsing': 'Đã bật bảo vệ duyệt web', enabled_safe_browsing_toast: 'Enabled safebrowsing',
'Disabled parental control': 'Đã tắt quản lý của phụ huynh', disabled_parental_toast: 'Disabled parental control',
'Enabled parental control': 'Đã bật quản lý của phụ huynh', enabled_parental_toast: 'Enabled parental control',
'Disabled safe search': 'Đã tắt tìm kiếm an toàn', disabled_safe_search_toast: 'Disabled safe search',
'Enabled safe search': 'Đã bật tìm kiếm an toàn', enabled_save_search_toast: 'Enabled safe search',
// Filters // Filters
Enabled: 'Kích hoạt', enabled_table_header: 'Enabled',
Name: 'Tên', name_table_header: 'Name',
'Filter URL': 'URL bộ lọc', filter_url_table_header: 'Filter URL',
'Rules count': 'Số quy tắc', rules_count_table_header: 'Rules count',
'Last time updated': 'Cập nhật cuối', last_time_updated_table_header: 'Last time updated',
Actions: 'Thao tác', actions_table_header: 'Actions',
Delete: 'Xoá', delete_table_action: 'Delete',
'Filters and hosts blocklists': 'Danh sách bộ lọc và hosts', filters_and_hosts: 'Filters and hosts blocklists',
'AdGuard Home understands basic adblock rules and hosts files syntax.': 'AdGuard home hiểu các quy tắc chặn quảng cáo đơn giản và cú pháp file hosts', filters_and_hosts_hint: 'AdGuard Home understands basic adblock rules and hosts files syntax.',
'No filters added': 'Không có bộ lọc nào được thêm', no_filters_added: 'No filters added',
'Add filter': 'Thêm bộ lọc', add_filter_btn: 'Add filter',
Cancel: 'Huỷ', cancel_btn: 'Cancel',
'Enter name': 'Nhập tên', enter_name_hint: 'Enter name',
'Enter URL': 'Nhập URL', enter_url_hint: 'Enter URL',
'Check updates': 'Kiểm tra cập nhật', check_updates_btn: 'Check updates',
'New filter subscription': 'Đăng ký bộ lọc mới', new_filter_btn: 'New filter subscription',
'Enter a valid URL to a filter subscription or a hosts file.': 'Nhập URL hợp lệ của bộ lọc hoặc file hosts', enter_valid_filter_url: 'Enter a valid URL to a filter subscription or a hosts file.',
'Custom filtering rules': 'Quy tắc lọc tuỳ chỉnh', custom_filter_rules: 'Custom filtering rules',
'Enter one rule on a line. You can use either adblock rules or hosts files syntax.': 'Nhập mỗi quy tắc 1 dòng. Có thể sử dụng quy tắc chặn quảng cáo hoặc cú pháp file host', custom_filter_rules_hint: 'Enter one rule on a line. You can use either adblock rules or hosts files syntax.',
Examples: 'Ví dụ', examples_title: 'Examples',
'block access to the example.org domain and all its subdomains': 'Chặn truy cập tới tên miền example.org và tất cả tên miền con', example_meaning_filter_block: 'block access to the example.org domain and all its subdomains',
'unblock access to the example.org domain and all its subdomains': 'Không chặn truy cập tới tên miền example.org và tất cả tên miền con', example_meaning_filter_whitelist: 'unblock access to the example.org domain and all its subdomains',
'AdGuard Home will now return 127.0.0.1 address for the example.org domain (but not its subdomains).': 'AdGuard Home sẽ phản hồi địa chỉ IP 127.0.0.1 cho tên miền example.org (không áp dụng tên miền con)', example_meaning_host_block: 'AdGuard Home will now return 127.0.0.1 address for the example.org domain (but not its subdomains).',
'! Here goes a comment': '! Đây là một chú thích', example_comment: '! Here goes a comment',
'just a comment': 'Chỉ là một chú thích', example_comment_meaning: 'just a comment',
'# Also a comment': '# Cũng là một chú thích', example_comment_hash: '# Also a comment',
'All filters are already up-to-date': 'Tất cả bộ lọc đã được cập nhật', all_filters_up_to_date_toast: 'All filters are already up-to-date',
'Updated the upstream DNS servers': 'Đã cập nhật máy chủ DNS tìm kiếm', updated_upstream_dns_toast: 'Updated the upstream DNS servers',
'Specified DNS servers are working correctly': 'Máy chủ DNS có thể sử dụng', dns_test_ok_toast: 'Specified DNS servers are working correctly',
'Server "{{key}}": could not be used, please check that you\'ve written it correctly': 'Máy chủ "{{key}}": không thể sử dụng, vui lòng kiểm tra bạn đã điền chính xác', dns_test_not_ok_toast: 'Server "{{key}}": could not be used, please check that you\'ve written it correctly',
// Logs // Logs
Unblock: 'Bỏ chặn', ublock_btn: 'Unblock',
Block: 'Chặn', block_btn: 'Block',
Time: 'Thời gian', time_table_header: 'Time',
'Domain name': 'Tên miền', domain_name_table_header: 'Domain name',
Type: 'Loại', type_table_header: 'Type',
Response: 'Phản hồi', response_table_header: 'Response',
Empty: 'Rỗng', empty_response_status: 'Empty',
'Show all': 'Hiện tất cả', show_all_filter_type: 'Show all',
'Show filtered': 'Chỉ hiện đã chặn', show_filtered_type: 'Show filtered',
'No logs found': 'Không có lịch sử truy vấn', no_logs_found: 'No logs found',
'Disable log': 'Tắt lịch sử truy vấn', disabled_log_btn: 'Disable log',
'Download log file': 'Tải tập tin lịch sử truy vấn', download_log_file_btn: 'Download log file',
Refresh: 'Làm mới', refresh_btn: 'Refresh',
'Enable log': 'Bật lịch sử truy vấn', enabled_log_btn: 'Enable log',
'Last 5000 DNS queries': '5000 truy vấn DNS gần nhất', last_5000_dns_queries: 'Last 5000 DNS queries',
Previous: 'Trang trước', previous_btn: 'Previous',
Next: 'Trang sau', next_btn: 'Next',
'Loading...': 'Đang tải...', loading_table_status: 'Loading...',
Page: 'Trang', page_table_footer_text: 'Page',
of: 'của', of_table_footer_text: 'of',
rows: 'hàng', rows_table_footer_text: 'rows',
'Updated the custom filtering rules': 'Đã cập nhật quy tắc lọc tuỳ chỉnh', updated_custom_filtering_toast: 'Updated the custom filtering rules',
'Rule removed from the custom filtering rules': 'Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh', rule_removed_from_custom_filtering_toast: 'Rule removed from the custom filtering rules',
'Rule added to the custom filtering rules': 'Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh', 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',
// Popover // Popover
Source: 'Nguồn', source_label: 'Source',
'Found in the known domains database.': 'Tìm thấy trong cơ sở dữ liệu tên miền', found_in_known_domain_db: 'Found in the known domains database.',
Category: 'Thể loại', category_label: 'Category',
// Popover filter // Popover filter
Rule: 'Quy tắc', rule_label: 'Rule',
Filter: 'Bộ lọc', filter_label: 'Filter',
}, },
} };

View File

@ -1,132 +1,134 @@
export default { export default {
translation: { translation: {
// Header // Header
Back: 'Quay lại', back: 'Quay lại',
Dashboard: 'Tổng quan', dashboard: 'Tổng quan',
Settings: 'Cài đặt', settings: 'Cài đặt',
Filters: 'Bộ lọc', filters: 'Bộ lọc',
'Query Log': 'Lịch sử truy vấn', query_log: 'Lịch sử truy vấn',
FAQ: 'Hỏi đáp', faq: 'Hỏi đáp',
version: 'phiên bản', version: 'phiên bản',
address: 'địa chỉ', address: 'địa chỉ',
ON: 'Đang bật', on: 'Đang bật',
OFF: 'Đang tắt', off: 'Đang tắt',
// Footer // Footer
Homepage: 'Trang chủ', homepage: 'Trang chủ',
'Report an issue': 'Báo lỗi', report_an_issue: 'Báo lỗi',
// Dashboard // Dashboard
'Enable protection': 'Bật bảo vệ', enable_protection: 'Bật bảo vệ',
'Disable protection': 'Tắt bảo vệ', enabled_protection: 'Đã bật bảo vệ',
'Refresh statistics': 'Làm mới thống kê', disable_protection: 'Tắt bảo vệ',
'DNS Queries': 'Truy vấn DNS', disabled_protection: 'Đã tắt bảo vệ',
'Blocked by': 'Chặn bởi', refresh_statics: 'Làm mới thống kê',
'Blocked malware/phishing': 'Mã độc/lừa đảo đã chặn', dns_query: 'Truy vấn DNS',
'Blocked adult websites': 'Website người lớn đã chặn', blocked_by: 'Chặn bởi',
'Top queried domains': 'Tên miền truy vấn nhiều', stats_malware_phishing: 'Mã độc/lừa đảo đã chặn',
'for the last 24 hours': 'trong 24 giờ qua', stats_adult: 'Website người lớn đã chặn',
'No domains found': 'Không có tên miền nào', stats_query_domain: 'Tên miền truy vấn nhiều',
'Requests count': 'Số lần yêu cầu', for_last_24_hours: 'trong 24 giờ qua',
'Top blocked domains': 'Tên miền chặn nhiều', no_domains_found: 'Không có tên miền nào',
'Top clients': 'Client dùng nhiều', requests_count: 'Số lần yêu cầu',
'No clients found': 'Không có client nào', top_blocked_domains: 'Tên miền chặn nhiều',
'General statistics': 'Thống kê chung', top_clients: 'Client dùng nhiều',
'A number of DNS quieries processed for the last 24 hours': 'Số yêu cầu DNS đã xử lý trong 24 giờ qua', no_clients_found: 'Không có client nào',
'A number of DNS requests blocked by adblock filters and hosts blocklists': 'Số yêu cầu DNS bị chặn bởi bộ lọc quảng cáo và danh sách chặn host', general_statistics: 'Thống kê chung',
'A number of DNS requests blocked by the AdGuard browsing security module': 'Số yêu cầu DNS bị chặn bởi chế độ bảo vệ duyệt web AdGuard', number_of_dns_query_24_hours: 'Số yêu cầu DNS đã xử lý trong 24 giờ qua',
'A number of adult websites blocked': 'Số website người lớn đã chặn', number_of_dns_query_blocked_24_hours: 'Số yêu cầu DNS bị chặn bởi bộ lọc quảng cáo và danh sách chặn host',
'Enforced safe search': 'Tìm kiếm an toàn', number_of_dns_query_blocked_24_hours_by_sec: 'Số yêu cầu DNS bị chặn bởi chế độ bảo vệ duyệt web AdGuard',
'A number of DNS requests to search engines for which Safe Search was enforced': 'Số yêu cầu DNS tới công cụ tìm kiếm đã chuyển thành tìm kiếm an toàn', number_of_dns_query_blocked_24_hours_adult: 'Số website người lớn đã chặn',
'Average processing time': 'Thời gian xử lý trung bình', enforced_save_search: 'Tìm kiếm an toàn',
'Average time in milliseconds on processing a DNS request': 'Thời gian trung bình cho một yêu cầu DNS tính bằng mili giây', number_of_dns_query_to_safe_search: 'Số yêu cầu DNS tới công cụ tìm kiếm đã chuyển thành tìm kiếm an toàn',
average_processing_time: 'Thời gian xử lý trung bình',
average_processing_time_hint: 'Thời gian trung bình cho một yêu cầu DNS tính bằng mili giây',
// Settings // Settings
'Block domains using filters and hosts files': 'Chặn tên miền sử dụng các bộ lọc và file hosts', block_domain_use_filters_and_hosts: 'Chặn tên miền sử dụng các bộ lọc và file hosts',
'You can setup blocking rules in the <a href="#filters">Filters</a> settings.': 'Bạn có thể thiết lập quy tắc chặn tại cài đặt <a href="#filters">Bộ lọc</a>.', filters_block_toggle_hint: 'Bạn có thể thiết lập quy tắc chặn tại cài đặt <a href="#filters">Bộ lọc</a>.',
'Use AdGuard browsing security web service': 'Sử dụng dịch vụ bảo vệ duyệt web AdGuard', use_adguard_browsing_sec: 'Sử dụng dịch vụ bảo vệ duyệt web AdGuard',
'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.': 'AdGuard Home sẽ kiểm tra tên miền với dịch vụ bảo vệ duyệt web. Tính năng sử dụng một API thân thiện với quyền riêng tư: chỉ một phần ngắn tiền tố mã băm SHA256 được gửi đến máy chủ', use_adguard_browsing_sec_hint: 'AdGuard Home sẽ kiểm tra tên miền với dịch vụ bảo vệ duyệt web. Tính năng sử dụng một API thân thiện với quyền riêng tư: chỉ một phần ngắn tiền tố mã băm SHA256 được gửi đến máy chủ',
'Use AdGuard parental control web service': 'Sử dụng dịch vụ quản lý của phụ huynh AdGuard', use_adguard_parental: 'Sử dụng dịch vụ quản lý của phụ huynh AdGuard',
'AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.': 'AdGuard Home sẽ kiểm tra nếu tên miền chứa từ khoá người lớn. Tính năng sử dụng API thân thiện với quyền riêng tư tương tự với dịch vụ bảo vệ duyệt web', use_adguard_parental_hint: 'AdGuard Home sẽ kiểm tra nếu tên miền chứa từ khoá người lớn. Tính năng sử dụng API thân thiện với quyền riêng tư tương tự với dịch vụ bảo vệ duyệt web',
'Enforce safe search': 'Bắt buộc tìm kiếm an toàn', enforce_safe_search: 'Bắt buộc tìm kiếm an toàn',
'AdGuard Home can enforce safe search in the following search engines: Google, Bing, Yandex.': 'AdGuard Home có thể bắt buộc tìm kiếm an toàn với các dịch vụ tìm kiếm: Google, Bing, Yandex.', enforce_save_search_hint: 'AdGuard Home có thể bắt buộc tìm kiếm an toàn với các dịch vụ tìm kiếm: Google, Bing, Yandex.',
'No servers specified': 'Không có máy chủ nào được liệt kê', no_servers_specified: 'Không có máy chủ nào được liệt kê',
'No settings': 'Không có cài đặt nào', no_settings: 'Không có cài đặt nào',
'General settings': 'Cài đặt chung', general_settings: 'Cài đặt chung',
'Upstream DNS servers': 'Máy chủ DNS tìm kiếm', upstream_dns: 'Máy chủ DNS tìm kiếm',
'If you keep this field empty, AdGuard Home will use <a href="https://1.1.1.1/" target="_blank">Cloudflare DNS</a> as an upstream. Use tls:// prefix for DNS over TLS servers.': 'Nếu bạn để trống mục này, AdGuard Home sẽ sử dụng <a href="https://1.1.1.1/" target="_blank">Cloudflare DNS</a> để tìm kiếm. Sử dụng tiền tố tls:// cho các máy chủ DNS dựa trên TLS.', upstream_dns_hint: 'Nếu bạn để trống mục này, AdGuard Home sẽ sử dụng <a href="https://1.1.1.1/" target="_blank">Cloudflare DNS</a> để tìm kiếm. Sử dụng tiền tố tls:// cho các máy chủ DNS dựa trên TLS.',
'Test upstreams': 'Kiểm tra', test_upstream_btn: 'Kiểm tra',
Apply: 'Áp dụng', apply_btn: 'Áp dụng',
// Settings Toasts // Settings Toasts
'Disabled filtering': 'Đã tắt chặn quảng cáo', disabled_filtering_toast: 'Đã tắt chặn quảng cáo',
'Enabled filtering': 'Đã bật chặn quảng cáo', enabled_filtering_toast: 'Đã bật chặn quảng cáo',
'Disabled safebrowsing': 'Đã tắt bảo vệ duyệt web', disabled_safe_browsing_toast: 'Đã tắt bảo vệ duyệt web',
'Enabled safebrowsing': 'Đã bật bảo vệ duyệt web', enabled_safe_browsing_toast: 'Đã bật bảo vệ duyệt web',
'Disabled parental control': 'Đã tắt quản lý của phụ huynh', disabled_parental_toast: 'Đã tắt quản lý của phụ huynh',
'Enabled parental control': 'Đã bật quản lý của phụ huynh', enabled_parental_toast: 'Đã bật quản lý của phụ huynh',
'Disabled safe search': 'Đã tắt tìm kiếm an toàn', disabled_safe_search_toast: 'Đã tắt tìm kiếm an toàn',
'Enabled safe search': 'Đã bật tìm kiếm an toàn', enabled_save_search_toast: 'Đã bật tìm kiếm an toàn',
// Filters // Filters
Enabled: 'Kích hoạt', enabled_table_header: 'Kích hoạt',
Name: 'Tên', name_table_header: 'Tên',
'Filter URL': 'URL bộ lọc', filter_url_table_header: 'URL bộ lọc',
'Rules count': 'Số quy tắc', rules_count_table_header: 'Số quy tắc',
'Last time updated': 'Cập nhật cuối', last_time_updated_table_header: 'Cập nhật cuối',
Actions: 'Thao tác', actions_table_header: 'Thao tác',
Delete: 'Xoá', delete_table_action: 'Xoá',
'Filters and hosts blocklists': 'Danh sách bộ lọc và hosts', filters_and_hosts: 'Danh sách bộ lọc và hosts',
'AdGuard Home understands basic adblock rules and hosts files syntax.': 'AdGuard home hiểu các quy tắc chặn quảng cáo đơn giản và cú pháp file hosts', filters_and_hosts_hint: 'AdGuard home hiểu các quy tắc chặn quảng cáo đơn giản và cú pháp file hosts',
'No filters added': 'Không có bộ lọc nào được thêm', no_filters_added: 'Không có bộ lọc nào được thêm',
'Add filter': 'Thêm bộ lọc', add_filter_btn: 'Thêm bộ lọc',
Cancel: 'Huỷ', cancel_btn: 'Huỷ',
'Enter name': 'Nhập tên', enter_name_hint: 'Nhập tên',
'Enter URL': 'Nhập URL', enter_url_hint: 'Nhập URL',
'Check updates': 'Kiểm tra cập nhật', check_updates_btn: 'Kiểm tra cập nhật',
'New filter subscription': 'Đăng ký bộ lọc mới', new_filter_btn: 'Đăng ký bộ lọc mới',
'Enter a valid URL to a filter subscription or a hosts file.': 'Nhập URL hợp lệ của bộ lọc hoặc file hosts', enter_valid_filter_url: 'Nhập URL hợp lệ của bộ lọc hoặc file hosts',
'Custom filtering rules': 'Quy tắc lọc tuỳ chỉnh', custom_filter_rules: 'Quy tắc lọc tuỳ chỉnh',
'Enter one rule on a line. You can use either adblock rules or hosts files syntax.': 'Nhập mỗi quy tắc 1 dòng. Có thể sử dụng quy tắc chặn quảng cáo hoặc cú pháp file host', custom_filter_rules_hint: 'Nhập mỗi quy tắc 1 dòng. Có thể sử dụng quy tắc chặn quảng cáo hoặc cú pháp file host',
Examples: 'Ví dụ', examples_title: 'Ví dụ',
'block access to the example.org domain and all its subdomains': 'Chặn truy cập tới tên miền example.org và tất cả tên miền con', example_meaning_filter_block: 'Chặn truy cập tới tên miền example.org và tất cả tên miền con',
'unblock access to the example.org domain and all its subdomains': 'Không chặn truy cập tới tên miền example.org và tất cả tên miền con', example_meaning_filter_whitelist: 'Không chặn truy cập tới tên miền example.org và tất cả tên miền con',
'AdGuard Home will now return 127.0.0.1 address for the example.org domain (but not its subdomains).': 'AdGuard Home sẽ phản hồi địa chỉ IP 127.0.0.1 cho tên miền example.org (không áp dụng tên miền con)', example_meaning_host_block: 'AdGuard Home sẽ phản hồi địa chỉ IP 127.0.0.1 cho tên miền example.org (không áp dụng tên miền con)',
'! Here goes a comment': '! Đây là một chú thích', example_comment: '! Đây là một chú thích',
'just a comment': 'Chỉ là một chú thích', example_comment_meaning: 'Chỉ là một chú thích',
'# Also a comment': '# Cũng là một chú thích', example_comment_hash: '# Cũng là một chú thích',
'All filters are already up-to-date': 'Tất cả bộ lọc đã được cập nhật', all_filters_up_to_date_toast: 'Tất cả bộ lọc đã được cập nhật',
'Updated the upstream DNS servers': 'Đã cập nhật máy chủ DNS tìm kiếm', updated_upstream_dns_toast: 'Đã cập nhật máy chủ DNS tìm kiếm',
'Specified DNS servers are working correctly': 'Máy chủ DNS có thể sử dụng', dns_test_ok_toast: 'Máy chủ DNS có thể sử dụng',
'Server "{{key}}": could not be used, please check that you\'ve written it correctly': 'Máy chủ "{{key}}": không thể sử dụng, vui lòng kiểm tra bạn đã điền chính xác', dns_test_not_ok_toast: 'Máy chủ "{{key}}": không thể sử dụng, vui lòng kiểm tra bạn đã điền chính xác',
// Logs // Logs
Unblock: 'Bỏ chặn', ublock_btn: 'Bỏ chặn',
Block: 'Chặn', block_btn: 'Chặn',
Time: 'Thời gian', time_table_header: 'Thời gian',
'Domain name': 'Tên miền', domain_name_table_header: 'Tên miền',
Type: 'Loại', type_table_header: 'Loại',
Response: 'Phản hồi', response_table_header: 'Phản hồi',
Empty: 'Rỗng', empty_response_status: 'Rỗng',
'Show all': 'Hiện tất cả', show_all_filter_type: 'Hiện tất cả',
'Show filtered': 'Chỉ hiện đã chặn', show_filtered_type: 'Chỉ hiện đã chặn',
'No logs found': 'Không có lịch sử truy vấn', no_logs_found: 'Không có lịch sử truy vấn',
'Disable log': 'Tắt lịch sử truy vấn', disabled_log_btn: 'Tắt lịch sử truy vấn',
'Download log file': 'Tải tập tin lịch sử truy vấn', download_log_file_btn: 'Tải tập tin lịch sử truy vấn',
Refresh: 'Làm mới', refresh_btn: 'Làm mới',
'Enable log': 'Bật lịch sử truy vấn', enabled_log_btn: 'Bật lịch sử truy vấn',
'Last 5000 DNS queries': '5000 truy vấn DNS gần nhất', last_5000_dns_queries: '5000 truy vấn DNS gần nhất',
Previous: 'Trang trước', previous_btn: 'Trang trước',
Next: 'Trang sau', next_btn: 'Trang sau',
'Loading...': 'Đang tải...', loading_table_status: 'Đang tải...',
Page: 'Trang', page_table_footer_text: 'Trang',
of: 'của', of_table_footer_text: 'của',
rows: 'hàng', rows_table_footer_text: 'hàng',
'Updated the custom filtering rules': 'Đã cập nhật quy tắc lọc tuỳ chỉnh', updated_custom_filtering_toast: 'Đã cập nhật quy tắc lọc tuỳ chỉnh',
'Rule removed from the custom filtering rules': 'Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh', rule_removed_from_custom_filtering_toast: 'Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh',
'Rule added to the custom filtering rules': 'Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh', rule_added_to_custom_filtering_toast: 'Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh',
'Query log disabled': 'Đã bật lịch sử truy vấn', query_log_disabled_toast: 'Đã tắt lịch sử truy vấn',
'Query log enabled': 'Đã tắt lịch sử truy vấn', query_log_enabled_toast: 'Đã bật lịch sử truy vấn',
// Popover // Popover
Source: 'Nguồn', source_label: 'Nguồn',
'Found in the known domains database.': 'Tìm thấy trong cơ sở dữ liệu tên miền', found_in_known_domain_db: 'Tìm thấy trong cơ sở dữ liệu tên miền',
Category: 'Thể loại', category_label: 'Thể loại',
// Popover filter // Popover filter
Rule: 'Quy tắc', rule_label: 'Quy tắc',
Filter: 'Bộ lọc', filter_label: 'Bộ lọc',
}, },
}; };

View File

@ -22,40 +22,40 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
switch (settingKey) { switch (settingKey) {
case 'filtering': case 'filtering':
if (status) { if (status) {
successMessage = 'Disabled filtering'; successMessage = 'disabled_filtering_toast';
await apiClient.disableFiltering(); await apiClient.disableFiltering();
} else { } else {
successMessage = 'Enabled filtering'; successMessage = 'enabled_filtering_toast';
await apiClient.enableFiltering(); await apiClient.enableFiltering();
} }
dispatch(toggleSettingStatus({ settingKey })); dispatch(toggleSettingStatus({ settingKey }));
break; break;
case 'safebrowsing': case 'safebrowsing':
if (status) { if (status) {
successMessage = 'Disabled safebrowsing'; successMessage = 'disabled_safe_browsing_toast';
await apiClient.disableSafebrowsing(); await apiClient.disableSafebrowsing();
} else { } else {
successMessage = 'Enabled safebrowsing'; successMessage = 'enabled_safe_browsing_toast';
await apiClient.enableSafebrowsing(); await apiClient.enableSafebrowsing();
} }
dispatch(toggleSettingStatus({ settingKey })); dispatch(toggleSettingStatus({ settingKey }));
break; break;
case 'parental': case 'parental':
if (status) { if (status) {
successMessage = 'Disabled parental control'; successMessage = 'disabled_parental_toast';
await apiClient.disableParentalControl(); await apiClient.disableParentalControl();
} else { } else {
successMessage = 'Enabled parental control'; successMessage = 'enabled_parental_toast';
await apiClient.enableParentalControl(); await apiClient.enableParentalControl();
} }
dispatch(toggleSettingStatus({ settingKey })); dispatch(toggleSettingStatus({ settingKey }));
break; break;
case 'safesearch': case 'safesearch':
if (status) { if (status) {
successMessage = 'Disabled safe search'; successMessage = 'disabled_safe_search_toast';
await apiClient.disableSafesearch(); await apiClient.disableSafesearch();
} else { } else {
successMessage = 'Enabled safe search'; successMessage = 'enabled_save_search_toast';
await apiClient.enableSafesearch(); await apiClient.enableSafesearch();
} }
dispatch(toggleSettingStatus({ settingKey })); dispatch(toggleSettingStatus({ settingKey }));
@ -124,10 +124,10 @@ export const toggleProtection = status => async (dispatch) => {
try { try {
if (status) { if (status) {
successMessage = 'Disabled protection'; successMessage = 'disabled_protection';
await apiClient.disableGlobalProtection(); await apiClient.disableGlobalProtection();
} else { } else {
successMessage = 'Enabled protection'; successMessage = 'enabled_protection';
await apiClient.enableGlobalProtection(); await apiClient.enableGlobalProtection();
} }
@ -272,14 +272,14 @@ export const toggleLogStatus = queryLogEnabled => async (dispatch) => {
let successMessage; let successMessage;
if (queryLogEnabled) { if (queryLogEnabled) {
toggleMethod = apiClient.disableQueryLog.bind(apiClient); toggleMethod = apiClient.disableQueryLog.bind(apiClient);
successMessage = 'disabled'; successMessage = 'query_log_disabled_toast';
} else { } else {
toggleMethod = apiClient.enableQueryLog.bind(apiClient); toggleMethod = apiClient.enableQueryLog.bind(apiClient);
successMessage = 'enabled'; successMessage = 'query_log_enabled_toast';
} }
try { try {
await toggleMethod(); await toggleMethod();
dispatch(addSuccessToast(`Query log ${successMessage}`)); dispatch(addSuccessToast(successMessage));
dispatch(toggleLogStatusSuccess()); dispatch(toggleLogStatusSuccess());
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
@ -298,7 +298,7 @@ export const setRules = rules => async (dispatch) => {
.replace(/^\n/g, '') .replace(/^\n/g, '')
.replace(/\n\s*\n/g, '\n'); .replace(/\n\s*\n/g, '\n');
await apiClient.setRules(replacedLineEndings); await apiClient.setRules(replacedLineEndings);
dispatch(addSuccessToast('Updated the custom filtering rules')); dispatch(addSuccessToast('updated_custom_filtering_toast'));
dispatch(setRulesSuccess()); dispatch(setRulesSuccess());
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
@ -360,7 +360,7 @@ export const refreshFilters = () => async (dispatch) => {
if (refreshText.includes('OK')) { if (refreshText.includes('OK')) {
if (refreshText.includes('OK 0')) { if (refreshText.includes('OK 0')) {
dispatch(addSuccessToast('All filters are already up-to-date')); dispatch(addSuccessToast('all_filters_up_to_date_toast'));
} else { } else {
dispatch(addSuccessToast(refreshText.replace(/OK /g, ''))); dispatch(addSuccessToast(refreshText.replace(/OK /g, '')));
} }
@ -457,7 +457,7 @@ export const setUpstream = url => async (dispatch) => {
dispatch(setUpstreamRequest()); dispatch(setUpstreamRequest());
try { try {
await apiClient.setUpstream(url); await apiClient.setUpstream(url);
dispatch(addSuccessToast('Updated the upstream DNS servers')); dispatch(addSuccessToast('updated_upstream_dns_toast'));
dispatch(setUpstreamSuccess()); dispatch(setUpstreamSuccess());
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
@ -477,13 +477,13 @@ export const testUpstream = servers => async (dispatch) => {
const testMessages = Object.keys(upstreamResponse).map((key) => { const testMessages = Object.keys(upstreamResponse).map((key) => {
const message = upstreamResponse[key]; const message = upstreamResponse[key];
if (message !== 'OK') { if (message !== 'OK') {
dispatch(addErrorToast({ error: t('Server "{{key}}": could not be used, please check that you\'ve written it correctly', { key }) })); dispatch(addErrorToast({ error: t('dns_test_not_ok_toast', { key }) }));
} }
return message; return message;
}); });
if (testMessages.every(message => message === 'OK')) { if (testMessages.every(message => message === 'OK')) {
dispatch(addSuccessToast('Specified DNS servers are working correctly')); dispatch(addSuccessToast('dns_test_ok_toast'));
} }
dispatch(testUpstreamSuccess()); dispatch(testUpstreamSuccess());

View File

@ -30,7 +30,7 @@ class BlockedDomains extends Component {
); );
}, },
}, { }, {
Header: <Trans>Requests count</Trans>, Header: <Trans>requests_count</Trans>,
accessor: 'domain', accessor: 'domain',
maxWidth: 190, maxWidth: 190,
Cell: ({ value }) => { Cell: ({ value }) => {
@ -51,14 +51,14 @@ class BlockedDomains extends Component {
render() { render() {
const { t } = this.props; const { t } = this.props;
return ( return (
<Card title={ t('Top blocked domains') } subtitle={ t('for the last 24 hours') } bodyType="card-table" refresh={this.props.refreshButton}> <Card title={ t('top_blocked_domains') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable <ReactTable
data={map(this.props.topBlockedDomains, (value, prop) => ( data={map(this.props.topBlockedDomains, (value, prop) => (
{ ip: prop, domain: value } { ip: prop, domain: value }
))} ))}
columns={this.columns} columns={this.columns}
showPagination={false} showPagination={false}
noDataText={ t('No domains found') } noDataText={ t('no_domains_found') }
minRows={6} minRows={6}
className="-striped -highlight card-table-overflow stats__table" className="-striped -highlight card-table-overflow stats__table"
/> />

View File

@ -26,7 +26,7 @@ class Clients extends Component {
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>), Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10), sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
}, { }, {
Header: <Trans>Requests count</Trans>, Header: <Trans>requests_count</Trans>,
accessor: 'count', accessor: 'count',
Cell: ({ value }) => { Cell: ({ value }) => {
const percent = getPercent(this.props.dnsQueries, value); const percent = getPercent(this.props.dnsQueries, value);
@ -41,14 +41,14 @@ class Clients extends Component {
render() { render() {
const { t } = this.props; const { t } = this.props;
return ( return (
<Card title={ t('Top clients') } subtitle={ t('for the last 24 hours') } bodyType="card-table" refresh={this.props.refreshButton}> <Card title={ t('top_clients') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable <ReactTable
data={map(this.props.topClients, (value, prop) => ( data={map(this.props.topClients, (value, prop) => (
{ ip: prop, count: value } { ip: prop, count: value }
))} ))}
columns={this.columns} columns={this.columns}
showPagination={false} showPagination={false}
noDataText={ t('No clients found') } noDataText={ t('no_clients_found') }
minRows={6} minRows={6}
className="-striped -highlight card-table-overflow" className="-striped -highlight card-table-overflow"
/> />

View File

@ -8,13 +8,13 @@ import Tooltip from '../ui/Tooltip';
const tooltipType = 'tooltip-custom--narrow'; const tooltipType = 'tooltip-custom--narrow';
const Counters = props => ( const Counters = props => (
<Card title={ props.t('General statistics') } subtitle={ props.t('for the last 24 hours') } bodyType="card-table" refresh={props.refreshButton}> <Card title={ props.t('general_statistics') } subtitle={ props.t('for_last_24_hours') } bodyType="card-table" refresh={props.refreshButton}>
<table className="table card-table"> <table className="table card-table">
<tbody> <tbody>
<tr> <tr>
<td> <td>
<Trans>DNS Queries</Trans> <Trans>dns_query</Trans>
<Tooltip text={ props.t('A number of DNS quieries processed for the last 24 hours') } type={tooltipType} /> <Tooltip text={ props.t('number_of_dns_query_24_hours') } type={tooltipType} />
</td> </td>
<td className="text-right"> <td className="text-right">
<span className="text-muted"> <span className="text-muted">
@ -24,8 +24,8 @@ const Counters = props => (
</tr> </tr>
<tr> <tr>
<td> <td>
<Trans>Blocked by</Trans> <a href="#filters"><Trans>Filters</Trans></a> <Trans>blocked_by</Trans> <a href="#filters"><Trans>Filters</Trans></a>
<Tooltip text={ props.t('A number of DNS requests blocked by adblock filters and hosts blocklists') } type={tooltipType} /> <Tooltip text={ props.t('number_of_dns_query_blocked_24_hours') } type={tooltipType} />
</td> </td>
<td className="text-right"> <td className="text-right">
<span className="text-muted"> <span className="text-muted">
@ -35,8 +35,8 @@ const Counters = props => (
</tr> </tr>
<tr> <tr>
<td> <td>
<Trans>Blocked malware/phishing</Trans> <Trans>stats_malware_phishing</Trans>
<Tooltip text={ props.t('A number of DNS requests blocked by the AdGuard browsing security module') } type={tooltipType} /> <Tooltip text={ props.t('number_of_dns_query_blocked_24_hours_by_sec') } type={tooltipType} />
</td> </td>
<td className="text-right"> <td className="text-right">
<span className="text-muted"> <span className="text-muted">
@ -46,8 +46,8 @@ const Counters = props => (
</tr> </tr>
<tr> <tr>
<td> <td>
<Trans>Blocked adult websites</Trans> <Trans>stats_adult</Trans>
<Tooltip text={ props.t('A number of adult websites blocked') } type={tooltipType} /> <Tooltip text={ props.t('number_of_dns_query_blocked_24_hours_adult') } type={tooltipType} />
</td> </td>
<td className="text-right"> <td className="text-right">
<span className="text-muted"> <span className="text-muted">
@ -57,8 +57,8 @@ const Counters = props => (
</tr> </tr>
<tr> <tr>
<td> <td>
<Trans>Enforced safe search</Trans> <Trans>enforced_save_search</Trans>
<Tooltip text={ props.t('A number of DNS requests to search engines for which Safe Search was enforced') } type={tooltipType} /> <Tooltip text={ props.t('number_of_dns_query_to_safe_search') } type={tooltipType} />
</td> </td>
<td className="text-right"> <td className="text-right">
<span className="text-muted"> <span className="text-muted">
@ -68,8 +68,8 @@ const Counters = props => (
</tr> </tr>
<tr> <tr>
<td> <td>
<Trans>Average processing time</Trans> <Trans>average_processing_time</Trans>
<Tooltip text={ props.t('Average time in milliseconds on processing a DNS request') } type={tooltipType} /> <Tooltip text={ props.t('average_processing_time_hint') } type={tooltipType} />
</td> </td>
<td className="text-right"> <td className="text-right">
<span className="text-muted"> <span className="text-muted">

View File

@ -39,7 +39,7 @@ class QueriedDomains extends Component {
); );
}, },
}, { }, {
Header: <Trans>Requests count</Trans>, Header: <Trans>requests_count</Trans>,
accessor: 'count', accessor: 'count',
maxWidth: 190, maxWidth: 190,
Cell: ({ value }) => { Cell: ({ value }) => {
@ -55,14 +55,14 @@ class QueriedDomains extends Component {
render() { render() {
const { t } = this.props; const { t } = this.props;
return ( return (
<Card title={ t('Top queried domains') } subtitle={ t('for the last 24 hours') } bodyType="card-table" refresh={this.props.refreshButton}> <Card title={ t('stats_query_domain') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable <ReactTable
data={map(this.props.topQueriedDomains, (value, prop) => ( data={map(this.props.topQueriedDomains, (value, prop) => (
{ ip: prop, count: value } { ip: prop, count: value }
))} ))}
columns={this.columns} columns={this.columns}
showPagination={false} showPagination={false}
noDataText={ t('No domains found') } noDataText={ t('no_domains_found') }
minRows={6} minRows={6}
className="-striped -highlight card-table-overflow stats__table" className="-striped -highlight card-table-overflow stats__table"
/> />

View File

@ -31,7 +31,7 @@ class Statistics extends Component {
{dnsQueries} {dnsQueries}
</div> </div>
<div className="card-title-stats"> <div className="card-title-stats">
<Trans>DNS Queries</Trans> <Trans>dns_query</Trans>
</div> </div>
</div> </div>
<div className="card-chart-bg"> <div className="card-chart-bg">
@ -49,7 +49,7 @@ class Statistics extends Component {
{getPercent(dnsQueries, blockedFiltering)} {getPercent(dnsQueries, blockedFiltering)}
</div> </div>
<div className="card-title-stats"> <div className="card-title-stats">
<Trans>Blocked by</Trans> <a href="#filters"><Trans>Filters</Trans></a> <Trans>blocked_by</Trans> <a href="#filters"><Trans>Filters</Trans></a>
</div> </div>
</div> </div>
<div className="card-chart-bg"> <div className="card-chart-bg">
@ -67,7 +67,7 @@ class Statistics extends Component {
{getPercent(dnsQueries, replacedSafebrowsing)} {getPercent(dnsQueries, replacedSafebrowsing)}
</div> </div>
<div className="card-title-stats"> <div className="card-title-stats">
<Trans>Blocked malware/phishing</Trans> <Trans>stats_malware_phishing</Trans>
</div> </div>
</div> </div>
<div className="card-chart-bg"> <div className="card-chart-bg">
@ -85,7 +85,7 @@ class Statistics extends Component {
{getPercent(dnsQueries, replacedParental)} {getPercent(dnsQueries, replacedParental)}
</div> </div>
<div className="card-title-stats"> <div className="card-title-stats">
<Trans>Blocked adult websites</Trans> <Trans>stats_adult</Trans>
</div> </div>
</div> </div>
<div className="card-chart-bg"> <div className="card-chart-bg">

View File

@ -26,12 +26,12 @@ class Dashboard extends Component {
getToggleFilteringButton = () => { getToggleFilteringButton = () => {
const { protectionEnabled } = this.props.dashboard; const { protectionEnabled } = this.props.dashboard;
const buttonText = protectionEnabled ? 'Disable' : 'Enable'; const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection';
const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success'; const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success';
return ( return (
<button type="button" className={`btn btn-sm mr-2 ${buttonClass}`} onClick={() => this.props.toggleProtection(protectionEnabled)}> <button type="button" className={`btn btn-sm mr-2 ${buttonClass}`} onClick={() => this.props.toggleProtection(protectionEnabled)}>
<Trans>{buttonText} protection</Trans> <Trans>{buttonText}</Trans>
</button> </button>
); );
} }
@ -44,12 +44,12 @@ class Dashboard extends Component {
dashboard.processingStatsHistory || dashboard.processingStatsHistory ||
dashboard.processingTopStats; dashboard.processingTopStats;
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}><Trans>Refresh statistics</Trans></button>; const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}><Trans>refresh_statics</Trans></button>;
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.getAllStats()} />; const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.getAllStats()} />;
return ( return (
<Fragment> <Fragment>
<PageTitle title={ t('Dashboard') }> <PageTitle title={ t('dashboard') }>
<div className="page-title__actions"> <div className="page-title__actions">
{this.getToggleFilteringButton()} {this.getToggleFilteringButton()}
{refreshFullButton} {refreshFullButton}

View File

@ -18,8 +18,8 @@ class UserRules extends Component {
const { t } = this.props; const { t } = this.props;
return ( return (
<Card <Card
title={ t('Custom filtering rules') } title={ t('custom_filter_rules') }
subtitle={ t('Enter one rule on a line. You can use either adblock rules or hosts files syntax.') } subtitle={ t('custom_filter_rules_hint') }
> >
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<textarea className="form-control form-control--textarea-large" value={this.props.userRules} onChange={this.handleChange} /> <textarea className="form-control form-control--textarea-large" value={this.props.userRules} onChange={this.handleChange} />
@ -29,28 +29,28 @@ class UserRules extends Component {
type="submit" type="submit"
onClick={this.handleSubmit} onClick={this.handleSubmit}
> >
<Trans>Apply</Trans> <Trans>apply_btn</Trans>
</button> </button>
</div> </div>
</form> </form>
<hr/> <hr/>
<div className="list leading-loose"> <div className="list leading-loose">
<Trans>Examples</Trans>: <Trans>examples_title</Trans>:
<ol className="leading-loose"> <ol className="leading-loose">
<li> <li>
<code>||example.org^</code> - { t('block access to the example.org domain and all its subdomains') } <code>||example.org^</code> - { t('example_meaning_filter_block') }
</li> </li>
<li> <li>
<code> @@||example.org^</code> - { t('unblock access to the example.org domain and all its subdomains') } <code> @@||example.org^</code> - { t('example_meaning_filter_whitelist') }
</li> </li>
<li> <li>
<code>127.0.0.1 example.org</code> - { t('AdGuard Home will now return 127.0.0.1 address for the example.org domain (but not its subdomains).') } <code>127.0.0.1 example.org</code> - { t('example_comment') }
</li> </li>
<li> <li>
<code>{ t('! Here goes a comment') }</code> - { t('just a comment') } <code>{ t('example_comment') }</code> - { t('example_comment_meaning') }
</li> </li>
<li> <li>
<code>{ t('# Also a comment') }</code> - { t('just a comment') } <code>{ t('example_comment_hash') }</code> - { t('example_comment_meaning') }
</li> </li>
</ol> </ol>
</div> </div>

View File

@ -34,32 +34,32 @@ class Filters extends Component {
}; };
columns = [{ columns = [{
Header: this.props.t('Enabled'), Header: this.props.t('enabled_table_header'),
accessor: 'enabled', accessor: 'enabled',
Cell: this.renderCheckbox, Cell: this.renderCheckbox,
width: 90, width: 90,
className: 'text-center', className: 'text-center',
}, { }, {
Header: this.props.t('Name'), Header: this.props.t('name_table_header'),
accessor: 'name', accessor: 'name',
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>), Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
}, { }, {
Header: this.props.t('Filter URL'), Header: this.props.t('filter_url_table_header'),
accessor: 'url', accessor: 'url',
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><a href={value} target='_blank' rel='noopener noreferrer' className="link logs__text">{value}</a></div>), Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><a href={value} target='_blank' rel='noopener noreferrer' className="link logs__text">{value}</a></div>),
}, { }, {
Header: this.props.t('Rules count'), Header: this.props.t('rules_count_table_header'),
accessor: 'rulesCount', accessor: 'rulesCount',
className: 'text-center', className: 'text-center',
Cell: props => props.value.toLocaleString(), Cell: props => props.value.toLocaleString(),
}, { }, {
Header: this.props.t('Last time updated'), Header: this.props.t('last_time_updated_table_header'),
accessor: 'lastUpdated', accessor: 'lastUpdated',
className: 'text-center', className: 'text-center',
}, { }, {
Header: this.props.t('Actions'), Header: this.props.t('actions_table_header'),
accessor: 'url', accessor: 'url',
Cell: ({ value }) => (<span title={ this.props.t('Delete') } className='remove-icon fe fe-trash-2' onClick={() => this.props.removeFilter(value)}/>), Cell: ({ value }) => (<span title={ this.props.t('delete_table_action') } className='remove-icon fe fe-trash-2' onClick={() => this.props.removeFilter(value)}/>),
className: 'text-center', className: 'text-center',
width: 75, width: 75,
sortable: false, sortable: false,
@ -71,24 +71,24 @@ class Filters extends Component {
const { filters, userRules } = this.props.filtering; const { filters, userRules } = this.props.filtering;
return ( return (
<div> <div>
<PageTitle title={ t('Filters') } /> <PageTitle title={ t('filters') } />
<div className="content"> <div className="content">
<div className="row"> <div className="row">
<div className="col-md-12"> <div className="col-md-12">
<Card <Card
title={ t('Filters and hosts blocklists') } title={ t('filters_and_hosts') }
subtitle={ t('AdGuard Home understands basic adblock rules and hosts files syntax.') } subtitle={ t('filters_and_hosts_hint') }
> >
<ReactTable <ReactTable
data={filters} data={filters}
columns={this.columns} columns={this.columns}
showPagination={false} showPagination={false}
noDataText={ t('No filters added') } noDataText={ t('no_filters_added') }
minRows={4} // TODO find out what to show if rules.length is 0 minRows={4} // TODO find out what to show if rules.length is 0
/> />
<div className="card-actions"> <div className="card-actions">
<button className="btn btn-success btn-standart mr-2" type="submit" onClick={this.props.toggleFilteringModal}><Trans>Add filter</Trans></button> <button className="btn btn-success btn-standart mr-2" type="submit" onClick={this.props.toggleFilteringModal}><Trans>add_filter_btn</Trans></button>
<button className="btn btn-primary btn-standart" type="submit" onClick={this.props.refreshFilters}><Trans>Check updates</Trans></button> <button className="btn btn-primary btn-standart" type="submit" onClick={this.props.refreshFilters}><Trans>cancel_btn</Trans></button>
</div> </div>
</Card> </Card>
</div> </div>
@ -106,8 +106,8 @@ class Filters extends Component {
toggleModal={this.props.toggleFilteringModal} toggleModal={this.props.toggleFilteringModal}
addFilter={this.props.addFilter} addFilter={this.props.addFilter}
isFilterAdded={this.props.filtering.isFilterAdded} isFilterAdded={this.props.filtering.isFilterAdded}
title={ t('New filter subscription') } title={ t('new_filter_btn') }
inputDescription={ t('Enter a valid URL to a filter subscription or a hosts file.') } inputDescription={ t('enter_valid_filter_url') }
/> />
</div> </div>
); );

View File

@ -28,37 +28,37 @@ class Menu extends Component {
<li className="nav-item border-bottom d-lg-none" onClick={this.toggleMenu}> <li className="nav-item border-bottom d-lg-none" onClick={this.toggleMenu}>
<div className="nav-link nav-link--back"> <div className="nav-link nav-link--back">
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m19 12h-14"/><path d="m12 19-7-7 7-7"/></svg> <svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m19 12h-14"/><path d="m12 19-7-7 7-7"/></svg>
<Trans>Back</Trans> <Trans>back</Trans>
</div> </div>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<NavLink to="/" exact={true} className="nav-link"> <NavLink to="/" exact={true} className="nav-link">
<svg className="nav-icon" fill="none" height="24" stroke="#9aa0ac" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 9 9-7 9 7v11a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2-2z"/><path d="m9 22v-10h6v10"/></svg> <svg className="nav-icon" fill="none" height="24" stroke="#9aa0ac" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 9 9-7 9 7v11a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2-2z"/><path d="m9 22v-10h6v10"/></svg>
<Trans>Dashboard</Trans> <Trans>dashboard</Trans>
</NavLink> </NavLink>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<NavLink to="/settings" className="nav-link"> <NavLink to="/settings" className="nav-link">
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="3"/><path d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z"/></svg> <svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="3"/><path d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z"/></svg>
<Trans>Settings</Trans> <Trans>settings</Trans>
</NavLink> </NavLink>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<NavLink to="/filters" className="nav-link"> <NavLink to="/filters" className="nav-link">
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m22 3h-20l8 9.46v6.54l4 2v-8.54z"/></svg> <svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m22 3h-20l8 9.46v6.54l4 2v-8.54z"/></svg>
<Trans>Filters</Trans> <Trans>filters</Trans>
</NavLink> </NavLink>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<NavLink to="/logs" className="nav-link"> <NavLink to="/logs" className="nav-link">
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14 2h-8a2 2 0 0 0 -2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-12z"/><path d="m14 2v6h6"/><path d="m16 13h-8"/><path d="m16 17h-8"/><path d="m10 9h-1-1"/></svg> <svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14 2h-8a2 2 0 0 0 -2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-12z"/><path d="m14 2v6h6"/><path d="m16 13h-8"/><path d="m16 17h-8"/><path d="m10 9h-1-1"/></svg>
<Trans>Query Log</Trans> <Trans>query_log</Trans>
</NavLink> </NavLink>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<a href={`${REPOSITORY.URL}/wiki`} className="nav-link" target="_blank" rel="noopener noreferrer"> <a href={`${REPOSITORY.URL}/wiki`} className="nav-link" target="_blank" rel="noopener noreferrer">
<svg className="nav-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg> <svg className="nav-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
<Trans>FAQ</Trans> <Trans>faq</Trans>
</a> </a>
</li> </li>
</ul> </ul>

View File

@ -45,7 +45,7 @@ class Header extends Component {
</Link> </Link>
{!dashboard.proccessing && dashboard.isCoreRunning && {!dashboard.proccessing && dashboard.isCoreRunning &&
<span className={badgeClass}> <span className={badgeClass}>
<Trans>{dashboard.protectionEnabled ? 'ON' : 'OFF'}</Trans> <Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
</span> </span>
} }
</div> </div>

View File

@ -57,10 +57,10 @@ class Logs extends Component {
if (userRules.match(preparedBlockingRule)) { if (userRules.match(preparedBlockingRule)) {
this.props.setRules(userRules.replace(`${blockingRule}`, '')); this.props.setRules(userRules.replace(`${blockingRule}`, ''));
this.props.addSuccessToast(`${t('Rule removed from the custom filtering rules')}: ${blockingRule}`); this.props.addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
} else if (!userRules.match(preparedUnblockingRule)) { } else if (!userRules.match(preparedUnblockingRule)) {
this.props.setRules(`${userRules}${lineEnding}${unblockingRule}\n`); this.props.setRules(`${userRules}${lineEnding}${unblockingRule}\n`);
this.props.addSuccessToast(`${t('Rule added to the custom filtering rules')}: ${unblockingRule}`); this.props.addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
} }
this.props.getFilteringStatus(); this.props.getFilteringStatus();
@ -68,7 +68,7 @@ class Logs extends Component {
renderBlockingButton(isFiltered, domain) { renderBlockingButton(isFiltered, domain) {
const buttonClass = isFiltered ? 'btn-outline-secondary' : 'btn-outline-danger'; const buttonClass = isFiltered ? 'btn-outline-secondary' : 'btn-outline-danger';
const buttonText = isFiltered ? 'Unblock' : 'Block'; const buttonText = isFiltered ? 'ublock_btn' : 'block_btn';
return ( return (
<div className="logs__action"> <div className="logs__action">
@ -86,13 +86,13 @@ class Logs extends Component {
renderLogs(logs) { renderLogs(logs) {
const { t } = this.props; const { t } = this.props;
const columns = [{ const columns = [{
Header: t('Time'), Header: t('time_table_header'),
accessor: 'time', accessor: 'time',
maxWidth: 110, maxWidth: 110,
filterable: false, filterable: false,
Cell: ({ value }) => (<div className="logs__row"><span className="logs__text" title={value}>{formatTime(value)}</span></div>), Cell: ({ value }) => (<div className="logs__row"><span className="logs__text" title={value}>{formatTime(value)}</span></div>),
}, { }, {
Header: t('Domain name'), Header: t('domain_name_table_header'),
accessor: 'domain', accessor: 'domain',
Cell: (row) => { Cell: (row) => {
const response = row.value; const response = row.value;
@ -108,11 +108,11 @@ class Logs extends Component {
); );
}, },
}, { }, {
Header: t('Type'), Header: t('type_table_header'),
accessor: 'type', accessor: 'type',
maxWidth: 60, maxWidth: 60,
}, { }, {
Header: t('Response'), Header: t('response_table_header'),
accessor: 'response', accessor: 'response',
Cell: (row) => { Cell: (row) => {
const responses = row.value; const responses = row.value;
@ -126,7 +126,7 @@ class Logs extends Component {
if (reason === 'FilteredBlackList' || reason === 'NotFilteredWhiteList') { if (reason === 'FilteredBlackList' || reason === 'NotFilteredWhiteList') {
if (filterId === 0) { if (filterId === 0) {
filterName = 'Custom filtering rules'; filterName = 'custom_filter_rules';
} else { } else {
const filterItem = Object.keys(filters) const filterItem = Object.keys(filters)
.filter(key => filters[key].id === filterId); .filter(key => filters[key].id === filterId);
@ -159,7 +159,7 @@ class Logs extends Component {
} }
return ( return (
<div className="logs__row"> <div className="logs__row">
<span><Trans>Empty</Trans></span> <span><Trans>empty_response_status</Trans></span>
{this.renderTooltip(isFiltered, rule, filterName)} {this.renderTooltip(isFiltered, rule, filterName)}
</div> </div>
); );
@ -177,8 +177,8 @@ class Logs extends Component {
className="form-control" className="form-control"
value={filter ? filter.value : 'all'} value={filter ? filter.value : 'all'}
> >
<option value="all">{ t('Show all') }</option> <option value="all">{ t('show_all_filter_type') }</option>
<option value="filtered">{ t('Show filtered') }</option> <option value="filtered">{ t('show_filtered_type') }</option>
</select>, </select>,
}, { }, {
Header: t('Client'), Header: t('Client'),
@ -210,13 +210,13 @@ class Logs extends Component {
defaultPageSize={50} defaultPageSize={50}
minRows={7} minRows={7}
// Text // Text
previousText={ t('Previous') } previousText={ t('previous_btn') }
nextText={ t('Next') } nextText={ t('next_btn') }
loadingText={ t('Loading...') } loadingText={ t('loading_table_status') }
pageText={ t('Page') } pageText={ t('page_table_footer_text') }
ofText={ t('of') } ofText={ t('of_table_footer_text') }
rowsText={ t('rows') } rowsText={ t('rows_table_footer_text') }
noDataText={ t('No logs found') } noDataText={ t('no_logs_found') }
defaultFilterMethod={(filter, row) => { defaultFilterMethod={(filter, row) => {
const id = filter.pivotId || filter.id; const id = filter.pivotId || filter.id;
return row[id] !== undefined ? return row[id] !== undefined ?
@ -269,17 +269,17 @@ class Logs extends Component {
className="btn btn-gray btn-sm mr-2" className="btn btn-gray btn-sm mr-2"
type="submit" type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)} onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
><Trans>Disable log</Trans></button> ><Trans>disabled_log_btn</Trans></button>
<button <button
className="btn btn-primary btn-sm mr-2" className="btn btn-primary btn-sm mr-2"
type="submit" type="submit"
onClick={this.handleDownloadButton} onClick={this.handleDownloadButton}
><Trans>Download log file</Trans></button> ><Trans>download_log_file_btn</Trans></button>
<button <button
className="btn btn-outline-primary btn-sm" className="btn btn-outline-primary btn-sm"
type="submit" type="submit"
onClick={this.getLogs} onClick={this.getLogs}
><Trans>Refresh</Trans></button> ><Trans>refresh_btn</Trans></button>
</Fragment> </Fragment>
); );
} }
@ -289,7 +289,7 @@ class Logs extends Component {
className="btn btn-success btn-sm mr-2" className="btn btn-success btn-sm mr-2"
type="submit" type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)} onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
><Trans>Enable log</Trans></button> ><Trans>enabled_log_btn</Trans></button>
); );
} }
@ -298,7 +298,7 @@ class Logs extends Component {
const { queryLogEnabled } = dashboard; const { queryLogEnabled } = dashboard;
return ( return (
<Fragment> <Fragment>
<PageTitle title={ t('Query Log') } subtitle={ t('Last 5000 DNS queries') }> <PageTitle title={ t('query_log') } subtitle={ t('last_5000_dns_queries') }>
<div className="page-title__actions"> <div className="page-title__actions">
{this.renderButtons(queryLogEnabled)} {this.renderButtons(queryLogEnabled)}
</div> </div>

View File

@ -28,8 +28,8 @@ class Upstream extends Component {
return ( return (
<Card <Card
title={ t('Upstream DNS servers') } title={ t('upstream_dns') }
subtitle={ t('If you keep this field empty, AdGuard Home will use <a href="https://1.1.1.1/" target="_blank">Cloudflare DNS</a> as an upstream. Use tls:// prefix for DNS over TLS servers.') } subtitle={ t('upstream_dns_hint') }
bodyType="card-body box-body--settings" bodyType="card-body box-body--settings"
> >
<div className="row"> <div className="row">
@ -46,14 +46,14 @@ class Upstream extends Component {
type="button" type="button"
onClick={this.handleTest} onClick={this.handleTest}
> >
<Trans>Test upstreams</Trans> <Trans>test_upstream_btn</Trans>
</button> </button>
<button <button
className="btn btn-success btn-standart" className="btn btn-success btn-standart"
type="submit" type="submit"
onClick={this.handleSubmit} onClick={this.handleSubmit}
> >
<Trans>Apply</Trans> <Trans>apply_btn</Trans>
</button> </button>
</div> </div>
</form> </form>

View File

@ -12,23 +12,23 @@ class Settings extends Component {
settings = { settings = {
filtering: { filtering: {
enabled: false, enabled: false,
title: 'Block domains using filters and hosts files', title: 'block_domain_use_filters_and_hosts',
subtitle: 'You can setup blocking rules in the <a href="#filters">Filters</a> settings.', subtitle: 'filters_block_toggle_hint',
}, },
safebrowsing: { safebrowsing: {
enabled: false, enabled: false,
title: 'Use AdGuard browsing security web service', title: 'use_adguard_browsing_sec',
subtitle: '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.', subtitle: 'use_adguard_browsing_sec_hint',
}, },
parental: { parental: {
enabled: false, enabled: false,
title: 'Use AdGuard parental control web service', title: 'use_adguard_parental',
subtitle: 'AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.', subtitle: 'use_adguard_parental_hint',
}, },
safesearch: { safesearch: {
enabled: false, enabled: false,
title: 'Enforce safe search', title: 'enforce_safe_search',
subtitle: 'AdGuard Home can enforce safe search in the following search engines: Google, Bing, Yandex.', subtitle: 'enforce_save_search_hint',
}, },
}; };
@ -48,7 +48,7 @@ class Settings extends Component {
if (this.props.dashboard.upstreamDns.length > 0) { if (this.props.dashboard.upstreamDns.length > 0) {
this.props.testUpstream(this.props.dashboard.upstreamDns); this.props.testUpstream(this.props.dashboard.upstreamDns);
} else { } else {
this.props.addErrorToast({ error: this.props.t('No servers specified') }); this.props.addErrorToast({ error: this.props.t('no_servers_specified') });
} }
}; };
@ -65,7 +65,7 @@ class Settings extends Component {
}); });
} }
return ( return (
<div><Trans>No settings</Trans></div> <div><Trans>no_settings</Trans></div>
); );
} }
@ -74,13 +74,13 @@ class Settings extends Component {
const { upstreamDns } = this.props.dashboard; const { upstreamDns } = this.props.dashboard;
return ( return (
<Fragment> <Fragment>
<PageTitle title={ t('Settings') } /> <PageTitle title={ t('settings') } />
{settings.processing && <Loading />} {settings.processing && <Loading />}
{!settings.processing && {!settings.processing &&
<div className="content"> <div className="content">
<div className="row"> <div className="row">
<div className="col-md-12"> <div className="col-md-12">
<Card title={ t('General settings') } bodyType="card-body box-body--settings"> <Card title={ t('general_settings') } bodyType="card-body box-body--settings">
<div className="form"> <div className="form">
{this.renderSettings(settings.settingsList)} {this.renderSettings(settings.settingsList)}
</div> </div>

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Trans, withNamespaces } from 'react-i18next'; import { Trans, withNamespaces } from 'react-i18next';
import { REPOSITORY } from '../../helpers/constants'; import { REPOSITORY } from '../../helpers/constants';
import i18n from '../../i18n'; import i18n, { languages } from '../../i18n';
class Footer extends Component { class Footer extends Component {
getYear = () => { getYear = () => {
@ -9,8 +9,8 @@ class Footer extends Component {
return today.getFullYear(); return today.getFullYear();
}; };
changeLanguage = () => { changeLanguage = (event) => {
i18n.changeLanguage(i18n.language === 'en' ? 'vi' : 'en'); i18n.changeLanguage(event.target.value);
} }
render() { render() {
@ -23,22 +23,24 @@ class Footer extends Component {
<div className="col-auto"> <div className="col-auto">
Copyright © {this.getYear()} <a href="https://adguard.com/">AdGuard</a> Copyright © {this.getYear()} <a href="https://adguard.com/">AdGuard</a>
</div> </div>
<div className="col-auto">
<select className="form-control" value={i18n.language} onChange={this.changeLanguage}>
{ languages.map(language => <option
key={language.key} value={language.key}>
{language.name}
</option>) }
</select>
</div>
<div className="col-auto"> <div className="col-auto">
<ul className="list-inline text-center mb-0"> <ul className="list-inline text-center mb-0">
<li className="list-inline-item"> <li className="list-inline-item">
<a href={REPOSITORY.URL} target="_blank" rel="noopener noreferrer"><Trans>Homepage</Trans></a> <a href={REPOSITORY.URL} target="_blank" rel="noopener noreferrer"><Trans>homepage</Trans></a>
</li> </li>
</ul> </ul>
</div> </div>
<div className="col-auto">
<button className="btn btn-outline-info btn-sm" onClick={this.changeLanguage}
title={i18n.language === 'en' ? 'Chuyển sang Tiếng Việt' : 'Change to English'}>
{i18n.language === 'en' ? 'English' : 'Tiếng Việt'}
</button>
</div>
<div className="col-auto"> <div className="col-auto">
<a href={`${REPOSITORY.URL}/issues/new`} className="btn btn-outline-primary btn-sm" target="_blank" rel="noopener noreferrer"> <a href={`${REPOSITORY.URL}/issues/new`} className="btn btn-outline-primary btn-sm" target="_blank" rel="noopener noreferrer">
<Trans>Report an issue</Trans> <Trans>report_an_issue</Trans>
</a> </a>
</div> </div>
</div> </div>

View File

@ -71,8 +71,8 @@ class Modal extends Component {
if (!this.props.isFilterAdded) { if (!this.props.isFilterAdded) {
return ( return (
<React.Fragment> <React.Fragment>
<input type="text" className={inputNameClass} placeholder={ this.props.t('Enter name') } onChange={this.handleNameChange} /> <input type="text" className={inputNameClass} placeholder={ this.props.t('enter_name_hint') } onChange={this.handleNameChange} />
<input type="text" className={inputUrlClass} placeholder={ this.props.t('Enter URL') } onChange={this.handleUrlChange} /> <input type="text" className={inputUrlClass} placeholder={ this.props.t('enter_url_hint') } onChange={this.handleUrlChange} />
{inputDescription && {inputDescription &&
<div className="description"> <div className="description">
{inputDescription} {inputDescription}
@ -111,8 +111,8 @@ class Modal extends Component {
{ {
!this.props.isFilterAdded && !this.props.isFilterAdded &&
<div className="modal-footer"> <div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={this.closeModal}><Trans>Cancel</Trans></button> <button type="button" className="btn btn-secondary" onClick={this.closeModal}><Trans>cancel_btn</Trans></button>
<button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}><Trans>Add filter</Trans></button> <button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}><Trans>add_filter_btn</Trans></button>
</div> </div>
} }
</div> </div>

View File

@ -14,13 +14,13 @@ class Popover extends Component {
const source = ( const source = (
<div className="popover__list-item"> <div className="popover__list-item">
<Trans>Source</Trans>: <a className="popover__link" target="_blank" rel="noopener noreferrer" href={sourceData.url}><strong>{sourceData.name}</strong></a> <Trans>source_label</Trans>: <a className="popover__link" target="_blank" rel="noopener noreferrer" href={sourceData.url}><strong>{sourceData.name}</strong></a>
</div> </div>
); );
const tracker = ( const tracker = (
<div className="popover__list-item"> <div className="popover__list-item">
<Trans>Name</Trans>: <a className="popover__link" target="_blank" rel="noopener noreferrer" href={data.url}><strong>{data.name}</strong></a> <Trans>name_table_header</Trans>: <a className="popover__link" target="_blank" rel="noopener noreferrer" href={data.url}><strong>{data.name}</strong></a>
</div> </div>
); );
@ -34,11 +34,12 @@ class Popover extends Component {
<div className="popover__body"> <div className="popover__body">
<div className="popover__list"> <div className="popover__list">
<div className="popover__list-title"> <div className="popover__list-title">
<Trans>Found in the known domains database.</Trans> <Trans>found_in_known_domain_db</Trans>
</div> </div>
{tracker} {tracker}
<div className="popover__list-item"> <div className="popover__list-item">
<Trans>Category</Trans>: <strong><Trans>{categoryName}</Trans></strong> <Trans>category_label</Trans>: <strong>
<Trans>{categoryName}</Trans></strong>
</div> </div>
{source} {source}
</div> </div>

View File

@ -14,10 +14,10 @@ class PopoverFilter extends Component {
<div className="popover__body popover__body--filter"> <div className="popover__body popover__body--filter">
<div className="popover__list"> <div className="popover__list">
<div className="popover__list-item popover__list-item--nowrap"> <div className="popover__list-item popover__list-item--nowrap">
<Trans>Rule</Trans>: <strong>{this.props.rule}</strong> <Trans>rule_label</Trans>: <strong>{this.props.rule}</strong>
</div> </div>
{this.props.filter && <div className="popover__list-item popover__list-item--nowrap"> {this.props.filter && <div className="popover__list-item popover__list-item--nowrap">
<Trans>Filter</Trans>: <strong>{this.props.filter}</strong> <Trans>filter_label</Trans>: <strong>{this.props.filter}</strong>
</div>} </div>}
</div> </div>
</div> </div>

View File

@ -1,12 +1,12 @@
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/\w[\w_\-.]*\.[a-z]{2,8}[^\s]*$/; export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/\w[\w_\-.]*\.[a-z]{2,8}[^\s]*$/;
export const STATS_NAMES = { export const STATS_NAMES = {
avg_processing_time: 'Average processing time', avg_processing_time: 'average_processing_time',
blocked_filtering: 'Blocked by filters', blocked_filtering: 'Blocked by filters',
dns_queries: 'DNS queries', dns_queries: 'DNS queries',
replaced_parental: 'Blocked adult websites', replaced_parental: 'stats_adult',
replaced_safebrowsing: 'Blocked malware/phishing', replaced_safebrowsing: 'stats_malware_phishing',
replaced_safesearch: 'Enforced safe search', replaced_safesearch: 'enforced_save_search',
}; };
export const STATUS_COLORS = { export const STATUS_COLORS = {

View File

@ -3,7 +3,20 @@ import i18n from 'i18next';
import { reactI18nextModule } from 'react-i18next'; import { reactI18nextModule } from 'react-i18next';
import { initReactI18n } from 'react-i18next/hooks'; import { initReactI18n } from 'react-i18next/hooks';
import langDetect from 'i18next-browser-languagedetector'; import langDetect from 'i18next-browser-languagedetector';
import viResource from './__locales/vi'; import vi from './__locales/vi';
import en from './__locales/en';
export const languages = [
{
key: 'vi',
name: 'Tiếng Việt',
},
{
key: 'en',
name: 'English',
},
];
i18n i18n
.use(langDetect) .use(langDetect)
@ -11,7 +24,8 @@ i18n
.use(reactI18nextModule) // passes i18n down to react-i18next .use(reactI18nextModule) // passes i18n down to react-i18next
.init({ .init({
resources: { resources: {
vi: viResource, vi,
en,
}, },
fallbackLng: 'en', fallbackLng: 'en',
keySeparator: false, // we use content as keys keySeparator: false, // we use content as keys