Kibana Globalization - Phase 2 (#8766)

* Integrate angular-translate globalization framework with i18n engine
* Provide template for enabling translations in an AngularJS view by translating a view
* Verification tool for translation keys used in angular-translate
* Documentation
This commit is contained in:
Martin Hickey 2017-03-20 18:09:06 +00:00 committed by Matt Bargar
parent d22861f3ec
commit 3aa5938daa
16 changed files with 380 additions and 85 deletions

View file

@ -0,0 +1,187 @@
[[translating-kibana]]
Translating Kibana
------------------
[[background]]
Background
~~~~~~~~~
Please see https://github.com/elastic/kibana/issues/6515[kibana#6515]
for background discussion on the Kibana translation work.
[[prerequisites]]
Prerequisites
~~~~~~~~~~~~
Kibana must be installed and operational, see README.md
[[audience]]
Audience
~~~~~~~
There are three audiences for this document:
* those that want to contribute language packs to Kibana
* those that want to enable translations in existing Kibana plugins
* those that want to create a new Kibana Plugin
[[contributing-language-packs]]
Contributing Language Packs
~~~~~~~~~~~~~~~~~~~~~~~~~~
For this example, we will demonstrate translation into Maltese (Language
code `mt`). Add-on functionality for Kibana is implemented with plug-in modules.
Refer to
https://www.elastic.co/guide/en/kibana/current/kibana-plugins.html[Kibana
Plugins] for more details.
* Fork the `kibana` source, and ensure you have an up to date copy of
the source.
* Ensure you have signed the agreement as in CONTRIBUTING.md
* Choose the right link:[bcp47] language code for your work. In this
example, we will use the `kibana` plugin for translating and `mt` for
Maltese. Other examples might be `zh-Hans` for Chinese using Simplified
characters, or `az-Cyrl` for Azerbaijani using Cyrillic characters. The
following links can help:
* https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes[List of ISO
639-1 codes]
*
http://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code[“Picking
the right language code”]
* Create a new branch for your work:
+
git checkout -b translate-mt
* For each translation scope (see link:#Enabling%20Translations%20on%20Existing%20Plugins[Enabling Translations on Existing Plugins], below), generate a Kibana plugin with name _plugin_-_languagecode_ using the https://github.com/elastic/generator-kibana-plugin[Kibana Plugin Yeoman Generator]:
+
* Replace the the `es.json` translation file with _languagecode_`.json`:
`mv src/plugins/kibana-mt/translations/es.json src/plugins/kibana-mt/translations/mt.json`
* Translate the `mt.json` file in a JSON editor
* Edit index file (`kibana-mt/index.js`), updating the
'translations' item in 'uiExports' as per your plugin translation file(s)
* Copy translations plugin (`kibana-mt/`) to the Kibana plugins (`<kibana_root>/plugins`) directory
* Start up Kibana and verify the translation works as expected
* Ensure Kibana tests pass
* Commit the `kibana-mt` directory and files, and push them to your own
fork of `kibana`
* Open a pull request titled `Translation: Maltese (mt)`
[[enabling-ranslations-on-existing-plugins]]
Enabling Translations on Existing Plugins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Kibana translates according to plugin scope, so there is a `.json` file
in `translations` subdirectory for each plugin.
[[enabling-translation-of-an-angular-view]]
Enabling Translation of an Angular view
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Determine which views share a plugin scope. In this example, `create`
and `edit` will share scope.
* If it does not already exist, Create the appropriate `translation`
directory and the new translation file `en.json` inside it. In the above
example, it would be: `src/core_plugins/kibana/translations/en.json`
* If it does not exist add 'translations' item to the 'uiExports' in the plugin creation (`src/core_plugins/kibana/translations/en.json`) as follows:
-------------------------------------------------------------------------
uiExports {
translations: [
resolve(__dirname, './translations/en.json')
], ….
}
-------------------------------------------------------------------------
* In the view (HTML) file, such as
`src/core_plugins/kibana/public/management/sections/indices/_create.html`
Replace English text with translation keys, and copy the English text
into the `en.json` file appropriately. Note that loose text was put into
a `<p></p>` tag for translation purposes. Also note the prefix `KIBANA-`
matching the plugin being translated.
[[before]]
Before
++++++
`_create.html`
-----------------------------------------------------------------------------------------------------
<h1>Configure an index pattern</h1>
In order to use Kibana you must configure at least one index pattern…
<kbn-info info="This field will be used to filter events with the global time filter"></kbn-info>
-----------------------------------------------------------------------------------------------------
[[after]]
After
+++++
`_create.html`
-------------------------------------------------------------------------------------------
<h1 translate="KIBANA-CONFIGURE_INDEX_PATTERN"</h1>
<p translate="KIBANA-MUST_CONFIGURE_INDEX_PATTERN"</p>
<kbn-info info="{{ 'KIBANA-FIELD_FILTER_EVENTS_GLOBAL_TIME' | translate }}"></kbn-info>
-------------------------------------------------------------------------------------------
* In the view (JS) file, such as
`src/core_plugins/kibana/public/management/sections/indices/_create.js`
As above, replace English text with translation keys, and copy the English text
into the `en.json` file appropriately. Note that some strings may not be user facing
and do not need to be replaced then. Also note the prefix `KIBANA-` matching the plugin
being translated.
[[before]]
Before
++++++
`_create.js`
--------------------------------------------------------------------------------------------------------------
notify.error('Could not locate any indices matching that pattern. Please add the index to Elasticsearch');
--------------------------------------------------------------------------------------------------------------
[[after]]
After
+++++
`_create.js`
-------------------------------------------------------------------------------------------
notify.error($translate.instant('KIBANA-NO_INDICES_MATCHING_PATTERN'));
-------------------------------------------------------------------------------------------
`en.json`
-----------------------------------------------------------------------------------------------------------------------------------------
{
"KIBANA-CONFIGURE_INDEX_PATTERN": "Configure an index pattern",
"KIBANA-MUST_CONFIGURE_INDEX_PATTERN": "In order to use Kibana you must…",
"KIBANA-FIELD_FILTER_EVENTS_GLOBAL_TIME" : "This field will be used to filter events with the global time filter",
"KIBANA-NO_INDICES_MATCHING_PATTERN": "Could not locate any indices matching that pattern. Please add the index to Elasticsearch",
}
-----------------------------------------------------------------------------------------------------------------------------------------
* Refresh the Kibana page and verify the UI looks the same.
* Refer to Kibana core plugin (`src/core_plugins/kibana/`) for examples.
[[new-plugin-authors]]
New Plugin Authors
~~~~~~~~~~~~~~~~~
Add-on functionality for Kibana is implemented with plug-in modules.
Refer to
https://www.elastic.co/guide/en/kibana/current/kibana-plugins.html[Kibana
Plugins] for more details. It is recommended that when creating a plugin
you enable translations (see link:#Enabling%20Translations%20on%20Existing%20Plugins[Enabling Translations on Existing Plugins], above).
[[enabling-translation-in-a-plugin]]
Enabling Translation in a New Plugin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Generate a Kibana plugin using the https://github.com/elastic/generator-kibana-plugin[Kibana Plugin Yeoman Generator]. In this
example, `plugin1`
* Add the translation IDs to the views
* Add the corresponding translation IDs and text to the default translation file (`translations/en.json`)
* Refer to link:#Enabling%20Translations%20on%20Existing%20Plugins[Enabling Translations on Existing Plugins] for more
details on enabling translation in your plugin views and refer to Kibana
core plugin (`src/core_plugins/kibana/`) for an example.

View file

@ -89,6 +89,7 @@
"angular-route": "1.4.7",
"angular-sanitize": "1.5.7",
"angular-sortable-view": "0.0.15",
"angular-translate": "2.13.1",
"ansicolors": "0.3.2",
"autoprefixer": "6.5.4",
"autoprefixer-loader": "2.0.0",
@ -216,6 +217,7 @@
"expect.js": "0.3.1",
"faker": "1.1.0",
"grunt": "1.0.1",
"grunt-angular-translate": "0.3.0",
"grunt-aws-s3": "0.14.5",
"grunt-babel": "6.0.0",
"grunt-cli": "0.1.13",

View file

@ -2,20 +2,16 @@
<kbn-management-indices>
<div ng-controller="managementIndicesCreate" class="kbn-management-indices-create">
<div class="page-header">
<h1>Configure an index pattern</h1>
In order to use Kibana you must configure at least one index pattern. Index patterns are
used to identify the Elasticsearch index to run search and analytics against. They are also
used to configure fields.
<h1 translate="KIBANA-CONFIGURE_INDEX_PATTERN"></h1>
<p translate="KIBANA-MUST_CONFIGURE_INDEX_PATTERN"></p>
</div>
<div>
<form name="form" role="form" class="well" ng-submit="createIndexPattern()">
<div class="form-group">
<label>
Index name or pattern
</label>
<p class="help-block" ng-if="!index.nameIsPattern">Patterns allow you to define dynamic index names using * as a wildcard. Example: logstash-*</p>
<p class="help-block" ng-if="index.isTimeBased && index.nameIsPattern">Patterns allow you to define dynamic index names. Static text in an index name is denoted using brackets. Example: [logstash-]YYYY.MM.DD. Please note that weeks are setup to use ISO weeks which start on Monday. &mdash;
<small><a target="_blank" href="http://momentjs.com/docs/#/displaying/format/">Date Format Documentation</a></small></p>
<label translate="KIBANA-INDEX_NAME_OR_PATTERN"></label>
<p class="help-block" ng-if="!index.nameIsPattern" translate="KIBANA-WILDCARD_DYNAMIC_INDEX_PATTERNS"></p>
<p class="help-block" ng-if="index.isTimeBased && index.nameIsPattern"><span translate="KIBANA-STATIC_TEXT_IN_DYNAMIC_INDEX_PATTERNS"></span> &mdash;
<small><a target="_blank" href="http://momentjs.com/docs/#/displaying/format/" translate="KIBANA-DATE_FORMAT_DOCS"></a></small></p>
<input
ng-model="index.name"
ng-attr-placeholder="{{index.defaultName}}"
@ -27,9 +23,10 @@
type="text"
class="form-control">
<small ng-show="index.nameInterval.name == 'weeks'">
<strong>Note: </strong>
I noticed you're using weekly indices. Kibana requires ISO weeks be used in your index creation.
See <a href="https://en.wikipedia.org/wiki/ISO_week_date" target="_blank" title="Wikipedia: ISO Week Date">Wikipedia: ISO Week Date</a>
<strong translate="KIBANA-NOTE_COLON"></strong>&nbsp;
<span translate="KIBANA-WEEKLY_ISO_NOTICE"></span>
<span translate="KIBANA-SEE"></span>
<a href="https://en.wikipedia.org/wiki/ISO_week_date" target="_blank" title="Wikipedia: ISO Week Date" translate="KIBANA-WIKI_ISO_WEEK_DATE"></a>
</small>
</div>
@ -38,18 +35,18 @@
<input
ng-model="index.isTimeBased"
type="checkbox">
Index contains time-based events
<span translate="KIBANA-CONTAINS_TIME_BASED_EVENTS"></span>
</label>
</div>
<div class="form-group" ng-if="index.isTimeBased">
<label>
Time-field name
<span translate="KIBANA-TIME_FIELD_NAME"></span>
&nbsp;
<kbn-info info="This field will be used to filter events with the global time filter"></kbn-info>
<kbn-info info="{{ 'KIBANA-FIELD_FILTER_EVENTS_GLOBAL_TIME' | translate }}"></kbn-info>
&nbsp;
<small>
<a ng-click="refreshFieldList();"> refresh fields</a>
<a ng-click="refreshFieldList();" translate="KIBANA-REFRESH_FIELDS"></a>
</small>
</label>
<select
@ -65,58 +62,50 @@
<div class="form-group" ng-if="canExpandIndices()">
<label>
<input ng-model="index.notExpandable" type="checkbox">
Do not expand index pattern when searching <small>(Not recommended)</small>
<span translate="KIBANA-NOT_EXPAND_INDEX_PATTERN"></span>
<small translate="KIBANA-NOT_RECOMMENDED"></small>
</label>
<div ng-if="index.notExpandable" class="alert alert-info">
This index pattern will be queried directly rather than being
expanded into more performant searches against individual indices.
<span translate="KIBANA-NOT_EXPANDABLE"></span>
Elasticsearch will receive a query against <em>{{index.name}}</em>
and will have to search through all matching indices regardless
of whether they have data that matches the current time range.
<span translate="KIBANA-ES_QUERY"></span>
<em translate="KIBANA-INDEX_NAME_VAR" translate-values="{ indexName: '{{index.name}}' }"></em>
<span translate="KIBANA-SEARCH_ALL_DATA"></span>
</div>
<p class="help-block">
By default, searches against any time-based index pattern that
contains a wildcard will automatically be expanded to query only
the indices that contain data within the currently selected time
range.
</p>
<p class="help-block" translate="KIBANA-WILDCARD_DEFAULT_EXPANDED_TO_CURRENT_TIME_RANGE"></p>
<p class="help-block">
Searching against the index pattern <em>logstash-*</em> will
actually query elasticsearch for the specific matching indices
(e.g. <em>logstash-2015.12.21</em>) that fall within the current
time range.
<span translate="KIBANA-SEARCH_AGAINST_INDEX_PATTERN"></span>
<em translate="KIBANA-LOGSTASH_WILDCARD"></em>
<span translate="KIBANA-ACTUALLY_QUERY"></span>
<em translate="KIBANA-EXAMPLE_TIME_RANGE"></em>
<span translate="KIBANA-FALL_WITHIN_CURRENT_TIME_RANGE"></span>
</p>
</div>
<div class="form-group time-and-pattern">
<label ng-if="index.isTimeBased">
<input ng-model="index.nameIsPattern" type="checkbox">
Use event times to create index names <small>[DEPRECATED]</small>
<span translate="KIBANA-INDEX_NAME_CREATED_BY_EVENT_TIMES"></span>
<small translate="KIBANA-DEPRECATED"></small>
</label>
</div>
<div class="form-group" ng-if="index.isTimeBased && index.nameIsPattern">
<div class="alert alert-warning">
<h4>Time-interval based index patterns are deprecated!</h4>
<p>
We <strong>strongly recommend</strong> using wildcard pattern names instead of
time-interval based index patterns.
</p>
<h4 translate="KIBANA-ALERT_INDEX_PATTERN_DEPRECATED"></h4>
<p>
Kibana is now smart enough to automatically determine which
indices to search against within the current time range for
wildcard index patterns. This means that wildcard
index patterns now get the same performance optimizations when
searching within a time range as time-interval patterns.
<span translate="KIBANA-WE"></span>
<strong translate="KIBANA-STRONGLY_RECOMMEND"></strong>
<span translate="KIBANA-WILD_CARD_PATTERN"></span>
</p>
</div>
<label>
Index pattern interval&nbsp;
<kbn-info info="The interval at which index names rotate."></kbn-info>
<span translate="KIBANA-INDEX_PATTERN_INTERVAL"></span>&nbsp;
<kbn-info info="{{ 'KIBANA-INTERVAL_INDEX_NAMES_ROTATE' | translate }}"></kbn-info>
</label>
<select
required
@ -132,17 +121,17 @@
</div>
<div class="alert alert-info" ng-if="index.samples">
Attempted to match the following indices and aliases:
<span translate="KIBANA-SAMPLE_ALERT"></span>
<ul>
<li ng-repeat="sample in index.samples">{{sample}}</li>
</ul>
<button type="button" ng-click="moreSamples(true)" class="btn btn-default">
Expand Search
<span translate="KIBANA-EXPAND_SEARCH"></span>
</button>
</div>
<div class="alert alert-{{index.existing.class}}" ng-if="index.existing">
Pattern matches {{index.existing.matchPercent}} of existing indices and aliases
<span translate="KIBANA-EXISTING_MATCH_PERCENT" translate-values="{ indexExistingMatchPercent: '{{index.existing.matchPercent}}' }"></span>
<ul>
<li ng-repeat="match in index.existing.matches | orderBy:'toString()'| limitTo: index.sampleCount">{{match}}</li>
</ul>
@ -151,12 +140,12 @@
ng-click="moreSamples()"
type="button"
class="btn btn-default">
Expand Search
<span translate="KIBANA-EXPAND_SEARCH"></span>
</button>
</div>
<div class="alert alert-info" ng-if="index.existing.failures.length">
Indices and aliases that were found, but did not match the pattern:
<span translate="KIBANA-NON_MATCHING_INDICES_AND_ALIASES"></span>
<ul>
<li ng-repeat="match in index.existing.failures | limitTo: index.sampleCount">{{match}}</li>
</ul>
@ -164,7 +153,7 @@
ng-if="index.sampleCount < index.existing.matches.length"
ng-click="moreSamples()"
class="alert-link">
more
<span translate="KIBANA-MORE"></span>
</a>
</div>
</section>
@ -175,8 +164,9 @@
ng-class="index.fetchFieldsError ? 'btn-default' : 'btn-success'"
type="submit"
class="btn">
<span ng-hide="form.name.$error.indexNameInput">{{index.fetchFieldsError || "Create" }}</span>
<span ng-show="form.name.$error.indexNameInput">Invalid index name pattern.</span>
<span ng-hide="form.name.$error.indexNameInput" ng-if="index.fetchFieldsError">{{index.fetchFieldsError}}</span>
<span ng-hide="form.name.$error.indexNameInput" ng-if="!index.fetchFieldsError" translate="KIBANA-CREATE"></span>
<span ng-show="form.name.$error.indexNameInput" translate="KIBANA-INVALID_INDEX_PATTERN"></span>
</button>
</form>
</div>

View file

@ -14,7 +14,7 @@ uiRoutes
});
uiModules.get('apps/management')
.controller('managementIndicesCreate', function ($scope, kbnUrl, Private, Notifier, indexPatterns, es, config, Promise) {
.controller('managementIndicesCreate', function ($scope, kbnUrl, Private, Notifier, indexPatterns, es, config, Promise, $translate) {
const notify = new Notifier();
const refreshKibanaIndex = Private(RefreshKibanaIndex);
const intervals = indexPatterns.intervals;
@ -29,7 +29,7 @@ uiModules.get('apps/management')
sampleCount: 5,
nameIntervalOptions: intervals,
fetchFieldsError: 'Loading'
fetchFieldsError: $translate.instant('KIBANA-LOADING')
};
index.nameInterval = _.find(index.nameIntervalOptions, { name: 'daily' });
@ -88,7 +88,7 @@ uiModules.get('apps/management')
})
.catch(function (err) {
if (err instanceof IndexPatternMissingIndices) {
notify.error('Could not locate any indices matching that pattern. Please add the index to Elasticsearch');
notify.error($translate.instant('KIBANA-NO_INDICES_MATCHING_PATTERN'));
}
else notify.fatal(err);
});
@ -192,12 +192,12 @@ uiModules.get('apps/management')
return;
}
patternErrors.push('Pattern does not match any existing indices');
patternErrors.push($translate.instant('KIBANA-PATTERN_DOES_NOT_MATCH_EXIST_INDICES'));
const radius = Math.round(index.sampleCount / 2);
const samples = intervals.toIndexList(index.name, index.nameInterval, -radius, radius);
if (_.uniq(samples).length !== samples.length) {
patternErrors.push('Invalid pattern, interval does not create unique index names');
patternErrors.push($translate.instant('KIBANA-INVALID_NON_UNIQUE_INDEX_NAME_CREATED'));
} else {
index.samples = samples;
}
@ -214,12 +214,12 @@ uiModules.get('apps/management')
// we don't have enough info to continue
if (!index.name) {
fetchFieldsError = 'Set an index name first';
fetchFieldsError = $translate.instant('KIBANA-SET_INDEX_NAME_FIRST');
return;
}
if (useIndexList && !index.nameInterval) {
fetchFieldsError = 'Select the interval at which your indices are populated.';
fetchFieldsError = $translate.instant('KIBANA-INTERVAL_INDICES_POPULATED');
return;
}
@ -231,7 +231,7 @@ uiModules.get('apps/management')
.catch(function (err) {
// TODO: we should probably display a message of some kind
if (err instanceof IndexPatternMissingIndices) {
fetchFieldsError = 'Unable to fetch mapping. Do you have indices matching the pattern?';
fetchFieldsError = $translate.instant('KIBANA-INDICES_MATCH_PATTERN');
return [];
}
@ -287,7 +287,7 @@ uiModules.get('apps/management')
index.patternErrors = [];
index.samples = null;
index.existing = null;
index.fetchFieldsError = 'Loading';
index.fetchFieldsError = $translate.instant('KIBANA-LOADING');
}
function getPatternDefault(interval) {

View file

@ -6,9 +6,10 @@
ng-if="editingId"
href="#/management/kibana/index"
class="btn btn-primary btn-xs"
aria-label="Add New">
<span class="sr-only">Add New</span>
<i aria-hidden="true" class="fa fa-plus"></i> Add New
aria-label="{{ 'KIBANA-ADD_NEW' | translate }}">
<span class="sr-only" translate="KIBANA-ADD_NEW"></span>
<i aria-hidden="true" class="fa fa-plus"></i>
<span class="sr-only" translate="KIBANA-ADD_NEW"></span>
</a>
</h5>
</div>
@ -17,7 +18,8 @@
ng-if="!defaultIndex"
class="sidebar-item">
<div class="sidebar-item-title full-title">
<span class="label label-warning">Warning</span> No default index pattern. You must select or create one to continue.
<span class="label label-warning" translate="KIBANA-WARNING"></span>
<p translate="KIBANA-NO_DEFAULT_INDEX_PATTERN"></p>
</div>
</li>
<li

View file

@ -1,4 +1,56 @@
{
"UI-WELCOME_MESSAGE": "Loading Kibana",
"UI-WELCOME_ERROR": "Kibana did not load properly. Check the server output for more information."
"UI-WELCOME_ERROR": "Kibana did not load properly. Check the server output for more information.",
"KIBANA-CONFIGURE_INDEX_PATTERN": "Configure an index pattern",
"KIBANA-MUST_CONFIGURE_INDEX_PATTERN": "In order to use Kibana you must configure at least one index pattern. Index patterns are used to identify the Elasticsearch index to run search and analytics against. They are also used to configure fields.",
"KIBANA-CONTAINS_TIME_BASED_EVENTS": "Index contains time-based events",
"KIBANA-INDEX_NAME_CREATED_BY_EVENT_TIMES": "Use event times to create index names ",
"KIBANA-DEPRECATED": "[DEPRECATED]",
"KIBANA-ALERT_INDEX_PATTERN_DEPRECATED": "Time-interval based index patterns are deprecated!",
"KIBANA-WE": " We",
"KIBANA-STRONGLY_RECOMMEND": " strongly recommend",
"KIBANA-WILD_CARD_PATTERN": " using wildcard pattern names instead of time-interval based index patterns.",
"KIBANA-RECOMMEND_WILD_CARD_PATTERN_DETAILS": "Kibana is now smart enough to automatically determine which indices to search against within the current time range for wildcard index patterns. This means that wildcard index patterns now get the same performance optimizations when searching within a time range as time-interval patterns.",
"KIBANA-INDEX_PATTERN_INTERVAL": "Index pattern interval",
"KIBANA-INDEX_NAME_OR_PATTERN": "Index name or pattern",
"KIBANA-WILDCARD_DYNAMIC_INDEX_PATTERNS": "Patterns allow you to define dynamic index names using * as a wildcard. Example: logstash-*",
"KIBANA-STATIC_TEXT_IN_DYNAMIC_INDEX_PATTERNS": "Patterns allow you to define dynamic index names. Static text in an index name is denoted using brackets. Example: [logstash-]YYYY.MM.DD. Please note that weeks are setup to use ISO weeks which start on Monday.",
"KIBANA-NOTE_COLON": "Note:",
"KIBANA-WEEKLY_ISO_NOTICE": "I noticed you are using weekly indices. Kibana requires ISO weeks be used in your index creation.",
"KIBANA-NOT_EXPAND_INDEX_PATTERN": "Do not expand index pattern when searching ",
"KIBANA-NOT_RECOMMENDED": "(Not recommended)",
"KIBANA-NOT_EXPANDABLE": "This index pattern will be queried directly rather than being expanded into more performant searches against individual indices.",
"KIBANA-ES_QUERY": "Elasticsearch will receive a query against ",
"KIBANA-INDEX_NAME_VAR": "{{indexName}} ",
"KIBANA-SEARCH_ALL_DATA": "and will have to search through all matching indices regardless of whether they have data that matches the current time range.",
"KIBANA-WILDCARD_DEFAULT_EXPANDED_TO_CURRENT_TIME_RANGE": "By default, searches against any time-based index pattern that contains a wildcard will automatically be expanded to query only the indices that contain data within the currently selected time range.",
"KIBANA-SEARCH_AGAINST_INDEX_PATTERN": "Searching against the index pattern ",
"KIBANA-LOGSTASH_WILDCARD": "logstash-*",
"KIBANA-ACTUALLY_QUERY": " will actually query elasticsearch for the specific matching indices (e.g. ",
"KIBANA-EXAMPLE_TIME_RANGE": "logstash-2015.12.21",
"KIBANA-FALL_WITHIN_CURRENT_TIME_RANGE": ") that fall within the current time range.",
"KIBANA-SAMPLE_ALERT": "Attempted to match the following indices and aliases:",
"KIBANA-EXPAND_SEARCH": "Expand Search",
"KIBANA-EXISTING_MATCH_PERCENT": "Pattern matches {{indexExistingMatchPercent}} of existing indices and aliases",
"KIBANA-NON_MATCHING_INDICES_AND_ALIASES": "Indices and aliases that were found, but did not match the pattern:",
"KIBANA-MORE": "more",
"KIBANA-TIME_FIELD_NAME": "Time-field name",
"KIBANA-REFRESH_FIELDS": "refresh fields",
"KIBANA-INVALID_INDEX_PATTERN": "Invalid index name pattern.",
"KIBANA-DATE_FORMAT_DOCS": "Date Format Documentation",
"KIBANA-WIKI_ISO_WEEK_DATE": "Wikipedia: ISO Week Date",
"KIBANA-NO_INDICES_MATCHING_PATTERN": "Could not locate any indices matching that pattern. Please add the index to Elasticsearch",
"KIBANA-PATTERN_DOES_NOT_MATCH_EXIST_INDICES": "Pattern does not match any existing indices",
"KIBANA-INVALID_NON_UNIQUE_INDEX_NAME_CREATED": "Invalid pattern, interval does not create unique index names",
"KIBANA-FIELD_FILTER_EVENTS_GLOBAL_TIME" : "This field will be used to filter events with the global time filter",
"KIBANA-INTERVAL_INDEX_NAMES_ROTATE" : "The interval at which index names rotate.",
"KIBANA-WARNING" : "Warning",
"KIBANA-NO_DEFAULT_INDEX_PATTERN" : "No default index pattern. You must select or create one to continue.",
"KIBANA-LOADING": "Loading",
"KIBANA-SET_INDEX_NAME_FIRST": "Set an index name first",
"KIBANA-INTERVAL_INDICES_POPULATED" : "Select the interval at which your indices are populated.",
"KIBANA-INDICES_MATCH_PATTERN" : "Unable to fetch mapping. Do you have indices matching the pattern?",
"KIBANA-ADD_NEW" : "Add New",
"KIBANA-SEE" : "See",
"KIBANA-CREATE" : "Create"
}

View file

@ -66,6 +66,7 @@ export default async (kbnServer, server, config) => {
async function getKibanaPayload({ app, request, includeUserProvidedConfig }) {
const uiSettings = server.uiSettings();
const translations = await uiI18n.getTranslationsForRequest(request);
return {
app: app,
@ -76,6 +77,7 @@ export default async (kbnServer, server, config) => {
basePath: config.get('server.basePath'),
serverName: config.get('server.name'),
devMode: config.get('env.dev'),
translations: translations,
uiSettings: await props({
defaults: uiSettings.getDefaults(),
user: includeUserProvidedConfig && uiSettings.getUserProvided(request)

View file

@ -0,0 +1,15 @@
module.exports = function (chrome, internals) {
/**
* ui/chrome Translations API
*
* Translations
* Returns the translations which have been loaded by the Kibana server instance
*/
/**
* @return {Object} - Translations
*/
chrome.getTranslations = function () {
return internals.translations || [];
};
};

View file

@ -18,6 +18,7 @@ import controlsApi from './api/controls';
import navApi from './api/nav';
import templateApi from './api/template';
import themeApi from './api/theme';
import translationsApi from './api/translations';
import xsrfApi from './api/xsrf';
const chrome = {};
@ -43,6 +44,7 @@ angularApi(chrome, internals);
controlsApi(chrome, internals);
templateApi(chrome, internals);
themeApi(chrome, internals);
translationsApi(chrome, internals);
chrome.bootstrap = function () {
chrome.setupAngular();

View file

@ -15,6 +15,9 @@ function init() {
$timeoutSpy = sinon.spy($timeout);
debounce = $injector.get('debounce');
// ensure there is a clean slate before testing deferred tasks
$timeout.flush();
});
}

View file

@ -38,11 +38,11 @@ export class UiI18n {
* do with the uiExports defined by each plugin.
*
* This consumer will allow plugins to define export with the
* "language" type like so:
* "translations" type like so:
*
* new kibana.Plugin({
* uiExports: {
* languages: [
* translations: [
* resolve(__dirname, './translations/es.json'),
* ],
* },

View file

@ -6,9 +6,14 @@ import KbnServer from '../../src/server/kbn_server';
import * as i18nVerify from '../utils/i18n_verify_keys';
export default function (grunt) {
grunt.registerTask('_build:verifyTranslations', function () {
grunt.registerTask('_build:verifyTranslations', [
'i18nextract',
'_build:check'
]);
grunt.registerTask('_build:check', function () {
const done = this.async();
const parsePaths = [fromRoot('/src/ui/views/*.jade')];
const serverConfig = {
env: 'production',
@ -35,7 +40,7 @@ export default function (grunt) {
const kbnServer = new KbnServer(serverConfig);
kbnServer.ready()
.then(() => verifyTranslations(kbnServer.uiI18n, parsePaths))
.then(() => verifyTranslations(kbnServer.uiI18n))
.then(() => kbnServer.close())
.then(done)
.catch((err) => {
@ -43,14 +48,32 @@ export default function (grunt) {
.then(() => done(err));
});
});
}
function verifyTranslations(uiI18nObj, parsePaths)
function verifyTranslations(uiI18nObj)
{
return uiI18nObj.getAllTranslations()
.then(function (translations) {
return i18nVerify.getTranslationKeys(parsePaths)
.then(function (translationKeys) {
const angularTranslations = require(fromRoot('build/i18n_extract/en.json'));
const translationKeys = Object.keys(angularTranslations);
const translationPatterns = [
{ regEx: 'i18n\\(\'(.*)\'\\)',
parsePaths: [fromRoot('/src/ui/views/*.jade')] }
];
const keyPromises = _.map(translationPatterns, (pattern) => {
return i18nVerify.getTranslationKeys(pattern.regEx, pattern.parsePaths)
.then(function (keys) {
const arrayLength = keys.length;
for (let i = 0; i < arrayLength; i++) {
translationKeys.push(keys[i]);
}
});
});
return Promise.all(keyPromises)
.then(function () {
return uiI18nObj.getAllTranslations()
.then(function (translations) {
const keysNotTranslatedPerLocale = i18nVerify.getNonTranslatedKeys(translationKeys, translations);
if (!_.isEmpty(keysNotTranslatedPerLocale)) {
const str = JSON.stringify(keysNotTranslatedPerLocale);

View file

@ -0,0 +1,9 @@
export default function (grunt) {
return {
default_options: {
src: ['src/**/*.js', 'src/**/*.html'],
lang: ['en'],
dest: 'build/i18n_extract'
}
};
}

View file

@ -9,15 +9,15 @@ const globProm = Promise.promisify(glob);
/**
* Return all the translation keys found for the file pattern
* @param {String} translationPattern - regEx pattern for translations
* @param {Array<String>} filesPatterns - List of file patterns to be checkd for translation keys
* @param {Array<String>} translations - List of translations keys
* @return {Promise} - A Promise object which will return a String Array of the translation keys
* not translated then the Object will contain all non translated translation keys with value of file the key is from
*/
export function getTranslationKeys(filesPatterns) {
export function getTranslationKeys(translationPattern, filesPatterns) {
return getFilesToVerify(filesPatterns)
.then(function (filesToVerify) {
return getKeys(filesToVerify);
return getKeys(translationPattern, filesToVerify);
});
}
@ -44,7 +44,7 @@ function getFilesToVerify(verifyFilesPatterns) {
return Promise.map(verifyFilesPatterns, (verifyFilesPattern) => {
const baseSearchDir = path.dirname(verifyFilesPattern);
const pattern = path.basename(verifyFilesPattern);
const pattern = path.join('**', path.basename(verifyFilesPattern));
return globProm(pattern, { cwd: baseSearchDir, matchBase: true })
.then(function (files) {
for (const file of files) {
@ -57,9 +57,8 @@ function getFilesToVerify(verifyFilesPatterns) {
});
}
function getKeys(filesToVerify) {
function getKeys(translationPattern, filesToVerify) {
const translationKeys = [];
const translationPattern = 'i18n\\(\'(.*)\'\\)';
const translationRegEx = new RegExp(translationPattern, 'g');
const filePromises = _.map(filesToVerify, (file) => {

View file

@ -1,7 +1,8 @@
require('jquery');
require('node_modules/angular/angular');
require('node_modules/angular-translate');
module.exports = window.angular;
require('node_modules/angular-elastic/elastic');
require('ui/modules').get('kibana', ['monospaced.elastic']);
require('ui/modules').get('kibana', ['monospaced.elastic', 'pascalprecht.translate']);

View file

@ -1,11 +1,19 @@
define(function (require) {
require('angular');
require('ui/angular-bootstrap/index');
const chrome = require('../src/ui/public/chrome/chrome');
return require('ui/modules')
.get('kibana', ['ui.bootstrap'])
.get('kibana', ['ui.bootstrap', 'pascalprecht.translate'])
.config(function ($tooltipProvider) {
$tooltipProvider.setTriggers({ 'mouseenter': 'mouseleave click' });
})
.config(function ($translateProvider) {
$translateProvider.translations('default', chrome.getTranslations());
$translateProvider.preferredLanguage('default');
// Enable escaping of HTML
// issue in https://angular-translate.github.io/docs/#/guide/19_security
$translateProvider.useSanitizeValueStrategy('escape');
});
});