From a13d052a916064a1c5079cee954895bb956732db Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 11:22:26 +0200 Subject: [PATCH] core-plugin-api: move TranslationResource from app-api + refactor Signed-off-by: Patrik Oldsberg --- packages/core-app-api/src/alpha.ts | 1 - .../translation/TranslationResource.test.ts | 116 ++++++++++++++++++ .../src/translation/TranslationResource.ts | 104 ++++++++++++++++ .../translation/__fixtures__/counting-de.ts | 27 ++++ .../translation/__fixtures__/counting-sv.json | 5 + .../translation/__fixtures__/fruits-de.json | 4 + .../src/translation/__fixtures__/fruits-sv.ts | 26 ++++ .../src/translation/__fixtures__/refs.ts} | 35 +++--- 8 files changed, 301 insertions(+), 17 deletions(-) create mode 100644 packages/core-plugin-api/src/translation/TranslationResource.test.ts create mode 100644 packages/core-plugin-api/src/translation/TranslationResource.ts create mode 100644 packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts create mode 100644 packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json create mode 100644 packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json create mode 100644 packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts rename packages/{core-app-api/src/app/TranslationResource.ts => core-plugin-api/src/translation/__fixtures__/refs.ts} (57%) diff --git a/packages/core-app-api/src/alpha.ts b/packages/core-app-api/src/alpha.ts index 288676f509..41037a539c 100644 --- a/packages/core-app-api/src/alpha.ts +++ b/packages/core-app-api/src/alpha.ts @@ -14,4 +14,3 @@ * limitations under the License. */ export * from './apis/implementations/AppTranslationApi'; -export * from './app/TranslationResource'; diff --git a/packages/core-plugin-api/src/translation/TranslationResource.test.ts b/packages/core-plugin-api/src/translation/TranslationResource.test.ts new file mode 100644 index 0000000000..1814311f71 --- /dev/null +++ b/packages/core-plugin-api/src/translation/TranslationResource.test.ts @@ -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', + }, + }); + }); +}); diff --git a/packages/core-plugin-api/src/translation/TranslationResource.ts b/packages/core-plugin-api/src/translation/TranslationResource.ts new file mode 100644 index 0000000000..d10d1b80e3 --- /dev/null +++ b/packages/core-plugin-api/src/translation/TranslationResource.ts @@ -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 { + $$type: '@backstage/TranslationResource'; + id: TId; +} + +/** @internal */ +export interface InternalTranslationResource + extends TranslationResource { + version: 'v1'; + resources: Array<{ + language: string; + loader(): Promise<{ messages: TranslationMessages }>; + }>; +} + +/** @internal */ +export function toInternalTranslationResource( + resource: TranslationResource, +): InternalTranslationResource { + const r = resource as InternalTranslationResource; + 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 + | { [key in keyof TMessages]: string | null }; + }>; + }, +> { + ref: TranslationRef; + + translations: TTranslations; +} + +/** @alpha */ +export function createTranslationResource< + TId extends string, + TMessages extends { [key in string]: string }, + TTranslations extends { + [language in string]: () => Promise<{ + default: + | TranslationMessages + | { [key in keyof TMessages]: string | null }; + }>; + }, +>( + options: TranslationResourceOptions, +): TranslationResource { + 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; +} diff --git a/packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts b/packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts new file mode 100644 index 0000000000..038432141d --- /dev/null +++ b/packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts @@ -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', + }, +}); diff --git a/packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json b/packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json new file mode 100644 index 0000000000..fdfbde7bd8 --- /dev/null +++ b/packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json @@ -0,0 +1,5 @@ +{ + "one": "ett", + "two": "två", + "three": "tre" +} diff --git a/packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json b/packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json new file mode 100644 index 0000000000..11fb3e78f6 --- /dev/null +++ b/packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json @@ -0,0 +1,4 @@ +{ + "apple": "apfel", + "orange": "apfelsine" +} diff --git a/packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts b/packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts new file mode 100644 index 0000000000..f6c022abb4 --- /dev/null +++ b/packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts @@ -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', + }, +}); diff --git a/packages/core-app-api/src/app/TranslationResource.ts b/packages/core-plugin-api/src/translation/__fixtures__/refs.ts similarity index 57% rename from packages/core-app-api/src/app/TranslationResource.ts rename to packages/core-plugin-api/src/translation/__fixtures__/refs.ts index 65f1c4622c..45fc692c9a 100644 --- a/packages/core-app-api/src/app/TranslationResource.ts +++ b/packages/core-plugin-api/src/translation/__fixtures__/refs.ts @@ -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 extends TranslationRef - ? Partial - : never; +export const countingTranslationRef = createTranslationRef({ + id: 'counting', + messages: { + one: 'one', + two: 'two', + three: 'three', + }, +}); -/** @alpha */ -export function createTranslationResource(options: { - ref: T; - messages?: Record>; - lazyMessages: Record< - string, - () => Promise<{ messages: TranslationMessages }> - >; -}) { - return options; -} +export const fruitsTranslationRef = createTranslationRef({ + id: 'fruits', + messages: { + apple: 'apple', + orange: 'orange', + }, + translations: { + de: () => import('./fruits-de.json'), + }, +});