mirror of https://github.com/linkerd/linkerd2.git
Add more translations to dashboard and introduce i18n test wrapper (#5082)
Add more translations to the dashboard; introduce i18n test wrapper
This commit is contained in:
parent
15bd95ee1d
commit
79893c9ad6
|
@ -3,17 +3,18 @@ import { friendlyTitle, isResource, singularResource } from './util/Utils.js';
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactRouterPropTypes from 'react-router-prop-types';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import _chunk from 'lodash/chunk';
|
||||
import _takeWhile from 'lodash/takeWhile';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
|
||||
const routeToCrumbTitle = {
|
||||
controlplane: 'Control Plane',
|
||||
overview: 'Overview',
|
||||
tap: 'Tap',
|
||||
top: 'Top',
|
||||
routes: 'Top Routes',
|
||||
community: 'Community',
|
||||
controlplane: <Trans>menuItemControlPlane</Trans>,
|
||||
tap: <Trans>menuItemTap</Trans>,
|
||||
top: <Trans>menuItemTop</Trans>,
|
||||
routes: <Trans>menuItemRoutes</Trans>,
|
||||
community: <Trans>menuItemCommunity</Trans>,
|
||||
gateways: <Trans>menuItemGateway</Trans>,
|
||||
};
|
||||
|
||||
class BreadcrumbHeader extends React.Component {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { BrowserRouter } from 'react-router-dom';
|
|||
import BreadcrumbHeader from './BreadcrumbHeader.jsx';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { i18nWrap } from '../../test/testHelpers.jsx';
|
||||
|
||||
const loc = {
|
||||
pathname: '',
|
||||
|
@ -48,13 +49,13 @@ describe('Tests for <BreadcrumbHeader>', () => {
|
|||
it("renders correct breadcrumb text for top-level pages [Control Plane]",
|
||||
() => {
|
||||
loc.pathname = "/controlplane";
|
||||
const component = mount(
|
||||
const component = mount(i18nWrap(
|
||||
<BrowserRouter>
|
||||
<BreadcrumbHeader
|
||||
location={loc}
|
||||
pathPrefix="" />
|
||||
</BrowserRouter>
|
||||
);
|
||||
));
|
||||
const crumbs = component.find("span");
|
||||
expect(crumbs).toHaveLength(1);
|
||||
const crumbText = crumbs.reduce((acc, crumb) => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Plural, Trans } from '@lingui/macro';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Step from '@material-ui/core/Step';
|
||||
|
@ -17,9 +18,9 @@ const styles = theme => ({
|
|||
|
||||
function getSteps(numResources, resource) {
|
||||
return [
|
||||
{ label: 'Controller successfully installed' },
|
||||
{ label: `${numResources || 'No'} ${resource}s detected` },
|
||||
{ label: `Connect your first ${resource}`, content: incompleteMeshMessage() },
|
||||
{ label: <Trans>controllerInstalledMsg</Trans>, key: 'installedMsg' },
|
||||
{ label: <Plural value={numResources} zero={resource} one={resource} other={resource} />, key: 'resourceMsg' },
|
||||
{ label: <Trans>connectResourceMsg {resource}</Trans>, content: incompleteMeshMessage(), key: 'connectMsg' },
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -29,7 +30,7 @@ const CallToAction = ({ resource, numResources, classes }) => {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typography>The service mesh was successfully installed!</Typography>
|
||||
<Typography><Trans>serviceMeshInstalledMsg</Trans></Typography>
|
||||
<Stepper
|
||||
activeStep={lastStep}
|
||||
className={classes.instructions}
|
||||
|
@ -40,7 +41,7 @@ const CallToAction = ({ resource, numResources, classes }) => {
|
|||
props.completed = i < lastStep; // select the last step as the currently active one
|
||||
|
||||
return (
|
||||
<Step key={step.label} {...props}>
|
||||
<Step key={step.key} {...props}>
|
||||
<StepLabel>{step.label}</StepLabel>
|
||||
<StepContent>{step.content}</StepContent>
|
||||
</Step>
|
||||
|
|
|
@ -285,7 +285,7 @@ class CheckModal extends React.Component {
|
|||
{success !== undefined &&
|
||||
<Grid item>
|
||||
<SimpleChip
|
||||
label={success ? 'Success' : 'Error'}
|
||||
label={success ? <Trans>labelSuccess</Trans> : <Trans>labelError</Trans>}
|
||||
type={success ? 'good' : 'bad'} />
|
||||
</Grid>
|
||||
}
|
||||
|
@ -316,11 +316,11 @@ class CheckModal extends React.Component {
|
|||
|
||||
<DialogActions>
|
||||
<Button onClick={this.runCheck} color="primary">
|
||||
Re-Run Check
|
||||
<Trans>buttonReRunCheck</Trans>
|
||||
</Button>
|
||||
|
||||
<Button onClick={this.handleOpenChange} color="primary">
|
||||
Close
|
||||
<Trans>buttonClose</Trans>
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
|
|
@ -13,6 +13,7 @@ import InputLabel from '@material-ui/core/InputLabel';
|
|||
import NoteAddIcon from '@material-ui/icons/NoteAdd';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import _isEmpty from 'lodash/isEmpty';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
|
@ -131,7 +132,7 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
color="primary"
|
||||
size="small"
|
||||
onClick={this.handleClickOpen}>
|
||||
Create Service Profile
|
||||
<Trans>buttonCreateServiceProfile</Trans>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
@ -144,7 +145,7 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
disabled={disableDownloadButton}
|
||||
onClick={() => this.handleClose(downloadUrl)}
|
||||
color="primary">
|
||||
Download
|
||||
<Trans>buttonDownload</Trans>
|
||||
</Button>
|
||||
);
|
||||
|
||||
|
@ -155,11 +156,12 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
open={open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">New service profile</DialogTitle>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
<Trans>New service profile</Trans>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
To create a service profile, download a profile and then apply it
|
||||
with `kubectl apply`.
|
||||
<Trans>formCreateServiceProfileHelpText</Trans>
|
||||
</DialogContentText>
|
||||
<FormControl
|
||||
className={classes.textField}
|
||||
|
@ -173,8 +175,7 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
aria-describedby="component-error-text" />
|
||||
{error.service && (
|
||||
<FormHelperText id="component-error-text">
|
||||
Service name must consist of lower case alphanumeric characters or -
|
||||
start with an alphabetic character, and end with an alphanumeric character
|
||||
<Trans>formServiceNameErrorText</Trans>
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
@ -190,15 +191,14 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
aria-describedby="component-error-text" />
|
||||
{error.namespace && (
|
||||
<FormHelperText id="component-error-text">
|
||||
Namespace must consist of lower case alphanumeric characters or -
|
||||
and must start and end with an alphanumeric character
|
||||
<Trans>formNamespaceErrorText</Trans>
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color="primary">
|
||||
Cancel
|
||||
<Trans>buttonCancel</Trans>
|
||||
</Button>
|
||||
{disableDownloadButton ?
|
||||
downloadButton :
|
||||
|
@ -221,9 +221,7 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
return (
|
||||
<CardContent>
|
||||
<Typography component="div">
|
||||
No named route traffic found. This could be because the service is
|
||||
not receiving any traffic, or because there is no service profile
|
||||
configured. Does the service have a service profile?
|
||||
<Trans>formNoNamedRouteTrafficFound</Trans>
|
||||
{this.renderDownloadProfileForm()}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
|
|
|
@ -14,6 +14,7 @@ import TableBody from '@material-ui/core/TableBody';
|
|||
import TableCell from '@material-ui/core/TableCell';
|
||||
import TableHead from '@material-ui/core/TableHead';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const styles = theme => ({
|
||||
|
@ -131,7 +132,7 @@ class ExpandableTable extends React.Component {
|
|||
open={open}
|
||||
onClose={this.handleDialogClose}
|
||||
aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Request Details</DialogTitle>
|
||||
<DialogTitle id="form-dialog-title"><Trans>tableTitleRequestDetails</Trans></DialogTitle>
|
||||
<DialogContent>
|
||||
{expandedRowRender(datum, classes.expandedWrap)}
|
||||
</DialogContent>
|
||||
|
|
|
@ -99,7 +99,7 @@ class Gateways extends React.Component {
|
|||
<Spinner />
|
||||
) : (
|
||||
<div>
|
||||
{noMetrics ? <div>No resources detected.</div> : null}
|
||||
{noMetrics ? <div><Trans>noResourcesDetectedMsg</Trans></div> : null}
|
||||
{noMetrics ? null : (
|
||||
<div className="page-section">
|
||||
<MetricsTable
|
||||
|
|
|
@ -356,7 +356,7 @@ class Octopus extends React.Component {
|
|||
|
||||
Octopus.propTypes = {
|
||||
api: PropTypes.shape({
|
||||
ResourceLink: PropTypes.element,
|
||||
ResourceLink: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
|
||||
}),
|
||||
neighbors: PropTypes.shape({}),
|
||||
resource: PropTypes.shape({}),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import CardContent from '@material-ui/core/CardContent';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import _isEmpty from 'lodash/isEmpty';
|
||||
import _startCase from 'lodash/startCase';
|
||||
|
@ -32,11 +33,13 @@ class QueryToCliCmd extends React.Component {
|
|||
render = () => {
|
||||
const { cmdName, query, resource, controllerNamespace } = this.props;
|
||||
|
||||
const cmdNameDisplay = _startCase(cmdName);
|
||||
|
||||
return (
|
||||
_isEmpty(resource) ? null :
|
||||
<CardContent>
|
||||
<Typography variant="caption" gutterBottom>
|
||||
Current {_startCase(cmdName)} query
|
||||
<Trans>Current {cmdNameDisplay} query</Trans>
|
||||
</Typography>
|
||||
|
||||
<br />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import QueryToCliCmd from './QueryToCliCmd.jsx';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { i18nWrap } from '../../test/testHelpers.jsx';
|
||||
|
||||
describe('QueryToCliCmd', () => {
|
||||
it('renders a query as a linkerd CLI command', () => {
|
||||
|
@ -10,12 +11,12 @@ describe('QueryToCliCmd', () => {
|
|||
"scheme": ""
|
||||
}
|
||||
|
||||
let component = mount(
|
||||
let component = mount(i18nWrap(
|
||||
<QueryToCliCmd
|
||||
cmdName="routes"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
controllerNamespace="linkerd" />
|
||||
controllerNamespace="linkerd" />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Routes query");
|
||||
|
@ -28,12 +29,12 @@ describe('QueryToCliCmd', () => {
|
|||
"namespace": "linkerd"
|
||||
}
|
||||
|
||||
let component = mount(
|
||||
let component = mount(i18nWrap(
|
||||
<QueryToCliCmd
|
||||
cmdName="routes"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
controllerNamespace="my-linkerd-ns" />
|
||||
controllerNamespace="my-linkerd-ns" />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Routes query");
|
||||
|
@ -49,12 +50,12 @@ describe('QueryToCliCmd', () => {
|
|||
"authority": "foo.bar:8080"
|
||||
}
|
||||
|
||||
let component = mount(
|
||||
let component = mount(i18nWrap(
|
||||
<QueryToCliCmd
|
||||
cmdName="tap"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
controllerNamespace="linkerd" />
|
||||
controllerNamespace="linkerd" />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Tap query");
|
||||
|
@ -71,12 +72,12 @@ describe('QueryToCliCmd', () => {
|
|||
"authority": "foo.bar:8080"
|
||||
}
|
||||
|
||||
let component = mount(
|
||||
let component = mount(i18nWrap(
|
||||
<QueryToCliCmd
|
||||
cmdName="tap"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
controllerNamespace="linkerd" />
|
||||
controllerNamespace="linkerd" />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Tap query");
|
||||
|
@ -89,12 +90,12 @@ describe('QueryToCliCmd', () => {
|
|||
"namespace": "linkerd"
|
||||
}
|
||||
|
||||
let component = mount(
|
||||
let component = mount(i18nWrap(
|
||||
<QueryToCliCmd
|
||||
cmdName="top"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
controllerNamespace="linkerd" />
|
||||
controllerNamespace="linkerd" />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Top query");
|
||||
|
@ -109,12 +110,12 @@ describe('QueryToCliCmd', () => {
|
|||
"theLimitDoesNotExist": 999
|
||||
}
|
||||
|
||||
let component = mount(
|
||||
let component = mount(i18nWrap(
|
||||
<QueryToCliCmd
|
||||
cmdName="tap"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
controllerNamespace="linkerd" />
|
||||
controllerNamespace="linkerd" />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Tap query");
|
||||
|
|
|
@ -367,11 +367,11 @@ export class ResourceDetailBase extends React.Component {
|
|||
<Grid item><Typography variant="h5">{resourceType}/{resourceName}</Typography></Grid>
|
||||
<Grid item>
|
||||
<Grid container spacing={1}>
|
||||
{showNoTrafficMsg ? <Grid item><SimpleChip label="no traffic" type="warning" /></Grid> : null}
|
||||
{showNoTrafficMsg ? <Grid item><SimpleChip label={<Trans>columnTitleNoTraffic</Trans>} type="warning" /></Grid> : null}
|
||||
<Grid item>
|
||||
{resourceIsMeshed ?
|
||||
<SimpleChip label="meshed" type="good" /> :
|
||||
<SimpleChip label="unmeshed" type="bad" />
|
||||
<SimpleChip label={<Trans>columnTitleMeshed</Trans>} type="good" /> :
|
||||
<SimpleChip label={<Trans>columnTitleUnmeshed</Trans>} type="bad" />
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Plural, Trans } from '@lingui/macro';
|
||||
import { formatDistanceToNow, subSeconds } from 'date-fns';
|
||||
import { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';
|
||||
import BaseTable from './BaseTable.jsx';
|
||||
|
@ -13,7 +14,6 @@ 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';
|
||||
|
@ -107,7 +107,7 @@ class ServiceMesh extends React.Component {
|
|||
const { productName, releaseVersion, controllerNamespace } = this.props;
|
||||
|
||||
return [
|
||||
{ key: 1, name: `${productName} version`, value: releaseVersion },
|
||||
{ key: 1, name: <Trans>{productName} version</Trans>, value: releaseVersion },
|
||||
{ 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() },
|
||||
|
@ -223,7 +223,7 @@ class ServiceMesh extends React.Component {
|
|||
<React.Fragment>
|
||||
<Grid container justify="space-between">
|
||||
<Grid item xs={3}>
|
||||
<Typography variant="h6">Control plane</Typography>
|
||||
<Typography variant="h6"><Trans>Control plane</Trans></Typography>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Typography align="right"><Trans>componentsMsg</Trans></Typography>
|
||||
|
@ -243,7 +243,7 @@ class ServiceMesh extends React.Component {
|
|||
renderServiceMeshDetails() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typography variant="h6">Service mesh details</Typography>
|
||||
<Typography variant="h6"><Trans>Service mesh details</Trans></Typography>
|
||||
|
||||
<BaseTable
|
||||
tableClassName="metric-table"
|
||||
|
@ -263,14 +263,18 @@ class ServiceMesh extends React.Component {
|
|||
let numUnadded = 0;
|
||||
|
||||
if (_isEmpty(nsStatuses)) {
|
||||
message = 'No resources detected.';
|
||||
message = <Trans>noNamespacesDetectedMsg</Trans>;
|
||||
} else {
|
||||
const meshedCount = _countBy(nsStatuses, pod => {
|
||||
return pod.meshedPercent.get() > 0;
|
||||
});
|
||||
numUnadded = meshedCount.false || 0;
|
||||
message = numUnadded === 0 ? `All namespaces have a ${productName} install.` :
|
||||
`${numUnadded} ${numUnadded === 1 ? 'namespace has' : 'namespaces have'} no meshed resources.`;
|
||||
message = numUnadded === 0 ? <Trans>All namespaces have a {productName} install.</Trans> : (
|
||||
<Plural
|
||||
value={numUnadded}
|
||||
one="# namespace has no meshed resources"
|
||||
other="# namespaces have no meshed resources" />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,6 +8,7 @@ import sinon from 'sinon';
|
|||
import sinonStubPromise from 'sinon-stub-promise';
|
||||
import Spinner from './util/Spinner.jsx';
|
||||
import { mount } from 'enzyme';
|
||||
import { i18nWrap } from '../../test/testHelpers.jsx';
|
||||
|
||||
sinonStubPromise(sinon);
|
||||
|
||||
|
@ -91,7 +92,7 @@ describe('ServiceMesh', () => {
|
|||
ok: true,
|
||||
json: () => Promise.resolve({ metrics: [] })
|
||||
});
|
||||
component = mount(routerWrap(ServiceMesh));
|
||||
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||
|
||||
return withPromise(() => {
|
||||
component.update();
|
||||
|
@ -107,7 +108,7 @@ describe('ServiceMesh', () => {
|
|||
ok: true,
|
||||
json: () => Promise.resolve({ metrics: [] })
|
||||
});
|
||||
component = mount(routerWrap(ServiceMesh));
|
||||
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||
|
||||
return withPromise(() => {
|
||||
component.update();
|
||||
|
@ -122,7 +123,7 @@ describe('ServiceMesh', () => {
|
|||
ok: true,
|
||||
json: () => Promise.resolve({ metrics: [] })
|
||||
});
|
||||
component = mount(routerWrap(ServiceMesh));
|
||||
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||
|
||||
return withPromise(() => {
|
||||
component.update();
|
||||
|
@ -138,7 +139,7 @@ describe('ServiceMesh', () => {
|
|||
ok: true,
|
||||
json: () => Promise.resolve({})
|
||||
});
|
||||
component = mount(routerWrap(ServiceMesh));
|
||||
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||
|
||||
return withPromise(() => {
|
||||
expect(component).toIncludeText("No namespaces detected");
|
||||
|
@ -163,7 +164,7 @@ describe('ServiceMesh', () => {
|
|||
ok: true,
|
||||
json: () => Promise.resolve(nsAllResourcesAdded)
|
||||
});
|
||||
component = mount(routerWrap(ServiceMesh));
|
||||
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||
|
||||
return withPromise(() => {
|
||||
expect(component).toIncludeText("4 namespaces have no meshed resources.");
|
||||
|
@ -183,7 +184,7 @@ describe('ServiceMesh', () => {
|
|||
ok: true,
|
||||
json: () => Promise.resolve(nsOneResourceNotAdded)
|
||||
});
|
||||
component = mount(routerWrap(ServiceMesh));
|
||||
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||
|
||||
return withPromise(() => {
|
||||
expect(component).toIncludeText("1 namespace has no meshed resources.");
|
||||
|
@ -200,7 +201,7 @@ describe('ServiceMesh', () => {
|
|||
ok: true,
|
||||
json: () => Promise.resolve(nsAllResourcesAdded)
|
||||
});
|
||||
component = mount(routerWrap(ServiceMesh));
|
||||
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||
|
||||
return withPromise(() => {
|
||||
expect(component).toIncludeText("All namespaces have a ShinyProductName install.");
|
||||
|
|
|
@ -25,18 +25,18 @@ const columnConfig = {
|
|||
width: 200,
|
||||
wrapDotsAt: 7, // dots take up more than one line in the table; space them out
|
||||
dotExplanation: status => {
|
||||
return status.value === 'good' ? 'is up and running' : 'has not been started';
|
||||
return status.value === 'good' ? <Trans>statusExplanationGood</Trans> : <Trans>statusExplanationNotStarted</Trans>;
|
||||
},
|
||||
},
|
||||
'Proxy Status': {
|
||||
width: 250,
|
||||
wrapDotsAt: 9,
|
||||
dotExplanation: pod => {
|
||||
const addedStatus = !pod.added ? 'Not in mesh' : 'Added to mesh';
|
||||
const addedStatus = !pod.added ? <Trans>statusExplanationNotInMesh</Trans> : <Trans>statusExplanationInMesh</Trans>;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>Pod status: {pod.status}</div>
|
||||
<div><Trans>Pod status: {pod.status}</Trans></div>
|
||||
<div>{addedStatus}</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
@ -138,39 +138,39 @@ const itemDisplay = (title, value) => {
|
|||
|
||||
const requestInitSection = d => (
|
||||
<React.Fragment>
|
||||
<Typography variant="subtitle2">Request Init</Typography>
|
||||
<Typography variant="subtitle2"><Trans>tableTitleRequestInit</Trans></Typography>
|
||||
<br />
|
||||
<List dense>
|
||||
{itemDisplay('Authority', _get(d, 'requestInit.http.requestInit.authority'))}
|
||||
{itemDisplay('Path', _get(d, 'requestInit.http.requestInit.path'))}
|
||||
{itemDisplay('Scheme', _get(d, 'requestInit.http.requestInit.scheme.registered'))}
|
||||
{itemDisplay('Method', _get(d, 'requestInit.http.requestInit.method.registered'))}
|
||||
{headersDisplay('Headers', _get(d, 'requestInit.http.requestInit.headers'))}
|
||||
{itemDisplay(<Trans>formAuthority</Trans>, _get(d, 'requestInit.http.requestInit.authority'))}
|
||||
{itemDisplay(<Trans>formPath</Trans>, _get(d, 'requestInit.http.requestInit.path'))}
|
||||
{itemDisplay(<Trans>formScheme</Trans>, _get(d, 'requestInit.http.requestInit.scheme.registered'))}
|
||||
{itemDisplay(<Trans>formMethod</Trans>, _get(d, 'requestInit.http.requestInit.method.registered'))}
|
||||
{headersDisplay(<Trans>formHeaders</Trans>, _get(d, 'requestInit.http.requestInit.headers'))}
|
||||
</List>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const responseInitSection = d => _isEmpty(d.responseInit) ? null : (
|
||||
<React.Fragment>
|
||||
<Typography variant="subtitle2">Response Init</Typography>
|
||||
<Typography variant="subtitle2"><Trans>tableTitleResponseInit</Trans></Typography>
|
||||
<br />
|
||||
<List dense>
|
||||
{itemDisplay('HTTP Status', _get(d, 'responseInit.http.responseInit.httpStatus'))}
|
||||
{itemDisplay('Latency', formatTapLatency(_get(d, 'responseInit.http.responseInit.sinceRequestInit')))}
|
||||
{headersDisplay('Headers', _get(d, 'responseInit.http.responseInit.headers'))}
|
||||
{itemDisplay(<Trans>formHTTPStatus</Trans>, _get(d, 'responseInit.http.responseInit.httpStatus'))}
|
||||
{itemDisplay(<Trans>formLatency</Trans>, formatTapLatency(_get(d, 'responseInit.http.responseInit.sinceRequestInit')))}
|
||||
{headersDisplay(<Trans>formHeaders</Trans>, _get(d, 'responseInit.http.responseInit.headers'))}
|
||||
</List>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const responseEndSection = d => _isEmpty(d.responseEnd) ? null : (
|
||||
<React.Fragment>
|
||||
<Typography variant="subtitle2">Response End</Typography>
|
||||
<Typography variant="subtitle2"><Trans>tableTitleResponseEnd</Trans></Typography>
|
||||
<br />
|
||||
|
||||
<List dense>
|
||||
{itemDisplay('GRPC Status', _isNull(_get(d, 'responseEnd.http.responseEnd.eos')) ? 'N/A' : grpcStatusCodes[_get(d, 'responseEnd.http.responseEnd.eos.grpcStatusCode')])}
|
||||
{itemDisplay('Latency', formatTapLatency(_get(d, 'responseEnd.http.responseEnd.sinceResponseInit')))}
|
||||
{itemDisplay('Response Length (B)', formatWithComma(_get(d, 'responseEnd.http.responseEnd.responseBytes')))}
|
||||
{itemDisplay(<Trans>formGRPCStatus</Trans>, _isNull(_get(d, 'responseEnd.http.responseEnd.eos')) ? 'N/A' : grpcStatusCodes[_get(d, 'responseEnd.http.responseEnd.eos.grpcStatusCode')])}
|
||||
{itemDisplay(<Trans>formLatency</Trans>, formatTapLatency(_get(d, 'responseEnd.http.responseEnd.sinceResponseInit')))}
|
||||
{itemDisplay(<Trans>formResponseLengthB</Trans>, formatWithComma(_get(d, 'responseEnd.http.responseEnd.responseBytes')))}
|
||||
</List>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
@ -36,7 +36,6 @@ import _noop from 'lodash/noop';
|
|||
import _omit from 'lodash/omit';
|
||||
import _pick from 'lodash/pick';
|
||||
import _some from 'lodash/some';
|
||||
import _startCase from 'lodash/startCase';
|
||||
import _uniq from 'lodash/uniq';
|
||||
import _values from 'lodash/values';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
@ -245,7 +244,7 @@ class TapQueryForm extends React.Component {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<InputLabel htmlFor={resourceKey}>{_startCase(resourceKey)}</InputLabel>
|
||||
<InputLabel htmlFor={resourceKey}>{resourceKey === 'resource' ? <Trans>formResource</Trans> : <Trans>formToResource</Trans>}</InputLabel>
|
||||
<Select
|
||||
value={!nsEmpty && resourceOptions.includes(query[resourceKey]) ? query[resourceKey] : ''}
|
||||
onChange={this.handleFormChange(resourceKey)}
|
||||
|
@ -364,20 +363,20 @@ class TapQueryForm extends React.Component {
|
|||
))
|
||||
}
|
||||
</Select>
|
||||
<FormHelperText>Display requests with this :authority</FormHelperText>
|
||||
<FormHelperText><Trans>formAuthorityHelpText</Trans></FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
||||
{ this.renderTextInput(<Trans>formPath</Trans>, 'path', 'Display requests with paths that start with this prefix') }
|
||||
{ this.renderTextInput(<Trans>formPath</Trans>, 'path', <Trans>formPathHelpText</Trans>) }
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
||||
{ this.renderTextInput(<Trans>formScheme</Trans>, 'scheme', 'Display requests with this scheme') }
|
||||
{ this.renderTextInput(<Trans>formScheme</Trans>, 'scheme', <Trans>formSchemeHelpText</Trans>) }
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
||||
{ this.renderTextInput(<Trans>formMaxRPS</Trans>, 'maxRps', `Maximum requests per second to tap. Default ${defaultMaxRps}`) }
|
||||
{ this.renderTextInput(<Trans>formMaxRPS</Trans>, 'maxRps', <Trans>formMaxRPSHelpText {defaultMaxRps}</Trans>) }
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
||||
<FormControl className={classes.formControl}>
|
||||
|
@ -393,7 +392,7 @@ class TapQueryForm extends React.Component {
|
|||
))
|
||||
}
|
||||
</Select>
|
||||
<FormHelperText>Display requests with this HTTP method</FormHelperText>
|
||||
<FormHelperText><Trans>formHTTPMethodHelpText</Trans></FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
|
|
@ -203,11 +203,11 @@ class TopRoutes extends React.Component {
|
|||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item container spacing={4} alignItems="center" justify="flex-start">
|
||||
<Grid item>
|
||||
{ this.renderNamespaceDropdown('Namespace', 'namespace', 'Namespace to query') }
|
||||
{ this.renderNamespaceDropdown(<Trans>formNamespace</Trans>, 'namespace', <Trans>formNamespaceHelpText</Trans>) }
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
{ this.renderResourceDropdown('Resource', 'resource_name', 'resource_type', 'Resource to query') }
|
||||
{ this.renderResourceDropdown(<Trans>formResource</Trans>, 'resource_name', 'resource_type', <Trans>formResourceHelpText</Trans>) }
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
|
@ -216,7 +216,7 @@ class TopRoutes extends React.Component {
|
|||
variant="outlined"
|
||||
disabled={requestInProgress || !query.namespace || !query.resource_type}
|
||||
onClick={this.handleBtnClick(true)}>
|
||||
Start
|
||||
<Trans>buttonStart</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
|
@ -226,23 +226,23 @@ class TopRoutes extends React.Component {
|
|||
variant="outlined"
|
||||
disabled={!requestInProgress}
|
||||
onClick={this.handleBtnClick(false)}>
|
||||
Stop
|
||||
<Trans>buttonStop</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid item container spacing={4} alignItems="center" justify="flex-start">
|
||||
<Grid item>
|
||||
{ this.renderNamespaceDropdown(<Trans>formToNamespace</Trans>, 'to_namespace', 'Namespace of target resource') }
|
||||
{ this.renderNamespaceDropdown(<Trans>formToNamespace</Trans>, 'to_namespace', <Trans>formToNamespaceHelpText</Trans>) }
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
{ this.renderResourceDropdown(<Trans>formToResource</Trans>, 'to_name', 'to_type', 'Target resource') }
|
||||
{ this.renderResourceDropdown(<Trans>formToResource</Trans>, 'to_name', 'to_type', <Trans>formToResourceHelpText</Trans>) }
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider light className={classes.root} />
|
||||
<Typography variant="caption">You can also create a new profile <ConfigureProfilesMsg showAsIcon /></Typography>
|
||||
<Typography variant="caption"><Trans>createNewProfileMsg</Trans> <ConfigureProfilesMsg showAsIcon /></Typography>
|
||||
</CardContent>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import Tab from '@material-ui/core/Tab';
|
|||
import Tabs from '@material-ui/core/Tabs';
|
||||
import TopModule from './TopModule.jsx';
|
||||
import TopRoutesModule from './TopRoutesModule.jsx';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import _isEmpty from 'lodash/isEmpty';
|
||||
import _noop from 'lodash/noop';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
@ -91,8 +92,8 @@ class TopRoutesTabs extends React.Component {
|
|||
onChange={this.handleChange}
|
||||
indicatorColor="primary"
|
||||
textColor="primary">
|
||||
<Tab label="Live Calls" />
|
||||
<Tab label="Route Metrics" />
|
||||
<Tab label={<Trans>tabLiveCalls</Trans>} />
|
||||
<Tab label={<Trans>tabRouteMetrics</Trans>} />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
{value === 0 && this.renderTopComponent()}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Button from '@material-ui/core/Button';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { apiErrorPropType } from './util/ApiHelpers.jsx';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
|
@ -39,25 +40,31 @@ class Version extends React.Component {
|
|||
if (!latestVersion) {
|
||||
return (
|
||||
<Typography className={classes.versionMsg}>
|
||||
Version check failed{error ? `: ${error.statusText}` : ''}.
|
||||
<Trans>Version check failed{error ? `: ${error.statusText}` : ''}.</Trans>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLatest) {
|
||||
return <Typography className={classes.versionMsg}>Linkerd is up to date.</Typography>;
|
||||
return <Typography className={classes.versionMsg}><Trans>LinkerdIsUpToDateMsg</Trans></Typography>;
|
||||
}
|
||||
|
||||
const versionText = this.numericVersion(latestVersion);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography className={classes.versionMsg}>A new version ({this.numericVersion(latestVersion)}) is available.</Typography>
|
||||
<Typography className={classes.versionMsg}>
|
||||
<Trans>
|
||||
A new version ({versionText}) is available.
|
||||
</Trans>
|
||||
</Typography>
|
||||
<Button
|
||||
className={classes.updateBtn}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
target="_blank"
|
||||
href="https://versioncheck.linkerd.io/update">
|
||||
Update Now
|
||||
<Trans>Update Now</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
@ -66,7 +73,7 @@ class Version extends React.Component {
|
|||
render() {
|
||||
const { classes, releaseVersion, productName } = this.props;
|
||||
const channel = this.versionChannel(releaseVersion);
|
||||
let message = `Running ${productName || 'controller'}`;
|
||||
let message = ` ${productName || 'controller'}`;
|
||||
message += ` ${this.numericVersion(releaseVersion)}`;
|
||||
if (channel) {
|
||||
message += ` (${channel})`;
|
||||
|
@ -75,7 +82,7 @@ class Version extends React.Component {
|
|||
|
||||
return (
|
||||
<div className={classes.version}>
|
||||
<Typography className={classes.versionMsg}>{message}</Typography>
|
||||
<Typography className={classes.versionMsg}><Trans>Running</Trans>{message}</Typography>
|
||||
{this.renderVersionCheck()}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,30 +1,31 @@
|
|||
import React from 'react';
|
||||
import Version from './Version.jsx';
|
||||
import { mount } from 'enzyme';
|
||||
import { i18nWrap } from '../../test/testHelpers.jsx';
|
||||
|
||||
describe('Version', () => {
|
||||
let curVer = "edge-1.2.3";
|
||||
let newVer = "edge-2.3.4";
|
||||
|
||||
it('renders up to date message when versions match', () => {
|
||||
const component = mount(
|
||||
const component = mount(i18nWrap(
|
||||
<Version
|
||||
classes={{}}
|
||||
isLatest
|
||||
latestVersion={curVer}
|
||||
releaseVersion={curVer} />
|
||||
releaseVersion={curVer} />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Linkerd is up to date");
|
||||
});
|
||||
|
||||
it('renders update message when versions do not match', () => {
|
||||
const component = mount(
|
||||
const component = mount(i18nWrap(
|
||||
<Version
|
||||
classes={{}}
|
||||
isLatest={false}
|
||||
latestVersion={newVer}
|
||||
releaseVersion={curVer} />
|
||||
releaseVersion={curVer} />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("A new version (2.3.4) is available.");
|
||||
|
@ -33,14 +34,14 @@ describe('Version', () => {
|
|||
it('renders error when version check fails', () => {
|
||||
let errMsg = "Fake error";
|
||||
|
||||
const component = mount(
|
||||
const component = mount(i18nWrap(
|
||||
<Version
|
||||
classes={{}}
|
||||
error={{
|
||||
statusText: errMsg,
|
||||
}}
|
||||
isLatest={false}
|
||||
releaseVersion={curVer} />
|
||||
releaseVersion={curVer} />)
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Version check failed: Fake error.");
|
||||
|
|
|
@ -30,7 +30,7 @@ function SimpleChip(props) {
|
|||
}
|
||||
|
||||
SimpleChip.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
label: PropTypes.shape({}).isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import TapLink from '../TapLink.jsx';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import _each from 'lodash/each';
|
||||
import _get from 'lodash/get';
|
||||
import _has from 'lodash/has';
|
||||
|
@ -228,9 +229,9 @@ export const processTapEvent = jsonString => {
|
|||
|
||||
const displayLimit = 3; // how many upstreams/downstreams to display in the popover table
|
||||
const popoverSrcDstColumns = [
|
||||
{ title: 'Source', dataIndex: 'source' },
|
||||
{ title: <Trans>columnTitleSource</Trans>, dataIndex: 'source' },
|
||||
{ title: '', key: 'arrow', render: () => <FontAwesomeIcon icon={faLongArrowAltRight} /> },
|
||||
{ title: 'Destination', dataIndex: 'destination' },
|
||||
{ title: <Trans>columnTitleDestination</Trans>, dataIndex: 'destination' },
|
||||
];
|
||||
|
||||
const getPodOwner = (labels, ResourceLink) => {
|
||||
|
@ -310,8 +311,8 @@ const popoverResourceTable = (d, ResourceLink) => { // eslint-disable-line no-un
|
|||
};
|
||||
|
||||
export const directionColumn = d => (
|
||||
<Tooltip title={d} placement="right">
|
||||
<span>{d === 'INBOUND' ? 'FROM' : 'TO'}</span>
|
||||
<Tooltip title={d === 'INBOUND' ? <Trans>tooltipInbound</Trans> : <Trans>tooltipOutbound</Trans>} placement="right">
|
||||
<span>{d === 'INBOUND' ? <Trans>columnTitleFrom</Trans> : <Trans>columnTitleTo</Trans>}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
{
|
||||
"404Msg": "Page not found.",
|
||||
"A new version ({versionText}) is available.": "A new version ({versionText}) is available.",
|
||||
"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",
|
||||
"All namespaces have a {productName} install.": "All namespaces have a {productName} install.",
|
||||
"Control plane": "Control plane",
|
||||
"Control plane components": "Control plane components",
|
||||
"Current {cmdNameDisplay} query": "Current {cmdNameDisplay} query",
|
||||
"Data plane proxies": "Data plane proxies",
|
||||
"LinkerdIsUpToDateMsg": "Linkerd is up to date.",
|
||||
"New service profile": "New service profile",
|
||||
"NoDataToDisplayMsg": "No data to display",
|
||||
"Pod status: {0}": "Pod status: {0}",
|
||||
"Running": "Running",
|
||||
"Service mesh details": "Service mesh details",
|
||||
"Update Now": "Update Now",
|
||||
"Version check failed{0}.": "Version check failed{0}.",
|
||||
"buttonCancel": "Cancel",
|
||||
"buttonClose": "Close",
|
||||
"buttonCreateServiceProfile": "Create Service Profile",
|
||||
"buttonDownload": "Download",
|
||||
"buttonReRunCheck": "Re-Run Check",
|
||||
"buttonReset": "Reset",
|
||||
"buttonRunLinkerdCheck": "Run Linkerd Check",
|
||||
"buttonStart": "Start",
|
||||
|
@ -15,7 +30,9 @@
|
|||
"columnTitleClusterName": "Cluster Name",
|
||||
"columnTitleCount": "Count",
|
||||
"columnTitleDeployment": "Deployment",
|
||||
"columnTitleDestination": "Destination",
|
||||
"columnTitleDirection": "Direction",
|
||||
"columnTitleFrom": "FROM",
|
||||
"columnTitleGRPCStatus": "GRPC Status",
|
||||
"columnTitleGrafana": "Grafana",
|
||||
"columnTitleHTTPStatus": "HTTP Status",
|
||||
|
@ -30,6 +47,7 @@
|
|||
"columnTitleMethod": "Method",
|
||||
"columnTitleName": "Name",
|
||||
"columnTitleNamespace": "Namespace",
|
||||
"columnTitleNoTraffic": "No Traffic",
|
||||
"columnTitleOpenConnections": "Connections",
|
||||
"columnTitleP50Latency": "P50 Latency",
|
||||
"columnTitleP95Latency": "P95 Latency",
|
||||
|
@ -43,23 +61,51 @@
|
|||
"columnTitleRoute": "Route",
|
||||
"columnTitleSecured": "Secured",
|
||||
"columnTitleService": "Service",
|
||||
"columnTitleSource": "Source",
|
||||
"columnTitleSuccessRate": "Success Rate",
|
||||
"columnTitleTap": "Tap",
|
||||
"columnTitleTo": "TO",
|
||||
"columnTitleUnmeshed": "Unmeshed",
|
||||
"columnTitleValue": "Value",
|
||||
"columnTitleWeight": "Weight",
|
||||
"columnTitleWorst": "Worst",
|
||||
"columnTitleWriteRate": "Write Bytes / sec",
|
||||
"componentsMsg": "Components",
|
||||
"connectResourceMsg {resource}": "Connect your first {resource}",
|
||||
"controllerInstalledMsg": "Controller successfully installed",
|
||||
"createNewProfileMsg": "You can also create a new profile",
|
||||
"formAuthority": "Authority",
|
||||
"formAuthorityHelpText": "Display requests with this :authority",
|
||||
"formCreateServiceProfileHelpText": "To create a service profile, download a profile and then apply it with `kubectl apply`.",
|
||||
"formGRPCStatus": "GRPC Status",
|
||||
"formHTTPMethod": "HTTP Method",
|
||||
"formHTTPMethodHelpText": "Display requests with this HTTP method",
|
||||
"formHTTPStatus": "HTTP Status",
|
||||
"formHeaders": "Headers",
|
||||
"formHideFilters": "Hide filters",
|
||||
"formLatency": "Latency",
|
||||
"formMaxRPS": "Max RPS",
|
||||
"formMaxRPSHelpText {defaultMaxRps}": "Maximum requests per second to tap. Default {defaultMaxRps}",
|
||||
"formMethod": "Method",
|
||||
"formNamespace": "Namespace",
|
||||
"formNamespaceErrorText": "Namespace must consist of lower case alphanumeric characters or '-' and must start and end with an alphanumeric character",
|
||||
"formNamespaceHelpText": "Namespace to query",
|
||||
"formNoNamedRouteTrafficFound": "No named route traffic found. This could be because the service is not receiving any traffic, or because there is no service profile configured. Does the service have a service profile?",
|
||||
"formPath": "Path",
|
||||
"formPathHelpText": "Display requests with paths that start with this prefix",
|
||||
"formResource": "Resource",
|
||||
"formResourceHelpText": "Resource to query",
|
||||
"formResponseLengthB": "Response Length (B)",
|
||||
"formScheme": "Scheme",
|
||||
"formSchemeHelpText": "Display requests with this scheme",
|
||||
"formServiceNameErrorText": "Service name must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character",
|
||||
"formShowFilters": "Show more filters",
|
||||
"formToNamespace": "To Namespace",
|
||||
"formToNamespaceHelpText": "Namespace of target resource",
|
||||
"formToResource": "To Resource",
|
||||
"formToResourceHelpText": "Target resource",
|
||||
"labelError": "Error",
|
||||
"labelSuccess": "Success",
|
||||
"menuItemCommunity": "Community",
|
||||
"menuItemControlPlane": "Control Plane",
|
||||
"menuItemCronJobs": "Cron Jobs",
|
||||
|
@ -80,12 +126,20 @@
|
|||
"menuItemTap": "Tap",
|
||||
"menuItemTop": "Top",
|
||||
"menuItemTrafficSplits": "Traffic Splits",
|
||||
"noNamespacesDetectedMsg": "No namespaces detected.",
|
||||
"noResourcesDetectedMsg": "No resources detected.",
|
||||
"podsAreInitializingMsg": "Pods are initializing",
|
||||
"serviceMeshInstalledMsg": "The service mesh was successfully installed!",
|
||||
"sidebarHeadingCluster": "Cluster",
|
||||
"sidebarHeadingConfiguration": "Configuration",
|
||||
"sidebarHeadingTools": "Tools",
|
||||
"sidebarHeadingWorkloads": "Workloads",
|
||||
"statusExplanationGood": "is up and running",
|
||||
"statusExplanationInMesh": "Added to mesh",
|
||||
"statusExplanationNotInMesh": "Not in mesh",
|
||||
"statusExplanationNotStarted": "has not been started",
|
||||
"tabLiveCalls": "Live Calls",
|
||||
"tabRouteMetrics": "Route Metrics",
|
||||
"tableTitleEdgesEmpty": "Edges",
|
||||
"tableTitleEdgesWithIdentity {identity}": "Edges (Identity: {identity})",
|
||||
"tableTitleGateways": "Gateways",
|
||||
|
@ -94,11 +148,17 @@
|
|||
"tableTitleLeafServices": "Leaf Services",
|
||||
"tableTitleOutbound": "Outbound",
|
||||
"tableTitlePods": "Pods",
|
||||
"tableTitleRequestDetails": "Request Details",
|
||||
"tableTitleRequestInit": "Request Init",
|
||||
"tableTitleResponseEnd": "Response End",
|
||||
"tableTitleResponseInit": "Response Init",
|
||||
"tableTitleTCP": "TCP",
|
||||
"tableTitleTCPMetrics": "TCP Metrics",
|
||||
"tooltipInbound": "INBOUND",
|
||||
"tooltipOutbound": "OUTBOUND",
|
||||
"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ß",
|
||||
"{numResources, plural, zero {{resource}} one {{resource}} other {{resource}}}": "{numResources, plural, zero {No {resource}s detected} one {# {resource} detected} other {# {resource}s detected}}",
|
||||
"{numUnadded, plural, one {# namespace has no meshed resources} other {# namespaces have no meshed resources}}": "{numUnadded, plural, one {# namespace has no meshed resources.} other {# namespaces have no meshed resources.}}",
|
||||
"{productName} namespace": "{productName} namespace",
|
||||
"{productName} version": "{productName} version"
|
||||
}
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
{
|
||||
"404Msg": "Página no encontrada.",
|
||||
"A new version ({versionText}) is available.": "Una nueva version ({versionText}) está disponible",
|
||||
"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": "Plano de control",
|
||||
"Control plane components": "Componentes del plano de control",
|
||||
"Current {cmdNameDisplay} query": "Consulta {cmdNameDisplay} actual",
|
||||
"Data plane proxies": "Proxies del plano de datos",
|
||||
"LinkerdIsUpToDateMsg": "Linkerd está actualizado.",
|
||||
"New service profile": "Nuevo service profile",
|
||||
"NoDataToDisplayMsg": "No hay datos que mostrar",
|
||||
"Pod status: {0}": "Estado pod: {0}",
|
||||
"Running": "Ejecutando",
|
||||
"Service mesh details": "Detalles de la malla de servicios",
|
||||
"Update Now": "Actualizar Ahora",
|
||||
"Version check failed{0}.": "Error al comprobar la versión{0}",
|
||||
"buttonCancel": "Cancelar",
|
||||
"buttonClose": "Cerrar",
|
||||
"buttonCreateServiceProfile": "Crear Service Profile",
|
||||
"buttonDownload": "Descargar",
|
||||
"buttonReRunCheck": "Volver a Check",
|
||||
"buttonReset": "Restablecer",
|
||||
"buttonRunLinkerdCheck": "Ejecutar Linkerd Check",
|
||||
"buttonStart": "Empezar",
|
||||
|
@ -15,7 +30,9 @@
|
|||
"columnTitleClusterName": "Nombre del Clúster",
|
||||
"columnTitleCount": "Total",
|
||||
"columnTitleDeployment": "Deployment",
|
||||
"columnTitleDestination": "Destino",
|
||||
"columnTitleDirection": "Dirección",
|
||||
"columnTitleFrom": "DESDE",
|
||||
"columnTitleGRPCStatus": "Estado GRPC",
|
||||
"columnTitleGrafana": "Grafana",
|
||||
"columnTitleHTTPStatus": "Estado HTTP",
|
||||
|
@ -30,6 +47,7 @@
|
|||
"columnTitleMethod": "Método",
|
||||
"columnTitleName": "Nombre",
|
||||
"columnTitleNamespace": "Namespace",
|
||||
"columnTitleNoTraffic": "No Hay Tráfico",
|
||||
"columnTitleOpenConnections": "Conexiones",
|
||||
"columnTitleP50Latency": "Latencia P50",
|
||||
"columnTitleP95Latency": "Latencia P95",
|
||||
|
@ -43,23 +61,51 @@
|
|||
"columnTitleRoute": "Ruta",
|
||||
"columnTitleSecured": "Asegurado",
|
||||
"columnTitleService": "Servicio",
|
||||
"columnTitleSource": "Fuente",
|
||||
"columnTitleSuccessRate": "Tasa de éxito",
|
||||
"columnTitleTap": "Tap",
|
||||
"columnTitleTo": "HACIA",
|
||||
"columnTitleUnmeshed": "Ausente en la malla de servicios",
|
||||
"columnTitleValue": "Valor",
|
||||
"columnTitleWeight": "Peso",
|
||||
"columnTitleWorst": "Peor",
|
||||
"columnTitleWriteRate": "Escritura Bytes / seg",
|
||||
"componentsMsg": "Componentes",
|
||||
"connectResourceMsg {resource}": "Conecta tu primer {resource}",
|
||||
"controllerInstalledMsg": "Controller instalado correctamente",
|
||||
"createNewProfileMsg": "También puede crear un nuevo perfil",
|
||||
"formAuthority": "Authority",
|
||||
"formAuthorityHelpText": "Mostrar solicitudes con este :authority",
|
||||
"formCreateServiceProfileHelpText": "Para crear un service profile, descargue un perfil y luego aplíquelo con `kubectl apply`.",
|
||||
"formGRPCStatus": "Estado GRPC",
|
||||
"formHTTPMethod": "Método HTTP",
|
||||
"formHTTPMethodHelpText": "Mostrar solicitudes con este método HTTP",
|
||||
"formHTTPStatus": "Estado HTTP",
|
||||
"formHeaders": "Encabezados",
|
||||
"formHideFilters": "Ocultar filtros",
|
||||
"formLatency": "Latencia",
|
||||
"formMaxRPS": "Max PPS",
|
||||
"formMaxRPSHelpText {defaultMaxRps}": "Máximo de solicitudes por segundo para tap. Por defecto {defaultMaxRps}",
|
||||
"formMethod": "Método",
|
||||
"formNamespace": "Namespace",
|
||||
"formNamespaceErrorText": "El namespace debe constar de caracteres alfanuméricos en minúscula o '-' y debe comenzar y terminar con un carácter alfanumérico",
|
||||
"formNamespaceHelpText": "Namespace para consultar",
|
||||
"formNoNamedRouteTrafficFound": "No se encontró tráfico de ruta con nombre. Esto podría deberse a que el servicio no está recibiendo tráfico o porque no hay ningún service profile configurado. ¿El servicio tiene un service profile?",
|
||||
"formPath": "Ruta",
|
||||
"formPathHelpText": "Mostrar solicitudes con rutas que comienzan con este prefijo",
|
||||
"formResource": "Recurso",
|
||||
"formResourceHelpText": "Recurso para consultar",
|
||||
"formResponseLengthB": "Longitud De Respuesta (B)",
|
||||
"formScheme": "Esquema",
|
||||
"formSchemeHelpText": "Mostrar solicitudes con este esquema",
|
||||
"formServiceNameErrorText": "El nombre del servicio debe constar de caracteres alfanuméricos en minúscula o '-' comenzar con un carácter alfabético y terminar con un carácter alfanumérico",
|
||||
"formShowFilters": "Mostrar más filtros",
|
||||
"formToNamespace": "Al Namespace",
|
||||
"formToNamespaceHelpText": "Namespace del recurso de destino",
|
||||
"formToResource": "Al Recurso",
|
||||
"formToResourceHelpText": "Recurso destino",
|
||||
"labelError": "Error",
|
||||
"labelSuccess": "Éxito",
|
||||
"menuItemCommunity": "Comunidad",
|
||||
"menuItemControlPlane": "Plano de Control",
|
||||
"menuItemCronJobs": "Cron Jobs",
|
||||
|
@ -80,12 +126,20 @@
|
|||
"menuItemTap": "Tap",
|
||||
"menuItemTop": "Top",
|
||||
"menuItemTrafficSplits": "Traffic Splits",
|
||||
"noNamespacesDetectedMsg": "No se han encontrado namespaces.",
|
||||
"noResourcesDetectedMsg": "No se han encontrado recursos.",
|
||||
"podsAreInitializingMsg": "Los pods se están inicializando",
|
||||
"serviceMeshInstalledMsg": "¡La malla de servicios se instaló correctamente!",
|
||||
"sidebarHeadingCluster": "Clúster",
|
||||
"sidebarHeadingConfiguration": "Configuración",
|
||||
"sidebarHeadingTools": "Herramientas",
|
||||
"sidebarHeadingWorkloads": "Cargas de trabajo",
|
||||
"statusExplanationGood": "está en funcionamiento",
|
||||
"statusExplanationInMesh": "Añadido a la malla",
|
||||
"statusExplanationNotInMesh": "No en malla",
|
||||
"statusExplanationNotStarted": "no se ha iniciado",
|
||||
"tabLiveCalls": "Llamadas En Vivo",
|
||||
"tabRouteMetrics": "Métricas De Ruta",
|
||||
"tableTitleEdgesEmpty": "Bordes",
|
||||
"tableTitleEdgesWithIdentity {identity}": "Bordes (Identidad: {identity})",
|
||||
"tableTitleGateways": "Gateways",
|
||||
|
@ -94,11 +148,17 @@
|
|||
"tableTitleLeafServices": "Servicios Hoja",
|
||||
"tableTitleOutbound": "Salida",
|
||||
"tableTitlePods": "Pods",
|
||||
"tableTitleRequestDetails": "Detalle Solicitudes",
|
||||
"tableTitleRequestInit": "Solicitud Inicial",
|
||||
"tableTitleResponseEnd": "Fin De Respuesta",
|
||||
"tableTitleResponseInit": "Respuesta Inicial",
|
||||
"tableTitleTCP": "TCP",
|
||||
"tableTitleTCPMetrics": "Métricas TCP",
|
||||
"tooltipInbound": "ENTRADA",
|
||||
"tooltipOutbound": "SALIDA",
|
||||
"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.",
|
||||
"{numResources, plural, zero {{resource}} one {{resource}} other {{resource}}}": "{numResources, plural, zero {No {resource}s detectados} one {# {resource} detectado} other {# {resource}s detectados}}",
|
||||
"{numUnadded, plural, one {# namespace has no meshed resources} other {# namespaces have no meshed resources}}": "{numUnadded, plural, one {# namespace no tiene recursos en la malla de servicios.} other {# namespaces no tienen recursos en la malla de servicios.}}",
|
||||
"{productName} namespace": "Namespace de {productName}",
|
||||
"{productName} version": "Versión de {productName}"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import ApiHelpers from '../js/components/util/ApiHelpers.jsx';
|
|||
import { createMemoryHistory } from 'history';
|
||||
import React from 'react';
|
||||
import { Route, Router } from 'react-router';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import catalogEn from './../js/locales/en/messages.js';
|
||||
|
||||
const componentDefaultProps = {
|
||||
api: ApiHelpers(''),
|
||||
|
@ -11,6 +13,9 @@ const componentDefaultProps = {
|
|||
releaseVersion: ''
|
||||
};
|
||||
|
||||
const selectedLocale = 'en';
|
||||
const selectedCatalog = catalogEn;
|
||||
|
||||
export function routerWrap(Component, extraProps={}, route="/", currentLoc="/") {
|
||||
const createElement = (ComponentToWrap, props) => (
|
||||
<ComponentToWrap {...(_merge({}, componentDefaultProps, props, extraProps))} />
|
||||
|
@ -21,3 +26,13 @@ export function routerWrap(Component, extraProps={}, route="/", currentLoc="/")
|
|||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export function i18nWrap(Component) {
|
||||
return (
|
||||
<I18nProvider
|
||||
language={selectedLocale}
|
||||
catalogs={{ [selectedLocale]: selectedCatalog }}>
|
||||
{Component}
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue