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

Init I18n before Notifications start #23902

Merged
merged 14 commits into from
Oct 19, 2018

Conversation

maryia-lapata
Copy link
Contributor

@maryia-lapata maryia-lapata commented Oct 8, 2018

Issue: Since Notifications are rendered before i18n engine initializing with specified locale, labels in Notifications are not localized with the specified locale, they are displayed in default locale.

Solution: initialize i18n engine before Notifications are rendered.

Fixed: #23903

With this fix there will be no need for wrapping constants with translated labels into a function in client code. Like I'm trying to make translations to work here and here
In addition I found out that these, these and these translations are currently broken without this fix.

@azasypkin azasypkin added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Feature:New Platform labels Oct 8, 2018
const injectedMetadata = this.injectedMetadata.start();
this.i18nService.start({ injectedMetadata });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialization of i18n engine should be before Notifications start.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we don't need src/ui/public/chrome/api/translations.js anymore, can you please remove it (including its usage in src/ui/public/chrome/chrome.js)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I believe usage of i18n.init in src/ui/ui_render/bootstrap/app_bootstrap.js (and hence usage of await server.getUiTranslations() in src/ui/ui_render/ui_render_mixin.js) isn't not needed anymore as well. Can you please double check and remove everything redundant where possible? With this we'll have just 2 places where we init i18n - one for server and one for client.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@azasypkin sure.
Done.

