Add es translations and docs instructions to dashboard (#4866)

* Adding es translations

Signed-off-by: Carol Scott carol@buoyant.io
This commit is contained in:
Carol A. Scott 2020-08-14 10:50:51 -07:00 committed by GitHub
parent c1fd6c3cae
commit 0ffc44ad2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 375 additions and 124 deletions

View File

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

View File

@ -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 = {

View File

@ -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
<Trans>buttonRunLinkerdCheck</Trans>
</Button>
</Grid>
</Grid>

View File

@ -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: <Trans>columnTitleNamespace</Trans>,
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: <Trans>columnTitleName</Trans>,
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: <Trans>columnTitleIdentity</Trans>,
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: <Trans>columnTitleSecured</Trans>,
dataIndex: 'message',
isNumeric: true,
render: d => {
@ -83,12 +84,12 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => {
};
const generateEdgesTableTitle = edges => {
let title = 'Edges';
let title = <Trans>tableTitleEdgesEmpty</Trans>;
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 = <Trans>tableTitleEdgesWithIdentity {identity}</Trans>;
}
}
return title;

View File

@ -17,7 +17,7 @@ const EmptyCard = ({ classes }) => {
<Card className={classes.card} elevation={3}>
<CardContent>
<Typography>
<Trans>No data to display</Trans>
<Trans>NoDataToDisplayMsg</Trans>
</Typography>
</CardContent>
</Card>

View File

@ -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 (
<Tooltip title="Pods are initializing"><CircularProgress size={20} thickness={4} /></Tooltip>
<Tooltip title={<Trans>podsAreInitializingMsg</Trans>}>
<CircularProgress size={20} thickness={4} />
</Tooltip>
);
} else {
return (

View File

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

View File

@ -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 : (
<div className="page-section">
<MetricsTable
title="Gateways"
title={<Trans>tableTitleGateways</Trans>}
resource="gateway"
metrics={metrics} />
</div>

View File

@ -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: <Trans>columnTitleNamespace</Trans>,
dataIndex: 'namespace',
sorter: d => d.namespace,
render: d => {
@ -37,12 +38,12 @@ const namespacesColumns = PrefixedLink => [
},
},
{
title: 'Meshed pods',
title: <Trans>columnTitleMeshedPods</Trans>,
dataIndex: 'meshedPodsStr',
isNumeric: true,
},
{
title: 'Meshed Status',
title: <Trans>columnTitleMeshedStatus</Trans>,
key: 'meshification',
render: row => {
const percent = row.meshedPercent.get();

View File

@ -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: <Trans>columnTitleOpenConnections</Trans>,
dataIndex: 'tcp.openConnections',
isNumeric: true,
render: d => metricToFormatter.NO_UNIT(d.tcp.openConnections),
sorter: d => d.tcp.openConnections,
},
{
title: 'Read Bytes / sec',
title: <Trans>columnTitleReadRate</Trans>,
dataIndex: 'tcp.readRate',
isNumeric: true,
render: d => metricToFormatter.BYTES(d.tcp.readRate),
sorter: d => d.tcp.readRate,
},
{
title: 'Write Bytes / sec',
title: <Trans>columnTitleWriteRate</Trans>,
dataIndex: 'tcp.writeRate',
isNumeric: true,
render: d => metricToFormatter.BYTES(d.tcp.writeRate),
@ -40,35 +41,35 @@ const tcpStatColumns = [
const httpStatColumns = [
{
title: 'Success Rate',
title: <Trans>columnTitleSuccessRate</Trans>,
dataIndex: 'successRate',
isNumeric: true,
render: d => <SuccessRateMiniChart sr={d.successRate} />,
sorter: d => d.successRate,
},
{
title: 'RPS',
title: <Trans>columnTitleRPS</Trans>,
dataIndex: 'requestRate',
isNumeric: true,
render: d => metricToFormatter.NO_UNIT(d.requestRate),
sorter: d => d.requestRate,
},
{
title: 'P50 Latency',
title: <Trans>columnTitleP50Latency</Trans>,
dataIndex: 'P50',
isNumeric: true,
render: d => metricToFormatter.LATENCY(d.P50),
sorter: d => d.P50,
},
{
title: 'P95 Latency',
title: <Trans>columnTitleP95Latency</Trans>,
dataIndex: 'P95',
isNumeric: true,
render: d => metricToFormatter.LATENCY(d.P95),
sorter: d => d.P95,
},
{
title: 'P99 Latency',
title: <Trans>columnTitleP99Latency</Trans>,
dataIndex: 'P99',
isNumeric: true,
render: d => metricToFormatter.LATENCY(d.P99),
@ -79,7 +80,7 @@ const httpStatColumns = [
const trafficSplitDetailColumns = [
{
title: 'Apex Service',
title: <Trans>columnTitleApexService</Trans>,
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: <Trans>columnTitleLeafService</Trans>,
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: <Trans>columnTitleWeight</Trans>,
dataIndex: 'weight',
isNumeric: true,
filter: d => !d.tsStats ? null : d.tsStats.weight,
@ -113,42 +114,42 @@ const trafficSplitDetailColumns = [
const gatewayColumns = [
{
title: 'ClusterName',
title: <Trans>columnTitleClusterName</Trans>,
dataIndex: 'clusterName',
isNumeric: false,
render: d => !d.clusterName ? '---' : d.clusterName,
sorter: d => !d.clusterName ? '---' : d.clusterName,
},
{
title: 'Alive',
title: <Trans>columnTitleAlive</Trans>,
dataIndex: 'alive',
isNumeric: false,
render: d => !d.alive ? 'FALSE' : 'TRUE',
sorter: d => d.alive,
},
{
title: 'Paired Services',
title: <Trans>columnTitlePairedServices</Trans>,
dataIndex: 'pairedServices',
isNumeric: false,
render: d => d.pairedServices,
sorter: d => d.pairedServices,
},
{
title: 'P50 Latency',
title: <Trans>columnTitleP50Latency</Trans>,
dataIndex: 'P50',
isNumeric: true,
render: d => metricToFormatter.LATENCY(d.P50),
sorter: d => d.P50,
},
{
title: 'P95 Latency',
title: <Trans>columnTitleP95Latency</Trans>,
dataIndex: 'P95',
isNumeric: true,
render: d => metricToFormatter.LATENCY(d.P95),
sorter: d => d.P95,
},
{
title: 'P99 Latency',
title: <Trans>columnTitleP99Latency</Trans>,
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: <Trans>columnTitleNamespace</Trans>,
dataIndex: 'namespace',
filter: d => d.namespace,
isNumeric: false,
@ -175,7 +176,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix
];
const meshedColumn = {
title: 'Meshed',
title: <Trans>columnTitleMeshed</Trans>,
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: <Trans>columnTitleGrafana</Trans>,
key: 'grafanaDashboard',
isNumeric: true,
render: row => {
@ -203,7 +204,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix
const jaegerColumn = {
title: 'Jaeger',
title: <Trans>columnTitleJaeger</Trans>,
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,
};

View File

@ -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 : <ErrorBanner message={error} />}
{!loaded ? <Spinner /> : (
<div>
{noMetrics ? <div>No resources detected.</div> : null}
{noMetrics ? <div><Trans>noResourcesDetectedMsg</Trans></div> : null}
{
_isEmpty(deploymentsWithMetrics) ? null :
<NetworkGraph namespace={ns} deployments={metrics.deployment} />
@ -166,7 +167,7 @@ class Namespaces extends React.Component {
noMetrics ? null :
<div className="page-section">
<MetricsTable
title="TCP"
title={<Trans>tableTitleTCP</Trans>}
resource="pod"
metrics={metrics.pod}
isTcpTable />

View File

@ -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 {
<Divider />
<MenuList>
<Typography variant="button" component="div" className={classes.sidebarHeading}>
Cluster
<Trans>sidebarHeadingCluster</Trans>
</Typography>
{ this.menuItem('/namespaces', 'Namespaces', namespaceIcon) }
{ this.menuItem('/namespaces', <Trans>menuItemNamespaces</Trans>, namespaceIcon) }
{ this.menuItem('/controlplane', 'Control Plane',
{ this.menuItem('/controlplane', <Trans>menuItemControlPlane</Trans>,
<FontAwesomeIcon icon={faCloud} className={classes.shrinkCloudIcon} />) }
{ this.menuItem('/gateways', 'Gateway',
{ this.menuItem('/gateways', <Trans>menuItemGateway</Trans>,
<FontAwesomeIcon icon={faDungeon} className={classes.shrinkIcon} />) }
</MenuList>
@ -500,48 +501,48 @@ class NavigationBase extends React.Component {
<MenuList>
<Typography variant="button" component="div" className={classes.sidebarHeading}>
Workloads
<Trans>sidebarHeadingWorkloads</Trans>
</Typography>
{ this.menuItem(`/namespaces/${selectedNamespace}/cronjobs`, 'Cron Jobs', cronJobIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/cronjobs`, <Trans>menuItemCronJobs</Trans>, cronJobIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/daemonsets`, 'Daemon Sets', daemonsetIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/daemonsets`, <Trans>menuItemDaemonSets</Trans>, daemonsetIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/deployments`, 'Deployments', deploymentIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/deployments`, <Trans>menuItemDeployments</Trans>, deploymentIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/jobs`, 'Jobs', jobIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/jobs`, <Trans>menuItemJobs</Trans>, jobIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/pods`, 'Pods', podIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/pods`, <Trans>menuItemPods</Trans>, podIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/replicasets`, 'Replica Sets', replicaSetIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/replicasets`, <Trans>menuItemReplicaSets</Trans>, replicaSetIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/replicationcontrollers`, 'Replication Controllers', replicaSetIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/replicationcontrollers`, <Trans>menuItemReplicationControllers</Trans>, replicaSetIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/statefulsets`, 'Stateful Sets', statefulSetIcon) }
{ this.menuItem(`/namespaces/${selectedNamespace}/statefulsets`, <Trans>menuItemStatefulSets</Trans>, statefulSetIcon) }
</MenuList>
<MenuList>
<Typography variant="button" component="div" className={classes.sidebarHeading}>
Configuration
<Trans>sidebarHeadingConfiguration</Trans>
</Typography>
{ this.menuItem(`/namespaces/${selectedNamespace}/trafficsplits`, 'Traffic Splits', <FontAwesomeIcon icon={faFilter} className={classes.shrinkIcon} />) }
{ this.menuItem(`/namespaces/${selectedNamespace}/trafficsplits`, <Trans>menuItemTrafficSplits</Trans>, <FontAwesomeIcon icon={faFilter} className={classes.shrinkIcon} />) }
</MenuList>
<Divider />
<MenuList>
<Typography variant="button" component="div" className={classes.sidebarHeading}>
Tools
<Trans>sidebarHeadingTools</Trans>
</Typography>
{ this.menuItem('/tap', 'Tap', <FontAwesomeIcon icon={faMicroscope} className={classes.shrinkIcon} />) }
{ this.menuItem('/top', 'Top', <FontAwesomeIcon icon={faStream} className={classes.shrinkIcon} />) }
{ this.menuItem('/routes', 'Routes', <FontAwesomeIcon icon={faRandom} className={classes.shrinkIcon} />) }
{ this.menuItem('/tap', <Trans>menuItemTap</Trans>, <FontAwesomeIcon icon={faMicroscope} className={classes.shrinkIcon} />) }
{ this.menuItem('/top', <Trans>menuItemTop</Trans>, <FontAwesomeIcon icon={faStream} className={classes.shrinkIcon} />) }
{ this.menuItem('/routes', <Trans>menuItemRoutes</Trans>, <FontAwesomeIcon icon={faRandom} className={classes.shrinkIcon} />) }
</MenuList>
<Divider />
<MenuList>
{ this.menuItem('/community', 'Community',
{ this.menuItem('/community', <Trans>menuItemCommunity</Trans>,
<Badge
classes={{ badge: classes.badge }}
invisible={hideUpdateBadge}
@ -551,25 +552,25 @@ class NavigationBase extends React.Component {
<MenuItem component="a" href="https://linkerd.io/2/overview/" target="_blank" className={classes.navMenuItem}>
<ListItemIcon><LibraryBooksIcon className={classes.shrinkIcon} /></ListItemIcon>
<ListItemText primary="Documentation" />
<ListItemText primary={<Trans>menuItemDocumentation</Trans>} />
<FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size="xs" />
</MenuItem>
<MenuItem component="a" href="https://github.com/linkerd/linkerd2/issues/new/choose" target="_blank" className={classes.navMenuItem}>
<ListItemIcon>{githubIcon}</ListItemIcon>
<ListItemText primary="GitHub" />
<ListItemText primary={<Trans>menuItemGitHub</Trans>} />
<FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size="xs" />
</MenuItem>
<MenuItem component="a" href="https://lists.cncf.io/g/cncf-linkerd-users" target="_blank" className={classes.navMenuItem}>
<ListItemIcon><EmailIcon className={classes.shrinkIcon} /></ListItemIcon>
<ListItemText primary="Mailing List" />
<ListItemText primary={<Trans>menuItemMailingList</Trans>} />
<FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size="xs" />
</MenuItem>
<MenuItem component="a" href="https://slack.linkerd.io" target="_blank" className={classes.navMenuItem}>
<ListItemIcon>{slackIcon}</ListItemIcon>
<ListItemText primary="Slack" />
<ListItemText primary={<Trans>menuItemSlack</Trans>} />
<FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size="xs" />
</MenuItem>

View File

@ -1,9 +1,14 @@
import React from 'react';
import { Trans } from '@lingui/macro';
const NoMatch = () => (
<div>
<h3>404</h3>
<div>Page not found.</div>
<div>
<Trans>
404Msg
</Trans>
</div>
</div>
);

View File

@ -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 :
<MetricsTable
resource="multi_resource"
title="Inbound"
title={<Trans>tableTitleInbound</Trans>}
metrics={upstreamDisplayMetrics} />
}
{_isEmpty(downstreamDisplayMetrics) ? null :
<MetricsTable
resource="multi_resource"
title="Outbound"
title={<Trans>tableTitleOutbound</Trans>}
metrics={downstreamDisplayMetrics} />
}
@ -417,13 +418,13 @@ export class ResourceDetailBase extends React.Component {
resourceType === 'pod' || isTcpOnly ? null :
<MetricsTable
resource="pod"
title="Pods"
title={<Trans>tableTitlePods</Trans>}
metrics={podMetrics} />
}
<MetricsTable
resource="pod"
title="TCP"
title={<Trans>tableTitleTCP</Trans>}
isTcpTable
metrics={podMetrics} />

View File

@ -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 {
<MetricsTable
resource={resource}
metrics={processedMetrics}
title="HTTP metrics" />
title={<Trans>tableTitleHTTPMetrics</Trans>} />
{resource !== 'trafficsplit' &&
<MetricsTable
resource={resource}
isTcpTable
metrics={processedMetrics}
title="TCP metrics" />
title={<Trans>tableTitleTCPMetrics</Trans>} />
}
</React.Fragment>
);

View File

@ -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: <Trans>columnTitleName</Trans>,
dataIndex: 'name',
},
{
title: 'Value',
title: <Trans>columnTitleValue</Trans>,
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: <Trans>{productName} namespace</Trans>, value: controllerNamespace },
{ key: 3, name: <Trans>Control plane components</Trans>, value: components.length },
{ key: 4, name: <Trans>Data plane proxies</Trans>, value: this.proxyCount() },
];
}
@ -225,7 +226,7 @@ class ServiceMesh extends React.Component {
<Typography variant="h6">Control plane</Typography>
</Grid>
<Grid item xs={3}>
<Typography align="right">Components</Typography>
<Typography align="right"><Trans>componentsMsg</Trans></Typography>
<Typography align="right">{components.length}</Typography>
</Grid>
</Grid>

View File

@ -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: <Trans>columnTitleDeployment</Trans>,
dataIndex: 'name',
},
pods: {
title: 'Pods',
title: <Trans>columnTitlePods</Trans>,
key: 'numEntities',
isNumeric: true,
render: d => d.pods.length,
},
status: (name, classes) => {
return {
title: name,
title: <Trans>columnTitlePodStatus</Trans>,
key: 'status',
render: d => {
return d.pods.map(status => (

View File

@ -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: <Trans>columnTitleHTTPStatus</Trans>,
key: 'http-status',
render: datum => {
const d = _get(datum, 'responseInit.http.responseInit');
@ -62,7 +63,7 @@ const httpStatusCol = {
};
const responseInitLatencyCol = {
title: 'Latency',
title: <Trans>columnTitleLatency</Trans>,
key: 'rsp-latency',
isNumeric: true,
render: datum => {
@ -72,7 +73,7 @@ const responseInitLatencyCol = {
};
const grpcStatusCol = {
title: 'GRPC status',
title: <Trans>columnTitleGRPCStatus</Trans>,
key: 'grpc-status',
render: datum => {
const d = _get(datum, 'responseEnd.http.responseEnd');
@ -82,7 +83,7 @@ const grpcStatusCol = {
};
const pathCol = {
title: 'Path',
title: <Trans>columnTitlePath</Trans>,
key: 'path',
render: datum => {
const d = _get(datum, 'requestInit.http.requestInit');
@ -91,7 +92,7 @@ const pathCol = {
};
const methodCol = {
title: 'Method',
title: <Trans>columnTitleMethod</Trans>,
key: 'method',
render: datum => {
const d = _get(datum, 'requestInit.http.requestInit');
@ -101,12 +102,12 @@ const methodCol = {
const topLevelColumns = (resourceType, ResourceLink) => [
{
title: 'Direction',
title: <Trans>columnTitleDirection</Trans>,
key: 'direction',
render: d => directionColumn(d.base.proxyDirection),
},
{
title: 'Name',
title: <Trans>columnTitleName</Trans>,
key: 'src-dst',
render: d => {
const datum = {

View File

@ -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 (<Button variant="outlined" color="primary" className="tap-ctrl tap-stop" disabled>Stop</Button>);
return (
<Button variant="outlined" color="primary" className="tap-ctrl tap-stop" disabled>
<Trans>buttonStop</Trans>
</Button>
);
} else if (tapInProgress) {
return (<Button variant="outlined" color="primary" className="tap-ctrl tap-stop" onClick={handleTapStop}>Stop</Button>);
return (
<Button variant="outlined" color="primary" className="tap-ctrl tap-stop" onClick={handleTapStop}>
<Trans>buttonStop</Trans>
</Button>
);
} else {
return (
<Button
@ -298,7 +307,7 @@ class TapQueryForm extends React.Component {
className="tap-ctrl tap-start"
disabled={!query.namespace || !query.resource}
onClick={handleTapStart}>
Start
<Trans>buttonStart</Trans>
</Button>
);
}
@ -330,7 +339,7 @@ class TapQueryForm extends React.Component {
<Grid container spacing={3}>
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
<FormControl className={classes.formControl}>
{this.renderNamespaceSelect('To Namespace', 'toNamespace', 'toResource')}
{this.renderNamespaceSelect(<Trans>formToNamespace</Trans>, 'toNamespace', 'toResource')}
</FormControl>
</Grid>
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
@ -343,7 +352,7 @@ class TapQueryForm extends React.Component {
<Grid container spacing={3}>
<Grid item xs={6} md={3} classes={{ item: classes.formControlWrapper }}>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="authority">Authority</InputLabel>
<InputLabel htmlFor="authority"><Trans>formAuthority</Trans></InputLabel>
<Select
value={query.authority}
onChange={this.handleFormChange('authority')}
@ -359,20 +368,20 @@ class TapQueryForm extends React.Component {
</FormControl>
</Grid>
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
{ this.renderTextInput('Path', 'path', 'Display requests with paths that start with this prefix') }
{ this.renderTextInput(<Trans>formPath</Trans>, 'path', 'Display requests with paths that start with this prefix') }
</Grid>
</Grid>
<Grid container spacing={3}>
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
{ this.renderTextInput('Scheme', 'scheme', 'Display requests with this scheme') }
{ this.renderTextInput(<Trans>formScheme</Trans>, 'scheme', 'Display requests with this scheme') }
</Grid>
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
{ this.renderTextInput('Max RPS', 'maxRps', `Maximum requests per second to tap. Default ${defaultMaxRps}`) }
{ this.renderTextInput(<Trans>formMaxRPS</Trans>, 'maxRps', `Maximum requests per second to tap. Default ${defaultMaxRps}`) }
</Grid>
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="method">HTTP method</InputLabel>
<InputLabel htmlFor="method"><Trans>formHTTPMethod</Trans></InputLabel>
<Select
value={query.method}
onChange={this.handleFormChange('method')}
@ -400,7 +409,7 @@ class TapQueryForm extends React.Component {
<ExpansionPanel expanded={advancedFormExpanded} onChange={this.handleAdvancedFormExpandClick} elevation={3}>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="caption" gutterBottom>
{advancedFormExpanded ? 'Hide filters' : 'Show more filters'}
{advancedFormExpanded ? <Trans>formHideFilters</Trans> : <Trans>formShowFilters</Trans>}
</Typography>
</ExpansionPanelSummary>
@ -421,7 +430,7 @@ class TapQueryForm extends React.Component {
<Grid container spacing={3}>
<Grid item xs={6} md="auto" className={classes.formControlWrapper}>
<FormControl className={classes.formControl} fullWidth>
{this.renderNamespaceSelect('Namespace', 'namespace', 'resource')}
{this.renderNamespaceSelect(<Trans>formNamespace</Trans>, 'namespace', 'resource')}
</FormControl>
</Grid>
@ -433,7 +442,9 @@ class TapQueryForm extends React.Component {
<Grid item>
{ this.renderTapButton(tapRequestInProgress, tapIsClosing) }
<Button onClick={this.resetTapForm} disabled={tapRequestInProgress} className={classes.resetButton}>Reset</Button>
<Button onClick={this.resetTapForm} disabled={tapRequestInProgress} className={classes.resetButton}>
<Trans>buttonReset</Trans>
</Button>
</Grid>
</Grid>
</CardContent>

View File

@ -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: <Trans>columnTitleName</Trans>,
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: <Trans>columnTitleMethod</Trans>,
dataIndex: 'httpMethod',
filter: d => d.httpMethod,
sorter: d => d.httpMethod,
},
{
title: 'Path',
title: <Trans>columnTitlePath</Trans>,
dataIndex: 'path',
filter: d => d.path,
sorter: d => d.path,
},
{
title: 'Count',
title: <Trans>columnTitleCount</Trans>,
dataIndex: 'count',
isNumeric: true,
defaultSortOrder: 'desc',
sorter: d => d.count,
},
{
title: 'Best',
title: <Trans>columnTitleBest</Trans>,
dataIndex: 'best',
isNumeric: true,
render: d => formatLatencySec(d.best),
sorter: d => d.best,
},
{
title: 'Worst',
title: <Trans>columnTitleWorst</Trans>,
dataIndex: 'worst',
isNumeric: true,
defaultSortOrder: 'desc',
@ -61,14 +62,14 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [
sorter: d => d.worst,
},
{
title: 'Last',
title: <Trans>columnTitleLast</Trans>,
dataIndex: 'last',
isNumeric: true,
render: d => formatLatencySec(d.last),
sorter: d => d.last,
},
{
title: 'Success Rate',
title: <Trans>columnTitleSuccessRate</Trans>,
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: <Trans>columnTitleTap</Trans>,
key: 'tap',
isNumeric: true,
render: d => tapLink(d, resourceType, PrefixedLink),

View File

@ -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 {
<Grid item container spacing={4} alignItems="center" justify="flex-start">
<Grid item>
{ this.renderNamespaceDropdown('To Namespace', 'to_namespace', 'Namespace of target resource') }
{ this.renderNamespaceDropdown(<Trans>formToNamespace</Trans>, 'to_namespace', 'Namespace of target resource') }
</Grid>
<Grid item>
{ this.renderResourceDropdown('To Resource', 'to_name', 'to_type', 'Target resource') }
{ this.renderResourceDropdown(<Trans>formToResource</Trans>, 'to_name', 'to_type', 'Target resource') }
</Grid>
</Grid>
</Grid>

View File

@ -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: <Trans>columnTitleRoute</Trans>,
dataIndex: 'route',
filter: d => d.route,
sorter: d => d.route,
},
{
title: 'Service',
title: <Trans>columnTitleService</Trans>,
tooltip: 'hostname:port used when communicating with this target',
dataIndex: 'authority',
filter: d => d.authority,
sorter: d => d.authority,
},
{
title: 'Success Rate',
title: <Trans>columnTitleSuccessRate</Trans>,
dataIndex: 'successRate',
isNumeric: true,
render: d => <SuccessRateMiniChart sr={d.successRate} />,
sorter: d => d.successRate,
},
{
title: 'RPS',
title: <Trans>columnTitleRPS</Trans>,
dataIndex: 'requestRate',
isNumeric: true,
render: d => metricToFormatter.NO_UNIT(d.requestRate),
sorter: d => d.requestRate,
},
{
title: 'P50 Latency',
title: <Trans>columnTitleP50Latency</Trans>,
dataIndex: 'latency.P50',
isNumeric: true,
render: d => metricToFormatter.LATENCY(d.latency.P50),
sorter: d => d.latency.P50,
},
{
title: 'P95 Latency',
title: <Trans>columnTitleP95Latency</Trans>,
dataIndex: 'latency.P95',
isNumeric: true,
render: d => metricToFormatter.LATENCY(d.latency.P95),
sorter: d => d.latency.P95,
},
{
title: 'P99 Latency',
title: <Trans>columnTitleP99Latency</Trans>,
dataIndex: 'latency.P99',
isNumeric: true,
render: d => metricToFormatter.LATENCY(d.latency.P99),

View File

@ -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={<Trans>tableTitleLeafServices</Trans>} />
</div>
);
};

View File

@ -6,7 +6,7 @@ import Typography from '@material-ui/core/Typography';
* Instructions for adding resources to service mesh
*/
export const incompleteMeshMessage = name => {
const unspecifiedResources = <Trans>one or more resources</Trans>;
const unspecifiedResources = <Trans>unspecifiedResourcesMsg</Trans>;
const inject = <code>linkerd inject k8s.yml | kubectl apply -f -</code>;
return (

View File

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

View File

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