From 316c610649730c903d7db234f9183e48ad173caf Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Sep 2023 09:57:48 +0200 Subject: [PATCH 01/16] core-plugin-api: refactor translation ref to be opaque + fix some types Signed-off-by: Patrik Oldsberg --- packages/core-plugin-api/alpha-api-report.md | 95 +++++-------- .../src/apis/definitions/AppTranslationApi.ts | 6 +- .../src/translation/TranslationRef.test.ts | 27 ++-- .../src/translation/TranslationRef.ts | 133 +++++++++++++----- .../core-plugin-api/src/translation/index.ts | 12 +- .../core-plugin-api/src/translation/types.ts | 45 ------ .../src/translation/useTranslationRef.ts | 23 +-- 7 files changed, 170 insertions(+), 171 deletions(-) delete mode 100644 packages/core-plugin-api/src/translation/types.ts diff --git a/packages/core-plugin-api/alpha-api-report.md b/packages/core-plugin-api/alpha-api-report.md index 57cf4a2cb4..119f5c570e 100644 --- a/packages/core-plugin-api/alpha-api-report.md +++ b/packages/core-plugin-api/alpha-api-report.md @@ -11,8 +11,8 @@ import { ReactNode } from 'react'; // @alpha (undocumented) export type AppTranslationApi = { getI18n(): i18n; - addResourcesByRef>( - translationRef: TranslationRef, + addResourcesByRef>( + translationRef: TranslationRef, ): void; }; @@ -20,11 +20,14 @@ export type AppTranslationApi = { export const appTranslationApiRef: ApiRef; // @alpha (undocumented) -export const createTranslationRef: < - Messages extends Record = {}, +export function createTranslationRef< + TMessages extends { + [key in string]: string; + }, + TId extends string = string, >( - config: TranslationRefConfig, -) => TranslationRef; + config: TranslationRefOptions, +): TranslationRef; // @alpha export interface PluginOptionsProviderProps { @@ -38,75 +41,45 @@ export interface PluginOptionsProviderProps { export const PluginProvider: (props: PluginOptionsProviderProps) => JSX.Element; // @alpha (undocumented) -export type TranslationOptions< - Messages extends Record = Record, -> = Messages; +export interface TranslationOptions {} // @alpha (undocumented) export interface TranslationRef< - Messages extends Record = Record, + TMessages extends { + [key in string]: string; + } = { + [key in string]: string; + }, + TId extends string = string, > { // (undocumented) - getDefaultMessages(): Messages; + $$type: '@backstage/TranslationRef'; // (undocumented) - getId(): string; + id: TId; // (undocumented) - getLazyResources(): - | Record< - string, - () => Promise<{ - messages: Messages; - }> - > - | undefined; - // (undocumented) - getResources(): Record | undefined; + T: TMessages; } // @alpha (undocumented) -export interface TranslationRefConfig< - Messages extends Record, +export interface TranslationRefOptions< + TMessages extends { + [key in string]: string; + }, + TId extends string = string, > { // (undocumented) - id: string; + id: TId; // (undocumented) lazyResources?: Record< string, () => Promise<{ - messages: Messages; + messages: TMessages; }> >; // (undocumented) - messages: Messages; + messages: TMessages; // (undocumented) - resources?: Record; -} - -// @alpha (undocumented) -export class TranslationRefImpl> - implements TranslationRef -{ - // (undocumented) - static create>( - config: TranslationRefConfig, - ): TranslationRefImpl; - // (undocumented) - getDefaultMessages(): Messages; - // (undocumented) - getId(): string; - // (undocumented) - getLazyResources(): - | Record< - string, - () => Promise<{ - messages: Messages; - }> - > - | undefined; - // (undocumented) - getResources(): Record | undefined; - // (undocumented) - toString(): string; + resources?: Record; } // @alpha @@ -116,13 +89,15 @@ export function usePluginOptions< // @alpha (undocumented) export const useTranslationRef: < - Messages extends Record, + TMessages extends { + [x: string]: string; + }, >( - translationRef: TranslationRef, -) => ( - key: Tkey, + translationRef: TranslationRef, +) => ( + key: TKey, options?: TranslationOptions, -) => Messages[Tkey]; +) => TMessages[TKey]; // (No @packageDocumentation comment for this package) ``` diff --git a/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts b/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts index 463aee02bf..b94e5add87 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts @@ -22,8 +22,8 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; export type AppTranslationApi = { getI18n(): i18n; - addResourcesByRef>( - translationRef: TranslationRef, + addResourcesByRef>( + translationRef: TranslationRef, ): void; }; @@ -31,5 +31,5 @@ export type AppTranslationApi = { * @alpha */ export const appTranslationApiRef: ApiRef = createApiRef({ - id: 'core.apptranslation', + id: 'core.translation', }); diff --git a/packages/core-plugin-api/src/translation/TranslationRef.test.ts b/packages/core-plugin-api/src/translation/TranslationRef.test.ts index 155624a122..41d5d9ab85 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.test.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.test.ts @@ -13,30 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TranslationRefImpl, createTranslationRef } from './TranslationRef'; +import { + createTranslationRef, + toInternalTranslationRef, +} from './TranslationRef'; describe('TranslationRefImpl', () => { - it('should create a TranslationRef instance', () => { - const config = { - id: 'testId', - messages: { key: 'value' }, - }; - - const translationRef = TranslationRefImpl.create(config); - - expect(translationRef.getId()).toBe('testId'); - expect(translationRef.getDefaultMessages()).toEqual({ key: 'value' }); - }); - it('should create a TranslationRef instance using the factory function', () => { const config = { id: 'testId', messages: { key: 'value' }, }; - const translationRef = createTranslationRef(config); + const translationRef = toInternalTranslationRef( + createTranslationRef(config), + ); - expect(translationRef.getId()).toBe('testId'); + expect(translationRef.id).toBe('testId'); expect(translationRef.getDefaultMessages()).toEqual({ key: 'value' }); }); @@ -49,7 +42,9 @@ describe('TranslationRefImpl', () => { }, }; - const translationRef = TranslationRefImpl.create(config); + const translationRef = toInternalTranslationRef( + createTranslationRef(config), + ); const lazyResources = translationRef.getLazyResources(); diff --git a/packages/core-plugin-api/src/translation/TranslationRef.ts b/packages/core-plugin-api/src/translation/TranslationRef.ts index d01f7ca261..2a135fe825 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.ts @@ -14,48 +14,109 @@ * limitations under the License. */ -import { TranslationRef, TranslationRefConfig } from './types'; - /** @alpha */ -export class TranslationRefImpl> - implements TranslationRef -{ - static create>( - config: TranslationRefConfig, - ) { - return new TranslationRefImpl(config); - } +export interface TranslationRef< + TMessages extends { [key in string]: string } = { [key in string]: string }, + TId extends string = string, +> { + $$type: '@backstage/TranslationRef'; - getId() { - return this.config.id; - } + id: TId; - getDefaultMessages(): Messages { - return this.config.messages; - } + T: TMessages; +} + +/** @internal */ +type AnyMessages = { [key in string]: string }; + +/** @internal */ +export interface InternalTranslationRef< + TMessages extends { [key in string]: string } = { [key in string]: string }, + TId extends string = string, +> extends TranslationRef { + version: 'v1'; + + getDefaultMessages(): AnyMessages; + + getResources(): Record | undefined; getLazyResources(): - | Record Promise<{ messages: Messages }>> - | undefined { - return this.config.lazyResources; - } - - getResources(): Record | undefined { - return this.config.resources; - } - - toString() { - return `TranslationRef(${this.getId()})`; - } - - private constructor( - private readonly config: TranslationRefConfig, - ) {} + | Record Promise<{ messages: AnyMessages }>> + | undefined; } /** @alpha */ -export const createTranslationRef = < - Messages extends Record = {}, +export interface TranslationRefOptions< + TMessages extends { [key in string]: string }, + TId extends string = string, +> { + id: TId; + messages: TMessages; + lazyResources?: Record Promise<{ messages: TMessages }>>; + resources?: Record; +} + +/** @internal */ +class TranslationRefImpl< + TMessages extends { [key in string]: string }, + TId extends string = string, +> implements InternalTranslationRef +{ + constructor( + private readonly options: TranslationRefOptions, + ) {} + + $$type = '@backstage/TranslationRef' as const; + + version = 'v1' as const; + + get id(): TId { + return this.options.id; + } + + get T(): never { + throw new Error('Not implemented'); + } + + getDefaultMessages(): AnyMessages { + return this.options.messages; + } + + getLazyResources(): + | Record Promise<{ messages: AnyMessages }>> + | undefined { + return this.options.lazyResources; + } + + getResources(): Record | undefined { + return this.options.resources; + } + + toString() { + return `TranslationRef{id=${this.id}}`; + } +} + +/** @alpha */ +export function createTranslationRef< + TMessages extends { [key in string]: string }, + TId extends string = string, >( - config: TranslationRefConfig, -): TranslationRef => TranslationRefImpl.create(config); + config: TranslationRefOptions, +): TranslationRef { + return new TranslationRefImpl(config); +} + +/** @internal */ +export function toInternalTranslationRef( + ref: TranslationRef, +): InternalTranslationRef { + const r = ref as InternalTranslationRef; + if (r.$$type !== '@backstage/TranslationRef') { + throw new Error(`Invalid translation ref, bad type '${r.$$type}'`); + } + if (r.version !== 'v1') { + throw new Error(`Invalid translation ref, bad version '${r.version}'`); + } + return r; +} diff --git a/packages/core-plugin-api/src/translation/index.ts b/packages/core-plugin-api/src/translation/index.ts index 9eaef57206..6c97fb772e 100644 --- a/packages/core-plugin-api/src/translation/index.ts +++ b/packages/core-plugin-api/src/translation/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ -export * from './TranslationRef'; -export * from './types'; -export * from './useTranslationRef'; +export { + type TranslationRef, + type TranslationRefOptions, + createTranslationRef, +} from './TranslationRef'; +export { + type TranslationOptions, + useTranslationRef, +} from './useTranslationRef'; diff --git a/packages/core-plugin-api/src/translation/types.ts b/packages/core-plugin-api/src/translation/types.ts deleted file mode 100644 index 4811c82eb7..0000000000 --- a/packages/core-plugin-api/src/translation/types.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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. - */ - -/** @alpha */ -export interface TranslationRefConfig< - Messages extends Record, -> { - id: string; - messages: Messages; - lazyResources?: Record Promise<{ messages: Messages }>>; - resources?: Record; -} - -/** @alpha */ -export interface TranslationRef< - Messages extends Record = Record, -> { - getId(): string; - - getDefaultMessages(): Messages; - - getResources(): Record | undefined; - - getLazyResources(): - | Record Promise<{ messages: Messages }>> - | undefined; -} - -/** @alpha */ -export type TranslationOptions< - Messages extends Record = Record, -> = Messages; diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.ts b/packages/core-plugin-api/src/translation/useTranslationRef.ts index 083bcd1f0e..f88cba36a4 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.ts +++ b/packages/core-plugin-api/src/translation/useTranslationRef.ts @@ -16,26 +16,33 @@ import { useTranslation } from 'react-i18next'; -import { TranslationOptions, TranslationRef } from './types'; import { useApi } from '../apis'; import { appTranslationApiRef } from '../apis/alpha'; +import { toInternalTranslationRef, TranslationRef } from './TranslationRef'; + +/** @alpha */ +export interface TranslationOptions { + /* no options supported for now */ +} /** @alpha */ export const useTranslationRef = < - Messages extends Record, + TMessages extends { [key in string]: string }, >( - translationRef: TranslationRef, + translationRef: TranslationRef, ) => { const appTranslationApi = useApi(appTranslationApiRef); appTranslationApi.addResourcesByRef(translationRef); - const { t } = useTranslation(translationRef.getId()); + const internalRef = toInternalTranslationRef(translationRef); - const defaulteMessage = translationRef.getDefaultMessages(); + const { t } = useTranslation(internalRef.id); - return ( - key: Tkey, + const defaultMessages = internalRef.getDefaultMessages(); + + return ( + key: TKey, options?: TranslationOptions, - ): Messages[Tkey] => t(key as string, defaulteMessage[key], options); + ): TMessages[TKey] => t(key, defaultMessages[key], options); }; From 5c3d68fce2f49a32a9207d6c838b90ff9d41856f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Sep 2023 10:15:53 +0200 Subject: [PATCH 02/16] core-plugin-api: avoid using suspense mode of useTranslation in tests Signed-off-by: Patrik Oldsberg --- packages/core-plugin-api/src/translation/useTranslationRef.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.ts b/packages/core-plugin-api/src/translation/useTranslationRef.ts index f88cba36a4..605a220914 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.ts +++ b/packages/core-plugin-api/src/translation/useTranslationRef.ts @@ -37,7 +37,9 @@ export const useTranslationRef = < const internalRef = toInternalTranslationRef(translationRef); - const { t } = useTranslation(internalRef.id); + const { t } = useTranslation(internalRef.id, { + useSuspense: process.env.NODE_ENV !== 'test', + }); const defaultMessages = internalRef.getDefaultMessages(); From de3904a5112a987060c795133dfb4a5180e6939c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Sep 2023 10:36:36 +0200 Subject: [PATCH 03/16] user-settings: cleanup test + declare translations as const Signed-off-by: Patrik Oldsberg --- plugins/user-settings/alpha-api-report.md | 33 ++++++++++--------- .../General/UserSettingsThemeToggle.test.tsx | 16 --------- plugins/user-settings/src/translation.ts | 2 +- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/plugins/user-settings/alpha-api-report.md b/plugins/user-settings/alpha-api-report.md index 925d387882..673763745f 100644 --- a/plugins/user-settings/alpha-api-report.md +++ b/plugins/user-settings/alpha-api-report.md @@ -6,21 +6,24 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) -export const userSettingsTranslationRef: TranslationRef<{ - language: string; - change_the_language: string; - theme: string; - theme_light: string; - theme_dark: string; - theme_auto: string; - change_the_theme_mode: string; - select_theme_light: string; - select_theme_dark: string; - select_theme_auto: string; - select_theme_custom: string; - lng: string; - select_lng: string; -}>; +export const userSettingsTranslationRef: TranslationRef< + { + readonly language: 'Language'; + readonly change_the_language: 'Change the language'; + readonly theme: 'Theme'; + readonly theme_light: 'Light'; + readonly theme_dark: 'Dark'; + readonly theme_auto: 'Auto'; + readonly change_the_theme_mode: 'Change the theme mode'; + readonly select_theme_light: 'Select light'; + readonly select_theme_dark: 'Select dark'; + readonly select_theme_auto: 'Select Auto Theme'; + readonly select_theme_custom: 'Select {{custom}}'; + readonly lng: '{{language}}'; + readonly select_lng: 'Select language {{language}}'; + }, + 'user-settings' +>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/user-settings/src/components/General/UserSettingsThemeToggle.test.tsx b/plugins/user-settings/src/components/General/UserSettingsThemeToggle.test.tsx index 48d16a9fde..0e07fa8c59 100644 --- a/plugins/user-settings/src/components/General/UserSettingsThemeToggle.test.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsThemeToggle.test.tsx @@ -15,7 +15,6 @@ */ import { AppTheme, appThemeApiRef } from '@backstage/core-plugin-api'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { renderWithEffects, TestApiRegistry, @@ -28,7 +27,6 @@ import { fireEvent } from '@testing-library/react'; import React from 'react'; import { UserSettingsThemeToggle } from './UserSettingsThemeToggle'; import { ApiProvider, AppThemeSelector } from '@backstage/core-app-api'; -import { userSettingsTranslationRef } from '../../translation'; const mockTheme: AppTheme = { id: 'light-theme', @@ -41,11 +39,6 @@ const mockTheme: AppTheme = { ), }; -jest.mock('@backstage/core-plugin-api/alpha', () => ({ - ...jest.requireActual('@backstage/core-plugin-api/alpha'), - useTranslationRef: jest.fn(), -})); - const apiRegistry = TestApiRegistry.from([ appThemeApiRef, AppThemeSelector.createWithStorage([mockTheme]), @@ -54,15 +47,6 @@ const apiRegistry = TestApiRegistry.from([ describe('', () => { it('toggles the theme select button', async () => { const themeApi = apiRegistry.get(appThemeApiRef); - // todo: general test provider - const messages: Record = - userSettingsTranslationRef.getDefaultMessages(); - - const useTranslationRefMock = jest - .fn() - .mockReturnValue((key: string) => messages[key]); - - (useTranslationRef as jest.Mock).mockImplementation(useTranslationRefMock); const rendered = await renderWithEffects( wrapInTestApp( diff --git a/plugins/user-settings/src/translation.ts b/plugins/user-settings/src/translation.ts index 920f499082..ffe4174d99 100644 --- a/plugins/user-settings/src/translation.ts +++ b/plugins/user-settings/src/translation.ts @@ -33,5 +33,5 @@ export const userSettingsTranslationRef = createTranslationRef({ select_theme_custom: 'Select {{custom}}', lng: '{{language}}', select_lng: 'Select language {{language}}', - }, + } as const, }); From a75247a722894bbfd2d17baf966eda3801bb151a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Sep 2023 10:37:05 +0200 Subject: [PATCH 04/16] core-app-api: updates for new translation ref type Signed-off-by: Patrik Oldsberg --- .../AppTranslationApi/AppTranslationImpl.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts b/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts index 69820e7b67..519a406628 100644 --- a/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts +++ b/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts @@ -23,6 +23,9 @@ import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import { TranslationMessages } from '../../../alpha'; +// Internal import to avoid code duplication, this will lead to duplication in build output +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalTranslationRef } from '../../../../../core-plugin-api/src/translation/TranslationRef'; /** @alpha */ export type ExperimentalI18n = { @@ -94,15 +97,16 @@ export class AppTranslationApiImpl implements AppTranslationApi { TranslationMessages> >, ) { - const resources = initResources || translationRef.getResources(); - if (!resources || this.cache.has(translationRef)) { + const internalRef = toInternalTranslationRef(translationRef); + const resources = initResources || internalRef.getResources(); + if (!resources || this.cache.has(internalRef)) { return; } - this.cache.add(translationRef); + this.cache.add(internalRef); Object.entries(resources).forEach(([language, messages]) => { this.i18n.addResourceBundle( language, - translationRef.getId(), + internalRef.id, messages, true, false, @@ -136,8 +140,9 @@ export class AppTranslationApiImpl implements AppTranslationApi { return; } - const namespace = translationRef.getId(); - const lazyResources = initResources || translationRef.getLazyResources(); + const internalRef = toInternalTranslationRef(translationRef); + const namespace = internalRef.id; + const lazyResources = initResources || internalRef.getLazyResources(); Promise.allSettled((options.supportedLngs || []).map(addLanguage)).then( results => { From 2fcadae9df42e0decb73127828f688125c3153c1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 11:19:52 +0200 Subject: [PATCH 05/16] core-plugin-api: additional translation ref refactor Signed-off-by: Patrik Oldsberg --- .../src/translation/TranslationRef.test.ts | 62 +++++++++----- .../src/translation/TranslationRef.ts | 80 ++++++++++++------- 2 files changed, 92 insertions(+), 50 deletions(-) diff --git a/packages/core-plugin-api/src/translation/TranslationRef.test.ts b/packages/core-plugin-api/src/translation/TranslationRef.test.ts index 41d5d9ab85..b73b521774 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.test.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.test.ts @@ -17,39 +17,57 @@ import { createTranslationRef, toInternalTranslationRef, } from './TranslationRef'; +import { toInternalTranslationResource } from './TranslationResource'; describe('TranslationRefImpl', () => { it('should create a TranslationRef instance using the factory function', () => { - const config = { - id: 'testId', + const ref = createTranslationRef({ + id: 'test', messages: { key: 'value' }, - }; + }); - const translationRef = toInternalTranslationRef( - createTranslationRef(config), - ); + const internalRef = toInternalTranslationRef(ref); - expect(translationRef.id).toBe('testId'); - expect(translationRef.getDefaultMessages()).toEqual({ key: 'value' }); + expect(internalRef.$$type).toBe('@backstage/TranslationRef'); + expect(internalRef.version).toBe('v1'); + expect(internalRef.id).toBe('test'); + expect(internalRef.getDefaultMessages()).toEqual({ key: 'value' }); }); - it('should get lazy resources', async () => { - const config = { - id: 'testId', + it('should be created with lazy translations', async () => { + const ref = createTranslationRef({ + id: 'test', messages: { key: 'value' }, - lazyResources: { - en: () => Promise.resolve({ messages: { key: 'value' } }), + translations: { + de: () => Promise.resolve({ default: { key: 'other-value' } }), }, - }; + }); - const translationRef = toInternalTranslationRef( - createTranslationRef(config), + const internalRef = toInternalTranslationRef(ref); + + expect(internalRef.$$type).toBe('@backstage/TranslationRef'); + expect(internalRef.version).toBe('v1'); + expect(internalRef.id).toBe('test'); + expect(internalRef.getDefaultMessages()).toEqual({ key: 'value' }); + + const internalResource = toInternalTranslationResource( + internalRef.getDefaultResource()!, ); - - const lazyResources = translationRef.getLazyResources(); - - const messages = await lazyResources?.en(); - - expect(messages).toEqual({ messages: { key: 'value' } }); + expect(internalResource).toEqual({ + $$type: '@backstage/TranslationResource', + version: 'v1', + id: 'test', + resources: [ + { + language: 'de', + loader: expect.any(Function), + }, + ], + }); + await expect(internalResource.resources[0].loader()).resolves.toEqual({ + messages: { + key: 'other-value', + }, + }); }); }); diff --git a/packages/core-plugin-api/src/translation/TranslationRef.ts b/packages/core-plugin-api/src/translation/TranslationRef.ts index 2a135fe825..91b5d7850d 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.ts @@ -14,10 +14,15 @@ * limitations under the License. */ +import { + createTranslationResource, + TranslationResource, +} from './TranslationResource'; + /** @alpha */ export interface TranslationRef< - TMessages extends { [key in string]: string } = { [key in string]: string }, TId extends string = string, + TMessages extends { [key in string]: string } = { [key in string]: string }, > { $$type: '@backstage/TranslationRef'; @@ -31,47 +36,54 @@ type AnyMessages = { [key in string]: string }; /** @internal */ export interface InternalTranslationRef< - TMessages extends { [key in string]: string } = { [key in string]: string }, TId extends string = string, -> extends TranslationRef { + TMessages extends { [key in string]: string } = { [key in string]: string }, +> extends TranslationRef { version: 'v1'; getDefaultMessages(): AnyMessages; - getResources(): Record | undefined; - - getLazyResources(): - | Record Promise<{ messages: AnyMessages }>> - | undefined; + getDefaultResource(): TranslationResource | undefined; } +export type NoInfer = T extends infer S ? S : never; + /** @alpha */ export interface TranslationRefOptions< + TId extends string, TMessages extends { [key in string]: string }, - TId extends string = string, + TTranslations extends { + [language in string]: () => Promise<{ + default: { [key in keyof TMessages]: string | null }; + }>; + }, > { id: TId; messages: TMessages; - lazyResources?: Record Promise<{ messages: TMessages }>>; - resources?: Record; + translations?: TTranslations; } /** @internal */ class TranslationRefImpl< + TId extends string, TMessages extends { [key in string]: string }, - TId extends string = string, -> implements InternalTranslationRef +> implements InternalTranslationRef { - constructor( - private readonly options: TranslationRefOptions, - ) {} + #id: TId; + #messages: TMessages; + #resources: TranslationResource | undefined; + + constructor(options: TranslationRefOptions) { + this.#id = options.id; + this.#messages = options.messages; + } $$type = '@backstage/TranslationRef' as const; version = 'v1' as const; get id(): TId { - return this.options.id; + return this.#id; } get T(): never { @@ -79,17 +91,15 @@ class TranslationRefImpl< } getDefaultMessages(): AnyMessages { - return this.options.messages; + return this.#messages; } - getLazyResources(): - | Record Promise<{ messages: AnyMessages }>> - | undefined { - return this.options.lazyResources; + setDefaultResource(resources: TranslationResource): void { + this.#resources = resources; } - getResources(): Record | undefined { - return this.options.resources; + getDefaultResource(): TranslationResource | undefined { + return this.#resources; } toString() { @@ -99,12 +109,26 @@ class TranslationRefImpl< /** @alpha */ export function createTranslationRef< + TId extends string, TMessages extends { [key in string]: string }, - TId extends string = string, + TTranslations extends { + [language in string]: () => Promise<{ + default: { [key in keyof TMessages]: string | null }; + }>; + }, >( - config: TranslationRefOptions, -): TranslationRef { - return new TranslationRefImpl(config); + config: TranslationRefOptions, +): TranslationRef { + const ref = new TranslationRefImpl(config); + if (config.translations) { + ref.setDefaultResource( + createTranslationResource({ + ref, + translations: config.translations as any, + }), + ); + } + return ref; } /** @internal */ From 7d85a9da3cadd84c4cef8a4178de90d74e866efc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 11:20:30 +0200 Subject: [PATCH 06/16] core-plugin-api: add TranslationMessages Signed-off-by: Patrik Oldsberg --- .../translation/TranslationMessages.test.ts | 92 +++++++++++++++++++ .../src/translation/TranslationMessages.ts | 80 ++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 packages/core-plugin-api/src/translation/TranslationMessages.test.ts create mode 100644 packages/core-plugin-api/src/translation/TranslationMessages.ts diff --git a/packages/core-plugin-api/src/translation/TranslationMessages.test.ts b/packages/core-plugin-api/src/translation/TranslationMessages.test.ts new file mode 100644 index 0000000000..5314c8a054 --- /dev/null +++ b/packages/core-plugin-api/src/translation/TranslationMessages.test.ts @@ -0,0 +1,92 @@ +/* + * 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 { createTranslationRef } from './TranslationRef'; + +const ref = createTranslationRef({ + id: 'counting', + messages: { + one: 'one', + two: 'two', + three: 'three', + } as const, +}); + +describe('createTranslationMessages', () => { + it('should create a partial message set', () => { + expect( + createTranslationMessages({ + ref, + messages: { + one: 'uno', + }, + }), + ).toEqual({ + $$type: '@backstage/TranslationMessages', + id: 'counting', + full: false, + messages: { + one: 'uno', + }, + }); + }); + + it('should create a full message set', () => { + expect( + createTranslationMessages({ + ref, + full: true, + messages: { + one: 'uno', + two: 'dos', + three: null, + }, + }), + ).toEqual({ + $$type: '@backstage/TranslationMessages', + id: 'counting', + full: true, + messages: { + one: 'uno', + two: 'dos', + three: null, + }, + }); + }); + + it('should require all messages in a full set', () => { + expect( + createTranslationMessages({ + ref, + full: true, + // @ts-expect-error + messages: { + one: 'uno', + two: 'dos', + }, + }), + ).toEqual({ + $$type: '@backstage/TranslationMessages', + id: 'counting', + full: true, + messages: { + one: 'uno', + two: 'dos', + }, + }); + }); +}); diff --git a/packages/core-plugin-api/src/translation/TranslationMessages.ts b/packages/core-plugin-api/src/translation/TranslationMessages.ts new file mode 100644 index 0000000000..0108fb9f18 --- /dev/null +++ b/packages/core-plugin-api/src/translation/TranslationMessages.ts @@ -0,0 +1,80 @@ +/* + * 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 { TranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** + * Represents a collection of messages to be provided for a given translation ref. + * + * @alpha + * @remarks + * + * This collection of messages can either be used directly as an override for the + * default messages, or it can be used to provide translations for a language by + * by being referenced by a {@link TranslationResource}. + */ +export interface TranslationMessages< + TId extends string = string, + TMessages extends { [key in string]: string } = { [key in string]: string }, + TFull extends boolean = boolean, +> { + $$type: '@backstage/TranslationMessages'; + /** The ID of the translation ref that these messages are for */ + id: TId; + /** Whether or not these messages override all known messages */ + full: TFull; + /** The messages provided for the given translation ref */ + messages: TMessages; +} + +/** + * Options for {@link createTranslationMessages}. + * + * @alpha + */ +export interface TranslationMessagesOptions< + TId extends string, + TMessages extends { [key in string]: string }, + TFull extends boolean, +> { + ref: TranslationRef; + + full?: TFull; + + messages: false extends TFull + ? { [key in keyof TMessages]?: string | null } + : { [key in keyof TMessages]: string | null }; +} + +/** + * Creates a collection of messages for a given translation ref. + * + * @alpha + */ +export function createTranslationMessages< + TId extends string, + TMessages extends { [key in string]: string }, + TFull extends boolean, +>( + options: TranslationMessagesOptions, +): TranslationMessages { + return { + $$type: '@backstage/TranslationMessages', + id: options.ref.id, + full: Boolean(options.full) as TFull, + messages: options.messages as TMessages, + }; +} From a13d052a916064a1c5079cee954895bb956732db Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 11:22:26 +0200 Subject: [PATCH 07/16] 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'), + }, +}); From 2294b438dca1bc9248f9b636cee7db9961b1f16b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 11:23:07 +0200 Subject: [PATCH 08/16] core-plugin-api: export new translation APIs Signed-off-by: Patrik Oldsberg --- packages/core-plugin-api/src/translation/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/core-plugin-api/src/translation/index.ts b/packages/core-plugin-api/src/translation/index.ts index 6c97fb772e..a03bf4b1f7 100644 --- a/packages/core-plugin-api/src/translation/index.ts +++ b/packages/core-plugin-api/src/translation/index.ts @@ -14,6 +14,16 @@ * limitations under the License. */ +export { + type TranslationMessages, + type TranslationMessagesOptions, + createTranslationMessages, +} from './TranslationMessages'; +export { + type TranslationResource, + type TranslationResourceOptions, + createTranslationResource, +} from './TranslationResource'; export { type TranslationRef, type TranslationRefOptions, From d3c7b71929568509162370b61124e1589c67bb46 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 12:30:29 +0200 Subject: [PATCH 09/16] core-app-api: initial translation api refactor for new message/resource strucutre Signed-off-by: Patrik Oldsberg --- ...ion.test.ts => AppTranslationImpl.test.ts} | 118 ++++++------------ .../AppTranslationApi/AppTranslationImpl.ts | 115 ++++++++--------- packages/core-app-api/src/app/types.ts | 34 ++--- .../src/apis/definitions/AppTranslationApi.ts | 4 +- .../src/translation/useTranslationRef.ts | 7 +- 5 files changed, 105 insertions(+), 173 deletions(-) rename packages/core-app-api/src/apis/implementations/AppTranslationApi/{AppTranslation.test.ts => AppTranslationImpl.test.ts} (55%) diff --git a/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslation.test.ts b/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.test.ts similarity index 55% rename from packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslation.test.ts rename to packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.test.ts index b6eba8a1c7..5b7304d36b 100644 --- a/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslation.test.ts +++ b/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.test.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { + createTranslationMessages, + createTranslationRef, + createTranslationResource, +} from '@backstage/core-plugin-api/alpha'; import { AppTranslationApiImpl } from './AppTranslationImpl'; import i18next from 'i18next'; @@ -70,112 +74,68 @@ describe('AppTranslationApiImpl', () => { }); it('should init messages correctly', () => { - const useResourcesMock = jest.spyOn( + const addMessagesMock = jest.spyOn( AppTranslationApiImpl.prototype, - 'addResources', + 'addMessages', ); - const useLazyResourcesMock = jest.spyOn( + const addLazyResourcesMock = jest.spyOn( AppTranslationApiImpl.prototype, 'addLazyResources', ); const ref = createTranslationRef({ id: 'ref-id', messages: { - key1: '', + key: '', }, }); - const options = { - supportedLanguages: ['en'], - messages: [ - { - ref, - messages: { - en: { key1: 'value1' }, - }, - lazyMessages: { - en: () => Promise.resolve({ key2: 'value2' }), - } as any, - }, - ], - }; - - const instance = AppTranslationApiImpl.create({ - supportedLanguages: ['en'], + const overrides = createTranslationMessages({ + ref, + messages: { key: 'value1' }, + }); + const resource = createTranslationResource({ + ref, + translations: { + en: () => + Promise.resolve({ + default: { + key: 'value2', + }, + }), + }, }); - instance.initMessages(options); - expect(useResourcesMock).toHaveBeenCalledWith( - options.messages[0].ref, - options.messages[0].messages, - ); - expect(useLazyResourcesMock).toHaveBeenCalledWith( - options.messages[0].ref, - options.messages[0].lazyMessages, - ); + AppTranslationApiImpl.create({ + supportedLanguages: ['en'], + resources: [overrides, resource], + }); + + expect(addMessagesMock).toHaveBeenCalledWith(overrides); + expect(addLazyResourcesMock).toHaveBeenCalledWith(resource); }); it('should useResources correctly', () => { - const i18nMock = i18next.createInstance() as any; - const useReturnMock = i18nMock.use(); - jest.spyOn(i18nMock, 'use').mockReturnValue(useReturnMock); - jest.spyOn(i18next, 'createInstance').mockReturnValue(i18nMock); - - const ref = createTranslationRef({ - id: 'ref-id', - messages: { - key1: 'value1', - }, - resources: { - en: { - key1: 'value2', - }, - }, - }); - - const instance = AppTranslationApiImpl.create({ - supportedLanguages: ['en'], - }); - instance.addResources(ref); - - expect(useReturnMock.addResourceBundle).toHaveBeenCalledWith( - 'en', - 'ref-id', - { key1: 'value2' }, - true, - false, + const addLazyResourcesMock = jest.spyOn( + AppTranslationApiImpl.prototype, + 'addLazyResources', ); - }); - - it('should useLazyResources correctly', () => { - const i18nMock = i18next.createInstance() as any; - const useReturnMock = i18nMock.use(); - - jest.spyOn(i18nMock, 'use').mockReturnValue(useReturnMock); - jest.spyOn(i18next, 'createInstance').mockReturnValue(i18nMock); const ref = createTranslationRef({ id: 'ref-id', messages: { key1: 'value1', }, - lazyResources: { - en: () => Promise.resolve({ messages: { key1: 'value2' } }), + translations: { + en: () => Promise.resolve({ default: { key1: 'value2' } }), }, }); const instance = AppTranslationApiImpl.create({ supportedLanguages: ['en'], }); - instance.addLazyResources(ref); - setTimeout(() => { - expect(useReturnMock.addResourceBundle).toHaveBeenCalledWith( - 'en', - 'ref-id', - { key1: 'value2' }, - true, - false, - ); - }); + instance.addResource(ref); + + expect(addLazyResourcesMock).toHaveBeenCalledTimes(1); + expect(addLazyResourcesMock.mock.calls[0][0].id).toBe('ref-id'); }); }); diff --git a/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts b/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts index 519a406628..c55c8bd7e8 100644 --- a/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts +++ b/packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts @@ -16,31 +16,39 @@ import { AppTranslationApi, + TranslationMessages, TranslationRef, + TranslationResource, } from '@backstage/core-plugin-api/alpha'; import i18next, { type i18n } from 'i18next'; import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; -import { TranslationMessages } from '../../../alpha'; // Internal import to avoid code duplication, this will lead to duplication in build output // eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalTranslationResource } from '../../../../../core-plugin-api/src/translation/TranslationResource'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalTranslationRef } from '../../../../../core-plugin-api/src/translation/TranslationRef'; +const DEFAULT_LANGUAGE = 'en'; + /** @alpha */ export type ExperimentalI18n = { - supportedLanguages: string[]; fallbackLanguage?: string | string[]; - messages?: Array<{ - ref: TranslationRef; - messages?: Record>; - lazyMessages: Record< - string, - () => Promise<{ messages: TranslationMessages }> - >; - }>; + supportedLanguages?: string[]; + resources?: Array; }; +function removeNulls( + messages: Record, +): Record { + return Object.fromEntries( + Object.entries(messages).filter( + (e): e is [string, string] => e[1] !== null, + ), + ); +} + /** @alpha */ export class AppTranslationApiImpl implements AppTranslationApi { static create(options?: ExperimentalI18n) { @@ -49,8 +57,8 @@ export class AppTranslationApiImpl implements AppTranslationApi { i18n.use(LanguageDetector); i18n.init({ - fallbackLng: options?.fallbackLanguage || 'en', - supportedLngs: options?.supportedLanguages || ['en'], + fallbackLng: options?.fallbackLanguage || DEFAULT_LANGUAGE, + supportedLngs: options?.supportedLanguages || [DEFAULT_LANGUAGE], interpolation: { escapeValue: false, }, @@ -62,70 +70,52 @@ export class AppTranslationApiImpl implements AppTranslationApi { return new AppTranslationApiImpl(i18n, options); } - private readonly cache = new WeakSet(); - private readonly lazyCache = new WeakMap>(); + private readonly cache = new Set(); + private readonly lazyCache = new Map>(); getI18n() { return this.i18n; } initMessages(options?: ExperimentalI18n) { - if (options?.messages?.length) { - options.messages.forEach(appMessage => { - if (appMessage.messages) { - this.addResources(appMessage.ref, appMessage.messages); - } - - if (appMessage.lazyMessages) { - this.addLazyResources(appMessage.ref, appMessage.lazyMessages); - } - }); + for (const resource of options?.resources || []) { + if (resource.$$type === '@backstage/TranslationResource') { + this.addLazyResources(resource); + } else if (resource.$$type === '@backstage/TranslationMessages') { + // Overrides for default messages, created with createTranslationMessages and installed via app + this.addMessages(resource); + } } } - addResourcesByRef>( - translationRef: TranslationRef, - ): void { - this.addResources(translationRef); - this.addLazyResources(translationRef); + addResource(translationRef: TranslationRef): void { + const internalRef = toInternalTranslationRef(translationRef); + const defaultResource = internalRef.getDefaultResource(); + if (defaultResource) { + this.addLazyResources(defaultResource); + } } - addResources>( - translationRef: TranslationRef, - initResources?: Record< - string, - TranslationMessages> - >, - ) { - const internalRef = toInternalTranslationRef(translationRef); - const resources = initResources || internalRef.getResources(); - if (!resources || this.cache.has(internalRef)) { + addMessages(messages: TranslationMessages) { + if (this.cache.has(messages.id)) { return; } - this.cache.add(internalRef); - Object.entries(resources).forEach(([language, messages]) => { - this.i18n.addResourceBundle( - language, - internalRef.id, - messages, - true, - false, - ); - }); + this.cache.add(messages.id); + this.i18n.addResourceBundle( + DEFAULT_LANGUAGE, + messages.id, + removeNulls(messages.messages), + true, + false, + ); } - addLazyResources>( - translationRef: TranslationRef, - initResources?: Record< - string, - () => Promise<{ messages: TranslationMessages }> - >, - ) { - let cache = this.lazyCache.get(translationRef); + addLazyResources(resource: TranslationResource) { + let cache = this.lazyCache.get(resource.id); if (!cache) { cache = new Set(); - this.lazyCache.set(translationRef, cache); + this.lazyCache.set(resource.id, cache); } const { @@ -140,9 +130,8 @@ export class AppTranslationApiImpl implements AppTranslationApi { return; } - const internalRef = toInternalTranslationRef(translationRef); - const namespace = internalRef.id; - const lazyResources = initResources || internalRef.getLazyResources(); + const internalResource = toInternalTranslationResource(resource); + const namespace = internalResource.id; Promise.allSettled((options.supportedLngs || []).map(addLanguage)).then( results => { @@ -165,7 +154,9 @@ export class AppTranslationApiImpl implements AppTranslationApi { loadBackend = reloadResources([language], [namespace]); } - const loadLazyResources = lazyResources?.[language]; + const loadLazyResources = internalResource.resources.find( + entry => entry.language === language, + )?.loader; if (!loadLazyResources) { await loadBackend; diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 3d4b9cf8c5..ea51723d21 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -27,7 +27,11 @@ import { FeatureFlag, } from '@backstage/core-plugin-api'; import { AppConfig } from '@backstage/config'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { + TranslationMessages, + TranslationRef, + TranslationResource, +} from '@backstage/core-plugin-api/alpha'; /** * Props for the `BootErrorPage` component of {@link AppComponents}. @@ -178,16 +182,6 @@ export type AppRouteBinder = < >, ) => void; -/** - * TODO: To be remove when TranslationMessages in packages/core-app-api/src/app/TranslationResource.ts - * come to be public - * - * @ignore - * */ -type TranslationMessages = T extends TranslationRef - ? Partial - : never; - /** * The options accepted by {@link createSpecializedApp}. * @@ -290,21 +284,11 @@ export type AppOptions = { */ bindRoutes?(context: { bind: AppRouteBinder }): void; - /** - * TODO: Change to ExperimentalI18n type when packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts - * become to public - */ - __experimentalI18n?: { - supportedLanguages: string[]; + // TODO: Change to ExperimentalI18n type when packages/core-app-api/src/apis/implementations/AppTranslationApi/AppTranslationImpl.ts + __experimentalTranslations?: { fallbackLanguage?: string | string[]; - messages?: Array<{ - ref: TranslationRef; - messages?: Record>; - lazyMessages: Record< - string, - () => Promise<{ messages: TranslationMessages }> - >; - }>; + supportedLanguages?: string[]; + resources?: Array; }; }; diff --git a/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts b/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts index b94e5add87..44f85c0c89 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts @@ -22,9 +22,7 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; export type AppTranslationApi = { getI18n(): i18n; - addResourcesByRef>( - translationRef: TranslationRef, - ): void; + addResource(resource: TranslationRef): void; }; /** diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.ts b/packages/core-plugin-api/src/translation/useTranslationRef.ts index 605a220914..a11ab9c25b 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.ts +++ b/packages/core-plugin-api/src/translation/useTranslationRef.ts @@ -29,13 +29,12 @@ export interface TranslationOptions { export const useTranslationRef = < TMessages extends { [key in string]: string }, >( - translationRef: TranslationRef, + translationRef: TranslationRef, ) => { - const appTranslationApi = useApi(appTranslationApiRef); - - appTranslationApi.addResourcesByRef(translationRef); + const translationApi = useApi(appTranslationApiRef); const internalRef = toInternalTranslationRef(translationRef); + translationApi.addResource(translationRef); const { t } = useTranslation(internalRef.id, { useSuspense: process.env.NODE_ENV !== 'test', From 4ba6201070154376f819a252bd2664f379ce0976 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 12:30:54 +0200 Subject: [PATCH 10/16] core-plugin-api: fix TranslationResource loader result type Signed-off-by: Patrik Oldsberg --- packages/core-plugin-api/src/translation/TranslationResource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-plugin-api/src/translation/TranslationResource.ts b/packages/core-plugin-api/src/translation/TranslationResource.ts index d10d1b80e3..50d10546cd 100644 --- a/packages/core-plugin-api/src/translation/TranslationResource.ts +++ b/packages/core-plugin-api/src/translation/TranslationResource.ts @@ -31,7 +31,7 @@ export interface InternalTranslationResource version: 'v1'; resources: Array<{ language: string; - loader(): Promise<{ messages: TranslationMessages }>; + loader(): Promise<{ messages: { [key in string]: string | null } }>; }>; } From 251c19438a4b24d50675717f37f42eba3ce2e76a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 12:43:27 +0200 Subject: [PATCH 11/16] core-app,plugin-api: update API reports + fixes Signed-off-by: Patrik Oldsberg --- packages/core-app-api/alpha-api-report.md | 63 +------- packages/core-app-api/api-report.md | 18 +-- .../core-app-api/src/app/AppManager.test.tsx | 4 +- packages/core-app-api/src/app/AppManager.tsx | 2 +- packages/core-app-api/src/app/types.ts | 1 - packages/core-plugin-api/alpha-api-report.md | 152 +++++++++++++++--- 6 files changed, 144 insertions(+), 96 deletions(-) diff --git a/packages/core-app-api/alpha-api-report.md b/packages/core-app-api/alpha-api-report.md index f35adfdbaa..9b6ffea91e 100644 --- a/packages/core-app-api/alpha-api-report.md +++ b/packages/core-app-api/alpha-api-report.md @@ -5,32 +5,18 @@ ```ts import { AppTranslationApi } from '@backstage/core-plugin-api/alpha'; import { i18n } from 'i18next'; +import { TranslationMessages } from '@backstage/core-plugin-api/alpha'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationResource } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) export class AppTranslationApiImpl implements AppTranslationApi { // (undocumented) - addLazyResources>( - translationRef: TranslationRef, - initResources?: Record< - string, - () => Promise<{ - messages: TranslationMessages; - }> - >, - ): void; + addLazyResources(resource: TranslationResource): void; // (undocumented) - addResources>( - translationRef: TranslationRef, - initResources?: Record< - string, - TranslationMessages> - >, - ): void; + addMessages(messages: TranslationMessages): void; // (undocumented) - addResourcesByRef>( - translationRef: TranslationRef, - ): void; + addResource(translationRef: TranslationRef): void; // (undocumented) static create(options?: ExperimentalI18n): AppTranslationApiImpl; // (undocumented) @@ -39,47 +25,12 @@ export class AppTranslationApiImpl implements AppTranslationApi { initMessages(options?: ExperimentalI18n): void; } -// @alpha (undocumented) -export function createTranslationResource(options: { - ref: T; - messages?: Record>; - lazyMessages: Record< - string, - () => Promise<{ - messages: TranslationMessages; - }> - >; -}): { - ref: T; - messages?: Record> | undefined; - lazyMessages: Record< - string, - () => Promise<{ - messages: TranslationMessages; - }> - >; -}; - // @alpha (undocumented) export type ExperimentalI18n = { - supportedLanguages: string[]; fallbackLanguage?: string | string[]; - messages?: Array<{ - ref: TranslationRef; - messages?: Record>; - lazyMessages: Record< - string, - () => Promise<{ - messages: TranslationMessages; - }> - >; - }>; + supportedLanguages?: string[]; + resources?: Array; }; -// @alpha (undocumented) -export type TranslationMessages = T extends TranslationRef - ? Partial - : never; - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index e53d2eed83..1b5a5b4798 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -64,7 +64,8 @@ import { SessionState } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; import { StorageValueSnapshot } from '@backstage/core-plugin-api'; import { SubRouteRef } from '@backstage/core-plugin-api'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { TranslationResource } from '@backstage/core-plugin-api/alpha'; // @public export class AlertApiForwarder implements AlertApi { @@ -221,19 +222,10 @@ export type AppOptions = { themes: (Partial & Omit)[]; configLoader?: AppConfigLoader; bindRoutes?(context: { bind: AppRouteBinder }): void; - __experimentalI18n?: { - supportedLanguages: string[]; + __experimentalTranslations?: { fallbackLanguage?: string | string[]; - messages?: Array<{ - ref: TranslationRef; - messages?: Record>; - lazyMessages: Record< - string, - () => Promise<{ - messages: TranslationMessages; - }> - >; - }>; + supportedLanguages?: string[]; + resources?: Array; }; }; diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx index 1b68cfd6ca..2eddcfc3ae 100644 --- a/packages/core-app-api/src/app/AppManager.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -260,7 +260,7 @@ describe('Integration Test', () => { expect(screen.getByText('extLink4: ')).toBeInTheDocument(); }); - it('runs success with __experimentalI18n', async () => { + it('runs success with __experimentalTranslations', async () => { const app = new AppManager({ apis: [noOpAnalyticsApi], defaultApis: [], @@ -275,7 +275,7 @@ describe('Integration Test', () => { extRouteRef2: plugin2RouteRef, }); }, - __experimentalI18n: { + __experimentalTranslations: { supportedLanguages: ['en'], }, }); diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 0bd495c03b..bd6a1ba3d0 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -179,7 +179,7 @@ export class AppManager implements BackstageApp { this.bindRoutes = options.bindRoutes; this.apiFactoryRegistry = new ApiFactoryRegistry(); this.appTranslationApi = AppTranslationApiImpl.create( - options.__experimentalI18n, + options.__experimentalTranslations, ); } diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index ea51723d21..0adb93cfd1 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -29,7 +29,6 @@ import { import { AppConfig } from '@backstage/config'; import { TranslationMessages, - TranslationRef, TranslationResource, } from '@backstage/core-plugin-api/alpha'; diff --git a/packages/core-plugin-api/alpha-api-report.md b/packages/core-plugin-api/alpha-api-report.md index 119f5c570e..f186c36b7f 100644 --- a/packages/core-plugin-api/alpha-api-report.md +++ b/packages/core-plugin-api/alpha-api-report.md @@ -7,27 +7,64 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { i18n } from 'i18next'; import { ReactNode } from 'react'; +import { TranslationMessages as TranslationMessages_2 } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef as TranslationRef_2 } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) export type AppTranslationApi = { getI18n(): i18n; - addResourcesByRef>( - translationRef: TranslationRef, - ): void; + addResource(resource: TranslationRef): void; }; // @alpha (undocumented) export const appTranslationApiRef: ApiRef; -// @alpha (undocumented) -export function createTranslationRef< +// @alpha +export function createTranslationMessages< + TId extends string, TMessages extends { [key in string]: string; }, - TId extends string = string, + TFull extends boolean, >( - config: TranslationRefOptions, -): TranslationRef; + options: TranslationMessagesOptions, +): TranslationMessages; + +// @alpha (undocumented) +export function createTranslationRef< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TTranslations extends { + [language in string]: () => Promise<{ + default: { + [key in keyof TMessages]: string | null; + }; + }>; + }, +>( + config: TranslationRefOptions, +): TranslationRef; + +// @alpha (undocumented) +export function createTranslationResource< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TTranslations extends { + [language in string]: () => Promise<{ + default: + | TranslationMessages_2 + | { + [key in keyof TMessages]: string | null; + }; + }>; + }, +>( + options: TranslationResourceOptions, +): TranslationResource; // @alpha export interface PluginOptionsProviderProps { @@ -40,17 +77,56 @@ export interface PluginOptionsProviderProps { // @alpha export const PluginProvider: (props: PluginOptionsProviderProps) => JSX.Element; -// @alpha (undocumented) -export interface TranslationOptions {} - -// @alpha (undocumented) -export interface TranslationRef< +// @alpha +export interface TranslationMessages< + TId extends string = string, TMessages extends { [key in string]: string; } = { [key in string]: string; }, + TFull extends boolean = boolean, +> { + // (undocumented) + $$type: '@backstage/TranslationMessages'; + full: TFull; + id: TId; + messages: TMessages; +} + +// @alpha +export interface TranslationMessagesOptions< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TFull extends boolean, +> { + // (undocumented) + full?: TFull; + // (undocumented) + messages: false extends TFull + ? { + [key in keyof TMessages]?: string | null; + } + : { + [key in keyof TMessages]: string | null; + }; + // (undocumented) + ref: TranslationRef_2; +} + +// @alpha (undocumented) +export interface TranslationOptions {} + +// @alpha (undocumented) +export interface TranslationRef< TId extends string = string, + TMessages extends { + [key in string]: string; + } = { + [key in string]: string; + }, > { // (undocumented) $$type: '@backstage/TranslationRef'; @@ -62,24 +138,54 @@ export interface TranslationRef< // @alpha (undocumented) export interface TranslationRefOptions< + TId extends string, TMessages extends { [key in string]: string; }, - TId extends string = string, + TTranslations extends { + [language in string]: () => Promise<{ + default: { + [key in keyof TMessages]: string | null; + }; + }>; + }, > { // (undocumented) id: TId; // (undocumented) - lazyResources?: Record< - string, - () => Promise<{ - messages: TMessages; - }> - >; - // (undocumented) messages: TMessages; // (undocumented) - resources?: Record; + translations?: TTranslations; +} + +// @alpha (undocumented) +export interface TranslationResource { + // (undocumented) + $$type: '@backstage/TranslationResource'; + // (undocumented) + id: TId; +} + +// @alpha (undocumented) +export interface TranslationResourceOptions< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TTranslations extends { + [language in string]: () => Promise<{ + default: + | TranslationMessages_2 + | { + [key in keyof TMessages]: string | null; + }; + }>; + }, +> { + // (undocumented) + ref: TranslationRef_2; + // (undocumented) + translations: TTranslations; } // @alpha @@ -93,7 +199,7 @@ export const useTranslationRef: < [x: string]: string; }, >( - translationRef: TranslationRef, + translationRef: TranslationRef, ) => ( key: TKey, options?: TranslationOptions, From 3dc4e9d8b672a0b413552bcd4f6c195e69a89bf2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 17:56:01 +0200 Subject: [PATCH 12/16] user-settings: API report update Signed-off-by: Patrik Oldsberg --- plugins/user-settings/alpha-api-report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/user-settings/alpha-api-report.md b/plugins/user-settings/alpha-api-report.md index 673763745f..4c6ac6ef39 100644 --- a/plugins/user-settings/alpha-api-report.md +++ b/plugins/user-settings/alpha-api-report.md @@ -7,6 +7,7 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) export const userSettingsTranslationRef: TranslationRef< + 'user-settings', { readonly language: 'Language'; readonly change_the_language: 'Change the language'; @@ -21,8 +22,7 @@ export const userSettingsTranslationRef: TranslationRef< readonly select_theme_custom: 'Select {{custom}}'; readonly lng: '{{language}}'; readonly select_lng: 'Select language {{language}}'; - }, - 'user-settings' + } >; // (No @packageDocumentation comment for this package) From 5a1c6a35f5d8ffcc946ddb34beb2a9cd2e1cae88 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 17:57:30 +0200 Subject: [PATCH 13/16] core-app-api: stop exporting AppTranslationApi implementation Signed-off-by: Patrik Oldsberg --- packages/core-app-api/alpha-api-report.md | 36 ----------------------- packages/core-app-api/package.json | 4 --- packages/core-app-api/src/alpha.ts | 16 ---------- 3 files changed, 56 deletions(-) delete mode 100644 packages/core-app-api/alpha-api-report.md delete mode 100644 packages/core-app-api/src/alpha.ts diff --git a/packages/core-app-api/alpha-api-report.md b/packages/core-app-api/alpha-api-report.md deleted file mode 100644 index 9b6ffea91e..0000000000 --- a/packages/core-app-api/alpha-api-report.md +++ /dev/null @@ -1,36 +0,0 @@ -## API Report File for "@backstage/core-app-api" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts -import { AppTranslationApi } from '@backstage/core-plugin-api/alpha'; -import { i18n } from 'i18next'; -import { TranslationMessages } from '@backstage/core-plugin-api/alpha'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -import { TranslationResource } from '@backstage/core-plugin-api/alpha'; - -// @alpha (undocumented) -export class AppTranslationApiImpl implements AppTranslationApi { - // (undocumented) - addLazyResources(resource: TranslationResource): void; - // (undocumented) - addMessages(messages: TranslationMessages): void; - // (undocumented) - addResource(translationRef: TranslationRef): void; - // (undocumented) - static create(options?: ExperimentalI18n): AppTranslationApiImpl; - // (undocumented) - getI18n(): i18n; - // (undocumented) - initMessages(options?: ExperimentalI18n): void; -} - -// @alpha (undocumented) -export type ExperimentalI18n = { - fallbackLanguage?: string | string[]; - supportedLanguages?: string[]; - resources?: Array; -}; - -// (No @packageDocumentation comment for this package) -``` diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 0509af9bd4..c63f64c1ce 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -7,14 +7,10 @@ }, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", "./package.json": "./package.json" }, "typesVersions": { "*": { - "alpha": [ - "src/alpha.ts" - ], "package.json": [ "package.json" ] diff --git a/packages/core-app-api/src/alpha.ts b/packages/core-app-api/src/alpha.ts deleted file mode 100644 index 41037a539c..0000000000 --- a/packages/core-app-api/src/alpha.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 * from './apis/implementations/AppTranslationApi'; From 8a5b04308b331a706b22b882819f1a1b2b8a545f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 17:58:34 +0200 Subject: [PATCH 14/16] core-plugin-api: fix useTranslationRef test Signed-off-by: Patrik Oldsberg --- .../core-plugin-api/src/translation/useTranslationRef.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.test.ts b/packages/core-plugin-api/src/translation/useTranslationRef.test.ts index 372d72eb23..54e4440928 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.test.ts +++ b/packages/core-plugin-api/src/translation/useTranslationRef.test.ts @@ -51,7 +51,7 @@ describe('useTranslationRef', () => { }; (useApi as jest.Mock).mockReturnValue({ - addResourcesByRef: jest.fn(), + addResource: jest.fn(), }); (useTranslation as jest.Mock).mockReturnValue(i18nMock); From f22665e65f13f99da08bd4932b899c129c6b9bf3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 12 Sep 2023 18:32:52 +0200 Subject: [PATCH 15/16] adr: update API report Signed-off-by: Patrik Oldsberg --- plugins/adr/alpha-api-report.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/adr/alpha-api-report.md b/plugins/adr/alpha-api-report.md index 1d2b3f313d..7ce9f6f055 100644 --- a/plugins/adr/alpha-api-report.md +++ b/plugins/adr/alpha-api-report.md @@ -6,11 +6,14 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) -export const adrTranslationRef: TranslationRef<{ - readonly content_header_title: 'Architecture Decision Records'; - readonly failed_to_fetch: 'Failed to fetch ADRs'; - readonly no_adrs: 'No ADRs found'; -}>; +export const adrTranslationRef: TranslationRef< + 'adr', + { + readonly content_header_title: 'Architecture Decision Records'; + readonly failed_to_fetch: 'Failed to fetch ADRs'; + readonly no_adrs: 'No ADRs found'; + } +>; // (No @packageDocumentation comment for this package) ``` From e68002b7cb8214ae9ded26d49d49d0cec8fca61e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 Sep 2023 11:02:11 +0200 Subject: [PATCH 16/16] core-plugin-api: remove stray NoInfer Signed-off-by: Patrik Oldsberg --- packages/core-plugin-api/src/translation/TranslationRef.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core-plugin-api/src/translation/TranslationRef.ts b/packages/core-plugin-api/src/translation/TranslationRef.ts index 91b5d7850d..748473f92d 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.ts @@ -46,8 +46,6 @@ export interface InternalTranslationRef< getDefaultResource(): TranslationResource | undefined; } -export type NoInfer = T extends infer S ? S : never; - /** @alpha */ export interface TranslationRefOptions< TId extends string,