-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Add draft implementation of I18n engine #19555
Conversation
💔 Build Failed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't run Kibana with this PR yet, but went through code and left several comments that I'd like to discuss/handle first.
Also it looks like a good moment to start adding Jest unit tests for the new code.
src/ui/ui_i18n/README.md
Outdated
@@ -107,6 +109,7 @@ when missing translations | |||
- `getDefaultLocale()` - returns the default locale | |||
- `defineFormats(formats: object)` - supplies a set of options to the underlying formatter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: now when we have getFormats
it would probably make sense to unify naming and rename defineFormats
into setFormats
. Same for ##Angular section below.
src/ui/ui_render/ui_render_mixin.js
Outdated
@@ -148,7 +150,7 @@ export function uiRenderMixin(kbnServer, server, config) { | |||
injectedVarsOverrides | |||
}), | |||
bundlePath: `${config.get('server.basePath')}/bundles`, | |||
i18n: key => get(translations, key, ''), | |||
i18n: i18n.translate.bind(i18n), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's use arrow function here instead, it will allow us to quickly see what the translate
signature is.
packages/kbn-i18n/package.json
Outdated
"license": "Apache-2.0", | ||
"private": true, | ||
"dependencies": { | ||
"intl-format-cache": "2.1.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's use ^
-versions for all dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really sure that's a nit? I would say it's important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yeah, it's, I need some new keyword for important-but-trivial-change:
:)
* under the License. | ||
*/ | ||
|
||
import { uiModules } from 'ui/modules'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: hmm, if we make knb-i18n
a separate package I'd not expect seeing Kibana specific dependencies here at all. I believe we should either create our own Angular i18n module that will include directive/filter/provider that will be registered with other kibana angular modules or export filter/directive and provider like that and register them manually in KIbana:
// src/angular/filter.js
export function filter(i18n) {
return function(id, { defaultMessage = '', values = {} } = {}) {
......
};
// src/angular/directive.js
export function directive(i18n) {
return {
restrict: 'A',
....
};
};
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added src/ui/public/i18n module that registers directive/filter/provider from knb-i18n
package.
@@ -0,0 +1,60 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: technically it's provider
, not angular service
(there is typo in file name btw).
packages/kbn-i18n/src/core.js
Outdated
|
||
const hasValues = values => Object.keys(values).length > 0; | ||
|
||
const addLocaleData = localeData => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please add comment with explanation what this code does exactly and why.
packages/kbn-i18n/src/core.js
Outdated
|
||
const message = getMessageById(this.getMessages(), id); | ||
|
||
if (!hasValues(values) && process.env.NODE_ENV === 'production') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: why do we need process.env.NODE_ENV === 'production'
here? Shouldn't it fail for angular and react?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's needed to avoid expensive message formatting for simple messages without values. In dev environment messages will always be formatted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't really get what you mean. Maybe small example?
packages/kbn-i18n/src/core.js
Outdated
|
||
const EN_LOCALE = 'en'; | ||
|
||
const getMessageById = (messages, id) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it'd be helpful to have comment with explanation and example
packages/kbn-i18n/src/core.js
Outdated
|
||
formattedMessage = msg.format(values); | ||
} catch (e) { | ||
showError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: I'm wondering if we shouldn't swallow error when we had message for the specified locale, but failed to format it. Looks like it's a way too tolerant to developer/translator mistakes. What do you think?
packages/kbn-i18n/src/core.js
Outdated
this.getFormats() | ||
); | ||
|
||
formattedMessage = msg.format(values); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: can't we just return here? Or there are cases when format
doesn't throw and return falsy value?
💔 Build Failed |
💔 Build Failed |
packages/kbn-i18n/src/index.js
Outdated
export * from './react_i18n'; | ||
|
||
export { default as I18n } from './core'; | ||
export { default as i18n } from './i18n'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file has some issues.
- I don't see the point of the file
src/i18n.js
as it currently stands. I agree with Aleh about renamingsrc/core.js
tosrc/i18n.js
, and then we can export theI18n
class. - what is the point of the unused import on line 20?
- looking at how the docs read, people are only going to use the
I18n
class? What is the point of thei18n
class? is it just to provide a default instance of the class? Is that used anywhere in this library, or could it just be created by consumers of the library?
I would like to hear the intention behind this current approach, if you feel my comments are in error!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see the i18n
instance is what's used to create the angular and react providers. Let me give my last point (above) a little more thought.
packages/kbn-i18n/src/core.js
Outdated
return this.defaultLocale; | ||
} | ||
|
||
defineFormats(formats) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setFormats
?
💔 Build Failed |
packages/kbn-i18n/README.md
Outdated
|
||
```js | ||
import i18n from 'kbn-i18n'; | ||
import { I18n } from '@kbn/i18n/src/i18n'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can actually say import I18n from @kbn/i18n;
because inside of packages/kbn-i18n/package.json
you have a line "main": "src/index.js"
.
src/ui/ui_render/ui_render_mixin.js
Outdated
import { props, reduce as reduceAsync } from 'bluebird'; | ||
import Boom from 'boom'; | ||
import { resolve } from 'path'; | ||
import { I18n } from '@kbn/i18n/src/i18n'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { I18n } from '@kbn/i18n';
import { i18n } from '../i18n'; | ||
|
||
export function i18nProvider() { | ||
this.addMessages = function(messages, locale) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[redacted]
I got some clarity from Aleh about this. We always want the inner i18n context for this
. Hm. I'm still thinking if there's another way. That's a bit like an antipattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I have some thoughts now that I understand what we're doing.
Why I think there is something weird/antipattern/code smell:
- Normally when we use JavaScript
class
syntax, if the class's methods usethis
internally, we design it that way so that any instance of thatclass
can pass along its ownthis
context, and the methods use that context. However in this case, we always want to use thei18n
instance'sthis
. - The angular provider (
i18nProvider
) is not an instance or a class, yet it is setting its ownthis.[properties]
. That's a strange thing. Is it because of how angular expects the provider? Forgive me I don't remember exactly how angular 1.x works and maybe this is just something we have to do. - Additionally, the capital-case
I18n
is used as a singleton (asi18n
), so thethis
is always going to be the same, right? - And also, arrow functions are normally good for solving issues of the wrong
this
context, but using them here would be absolutely wrong. It would pass along the wrongthis
. That seems to me like we're creating unnecessary indirection withini18n
. - It’s kind of weird to have to do
i18n.translate.bind(i18n)
because it seems like calling translate on thei18n
should already provide the rightthis
context. We’re calling it on the i18n object, after all.
All this points to how I18n
is designed. Would it work to forgo the class, make all the methods no longer call this.[whatever]
, and make I18n a function that accepts messages/locale and return an object that do its methods on that? It would follow that the methods of I18n
would not have to explicitly call this.[whatever]
because there's no need to take the this
context--it's always the same context. And thus the angular provider would be something more like a simple wrapper around I18n
. Basically, if you want to talk about this in the classical way, this would become a fully static class. Does that make sense, and would it make sense for this library?
Something like this?
export function I18n(messages, ...) {
const _messages = // set it
const _currentLocale = // set it
return {
getMessages: function() {
return _messages[_currentLocale];
},
// ...
};
}
There would be less indirection, less potential confusion around this
because it isn't actually being used as intended, and I think the angular provider becomes simpler, too. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed I18n class and made i18n a singleton because I don't see real use cases for multiple I18n instances. So we no longer use this
keyword in i18n engine.
*/ | ||
|
||
import expect from 'expect.js'; | ||
import _ from 'lodash'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe just import { isEqual } from 'lodash';
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm wondering why built-in Jest/Mocha/Expect matchers didn't do the job and we resorted to Lodash
?
💚 Build Succeeded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, with a request for unit tests within the kbn-i18n lib. I'm assuming that's meant to be part of this work, but please comment if it's not.
packages/kbn-i18n/src/core/i18n.js
Outdated
import { formats as EN_FORMATS } from './formats'; | ||
|
||
// Add all locale data to `IntlMessageFormat`. | ||
// TODO: Use dynamic import for asynchronous loading of specific locale data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before merging this in, I think we should either resolve the TODO comments or else create an issue and drop the link here, so we don't lose track.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for issue
@@ -17,11 +17,13 @@ | |||
* under the License. | |||
*/ | |||
|
|||
// TODO: Integrate a new tool for translations checking |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before merging this in, I think we should either resolve the TODO comments or else create an issue and drop the link here, so we don't lose track.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM provided we do something about the TODO comments (left notes for those).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM once you merge master in, resolve existing minor conflicts and make CI happy.
💚 Build Succeeded |
I found a small issue related to |
Can you please file a separate issue (and link with this PR) where we can discuss/investigate alternatives and their impact on distributable size and the way Kibana is run? |
* Add draft implementation of I18n engine * Add i18n loader * kbn-i18n refactoring * Fix react i18n context and update doc * i18n engine refactoring * Fix locales data loading and add more jsdoc comments * Fix verify_translations task * I18n tests refactoring * Add build scripts to kbn-i18n package * Fix some bugs * Move uiI18nMixin into ui_i18n folder * Add 'browser' field to kbn-i18n package.json * Get rid of "showError" method * Make i18n and i18nLoader a singleton object * Add default locale as fallback if translation files were not registered * Update yarn.lock * kbn-i18n fix * Add default formats * Try to fix build * Add more examples into kbn-i18n/README.md * kbn-i18n fix * Fix app_bootstrap tests * Add links to issues in TODO comments
* Add draft implementation of I18n engine * Add i18n loader * kbn-i18n refactoring * Fix react i18n context and update doc * i18n engine refactoring * Fix locales data loading and add more jsdoc comments * Fix verify_translations task * I18n tests refactoring * Add build scripts to kbn-i18n package * Fix some bugs * Move uiI18nMixin into ui_i18n folder * Add 'browser' field to kbn-i18n package.json * Get rid of "showError" method * Make i18n and i18nLoader a singleton object * Add default locale as fallback if translation files were not registered * Update yarn.lock * kbn-i18n fix * Add default formats * Try to fix build * Add more examples into kbn-i18n/README.md * kbn-i18n fix * Fix app_bootstrap tests * Add links to issues in TODO comments
* Add draft implementation of I18n engine * Add i18n loader * kbn-i18n refactoring * Fix react i18n context and update doc * i18n engine refactoring * Fix locales data loading and add more jsdoc comments * Fix verify_translations task * I18n tests refactoring * Add build scripts to kbn-i18n package * Fix some bugs * Move uiI18nMixin into ui_i18n folder * Add 'browser' field to kbn-i18n package.json * Get rid of "showError" method * Make i18n and i18nLoader a singleton object * Add default locale as fallback if translation files were not registered * Update yarn.lock * kbn-i18n fix * Add default formats * Try to fix build * Add more examples into kbn-i18n/README.md * kbn-i18n fix * Fix app_bootstrap tests * Add links to issues in TODO comments
6.x/6.5: 1d0e438 |
It's just a draft implementation of i18n engine. I'm going to encapsulate all i18n logic into
@kbn/i18n
package.