From aeb80085cc57f2bd80da267192bb1c0138e1bd06 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 25 Nov 2023 16:31:09 +0100 Subject: [PATCH 1/2] frontend-plugin-api: add createTranslationExtension Signed-off-by: Patrik Oldsberg --- .changeset/breezy-houses-eat.md | 5 + .../frontend-app-api/src/extensions/Core.tsx | 4 + .../frontend-app-api/src/wiring/createApp.tsx | 23 ++-- packages/frontend-plugin-api/api-report.md | 24 ++++ .../createTranslationExtension.test.ts | 128 ++++++++++++++++++ .../extensions/createTranslationExtension.ts | 45 ++++++ .../src/extensions/index.ts | 1 + 7 files changed, 220 insertions(+), 10 deletions(-) create mode 100644 .changeset/breezy-houses-eat.md create mode 100644 packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts create mode 100644 packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts diff --git a/.changeset/breezy-houses-eat.md b/.changeset/breezy-houses-eat.md new file mode 100644 index 0000000000..184f7ecf45 --- /dev/null +++ b/.changeset/breezy-houses-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Add support for translation extensions. diff --git a/packages/frontend-app-api/src/extensions/Core.tsx b/packages/frontend-app-api/src/extensions/Core.tsx index 472ed4905a..0f6df3e8b2 100644 --- a/packages/frontend-app-api/src/extensions/Core.tsx +++ b/packages/frontend-app-api/src/extensions/Core.tsx @@ -18,6 +18,7 @@ import { coreExtensionData, createExtension, createExtensionInput, + createTranslationExtension, } from '@backstage/frontend-plugin-api'; export const Core = createExtension({ @@ -33,6 +34,9 @@ export const Core = createExtension({ components: createExtensionInput({ component: coreExtensionData.component, }), + translations: createExtensionInput({ + translation: createTranslationExtension.translationDataRef, + }), root: createExtensionInput( { element: coreExtensionData.reactElement, diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index f58dfb3159..e8bb401504 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -23,6 +23,7 @@ import { ComponentRef, componentsApiRef, coreExtensionData, + createTranslationExtension, ExtensionDataRef, ExtensionOverrides, RouteRef, @@ -415,6 +416,16 @@ function createApiHolder( ?.map(e => e.instance?.getData(coreExtensionData.theme)) .filter((x): x is AppTheme => !!x) ?? []; + const translationResources = + tree.root.edges.attachments + .get('translations') + ?.map(e => + e.instance?.getData(createTranslationExtension.translationDataRef), + ) + .filter( + (x): x is typeof createTranslationExtension.translationDataRef.T => !!x, + ) ?? []; + for (const factory of [...defaultApis, ...pluginApis]) { factoryRegistry.register('default', factory); } @@ -471,15 +482,6 @@ function createApiHolder( factory: () => AppLanguageSelector.createWithStorage(), }); - factoryRegistry.register('default', { - api: translationApiRef, - deps: { languageApi: appLanguageApiRef }, - factory: ({ languageApi }) => - I18nextTranslationApi.create({ - languageApi, - }), - }); - factoryRegistry.register('static', { api: configApiRef, deps: {}, @@ -492,12 +494,13 @@ function createApiHolder( factory: () => AppLanguageSelector.createWithStorage(), }); - factoryRegistry.register('default', { + factoryRegistry.register('static', { api: translationApiRef, deps: { languageApi: appLanguageApiRef }, factory: ({ languageApi }) => I18nextTranslationApi.create({ languageApi, + resources: translationResources, }), }); diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index c491579eaf..c5b586615f 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -74,6 +74,8 @@ import { SignInPageProps } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; import { storageApiRef } from '@backstage/core-plugin-api'; import { StorageValueSnapshot } from '@backstage/core-plugin-api'; +import { TranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { TranslationResource } from '@backstage/core-plugin-api/alpha'; import { TypesToApiRefs } from '@backstage/core-plugin-api'; import { useApi } from '@backstage/core-plugin-api'; import { useApiHolder } from '@backstage/core-plugin-api'; @@ -628,6 +630,28 @@ export function createThemeExtension( theme: AppTheme, ): ExtensionDefinition; +// @public (undocumented) +export function createTranslationExtension(options: { + name?: string; + resource: TranslationResource | TranslationMessages; +}): ExtensionDefinition; + +// @public (undocumented) +export namespace createTranslationExtension { + const // (undocumented) + translationDataRef: ConfigurableExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + {} + >; +} + export { DiscoveryApi }; export { discoveryApiRef }; diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts new file mode 100644 index 0000000000..3a0eee91b5 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts @@ -0,0 +1,128 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createTranslationRef, + createTranslationMessages, + createTranslationResource, +} from '@backstage/core-plugin-api/alpha'; +import { createTranslationExtension } from './createTranslationExtension'; + +const translationRef = createTranslationRef({ + id: 'test', + messages: { + a: 'a', + b: 'b', + }, +}); + +describe('createTranslationExtension', () => { + it('creates a translation message extension', () => { + const messages = createTranslationMessages({ + ref: translationRef, + messages: { + a: 'A', + }, + }); + const extension = createTranslationExtension({ resource: messages }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + kind: 'translation', + namespace: 'test', + attachTo: { id: 'core', input: 'translations' }, + disabled: false, + inputs: {}, + output: { + resource: createTranslationExtension.translationDataRef, + }, + factory: expect.any(Function), + }); + + expect(extension.factory({} as any)).toEqual({ + resource: messages, + }); + }); + + it('creates a translation resource extension', () => { + const resource = createTranslationResource({ + ref: translationRef, + translations: { + sv: () => + Promise.resolve({ + default: createTranslationMessages({ + ref: translationRef, + messages: { + a: 'Ä', + b: 'Ö', + }, + }), + }), + }, + }); + const extension = createTranslationExtension({ resource }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + kind: 'translation', + namespace: 'test', + attachTo: { id: 'core', input: 'translations' }, + disabled: false, + inputs: {}, + output: { + resource: createTranslationExtension.translationDataRef, + }, + factory: expect.any(Function), + }); + + expect(extension.factory({} as any)).toEqual({ resource }); + }); + + it('creates a translation resource extension with a name', () => { + expect( + createTranslationExtension({ + name: 'sv', + resource: createTranslationResource({ + ref: translationRef, + translations: { + sv: () => + Promise.resolve({ + default: createTranslationMessages({ + ref: translationRef, + messages: { + a: 'Ä', + b: 'Ö', + }, + }), + }), + }, + }), + }), + ).toEqual({ + $$type: '@backstage/ExtensionDefinition', + kind: 'translation', + namespace: 'test', + name: 'sv', + attachTo: { id: 'core', input: 'translations' }, + disabled: false, + inputs: {}, + output: { + resource: createTranslationExtension.translationDataRef, + }, + factory: expect.any(Function), + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts new file mode 100644 index 0000000000..b120a15c8d --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createExtension, createExtensionDataRef } from '../wiring'; +import { + TranslationResource, + TranslationMessages, +} from '@backstage/core-plugin-api/alpha'; + +/** @public */ +export function createTranslationExtension(options: { + name?: string; + resource: TranslationResource | TranslationMessages; +}) { + return createExtension({ + kind: 'translation', + namespace: options.resource.id, + name: options.name, + attachTo: { id: 'core', input: 'translations' }, + output: { + resource: createTranslationExtension.translationDataRef, + }, + factory: () => ({ resource: options.resource }), + }); +} + +/** @public */ +export namespace createTranslationExtension { + export const translationDataRef = createExtensionDataRef< + TranslationResource | TranslationMessages + >('core.translationResource'); +} diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index a3be81f2a3..80ad0290dd 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -20,3 +20,4 @@ export { createNavItemExtension } from './createNavItemExtension'; export { createSignInPageExtension } from './createSignInPageExtension'; export { createThemeExtension } from './createThemeExtension'; export { createComponentExtension } from './createComponentExtension'; +export { createTranslationExtension } from './createTranslationExtension'; From 73246ec065e18ab354c307aeff6b326ef0c9899c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 25 Nov 2023 16:59:06 +0100 Subject: [PATCH 2/2] frontend-plugin-api: re-export translation APIs Signed-off-by: Patrik Oldsberg --- .changeset/six-cooks-attack.md | 5 ++++ packages/frontend-plugin-api/api-report.md | 28 +++++++++++++++++++ .../extensions/createTranslationExtension.ts | 5 +--- packages/frontend-plugin-api/src/index.ts | 1 + .../src/translation/index.ts | 28 +++++++++++++++++++ 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 .changeset/six-cooks-attack.md create mode 100644 packages/frontend-plugin-api/src/translation/index.ts diff --git a/.changeset/six-cooks-attack.md b/.changeset/six-cooks-attack.md new file mode 100644 index 0000000000..0182f1e06a --- /dev/null +++ b/.changeset/six-cooks-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added translation APIs as well as `createTranslationExtension`. diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index c5b586615f..52445923fe 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -31,6 +31,9 @@ import { ConfigApi } from '@backstage/core-plugin-api'; import { configApiRef } from '@backstage/core-plugin-api'; import { createApiFactory } from '@backstage/core-plugin-api'; import { createApiRef } from '@backstage/core-plugin-api'; +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { discoveryApiRef } from '@backstage/core-plugin-api'; import { ErrorApi } from '@backstage/core-plugin-api'; @@ -75,10 +78,15 @@ import { StorageApi } from '@backstage/core-plugin-api'; import { storageApiRef } from '@backstage/core-plugin-api'; import { StorageValueSnapshot } from '@backstage/core-plugin-api'; import { TranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { TranslationMessagesOptions } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRefOptions } from '@backstage/core-plugin-api/alpha'; import { TranslationResource } from '@backstage/core-plugin-api/alpha'; +import { TranslationResourceOptions } from '@backstage/core-plugin-api/alpha'; import { TypesToApiRefs } from '@backstage/core-plugin-api'; import { useApi } from '@backstage/core-plugin-api'; import { useApiHolder } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { withApis } from '@backstage/core-plugin-api'; import { z } from 'zod'; import { ZodSchema } from 'zod'; @@ -652,6 +660,12 @@ export namespace createTranslationExtension { >; } +export { createTranslationMessages }; + +export { createTranslationRef }; + +export { createTranslationResource }; + export { DiscoveryApi }; export { discoveryApiRef }; @@ -969,6 +983,18 @@ export interface SubRouteRef< readonly T: TParams; } +export { TranslationMessages }; + +export { TranslationMessagesOptions }; + +export { TranslationRef }; + +export { TranslationRefOptions }; + +export { TranslationResource }; + +export { TranslationResourceOptions }; + export { TypesToApiRefs }; // @public @@ -996,5 +1022,7 @@ export function useRouteRefParams( _routeRef: RouteRef | SubRouteRef, ): Params; +export { useTranslationRef }; + export { withApis }; ``` diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts index b120a15c8d..59cc87948f 100644 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts @@ -14,11 +14,8 @@ * limitations under the License. */ +import { TranslationMessages, TranslationResource } from '../translation'; import { createExtension, createExtensionDataRef } from '../wiring'; -import { - TranslationResource, - TranslationMessages, -} from '@backstage/core-plugin-api/alpha'; /** @public */ export function createTranslationExtension(options: { diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts index a38a7991fb..ca3b291d64 100644 --- a/packages/frontend-plugin-api/src/index.ts +++ b/packages/frontend-plugin-api/src/index.ts @@ -28,6 +28,7 @@ export * from './icons'; export * from './routing'; export * from './schema'; export * from './apis/system'; +export * from './translation'; export * from './wiring'; export type { diff --git a/packages/frontend-plugin-api/src/translation/index.ts b/packages/frontend-plugin-api/src/translation/index.ts new file mode 100644 index 0000000000..ef3ccccbe3 --- /dev/null +++ b/packages/frontend-plugin-api/src/translation/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + type TranslationMessages, + type TranslationMessagesOptions, + type TranslationResource, + type TranslationResourceOptions, + type TranslationRef, + type TranslationRefOptions, + createTranslationMessages, + createTranslationResource, + createTranslationRef, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha';