mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[6.x] Expose */translations/{locale}.json
endpoint for the translations instead of embedding them into every app HTML index template. (#29075) (#29426)
This commit is contained in:
parent
caf4e43221
commit
6fe2e6b2d7
10 changed files with 121 additions and 23 deletions
|
@ -32,5 +32,6 @@ export class I18nProvider implements angular.IServiceProvider {
|
|||
public getFormats = i18n.getFormats;
|
||||
public getRegisteredLocales = i18n.getRegisteredLocales;
|
||||
public init = i18n.init;
|
||||
public load = i18n.load;
|
||||
public $get = () => i18n.translate;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ describe('I18n engine', () => {
|
|||
afterEach(() => {
|
||||
// isolate modules for every test so that local module state doesn't conflict between tests
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('addMessages', () => {
|
||||
|
@ -882,4 +883,47 @@ describe('I18n engine', () => {
|
|||
expect(message).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
let mockFetch: jest.Mock<unknown>;
|
||||
beforeEach(() => {
|
||||
mockFetch = jest.spyOn(global as any, 'fetch').mockImplementation();
|
||||
});
|
||||
|
||||
test('fails if server returns >= 300 status code', async () => {
|
||||
mockFetch.mockResolvedValue({ status: 301 });
|
||||
|
||||
await expect(i18n.load('some-url')).rejects.toMatchInlineSnapshot(
|
||||
`[Error: Translations request failed with status code: 301]`
|
||||
);
|
||||
|
||||
mockFetch.mockResolvedValue({ status: 404 });
|
||||
|
||||
await expect(i18n.load('some-url')).rejects.toMatchInlineSnapshot(
|
||||
`[Error: Translations request failed with status code: 404]`
|
||||
);
|
||||
});
|
||||
|
||||
test('initializes engine with received translations', async () => {
|
||||
const translations = {
|
||||
locale: 'en-XA',
|
||||
formats: {
|
||||
number: { currency: { style: 'currency' } },
|
||||
},
|
||||
messages: { 'common.ui.someLabel': 'some label' },
|
||||
};
|
||||
|
||||
mockFetch.mockResolvedValue({
|
||||
status: 200,
|
||||
json: jest.fn().mockResolvedValue(translations),
|
||||
});
|
||||
|
||||
await expect(i18n.load('some-url')).resolves.toBeUndefined();
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
expect(mockFetch).toHaveBeenCalledWith('some-url');
|
||||
|
||||
expect(i18n.getTranslation()).toEqual(translations);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -234,3 +234,17 @@ export function init(newTranslation?: Translation) {
|
|||
setFormats(newTranslation.formats);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads JSON with translations from the specified URL and initializes i18n engine with them.
|
||||
* @param translationsUrl URL pointing to the JSON bundle with translations.
|
||||
*/
|
||||
export async function load(translationsUrl: string) {
|
||||
const response = await fetch(translationsUrl);
|
||||
|
||||
if (response.status >= 300) {
|
||||
throw new Error(`Translations request failed with status code: ${response.status}`);
|
||||
}
|
||||
|
||||
init(await response.json());
|
||||
}
|
||||
|
|
|
@ -286,7 +286,7 @@ describe('#start()', () => {
|
|||
rootDomElement,
|
||||
});
|
||||
|
||||
core.start();
|
||||
return core.start();
|
||||
}
|
||||
|
||||
it('clears the children of the rootDomElement and appends container for legacyPlatform and notifications', () => {
|
||||
|
@ -353,6 +353,10 @@ describe('#start()', () => {
|
|||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('returns start contract', () => {
|
||||
expect(startCore()).toEqual({ fatalErrors: mockFatalErrorsStartContract });
|
||||
});
|
||||
});
|
||||
|
||||
describe('LegacyPlatform targetDomElement', () => {
|
||||
|
|
|
@ -120,6 +120,8 @@ export class CoreSystem {
|
|||
uiSettings,
|
||||
chrome,
|
||||
});
|
||||
|
||||
return { fatalErrors };
|
||||
} catch (error) {
|
||||
this.fatalErrors.add(error);
|
||||
}
|
||||
|
|
|
@ -55,16 +55,11 @@ export async function i18nMixin(kbnServer, server, config) {
|
|||
]);
|
||||
|
||||
const translationPaths = [].concat(...groupedEntries);
|
||||
|
||||
i18nLoader.registerTranslationFiles(translationPaths);
|
||||
|
||||
const pureTranslations = await i18nLoader.getTranslationsByLocale(locale);
|
||||
const translations = Object.freeze({
|
||||
const translations = await i18nLoader.getTranslationsByLocale(locale);
|
||||
i18n.init(Object.freeze({
|
||||
locale,
|
||||
...pureTranslations,
|
||||
});
|
||||
|
||||
i18n.init(translations);
|
||||
|
||||
server.decorate('server', 'getUiTranslations', () => translations);
|
||||
...translations,
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ export default class KbnServer {
|
|||
// writes pid file
|
||||
pidMixin,
|
||||
|
||||
// scan translations dirs, register locale files, initialize i18n engine and define `server.getUiTranslations`
|
||||
// scan translations dirs, register locale files and initialize i18n engine.
|
||||
i18nMixin,
|
||||
|
||||
// find plugins and set this.plugins and this.pluginSpecs
|
||||
|
|
|
@ -37,13 +37,22 @@ 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,
|
||||
rootDomElement: document.body,
|
||||
requireLegacyFiles: () => {
|
||||
${bundle.getRequires().join('\n ')}
|
||||
}
|
||||
}).start()
|
||||
i18n.load(injectedMetadata.i18n.translationsUrl)
|
||||
.catch(e => e)
|
||||
.then((i18nError) => {
|
||||
const coreSystem = new CoreSystem({
|
||||
injectedMetadata,
|
||||
rootDomElement: document.body,
|
||||
requireLegacyFiles: () => {
|
||||
${bundle.getRequires().join('\n ')}
|
||||
}
|
||||
});
|
||||
|
||||
const coreStartContract = coreSystem.start();
|
||||
|
||||
if (i18nError) {
|
||||
coreStartContract.fatalErrors.add(i18nError);
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
|
|
@ -35,8 +35,6 @@ export const UI_EXPORT_DEFAULTS = {
|
|||
'moment-timezone$': resolve(ROOT, 'webpackShims/moment-timezone')
|
||||
},
|
||||
|
||||
translationPaths: [],
|
||||
|
||||
styleSheetPaths: [],
|
||||
|
||||
appExtensions: {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { createHash } from 'crypto';
|
||||
import { props, reduce as reduceAsync } from 'bluebird';
|
||||
import Boom from 'boom';
|
||||
import { resolve } from 'path';
|
||||
|
@ -54,6 +55,35 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
// expose built css
|
||||
server.exposeStaticDir('/built_assets/css/{path*}', fromRoot('built_assets/css'));
|
||||
|
||||
const translationsCache = { translations: null, hash: null };
|
||||
server.route({
|
||||
path: '/translations/{locale}.json',
|
||||
method: 'GET',
|
||||
config: { auth: false },
|
||||
handler(request, h) {
|
||||
// Kibana server loads translations only for a single locale
|
||||
// that is specified in `i18n.locale` config value.
|
||||
const { locale } = request.params;
|
||||
if (i18n.getLocale() !== locale.toLowerCase()) {
|
||||
throw Boom.notFound(`Unknown locale: ${locale}`);
|
||||
}
|
||||
|
||||
// Stringifying thousands of labels and calculating hash on the resulting
|
||||
// string can be expensive so it makes sense to do it once and cache.
|
||||
if (translationsCache.translations == null) {
|
||||
translationsCache.translations = JSON.stringify(i18n.getTranslation());
|
||||
translationsCache.hash = createHash('sha1')
|
||||
.update(translationsCache.translations)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
return h.response(translationsCache.translations)
|
||||
.header('cache-control', 'must-revalidate')
|
||||
.header('content-type', 'application/json')
|
||||
.etag(translationsCache.hash);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/bundles/app/{id}/bootstrap.js',
|
||||
method: 'GET',
|
||||
|
@ -144,7 +174,6 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
|
||||
async function renderApp({ app, h, includeUserProvidedConfig = true, injectedVarsOverrides = {} }) {
|
||||
const request = h.request;
|
||||
const translations = await server.getUiTranslations();
|
||||
const basePath = request.getBasePath();
|
||||
|
||||
return h.view('ui_app', {
|
||||
|
@ -157,6 +186,9 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
version: kbnServer.version,
|
||||
buildNumber: config.get('pkg.buildNum'),
|
||||
basePath,
|
||||
i18n: {
|
||||
translationsUrl: `${basePath}/translations/${i18n.getLocale()}.json`,
|
||||
},
|
||||
vars: await replaceInjectedVars(
|
||||
request,
|
||||
mergeVariables(
|
||||
|
@ -168,7 +200,6 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
|
||||
legacyMetadata: await getLegacyKibanaPayload({
|
||||
app,
|
||||
translations,
|
||||
request,
|
||||
includeUserProvidedConfig,
|
||||
injectedVarsOverrides
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue