Add i18n library to Linkerd dashboard (#4803)

This PR adds the LinguiJS project to the Linkerd dashboard for i18n and 
translation. It is a precursor to adding translations to the dashboard. Only 
two components have been translated in this PR, to allow reviewers to evaluate 
the ease of use; A second PR will add translations for the remaining components.
This commit is contained in:
Carol A. Scott 2020-07-30 09:09:59 -07:00 committed by GitHub
parent 4ffea3ba08
commit eec8905660
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 907 additions and 53 deletions

2
.gitignore vendored
View File

@ -8,6 +8,8 @@ tmp.discovery
**/node_modules
web/web
web/app/dist
web/app/js/locales/_build
web/app/js/locales/**/*.js
web/app/yarn-error.log
vendor
**/*.gogen*

View File

@ -55,7 +55,7 @@ dev() {
build() {
cd "$ROOT"/web/app
yarn webpack
yarn lingui compile && yarn webpack
}
get-pod() {

View File

@ -1,5 +1,5 @@
{
"plugins": ["@babel/plugin-proposal-class-properties"],
"plugins": ["@babel/plugin-proposal-class-properties", "macros"],
"env": {
"production": {
"plugins": ["transform-react-remove-prop-types"]

5
web/app/.linguirc Normal file
View File

@ -0,0 +1,5 @@
{
"localeDir": "js/locales/",
"srcPathDirs": ["js/"],
"format": "minimal"
}

View File

@ -1,7 +1,7 @@
import Card from '@material-ui/core/Card';
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 { withStyles } from '@material-ui/core/styles';
@ -12,24 +12,16 @@ const styles = () => ({
},
});
const EmptyCard = ({ content, classes }) => {
const EmptyCard = ({ classes }) => {
return (
<Card className={classes.card} elevation={3}>
<CardContent>
<Typography>
{content}
<Trans>No data to display</Trans>
</Typography>
</CardContent>
</Card>
);
};
EmptyCard.propTypes = {
content: PropTypes.string,
};
EmptyCard.defaultProps = {
content: 'No data to display',
};
export default withStyles(styles)(EmptyCard);

View File

@ -1,23 +1,20 @@
import React from 'react';
import { Trans } from '@lingui/macro';
import Typography from '@material-ui/core/Typography';
/*
* Instructions for adding resources to service mesh
*/
export const incompleteMeshMessage = name => {
if (name) {
return (
<Typography variant="body2">
Add {name} to the k8s.yml file<br /><br />
Then run <code>linkerd inject k8s.yml | kubectl apply -f -</code> to add it to the service mesh
</Typography>
);
} else {
return (
<Typography variant="body2">
Add one or more resources to the k8s.yml file<br /><br />
Then run <code>linkerd inject k8s.yml | kubectl apply -f -</code> to add them to the service mesh
</Typography>
);
}
const unspecifiedResources = <Trans>one or more resources</Trans>;
const inject = <code>linkerd inject k8s.yml | kubectl apply -f -</code>;
return (
<Typography variant="body2">
<Trans>
Add {name || unspecifiedResources } to the k8s.yml file<br /><br />
Then run {inject} to add it to the service mesh
</Trans>
</Typography>
);
};

View File

@ -2,6 +2,7 @@ import '../css/styles.css';
import '../img/favicon.png'; // needs to be referenced somewhere so webpack bundles it
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
import { DETECTORS, LocaleResolver, TRANSFORMERS } from 'locales-detector';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import ApiHelpers from './components/util/ApiHelpers.jsx';
@ -9,6 +10,7 @@ import AppContext from './components/util/AppContext.jsx';
import Community from './components/Community.jsx';
import CssBaseline from '@material-ui/core/CssBaseline';
import Gateway from './components/Gateway.jsx';
import { I18nProvider } from '@lingui/react';
import Namespace from './components/Namespace.jsx';
import Navigation from './components/Navigation.jsx';
import NoMatch from './components/NoMatch.jsx';
@ -21,6 +23,10 @@ import ServiceMesh from './components/ServiceMesh.jsx';
import Tap from './components/Tap.jsx';
import Top from './components/Top.jsx';
import TopRoutes from './components/TopRoutes.jsx';
import _find from 'lodash/find';
import _isEmpty from 'lodash/isEmpty';
import catalogEn from './locales/en/messages.js';
import catalogEs from './locales/es/messages.js';
import { dashboardTheme } from './components/util/theme.js';
const appMain = document.getElementById('main');
@ -45,6 +51,15 @@ if (pathArray[0] === '' && pathArray[1] === 'namespaces' && pathArray[2]) {
defaultNamespace = '_all';
}
const detectedLocales = new LocaleResolver(
[new DETECTORS.NavigatorDetector()],
[new TRANSFORMERS.FallbacksTransformer()],
).getLocales();
const catalogOptions = { en: catalogEn, es: catalogEs };
const selectedLocale =
_find(detectedLocales, l => !_isEmpty(catalogOptions[l])) || 'en';
const selectedCatalog = catalogOptions[selectedLocale] || catalogEn;
class App extends React.Component {
constructor(props) {
super(props);
@ -74,7 +89,11 @@ class App extends React.Component {
render() {
return (
<AppContext.Provider value={this.state}>
<AppHTML />
<I18nProvider
language={selectedLocale}
catalogs={{ [selectedLocale]: selectedCatalog }}>
<AppHTML />
</I18nProvider>
</AppContext.Provider>
);
}

View File

@ -0,0 +1,5 @@
{
"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"
}

View File

@ -0,0 +1,5 @@
{
"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"
}

View File

@ -8,6 +8,8 @@
"@fortawesome/free-regular-svg-icons": "5.13.0",
"@fortawesome/free-solid-svg-icons": "5.13.0",
"@fortawesome/react-fontawesome": "0.1.9",
"@lingui/macro": "2.9.1",
"@lingui/react": "2.9.1",
"@material-ui/core": "4.9.11",
"@material-ui/icons": "4.9.1",
"@material-ui/lab": "^4.0.0-alpha.50",
@ -18,6 +20,7 @@
"d3-format": "1.4.4",
"d3-selection": "1.4.1",
"date-fns": "2.12.0",
"locales-detector": "2.26.0",
"lodash": "4.17.19",
"path": "0.12.7",
"prop-types": "15.7.2",
@ -33,19 +36,22 @@
"whatwg-fetch": "3.0.0"
},
"devDependencies": {
"@babel/core": "7.9.0",
"@babel/core": "7.10.5",
"@babel/plugin-proposal-class-properties": "7.8.3",
"@babel/preset-env": "7.9.5",
"@babel/preset-react": "7.9.4",
"@babel/runtime": "7.9.2",
"@lingui/cli": "2.9.1",
"@wdio/cli": "6.1.2",
"@wdio/local-runner": "6.1.2",
"@wdio/mocha-framework": "6.1.0",
"@wdio/sync": "6.1.0",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "10.1.0",
"babel-jest": "25.4.0",
"babel-loader": "8.1.0",
"babel-plugin-import": "1.13.0",
"babel-plugin-macros": "2.8.0",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"chai": "4.2.0",
"chai-webdriverio": "1.0.0",

File diff suppressed because it is too large Load Diff