);
diff --git a/web/app/js/components/Version.test.js b/web/app/js/components/Version.test.js
index 73ea11f99..175be1ae2 100644
--- a/web/app/js/components/Version.test.js
+++ b/web/app/js/components/Version.test.js
@@ -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(
+ 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(
+ 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(
+ releaseVersion={curVer} />)
);
expect(component).toIncludeText("Version check failed: Fake error.");
diff --git a/web/app/js/components/util/Chip.jsx b/web/app/js/components/util/Chip.jsx
index 16349df16..512c3ffdc 100644
--- a/web/app/js/components/util/Chip.jsx
+++ b/web/app/js/components/util/Chip.jsx
@@ -30,7 +30,7 @@ function SimpleChip(props) {
}
SimpleChip.propTypes = {
- label: PropTypes.string.isRequired,
+ label: PropTypes.shape({}).isRequired,
type: PropTypes.string.isRequired,
};
diff --git a/web/app/js/components/util/TapUtils.jsx b/web/app/js/components/util/TapUtils.jsx
index 2fe3d8533..408a311d0 100644
--- a/web/app/js/components/util/TapUtils.jsx
+++ b/web/app/js/components/util/TapUtils.jsx
@@ -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: columnTitleSource, dataIndex: 'source' },
{ title: '', key: 'arrow', render: () => },
- { title: 'Destination', dataIndex: 'destination' },
+ { title: columnTitleDestination, dataIndex: 'destination' },
];
const getPodOwner = (labels, ResourceLink) => {
@@ -310,8 +311,8 @@ const popoverResourceTable = (d, ResourceLink) => { // eslint-disable-line no-un
};
export const directionColumn = d => (
-
- {d === 'INBOUND' ? 'FROM' : 'TO'}
+ tooltipInbound : tooltipOutbound} placement="right">
+ {d === 'INBOUND' ? columnTitleFrom : columnTitleTo}
);
diff --git a/web/app/js/locales/en/messages.json b/web/app/js/locales/en/messages.json
index fac755632..81dd7feb1 100644
--- a/web/app/js/locales/en/messages.json
+++ b/web/app/js/locales/en/messages.json
@@ -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"
}
diff --git a/web/app/js/locales/es/messages.json b/web/app/js/locales/es/messages.json
index 698825593..eb8f46130 100644
--- a/web/app/js/locales/es/messages.json
+++ b/web/app/js/locales/es/messages.json
@@ -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}"
}
diff --git a/web/app/test/testHelpers.jsx b/web/app/test/testHelpers.jsx
index d5c8d1cbe..fab5296a7 100644
--- a/web/app/test/testHelpers.jsx
+++ b/web/app/test/testHelpers.jsx
@@ -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) => (
@@ -21,3 +26,13 @@ export function routerWrap(Component, extraProps={}, route="/", currentLoc="/")
);
}
+
+export function i18nWrap(Component) {
+ return (
+
+ {Component}
+
+ );
+}