core-plugin-api: move TranslationResource from app-api + refactor

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-12 11:22:26 +02:00
parent 7d85a9da3c
commit a13d052a91
8 changed files with 301 additions and 17 deletions
-1
View File
@@ -14,4 +14,3 @@
* limitations under the License.
*/
export * from './apis/implementations/AppTranslationApi';
export * from './app/TranslationResource';
@@ -0,0 +1,116 @@
/*
* 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 {
createTranslationResource,
toInternalTranslationResource,
} from './TranslationResource';
import { countingTranslationRef } from './__fixtures__/refs';
describe('createTranslationResource', () => {
it('should create a simple resource', async () => {
const resource = createTranslationResource({
ref: countingTranslationRef,
translations: {
sv: () =>
Promise.resolve({
default: {
one: 'ett',
two: 'två',
three: 'tre',
},
}),
},
});
expect(toInternalTranslationResource(resource)).toEqual({
$$type: '@backstage/TranslationResource',
version: 'v1',
id: 'counting',
resources: [
{
language: 'sv',
loader: expect.any(Function),
},
],
});
await expect(
toInternalTranslationResource(resource).resources[0].loader(),
).resolves.toEqual({
messages: {
one: 'ett',
two: 'två',
three: 'tre',
},
});
});
it('should create a resource with lazy loaded messages', async () => {
const resource = createTranslationResource({
ref: countingTranslationRef,
translations: {
de: () => import('./__fixtures__/counting-de'),
sv: () => import('./__fixtures__/counting-sv.json'),
// @ts-expect-error
deBad: () => import('./__fixtures__/fruits-de.json'),
// @ts-expect-error
svBad: () => import('./__fixtures__/fruits-sv'),
},
});
expect(toInternalTranslationResource(resource)).toEqual({
$$type: '@backstage/TranslationResource',
version: 'v1',
id: 'counting',
resources: [
{
language: 'de',
loader: expect.any(Function),
},
{
language: 'sv',
loader: expect.any(Function),
},
{
language: 'deBad',
loader: expect.any(Function),
},
{
language: 'svBad',
loader: expect.any(Function),
},
],
});
await expect(
toInternalTranslationResource(resource).resources[0].loader(),
).resolves.toEqual({
messages: {
one: 'eins',
two: 'zwei',
three: 'polizei',
},
});
await expect(
toInternalTranslationResource(resource).resources[1].loader(),
).resolves.toEqual({
messages: {
one: 'ett',
two: 'två',
three: 'tre',
},
});
});
});
@@ -0,0 +1,104 @@
/*
* 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 {
TranslationMessages,
TranslationRef,
} from '@backstage/core-plugin-api/alpha';
/** @alpha */
export interface TranslationResource<TId extends string = string> {
$$type: '@backstage/TranslationResource';
id: TId;
}
/** @internal */
export interface InternalTranslationResource<TId extends string = string>
extends TranslationResource<TId> {
version: 'v1';
resources: Array<{
language: string;
loader(): Promise<{ messages: TranslationMessages<TId> }>;
}>;
}
/** @internal */
export function toInternalTranslationResource<TId extends string>(
resource: TranslationResource<TId>,
): InternalTranslationResource<TId> {
const r = resource as InternalTranslationResource<TId>;
if (r.$$type !== '@backstage/TranslationResource') {
throw new Error(`Invalid translation resource, bad type '${r.$$type}'`);
}
if (r.version !== 'v1') {
throw new Error(`Invalid translation resource, bad version '${r.version}'`);
}
return r;
}
/** @alpha */
export interface TranslationResourceOptions<
TId extends string,
TMessages extends { [key in string]: string },
TTranslations extends {
[language in string]: () => Promise<{
default:
| TranslationMessages<TId>
| { [key in keyof TMessages]: string | null };
}>;
},
> {
ref: TranslationRef<TId, TMessages>;
translations: TTranslations;
}
/** @alpha */
export function createTranslationResource<
TId extends string,
TMessages extends { [key in string]: string },
TTranslations extends {
[language in string]: () => Promise<{
default:
| TranslationMessages<TId>
| { [key in keyof TMessages]: string | null };
}>;
},
>(
options: TranslationResourceOptions<TId, TMessages, TTranslations>,
): TranslationResource<TId> {
return {
$$type: '@backstage/TranslationResource',
version: 'v1',
id: options.ref.id,
resources: Object.entries(options.translations).map(
([language, loader]) => ({
language,
loader: () =>
loader().then(m => {
const value = m.default;
return {
messages:
value?.$$type === '@backstage/TranslationMessages'
? value.messages
: value,
};
}),
}),
),
} as InternalTranslationResource<TId>;
}
@@ -0,0 +1,27 @@
/*
* 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 { createTranslationMessages } from '../TranslationMessages';
import { countingTranslationRef } from './refs';
export default createTranslationMessages({
ref: countingTranslationRef,
messages: {
one: 'eins',
two: 'zwei',
three: 'polizei',
},
});
@@ -0,0 +1,5 @@
{
"one": "ett",
"two": "två",
"three": "tre"
}
@@ -0,0 +1,4 @@
{
"apple": "apfel",
"orange": "apfelsine"
}
@@ -0,0 +1,26 @@
/*
* 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 { createTranslationMessages } from '../TranslationMessages';
import { fruitsTranslationRef } from './refs';
export default createTranslationMessages({
ref: fruitsTranslationRef,
messages: {
apple: 'äpple',
orange: 'apelsin',
},
});
@@ -14,21 +14,24 @@
* limitations under the License.
*/
import { TranslationRef } from '@backstage/core-plugin-api/alpha';
import { createTranslationRef } from '../TranslationRef';
/** @alpha */
export type TranslationMessages<T> = T extends TranslationRef<infer R>
? Partial<R>
: never;
export const countingTranslationRef = createTranslationRef({
id: 'counting',
messages: {
one: 'one',
two: 'two',
three: 'three',
},
});
/** @alpha */
export function createTranslationResource<T extends TranslationRef>(options: {
ref: T;
messages?: Record<string, TranslationMessages<T>>;
lazyMessages: Record<
string,
() => Promise<{ messages: TranslationMessages<T> }>
>;
}) {
return options;
}
export const fruitsTranslationRef = createTranslationRef({
id: 'fruits',
messages: {
apple: 'apple',
orange: 'orange',
},
translations: {
de: () => import('./fruits-de.json'),
},
});