mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add translation files to CDN assets (#181650)
## Summary
Part of https://github.com/elastic/kibana/issues/72880
- Generate translation files for all locales (including all internal
plugins) during the CDN asset generation task
- Adapt the `rendering` service to use the translation files from the
CDN if configured/enabled
### How to test
Connect to the serverless project that was created for the PR, and
confirm the translation file is being loaded from the CDN
<img width="907" alt="Screenshot 2024-04-25 at 15 55 23"
src="5a6d9110
-2e92-41e5-b066-e792e0015134">
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e2aa9fdbac
commit
2911f597a4
9 changed files with 110 additions and 7 deletions
|
@ -35,6 +35,19 @@ describe('StaticAssets', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#isUsingCdn()', () => {
|
||||
it('returns false when the CDN is not configured', () => {
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.isUsingCdn()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when the CDN is configured', () => {
|
||||
args.cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' });
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.isUsingCdn()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getPluginAssetHref()', () => {
|
||||
it('returns the expected value when CDN is not configured', () => {
|
||||
staticAssets = new StaticAssets(args);
|
||||
|
|
|
@ -16,6 +16,11 @@ import {
|
|||
|
||||
export interface InternalStaticAssets {
|
||||
getHrefBase(): string;
|
||||
/**
|
||||
* Returns true if a CDN has been configured and should be used to serve static assets.
|
||||
* Should only be used in scenarios where different behavior has to be used when CDN is enabled or not.
|
||||
*/
|
||||
isUsingCdn(): boolean;
|
||||
/**
|
||||
* Intended for use by server code rendering UI or generating links to static assets
|
||||
* that will ultimately be called from the browser and must respect settings like
|
||||
|
@ -67,6 +72,10 @@ export class StaticAssets implements InternalStaticAssets {
|
|||
this.assetsServerPathBase = `/${shaDigest}`;
|
||||
}
|
||||
|
||||
public isUsingCdn() {
|
||||
return this.hasCdnHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a href (hypertext reference) intended to be used as the base for constructing
|
||||
* other hrefs to static assets.
|
||||
|
|
|
@ -87,6 +87,7 @@ const createInternalStaticAssetsMock = (
|
|||
basePath: BasePathMocked,
|
||||
cdnUrl: undefined | string = undefined
|
||||
): InternalStaticAssetsMocked => ({
|
||||
isUsingCdn: jest.fn().mockReturnValue(!!cdnUrl),
|
||||
getHrefBase: jest.fn().mockReturnValue(cdnUrl ?? basePath.serverBasePath),
|
||||
getPluginAssetHref: jest.fn().mockReturnValue(cdnUrl ?? basePath.serverBasePath),
|
||||
getPluginServerPath: jest.fn((v, _) => v),
|
||||
|
|
|
@ -8,3 +8,5 @@
|
|||
|
||||
export type { I18nConfigType, InternalI18nServicePreboot } from './src';
|
||||
export { config, I18nService } from './src';
|
||||
export { getKibanaTranslationFiles } from './src/get_kibana_translation_files';
|
||||
export { supportedLocale } from './src/constants';
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
* List of all locales that are officially supported.
|
||||
*/
|
||||
export const supportedLocale = ['en', 'fr-FR', 'ja-JP', 'zh-CN'];
|
|
@ -258,6 +258,42 @@ function renderTestCases(
|
|||
const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""');
|
||||
expect(data.logging).toEqual(loggingConfig);
|
||||
});
|
||||
|
||||
it('use the correct translation url when CDN is enabled', async () => {
|
||||
const userSettings = { 'theme:darkMode': { userValue: true } };
|
||||
uiSettings.client.getUserProvided.mockResolvedValue(userSettings);
|
||||
|
||||
const [render, deps] = await getRender();
|
||||
|
||||
(deps.http.staticAssets.getHrefBase as jest.Mock).mockReturnValueOnce('http://foo.bar:1773');
|
||||
(deps.http.staticAssets.isUsingCdn as jest.Mock).mockReturnValueOnce(true);
|
||||
|
||||
const content = await render(createKibanaRequest(), uiSettings, {
|
||||
isAnonymousPage: false,
|
||||
});
|
||||
const dom = load(content);
|
||||
const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""');
|
||||
expect(data.i18n.translationsUrl).toEqual('http://foo.bar:1773/translations/en.json');
|
||||
});
|
||||
|
||||
it('use the correct translation url when CDN is disabled', async () => {
|
||||
const userSettings = { 'theme:darkMode': { userValue: true } };
|
||||
uiSettings.client.getUserProvided.mockResolvedValue(userSettings);
|
||||
|
||||
const [render, deps] = await getRender();
|
||||
|
||||
(deps.http.staticAssets.getHrefBase as jest.Mock).mockReturnValueOnce('http://foo.bar:1773');
|
||||
(deps.http.staticAssets.isUsingCdn as jest.Mock).mockReturnValueOnce(false);
|
||||
|
||||
const content = await render(createKibanaRequest(), uiSettings, {
|
||||
isAnonymousPage: false,
|
||||
});
|
||||
const dom = load(content);
|
||||
const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""');
|
||||
expect(data.i18n.translationsUrl).toEqual(
|
||||
'/mock-server-basepath/translations/MOCK_HASH/en.json'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ export class RenderingService {
|
|||
packageInfo: this.coreContext.env.packageInfo,
|
||||
};
|
||||
const staticAssetsHrefBase = http.staticAssets.getHrefBase();
|
||||
const usingCdn = http.staticAssets.isUsingCdn();
|
||||
const basePath = http.basePath.get(request);
|
||||
const { serverBasePath, publicBaseUrl } = http.basePath;
|
||||
|
||||
|
@ -205,8 +206,14 @@ export class RenderingService {
|
|||
|
||||
const loggingConfig = await getBrowserLoggingConfig(this.coreContext.configService);
|
||||
|
||||
const translationHash = i18n.getTranslationHash();
|
||||
const translationsUrl = `${serverBasePath}/translations/${translationHash}/${i18nLib.getLocale()}.json`;
|
||||
const locale = i18nLib.getLocale();
|
||||
let translationsUrl: string;
|
||||
if (usingCdn) {
|
||||
translationsUrl = `${staticAssetsHrefBase}/translations/${locale}.json`;
|
||||
} else {
|
||||
const translationHash = i18n.getTranslationHash();
|
||||
translationsUrl = `${serverBasePath}/translations/${translationHash}/${locale}.json`;
|
||||
}
|
||||
|
||||
const filteredPlugins = filterUiPlugins({ uiPlugins, isAnonymousPage });
|
||||
const bootstrapScript = isAnonymousPage ? 'bootstrap-anonymous.js' : 'bootstrap.js';
|
||||
|
@ -215,7 +222,7 @@ export class RenderingService {
|
|||
uiPublicUrl: `${staticAssetsHrefBase}/ui`,
|
||||
bootstrapScriptUrl: `${basePath}/${bootstrapScript}`,
|
||||
i18n: i18nLib.translate,
|
||||
locale: i18nLib.getLocale(),
|
||||
locale,
|
||||
themeVersion,
|
||||
darkMode,
|
||||
stylesheetPaths: commonStylesheetPaths,
|
||||
|
@ -239,7 +246,6 @@ export class RenderingService {
|
|||
clusterInfo,
|
||||
anonymousStatusPage: status?.isStatusPageAnonymous() ?? false,
|
||||
i18n: {
|
||||
// TODO: Make this load as part of static assets!
|
||||
translationsUrl,
|
||||
},
|
||||
theme: {
|
||||
|
|
|
@ -12,11 +12,13 @@ import { access } from 'fs/promises';
|
|||
import { resolve, dirname } from 'path';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { Jsonc } from '@kbn/repo-packages';
|
||||
import { getKibanaTranslationFiles, supportedLocale } from '@kbn/core-i18n-server-internal';
|
||||
import { i18n, i18nLoader } from '@kbn/i18n';
|
||||
|
||||
import del from 'del';
|
||||
import globby from 'globby';
|
||||
|
||||
import { mkdirp, compressTar, Task, copyAll } from '../lib';
|
||||
import { mkdirp, compressTar, Task, copyAll, write } from '../lib';
|
||||
|
||||
export const CreateCdnAssets: Task = {
|
||||
description: 'Creating CDN assets',
|
||||
|
@ -31,9 +33,19 @@ export const CreateCdnAssets: Task = {
|
|||
await del(assets);
|
||||
await mkdirp(assets);
|
||||
|
||||
// Plugins
|
||||
|
||||
const plugins = globby.sync([`${buildSource}/node_modules/@kbn/**/*/kibana.jsonc`]);
|
||||
|
||||
// translation files
|
||||
const pluginPaths = plugins.map((plugin) => resolve(dirname(plugin)));
|
||||
for (const locale of supportedLocale) {
|
||||
const translationFileContent = await generateTranslationFile(locale, pluginPaths);
|
||||
await write(
|
||||
resolve(assets, buildSha, `translations`, `${locale}.json`),
|
||||
translationFileContent
|
||||
);
|
||||
}
|
||||
|
||||
// Plugins static assets
|
||||
await asyncForEach(plugins, async (path) => {
|
||||
const manifest = Jsonc.parse(readFileSync(path, 'utf8')) as any;
|
||||
if (manifest?.plugin?.id) {
|
||||
|
@ -101,3 +113,14 @@ export const CreateCdnAssets: Task = {
|
|||
});
|
||||
},
|
||||
};
|
||||
|
||||
async function generateTranslationFile(locale: string, pluginPaths: string[]) {
|
||||
const translationFiles = await getKibanaTranslationFiles(locale, pluginPaths);
|
||||
i18nLoader.registerTranslationFiles(translationFiles);
|
||||
const translations = await i18nLoader.getTranslationsByLocale(locale);
|
||||
i18n.init({
|
||||
locale,
|
||||
...translations,
|
||||
});
|
||||
return JSON.stringify(i18n.getTranslation());
|
||||
}
|
||||
|
|
|
@ -42,5 +42,6 @@
|
|||
"@kbn/core-test-helpers-so-type-serializer",
|
||||
"@kbn/core-test-helpers-kbn-server",
|
||||
"@kbn/dev-proc-runner",
|
||||
"@kbn/core-i18n-server-internal",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue