Skip to content

Commit

Permalink
[6.5] [Rollups] Fix i18n bugs (elastic#23848) (elastic#24871)
Browse files Browse the repository at this point in the history
* [Rollups] Fix i18n bugs (elastic#23848)

* Internationalize job details tabs and wrap instances in EuiErrorBoundary to visually localize the error.
* Localize no default index pattern message.
* Localize es interval errors.
* Localize job action menu and confirm delete modal.
* Remove unnecessary use of injectI18n from tabs.
* Localize job status.
* Localize steps.
* Remove template literals from FormattedMessages.

* Remove accidental security addition to translation file.
  • Loading branch information
cjcenizal authored and jasonrhodes committed Oct 31, 2018
1 parent f5096ad commit c122bd2
Show file tree
Hide file tree
Showing 25 changed files with 253 additions and 167 deletions.
3 changes: 2 additions & 1 deletion .i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"statusPage": "src/core_plugins/status_page",
"tagCloud": "src/core_plugins/tagcloud",
"xpack.idxMgmt": "x-pack/plugins/index_management",
"xpack.watcher": "x-pack/plugins/watcher"
"xpack.watcher": "x-pack/plugins/watcher",
"xpack.rollupJobs": "x-pack/plugins/rollup"
},
"exclude": [
"src/ui/ui_render/bootstrap/app_bootstrap.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import indexTemplate from './index.html';
import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';

import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
Expand All @@ -47,11 +48,13 @@ export function updateIndexPatternList(
}

render(
<IndexPatternList
indexPatternCreationOptions={indexPatternCreationOptions}
defaultIndex={defaultIndex}
indexPatterns={indexPatterns}
/>,
<I18nProvider>
<IndexPatternList
indexPatternCreationOptions={indexPatternCreationOptions}
defaultIndex={defaultIndex}
indexPatterns={indexPatterns}
/>
</I18nProvider>,
node,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';

import {
EuiButtonEmpty,
Expand All @@ -27,35 +28,77 @@ import {
EuiSpacer,
} from '@elastic/eui';

export class List extends Component {
class ListUi extends Component {
static propTypes = {
indexPatterns: PropTypes.array,
defaultIndex: PropTypes.string,
}

renderList() {
const { indexPatterns } = this.props;
return indexPatterns && indexPatterns.length ? (
<div>
{
indexPatterns.map(pattern => {
return (
<div key={pattern.id} >
<EuiButtonEmpty size="xs" href={pattern.url} data-test-subj="indexPatternLink">
{pattern.default ? <Fragment><i aria-label="Default index pattern" className="fa fa-star" /> </Fragment> : ''}
{pattern.active ? <strong>{pattern.title}</strong> : pattern.title} {pattern.tag ? (
<Fragment key={pattern.tag.key}>
{<EuiBadge color={pattern.tag.color || 'primary'}>{pattern.tag.name}</EuiBadge> }
</Fragment>
) : null}
</EuiButtonEmpty>
<EuiSpacer size="xs"/>
</div>
);
})
}
</div>
) : null;
const { indexPatterns, intl } = this.props;

if (indexPatterns && indexPatterns.length) {
return (
<div>
{
indexPatterns.map(pattern => {
const { id, default: isDefault, active, url, title, tag } = pattern;

let icon;

if (isDefault) {
icon = (
<Fragment>
<em
aria-label={intl.formatMessage({
id: 'kbn.management.indexPatternList.defaultIndexPatternIconAriaLabel',
defaultMessage: 'Default index pattern',
})}
className="fa fa-star"
/>
{' '}
</Fragment>
);
}

let titleElement;

if (active) {
titleElement = <strong>{title}</strong>;
} else {
titleElement = title;
}

let tagElement;

if (tag) {
const { key, color, name } = tag;

tagElement = (
<Fragment key={key}>
{' '}
<EuiBadge color={color || 'primary'}>{name}</EuiBadge>
</Fragment>
);
}

return (
<div key={id}>
<EuiButtonEmpty size="xs" href={url} data-test-subj="indexPatternLink">
{icon}
{titleElement}
{tagElement}
</EuiButtonEmpty>
<EuiSpacer size="xs"/>
</div>
);
})
}
</div>
);
}

return null;
}

renderNoDefaultMessage() {
Expand All @@ -66,7 +109,12 @@ export class List extends Component {
color="warning"
size="s"
iconType="alert"
title="No default index pattern. You must select or create one to continue."
title={(
<FormattedMessage
id="kbn.management.indexPatternList.noDefaultIndexPatternTitle"
defaultMessage="No default index pattern. You must select or create one to continue."
/>
)}
/>
</div>
) : null;
Expand All @@ -81,3 +129,5 @@ export class List extends Component {
);
}
}

export const List = injectI18n(ListUi);
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/

import { i18n } from '@kbn/i18n';

const indexPatternTypeName = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultTypeName',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import { Unit } from '@kbn/datemath';
import { i18n } from '@kbn/i18n';

export class InvalidEsCalendarIntervalError extends Error {
constructor(
Expand All @@ -26,7 +27,12 @@ export class InvalidEsCalendarIntervalError extends Error {
public readonly unit: Unit,
public readonly type: string
) {
super(`Invalid calendar interval: ${interval}, value must be 1`);
super(
i18n.translate('common.ui.parseEsInterval.invalidEsCalendarIntervalErrorMessage', {
defaultMessage: 'Invalid calendar interval: {interval}, value must be 1',
values: { interval },
})
);

this.name = 'InvalidEsCalendarIntervalError';
this.value = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@
* under the License.
*/

import { i18n } from '@kbn/i18n';

export class InvalidEsIntervalFormatError extends Error {
constructor(public readonly interval: string) {
super(`Invalid interval format: ${interval}`);
super(
i18n.translate('common.ui.parseEsInterval.invalidEsIntervalFormatErrorMessage', {
defaultMessage: 'Invalid interval format: {interval}',
values: { interval },
})
);

this.name = 'InvalidEsIntervalFormatError';

// captureStackTrace is only available in the V8 engine, so any browser using
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export {
JOB_DETAILS_TAB_HISTOGRAM,
JOB_DETAILS_TAB_METRICS,
JOB_DETAILS_TAB_JSON,
tabToHumanizedMap,
} from './job_details';

export { JobStatus } from './job_status';
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
class ConfirmDeleteModalUi extends Component {
static propTypes = {
isSingleSelection: PropTypes.bool.isRequired,
entity: PropTypes.string.isRequired,
jobs: PropTypes.array.isRequired,
onCancel: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
Expand All @@ -39,7 +38,6 @@ class ConfirmDeleteModalUi extends Component {
render() {
const {
isSingleSelection,
entity,
jobs,
onCancel,
onConfirm,
Expand All @@ -52,36 +50,34 @@ class ConfirmDeleteModalUi extends Component {
if (isSingleSelection) {
const { id, status } = jobs[0];
title = intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.modalTitleSingle',
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.deleteSingleJobTitle',
defaultMessage: 'Delete rollup job \'{id}\'?',
}, { id });

if (status === 'started') {
content = (
<p>
<FormattedMessage
id="xpack.rollupJobs.jobActionMenu.deleteJob.deleteDescriptionSingleRunning"
id="xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.deleteSingleJobDescription"
defaultMessage="This job has been started."
/>
</p>
);
}
} else {
title = intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.modalTitleMultiple',
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.multipleDeletionTitle',
defaultMessage: 'Delete {count} rollup jobs?',
}, { count: jobs.length });

content = (
<Fragment>
<p>
<FormattedMessage
id="xpack.rollupJobs.jobActionMenu.deleteJob.deleteDescriptionMultiple"
defaultMessage="You are about to delete {mergedKeyword}"
values={{ mergedKeyword: isSingleSelection ? 'this' : 'these' }}
id="xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.multipleDeletionDescription"
defaultMessage="You are about to delete {isSingleSelection, plural, one {this job} other {these jobs}}"
values={{ isSingleSelection: isSingleSelection ? 1 : 0 }}
/>
{' '}
{entity}:
</p>
{this.renderJobs()}
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,18 @@ class JobActionMenuUi extends Component {
intl,
} = this.props;

const isSingleSelection = this.isSingleSelection();
const entity = this.getEntity(isSingleSelection);
const isSingleSelection = this.isSingleSelection() ? 1 : 0;

const items = [];

if (this.canStartJobs()) {
items.push({
name: intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.startJobLabel',
defaultMessage: 'Start {entity}',
}, { entity }),
defaultMessage: 'Start {isSingleSelection, plural, one {job} other {jobs}}',
}, {
isSingleSelection,
}),
icon: <EuiIcon type="play" />,
onClick: () => {
this.closePopover();
Expand All @@ -76,8 +77,10 @@ class JobActionMenuUi extends Component {
items.push({
name: intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.stopJobLabel',
defaultMessage: 'Stop {entity}',
}, { entity }),
defaultMessage: 'Stop {isSingleSelection, plural, one {job} other {jobs}}',
}, {
isSingleSelection,
}),
icon: <EuiIcon type="stop" />,
onClick: () => {
this.closePopover();
Expand All @@ -89,22 +92,23 @@ class JobActionMenuUi extends Component {
items.push({
name: intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.deleteJobLabel',
defaultMessage: 'Delete {entity}',
}, { entity }),
defaultMessage: 'Delete {isSingleSelection, plural, one {job} other {jobs}}',
}, {
isSingleSelection,
}),
icon: <EuiIcon type="trash" />,
onClick: () => {
this.closePopover();
this.openDeleteConfirmationModal();
},
});

const upperCasedEntity = `${entity[0].toUpperCase()}${entity.slice(1)}`;
const panelTree = {
id: 0,
title: intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.panelTitle',
defaultMessage: '{upperCasedEntity} options',
}, { upperCasedEntity }),
defaultMessage: 'Job options',
}),
items,
};

Expand Down Expand Up @@ -159,12 +163,10 @@ class JobActionMenuUi extends Component {
};

const isSingleSelection = this.isSingleSelection();
const entity = this.getEntity(isSingleSelection);

return (
<ConfirmDeleteModal
isSingleSelection={isSingleSelection}
entity={entity}
jobs={jobs}
onConfirm={onConfirmDelete}
onCancel={this.closeDeleteConfirmationModal}
Expand All @@ -176,10 +178,6 @@ class JobActionMenuUi extends Component {
return this.props.jobs.length === 1;
};

getEntity = isSingleSelection => {
return isSingleSelection ? 'job' : 'jobs';
};

render() {
const { intl } = this.props;
const jobCount = this.props.jobs.length;
Expand All @@ -195,12 +193,12 @@ class JobActionMenuUi extends Component {
} = this.props;

const panels = this.panels();
const isSingleSelection = this.isSingleSelection();
const entity = this.getEntity(isSingleSelection);

const actionsAriaLabel = intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.jobActionMenuButtonAriaLabel',
defaultMessage: '{entity} options',
}, { entity });
defaultMessage: 'Job options',
});

const button = (
<EuiButton
data-test-subj="jobActionMenuButton"
Expand All @@ -218,7 +216,6 @@ class JobActionMenuUi extends Component {
<div>
{this.confirmDeleteModal()}
<EuiPopover
id={`actionMenu${entity}`}
button={button}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
Expand Down
Loading

0 comments on commit c122bd2

Please sign in to comment.