Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[I18n] Add one-time binding to angularjs i18n #23499

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions packages/kbn-i18n/GUIDELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The following types are supported:
- ToggleSwitch
- LinkLabel and etc.

There is one more complex case, when we have to divide a single expression into different labels.
There is one more complex case, when we have to divide a single expression into different labels.

For example the message before translation looks like:

Expand Down Expand Up @@ -221,8 +221,8 @@ For example:

```js
<button
aria-label="{{'kbn.management.editIndexPattern.removeAriaLabel' | i18n: {defaultMessage: 'Remove index pattern'} }}"
tooltip="{{'kbn.management.editIndexPattern.removeTooltip' | i18n: {defaultMessage: 'Remove index pattern'} }}"
aria-label="{{ ::'kbn.management.editIndexPattern.removeAriaLabel' | i18n: {defaultMessage: 'Remove index pattern'} }}"
tooltip="{{ ::'kbn.management.editIndexPattern.removeTooltip' | i18n: {defaultMessage: 'Remove index pattern'} }}"
>
</button>
```
Expand Down Expand Up @@ -333,4 +333,3 @@ it('should render normally', async () => {
});
// ...
```

4 changes: 2 additions & 2 deletions packages/kbn-i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ import { i18n } from '@kbn/i18n';
i18n.init(messages);
```

One common use-case is that of internationalizing a string constant. Here's an
One common use-case is that of internationalizing a string constant. Here's an
example of how we'd do that:

```js
Expand Down Expand Up @@ -399,7 +399,7 @@ In order to translate attributes in Angular we should use `i18nFilter`:
```html
<input
type="text"
placeholder="{{'KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER' | i18n: {
placeholder="{{ ::'KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER' | i18n: {
defaultMessage: 'Search'
} }}"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`i18nDirective inserts correct translation html content with values 1`] = `"default-message word"`;

exports[`i18nDirective inserts correct translation html content with values 2`] = `"default-message anotherWord"`;
13 changes: 9 additions & 4 deletions packages/kbn-i18n/src/angular/directive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ angular

