diff --git a/client/package-lock.json b/client/package-lock.json index 1e9b0f5d..5b6b2e45 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.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz", + "integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==", + "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", @@ -5569,7 +5593,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5590,12 +5615,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5610,17 +5637,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5737,7 +5767,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5749,6 +5780,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5763,6 +5795,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5770,12 +5803,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5794,6 +5829,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5874,7 +5910,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5886,6 +5923,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5971,7 +6009,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6007,6 +6046,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6026,6 +6066,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6069,12 +6110,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -6230,6 +6273,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 +6591,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 +6756,11 @@ "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==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -12850,6 +12911,32 @@ "prop-types": "^15.6.0" } }, + "react-i18next": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-8.1.1.tgz", + "integrity": "sha512-o/33dyl5Wc8z/6QTsj6zU567oRK2pIDhlhNzelpTSmKrZuCsBPZZxEEOW6T+5MgDpCfr6RCsZbL1M2S+RH6i1w==", + "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.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz", + "integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==" + }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -15604,6 +15691,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 38e18a30..a85d00a4 100644 --- a/client/package.json +++ b/client/package.json @@ -6,7 +6,8 @@ "build-dev": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.dev.js", "watch": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.dev.js --watch", "build-prod": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.prod.js", - "lint": "eslint frontend/" + "lint": "eslint frontend/", + "start": "react-scripts start" }, "dependencies": { "@nivo/line": "^0.49.1", @@ -14,12 +15,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.1.1", "react-modal": "^3.4.5", "react-redux": "^5.0.7", "react-redux-loading-bar": "^4.0.7", diff --git a/client/src/__locales/vi.js b/client/src/__locales/vi.js new file mode 100644 index 00000000..e2a03d14 --- /dev/null +++ b/client/src/__locales/vi.js @@ -0,0 +1,42 @@ +export default { + translation: { + // Header + Back: 'Quay lại', + Dashboard: 'Tổng quan', + Settings: 'Cài đặt', + Filters: 'Bộ lọc', + 'Query Log': 'Lịch sử truy vấn', + FAQ: 'Hỏi đáp', + version: 'phiên bản', + address: 'địa chỉ', + ON: 'Đang bật', + OFF: 'Đang tắt', + // Footer + Homepage: 'Trang chủ', + 'Report an issue': 'Báo lỗi', + // Dashboard + 'Enable protection': 'Bật bảo vệ', + 'Disable protection': 'Tắt bảo vệ', + 'Refresh statistics': 'Làm mới thống kê', + 'DNS Queries': 'Truy vấn DNS', + 'Blocked by': 'Chặn bởi', + 'Blocked malware/phishing': 'Mã độc/lừa đảo đã chặn', + 'Blocked adult websites': 'Website người lớn đã chặn', + 'Top queried domains': 'Tên miền truy vấn nhiều', + 'for the last 24 hours': 'trong 24 giờ qua', + 'No domains found': 'Không có tên miền nào', + 'Requests count': 'Số lần yêu cầu', + 'Top blocked domains': 'Tên miền chặn nhiều', + 'Top clients': 'Client dùng nhiều', + 'No clients found': 'Không có client nào', + 'General statistics': 'Thống kê chung', + 'A number of DNS quieries processed for the last 24 hours': 'Số yêu cầu DNS đã xử lý trong 24 giờ qua', + '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', + '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', + 'A number of adult websites blocked': 'Số website người lớn đã chặn', + 'Enforced safe search': 'Tìm kiếm an toàn', + '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', + 'Average processing time': 'Thời gian xử lý trung bình', + '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', + }, +}; diff --git a/client/src/components/Dashboard/BlockedDomains.js b/client/src/components/Dashboard/BlockedDomains.js index 707007a7..4ec291cd 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 633ef08b..6a3596dd 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'; @@ -24,7 +25,7 @@ class Clients extends Component { accessor: 'ip', Cell: ({ value }) => (
{value}
), }, { - Header: 'Requests count', + Header: Requests count, accessor: 'count', Cell: ({ value }) => { const percent = getPercent(this.props.dnsQueries, value); @@ -37,15 +38,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" /> @@ -58,6 +60,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..7b4c6505 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 Queries + @@ -23,8 +24,8 @@ const Counters = props => (
- Blocked by Filters - + Blocked by Filters + @@ -34,8 +35,8 @@ const Counters = props => (
- Blocked malware/phishing - + Blocked malware/phishing + @@ -45,8 +46,8 @@ const Counters = props => (
- Blocked adult websites - + Blocked adult websites + @@ -56,8 +57,8 @@ const Counters = props => (
- Enforced safe search - + Enforced safe 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 045ceb6c..fd30f0eb 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..3e7842b0 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 } from 'react-i18next'; import Card from '../ui/Card'; import Line from '../ui/Line'; @@ -30,7 +31,7 @@ class Statistics extends Component { {dnsQueries}
- DNS Queries + DNS Queries
@@ -48,7 +49,7 @@ class Statistics extends Component { {getPercent(dnsQueries, blockedFiltering)}
- Blocked by Filters + Blocked by Filters
@@ -66,7 +67,7 @@ class Statistics extends Component { {getPercent(dnsQueries, replacedSafebrowsing)}
- Blocked malware/phishing + Blocked malware/phishing
@@ -84,7 +85,7 @@ class Statistics extends Component { {getPercent(dnsQueries, replacedParental)}
- Blocked adult websites + Blocked adult websites
diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index bf49b2a4..c8d43a2a 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'; @@ -30,25 +31,25 @@ class Dashboard extends Component { 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 =
diff --git a/client/src/i18n.js b/client/src/i18n.js new file mode 100644 index 00000000..cf5bfb71 --- /dev/null +++ b/client/src/i18n.js @@ -0,0 +1,25 @@ + +import i18n from 'i18next'; +import { reactI18nextModule } from 'react-i18next'; +import langDetect from 'i18next-browser-languagedetector'; +import viResource from './__locales/vi'; + +i18n + .use(langDetect) + .use(reactI18nextModule) // passes i18n down to react-i18next + .init({ + resources: { + vi: viResource, + }, + fallbackLng: 'en', + keySeparator: false, // we use content as keys + interpolation: { + escapeValue: false, // not needed for react!! + formatSeparator: ',', + }, + react: { + wait: true, + }, + }); + +export default i18n; diff --git a/client/src/index.js b/client/src/index.js index 16465633..1e95a210 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -5,6 +5,7 @@ import './components/App/index.css'; import App from './containers/App'; import configureStore from './configureStore'; import reducers from './reducers'; +import './i18n'; const store = configureStore(reducers, {}); // set initial state ReactDOM.render(