frontend-plugin-api: add createTranslationExtension

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-11-25 16:31:09 +01:00
parent 0e1a53db1b
commit aeb80085cc
7 changed files with 220 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
Add support for translation extensions.
@@ -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,
@@ -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,
}),
});
@@ -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<never>;
// @public (undocumented)
export function createTranslationExtension(options: {
name?: string;
resource: TranslationResource | TranslationMessages;
}): ExtensionDefinition<never>;
// @public (undocumented)
export namespace createTranslationExtension {
const // (undocumented)
translationDataRef: ConfigurableExtensionDataRef<
| TranslationResource<string>
| TranslationMessages<
string,
{
[x: string]: string;
},
boolean
>,
{}
>;
}
export { DiscoveryApi };
export { discoveryApiRef };
@@ -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),
});
});
});
@@ -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');
}
@@ -20,3 +20,4 @@ export { createNavItemExtension } from './createNavItemExtension';
export { createSignInPageExtension } from './createSignInPageExtension';
export { createThemeExtension } from './createThemeExtension';
export { createComponentExtension } from './createComponentExtension';
export { createTranslationExtension } from './createTranslationExtension';