From 0ffc44ad2e21f2d4674bdaa17c83b85b70b22915 Mon Sep 17 00:00:00 2001 From: "Carol A. Scott" Date: Fri, 14 Aug 2020 10:50:51 -0700 Subject: [PATCH] Add es translations and docs instructions to dashboard (#4866) * Adding es translations Signed-off-by: Carol Scott carol@buoyant.io --- BUILD.md | 18 ++++ web/app/js/components/BaseTable.jsx | 4 +- web/app/js/components/CheckModal.jsx | 3 +- web/app/js/components/EdgesTable.jsx | 13 +-- web/app/js/components/EmptyCard.jsx | 2 +- web/app/js/components/ErrorModal.jsx | 5 +- web/app/js/components/ExpandableTable.jsx | 2 +- web/app/js/components/Gateway.jsx | 3 +- web/app/js/components/MeshedStatusTable.jsx | 7 +- web/app/js/components/MetricsTable.jsx | 45 ++++---- web/app/js/components/Namespace.jsx | 5 +- web/app/js/components/Navigation.jsx | 49 ++++----- web/app/js/components/NoMatch.jsx | 7 +- web/app/js/components/ResourceDetail.jsx | 9 +- web/app/js/components/ResourceList.jsx | 5 +- web/app/js/components/ServiceMesh.jsx | 13 +-- web/app/js/components/StatusTable.jsx | 7 +- web/app/js/components/TapEventTable.jsx | 15 +-- web/app/js/components/TapQueryForm.jsx | 35 ++++--- web/app/js/components/TopEventTable.jsx | 19 ++-- web/app/js/components/TopRoutes.jsx | 5 +- web/app/js/components/TopRoutesTable.jsx | 15 +-- web/app/js/components/TrafficSplitDetail.jsx | 3 +- web/app/js/components/util/CopyUtils.jsx | 2 +- web/app/js/locales/en/messages.json | 103 +++++++++++++++++- web/app/js/locales/es/messages.json | 105 ++++++++++++++++++- 26 files changed, 375 insertions(+), 124 deletions(-) diff --git a/BUILD.md b/BUILD.md index 57565c725..a01ca399f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -340,6 +340,24 @@ cd web/app yarn add [dep] ``` +#### Translations + +To add a locale: + +```bash +cd web/app +yarn lingui add-locale [locales...] # will create a messages.json file for new locale(s) +``` + +To extract message keys from existing components: + +```bash +cd web/app +yarn lingui extract +... +yarn lingui compile # done automatically in bin/web run +``` + ### Rust All Rust development happens in the diff --git a/web/app/js/components/BaseTable.jsx b/web/app/js/components/BaseTable.jsx index 33573c46e..1418cd943 100644 --- a/web/app/js/components/BaseTable.jsx +++ b/web/app/js/components/BaseTable.jsx @@ -261,10 +261,10 @@ BaseTable.propTypes = { isNumeric: PropTypes.bool, render: PropTypes.func, sorter: PropTypes.func, - title: PropTypes.string, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), })).isRequired, tableRows: PropTypes.arrayOf(PropTypes.shape({})), - title: PropTypes.string, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), }; BaseTable.defaultProps = { diff --git a/web/app/js/components/CheckModal.jsx b/web/app/js/components/CheckModal.jsx index 6edadd8ba..d284b2ac1 100644 --- a/web/app/js/components/CheckModal.jsx +++ b/web/app/js/components/CheckModal.jsx @@ -14,6 +14,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import SimpleChip from './util/Chip.jsx'; import Slide from '@material-ui/core/Slide'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import withMobileDialog from '@material-ui/core/withMobileDialog'; import { withStyles } from '@material-ui/core/styles'; @@ -254,7 +255,7 @@ class CheckModal extends React.Component { color="primary" disabled={running} onClick={this.runCheck}> - Run Linkerd Check + buttonRunLinkerdCheck diff --git a/web/app/js/components/EdgesTable.jsx b/web/app/js/components/EdgesTable.jsx index 6eb0735b7..10c86d493 100644 --- a/web/app/js/components/EdgesTable.jsx +++ b/web/app/js/components/EdgesTable.jsx @@ -3,6 +3,7 @@ import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline'; import PropTypes from 'prop-types'; import React from 'react'; import Tooltip from '@material-ui/core/Tooltip'; +import { Trans } from '@lingui/macro'; import WarningIcon from '@material-ui/icons/Warning'; import { directionColumn } from './util/TapUtils.jsx'; import { processedEdgesPropType } from './util/EdgesUtils.jsx'; @@ -25,7 +26,7 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => { render: d => directionColumn(d.direction), }, { - title: 'Namespace', + title: columnTitleNamespace, dataIndex: 'namespace', isNumeric: false, filter: d => d.namespace, @@ -37,7 +38,7 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => { sorter: d => d.namespace, }, { - title: 'Name', + title: columnTitleName, dataIndex: 'name', isNumeric: false, filter: d => d.name, @@ -56,7 +57,7 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => { sorter: d => d.name, }, { - title: 'Identity', + title: columnTitleIdentity, dataIndex: 'identity', isNumeric: false, filter: d => d.identity, @@ -64,7 +65,7 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => { sorter: d => d.identity, }, { - title: 'Secured', + title: columnTitleSecured, dataIndex: 'message', isNumeric: true, render: d => { @@ -83,12 +84,12 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => { }; const generateEdgesTableTitle = edges => { - let title = 'Edges'; + let title = tableTitleEdgesEmpty; if (edges.length > 0) { let identity = edges[0].direction === 'INBOUND' ? edges[0].serverId : edges[0].clientId; if (identity) { identity = `${identity.split('.')[0]}.${identity.split('.')[1]}`; - title = `${title} (Identity: ${identity})`; + title = tableTitleEdgesWithIdentity {identity}; } } return title; diff --git a/web/app/js/components/EmptyCard.jsx b/web/app/js/components/EmptyCard.jsx index 4adeb7062..b3e7b8fe6 100644 --- a/web/app/js/components/EmptyCard.jsx +++ b/web/app/js/components/EmptyCard.jsx @@ -17,7 +17,7 @@ const EmptyCard = ({ classes }) => { - No data to display + NoDataToDisplayMsg diff --git a/web/app/js/components/ErrorModal.jsx b/web/app/js/components/ErrorModal.jsx index c649b7d37..bdf223dbd 100644 --- a/web/app/js/components/ErrorModal.jsx +++ b/web/app/js/components/ErrorModal.jsx @@ -10,6 +10,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import Switch from '@material-ui/core/Switch'; import Tooltip from '@material-ui/core/Tooltip'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import _each from 'lodash/each'; import _get from 'lodash/get'; @@ -150,7 +151,9 @@ class ErrorModal extends React.Component { if (showInit) { return ( - + podsAreInitializingMsg}> + + ); } else { return ( diff --git a/web/app/js/components/ExpandableTable.jsx b/web/app/js/components/ExpandableTable.jsx index 8244fed82..89af24afe 100644 --- a/web/app/js/components/ExpandableTable.jsx +++ b/web/app/js/components/ExpandableTable.jsx @@ -148,7 +148,7 @@ ExpandableTable.propTypes = { expandedRowRender: PropTypes.func.isRequired, tableClassName: PropTypes.string, tableColumns: PropTypes.arrayOf(PropTypes.shape({ - title: PropTypes.string, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), isNumeric: PropTypes.bool, render: PropTypes.func, })).isRequired, diff --git a/web/app/js/components/Gateway.jsx b/web/app/js/components/Gateway.jsx index f41548cb3..ddee3a936 100644 --- a/web/app/js/components/Gateway.jsx +++ b/web/app/js/components/Gateway.jsx @@ -4,6 +4,7 @@ import MetricsTable from './MetricsTable.jsx'; import PropTypes from 'prop-types'; import React from 'react'; import Spinner from './util/Spinner.jsx'; +import { Trans } from '@lingui/macro'; import _isEmpty from 'lodash/isEmpty'; import { processGatewayResults } from './util/MetricUtils.jsx'; import { withContext } from './util/AppContext.jsx'; @@ -102,7 +103,7 @@ class Gateways extends React.Component { {noMetrics ? null : (
tableTitleGateways} resource="gateway" metrics={metrics} />
diff --git a/web/app/js/components/MeshedStatusTable.jsx b/web/app/js/components/MeshedStatusTable.jsx index 4fb24b4ab..dcff295df 100644 --- a/web/app/js/components/MeshedStatusTable.jsx +++ b/web/app/js/components/MeshedStatusTable.jsx @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { StyledProgress } from './util/Progress.jsx'; import Tooltip from '@material-ui/core/Tooltip'; +import { Trans } from '@lingui/macro'; import _isEmpty from 'lodash/isEmpty'; import { withContext } from './util/AppContext.jsx'; @@ -20,7 +21,7 @@ const getClassification = (meshedPodCount, failedPodCount) => { const namespacesColumns = PrefixedLink => [ { - title: 'Namespace', + title: columnTitleNamespace, dataIndex: 'namespace', sorter: d => d.namespace, render: d => { @@ -37,12 +38,12 @@ const namespacesColumns = PrefixedLink => [ }, }, { - title: 'Meshed pods', + title: columnTitleMeshedPods, dataIndex: 'meshedPodsStr', isNumeric: true, }, { - title: 'Meshed Status', + title: columnTitleMeshedStatus, key: 'meshification', render: row => { const percent = row.meshedPercent.get(); diff --git a/web/app/js/components/MetricsTable.jsx b/web/app/js/components/MetricsTable.jsx index cf2bf7abd..8c8415023 100644 --- a/web/app/js/components/MetricsTable.jsx +++ b/web/app/js/components/MetricsTable.jsx @@ -7,6 +7,7 @@ import JaegerLink from './JaegerLink.jsx'; import PropTypes from 'prop-types'; import React from 'react'; import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx'; +import { Trans } from '@lingui/macro'; import _cloneDeep from 'lodash/cloneDeep'; import _each from 'lodash/each'; import _get from 'lodash/get'; @@ -16,21 +17,21 @@ import { withContext } from './util/AppContext.jsx'; const tcpStatColumns = [ { - title: 'Connections', + title: columnTitleOpenConnections, dataIndex: 'tcp.openConnections', isNumeric: true, render: d => metricToFormatter.NO_UNIT(d.tcp.openConnections), sorter: d => d.tcp.openConnections, }, { - title: 'Read Bytes / sec', + title: columnTitleReadRate, dataIndex: 'tcp.readRate', isNumeric: true, render: d => metricToFormatter.BYTES(d.tcp.readRate), sorter: d => d.tcp.readRate, }, { - title: 'Write Bytes / sec', + title: columnTitleWriteRate, dataIndex: 'tcp.writeRate', isNumeric: true, render: d => metricToFormatter.BYTES(d.tcp.writeRate), @@ -40,35 +41,35 @@ const tcpStatColumns = [ const httpStatColumns = [ { - title: 'Success Rate', + title: columnTitleSuccessRate, dataIndex: 'successRate', isNumeric: true, render: d => , sorter: d => d.successRate, }, { - title: 'RPS', + title: columnTitleRPS, dataIndex: 'requestRate', isNumeric: true, render: d => metricToFormatter.NO_UNIT(d.requestRate), sorter: d => d.requestRate, }, { - title: 'P50 Latency', + title: columnTitleP50Latency, dataIndex: 'P50', isNumeric: true, render: d => metricToFormatter.LATENCY(d.P50), sorter: d => d.P50, }, { - title: 'P95 Latency', + title: columnTitleP95Latency, dataIndex: 'P95', isNumeric: true, render: d => metricToFormatter.LATENCY(d.P95), sorter: d => d.P95, }, { - title: 'P99 Latency', + title: columnTitleP99Latency, dataIndex: 'P99', isNumeric: true, render: d => metricToFormatter.LATENCY(d.P99), @@ -79,7 +80,7 @@ const httpStatColumns = [ const trafficSplitDetailColumns = [ { - title: 'Apex Service', + title: columnTitleApexService, dataIndex: 'apex', isNumeric: false, filter: d => !d.tsStats ? null : d.tsStats.apex, @@ -87,7 +88,7 @@ const trafficSplitDetailColumns = [ sorter: d => !d.tsStats ? null : d.tsStats.apex, }, { - title: 'Leaf Service', + title: columnTitleLeafService, dataIndex: 'leaf', isNumeric: false, filter: d => !d.tsStats ? null : d.tsStats.leaf, @@ -95,7 +96,7 @@ const trafficSplitDetailColumns = [ sorter: d => !d.tsStats ? null : d.tsStats.leaf, }, { - title: 'Weight', + title: columnTitleWeight, dataIndex: 'weight', isNumeric: true, filter: d => !d.tsStats ? null : d.tsStats.weight, @@ -113,42 +114,42 @@ const trafficSplitDetailColumns = [ const gatewayColumns = [ { - title: 'ClusterName', + title: columnTitleClusterName, dataIndex: 'clusterName', isNumeric: false, render: d => !d.clusterName ? '---' : d.clusterName, sorter: d => !d.clusterName ? '---' : d.clusterName, }, { - title: 'Alive', + title: columnTitleAlive, dataIndex: 'alive', isNumeric: false, render: d => !d.alive ? 'FALSE' : 'TRUE', sorter: d => d.alive, }, { - title: 'Paired Services', + title: columnTitlePairedServices, dataIndex: 'pairedServices', isNumeric: false, render: d => d.pairedServices, sorter: d => d.pairedServices, }, { - title: 'P50 Latency', + title: columnTitleP50Latency, dataIndex: 'P50', isNumeric: true, render: d => metricToFormatter.LATENCY(d.P50), sorter: d => d.P50, }, { - title: 'P95 Latency', + title: columnTitleP95Latency, dataIndex: 'P95', isNumeric: true, render: d => metricToFormatter.LATENCY(d.P95), sorter: d => d.P95, }, { - title: 'P99 Latency', + title: columnTitleP99Latency, dataIndex: 'P99', isNumeric: true, render: d => metricToFormatter.LATENCY(d.P99), @@ -165,7 +166,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix const nsColumn = [ { - title: 'Namespace', + title: columnTitleNamespace, dataIndex: 'namespace', filter: d => d.namespace, isNumeric: false, @@ -175,7 +176,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix ]; const meshedColumn = { - title: 'Meshed', + title: columnTitleMeshed, dataIndex: 'pods.totalPods', isNumeric: true, render: d => !d.pods ? null : `${d.pods.meshedPods}/${d.pods.totalPods}`, @@ -183,7 +184,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix }; const grafanaColumn = { - title: 'Grafana', + title: columnTitleGrafana, key: 'grafanaDashboard', isNumeric: true, render: row => { @@ -203,7 +204,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix const jaegerColumn = { - title: 'Jaeger', + title: columnTitleJaeger, key: 'JaegerDashboard', isNumeric: true, render: row => { @@ -327,7 +328,7 @@ MetricsTable.propTypes = { selectedNamespace: PropTypes.string.isRequired, showName: PropTypes.bool, showNamespaceColumn: PropTypes.bool, - title: PropTypes.string, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), grafana: PropTypes.string, jaeger: PropTypes.string, }; diff --git a/web/app/js/components/Namespace.jsx b/web/app/js/components/Namespace.jsx index 65f32e77c..be6392f80 100644 --- a/web/app/js/components/Namespace.jsx +++ b/web/app/js/components/Namespace.jsx @@ -7,6 +7,7 @@ import NetworkGraph from './NetworkGraph.jsx'; import PropTypes from 'prop-types'; import React from 'react'; import Spinner from './util/Spinner.jsx'; +import { Trans } from '@lingui/macro'; import _filter from 'lodash/filter'; import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; @@ -147,7 +148,7 @@ class Namespaces extends React.Component { {!error ? null : } {!loaded ? : (
- {noMetrics ?
No resources detected.
: null} + {noMetrics ?
noResourcesDetectedMsg
: null} { _isEmpty(deploymentsWithMetrics) ? null : @@ -166,7 +167,7 @@ class Namespaces extends React.Component { noMetrics ? null :
tableTitleTCP} resource="pod" metrics={metrics.pod} isTcpTable /> diff --git a/web/app/js/components/Navigation.jsx b/web/app/js/components/Navigation.jsx index 91e0884cb..d6034d770 100644 --- a/web/app/js/components/Navigation.jsx +++ b/web/app/js/components/Navigation.jsx @@ -22,6 +22,7 @@ import React from 'react'; import ReactRouterPropTypes from 'react-router-prop-types'; import TextField from '@material-ui/core/TextField'; import Toolbar from '@material-ui/core/Toolbar'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import Version from './Version.jsx'; import _maxBy from 'lodash/maxBy'; @@ -450,15 +451,15 @@ class NavigationBase extends React.Component { - Cluster + sidebarHeadingCluster - { this.menuItem('/namespaces', 'Namespaces', namespaceIcon) } + { this.menuItem('/namespaces', menuItemNamespaces, namespaceIcon) } - { this.menuItem('/controlplane', 'Control Plane', + { this.menuItem('/controlplane', menuItemControlPlane, ) } - { this.menuItem('/gateways', 'Gateway', + { this.menuItem('/gateways', menuItemGateway, ) } @@ -500,48 +501,48 @@ class NavigationBase extends React.Component { - Workloads + sidebarHeadingWorkloads - { this.menuItem(`/namespaces/${selectedNamespace}/cronjobs`, 'Cron Jobs', cronJobIcon) } + { this.menuItem(`/namespaces/${selectedNamespace}/cronjobs`, menuItemCronJobs, cronJobIcon) } - { this.menuItem(`/namespaces/${selectedNamespace}/daemonsets`, 'Daemon Sets', daemonsetIcon) } + { this.menuItem(`/namespaces/${selectedNamespace}/daemonsets`, menuItemDaemonSets, daemonsetIcon) } - { this.menuItem(`/namespaces/${selectedNamespace}/deployments`, 'Deployments', deploymentIcon) } + { this.menuItem(`/namespaces/${selectedNamespace}/deployments`, menuItemDeployments, deploymentIcon) } - { this.menuItem(`/namespaces/${selectedNamespace}/jobs`, 'Jobs', jobIcon) } + { this.menuItem(`/namespaces/${selectedNamespace}/jobs`, menuItemJobs, jobIcon) } - { this.menuItem(`/namespaces/${selectedNamespace}/pods`, 'Pods', podIcon) } + { this.menuItem(`/namespaces/${selectedNamespace}/pods`, menuItemPods, podIcon) } - { this.menuItem(`/namespaces/${selectedNamespace}/replicasets`, 'Replica Sets', replicaSetIcon) } + { this.menuItem(`/namespaces/${selectedNamespace}/replicasets`, menuItemReplicaSets, replicaSetIcon) } - { this.menuItem(`/namespaces/${selectedNamespace}/replicationcontrollers`, 'Replication Controllers', replicaSetIcon) } + { this.menuItem(`/namespaces/${selectedNamespace}/replicationcontrollers`, menuItemReplicationControllers, replicaSetIcon) } - { this.menuItem(`/namespaces/${selectedNamespace}/statefulsets`, 'Stateful Sets', statefulSetIcon) } + { this.menuItem(`/namespaces/${selectedNamespace}/statefulsets`, menuItemStatefulSets, statefulSetIcon) } - Configuration + sidebarHeadingConfiguration - { this.menuItem(`/namespaces/${selectedNamespace}/trafficsplits`, 'Traffic Splits', ) } + { this.menuItem(`/namespaces/${selectedNamespace}/trafficsplits`, menuItemTrafficSplits, ) } - Tools + sidebarHeadingTools - { this.menuItem('/tap', 'Tap', ) } - { this.menuItem('/top', 'Top', ) } - { this.menuItem('/routes', 'Routes', ) } + { this.menuItem('/tap', menuItemTap, ) } + { this.menuItem('/top', menuItemTop, ) } + { this.menuItem('/routes', menuItemRoutes, ) } - { this.menuItem('/community', 'Community', + { this.menuItem('/community', menuItemCommunity, - + menuItemDocumentation} /> {githubIcon} - + menuItemGitHub} /> - + menuItemMailingList} /> {slackIcon} - + menuItemSlack} /> diff --git a/web/app/js/components/NoMatch.jsx b/web/app/js/components/NoMatch.jsx index b68fec913..1e2d0d19c 100644 --- a/web/app/js/components/NoMatch.jsx +++ b/web/app/js/components/NoMatch.jsx @@ -1,9 +1,14 @@ import React from 'react'; +import { Trans } from '@lingui/macro'; const NoMatch = () => (

404

-
Page not found.
+
+ + 404Msg + +
); diff --git a/web/app/js/components/ResourceDetail.jsx b/web/app/js/components/ResourceDetail.jsx index 24fe481a4..3b0edeb9e 100644 --- a/web/app/js/components/ResourceDetail.jsx +++ b/web/app/js/components/ResourceDetail.jsx @@ -14,6 +14,7 @@ import SimpleChip from './util/Chip.jsx'; import Spinner from './util/Spinner.jsx'; import TopRoutesTabs from './TopRoutesTabs.jsx'; import TrafficSplitDetail from './TrafficSplitDetail.jsx'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import _filter from 'lodash/filter'; import _get from 'lodash/get'; @@ -402,14 +403,14 @@ export class ResourceDetailBase extends React.Component { {_isEmpty(upstreams) ? null : tableTitleInbound} metrics={upstreamDisplayMetrics} /> } {_isEmpty(downstreamDisplayMetrics) ? null : tableTitleOutbound} metrics={downstreamDisplayMetrics} /> } @@ -417,13 +418,13 @@ export class ResourceDetailBase extends React.Component { resourceType === 'pod' || isTcpOnly ? null : tableTitlePods} metrics={podMetrics} /> } tableTitleTCP} isTcpTable metrics={podMetrics} /> diff --git a/web/app/js/components/ResourceList.jsx b/web/app/js/components/ResourceList.jsx index 89545d7b0..c1e48aacb 100644 --- a/web/app/js/components/ResourceList.jsx +++ b/web/app/js/components/ResourceList.jsx @@ -7,6 +7,7 @@ import MetricsTable from './MetricsTable.jsx'; import PropTypes from 'prop-types'; import React from 'react'; import Spinner from './util/Spinner.jsx'; +import { Trans } from '@lingui/macro'; import { apiErrorPropType } from './util/ApiHelpers.jsx'; import withREST from './util/withREST.jsx'; @@ -33,14 +34,14 @@ export class ResourceListBase extends React.Component { + title={tableTitleHTTPMetrics} /> {resource !== 'trafficsplit' && + title={tableTitleTCPMetrics} /> } ); diff --git a/web/app/js/components/ServiceMesh.jsx b/web/app/js/components/ServiceMesh.jsx index e3d445f44..92a28d1eb 100644 --- a/web/app/js/components/ServiceMesh.jsx +++ b/web/app/js/components/ServiceMesh.jsx @@ -13,6 +13,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import Spinner from './util/Spinner.jsx'; import StatusTable from './StatusTable.jsx'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import _compact from 'lodash/compact'; import _countBy from 'lodash/countBy'; @@ -35,11 +36,11 @@ const styles = { const serviceMeshDetailsColumns = [ { - title: 'Name', + title: columnTitleName, dataIndex: 'name', }, { - title: 'Value', + title: columnTitleValue, dataIndex: 'value', isNumeric: true, }, @@ -107,9 +108,9 @@ class ServiceMesh extends React.Component { return [ { key: 1, name: `${productName} version`, value: releaseVersion }, - { key: 2, name: `${productName} namespace`, value: controllerNamespace }, - { key: 3, name: 'Control plane components', value: components.length }, - { key: 4, name: 'Data plane proxies', value: this.proxyCount() }, + { key: 2, name: {productName} namespace, value: controllerNamespace }, + { key: 3, name: Control plane components, value: components.length }, + { key: 4, name: Data plane proxies, value: this.proxyCount() }, ]; } @@ -225,7 +226,7 @@ class ServiceMesh extends React.Component { Control plane - Components + componentsMsg {components.length} diff --git a/web/app/js/components/StatusTable.jsx b/web/app/js/components/StatusTable.jsx index 32ed39744..6ba5089db 100644 --- a/web/app/js/components/StatusTable.jsx +++ b/web/app/js/components/StatusTable.jsx @@ -2,6 +2,7 @@ import BaseTable from './BaseTable.jsx'; import PropTypes from 'prop-types'; import React from 'react'; import Tooltip from '@material-ui/core/Tooltip'; +import { Trans } from '@lingui/macro'; import _get from 'lodash/get'; import _merge from 'lodash/merge'; import classNames from 'classnames'; @@ -75,18 +76,18 @@ StatusDot.propTypes = { const columns = { resourceName: { - title: 'Deployment', + title: columnTitleDeployment, dataIndex: 'name', }, pods: { - title: 'Pods', + title: columnTitlePods, key: 'numEntities', isNumeric: true, render: d => d.pods.length, }, status: (name, classes) => { return { - title: name, + title: columnTitlePodStatus, key: 'status', render: d => { return d.pods.map(status => ( diff --git a/web/app/js/components/TapEventTable.jsx b/web/app/js/components/TapEventTable.jsx index 3972fa598..e07c1b852 100644 --- a/web/app/js/components/TapEventTable.jsx +++ b/web/app/js/components/TapEventTable.jsx @@ -11,6 +11,7 @@ import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import PropTypes from 'prop-types'; import React from 'react'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; @@ -53,7 +54,7 @@ const formatTapLatency = str => { }; const httpStatusCol = { - title: 'HTTP status', + title: columnTitleHTTPStatus, key: 'http-status', render: datum => { const d = _get(datum, 'responseInit.http.responseInit'); @@ -62,7 +63,7 @@ const httpStatusCol = { }; const responseInitLatencyCol = { - title: 'Latency', + title: columnTitleLatency, key: 'rsp-latency', isNumeric: true, render: datum => { @@ -72,7 +73,7 @@ const responseInitLatencyCol = { }; const grpcStatusCol = { - title: 'GRPC status', + title: columnTitleGRPCStatus, key: 'grpc-status', render: datum => { const d = _get(datum, 'responseEnd.http.responseEnd'); @@ -82,7 +83,7 @@ const grpcStatusCol = { }; const pathCol = { - title: 'Path', + title: columnTitlePath, key: 'path', render: datum => { const d = _get(datum, 'requestInit.http.requestInit'); @@ -91,7 +92,7 @@ const pathCol = { }; const methodCol = { - title: 'Method', + title: columnTitleMethod, key: 'method', render: datum => { const d = _get(datum, 'requestInit.http.requestInit'); @@ -101,12 +102,12 @@ const methodCol = { const topLevelColumns = (resourceType, ResourceLink) => [ { - title: 'Direction', + title: columnTitleDirection, key: 'direction', render: d => directionColumn(d.base.proxyDirection), }, { - title: 'Name', + title: columnTitleName, key: 'src-dst', render: d => { const datum = { diff --git a/web/app/js/components/TapQueryForm.jsx b/web/app/js/components/TapQueryForm.jsx index 55173e646..e4d5e97dd 100644 --- a/web/app/js/components/TapQueryForm.jsx +++ b/web/app/js/components/TapQueryForm.jsx @@ -24,6 +24,7 @@ import QueryToCliCmd from './QueryToCliCmd.jsx'; import React from 'react'; import Select from '@material-ui/core/Select'; import TextField from '@material-ui/core/TextField'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import _flatten from 'lodash/flatten'; import _isEmpty from 'lodash/isEmpty'; @@ -287,9 +288,17 @@ class TapQueryForm extends React.Component { const { handleTapStart, handleTapStop } = this.props; if (tapIsClosing) { - return (); + return ( + + ); } else if (tapInProgress) { - return (); + return ( + + ); } else { return ( ); } @@ -330,7 +339,7 @@ class TapQueryForm extends React.Component { - {this.renderNamespaceSelect('To Namespace', 'toNamespace', 'toResource')} + {this.renderNamespaceSelect(formToNamespace, 'toNamespace', 'toResource')} @@ -343,7 +352,7 @@ class TapQueryForm extends React.Component { - Authority + formAuthority }> - {advancedFormExpanded ? 'Hide filters' : 'Show more filters'} + {advancedFormExpanded ? formHideFilters : formShowFilters} @@ -421,7 +430,7 @@ class TapQueryForm extends React.Component { - {this.renderNamespaceSelect('Namespace', 'namespace', 'resource')} + {this.renderNamespaceSelect(formNamespace, 'namespace', 'resource')} @@ -433,7 +442,9 @@ class TapQueryForm extends React.Component { { this.renderTapButton(tapRequestInProgress, tapIsClosing) } - + diff --git a/web/app/js/components/TopEventTable.jsx b/web/app/js/components/TopEventTable.jsx index af262e71b..8638f2677 100644 --- a/web/app/js/components/TopEventTable.jsx +++ b/web/app/js/components/TopEventTable.jsx @@ -5,6 +5,7 @@ import BaseTable from './BaseTable.jsx'; import PropTypes from 'prop-types'; import React from 'react'; import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx'; +import { Trans } from '@lingui/macro'; import _isEmpty from 'lodash/isEmpty'; import _isNil from 'lodash/isNil'; import { withContext } from './util/AppContext.jsx'; @@ -16,7 +17,7 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [ render: d => directionColumn(d.direction), }, { - title: 'Name', + title: columnTitleName, filter: d => { const [labels, display] = extractDisplayName(d); return _isEmpty(labels[resourceType]) ? @@ -27,33 +28,33 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [ render: d => srcDstColumn(d, resourceType, ResourceLink), }, { - title: 'Method', + title: columnTitleMethod, dataIndex: 'httpMethod', filter: d => d.httpMethod, sorter: d => d.httpMethod, }, { - title: 'Path', + title: columnTitlePath, dataIndex: 'path', filter: d => d.path, sorter: d => d.path, }, { - title: 'Count', + title: columnTitleCount, dataIndex: 'count', isNumeric: true, defaultSortOrder: 'desc', sorter: d => d.count, }, { - title: 'Best', + title: columnTitleBest, dataIndex: 'best', isNumeric: true, render: d => formatLatencySec(d.best), sorter: d => d.best, }, { - title: 'Worst', + title: columnTitleWorst, dataIndex: 'worst', isNumeric: true, defaultSortOrder: 'desc', @@ -61,14 +62,14 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [ sorter: d => d.worst, }, { - title: 'Last', + title: columnTitleLast, dataIndex: 'last', isNumeric: true, render: d => formatLatencySec(d.last), sorter: d => d.last, }, { - title: 'Success Rate', + title: columnTitleSuccessRate, dataIndex: 'successRate', isNumeric: true, render: d => _isNil(d) || _isNil(d.successRate) ? '---' : @@ -76,7 +77,7 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [ sorter: d => d.successRate.get(), }, { - title: 'Tap', + title: columnTitleTap, key: 'tap', isNumeric: true, render: d => tapLink(d, resourceType, PrefixedLink), diff --git a/web/app/js/components/TopRoutes.jsx b/web/app/js/components/TopRoutes.jsx index 82705058b..f3fd5f6ce 100644 --- a/web/app/js/components/TopRoutes.jsx +++ b/web/app/js/components/TopRoutes.jsx @@ -17,6 +17,7 @@ import QueryToCliCmd from './QueryToCliCmd.jsx'; import React from 'react'; import Select from '@material-ui/core/Select'; import TopRoutesModule from './TopRoutesModule.jsx'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; @@ -232,11 +233,11 @@ class TopRoutes extends React.Component { - { this.renderNamespaceDropdown('To Namespace', 'to_namespace', 'Namespace of target resource') } + { this.renderNamespaceDropdown(formToNamespace, 'to_namespace', 'Namespace of target resource') } - { this.renderResourceDropdown('To Resource', 'to_name', 'to_type', 'Target resource') } + { this.renderResourceDropdown(formToResource, 'to_name', 'to_type', 'Target resource') } diff --git a/web/app/js/components/TopRoutesTable.jsx b/web/app/js/components/TopRoutesTable.jsx index 266d9afac..d13f929a8 100644 --- a/web/app/js/components/TopRoutesTable.jsx +++ b/web/app/js/components/TopRoutesTable.jsx @@ -2,52 +2,53 @@ import BaseTable from './BaseTable.jsx'; import PropTypes from 'prop-types'; import React from 'react'; import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx'; +import { Trans } from '@lingui/macro'; import { metricToFormatter } from './util/Utils.js'; const routesColumns = [ { - title: 'Route', + title: columnTitleRoute, dataIndex: 'route', filter: d => d.route, sorter: d => d.route, }, { - title: 'Service', + title: columnTitleService, tooltip: 'hostname:port used when communicating with this target', dataIndex: 'authority', filter: d => d.authority, sorter: d => d.authority, }, { - title: 'Success Rate', + title: columnTitleSuccessRate, dataIndex: 'successRate', isNumeric: true, render: d => , sorter: d => d.successRate, }, { - title: 'RPS', + title: columnTitleRPS, dataIndex: 'requestRate', isNumeric: true, render: d => metricToFormatter.NO_UNIT(d.requestRate), sorter: d => d.requestRate, }, { - title: 'P50 Latency', + title: columnTitleP50Latency, dataIndex: 'latency.P50', isNumeric: true, render: d => metricToFormatter.LATENCY(d.latency.P50), sorter: d => d.latency.P50, }, { - title: 'P95 Latency', + title: columnTitleP95Latency, dataIndex: 'latency.P95', isNumeric: true, render: d => metricToFormatter.LATENCY(d.latency.P95), sorter: d => d.latency.P95, }, { - title: 'P99 Latency', + title: columnTitleP99Latency, dataIndex: 'latency.P99', isNumeric: true, render: d => metricToFormatter.LATENCY(d.latency.P99), diff --git a/web/app/js/components/TrafficSplitDetail.jsx b/web/app/js/components/TrafficSplitDetail.jsx index a24bd83c7..da9521769 100644 --- a/web/app/js/components/TrafficSplitDetail.jsx +++ b/web/app/js/components/TrafficSplitDetail.jsx @@ -3,6 +3,7 @@ import MetricsTable from './MetricsTable.jsx'; import Octopus from './Octopus.jsx'; import PropTypes from 'prop-types'; import React from 'react'; +import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import _each from 'lodash/each'; import _isNil from 'lodash/isNil'; @@ -73,7 +74,7 @@ const TrafficSplitDetail = ({ resourceMetrics, resourceName, resourceRsp, resour metrics={resourceMetrics} showName={false} showNamespaceColumn={false} - title="Leaf Services" /> + title={tableTitleLeafServices} />
); }; diff --git a/web/app/js/components/util/CopyUtils.jsx b/web/app/js/components/util/CopyUtils.jsx index b62d45e1b..dbb769ff1 100644 --- a/web/app/js/components/util/CopyUtils.jsx +++ b/web/app/js/components/util/CopyUtils.jsx @@ -6,7 +6,7 @@ import Typography from '@material-ui/core/Typography'; * Instructions for adding resources to service mesh */ export const incompleteMeshMessage = name => { - const unspecifiedResources = one or more resources; + const unspecifiedResources = unspecifiedResourcesMsg; const inject = linkerd inject k8s.yml | kubectl apply -f -; return ( diff --git a/web/app/js/locales/en/messages.json b/web/app/js/locales/en/messages.json index 59ed9efd0..fac755632 100644 --- a/web/app/js/locales/en/messages.json +++ b/web/app/js/locales/en/messages.json @@ -1,5 +1,104 @@ { + "404Msg": "Page not found.", "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh", - "No data to display": "No data to display", - "one or more resources": "one or more resources" + "All namespaces have a {productName} install.": "All namespaces have a {productName} install.", + "Control plane components": "Control plane components", + "Data plane proxies": "Data plane proxies", + "NoDataToDisplayMsg": "No data to display", + "buttonReset": "Reset", + "buttonRunLinkerdCheck": "Run Linkerd Check", + "buttonStart": "Start", + "buttonStop": "Stop", + "columnTitleAlive": "Alive", + "columnTitleApexService": "Apex Service", + "columnTitleBest": "Best", + "columnTitleClusterName": "Cluster Name", + "columnTitleCount": "Count", + "columnTitleDeployment": "Deployment", + "columnTitleDirection": "Direction", + "columnTitleGRPCStatus": "GRPC Status", + "columnTitleGrafana": "Grafana", + "columnTitleHTTPStatus": "HTTP Status", + "columnTitleIdentity": "Identity", + "columnTitleJaeger": "Jaeger", + "columnTitleLast": "Last", + "columnTitleLatency": "Latency", + "columnTitleLeafService": "Leaf Service", + "columnTitleMeshed": "Meshed", + "columnTitleMeshedPods": "Meshed Pods", + "columnTitleMeshedStatus": "Meshed Status", + "columnTitleMethod": "Method", + "columnTitleName": "Name", + "columnTitleNamespace": "Namespace", + "columnTitleOpenConnections": "Connections", + "columnTitleP50Latency": "P50 Latency", + "columnTitleP95Latency": "P95 Latency", + "columnTitleP99Latency": "P99 Latency", + "columnTitlePairedServices": "Paired Services", + "columnTitlePath": "Path", + "columnTitlePodStatus": "Pod Status", + "columnTitlePods": "Pods", + "columnTitleRPS": "RPS", + "columnTitleReadRate": "Read Bytes / sec", + "columnTitleRoute": "Route", + "columnTitleSecured": "Secured", + "columnTitleService": "Service", + "columnTitleSuccessRate": "Success Rate", + "columnTitleTap": "Tap", + "columnTitleValue": "Value", + "columnTitleWeight": "Weight", + "columnTitleWorst": "Worst", + "columnTitleWriteRate": "Write Bytes / sec", + "componentsMsg": "Components", + "formAuthority": "Authority", + "formHTTPMethod": "HTTP Method", + "formHideFilters": "Hide filters", + "formMaxRPS": "Max RPS", + "formNamespace": "Namespace", + "formPath": "Path", + "formScheme": "Scheme", + "formShowFilters": "Show more filters", + "formToNamespace": "To Namespace", + "formToResource": "To Resource", + "menuItemCommunity": "Community", + "menuItemControlPlane": "Control Plane", + "menuItemCronJobs": "Cron Jobs", + "menuItemDaemonSets": "Daemon Sets", + "menuItemDeployments": "Deployments", + "menuItemDocumentation": "Documentation", + "menuItemGateway": "Gateway", + "menuItemGitHub": "GitHub", + "menuItemJobs": "Jobs", + "menuItemMailingList": "Mailing List", + "menuItemNamespaces": "Namespaces", + "menuItemPods": "Pods", + "menuItemReplicaSets": "Replica Sets", + "menuItemReplicationControllers": "Replication Controllers", + "menuItemRoutes": "Routes", + "menuItemSlack": "Slack", + "menuItemStatefulSets": "Stateful Sets", + "menuItemTap": "Tap", + "menuItemTop": "Top", + "menuItemTrafficSplits": "Traffic Splits", + "noResourcesDetectedMsg": "No resources detected.", + "podsAreInitializingMsg": "Pods are initializing", + "sidebarHeadingCluster": "Cluster", + "sidebarHeadingConfiguration": "Configuration", + "sidebarHeadingTools": "Tools", + "sidebarHeadingWorkloads": "Workloads", + "tableTitleEdgesEmpty": "Edges", + "tableTitleEdgesWithIdentity {identity}": "Edges (Identity: {identity})", + "tableTitleGateways": "Gateways", + "tableTitleHTTPMetrics": "HTTP Metrics", + "tableTitleInbound": "Inbound", + "tableTitleLeafServices": "Leaf Services", + "tableTitleOutbound": "Outbound", + "tableTitlePods": "Pods", + "tableTitleTCP": "TCP", + "tableTitleTCPMetrics": "TCP Metrics", + "unspecifiedResourcesMsg": "one or more resources", + "{numUnadded} namespace has no meshed resources.": "{numUnadded} namespace has no meshed resources.", + "{numUnadded} namespaces have no meshed resources.": "{numUnadded} namespaces have no meshed resourcesß", + "{productName} namespace": "{productName} namespace", + "{productName} version": "{productName} version" } diff --git a/web/app/js/locales/es/messages.json b/web/app/js/locales/es/messages.json index a390f26d7..698825593 100644 --- a/web/app/js/locales/es/messages.json +++ b/web/app/js/locales/es/messages.json @@ -1,5 +1,104 @@ { - "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Agregue {0} a la k8s.yml file<0/><1/>Luego ejecuta {inject} para inyectarla en la malla de servicios", - "No data to display": "No hay datos que mostrar", - "one or more resources": "uno más recursos más" + "404Msg": "Página no encontrada.", + "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Agrega {0} al archivo k8s.yml<0/><1/>Luego ejecuta {inject} para inyectarlo en la malla de servicios", + "All namespaces have a {productName} install.": "Todos los namespaces tienen una instalación de {productName}.", + "Control plane components": "Componentes del plano de control", + "Data plane proxies": "Proxies del plano de datos", + "NoDataToDisplayMsg": "No hay datos que mostrar", + "buttonReset": "Restablecer", + "buttonRunLinkerdCheck": "Ejecutar Linkerd Check", + "buttonStart": "Empezar", + "buttonStop": "Terminar", + "columnTitleAlive": "Vivo", + "columnTitleApexService": "Servicio Apice", + "columnTitleBest": "Mejor", + "columnTitleClusterName": "Nombre del Clúster", + "columnTitleCount": "Total", + "columnTitleDeployment": "Deployment", + "columnTitleDirection": "Dirección", + "columnTitleGRPCStatus": "Estado GRPC", + "columnTitleGrafana": "Grafana", + "columnTitleHTTPStatus": "Estado HTTP", + "columnTitleIdentity": "Identidad", + "columnTitleJaeger": "Jaeger", + "columnTitleLast": "Último", + "columnTitleLatency": "Latencia", + "columnTitleLeafService": "Servicio Hoja", + "columnTitleMeshed": "En la malla de servicios", + "columnTitleMeshedPods": "Pods en la malla de servicios", + "columnTitleMeshedStatus": "Estado de la malla de servicios", + "columnTitleMethod": "Método", + "columnTitleName": "Nombre", + "columnTitleNamespace": "Namespace", + "columnTitleOpenConnections": "Conexiones", + "columnTitleP50Latency": "Latencia P50", + "columnTitleP95Latency": "Latencia P95", + "columnTitleP99Latency": "Latencia P99", + "columnTitlePairedServices": "Servicios Emparejados", + "columnTitlePath": "Ruta", + "columnTitlePodStatus": "Estado del Pod", + "columnTitlePods": "Pods", + "columnTitleRPS": "PPS", + "columnTitleReadRate": "Lectura Bytes / seg", + "columnTitleRoute": "Ruta", + "columnTitleSecured": "Asegurado", + "columnTitleService": "Servicio", + "columnTitleSuccessRate": "Tasa de éxito", + "columnTitleTap": "Tap", + "columnTitleValue": "Valor", + "columnTitleWeight": "Peso", + "columnTitleWorst": "Peor", + "columnTitleWriteRate": "Escritura Bytes / seg", + "componentsMsg": "Componentes", + "formAuthority": "Authority", + "formHTTPMethod": "Método HTTP", + "formHideFilters": "Ocultar filtros", + "formMaxRPS": "Max PPS", + "formNamespace": "Namespace", + "formPath": "Ruta", + "formScheme": "Esquema", + "formShowFilters": "Mostrar más filtros", + "formToNamespace": "Al Namespace", + "formToResource": "Al Recurso", + "menuItemCommunity": "Comunidad", + "menuItemControlPlane": "Plano de Control", + "menuItemCronJobs": "Cron Jobs", + "menuItemDaemonSets": "Daemon Sets", + "menuItemDeployments": "Deployments", + "menuItemDocumentation": "Documentación", + "menuItemGateway": "Gateway", + "menuItemGitHub": "GitHub", + "menuItemJobs": "Jobs", + "menuItemMailingList": "Lista de Correo", + "menuItemNamespaces": "Namespaces", + "menuItemPods": "Pods", + "menuItemReplicaSets": "Replica Sets", + "menuItemReplicationControllers": "Replication Controllers", + "menuItemRoutes": "Rutas", + "menuItemSlack": "Slack", + "menuItemStatefulSets": "Stateful Sets", + "menuItemTap": "Tap", + "menuItemTop": "Top", + "menuItemTrafficSplits": "Traffic Splits", + "noResourcesDetectedMsg": "No se han encontrado recursos.", + "podsAreInitializingMsg": "Los pods se están inicializando", + "sidebarHeadingCluster": "Clúster", + "sidebarHeadingConfiguration": "Configuración", + "sidebarHeadingTools": "Herramientas", + "sidebarHeadingWorkloads": "Cargas de trabajo", + "tableTitleEdgesEmpty": "Bordes", + "tableTitleEdgesWithIdentity {identity}": "Bordes (Identidad: {identity})", + "tableTitleGateways": "Gateways", + "tableTitleHTTPMetrics": "Métricas HTTP", + "tableTitleInbound": "Entrada", + "tableTitleLeafServices": "Servicios Hoja", + "tableTitleOutbound": "Salida", + "tableTitlePods": "Pods", + "tableTitleTCP": "TCP", + "tableTitleTCPMetrics": "Métricas TCP", + "unspecifiedResourcesMsg": "uno o más recursos", + "{numUnadded} namespace has no meshed resources.": "El namespace {numUnadded} no tiene recursos en la malla de servicios.", + "{numUnadded} namespaces have no meshed resources.": "Los namespaces {numUnadded} no tienen recursos en la malla de servicios.", + "{productName} namespace": "Namespace de {productName}", + "{productName} version": "Versión de {productName}" }