describe('i18nDirective', () => {
let compile: angular.ICompileService;
let scope: angular.IRootScopeService;
let scope: angular.IRootScopeService & { word?: string };

beforeEach(angular.mock.module('app'));
beforeEach(
angular.mock.inject(
($compile: angular.ICompileService, $rootScope: angular.IRootScopeService) => {
compile = $compile;
scope = $rootScope.$new();
scope.word = 'word';
}
)
);
Expand All @@ -62,19 +63,23 @@ describe('i18nDirective', () => {
test('inserts correct translation html content with values', () => {
const id = 'id';
const defaultMessage = 'default-message {word}';
const compiledContent = 'default-message word';

const element = angular.element(
`<div
i18n-id="${id}"
i18n-default-message="${defaultMessage}"
i18n-values="{ word: 'word' }"
i18n-values="{ word }"
/>`
);

compile(element)(scope);
scope.$digest();

expect(element.html()).toEqual(compiledContent);
expect(element.html()).toMatchSnapshot();

scope.word = 'anotherWord';
scope.$digest();

expect(element.html()).toMatchSnapshot();
});
});
39 changes: 25 additions & 14 deletions packages/kbn-i18n/src/angular/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,37 @@ import { IDirective, IRootElementService, IScope } from 'angular';

import { I18nServiceType } from './provider';

export function i18nDirective(i18n: I18nServiceType): IDirective {
interface I18nScope extends IScope {
values: any;
LeanidShutau marked this conversation as resolved.
Show resolved Hide resolved
defaultMessage: string;
id: string;
}

export function i18nDirective(i18n: I18nServiceType): IDirective<I18nScope> {
return {
restrict: 'A',
scope: {
id: '@i18nId',
defaultMessage: '@i18nDefaultMessage',
values: '=i18nValues',
values: '<?i18nValues',
},
link($scope: IScope, $element: IRootElementService) {
$scope.$watchGroup(
['id', 'defaultMessage', 'values'],
([id, defaultMessage = '', values = {}]) => {
$element.html(
i18n(id, {
values,
defaultMessage,
})
);
}
);
link($scope, $element) {
LeanidShutau marked this conversation as resolved.
Show resolved Hide resolved
if ($scope.values) {
$scope.$watchCollection('values', () => {
setHtmlContent($element, $scope, i18n);
});
} else {
setHtmlContent($element, $scope, i18n);
}
},
};
}

function setHtmlContent($element: IRootElementService, $scope: I18nScope, i18n: I18nServiceType) {
$element.html(
i18n($scope.id, {
values: $scope.values,
defaultMessage: $scope.defaultMessage,
})
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
data-test-subj="editIndexPattern"
class="kuiViewContent"
role="region"
aria-label="{{'kbn.management.editIndexPattern.detailsAria' | i18n: { defaultMessage: 'Index pattern details' } }}"
aria-label="{{::'kbn.management.editIndexPattern.detailsAria' | i18n: { defaultMessage: 'Index pattern details' } }}"
>
<!-- Header -->
<kbn-management-index-header
Expand Down Expand Up @@ -103,9 +103,9 @@
<input
class="kuiSearchInput__input"
type="text"
aria-label="{{'kbn.management.editIndexPattern.fields.filterAria' | i18n: {defaultMessage: 'Filter'} }}"
aria-label="{{::'kbn.management.editIndexPattern.fields.filterAria' | i18n: {defaultMessage: 'Filter'} }}"
ng-model="fieldFilter"
placeholder="{{'kbn.management.editIndexPattern.fields.filterPlaceholder' | i18n: {defaultMessage: 'Filter'} }}"
placeholder="{{::'kbn.management.editIndexPattern.fields.filterPlaceholder' | i18n: {defaultMessage: 'Filter'} }}"
data-test-subj="indexPatternFieldFilter"
>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<a
data-test-subj="indexPatternFieldEditButton"
ng-href="{{ kbnUrl.getRouteHref(field, 'edit') }}"
aria-label="{{'kbn.management.editIndexPattern.editFieldButton' | i18n: { defaultMessage: 'Edit' } }}"
aria-label="{{::'kbn.management.editIndexPattern.editFieldButton' | i18n: { defaultMessage: 'Edit' } }}"
class="kuiButton kuiButton--basic kuiButton--small"
>
<span aria-hidden="true" class="kuiIcon fa-pencil"></span>
Expand All @@ -12,7 +12,7 @@
ng-if="field.scripted"
ng-click="remove(field)"
class="kuiButton kuiButton--danger kuiButton--small"
aria-label="{{'kbn.management.editIndexPattern.deleteFieldButton' | i18n: { defaultMessage: 'Delete' } }}"
aria-label="{{::'kbn.management.editIndexPattern.deleteFieldButton' | i18n: { defaultMessage: 'Delete' } }}"
>
<span aria-hidden="true" class="kuiIcon fa-trash"></span>
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
<button
ng-if="setDefault"
ng-click="setDefault()"
aria-label="{{'kbn.management.editIndexPattern.setDefaultAria' | i18n: { defaultMessage: 'Set as default index' } }}"
tooltip="{{'kbn.management.editIndexPattern.setDefaultTooltip' | i18n: { defaultMessage: 'Set as default index' } }}"
aria-label="{{::'kbn.management.editIndexPattern.setDefaultAria' | i18n: { defaultMessage: 'Set as default index' } }}"
tooltip="{{::'kbn.management.editIndexPattern.setDefaultTooltip' | i18n: { defaultMessage: 'Set as default index' } }}"
class="kuiButton kuiButton--basic"
data-test-subj="setDefaultIndexPatternButton"
>
Expand All @@ -32,8 +32,8 @@
<button
ng-if="refreshFields"
ng-click="refreshFields()"
aria-label="{{'kbn.management.editIndexPattern.refreshAria' | i18n: { defaultMessage: 'Reload field list' } }}"
tooltip="{{'kbn.management.editIndexPattern.refreshTooltip' | i18n: { defaultMessage: 'Refresh field list' } }}"
aria-label="{{::'kbn.management.editIndexPattern.refreshAria' | i18n: { defaultMessage: 'Reload field list' } }}"
tooltip="{{::'kbn.management.editIndexPattern.refreshTooltip' | i18n: { defaultMessage: 'Refresh field list' } }}"
class="kuiButton kuiButton--basic"
>
<span
Expand All @@ -45,8 +45,8 @@
<button
ng-if="delete"
ng-click="delete()"
aria-label="{{'kbn.management.editIndexPattern.removeAria' | i18n: { defaultMessage: 'Remove index pattern' } }}"
tooltip="{{'kbn.management.editIndexPattern.removeTooltip' | i18n: { defaultMessage: 'Remove index pattern' } }}"
aria-label="{{::'kbn.management.editIndexPattern.removeAria' | i18n: { defaultMessage: 'Remove index pattern' } }}"
tooltip="{{::'kbn.management.editIndexPattern.removeTooltip' | i18n: { defaultMessage: 'Remove index pattern' } }}"
class="kuiButton kuiButton--danger"
data-test-subj="deleteIndexPatternButton"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="euiPage">
<div class="col-md-2 sidebar-container" role="region" aria-label="{{'kbn.management.editIndexPatternAria' | i18n: { defaultMessage: 'Index patterns' } }}">
<div class="col-md-2 sidebar-container" role="region" aria-label="{{::'kbn.management.editIndexPatternAria' | i18n: { defaultMessage: 'Index patterns' } }}">
<div class="sidebar-list">
<div class="sidebar-item-title full-title">
<h5 data-test-subj="createIndexPatternParent">
Expand Down Expand Up @@ -55,4 +55,4 @@ <h5 data-test-subj="createIndexPatternParent">
<div ng-transclude></div>
</div>
</div>
</div>
</div>
6 changes: 3 additions & 3 deletions src/core_plugins/metric_vis/public/metric_vis_params.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
kbn-accessible-click
aria-expanded="{{!!showColorRange}}"
aria-controls="metricOptionsRanges"
aria-label="{{'metricVis.params.ranges.toggleOptionsAriaLabel' | i18n: { defaultMessage: 'Toggle range options' } }}"
aria-label="{{::'metricVis.params.ranges.toggleOptionsAriaLabel' | i18n: { defaultMessage: 'Toggle range options' } }}"
class="kuiSideBarCollapsibleTitle__label"
ng-click="showColorRange = !showColorRange"
>
Expand Down Expand Up @@ -135,7 +135,7 @@
kbn-accessible-click
aria-expanded="{{!!showColorOptions}}"
aria-controls="metricOptionsColors"
aria-label="{{'metricVis.params.color.toggleOptionsAriaLabel' | i18n: { defaultMessage: 'Toggle color options' } }}"
aria-label="{{::'metricVis.params.color.toggleOptionsAriaLabel' | i18n: { defaultMessage: 'Toggle color options' } }}"
class="kuiSideBarCollapsibleTitle__label"
ng-click="showColorOptions = !showColorOptions"
>
Expand Down Expand Up @@ -219,7 +219,7 @@
kbn-accessible-click
aria-expanded="{{!!showStyle}}"
aria-controls="metricOptionsStyle"
aria-label="{{'metricVis.params.style.toggleOptionsAriaLabel' | i18n: { defaultMessage: 'Toggle style options' } }}"
aria-label="{{::'metricVis.params.style.toggleOptionsAriaLabel' | i18n: { defaultMessage: 'Toggle style options' } }}"
class="kuiSideBarCollapsibleTitle__label"
ng-click="showStyle = !showStyle"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<p>{{ 'plugin_2.message-id' | i18n: { defaultMessage: 'Message text' } }}</p>
<p>{{ ::'plugin_2.message-id' | i18n: { defaultMessage: 'Message text' } }}</p>
12 changes: 12 additions & 0 deletions src/dev/i18n/extractors/__snapshots__/html.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ Array [
]
`;

exports[`dev/i18n/extractors/html extracts default messages from HTML with one-time binding 1`] = `
Array [
Array [
"kbn.id",
Object {
"context": undefined,
"message": "Message text with {value}",
},
],
]
`;

exports[`dev/i18n/extractors/html throws on empty i18n-id 1`] = `"Empty \\"i18n-id\\" value in angular directive is not allowed."`;

exports[`dev/i18n/extractors/html throws on missing i18n-default-message attribute 1`] = `"Empty defaultMessage in angular directive is not allowed (\\"message-id\\")."`;
33 changes: 32 additions & 1 deletion src/dev/i18n/extractors/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,45 @@ function trimCurlyBraces(string) {
return string.slice(2, -2).trim();
}

/**
* Removes parentheses from the start and the end of a string
LeanidShutau marked this conversation as resolved.
Show resolved Hide resolved
*
* Example: `('id' | i18n: { defaultMessage: 'Message' })`
* @param {string} string string to trim
*/
function trimParentheses(string) {
LeanidShutau marked this conversation as resolved.
Show resolved Hide resolved
if (string.startsWith('(') && string.endsWith(')')) {
return string.slice(1, -1);
}

return string;
}

/**
* Removes one-time binding operator `::` from the start of a string.
*
* Example: `::'id' | i18n: { defaultMessage: 'Message' }`
* @param {string} string string to trim
*/
function trimOneTimeBindingOperator(string) {
LeanidShutau marked this conversation as resolved.
Show resolved Hide resolved
if (string.startsWith('::')) {
return string.slice(2);
}

return string;
}

function* getFilterMessages(htmlContent) {
const expressions = (htmlContent.match(ANGULAR_EXPRESSION_REGEX) || [])
.filter(expression => expression.includes(I18N_FILTER_MARKER))
.map(trimCurlyBraces);

for (const expression of expressions) {
const filterStart = expression.indexOf(I18N_FILTER_MARKER);
const idExpression = expression.slice(0, filterStart).trim();
const idExpression = trimParentheses(
LeanidShutau marked this conversation as resolved.
Show resolved Hide resolved
trimOneTimeBindingOperator(expression.slice(0, filterStart).trim())
);

const filterObjectExpression = expression.slice(filterStart + I18N_FILTER_MARKER.length).trim();

if (!filterObjectExpression || !idExpression) {
Expand Down
11 changes: 11 additions & 0 deletions src/dev/i18n/extractors/html.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ describe('dev/i18n/extractors/html', () => {
expect(actual.sort()).toMatchSnapshot();
});

test('extracts default messages from HTML with one-time binding', () => {
const actual = Array.from(
extractHtmlMessages(`
<div>
{{::'kbn.id' | i18n: { defaultMessage: 'Message text with {value}', values: { value: 'value' } }}}
</div>
`)
);
expect(actual.sort()).toMatchSnapshot();
});

test('throws on empty i18n-id', () => {
const source = Buffer.from(`\
<p
Expand Down