-
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
RFC: Kibana Internationalization #17201
Comments
Given that Kibana itself is powered by a bunch of core plugins, which don't do anything "special" compared to other plugins other than the fact that they can't be uninstalled, I don't think we can ignore the plugin aspect of this.
This seems reasonable to me, though "good enough" may be hard to identify. We'll probably just need to identify an approach for those technologies and then make a relatively subjective decision at that time. |
This is fair point, thanks. Updated.
Yes, it will become clearer once we have concrete proposals. By "good enough" in this context I mean "supports all the features we need, but not as expressive and easy-to-read as the best possible one", but we'll see. |
Here you can see the examples based on PoC for angular-translate and react-intl. Simple textAngular<h2
translate="KIBANA-DISCOVER-SEARCHING"
translate-default="Searching"
></h2> React<h2>
<FormattedMessage
id="KIBANA-DISCOVER-SEARCHING"
defaultMessage="Searching"
/>
</h2> Translation{
"KIBANA-DISCOVER-SEARCHING": "Searching"
} AttributeAngular<input
type="text"
placeholder="{{'KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER' | translate}}"
> Reactimport React from 'react';
import { injectIntl, intlShape } from 'react-intl';
const Component = ({ intl }) => (
<input
type="text"
placeholder={intl.formatMessage({
id: 'KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER',
defaultMessage: 'Search...',
})}
/>
);
export default injectIntl(Component); Attribute with variables interpolationAngular<input
type="text"
placeholder="{{'KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER' | translate: { TITLE: service.title }}}"
> Reactimport React from 'react';
import { injectIntl, intlShape } from 'react-intl';
const Component = ({ intl, service }) => (
<input
type="text"
placeholder={intl.formatMessage({
id: 'KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER',
defaultMessage: '{TITLE} search',
}, { TITLE: service.title })}
/>
);
export default injectIntl(Component); Text with pluralsAngular<span
translate="KIBANA-DISCOVER-HITS"
translate-values="{HITS: hits}"
translate-default="{HITS, plural, one {# hit} other {# hits}}"
></span> React<FormattedMessage
id="KIBANA-DISCOVER-HITS"
values={{ HITS: hits }}
defaultMessage="{HITS, plural, one {# hit} other {# hits}}"
/> Translation{
"KIBANA-DISCOVER-HITS": "{HITS, plural, one {# hit} other {# hits}}"
} Text with nested formattingAngular<span
translate="KIBANA-DISCOVER-REFINE_SEARCH"
translate-values="{SIZE: '<b>{{opts.sampleSize}}</b>'}"
translate-default="These are the first {SIZE} documents matching your search, refine your search to see others."
></span> React<FormattedMessage
id="KIBANA-DISCOVER-REFINE_SEARCH"
values={{ SIZE: <b>{opts.sampleSize}</b> }}
defaultMessage="These are the first {SIZE} documents matching your search, refine your search to see others."
/> Translation{
"KIBANA-DISCOVER-REFINE_SEARCH": "These are the first {SIZE} documents matching your search, refine your search to see others."
} From my point of view attribute translation in react-intl is not very convenient, so we can write our own component or helper for this purpose. Also we have to wrap each top-level react component into IntlProvider which leads to code overhead. |
@chiweichang I used ICU message-format (http://userguide.icu-project.org/formatparse/messages) for pluralization in this example. The numeric input is mapped to a plural category, some subset of "zero", "one", "two", "few", "many", and "other" depending on the locale and the type of plural. Also we can use "=" prefix to match for exact values (=0, =1, etc). |
@chiweichang np. Yes, you are right. |
Here you can see the examples based on PoC for i18next. Simple textAngular<h2 ng-i18next="[i18next]({ defaultValue: 'Searching' })KIBANA-DISCOVER-SEARCHING"></h2> React<h2>
<Trans i18nKey="KIBANA-DISCOVER-SEARCHING" />
Searching
</Trans>
</h2> Translation{
"KIBANA-DISCOVER-SEARCHING": "Searching"
} AttributeAngular<input
type="text"
ng-i18next="[placeholder:i18next]({ defaultValue: 'Search...' })KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER"
> Reactimport React from 'react';
import { I18n } from 'react-i18next';
const Component = () => (
<I18n>
{(t, { i18n }) => (
<input
type="text"
placeholder={t(['KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER', 'Search...'])}
/>
)}
</I18n>
); Attribute with variables interpolationAngular<input
type="text"
ng-i18next="[placeholder:i18next]({
defaultValue: '\{\{TITLE\}\} search',
TITLE: {{ service.title }}
})KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER"
> Reactimport React from 'react';
import { I18n } from 'react-i18next';
const Component = ({ service }) => (
<I18n>
{(t, { i18n }) => (
<input
type="text"
placeholder={t(['KIBANA-MANAGEMENT-OBJECTS-SEARCH_PLACEHOLDER', '{{TITLE}} search'], { TITLE: service.title })}
/>
)}
</I18n>
); Text with pluralsAngular<span
ng-i18next="[i18next]({
count: {{ hits }},
})KIBANA-DISCOVER-HITS"
></span> React<Trans
i18nKey="KIBANA-DISCOVER-HITS"
count={hits}
/> Translation{
"KIBANA-DISCOVER-HITS": "{{count}} hit",
"KIBANA-DISCOVER-HITS_plural": "{{count}} hits",
} Text with nested formattingAngular<span
ng-i18next="[html:i18next]({
SIZE: '<b>{{opts.sampleSize}}</b>',
defaultValue: 'These are the first \{\{SIZE\}\} documents matching your search, refine your search to see others.',
})KIBANA-DISCOVER-REFINE_SEARCH"
></span> React<Trans i18nKey="KIBANA-DISCOVER-REFINE_SEARCH-REACT">
These are the first <b>{{ SIZE: opts.sampleSize }}</b> documents matching your search, refine your search to see others.
</Trans> Translation{
"KIBANA-DISCOVER-REFINE_SEARCH": "These are the first {{SIZE}} documents matching your search, refine your search to see others.",
"KIBANA-DISCOVER-REFINE_SEARCH-REACT": "These are the first <1><0>{{SIZE}}</0></1> documents matching your search, refine your search to see others."
} It seems to me that ng-i18next is too complicated compared to angular-translate. Also i18next pluralization is not so flexible as ICU message-format. |
@azasypkin , this is great. Are we still following the design described #6515. Would love to collaborate again |
@azasypkin what @shikhasriva said, great to see this starting up again. |
I believe some parts will stay the same, but some will likely change based on the current state of Kibana and how we see it in the future. We are figuring it out at the moment.
It's great to know! We'll be updating this RFC as we progress through our explorations, design and implementation, please stick around 🙂 |
Hey @maksim-tolo, just few questions regarding your PoC's: PoC with angular-translate/react-intl.
I see there is only one
What is the
Could you please elaborate on this a bit more, what problems exactly do you see? Btw what is the syntax for "Text with plurals" within attributes for Angular/React (if it's supported)? PoC with i18next.
Same question here, is it possible to localize multiple attributes?
I don't see any examples with default message in this section. Is there any reason why we can't define default messages when dealing with plurals?
Do you have any pluralization related use cases in mind that are supported by ICU message format, but aren't by i18next or just the syntax is cumbersome? |
Hi @azasypkin!
Oh, my fault.
It's just example from the app, so you can pass any value here.
It's just my personal opinion. We have to wrap each component into HoC using injectIntl in order to translate attributes. I prefer to use render callbacks approach.
The syntax exactly the same as for "Attribute with variables interpolation".
Yes, it's possible. You should split attributes translation using ";" separator. The example is below:
If we didn't escape the value, Angular would interpolate this expression from the $scope.
Pluralization in i18next requires additional translation key with "_plural" postfix. I don't know how to pass 2 default messages (singular and plural) into the component.
There are languages with multiple plural forms (Language Plural Rules). i18next pluralization format allows to declare only two forms: singular and plural. |
Here you can see the examples based on PoC with custom translation components using https://github.com/messageformat/messageformat.js under the hood. Also I've added addition examples for date, time, duration and number translation. Simple textAngular
React
Translation
AttributeAngular
React
Attribute with variables interpolationAngular
React
Text with pluralsAngular
React
Translation
Text with nested formattingAngular
React
Translation
DateSupported parameters are Angular
React
Translation
DurationRepresent a duration in seconds as a string. Angular
React
Translation
NumberSupported parameters are Angular
React
Translation
TimeSupported parameters are Angular
React
Translation
As any custom solution, this approach has own pros and cons. Pros:
Cons:
|
I've spent some time plyaing with Fluent and here is PoC based on it. Unfortunately there is no "native" Angular wrapper for it, so I used framework-independent JS version instead. Common bootstrapping code: import { MessageContext } from 'fluent/compat';
import { negotiateLanguages } from 'fluent-langneg/compat';
// Asynchronous generator/iterator (semi-pseudo code).
function* generateMessages(resourceIds) {
const resources = await Promise.all(
resourceIds.map((resourcePath) => fetch(resourcePath))
);
const requestedLocales = navigator.languages // ['en-US', 'ru', ...];
const availableLocales = // e.g. extract BCP47 locale IDs from loaded resources.
// Choose locales that are best for the user.
const currentLocales = negotiateLanguages(
requestedLocales,
availableLocales,
{ defaultLocale: 'en-US' }
);
for (const locale of currentLocales) {
const cx = new MessageContext(locale);
for (const resource of resources) {
cx.addMessages(resources[locale]);
}
yield cx;
}
} Angular bootstrapping code: import { DOMLocalization } from 'fluent-dom/compat';
// Called once, then observes root for mutations and (re-)translate when needed.
const l10n = new DOMLocalization(window, ['/plugins/security.ftl'], generateMessages);
l10n.connectRoot(document.documentElement);
l10n.translateRoots(); React bootstrapping code: import { LocalizationProvider } from 'fluent-react/compat';
export function Root() {
return (
<LocalizationProvider messages={generateMessages(['/plugins/security.ftl'])}>
<App />
</LocalizationProvider>
);
} Simple textAngular<h2 data-l10n-id="L10N.SEARCH">Search...</h2> React<Localized id="L10N.SEARCH">
<h2>Search...</h2>
</Localized> Message format (*.ftl){
'en-US': `
L10N.SEARCH = Search...
`
} Attribute (multiple attributes, attribute + main content)Angular<input data-l10n-id="L10N.SEARCH" type="text" placeholder="Search..." title="Title..." />
<span data-l10n-id="L10N.SOMETHING" title="Title...">Something...</span> Reactimport { Localized } from 'fluent-react/compat';
<Localized id="L10N.SEARCH" attrs={{ placeholder: true, title: true }}>
<input type="text" placeholder="Search..." title="Title..." />
</Localized>
<Localized id="L10N.SOMETHING" attrs={{ title: true }}>
<span title="Title...">Something...</span>
</Localized> Message format (*.ftl){
'en-US': `
L10N.SEARCH =
.placeholder = Search...
.title = Title...
L10N.SOMETHING = Something...
.title = Title...
`
} Text with parametersAngular<div data-l10n-id="L10N.TEXT_WITH_PARAM" data-l10n-args='{"param": "some param"}'
title="Title with $param">
Text with $param
</div> Reactimport { Localized } from 'fluent-react/compat';
<Localized id="L10N.TEXT_WITH_PARAM" $param="some param" attrs={{ title: true }}>
<div title="Title with $param">
Text with $param
</div>
</Localized> Message format (*.ftl){
'en-US': `
L10N.TEXT_WITH_PARAM = Text with {$param}
.title = Title with {$param}
`
} Text with pluralsAngular<div data-l10n-id="L10N.TEXT_WITH_PLURALS" data-l10n-args='{"numberOfParams": "5"}'
title="Title with $numberOfParams">
Text with $numberOfParams
</div> Reactimport { Localized } from 'fluent-react/compat';
<Localized id="L10N.TEXT_WITH_PLURALS" $numberOfParams={5} attrs={{ title: true }}>
<div title="Title with $numberOfParams">
Text with $numberOfParams
</div>
</Localized> Message format (*.ftl){
'en-US': `
L10N.TEXT_WITH_PLURALS =
{ $numberOfParams ->
[one] One param.
*[other] Params number: { $numberOfParams }.
}
.title =
{ $numberOfParams ->
[one] One param in title.
*[other] Params number in title: { $numberOfParams }.
}
`
} Text with nested formattingAngular (based on DOM Overlays)<div data-l10n-id="L10N.TEXT_WITH_STYLE">
Text with <span class="some-style">styled arg</span>.
</div> {
'en-US': `
L10N.TEXT_WITH_STYLE = Text with <span>styled arg</span>.
`
} Reactimport { Localized } from 'fluent-react/compat';
<Localized id="L10N.TEXT_WITH_STYLE"
arg={<span className="some-style">styled arg</span>}>
<div>Text with <arg />.</div>
</Localized> {
'en-US': `
L10N.TEXT_WITH_STYLE = Text with <arg>styled arg</arg>.
`
} Pros:
Cons:
|
Out of a forum post I am wondering, what is the current plan for server side internationalization? We're having some strings that are generated server side, like app names and descriptions. Will that work with the above approaches, will we introduce a new header to signal the server which locale the current user is using per request, will we only have one language per Kibana instance available and thus can read it out of the config? I think for some of the static code like app names and descriptions, that's still rather easy to transfer the translation key and translate it in the frontend, but what's about more dynamic content like server side generated error messages, etc? |
Ability to localize text/numbers/dates on the server side (NodeJS) is something we plan to support from the day one.
Everything is still in flux, but we can rely on
Yeah, agree, user-facing errors should be localized too. |
Another examples based on PoC for react-intl and custom AngularJS wrapper for format.js library are below. Simple textAngular
React
Translation
AttributeAngular
React
Attribute with variables interpolationAngular
React
Text with pluralsAngular
React
Translation
Text with nested formattingAngular
React
Translation
DateSupported parameters are Angular
React
Translation
NumberSupported parameters are Angular
React
Translation
TimeSupported parameters are Angular
React
Translation
Pros:
Cons:
|
Here is the summary for all the frameworks and approaches we tried. TL;DR: we decided to move forward with solution based on FormatJS core libs: it has relatively small code base, good documentation, uses well-known ICU message syntax and can be used on the server side (NodeJS), in React (react-intl) and AngularJS (custom component that we'll build). The other front runners were Fluent and custom solution based on messageformat.js, but for the time being we decided to not base our initiative on these tools due to low adoption/immaturity (Fluent) or significantly larger amount of work needed for the bootstrap phase (messageformat.js). Angular Translate with its very outdated messageformat.js dependency wouldn't allow us to use the same message parsing and formatting engine across the "stack" that may lead to various subtle issues and inconsistencies. Even though i18next seems to be well supported, works in NodeJS and has components for both Angular and React, currently we don't see it as a good solution for Kibana: custom ICU-incompatible message format (at the time of writing) and hard-to-follow code base that may not give us the level of flexibility we need. See table below for more details.
|
Wrote a blog about the POC. https://www.elastic.co/blog/keeping-up-with-kibana-2018-04-16 |
Aren't you guys way overthinking this? There's so little text on the UI as is, in English, I find it hard to see the big problem? It was asked for 5-years ago. Can you please just do it, instead of worrying about the perfect implementation which seems to never happen? |
@amivit Internationalizing a large UI like Kibana, isn't as easy as it sounds. Also internationalizing is way more then "replacing text strings". When talking about internationalization you'll always also talk about different number or date formats, pluralization of strings (since every language has a flexible amount of plural forms, and different forms for ordinal, cardinal and nominal - see e.g. Language Plural Rules) and way more (which we might not even touch, like different cultural understanding of colors). Also since we are using several different web frameworks we need a solution, that works at least in Angular, React and Vanilla JS. Since internationalization hasn't been build in from the beginning it means, we currently need to replace around 8500 unique strings in a couple of thousand files.. and all that while ideally not a whole team of developers can continue working at the code base in the meanwhile. The team will happily evaluate any constructive suggestions on how to improve the technical implementation in a clean and future-proof manner to solve these common i18n issues. |
UPDATE: Initial version of technical-level explanation has just been merged: https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md (main RFC comment has been updated too) |
UPDATE: Initial version of guide-level explanation has just been merged: https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/GUIDELINE.md (main RFC comment has been updated too). We also migrated |
UPDATE: There is a good progress on usage of
|
The initial support for Kibana i18n has been just merged into 6.7 branch. The remaining work will be done in a separate more focused issues. |
I hope spanish translation will be included. |
Not at the initial release, but at some point it will definitely be included. |
@azasypkin I'm sorry I didn't follow along the entire thread or the internals of the feature. But will translating to a given language be as simple as touching a es_ES.txt, pt_BR.txt file? If so, I volunteer to work on the pt_BR one |
Hey @pbassut,
Yeah, almost. You'll need to generate You can take a look at zh-CN.json to see how file with translations looks like. Current For |
Summary
All user-facing text in core Kibana and plugins should be localizable. Localizable user defined text is out of scope for this RFC.
What won't be translated?
Motivation
Kibana is used by people from all over the world that speak different languages, use different date and currency formats. Localized Kibana will reduce unnecessary friction and fit naturally into existing users' workflow.
Internationalization engine will provide a means to supply translations in a standard and future-proof format that it not dependent on a localization framework we or plugin developers may use in the future.
The feature set includes, but not limited to:
Guide-level explanation
Use this link to read in-depth and up-to-date guide-level explanation.
Technical-level explanation
Use this link to read in-depth and up-to-date technical-level explanation.
Experiments & proofs of concept
We decided to move forward with solution based on FormatJS core libs: it has relatively small code base, good documentation, uses well-known ICU message syntax and can be used on the server side (NodeJS), in React (react-intl) and AngularJS (custom component that we'll build).
The other front runners were Fluent and custom solution based on messageformat.js, but for the time being we decided to not base our initiative on these tools due to low adoption/immaturity (Fluent) or significantly larger amount of work needed for the bootstrap phase (messageformat.js).
Angular Translate with its very outdated messageformat.js dependency wouldn't allow us to use the same message parsing and formatting engine across the "stack" that may lead to various subtle issues and inconsistencies.
Even though i18next seems to be well supported, works in NodeJS and has components for both Angular and React, currently we don't see it as a good solution for Kibana: custom ICU-incompatible message format (at the time of writing) and hard-to-follow code base that may not give us the level of flexibility we need.
See links below for more details:
Unresolved questions
References
History
References
sectionTechnical-level explanation
detailsTechnical-level explanation
Guide-level explanation
The text was updated successfully, but these errors were encountered: