Discover translations (#24079)

* translate discover plugin

* update discover translation PR

* Update fetch_error.js

* Update unit tests

* Fix eslint

* use separate span tag for label

* use separate tags for translated labels

* resolve review comments

* Update snapshot

* fix issue with bucket aria-label

* fix quotes

* Update snapshot

* fix quotes

* update snapshots
This commit is contained in:
tibmt 2018-11-20 17:09:55 +03:00 committed by Maryia Lapata
parent 40960f98ab
commit 973640c3b8
20 changed files with 481 additions and 142 deletions

View file

@ -21,6 +21,7 @@ import 'ngreact';
import React, { Fragment } from 'react';
import { uiModules } from 'ui/modules';
import chrome from 'ui/chrome';
import { FormattedMessage, injectI18nProvider } from '@kbn/i18n/react';
import {
EuiFlexGroup,
@ -43,9 +44,26 @@ const DiscoverFetchError = ({ fetchError }) => {
body = (
<p>
You can address this error by editing the &lsquo;{fetchError.script}&rsquo; field
in <a href={url}>Management &gt; Index Patterns</a>,
under the &ldquo;Scripted fields&rdquo; tab.
<FormattedMessage
id="kbn.discover.fetchError.howToAddressErrorDescription"
defaultMessage="You can address this error by editing the {fetchErrorScript} field
in {managementLink}, under the {scriptedFields} tab."
values={{
fetchErrorScript: '&lsquo;' + fetchError.script + '&rsquo;',
scriptedFields: <FormattedMessage
id="kbn.discover.fetchError.scriptedFieldsText"
defaultMessage="&ldquo;Scripted fields&rdquo;"
/>,
managementLink: (
<a href={url}>
<FormattedMessage
id="kbn.discover.fetchError.managmentLinkText"
defaultMessage="Management &gt; Index Patterns"
/>
</a>
)
}}
/>
</p>
);
}
@ -77,4 +95,4 @@ const DiscoverFetchError = ({ fetchError }) => {
const app = uiModules.get('apps/discover', ['react']);
app.directive('discoverFetchError', reactDirective => reactDirective(DiscoverFetchError));
app.directive('discoverFetchError', reactDirective => reactDirective(injectI18nProvider(DiscoverFetchError)));

View file

@ -17,7 +17,7 @@
ng-if="field.name !== '_source'"
ng-click="toggleDisplay(field)"
ng-class="::field.display ? 'kuiButton--danger' : 'kuiButton--primary'"
ng-bind="::field.display ? 'remove' : 'add'"
ng-bind="::addRemoveButtonLabel"
class="dscSidebarItem__action kuiButton kuiButton--small"
data-test-subj="fieldToggle-{{::field.name}}"
></button>

View file

@ -26,7 +26,7 @@ import detailsHtml from './lib/detail_views/string.html';
import { uiModules } from 'ui/modules';
const app = uiModules.get('apps/discover');
app.directive('discoverField', function ($compile) {
app.directive('discoverField', function ($compile, i18n) {
return {
restrict: 'E',
template: html,
@ -42,11 +42,18 @@ app.directive('discoverField', function ($compile) {
let detailsElem;
let detailScope;
const init = function () {
if ($scope.field.details) {
$scope.toggleDetails($scope.field, true);
}
$scope.addRemoveButtonLabel = $scope.field.display
? i18n('kbn.discover.fieldChooser.discoverField.removeButtonLabel', {
defaultMessage: 'remove',
})
: i18n('kbn.discover.fieldChooser.discoverField.addButtonLabel', {
defaultMessage: 'add',
});
};
const getWarnings = function (field) {
@ -92,6 +99,18 @@ app.directive('discoverField', function ($compile) {
$scope.onShowDetails(field, recompute);
detailScope = $scope.$new();
detailScope.warnings = getWarnings(field);
detailScope.getBucketAriaLabel = (bucket) => {
return i18n('kbn.discover.fieldChooser.discoverField.bucketAriaLabel', {
defaultMessage: 'Value: {value}',
values: {
value: bucket.display === ''
? i18n('kbn.discover.fieldChooser.discoverField.emptyStringText', {
defaultMessage: 'Empty string',
})
: bucket.display,
},
});
};
detailsElem = $(detailsHtml);
$compile(detailsElem)(detailScope);

View file

@ -1,4 +1,4 @@
<section class="sidebar-list" aria-label="Index and fields">
<section class="sidebar-list" aria-label="{{::'kbn.discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel' | i18n: {defaultMessage: 'Index and fields'} }}">
<div ng-show="indexPatternList.length > 1">
<ui-select
class="index-pattern-selection"
@ -32,9 +32,13 @@
</div>
<div class="dscSidebar__listHeader sidebar-list-header" ng-if="fields.length">
<h3 class="sidebar-list-header-label" id="selected_fields" tabindex="0">
Selected fields
</h3>
<h3
class="sidebar-list-header-label"
id="selected_fields"
tabindex="0"
i18n-id="kbn.discover.fieldChooser.filter.selectedFieldsTitle"
i18n-default-message="Selected fields"
></h3>
</div>
<ul class="list-unstyled dscFieldList--selected" >
<discover-field
@ -49,9 +53,13 @@
</ul>
<div class="sidebar-list-header sidebar-item euiFlexGroup euiFlexGroup--gutterMedium" ng-if="fields.length">
<h3 class="euiFlexItem sidebar-list-header-label" id="available_fields" tabindex="0">
Available fields
</h3>
<h3
class="euiFlexItem sidebar-list-header-label"
id="available_fields"
tabindex="0"
i18n-id="kbn.discover.fieldChooser.filter.availableFieldsTitle"
i18n-default-message="Available fields"
></h3>
<div class="euiFlexItem euiFlexItem--flexGrowZero">
<button
@ -72,7 +80,7 @@
ng-class="{ 'kuiButton--basic': !filter.active, 'kuiButton--primary': filter.active, 'hidden-xs': !$parent.showFields, 'hidden-sm': !$parent.showFields }"
class="kuiButton kuiButton--small pull-right discover-field-filter-toggle"
ng-click="$parent.showFilter = !$parent.showFilter"
aria-label="{{$parent.showFilter ? 'Hide' : 'Show'}} field settings"
aria-label="{{toggleFieldFilterButtonAriaLabel}}"
aria-expanded="{{!!$parent.showFilter}}"
aria-controls="discoverFieldFilter"
data-test-subj="toggleFieldFilterButton"
@ -85,9 +93,11 @@
<div class="sidebar-item dscFieldDetails" ng-show="showFilter" id="discoverFieldFilter" data-test-subj="discoverFieldFilter">
<form role="form">
<div class="form-group">
<label for="discoverFieldChooserFilterAggregatable">
Aggregatable
</label>
<label
for="discoverFieldChooserFilterAggregatable"
i18n-id="kbn.discover.fieldChooser.filter.aggregatableLabel"
i18n-default-message="Aggregatable"
></label>
<select
id="discoverFieldChooserFilterAggregatable"
ng-options="opt.value as opt.label for opt in filter.boolOpts"
@ -96,9 +106,11 @@
</select>
</div>
<div class="form-group">
<label for="discoverFieldChooserFilterSearchable">
Searchable
</label>
<label
for="discoverFieldChooserFilterSearchable"
i18n-id="kbn.discover.fieldChooser.filter.searchableLabel"
i18n-default-message="Searchable"
></label>
<select
id="discoverFieldChooserFilterSearchable"
ng-options="opt.value as opt.label for opt in filter.boolOpts"
@ -107,9 +119,11 @@
</select>
</div>
<div class="form-group">
<label for="discoverFieldChooserFilterType">
Type
</label>
<label
for="discoverFieldChooserFilterType"
i18n-id="kbn.discover.fieldChooser.filter.typeLabel"
i18n-default-message="Type"
></label>
<select
id="discoverFieldChooserFilterType"
ng-options="field as field for field in fieldTypes"
@ -118,9 +132,11 @@
</select>
</div>
<div class="form-group">
<label for="discoverFieldChooserFilterFieldName">
Field name
</label>
<label
for="discoverFieldChooserFilterFieldName"
i18n-id="kbn.discover.fieldChooser.filter.fieldNameLabel"
i18n-default-message="Field name"
></label>
<input
id="discoverFieldChooserFilterFieldName"
type="text"
@ -131,16 +147,19 @@
<div class="form-group">
<label for="discoverFieldChooserHideMissingFields">
<input id="discoverFieldChooserHideMissingFields" type="checkbox" ng-model="filter.vals.missing">
Hide missing fields
<span
i18n-id="kbn.discover.fieldChooser.filter.hideMissingFieldsLabel"
i18n-default-message="Hide missing fields"
></span>
</label>
</div>
<button
ng-click="filter.reset()"
ng-disabled="!filter.active"
class="kuiButton kuiButton--danger kuiButton--fullWidth"
>
Reset filters
</button>
i18n-id="kbn.discover.fieldChooser.filter.resetFiltersButtonLabel"
i18n-default-message="Reset filters"
></button>
</form>
</div>
@ -149,7 +168,10 @@
ng-class="{ 'hidden-sm': !showFields, 'hidden-xs': !showFields }"
class="list-unstyled sidebar-well dscFieldList--popular">
<li class="sidebar-item sidebar-list-header">
<h6>Popular</h6>
<h6
i18n-id="kbn.discover.fieldChooser.filter.popularTitle"
i18n-default-message="Popular"
></h6>
</li>
<discover-field
ng-repeat="field in popularFields | filter:filter.isFieldFiltered"

View file

@ -31,7 +31,7 @@ import { uiModules } from 'ui/modules';
import fieldChooserTemplate from './field_chooser.html';
const app = uiModules.get('apps/discover');
app.directive('discFieldChooser', function ($location, globalState, config, $route) {
app.directive('discFieldChooser', function ($location, globalState, config, $route, i18n) {
return {
restrict: 'E',
scope: {
@ -47,6 +47,16 @@ app.directive('discFieldChooser', function ($location, globalState, config, $rou
},
template: fieldChooserTemplate,
link: function ($scope) {
$scope.$parent.$watch('showFilter', () =>{
$scope.toggleFieldFilterButtonAriaLabel = $scope.$parent.showFilter
? i18n('kbn.discover.fieldChooser.toggleFieldFilterButtonHideAriaLabel', {
defaultMessage: 'Hide field settings',
})
: i18n('kbn.discover.fieldChooser.toggleFieldFilterButtonShowAriaLabel', {
defaultMessage: 'Show field settings',
});
});
$scope.selectedIndexPattern = $scope.indexPatternList.find(
(pattern) => pattern.id === $scope.indexPattern.id
);

View file

@ -1,7 +1,10 @@
<div class="dscFieldDetails">
<div class="kuiVerticalRhythmSmall">
<p class="euiText euiText--extraSmall euiTextColor--subdued" ng-show="!field.details.error">
Top 5 values in
<span
i18n-id="kbn.discover.fieldChooser.detailViews.topValuesInRecordsDescription"
i18n-default-message="Top 5 values in"
></span>
<span ng-if="!field.details.error">
<a
class="kuiLink"
@ -14,7 +17,11 @@
ng-show="field.indexPattern.metaFields.includes(field.name) || field.scripted">
{{::field.details.exists}}
</span>
/ {{::field.details.total}} records
/ {{::field.details.total}}
<span
i18n-id="kbn.discover.fieldChooser.detailViews.recordsText"
i18n-default-message="records"
></span>
</span>
</p>
@ -30,9 +37,14 @@
css-truncate
css-truncate-expandable="true"
class="dscFieldDetails__value"
aria-label="Value: {{:: bucket.display === '' ? 'Empty string' : bucket.display }}"
aria-label="{{::getBucketAriaLabel(bucket)}}"
>
{{::bucket.display}} <em ng-show="bucket.display === ''">Empty string</em>
{{::bucket.display}}
<em
ng-show="bucket.display === ''"
i18n-id="kbn.discover.fieldChooser.detailViews.emptyStringText"
i18n-default-message="Empty string"
></em>
</div>
<!-- Add/remove filter buttons -->
@ -43,7 +55,7 @@
<button
class="dscFieldDetailsItem__button"
ng-click="onAddFilter(field, bucket.value, '+')"
aria-label="Filter for this value"
aria-label="{{::'kbn.discover.fieldChooser.detailViews.filterValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter for this value'} }}"
data-test-subj="plus-{{::field.name}}-{{::bucket.display}}"
>
<span
@ -55,7 +67,7 @@
<button
class="dscFieldDetailsItem__button"
ng-click="onAddFilter(field, bucket.value, '-')"
aria-label="Filter out this value"
aria-label="{{::'kbn.discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter out this value'} }}"
data-test-subj="minus-{{::field.name}}-{{::bucket.display}}"
>
<span
@ -84,9 +96,16 @@
class="kuiButton kuiButton--secondary kuiButton--small kuiButton--fullWidth kuiVerticalRhythmSmall"
data-test-subj="fieldVisualize-{{::field.name}}"
>
Visualize
<span
i18n-id="kbn.discover.fieldChooser.detailViews.visualizeLinkText"
i18n-default-message="Visualize"
></span>
<span class="discover-field-vis-warning" ng-show="warnings.length" tooltip="{{warnings.join(' ')}}">
( {{::warnings.length}} <ng-pluralize count="warnings.length" when="{'1':'warning', 'other':'warnings'}"></ng-pluralize> <i aria-hidden="true" class="fa fa-warning"></i> )
( <span
i18n-id="kbn.discover.fieldChooser.detailViews.warningsText"
i18n-default-message="{warningsLength, plural, one {# warning} other {# warnings}}"
i18n-values="{ warningsLength: warnings.length }"
></span> <i aria-hidden="true" class="fa fa-warning"></i> )
</span>
</a>
</div>

View file

@ -18,6 +18,7 @@
*/
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
function getFieldValues(hits, field) {
const name = field.name;
@ -38,7 +39,9 @@ function getFieldValueCounts(params) {
|| params.field.type === 'geo_shape'
|| params.field.type === 'attachment'
) {
return { error: 'Analysis is not available for geo fields.' };
return { error: i18n.translate('kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage', {
defaultMessage: 'Analysis is not available for geo fields.',
}) };
}
const allValues = getFieldValues(params.hits, params.field);
@ -59,9 +62,13 @@ function getFieldValueCounts(params) {
if (params.hits.length - missing === 0) {
return {
error: 'This field is present in your Elasticsearch mapping' +
' but not in the ' + params.hits.length + ' documents shown in the doc table.' +
' You may still be able to visualize or search on it.'
error: i18n.translate('kbn.discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage', {
// eslint-disable-next-line max-len
defaultMessage: 'This field is present in your Elasticsearch mapping but not in the {hitsLength} documents shown in the doc table. You may still be able to visualize or search on it.',
values: {
hitsLength: params.hits.length,
},
})
};
}
@ -89,7 +96,9 @@ function _groupValues(allValues, params) {
allValues.forEach(function (value) {
if (_.isObject(value) && !Array.isArray(value)) {
throw new Error('Analysis is not available for object fields');
throw new Error(i18n.translate('kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage', {
defaultMessage: 'Analysis is not available for object fields.',
}));
}
if (Array.isArray(value) && !params.grouped) {

View file

@ -63,7 +63,6 @@ import { showOpenSearchPanel } from '../top_nav/show_open_search_panel';
import { tabifyAggResponse } from 'ui/agg_response/tabify';
import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal';
import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
import { i18n } from '@kbn/i18n';
const app = uiModules.get('apps/discover', [
'kibana/notify',
@ -156,7 +155,8 @@ function discoverController(
courier,
kbnUrl,
localStorage,
breadcrumbState
breadcrumbState,
i18n,
) {
const Vis = Private(VisProvider);
const docTitle = Private(DocTitleProvider);
@ -191,13 +191,21 @@ function discoverController(
};
$scope.topNavMenu = [{
key: 'new',
description: 'New Search',
key: i18n('kbn.discover.localMenu.localMenu.newSearchTitle', {
defaultMessage: 'new',
}),
description: i18n('kbn.discover.localMenu.newSearchDescription', {
defaultMessage: 'New Search',
}),
run: function () { kbnUrl.change('/discover'); },
testId: 'discoverNewButton',
}, {
key: 'save',
description: 'Save Search',
key: i18n('kbn.discover.localMenu.saveTitle', {
defaultMessage: 'save',
}),
description: i18n('kbn.discover.localMenu.saveSearchDescription', {
defaultMessage: 'Save Search',
}),
testId: 'discoverSaveButton',
run: async () => {
const onSave = ({ newTitle, newCopyOnSave, isTitleDuplicateConfirmed, onTitleDuplicate }) => {
@ -229,8 +237,12 @@ function discoverController(
showSaveModal(saveModal);
}
}, {
key: 'open',
description: 'Open Saved Search',
key: i18n('kbn.discover.localMenu.openTitle', {
defaultMessage: 'open',
}),
description: i18n('kbn.discover.localMenu.openSavedSearchDescription', {
defaultMessage: 'Open Saved Search',
}),
testId: 'discoverOpenButton',
run: () => {
showOpenSearchPanel({
@ -240,8 +252,12 @@ function discoverController(
});
}
}, {
key: 'share',
description: 'Share Search',
key: i18n('kbn.discover.localMenu.shareTitle', {
defaultMessage: 'share',
}),
description: i18n('kbn.discover.localMenu.shareSearchDescription', {
defaultMessage: 'Share Search',
}),
testId: 'shareTopNavButton',
run: async (menuItem, navController, anchorElement) => {
const sharingData = await this.getSharingData();
@ -260,8 +276,12 @@ function discoverController(
});
}
}, {
key: 'inspect',
description: 'Open Inspector for search',
key: i18n('kbn.discover.localMenu.inspectTitle', {
defaultMessage: 'inspect',
}),
description: i18n('kbn.discover.localMenu.openInspectorForSearchDescription', {
defaultMessage: 'Open Inspector for search',
}),
testId: 'openInspectorButton',
run() {
Inspector.open(inspectorAdapters, {
@ -295,8 +315,7 @@ function discoverController(
const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : '';
docTitle.change(`Discover${pageTitleSuffix}`);
const discoverBreadcrumbsTitle = i18n.translate('kbn.discover.discoverBreadcrumbsTitle', {
const discoverBreadcrumbsTitle = i18n('kbn.discover.discoverBreadcrumbTitle', {
defaultMessage: 'Discover',
});
@ -397,8 +416,20 @@ function discoverController(
$scope.getBucketIntervalToolTipText = () => {
return (
`This interval creates ${$scope.bucketInterval.scale > 1 ? 'buckets that are too large' : 'too many buckets'}
to show in the selected time range, so it has been scaled to ${$scope.bucketInterval.description }`
i18n('kbn.discover.bucketIntervalTooltip', {
// eslint-disable-next-line max-len
defaultMessage: 'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}',
values: {
bucketsDescription: $scope.bucketInterval.scale > 1
? i18n('kbn.discover.bucketIntervalTooltip.tooLargeBucketsText', {
defaultMessage: 'buckets that are too large',
})
: i18n('kbn.discover.bucketIntervalTooltip.tooManyBucketsText', {
defaultMessage: 'too many buckets',
}),
bucketIntervalDescription: $scope.bucketInterval.description,
},
})
);
};
@ -544,7 +575,12 @@ function discoverController(
stateMonitor.setInitialState($state.toJSON());
if (id) {
toastNotifications.addSuccess({
title: `Search '${savedSearch.title}' was saved`,
title: i18n('kbn.discover.notifications.savedSearchTitle', {
defaultMessage: `Search '{savedSearchTitle}' was saved`,
values: {
savedSearchTitle: savedSearch.title,
}
}),
'data-test-subj': 'saveSearchSuccess',
});
@ -560,7 +596,12 @@ function discoverController(
return { id };
} catch(saveError) {
toastNotifications.addDanger({
title: `Search '${savedSearch.title}' was not saved.`,
title: i18n('kbn.discover.notifications.notSavedSearchTitle', {
defaultMessage: `Search '{savedSearchTitle}' was not saved.`,
values: {
savedSearchTitle: savedSearch.title,
}
}),
text: saveError.message
});
return { error: saveError };
@ -657,9 +698,16 @@ function discoverController(
if (status.remaining > 0) {
const inspectorRequest = inspectorAdapters.requests.start(
`Segment ${$scope.fetchStatus.complete}`,
i18n('kbn.discover.inspectorRequest.segmentFetchCompleteStatusTitle', {
defaultMessage: 'Segment {fetchCompleteStatus}',
values: {
fetchCompleteStatus: $scope.fetchStatus.complete,
}
}),
{
description: `This request queries Elasticsearch to fetch the data for the search.`,
description: i18n('kbn.discover.inspectorRequest.segmentFetchCompleteStatusDescription', {
defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.',
}),
});
inspectorRequest.stats(getRequestInspectorStats($scope.searchSource));
$scope.searchSource.getSearchRequestBody().then(body => {
@ -887,19 +935,36 @@ function discoverController(
}
if (stateVal && !stateValFound) {
const warningTitle = `"${stateVal}" is not a configured index pattern ID`;
const warningTitle = i18n('kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle', {
defaultMessage: '{stateVal} is not a configured index pattern ID',
values: {
stateVal: `"${stateVal}"`,
},
});
if (ownIndexPattern) {
toastNotifications.addWarning({
title: warningTitle,
text: `Showing the saved index pattern: "${ownIndexPattern.title}" (${ownIndexPattern.id})`,
text: i18n('kbn.discover.showingSavedIndexPatternWarningDescription', {
defaultMessage: 'Showing the saved index pattern: "{ownIndexPatternTitle}" ({ownIndexPatternId})',
values: {
ownIndexPatternTitle: ownIndexPattern.title,
ownIndexPatternId: ownIndexPattern.id,
},
}),
});
return ownIndexPattern;
}
toastNotifications.addWarning({
title: warningTitle,
text: `Showing the default index pattern: "${loadedIndexPattern.title}" (${loadedIndexPattern.id})`,
text: i18n('kbn.discover.showingDefaultIndexPatternWarningDescription', {
defaultMessage: 'Showing the default index pattern: "{loadedIndexPatternTitle}" ({loadedIndexPatternId})',
values: {
loadedIndexPatternTitle: loadedIndexPattern.title,
loadedIndexPatternId: loadedIndexPattern.id,
},
}),
});
}

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
export function getPainlessError(error: Error) {
@ -38,7 +39,10 @@ export function getPainlessError(error: Error) {
return {
lang,
script,
message: `Error with Painless scripted field '${script}'`,
message: i18n.translate('kbn.discover.painlessError.painlessScriptedFieldErrorMessage', {
defaultMessage: "Error with Painless scripted field '{script}'.",
values: { script },
}),
error: error.message,
};
}

View file

@ -317,9 +317,9 @@ Array [
class="euiText euiText--extraSmall"
>
<strong>
Index A
Index &lsquo;A&rsquo;
</strong>
, shard 1
, shard &lsquo;1&rsquo;
</div>
<div
class="euiSpacer euiSpacer--s"
@ -346,9 +346,9 @@ Array [
class="euiText euiText--extraSmall"
>
<strong>
Index B
Index &lsquo;B&rsquo;
</strong>
, shard 2
, shard &lsquo;2&rsquo;
</div>
<div
class="euiSpacer euiSpacer--s"

View file

@ -19,6 +19,7 @@
import 'ngreact';
import { uiModules } from 'ui/modules';
import { injectI18nProvider } from '@kbn/i18n/react';
import {
DiscoverNoResults,
@ -32,9 +33,9 @@ import './timechart';
const app = uiModules.get('apps/discover', ['react']);
app.directive('discoverNoResults', reactDirective => reactDirective(DiscoverNoResults));
app.directive('discoverNoResults', reactDirective => reactDirective(injectI18nProvider(DiscoverNoResults)));
app.directive(
'discoverUnsupportedIndexPattern',
reactDirective => reactDirective(DiscoverUnsupportedIndexPattern, ['unsupportedType'])
reactDirective => reactDirective(injectI18nProvider(DiscoverUnsupportedIndexPattern), ['unsupportedType'])
);

View file

@ -18,6 +18,7 @@
*/
import React, { Component, Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import PropTypes from 'prop-types';
import {
@ -61,7 +62,24 @@ export class DiscoverNoResults extends Component {
const failures = shardFailures.map((failure, index) => (
<div key={`${failure.index}${failure.shard}${failure.reason}`}>
<EuiText size="xs">
<strong>Index &lsquo;{failure.index}&rsquo;</strong>, shard &lsquo;{failure.shard}&rsquo;
<FormattedMessage
id="kbn.discover.noResults.indexFailureShardText"
defaultMessage="{index}, shard {failureShard}"
values={{
index: (
<strong>
<FormattedMessage
id="kbn.discover.noResults.indexFailureIndexText"
defaultMessage="Index {failureIndex}"
values={{
failureIndex: `&lsquo;${failure.index}&rsquo;`,
}}
/>
</strong>
),
failureShard: `&lsquo;${failure.shard}&rsquo;`,
}}
/>
</EuiText>
<EuiSpacer size="s" />
@ -80,11 +98,17 @@ export class DiscoverNoResults extends Component {
<EuiText>
<h3>
Address shard failures
<FormattedMessage
id="kbn.discover.noResults.addressShardFailuresTitle"
defaultMessage="Address shard failures"
/>
</h3>
<p>
The following shard failures occurred:
<FormattedMessage
id="kbn.discover.noResults.shardFailuresDescription"
defaultMessage="The following shard failures occurred:"
/>
</p>
{failures}
@ -102,21 +126,33 @@ export class DiscoverNoResults extends Component {
<EuiText>
<h3>
Expand your time range
<FormattedMessage
id="kbn.discover.noResults.expandYourTimeRangeTitle"
defaultMessage="Expand your time range"
/>
</h3>
<p>
One or more of the indices you&rsquo;re looking at contains a date field. Your query may
not match anything in the current time range, or there may not be any data at all in
the currently selected time range. You can try {(
<EuiLink
data-test-subj="discoverNoResultsTimefilter"
onClick={this.onClickTimePickerButton}
aria-expanded={isTimePickerOpen}
>
opening the time picker
</EuiLink>
)} and changing the time range to one which contains data.
<FormattedMessage
id="kbn.discover.noResults.queryMayNotMatchTitle"
defaultMessage="One or more of the indices you&rsquo;re looking at contains a date field. Your query may
not match anything in the current time range, or there may not be any data at all in
the currently selected time range. You can try {timepickerLink} and changing the time range to one which contains data."
values={{
timepickerLink: (
<EuiLink
data-test-subj="discoverNoResultsTimefilter"
onClick={this.onClickTimePickerButton}
aria-expanded={isTimePickerOpen}
>
<FormattedMessage
id="kbn.discover.noResults.openingTimepickerLinkText"
defaultMessage="opening the time picker"
/>
</EuiLink>
)
}}
/>
</p>
</EuiText>
@ -129,19 +165,64 @@ export class DiscoverNoResults extends Component {
if (queryLanguage === 'lucene') {
const searchExamples = [{
description: <EuiCode>200</EuiCode>,
title: <EuiText><strong>Find requests that contain the number 200, in any field</strong></EuiText>,
title: (
<EuiText>
<strong>
<FormattedMessage
id="kbn.discover.noResults.searchExamples.anyField200StatusCodeExampleTitle"
defaultMessage="Find requests that contain the number 200, in any field"
/>
</strong>
</EuiText>
),
}, {
description: <EuiCode>status:200</EuiCode>,
title: <EuiText><strong>Find 200 in the status field</strong></EuiText>,
title: (
<EuiText>
<strong>
<FormattedMessage
id="kbn.discover.noResults.searchExamples.statusField200StatusCodeExampleTitle"
defaultMessage="Find 200 in the status field"
/>
</strong>
</EuiText>
),
}, {
description: <EuiCode>status:[400 TO 499]</EuiCode>,
title: <EuiText><strong>Find all status codes between 400-499</strong></EuiText>,
title: (
<EuiText>
<strong>
<FormattedMessage
id="kbn.discover.noResults.searchExamples.400to499StatusCodeExampleTitle"
defaultMessage="Find all status codes between 400-499"
/>
</strong>
</EuiText>
),
}, {
description: <EuiCode>status:[400 TO 499] AND extension:PHP</EuiCode>,
title: <EuiText><strong>Find status codes 400-499 with the extension php</strong></EuiText>,
title: (
<EuiText>
<strong>
<FormattedMessage
id="kbn.discover.noResults.searchExamples.400to499StatusCodeWithPhpExtensionExampleTitle"
defaultMessage="Find status codes 400-499 with the extension php"
/>
</strong>
</EuiText>
),
}, {
description: <EuiCode>status:[400 TO 499] AND (extension:php OR extension:html)</EuiCode>,
title: <EuiText><strong>Find status codes 400-499 with the extension php or html</strong></EuiText>,
title: (
<EuiText>
<strong>
<FormattedMessage
id="kbn.discover.noResults.searchExamples.400to499StatusCodeWithPhpOrHtmlExtensionExampleTitle"
defaultMessage="Find status codes 400-499 with the extension php or html"
/>
</strong>
</EuiText>
),
}];
luceneQueryMessage = (
@ -150,19 +231,31 @@ export class DiscoverNoResults extends Component {
<EuiText>
<h3>
Refine your query
<FormattedMessage
id="kbn.discover.noResults.searchExamples.refineYourQueryTitle"
defaultMessage="Refine your query"
/>
</h3>
<p>
The search bar at the top uses Elasticsearch&rsquo;s support for Lucene {(
<EuiLink
target="_blank"
href={getDocLink('query.luceneQuerySyntax')}
>
Query String syntax
</EuiLink>
)}. Here are some examples of how you can search for web server logs that have been
parsed into a few fields.
<FormattedMessage
id="kbn.discover.noResults.searchExamples.howTosearchForWebServerLogsDescription"
defaultMessage="The search bar at the top uses Elasticsearch&rsquo;s support for Lucene {queryStringSyntaxLink}.
Here are some examples of how you can search for web server logs that have been parsed into a few fields."
values={{
queryStringSyntaxLink: (
<EuiLink
target="_blank"
href={getDocLink('query.luceneQuerySyntax')}
>
<FormattedMessage
id="kbn.discover.noResults.searchExamples.queryStringSyntaxLinkText"
defaultMessage="Query String syntax"
/>
</EuiLink>
)
}}
/>
</p>
</EuiText>
@ -185,7 +278,10 @@ export class DiscoverNoResults extends Component {
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false} className="dscNoResults">
<EuiCallOut
title="No results match your search criteria"
title={<FormattedMessage
id="kbn.discover.noResults.searchExamples.noResultsMatchSearchCriteriaTitle"
defaultMessage="No results match your search criteria"
/>}
color="warning"
iconType="help"
data-test-subj="discoverNoResults"

View file

@ -18,7 +18,7 @@
*/
import React from 'react';
import { render, mount } from 'enzyme';
import { renderWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
import sinon from 'sinon';
import { findTestSubject } from '@elastic/eui/lib/test';
@ -40,7 +40,7 @@ describe('DiscoverNoResults', () => {
reason: { reason: 'Bad error' },
}];
const component = render(
const component = renderWithIntl(
<DiscoverNoResults
shardFailures={shardFailures}
isTimePickerOpen={false}
@ -55,7 +55,7 @@ describe('DiscoverNoResults', () => {
test(`doesn't render failures list when there are no failures`, () => {
const shardFailures = [];
const component = render(
const component = renderWithIntl(
<DiscoverNoResults
shardFailures={shardFailures}
isTimePickerOpen={false}
@ -70,7 +70,7 @@ describe('DiscoverNoResults', () => {
describe('isTimePickerOpen', () => {
test('false is reflected in the aria-expanded state of the button', () => {
const component = render(
const component = renderWithIntl(
<DiscoverNoResults
timeFieldName="awesome_time_field"
isTimePickerOpen={false}
@ -85,7 +85,7 @@ describe('DiscoverNoResults', () => {
});
test('true is reflected in the aria-expanded state of the button', () => {
const component = render(
const component = renderWithIntl(
<DiscoverNoResults
timeFieldName="awesome_time_field"
isTimePickerOpen={true}
@ -102,7 +102,7 @@ describe('DiscoverNoResults', () => {
describe('timeFieldName', () => {
test('renders time range feedback', () => {
const component = render(
const component = renderWithIntl(
<DiscoverNoResults
timeFieldName="awesome_time_field"
isTimePickerOpen={false}
@ -117,7 +117,7 @@ describe('DiscoverNoResults', () => {
describe('queryLanguage', () => {
test('supports lucene and renders doc link', () => {
const component = render(
const component = renderWithIntl(
<DiscoverNoResults
queryLanguage="lucene"
isTimePickerOpen={false}
@ -133,7 +133,7 @@ describe('DiscoverNoResults', () => {
describe('topNavToggle', () => {
test('is called whe time picker button is clicked', () => {
const topNavToggleSpy = sinon.stub();
const component = mount(
const component = mountWithIntl(
<DiscoverNoResults
timeFieldName="awesome_time_field"
isTimePickerOpen={false}

View file

@ -25,12 +25,21 @@ import {
EuiFlexItem,
EuiSpacer,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export const DiscoverUnsupportedIndexPattern = ({ unsupportedType }) => {
// This message makes the assumption that X-Pack will support this type, as is the case with
// rollup index patterns.
const message = `Index patterns based on ${unsupportedType} indices require the` +
` ${unsupportedType} plugin from X-Pack, which is not installed or disabled`;
const message = (
<FormattedMessage
id="kbn.discover.noResults.requiredPluginIsNotInstalledOrDisabledTitle"
defaultMessage="Index patterns based on {unsupportedType} indices
require the {unsupportedType} plugin from X-Pack, which is not installed or disabled"
values={{
unsupportedType,
}}
/>
);
return (
<Fragment>

View file

@ -10,15 +10,19 @@
<span data-test-subj="discoverCurrentQuery" ng-bind="opts.savedSearch.lastSavedTitle"></span>
<span
id="reload_saved_search"
aria-label="Reload Saved Search"
tooltip="Reload Saved Search"
aria-label="{{::'kbn.discover.reloadSavedSearchAriaLabel' | i18n: {defaultMessage: 'Reload Saved Search'} }}"
tooltip="{{::'kbn.discover.reloadSavedSearchTooltip' | i18n: {defaultMessage: 'Reload Saved Search'} }}"
ng-click="resetQuery()"
kbn-accessible-click
class="kuiIcon fa-undo small"
></span>&nbsp;
</span>
<span data-test-subj="discoverQueryHits" class="kuiLocalBreadcrumb__emphasis">{{(hits || 0) | number:0}}</span>
<ng-pluralize count="hits" when="{'1':'hit', 'other':'hits'}"></ng-pluralize>
<span
i18n-id="kbn.discover.hitsPluralTitle"
i18n-default-message="{hits, plural, one {hit} other {hits}}"
i18n-values="{ hits }"
></span>
</h1>
</div>
@ -87,7 +91,10 @@
class="dscOverlay"
>
<div class="euiTitle" >
<h2>Searching</h2>
<h2
i18n-id="kbn.discover.searchingTitle"
i18n-default-message="Searching"
></h2>
</div>
<div class="euiSpacer euiSpacer--m"></div>
<div ng-show="fetchStatus">{{fetchStatus.complete}}/{{fetchStatus.total}}</div>
@ -102,7 +109,10 @@
>
<span class="kuiButton__inner">
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-chevron-down"></span>
<span>Skip to bottom</span>
<span
i18n-id="kbn.discover.skipToBottomButtonLabel"
i18n-default-message="Skip to bottom"
></span>
</span>
</button>
@ -127,7 +137,13 @@
content="getBucketIntervalToolTipText()"
position="'top'"
></icon-tip>
Scaled to {{ bucketInterval.description }}
<span
i18n-id="kbn.discover.scaledToDescription"
i18n-default-message="Scaled to {bucketIntervalDescription}"
i18n-values="{
bucketIntervalDescription: bucketInterval.description
}"
></span>
</span>
</span>
</div>
@ -163,10 +179,24 @@
<a tabindex="0" id="discoverBottomMarker"></a>
<div ng-if="rows.length == opts.sampleSize" class="dscTable__footer">
These are the first {{opts.sampleSize}} documents matching
your search, refine your search to see others.
<a kbn-accessible-click ng-click="scrollToTop()">Back to top.</a>
<div
ng-if="rows.length == opts.sampleSize"
class="dscTable__footer"
>
<span
i18n-id="kbn.discover.howToSeeOtherMatchingDocumentsDescription"
i18n-default-message="These are the first {sampleSize} documents matching
your search, refine your search to see others. "
i18n-values="{
sampleSize: opts.sampleSize,
}"
></span>
<a
kbn-accessible-click
ng-click="scrollToTop()"
i18n-id="kbn.discover.backToTopLinkText"
i18n-default-message="Back to top."
></a>
</div>
</section>
</div>

View file

@ -25,11 +25,15 @@ import './controllers/discover';
import 'ui/doc_table/components/table_row';
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
FeatureCatalogueRegistryProvider.register(() => {
FeatureCatalogueRegistryProvider.register(i18n => {
return {
id: 'discover',
title: 'Discover',
description: 'Interactively explore your data by querying and filtering raw documents.',
title: i18n('kbn.discover.discoverTitle', {
defaultMessage: 'Discover',
}),
description: i18n('kbn.discover.discoverDescription', {
defaultMessage: 'Interactively explore your data by querying and filtering raw documents.',
}),
icon: 'discoverApp',
path: '/app/kibana#/discover',
showOnHomePage: true,

View file

@ -27,7 +27,7 @@ const module = uiModules.get('discover/saved_searches', [
'kibana/courier'
]);
module.factory('SavedSearch', function (Private) {
module.factory('SavedSearch', function (Private, i18n) {
const SavedObject = Private(SavedObjectProvider);
createLegacyClass(SavedSearch).inherits(SavedObject);
function SavedSearch(id) {
@ -38,7 +38,9 @@ module.factory('SavedSearch', function (Private) {
id: id,
defaults: {
title: 'New Saved Search',
title: i18n('kbn.discover.savedSearch.newSavedSearchTitle', {
defaultMessage: 'New Saved Search',
}),
description: '',
columns: [],
hits: 0,

View file

@ -16,7 +16,11 @@ exports[`render 1`] = `
textTransform="none"
>
<h1>
Open Search
<FormattedMessage
defaultMessage="Open Search"
id="kbn.discover.topNav.openSearchPanel.openSearchTitle"
values={Object {}}
/>
</h1>
</EuiTitle>
<EuiSpacer
@ -32,11 +36,21 @@ exports[`render 1`] = `
onClick={[Function]}
type="button"
>
Manage searches
<FormattedMessage
defaultMessage="Manage searches"
id="kbn.discover.topNav.openSearchPanel.manageSearchesButtonLabel"
values={Object {}}
/>
</EuiButton>
}
makeUrl={[Function]}
noItemsMessage="No matching searches found."
noItemsMessage={
<FormattedMessage
defaultMessage="No matching searches found."
id="kbn.discover.topNav.openSearchPanel.noSearchesFoundDescription"
values={Object {}}
/>
}
onChoose={[Function]}
savedObjectType="search"
/>

View file

@ -21,6 +21,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
import rison from 'rison-node';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiSpacer,
@ -40,7 +41,10 @@ export class OpenSearchPanel extends React.Component {
onClick={this.props.onClose}
href={`#/management/kibana/objects?_a=${rison.encode({ tab: SEARCH_OBJECT_TYPE })}`}
>
Manage searches
<FormattedMessage
id="kbn.discover.topNav.openSearchPanel.manageSearchesButtonLabel"
defaultMessage="Manage searches"
/>
</EuiButton>
);
}
@ -55,13 +59,23 @@ export class OpenSearchPanel extends React.Component {
<EuiFlyoutBody>
<EuiTitle size="s">
<h1>Open Search</h1>
<h1>
<FormattedMessage
id="kbn.discover.topNav.openSearchPanel.openSearchTitle"
defaultMessage="Open Search"
/>
</h1>
</EuiTitle>
<EuiSpacer size="m" />
<SavedObjectFinder
noItemsMessage="No matching searches found."
noItemsMessage={
<FormattedMessage
id="kbn.discover.topNav.openSearchPanel.noSearchesFoundDescription"
defaultMessage="No matching searches found."
/>
}
savedObjectType={SEARCH_OBJECT_TYPE}
makeUrl={this.props.makeUrl}
onChoose={this.props.onClose}

View file

@ -20,6 +20,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { OpenSearchPanel } from './open_search_panel';
import { I18nProvider } from '@kbn/i18n/react';
let isOpen = false;
@ -38,10 +39,12 @@ export function showOpenSearchPanel({ makeUrl }) {
document.body.appendChild(container);
const element = (
<OpenSearchPanel
onClose={onClose}
makeUrl={makeUrl}
/>
<I18nProvider>
<OpenSearchPanel
onClose={onClose}
makeUrl={makeUrl}
/>
</I18nProvider>
);
ReactDOM.render(element, container);
}