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 f35adfdbaa..0000000000 --- a/packages/core-app-api/alpha-api-report.md +++ /dev/null @@ -1,85 +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 { TranslationRef } from '@backstage/core-plugin-api/alpha'; - -// @alpha (undocumented) -export class AppTranslationApiImpl implements AppTranslationApi { - // (undocumented) - addLazyResources>( - translationRef: TranslationRef, - initResources?: Record< - string, - () => Promise<{ - messages: TranslationMessages; - }> - >, - ): void; - // (undocumented) - addResources>( - translationRef: TranslationRef, - initResources?: Record< - string, - TranslationMessages> - >, - ): void; - // (undocumented) - addResourcesByRef>( - translationRef: TranslationRef, - ): void; - // (undocumented) - static create(options?: ExperimentalI18n): AppTranslationApiImpl; - // (undocumented) - getI18n(): i18n; - // (undocumented) - 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; - }> - >; - }>; -}; - -// @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/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/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 69820e7b67..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,28 +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) { @@ -46,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, }, @@ -59,69 +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 resources = initResources || translationRef.getResources(); - if (!resources || this.cache.has(translationRef)) { + addMessages(messages: TranslationMessages) { + if (this.cache.has(messages.id)) { return; } - this.cache.add(translationRef); - Object.entries(resources).forEach(([language, messages]) => { - this.i18n.addResourceBundle( - language, - translationRef.getId(), - 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 { @@ -136,8 +130,8 @@ export class AppTranslationApiImpl implements AppTranslationApi { return; } - const namespace = translationRef.getId(); - const lazyResources = initResources || translationRef.getLazyResources(); + const internalResource = toInternalTranslationResource(resource); + const namespace = internalResource.id; Promise.allSettled((options.supportedLngs || []).map(addLanguage)).then( results => { @@ -160,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/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 3d4b9cf8c5..0adb93cfd1 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -27,7 +27,10 @@ import { FeatureFlag, } from '@backstage/core-plugin-api'; import { AppConfig } from '@backstage/config'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { + TranslationMessages, + TranslationResource, +} from '@backstage/core-plugin-api/alpha'; /** * Props for the `BootErrorPage` component of {@link AppComponents}. @@ -178,16 +181,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 +283,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/alpha-api-report.md b/packages/core-plugin-api/alpha-api-report.md index 57cf4a2cb4..f186c36b7f 100644 --- a/packages/core-plugin-api/alpha-api-report.md +++ b/packages/core-plugin-api/alpha-api-report.md @@ -7,24 +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 const createTranslationRef: < - Messages extends Record = {}, +// @alpha +export function createTranslationMessages< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TFull extends boolean, >( - config: TranslationRefConfig, -) => 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 { @@ -37,76 +77,115 @@ export interface PluginOptionsProviderProps { // @alpha export const PluginProvider: (props: PluginOptionsProviderProps) => JSX.Element; +// @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 type TranslationOptions< - Messages extends Record = Record, -> = Messages; +export interface TranslationOptions {} // @alpha (undocumented) export interface TranslationRef< - Messages extends Record = Record, + TId extends string = string, + TMessages extends { + [key in string]: string; + } = { + [key in 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< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TTranslations extends { + [language in string]: () => Promise<{ + default: { + [key in keyof TMessages]: string | null; + }; + }>; + }, > { // (undocumented) - id: string; + id: TId; // (undocumented) - lazyResources?: Record< - string, - () => Promise<{ - messages: Messages; - }> - >; + messages: TMessages; // (undocumented) - messages: Messages; - // (undocumented) - resources?: Record; + translations?: TTranslations; } // @alpha (undocumented) -export class TranslationRefImpl> - implements TranslationRef -{ +export interface TranslationResource { // (undocumented) - static create>( - config: TranslationRefConfig, - ): TranslationRefImpl; + $$type: '@backstage/TranslationResource'; // (undocumented) - getDefaultMessages(): Messages; + 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) - getId(): string; + ref: TranslationRef_2; // (undocumented) - getLazyResources(): - | Record< - string, - () => Promise<{ - messages: Messages; - }> - > - | undefined; - // (undocumented) - getResources(): Record | undefined; - // (undocumented) - toString(): string; + translations: TTranslations; } // @alpha @@ -116,13 +195,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..44f85c0c89 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AppTranslationApi.ts @@ -22,14 +22,12 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; export type AppTranslationApi = { getI18n(): i18n; - addResourcesByRef>( - translationRef: TranslationRef, - ): void; + addResource(resource: TranslationRef): void; }; /** * @alpha */ export const appTranslationApiRef: ApiRef = createApiRef({ - id: 'core.apptranslation', + id: 'core.translation', }); 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, + }; +} diff --git a/packages/core-plugin-api/src/translation/TranslationRef.test.ts b/packages/core-plugin-api/src/translation/TranslationRef.test.ts index 155624a122..b73b521774 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.test.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.test.ts @@ -13,48 +13,61 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TranslationRefImpl, createTranslationRef } from './TranslationRef'; +import { + createTranslationRef, + toInternalTranslationRef, +} from './TranslationRef'; +import { toInternalTranslationResource } from './TranslationResource'; 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', + const ref = createTranslationRef({ + id: 'test', messages: { key: 'value' }, - }; + }); - const translationRef = createTranslationRef(config); + const internalRef = toInternalTranslationRef(ref); - expect(translationRef.getId()).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 = TranslationRefImpl.create(config); + const internalRef = toInternalTranslationRef(ref); - const lazyResources = translationRef.getLazyResources(); + expect(internalRef.$$type).toBe('@backstage/TranslationRef'); + expect(internalRef.version).toBe('v1'); + expect(internalRef.id).toBe('test'); + expect(internalRef.getDefaultMessages()).toEqual({ key: 'value' }); - const messages = await lazyResources?.en(); - - expect(messages).toEqual({ messages: { key: 'value' } }); + const internalResource = toInternalTranslationResource( + internalRef.getDefaultResource()!, + ); + 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 d01f7ca261..748473f92d 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.ts @@ -14,48 +14,131 @@ * limitations under the License. */ -import { TranslationRef, TranslationRefConfig } from './types'; +import { + createTranslationResource, + TranslationResource, +} from './TranslationResource'; /** @alpha */ -export class TranslationRefImpl> - implements TranslationRef -{ - static create>( - config: TranslationRefConfig, - ) { - return new TranslationRefImpl(config); - } +export interface TranslationRef< + TId extends string = string, + TMessages extends { [key in string]: string } = { [key in string]: string }, +> { + $$type: '@backstage/TranslationRef'; - getId() { - return this.config.id; - } + id: TId; - getDefaultMessages(): Messages { - return this.config.messages; - } + T: TMessages; +} - getLazyResources(): - | Record Promise<{ messages: Messages }>> - | undefined { - return this.config.lazyResources; - } +/** @internal */ +type AnyMessages = { [key in string]: string }; - getResources(): Record | undefined { - return this.config.resources; - } +/** @internal */ +export interface InternalTranslationRef< + TId extends string = string, + TMessages extends { [key in string]: string } = { [key in string]: string }, +> extends TranslationRef { + version: 'v1'; - toString() { - return `TranslationRef(${this.getId()})`; - } + getDefaultMessages(): AnyMessages; - private constructor( - private readonly config: TranslationRefConfig, - ) {} + getDefaultResource(): TranslationResource | undefined; } /** @alpha */ -export const createTranslationRef = < - Messages extends Record = {}, +export interface TranslationRefOptions< + TId extends string, + TMessages extends { [key in string]: string }, + TTranslations extends { + [language in string]: () => Promise<{ + default: { [key in keyof TMessages]: string | null }; + }>; + }, +> { + id: TId; + messages: TMessages; + translations?: TTranslations; +} + +/** @internal */ +class TranslationRefImpl< + TId extends string, + TMessages extends { [key in string]: string }, +> implements InternalTranslationRef +{ + #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.#id; + } + + get T(): never { + throw new Error('Not implemented'); + } + + getDefaultMessages(): AnyMessages { + return this.#messages; + } + + setDefaultResource(resources: TranslationResource): void { + this.#resources = resources; + } + + getDefaultResource(): TranslationResource | undefined { + return this.#resources; + } + + toString() { + return `TranslationRef{id=${this.id}}`; + } +} + +/** @alpha */ +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: TranslationRefConfig, -): TranslationRef => TranslationRefImpl.create(config); + config: TranslationRefOptions, +): TranslationRef { + const ref = new TranslationRefImpl(config); + if (config.translations) { + ref.setDefaultResource( + createTranslationResource({ + ref, + translations: config.translations as any, + }), + ); + } + return ref; +} + +/** @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/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..50d10546cd --- /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: { [key in string]: string | null } }>; + }>; +} + +/** @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-app-api/src/alpha.ts b/packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts similarity index 70% rename from packages/core-app-api/src/alpha.ts rename to packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts index 288676f509..f6c022abb4 100644 --- a/packages/core-app-api/src/alpha.ts +++ b/packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts @@ -13,5 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './apis/implementations/AppTranslationApi'; -export * from './app/TranslationResource'; + +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'), + }, +}); diff --git a/packages/core-plugin-api/src/translation/index.ts b/packages/core-plugin-api/src/translation/index.ts index 9eaef57206..a03bf4b1f7 100644 --- a/packages/core-plugin-api/src/translation/index.ts +++ b/packages/core-plugin-api/src/translation/index.ts @@ -14,6 +14,22 @@ * limitations under the License. */ -export * from './TranslationRef'; -export * from './types'; -export * from './useTranslationRef'; +export { + type TranslationMessages, + type TranslationMessagesOptions, + createTranslationMessages, +} from './TranslationMessages'; +export { + type TranslationResource, + type TranslationResourceOptions, + createTranslationResource, +} from './TranslationResource'; +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.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); diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.ts b/packages/core-plugin-api/src/translation/useTranslationRef.ts index 083bcd1f0e..a11ab9c25b 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.ts +++ b/packages/core-plugin-api/src/translation/useTranslationRef.ts @@ -16,26 +16,34 @@ 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); + const translationApi = useApi(appTranslationApiRef); - appTranslationApi.addResourcesByRef(translationRef); + const internalRef = toInternalTranslationRef(translationRef); + translationApi.addResource(translationRef); - const { t } = useTranslation(translationRef.getId()); + const { t } = useTranslation(internalRef.id, { + useSuspense: process.env.NODE_ENV !== 'test', + }); - const defaulteMessage = translationRef.getDefaultMessages(); + const defaultMessages = internalRef.getDefaultMessages(); - return ( - key: Tkey, + return ( + key: TKey, options?: TranslationOptions, - ): Messages[Tkey] => t(key as string, defaulteMessage[key], options); + ): TMessages[TKey] => t(key, defaultMessages[key], options); }; 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) ``` diff --git a/plugins/user-settings/alpha-api-report.md b/plugins/user-settings/alpha-api-report.md index 925d387882..4c6ac6ef39 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< + 'user-settings', + { + 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}}'; + } +>; // (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, });