injectedMetadata: InjectedMetadataStartContract;
}
export class I18nService {
public start({ injectedMetadata }: Deps) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method isn't actually a start method, but I named as start to be consistent with other services.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe at some point we'll do more than just i18n.init, so I'm +1 for consistency.

@@ -28,7 +27,4 @@ import {
uiModules.get('i18n')
.provider('i18n', I18nProvider)
.filter('i18n', i18nFilter)
.directive('i18nId', i18nDirective)
.config((i18nProvider) => {
i18nProvider.init(metadata.translations);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed initialization of i18n from here, because it will be already initialized at very beginning of loading application (https://github.com/elastic/kibana/pull/23902/files#diff-ac3204e754680bbed67c8193da8cc11bR103)

Copy link
Member

@azasypkin azasypkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just a bunch of nits here and there.

@spalger could you please let us know if this PR looks good to you (it touches CoreSystem)?

@@ -22,3 +22,4 @@ import * as loader from './loader';

export const i18n = i18nCore;
export const i18nLoader = loader;
export { PlainMessages } from './messages';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: We'll eventually rename PlainMessages to something that is clearer in one of the upcoming PRs (I believe there is one is ready for review already).

src/core/public/i18n/i18n_service.ts Outdated Show resolved Hide resolved
injectedMetadata: InjectedMetadataStartContract;
}
export class I18nService {
public start({ injectedMetadata }: Deps) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe at some point we'll do more than just i18n.init, so I'm +1 for consistency.

* specific language governing permissions and limitations
* under the License.
*/
const mockI18n = jest.fn();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: same nit about new lines here as well, + new line before beforeEach, it etc.

* specific language governing permissions and limitations
* under the License.
*/
const mockI18n = jest.fn();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe mockI18nInit to make it clearer what we actually mock.

const i18nService = new I18nService();
expect(mockI18n).not.toHaveBeenCalled();
i18nService.start({ injectedMetadata });
expect(mockI18n.mock.calls).toMatchSnapshot();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: for such small snapshots I'd better use toMatchInlineSnapshot or even better something like this:

 expect(mockI18nInit).toHaveBeenCalledTimes(1);
 expect(mockI18nInit).toHaveBeenCalledWith(translations);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, done.

const injectedMetadata = this.injectedMetadata.start();
this.i18nService.start({ injectedMetadata });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we don't need src/ui/public/chrome/api/translations.js anymore, can you please remove it (including its usage in src/ui/public/chrome/chrome.js)?

src/core/public/i18n/index.ts Outdated Show resolved Hide resolved
const injectedMetadata = this.injectedMetadata.start();
this.i18nService.start({ injectedMetadata });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I believe usage of i18n.init in src/ui/ui_render/bootstrap/app_bootstrap.js (and hence usage of await server.getUiTranslations() in src/ui/ui_render/ui_render_mixin.js) isn't not needed anymore as well. Can you please double check and remove everything redundant where possible? With this we'll have just 2 places where we init i18n - one for server and one for client.

@elastic elastic deleted a comment from elasticmachine Oct 16, 2018
@elastic elastic deleted a comment from elasticmachine Oct 16, 2018
@elastic elastic deleted a comment from elasticmachine Oct 16, 2018
@elastic elastic deleted a comment from elasticmachine Oct 17, 2018
@elastic elastic deleted a comment from elasticmachine Oct 17, 2018
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elastic elastic deleted a comment from elasticmachine Oct 17, 2018

export class I18nService {
public start({ injectedMetadata }: Deps) {
i18n.init(injectedMetadata.getLegacyMetadata().translations);
Copy link
Contributor

@spalger spalger Oct 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a while to track this down and figure out how everything was taking advantage of the translations without consuming the new platform at all, turns out it's because we are just using new platform constructs to store the translations in @kbn/i18n module. Based on this I don't think the I18nService is necessary, and I don't really think this does what we want for the new platform.

I'm down to move this to the new platform, but if we are going to do that then we should be instantiating something that will wrap the translations, and exposing that on the I18nService start contract, not by caching the state in @kbn/i18n.

In order to address https://github.com/elastic/kibana/pull/23603/files#r225105739 all we would really need to do is move the call to i18n.init() out of an angular .config() function, and we can just do that by adding it to src/ui/public/i18n/index.js for now until we want to move @kbn/i18n fully to the new platform:

diff --git a/src/ui/public/chrome/chrome.js b/src/ui/public/chrome/chrome.js
index cfae977feb..fe0cc3011f 100644
--- a/src/ui/public/chrome/chrome.js
+++ b/src/ui/public/chrome/chrome.js
@@ -21,6 +21,7 @@ import _ from 'lodash';
 import angular from 'angular';
 
 import { metadata } from '../metadata';
+import '../i18n';
 import '../state_management/global_state';
 import '../config';
 import '../notify';
diff --git a/src/ui/public/i18n/index.js b/src/ui/public/i18n/index.js
index 5def86410e..1f4b435f61 100644
--- a/src/ui/public/i18n/index.js
+++ b/src/ui/public/i18n/index.js
@@ -18,12 +18,16 @@
  */
 
 import { uiModules } from 'ui/modules';
+import { metadata } from 'ui/metadata';
+import { i18n } from '@kbn/i18n';
 import {
   I18nProvider,
   i18nFilter,
   i18nDirective,
 } from '@kbn/i18n/angular';
 
+i18n.init(metadata.translations);
+
 uiModules.get('i18n')
   .provider('i18n', I18nProvider)
   .filter('i18n', i18nFilter)

(or something like that)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@spalger The main reason for this PR is that Notifications template is rendered earlier than i18n is initialized with specified locale.
It's important because to make labels, passing in Notifications, translatable we should wrap Notifications template by I18nProvider https://github.com/elastic/kibana/pull/22757/files#diff-53c9b70520405921f846a26c104aeb23R40 which is used to setup the i18n context for a tree. And in the case when we setup the locale in src/ui/public/i18n/index.js, the i18n context for Notifications will be defined with a default locale not counting the data specified in src/ui/public/i18n/index.js.

Here the order of files loading:

image

That's why I placed i18n initialization just before Notifications template is rendered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I didn't see that we were going to start consuming <I18nProvider /> from the new platform. In that case, can we just move the init() call to the entry file so it's initialized before any other code is run? I just don't want to have an i18n service in the new platform unless it's useful, and I don't think this version is really there.

diff --git a/src/ui/ui_bundles/app_entry_template.js b/src/ui/ui_bundles/app_entry_template.js
index 764e70eaa1..a74ef61848 100644
--- a/src/ui/ui_bundles/app_entry_template.js
+++ b/src/ui/ui_bundles/app_entry_template.js
@@ -33,10 +33,15 @@ import 'whatwg-fetch';
 import 'abortcontroller-polyfill';
 import 'childnode-remove-polyfill';
 
+import { i18n } from '@kbn/i18n';
 import { CoreSystem } from '__kibanaCore__'
 
+const injectedMetadata = JSON.parse(document.querySelector('kbn-injected-metadata').getAttribute('data'));
+i18n.init(injectedMetadata.legacyMetadata.translations);
+
 new CoreSystem({
-  injectedMetadata: JSON.parse(document.querySelector('kbn-injected-metadata').getAttribute('data')),
+  injectedMetadata: injectedMetadata,
   rootDomElement: document.body,
   requireLegacyFiles: () => {
     ${bundle.getRequires().join('\n  ')}

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Contributor

@spalger spalger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I don't have any translations to test, but the logic looks good!

@elastic elastic deleted a comment from elasticmachine Oct 19, 2018
@maryia-lapata
Copy link
Contributor Author

@spalger I've checked this code with translations.

@maryia-lapata maryia-lapata merged commit 4b9e13c into elastic:master Oct 19, 2018
@maryia-lapata maryia-lapata deleted the fix-i18n-init branch October 19, 2018 14:59
maryia-lapata added a commit to maryia-lapata/kibana that referenced this pull request Oct 19, 2018
* Init I18n before Notifications start

* Remove i18n initialization in AngularJS since it will be already initialized in CoreSystem

* Remove unused translationsApi

* Remove redundant i18n.init invocation

* Remove i18n tests since  i18n.init was removed and input params for AppBootstrap were changed

* Move i18n initialization to the entry file.
maryia-lapata added a commit that referenced this pull request Oct 19, 2018
* Init I18n before Notifications start

* Remove i18n initialization in AngularJS since it will be already initialized in CoreSystem

* Remove unused translationsApi

* Remove redundant i18n.init invocation

* Remove i18n tests since  i18n.init was removed and input params for AppBootstrap were changed

* Move i18n initialization to the entry file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backported Feature:New Platform Project:i18n Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix i18n engine initialization order
4 participants