Translate dashboard to Vietnamese with i18n

This commit is contained in:
hoangnd 2018-10-25 13:52:03 +07:00
parent f8ee8a7907
commit e1069f6bd1
15 changed files with 240 additions and 59 deletions

114
client/package-lock.json generated vendored
View File

@ -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",

6
client/package.json vendored
View File

@ -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",

View File

@ -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',
},
};

View File

@ -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: <Trans>Requests count</Trans>,
accessor: 'domain',
maxWidth: 190,
Cell: ({ value }) => {
@ -48,15 +49,16 @@ class BlockedDomains extends Component {
}];
render() {
const { t } = this.props;
return (
<Card title="Top blocked domains" subtitle="for the last 24 hours" bodyType="card-table" refresh={this.props.refreshButton}>
<Card title={ t('Top blocked domains') } subtitle={ t('for the last 24 hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable
data={map(this.props.topBlockedDomains, (value, prop) => (
{ 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);

View File

@ -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 }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
}, {
Header: 'Requests count',
Header: <Trans>Requests count</Trans>,
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 (
<Card title="Top clients" subtitle="for the last 24 hours" bodyType="card-table" refresh={this.props.refreshButton}>
<Card title={ t('Top clients') } subtitle={ t('for the last 24 hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable
data={map(this.props.topClients, (value, prop) => (
{ 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);

View File

@ -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 => (
<Card title="General statistics" subtitle="for the last 24 hours" bodyType="card-table" refresh={props.refreshButton}>
<Card title={ props.t('General statistics') } subtitle={ props.t('for the last 24 hours') } bodyType="card-table" refresh={props.refreshButton}>
<table className="table card-table">
<tbody>
<tr>
<td>
DNS Queries
<Tooltip text="A number of DNS quieries processed for the last 24 hours" type={tooltipType} />
<Trans>DNS Queries</Trans>
<Tooltip text={ props.t('A number of DNS quieries processed for the last 24 hours') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@ -23,8 +24,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked by <a href="#filters">Filters</a>
<Tooltip text="A number of DNS requests blocked by adblock filters and hosts blocklists" type={tooltipType} />
<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} />
</td>
<td className="text-right">
<span className="text-muted">
@ -34,8 +35,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked malware/phishing
<Tooltip text="A number of DNS requests blocked by the AdGuard browsing security module" type={tooltipType} />
<Trans>Blocked malware/phishing</Trans>
<Tooltip text={ props.t('A number of DNS requests blocked by the AdGuard browsing security module') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@ -45,8 +46,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked adult websites
<Tooltip text="A number of adult websites blocked" type={tooltipType} />
<Trans>Blocked adult websites</Trans>
<Tooltip text={ props.t('A number of adult websites blocked') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@ -56,8 +57,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Enforced safe search
<Tooltip text="A number of DNS requests to search engines for which Safe Search was enforced" type={tooltipType} />
<Trans>Enforced safe search</Trans>
<Tooltip text={ props.t('A number of DNS requests to search engines for which Safe Search was enforced') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@ -67,8 +68,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Average processing time
<Tooltip text="Average time in milliseconds on processing a DNS request" type={tooltipType} />
<Trans>Average processing time</Trans>
<Tooltip text={ props.t('Average time in milliseconds on processing a DNS request') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@ -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);

View File

@ -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: <Trans>Requests count</Trans>,
accessor: 'count',
maxWidth: 190,
Cell: ({ value }) => {
@ -52,15 +53,16 @@ class QueriedDomains extends Component {
}];
render() {
const { t } = this.props;
return (
<Card title="Top queried domains" subtitle="for the last 24 hours" bodyType="card-table" refresh={this.props.refreshButton}>
<Card title={ t('Top queried domains') } subtitle={ t('for the last 24 hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable
data={map(this.props.topQueriedDomains, (value, prop) => (
{ 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);

View File

@ -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}
</div>
<div className="card-title-stats">
DNS Queries
<Trans>DNS Queries</Trans>
</div>
</div>
<div className="card-chart-bg">
@ -48,7 +49,7 @@ class Statistics extends Component {
{getPercent(dnsQueries, blockedFiltering)}
</div>
<div className="card-title-stats">
Blocked by <a href="#filters">Filters</a>
<Trans>Blocked by</Trans> <a href="#filters"><Trans>Filters</Trans></a>
</div>
</div>
<div className="card-chart-bg">
@ -66,7 +67,7 @@ class Statistics extends Component {
{getPercent(dnsQueries, replacedSafebrowsing)}
</div>
<div className="card-title-stats">
Blocked malware/phishing
<Trans>Blocked malware/phishing</Trans>
</div>
</div>
<div className="card-chart-bg">
@ -84,7 +85,7 @@ class Statistics extends Component {
{getPercent(dnsQueries, replacedParental)}
</div>
<div className="card-title-stats">
Blocked adult websites
<Trans>Blocked adult websites</Trans>
</div>
</div>
<div className="card-chart-bg">

View File

@ -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 (
<button type="button" className={`btn btn-sm mr-2 ${buttonClass}`} onClick={() => this.props.toggleProtection(protectionEnabled)}>
{buttonText} protection
<Trans>{buttonText} protection</Trans>
</button>
);
}
render() {
const { dashboard } = this.props;
const { dashboard, t } = this.props;
const dashboardProcessing =
dashboard.processing ||
dashboard.processingStats ||
dashboard.processingStatsHistory ||
dashboard.processingTopStats;
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}>Refresh statistics</button>;
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}><Trans>Refresh statistics</Trans></button>;
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.getAllStats()} />;
return (
<Fragment>
<PageTitle title="Dashboard">
<PageTitle title={ t('Dashboard') }>
<div className="page-title__actions">
{this.getToggleFilteringButton()}
{refreshFullButton}
@ -124,6 +125,7 @@ Dashboard.propTypes = {
isCoreRunning: PropTypes.bool,
getFiltering: PropTypes.func,
toggleProtection: PropTypes.func,
t: PropTypes.func,
};
export default Dashboard;
export default withNamespaces()(Dashboard);

View File

@ -3,7 +3,7 @@ import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import enhanceWithClickOutside from 'react-click-outside';
import classnames from 'classnames';
import { Trans } from 'react-i18next';
import { REPOSITORY } from '../../helpers/constants';
class Menu extends Component {
@ -28,37 +28,37 @@ class Menu extends Component {
<li className="nav-item border-bottom d-lg-none" onClick={this.toggleMenu}>
<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>
Back
<Trans>Back</Trans>
</div>
</li>
<li className="nav-item">
<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>
Dashboard
<Trans>Dashboard</Trans>
</NavLink>
</li>
<li className="nav-item">
<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>
Settings
<Trans>Settings</Trans>
</NavLink>
</li>
<li className="nav-item">
<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>
Filters
<Trans>Filters</Trans>
</NavLink>
</li>
<li className="nav-item">
<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>
Query Log
<Trans>Query Log</Trans>
</NavLink>
</li>
<li className="nav-item">
<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>
FAQ
<Trans>FAQ</Trans>
</a>
</li>
</ul>

View File

@ -1,15 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
export default function Version(props) {
const { dnsVersion, dnsAddress, dnsPort } = props;
return (
<div className="nav-version">
<div className="nav-version__text">
version: <span className="nav-version__value">{dnsVersion}</span>
<Trans>version</Trans>: <span className="nav-version__value">{dnsVersion}</span>
</div>
<div className="nav-version__text">
address: <span className="nav-version__value">{dnsAddress}:{dnsPort}</span>
<Trans>address</Trans>: <span className="nav-version__value">{dnsAddress}:{dnsPort}</span>
</div>
</div>
);

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Trans } from 'react-i18next';
import Menu from './Menu';
import Version from './Version';
@ -44,7 +45,7 @@ class Header extends Component {
</Link>
{!dashboard.proccessing && dashboard.isCoreRunning &&
<span className={badgeClass}>
{dashboard.protectionEnabled ? 'ON' : 'OFF'}
<Trans>{dashboard.protectionEnabled ? 'ON' : 'OFF'}</Trans>
</span>
}
</div>

View File

@ -1,4 +1,5 @@
import React, { Component } from 'react';
import { Trans } from 'react-i18next';
import { REPOSITORY } from '../../helpers/constants';
class Footer extends Component {
@ -20,13 +21,13 @@ class Footer extends Component {
<div className="col-auto">
<ul className="list-inline text-center mb-0">
<li className="list-inline-item">
<a href={REPOSITORY.URL} target="_blank" rel="noopener noreferrer">Homepage</a>
<a href={REPOSITORY.URL} target="_blank" rel="noopener noreferrer"><Trans>Homepage</Trans></a>
</li>
</ul>
</div>
<div className="col-auto">
<a href={`${REPOSITORY.URL}/issues/new`} className="btn btn-outline-primary btn-sm" target="_blank" rel="noopener noreferrer">
Report an issue
<Trans>Report an issue</Trans>
</a>
</div>
</div>

25
client/src/i18n.js Normal file
View File

@ -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;

View File

@ -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(