Skip to content

[i18n] support multiple locales #146957

Open

Description

For some of Kibana's long term goal, we will need to support serving multiple locales on a same Kibana instance (depending on some info from the current user / context, preferred locale, space, or other...).

Why doing this now?

  • We know we will need to for our long term goals
  • Low hanging fruit without that much required work from the Core team
  • Changing all the i18n calls in the whole codebase to use the new translation system is some long haul work, so we should provide the system, deprecates the current way and inform the teams as soon as possible so that they can prepare.

Necessary changes to Core's API

The needs are different on the browser and on the server

On the browser

This is the easy part of the feature.

On the browser, we just want the system to be able to load a different locale depending on some kind of context. We're not planning on support dynamic locale switch (without full page reload).

So the only required changes are:

  • to adapt the i18n service from Core to load all the available locale (as opposed to only load the locale specified by the configuration file as it is done currently)
  • to adapt the /translations/{locale}.json route to accept serving any loaded locale (as opposed to returning a 404 if the requested locale is not the one specified in the config file)
  • to expose a 'scoped' i18n from core's i18n service (to mimic the way translation will be done on the server and to avoid calling import i18n; i18n.translate directly)

On the server

As you can guess, this will be the hard part.

The idea will simply be to extend Core's i18n APIs:

1. Add new APIs to the I18nService

to be able to:

  • retrieve the locale bound to a request
    • initially the underlying will always return the locale from the config, but we will then be able to change it whenever it will be necessary
  • create a 'translator' (scoped i18n service) bound to a given locale

The contracts could look like:

type ScopedTranslateArgument = Omit<TranslateArguments, 'locale'>;

/** An instance of a translator scoped to a specific locale. */
export interface ScopedTranslator {
  /** The locale the scoped translator is bound to */
  readonly locale: string;
  /** Translate message to the bound locale */
  translate(id: string, options: ScopedTranslateArgument): string;
}

export interface I18nServiceSetup {
  /** @deprecated use `getDefaultLocale` instead */
  getLocale(): string;
  getDefaultLocale(): string;
  getTranslationFiles(): string[];
  getScopedTranslator(locale: string): ScopedTranslator;
}

export interface I18nServiceStart {
  getLocaleForRequest(request: KibanaRequest): string;
  getScopedTranslator(locale: string): ScopedTranslator;
}

2. Introduce a i18n request handler context

So that API consumers can easily access the scoped i18n service from the request handler, as it's done for other scoped services

e.g

handler(ctx, req, res) {
   const i18n = ctx.i18n.translator;
   i18n.translate('my.key', { defaultValue: '...' })
}

3. deprecate direct usages of i18n.translate

All translations should be performed using 'translators' (name open to discussion) retrieved via the new Core i18n API, as these will be the only way to know that all translations are properly scoped.

So we will deprecate i18n.translate (the translate function exposed from @kbn/i18n) and inform/document of it's future removal.

4. adapt the i18n scripts

The i18n check scripts are all based on JS (and not TS) ast, and are (basically) checking for references to i18n.translate to extract/check consistency from. We will need to adapt the parsing to take the new intermediary services into account.

For instance, we need to detect i18n translations at least for scenarios like:

handler(ctx, req, res) {
   const translator = ctx.i18n.translator;
   translator.translate('my.key', { defaultValue: '...' })
}
handler(ctx, req, res) {
   const translator = ctx.i18n.translator;
   ctx.i18n.translate('my.key', { defaultValue: '...' })
}
function doSomeTranslate(core: CoreStart, locale: string) {
   const translator = core.i18n.getTranslator(locale);
   return translator.translate('my.key');
}

Plus, we also need to make sure that the proxy translator (calling i18n.translate with a variable instead of a constant string) is added to the list of exclusions.

Related PRs:

Impacts on other teams

Overall that will be some massive (not hard, but long and sometimes tedious) chore work, as all calls to i18n.translate will have to be adapted (which is why we should ready the framework as soon as possible)

i18n.translate will be deprecated and flagged to removal. And all calls to the translation system will have to be performed using scoped services from Core instead of from the static i18n import.

This means that statically declaring translations will no longer be possible:

import i18n from '@kbn/i18n'

const myStaticLabel = i18n.translate('my.key'); // no longer possible

const getSomeData = () => {
   return {
     title: i18n.translate('my.key') // no longer possible either
   }
}

This will have to be replaced by something like

const myStaticLabelFn = (translator: ScopedTranslator)  => translator.translate('my.key');

const getSomeData = (translator: ScopedTranslator) => {
   return {
     title: translator.translate('my.key') // no longer possible either
   }
}

Note that, ideally, we would find a way to decouple the i18n message from the i18n call, with something like

const myMessage: I18nMessage = {
  id: 'my.key',
  defaultValue: 'the cat is {state}',
}

const getSomeData = (translator: ScopedTranslator) => {
   return {
     title: translator.translate(myMessage, { state: 'alive' })
   }
}

But I guess this would significantly increase the amount of work on the i18n script / atm parsing tools, so I'm not sure on the feasibility @Bamieh?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    Project:i18nTeam:CoreCore services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etcenhancementNew value added to drive a business result

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions