From 452764b2471e6fc735df4a008c2cfb75d53f5963 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 25 Apr 2024 08:29:41 +0200 Subject: [PATCH 1/4] Add translation files to CDN assets --- .../i18n/core-i18n-server-internal/index.ts | 2 ++ .../src/constants.ts | 12 ++++++++ src/dev/build/tasks/create_cdn_assets_task.ts | 29 +++++++++++++++++-- 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 packages/core/i18n/core-i18n-server-internal/src/constants.ts diff --git a/packages/core/i18n/core-i18n-server-internal/index.ts b/packages/core/i18n/core-i18n-server-internal/index.ts index e4709a8cd7b832..e7a7daa8016095 100644 --- a/packages/core/i18n/core-i18n-server-internal/index.ts +++ b/packages/core/i18n/core-i18n-server-internal/index.ts @@ -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'; diff --git a/packages/core/i18n/core-i18n-server-internal/src/constants.ts b/packages/core/i18n/core-i18n-server-internal/src/constants.ts new file mode 100644 index 00000000000000..c662f77b6adbfd --- /dev/null +++ b/packages/core/i18n/core-i18n-server-internal/src/constants.ts @@ -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']; diff --git a/src/dev/build/tasks/create_cdn_assets_task.ts b/src/dev/build/tasks/create_cdn_assets_task.ts index 870fd05c4ae39e..79e79fba548a0b 100644 --- a/src/dev/build/tasks/create_cdn_assets_task.ts +++ b/src/dev/build/tasks/create_cdn_assets_task.ts @@ -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()); +} From b4625aadccb0a2ebe335e8b7277152b20e1553a5 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 25 Apr 2024 08:45:29 +0200 Subject: [PATCH 2/4] add isUsingCdn to static assets service --- .../src/static_assets/static_assets.test.ts | 13 +++++++++++++ .../src/static_assets/static_assets.ts | 9 +++++++++ .../core-http-server-mocks/src/http_service.mock.ts | 1 + 3 files changed, 23 insertions(+) diff --git a/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts index 438a87765d85ee..9d2c58e85b8ae3 100644 --- a/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts +++ b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts @@ -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); diff --git a/packages/core/http/core-http-server-internal/src/static_assets/static_assets.ts b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.ts index f5f7d7ac804304..f67dfbc46d9b7c 100644 --- a/packages/core/http/core-http-server-internal/src/static_assets/static_assets.ts +++ b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.ts @@ -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. diff --git a/packages/core/http/core-http-server-mocks/src/http_service.mock.ts b/packages/core/http/core-http-server-mocks/src/http_service.mock.ts index 7172accf98a9f9..01f8c99bf23328 100644 --- a/packages/core/http/core-http-server-mocks/src/http_service.mock.ts +++ b/packages/core/http/core-http-server-mocks/src/http_service.mock.ts @@ -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), From cd7026b55defc286dc6b9159a29ad5e90fdacc9d Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 25 Apr 2024 09:23:57 +0200 Subject: [PATCH 3/4] Add tests --- .../src/rendering_service.test.ts | 36 +++++++++++++++++++ .../src/rendering_service.tsx | 14 +++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.ts b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.ts index 7ebb97fb0779ba..b07b8a1cd6fa13 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.ts @@ -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' + ); + }); }); } diff --git a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx index cf97bad34fc605..498cc60b49376c 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx +++ b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx @@ -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: { From 1c6213fec8d68631c78acb24a9231f0f6608653c Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 25 Apr 2024 07:31:06 +0000 Subject: [PATCH 4/4] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- src/dev/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dev/tsconfig.json b/src/dev/tsconfig.json index ca2253b2ee0550..e028b31a931f71 100644 --- a/src/dev/tsconfig.json +++ b/src/dev/tsconfig.json @@ -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", ] }